TensorFlow 2.0教程 | 五、卷积神经网络

5.1 卷积计算过程

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

全连接神经网络参数个数: $$ \sum_{\text {各层 }}(\text { 前层神经元个数 } \times \text { 后层神经元个数 }+\text { 后层神经元个数 }) $$

w:前层*后层
b:后层

上一讲图片识别中,每张图片28×28个点,128个神经元参数个数为:

第一层参数:28×28×128个w+128个b
第二层参数:128×10个w+10个b
共计:101770个参数

实际项目中的图片多是高分辨率彩色图,待优化参数过多容易造成模型过拟合。

实际应用时会先对原始图像进行特征提取,再把提取到的特征送给全连接网络。

卷积计算可认为是一种有效提取图像特征的方法。

一般会用一个正方形的卷积核,按指定步长,在输入特征图上滑动遍历输入特征图中的每个像素点。每一个步长,卷积核会与输入特征图出现重合区域,重合区域对应元素相乘、求和再加上偏置项,得到输出特征的一个像素点。

  • 如果输入特征是单通道灰度图,深度为1的单通道卷积核
  • 如果输入特征是三通道彩色图,一个3×3×3的卷积核或5×5×3的卷积核

总之要使卷积核的通道数与输入特征图的通道数一致。因为要想让卷积核与输入特征图对应点匹配上,必须让卷积核的深度与输入特征图的深度一致。

输入特征图的深度(channel数),决定了当前层卷积核的深度。

由于每个卷积核在卷积计算后,会得到一张输出特征图,所以当前层使用了几个卷积核,就有几张输出特征图。

如果觉得某层模型的特征提取能力不足,可以在这一层多用几个卷积核提高这一层的特征提取能力。

卷积核示意图:

3×3×1卷积核,每个核3×3+1=10个参数

3×3×3卷积核,每个核3×3×3+1=28个参数

5×5×3卷积核,每个核5×5×3+1=76个参数

里面的每一个小颗粒都存储着一个待训练参数,在执行卷积计算时,卷积核里的参数时固定的,在每次反向传播时,这些小颗粒中存储的待训练参数,会被梯度下降法更新。卷积就是利用立体卷积核,实现参数共享。

​例如输入特征图为单通道,卷积核在输入特征图上滑动,每滑动一步输入特征图与卷积核里的9个元素重合,对应元素相乘求和再加上偏置项b,得到输出特征图中的一个像素值。

计算过程:(-1)1+00+1 2+(-1)5+04+12+(-1)3+04+1*5+1=1

​输入特征为3通道的,选用3通道卷积核。本例中输入是是5行5列红绿蓝三通道数据,选用我选用了3*3三通道卷积核,滑动步长是1,在输入特征图上滑动,每滑动一步输入特征图与卷积核里的27个元素重合。


动图理解

帮助你理解卷积核在输入特征图上按指定步长滑动,每个步长卷积核会与输入特征图上部分像素点重合,重合区域输入特征图与卷积核对应元素相乘求和,得到输出特征图中的一个像素点,当输入特征图被遍历完成,得到一张输出特征图,完成了一个卷积核的卷积计算过程。当有n个卷积核时,会有n张输出特征图,叠加在这张输出特征图的后面。

5.2 感受野

感受野(Receptive Field) : 卷积神经网络各输出特征图中的每个像素点,在原始输入图片上映射区域的大小。

例如:对于输入特征为5×5的图片,其中卷积核(filter)的步长(stride)为1、padding为0。

用黄色3×3的卷积核卷积操作,第一层这个输出特征图上的每个像素点,映射到原始图片是3×3的区域,所以这一层感受野为3。如果再对这个3×3的特征图,用这个绿色的3×3卷积核作用,会输出一个1×1的输出特征图,这个输出特征图上的像素点,映射到原始图片是5×5的区域,所以感受野为5。

如果对这个5×5的原始输入图片,直接用蓝色的5×5卷积核作用,会输出一个1×1的输出特征图,这个像素点映射到原始输入图片是5×5的区域,所以它的感受野也是5。

