pytorch中实现的AlexNet,有些不同的地方。需要注意的事项如下:
(1)论文中图像是224*224*3的RGB图像。但是模型接受的是4D的张量。所以,必须将图像变换为4D的张量,多出来的一维是Batch这个维度。要把H*W*C的RGB 转化为 (B, C, H , W)的形式。
(2)pytorch提供的官方AlexNet已经弃用了LRN。这个技巧已经过时。
(3)增加了AdaptiveAvgPool2d这个trick。自适应的池化。这个trick就是使得图片,不管多大的分辨率,输出都是6*6的。为什么要都统一成6*6呢?因为后面接了FC层,如果前面的输入的神经元个数不匹配的话,那么这个FC层是没办法运行的。所以接了一个自适应的池化,把尺寸不匹配的特征图都池化到6*6.
(4)卷积核数量发生了改变。没有完全按照paper中的卷积核数量去设置。
卷积核的数量改变了。
下面是torchvision中的AlexNet的实现。代码没有修改。加了一些注释。
torchvision/models/alexnet.py
import torch
import torch.nn as nn
from .utils import load_state_dict_from_url
__all__ = ['AlexNet', 'alexnet']
model_urls = {
'alexnet': 'https://download.pytorch.org/models/alexnet-owt-4df8aa71.pth',
}
class AlexNet(nn.Module):
def __init__(self, num_classes=1000):
super(AlexNet, self).__init__()
self.features = nn.Sequential(
#conv1
nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2), #3表示输入的图像是3通道的。64表示输出通道数,也就是卷积核的数量。
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
#conv2
nn.Conv2d(64, 192, kernel_size=5, padding=2),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
#conv3
nn.Conv2d(192, 384, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
#conv4
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
#conv5
nn.Conv2d(256, 256, kernel_size=3, padding=1),
nn.ReLU(inplace=True),
nn.MaxPool2d(kernel_size=3, stride=2),
)
self.avgpool = nn.AdaptiveAvgPool2d((6, 6)) #如果输入的图片不是224*224,那上面的池化输出的就可能不是6*6。而下面的FC层要求的输入必须是6*6,否则就没法运算。
self.classifier = nn.Sequential(
#FC1
nn.Dropout(), #用于减轻过拟合
nn.Linear(256 * 6 * 6, 4096), #全连接层。输入的是前面的特征图。特征图是6*6大小的,有256个特征图。自己有4096个神经元。
nn.ReLU(inplace=True),
#FC2
nn.Dropout(),
nn.Linear(4096, 4096),
nn.ReLU(inplace=True),
#FC3
nn.Linear(4096, num_classes), #num_classes:这里是1000.
)
def forward(self, x):
x = self.features(x)
x = self.avgpool(x) #liupc:自适应的池化。
x = torch.flatten(x, 1) #拉成一个向量的形式。
x = self.classifier(x)
return x
def alexnet(pretrained=False, progress=True, **kwargs):
r"""AlexNet model architecture from the
`"One weird trick..." `_ paper.
Args:
pretrained (bool): If True, returns a model pre-trained on ImageNet
progress (bool): If True, displays a progress bar of the download to stderr
"""
model = AlexNet(**kwargs)
if pretrained:
state_dict = load_state_dict_from_url(model_urls['alexnet'],
progress=progress)
model.load_state_dict(state_dict)
return model
我们手动分析一下:
1. conv1 --> ReLU --> Pool
conv1: nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2)
其中3表示输入的图像是3通道的。64表示输出通道数,也就是卷积核的数量。输入的图片是224*224的。得到的特征图尺寸= (224+4-11)/4 + 1 = 55。所以特征图为55*55*64。所需参数个数为:(11*11*3+1)*64 = 23296个。
ReLU: nn.ReLU(inplace=True),
Pool: nn.MaxPool2d(kernel_size=3, stride=2),
输入的特征图是55*55*64的。得到的特征图数量是不变的还是64个。尺寸变为(55-3)/2+1 = 27。所以输出为27*27*64。参数个数为0.
2. conv2 --> ReLU --> Pool
conv2: nn.Conv2d(64, 192, kernel_size=5, padding=2),
输入通道数是64,是因为上面的输出有64个特征图。输出通道数为192。输出尺寸为(27+4-5)/1 + 1 = 27。输出:27*27*192。参数个数为(5*5*64+1)*192 = 307392个。
ReLU: nn.ReLU(inplace=True)
Pool: nn.MaxPool2d(kernel_size=3, stride=2)
输出尺寸为(27-3)/2 + 1 = 13。输出13*13*192。
3. Conv3 --> ReLU
conv3: nn.Conv2d(192, 384, kernel_size=3, padding=1)
输出尺寸(13+2-3)/1 + 1 = 13。输出13*13*384。参数个数:(3*3*192+1) * 384 = 663936。
ReLU: nn.ReLU(inplace=True)
4. Conv4 --> ReLU
conv4: nn.Conv2d(384, 256, kernel_size=3, padding=1)
输出尺寸(13+2-3)/1 +1 = 13。输出13*13*256。参数个数(3*3*384+1)*256 = 884992。
ReLU: nn.ReLU(inplace=True)
5. conv5 --> ReLU --> Pool
conv5: nn.Conv2d(256, 256, kernel_size=3, padding=1)
输出尺寸:(13+2-3)/1 +1 = 13。输出13*13*256。参数个数(3*3*256+1)*256 = 590080。
ReLU:nn.ReLU(inplace=True)
Pool: nn.MaxPool2d(kernel_size=3, stride=2)
输出尺寸:(13-3)/2 + 1 = 6。输出6*6*256。
6. avgpool : nn.AdaptiveAvgPool2d((6,6))
如果输入的图片不是224*224,那上面的池化输出的就可能不是6*6。而下面的FC层要求的输入必须是6*6,否则就没法运算。
7. x = torch.flatten(x, 1) #拉成一个向量的形式。
8. FC1: dropout --> Linear --> ReLU
dropout: nn.Dropout(), #用于减轻过拟合
Linear: nn.Linear(256 * 6 * 6, 4096),
全连接层。输入的节点数是256*6*6。因为前面输入的是特征图,特征图是6*6大小的,有256个特征图。自己有4096个神经元。参数个数:(256*6*6 +1) * 4096 = 37,752,832
ReLU: nn.ReLU(inplace=True),
9. FC2: dropout --> linear -->ReLU
dropout: nn.Dropout()
linear: nn.Linear(4096,4096)
全连接层,输入节点数是4096,输出节点数4096。参数个数:4097*4096 = 16,781,312
ReLU: nn.ReLU(inplace=True)
10. FC3: linear
linear: nn.Linear(4096, num_classes)
num_classes为1000分类。需要的参数个数:4097*1000 = 4097000.
与下面代码输出的网络结构的参数完全一致!