深度学习CNN笔记
CNN是什么?
卷积神经网络(Convolutional neural networks 简称 CNN
首先介绍些背景。当你第一次听到卷积神经网络这一术语,可能会联想到神经科学或生物学,那就对了。可以这样说。CNN 的确是从视觉皮层的生物学上获得启发的。视觉皮层有小部分细胞对特定部分的视觉区域敏感。Hubel 和 Wiesel 于 1962 年进行的一项有趣的试验详细说明了这一观点,他们验证出大脑中的一些个体神经细胞只有在特定方向的边缘存在时才能做出反应(即放电)。例如,一些神经元只对垂直边缘兴奋,另一些对水平或对角边缘兴奋。Hubel 和 Wisesl 发现所有这些神经元都以柱状结构的形式进行排列,而且一起工作才能产生视觉感知。这种一个系统中的特定组件有特定任务的观点(视觉皮层的神经元细胞寻找特定特征)在机器中同样适用,这就是 CNN 的基础。
全连接NN: 每个神经元与前后相邻层的每一个神经元都有连接关系,输入时特征,输出为预测的结果。
实践证明,全连接网络对识别和预测都有非常好的效果,在Tensorflow中的手写数字识别Demo中,使用28X28的灰度图,仅两层全连接网络,就有十万多个待训练参数,在实际项目中输入神经网络的是具有更高分辨率的彩色图片,会导致送入全连接网络的输入特征过多,随着隐藏层层数的增加,网络规模过大,待优化参数过都,会造成模型**“过拟合"**
为了减少输入特征,会先对原始图片进行特征提取,把提取出来的特征再送给全连接网络,让全连接网络输出识别结果。
卷积计算便是一种可认为有效的提取图像特征的方法
- 一般会用一个正方形的卷积核,按指定步长,在输入特征图上滑动,遍历输入特征图中的每个像素点。每一个步长,卷积核会与输入特征图出现重合区域,重合区域对应的元素相乘、求和再加上偏置项得到输出特征的一个像素点。
如果是一个单通道灰度图,使用一个深度为1的单通道卷积核,如果是一个三通道的彩色图,可以使用一个 3x3X5的卷积核,或者 3x3x3的卷积核
- 输入特征图的深度(channel数),决定了当前层卷积核的深度
由于每个卷积核在卷积计算后,会得到一个输出特征图,所以当前层使用了几个卷积核,就有几张输出特征图
- 当前卷积核的个数,决定了当前层输出特征图的深度。
如果你觉得某层模型的特征提取能力不足,可以在这一层多用几个卷积核,提高这一层的特征提取能力
每个颗粒中存储着代训练参数会被梯度下降法更新,卷积就是利用立体卷积核,实现了参数空间的共享。 这些颗粒组成一起就是一个卷积核
卷积核的计算过程
对于一个单通道灰度图,选择单通道卷积核,这个例子的输入特征图是一个5X5的图片,我们选用3x3的单通道卷积核,滑动步长为1,每滑动一步输入特征图与卷积核的9个元素重合,他们对应元素相乘求和再加上偏置项b。
如图,假设我们有用一个3X3的卷积核扫描到了左侧这么一副像素图,那么计算为:
(原卷积核中的像素点值 X 偏置项中的像素点值) + (原卷积核中的像素点值 X 偏置项中的像素点值).... + 整体偏置项值
感受野
感受野(Receptive Field): 卷积神经网络各输出特征图中的每个像素点,在原始输入图片映射区域的大小
一张5X5的原始输入图,经过一层5X5的卷积核之后,得到一个小方块,这个小方块的感受野是5
同样一张5X5的原始输入图,经过一层3X3的卷积核之后,得到一个3X3的像素图,这像素图有9个小方块,每个小方块映射到原始图的感受野是3,将这个3X3的像素图再经过一层3X3的卷积核,可以得到一个小方块,这个小方块映射原始输入图的感受野是5
所以,在这个原始输入图中,一个5X5的卷积核和两层3X3的卷积核提取特征的能力是一样的,我们该如果决定使用哪种呢?
那么就应该从计算量的角度出发去考虑
对于两层3X3的卷积核,待训练参数共有18个
对于一层5X5的卷积核,待训练参数共有 25个
假设输入特征图宽、高为X,卷积步长为1
那么两层卷积核计算量: 18x² - 108x + 180 而一层5x5的卷积核,计算量为: 25x² - 200x + 400
- 当 x(像素图边长)>10时,两层3x3卷积核 优于一层5 x 5 卷积核
全零填充
有时候我们希望卷积计算保持输入特征图的尺寸不变,这时候可以使用全零填充,在输入特征图周围填充0
一个5x5的输入原始图,经过全零填充后,使用3x3卷积核输出的特征图,仍是5x5的体积
在Tensorflow中,使用padding表示全零填充, padding = ‘SAME' 全零填充 padding = 'VALID' 不全零填充
Tensorflow 描述卷积层
Tensorflow 给出了计算卷积核的函数
tf.keras.layers.Conv2D(
fiters = '卷积核个数',
kernel_size = '卷积核尺寸,核高h,核宽w',
strides = '滑动步长'
padding = '是否全零填充 same or valid'
activation = '激活函数 relu or sigmoid or tanh or softmax',
input_shape = '输入特征图维度, 高、宽、通道数 (可省略)'
)
下面是一份使用了3个不同代码风格的卷积核代码段
model = tf.keras.models.Sequential([
Conv2D(6, 5, padding='valid', activation='sigmoid'),
MaxPool2D(2, 2),
Conv2D(6, (5,5), padding='valid', activation='sigmoid'),
Maxpool2D(2, 2),
Conv2D(filters=6, kernel_size=(5,5), padding='vaild', activation='sigmoid'),
Maxpool2D(pool_size(2, 2), strides=2),
Flatten(),
Dense(10, activation='softMax')
])
批标准化
批标准化(Batch Normalization, BN),神经网络对0附件的数据更敏感
-
标准化: 使数据符合0均值,1为标准差的分布
-
批标准化: 对一小批数据(batch),做标准化处理
代码示例
'''
CNN 基础Demo
@Auther: KongShan
@Date: 2021-09-09
'''
import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
# 导入CNN所需要的依赖
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
from tensorflow.keras import Model
np.set_printoptions(threshold=np.inf)
# 导入cifar10彩色图片训练库
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# 将特征值RGB参数除255,使其归一 0~1
x_train, x_test = x_train / 255.0, x_test / 255.0
"""
配置CNN
"""
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.p1 = MaxPool2D(pool_size=(2, 2), strides=2, padding='same')
# 舍弃神经元函数
self.d1 = Dropout(0.2)
# 拉直神经元
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
# 经过卷积核特征提取
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_onlt=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()
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()
'''
##### 展示区域 #####
显示训练集呵验证集的acc呵loss曲线
'''
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.histor['loss']
plt.subplot(1, 2, 1)
plt.plot(loss, label="Training Loss")
plt.title("Training Loss")
plt.legend()
plt.show()
经典卷积网络
| 卷积网络名称 | 发明年份 |
|---|---|
| LeNet | 1998 |
| AlexNet | 2012 |
| VGGNet | 2014 |
| InceptionNet | 2014 |
| ResNet | 2015 |
LeNet5
LeNet由 Yann LeCun于1998提出,卷积网络开篇之作,通过共享卷积核减少了网络的参数
LeNet5网络模型使用 两层卷积神经,那个年代还没有 批处理 、全零填充 、Dropout 再经过 三层全连接 分别使用 128 84 10 个神经元,最后一个激活函数采用softmax 保证正态分布
代码示例
'''
CNN 基础Demo
@Auther: KongShan
@Date: 2021-09-09
'''
import tensorflow as tf
import os
import numpy as np
from matplotlib import pyplot as plt
# 导入CNN所需要的依赖
from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense
from tensorflow.keras import Model
np.set_printoptions(threshold=np.inf)
# 导入cifar10彩色图片训练库
cifar10 = tf.keras.datasets.cifar10
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# 将特征值RGB参数除255,使其归一 0~1
x_train, x_test = x_train / 255.0, x_test / 255.0
"""
配置CNN
"""
class LeNet5(Model):
'''
LeNet5网络模型使用 两层卷积神经,那个年代还没有 批处理 、全零填充 、Dropout
再经过 三层全连接 分别使用 128 84 10 个神经元,最后一个激活函数采用softmax 保证正态分布
'''
def __init__(self):
super(LeNet5, self).__init__()
# 第一层卷积层 使用6个卷积核
self.c1 = Conv2D(filters=6, kernel_size=(5, 5), activation='sigmoid')
# 第一层池化层
self.p1 = MaxPool2D(pool_size=(2, 2), strides=2)
# 第二层卷积层 使用16个卷积核
self.c2 = Conv2D(filters=16, kernel_size=(5, 5), activation='sigmoid')
# 第二层池化层
self.p2 = MaxPool2D(pool_size=(2, 2), strides=2)
# 拉直神经元
self.flatten = Flatten()
self.f1 = Dense(128, activation='sigmoid')
self.f2 = Dense(84, activation='sigmoid')
self.f3 = Dense(10, activation='softmax')
def call(self, x):
# 拿到输入特征X
# 经过第一层卷积核特征提取
x = self.c1(x)
# 经过第一层池化层
x = self.p1(x)
# 经过第二层卷积核特征提取
x = self.c2(x)
# 经过第二层池化层
x = self.p2(x)
# 拉直神经元
x = self.flatten(x)
x = self.f1(x)
x = self.f2(x)
y = self.f3(x)
return y
model = LeNet5()
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_onlt=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()
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()
'''
##### 展示区域 #####
显示训练集呵验证集的acc呵loss曲线
'''
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['loss']
plt.subplot(1, 2, 1)
plt.plot(loss, label="Training Loss")
plt.title("Training Loss")
plt.legend()
plt.show()