Remove["Global`*"] (* Remove other names *)

An Example of Training a Digit Classifier

一个简单的手写数字识别网络 LeNet 的笔记

author: 凉凉

参考 Wolfram U 中的课程 (HyperlinkURL["Introduction to Neural Networks", https://www.wolfram.com/wolfram-u/courses/machine-learning/introduction-neural-networks-ml010/]), 其中的训练手写数字识别的例子做的笔记. 其中关于 LeNet 网络的解释主要来源于 (HyperlinkURL["动手深度学习: LeNet", https://zh.d2l.ai/chapter_convolutional-neural-networks/lenet.html]). 但是没有对网络的解释, 只是机械地使用, 类似于过一遍如何使用 MMA 来操作神经网络的一个例子.

数据下载

trainingData=ResourceData["MNIST","TrainingData"];
testData=ResourceData["MNIST","TestData"];

RandomSample[trainingData,5]

对于这样的数据, 通过 NetEncode 和 NetDecode 来处理网络中的输入和输出:

输出是根据输入图片得到的一个分类, 每个分类对应的数字大小范围为 0~9

dec=NetDecoder[{"Class",Range[0,9]}]

将输入的图片以灰度形式规范为 28×28 的一个张量

enc=NetEncoder[{"Image",{28,28},"Grayscale"}]

为了方便之后的训练, 将输入的数据转换为 Association 的形式:

trainAssoc=<|"Input"->Keys@trainingData,"Target"->Values@trainingData+1|>;
testAssoc=<|"Input"->Keys@testData,"Target"->Values@testData+1|>;

Take[trainAssoc,All,5]

网络介绍

使用的是 (HyperlinkURL["LeNet (Wikipedia)", https://en.wikipedia.org/wiki/LeNet]) 的网络. 具体网络有什么用, 以及怎么构建这样的网络我不清楚, 可以去参考原始的论文: (HyperlinkURL["GradientBased Learning Applied to Do cument Recognition", http://vision.stanford.edu/cs598_spring07/papers/Lecun98.pdf]).

图片来源: (HyperlinkURL["动手深度学习: LeNet", https://zh.d2l.ai/chapter_convolutional-neural-networks/lenet.html#img-lenet])

uninitializedLenet=NetChain[{
ConvolutionLayer[20,5],
ElementwiseLayer[Ramp],
PoolingLayer[2,2],
ConvolutionLayer[50,5],
ElementwiseLayer[Ramp],
PoolingLayer[2,2],
LinearLayer[500],
ElementwiseLayer[Ramp],
LinearLayer[10],
SoftmaxLayer[]},
"Input"->enc,
"Output"->dec]

然后构建一个用于学习的网络:

trainingNet=
NetInitialize@NetGraph[<|
"lenet"->uninitializedLenet,
"loss"->CrossEntropyLossLayer["Index"]|>,
{NetPort["Input"]->"lenet"->NetPort["loss","Input"],
NetPort["Target"]->NetPort["loss","Target"]}]

于是就构建了一个用来训练用的网络, 其大意如下: 输入经过 LeNet 后应该得到的是一个 0~9 (共 10 种) 的输出, 对于这个输出, 分别和目标 Target 去进行比较, 比较函数是 CrossEntropyLossLayer. 最终计算得到 Loss 即偏差值.

注: LeNet 的输出是 0~9 的数字, 但是经过索引 (Index) 后是以 1~10 去和 Target 进行比较的. 所以在验证的时候要注意一下.

<|"Input"->Keys@#,"Target"->Values@#+1|>&@
RandomSample[trainingData,5]
trainingNet@%

可以看到, 在未训练的网络上, 预测的值和预期的偏差值有很大的偏差.

使用 NetTrain 来在数据的基础上进行训练. (注, 但是在我的电脑上不知道为什么好像有点小问题)

results=NetTrain[trainingNet,trainAssoc,All,ValidationSet->testAssoc,MaxTrainingRounds->5]

参数解析:

All 表示让训练的时候显示所有的信息

ValidationSet 用于传入一个和训练数据集不同的数据来防止过拟合的情况出现

MaxTrainingRounds 指定最多训练的轮数

results 中包含了训练的各种信息:

results["LossEvolutionPlot"]

results["TrainedNet"]

将训练好的部分提取出来, 并设置入口和出口的 Layer:

trainedLenet=NetReplacePart[NetExtract[results["TrainedNet"],"lenet"],
{"Input"->enc,"Output"->dec}]

训练结果和应用

随机选择几个作为例子:

RandomSample[testData,5]

trainedLenet[Keys@%,"TopProbabilities"]

嗯识别率真的挺高的.

训练完成的网络可以用于分类.

保存训练结果

最后保存一下这个训练的结果, 方便以后再调用 (毕竟训练挺费时间的).

Export["./trained-LeNet-for-digits.wlnet",trainedLenet,"WLNet"]

可以使用 Import 来重新导入.