卷积网络过程详解
一:卷积(Convolutional)
实际项目中,输入神经网络的是具有更高分辨率的彩色图片,使得送入全连接网络的输入特征数过多,随着隐藏层层数的增加,网络规模过大,待优化参数过多,很容易使模型过拟合。为了减少待训练参数,我们再实际应用时,会先对原始图片进行特征提取,把提取来的特征送给全连接网络,让全连接网络输出识别结果。
卷积计算可认为是一种有效提取图像特征的方法。一般会用一个正方形的卷积核,按指定步长,在输入特征图上滑动,遍历输入特征图中的每个像素点。每一个步长,卷积核会与输入特征图出现重合区域,重合区域对应元素相乘、求和再加上配置项得到输出特征的一个像素点。
如果输入特征是单通道灰度图,则使用深度为1的单通道卷积核;如果输入特征是三通道彩色图,则使用深度为3的3通道卷积核。总之要使卷积核的通道数与输入特征图的通道数一致。因为要想要让卷积核与输入特征图对应点匹配上,必须让卷积核的深度与输入特征图的深度一致,所以输入特征图的深度决定当前卷积核的深度。由于每一个卷积核在经过计算后,会得到一张输出特征图,所以当前层使用了几个卷积核,就有几张输出特征图。所以当前卷积核的个数,决定了当前层输出特征图的深度。
当某层的特征提取能力不足时,可以在这一层多用几个卷积核提高这一层的特征提取能力。
二:感受野(Receptive Field)
感受野:卷积神经网络各输出特征图中的每个像素点,在原始输入图片上映射区域的大小。
第一张输出特征图映射到原始图中,是一个3x3矩阵,所以它的感受野是3;第二张输出特征图映射到原始图中,是一个5x5矩阵,所以它的感受野是5。
当我们使用一个5x5的卷积核时,感受野与上图中的最终感受野是一样的,都为5。如下图所示
我们可以发现,2层3x3的卷积核作用和一层5x5的卷积核的作用都会得到一个感受野为5的输出特征图,所以它们的特征提取能力是一样的。对于它们的选择,我们需要考虑它们所承载的待训练参数量和计算量了。
假设输入特征图宽、高位x,卷积计算步长为1,则
-
对于2层3x3卷积核的训练,待训参数量共有18个,计算量为
9 ∗ ( x − 2 ) 2 + 9 ∗ ( x − 4 ) 2 9*(x-2)^2+9*(x-4)^2 9∗(x−2)2+9∗(x−4)2 -
对于一层5x5卷积核的训练,待训参数量共有25个,计算量为
25 ∗ ( x − 4 ) 2 25*(x-4)^2 25∗(x−4)2
通过对比发现,两层3x3卷积核优于一层5*5卷积核
三:全零填充
有时候我们希望卷积计算后,保持输出特征图和输入特征图的尺寸相同,这时就可以使用全零填充。在输入特征图周围填充0。比如一个5x5x1的输入特征图经过填充后,经过一个3x3的卷积核,输出特征图的结构仍为5x5x1。如下图所示
卷积输出特征图维度的计算公式为:
p
a
d
d
i
n
g
=
{
S
A
M
E
(
全
零
填
充
)
输
入
长
度
步
长
(
向
上
取
整
)
V
A
L
I
D
(
不
全
0
填
充
)
输
入
长
度
−
核
长
+
1
步
长
(
向
上
取
整
)
padding = \begin {cases} SAME(全零填充) \quad \frac{输入长度}{步长} \quad (向上取整) \\ VALID(不全0填充) \quad \frac{输入长度-核长+1}{步长} \quad (向上取整) \end {cases}
padding={SAME(全零填充)步长输入长度(向上取整)VALID(不全0填充)步长输入长度−核长+1(向上取整)
在tensorflow中,用参数Padding="SAME"表示全零填充,用Padding="VALID"表示不进行填充
四:tensorflow描述卷积层
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 = (高,宽,通道数) # 输入特征图维度,可省略
)
五:批标准化(BN)
神经网络对0附件的数据更敏感,但是随着网络层数的增加,特征数据会出现偏离0均值的情况,标准化可以使数据符合以0为均值,1为标准差的标准正太分布,把偏移的特征数据重新拉回到0附件。
标准化:使数据符合0均值,1为标准差的分布
批标准化:对一小批数据(batch),做标准化处理。
常用在卷积操作和激活操作之间,批标准化后的特征输出图可通过以下公式计算
H
i
‘
k
=
H
i
k
−
μ
b
a
t
c
h
k
σ
b
a
t
c
h
k
H_i^{`k} = \frac{H_i^k-μ_{batch}^k}{σ_{batch}^k}
Hi‘k=σbatchkHik−μbatchk
H‘ki:批标准化后,第k个卷积核的输出特征图中第i个像素点
Hik:批标准化前,第k个卷积核,输出特征图中第i个像素点
μkbatch:批标准化前,第k个卷积核,batch张输出特征图中所有像素点平均值
σkbatch:批标准化,第k个卷积核,batch张输出特征图中所有像素点标准差
BN操作将原本偏移的特征数据重新拉回到0均值,使进入激活函数的数据分布在激活函数线性区,使得输入数据的微小变化更明显的体现到激活函数的输出中,提升了激活函数对输入数据的区分力,但是这种简单的特征数据标准化使特征数据完全满足正态分布 ,集中在激活函数的线性区域,使激活函数丧失了非线性特性,因此在BN操作中为每个卷积核引入了两个可训练参数 ,缩放因子γ和偏移因子β。
反向传播时,γ和β会与其他待训练参数一同被训练优化,使标准正态分布后的特征数据通过缩放因子和偏移因子优化了特征数据分布的宽窄和偏移量,保证了网络的非线性表达力。
BN层位于卷积层之后,激活层之前。
tensorflow描述批标准化
tf.keras.layers.BatchNormalization()
tf.keras.models.Sequential([
Conv2D(filters=6, kernel_size=(5,5), padding="same"), # 卷积层
BatchNormalization(), # BN层
Activation("relu"), # 激活层
])
六:池化(Pooling)
池化用于减少特征数据量
最大值池化是取出卷积核对应特征输入图中最大的值,均值池化是取出卷积核对应特征输入图中的平均值。当以一个2x2卷积核,步长为2对特征输入图经过池化后,输出图片将变为输入图片的四分之一大小。
最大值池化可提取图片纹理,均值池化可保留背景特征。
tensorflow中池化的表示方式:
# 最大值池化
tf.keras,layers.MaxPool2D(
pool_size = 池化核尺寸, # 正方形写核长整数,或(高h,宽w)
strides = 池化步长, # 步长整数,或(纵h,横w),默认为pool_size
padding = "valid"or"same" # 不使用/使用零填充
)
# 均值池化
tf.keras.layers.AveragePooling2D(
pool_size = 池化核尺寸,
strides = 池化步长,
padding = "valid" or "same",
)
# 实例
tf.keras.models.Sequential([
Conv2D(filters=6, kernel_size=(5,5), padding="same"), # 卷积层
BatchNormalization(), # BN层
Activation("relu"), # 激活层
MaxPool2D(pool_size=(2,2), strides=2, padding="same") # 池化层
])
七:舍弃(Dropout)
为了缓解过拟合,在神经网络训练时,将一部分神经元按照一定概率从神经网络中暂时舍弃。神经网络使用时,被舍弃的神经元恢复链接。
tensorflow中舍弃的表达方式
tf.keras.layers.Dropout(舍弃的概率)
tf.keras.models.Sequential([
Conv2D(filters=6, kernel_size=(5,5), padding="same"), # 卷积层
BatchNormalization(), # BN层
Activation("relu"), # 激活层
MaxPool2D(pool_size=(2,2), strides=2, padding="same") # 池化层
Dropout(0,2) # dropout层
])
以上就是卷积神经网络相关的所有操作。
八:卷积神经网络
卷积神经网络就是借助卷积核对输入特征进行特征提取,再把提取到的特征送入全连接网络进行识别预测,提取特征包括卷积、批标准化、激活、池化四步
卷积神经网络的主要抹掉
卷积实质上就是特征提取器,就是CBPAD。
九: CIFAR10数据集案例
-
数据集介绍
cifar10提供了5万张32*32像素点的十分类彩色图片和标签,用于训练;提供了1万张32*32像素点的十分类彩色图片和标签,用于测试。
十分类(0~9)为:airplane,automobile,bird,cat,deer,dog,frog,horse,ship,truck
-
模型选择
搭建一个一层卷积、两层全连接的网络,使用6个5x5卷积核,过2*2的池化核,池化步长是2,过128个神经元的全连接层,输出层为10个神经元的全连接层
-
模型搭建
import os import numpy as np import tensorflow as tf import matplotlib.pyplot as plt from tensorflow.keras import Model from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense np.set_printoptions(threshold=np.inf) # 导入数据集 cifar = tf.keras.datasets.cifar10 (x_train, y_train), (x_test, y_test) = cifar.load_data() # 归一化 x_train = x_train/255 x_test = x_test/255 # 搭建网络模型 class BaseLine(Model): def __init__(self): super(BaseLine, self).__init__() self.c1 = Conv2D(filters=6, kernel_size=(5, 5), padding="same") self.b1 = BatchNormalization() self.a1 = Activation("relu") self.m1 = MaxPool2D(pool_size=(2, 2), strides=2, padding="same") self.d1 = Dropout(0.2) self.flatten = Flatten() self.de1 = Dense(128, activation="relu") self.d2 = Dropout(0.2) self.de2 = Dense(10, activation="softmax") def call(self, x): x = self.c1(x) x = self.b1(x) x = self.a1(x) x = self.m1(x) x = self.d1(x) x = self.flatten(x) x = self.de1(x) x = self.d2(x) y = self.de2(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 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, epochs=5, batch_size=32, validation_data=(x_test, y_test), validation_freq=1, callbacks=[cp_callback]) # 统计信息 model.summary() # 将相关参数保存到txt文本中 with open("./weights.txt", "w") as f: for v in model.trainable_variables: f.write(str(v.name) + "\n") f.write(str(v.shape) + "\n") f.write(str(v.numpy()) + "\n") # 显示训练集和测试集的loss和acc曲线 acc = history.history["sparse_categorical_accuracy"] val_acc = history.history["val_sparse_categorical_accuracy"] loss = history.history["loss"] val_loss = history.history["loss"] # 绘制训练集和测试集的loss和acc曲线图 plt.subplot(1,2,1) plt.plot(acc, label="Training Accuracy") plt.plot(val_acc, label="Validation Training 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()