卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

内容

1. 全连接神经网络存在的问题

2. 卷积是什么?

2.1。1D卷积

2.2. 2D卷积

2.3。卷积扩展-步长和填充

3. 通道与卷积核

3.1。单通道-单卷积核

3.2。多通道-单卷积核

3.3。多通道-多卷积核

4. 卷积运算(实战)

4.1。卷积计算

5. 池化层

6. 卷积网络结构

7. 梯度计算

8. LeNet-5(实战)

9. AlexNet ——-第一个现代深度卷积网络模型(理论+实战)

10. BN (理论+实战)

11. VGG (理论+实战)

12. GoogleNet(理论+实战)

13. ResNet(实战)

14. 卷积的变种、应用、总结

1. 全连接神经网络存在的问题

  • 权重矩阵有很多参数

具有高内存占用并且依赖于硬件。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

我们通常取I层中对J层中j点影响最大的前k个点作为集合去计算。

因此我们需要知道第I层节点对第J层节点影响大小分布。

为了解决这个问题,我们一般会引入一些先验知识

  1. 位置近吗?

用一个中心点指向窗口宽度的外侧,我们认为这很重要,这个区域成为感受野

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

上述总的中心点在输出中设为j,感受野是输入中的关于j的一块小区域。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

2. 时间近与否?

除了上面提到的感受野局部相关的思想,我们还可以通过权重共享的思想来减少参数。网络部分区域始终使用一个参数。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

  • 局部未变形特征

自然图像中的对象具有局部不变的特征,例如尺寸缩放、平移、旋转等操作不影响其语义信息。

全连接网络很难提取这些局部不变的特征。

2. 卷积是什么?

两个函数的卷积本质上是先翻转一个函数,然后进行滑动叠加。

1D连续卷积:连续形式,相当于积分求面积。离散时相当于求所有点值的和。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

g(t) 是一个响应信号,f(t)是个输入信号。输出与输入信号与响应的程度相关。如下图,响应  程度越来越小,即使输入信号有时很大,但最后还是会对输入影响越来越小。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

在1D中的翻转可以理解为响应信号的翻转。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

接下来是刷机。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

而叠加,就是上面说的“ 1D连续卷积:连续形式,相当于积分求面积。离散时相当于求所有点值的和。”

2.1. 1D卷积

现在我们假设信号发生器每个时刻t产生一个信号xt,其信号衰减率为wk(上述说的g(t) ),即在k-1个时间步长后,信息为原来的wk倍。(假设W1 = 1,W2 = 1/2 , w3 = 1/4)

时刻t收到的信号yt为当前产生的信息和以前时刻延迟信息的叠加。这也是离散的卷积,其中wk也被称为滤波器(或卷积核)。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

信号序列x和滤波器w,卷积的输出为:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

举一个1D卷积的例子:

输入:卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

卷积核:[-1,0,1]

卷积过程:翻转滑动求和

则第一步:翻转卷积核 [1,0,-1]

输入序列分别与卷积三三相乘并求和(第3步)。假设滑动(第二步)步长为1的情况小,得出输出序列 [-1,2,1,1,0]

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

2.2.2D卷积

在图像处理中,图像以二维矩阵的形式输入到神经网络中,所以我们需要二维卷积。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

步长为1的情况下,先将卷积核翻转,再进行以下这样的滑动,滑动完就进行求和。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

卷积通常用作特征提取器。

计算卷积时也需要进行翻转操作,但卷积的目标是提取特征,所以翻转往往是不必要的。这种卷积运算称为互相关运算。除非另有说明,卷积一般是指“相互关联”。

2.3. 卷积扩展-步长和填充

这里的步长s和零填充p都是针对滤波器的。

步长是指滤波器滑动的时间间隔或步数。

步长越大,输出形状越小。

填充的作用是什么呢?如果我们经过卷积计算后,输出过小,我们想维持特征的数量,这时就要对卷积核进行0填充。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

输出的大小可以通过一个公式算出:其中n为输入矩阵(n*n),m是卷积核(m*m),p是0填充数,s是步长。

\frac{n - m + 2p}{s} + 1

因此,我们需要设置卷积核的大小以保持输出为整数。并且通过公式,我们进一步得到步长越大,输出形状越小。

回到0补充,为了保持特征的数量,我们一般可以通过计算得出正确的补0数。

3. 通道与卷积核

综上所述,我们可以通过将全连接层替换为卷积层来解决权重矩阵参数过多的问题。

3.1. 单通道-单卷积核

单通道用于黑白照片,矩阵中的每个值都是一个标量,代表一个像素。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

3.2. 多通道-单卷积核

一张彩色图片一个像素点有3个通道。输入图片有三个通道,那么我们的卷积核也应该有对于的三个通道。即卷积的通道数与输入通道数一致!

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

