zoukankan      html  css  js  c++  java
  • Tensorflow之卷积神经网络(CNN)

    前馈神经网络的弊端

    前一篇文章介绍过MNIST,是采用的前馈神经网络的结构,这种结构有一个很大的弊端,就是提供的样本必须面面俱到,否则就容易出现预测失败。如下图:

    同样是在一个图片中找圆形,如果左边为训练样本,右边为测试样本,如果只训练了左边的情况,右边的一定会预测错误,然而在我们人眼看来,这两个圆形的特征其实是一样的,不过是移动了一个位置而已,但是因为前馈网络结构的原因,导致在做权重分配的时候,把更多的权重分配给了左上角,右下角分配的较少,所以在做最终预测,便会出现较大的误差。所以,我们需要在图片中找出圆形的特征,即使是变换了位置,这些特征仍然是一样的,这样既减少了训练成本,又提升了训练的效率。就好像写代码把公共的部分封装起来复用一样。这个时候卷积神经网络就隆重登场了。

    卷积神经网络(CNN)

    根据前面的论述,我们发现,样本可以理解为是一个特征集,我们要找出这个特征集并过滤掉重复的特征,以提升训练的效率,降低成本。那么,我们如何来找寻图片中的各个特征呢?让我们来看看卷积神经网络的结构就可以明白了。

    卷积神经网络一般由:卷积层,池化层,全连接层组成。其中卷积和池化会根据不同的需要来做多次。

    在介绍这些结构之前,先要说一下图片是如何用矩阵来表达的。看如下代码:

    #!/usr/bin/python
    # -*- coding: UTF-8 -*-
    import PIL.Image as Image
    import numpy as np
    import pandas as pd
    import os
    import matplotlib.pyplot as plt
    
    #颜色信息会被去除,如果要带颜色,则是三维数组
    def imageToMatrix(filename):
        # 读取图片
        im = Image.open(filename)
        # 显示图片
        # im.show()
        width, height = im.size
        #灰度化
        im = im.convert("L")
        data = im.getdata()
        data = np.matrix(data, dtype="float") / 255.0
        new_data = np.reshape(data, (height, width))
        return new_data
    
    def matrixToImage(data):
        data = data * 255.0
        new_im = Image.fromarray(data)
        return new_im
    
    filename = "m2.png"
    data = imageToMatrix(filename)
    dataframe = pd.DataFrame(data=data)
    dataframe.to_csv('out.csv', sep=' ', header=False, float_format='%.2f', index=False)
    #后面是将矩阵转换为图片
    #new_im = matrixToImage(data)
    #plt.imshow(data, cmap=plt.cm.gray, interpolation="nearest")
    #new_im.show()
    

    代码所表达的意思就是将m2.png转换为矩阵(去掉色彩信息),将矩阵输出到out.csv文件中,然后我们将csv文件打开后,看看对比效果,如下图:

     (csv文件)

     (原图,50x50的png图片)

    csv文件中的数字表示的是每一个点的亮度,越靠近1越亮。如果是彩色图片,则每个点都包含了RGB三种颜色(因为色彩可以认为是RGB三种颜色叠加在一起来表达的),将会变为一个三维数组。

    1、卷积层

    卷积层主要是使用过滤器(filter)找寻样本特征的一个过程。过滤器,又叫kernel或者filter,是一个表示权重的矩阵,听名字就可以知道(卷积神经网络的卷积层)这一层非常重要。

    由浅入深,我们先讨论只含有灰度信息的图片,也就是图片的深度为1,这样使用得filter就只含有w(宽)和h(高)。

    一般我们会使用2x2,3x3,5x5等(一般来说,图片越大,采用的过滤器都会大一点)矩阵来与图片矩阵中相应位置的子矩阵做“点积”,如下图(为了看得清楚我使用的是20x20的png):

    (注:矩阵的点积就是矩阵对应位置的元素做乘法,然后相加,例如上图黄色矩阵和蓝色矩阵做点积就是:1x0+1x0+1x0+1x0+1x1+1x0+1x0+1x0+1x0=1,这个1就是第一次卷积得到的矩阵的第一个元素的值)。

    上图就是过滤器工作的方式,会向右以一定的步长来移动,到最右边以后,会以一定的步长(stride)来向下移动,直到整个图片结束。然后会得到一个新的矩阵,这个矩阵就是我们的特征矩阵。

    所以,如果我们使用不同的过滤器(权重值不同)来对图片做卷积,就会得到图片不同的信息,比如:我如果使用

    这样的过滤器,就可以得到图片的边缘特征。所以,一般在卷积层,都会使用多个过滤器来采集图片的特征。

    这里有个链接可以观看不同过滤器卷积的过程。

    不难发现,如果是20x20的矩阵,采用3x3,步长为1的话,一次卷积过后,矩阵会变为:18x18,那么如果多几个卷积层,那图片岂不是要被卷没了?所以为了让卷积不改变图片大小,增加了zero padding(0填充)的概念,就是往图片的四周补0,这样就不会改变图片大小了。这里有一个公式来计算卷积以后图片的大小:

    (input size - filter size + 2 * zero padding size) / stride + 1

    现在我们来看看带有RGB的图片如何做卷积。图片带有了R、G、B三种颜色通道,就变为了20x20x3,那么相应的过滤器也要带有三个通道,所以之前的3x3,就变成了3x3x3,如下图:

    三个权重数值可以不同,然后做点积,结果就是:

    Xr0 * Wr0 + Xr1 * Wr1 + Xr2 * Wr2 + Xr3 * Wr3 + Xr4 * Wr4 + Xr5 * Wr5 + Xr6 * Wr6 + Xr7 * Wr7 + Xr8 * Wr8

    Xg0 * Wg0 + Xg1 * Wg1 + Xg2 * Wg2 + Xg3 * Wg3 + Xg4 * Wg4 + Xg5 * Wg5 + Xg6 * Wg6 + Xg7 * Wg7 + Xg8 * Wg8

    Xb0 * Wb0 + Xb1 * Wb1 + Xb2 * Wb2 + Xb3 * Wb3 + Xb4 * Wb4 + Xb5 * Wb5 + Xb6 * Wb6 + Xb7 * Wb7 + Xb8 * Wb8 +

    B(偏置量)

    所以其实卷积最后得到的矩阵是:20x20。

    经过卷积之后的矩阵,要做ReLU变换来增强拟合能力,ReLU可以保证得到的矩阵所有元素的取值都大于0。

    2、池化层

    在卷积过后,我们要将结果进行池化,池化层的目的就是为了降低不必要的冗余信息(downsampling),有两种池化操作:求最大和求平均,一般都采用求最大值得方式。

    选取一个2x2的filter,然后将卷积后的矩阵分割为不同的块,选取步长为2,然后将里面的最大值取出来得到一个新的矩阵的过程,就是最大池化操作,如图:

    那么,我们不禁会问,将其中的一些值舍弃掉,只留最大值,对判定结果有没有影响?

    答案是会有一定影响,但是影响会比较小,因为选择的过滤器比图片本身要小很多,那经过过滤器筛选出来的特征本身也是很小的局部特征,而这些特征中,经过权值计算,权值比较小的值则表明对该局部特征的影响比较小,所以只保留影响最大的最大值,这样既能减少矩阵大小,而又不会对预测结果产生太大影响。

    3、全连接层

    即是将所有特征组合在一起组成一个一维数组的过程。

    总体

    至此,卷积神经网络的主要层级功能都介绍完毕了,我们来看看一种卷积神经网络的整体结构,如下图:

    它采用了两个卷积层,两个池化层,一个全连接层来表达。

    目前比较流行的卷积网络的结构有:

    LeNet(1990年诞生的第一个成功的卷积神经网络的模型)、AlexNetZFNetGoogLeNet、VGGNet、ResNet等,感兴趣的朋友可以自行搜索一下。

    TensorFlow的官方例子

    TensorFlow在对MNIST的图片集上采用了卷积神经网络来做图形识别,代码如下:

    # coding=utf-8
    # Disable linter warnings to maintain consistency with tutorial.
    # pylint: disable=invalid-name
    # pylint: disable=g-bad-import-order
    from __future__ import absolute_import
    from __future__ import division
    from __future__ import print_function
    import argparse
    import sys
    import tempfile
    from tensorflow.examples.tutorials.mnist import input_data
    import tensorflow as tf
    FLAGS = None
    
    def deepnn(x):
        with tf.name_scope('reshape'):
            x_image = tf.reshape(x, [-1, 28, 28, 1])
        ## 第一层卷积操作 ##
        with tf.name_scope('conv1'):
            W_conv1 = weight_variable([5, 5, 1, 32])
            b_conv1 = bias_variable([32])
            h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
    
        with tf.name_scope('pool1'):
            h_pool1 = max_pool_2x2(h_conv1)
    
        # Second convolutional layer -- maps 32 feature maps to 64.
        ## 第二层卷积操作 ##
        with tf.name_scope('conv2'):
            W_conv2 = weight_variable([5, 5, 32, 64])
            b_conv2 = bias_variable([64])
            h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
    
        with tf.name_scope('pool2'):
            h_pool2 = max_pool_2x2(h_conv2)
    
        ## 第三层全连接操作 ##
        with tf.name_scope('fc1'):
            W_fc1 = weight_variable([7 * 7 * 64, 1024])
            b_fc1 = bias_variable([1024])
            h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64])
            h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1)
    
        with tf.name_scope('dropout'):
            keep_prob = tf.placeholder(tf.float32)
            h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
    
        ## 第四层输出操作 ##
        with tf.name_scope('fc2'):
            W_fc2 = weight_variable([1024, 10])
            b_fc2 = bias_variable([10])
            y_conv = tf.matmul(h_fc1_drop, W_fc2) + b_fc2
        return y_conv, keep_prob
    
    def conv2d(x, W):
        return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')
    
    def max_pool_2x2(x):
        return tf.nn.max_pool(x, ksize=[1, 2, 2, 1],
                              strides=[1, 2, 2, 1], padding='SAME')
    
    def weight_variable(shape):
        initial = tf.truncated_normal(shape, stddev=0.1)
        return tf.Variable(initial)
    
    def bias_variable(shape):
        initial = tf.constant(0.1, shape=shape)
        return tf.Variable(initial)
    
    def main(_):
        # Import data
        mnist = input_data.read_data_sets(FLAGS.data_dir, one_hot=True)
    
        # Create the model
        # 声明一个占位符,None表示输入图片的数量不定,28*28图片分辨率
        x = tf.placeholder(tf.float32, [None, 784])
    
        # 类别是0-9总共10个类别,对应输出分类结果
        y_ = tf.placeholder(tf.float32, [None, 10])
        y_conv, keep_prob = deepnn(x)
    
        with tf.name_scope('loss'):
            cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=y_,
                                                                    logits=y_conv)
        cross_entropy = tf.reduce_mean(cross_entropy)
    
        with tf.name_scope('adam_optimizer'):
            train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy)
    
        with tf.name_scope('accuracy'):
            correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(y_, 1))
            correct_prediction = tf.cast(correct_prediction, tf.float32)
        accuracy = tf.reduce_mean(correct_prediction)
    
        graph_location = tempfile.mkdtemp()
        print('Saving graph to: %s' % graph_location)
        train_writer = tf.summary.FileWriter(graph_location)
        train_writer.add_graph(tf.get_default_graph())
    
        with tf.Session() as sess:
            sess.run(tf.global_variables_initializer())
            for i in range(20000):
                batch = mnist.train.next_batch(50)
                if i % 1000 == 0:
                    train_accuracy = accuracy.eval(feed_dict={
                        x: batch[0], y_: batch[1], keep_prob: 1.0})
                    print('step %d, training accuracy %g' % (i, train_accuracy))
                train_step.run(feed_dict={x: batch[0], y_: batch[1], keep_prob: 0.5})
    
            print('test accuracy %g' % accuracy.eval(feed_dict={
                x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0}))
    
    if __name__ == '__main__':
        parser = argparse.ArgumentParser()
        parser.add_argument('--data_dir', type=str,
                            default='/tmp/tensorflow/mnist/input_data',
                            help='Directory for storing input data')
        FLAGS, unparsed = parser.parse_known_args()
        tf.app.run(main=main, argv=[sys.argv[0]] + unparsed)
    

    代码比较清楚,就不过多讲述了,基本就是:卷积->池化->卷积->池化->全连接->dropout->全连接->训练->测试。

    注意到代码中的:with tf.name_scope('xxx'): 是在构建计算图,看其中的代码:

    graph_location = tempfile.mkdtemp()
    print('Saving graph to: %s' % graph_location)
    train_writer = tf.summary.FileWriter(graph_location)
    train_writer.add_graph(tf.get_default_graph())
    

    是将计算图存储下来,打印出路径,我们找到相应的路径以后,在命令行中执行:

    将地址复制到Chrome中,点击Graph标签,就可以看到计算图了。

    上面的代码都跟是遵循我们之前介绍的卷积网络的卷积层,池化层,全连接层来组成的,其中多了一个dropout,如下:

        with tf.name_scope('dropout'):
            keep_prob = tf.placeholder(tf.float32)
            h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)
    

    dropout的目的是为了防止产生过拟合的问题,这里解释一下过拟合和欠拟合。

    过拟合(overfitting):当某个模型在经过训练后,过度的学习了训练集的噪音和干扰项,从而导致在测试集上表现很差,但是在训练集上表现很好,就称为过拟合。

    欠拟合(underfitting):值模型无论在测试集还是训练集上表现都不好,也就是说训练样本不够。

    那么dropout如何来避免这些噪音的影响呢?通常做法是随机的将某些特征的权值置为0。

    详细解释看这里:https://yq.aliyun.com/articles/68901

    常见问题

    1、官网的例子filter为什么要选32个?filter size为什么要选5x5?stride为什么要选2?

    网上有人说是因为这些数字到底选多少是一门艺术和经验的总结。

    2、在做卷积的时候,为什么可以使用同一个filter(权重矩阵),而不是每移动一步就换一个矩阵?

    因为filter的作用在于将图片中的某一类特征卷积出来,将符合条件的特征都筛选出来。所以可以共享权值矩阵。

    3、全连接层到底是做什么的?有什么用?

    将之前的结果,例如官网例子中的第二轮pool2的结果 7x7x64,通过跟乘以权重矩阵(W),得到1024个元素的一维数组的过程。就跟之前讲MNIST一样,我们就是要通过训练来得到这个W的各个元素的值。这个值会影响预测结果。

    4、卷积网络如何保证特征不变性(同样的特征不因位置改变而改变)?

    因为对同一个图片来说,在同一深度同一卷积层下,权值是共享的,也就是说,相同的特征会被统一识别出来而不需要再次学习。

    参考文章:

    https://www.zhihu.com/question/39022858

    http://cs231n.github.io/convolutional-networks/#case

    http://www.sohu.com/a/132394579_714863

  • 相关阅读:
    [数据结构]直接插入排序
    隐藏小程序scroll-view组件的滚动条
    当 uni-app 遇见 vscode
    npm(你怕吗) 全局安装与本地安装、开发依赖和生产依赖
    Vue-resource的使用
    spy-debugger调试、抓包工具
    一个小时学会Git
    flex布局踩过的坑
    Html5移动端布局及(rem布局)页面自适应布局详解
    使用vscode自动编译less
  • 原文地址:https://www.cnblogs.com/lizheng114/p/7498328.html
Copyright © 2011-2022 走看看