算法大数据部落我爱编程

深度学习笔记(二)卷积神经网络

2017-09-15  本文已影响425人  nfter

在接触卷积神经网络以前,我们学习的神经网络,叫全连接神经网络,见深度学习笔记(一)感知器、梯度下降、反向传播。并且,我们用全连接神经网络做了MNIST,用来识别手写数字,见【深度学习 Hello World系列(二)】 用TensorFlow实现MNIST

大家可能会觉得全连接神经网络不是挺好的么,为啥还要搞其他有的没的,下面我们先谈一谈全连接神经网络在图像识别时的弊端,以及为啥要用卷积神经网络。

全连接神经网络在图像识别时的弊端

全连接神经网络,图片来自Michael Nielsen的博客
  1. 用全连接神经网络识别图片,参数太多了。我们在做MNIST时图片大小是28*28,可能感觉不出来,但是现实中的图片远不止这个大小,比如1000*1000,假设隐藏层有100个节点,那么这个隐藏层就有超过1亿的参数,这样量级的参数会让训练和扩展都变得极其艰难。

  2. 全连接神经网络没有利用图片的空间结构。比如说,有一张猫的图片,猫在上面还是下面并不会改变我们识别这个图片的结果,也就是说,猫的位置不会给我们的结果带来影响。

图片来自优达学城深度学习课程

从上面我们可以看出,用神经网络做图像识别,最大的难点在于参数太多,导致模型难以训练并且效果很差,那么卷积神经网络(Convolutional Neural Networks)是如何利用图像自身的特点来减少参数的?

卷积神经网络

在谈卷积神经网络如何减少参数之前,我们先了解下卷积神经网络是个啥。

图片来自hanbingtao的博客

一个卷积神经网络一般会由数个卷积层(Convolution Layer),池化层(Pooling Layer),全连接层(Fully Connected Layer)组成。上图就是由,两个卷积+池化,再加两个全连接层组成。

卷积神经网络减参三宝

对卷积神经网络有了一个大概的印象后,我们来看卷积神经网络减少参数的三宝:

1. 局部连接(local receptive fields)

之前全连接神经网络,会将每一个隐藏层的节点与输入层的节点连接,而卷积神经网络,使用卷积核(kernal)或者叫过滤器(filter),比如下图的5*5的卷积核,与输入层的节点连接。通过卷积核的平移(扫描)来获取隐藏层节点,平移的跨距叫做strides。


局部连接,图片来自Michael Nielsen的博客
2. 权值共享(Shared weights and biases)

权值共享是指,在输入层和隐藏层之间连接的权值(w),就是卷积核的大小。为什么能有这种操作?这是由图片的特征觉得的,图片是由很多细节组成的,比如一个狗,它有眼睛、鼻子、毛发,各种各样的特征,并且特征的位置不会影响最后的结果。


而我们用卷积核来在输入层扫描来获取小的特征,所以通过卷积核扫描得到的叫feature map,多个feature map就可以表示这个图片,这个操作就是局部连接。由于特征的位置不会影响识别的结果,在左边的狗鼻子和在右边的狗鼻子都是狗鼻子,这样使得我们可以权值共享。

feature map,图片来自Michael Nielsen的博客

经过这两步操作,我们可以感觉到,参数会有明显的减少。我们举个例子:

  • 输入数据,维度为 32 * 32 *3
    20个卷积核,维度为8 * 8 * 3
    stride宽和高都为2
    padding为1
    输出层为 14 * 14 * 20

如果没有局部连接和权重共享,参数将有:
(32 * 32 * 3 + 1)*(14 * 14 * 20) = 12046160
经过局部连接和权重共享操作后,参数有:
(8 * 8 * 3 + 1) * 20 = 3860
减少了3120倍。

3.等变表示(equivariant representations)

池化(pooling)就是一种常见的等变表示,并且很容易理解,用一个图就可以表示清楚。

Max Pooling,图片来自cs231

上图中的Max Pooling就是找到范围的最大值,比如红色区域有1,1,5,6四个值,就取6。这样也行?这又回到了图片的特征,如果这个区域有某个特征,我们就抓住它就可以了,并且实践表明,这并不会改变图片的形状。

图片来自cs231

用TensorFlow实现卷积神经网络

上面讲了那么多,我们来实战一下,用卷积神经网络来对 CIFAR-10 数据集进行图像分类,该数据集包含飞机、狗、猫等十种图片。

下面贴一些我认为重点的代码,完整的代码在:https://github.com/freeman93/dlnd-image-classification。里面的notebook,有详细的指引与说明。代码可能比较丑,原谅我还是个初学者。

我们构建一个函数conv2d_maxpool来完成卷积与池化的操作。

def conv2d_maxpool(x_tensor, conv_num_outputs, conv_ksize, conv_strides, pool_ksize, pool_strides):
    """
    Apply convolution then max pooling to x_tensor
    :param x_tensor: TensorFlow Tensor
    :param conv_num_outputs: Number of outputs for the convolutional layer
    :param conv_ksize: kernal size 2-D Tuple for the convolutional layer
    :param conv_strides: Stride 2-D Tuple for convolution
    :param pool_ksize: kernal size 2-D Tuple for pool
    :param pool_strides: Stride 2-D Tuple for pool
    : return: A tensor that represents convolution and max pooling of x_tensor
    """
    # x_tensor shape
    shape = x_tensor.get_shape().as_list()
    # weight
    weight = tf.Variable(tf.truncated_normal([*conv_ksize, shape[3], \
conv_num_outputs],stddev=0.05,mean=0.0))
    # bias
    bias = tf.Variable(tf.zeros(conv_num_outputs))
    # convolution
    x_tensor = tf.nn.conv2d(x_tensor, weight, strides=[1,*conv_strides,1], padding='SAME') 
    x_tensor = tf.nn.bias_add(x_tensor, bias)
    x_tensor = tf.nn.relu(x_tensor)
    # pooling
    x_tensor = tf.nn.max_pool(x_tensor, 
                              ksize=[1,*pool_ksize, 1],
                              strides=[1, *pool_strides, 1],
                              padding='SAME')
    
    return x_tensor

以上是用TensorFlow比较低阶的api实现的,当然你也可以用TensorFlow Layers or TensorFlow Layers (contrib) ,事情会变得非常简单。

比如我们用TensorFlow Layers 来实现全连接层的函数。

def fully_conn(x_tensor, num_outputs):
    """
    Apply a fully connected layer to x_tensor using weight and bias
    : x_tensor: A 2-D tensor where the first dimension is batch size.
    : num_outputs: The number of output that the new tensor should be.
    : return: A 2-D tensor where the second dimension is num_outputs.
    """
    # 低阶函数实现
#     shape = x_tensor.get_shape().as_list()
#     weight = tf.Variable(tf.truncated_normal([shape[1], num_outputs], stddev=0.05, mean=0.0))
#     bias = tf.Variable(tf.zeros(num_outputs))
#     fc = tf.add(tf.matmul(x_tensor, weight), bias)
#     fc = tf.nn.relu(fc)

    # 用tf.layers实现
    return tf.layers.dense(x_tensor, num_outputs, activation=tf.nn.relu)

完整的代码在:https://github.com/freeman93/dlnd-image-classification

总结一下

参考资料

优达学城深度学习课程
TensorFlow 官网
零基础入门深度学习(4) - 卷积神经网络
Neural Networks and Deep Learning By Michael Nielsen / Aug 2017
Deep Learning By Ian Goodfellow and Yoshua Bengio and Aaron Courville
CS231n Convolutional Neural Networks for Visual Recognition

上一篇 下一篇

猜你喜欢

热点阅读