Convolution 卷积
卷积的定义这里不再赘述,大家只要知道是一种矩阵运算即可,更多相关的内容可以参考 这里
。因为卷积实际上是一个确定的数学运算,我们只要设置好 filter,直接计算就可以看到效果。
在 Tensorflow 中,已经提供给我们几种内置的卷积方法(1 维卷积输入是 2 维,2 维卷积输入是 3 维,更多详细的说明可以参考 这里
)
参考代码 16_basic_kernels.py
,我们直接用预置的权重来处理图像,大家可以看看不同的效果。我自己找了一张图效果如下:
Convnet on MNIST
接下来我们详细说明一下如何用 CNN 进行 MNIST 手写数字识别,具体代码参考 17_conv_mnist.py
我们构建的网络如下图所示:有 2 个卷积层,每个都跟着一个 maxpool 池化层,最后是两个全连接层,所有卷积层的步长都是 [1,1,1,1]
这里我们有两个卷积层,两个全连接层,两个池化层,所以我们要注意复用代码,具体请参考代码中的写法,用好 variable scope。
Convolutional Layer
对于卷积层,我们使用 tf.nn.conv2d
来构建,一般来说会把卷积层和 relu 合并到一起,函数大概长这样:
def conv_relu(inputs, filters, k_size, stride, padding, scope_name): with tf.variable_scope(scope_name, reuse=tf.AUTO_REUSE) as scope: in_channels = inputs.shape[-1] kernel = tf.get_variable('kernel', [k_size, k_size, in_channels, filters], initializer=tf.truncated_normal_initializer()) biases = tf.get_variable('biases', [filters], initializer=tf.random_normal_initializer()) conv = tf.nn.conv2d(inputs, kernel, strides=[1, stride, stride, 1], padding=padding) return tf.nn.relu(conv + biases, name=scope.name)
我们不需要去手动计算输出的维度,但是大概知道输入是如何变换的可以方便我们去调优,我们可以通过下面的公式来计算维度,其中:
输入图片的尺寸 W(对于 MNIST 来说是 28×28)
过滤器的尺寸 F(这里用 5×5)
步长 Stride S(这里用 1×1)
Zero Padding 个数 P(这里是 2)
公式为 $ (W-F+2P)/S + 1 $,对应 MNIST 就是 (28 - 5 + 2*2)/1 + 1 = 28
。关于 stride 如何影响尺寸的,可以参考 这里
Pooling
池化是一种用来降低维度的下采样技术,这里我们采用最常用的 MaxPooling 方法(关于 MaxPooling 可以参考上一讲的内容)。在 Tensorflow 中,我们可以使用 tf.nn.max_pool
来实现,具体为:
def maxpool(inputs, ksize, stride, padding='VALID', scope_name='pool'): with tf.variable_scope(scope_name, reuse=tf.AUTO_REUSE) as scope: pool = tf.nn.max_pool(inputs, ksize=[1, ksize, ksize, 1], strides=[1, stride, stride, 1], padding=padding) return pool
同样,我们给出 MaxPooling 输出的计算公式,其中:
输入图片的尺寸 W(对于 MNIST 来说是 28×28)
池化大小 K(这里用 2×2)
步长 Stride S(这里用 2×2)
Zero Padding 个数 P(这里是 0)
公式为 $ (W-K+2P)/S + 1 $,对应 MNIST 就是 (28 - 2 + 2*0)/2 + 1 = 14
。
Fully Connected
最后我们来看看全连接层,和我们之前实现的类似,具体如下:
def fully_connected(inputs, out_dim, scope_name='fc'): with tf.variable_scope(scope_name, reuse=tf.AUTO_REUSE) as scope: in_dim = inputs.shape[-1] w = tf.get_variable('weights', [in_dim, out_dim], initializer=tf.truncated_normal_initializer()) b = tf.get_variable('biases', [out_dim], initializer=tf.constant_initializer(0.0)) out = tf.matmul(inputs, w) + b return out
最终结果
我们把前面的几层合到一起,训练一次之后打开 Tensorboard 看一下(命令 tensorboard --logdir='graphs/convnet/'
),就可以看到非常整齐的模型结构!
准确率和 loss 的趋势为:
注:在我的机器上训练一轮大约需要 45s(没有 GPU)
其实….
其实,我们并不需要自己去写前面的各种层,因为 Tensorflow 提供了更加高阶的 tf.layers
接口,前面的几层代码可以这样写:
# 卷积层 conv1 = tf.layers.conv2d(inputs=self.img, filters=32, kernel_size=[5, 5], padding='SAME', activation=tf.nn.relu, name='conv1') # 池化层 pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2, name='pool1') # 全连接层 fc = tf.layers.dense(pool2, 1024, activation=tf.nn.relu, name='fc') # Dropout dropout = tf.layers.dropout(fc, self.keep_prob, training=self.training, name='dropout')
Be First to Comment