每个通道用对应的权重矩阵滑动,求和,最后求和一个权重向量。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

但是单个卷积核只能应用于某个特征的特征提取,而一张图片往往不可能只有一个特征,所以多通道-多卷积核的操作是最常用的.

3.3. 多通道-多卷积核

一个卷积核,虽然有多个通道,但是最后使用求和方式让权重叠加,这仍是一个特征。而两个卷积核可以提取两个特征,最后将两个个特征stack,这样就代表两个特征。多个卷积核同理。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

4. 卷积运算(实战)

4.1. 卷积计算

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
tf.test.is_gpu_available()
import matplotlib.pyplot as plt

# batch_shape + [in_height, in_width, in_channels]
#定义输入
x = tf.random.normal([1,5,5,3])
x

plt.imshow(x[0])

# 定义卷积核
# [filter_height, filter_width, in_channels, out_channels]
w = tf.random.normal([3,3,3,6]) # 6个卷积核在做运算。最后得出6个特征。

out1 = tf.nn.conv2d(x,w,[1,1],[[0, 0], [0, 0], [0, 0], [0, 0]])
# 步长 :1(水平,竖直都移动这个数) , 2(分别代表水平竖直), 4个数
# padding: ‘SAME’输入和输出都是同样大小的。自动在四周补0(直接等于一个字符串)padding = 'SAME',
# 而VALID操作则是不够的地方直接抛弃,不再计算边缘。就是和不补0是一样的结果
out1

out2 = tf.nn.conv2d(x,w,[1,1],'SAME')
out2

# 而VALID操作则是不够的地方直接抛弃,不再计算边缘。就是和不补0是一样的结果
out3 = tf.nn.conv2d(x,w,[1,1],'VALID')
out3

# 与卷积计算不一样的是,卷积层不用设置卷积的权值张量和偏执,他会自动设置这些。
cnnlayer = layers.Conv2D(filters=4,kernel_size=3) #kernel_size 是指卷积核的宽度,并不是通道数。

x

out = cnnlayer(x)
out

5. 池化层

直观上,还进行了特征选择以减少特征的数量,从而进一步减少参数的数量。此外,它还解决了全连接前馈网络不具备空间不变性的问题。

通过卷积层得到的特征数据不具有空间不变性(空间不变性如下图所示,位置、大小、亮度等),只有通过池化层才能具有空间不变性的特性。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

池化层可以理解为对图像进行下采样的过程。对于每次滑动窗口中的所有值,输出最大值、均值或其他方法产生的值,而不是像卷积中那样全部相乘。和解后。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

从上图中我们也可以看出,特征图空间在经过池化层之后也可以大大减少特征空间,而减少特征空间之后还有一个非常直接的好处,那就是减少过拟合。

同时还能增加下一次的kernel感受野。

池化也有一个严重的问题:

如果我们的采样区域对于图片来说太大了,直接的结果就是信息丢失。

6. 卷积网络结构

卷积网络由卷积层组成。池化层和全连接层是交叉堆叠的:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

上面的结构也可以由多个组成,最后加上一个全连接层。

卷积往往是小卷积,大深度。全卷积和少池化的趋势。

表征学习:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

我们总是说层数越深,网络的表达能力就越强,那么如何表达能力呢?所以人们提出了反卷积神经网络来可视化特征图的样子。因此,图像学习也是一个表示学习的过程,先提取颜色特征,再提取边缘特征,再提取高级特征……

7. 梯度计算

卷积反向传播如何工作?

假设我们有一张图片(3*3),卷积核(2*2)。s = 1,p = 0时,通过点积运算得到一个输出。再利用这个输出与真实值比较得到一个loss从而进行反向传播得到梯度,再来进行梯度更新。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

8. LeNet-5(实战)

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
tf.test.is_gpu_available()

batch = 32

model = tf.keras.Sequential([
    layers.Conv2D(6,3),#卷积核的数量是6,卷积核的大小是3*3
    layers.MaxPool2D(pool_size=2,strides=2),#最大值池化层
    keras.layers.ReLU(),
    layers.Conv2D(16,3),#卷积核的数量是16,卷积核的大小是3*3
    layers.MaxPool2D(pool_size=2,strides=2),#最大值池化层
    layers.ReLU(),
    layers.Flatten(),#把原来权重矩阵拉平,方便做全连接层
    layers.Dense(120,activation='relu'),
    layers.Dense(84,activation='relu'),
    layers.Dense(10,activation='softmax')
])

model.build(input_shape=(batch,28,28,1))

model.summary()

model.compile(
    optimizer=keras.optimizers.Adam(),
    loss = keras.losses.CategoricalCrossentropy(),
    metrics=['accuracy']
)

