卷积神经网络简介

全连接神经网络

每个神经元与前后相邻层的每一个神经元都有连接关系,输入是特征。输出是预测的结果。

1
参数个数:∑(前层X后层+后层)

一张分辨率仅仅是28x28 的黑白图像,就有近40 万个待优化的参数。现实生活中高分辨率的彩色图像,像素点更多,且为红绿蓝三通道信息。待优化的参数过多,容易导致模型过拟合。为避免这种现象,实际应用中一般不会将原始图片直接喂入全连接网络。

在实际应用中,会先对原始图像进行特征提取,把提取到的特征喂给全连接网络,再让全连接网络计算出分类评估值。

例:先将此图进行多次特征提取,再把提取后的计算机可读特征喂给全连接网络 。

卷积

卷积是一种有效提取图片特征的方法。一般用一个正方形卷积核,遍历图片上的每一个像素点。图片与卷积核重合区域内相对应的每一个像素值乘卷积核内相对应点的权重,然后求和,再加上偏置后,最后得到输出图片中的一个像素值。

例:上面是 5x5x1 的灰度图片,1 表示单通道,5x5 表示分辨率,共有 5 行 5列个灰度值。若用一个 3x3x1 的卷积核对此 5x5x1 的灰度图片进行卷积,偏置项b=1,则求卷积的计算是:(-1)x1+0x0+1x2+(-1)x5+0x4+1x2+(-1)x3+0x4+1x5+1=1(注意不要忘记加偏置 1)。
输出图片边长=(输入图片边长–卷积核长+1)/步长,此图为:(5 – 3 + 1)/ 1 = 3,输出图片是 3x3 的分辨率,用了 1 个卷积核,输出深度是 1,最后输出的是3x3x1 的图片。

全零填充

有时会在输入图片周围进行全零填充,这样可以保证输出图片的尺寸和输入图片一致。

例:在前面 5x5x1 的图片周围进行全零填充,可使输出图片仍保持 5x5x1 的维度。这个全零填充的过程叫做 padding。

1
2
输出数据体的尺寸=(W−F+2P)/S+1
W:输入数据体尺寸,F:卷积层中神经元感知域,S:步长,P:零填充的数量。

例:输入是 7×7,滤波器是 3×3,步长为 1,填充为 0,那么就能得到一个 5×5的输出。如果步长为 2,输出就是 3×3。 如果输入量是 32x32x3,核是 5x5x3,不用全零填充,输出是(32-5+1)/1=28,如果要让输出量保持在 32x32x3,可以对该层加一个大小为 2 的零填充。可以根据需求计算出需要填充几层零。32=(32-5+2P)/1 +1,计算出 P=2,即需填充 2层零。

使用padding和不使用padding的输出维度

上一行公式是使用 padding 的输出图片边长,下一行公式是不使用 padding的输出图片边长。公式如果不能整除,需要向上取整数。如果用全零填充,也就是padding=SAME。如果不用全零填充,也就是 padding=VALID。

Tenorflow给出的计算卷积核的函数

函數中要给出四个信息:对输入图片的描述、对卷积核的描述、对卷积核滑动步长的描述以及是否使用padding。

1)对输入图片的描述:用batch给出一次喂入多少张图片,每张图片分辨率大小,比如5x5,以及这些图片包含几个通道的信息,如果是灰度图则是单通道,参数为1,如果是彩色图像则为红绿蓝三通道,则为3。

2)对卷积核的描述:要给出卷积核的行分辨率和列分辨率、通道数以及用了几个卷积核。比如上图描述,辨识卷积核行列分辨率为3行和3列,而且是1通道的,一共有16个这样的卷积核,卷积核的通道数是由输入图片的通道数决定的,卷积核的通道数等于输入图片的通道数,所以卷积核的通道数也是1,一共有16个这样的卷积核,说明卷积操作后输出图片的深度是16,也就是输出为16个通道。

3)对卷积核滑动步长的描述:上图第二个参数表示横向滑动步长,第三个参数表示纵向滑动步长,第一个和最后一个都要求是1.

