分享

【连载13】详解CNN五大经典模型之一AlexNet

 新用户8173JS52 2020-10-27
AlexNet在ILSVRC-2012的比赛中获得top5错误率15.3%的突破(第二名为26.2%),其原理来源于2012年Alex的论文《ImageNet Classification with Deep Convolutional Neural Networks》,这篇论文是深度学习火爆发展的一个里程碑和分水岭,加上硬件技术的发展,深度学习还会继续火下去。

网络结构分析


由于受限于当时的硬件设备,AlexNet在GPU粒度都做了设计,当时的GTX 580只有3G显存,为了能让模型在大量数据上跑起来,作者使用了两个GPU并行,并对网络结构做了切分,如下:
· 输入层
输入为224×224×3的三通道RGB图像,为方便后续计算,实际操作中通过padding做预处理,把图像变成227×227×3。

· C1卷积层
该层由:卷积操作 + Max Pooling + LRN(后面详细介绍它)组成。
(1)、卷积层:由96个feature map组成,每个feature map由11×11卷积核在stride=4下生成,输出feature map为55×55×48×2,其中55=(227-11)/4+1,48为分在每个GPU上的feature map数,2为GPU个数;
(2)、激活函数:采用ReLU;
(3)、Max Pooling:采用stride=2且核大小为3×3(文中实验表明采用2×2的非重叠模式的Max Pooling相对更容易过拟合,在top 1和top 5下的错误率分别高0.4%和0.3%),输出feature map为27×27×48×2,其中27=(55-3)/2+1,48为分在每个GPU上的feature map数,2为GPU个数;
(4)、LRN:邻居数设置为5做归一化。
最终输出数据为归一化后的:27×27×48×2

· C2卷积层
该层由:卷积操作 + Max Pooling + LRN组成
(1)、卷积层:由256个feature map组成,每个feature map由5×5卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为2的padding,输出feature map为27×27×128×2,其中27=(27-5+2×2)/1+1,128为分在每个GPU上的feature map数,2为GPU个数;
(2)、激活函数:采用ReLU;
(3)、Max Pooling:采用stride=2且核大小为3×3,输出feature map为13×13×128×2,其中13=(27-3)/2+1,128为分在每个GPU上的feature map数,2为GPU个数;
(4)、LRN:邻居数设置为5做归一化。
最终输出数据为归一化后的:13×13×128×2

· C3卷积层
该层由:卷积操作 + LRN组成(注意,没有Pooling层)
(0)、输入为13×13×256,因为这一层两个GPU会做通信(途中虚线交叉部分)
(1)、卷积层:之后由384个feature map组成,每个feature map由3×3卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为1的padding,输出feature map为13×13×192×2,其中13=(13-3+2×1)/1+1,192为分在每个GPU上的feature map数,2为GPU个数;
(2)、激活函数:采用ReLU;
最终输出数据为归一化后的:13×13×192×2

· C4卷积层
该层由:卷积操作 + LRN组成(注意,没有Pooling层)
(1)、卷积层:由384个feature map组成,每个feature map由3×3卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为1的padding,输出feature map为13×13×192×2,其中13=(13-3+2×1)/1+1,192为分在每个GPU上的feature map数,2为GPU个数;
(2)、激活函数:采用ReLU;
最终输出数据为归一化后的:13×13×192×2

· C5卷积层
该层由:卷积操作 + Max Pooling组成
(1)、卷积层:由256个feature map组成,每个feature map由3×3卷积核在stride=1下生成,为使输入和卷积输出大小一致,需要做参数为1的padding,输出feature map为13×13×128×2,其中13=(13-3+2×1)/1+1,128为分在每个GPU上的feature map数,2为GPU个数;
(2)、激活函数:采用ReLU;
(3)、Max Pooling:采用stride=2且核大小为3×3,输出feature map为6×6×128×2,其中6=(13-3)/2+1,128为分在每个GPU上的feature map数,2为GPU个数.
最终输出数据为归一化后的:6×6×128×2

· F6全连接层
该层为全连接层 + Dropout
(1)、使用4096个节点;
(2)、激活函数:采用ReLU;
(3)、采用参数为0.5的Dropout操作
最终输出数据为4096个神经元节点。

· F7全连接层
该层为全连接层 + Dropout
(1)、使用4096个节点;
(2)、激活函数:采用ReLU;
(3)、采用参数为0.5的Dropout操作
最终输出为4096个神经元节点。

· F8输出层
该层为全连接层 + Softmax
(1)、使用1000个输出的Softmax
最终输出为1000个分类。


ReLu激活函数


AlexNet引入了ReLU激活函数,这个函数是神经科学家Dayan、Abott在《Theoretical Neuroscience》一书中提出的更精确的激活模型:
其中:

详情请阅读书中2.2 Estimating Firing Rates这一节。新激活模型的特点是:
·  激活稀疏性(小于1时为0)
·  单边抑制(不像Sigmoid是双边的)
·  宽兴奋边界,非饱和性(ReLU导数始终为1),很大程度缓
解了梯度消失问题

1、 原始ReLu
在这些前人研究的基础上(可参见 Hinton论文:《Rectified Linear Units Improve Restricted Boltzmann Machines》),类似Eq.2.9的新激活函数被引入:

这个激活函数把负激活全部清零(模拟上面提到的稀疏性),这种做法在实践中即保留了神经网络的非线性能力,又加快了训练速度。
但是这个函数也有缺点:
· 在原点不可微
反向传播的梯度计算中会带来麻烦,所以Charles Dugas等人又提出Softplus来模拟上述ReLu函数(可视作其平滑版):

实际上它的导数就是一个logistic-sigmoid函数:

·过稀疏性
当学习率设置不合理时,即使是一个很大的梯度,在经过ReLu单元并更新参数后该神经元可能永不被激活。

2、 Leaky ReLu
为了解决上述过稀疏性导致的大量神经元不被激活的问题,Leaky ReLu被提了出来:

其中是人工指定的较小值(如:0.1),它一定程度保留了负激活信息。

3、Parametric ReLu
上述值是可以不通过人为指定而学习出的,于是Parametric ReLu被提了出来:
利用误差反向传播原理:

当采用动量法更新权重:

详情请阅读Kaiming He等人的《Delving Deep into Rectifiers: Surpassing Human-Level Performance on ImageNet Classification》论文。
4、Randomized ReLu
Randomized ReLu 可以看做是leaky ReLu的随机版本,原理是:假设

然后再做权重调整。

其中:


Local Response Normalization


LRN利用相邻feature map做特征显著化,文中实验表明可以降低错误率,公式如下:

公式的直观解释如下:


Overlapping Pooling

如其名,实验表明有重叠的抽样可以提高泛化性。


Dropout

Dropout是文章亮点之一,属于提高模型泛化性的方法,操作比较简单,以一定概率随机让某些神经元输出设置为0,既不参与前向传播也不参与反向传播,也可以从正则化角度去看待它。
· 从模型集成的角度看
无Dropout网络:

有Dropout网络:


其中为Dropout的概率,为所在层。
它是极端情况下的Bagging,由于在每步训练中,神经元会以某种概率随机被置为无效,相当于是参数共享的新网络结构,每个模型为了使损失降低会尽可能学最“本质”的特征,“本质”可以理解为由更加独立的、和其他神经元相关性弱的、泛化能力强的神经元提取出来的特征;而如果采用类似SGD的方式训练,每步迭代都会选取不同的数据集,这样整个网络相当于是用不同数据集学习的多个模型的集成组合。

· 从数据扩充(Data Augmentation)的角度看
机器学习学的就是原始数据的数据分布,而泛化能力强的模型自然不能只针对训练集上的数据正确映射输出,但要想学到好的映射又需要数据越多越好,很多论文已经证明,带领域知识的数据扩充能够提高训练数据对原始真实分布的覆盖度,从而能够提高模型泛化效果。
《Dropout as Data Augmentation》将Dropout看做数据扩充的方法,文中证明了:总能找到一个样本,使得原始神经网络的输出与Dropout神经网络的输出一致(projecting noise back into the input space)。
用论文中符号说明如下:

对于一个层的神经网络:
原始神经网络表示为:

Dropout神经网络表示为:



数据扩充


基本方法
正如前面所说,数据扩充本质是减少过拟合的方法,AlexNet使用的方法计算量较小,所以也不用存储在磁盘,代码实现时,当GPU在训练前一轮图像时,后一轮的图像扩充在CPU上完成,扩充使用了两种方法:
1、图像平移和图像反射(关于某坐标轴对称);
2、通过ImageNet训练集做PCA,用PCA产生的特征值和特征向量及期望为0标准差为0.1的高斯分布改变原图RGB三个通道的强度,该方法使得top-1错误率降低1%。

多GPU训练

作者使用GTX 580来加速训练,但受限于当时硬件设备的发展,作者需要对网络结构做精细化设计,甚至需要考虑两块GPU之间如何及何时通信,现在的我们比较幸福,基本不用考虑这些。

AlexNet代码实践