def preprocess(x,y):
    x = tf.cast(x,dtype=tf.float32)/255.
    x = tf.reshape(x,[-1,28,28,1])
    y = tf.one_hot(y,depth=10)
    return x,y
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# 合并
train_db = tf.data.Dataset.from_tensor_slices( (x_train , y_train) )
# 打乱操作
train_db = train_db.shuffle(10000) #buffer_size 缓冲区大小,防止随机每次按照不一定的随机。
# 每次训练只是进行 小批量训练
# batch_size ,每一批训练的样本大小
train_db = train_db.batch(128)
# 预处理
#一开始的数据不满足我门的要求
# 直接就是一个映射
train_db = train_db.map(preprocess)
# 合并
test_db = tf.data.Dataset.from_tensor_slices( (x_test , y_test) )
# 打乱操作
test_db = test_db.shuffle(10000) #buffer_size 缓冲区大小,防止随机每次按照不一定的随机。
# 每次训练只是进行 小批量训练
# batch_size ,每一批训练的样本大小
test_db = test_db.batch(128)
# 预处理
#一开始的数据不满足我门的要求
# 直接就是一个映射
test_db = test_db.map(preprocess)

model.fit(train_db,epochs=5)

model.evaluate(test_db)

9. AlexNet ——-第一个现代深度卷积网络模型(理论+实战)

革新:

  • 使用多GPU进行并行训练
  • 使用了ReLu作为非线性激活函数
  • 使用Dropout防止过拟合

由下图可以看出,Dropout会使一部分神经元在作用时消失。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

Dropout具体工作流程:

  1. 首先,网络中的一般半隐藏神经元被随机删除,输入和输出神经元保持不变。
  2. 然后把输入x通过修改后的网络向前传播,然后把得到的损失值通过修改后的网络进行反向传播。一小批样本执行玩这个过程后,在没有被删除的神经元上按照随机梯度下降法更新对应的(w,b)
  3. 然后重复这个过程:恢复被删除的神经元,随机删除,然后通过删除一些隐藏的神经元进行训练,得到的输出保持不变。也就是说,删除的神经元仅在训练期间被删除。
  • 使用数据增强技术

由于神经网络有很多用于训练的参数,因此具有很强的表达能力。因此,我们需要比较大量的数据,否则容易造成过拟合。当训练数据有限时,可以通过一些变换从现有数据集中生成新数据。对于图像,我们可以进行一些变形(翻转、随机裁剪、平移、颜色和光变换)操作。

此外,AlexNet还进行了主成分分析。

  • 级联池

在LeNet中池化是不重叠的,即吃化的窗口的大小和步长相等的。

而在AlexNet中使用的池化确实可以重叠的,也就是说,在池化的时候,每次移动的步长小于池化窗口的长度,这样就有部分的重叠,重叠池化有个好处就是抑制过拟合。

  • 局部响应归一化(LRN)

LRN是仿造生物学上,活跃的神经元会对相邻的神经元造成抑制现象,好处是:

归一化有助于快速收敛’;

为局部神经元活动创建竞争机制,使响应较大的值变大,抑制其他反馈较小的神经元,增强模型的泛化能力;

局部响应的亮点是要归一化的点,同一通道上附近点的总和,以及乘法、求和、偏执等操作。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
tf.test.is_gpu_available()

x = tf.random.normal([1,227,227,3])
x

#对于x这张图片
#第一层:卷积
c1_c = layers.Conv2D(filters=96,kernel_size=11,strides=4)
out1 = c1_c(x)
out1

#第二层 relu
relu = layers.ReLU()
out2 = relu(out1)
out2

# 第三层:MaxPooling
mp = layers.MaxPool2D(pool_size = (3,3) , strides=2)
out3 = mp(out2)
out3

#第四层:局部归一化
out4 = tf.nn.local_response_normalization(out3,5,2,0.0001,0.75)
out4

class c2(layers.Layer):
    def __init__(self):
        super().__init__()

    def build(self, input_shape):
        self.w = tf.random.normal([5,5,input_shape[-1] ,256])

    def call(self, inputs, **kwargs):
        return tf.nn.conv2d(inputs,filters=self.w,strides=1,padding = [[0, 0], [2, 2], [2, 2], [0, 0]])

## 卷积核的数量有点像特征的数量。

x = tf.random.normal([1,227,227,3])

alexNet = keras.Sequential([
    #第一大层
    layers.Conv2D(96,11,4),
    layers.ReLU(),
    layers.MaxPool2D((3,3),2),
    layers.BatchNormalization(),

    #第二大层
    c2(),
    layers.ReLU(),
    layers.MaxPool2D((3,3),2),
    layers.BatchNormalization(),

    #第三大层
    layers.Conv2D(384,3,1,padding='SAME'),
    layers.ReLU(),

    #第四大层
    layers.Conv2D(384,3,1,padding='SAME'),
    layers.ReLU(),

    #第五大层
    layers.Conv2D(256,3,1,padding='SAME'),
    layers.ReLU(),
    layers.MaxPool2D((3,3),2),

    #第六层
    layers.Flatten(),
    layers.Dense(4096),
    layers.ReLU(),
    layers.Dropout(0.25),

    #第七层
    layers.Flatten(),
    layers.Dense(4096),
    layers.ReLU(),
    layers.Dropout(0.25),

    layers.Dense(1000),
    layers.Softmax()
])