4)是否使用padding

多数情况下,输入图片是RGB三个颜色组成的彩色图,输入图片包含了红绿蓝三层数据,卷积核的深度应该等于输入图片的通道数,所以使用3x3x3的卷积核,最后一个3表示匹配输入图像的3个通道,这样的卷积核有3层,每层会随机生成9个待优化参数,一共27个待优化参数w和一个偏置b。

对于彩色图,按层分解开,可以直观表示为上面这张图,红绿蓝三个颜色分量。卷积核为了匹配三个颜色,把三层的卷积核套在三层的彩色图片上,重合的27个像素进行对应点的乘加运算,最后的结果再加上偏置项b,求得输出图片中的一个值。
这个5x5x3的输入图片加了全零填充,使用3x3x卷积核,所有27个点与对应的待优化参数相乘,乘机求和再加上偏置b得到输出图片中的一个值6。
针对上面这幅彩色图,用conv2d函数实现可以表示为:
一次输入batch张图片,输入图片的分辨率是5x5,3通道,卷积核是3x3x3,一共有16个卷积核,这样输出的深度就是16,核滑动步长是1,纵向步长也是1,padding选择same,保证输出5x5分辨率。由于一共用了16个卷积核,所以输出图片是5x5x16。

池化Pooling

池化层也称子采样层或下采样层。通过对输入数据的各个维度进行空间采样,可以进一步降低数据规模,并且对输入数据具有局部线性转换不变性,增强网络的泛化处理能力。

1)对输入的描述:给出一次输入batch张图片、行列分辨率、输入通道的个数

2)对池化核的描述:只描述行列分辨率,第一个和最后一个参数固定是1。

3)对池化核滑动步长的描述:只描述横向滑动步长和纵向滑动步长,第一个和最后一个参数固定是1。

4)是否使用padding:SAME零填充或VALID不使用0填充。
池化与卷积很像,只是算法不同:

  • 卷积是将对应像素上的点相乘,然后相加
  • 池化只关心滤波器的尺寸,不考虑内部的值。算法是,滤波器映射区域内的像素点去平均值或最大值。
    均值池化:得到的特征对背景信息更敏感。
    最大池化:对纹理特征信息更敏感。

舍弃Dropout

在神经网络驯良过程中,为了减少过多参数常使用dropout的方法,将一部分神经元按照一定的概率从神经网络中舍弃。这种舍弃是临时性的,仅在训练时舍弃一些神经元;在使用神经网络时,会 把所有的神经元恢复到神经网络中。比如上面这张图,在训练时一些神经元不参与神经网络计算了,Drop可以有效减少过拟合。
用tf.nn.dropout函数,第一个参数链接上一层的输出,第二个参数给出神经元舍弃的概率。在实际应用中,常常在前向传播构建神经网络时使用dropout来减小过拟合加快模型的训练速度。
dropout一般会放到全连接网络中。如果在训练参数的过程中,输出=tf.nn.dropout(上层输出,暂时舍弃神经元的概率),这样就有指定概率的神经元被随机置0,置0的神经元不参加当前轮的参数优化。

卷积NN

借助卷积核提取特征后,送入全连接网络。卷积神经网络可以认为由两部分组成,一部分是对输入图片进行特征提取,另一部分就是全连接网络,只不过喂入全连接网络的不再是原始图片,而是经过若干次卷积、激活和池化后的特征信息。

Letnet-5

Letnet-5模型结构

第一层,卷积层

这一层的输入就是原始的图像像素,LetNet-5模型接受的输入层大小为32x32x1。第一个卷积层过滤器尺寸为5x5,深度为6,不使用全0填充,步长为1。因为没有使用全0填充,所以这一层的输出尺寸为32-5+1=28,深度为6.这个卷积层共有5x5x1x6+6=156个参数,其中6个为偏置项参数。因为下一层的节点矩阵有28x28x6=4704个节点,每个节点和5x5=25个当前层节点相连,所以本层卷积层共有4704x(25+1)=122304个连接。

