alexnet论文复现

2020-11-09 10:42:55 浏览数 (1)

今天复现一篇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]

0 人点赞