alexNet(x).shape

alexNet.build(input_shape=([None,227,227,3]))
alexNet.summary()

# 读取数据
# alexNet.compile()
# alexNet.fit(train_db,epochs=5)

10. BN (理论+实战)

BN层:batch normalization

为什么要有normalization(归一化)

为了解决全连接神经网络中参数过多的问题,我们选择卷积,可以减少参数,增加网络的深度。然而,网络越深,它对超参数就越敏感,即超参数的微小变化可能对网络训练轨迹产生很大影响。

为了解决这个问题,有人设计了一个参数标准化的准则。

而BN层也是这种标准化,他可以让网络超参初始化能够更加的随意、自由。切网络的收敛速度会更快,性能也会更好。此后卷积层,BN层,ReLu层,池化层一度成为网络结构的标配。

那么当第一层的输出作为第二层的输入时,归一化的具体好处是什么?

之前我们在做MNIST数据集手写数字识别的时候,开始做了一个归一化的操作,即把所有数除以255,让所有数全部在0到1之间,这样有什么好处呢?

1. 网络学习的本质:输入数据的一个分布,一旦训练数据和测试数据分布不同,泛化能力就不同。所以我们在选择样本的时候都要满足独立同分布。

2. 如果分布不同的话,网络收敛速度也会降低。

3. 输入的图片可能只是光照不同,可是读数据的时候,差别可能会很远,但事实上他们是一个东西。

为什么我们需要在模型中进行归一化?

模型训练后,训练的参数就会发生变化,比如W,W变化的时候,下一层输入也会发生相应变化,这种现象叫做ICS。这种偏移现象我们也不希望发生,所以希望在输入到下一层的时候,我们也希望它能够标准化。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

如上图,梯度趋近 与0和趋近于正无穷的地方,通过标准化,让x趋近于0和正无穷的数趋近于0,也就是让样本几乎都在这中间。这样就减轻了梯度弥散的现象。

再举个例子,现有两个输入,y = x1*w1 + x2*w2,

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

当两个输入分布相同时(如右图),它可以逐渐找到最优值,但当分布不同时(如左图),达到最优值是非常曲折的。

结论是我们希望分布小而接近。

如何标准化?

我们可以在输入的时候直接加一个预处理,比如,我们希望均值为0,方差为1.

原本经过ReLu小于0的样本会被截断,但是在进行一般标准化卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)后,有部分样本又会小于0,因此又要经过一些变化(如下图),乘以的是缩放/放大,加的数是平移,如刚讲的我们希望大于0的话,让样本往右适当平移即可,有其他需求就进行相应的变换即可。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

训练过程是:1.先计算均值,和方差。再根据上图公式 ,计算BN层的输入。同时按照下图更新方式,更新全局统计值均值和方差。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

测试过程是:直接计算并输出x_test。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

之后,执行反向传播。

BN存在的问题:

如果下图中,batchSize设置的过小的话,或者样本数据本来就很少,那么那些数据并不能代表整体的分布,但是太大的话,硬件又不能支持训练所需的条件。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

为了解决BN的这个问题,有人又提出了GN层(Group Normalization)。

下图红色曲线是GN,其在batch_size逐渐增大的时候,错误率任然很稳定。而BN(下图蓝线 )却在逐步上升。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

H、W是高宽,C是通道数,N是batch坐标轴,如下图知,BN是在做batch上的归一化(N*H*W).Layer Norm 是在channel上做归一化(C*H*W),Instance Norm统计每个样本每个通道上的均值和方差(H*W)。GN在channel上做了一个分组,在每个组上做归一化(G*H*W)。因此GN就与batch是没有关系的。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
tf.test.is_gpu_available()

# 构造BN层输入

# [b,h,w,c]
x = tf.random.normal([100,32,32,3])

x = tf.reshape(x,[-1,3])

#第一步,计算三个通道上的均值μ
tf.reduce_mean(x,axis = 0)

model = keras.Sequential([
    layers.Conv2D(6,3),
    layers.BatchNormalization(),
    layers.ReLU(),
    layers.MaxPool2D()
])

out1 = model(x)
model.variables

out2 = model(x,training=False)
model.variables

11. VGG (理论+实战)

VGG的改进:

1. 显著增加了网络层数(16到19)

2. 大大减小了Kernnel size,全部使用3*3的CONV stride 1 padding 1的卷积核,相对于AAlexNet中的11*11、5*5、3*3的卷积核,参数量更小,计算代价更低。