第二层,池化层

这一层的输入为上一层的输出,是一个28x28x6的节点矩阵。本层采用过滤器大小为2x2,长和宽的步长均为2,所以本层的输出矩阵大小为14x14x6。

第三层,卷积层

本层输入矩阵大小为14x14x6,使用的过滤器大小为5x5,深度为16.本层不使用全0填充,步长为1.本层的输出矩阵大小为10x10x16.按照标准的卷积层,本层应该有5x5x6x16+16=2416个参数,10x10x15x(25+1)=41600个连接。

第四层,池化层

本层的输入矩阵大小为10x10x16,采用的过滤器大小为2x2,步长为2.本层的输出矩阵大小为5x5x16。

第五层,全连接层

本层的输入矩阵5x5x16,在LetNet-5模型论文中将这一层称为卷积层,但是因为过滤器的大小就是5x5,所以和全连接层没有区别。本层的输出节点为120,总共有5x5x16x120+120=48120个参数。

第六层,全连接层

本层的输入节点个数为120个,输出节点个数为84个,总共参数为120x84+84=10164个。

第七层,全连接层

本层的输入节点个数为84个,输出节点为10个,总共参数为120x84+84=10164个。

Letnet神经网络的输入是32321,经过551的卷积核,卷积核的个数为6个,采用非全零填充方式,步长为1,根据非全零填充计算公司:输出尺寸=(输入尺寸-卷积核尺寸+1)/步长=(32-5+1)/1=28.故经过卷积后输出为28286.
经过池化第一层池化层,池化大小为22,全零填充,步长为2,由全零填充计算公式:输出尺寸=输入尺寸/步长=28/2=14.池化层不改变深度,深度仍为6.用同样的计算方法,得到第二层池化后的输出为55*16.将第二池化层后的输出拉直送入全连接层。

