本文基于北京大学软件与微电子学院曹健老师的Tensorflow笔记整理——b站视频教程
卷积神经网络
卷积
在实际应用中,图像的分辨率远高于此,且大多数是彩色图像。虽然全连接网络一般被认为是分类预测的最佳网络,但待优化的参数过多,容易导致模型过拟合。为了解决参数量过大而导致模型过拟合的问题,一般不会将原始图像直接输入,而是先对图像进行特征提取,再将提取到的特征输入全连接网络,就是将汽车图片经过多次特征提取后再喂入全连接网络。
卷积的概念:卷积可以认为是一种有效提取图像特征的方法。一般会用一个正方形的卷积核,按指定步长,在输入特征图上滑动,遍历输入特征图中的每个像素点。每一个步长,卷积核会与输入特征图出现重合区域,重合区域对应元素相乘、求和再加上偏置项得到输出特征的一个像素点,如下图所示,利用大小为 3×3×1 的卷积核对 5×5×1 的单通道图像做卷积计算得到相应结果。
对于彩色图像(多通道)来说,卷积核通道数与输入特征一致,套接后在对应位置上进行乘加和操作,如下图所示,利用三通道卷积核对三通道的彩色特征图做卷积计算。(下图经过全0填充)
用多个卷积核可实现对同一层输入特征的多次特征提取,卷积核的个数决定输出层的通道(channels)数,即输出特征图的深度。
感受野
感受野(Receptive Field)的概念:卷积神经网络各输出层每个像素点在原始图像上的映射区域大小,如图所示。
当我们采用尺寸不同的卷积核时,最大的区别就是感受野的大小不同,所以经常会采用多层小卷积核来替换一层大卷积核,在保持感受野相同的情况下减少参数量和计算量,例如十分常见的用 2 层 3 * 3 卷积核来替换 1 层 5 * 5 卷积核的方法
全零填充
全零填充(padding):为了保持输出图像尺寸与输入图像一致,经常会在输入图像周围进行全零填充,如图 示,在 5×5 的输入图像周围填 0,则输出特征尺寸同为 5×5。
在 Tensorflow 框架中,用参数 padding = ‘SAME’或 padding = ‘VALID’表示是否进行全零填充,其对输出特征尺寸大小的影响如下:
上下两行分别代表对输入图像进行全零填充或不进行填充,对于 5×5×1 的图像来说,当 padding = ‘SAME’时,输出图像边长为 5;当 padding = ‘VALID’时,输出图像边长为 3。
TF描述卷积计算层
具备以上知识后,就可以在 Tensorflow 框架下利用 Keras 来构建 CNN 中的卷积层,使用的是 tf.keras.layers.Conv2D 函数,具体的使用方法如下:
tf.keras.layers.Conv2D(
input_shape = (高, 宽, 通道数), #仅在第一层有(可省略)
filters = 卷积核个数,
kernel_size = 卷积核尺寸,
strides = 卷积步长, 默认为1
padding = ‘SAME’ or ‘VALID’, 默认为VALID
activation = ‘relu’ or ‘sigmoid’ or ‘tanh’ or ‘softmax’等 #如有 BN 则此处不用写 )
使用此函数构建卷积层时,需要给出的信息有:
A)输入图像的信息,即宽高和通道数;
B)卷积核的个数以及尺寸,如 filters = 16, kernel_size = (3, 3)代表采用 16 个大小为 3×3 的卷积核;
C)卷积步长,即卷积核在输入图像上滑动的步长,纵向步长与横向步长通常是相同的,默认值为 1;
D)是否进行全零填充,全零填充的具体作用上文有描述;
E)采用哪种激活函数,例如 relu、softmax 等,各种函数的具体效果在前面章节中有详细描述;
这里需要注意的是,在利用 Tensorflow 框架构建卷积网络时,一般会利用 Batch Normalization函数来构建 BN 层,进行批归一化操作,所以在 Conv2D 函数中经常不写 BN。
Batch Normalization(批标准化)
对一小批数据在网络各层的输出做标准化处理,其具体实现方式如图所示。(标准化:使数据符合 0 均值,1 为标准差的分布。)
Batch Normalization 将神经网络每层的输入都调整到均值为 0,方差为 1 的标准正态分布,其目的是解决神经网络中梯度消失的问题 (以 Sigmoid 激活函数为例)
BN 操作的另一个重要步骤是缩放和偏移,值得注意的是,缩放因子 γ 以及偏移因子 β都是可训练参数
BN 操作通常位于卷积层之后,激活层之前,在 Tensorflow 框架中,通常使用 Keras 中的tf.keras.layers.BatchNormalization 函数来构建 BN 层。
在调用此函数时,需要注意的一个参数是 training,此参数只在调用时指定,在模型进行前向推理时产生作用,当 training = True 时,BN 操作采用当前 batch 的均值和标准差;当training = False 时,BN 操作采用滑动平均(running)的均值和标准差。在 Tensorflow 中,通常会指定 training = False,可以更好地反映模型在测试集上的真实效果。
滑动平均(running)的解释:滑动平均,即通过一个个 batch 历史的叠加,最终趋向数据集整体分布的过程,在测试集上进行推理时,滑动平均的参数也就是最终保存的参数。
此外,Tensorflow 中的 BN 函数其实还有很多参数,其中比较常用的是 momentum,即动量参数,与 sgd 优化器中的动量参数含义类似但略有区别,具体作用为滑动平均 running =momentum * running + (1 – momentum) * batch,一般设置一个比较大的值,在 Tensorflow 框架中默认为 0.99。
池化(pooling)
池化的作用是减少特征数量(降维)。最大值池化可提取图片纹理,均值池化可保留背景特征
在Tensorflow框架下,可以利用Keras来构建池化层,使用的是tf.keras.layers.MaxPool2D 函数和 tf.keras.layers.AveragePooling2D 函数,具体的使用方法如下:
tf.keras.layers.MaxPool2D( pool_size = 池化核尺寸, strides = 池化步长, padding = ‘SAME’ or ‘VALID’ ) tf.keras.layers.AveragePooling2D( pool_size = 池化核尺寸, strides = 池化步长, padding = ‘SAME’ or ‘VALID’ )
舍弃(Dropout)
在神经网络的训练过程中,将一部分神经元按照一定概率从神经网络中暂时舍弃,使用时被舍弃的神经元恢复链接
在 Tensorflow 框架下,利用 tf.keras.layers.Dropout 函数构建 Dropout 层,参数为舍弃的概率(大于 0 小于 1)。
利用上述知识,就可以构建出基本的卷积神经网络(CNN)了,其核心思路为在 CNN中利用卷积核(kernel)提取特征后,送入全连接网络。
卷积就是CBAPD
卷积神经网络主要模块
CNN 模型的主要模块:一般包括上述的卷积层、BN 层、激活函数、池化层以及全连接层。
在此基础上,可以总结出在 Tensorflow 框架下,利用 Keras 来搭建神经网络的“八股”套路,在主干的基础上,还可以添加其他内容,来完善神经网络的功能,如利用自己的图片和标签文件来自制数据集;通过旋转、缩放、平移等操作对数据集进行数据增强;保存模型文件进行断点续训;提取训练后得到的模型参数以及准确率曲线,实现可视化等。
搭建卷积神经网络流程
A)import 引入 tensorflow 及 keras、numpy 等所需模块。
B)读取数据集,课程中所利用的 MNIST、cifar10 等数据集比较基础,可以直接从 sklearn 等模块中引入,但是在实际应用中,大多需要从图片和标签文件中读取所需的数据集。
C)搭建所需的网络结构,当网络结构比较简单时,可以利用 keras 模块中的 tf.keras.Sequential 来搭建顺序网络模型;但是当网络不再是简单的顺序结构,而是有其它特殊结构出现时(例如 ResNet 中的跳连结构),便需要利用 class 来定义自己的网络结构。前者使用起来更加方便,但实际应用中往往需要利用后者来搭建网络。
D)对搭建好的网络进行编译(compile),通常在这一步指定所采用的优化器(如 Adam、 sgd、RMSdrop 等)以及损失函数(如交叉熵函数、均方差函数等),选择哪种优化器和损失函数往往对训练的速度和效果有很大的影响,至于具体如何进行选择,前面的章节中有比较详细的介绍。
E)将数据输入编译好的网络来进行训练(model.fit),在这一步中指定训练轮数 epochs 以及 batch_size 等信息,由于神经网络的参数量和计算量一般都比较大,训练所需的时间也会比较长,尤其是在硬件条件受限的情况下,所以在这一步中通常会加入断点续训以及模型参数保存等功能,使训练更加方便,同时防止程序意外停止导致数据丢失的情况发生。
F)将神经网络模型的具体信息打印出来(model.summary),包括网络结构、网络各层的参数等,便于对网络进行浏览和检查。
基础卷积神经网络实现(☆)
代码 数据集cifar10
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
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()
Comments NOTHING