使用CIFAR-10标准数据集,由6w张32×32像素图片组成,一共10个分类。像这样:
代码实现:
# -*- coding: utf-8 -*-
import copy
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import os
from matplotlib.pyplot import plot,savefig
from scipy.misc import toimage
from keras.datasets import cifar10,mnist
from keras.models import Sequential, Graph
from keras.layers.core import Dense, Dropout, Activation, Flatten, Reshape
from keras.optimizers import SGD, RMSprop
from keras.utils import np_utils
from keras.regularizers import l2
from keras.layers.convolutional import Convolution2D, MaxPooling2D, ZeroPadding2D, AveragePooling2D
from keras.callbacks import EarlyStopping
from keras.preprocessing.image import ImageDataGenerator
from keras.layers.normalization import BatchNormalization
from keras.callbacks import ModelCheckpoint
from keras import backend as K
import tensorflow as tf
tf.python.control_flow_ops = tf
from PIL import Image
def data_visualize(x, y, num):
plt.figure()
for i in range(0, num*num):
axes=plt.subplot(num,num,i + 1)
axes.set_title("label=" + str(y[i]))
axes.set_xticks([0,10,20,30])
axes.set_yticks([0,10,20,30])
plt.imshow(toimage(x[i]))
plt.tight_layout()
plt.savefig('sample.jpg')
#以下结构统一忽略LRN层
def build_AlexNet(s):
model = Sequential()
#第一层,卷积层 + max pooling
model.add(Convolution2D(96, 11, 11, border_mode='same', input_shape = s))
model.add(Activation("relu"))
model.add(MaxPooling2D(pool_size=(2, 2)))
#第二层,卷积层 + max pooling
model.add(Convolution2D(256, 5, 5, border_mode='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
#第三层,卷积层
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(512, 3, 3, border_mode='same', activation='relu'))
#第四层,卷积层
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(1024, 3, 3, border_mode='same', activation='relu'))
#第五层,卷积层
model.add(ZeroPadding2D((1,1)))
model.add(Convolution2D(1024, 3, 3, border_mode='same', activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Flatten())
#第六层,全连接层
model.add(Dense(3072, activation='relu'))
model.add(Dropout(0.5))
#第七层,全连接层
model.add(Dense(4096, activation='relu'))
model.add(Dropout(0.5))
#第八层, 输出层
model.add(Dense(10))
model.add(Activation('softmax'))
return model
if __name__=="__main__":
from keras.utils.visualize_util import plot
//使用第三个GPU卡
with tf.device('/gpu:3'):
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=1, allow_growth=True)
//只有卡3可见防止tensorflow占用所有卡
os.environ["CUDA_VISIBLE_DEVICES"]="3"
tf.Session(config=K.tf.ConfigProto(allow_soft_placement=True,
log_device_placement=True,
gpu_options=gpu_options))
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
data_visualize(X_train, y_train, 4)
s = X_train.shape[1:]
model = build_AlexNet(s)
model.summary()
plot(model, to_file="AlexNet.jpg", show_shapes=True)
#定义输入数据并做归一化
dim = 32
channel = 3
class_num = 10
X_train = X_train.reshape(X_train.shape[0], dim, dim, channel).astype('float32') / 255
X_test = X_test.reshape(X_test.shape[0], dim, dim, channel).astype('float32') / 255
Y_train = np_utils.to_categorical(y_train, class_num)
Y_test = np_utils.to_categorical(y_test, class_num)
#预处理与数据扩充
datagen = ImageDataGenerator(
featurewise_center=False,
samplewise_center=False,
featurewise_std_normalization=False,
samplewise_std_normalization=False,
zca_whitening=False,
rotation_range=25,
width_shift_range=0.1,
height_shift_range=0.1,
horizontal_flip=False,
vertical_flip=False)
datagen.fit(X_train)
model.compile(loss='categorical_crossentropy',
optimizer='adadelta',
metrics=['accuracy'])
batch_size = 32
nb_epoch = 10
#import pdb
#pdb.set_trace()
ModelCheckpoint("weights-improvement-{epoch:02d}-{val_acc:.2f}.hdf5", monitor='val_loss', verbose=0, save_best_only=True, save_weights_only=False, mode='auto')
model.fit(X_train, Y_train, batch_size=batch_size, nb_epoch=nb_epoch,
verbose=1, validation_data=(X_test, Y_test))
score = model.evaluate(X_test, Y_test, verbose=0)
print('Test score:', score[0])
print('Test accuracy:', score[1])
y_hat = model.predict_classes(X_test)
test_wrong = [im for im in zip(X_test,y_hat,y_test) if im[1] != im[2]]
plt.figure(figsize=(10, 10))
for ind, val in enumerate(test_wrong[:100]):
plt.subplots_adjust(left=0, right=1, bottom=0, top=1)
plt.subplot(10, 10, ind + 1)
plt.axis("off")
plt.text(0, 0, val[2][0], fontsize=14, color='blue')
plt.text(8, 0, val[1], fontsize=14, color='red')
plt.imshow(toimage(val[0]))
savefig('Wrong.jpg')
训练数据可视化
网络结构
可以看到实践中,AlexNet的参数规模巨大(将近2亿个参数),所以即使在GPU上训练也很慢。
· 错误分类可视化
蓝色为实际分类,红色为预测分类。

    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多