神经网络简介

LeNet 和 MNIST
该教程通过显示如何训练接受手写单个数字的输入图像到预测数字,给出 Wolfram 语言神经网络架构的简短综述. 我们训练的数据集是经典的 MNIST 数据集,还会训练 LeNet 的变体,首批卷积网络之一,在 Wolfram Neural Net Repository 已经可用.
获取 MNIST 数据集,其包含 60,000 幅训练和 10,000 幅测试图像:
显示训练集中多个随机范例:
从 Wolfram Neural Net Repository 中获取预训练的 LeNet 版本:
使用预训练网络分类图像列表:
从零开始训练像 LeNet 这样的网络非常容易. NetTrain 会自动处理训练过程中的许多细节,例如,选择合适的损失函数,附加编码器和解码器以及选择批量大小. 这是它的样子.
从零开始训练 LeNet:
然而,这不是本教程的结尾.
为了全面了解 Wolfram 语言的深度学习基础知识,现在我们将通过将 LeNet 从其组件层构建出来,选择损失函数,定义训练网络,附加编码器和解码器以及最后训练和评估网络. 理解这个特定任务背后的一般原则将使您很好地运用Wolfram 语言来轻松高效地处理复杂的学习任务.
神经网络最简单的构建块是,你可以认为它是传递数值数组的简单函数.
再看看预先训练好的 LeNet 模型(再显示格式上点击 按钮显示构成的层):
该网络由很多层构成,例如 ConvolutionLayerPoolingLayer 等. 每层完成不同的任务,这种情况下,任务与计算机视觉有关.
看一下网络的最后一层.
使用 NetExtract 提取网络的最后一层:
看到该层的很多信息. 例如,期待输入向量长度为 10 并返回同样的. 像任何层一样,我们可以把该层应用到输入来获取输出:
与任何图层一样,我们可以将此图层应用于输入以获取输出:
图层也可以接受 NumericArray 输入,在这种情况下产生 NumericArray
SoftmaxLayer 的目标是产生概率,其和为 1.
相加之前的输出:
让我们构建一个新层.
创建一个 LinearLayer ,接受输入向量的长度为 2 并产生输出向量的长度为 3
请注意,在上面的摘要框中,有一个uninitialized的标题,指示网络包含尚未提供的可学习参数.
把未初始化层应用到输入向量,这会失败:
只有某些层,例如,ConvolutionLayerLinearLayer 有可学习的参数. 这种层再显示格式中总会有 图标. 带有图标 的层,相反,没有包含任何可学习的参数.
可以使用 NetInitialize 为可学习参数提供随机值.
初始化层:
把初始化层应用到输入向量:
从初始层获取权和偏差:
注意到层的权重和偏差参数打包在 NumericArray 中. 可以使用 Normal 将它们转换为列表:
到目前为止,我们看见层只有 1 个输入. 某些层有多于 1 个输入. 例如,MeanSquaredLossLayer 比较两个数组,称之为 inputtarget,并产生表示 Mean[(input-target)^2] 的单个数字.
创建一个 MeanSquaredLossLayer
当应用网络时,命名层的输入并以关联形式提供.
把该层应用于两个输入:
有些层引入了神经网络架构的独特功能,有些镜像了已存在的 Wolfram 语言符号功能. 例如,FlattenLayer 行为类似 FlattenDotLayer 行为类似 Dot 等.
可用层的完整列表是:

层的更多属性(高级)

