第03章 卷积神经网络


第1节 卷积神经网络简介

1.1 历史发展

  1962年,Hubel和Wiesel通过对猫视觉皮层细胞的研究,提出了 感受野 (Receptive Field) 的概念。通俗地说,就是他们发现视觉皮层的神经元是局部接受信息的,只受某些特定区域刺激的响应,而不是对全局图像进行感知。

  1984年,日本学者Fukushima基于感受野概念提出 神经认知机 (Neocognitron)。这个神经认知机是感受野概念在人工神经网络领域的首次应用。简单地说,神经认知机将一个视觉模式分解为许多子模式,它试图将视觉系统模型化。所以卷积神经网络可以说是全连接网络和感受野概念的结合应用。

  卷积神经网络最近几年大热,但是不要误以为卷积神经网络是近几年才提出的。它既不是06年也不是12年提出的,早在1989年的时候,就已经提出了卷积神经网络,并在1995年应用于支票上的手写数字识别,获得了非常好的应用效果。

  卷积网络 (LeCun, 1989),也叫 卷积神经网络 (Convolutional Neural Network, CNN),是一种专门用来处理具有类似网格结构的数据的神经网络,例如时间序列数据和图像数据。它在深度学习的历史中发挥了重要作用,将研究大脑得到的深刻理解成功应用于机器学习应用的关键例子。卷积神经网络是计算机视觉应用几乎都在使用的一种深度学习模型,也是 第一批 能使用反向传播进行有效训练,解决重要商业应用的神经网络,并且仍然处于当今深度学习商业应用的前沿。

1.2 从FNN到CNN

  在用全连接前馈网络(FNN)来处理图像时,会存在以下两个问题:

  • 参数太多:如果输入图像大小为 100\times100\times3100×100×3 (即图像高度为 100,宽度为 100 以及 RGB 3 个颜色通道),在全连接前馈网络中,第一个隐藏层的每个神经元到输入层都有 100\times100\times3 = 30000100×100×3=30000 个互相独立的连接, 每个连接都对应一个权重参数。随着隐藏层神经元数量的增多,参数的规模也会急剧增加。这会导致整个神经网络的训练效率非常低,也很容易出现 过拟合
  • 局部不变性特征:自然图像中的物体都具有局部不变性特征,比如尺度缩放、平移、旋转等操作不影响其语义信息。而全连接前馈网络很难提取这些局部不变性特征,CNN提出进行 数据增强 来提高性能

  目前的卷积神经网络一般是由 卷积层汇聚层 和 全连接层 交叉堆叠而成的前馈神经网络。全连接层一般在卷积神经网络有三个结构上的特性:局部连接、权重共享以及 汇聚。这些特性使得卷积神经网络具有一定程度上的平移、缩放和旋转不变性。和前馈神经网络相比,卷积神经网络所使用的参数更少。

1.3 模型特性

  ”卷积神经网络“一词表明该网络使用了 卷积 (Convolution) 这种特殊的数学线性运算,换而言之,卷积网络是指那些至少在网络的一层中使用卷积运算来替代一般的矩阵乘法运算的神经网络。但是,通常来说卷积神经网络中用到的卷积运算和其他领域 (例如工程以及纯数学领域) 中的定义并不完全一致,后续卷积详解中会说明如何在多种不同维度的数据上使用卷积运算。

  同时卷积神经网络使用了 池化 (Pooling) 技术对输入特征图进行降维压缩。池化层也称为下采样层,会压缩输入的特征图,一方面减少了特征,导致了参数减少,进而简化了卷积网络计算的复杂度;另一方面也保证了特征的某种不变性 (旋转、平移、伸缩等)

  虽然全连接神经网络和卷积神经网在直观上看差异比较大,但它们的整体架构是比较相似的。在结构方面,两者的输入层、全连接层和Softmax层都是一样的。
除了在结构上相似,全连接神经网络的损失函数、参数的优化过程等都同样适用于卷积神经网络。两者唯一的区别就在于:神经网络中,相邻两层的连接方式不同。体现在网络结构上就是卷积层和降采样层。

  • 输入层: 将每个像素代表一个特征节点输入到网络中
  • 卷积层: 卷积运算的主要目的是使原信号特征增强,并降低噪音
  • 池化(降采样)层: 降低网络训练参数及模型的过拟合程度
  • 全连接层: 对生成的特征进行加权
  • Softmax层: 获得当前样例属于不同类别的概率

第2节 卷积神经网络卷积详解

  密集连接层 (Dense Layer/Full Connected Layer) 和卷积层的根本区别在于,Dense层从输入特征空间中学到的是全局模型,而卷积层学到的是 局部模型 。这个重要特性使卷积神经网络具有以下两个特质:

  • 卷积神经网络学到的模式具有 平移不变性 (Translation Invariant)。卷积神经网络在图像某处学到某个特征后,可以在任何位置重新识别同样特征。对于密集连接网络来说,如果特征模型出现在新的位置,只能重新学习整个图像模型。人类 视觉世界从根本上也兼具平移不变性,这使得CNN在处理图像时可以高效利用数据,它只需要很少的训练样本就可以学到具有泛化能力的数据表示
  • 卷积神经网络可以学到模型的 空间层次结构 (Spatial Hierarchies of Patterns)。每个卷积层通过学习上层的特征来组成更大的模式,以此类推。 人类 视觉世界从根本上也兼具空间层次结构, 这使得CNN可以有效地学习复杂抽象地数据概念。

2.1 卷积数学推导

  在通常形式中,卷积是对两个实变函数的一种 数学运算,其本质是通过对序列数据赋予不同的 权重,进行数据增强,同时降低数据的噪音。这种运算就叫做 卷积 (Convolution),通常用星号表示:

     s(t) = (x * w)(t)s(t)=(x∗w)(t)

  其中 ww 必须是一个有效的加权参数,否则输出就不再是一个加权平均。在卷积网络的术语中,卷积的第一个参数,函数 xx 通常叫做 输入 (input), 第二个参数,函数 ww 通常叫做 核函数 (Kernel Function), 输出 ss 被称为 特征映射 (Feature Map)

  在机器学习的应用中,输入通常是多维数组的数据,而核通常是有学习算法优化得到的多维数组的参数,这些多维数组叫做张量。因为在输入与核中的每个元素都必须明确地分开存储,通常假设在存储了数值地有限点集以外,这些函数地值都为零,这意味着可以通过对有限个数组元素地求和来实现无限求和。

  在机器学习的应用中,卷积运算通常运行在多个维度上。例如,如果把一张二维的图像 II 作为输入,就需要使用一个二维的核 KK:

     S_{(i,j)} = (I * K)(i,j) = \sum_m\sum_nI(m,n)K(i-m,j-n)S(i,j)​=(I∗K)(i,j)=∑m​∑n​I(m,n)K(i−m,j−n)

  卷积是可交换的(Commutative), 所以可以等价地写作:

     S_{(i,j)} = (K * I)(i,j) = \sum_m\sum_nI(i-m,j-n)K(m,n)S(i,j)​=(K∗I)(i,j)=∑m​∑n​I(i−m,j−n)K(m,n)

  卷积运算可交换性的出现是因为有时需要将核相对输入进行 翻转 (Flip), 但在神经网络的应用中却不是一个重要的性质。与之不同的是,许多神经网络库会实现一个相关的函数,叫做 互相关函数 (Cross-Correlation), 和卷积运算一样但是并没有对核进行翻转。许多机器学习的库实现是互相关函数但是也能称之为卷积:

     S_{(i,j)} = (I * K)(i,j) = \sum_m\sum_nI(i+m,j+n)K(m,n)S(i,j)​=(I∗K)(i,j)=∑m​∑n​I(i+m,j+n)K(m,n)

