卷积神经网络(含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. 全连接神经网络存在的问题

  • 权重矩阵的参数非常多

        有较高的内存占用,依赖硬件。

                                        

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

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

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

  1. 位置近与否?

        以一个中心点到之外的窗口宽的位置,我们认为他很重要,这个区域成为感受野

                                                

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

                                                              

   2. 时间近与否?

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

                                                  

  • 局部不变形的特征

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

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


2. 卷积是什么?

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

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

                                                        

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

                                

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

                                

        接下来是滑动。

                                 

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


      2.1. 1D卷积

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

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

                                                

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

                                                        

        举一个1D卷积的例子:

                输入:  

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

        卷积过程:翻转滑动求和 

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

        

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

                                 


          2.2. 2D卷积

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

                                        

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

           

        卷积常备作为特征的提取器。

        计算卷积时还需要进行翻转操作,但卷积的目标是:提取特征,因此往往翻转是不必要的,这样的卷积操作被称为互相关操作,除非特别声明,卷积一般是指“互相关”的。


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

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

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

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

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

                                

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

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

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

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


3. 通道与卷积核

        综上我们可以通过卷积层代替全连接层解决权重矩阵参数过过多的问题。 

              3.1. 单通道-单卷积核

        单通道是对于黑白照片而言的,他矩阵中的每一个值都是一个标量,表示像素点。

        

              3.2. 多通道-单卷积核

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

                                

        每个通道都与对应的权重矩阵滑动,求和,最后再进行一个权重向量的求和。

                 


         但是单卷积核只能适用于对某一特点的特征提取的作用,但往往图片也不可能只有一个特征,因此我们最常用的是多通道-多卷积核的操作。

             3.3. 多通道-多卷积核

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

                         

                         


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. 池化层

        直观上也会进行一个特征选择,从而降低特征的数量,从而进一步降低参数的数量,除此之外,他还解决全连接前馈网络不具备空间不变性的问题。

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

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

        我们从上图也可以看出经过池化层后特征映射空间也能在很大幅度上减小特征空间,在减小特征空间后又有一个很直接的好处,那就是减少过拟合。

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

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

        我们的采样区如果对于图片过大的时候,那么带来的直接的结果就是造成信息损失。

        


6. 卷积网络结构

        卷积网络是由卷积层。池化层、全连接层交叉堆叠而成:

         而上面的这个结构肯能也会由多个组成,最后再加上一个全连接层。

        卷积趋向于小卷积、大深度。全卷积、少池化的趋势发展。

        

        表示学习:

        我们总说层数越深,网络的表达能力就越强,那怎么样才能表示能力强呢?于是人们提出了反卷积神经网络使得可视化出特征映射到底是什么样的情况。因此图片学习也是一个表示学习的过程,先提取颜色特征再提取边缘特征再提取高阶的特征……

7. 梯度计算

        卷积的反向传播是怎样进行的呢?

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


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会使一部分神经元在作用时消失。

 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。这种偏移现象我们也不希望发生,所以希望在输入到下一层的时候,我们也希望它能够标准化。

                        

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


       

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

         当两个输入分布相同时(如右图),它能够很好的逐步找到最优值,但是当分布不同时(如左图),他很曲折的才能走到最优值。

        总结就是,希望分布较小,且接近。


        如何进行标准化呢?

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

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

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

                

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

         之后还要进行一个反向传播。

        


        

        BN存在的问题:

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

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

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

                

         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是没有关系的。

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逐步上升,开始朝着不好的方向训练,那原因是什么呢?可能是学习率比较大。初始化等多种原因。

        我们一般可以:

        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收敛图:


12. GoogleNet(理论+实战)

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

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

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

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

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

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

        能不能把全连接变成稀疏连接呢?其实无论是稠密矩阵,还是稀疏矩阵他在做相乘的时候,各地方的数字都是会相乘,那我们能不能利用稀疏矩阵的稀疏性,来直观地增大计算时间呢?

        已经有人证明,把稀疏矩阵聚集成一个密集的矩阵,是能够进行增加计算的性能。

        GoogleNet提出了Inception基本结构

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

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

         通过设计一个稀疏的网络结构,但是又能产生稠密的数据,因此满足了加强网络表现,又保证计算资源的使用效率。

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

       

        而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收敛图:


13. 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收敛图:

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


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

        其他的卷积种类:

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

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

         CNN总结:

         演化历史:

         卷积应用:

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

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: AlexNet是深度学习的一个经典模型,它是在2012年ImageNet大规模视觉识别竞赛中获胜的模型。在AlexNet中,Batch Normalization(BN)层被引入到卷神经网络中,以便更好地训练深度学习模型。 BN层在AlexNet中被用于规范化每个卷层的输入。在每个卷层之后,BN层在每个小批量数据中计算均值和方差,并对每个特征图执行规范化操作。这有助于提高模型的鲁棒性,减少过拟合,并提高模型的收敛速度。 使用BN层的好处包括: 1. 加速收敛:BN层可以使每个层的输入数据具有相似的分布,从而加速网络的收敛速度。 2. 抑制过拟合:BN层可以减少网络的过拟合,使得网络更容易泛化到未见过的数据。 3. 增加网络深度:BN层可以允许更深的网络结构,因为它们可以规范化每个层的输入,使得每层的输出更加稳定。 总之,BN层是在AlexNet中引入的一个重要的技术,它在深度学习中得到了广泛的应用。 ### 回答2: AlexNet中的BN层,即批量归一化层(Batch Normalization),是一种在神经网络中用于提高训练速度和提高模型性能的技术。 BN层主要解决的问题是深度神经网络训练过程中的内部协变量偏移问题。内部协变量偏移是指神经网络在每一层的输入分布都在不断变化,导致网络训练过程变得困难的现象。与此同时,协变量偏移也使得网络难以利用全部的神经元进行训练,导致训练过程变慢。 BN层通过对每一批次的数据进行归一化,将数据的均值和方差约束在一个较小的范围内,使得网络的每一层都能够接收到类似的输入分布,从而减少内部协变量偏移的问题。这样一来,网络的训练速度加快,模型的收敛速度也提高了。 此外,BN层也起到了一定的正则化作用,可以防止过拟合的发生。它在每个batch的数据上进行归一化,并引入了两个可训练的参数,即缩放因子和偏移量。这两个参数允许模型学习适应当前数据分布的缩放和平移,使得模型更加具有泛化能力。 总的来说,BN层作为一种在深度神经网络中应用广泛的层,可以有效地加快训练速度和提高模型性能。它通过解决内部协变量偏移问题,使得每一层都能够接收到类似的输入分布,同时起到了正则化的作用,防止模型过拟合。因此,BN层在神经网络中被广泛应用,并成为了深度学习的重要组件之一。 ### 回答3: AlexNet是一种深度卷神经网络模型,它的命名来源于其创建者Alex Krizhevsky。其中的"bn"层是指Batch Normalization(批量归一化)层。 批量归一化层是AlexNet中的一个重要组成部分,它的作用是对神经网络的中间输出进行标准化处理。具体而言,它通过在训练过程中,对每一层的输入进行归一化操作来提高网络的稳定性和训练速度。 批量归一化层可以有效地解决深度神经网络中的梯度消失和梯度爆炸的问题。在网络的各个层之间加入批量归一化层,可以使得网络更容易训练和调节。同时,它还具有一定的正则化效果,可以降低网络对超参数的敏感性,提高网络的泛化能力。 在实际应用中,批量归一化层的工作流程如下:对于每一层的输入,在其计算完成后,将其归一化为零均值和单位方差,然后通过两个可学习参数(拉伸和偏移)进行线性变换,使得网络可以自主地学习适应不同维度和尺度的输入数据。 总结来说,AlexNet中的批量归一化层通过标准化中间输出来提高网络的稳定性和训练速度,解决了梯度问题并增强了网络的泛化能力。它是深度卷神经网络中一个重要的创新点和性能提升因素。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值