卷积神经网络从诞生之处就是为图像处理,直接原始图像的像素数据简单处理作为模型的输入的。而传统的神经网络和其它机器学习方法不会这样做,而是在图像数据进入模型之前,需要经过人手工精心设计特征提取方法,最后将提取的特征再作为模型的输入。
在卷积神经网络出现的早期,由于计算设备的原因,能用于训练的数据想对较少,而且有因为大型的卷积神经网络的大量参数,导致模型很难被有效训练。因此,在很长一段时间,神经网络都落后于其它的机器学习方法。
传统的机器学习方法,在收集到数据集之后,需要使用光学、几何学等领域的知识来指导手工对数据的预处理,然后通过标准的特征提取算法获取特征,最后将提取的特征送入选择的分类器中。随着技术的发展,另一种想法开始出现:特征的提取本身,也应该由模型通过学习来提取;特征可以有多个神经网络层来共同学习。由此,诞生的 AlexNet 最终使得卷积神经网络开始在图像领域超越其它机器学习方法。
网络支持的输入的尺寸为:3×224×224。
Conv2d(3, 96, kernel_size=11, stride=4, padding=1)
使用一个11*11的卷积核窗口来捕捉对象,
padding=1 边缘都向外扩充1,
同时,步幅为4,以减少输出的高度和宽度,
最后,在尺寸减小的同时,增加输出通道的数目。
Conv2d output shape: torch.Size([1, 96, 54, 54])
nn.ReLU()
使用 ReLU 激活函数,一方面,ReLU激活函数的计算更简单,它不需要如sigmoid激活函数那般复杂的求幂运算。 另一方面,当使用不同的参数初始化方法时,ReLU激活函数使训练模型更加容易。 当sigmoid激活函数的输出非常接近于0或1时,这些区域的梯度几乎为0,因此反向传播无法继续更新一些模型参数。 相反,ReLU激活函数在正区间的梯度总是1。 因此,如果模型参数没有正确初始化,sigmoid函数可能在正区间内得到几乎为0的梯度,从而使模型无法得到有效的训练。
ReLU output shape: torch.Size([1, 96, 54, 54])
nn.MaxPool2d(kernel_size=3, stride=2)
最大池化,使用 3*3 的窗口,步幅为2。
MaxPool2d output shape: torch.Size([1, 96, 26, 26])
nn.Conv2d(96, 256, kernel_size=5, padding=2)
使用一个 5*5 的卷积核窗口来捕捉对象,
padding=2 边缘都向外扩充2,保持输出尺寸不变,
最后,在尺寸减小的同时,增加输出通道的数目。
torch.Size([1, 256, 26, 26])
nn.ReLU()
使用 ReLU 激活函数。
ReLU output shape: torch.Size([1, 256, 26, 26])
nn.MaxPool2d(kernel_size=3, stride=2)
最大池化,使用 3*3 的窗口,步幅为2。
MaxPool2d output shape: torch.Size([1, 256, 12, 12])
nn.Conv2d(256, 384, kernel_size=3, padding=1)
使用一个 3*3 的卷积核窗口来捕捉对象,
padding=2 边缘都向外扩充2,保持输出尺寸不变,
最后,在尺寸减小的同时,增加输出通道的数目。
Conv2d output shape: torch.Size([1, 384, 12, 12])
nn.ReLU()
使用 ReLU 激活函数。
ReLU output shape: torch.Size([1, 384, 12, 12])
nn.Conv2d(384, 384, kernel_size=3, padding=1)
使用一个 3*3 的卷积核窗口来捕捉对象,
padding=2 边缘都向外扩充2,保持输出尺寸不变,
最后,保持输出通道的数目不变。
Conv2d output shape: torch.Size([1, 384, 12, 12])
nn.ReLU()
使用 ReLU 激活函数。
ReLU output shape: torch.Size([1, 384, 12, 12])
nn.Conv2d384, 256, kernel_size=3, padding=1)
使用一个 3*3 的卷积核窗口来捕捉对象,
padding=2 边缘都向外扩充2,保持输出尺寸不变,
最后,降低输出通道的数目。
Conv2d output shape: torch.Size([1, 256, 12, 12])
nn.ReLU()
使用 ReLU 激活函数。
ReLU output shape: torch.Size([1, 256, 12, 12])
nn.MaxPool2d(kernel_size=3, stride=2)
最大池化,使用 3*3 的窗口,步幅为2。
MaxPool2d output shape: torch.Size([1, 256, 5, 5])
nn.Flatten()
展开为一维特征。
Flatten output shape: torch.Size([1, 6400])
nn.Linear(6400, 4096),
线性层。
Linear output shape: torch.Size([1, 4096])
nn.ReLU()
使用 ReLU 激活函数。
ReLU output shape: torch.Size([1, 4096])
nn.Dropout(p=0.5)
连接层的输出数量很高,使用dropout层来减轻过拟合。
Dropout output shape: torch.Size([1, 4096])
nn.Linear(4096, 4096)
线性层。
Linear output shape: torch.Size([1, 4096])
nn.ReLU()
使用 ReLU 激活函数。
ReLU output shape: torch.Size([1, 4096])
nn.Dropout(p=0.5)
连接层的输出数量很高,使用dropout层来减轻过拟合。
Dropout output shape: torch.Size([1, 4096])
nn.Linear(4096, 10)
输出层。
Linear output shape: torch.Size([1, 10])
class AlexNet(nn.Module):
def __init__(self, num_classes):
super(AlexNet, self).__init__()
self.feature_extr = nn.Sequential(
nn.Conv2d(3, 96, kernel_size=11, stride=4, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(96, 256, kernel_size=5, padding=2),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Conv2d(256, 384, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(384, 384, kernel_size=3, padding=1),
nn.ReLU(),
nn.Conv2d(384, 256, kernel_size=3, padding=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3, stride=2),
nn.Flatten()
)
self.classifier = nn.Sequential(
nn.Linear(6400, 4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(p=0.5),
nn.Linear(4096, num_classes)
)
def forward(self, x):
x = self.feature_extr(x)
x = self.classifier(x)
return x