3. 采用了更小的池化层:2*2的窗口,步长为2。不过AlexNet的池化层好处就是池化窗口宽度大于步长,因此是个叠加的池化层。

VGG的优点:

1. VGGNet的结构非常简洁,整个网络都使用了同样大小的卷积核尺寸(3*3)和最大池化尺寸(2*2)

2. 几个小滤波器(3*3)卷积层的组合比一个大滤波器(5*5)卷积层要好。

3. 验证了可以通过不断加深网络的结构来提升性能。

VGG的缺点:

消耗资源较多,全连接层参数较多。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets, layers , optimizers , models , regularizers
import numpy as np
import matplotlib.pyplot as plt
tf.__version__

# 零均值归一化
def normalize(X_train,X_test):
    #归到0到1之间
    X_train = X_train / 255.
    X_test = X_test / 255.

    mean = np.mean(X_train,axis=(0,1,2,3))
    std = np.std(X_train , axis=(0,1,2,3))
    print("mean: ",mean ,"std: ",std)

    # 0均值标准化
    X_train = (X_train - mean) / (std + 1e-7)
    X_test = (X_test - mean) / (std + 1e-7) #注意均值方差用的都是训练集的。

    return X_train,X_test

#读入数据
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
(x_train.shape, y_train.shape), (x_test.shape, y_test.shape)

#归一化数据
x_train,x_test = normalize(x_train,x_test)

def preprocess(x ,y):
    x = tf.cast(x,tf.float32)
    y = tf.cast(y,tf.int32)
    y = tf.squeeze(y,axis = 1)
    y = tf.one_hot(y,depth=10)
    return x,y

train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train))
train_db = train_db.shuffle(50000).batch(128).map(preprocess)

test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = train_db.shuffle(50000).batch(128).map(preprocess)

# VGG 16
num_classes = 10 #当前分类有10类,VGG16是1000,的数据集

model = keras.Sequential()