2.2 卷积运算示例

  接下来会演示一个二维卷积 (没有进行核翻转) 的例子。在此点积运算中,将 5\times55×5 输入矩阵中 3\times33×3 深蓝色区域中每个元素分别与其对应位置的权值(右下角的红色数字)相乘,然后再相加,所得到的值作为 3\times33×3 输出矩阵(绿色)的元素。

     x = \begin{bmatrix}3,3,2\\0,0,1\\3,1,2\\\end{bmatrix}x=⎣⎢⎡​3,3,20,0,13,1,2​⎦⎥⎤​  ,  w = \begin{bmatrix}0,1,2\\2,2,0\\3,1,2\\\end{bmatrix}w=⎣⎢⎡​0,1,22,2,03,1,2​⎦⎥⎤​

     s = 3\times0+3\times1+2\times2+0\times2+0\times2+1\times0+3\times0+1\times1+2\times2=12s=3×0+3×1+2×2+0×2+0×2+1×0+3×0+1×1+2×2=12

  将3×3权值矩阵(深蓝色区域)向右移动一个格(即步长为1)重复运算操作即为使用 滑动窗口 (Silde Window) 的算法。卷积核在二维输入数据上“滑动”,对当前输入部分的元素进行矩阵乘法,然后将结果汇为单个输出的像素值,重复这个过程直到遍历整张图像,这个过程就叫做卷积。权值矩阵(红色的数字)就是卷积核。卷积操作后的图像(绿色矩阵)称为特征映射或特征图。

  卷积实际上是一个提供权重的模版,用邻域的点按一定的权重去重新定义这一点的运算,简单来说用一个卷积核滑动地与图像相应位置的像素点相乘,汇总之后得到一个值,填充到新的位置上。

  在上面的例子中,我们输入的特征是 5\times5=255×5=25 ,输出的大小是 3\times3=93×3=9,如果我们使用标准的全连接层,就会产生一个 25\times9=22525×9=225 个参数的权值矩阵,因为后面一层中每个节点都是前面一层中所有节点输入的加权求和。而卷积操作中,每个深绿色的格子只与前面一层中 3\times33×3 大小的深蓝色区域相连,而不用查看 5\times55×5 的特征矩阵中所有的数值,这就是 局部连接。卷积核在图像上滑动的过程中,红色的权值矩阵是不变的,这就是 权值共享。因此,卷积神经网络只需要九个参数就可以实现从 5\times55×5 到 3\times33×3 的变换。

2.2.1 边际效用与填充

  上例中输入层使用了一个 5\times55×5 的特征图,其中每 9 个方块可以作为中心放入一个 3\times33×3 的窗口,这 9 个方块形成一个 3\times33×3 的网格。因此,输出特征图的尺寸是 3\times33×3,比输入尺寸小了一点,这就是 边界效应 (Marginal Effect)。

  观察上面的卷积示例,我们会发现一个现象:在卷积核滑动的过程中图像的边缘会被裁剪掉,5×5特征矩阵经过卷积之后转换为3×3的特征矩阵,这里免不了会丢失一部分信息。如何使得输出尺寸与输入保持一致呢?这里就用到了一个很重要的技术 填充 (Padding)。填充是在输入特征图的每一遍添加适当数目的行和列,使得每个输入防控都能作为卷积窗口的中心。通常用额外的“假”像素(通常值为0)填充边缘。这样,在滑动时的卷积核可以允许原始边缘像素位于卷积核的中心,同时延伸到边缘之外的假像素,从而产生与输入相同大小的输出。

  如果未填充零,则网络每一层的宽度会逐层递减。根据卷积的性质,网络每一层宽度减少的数量等于卷积核的宽度减 1。

  • 如果卷积核尺寸较大,则网络的宽度迅速缩减,这限制了卷积神经网络的网络深度。
  • 如果卷积核尺寸较小,则可用的卷积核的数量大幅度降低,这限制了卷积神经网络的表达能力。

  对输入有三种填充零的方式:

  • valid 填充:不使用零来填充输入,卷积核只允许访问那些图像中能完全包含整个核的位置。

    • 在valid 填充模式中,输出的大小在每一层都缩减。假设核的宽度是 kk,则每经过一层,输出的宽度减少了 k-1k−1 。
    • 如果输入图像的宽度是 mm ,则网络经过了 dd 层之后,输出的宽度变成 m- (k-1) \times dm−(k−1)×d 。如果核的宽度 kk 非常大时,缩减非常明显。最终网络会缩减到 1 。
  • same 填充:使用足够的零来填充,使得输出和输入保持相同的大小。这是最常见的填充方式。

    • same 填充模式中,网络可以包含任意多的卷积层,因为它不存在网络输出宽度缩减的问题。
    • same 填充模式的一个问题是:输入的边缘单元可能存在一定程度上的欠表达。因为输入的中间区域的单元的影响域为全部的输出单元,这意味着这些输入单元的信息会被很多输出单元所编码。而输入的边缘区域的单元的影响域只是输出单元的一部分,这意味着这些输入单元的信息仅仅被少量输出单元所编码。
  • full 填充:在输入的两端各填充 k-1k−1 个零,使得每个输入单元都恰好被卷积核访问 kk 次。其中 kk 为卷积核的宽度。

    • 它将从卷积核和输入开始相交的时候开始做卷积。
    • 假设核的宽度是 kk ,则每经过一层,输出的宽度增加了 k-1k−1 。
    • 如果输入图像的宽度是 mm ,则网络经过了 dd 层之后,输出的宽度变成 m + (k-1) \times dm+(k−1)×d 。它使得输入的边缘单元也能够得到充分表达。
    • full 填充的一个问题是:输出的边界单元依赖于更少的输入单元。这使得学习到的结果在输出的中间部分表现较好,边缘部分的表现较差。

2.2.2 多通道卷积

  每个卷积核都会将图像生成为另一幅特征映射图,即一个卷积核提取一种特征。为了使特征提取更充分,我们可以添加多个卷积核以提取不同的特征,也就是多通道卷积。

    1. 每个通道使用一个卷积核进行卷积操作的过程(RGB三通道)
    1. 将这些特征图相同位置上的权重值相加,生成另外一张特征图
    1. 进行传统神经网络中相似的添加偏置值,对每个特征图加一个偏置项,以便产生最终的输出特征图。

2.3 卷积核图像示例

  卷积是图像矩阵与权值矩阵的点乘。接下来基于Numpy的示例,展示不同的卷积核对图像进行卷积操作之后会产生的不同效果。首先,定义 conv() 函数,这是一个进行卷积操作的函数。

 