同样一个5×5的原始输入图片,经过两层3×3的卷积核作用,和经过一层5×5的卷积核作用,都得到一个感受野是5的输出特征图。所以这两种方式特征提取能力是一致的。

​ 是选择两层3×3卷积运算的好,还是一层5×5卷积运算的好?需要考虑他们所承载的待训练参数量和计算量。

设输入特征图宽、高为x,卷积计算步长为1,卷积核为n×n

每个n×n×1的卷积核经过n2n ^ 2计算得到一个输出像素点,得到的特征图为(x(n1))2(x-(n-1)) ^ 2大小,所以计算量计算公式: $$ n{ ^ 2}(x - (n -1) { ^ 2} + n{ ^ 2}(x - 2 * (n -1) { ^ 2} + … $$ 对于两层3*3卷积运算

参数量:3×3+3×3=18

计算量: $$ 3 ^ {2}(x-2) ^ 2+3 ^ {2}(x-4) ^ 2 $$ 对于一层5×5卷积运算

参数量:5×5=25

计算量: $$ 5 ^ {2}(x-4) ^ 2 $$

当x>10时,两层3×3卷积核 优于一层5×5卷积核,这也就是为什么现在的神经网络在卷积计算中,常用使用两层3×3卷积核。

5.3 全零填充

为保证卷积计算保持输入特征图的尺寸不变,可以使用全零填充,在输入特征图周围填充0,

例如对5×5×1的输入特征图经过全零填充后,再通过3×3×1的卷积核,进行进行步长为1的卷积计算,输出特征图仍是5×5×1

输出图片边长=输入图片边长/步长 此图:5/1=5

卷积输出特征图维度计算公式

SAME(全零填充)向上取整,例如2.3取3: $$ padding =\frac{\text { 输入特征图边长 }}{\text { 步长 }} $$

VALID(不全0填充)向上取整: $$ padding =\frac{\text {输入特征图边长 }-\text { 卷积核长 } +1}{\text {步长}} $$ TF描述是否全零填充

用参数padding = 'SAME’表示使用全零填充
用参数padding = 'VALID’表示不使用全零填充
示例:

5.4 TF描述卷积计算层

Tensorflow给出了计算卷积的函数

1
2
3
4
5
6
7
8
tf.keras.layers.Conv2D (
filters =卷积核个数,
kernel_size =卷积核尺寸,# 正方形写核长整数,或(核高h,核宽w)
strides =滑动步长,# 横纵向相同写步长整数,或纵向步长h,横向步长w), 默认1
padding = "same" or "Valid", # 使用全零填充是"same",不使用是"valid" (默认)
activation =“relu”or“sigmoid”or“tanh”or“softmax"等,# 如有BN此处不写
input_shape = (高,宽,通道数) # 输入特征图维度,可省略
)

示例

1
2
3
4
5
6
7
8
9
10
model = tf.keras.mode.Sequential([
Conv2D(6, 5,padding='valid', activation='sigmoid'),
MaxPoo12D(2,2),
Conv2D(6, (5, 5),padding=' valid', activation= 'sigmoid'),
MaxPool2D(2, (2, 2)),
Conv2D(filters=6, kerne1_size=(5,5), padding='valid', activation= 'sigmoid' ),
MaxPool2D(pooI_s1ze=(2, 2), strides=2),
Flatten(),
Dense(10, activation='softmax')
])

5.5 批标准化(BN)

批标准化(Batch Normalization, BN)

​ 神经网络对0附近的数据更敏感,但是随着网络层数的增加,特征数据会出现偏离0均值的情况。

标准化:可以使数据符合以0为均值,1为标准差的分布。把偏移的特征数据重新拉回到0附近。
批标准化:对一小批数据(batch) ,做标准化处理。使数据回归标准正态分布,常用在卷积操作和激活操作之间。
可以通过下方的式子计算批标准化后的输出特征图,得到第k个卷积核的输出特征图(feature map)中的第i个像素点。批标准化操作,会让每个像素点进行减均值除以标准差的自更新计算。 $$ H_{i} ^ {'k}=\frac{H_{i} ^ {k}-\mu_{\mathrm{batch}} ^ {k}}{\sigma_{\mathrm{batch}} ^ {k}} $$$$ H_{i} ^ {k}:批标准化前,第k个卷积核,输出特征图中第i个像素点 $$$$ \mu_{\mathrm{batch}} ^ {k}:批标准化前,第k个卷积核,batch张 输出特征图中所有像素点平均值 $$$$ \boldsymbol{\mu}{\text {batch }} ^ {k}=\frac{\mathbf{1}}{m} \sum{i=1} ^ {m} H_{i} ^ {k} $$$$ \sigma_{\text {batch }} ^ {k}=\sqrt{\delta+\frac{1}{m} \sum_{i=1} ^ {m}\left(H_{i} ^ {k}-\mu_{\text {batch }} ^ {k}\right) ^ {2}} $$$$ {\sigma_{\mathrm{batch}} ^ {k}}:批标准化前,第k个卷积核。batch张输出特征图中所有像素点标准差 $$$$ \sigma_{\text {batch }} ^ {k}=\sqrt{\delta+\frac{1}{m} \sum_{i=1} ^ {m}\left(H_{i} ^ {k}-\mu_{\text {batch }} ^ {k}\right) ^ {2}} $$

BN操作将原本偏移的特征数据,重新拉回到0均值,使进入激活函数的数据分布在激活函数线性区,使得输入数据的微小变化,更明显的体现到激活函数的输出,提升了激活函数对输入数据的区分力。

$$x_{i} ^ {k}=\gamma_{k} H_{i} ^ {\prime k}+\beta_{k} $$​ 反向传播时,缩放因子γ和偏移因子β会与其他待训练参数一同被训练优化,便标难正态分布后的特征数据,通过缩放因子和偏移因子,优化了特征数据分布的宽窄和偏移量,保证了网络的非线性表达力。

BN层位于卷积层之后,激活层之前。TF描述批标准化

1
tf.keras.layers.BatchNormalization()

示例

1
2
3
4
5
6
7
model = tf.keras.models.Sequential([
Conv2D(filters=6,kernel_size=(5, 5),padding='same'),#卷积层
BatchNormalization(),# BN层
Activation('relu), # 微祜层
MaxPoo12D(poo1_size=(2, 2),strides=2,padding='same'), # 池化层
Dropout(0.2),# dropout层
])

5.6 池化

池化操作用于减少告积神经网络中特征数据量。

  • 最大值池化可提取图片纹理
  • 均值池化可保留背景特征

如果用2×2的池化核对输入图片以2为步长进行池化,输出图片将变为输入图片的四分之一大小。

最大池化:是用2×2的池化核框住4个像素点,选择每次框住4个像素点中最大的值输出,直到遍历完整幅图片。

均值池:是用2×2的池化核框住4个像素点,每次输出四个像素点的均值,直到遍历完整幅图片。

Tensorflow池化函数

最大值池化

1
2
3
4
5
tf.keras.layers.MaxPool2D(
pool_size=池化核尺寸, # 正方形写核长整数,或(核高h,核宽w)
strides=池化步长, #步长整数,或(纵向步长h,横向步长w),默认为pool_size
padding='valid'or'same' #使用全零填充是"same",不使用是"valid" (默认)
)

均值池化

1
2
3
4
5
tf.keras.layers.AveragePooling2D(
pool_size=池化核尺寸, # 正方形写核长整数,或(核高h,核宽w)
strides=池化步长, #步长整数,或(纵向步长h,横向步长w),默认为pool_size
padding='valid'or'same' #使用全零填充是"same",不使用是"valid" (默认)
)

示例

1
2
3
4
5
6
7
model = tf.keras.models.Sequential([
Conv2D(filters=6,kernel size=(5, 5),padding='same'),#卷积层
BatchNormalization(),# BN层
Activation('relu), # 激活层
MaxPoo12D(poo1_size=(2, 2),strides=2,padding='same'), # 池化层
Dropout(0.2),# dropout层
])

补充:GlobalAveragePooling2D是平均池化的一个特例,它不需要指定pool_size和strides等参数,操作的实质是将输入特征图的每一个通道求平均得到一个数值。

5.7 Dropout

为了缓解神经网络过拟合,在神经网络训练过程中,常把隐藏层的部分神经元按照一定比例从神经网络中临时舍弃,在使用神经网络时再把所有神经元恢复到神经网络中。

Tensorflow Dropout函数

1
tf.keras.layers.Dropout(舍弃的概率)

示例

1
2
3
4
5
6
7
model = tf.keras.models.Sequential([
Conv2D(filters=6,kernel size=(5, 5),padding='same'),#卷积层
BatchNormalization(),# BN层
Activation('relu), # 激活层
MaxPoo12D(poo1_size=(2, 2),strides=2,padding='same'), # 池化层
Dropout(0.2),# dropout层,0.2表示随机舍弃掉20%的神经元
])

5.8 卷积神经网络

卷积神经网络:卷积神经网络就是借助卷积核对输入特征进行特征提取,再把提取到的特征送入全连接网络进行识别预测。

卷积神经网络网络的主要模块

卷积是什么?

卷积就是特征提取器 ,就是CBAPD

1
2
3
4
5
6
7
8
9
model = tf.keras.models.Sequential([
C Conv2D(filters=6,kernel size=(5, 5),padding='same'),
#卷积层
B BatchNormalization(),# BN层
A Activation('relu), # 激活层
P MaxPoo12D(poo1_size=(2, 2),strides=2,padding='same'),
# 池化层
D Dropout(0.2),# dropout层,0.2表示随机舍弃掉20%的神经元
])

5.9 CIFAR10数据集

cifar10数据集一共有6万张彩色图片,每张图片有32行32列像素点的红绿蓝三通道数据。

  • 提供5万张32*32像素点的十分类彩色图片和标签,用于训练。
  • 提供1万张32*32像素点的十分类彩色图片和标签,用于测试。
  • 十个分类分别是:飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船和卡车,2 分别对应标签0、1、2、3一直到9

导入cifar10数据集:

1
2
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train),(x_test, y_test) = cifar10.load_data()

代码示例:

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
import tensorflow as tf
from matplotlib import pyplot as plt
import numpy as np

np.set_printoptions(threshold=np.inf)

cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()

# 可视化训练集输入特征的第一个元素
plt.imshow(x_train[0]) # 绘制图片
plt.show()

# 打印出训练集输入特征的第一个元素
print("x_train[0]:\n", x_train[0])
# 打印出训练集标签的第一个元素
print("y_train[0]:\n", y_train[0])

# 打印出整个训练集输入特征形状
print("x_train.shape:\n", x_train.shape)
# 打印出整个训练集标签的形状
print("y_train.shape:\n", y_train.shape)
# 打印出整个测试集输入特征的形状
print("x_test.shape:\n", x_test.shape)
# 打印出整个测试集标签的形状
print("y_test.shape:\n", y_test.shape)

运行结果

用卷积神经网络训练cifar10数据集,搭建一个一层卷积、两层全连接的网络。

代码示例

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
79
80
81
82
83
84
85
86
87
88
import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
from tensorflow.keras import Model

np.set_printoptions(threshold=np.inf)

cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0

# 卷积神经网络CBAPD
class Baseline(Model):
def __init__(self):
super(Baseline, self).__init__()
self.c1 = Conv2D(filters=6, kernel_size=(5, 5), padding='same') # 卷积层
self.b1 = BatchNormalization() # BN层
self.a1 = Activation('relu') # 激活层
self.p1 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same') # 池化层
self.d1 = Dropout(0.2) # dropout层

self.flatten = Flatten()
self.f1 = Dense(128, activation='relu')
self.d2 = Dropout(0.2)
self.f2 = Dense(10, activation='softmax')

def call(self, x):
x = self.c1(x)
x = self.b1(x)
x = self.a1(x)
x = self.p1(x)
x = self.d1(x)

x = self.flatten(x)
x = self.f1(x)
x = self.d2(x)
y = self.f2(x)
return y


model = Baseline()

model.compile(optimizer='adam',
loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),
metrics=['sparse_categorical_accuracy'])

checkpoint_save_path = "./checkpoint/Baseline.ckpt"
if os.path.exists(checkpoint_save_path + '.index'):
print('-------------load the model-----------------')
model.load_weights(checkpoint_save_path)

cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path,
save_weights_only=True,
save_best_only=True)

history = model.fit(x_train, y_train, batch_size=32, epochs=5, validation_data=(x_test, y_test), validation_freq=1, callbacks=[cp_callback])
model.summary()

# print(model.trainable_variables)
file = open('./weights.txt', 'w')
for v in model.trainable_variables:
file.write(str(v.name) + '\n')
file.write(str(v.shape) + '\n')
file.write(str(v.numpy()) + '\n')
file.close()

############################################### show ###############################################

# 显示训练集和验证集的acc和loss曲线
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1, 2, 1)
plt.plot(acc, label='Training Accuracy')
plt.plot(val_acc, label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1, 2, 2)
plt.plot(loss, label='Training Loss')
plt.plot(val_loss, label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.show()

运行结果

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
......
48672/50000 [============================>.] - ETA: 0s - loss: 1.2555 - sparse_categorical_accuracy: 0.5483
48992/50000 [============================>.] - ETA: 0s - loss: 1.2556 - sparse_categorical_accuracy: 0.5484
49312/50000 [============================>.] - ETA: 0s - loss: 1.2560 - sparse_categorical_accuracy: 0.5483
49568/50000 [============================>.] - ETA: 0s - loss: 1.2558 - sparse_categorical_accuracy: 0.5482
49856/50000 [============================>.] - ETA: 0s - loss: 1.2556 - sparse_categorical_accuracy: 0.5484
50000/50000 [==============================] - 10s 207us/sample - loss: 1.2558 - sparse_categorical_accuracy: 0.5484 - val_loss: 1.1867 - val_sparse_categorical_accuracy: 0.5762
Model: "baseline"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) multiple 456
_________________________________________________________________
batch_normalization (BatchNo multiple 24
_________________________________________________________________
activation (Activation) multiple 0
_________________________________________________________________
max_pooling2d (MaxPooling2D) multiple 0
_________________________________________________________________
dropout (Dropout) multiple 0
_________________________________________________________________
flatten (Flatten) multiple 0
_________________________________________________________________
dense (Dense) multiple 196736
_________________________________________________________________
dropout_1 (Dropout) multiple 0
_________________________________________________________________
dense_1 (Dense) multiple 1290
=================================================================
Total params: 198,506
Trainable params: 198,494
Non-trainable params: 12
_________________________________________________________________

在wedithts.tet文件里记录了所有可训练参数

  • baseline/conv2d/kernel:0 (5, 5, 3, 6)记录了第层网络用的553的卷积核,一共6个,下边给出了这6个卷积核中的所有参数W;
  • baseline/conv2d/bias:0 (6,)这里记录了6个卷积核各自的偏置项b,每个卷积核一个 b,6个卷积核共有6个偏置6 ;
  • baseline/batch_normalization/gamma:0 (6,),这里记录了BN操作中的缩放因子γ,每个卷积核一个γ,一个6个γ;
  • baseline/batch_normalization/beta:0 (6,),里记录了BN操作中的偏移因子β,每个卷积核一个β,一个6个β;
  • baseline/dense/kernel:0 (1536, 128),这里记录了第一层全链接网络,1536 行、128列的线上的权量w;
  • baseline/dense/bias:0 (128,),这里记录了第一层全连接网络128个偏置b;
  • baseline/dense_1/kernel:0 (128, 10),这里记录了第二层全链接网络,128行、10列的线上的权量w;
  • baseline/dense_1/bias:0 (10,),这里记录了第二层全连接网络10个偏置b。
分享到