对letnet神经网络进行微调,使其适应Mnist数据集:
由于Mnist数据集中图片大小为28281的灰度图片,而Letnet神经网络的输入为32321,故要对Letnet神经网络进行微调。

  1. 输入为28281的图片大小,为单通道输入;
  2. 进行卷积,卷积核大小为551,个数为32,步长为1,全零填充模式;
  3. 将卷积结果通过非线性激活函数;
  4. 进行池化,池化大小为2*2,步长为2,全零填充模式;
  5. 进行卷积,卷积核大小为5532,个数为64,步长为1,全零填充模式;
  6. 将卷积结果通过非线性激活函数;
  7. 进行池化,池化大小为2*2,步长为2,全零填充模式;
  8. 全连接层,进行10分类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    import tensorflow as tf
    # 定义神经网络结构的相关参数
    INPUT_NODE = 784
    OUTPUT_NODE = 10
    LAYER1_NODE = 500

    IMAGE_SIZE = 28
    NUM_CHANNELS = 1
    NUM_LABELS = 10
    # 第一层的尺寸和深度
    CONV1_DEEP = 32
    CONV1_SIZE = 5
    # 第二层卷基层的尺度和深度
    CONV2_DEEP = 64
    CONV2_SIZE = 5
    # 全连接层的节点个数
    FC_SIZE = 512
    OUTPUT_NODE = 10
    def get_weight(shape, regularizer):
    w = tf.Variable(tf.truncated_normal(shape, stddev=0.1))
    if regularizer != None:
    tf.add_to_collection(
    name='losses',
    value=tf.contrib.layers.l2_regularizer(regularizer)(w))
    return w
    def get_bias(shape):
    b = tf.Variable(tf.zeros(shape))
    return b
    def conv2d(x, w):
    return tf.nn.conv2d(
    input=x, filter=w, strides=[1, 1, 1, 1], padding='SNAME')
    def max_pool_2x2(x):
    return tf.nn.max_pool(
    inoput=x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SNAME')
    def forward(x, train, regularizer):
    conv1_w = get_weight([CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_DEEP],
    regularizer)
    conv1_b = get_bias([CONV1_DEEP])
    conv1 = conv2d(x, conv1_w)
    relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_b))
    pool1 = max_pool_2x2(relu1)

    conv2_w = get_weight([CONV2_SIZE, CONV2_SIZE, CONV1_DEEP, CONV1_DEEP],
    regularizer)
    conv2_b = get_bias([CONV2_DEEP])
    conv2 = conv2d(pool1, conv2_w)
    relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_b))
    pool2 = max_pool_2x2(relu2)

    pool_shape = pool2.get_shape().as_list()
    nodes = pool_shape[1] * pool_shape[2] * pool_shape[3]
    reshaped = tf.reshape(pool2, [pool_shape[0], nodes])

    fc1_w = get_weight([nodes, FC_SIZE], regularizer)
    fc1_b = get_bias([FC_SIZE])
    fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_w) + fc1_b)
    if train:
    fc1 = tf.nn.dropout(fc1, 0.5)
    fc2_w = get_weight([FC_SIZE, OUTPUT_NODE], regularizer)
    fc2_b = get_bias([OUTPUT_NODE])
    y = tf.matmul(fc1, fc2_w) + fc2_b
    return y
  9. 定义前向传播过程中常用到的参数。
    图片大小即每张图片分辨率为28*28,故IMAGE_SIZE取值为28;Mnist数据集为灰度图,故输入图片的通道数NUM_CHANNELS取值为1;第一层卷积核大小为5,卷积核个数为32,故CONV1_SIZE取值为5,CONV1_DEEP取值为32;第二层卷积核大小为5,卷积核个数为64,故CONV2_SIZE取值为5,CONV2_DEEP为64.全连接层第一层参数为512个神经元,全连接层第二层为10个神经元,故FC_SIZE取值为512,OUTPUT_NODE取值为10,实现10分类输出。

  10. 吧把前向传播过程中,常用到的方法定义为函数,方便调用。
    定义常用的4个函数:权重w生成函数、偏置b生成函数、卷积层计算函数、最大池化层计算函数。
  11. 定义前向传播过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    # 实现第一层卷积 
    # 根据先抢定义的参数大小,初始化第一层卷积核和偏置项
    conv1_w = get_weight([CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_DEEP],
    regularizer)
    conv1_b = get_bias([CONV1_DEEP])
    # 实现卷积运算,输入参数为x和第一层卷积核参数
    conv1 = conv2d(x, conv1_w)
    # 第一层卷积的输出作为非线性激活函数的输入值,首先通过tf.nn.bias_add对卷积后的输出添加偏置项,
    并过tf.nn.relu完成非线性激活
    relu1=tf.nn.telu(tf.nn.bias_add(conv1,conv1_b))
    # 根据先前定义的池化函数,将第一层激活后的输出值进行最大池化
    pool=max_pool_2x2(relu1)

    # 实现第二层卷积
    # 初始化第二层卷积层的变量和偏置项,该层每个卷积核的通道数要与上一层卷积核的个数一致
    conv2_w=get_weight([CONV2_SIZE,CONV2_SIZE,CONV1_DEEP,CONV2_DEEP],regularizer)
    conv2_b=get_bias([CONV2_DEEP])
    # 实现卷积运算,输入参数为上一层的输出pool1和第二层卷积核参数
    conv_2=conv2d(pool1,conv2_w)
    # 实现第二层的非线性激活函数
    relu2=tf.nn.relu(tf.nn.boas_add(conv2,conv2_b))
    # 根据提前定义的池化函数,将第二层激活后的输出值进行最大池化
    pool2=max_pool_2x2(relu2)

    # 将第二层的池化层输出pool2矩阵转化为全连接层的输入格式即向量形式
    # 根据get_shape函数得到pool2输出矩阵的维度,并存入list.其中,pool_shape[0]为一个batch值
    pool_shape=pool2.get_shape().as_list()
    # 从list中依次取出矩阵的长宽及深度,并求三者的乘积,得到矩阵被拉长后的长度
    nodes=pool_shape[1] * pool_shape[2] * pool_shape[3]
    # 将pool2转换为一个batch的向量再传入后续的全连接
    reshaped=tf.reshape(pool2,[pool_shape[0],nodes)
    # get_shape函数用于获取一个张量的维度,并且输出张量每个维度上面的值
    # A=tf.random_normal(shape=[3,4]) # A.get_shape() (3,4)

    # 实现第三层全连接层
    # 初始化全连接层权重,并加入正则化
    fc1_w=get_weight([nodes,FC_SIZE],regularizer)
    # 初始化全连接层的偏置项
    fc1_b=get_bias([FC_SIZE])
    # 如果是训练阶段,则对该层输出使用dropout,也就是随机将该层输出中的一半神经元置为无效,
    #是为了避免过拟合而设置的,一半只在全连接层中使用
    fc1=tf.nn.relu(tf.matmul(reshaped,fc1_w)+fc1_b)

    # 实现第四层全连接层的前向传播过程:
    # 初始化全连接层对应的变量
    fc2_w=get_weight([FC_SIZE,OUTPUT_NODE],regularizer)
    fc2_b=get_bias([OUTPUT_NODE])
    # 将转换后的reshaped向量与权重fc2_w做矩阵乘法运算,然后再加上偏置
    y=tf.matmul(fc1,fc2_w)+fc2_b
    # 返回输出值,完成整个前向传播过程
    return y
  12. 反向传播过程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    # 1.定义训练过程中的超参数
    '''
    规定一个batch的数量为100,故BATCH_SIZE取值为100;设定初始学习率为0.005,
    学习衰减率为0.99;最大迭代次数为50000,故STEPS取值为50000;滑动平均衰减率设置为
    0.99,并规定模型保存的路径以及保存的模型名称
    '''
    # 2.完成反向传播过程
    # 1)x,y_
    x=tf.placeholder(tf.float32,shape=[
    BATCH_SIZE,
    forward.IMAGE_SIZE,
    forward.IMAGE_SIZE,
    forward.NUM_CHANNELS
    ]
    )
    y_=tf.placeholder(tf.float32,[None,forward.OUTPUT_NODE])
    # 调用前向传播过程
    # 调用前向传播网络得到维度为10的tensor
    y=forward.forward(x,True,REGULARIZER)
    # 2)求含有正则化的损失值
    # 声明一个全局计数器,并输出化为0
    global_step=tf.Variable(0,trainable=False)
    # 对网络最后一层的输出做softmax,求取输出属于某一类的概率,结果为num_classes大小的向量,
    # 再将此向量和实际标签值做交叉熵,返回一个向量值
    ce=tf.nn.sparse_softmax_cross_entropy_with_logits(logits=y,labels=tf.argmax(y_,1))
    # 通过reduce_mean函数对得到的向量求平均值得到loss
    cem=tf.reduce_mean(ce)
    # 添加正则化中的losses值到loss中
    loss=cem+tf.add_n(tf.get_collection('losses'))
    # 4)实现指数衰减学习率
    learning_rate=tf.train.exponential_decay(
    LEARNING_RATE_BASE,
    global_step,
    mnist.train.num_examples/BATCH_SIZE,
    LEARNING_RATE_DECAY,
    staircase=True
    )
    # 次函数的参数learning_rate为传入的学习率,构造一个实现梯度下降算法的优化器,再通过使用minimize更新存储
    要训练的变量的列表来减小loss
    train_step=tf.train.GradientDescentOptimizer(learning_rate).minimize(loss,global_step=global_step)
    # 5)实现滑动平均模型
    '''
    ExponentialMovingAverage函数采用滑动平均的方法更新参数,次函数的参数MONING_AVERAGE_DECAY表示衰减率,
    用于控制模型的更新速度;次函数维护一个影子变量,影子变量初始值作为变量初始值.
    影子变量值的更新方式如下:
    shadow_variable=decay * shadow_variable + (1-decay) * variable
    shadow_variable是影子变量,variable表示待更新的变量,decay为衰减率.
    decay一般设为接近于1的数,decay越大模型越稳定
    '''
    ema=tf.train.ExponentialMovingAverage(MONING_AVERAGE_DECAY,global_step)
    ema_op=ema.apply(tf.trainable_variables())
    # 6)将train_step和ema_op两个训练操作绑定到train_op
    with tf.control_dependencies([train_step,ema_op]):
    train_op=tf.no_op(name='train')
    # 7)实例化一个保存和恢复变量的saver,并创建一个会话
    saver=tf.train.Saver()
    with tf.Session() as sess:
    init_op=tf.global_variables_initializer()
    sess.run(init_op)
    # 创建一个会话,并通过python中的上下文管理器来管理这个会话,初始化计算图中的变量,并用sess.run实现初始化
    ckpt=tf.train.get_checkpoint_state(MODEL_SAVE_PATH)
    if ckpt and ckpt.model_checkpoint_path:
    saver.restore(sess,ckpt.model_checkpoint_path)
    # 通过checkpoing文件定位到最新保存的模型,若文件存在,则加载最新的模型
    for i in range(STEPS):
    # 读取一个batch数据,将输入数据xs转成与网络输入相同形状的矩阵
    xs,ys=mnist.train.next_batch(BATCH_SIZE)
    reshaped_xs=np.reshape(xs,(
    BATCH_SIZE,
    forward.IMAGE_SIZE,
    forward.IMAGE_SIZE,
    forward.NUM_CHANNELS
    ))
    # 喂入训练图像和标签,开始训练
    _,loss_value,step=sess.run([train_op,loss,global_step],feed_dict={x:reshaped_xs,y_:ys})
    # 每迭代100次打印loss信息,并保存最新模型
    if i % 100==0:
    print("After %d training step,loss on training batch is %g." % (step,loss_value))
  13. 测试过程
    1)在测试程序中使用的是训练好的网络,故不使用dropout,而是让所有神经元都参与运算,从而输出识别准确率.
    2)correct_predicition=tf.equal(tf.argmax(y,1),tf.argmax(y_,1)).
    tf.equal(x,y)次函数用于判断函数的两个参数x与y是否相等,一般x表示预测值,y表示实际值。
    3)accuracy=tf.reduce_mean(tf.cast(correct_prediction,tf.float32))求平均得到预测准确率。

    Inception-v3模型

    Inception-v3模型是将不同的卷积层通过并联的方式结合在一起。

