一.卷积神经网络
1.手动计算卷积神经网络
(1)现有特征图4*4. 卷积核大小为2, 卷出来的特征图多大。卷积核的参数量多少?
答:即为4*4的矩阵每2*2的矩阵可以乘一次可以得到3*3的矩阵,因此特征图为3*3,卷积核的参数量为2*2=4
(2)现有特征图4*4. 卷积核大小为3, 卷出来的特征图多大。
答:即为4*4的矩阵每3*3的矩阵可以乘一次可以得到2*2的矩阵,因此特征图为2*2
也可以得到一个公式:特征图-卷积核大小+1=特征图大小
(3)现有特征图4*4. 卷积核大小为3,padding 1, 卷出来的特征图多大。
注:Padding(填充)在卷积神经网络(CNN)中是指在输入数据的边缘添加额外的像素(通常是零),以影响卷积操作的输出。即为在矩阵周围添加一圈的0,再参与运算。
答:即可算出特征值为4-3+1+2=4,特征图表示不变,仍为4*4。
(4)特征图100*100,卷积核 7,padding多少,特征图大小不变。
100-7+1=94,若要使特征图大小不变,100-94=6,padding要为3
(5)特征图3*4*4. padding1, 卷积核数量1,卷出来的特征图为1*4*4。这套卷积核多大? 卷积核的参数量多少?
答:厚度为3,padding为1因此4*4的特征图不会变化,卷积的大小为3*3,厚度为3,因此卷积核为3*3*3,卷积核的参数量为1*3*3*3=27
(6)特征图3*4*4. 卷积核3*3*3, padding1, 卷积核数量7,卷出来的特征图多大。
答:因为padding为1,输出空间的尺寸为4*4,输出的通道数是7(即卷积核数量为7),因此特征图大小为7*4*4
(7)特征图3*224*224. 卷积核3*3*3, padding=1,卷积核数量64.卷出来的特征图多大?
答:64*224*224
(8)特征图64*224*224. 卷积核64*3*3, padding=1,卷积核数量128 卷出来特征图多大。这套卷积核的参数量多少?
答:特征图大小:128*224*224,卷积核的参数量可以通过以下公式计算:
卷积核参数量=(卷积核的高度×卷积核的宽度×输入通道数+1)×输出通道数(1为bias,可以省略)
卷积核的高度×卷积核的宽度:3*3
输入通道数与输入特征图的通道数一致,也为64
输出的通道数为128(即卷积核数量为128)
因此卷积核的参数量为(3*3*64)*128
二.AlexNet模型的代码解析
1. 引入库和预训练的AlexNet模型
import torchvision.models as models
import torch.nn as nn
alexnet = models.alexnet()
print(alexnet)
先从torchvision导入alexnet模型,并输出alexnet模型。
2. 定义类
class MyAlexNet(nn.Module):
def __init__(self):
super(MyAlexNet, self).__init__()
self.relu = nn.ReLU()
self.drop = nn.Dropout(0.5)
在初始化函数中,首先定义了ReLU激活函数和Dropout,其中Dropout为了防止过拟合,每个训练时的神经元有50%被丢弃
3.定义卷积层和池化层
self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=11, stride=4, padding=2)
self.pool1 = nn.MaxPool2d(3, stride=2)
self.conv2 = nn.Conv2d(64, 192, 5, 1, 2)
self.pool2 = nn.MaxPool2d(3, stride=2)
self.conv3 = nn.Conv2d(192, 384, 3, 1, 1)
self.conv4 = nn.Conv2d(384, 256, 3, 1, 1)
self.conv5 = nn.Conv2d(256, 256, 3, 1, 1)
self.pool3 = nn.MaxPool2d(3, stride=2)
self.adapool = nn.AdaptiveAvgPool2d(output_size=6)
conv1:卷积层,输入三通道(RGB图像),输出64通道,使用大小的卷积核kernel_size为11,步长stride为4,padding填充为2
pool1:池化层,为3*3的窗口,步长为2,用于减少空间维度
conv2到conv5的后续卷积层每一层将输入特征图的通道数逐渐改变,卷积核从5变到3,步长和填充保持1,每层卷积后都有ReLU激活函数进行非线性变换
pool2和pool3:池化层用于逐渐降低空间的维度,保留最显著的特征
adapool:自适应平均池化,最终将所有特征图池化为固定的6x6尺寸,无论输入图像的大小如何。
4. 定义全连接层
self.fc1 = nn.Linear(9216, 4096)
self.fc2 = nn.Linear(4096, 4096)
self.fc3 = nn.Linear(4096, 1000)
fc1:全连接层,将输入9216维,映射到4096维
fc2:另一个全连接层,输入4096,映射到4096维
fc3:最终的全连接层,将4096维,映射到1000维
5.forward方法
def forward(self, x):
x = self.conv1(x)
x = self.relu(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.relu(x)
x = self.pool2(x)
x = self.conv3(x)
x = self.relu(x)
print(x.size()) # 打印当前尺寸
x = self.conv4(x)
x = self.relu(x)
print(x.size())
x = self.conv5(x)
x = self.relu(x)
x = self.pool3(x)
print(x.size())
x = self.adapool(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
x = self.relu(x)
return x
卷积操作:数据从conv1开始,通过ReLU和池化层逐渐提取特征
打印尺寸:每一层卷积操作后,打印当前的张量尺寸(通过print(x.size())),帮助调试。随着层加深,图像的空间维度会减小
自适应池化:在最后,通过adapool将图像大小缩放为6*6
展平:x = x.view(x.size()[0], -1)将特征图展平为一维向量,准备用于全连接层
全连接层:数据通过三个全连接层,最终输出1000个类别的概率。
6. 计算模型的参数数量
def get_parameter_number(model):
total_num = sum(p.numel() for p in model.parameters())
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
return {'Total': total_num, 'Trainable': trainable_num}
print(get_parameter_number(myalexnet))
get_parameter_number计算模型的总参数数(total_num)和可训练参数(trainable_num)用于评估模型的大小。
model.parameters():这是PyTorch中每个模型对象的一个方法,它返回模型中所有参数(包括权重和偏置),这些参数通常是nn.Parameter类型的张量。
p.numel():是PyTorch中的一个函数,用来返回一个张量中元素的总数
7.模拟输入并查看输出
img = torch.zeros((4, 3, 224, 224))
out = myalexnet(img)
print(out.size())
img:我们使用torch.zeros创建了一个大小为(4, 3, 224, 224)的零图像张量,表示4个图像
8.模型源码
import torchvision.models as models
import torch.nn as nn
alexnet = models.alexnet()
print(alexnet)
class MyAlexNet(nn.Module):
def __init__(self):
super(MyAlexNet, self).__init__()
self.relu = nn.ReLU()
self.drop = nn.Dropout(0.5)
self.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=11, stride=4,padding=2)
self.pool1 = nn.MaxPool2d(3, stride=2)
self.conv2 = nn.Conv2d(64,192,5,1,2)
self.pool2 = nn.MaxPool2d(3, stride=2)
self.conv3 = nn.Conv2d(192,384,3,1,1)
self.conv4 = nn.Conv2d(384,256,3,1,1)
self.conv5 = nn.Conv2d(256, 256, 3, 1, 1)
self.pool3 = nn.MaxPool2d(3, stride=2)
self.adapool = nn.AdaptiveAvgPool2d(output_size=6)
self.fc1 = nn.Linear(9216,4096)
self.fc2 = nn.Linear(4096,4096)
self.fc3 = nn.Linear(4096,1000)
def forward(self,x):
x = self.conv1(x)
x = self.relu(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.relu(x)
x = self.pool2(x)
x = self.conv3(x)
x = self.relu(x)
print(x.size())
x = self.conv4(x)
x = self.relu(x)
print(x.size())
x = self.conv5(x)
x = self.relu(x)
x = self.pool3(x)
print(x.size())
x= self.adapool(x)
x = x.view(x.size()[0], -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
x = self.relu(x)
x = self.fc3(x)
x = self.relu(x)
return x
import torch
myalexnet = MyAlexNet()
def get_parameter_number(model):
total_num = sum(p.numel() for p in model.parameters())
trainable_num = sum(p.numel() for p in model.parameters() if p.requires_grad)
return {'Total': total_num, 'Trainable': trainable_num}
print(get_parameter_number(myalexnet))
img = torch.zeros((4,3,224,224))
out = myalexnet(img)
print(out.size())