一般卷积会采用3×3或5×5的滤波器,尺寸越大,可以提取越大的特征,但相对地,较小的特征就容易被忽略。而池化层通常会采用2×2,stride=2的滤波器,使用越大的尺寸,会使得参数个数减少很多,但提取到的特征也相对减少。
以下就先以CNN模型实操MNIST辨识。
范例1.将手写阿拉伯数字辨识的模型改用CNN。
完整程序请参阅【06_02_MNIST_CNN.ipynb】,以下仅介绍差异的程序代码。
(1)改用CNN模型:使用两组Conv2d/MaxPool2d,比较特别的是使用2个nn.Sequential各包一组Conv2d/MaxPool2d。Conv2d参数依次如下:
in-channel:输入通道数,第一个Conv2d要指定颜色数,单色为1,彩色为3(R/G/B)。
out-channel:输出通道数,为要产生的滤波器个数。
kernel size:滤波器尺寸。
Stride:滑动的步数。
Padding:补零的行数。
(2)之前讲过PyTorch的完全连接层需要指定输入及输出参数,这时就麻烦了,从卷积/池化层输出的神经元个数是多少呢?亦即第16行的“7*7*32”是怎么计算出来的?因Padding/Stride设定,输出会有所差异,因此,我们必须仔细计算,才能作为下一个完全连接层的输入参数。
卷积层输出图像宽度公式如下:
W _ out =( W - F +2 P )/ S +1
其中:
W :输入图像宽度。
F :滤波器(Filter)宽度。
P :补零的行数(Padding)。
S :滑动的步数(Stride)。
池化层输出图像宽度的公式与卷积层相同。
卷积/池化层输出神经元的公式:
输出图像宽度×输出图像高度×滤波器个数
TensorFlow完全连接层只须填输出参数,完全没有这方面的困扰。
笔者将相关计算公式写成多个函数如下。
(3)测试。
执行结果:1568,即7×7×32。
如果模型定义错误,会产生错误信息,同时也会出现正确的输出个数,届时再依据错误信息更正也可以,这是投机的小技巧,例如:
RuntimeError: mat1 and mat2 shapes cannot be multiplied (1000x12544 and 9216x128)
表示要把9216改成12544。
(4)验证:显示模型的汇总信息,内含各层输入及输出参数。
执行结果:如下程序代码最后一行,1568,即7×7×32。
(5)测试数据评分。
执行结果:准确率约97.11%,实际预测前20笔数据,完全正确。
(6)以笔者手写的文件测试。
执行结果:比之前的效果好多了,只有数字9辨识错误,不过,笔者把训练周期加大为10,因为只训练5周期,损失似乎还未趋于收敛。
从卷积层运算观察,CNN模型有两个特点:
(1)部分连接(Locally Connected or Sparse Connectivity):完全连接层(Linear)每个输入的神经元完全连接(Full Connected)至每个输出的神经元,但卷积层的输出神经元则只连接滑动窗口神经元,如图6.9所示。想象一下,假设在手臂上拍打一下,手臂以外的神经元应该不会收到信号,既然没收到信号,理所当然就不必往下一层传送信号了,所以,下一层的神经元只会收到上一层少数神经元的信号,接收到的范围称为感知域(Reception Field)。
由于部分连接的关系,神经层中每条回归线的输入特征大幅减少,要估算的权重个数也就少了很多。
图6.9 部分连接(Locally Connected)
(2)权重共享(Weight Sharing):单一滤波器应用到滑动窗口时,卷积矩阵值都是一样的,如图6.10所示,基于这个假设,要估计的权重个数就减少许多,模型复杂度因而进一步简化了。
基于以上两个假设,CNN模型参数会比较少。
另外为什么CNN模型输入数据要加入色彩信道(Channel)?这是因为有些情况加入色彩,会比较容易辨识,比如狮子毛发大部分是金黄色的,又或者侦测是否有戴口罩,只要图像上有一块白色的矩形,我们应该就能假定有戴口罩,当然目前口罩颜色已经五花八门,需要更多的训练数据,才能正确辨识。
范例2.加入标准化(Normalize)前置处理,通常训练可以较快速收敛,且准确度可以提高。
下列程序代码请参考【06_03_MNIST_CNN_Normalize.ipynb】。
(1)标准化:其中的平均数/标准偏差是依据ImageNet数据集统计出来的最佳值(第3行),并在DataSet中设定转换函数(第8行)。
(2)以测试数据评分的准确率为98.39%,无明显帮助,笔者写的数字还是没有全对。这部分与TensorFlow测试结果有所差异,TensorFlow采取MinMaxScaler准确率会明显提高。
范例3.前面的数据集都是单色的图像,我们也使用彩色的图像测试看看。
直接复制【06_03_MNIST_CNN_Normalize.ipynb】,稍作修改即可。
以下仅说明关键程序代码,完整程序参阅【06_04_Cifar_RGB_CNN.ipynb】。
(1)载入套件:PyTorch对影像、语音及文字提供个别的命名空间(Namespace),分别为torchvision、torchaudio、torchtext,相关的函数和数据集都涵盖在内。
(2)加载Cifar10数据,与单色的图像辨识相同,但不需转换为单色,标准化(Normalize)的平均数/标准偏差也可以依据ImageNet数据集统计出来的最佳值设定,这里单纯设定为0.5,读者可试试有无差异。
执行结果如下:训练数据共50000笔,测试数据共10000笔,图像尺寸为(32,32),有三个颜色(R/G/B)。
(50000, 32, 32, 3) (10000, 32, 32, 3)
(3)CIFAR 10数据集共10种类别。
(4)观察数据内容:显示8张图片。
注意,自DataLoader读出的维度为[8, 3, 32, 32],颜色放在第2维,这是配合Conv2d的输入要求,要显示图像,必须将颜色放在最后一维(第9行)。
以DataLoader读取的图像范围介于[0, 1]之间,经过转换后数据介于[-1, 1],要显示图像将数据还原(第6行),公式为( x × δ )+ μ 。
执行结果:分辨率不是很高,图像有点模糊,下面有对应的标注(Label)。
(5)建立CNN模型:第8行Conv2d第1个参数为3,即颜色通道数,表R/G/B三颜色。
(6)定义训练函数:同前,只是逐步将相关程序代码模块化,以提高生产力。
(7)定义测试函数:同前。
(8)执行训练:笔者使用之前的损失函数(F.nll_loss)及优化器(Adadelta),但训练效果不佳,后来依照原文使用nn.CrossEntropyLoss、SGD,才得到比较合理的模型。另外,调用train函数时,若使用nn.CrossEntropyLoss,后面要加(),因为它是类别,要先建立对象才能计算损失,其他损失函数以F.开头的,可直接使用。
(9)评分。
执行结果:准确率为57.31%,比单色图像辨识准确率低很多,因为背景图案不是单纯的白色。
(10)测试一批数据。
执行结果:有一半辨识错误。
(11)计算各类别的准确率:观察是否有特别难以辨识的类别,可针对该类别进一步处理。
执行结果:cat、dog准确率偏低,后续我们会试着改善模型准确率。
另外,数据前置处理补充说明如下:
影像/视频数据可使用Pillow、scikit-image、OpenCV套件存取。
语音数据可使用SciPy、librosa套件存取。
文字数据可使用NLTK、SpaCy套件存取。