7.5 CNN的实现

我们已经实现了卷积层和池化层,现在来组合这些层,搭建进行手写数字识别的 CNN。这里要实现如图 7-23 所示的 CNN。

7.5 CNN的实现 - 图1

图 7-23 简单 CNN 的网络构成

如图 7-23 所示,网络的构成是“Convolution - ReLU - Pooling -Affine - ReLU - Affine - Softmax”,我们将它实现为名为 SimpleConvNet 的类。

首先来看一下 SimpleConvNet 的初始化(__init__),取下面这些参数。

参数

  • input_dim——输入数据的维度:(通道,高,长)
  • conv_param——卷积层的超参数(字典)。字典的关键字如下:

    filter_num——滤波器的数量

    filter_size——滤波器的大小

    stride——步幅

    pad——填充

  • hidden_size——隐藏层(全连接)的神经元数量

  • output_size——输出层(全连接)的神经元数量
  • weitght_int_std——初始化时权重的标准差

这里,卷积层的超参数通过名为 conv_param 的字典传入。我们设想它会像 {'filter_num':30,'filter_size':5, 'pad':0, 'stride':1} 这样,保存必要的超参数值。

SimpleConvNet 的初始化的实现稍长,我们分成 3 部分来说明,首先是初始化的最开始部分。

  1. class SimpleConvNet:
  2. def __init__(self, input_dim=(1, 28, 28),
  3. conv_param={'filter_num':30, 'filter_size':5,
  4. 'pad':0, 'stride':1},
  5. hidden_size=100, output_size=10, weight_init_std=0.01):
  6. filter_num = conv_param['filter_num']
  7. filter_size = conv_param['filter_size']
  8. filter_pad = conv_param['pad']
  9. filter_stride = conv_param['stride']
  10. input_size = input_dim[1]
  11. conv_output_size = (input_size - filter_size + 2*filter_pad) / \
  12. filter_stride + 1
  13. pool_output_size = int(filter_num * (conv_output_size/2) *
  14. (conv_output_size/2))

这里将由初始化参数传入的卷积层的超参数从字典中取了出来(以方便后面使用),然后,计算卷积层的输出大小。接下来是权重参数的初始化部分。

  1. self.params = {}
  2. self.params['W1'] = weight_init_std * \
  3. np.random.randn(filter_num, input_dim[0],
  4. filter_size, filter_size)
  5. self.params['b1'] = np.zeros(filter_num)
  6. self.params['W2'] = weight_init_std * \
  7. np.random.randn(pool_output_size,
  8. hidden_size)
  9. self.params['b2'] = np.zeros(hidden_size)
  10. self.params['W3'] = weight_init_std * \
  11. np.random.randn(hidden_size, output_size)
  12. self.params['b3'] = np.zeros(output_size)

学习所需的参数是第 1 层的卷积层和剩余两个全连接层的权重和偏置。将这些参数保存在实例变量的 params 字典中。将第 1 层的卷积层的权重设为关键字 W1,偏置设为关键字 b1。同样,分别用关键字 W2b2 和关键字 W3b3 来保存第 2 个和第 3 个全连接层的权重和偏置。

最后,生成必要的层。

  1. self.layers = OrderedDict()
  2. self.layers['Conv1'] = Convolution(self.params['W1'],
  3. self.params['b1'],
  4. conv_param['stride'],
  5. conv_param['pad'])
  6. self.layers['Relu1'] = Relu()
  7. self.layers['Pool1'] = Pooling(pool_h=2, pool_w=2, stride=2)
  8. self.layers['Affine1'] = Affine(self.params['W2'],
  9. self.params['b2'])
  10. self.layers['Relu2'] = Relu()
  11. self.layers['Affine2'] = Affine(self.params['W3'],
  12. self.params['b3'])
  13. self.last_layer = softmaxwithloss()

从最前面开始按顺序向有序字典(OrderedDict)的 layers 中添加层。只有最后的 SoftmaxWithLoss 层被添加到别的变量 lastLayer 中。

以上就是 SimpleConvNet 的初始化中进行的处理。像这样初始化后,进行推理的 predict 方法和求损失函数值的 loss 方法就可以像下面这样实现。

  1. def predict(self, x):
  2. for layer in self.layers.values():
  3. x = layer.forward(x)
  4. return x
  5. def loss(self, x, t):
  6. y = self.predict(x)
  7. return self.lastLayer.forward(y, t)

这里,参数 x 是输入数据,t 是教师标签。用于推理的 predict 方法从头开始依次调用已添加的层,并将结果传递给下一层。在求损失函数的 loss 方法中,除了使用 predict 方法进行的 forward 处理之外,还会继续进行 forward 处理,直到到达最后的 SoftmaxWithLoss 层。

接下来是基于误差反向传播法求梯度的代码实现。

  1. def gradient(self, x, t):
  2. # forward
  3. self.loss(x, t)
  4. # backward
  5. dout = 1
  6. dout = self.lastLayer.backward(dout)
  7. layers = list(self.layers.values())
  8. layers.reverse()
  9. for layer in layers:
  10. dout = layer.backward(dout)
  11. # 设定
  12. grads = {}
  13. grads['W1'] = self.layers['Conv1'].dW
  14. grads['b1'] = self.layers['Conv1'].db
  15. grads['W2'] = self.layers['Affine1'].dW
  16. grads['b2'] = self.layers['Affine1'].db
  17. grads['W3'] = self.layers['Affine2'].dW
  18. grads['b3'] = self.layers['Affine2'].db
  19. return grads

参数的梯度通过误差反向传播法(反向传播)求出,通过把正向传播和反向传播组装在一起来完成。因为已经在各层正确实现了正向传播和反向传播的功能,所以这里只需要以合适的顺序调用即可。最后,把各个权重参数的梯度保存到 grads 字典中。这就是 SimpleConvNet 的实现。

现在,使用这个 SimpleConvNet 学习 MNIST 数据集。用于学习的代码与 4.5 节中介绍的代码基本相同,因此这里不再罗列(源代码在 ch07/train_convnet.py 中)。

如果使用 MNIST 数据集训练 SimpleConvNet,则训练数据的识别率为 99.82%,测试数据的识别率为 98.96%(每次学习的识别精度都会发生一些误差)。测试数据的识别率大约为 99%,就小型网络来说,这是一个非常高的识别率。下一章,我们会通过进一步叠加层来加深网络,实现测试数据的识别率超过 99% 的网络。

如上所述,卷积层和池化层是图像识别中必备的模块。CNN 可以有效读取图像中的某种特性,在手写数字识别中,还可以实现高精度的识别。