import numpy as np import matplotlib.pyplot as plt import matplotlib.cm as cm from PIL import Image def conv(image_array,kernel): ''' input: - image_array:输入层灰度图像array矩阵 - kernel: 卷积核 output: - 卷积运算后的特征映射矩阵 ''' image_copy = image_array.copy() height,width = image_copy.shape h,w = kernel.shape h_x = int((h-1)/2) w_x = int((w-1)/2) # padding填充临时图片 temp = np.zeros([height + h_x*2, width + w_x*2]) # 将原图拷贝到临时图片的中央 temp[h_x:h_x + height, w_x:w_x + width] = image_copy[:,:] # 初始化一张同样大小的图片作为输出图片 output = np.zeros_like(a=temp) # 进行卷积运算 for i in range(h_x, h_x + height): for j in range(w_x, w_x + width): output[i][j] = int(np.sum(temp[i-h_x:i+h_x+1,j-w_x:j+w_x+1] * kernel)) return output[h_x:height + h_x, w_x:width + w_x]

  然后定义三个不同的卷积核便于对比:

  • 第一个卷积核在纵向上取值,特点是提取纵向特征。
  • 第二个卷积核在横向上取值,特点是提取横向特征。
  • 第三个卷积核没有明显水平或是竖直方向的特点。
 

# 提取垂直特征 kernel_1 = np.array([[-1,0,1], [-2,0,2], [-1,0,1]]) # 提取水平特征 kernel_2 = np.array([[-1,-2,-1], [0,0,0], [1,2,1]]) # Laplace扩展算子 kernel_3 = np.array([[1,1,1], [1,-8,1], [1,1,1]])

 

# 打开图像并转换为灰度图像 image = Image.open('photos/example.jpg').convert('L') # 将图像转换为数组 image_array = np.array(image) # 卷积运算 image_vert = conv(image_array,kernel_1) image_horz = conv(image_array,kernel_2) image_lapc = conv(image_array,kernel_3)

 

# 展示原图像 plt.imshow(image_array,cmap=cm.gray) plt.axis('off') plt.show()

 

# 展示纵向卷积核灰度图像 plt.imshow(image_vert,cmap=cm.gray) plt.axis('off') plt.show()


 

# 展示横向卷积核灰度图像 plt.imshow(image_horz,cmap=cm.gray) plt.axis('off') plt.show()

 

# 展示Laplace卷积核灰度图像 plt.imshow(image_lapc,cmap=cm.gray) plt.axis('off') plt.show()

  • 水平方向的卷积核对图像进行卷积之后,得到的图像显示出显著的水平方向的边缘特征。
  • 竖直方向的卷积核对图像进行卷积之后,得到的图像显示出显著的竖直方向的边缘特征。
  • 拉普拉斯算子没有明显竖直或水平方向的特征,它显示出的特征图就兼顾了不同方向的特征。

  对于图像中没有边缘的区域,比如背景里的大海,大部分像素值几乎是相同的,所以卷积核在这些点,输出几乎也是相同的;而对于有边缘的区域,由于边缘两侧的像素是不一样的,卷积核计算结果因此也是不同的,从而能够检测到边缘,这就是 边缘检测 的原理。对图像用一个卷积核进行卷积运算,实际上是一个滤波的过程。每个卷积核都是一种特征提取方式,就像是一个筛子,将图像中符合条件的部分筛选出来。


第3节 卷积神经网络降采样详解

  在卷积层之后常常紧接着一个降采样层,通过减小矩阵的长和宽,从而达到减少参数的目的。而降采样是降低特定信号的采样率的过程。池化 (Pooling),思想来自于视觉机制,是对信息进行抽象的过程。池化就是计算图像一个区域上的某个特定特征的平均值或最大值。比如下图中,把四个点聚合成一个点,从而达到了降维的目的。

3.1 池化运算

  常用的池化方法有 均值池化 (Average Pooling) 和 最大池化 (Max Pooling),均值池化和最大池化适用于不同的场景。

  • 均值池化:对池化区域内的像素点取均值,这种方法得到的特征数据对背景信息更敏感

  \begin{bmatrix} 1 & 2 & 5 & 6\\3 & 7 & 6 & 7\\4 & 6 & 2 & 8\\1 & 5 & 4 & 9\\\end{bmatrix} \longrightarrow{Kernel=2, Stride=2}\longrightarrow \begin{bmatrix} \frac{\sum(1,2,3,7)}4 & \frac{\sum(5,6,6,7)}4\\\frac{\sum(4,6,1,5)}4 & \frac{\sum(2,8,4,9)}4\\\end{bmatrix} \longrightarrow \begin{bmatrix} 3.25 & 6 \\ 4 & 5.75 \\\end{bmatrix}⎣⎢⎢⎢⎡​1341​2765​5624​6789​⎦⎥⎥⎥⎤​⟶Kernel=2,Stride=2⟶[4∑(1,2,3,7)​4∑(4,6,1,5)​​4∑(5,6,6,7)​4∑(2,8,4,9)​​]⟶[3.254​65.75​]

  • 最大池化:对池化区域内所有像素点取最大值,这种方法得到的特征对纹理特征信息更加敏感

  \begin{bmatrix} 1 & 2 & 5 & 6\\3 & 7 & 6 & 7\\4 & 6 & 2 & 8\\1 & 5 & 4 & 9\\\end{bmatrix} \longrightarrow{Kernel=2, Stride=2}\longrightarrow \begin{bmatrix} {max(1,2,3,7)} & {max(5,6,6,7)}\\{max(4,6,1,5)} & {max(2,8,4,9)}\\\end{bmatrix} \longrightarrow \begin{bmatrix} 7 & 7 \\ 6 & 9 \\\end{bmatrix}⎣⎢⎢⎢⎡​1341​2765​5624​6789​⎦⎥⎥⎥⎤​⟶Kernel=2,Stride=2⟶[max(1,2,3,7)max(4,6,1,5)​max(5,6,6,7)max(2,8,4,9)​]⟶[76​79​]

  另外比较少用到的池化策略为:

  • 随机池化 (Stochastic Pooling): 对池化区域内的元素按照其概率值大小随机选择,元素被选中的概率与其数值大小正相关,这是一种正则化的技术操作
  • 混合池化 (Mixed Pooling): 在均值池化与最大池化中进行随机选择
  • 手动池化 (Data Driven/Detail Preserving Pooling): 早期根据数据类型手动设计(硬编码)池化细节,在深度学习往自动化发展后逐渐被弃用

3.2 卷积步长

  降采样同样可以通过卷积计算中的 步长 (Stride) 来实现,它表示卷积核在图片上移动的格数。通过步长的变换,可以得到不同尺寸的卷积输出结果。

  步长大于 1 的卷积操作也是降维的一种方式。步长为 2 意味着特征图的宽度和高度都被做了 2 倍下采样(除了边界效应引起的变化)。虽然步进卷积对某种类型的模型有用,但在实践中很少使用,CNN模型通常使用 最大池化 运算。

