Tensorflow--卷积神经网络

Tensorflow–卷积神经网络

卷积神经网络与全连接神经网络类似,可以理解为一种变换,这种变换一般由卷积,池化,加法,激活函数等一系列操作组合而成

一.浅层卷积神经网络

输入的三维张量首先与3个2行2列2深度的卷积核进行步长为1的same卷积,输出结果的尺寸是3行3列3深度

对应代码如下:

import tensorflow as tf

# 输入张量
input_tensor=tf.constant(
    [
        # 第1个高为3,宽为3,深度为2的三维张量
        [
            [[2,5],[3,3],[8,2]],
            [[6,1],[1,2],[5,4]],
            [[7,9],[2,8],[1,3]]
        ]
    ]
    ,tf.float32
)

# 3个高为2,宽为2,深度为2的卷积核
kernel=tf.constant(
    [
        [[[-1,1,0],[1,-1,-1],[0,0,-1],[0,0,0]]],
        [[[0,0,0],[0,0,-1],[1,-1,1],[-1,1,0]]]
    ]
    ,tf.float32
)

# 卷积
conv2d=tf.nn.conv2d(input_tensor,kernel,(1,1,1,1),\'SAME\')

将得到的卷积结果在每一个深度上加一个常数(即加偏置)

对应的代码如下:

# 偏置
bias=tf.constant([1,2,3],tf.float32)
conv2d_add_bias=tf.add(conv2d,bias)

用ReLU激活函数处理得到的结果

对应代码如下:

# 激活函数
active=tf.nn.relu(conv2d_add_bias)

将激活后的结果,经过2x2的步长为1的valid最大值池化操作

对应代码如下:

# pool操作
active_maxPool=tf.nn.max_pool(active,(1,2,2,1),(1,1,1,1),\'VALID\')

然后将结果进行拉伸操作

对应的代码如下:

# 拉伸
shape=active_maxPool.get_shape()
num=shape[1].value*shape[2].value+shape[3].value
flatten=tf.reshape(active_maxPool,[-1,num])
# 打印结果
session=tf.Session()
print(session.run(flatten))

完整代码如下:

import tensorflow as tf

# 输入张量
input_tensor=tf.constant(
    [
        # 第1个高为3,宽为3,深度为2的三维张量
        [
            [[2,5],[3,3],[8,2]],
            [[6,1],[1,2],[5,4]],
            [[7,9],[2,8],[1,3]]
        ]
    ]
    ,tf.float32
)

# 3个高为2,宽为2,深度为2的卷积核
kernel=tf.constant(
    [
        [[[-1,1,0],[1,-1,-1]],[[0,0,-1],[0,0,0]]],
        [[[0,0,0],[0,0,1]],[[1,-1,1],[-1,1,0]]]
    ]
    ,tf.float32
)

# 卷积
conv2d=tf.nn.conv2d(input_tensor,kernel,(1,1,1,1),\'SAME\')

# 偏置
bias=tf.constant([1,2,3],tf.float32)
conv2d_add_bias=tf.add(conv2d,bias)

# 激活函数
active=tf.nn.relu(conv2d_add_bias)

# pool操作
active_maxPool=tf.nn.max_pool(active,(1,2,2,1),(1,1,1,1),\'VALID\')

# 拉伸
shape=active_maxPool.get_shape()
num=shape[1].value*shape[2].value*shape[3].value
flatten=tf.reshape(active_maxPool,[-1,num])
# 打印结果
session=tf.Session()
print(session.run(flatten))
[[ 3. 13. 12.  2.  8.  5.  7. 13. 12.  7.  3.  5.]]

至此,输入的三维张量经过一系列变换后,转换为了长度为12的向量,上述变换称为卷积神经网络

接下来我们介绍如何实现输入3个不同的三维张量,即这3个张量分别经过该网络的结构,具体代码如下:

import tensorflow as tf
import numpy as np

# 输入张量
input_tensor=tf.placeholder(tf.float32,[None,3,3,2])

# 3个高为2,宽为2,深度为2的卷积核
kernel=tf.constant(
    [
        [[[-1,1,0],[1,-1,-1]],[[0,0,-1],[0,0,0]]],
        [[[0,0,0],[0,0,1]],[[1,-1,1],[-1,1,0]]]
    ]
    ,tf.float32
)

# 卷积
conv2d=tf.nn.conv2d(input_tensor,kernel,(1,1,1,1),\'SAME\')

# 偏置
bias=tf.constant([1,2,3],tf.float32)
conv2d_add_bias=tf.add(conv2d,bias)

# 激活函数
active=tf.nn.relu(conv2d_add_bias)

# pool操作
active_maxPool=tf.nn.max_pool(active,(1,2,2,1),(1,1,1,1),\'VALID\')

# 拉伸
shape=active_maxPool.get_shape()
num=shape[1].value*shape[2].value*shape[3].value
flatten=tf.reshape(active_maxPool,[-1,num])
# flatten=tf.contrib.layers.flatten(active_maxPool)

session=tf.Session()
print(session.run(flatten,feed_dict={
    input_tensor:np.array([
        # 第1个3行3列2深度的三维张量
        [
            [[2,5],[3,3],[8,2]],
            [[6,1],[1,2],[5,4]],
            [[7,9],[2,8],[1,3]]
        ],
         # 第2个3行3列2深度的三维张量
        [
            [[1,2],[3,6],[1,2]],
            [[3,1],[1,2],[2,1]],
            [[4,5],[2,7],[1,2]]
        ],
        [
         # 第3个3行3列2深度的三维张量
            [[2,3],[3,2],[1,2]],
            [[4,1],[3,2],[1,2]],
            [[1,0],[4,1],[4,3]]
        ]
    ],np.float32)
}))
[[ 3. 13. 12.  2.  8.  5.  7. 13. 12.  7.  3.  5.]
 [ 5.  9.  8.  5.  3.  7.  6.  9.  8.  6.  3.  7.]
 [ 3.  4.  5.  2.  4.  5.  1.  5.  5.  2.  5.  5.]]

了解了简单的卷积神经网络后,接下类介绍经典的卷积神经网络结构

二.LeNet

LeNet是第一个成熟的卷积神经网络,是专门为处理MNIST数字字符集的分类问题而设计的网络结构,该网络结构输入值的尺寸是32行32列1深度的三维张量(灰色图像),其经过LeNet网络的变换过程如下:

第1步:与6个高为5,宽为5,深度为1的卷积核valid卷积,然后将卷积结果的每一深度加偏置,最后将结果经过激活函数处理,得到的结果的尺寸高为28,宽为28,深度为6

第1步的对应代码如下,其中卷积核和偏置都由满足高斯分布的随机数生成

# 6个高为5,宽为5,深度为1的卷积核
k1=tf.Variable(tf.random_normal([5,5,1,6]),dtype=tf.float32)
c1=tf.nn.conv2d(x,k1,[1,1,1,1],\'VALID\')
#长度为6的偏置
b1=tf.Variable(tf.random_normal([6]),dtype=tf.float32)
# c1与b1求和,并激活函数
c1_b1=tf.add(c1,b1)
r1=tf.nn.relu(c1_b1)

第2步:进行2x2的步长为2的valid最大值池化,池化后结果的尺寸高为14,宽为14,深度为6

第2步对应的代码如下:

# 最大值池化操作,池化掩码的尺寸为高2宽2,步长为2
p1=tf.nn.max_pool(r1,[1,2,2,1],[1,2,2,1],\'VALID\')

第3步:先与16个高为5,宽为5,深度为6的卷积核valid卷积,然后将卷积结果在每一深度上加偏置,最后将加上偏置的结果经过激活处理。得到的结果的尺寸高为10,宽为10,深度为16

第3步对应的代码如下,其中卷积核和偏置都是由满足高斯分布的随机数生成的

# p1与16个高为5,宽为5,深度为6的卷积核的卷积
k2=tf.Variable(tf.random_normal([5,5,6,16]),dtype=tf.float32)
c2=tf.nn.conv2d(p1,k2,[1,1,1,1],\'VALID\')
# 长度为16的偏置
b2=tf.Variable(tf.random_normal([16]),dtype=tf.float32)
# c2与b2求和,并输入激活函数
c2_b2=tf.add(c2,b2)
r2=tf.nn.relu(c2_b2)

第4步:进行2x2的步长为2的valid最大值池化,池化后结果的尺寸是高为5,宽为5,深度为16

第4步对应的代码如下:

# 最大值池化操作,池化掩码的尺寸是高为2,宽为2,步长为2
p2=tf.nn.max_pool(c2,[1,2,2,1],[1,2,2,1],\'VALID\')

第5步:将第4步得到的结果拉伸为1个一维张量,其长度为5x5x16=400,然后将这个向量经过一个全连接神经网络处理,该全连接神经网络有3个隐含层,其中输入层有400个神经元,第1层隐含层有120个神经元,第2层隐含层有84个神经元。因为要处理的数据有10个类别,所以输出层有10个神经元

经过这5步变换,我们将1个高为32,宽为32,深度为1的张量转换成了1个长度为10的向量,其中第5步对应的代码如下:

# 拉伸为一维张量,作为一个全连接神经网络的输入
flatten_p2=tf.reshape(p2,[-1,5*5*16])
# 第1层的权重矩阵和偏置
w1=tf.Variable(tf.random_normal([5*5*16,120]))
bw1=tf.Variable(tf.random_normal([120]))
# 第1层的线性组合与偏置求和,并作为relu激活函数的输入
h1=tf.add(tf.matmul(flatten_p2,w1),bw1)
sigma1=tf.nn.relu(h1)
# 第2层的权重矩阵和偏置
w2=tf.Variable(tf.random_normal([120,84]))
bw2=tf.Variable(tf.random_normal([84]))
# 第2层的线性组合与偏置求和,并作为relu激活函数的输入
h2=tf.add(tf.matmul(sigma1,w2),bw2)
sigma2=tf.nn.relu(h2)
# 第3层线性组合与偏置求和
w3=tf.Variable(tf.random_normal([84,10]))
bw3=tf.Variable(tf.random_normal([10]))
h3=tf.add(tf.matmul(sigma2,w3),bw3)
# 将h3作为sigmod激活函数的输入,作为最后输出层的输出
out=tf.nn.sigmoid(h3)

session=tf.Session()

# 初始化变量
session.run(tf.global_variables_initializer())
session.run(tf.local_variables_initializer())

print(session.run(out,{x:np.random.normal(0,1,[]2,32,32,1)}))

完整代码如下:

import tensorflow as tf
import numpy as np

# 6个高为5,宽为5,深度为1的卷积核
k1=tf.Variable(tf.random_normal([5,5,1,6]),dtype=tf.float32)
x=np.random.normal(0,1,[2,32,32,1])
c1=tf.nn.conv2d(x,k1,[1,1,1,1],\'VALID\')
#长度为6的偏置
b1=tf.Variable(tf.random_normal([6]),dtype=tf.float32)
# c1与b1求和,并激活函数
c1_b1=tf.add(c1,b1)
r1=tf.nn.relu(c1_b1)

# 最大值池化操作,池化掩码的尺寸为高2宽2,步长为2
p1=tf.nn.max_pool(r1,[1,2,2,1],[1,2,2,1],\'VALID\')

# p1与16个高为5,宽为5,深度为6的卷积核的卷积
k2=tf.Variable(tf.random_normal([5,5,6,16]),dtype=tf.float32)
c2=tf.nn.conv2d(p1,k2,[1,1,1,1],\'VALID\')
# 长度为16的偏置
b2=tf.Variable(tf.random_normal([16]),dtype=tf.float32)
# c2与b2求和,并输入激活函数
c2_b2=tf.add(c2,b2)
r2=tf.nn.relu(c2_b2)

# 最大值池化操作,池化掩码的尺寸是高为2,宽为2,步长为2
p2=tf.nn.max_pool(c2,[1,2,2,1],[1,2,2,1],\'VALID\')

# 拉伸为一维张量,作为一个全连接神经网络的输入
flatten_p2=tf.reshape(p2,[-1,5*5*16])
# 第1层的权重矩阵和偏置
w1=tf.Variable(tf.random_normal([5*5*16,120]))
bw1=tf.Variable(tf.random_normal([120]))
# 第1层的线性组合与偏置求和,并作为relu激活函数的输入
h1=tf.add(tf.matmul(flatten_p2,w1),bw1)
sigma1=tf.nn.relu(h1)
# 第2层的权重矩阵和偏置
w2=tf.Variable(tf.random_normal([120,84]))
bw2=tf.Variable(tf.random_normal([84]))
# 第2层的线性组合与偏置求和,并作为relu激活函数的输入
h2=tf.add(tf.matmul(sigma1,w2),bw2)
sigma2=tf.nn.relu(h2)
# 第3层线性组合与偏置求和
w3=tf.Variable(tf.random_normal([84,10]))
bw3=tf.Variable(tf.random_normal([10]))
h3=tf.add(tf.matmul(sigma2,w3),bw3)
# 将h3作为sigmod激活函数的输入,作为最后输出层的输出
out=tf.nn.sigmoid(h3)

session=tf.Session()

# 初始化变量
session.run(tf.global_variables_initializer())
session.run(tf.local_variables_initializer())

print(session.run(out))

三.AlexNet

AlexNet卷积神经网络主要是针对高为224,宽为224的彩色图像的分类问题设计的卷积神经网络结构。AlexNet比LeNet具备更深的网络结构卷积核的深度页更深,全连接的神经元个数也更多

1.AlexNet网络结构详解

理解成针对224行224列3深度的三维张量的变换

AlexNet卷积神经网络处理的是1000个分类的任务,所以最后全连接神经网络的输出层的神经元个数是1000,具体过程如下:

import tensorflow as tf
import numpy as np

# 输入
x=tf.placeholder(tf.float32,[None,224,224,3])
keep_prob=tf.placeholder(tf.float32)

# 第1步:与96个11x11x3的卷积核卷积。第2步:加上偏置。第3步:使用激活函数
w1=tf.Variable(tf.random_normal([11,11,3,96]),dtype=tf.float32,name=\'w1\')
l1=tf.nn.conv2d(x,w1,[1,4,4,1],\'SAME\')
b1=tf.Variable(tf.random_normal([96]),dtype=tf.float32,name=\'b1\')
l1=tf.nn.bias_add(l1,b1)
l1=tf.nn.relu(l1)

# 2x2最大值池化操作,移动步长为2
pool_l1=tf.nn.max_pool(l1,[1,2,2,1],[1,2,2,1],\'SAME\')

# 第1步:与256个5x5x96的卷积核卷积。第2步:加上偏置。第3步:使用激活函数
w2=tf.Variable(tf.random_normal([5,5,96,256]),dtype=tf.float32,name=\'w2\')
l2=tf.nn.conv2d(pool_l1,w2,[1,1,1,1],\'SAME\')
b2=tf.Variable(tf.random_normal([256]),dtype=tf.float32,name=\'b2\')
l2=tf.nn.bias_add(l2,b2)
l2=tf.nn.relu(l2)

# 2x2最大值池化操作,移动步长为2
pool_l2=tf.nn.max_pool(l2,[1,2,2,1],[1,2,2,1],\'SAME\')

# 第1步:与384个3x3x256的卷积核卷积。第2步:加上偏置。第3步:使用激活函数
w3=tf.Variable(tf.random_normal([3,3,256,384]),dtype=tf.float32,name=\'w3\')
l3=tf.nn.conv2d(pool_l2,w3,[1,1,1,1],\'SAME\')
b3=tf.Variable(tf.random_normal([384]),dtype=tf.float32,name=\'b3\')
l3=tf.nn.bias_add(l3,b3)
l3=tf.nn.relu(l3)

# 第1步:与384个3x3x384的卷积核卷积。第2步:加上偏置。第3步:使用激活函数
w4=tf.Variable(tf.random_normal([3,3,384,384]),dtype=tf.float32,name=\'w4\')
l4=tf.nn.conv2d(l3,w4,[1,1,1,1],\'SAME\')
b4=tf.Variable(tf.random_normal([384]),dtype=tf.float32,name=\'b4\')
l4=tf.nn.bias_add(l4,b4)
l4=tf.nn.relu(l4)

# 第1步:与256个3x3x384的卷积核卷积。第2步:加上偏置。第3步:使用激活函数
w5=tf.Variable(tf.random_normal([3,3,384,256]),dtype=tf.float32,name=\'w5\')
l5=tf.nn.conv2d(l4,w5,[1,1,1,1],\'SAME\')
b5=tf.Variable(tf.random_normal([256]),dtype=tf.float32,name=\'b5\')
l5=tf.nn.bias_add(l5,b5)
l5=tf.nn.relu(l5)

# 2x2最大值池化操作,移动步长为2
pool_l5=tf.nn.max_pool(l5,[1,2,2,1],[1,2,2,1],\'SAME\')

# 拉伸,作为全连接神经网络的输入层
pool_l5_shape=pool_l5.get_shape()
num=pool_l5_shape[1].value*pool_l5_shape[2].value*pool_l5_shape[3].value
flatten=tf.reshape(pool_l5,[-1,num])

# 第1个隐含层
fcW1=tf.Variable(tf.random_normal([num,4096]),dtype=tf.float32,name=\'fcW1\')
fc_l1=tf.matmul(flatten,fcW1)
fcb1=tf.Variable(tf.random_normal([4096]),dtype=tf.float32,name=\'fcb1\')
fc_l1=tf.nn.bias_add(fc_l1,fcb1)
fc_l1=tf.nn.relu(fc_l1)
fc_l1=tf.nn.dropout(fc_l1,keep_prob)

# 第2个隐含层
fcW2=tf.Variable(tf.random_normal([4096,4096]),dtype=tf.float32,name=\'fcW2\')
fc_l2=tf.matmul(fc_l1,fcW2)
fcb2=tf.Variable(tf.random_normal([4096]),dtype=tf.float32,name=\'fcb2\')
fc_l2=tf.nn.bias_add(fc_l2,fcb2)
fc_l2=tf.nn.relu(fc_l2)
fc_l2=tf.nn.dropout(fc_l2,keep_prob)

# 输出层
fcW3=tf.Variable(tf.random_normal([4096,1000]),dtype=tf.float32,name=\'fcW3\')
out=tf.matmul(fc_l2,fcW3)
fcb3=tf.Variable(tf.random_normal([1000]),dtype=tf.float32,name=\'fcb3\')
out=tf.nn.bias_add(out,fcb3)
out=tf.nn.relu(out)

session=tf.Session()
session.run(tf.global_variables_initializer())
result=session.run(out,feed_dict={x:np.ones([2,224,224,3],np.float32),keep_prob:0.5})

print(np.shape(result))
(2, 1000)

AlexNet一共包括5个卷积层和3个全连接层,所以介绍AlexNet时,一般会说该网络有8层

AlexNet在算法上有两个非常重要的改进。第1个改进是提出ReLU激活函数。第2个改进是提出dropout,该操作可以有效地防止网络的过拟合,所谓的过拟合就是网络结构在训练集上的准确率很高,但在测试集上的准确率较低

2.dropout极其梯度下降

Tensorflow中的函数

dropout(x,keep_prob,noise_shape=None,seed=None,name=None)

其中参数x代表输入张量,参数keep_prob属于区间(0,1]中的一个值,输入张量x中每一个值以概率keep_prob变为原来的1/keep_prob倍,以概率1-keep_prob变为0

我们以2行4列的二维张量为例,假设该张量作为函数dropout的参数x的值,令参数keep_prob=0.5

import tensorflow as tf

# 输入的二维张量
t=tf.constant(
        [
        [1,3,2,6],
        [7,5,4,9]
        ],tf.float32
        )

# dropout处理
r=tf.nn.dropout(t,0.5)

session=tf.Session()

print(session.run(r))
[[ 0.  0.  4. 12.]
 [14. 10.  0. 18.]]

每次运行的结果可能有所不同。显然,张量中的值要么变为原来的1/0.5倍,要么变为0

在以上示例的基础上,我们来介绍参数noise_shape的作用,因为张量的尺寸为2行4列,如果令参数noise_shape=[2,1],则代表把每一行看成一个整体,同一行的值要么全变为原来的1/keep_prob倍,要么全变为0

import tensorflow as tf

# 输入的二维张量
t=tf.constant(
        [
        [1,3,2,6],
        [7,5,4,9]
        ],tf.float32
        )

# dropout处理
r=tf.nn.dropout(t,0.5,noise_shape=[2,1])

session=tf.Session()

print(session.run(r))
[[ 2.  6.  4. 12.]
 [14. 10.  8. 18.]]

同理,如果参数noise_shape=[1,4],代表把每一列看成一个整体,同一列的值要么全变为原来的1/keep_prob倍,要么全变为0

import tensorflow as tf

# 输入的二维张量
t=tf.constant(
        [
        [1,3,2,6],
        [7,5,4,9]
        ],tf.float32
        )

# dropout处理
r=tf.nn.dropout(t,0.5,noise_shape=[1,4])

session=tf.Session()

print(session.run(r))
[[ 2.  0.  4. 12.]
 [14.  0.  8. 18.]]

dropout层

dropout处理一般用在全连接神经网络的全连接层或者卷积神经网络后面的全连接层

仍以[2,3]和[1,4]作为网络的两个输入,通过以下代码分别经过该网络变换后的输入值,具体如下:

import tensorflow as tf
import numpy as np

# 占位符
x=tf.placeholder(tf.float32,[None,2])
keep_prob=tf.placeholder(tf.float32)

# 输入层到隐含层的权重矩阵
w1=tf.constant([
        [1,3,5],
        [2,4,6]
        ],tf.float32)

# 隐含层的值
h1=tf.matmul(x,w1)

# dropout层
h1_dropout=tf.nn.dropout(h1,keep_prob)

# dropout层到输出层的权重矩阵
w2=tf.constant(
        [
        [8,3],
        [7,2],
        [6,1]
        ],tf.float32
        )

# 输出层的值
o=tf.matmul(h1_dropout,w2)
x_input=np.array([[2,3],[1,4]],np.float32)

session=tf.Session()
h1_arr,h1_dropout_arr,o_arr=s=session.run([h1,h1_dropout,o],feed_dict={x:x_input,keep_prob:0.5})

print(\'隐含层的值:\')
print(h1_arr)
print("dropout层的值:")
print(h1_dropout_arr)
print(\'输出层的值:\')
print(o_arr)
隐含层的值:
[[ 8. 18. 28.]
 [ 9. 19. 29.]]
dropout层的值:
[[ 0.  0. 56.]
 [ 0. 38. 58.]]
输出层的值:
[[336.  56.]
 [614. 134.]]

dropout梯度下降

我们通过简单的网络结构理解有dropout层的全连接神经网络的梯度反向传播,其中dropout对应的概率为0.5,输入层,隐含层,输出层都只有一个神经元,激活函数为x

接下来我们初始化w11=10,w11=6,针对该函数进行标准梯度下降,其中学习率为0.01,具体代码如下:

import tensorflow as tf
import numpy as np

# 占位符,已知数据
x=tf.placeholder(tf.float32,(None,1))
keep_pro=tf.placeholder(tf.float32)

# 输入层到隐含层的权重矩阵
w1=tf.Variable(tf.constant([[10]],tf.float32),dtype=tf.float32)
l1=tf.matmul(x,w1)

# dropout层
l1_dropout=tf.nn.dropout(l1,keep_pro)

# 隐含层到输出层的权重矩阵
w2=tf.Variable(tf.constant([[6]],tf.float32),dtype=tf.float32)
l=tf.matmul(l1_dropout,w2)

# 利用网络输出值的构造函数f
f=tf.reduce_sum(l)

# 梯度下降法
opti=tf.train.GradientDescentOptimizer(0.01).minimize(f)
#"���������ֵ"
x_array=np.array([[3]],np.float32)

# 4次迭代,打印每一次迭代隐含层的值和网络权重
with tf.Session() as session:
    session.run(tf.global_variables_initializer())
    for i in range(4):
        _,l1_dropout_array=session.run([opti,l1_dropout],
                            {x:x_array,keep_pro:0.5})
        print(\'----第"{}"次迭代----\'.format(i+1))
        print("dropout层的值:")
        print(l1_dropout_array)
        print(\'网络当前的权重:\')
        print(session.run([w1,w2]))
----第"1"次迭代----
dropout层的值:
[[0.]]
网络当前的权重:
[array([[10.]], dtype=float32), array([[6.]], dtype=float32)]
----第"2"次迭代----
dropout层的值:
[[0.]]
网络当前的权重:
[array([[10.]], dtype=float32), array([[6.]], dtype=float32)]
----第"3"次迭代----
dropout层的值:
[[60.]]
网络当前的权重:
[array([[9.64]], dtype=float32), array([[5.4]], dtype=float32)]
----第"4"次迭代----
dropout层的值:
[[0.]]
网络当前的权重:
[array([[9.64]], dtype=float32), array([[5.4]], dtype=float32)]

四.VGGNet

VGGNet比AlexNet的网络层数多,不再使用尺寸较大的卷积核,如11x11,7x7,5x5,而是只采用尺寸为3x3的卷积核。这一点可以从卷积核的分离性理解,即计算量变小。以输入值的尺寸是224行224列3深度,接下来我们利用Tensorflow实现该网络结构

输入层:

tf.placeholder(tf.float32,[None,224,224,3])

第1层:输入层与64个3行3列3深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer1\',reuse=tf.AUTO_REUSE):
    # 64个3行3列3深度的卷积核
    w1=tf.Variable(tf.random_normal([3,3,3,64]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c1=tf.nn.conv2d(x,w1,[1,1,1,1],\'SAME\')
    # 因为c1的深度为64,所以偏置的长度为64
    b1=tf.Variable(tf.random_normal([64]),dtype=tf.float32,name=\'b\')
    # 卷积结果于偏置相加
    c1=tf.nn.bias_add(c1,b1)
    # relu激活函数
    c1=tf.nn.relu(c1)

第2层:将第1层的结果(其尺寸为224行224列64深度)与64个3行3列3深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer2\',reuse=tf.AUTO_REUSE):
    # 64个3行3列3深度的卷积核
    w2=tf.Variable(tf.random_normal([3,3,64,64]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c2=tf.nn.conv2d(c1,w2,[1,1,1,1],\'SAME\')
    # 因为c2的深度为64,所以偏置的长度为64
    b2=tf.Variable(tf.random_normal([64]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c2=tf.nn.bias_add(c2,b2)
    # relu激活函数
    c2=tf.nn.relu(c2)

经过第2层后,输出结果的尺寸仍是224行224列64深度,接着对其进行2x2的步长为2的最大值池化操作,对应代码如下:

# 对c2进行same最大值池化操作,邻域掩码的尺寸为2行2列,步长为2
p_c2=tf.nn.max_pool(c2,[1,2,2,1],[1,2,2,1],\'SAME\')

第3层:池化操作,结果的尺寸为112行112列64深度,也就是将高和宽的尺寸变小了,为了保证每一层的计算量差距不太大,在宽和高方向上虽然尺寸变小了,想办法在深度方向上增加尺寸即可,将池化结果与128个3行3列64深度的卷积核卷积,对应代码如下:

with tf.variable_scope(\'layer3\',reuse=tf.AUTO_REUSE):
    # 128个3行3列64深度的卷积核
    w3=tf.Variable(tf.random_normal([3,3,64,128]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c3=tf.nn.conv2d(p_c2,w3,[1,1,1,1],\'SAME\')
    # 因为c2的深度为128,所以偏置的长度为128
    b3=tf.Variable(tf.random_normal([128]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c3=tf.nn.bias_add(c3,b3)
    # relu激活函数
    c3=tf.nn.relu(c3)

第4层:经过第3层后,输出结果的尺寸仍为112行112列128深度,接着与128个3行3列128深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer4\',reuse=tf.AUTO_REUSE):
    # 128个3行3列128深度的卷积核
    w4=tf.Variable(tf.random_normal([3,3,128,128]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c4=tf.nn.conv2d(c3,w4,[1,1,1,1],\'SAME\')
    # 因为c2的深度为128,所以偏置的长度为128
    b4=tf.Variable(tf.random_normal([128]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c4=tf.nn.bias_add(c4,b4)
    # relu激活函数
    c4=tf.nn.relu(c4)

经过第4层后,输出结果的尺寸为112列112行128深度,接着对其进行2x2的步长为2的最大值池化操作,对应的代码如下:

# 对c4进行same最大值池化操作,邻域掩码的尺寸为2行2列,步长均为2
p_c4=tf.nn.max_pool(c4,[1,2,2,1],[1,2,2,1],\'SAME\')

第5层:池化操作后的结果的尺寸为56行56列128深度,显然这时宽和高的尺寸减小了,为了在深度方向上增加尺寸,接着与256个3行3列128深度卷积核卷积,将卷积结果加偏置,输入ReLU激活函数,对应的代码如下:

with tf.variable_scope(\'layer5\',reuse=tf.AUTO_REUSE):
    # 1256个3行3列128深度的卷积核
    w5=tf.Variable(tf.random_normal([3,3,128,256]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c5=tf.nn.conv2d(p_c4,w5,[1,1,1,1],\'SAME\')
    # 因为c2的深度为256,所以偏置的长度为256
    b5=tf.Variable(tf.random_normal([256]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c5=tf.nn.bias_add(c5,b5)
    # relu激活函数
    c5=tf.nn.relu(c5)

第6层:经过第5层后,输出结果的尺寸为56行56列256深度,接着与256个3行3列256深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer6\',reuse=tf.AUTO_REUSE):
    # 256个3行3列256深度的卷积核
    w6=tf.Variable(tf.random_normal([3,3,256,256]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c6=tf.nn.conv2d(c5,w6,[1,1,1,1],\'SAME\')
    # 因为c2的深度为256,所以偏置的长度为256
    b6=tf.Variable(tf.random_normal([256]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c6=tf.nn.bias_add(c6,b6)
    # relu激活函数
    c6=tf.nn.relu(c6)

第7层:经过第6层后,输出结果的尺寸为56行56列256深度,接着与256个3行3列256深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer7\',reuse=tf.AUTO_REUSE):
    # 256个3行3列256深度的卷积核
    w7=tf.Variable(tf.random_normal([3,3,256,256]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c7=tf.nn.conv2d(c6,w7,[1,1,1,1],\'SAME\')
    # 因为c2的深度为256,所以偏置的长度为256
    b6=tf.Variable(tf.random_normal([256]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c7=tf.nn.bias_add(c7,b7)
    # relu激活函数
    c7=tf.nn.relu(c7)

经过第7层后,输出结果的尺寸为56行56列256深度,接着对其进行2x2的步长为2的最大值池化操作:

p_c7=tf.nn.max_pool(c7,[1,2,2,1],[1,2,2,1],\'SAME\')

第8层:池化操作后的结果的尺寸为28行28列256深度,显然这时宽和高的尺寸减小了,为了在深度方向上增加尺寸,接着与512个3行3列256深度卷积核卷积,将卷积结果加偏置,输入ReLU激活函数,对应的代码如下:

with tf.variable_scope(\'layer8\',reuse=tf.AUTO_REUSE):
    # 512个3行3列256深度的卷积核
    w8=tf.Variable(tf.random_normal([3,3,256,512]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c8=tf.nn.conv2d(p_c7,w8,[1,1,1,1],\'SAME\')
    # 因为c2的深度为512,所以偏置的长度为512
    b8=tf.Variable(tf.random_normal([512]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c8=tf.nn.bias_add(c8,b8)
    # relu激活函数
    c8=tf.nn.relu(c8)

第9层:经过第8层后,输出结果的尺寸为28行28列512深度,接着与512个3行3列512深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer9\',reuse=tf.AUTO_REUSE):
    # 512个3行3列512深度的卷积核
    w9=tf.Variable(tf.random_normal([3,3,512,512]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c9=tf.nn.conv2d(c8,w9,[1,1,1,1],\'SAME\')
    # 因为c2的深度为512,所以偏置的长度为512
    b9=tf.Variable(tf.random_normal([512]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c9=tf.nn.bias_add(c9,b9)
    # relu激活函数
    c9=tf.nn.relu(c9)

第10层:经过第9层后,输出结果的尺寸为28行28列512深度,接着与512个3行3列512深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer10\',reuse=tf.AUTO_REUSE):
    # 512个3行3列512深度的卷积核
    w10=tf.Variable(tf.random_normal([3,3,512,512]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c10=tf.nn.conv2d(c9,w10,[1,1,1,1],\'SAME\')
    # 因为c2的深度为512,所以偏置的长度为512
    b10=tf.Variable(tf.random_normal([512]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c10=tf.nn.bias_add(c10,b10)
    # relu激活函数
    c10=tf.nn.relu(c10)

经过第10层后,输出结果的尺寸为28行28列512深度,接着对其进行2x2的步长为2的最大值池化操作:

p_c10=tf.nn.max_pool(c10,[1,2,2,1],[1,2,2,1],\'SAME\')

第11层:经过第10层后,输出结果的尺寸为14行14列512深度,接着与512个3行3列512深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer11\',reuse=tf.AUTO_REUSE):
    # 512个3行3列512深度的卷积核
    w11=tf.Variable(tf.random_normal([3,3,512,512]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c11=tf.nn.conv2d(p_c10,w11,[1,1,1,1],\'SAME\')
    # 因为c2的深度为512,所以偏置的长度为512
    b11=tf.Variable(tf.random_normal([512]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c11=tf.nn.bias_add(c11,b11)
    # relu激活函数
    c11=tf.nn.relu(c11)

第12层:经过第11层后,输出结果的尺寸为14行14列512深度,接着与512个3行3列512深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer12\',reuse=tf.AUTO_REUSE):
    # 512个3行3列512深度的卷积核
    w12=tf.Variable(tf.random_normal([3,3,512,512]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c12=tf.nn.conv2d(c12,w12,[1,1,1,1],\'SAME\')
    # 因为c2的深度为512,所以偏置的长度为512
    b12=tf.Variable(tf.random_normal([512]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c12=tf.nn.bias_add(c12,b12)
    # relu激活函数
    c12=tf.nn.relu(c12)

第13层:经过第12层后,输出结果的尺寸为14行14列512深度,接着与512个3行3列512深度的卷积核same卷积,将卷积结果加偏置,然后输入ReLU激活函数,对应代码如下:

with tf.variable_scope(\'layer13\',reuse=tf.AUTO_REUSE):
    # 512个3行3列512深度的卷积核
    w13=tf.Variable(tf.random_normal([3,3,512,512]),dtype=tf.float32,name=\'w\')
    # 步长为1的same卷积
    c13=tf.nn.conv2d(c12,w13,[1,1,1,1],\'SAME\')
    # 因为c2的深度为512,所以偏置的长度为512
    b13=tf.Variable(tf.random_normal([512]),dtype=tf.float32,name=\'b\')
    # 卷积结果与偏置相加
    c13=tf.nn.bias_add(c13,b13)
    # relu激活函数
    c13=tf.nn.relu(c13)

经过第13层后,输出结果的尺寸为14行14列512深度,接着对其进行2x2的步长为2的最大值池化操作:

p_c13=tf.nn.max_pool(c13,[1,2,2,1],[1,2,2,1],\'SAME\')

对上述池化操作结果进行拉伸操作,其结果可以看作一个全连接神经网络的输入层,代码如下:

shape=p_c13.get_shape()
flatten_p_c13=tf.reshape(p_c13,[-1,shape[1]*shape[2]*shape[3]])

第14层:作为全连接神经网络的第1个隐含层,其神经元的个数为4096,代码如下:

with tf.variable_scope("layer14",reuse=tf.AUTO_REUSE):
    # 权重矩阵和偏置
    w14=tf.Variable(
        tf.random_normal([shape[1].value*shape[2].value*shape[3].value,4096]),dtype=tf.float32,name=\'w\'
    )
    b14=tf.Variable(tf.random_normal([4096]),dtype=tf.float32,name=\'b\')
    # 线性组合
    fc14=tf.matmul(flatten_p_c13,w14)
    fc14=tf.nn.bias_add(fc14,b14)
    # relu线性组合激活
    fc14=tf.nn.relu(fc14)

第15层:作为全连接神经网络的第2个隐含层,其神经元的个数为4096,代码如下:

with tf.variable_scope("layer15",reuse=tf.AUTO_REUSE):
    # 权重矩阵和偏置
    w15=tf.Variable(
        tf.random_normal([4096,4096]),dtype=tf.float32,name=\'w\'
    )
    b15=tf.Variable(tf.random_normal([4096]),dtype=tf.float32,name=\'b\')
    # 线性组合
    fc15=tf.matmul(fc14,w15)
    fc15=tf.nn.bias_add(fc15,b15)
    # relu线性组合激活
    fc15=tf.nn.relu(fc15)

第16层:作为全连接神经网络的输出层,其神经元的个数为4096,代码如下:

with tf.variable_scope("layer16",reuse=tf.AUTO_REUSE):
    # 权重矩阵和偏置
    w16=tf.Variable(
        tf.random_normal([4096,1000]),dtype=tf.float32,name=\'w\'
    )
    b16=tf.Variable(tf.random_normal([1000]),dtype=tf.float32,name=\'b\')
    # 线性组合
    fc16=tf.matmul(fc15,w16)
    fc16=tf.nn.bias_add(fc16,b16)

构建完以上网络结构后,打印VGGNet后面的全连接神经网络的输入层的神经元个数,代码如下:

print(shape[1].value*shape[2].value*shape[3].value)

打印结果为:25088

因为第13层经过池化操作的尺寸为7行7列512深度,该张量拉伸为一个一维向量的长度为:7x7x512=25088

五.GoogleNet

GoogleNet是基于网中网(Network In Network)的一种网络结构

1.网中网结构

我们通过以下示例说明。1个3行3列2深度的张量与7个2行2列2深度的卷积核或者7个3行3列2深度的卷积核same卷积,两种都可以得到结果的尺寸为3行3列7深度。网中网结构通过多个分支的运算(分支的运算可以为卷积也可以为池化),将分支上的运算结果在深度上连接,得到的结果的尺寸为3行3列7深度

上述示例有3个分支,第1个分支:3行3列2深度的张量先与3个1行1列2深度的卷积核same卷积,其结果的尺寸为3行3列3深度。第2个分支:与2个2行2列2深度的卷积核same卷积,其结果的尺寸为3行3列2深度。第3个分支:进行3x3的步长为1的same最大值池化操作,其结果为3行3列2深度,将这三个分支上的结果在深度的方向上连接,最后的结果为3行3列7深度,上述示例的具体代码如下:

import tensorflow as tf

# 1个高为3,宽为3,深度为2的输入张量
inputTensor=tf.constant(
        [
        [
        [[2,5],[3,3],[8,2]],
        [[6,1],[1,2],[5,4]],
        [[7,9],[2,-3],[-1,3]]
        ]
        ],tf.float32
        )
#
session=tf.Session()

# 3个高为1,宽为1,深度为2的卷积核
filter112_3=tf.constant(
        [
        [[[1,2,-1],[-1,-2,2]]]
        ],tf.float32
        )
result1=tf.nn.conv2d(inputTensor,filter112_3,[1,1,1,1],\'SAME\')
print(session.run(result1))

# 2个高为2,宽为2,深度为2的卷积核
filter222_2=tf.constant(
        [
        [[[3,-1],[1,2]],[[-2,1],[2,3]]],
        [[[-1,1],[-3,7]],[[4,2],[5,4]]]
        ],tf.float32)
result2=tf.nn.conv2d(inputTensor,filter222_2,[1,1,1,1],\'SAME\')
print(session.run(result2))

# 最大值池化
maxPool_33=tf.nn.max_pool(inputTensor,[1,3,3,1],[1,1,1,1],\'SAME\')
print(session.run(maxPool_33))

# 深度方向连接
result=tf.concat([result1,result2,maxPool_33],3)
print(session.run(result))
[[[[-3. -6.  8.]
   [ 0.  0.  3.]
   [ 6. 12. -4.]]

  [[ 5. 10. -4.]
   [-1. -2.  3.]
   [ 1.  2.  3.]]

  [[-2. -4. 11.]
   [ 5. 10. -8.]
   [-4. -8.  7.]]]]
[[[[ 16.  43.]
   [ 33.  58.]
   [  9.  29.]]

  [[-20.  65.]
   [ 21.  11.]
   [ 11.  23.]]

  [[ 20.   4.]
   [ 11.   0.]
   [  0.   7.]]]]
[[[[6. 5.]
   [8. 5.]
   [8. 4.]]

  [[7. 9.]
   [8. 9.]
   [8. 4.]]

  [[7. 9.]
   [7. 9.]
   [5. 4.]]]]
[[[[ -3.  -6.   8.  16.  43.   6.   5.]
   [  0.   0.   3.  33.  58.   8.   5.]
   [  6.  12.  -4.   9.  29.   8.   4.]]

  [[  5.  10.  -4. -20.  65.   7.   9.]
   [ -1.  -2.   3.  21.  11.   8.   9.]
   [  1.   2.   3.  11.  23.   8.   4.]]

  [[ -2.  -4.  11.  20.   4.   7.   9.]
   [  5.  10.  -8.  11.   0.   7.   9.]
   [ -4.  -8.   7.   0.   7.   5.   4.]]]]

GoogleNet是基于类似网中网模块设计的网络结构,在GoogleNet中该模块称为Inception Module,多个Inception Module模块的组合即为GoogleNet

Inception Module有多种变形,接下来我们介绍通过1x1的卷积核修改的形式

利用1x1的卷积核减少计算成本

假设有1个高为30,宽为40,深度为200的三维张量与55个高为5,宽为5,深度为200的卷积核same卷积,假设移动步长均为1,则卷积结果是高为30,宽为40,深度为55的三维张量

该卷积过程的乘法计算量大约为5x5x200x30x40x55=330000000

接着,我们考虑以下卷积过程

第1步:高为30,宽为40,深度为200的三为张量,先与20个高为1,宽为1,深度为20的卷积核same卷积,假设步长为1,即在深度方向上降维,得到1个高为30,宽为40,深度为20的三维张量

第2步:与55个高为5,宽为5,深度为20的卷积核same卷积,移动步长为1,得到高为30,宽为40,深度为55的卷积结果

我们来计算上述卷积过程的总乘法计算量:

第1步的乘法计算量:1x1x200x30x40=4800000

第2步的乘法计算量:5x5x20x30x40x55=33000000

总的乘法计算量为:33000000+4800000=37800000

显然,同样是得到高为30,宽为40,深度为55的卷积结果,采用第2种方式,即首先在深度方向上降维,比第1中计算方式在计算量上减少了8.7倍

针对GoogleNet的改进,还需要特别介绍的就是著名的Batch Normalization(BN归一化,简称BN),它有效地解决了在网络层数很深的情况下,收敛速度很慢的问题,使用BN操作可加大网络梯度下降的学习率,加速网络收敛

2.Batch Normalization

Tensorflow提供函数batch_normalization(x,mean,variance,offset,scale,variance_epsilon,name=None)实现BN操作的功能,其中参数mean和参数variance的值(即均值和方差)是通过另一个函数moments(x,axes,shitf=None)进行计算的,利用这两个函数实现以上示例的代码如下:

import tensorflow as tf

x=tf.constant([1,10,23,15],tf.float32)

# 计算均值和方差
mean,variance=tf.nn.moments(x,[0])
# BatchNormalize
r=tf.nn.batch_normalization(x,mean,variance,0,1,1e-8)
session=tf.Session()
print(session.run(r))
[-1.4096959  -0.28193915  1.3470427   0.34459233]

假设在训练网络时,每次抽取2组训练数据,有2个2行2列2深度的三维张量,它是某2个样本数据经过某网络的某一卷积层时的结果,然后分别在每一深度上进行BN操作,假设对第1层进行BN操作时的γ=2,β=3;对第2层深度进行BN操作时的γ=5,β=8,

import tensorflow as tf

# 思维张量
t=tf.constant(
        [
        # 第1个2行2列2深度的三维张量
        [
        [[1,12],[6,18]],
        [[9,13],[4,11]],
        ],
        # 第2个2行2列2深度的三维张量
        [
        [[2,19],[7,17]],
        [[3,15],[8,11]]
        ]
        ],tf.float32
        )

# 计算均值和方差 moments
mean,variance=tf.nn.moments(t,[0,1,2])#[0,1,2]
# BatchNormalize
gamma=tf.Variable(tf.constant([2,5],tf.float32))
beta=tf.Variable(tf.constant([3,8],tf.float32))
r=tf.nn.batch_normalization(t,mean,variance,beta,gamma,1e-8)
session=tf.Session()
session.run(tf.global_variables_initializer())

print(\'均值和方差:\')
print(session.run([mean,variance]))

# BatchNormalize的结果
print(\'BN操作后的结果:\')
print(session.run(r))
均值和方差:
[array([ 5. , 14.5], dtype=float32), array([7.5, 9. ], dtype=float32)]
BN操作后的结果:
[[[[ 0.0788132  3.833332 ]
   [ 3.730297  13.833334 ]]

  [[ 5.921187   5.5      ]
   [ 2.2697034  2.166666 ]]]


 [[[ 0.8091099 15.5      ]
   [ 4.4605937 12.166666 ]]

  [[ 1.5394068  8.833334 ]
   [ 5.1908903  2.166666 ]]]]

3.指数移动平均

import tensorflow as tf

# 初始化变量,即t=1时的值
x=tf.Variable(initial_value=5,dtype=tf.float32,trainable=False,name=\'v\')

# 创建计算移动平均的对象
exp_moving_avg=tf.train.ExponentialMovingAverage(0.7)
update_moving_avg=exp_moving_avg.apply([x])

session=tf.Session()
session.run(tf.global_variables_initializer())
for i in range(4):
    # 打印指数移动平均值
    session.run(update_moving_avg)
    print(\'第{}次的移动平均值:\'.format(i+1))
    print(session.run(exp_moving_avg.average(x)))
    session.run(x.assign_add(5))
第1次的移动平均值:
5.0
第2次的移动平均值:
6.5
第3次的移动平均值:
9.05
第4次的移动平均值:
12.335

4.带有BN操作的卷积神经网络

其中输入的样本数据的尺寸为1行2列3深度,所有的偏置均为0,每次设计从以下代码中解析2个张量,作为该卷积神经网络的输入

import tensorflow as tf
#"输入值的占位符"
x=tf.placeholder(tf.float32,[None,1,2,3])
#"设置是否是训练阶段的占位符"
trainable=tf.placeholder(tf.bool)
#"移动平均"
ema=tf.train.ExponentialMovingAverage(0.7)
ema_var_list=[]
#"----------第一层-----------"
#"2个1行1列3深度的卷积核"
k1=tf.Variable(tf.constant([
                [[[1,0],[0,1],[0,0]]],
                ],tf.float32)
        )
#"偏置"
b1=tf.Variable(tf.zeros(2))
#"卷积结果加偏置"
c1=tf.nn.conv2d(x,k1,[1,1,1,1],\'SAME\')+b1
beta1=tf.Variable(tf.zeros(c1.get_shape()[-1].value))
gamma1=tf.Variable(tf.ones(c1.get_shape()[-1].value))
#"计算每一深度上的均值和方差"
m1,v1=tf.nn.moments(c1,[0,1,2])
ema_var_list+=[m1,v1]
#"为了保存均值和方差的指数移动平均"
m1_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
v1_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
#"BN操作"
c1_BN=tf.cond(trainable,
        lambda:tf.nn.batch_normalization(c1,m1,v1,beta1,gamma1,1e-8),
        lambda: tf.nn.batch_normalization(c1,m1_ema,
                                          v1_ema,beta1,gamma1,1e-8)
        )
#"relu激活"
r1=tf.nn.relu(c1_BN)
#"----------第二层-----------"
#"2个1行1列2深度的卷积核"
k2=tf.Variable(tf.constant(
        [
        [[[2,0],[0,2]]]
        ],tf.float32
        ))
#"偏置"
b2=tf.Variable(tf.zeros(2))
#"卷积结果加偏置"
c2=tf.nn.conv2d(r1,k2,[1,1,1,1],\'SAME\')+b2
beta2=tf.Variable(tf.zeros(c2.get_shape()[-1]))
gamma2=tf.Variable(tf.ones(c2.get_shape()[-1]))
#"计算每一深度上的均值和方差"
m2,v2=tf.nn.moments(c2,[0,1,2])
ema_var_list+=[m2,v2]
#"为了保存均值和方差的指数移动平均"
m2_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
v2_ema=tf.Variable(tf.zeros(c1.get_shape()[-1]),trainable=False)
#"BN操作"
c2_BN=tf.cond(trainable,
        lambda:tf.nn.batch_normalization(c2,m2,v2,beta2,gamma2,1e-8),
        lambda:tf.nn.batch_normalization(c2,m2_ema,
                                         v2_ema,beta2,gamma2,1e-8)
        )
#"relu激活"
r2=tf.nn.relu(c2_BN)
update_moving_avg=ema.apply(ema_var_list)
#"创建会话"
session=tf.Session()
session.run(tf.global_variables_initializer())
session.run(tf.local_variables_initializer())
coord=tf.train.Coordinator()
threads=tf.train.start_queue_runners(sess=session,coord=coord)
num=2
for i in range(num):
    arrs=session.run(r2)
    print(\'---第%(num)d 批array---\'%{\'num\':i+1})
    print(arrs)
    _,c1_arr=session.run([update_moving_avg,c1],
                        feed_dict={x:arrs,trainable:True})
    print(\'---第%(num)d 次迭代后第1个卷积层(卷积结果加偏置)的值---\'%{\'num\':i+1})
    print(c1_arr)
    #"将计算的指数移动平均的值赋值给 Variable 对象"
    session.run(m1_ema.assign(ema.average(m1)))
    session.run(v1_ema.assign(ema.average(v1)))
    session.run(m2_ema.assign(ema.average(m2)))
    session.run(v2_ema.assign(ema.average(v2)))
    print(\'---第%(num)d 次迭代后第1个卷积层的均值的移动平均值---\'%{\'num\':i+1})
    print(session.run(m1_ema))
coord.request_stop()
coord.join(threads)
session.close()