目录
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. 卷积的变种、应用、总结
有较高的内存占用,依赖硬件。
我们通常取I层中对J层中j点影响最大的前k个点作为集合去计算。
因此我们需要知道第I层节点对第J层节点影响大小分布。
为了解决这个问题,我们一般都会引入一些先验知识
以一个中心点到之外的窗口宽的位置,我们认为他很重要,这个区域成为感受野
上述总的中心点在输出中设为j,感受野是输入中的关于j的一块小区域。
2. 时间近与否?
除了上述讲的感受野的局部相关性的思想,我们还可以通过权值共享的思想减少参数。网络部分区域始终使用一个参数。
自然图像中的物体都具有局部不变性的特征,比如尺寸缩放、平移、旋转等操作不影响其语义信息。
而全连接网络很难提取这些局部不变特征。
两函数的卷积、本质上就是先将一个函数翻转、然后进行滑动叠加。
1D连续卷积:连续形式,相当于积分求面积。离散时相当于求所有点值的和。
g(t) 是一个响应信号,f(t)是个输入信号。输出与输入信号与响应的程度相关。如下图,响应 程度越来越小,即使输入信号有时很大,但最后还是会对输入影响越来越小。
在1D中的翻转可以理解为响应信号的翻转。
接下来是滑动。
而叠加,就是上面说的“ 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]
图像处理中,图像是以二维矩阵的形式输入到神经网络中,因此,我们需要二维卷积。
步长为1的情况下,先将卷积核翻转,再进行以下这样的滑动,滑动完就进行求和。
卷积常备作为特征的提取器。
计算卷积时还需要进行翻转操作,但卷积的目标是:提取特征,因此往往翻转是不必要的,这样的卷积操作被称为互相关操作,除非特别声明,卷积一般是指“互相关”的。
这里的步长s和零填充p都是针对滤波器的。
步长是指:滤波器滑动时的时间间隔或越过数量。
步长越大,输出形状就越小。
填充的作用是什么呢?如果我们经过卷积计算后,输出过小,我们想维持特征的数量,这时就要对卷积核进行0填充。
输出的大小可以通过一个公式算出:其中n为输入矩阵(n*n),m是卷积核(m*m),p是0填充数,s是步长。
因此卷积核的大小,我们要设定好,保持输出为整数。且通过公式我们又进一步得出,步长越大,输出形状越小。
回到0补充,为了保持特征的数量,我们一般可以通过计算得出正确的补0数。
综上我们可以通过卷积层代替全连接层解决权重矩阵参数过过多的问题。
单通道是对于黑白照片而言的,他矩阵中的每一个值都是一个标量,表示像素点。
一张彩色图片一个像素点有3个通道。输入图片有三个通道,那么我们的卷积核也应该有对于的三个通道。即卷积的通道数与输入通道数一致!
每个通道都与对应的权重矩阵滑动,求和,最后再进行一个权重向量的求和。
但是单卷积核只能适用于对某一特点的特征提取的作用,但往往图片也不可能只有一个特征,因此我们最常用的是多通道-多卷积核的操作。
一个卷积核,虽然有多个通道,但是最后使用求和方式让权重叠加,这仍是一个特征。而两个卷积核可以提取两个特征,最后将两个个特征stack,这样就代表两个特征。多个卷积核同理。
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
直观上也会进行一个特征选择,从而降低特征的数量,从而进一步降低参数的数量,除此之外,他还解决全连接前馈网络不具备空间不变性的问题。
通过卷积层得到的特征数据不具备空间不变性(空间不变性如下图,位置,大小,亮度等),只有通过池化层才能具备空间不变形的特征。
池化层可以理解为对图像进行一个下采样的过程,对于每一次滑动窗口内的所有值,输出其中的最大值,均值或其他方法产生的值,而不再是像卷积中一样全部乘后求和。
我们从上图也可以看出经过池化层后特征映射空间也能在很大幅度上减小特征空间,在减小特征空间后又有一个很直接的好处,那就是减少过拟合。
同时还能增加下一次的kernel感受野。
池化也有一个很严重的问题:
我们的采样区如果对于图片过大的时候,那么带来的直接的结果就是造成信息损失。
卷积网络是由卷积层。池化层、全连接层交叉堆叠而成:
而上面的这个结构肯能也会由多个组成,最后再加上一个全连接层。
卷积趋向于小卷积、大深度。全卷积、少池化的趋势发展。
表示学习:
我们总说层数越深,网络的表达能力就越强,那怎么样才能表示能力强呢?于是人们提出了反卷积神经网络使得可视化出特征映射到底是什么样的情况。因此图片学习也是一个表示学习的过程,先提取颜色特征再提取边缘特征再提取高阶的特征……
卷积的反向传播是怎样进行的呢?
假设我们有一张图片(3*3),卷积核(2*2)。s = 1,p = 0时,通过点积运算得到一个输出。再利用这个输出与真实值比较得到一个loss从而进行反向传播得到梯度,再来进行梯度更新。
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)
创新点:
由下图可以看出,Dropout会使一部分神经元在作用时消失。
Dropout具体工作流程:
因为神经网络训练的参数多,表达能力强。因此我们需要比较多的数据量,不然容易造成过拟合。当训练数据有限时,可以通过一些变换从已有的数据集中产生新的数据。对于图像而言,我们可以进行一些形变(翻转,随机裁剪,平移,颜色光照变换)操作。
此外,AlexNet还进行了主成分分析。
在LeNet中池化是不重叠的,即吃化的窗口的大小和步长相等的。
而在AlexNet中使用的池化确实可以重叠的,也就是说,在池化的时候,每次移动的步长小于池化窗口的长度,这样就有部分的重叠,重叠池化有个好处就是抑制过拟合。
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)
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
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收敛图:
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收敛图:
上面一直在讲,深度越大,网络表达能力越强,但是深度越大,有什么坏处吗?
会带来梯度消失的问题。
解决办法:残差网络:
通过在非线性的卷积层上增加直连边的方式提高星系传播效率,在另一个方向上直接越过当前网络。 值就是一个跨层链接的重要思想。
将目标函数分为两个部分:
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的正确率。
其他的卷积种类:
CNN总结:
演化历史:
卷积应用:
图像识别,目标检测,图像分割,OCR,图像生成,对抗样本。