model.add(layers.Conv2D(64 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(64 , (3,3) ,padding='same', activation='relu'))
model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(128 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(128 , (3,3) ,padding='same', activation='relu'))
model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(256 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(256 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(256 , (3,3) ,padding='same', activation='relu'))
model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(512 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(512 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(512 , (3,3) ,padding='same', activation='relu'))
model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(512 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(512 , (3,3) ,padding='same', activation='relu'))
model.add(layers.Conv2D(512 , (3,3) ,padding='same', activation='relu'))
model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Flatten())
model.add(layers.Dense(256 , activation='relu')) # VGG 16为4096,但是这里没有必要,因为其实输入的像素点也不是那么多
model.add(layers.Dense(128 , activation='relu')) # VGG 16为4096,
model.add(layers.Dense(num_classes,activation='softmax'))

model.build(input_shape = (None,32,32,3))

model.summary()

model.compile(optimizer=optimizers.Adam(0.0001),
              loss=keras.losses.CategoricalCrossentropy(from_logits=True),# API上说加上from_logits=True这句话可能可以得到更好的结果
              metrics=['accuracy']
              )

history = model.fit(train_db,epochs=50) #用history可以得到一些监控指标

import matplotlib.pyplot as plt

plt.plot(history.history['loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()

model.evaluate(test_db)

我们可以通过最后得到的loss知道,训练到还没到一半,他的loss逐步上升,开始朝着不好的方向训练,那原因是什么呢?可能是学习率比较大。初始化等多种原因。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

我们一般可以:

1. 调低学习率。

2. 调整参数的初始化方法。

3. 调整输入数据的标准化方法

4. 修改loss函数

5. 增加正则化

6.使用BN/GN层(中间层数据的标准化)(可以不受学习率的影响)

7.使用dropout

改进版:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets, layers , optimizers , models , regularizers
import numpy as np
import matplotlib.pyplot as plt
tf.__version__

# 零均值归一化
def normalize(X_train,X_test):
    #归到0到1之间
    X_train = X_train / 255.
    X_test = X_test / 255.

    mean = np.mean(X_train,axis=(0,1,2,3))
    std = np.std(X_train , axis=(0,1,2,3))
    print("mean: ",mean ,"std: ",std)

    # 0均值标准化
    X_train = (X_train - mean) / (std + 1e-7)
    X_test = (X_test - mean) / (std + 1e-7) #注意均值方差用的都是训练集的。

    return X_train,X_test

#读入数据
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
(x_train.shape, y_train.shape), (x_test.shape, y_test.shape)

#归一化数据
x_train,x_test = normalize(x_train,x_test)

def preprocess(x ,y):
    x = tf.cast(x,tf.float32)
    y = tf.cast(y,tf.int32)
    y = tf.squeeze(y,axis = 1)
    y = tf.one_hot(y,depth=10)
    return x,y

train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train))
train_db = train_db.shuffle(50000).batch(128).map(preprocess)

test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.shuffle(50000).batch(128).map(preprocess)

# VGG 16
num_classes = 10 #当前分类有10类,VGG16是1000,的数据集
weight_decay =  0.000
model = keras.Sequential()

model.add(layers.Conv2D(64 , (3,3),padding='same',kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.3))

model.add(layers.Conv2D(64 , (3,3) ,padding='same',kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())

model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(128 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.4))

model.add(layers.Conv2D(128 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())

model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(256 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.4))
model.add(layers.Conv2D(256 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.4))
model.add(layers.Conv2D(256 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())

model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(512 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.4))
model.add(layers.Conv2D(512 , (3,3) ,padding='same',kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.4))
model.add(layers.Conv2D(512 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())

model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Conv2D(512 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.4))
model.add(layers.Conv2D(512 , (3,3) ,padding='same',kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())
model.add(layers.Dropout(0.4))
model.add(layers.Conv2D(512 , (3,3) ,padding='same', kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())

model.add(layers.MaxPool2D(pool_size=(2,2) , strides=2)) #参数高宽减半

model.add(layers.Flatten())
model.add(layers.Dense(512 , kernel_regularizer=regularizers.l2(weight_decay)))
model.add(layers.Activation('relu'))
model.add(layers.BatchNormalization())

model.add(layers.Dropout(0.5))
model.add(layers.Dense(num_classes,activation='softmax'))

model.build(input_shape = (None,32,32,3))

model.summary()

model.compile(optimizer=optimizers.Adam(0.0001),
              loss=keras.losses.CategoricalCrossentropy(from_logits=True),# API上说加上from_logits=True这句话可能可以得到更好的结果
              metrics=['accuracy']
              )
history = model.fit(train_db,epochs=50) #用history可以得到一些监控指标

import matplotlib.pyplot as plt

plt.plot(history.history['loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()

model.evaluate(test_db)

50次epoch后,loss仍然在下降。结果还可以更好

loss收敛图:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

12. GoogleNet(理论+实战)

GoogleNet也是一个有很高的的深度的模型,但是他的参数量(500完)却比AlexNet以及VGG要小很多。VGG大概是GoogleNet的3倍。

GoogleNet是怎样进一步提高神经网络的性能的呢?首先来看深度网络结构存在的问题:

1. 增加深度(增加层数),增加宽度(增加神经元的数量),可以提高网络性能,但是会带来而更多的参数,如果训练数据集是有限的,就很容易造成一个过拟合。

2. 网络过大、参数过多,计算复杂度大,难以应用

3. 网络越深,容易出现梯度弥散的问题(梯度往后越容易消失),难以优化模型。

有没有办法既优化网络又提高性能计算?

全连接可以变成稀疏连接吗?实际上,在相乘密集矩阵或稀疏矩阵时,各个地方的数字都会相乘。我们可以利用稀疏矩阵的稀疏性来直观地增加计算时间吗?

将稀疏矩阵聚合成密集矩阵已被证明可以提高计算性能。

GoogleNet提出了Inception基本结构

如何设计卷积核的大小是一个很重要的问题,在Inception结构中,一个卷积层包含多个不同大小的卷及操作,称为Inception模块。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

Inception模块可以同时使用不同大小的卷积核,并将得到的特征映射在深度上堆叠起来作为输出的特征映射。当然即使是不同的卷积核,我们也希望结果大小是一样的,因此会使用一个‘SAME’的操作。

通过设计稀疏的网络结构,可以生成密集的数据,从而提高网络性能,保证计算资源的效率。

原始的Inception基本结构中5*5的卷积核会造成输出厚度过大,为了避免这样的情况。我们可以在3*3,5*5之前,maxpooling之后分别加上1*1的卷积核,以降低特征图的厚度。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

而GoogleNet就是用多个Inception模块和少量汇聚层堆叠。

V2版本中,发现,5*5的参数量是3*3的接近3倍,于是就提出,用两层3*3的卷积层来替代5*5。之后又想能不能用更小的卷积核呢?于是就考虑了n*1的网络。

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
tf.__version__

# 零均值归一化
def normalize(X_train,X_test):
    #归到0到1之间
    X_train = X_train / 255.
    X_test = X_test / 255.

    mean = np.mean(X_train,axis=(0,1,2,3))
    std = np.std(X_train , axis=(0,1,2,3))
    print("mean: ",mean ,"std: ",std)

    # 0均值标准化
    X_train = (X_train - mean) / (std + 1e-7)
    X_test = (X_test - mean) / (std + 1e-7) #注意均值方差用的都是训练集的。
    return X_train,X_test

def preprocess(x ,y):
    x = tf.cast(x,tf.float32)
    y = tf.cast(y,tf.int32)
    y = tf.squeeze(y,axis = 1)
    y = tf.one_hot(y,depth=10)
    return x,y

#读入数据
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
(x_train.shape, y_train.shape), (x_test.shape, y_test.shape)

#归一化数据
x_train,x_test = normalize(x_train,x_test)

train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train))
train_db = train_db.shuffle(50000).batch(128).map(preprocess)

test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.shuffle(50000).batch(128).map(preprocess)

class ConvBNRelu(keras.Model):
    # Conv + BN + ReLu,加了一个BN层
    def __init__(self,filters,kernelSize=3,strides=1,padding='same'):
        super().__init__()

        self.model = keras.models.Sequential([
            keras.layers.Conv2D(filters=filters,
                                kernel_size=kernelSize,
                                strides=strides,
                                padding=padding
                                ),
            keras.layers.BatchNormalization(),
            keras.layers.ReLU()
        ])

    def call(self,x,training=None):
        x =self.model(x,training=training)
        return x

# 注意这里的卷积 和 池化 是并行的

class Inception_v2(keras.Model):
    # 构造Inception_v2模块
    def __init__(self , filters , strides = 1):
        # strides 控制是否缩减特征图,=1 不缩减, =2 ,缩减
        super().__init__()

        self.conv1_1 = ConvBNRelu(filters , kernelSize=1,strides=1)
        self.conv1_2 = ConvBNRelu(filters , kernelSize=3,strides=1)
        self.conv1_3 = ConvBNRelu(filters , kernelSize=3,strides=strides)

        self.conv2_1 = ConvBNRelu(filters , kernelSize=1,strides=1)
        self.conv2_2 = ConvBNRelu(filters , kernelSize=3,strides=strides)

        self.pool = keras.layers.MaxPooling2D(pool_size=3,
                                              strides=strides,
                                              padding='SAME')

    def call(self, inputs, training=None):
        x1_1 = self.conv1_1(inputs,training=training)
        x1_2 = self.conv1_2(x1_1,training=training)
        x1_3 = self.conv1_3(x1_2,training=training)

        x2_1 = self.conv2_1(inputs , training=training)
        x2_2 = self.conv2_2(x2_1,training = training)

        x3 = self.pool(inputs)

        # 在最后一个,即通道维度上
        x = tf.concat([x1_3,x2_2,x3],axis = 3)

        return x

class GoogleNet(keras.Model):

    def __init__(self , num_blocks,num_classes,filters = 16):
        # 构造GoogleNet模型
        # num_blocks:包含具有相同filter的n个Inception_v2模块的模块数

        super().__init__()

        self.filters = filters
        self.conv1 = ConvBNRelu(filters)

        self.blocks = keras.models.Sequential()

        for block_id in range(num_blocks):
            for Inception_id in range(2): #每个block里有2个Inception,也可以设置变量
                if Inception_id == 0:
                    block = Inception_v2(self.filters , strides=2) #缩放
                else:
                    block = Inception_v2(self.filters , strides=1) #不缩放
                self.blocks.add(block)

            #下一层的block中的卷积数量闭上一层的卷积核多一倍
            self.filters *= 2

        self.avg_pool = keras.layers.GlobalAvgPool2D()
        self.fc = keras.layers.Dense(num_classes,activation='softmax')

    def call(self , x , training = None):
        out = self.conv1(x , training=training)
        out = self.blocks(out , training=training)
        out = self.avg_pool(out)
        out = self.fc(out)
        return out

model = GoogleNet(2,10)
model.build(input_shape=(None , 32,32,3))
model.summary()

model.compile(optimizer=keras.optimizers.Adam(0.0001),
              loss=keras.losses.CategoricalCrossentropy(from_logits=True),# API上说加上from_logits=True这句话可能可以得到更好的结果
              metrics=['accuracy']
              )

history = model.fit(train_db,epochs=50) #用history可以得到一些监控指标

import matplotlib.pyplot as plt

plt.plot(history.history['loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()

model.evaluate(test_db)

Loss收敛图:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

13. ResNet(实战)

上面已经说了,深度越大,网络表达能力越强,但是深度越大,有什么害处吗?

会带来梯度消失的问题。

解决方案:残差网络:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

通过在非线性卷积层中添加直接连接的边,在另一个方向直接穿过当前网络,提高了星系传播效率。值是跨层链接的一个重要思想。

将目标函数分为两部分:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

import os
import tensorflow as tf
import numpy as np
from tensorflow import keras
tf.__version__

# 零均值归一化
def normalize(X_train,X_test):
    #归到0到1之间
    X_train = X_train / 255.
    X_test = X_test / 255.

    mean = np.mean(X_train,axis=(0,1,2,3))
    std = np.std(X_train , axis=(0,1,2,3))
    print("mean: ",mean ,"std: ",std)

    # 0均值标准化
    X_train = (X_train - mean) / (std + 1e-7)
    X_test = (X_test - mean) / (std + 1e-7) #注意均值方差用的都是训练集的。
    return X_train,X_test

def preprocess(x ,y):
    x = tf.cast(x,tf.float32)
    y = tf.cast(y,tf.int32)
    y = tf.squeeze(y,axis = 1)
    y = tf.one_hot(y,depth=10)
    return x,y

#读入数据
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
(x_train.shape, y_train.shape), (x_test.shape, y_test.shape)

#归一化数据
x_train,x_test = normalize(x_train,x_test)

train_db = tf.data.Dataset.from_tensor_slices((x_train,y_train))
train_db = train_db.shuffle(50000).batch(128).map(preprocess)

test_db = tf.data.Dataset.from_tensor_slices((x_test,y_test))
test_db = test_db.shuffle(50000).batch(128).map(preprocess)

#第一个s=2,后面的都为1
class ResnetBlock(keras.Model):
    def __init__(self , filters , kernelsize = 3 , strides = 1,padding = 'same'):
        super().__init__()

        self.conv_model = keras.models.Sequential([
            # 第一个卷积层
            keras.layers.Conv2D(filters=filters,
                                kernel_size=kernelsize,
                                strides=strides,
                                padding=padding),
            keras.layers.BatchNormalization(),
            keras.layers.ReLU(),
            # 第2个卷积层
            keras.layers.Conv2D(filters=filters,
                                kernel_size=kernelsize,
                                strides=1,
                                padding=padding),
            keras.layers.BatchNormalization()
        ])
        if strides != 1:
            # 即f(x)和 x形状不同的时候就要构建identity让后面相加的时候保持形状相同。
            self.identity = keras.models.Sequential([
                keras.layers.Conv2D(filters=filters,
                                    kernel_size=1,
                                    strides=strides,
                                    padding=padding
                                    ),
            ])
        else:
            # 保持原样输出
            self.identity = lambda x:x

    def call(self , inputs,training=None):
        conv_out = self.conv_model(inputs)
        identity_out = self.identity(inputs)
        out = conv_out + identity_out
        out =tf.nn.relu(out)
        return out

class ResNet(keras.Model):
    def __init__(self,block_list,num_classes):
        # block_list: 卷积核的个数和block的数量 eg. [ [64,3],[128,4] ]
        super().__init__()

        self.conv__initial = keras.layers.Conv2D(16,5,1,padding='same')
        self.blocks = keras.models.Sequential()

        # build all the blocks
        for block_id in range(len(block_list)):
            for layer_id in range(block_list[block_id][1]):
                if layer_id == 0:
                    # 每个方块中的第一个conv的stride = 2
                    self.blocks.add(ResnetBlock(filters=block_list[block_id][0],strides=2))
                else:
                    # 其他conv的stride = 1
                    self.blocks.add(ResnetBlock(filters=block_list[block_id][0],strides=1))
        self.final_bn = keras.layers.BatchNormalization()
        self.avg_pool = keras.layers.GlobalAvgPool2D()
        self.fc = keras.layers.Dense(num_classes)

    def call(self,inputs,training=None):
        out = self.conv__initial(inputs)
        out = self.blocks(out , training=training)
        out = self.final_bn(out , training = training)
        out =tf.nn.relu(out)
        out = self.avg_pool(out)
        out = self.fc(out)

        return out

num_classes = 10
batch_size = 32
epochs = 10

model = ResNet([ [64,2] , [128,3] , [256, 4] ] , num_classes)
model.compile(optimizer=keras.optimizers.Adam(0.001),
                loss = keras.losses.CategoricalCrossentropy(from_logits=True),
                metrics = ['accuracy'])

model.build(input_shape=(None , 32,32,3))
model.summary()

history = model.fit(train_db,epochs=10) #用history可以得到一些监控指标

import matplotlib.pyplot as plt

plt.plot(history.history['loss'])
plt.title('model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.show()

model.evaluate(test_db)

Loss收敛图:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

在训练集上正确率高达0.97,但是在测试集上,却只有0.77的正确率。

14. 卷积的变种、应用、总结

其他卷积类型:

  • 转置卷积:低纬特征映射到高位特征,一般用在DCGAN,可以逐层放大特征图。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

  • 空洞卷积:直接增大感受野。却也增加了特征数,参数量也随之增加。因此可以使用空洞卷积,感受野变大了,但是取得值任然是3*3的。

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

CNN总结:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

进化史:

卷积神经网络(含LeNet-5、AlexNet、BN、VGG、GoogleNet、ResNet实战)

卷积应用:

图像识别,目标检测,图像分割,OCR,图像生成,对抗样本。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2022年3月23日 下午6:24
下一篇 2022年3月23日 下午7:05

相关推荐