该部分总结了 Wolfram 语言神经网络层的一些关键属性.
网络编码器
基本上说,因为它们必须可微,所以神经网络层对数组进行操作. 然而,我们经常想在其他数据上训练和使用数据,例如,图像、音频、文本等. 为了实现这些,我们可以使用 NetEncoder 把数据转换成值的数组.
我们可以使用 "Image" 编码器翻译 MNIST 数据集中的数字图像. 我们首先看看编码器的一些简单例子.
创建一个产生 1×12×12 数组的图像 NetEncoder
把图像 NetEncoder 应用于一幅图像上:
在转换成数组前,图像编码器整合图像使之具有特定的颜色空间、维度等.
把图像 NetEncoder 应用于大型彩色图像上:
当可独立于网络使用编码器,正如我们所做的,将编码器附加到层上更常见. 这也可以在创建层或之后实现. 下面是创建带有附加编码器的层.
通过 "Input" 选项,把图像 NetEncoder 附加在 PoolingLayer
PoolingLayer 直接应用于图像,它会使用图像 NetEncoder 把图像翻译成数组,以便 PoolingLayer 进行运算:
把输出转回图像:
MNIST 中的实际图像是大小为 28×28 的灰度图像. 现在让我们创建最终的图像编码器. 随后,当我们从零开始构建 LeNet 会附加该 NetEncoder.
为 MNIST 创建一个 "Image" 编码器:
编码器的维度匹配 MNIST 数据集中图像的维度:
网络解码器
神经网络的输出经常是一个预测. 对于回归问题,这个预测一般是点估计,这意味着,它是单个数字表示网络认为此任务最有可能的值. 这种输出一般不需要被解码.
对于分类问题,然而,网络的输出一般是向量,它的分量表示每一类的概率. 例如,分类食物图像,如:热狗披萨沙拉的网络产生一个带有三个分量的向量,相加成一个,表示那 3 类的概率.
对于这类概率向量输出,我们一般关心最可能的类而不是原概率. 为了确定这点,我们必须知道类如何与特殊的向量分量相关联.
我们还可以从概率向量中计算其他属性,例如,头 n 个概率(如果我们有多个类),指定类的概率或预测不确定性的度量.
为了使查询更方便,可以使用 "Class" NetDecoder 存储向量分量和类之间的映射,因此自动诠释网络的输出. NetDecoder 的其他类型也有可能把输出转换成一幅 Image、布尔值等,但是在本教程我们不会详细讨论.
对于 MNIST 任务,我们使用的 10 类是数字 0 到 9. 让我们创建合适的解码器.
创建一个 "Class" NetDecoder 诠释概率向量:
默认情况下,解码器会解码概率向量为最可能的类(如果这看上去有点乱,记住第一类实际上是 0).
把解码器应用于概率向量:
我们也可以计算其他属性,当应用网络时,提供已命名的属性作为第二个参数.
获取最可能的类和它们的概率列表:
获取指定类的概率:
获取作为关联的概率的完整列表:
获取预测的不确定性度量:
使用 NetEncoder,我们可以附加 NetDecoder 到层的输出. 这是我们之前显示的 PoolingLayer 更流线的范例,其中,"Image" NetEncoder 用于诠释层的输入,用 "Image" NetDecoder 把层的最后输出转换回图像.
NetEncoderNetDecoder 附加到层:
把层应用到一幅图像会产生一幅图像:
容器
单个神经网络层本身一般没有用. 我们一般需要组合多层来完成有趣的事情.
组合层的最简单方法是把它们一个接一个的链接起来,第一层的输出被用于下一层的输入,等等. 我们可以使用 NetChain 容器以这种方式连接层,但是当需要更复杂的连接格式时,则需要使用 NetGraph 容器.
现在,让我们创建一个简单的链.
创建一个计算 Cos[Sin[x]] 的简单的 NetChain
NetChain 应用于输入,并与应用 Sin,然后 Cos 于同样输入的结果进行比较:
创建一个简单的 NetChain,包含把 ElementwiseLayer 应用于 Ramp 函数,然后是 SoftmaxLayer
NetChain 应用于输入:
结果与连续应用单个层是一样的:
容器的一个重要属性是它们可以作为层,并可被用于其他容器内的层. 让我们来看下这个范例.
嵌套之前的 NetChain 于另一个 NetChain 之内:
把链应用于输入:
平坦嵌套的链:
之前,我们看见的 LeNet 模型是有更复杂的链. 下面是构建 LeNet 未初始化拷贝的代码.
从零开始构建 LeNet,提供之前构建的 NetEncoderNetDecoder
注意,包含可学习参数的层是红色,表明在网络应用于输入前需要初始化值.
作为一个快速练习,我们现在随机初始化 LeNet,并把它应用于来自于 MNIST 的同样输入. 我们获得的输出,当然也是随机的,但是用来阐明事情正常工作.
随机初始化 LeNet 的可学习参数:
把初始化的 LeNet 应用于输入图像,产生随机分类:
获取头几个概率的排序列表:
我们的最终目标,当然是训练这个随机初始化的网络正确分类手写数字.