3.3 使用降采样的动机

  池化操作把四个点平均成一个点,通过相似语义的聚合,达到降维的目的,同时不丢失过多的有效信息。但另一方面,为了检测更多的特征信息、形成更多不同通道特征的组合,从而形成更复杂的特征,需要逐渐增加每层所含的平面数。基于池化或卷积步长实现的降采样操作满足了:

  • 增大感受野: 所谓感受野,即一个像素对应回原图的区域大小,假如没有池化运算,一个 3\times33×3,步长为 1 的卷积,那么输出的一个像素的感受野就是 3\times33×3 的区域,再加一个 stride=1stride=1 的 3\times33×3 卷积,则感受野为 5\times55×5。在每一个卷积中间加上 3\times33×3 的池化运算之后,很明显感受野会迅速增大。感受野的增加对于模型的能力的提升是必要的。

  • 平移不变性: 目标的些许位置的移动,能得到相同的结果。因为池化不断地抽象了区域的特征而不关心位置,所以一定程度上增加了平移不变性。

  • 降低优化难度和参数: 用步长大于1的卷积来替代池化,但是池化每个特征通道单独做降采样,与基于卷积的降采样相比,不需要参数,更容易优化。全局池化更是可以大大降低模型的参数量和优化工作量。池化层同时没有任何附加参数

  降采样层本质上是一个特征选择,信息过滤的过程。实际池化操作过程中不可避免地损失一部分信息,这也是一个和计算性能的一个妥协。随着运算速度的不断提高,现代神经网络开始逐步减少降采样层的使用频率来试图追求更高的精度。


第4节 卷积神经网络代码实现

4.1 PyTorch

  • 第1个Sequential层
    • torch.nn.Conv2d 卷积层输入通道为1 (灰色图像),输出通道默认为32, 卷积核大小 3\times33×3
    • torch.nn.BatchNorm2d 进行数据归一化处理,加速收敛,提高泛化能力, 卷积核大小 3\times33×3
    • torch.nn.ReLU 激活函数
    • torch.nn.MaxPool2d 池化层进行降采样处理
  • 第2个Sequential层
    • torch.nn.Conv2d 卷积层输入通道默认为32,输出通道默认为64
    • torch.nn.BatchNorm2d 进行数据归一化处理,加速收敛,提高泛化能力
    • torch.nn.ReLU 激活函数
    • torch.nn.MaxPool2d 池化层进行降采样处理
  • 输出层
    • torch.nn.Linear 全连接层输入通道默认为 64\times6\times664×6×6, 输出通道取输入通道与目标类的中间值
    • torch.nn.Dropout 丢弃25%的特征映射,进行正规化处理,提高泛化能力
    • torch.nn.ReLU 激活函数
    • torch.nn.Linear 全连接层输出结果
  • build_cnn 函数预设置参照MNIST\Fashion-MNIST数据集
    • 图片格式为 1\times28\times281×28×28 ,即输入层采用784个特征
    • 图片标签为10个数字,即输出层生成10个标签
 

# %load cnn.py import math import torch import torch.nn as nn class ConvolutionalNN(nn.Module): ''' size 图像大小 input_size 输入通道数 output_size 输出通道数 num_classes 类别数 ''' def __init__(self,input_size,output_size,num_classes): super(ConvolutionalNN, self).__init__() self.layer1 = nn.Sequential( nn.Conv2d(in_channels=1, out_channels=input_size, kernel_size=3, padding=1), nn.BatchNorm2d(input_size), nn.ReLU(), nn.MaxPool2d(2) ) self.layer2 = nn.Sequential( nn.Conv2d(in_channels=input_size, out_channels=output_size, kernel_size=3), nn.BatchNorm2d(output_size), nn.ReLU(), nn.MaxPool2d(2) ) # 全连接层,类别数为num_classes total_features = output_size * 36 ratio = int(math.sqrt(total_features/num_classes)) floor = math.floor(math.log2(ratio)) trans_size = int(math.pow(2,math.log2(total_features)-floor)) self.fc = nn.Sequential( nn.Linear(total_features, trans_size), nn.Dropout(0.25), nn.ReLU(inplace=True), nn.Linear(trans_size, num_classes) ) def forward(self, x): out = self.layer1(x) out = self.layer2(out) out = out.view(out.size(0), -1) out = self.fc(out) return out def build_cnn(phase, input_size=32,output_size=64,num_classes=10): if phase != "test" and phase != "train": print("ERROR: Phase: " + phase + " not recognized") return return ConvolutionalNN(input_size, output_size, num_classes)

 

from torchsummary import summary net = build_cnn('train') net.cuda() summary(net,(1,28,28))

C:\ProgramData\Anaconda3\lib\site-packages\torch\nn\functional.py:718: UserWarning: Named tensors and all their associated APIs are an experimental feature and subject to change. Please do not use them for anything important until they are released as stable. (Triggered internally at  ..\c10/core/TensorImpl.h:1156.)
  return torch.max_pool2d(input, kernel_size, stride, padding, dilation, ceil_mode)


----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1           [-1, 32, 28, 28]             320
       BatchNorm2d-2           [-1, 32, 28, 28]              64
              ReLU-3           [-1, 32, 28, 28]               0
         MaxPool2d-4           [-1, 32, 14, 14]               0
            Conv2d-5           [-1, 64, 12, 12]          18,496
       BatchNorm2d-6           [-1, 64, 12, 12]             128
              ReLU-7           [-1, 64, 12, 12]               0
         MaxPool2d-8             [-1, 64, 6, 6]               0
            Linear-9                  [-1, 287]         661,535
          Dropout-10                  [-1, 287]               0
             ReLU-11                  [-1, 287]               0
           Linear-12                   [-1, 10]           2,880
================================================================
Total params: 683,423
Trainable params: 683,423
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.86
Params size (MB): 2.61
Estimated Total Size (MB): 3.47
----------------------------------------------------------------

4.2 Keras

  • 第1个Sequential层
    • Keras.layers.Conv2d 卷积层输入通道为1 (灰色图像),输出通道默认为32, 卷积核大小 3\times33×3
    • Keras.layers.ReLU 激活函数
    • Keras.layers.MaxPool2d 池化层进行降采样处理
    • Keras.layers.Dropout 丢弃25%的特征映射,进行正规化处理,提高泛化能力
  • 第2个Sequential层
    • Keras.layers.Conv2d 卷积层输入通道为32,输出通道默认为64, 卷积核大小 3\times33×3
    • Keras.layers.ReLU 激活函数
    • Keras.layers.MaxPool2d 池化层进行降采样处理
    • Keras.layers.Dropout 丢弃25%的特征映射,进行正规化处理,提高泛化能力
  • 输出层
    • Keras.layers.Dense 全连接层输入通道默认为 64\times7\times764×7×7, 输出通道取 256
    • Keras.layers.Dropout 丢弃25%的特征映射,进行正规化处理,提高泛化能力
    • Keras.layers.Dense 全连接层输出结果
 

import keras from keras.layers import Conv2D, MaxPool2D, Dropout, Dense, Flatten from keras.models import Sequential model = Sequential() # 32个滤波器,每个滤波器的大小都是3x3,步长为1 model.add(Conv2D(32,(3,3),activation='relu',padding='same',input_shape=(28,28,1))) model.add(Conv2D(32,(3,3),activation='relu',padding='same')) # 2x2的最大池优化,步长为2 model.add(MaxPool2D(pool_size=(2,2))) model.add(Dropout(0.25)) # 64个滤波器,每个滤波器的大小都是3x3,步长为1 model.add(Conv2D(64,(3,3),activation='relu',padding='same')) model.add(Conv2D(64,(3,3),activation='relu',padding='same')) # 2x2的最大池优化,步长为2 model.add(MaxPool2D(pool_size=(2,2))) model.add(Dropout(0.25)) # 压平层与全连接层 model.add(Flatten()) model.add(Dense(256,activation='relu')) model.add(Dropout(0.25)) # 输出分类层 model.add(Dense(10,activation='softmax'))

 

model.summary()

Model: "sequential_5"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d_11 (Conv2D)           (None, 28, 28, 32)        320       
_________________________________________________________________
conv2d_12 (Conv2D)           (None, 28, 28, 32)        9248      
_________________________________________________________________
max_pooling2d_4 (MaxPooling2 (None, 14, 14, 32)        0         
_________________________________________________________________
dropout_6 (Dropout)          (None, 14, 14, 32)        0         
_________________________________________________________________
conv2d_13 (Conv2D)           (None, 14, 14, 64)        18496     
_________________________________________________________________
conv2d_14 (Conv2D)           (None, 14, 14, 64)        36928     
_________________________________________________________________
max_pooling2d_5 (MaxPooling2 (None, 7, 7, 64)          0         
_________________________________________________________________
dropout_7 (Dropout)          (None, 7, 7, 64)          0         
_________________________________________________________________
flatten_2 (Flatten)          (None, 3136)              0         
_________________________________________________________________
dense_4 (Dense)              (None, 256)               803072    
_________________________________________________________________
dropout_8 (Dropout)          (None, 256)               0         
_________________________________________________________________
dense_5 (Dense)              (None, 10)                2570      
=================================================================
Total params: 870,634
Trainable params: 870,634
Non-trainable params: 0
_________________________________________________________________

4.3 TensorFlow

  • weight: tf.Variable() 初始化权重
  • bias:tf.Variable() 初始化偏差
  • conv2d:tf.nn.conv2d() 初始化卷积层
  • max_pool_2x2: tf.nn.max_pool() 初始化池化层
 

import tensorflow.compat.v1 as tf tf.disable_v2_behavior() # 定义权值 def weight(shape): # 在构建模型时,需要使用tf.Variable来创建一个变量 # 在训练时,这个变量不断更新 # 使用函数tf.truncated_normal(截断的正态分布)生成标准差为0.1的随机数来初始化权值 return tf.Variable(tf.truncated_normal(shape, stddev=0.1), name ='W') # 定义偏置 # 初始化为0.1 def bias(shape): return tf.Variable(tf.constant(0.1, shape=shape), name = 'b') # 定义卷积操作 # 步长为1,padding为'SAME' def conv2d(x, W): # tf.nn.conv2d(input, filter, strides, padding, use_cudnn_on_gpu=None, name=None) return tf.nn.conv2d(x, W, strides=[1,1,1,1], padding='SAME') # 定义池化操作 # 步长为2,即原尺寸的长和宽各除以2 def max_pool_2x2(x): # tf.nn.max_pool(value, ksize, strides, padding, name=None) return tf.nn.max_pool(x, ksize=[1,2,2,1], strides=[1,2,2,1], padding='SAME')

 

# 输入层 # 28x28图像,通道为1(Gray Image) with tf.name_scope('input_layer'): x = tf.placeholder('float',shape=[None, 28, 28, 1],name="x") # 第1个卷积层 # 输入通道:1,输出通道:32,卷积后图像尺寸不变,依然是28x28 with tf.name_scope('conv_1'): W1 = weight([3,3,1,32]) # [k_width, k_height, input_chn, output_chn] b1 = bias([32]) # 与output_chn 一致 conv_1=conv2d(x, W1)+ b1 conv_1 = tf.nn.relu(conv_1 ) # 第1个池化层 # 将28x28图像缩小为14x14,池化不改变通道数量,因此依然是32个 with tf.name_scope('pool_1'): pool_1 = max_pool_2x2(conv_1) # 第2个卷积层 # 输入通道:32,输出通道:64,卷积后图像尺寸不变,依然是14x14 with tf.name_scope('conv_2'): W2 = weight([3,3,32,64]) b2 = bias([64]) conv_2=conv2d(pool_1, W2)+ b2 conv_2 = tf.nn.relu(conv_2) # 第2个池化层 # 将14x14图像缩小为7x7,池化不改变通道数量,因此依然是64个 with tf.name_scope('pool_2'): pool_2 = max_pool_2x2(conv_2) # 全连接层 # 将池第2个池化层的64个7x7的图像转换为一维的向量,长度是 64*7*7=3136 # 256个神经元 with tf.name_scope('fc'): W3= weight([3136, 256]) #有256个神经元 b3= bias([256]) flat = tf.reshape(pool_2, [-1, 3136]) h = tf.nn.relu(tf.matmul(flat, W3) + b3) h_dropout= tf.nn.dropout(h, keep_prob=0.8) # 输出层 # 输出层共有10个神经元,对应到0-9这10个类别 with tf.name_scope('output_layer'): W4 = weight([256,10]) b4 = bias([10]) pred= tf.nn.softmax(tf.matmul(h_dropout, W4)+b4)


第5节 PyTorch 实践

5.1 模型训练代码

  加载同级目录下 train.py 程序代码

 

# %load train.py import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" import time import argparse import sys import torch import torch.nn as nn import torch.optim as optim import torch.backends.cudnn as cudnn from torchvision import transforms from torch.autograd import Variable import matplotlib as mpl import matplotlib.pyplot as plt mpl.rc('axes', labelsize = 14) mpl.rc('xtick', labelsize = 12) mpl.rc('ytick', labelsize = 12) sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from cnn import build_cnn from datasets.mnist import MNIST from datasets.fashion_mnist import FASHION_MNIST from datasets.config import FASHION_MNIST_ROOT, MNIST_ROOT def str2bool(v): return v.lower() in ("yes", "true", "t", "1") parser = argparse.ArgumentParser( description='Convolutional Neural Network Training With Pytorch') train_set = parser.add_mutually_exclusive_group() parser.add_argument('--dataset', default='MNIST', choices=['MNIST', 'FASHION_MNIST'], type=str, help='MNIST or FASHION_MNIST') parser.add_argument('--dataset_root', default=MNIST_ROOT, help='Dataset root directory path') parser.add_argument('--input_dim', default=1, choices=[1, 3], type=int, help='Grey or RGB image input') parser.add_argument('--input_size', default=32, type=int, help='Convolutional layer hidden size') parser.add_argument('--output_size', default=64, type=int, help='Convolutional layer output size') parser.add_argument('--batch_size', default=32, type=int, help='Batch size for training') parser.add_argument('--num_workers', default=0, type=int, help='Number of workers used in dataloading') parser.add_argument('--epoch_size', default=30, type=int, help='Number of Epoches for training') parser.add_argument('--cuda', default=True, type=str2bool, help='Use CUDA to train model') parser.add_argument('--lr', '--learning-rate', default=1e-3, type=float, help='initial learning rate') parser.add_argument('--save_folder', default='weights/', help='Directory for saving checkpoint models') parser.add_argument('--photo_folder', default='results/', help='Directory for saving photos') args = parser.parse_args() if not os.path.exists(args.save_folder): os.mkdir(args.save_folder) if not os.path.exists(args.photo_folder): os.mkdir(args.photo_folder) def train(): if args.dataset == 'MNIST': if args.dataset_root == MNIST_ROOT: if not os.path.exists(MNIST_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = MNIST_ROOT dataset = MNIST(root=args.dataset_root,folder='train', transform=transforms.ToTensor()) elif args.dataset == 'FASHION_MNIST': if args.dataset_root == MNIST_ROOT: print("Using default FASHION_MNIST dataset_root") args.dataset_root = FASHION_MNIST_ROOT dataset = FASHION_MNIST(root=args.dataset_root,folder='train', transform=transforms.ToTensor()) net = build_cnn(phase='train', dimension=args.input_dim, input_size=args.input_size, output_size=args.output_size, num_classes=len(dataset.classes)) if args.cuda and torch.cuda.is_available(): net = torch.nn.DataParallel(net) cudnn.benchmark = True net.cuda() optimizer = optim.Adam(net.parameters(), lr=args.lr) criterion = nn.CrossEntropyLoss() epoch_size = args.epoch_size print('Loading the dataset...') data_loader = torch.utils.data.DataLoader(dataset, args.batch_size, num_workers=args.num_workers, shuffle=True, pin_memory=True) print('Training on:', dataset.name) print('Using model: CNN') print('Using the specified args:') print(args) loss_list = [] acc_list = [] for epoch in range(epoch_size): net.train() train_loss = 0.0 correct = 0 total = len(dataset) t0 = time.perf_counter() for step, data in enumerate(data_loader, start=0): images, labels = data if args.cuda: images = Variable(images.cuda()) labels = Variable(labels.cuda()) else: images = Variable(images) labels = Variable(labels) # forward outputs = net(images) # backprop optimizer.zero_grad() loss = criterion(outputs, labels) loss.backward() optimizer.step() # print statistics train_loss += loss.item() _, predicted = outputs.max(1) correct += predicted.eq(labels).sum().item() # print train process rate = (step + 1) / len(data_loader) a = "*" * int(rate * 50) b = "." * int((1 - rate) * 50) print("\rEpoch {}: {:^3.0f}%[{}->{}]{:.3f}".format(epoch+1, int(rate * 100), a, b, loss), end="") print(' Running time: %.3f' % (time.perf_counter() - t0)) acc = 100.*correct/ total loss = train_loss / step print('train loss: %.6f, acc: %.3f%% (%d/%d)' % (loss, acc, correct, total)) loss_list.append(loss) acc_list.append(acc/100) torch.save(net.state_dict(),args.save_folder + args.dataset + '.pth') plt.plot(range(epoch_size), loss_list, range(epoch_size), acc_list) plt.xlabel('Epoches') plt.ylabel('Sparse CrossEntropy Loss | Accuracy') plt.savefig(os.path.join( os.path.dirname( os.path.abspath(__file__)), args.photo_folder, args.dataset + "_train_details.png")) if __name__ == '__main__': train()

程序输入参数说明

  • dataset

  训练采用的数据集,目前提供MNIST ,FASHION_MNIST供选择。 点击查看数据集加载Demo

  • dataset_root:

  数据集读取地址, default已设置为数据集相对路径,部署在云端可能需要修改

  • hidden_size:

  FFN网络隐藏层个数, default为500层

  • batch_size:

  单次训练所抓取的数据样本数量,default为32,MNIST数据集推荐参考为128

  • num_workers:

  加载数据所使用线程个数,default为0,n\in (2,4,8,12\dots)n∈(2,4,8,12…)

  • epoch_size:

  训练次数, default为30

  • cuda:

  是否调用GPU训练

  • lr:

  超参数学习率,FNN采用Adam优化函数,default为 0.0010.001

  • save_folder:

  模型权重保存地址

程序输出文件说明

  • 训练细节

  print 于 python console, 包括单个epoch训练时间、训练集损失值、准确率

  • 模型权重

  模型保存路径为 ./weight/%.pth

  • 损失函数与正确率

  图片保存路径为 ./photos/%_train_details.png

5.2 模型测试代码

  加载同级目录下 test.py 程序代码

 

# %load test.py import sys import os os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE" import argparse import torch import torch.nn as nn import torch.backends.cudnn as cudnn import torchvision.transforms as transforms from torch.autograd import Variable import itertools import numpy as np import matplotlib as mpl import matplotlib.pyplot as plt mpl.rc('axes', labelsize = 14) mpl.rc('xtick', labelsize = 12) mpl.rc('ytick', labelsize = 12) sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) from cnn import build_cnn from datasets.mnist import MNIST from datasets.fashion_mnist import FASHION_MNIST from datasets.config import FASHION_MNIST_ROOT, MNIST_ROOT parser = argparse.ArgumentParser( description='Convolutional Neural Network Testing With Pytorch') parser.add_argument('--dataset', default='MNIST', choices=['MNIST', 'FASHION_MNIST'], type=str, help='MNIST or FASHION_MNIST') parser.add_argument('--dataset_root', default=MNIST_ROOT, help='Location of MNIST root directory') parser.add_argument('--input_dim', default=1, choices=[1, 3], type=int, help='Grey or RGB image input') parser.add_argument('--input_size', default=32, type=int, help='Convolutional layer input size') parser.add_argument('--output_size', default=64, type=int, help='Convolutional layer output size') parser.add_argument('--batch_size', default=32, type=int, help='Batch size for training') parser.add_argument('--num_workers', default=0, type=int, help='Number of workers used in dataloading') parser.add_argument('--trained_model', default='weights/{}.pth', type=str, help='Trained state_dict file path to open') parser.add_argument('--cuda', default=True, type=bool, help='Use cuda to train model') parser.add_argument('-f', default=None, type=str, help="Dummy arg so we can load in Jupyter Notebooks") args = parser.parse_args() args.trained_model = args.trained_model.format(args.dataset) def confusion_matrix(preds, labels, conf_matrix): for p, t in zip(preds, labels): conf_matrix[p, t] += 1 return conf_matrix def save_confusion_matrix(cm, classes, normalize=False, title='Confusion matrix', cmap=plt.cm.Blues): plt.imshow(cm, interpolation='nearest', cmap=cmap) plt.title(title) plt.colorbar() tick_marks = np.arange(len(classes)) plt.xticks(tick_marks, classes, rotation=90) plt.yticks(tick_marks, classes) plt.axis("equal") ax = plt.gca() left, right = plt.xlim() ax.spines['left'].set_position(('data', left)) ax.spines['right'].set_position(('data', right)) for edge_i in ['top', 'bottom', 'right', 'left']: ax.spines[edge_i].set_edgecolor("white") thresh = cm.max() / 2. for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])): num = '{:.2f}'.format(cm[i, j]) if normalize else int(cm[i, j]) plt.text(j, i, num, verticalalignment='center', horizontalalignment="center", color="white" if num > thresh else "black") plt.ylabel('True label') plt.xlabel('Predicted label') plt.savefig(os.path.join( os.path.dirname( os.path.abspath(__file__)), "results", args.dataset + '_confusion_matrix.png')) def test(): # load data if args.dataset == 'MNIST': if args.dataset_root == MNIST_ROOT: if not os.path.exists(MNIST_ROOT): parser.error('Must specify dataset_root if specifying dataset') args.dataset_root = MNIST_ROOT dataset = MNIST(root=args.dataset_root,folder='test', transform=transforms.ToTensor()) elif args.dataset == 'FASHION_MNIST': if args.dataset_root == MNIST_ROOT: print("Using default FASHION_MNIST dataset_root") args.dataset_root = FASHION_MNIST_ROOT dataset = FASHION_MNIST(root=args.dataset_root,folder='test', transform=transforms.ToTensor()) data_loader = torch.utils.data.DataLoader(dataset, args.batch_size, num_workers=args.num_workers, shuffle=True, pin_memory=True) # load net net = build_cnn(phase='test', dimension=args.input_dim, input_size=args.input_size, output_size=args.output_size, num_classes=len(dataset.classes)) if args.cuda and torch.cuda.is_available(): net = torch.nn.DataParallel(net) cudnn.benchmark = True net.cuda() net.load_state_dict(torch.load(args.trained_model)) print('Finish loading model: ', args.trained_model) net.eval() print('Training on:', dataset.name) print('Using model: Basic CNN') print('Using the specified args:') print(args) # evaluation criterion = nn.CrossEntropyLoss() test_loss = 0 correct = 0 total = 0 conf_matrix = torch.zeros(10, 10) class_correct = list(0 for i in range(10)) class_total = list(0 for i in range(10)) with torch.no_grad(): for step, data in enumerate(data_loader): images, labels = data if args.cuda: images = Variable(images.cuda()) labels = Variable(labels.cuda()) else: images = Variable(images) labels = Variable(labels) # forward outputs = net(images) loss = criterion(outputs, labels) test_loss += loss.item() _, predicted = outputs.max(1) conf_matrix = confusion_matrix(predicted, labels=labels, conf_matrix=conf_matrix) total += labels.size(0) correct += predicted.eq(labels).sum().item() c = (predicted.eq(labels)).squeeze() for i in range(c.size(0)): label = labels[i] class_correct[label] += c[i].item() class_total[label] += 1 acc = 100.* correct / total loss = test_loss / step print('test loss: %.6f, acc: %.3f%% (%d/%d)' % (loss, acc, correct, total)) for i in range(len(dataset.classes)): print('accuracy of %s : %.3f%% (%d/%d)' % ( str(dataset.classes[i]), 100 * class_correct[i] / class_total[i], class_correct[i], class_total[i])) save_confusion_matrix(conf_matrix.numpy(), classes=dataset.classes, normalize=False, title = 'Normalized confusion matrix') if __name__ == '__main__': test()

程序输入参数说明

  • dataset

  训练采用的数据集,目前提供MNIST ,FASHION_MNIST供选择。 点击查看数据集加载Demo

  • dataset_root:

  数据集读取地址, default已设置为数据集相对路径,部署在云端可能需要修改

  • hidden_size:

  FFN网络隐藏层个数, default为500层

  • batch_size:

  单次训练所抓取的数据样本数量,default为32,MNIST数据集推荐参考为128

  • num_workers:

  加载数据所使用线程个数,default为0,n\in (2,4,8,12\dots)n∈(2,4,8,12…)

  • trained_model:

  模型权重保存路径,default为 train.py 生成的ptb文件路径

  • cuda:

  是否调用GPU训练

程序输出文件说明

  • 测试集损失值与准确率

  print 于 python console 第一行

  • 各类别准确率

  print 于 python console 后续列表

  • 混淆矩阵

  图片保存路径为 ./photos/%_confusion_matrix.png

5.3 MNIST数据集

  • MNIST 来自美国国家标准与技术研究所, National Institute of Standards and Technology (NIST). 训练集 (training set) 由来自 250 个不同人手写的数字构成, 其中 50% 是高中学生, 50% 来自人口普查局 (the Census Bureau) 的工作人员,测试集(test set) 也是同样比例的手写数字数据

  • MNIST 是机器学习领域中非常经典的一个数据集,由60000个训练样本和10000个测试样本组成,每个样本都是一张 28\times2828×28 像素的灰度手写数字图片

  • MNIST 已经是一个被”嚼烂”了的数据集, 作为机器学习在视觉领域的hello world,很多教程都会对它”下手”, 几乎成为一个 “典范”

 

%run train.py --batch_size=128 --epoch_size=20

Loading the dataset...
Training on: MNIST
Using model: CNN
Using the specified args:
Namespace(batch_size=128, cuda=True, dataset='MNIST', dataset_root='C:\\Users\\sbzy\\Documents/GitHub/dl_algorithm/datasets\\MNIST', epoch_size=20, input_dim=1, input_size=32, lr=0.001, num_workers=0, output_size=64, photo_folder='results/', save_folder='weights/')
Epoch 1: 100%[**************************************************->]0.096  Running time: 4.268
train loss: 0.142464, acc: 95.505% (57303/60000)
Epoch 2: 100%[**************************************************->]0.048  Running time: 4.162
train loss: 0.052557, acc: 98.390% (59034/60000)
Epoch 3: 100%[**************************************************->]0.034  Running time: 4.199
train loss: 0.041116, acc: 98.717% (59230/60000)
Epoch 4: 100%[**************************************************->]0.064  Running time: 4.045
train loss: 0.030572, acc: 99.033% (59420/60000)
Epoch 5: 100%[**************************************************->]0.040  Running time: 4.029
train loss: 0.027046, acc: 99.142% (59485/60000)
Epoch 6: 100%[**************************************************->]0.006  Running time: 4.047
train loss: 0.022617, acc: 99.275% (59565/60000)
Epoch 7: 100%[**************************************************->]0.025  Running time: 4.025
train loss: 0.019697, acc: 99.365% (59619/60000)
Epoch 8: 100%[**************************************************->]0.015  Running time: 4.078
train loss: 0.018360, acc: 99.378% (59627/60000)
Epoch 9: 100%[**************************************************->]0.030  Running time: 4.062
train loss: 0.014056, acc: 99.545% (59727/60000)
Epoch 10: 100%[**************************************************->]0.000  Running time: 4.033
train loss: 0.015583, acc: 99.477% (59686/60000)
Epoch 11: 100%[**************************************************->]0.080  Running time: 4.106
train loss: 0.012131, acc: 99.602% (59761/60000)
Epoch 12: 100%[**************************************************->]0.000  Running time: 4.096
train loss: 0.012068, acc: 99.598% (59759/60000)
Epoch 13: 100%[**************************************************->]0.000  Running time: 4.104
train loss: 0.011510, acc: 99.622% (59773/60000)
Epoch 14: 100%[**************************************************->]0.003  Running time: 4.095
train loss: 0.010077, acc: 99.643% (59786/60000)
Epoch 15: 100%[**************************************************->]0.000  Running time: 4.059
train loss: 0.008377, acc: 99.728% (59837/60000)
Epoch 16: 100%[**************************************************->]0.002  Running time: 4.093
train loss: 0.007739, acc: 99.737% (59842/60000)
Epoch 17: 100%[**************************************************->]0.000  Running time: 4.033
train loss: 0.007553, acc: 99.747% (59848/60000)
Epoch 18: 100%[**************************************************->]0.002  Running time: 4.163
train loss: 0.008951, acc: 99.700% (59820/60000)
Epoch 19: 100%[**************************************************->]0.000  Running time: 4.327
train loss: 0.005622, acc: 99.805% (59883/60000)
Epoch 20: 100%[**************************************************->]0.025  Running time: 4.270
train loss: 0.006955, acc: 99.752% (59851/60000)

 

%run test.py --batch_size=128

Finish loading model:  weights/MNIST.pth
Training on: MNIST
Using model: Basic CNN
Using the specified args:
Namespace(batch_size=128, cuda=True, dataset='MNIST', dataset_root='C:\\Users\\sbzy\\Documents/GitHub/dl_algorithm/datasets\\MNIST', f=None, input_dim=1, input_size=32, num_workers=0, output_size=64, trained_model='weights/MNIST.pth')
test loss: 0.042636, acc: 99.080% (9908/10000)
accuracy of 0 : 99.592% (976/980)
accuracy of 1 : 99.736% (1132/1135)
accuracy of 2 : 98.450% (1016/1032)
accuracy of 3 : 98.911% (999/1010)
accuracy of 4 : 99.695% (979/982)
accuracy of 5 : 99.439% (887/892)
accuracy of 6 : 99.165% (950/958)
accuracy of 7 : 99.319% (1021/1028)
accuracy of 8 : 99.589% (970/974)
accuracy of 9 : 96.928% (978/1009)

5.4 FASHION-MNIST数据集

  • Fashion-MNIST 的大小、格式和训练集/测试集划分与原始的 MNIST 完全一致。60000/10000 的训练测试数据划分,28\times2828×28 的灰度图片。

  • Fashion-MNIST 的目的是要成为 MNIST 数据集的一个直接替代品。MNIST 太简单了,只需要一个像素就可以区分开,很多算法在测试集上的性能已经达到 99.6%。MNIST 数字识别的任务不代表现代机器学习。Fashion-MNIST 迁移到真正的机器视觉图像分类问题。

 

%run train.py --dataset=FASHION_MNIST --batch_size=128 --epoch_size=20

Using default FASHION_MNIST dataset_root
Loading the dataset...
Training on: FASHION_MNIST
Using model: CNN
Using the specified args:
Namespace(batch_size=128, cuda=True, dataset='FASHION_MNIST', dataset_root='C:\\Users\\sbzy\\Documents/GitHub/dl_algorithm/datasets\\FASHION_MNIST', epoch_size=20, input_dim=1, input_size=32, lr=0.001, num_workers=0, output_size=64, photo_folder='results/', save_folder='weights/')
Epoch 1: 100%[**************************************************->]0.331  Running time: 4.152
train loss: 0.407117, acc: 85.200% (51120/60000)
Epoch 2: 100%[**************************************************->]0.246  Running time: 4.073
train loss: 0.280310, acc: 89.793% (53876/60000)
Epoch 3: 100%[**************************************************->]0.330  Running time: 4.053
train loss: 0.239892, acc: 91.213% (54728/60000)
Epoch 4: 100%[**************************************************->]0.180  Running time: 4.036
train loss: 0.217020, acc: 92.027% (55216/60000)
Epoch 5: 100%[**************************************************->]0.203  Running time: 4.151
train loss: 0.197507, acc: 92.663% (55598/60000)
Epoch 6: 100%[**************************************************->]0.185  Running time: 4.104
train loss: 0.177419, acc: 93.398% (56039/60000)
Epoch 7: 100%[**************************************************->]0.328  Running time: 4.065
train loss: 0.160301, acc: 93.953% (56372/60000)
Epoch 8: 100%[**************************************************->]0.121  Running time: 4.083
train loss: 0.146904, acc: 94.577% (56746/60000)
Epoch 9: 100%[**************************************************->]0.158  Running time: 4.088
train loss: 0.131364, acc: 95.163% (57098/60000)
Epoch 10: 100%[**************************************************->]0.064  Running time: 4.080
train loss: 0.122862, acc: 95.435% (57261/60000)
Epoch 11: 100%[**************************************************->]0.142  Running time: 4.179
train loss: 0.109991, acc: 95.925% (57555/60000)
Epoch 12: 100%[**************************************************->]0.201  Running time: 4.107
train loss: 0.098736, acc: 96.317% (57790/60000)
Epoch 13: 100%[**************************************************->]0.062  Running time: 4.060
train loss: 0.089864, acc: 96.623% (57974/60000)
Epoch 14: 100%[**************************************************->]0.083  Running time: 4.039
train loss: 0.078398, acc: 97.030% (58218/60000)
Epoch 15: 100%[**************************************************->]0.087  Running time: 4.094
train loss: 0.073593, acc: 97.245% (58347/60000)
Epoch 16: 100%[**************************************************->]0.039  Running time: 4.104
train loss: 0.064772, acc: 97.568% (58541/60000)
Epoch 17: 100%[**************************************************->]0.133  Running time: 4.093
train loss: 0.062107, acc: 97.683% (58610/60000)
Epoch 18: 100%[**************************************************->]0.159  Running time: 4.073
train loss: 0.054286, acc: 97.910% (58746/60000)
Epoch 19: 100%[**************************************************->]0.063  Running time: 4.070
train loss: 0.050712, acc: 98.115% (58869/60000)
Epoch 20: 100%[**************************************************->]0.047  Running time: 4.038
train loss: 0.047520, acc: 98.203% (58922/60000)

 

%run test.py --dataset=FASHION_MNIST --batch_size=128

Using default FASHION_MNIST dataset_root
Finish loading model:  weights/FASHION_MNIST.pth
Training on: FASHION_MNIST
Using model: Basic CNN
Using the specified args:
Namespace(batch_size=128, cuda=True, dataset='FASHION_MNIST', dataset_root='C:\\Users\\sbzy\\Documents/GitHub/dl_algorithm/datasets\\FASHION_MNIST', f=None, input_dim=1, input_size=32, num_workers=0, output_size=64, trained_model='weights/FASHION_MNIST.pth')
test loss: 0.416567, acc: 90.470% (9047/10000)
accuracy of T-shirt/top : 91.200% (912/1000)
accuracy of Trouser : 98.000% (980/1000)
accuracy of Pullover : 95.000% (950/1000)
accuracy of Dress : 93.600% (936/1000)
accuracy of Coat : 71.100% (711/1000)
accuracy of Sandal : 98.500% (985/1000)
accuracy of Shirt : 64.700% (647/1000)
accuracy of Sneaker : 97.200% (972/1000)
accuracy of Bag : 99.000% (990/1000)
accuracy of Ankle boot : 96.400% (964/1000)

开始实验

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

撸码的xiao摩羯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值