下面介绍使用Tensorflow-Slim实现一个卷积层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 用Tensorflow原始API
with tf.variable_scope(scope_name):
weights=tf.get_variable("weights",...)
biases=tf.get_variable("bias",...)
conv=tf.nn.conv2d(...)
relu=tf.nn.relu(tf.nn.bias_add(conv,biases))

'''
使用Tensorflow-Slim可以在一行中实现一个卷积层的前向传播算法.有3个必选参数:
1. 输入节点矩阵
2. 当前卷积层过滤器的深度
3. 过滤器的尺寸
可选参数:过滤器的移动步长、是否使用全0填充、激活函数的选择以及变量的命名空间
'''
net = slim.conv2d(input,32,[3,3])

卷积神经网络的迁移学习

迁移学习,就是将一个问题上训练好的模型通过简单的调整使其使用于一个新的问题。
根据论文DeCAF:A Deep Convolutional Activiation Feature for Generic Visual Recoginition中的结论,可以保留训练好的Inception-v3模型中所有卷积层的参数,只是替换最后这一层全连接层。在最后这一层全连接层之前的网络层称之为瓶颈层。
将新的图像通过训练好的卷积神经网络直到瓶颈层的过程可以看成是对图像进行特征提取的过程。在训练好的Inception-v3模型中,因为将瓶颈层的输出再通过一个单层的全连接层神经网络可以很好地区分1000种类别的图像,所以有理由认为瓶颈层输出的节点向量可以被作为任何图像的一个更加精简且表达能力更强的特征向量。于是,在新数据集上,可以直接利用这个训练好的神经网络对图像进行特征提取,然后再将提取得到的特征向量作为输入来训练一个新的单层全连接神经网络处理新的分类问题。