为了训练 LeNet,我们需要构建一个训练网络,把个人训练示例传给 LeNet. MNIST 数据集的每个训练示例包含输入图像和对应目标标签的组合.
显示 MNIST 中的一套范例:
NetChain 不允许网络接受多个输入,因此我们需要使用 NetGraph 构建训练网络. 训练网络的任务是计算由 LeNet 产生的预测,如果预测可以,就产生一个小数,不好则产生一个大数. 这称为损失. 最好是把它想成预测错误的一种代理.
一旦我们有了这个训练网络,我们就可以使用 NetTrain 函数逐步修改网络中的可学习参数,使得损失随着时间的推移而降低.
对于不同的学习任务,必须使用不同的方法计算损失. 对于分类任务,例如分类 MNIST 数字,损失的常用选择是交叉熵损失. 当给出预测和真标签或目标,层 CrossEntropyLossLayer 可以计算该损失.
我们使用的预测是概率向量形式,向量的每个元素表示对应数字 0、1、2 等的概率. 目标标签是正确类的索引(1 是数字 0,2 是数字 1 等).
下面是用于评分预测的 CrossEntropyLossLayer 的一个简单范例.
创建一个 CrossEntropyLossLayer 比较长度为 5 带有目标标签的输入预测向量:
把损失层应用于预测向量,第一个向量具有较大的概率:
如果目标是 1,损失会比较低,因为预测把高概率分给类 1:
如果目标是 5,损失会更高些,因为预测会把低概率分给类 5:
我们要构建的训练网络很简单:把 LeNet 应用于数字图像产生预测,然后把该预测和目标类比较.
通过提供层和连接列表,构建一个 NetGraph. 图的输入使用语法 NetPort["input"]destination 连接到层,损失层的输入是通过 source->NetPort["loss","input"] 连接的:
为了测试,让我们使用包含随机初始化的预测 LeNet 的训练网络,有输入集和对应的目标.
把图像列表传给 "Input" 端口,索引列表传给 "Target" 端口:
这些损失总结了给定图像,LeNet 如何预测目标的. 因为 LeNet 是被随机初始化的,我们期望它不比(平均)机会更好. 训练的时候,LeNet 中的可学习参数会逐渐调整把平均损失降低.
这个是怎么完成的呢?关键思想是通过 gradient. 这些是对可学习参数的调整,可以通过称为反向传播的过程来计算. 这些调整是为了稍微减少特定批次实例上训练网络的平均损失.
通过随机重复选择一批实例,计算调整量并将其应用于可学习参数,对于所需的任务网络会逐渐改善.
该过程由 NetTrain 处理,它通过许多方法调整和细调训练过程. 但是,我们可以使用 NetPortGradient 直接计算其中的梯度来了解所涉及的机制.
请求在 LeNet 第一个卷积层偏差,由特定输入产生的梯度:
现在我们有了梯度,可以使用该梯度实际修改对应的可学习参数,减少该范例的损失.
通过获取偏差值修改训练网络的对应值,使用梯度轻微调整,并在原始网络替代之:
比较修改网络和原始网络的损失. 损失减少了:
训练
现在,我们可以用 NetTrain 训练我们的网络.
一般来讲,NetTrain 自动执行训练网络的构建. 对于一个输入和一个输出的简单网络,通过把 ini 传给网络,处理形式为 {in->out,} 的训练数据,然后通过合适的损失函数比较带有 outi 的网络输出.
但是,因为我们已经明确构建了一个训练网络,我们必须以形式 <|"port"->list,|> 提供训练数据,并明确传递给带有数据列表的训练网络的输入端口. 因此,我们必须首先转换我们的数据,它是规则 in->out 的格式.
另一个复杂性是,我们必须考虑这个事实,训练数据包含整数 0 到 9 的标签,其中,训练网络的 "Target" 输入期待范围 1 到 10 的索引. We could use 我们可以使用 "Class" NetEncoder 进行转换(NetTrain 通常会自动化),单数我们会利用这个事实:只是加 1 来完成同样的事.
把训练和测试数据转换成关联格式,使用 KeysValues 从训练和测试数据的规则列表来获取图像和标签:
显示训练关联的较小范例:
现在使用 NetTrain 执行训练. 注意关于这个网络范例的几件事情:
训练 LeNet:
最终的l NetTrainResultsObject 为我们提供了丰富的信息. 其中一些以其显示形式显示,但可以通过编程方式从中检索更多信息. 但是我们可以立即看到,在 3 分钟的 CPU 训练后,我们的网络能够达到约 99.4% 的准确率.
我们看看损失演变的其他形式.
NetTrainResultsObject 获取损失演变图:
或许,更重要的是,我们可以提前最终网络.
NetTrainResultsObject 获取训练网络并提取预测网络:
重新附加在分类网络嵌入训练网络是去除的 NetEncoderNetDecoder
训练网络现在可以执行分类.
图像分类:
获取不同图像的前几个概率:
这就对了!我们已经从零开始构建和训练了 LeNet,在处理过程中覆盖以下主题:
在下一节我们介绍如何计算一个已训练的网络.
计算
现在我们已经训练了我们的网络,我们可以获得更多关于它的信息. 例如,我们可以获得另一个数据集的总体准确度. 我们也可以得到像混淆矩阵这样有用的总结,它总结了网络如何误分类的例子. 我们可以将使用深度学习的网络的性能与其他常用机器学习技术的性能进行比较.
让我们从构建一个 ClassifierMeasurementsObject 开始,它测量数据集上网络分类行为的各种属性. 使用我们未训练的数据集非常重要,因此我们使用内置于 MNIST 数据集中的测试集.
现在,我们有 ClassifierMeasurementsObject,我们可以高效查询各种属性.
获取总体准确度:
获取损失最高的范例列表:
获取网络中最不确定类的范例列表:
获取哪些错误分类最为常见的列表:
获取可用属性的完全集合:
我们也可以使用 NetMeasurements 度量网络的各种属性. NetMeasurementsClassifierMeasurements 共享许多同样的属性,但它们也有自己的属性. NetMeasurements 的优势是它已被高效的实现,甚至可以在 NVIDIA 显卡上运行,可通过 TargetDevice"GPU" 选项,使其更加适合于大型数据集.
获取整体精确度:
获取混淆矩阵:
获取各类是如何分类的分数列表:
对于神经网络,NetMeasurements 还有其他一些优势. 可以度量网络中任何层的输出和权重.
绘制我们训练的 LeNet 模型第一层的每个滤波的均值激活:
另外,NetMeasurements 使用缓存加速同样属性的重复度量或甚至是类似属性的度量.
测量 Cohen 的 Kappa 的时间,由于测量上面的 "ConfusionMatrixPlot",它将被缓存. 请注意,进行 10000 次测量只需要几分之一秒:
最后,NetMeasurements 可以在比 ClassifierMeasurements 更多的情况下使用:可用于当网络没有要求的丢失层并且没有附加 NetEncoderNetDecoder,以及当网络有多个输出时.
将网络与其他方法进行比较的最简单方法是使用 Classify,它会自动应用一系列常用方法并选择最佳方法.
使用 Classify 自动获取高效机器学习模型:
把分类器应用于输入:
神经网络优于测试集上由 Classify 选择的模型.
比较带有训练网络的分类器: