深度学习——手写数字识别
学习深度学习的朋友应该对MNIST数据集不陌生吧,相信很多人在刚开始学习深度学习的时候都会用到MNIST数据集进行书写数字识别。本篇文章参考鱼书创建一个深度网络来进行书写数字识别的任务。
如上图所示,这里使用的卷积层全都是 3 × 3 3 \times 3 3×3 的小型滤波器,特点是随着层的加深,通道数变大(卷积层的通道数从前面的层开始按顺序以16、16、32、32、64、64的方式增加)。此外,如图所示,插入了池化层,以逐渐减小中间数据的空间大小;并且,后面的全连接层中还使用了Dropout层(为了防止过拟合)。
这个网络使用He初始值(何恺明大神的提出的)作为权重的初始值,使用Adam更新权重参数。把上述内容总结起来,这个网络有如下特点。
- 基于 3 × 3 3 \times 3 3×3的小型滤波器的卷积层。
- 激活函数是 ReLU。
- 全连接层的后面使用 Dropout 层。
- 基于 Adam 的最优化。
- 使用 He 初始值作为权重初始值。
权重初始化对训练深度网络很重要,特别是ReLU激活函数流行之后。Xavier 初始化是针对Sigmoid和Tanh设计的,Sigmoid函数和Tanh函数左右对称,且中央附近可以视作线性函数,但ReLU的非线性特性导致前向传播时输出方差会变化。
而He初始化主要是为了解决ReLU激活函数在初始化时方差缩小的问题。He初始值一种专门为使用ReLu激活函数及其变体(如Leaky ReLU, PPeLU)的神经网络层设计的权重初始化方法。它是由何恺明(Kaiming He)等人在2015年的论文《Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification》中提出的。ReLU会把负数部分归零,所以输出的方差会比输入小一半。如果每一层都这样,深度网络的方差会越来越小,导致梯度消失。
He初始值使用标准差为 2 n \sqrt{\frac{2}{n}} n2 的高斯分布,n是输入神经元的数量。与 Xavier 初始化相比, He 的方差更大,以补偿ReLU造成的方差减半。
代码:https://github.com/Benxiaogu/mnist
鱼书全书代码
https://github.com/qiaohaoforever/DeepLearningFromScratch
训练结果:
函数im2col
的作用是将数据展开以适合滤波器(权重)。如下图所示,对3维的输入数据应用此函数后,数据转换为2维矩阵(正确地讲,是把包含批数量的4维数据转换成了2维数据)。
im2col 是 "image to column"的缩写,翻译过来就是“从图像到矩阵”的意思。
使用im2col
展开输入数据之后,之后就只需将卷积层的滤波器(权重)纵向展开为1列,并计算2个矩阵的乘积即可,如下图所示。这和全连接层的 Affine 层进行的处理基本相同。
如上图所示,基于 im2col
方式的输出结果是2为矩阵。因为 CNN 中数据会保存为4维数组,所以要将 2 维输出数据转换为合适的形状。
函数col2im
是函数im2col
的逆过程
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):"""Parameters:input_data:由(数据量, 通道, 高, 长)的4维数组构成的输入数据filter_h:滤波器的高filter_w:滤波器的长stride:步幅pad:填充Returnscol:2维数组"""N, C, H, W = input_data.shapeout_h = 1 + (H + 2*pad - filter_h) // strideout_w = 1 + (W + 2*pad - filter_w) // strideimg = np.pad(input_data, [(0,0), (0,0), (pad,pad), (pad,pad)], 'constant')col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))for y in range(filter_h):y_max = y + stride*out_hfor x in range(filter_w):x_max = x + stride*out_wcol[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)return coldef col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):"""Parameters:col:2维数组input_data:由(数据量, 通道, 高, 长)的4维数组构成的输入数据filter_h:滤波器的高filter_w:滤波器的长stride:步幅pad:填充Returns:img:由(数据量, 通道, 高, 长)的4维数组构成的输出数据"""N, C, H, W = input_shapeout_h = (H + 2*pad - filter_h) // stride + 1out_w = (W + 2*pad - filter_w) // stride + 1col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2) # reshape形状,transpose调整维度顺序img = np.zeros((N, C, H + 2*pad + filter_h - 1, W + 2*pad + filter_w - 1))for y in range(filter_h):y_max = y + stride*out_hfor x in range(filter_w):x_max = x + stride*out_wimg[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]return img[:, :, pad:H + pad, pad:W + pad] # 去除填充部分
函数np.flatten()
的作用是将多维数组扁平化为一维数组。np.flatten()
默认按行优先进行降维,也就是将多维数组的第一行所有元素放到一位数组的前面,然后是第二行,依次类推。