今天复现一篇cv方面基础的论文,也可以说是计算机视觉,需要读的第一篇论文:
alexnet(当然还有很多cv方面奠基的文章,但是因为alexnet是第一个将卷积神经网络应用到大规模图片识别,并且取得很好的效果:ImageNet LSVRC-2010 取得top-1和top-5错误率为37.5%和17.0%的效果, ImageNet LSVRC-2012中,取得了top-5错误率为15.3%)。
论文主要贡献:
1> 训练了一个基于imagenet数据集的大型卷积神经网络,并且效果不错。
2> 实现了一个基于两块gpu的2d卷积网络实现,包含一些提高性能和减少训练时长的特性。
3> 为了避免过拟合,alexnet使用relu,数据增强的方法,有效的解决了过拟合问题。
4> alexnet发现和证明了,网络深度对神经网络的性能,至关重要。
可以看出,网络从左到右包含五个卷积层,三个全连接,最终输出1000个softmax,共八层。
下面就根据该图,从输入开始详细介绍:
输入层:
alexnet需要固定的图片大小,所以imagenet数据集中的图片需要进行下采样到统一的256 * 256.
方法是: 首先对图片矩形进行缩放,使得较短的变长变成256 长。之后对缩放后的数据进行裁剪得到256 * 256的正方形。
图片中每个像素在训练之前都会减去训练集均值,所以alexnet的训练。
从网络结构中输入层看到,输入数据是224 * 224.不禁生疑,输入图像不是256 * 256的吗?
原来,在输入图片上,alexnet还需要进行224 *224的随机裁剪。这样可以大大增加训练集的大小(2048倍 2 * 32 * 32),
论文中提到,如果没有这一步,整个网络的效果会差很多。
第一层:
根据输入3 * 224 * 224, 使用11 * 11 * 3 * 96 步长为4 padding为2的操作,,根据公式(224 - 11 2 * 2)/4 1 = 55 ,得到55 * 55 * 96的特征图
(网上有一种说法,说是论文错了,网络输入层应该是227 * 227 , 根据公式,(227 - 11 2*0)/4 1 = 55,这里笔者没有深入讨论是否论文错了)
值得注意的是,第二层开始,所有的特征图分为两部分,分布在两块显卡上,每个显卡的对应第二层有48个特征图。
第一层卷积之后经过一个maxpooling , 得到第二层输入,维度为 27 * 27 * 96 ,同样池化后的数据经过卷积依然分别存在两块显卡上,每个显卡48个特征图。
第二层:
上一次的输出27 * 27 * 96 经过这一层首先经过5 * 5 * 96 * 256 的卷积操作,padding为2 步长为1,根据公式 (27 4 - 5)/1 1 = 27 , 则最终输出
27 * 27 * 256的特征图。然后经过步长为2 的3*3池化 (27/2) = 13 ,即第二层最终输出为 13 * 13 * 256
第三层:
这一层开始不会做池化操作了,并且从网络架构图中可以看出,这层里,不同网卡中的数据进行了融合(论文里面没有特别提到这点,这里也不做详细介绍)
所以当前层使用 3 * 3 * 256 * 192 的卷积操作(步长为1 ,padding 为1),得到 13 * 13 * 192的 输出。
第四层:
第四层和第三层类似,只使用 3 * 3 * 192*192的卷积操作(步长为1,padding为1)这样的操作下, 输出和输出维度一致 , 依然为 13 * 13 * 192.
第五层:
这里使用 3 * 3 * 192 * 256 的卷积操作(步长为1, padding为1),输出 为 13 * 13 * 256,之后经过一个max pooling, (13/2)= 6 得到 6 * 6 * 256的特征图。
第六层:
全连接层: 这里将特征图拉长为一个向量,经过 6 * 6 * 256 * 4096的矩阵相乘得到4096的向量输出。
第七层:
依旧是一个全连接: 权重矩阵为 4096 * 4096,这里后面加一个dropout,会以0.5 的概率冻结部分全连接的输出。(最终当前轮 对应神经元的权重不会更新)
第八层: 全连接并最终softmax输出到1000个分类。
代码语言:javascript复制paddle paddle的实现如下:
import paddle
import paddle.fluid as fluid
import numpy as np
from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear
from paddle.fluid.dygraph import Dropout
# 定义 AlexNet 网络结构
class AlexNet(fluid.dygraph.Layer):
def __init__(self, num_classes=10):
super(AlexNet, self).__init__()
##### alexnet main structure
#data = fluid.data(name='data', shape=[None, 3, 224, 224], dtype='float32')
# output[96, 55, 55] , (224 2*2-11)/4 1) = int(55.25) = 55
self.conv1 = Conv2D(num_channels=3, num_filters=96, filter_size=11,stride=4, padding=2, act="relu")
# output[96, 27], ((55-3)/2 1) = 27
self.pool1 = Pool2D( pool_type='max',pool_stride=2, pool_size=3)
# output [256, 27, 27] ((27 4-5)/1 1) = 27
self.conv2 = Conv2D(num_channels=96, num_filters=256, filter_size=5, padding=2, act = "relu")
# output [256, 13, 13] ((27-3)/2 1) = 13
self.pool2 = Pool2D( pool_type='max', pool_size=3, pool_stride=2)
# output[192, 13, 13] ((13 2 - 3)/1 1)
self.conv3 = Conv2D(num_channels=256, num_filters=192, filter_size=3, padding=1, act="relu")
# output[192, 13, 13] ((13 2-3)/1 1)
self.conv4 = Conv2D(num_channels=192, num_filters=192, filter_size=3, padding=1, act="relu")
# output[256, 13, 13]
self.conv5 = Conv2D(num_channels = 192, num_filters=256, filter_size=3, padding=1, act="relu")
# output[256, 6, 6 ] ((13 -3)/2 1)
self.pool5 = Pool2D( pool_type='max', pool_size=3, pool_stride=2)
# input = 6 * 6 * 256
self.fc6 = Linear(input_dim=6*6*256, output_dim=4096, act='relu')
self.drop6 = Dropout(p=0.5) #fluid.layers.dropout(fc6,dropout_prob=0.5)
self.fc7 = Linear(input_dim=4096, output_dim=4096, act = "relu")#fluid.layers.fc(drop6, size=4096, act='relu')
self.drop7 = Dropout(p=0.5)
self.fc8 = Linear(input_dim=4096, output_dim=num_classes)#fluid.layers.fc(fc7, size=n_classes)
##### alexnet structure end.
# 网络的前向计算过程
def forward(self, x):
x = self.conv1(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = self.conv3(x)
x = self.conv4(x)
x = self.conv5(x)
x = self.pool5(x)
x = fluid.layers.reshape(x, [x.shape[0], -1])
x = self.fc6(x)
x = self.drop6(x)
x = self.fc7(x)
x = self.drop7(x)
x = self.fc8(x)
return x
x = np.random.randn(*[3,3,224,224])
print(x.shape)
x = x.astype('float32')
with fluid.dygraph.guard():
# 创建LeNet类的实例,指定模型名称和分类的类别数目
m = AlexNet(num_classes=10)
# 通过调用LeNet从基类继承的sublayers()函数,
# 查看LeNet中所包含的子层
#print(m.sublayers())
x = fluid.dygraph.to_variable(x)
for item in m.sublayers():
# item是LeNet类中的一个子层
# 查看经过子层之后的输出数据形状
try:
x = item(x)
except:
x = fluid.layers.reshape(x, [x.shape[0], -1])
x = item(x)
if len(item.parameters())==2:
# 查看卷积和全连接层的数据和参数的形状,
# 其中item.parameters()[0]是权重参数w,item.parameters()[1]是偏置参数b
print(item.full_name(), x.shape, item.parameters()[0].shape, item.parameters()[1].shape)
else:
# 池化层没有参数
print(item.full_name(), x.shape)
上述代码输出为:
代码语言:javascript复制conv2d_0 [3, 96, 55, 55] [96, 3, 11, 11] [96]
pool2d_0 [3, 96, 27, 27]
conv2d_1 [3, 256, 27, 27] [256, 96, 5, 5] [256]
pool2d_1 [3, 256, 13, 13]
conv2d_2 [3, 192, 13, 13] [192, 256, 3, 3] [192]
conv2d_3 [3, 192, 13, 13] [192, 192, 3, 3] [192]
conv2d_4 [3, 256, 13, 13] [256, 192, 3, 3] [256]
pool2d_2 [3, 256, 6, 6]
linear_0 [3, 4096] [9216, 4096] [4096]
dropout_0 [3, 4096]
linear_1 [3, 4096] [4096, 4096] [4096]
dropout_1 [3, 4096]
linear_2 [3, 10] [4096, 10] [10]