反卷积神经网络

反卷积是指测量输出和已知输入重构未知输入的过程。在神经网络中,反卷积过程并不具备学习能力,仅用于可视化一个已经训练好的卷积模型,没有学习训练的过程。
下图展示了VGG 16反卷积神经网络的结构,展示了一个卷积网络与反卷积网络结合的过程。其反卷积就是将中间的数据,按照前面的卷积、池化等变化过程,完全相反的做一遍,从而得到类似原始输入的数据。

反卷积原理

  1. 首先将卷积核反转
  2. 再将卷积结果作为输入,做补0的扩充操作,即往每一个元素后面补0。这一步是根据步长来的,对每一个元素沿着步长的方向补(步长-1)个0,如步长为1就不用补0.
  3. 在扩充后的输入基础上再对整体补0。以原始输入的shape作为输出,按照卷积padding规则计算padding的补0位置及个数,得到的补0位置要上下和左右各自颠倒一下。
  4. 将补0后的卷积结果作为真正的输入,反转后的卷积核为filter,步长为1的卷积操作。
    如下图所示,以一个[1,4,4,1]的矩阵为例,进行filter为2x2,步长为2x2的卷积与反卷积操作。在反卷积过程中,首先将2x2矩阵通过步长补0的方式变成4x4,再通过padding反向补0,然后与反转后的filter使用步长为1x1的卷积操作,最终得出了结果。

    Tensorflow中使用tf.nn.conv2d_transpoze来实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def conv2d_transpoze(
    value,
    filter,
    output_shape,
    strides,
    padding='SAME',
    data_format='NHWC',
    name=None
    ):
  • value:代表通过卷积操作后的张量,一般用NHWC类型。
  • filter:代表卷积核
  • output_shape:代表输出的张量形状也是个四维张量.
  • strides:代表步长
  • padding:代表原数据生成value时使用的补0,是用来检查输入形状和输出形状是否合规的,
  • return:反卷积后的结果,按照output_shape指定的形状。

    NHWC类型是神经网络中在处理图像方面常用的类型,N-个数、H-高、W-宽、C-通道数。
    output_shape必须是能够生成value参数的原数据的形状,如果输出形状不对就会报错。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import tensorflow as tf
    # 模拟数据
    img = tf.Variable(tf.constant(1.0, shape=[1, 4, 4, 1]))
    filter = tf.Variable(tf.constant([1.0, 0, -1, -2], shape=[2, 2, 1, 1]))
    # 分别进行VALID和SAME操作
    conv = tf.nn.conv2d(img, filter, strides=[1, 2, 2, 1], padding='VALID')
    cons = tf.nn.conv2d(img, filter, strides=[1, 2, 2, 1], padding='SAME')
    print(conv.shape)
    print(cons.shape)
    # 再进行反卷积
    contv = tf.nn.conv2d_transpose(
    conv, filter, [1, 4, 4, 1], strides=[1, 2, 2, 1], padding='VALID')
    conts = tf.nn.conv2d_transpose(
    cons, filter, [1, 4, 4, 1], strides=[1, 2, 2, 1], padding='SAME')
    with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    print("conv:", sess.run([conv]))
    print("cons:", sess.run([cons]))
    print("contv:", sess.run([contv]))
    print("conts:", sess.run([conts]))

反池化原理

池化过程是只保留主要信息,舍去部分信息,如果想从池化后的这些主要信息恢复出全部信息,则存在着信息缺失,这时只能通过补位来实现最大程度的信息完整。

  • 平均池化的反池化比较简单。首先还原成原来的大小,然后将池化结果中的每个值都填入其对应原始数据区域中相应位置即可。
  • 最大池化反池化。要求在池化过程中记录最大激活值的坐标位置,然后在反池化时,只把池化过程中最大激活值所在位置坐标的值激活,其他位置为0.