这篇文章详细给出了卷积层和池化层的各种参数,包含padding和stride,不需要手动计算。
这次不像LeNet那么直白的按顺序写层和结构了,这次是以嵌套循环的方式重复生成卷积层和池化层,这样的写法更聪明。
值得注意的是,pytoon中的新变量声明是弱声明,只要写出变量名= 变量内容即可。
另:
问题一:model没有显示有参数,为何后面使用的时候是model(x):
在这段代码中,虽然在创建model
对象时没有显式地调用__call__
方法(即没有使用括号传递参数),但是在PyTorch中,nn.Module
对象是可调用的(callable),这意味着可以像调用函数一样来使用它们。
当你调用model(x)
时,实际上是在调用model
对象的__call__
方法,这个方法会自动调用forward
方法。在nn.Module
的子类中,需要实现forward
方法来定义模型的前向传播逻辑。因此,通过调用model(x)
,PyTorch会自动调用model
对象的forward
方法,并将输入x
传递给这个方法来执行前向传播计算。
所以,虽然你没有显式地使用括号传递参数给model
对象,但是在PyTorch中,可以通过model(x)
这样的形式来调用模型的前向传播操作。这种设计使得模型的使用更加简洁和直观。
具体代码实现如下:
#Imports
import torch
import torch.nn as nn # All neural network modules,nn.Linear,nn.Conv2d,BatchNorm,Loss functions
import torch.optim as optim # For all optimization algorithms,SGD,Adam,etc.
import torch.nn.functional as F # All functions that don't have any parameters
from torch.utils.data import DataLoader # Gives easier dataset managment and creates mini batches
import torchvision.datasets as datasets # Has standard datasets we can import in a nice way
import torchvision.transforms as transforms # Transformations we can perform on our dataset
VGG_types = {
'VGG11': [64,'M',128,'M',256,256,'M',512,512,'M',512,512,'M'],
'VGG13': [64,64,'M',128,128,'M',256,256,'M',512,512,'M',512,512,'M'],
'VGG16': [64,64, 'M', 128,128,'M',256,256,256,'M',512,512,512,'M',512,512,512,'M'],
'VGG19': [64,64, 'M', 128,128, 'M',256,256,256,256,'M',512,512,512,512,'M',512,512,512,512,'M']
}
#Then flatten and 4096x4096x10000 Linear Layers
#VGG16 = [64,64,'M',128,128,'M',256,256,256,'M',512,512,512,'M', 512,512,512,'M']
##Then flatten and 4096x4096x10000 Linear Layers
class VGG_net(nn.Module):
def __init__(self,in_channels = 3,num_classes = 1000):
super(VGG_net,self).__init__()
# self.relu = nn.ReLU()
# self.pool = nn.AvgPool2d(kernel_size=(2,2),stride=(2,2))
# self.conv1 = nn.Conv2d(in_channels=1,out_channels=6,kernel_size=(5,5),stride=(1,1),padding=(0,0))
# self.conv2 = nn.Conv2d(in_channels=6,out_channels=16,kernel_size=(5,5),stride=(1,1),padding=(0,0))
# self.linear1 = nn.Linear(120,84) #基本上定义了所有需要用到的层操作,包括不限于激活函数、卷积操作、池化操作、线性操作即全连接层
#上面是之前的做法,这次采用更灵活更聪明的做法
self.in_channels = in_channels
self.conv_layers = self.create_conv_layers(VGG_types['VGG16'])
self.fcs = nn.Sequential( #当一个模型较简单的时候,我们可以使用torch.nn.Sequential类来实现简单的顺序连接模型
nn.Linear(512*7*7,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.conv_layers(x)
x = x.reshape(x.shape[0],-1)
x = self.fcs(x)
return x
def create_conv_layers(self,architecture):
layers = []
in_channels = self.in_channels
for x in architecture :
if type(x) == int :
out_channels = x
layers += [nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=(3,3),stride=(1,1),padding=(1,1)),
nn.BatchNorm2d(x),
nn.ReLU()]
in_channels = x
elif x == 'M':
layers += [nn.MaxPool2d(kernel_size=(2,2),stride=(2,2))]
return nn.Sequential(*layers)
device = 'cuda' if torch.cuda.is_available() else 'cpu'
model = VGG_net(in_channels=3,num_classes=1000).to(device)
x = torch.randn(1,3,224,224)
x = x.cuda()#没有这一行会报错:
#错误内容就在类型不匹配,根据报错内容可以看出Input type为torch.FloatTensor(CPU数据类型),而weight type(即网络权重参数这些)为torch.cuda.FloatTensor(GPU数据类型)。
#加了这一行
#inputs = inputs.to(device) # 方法一:将input这个tensor转换成了CUDA 类型
#inputs = inputs.cuda() # 方法二:将input这个tensor转换成了CUDA 类型
print(model(x).shape)
问题二:x的具体含义
在这段代码中,x
代表输入的张量,它是一个4维张量,形状为(1, 3, 224, 224)。具体来说,它表示了一个大小为1的batch中包含了一个RGB图像,图像大小为224x224像素。
在模型的前向传播过程中,x
会经过一系列的卷积、池化、激活函数等操作,最终被展平并输入到全连接层中进行分类。在forward
方法中,x
会依次经过self.conv_layers
和self.fcs
两个部分的处理。
-
x = self.conv_layers(x)
: 这一步中,输入x
会经过VGG网络的卷积层部分,self.conv_layers
是一个包含了多个卷积层和激活函数的序列。这些卷积层会提取输入图像的特征。 -
x = x.reshape(x.shape[0], -1)
: 在卷积层后,将特征张量展平成一维向量,以便输入到全连接层中。 -
x = self.fcs(x)
: 最后,展平后的特征向量经过全连接层部分self.fcs
,包含了多个线性层和激活函数,最终输出预测结果。
所以,x
在这段代码中代表输入张量,经过模型的前向传播操作后,会得到模型对输入图像的分类结果。
问题三:for x in architecture 里的x和前面说的张量x,是同一个变量吗,还是两个?
在代码中,for x in architecture
中的x
和我之前提到的张量x
是两个不同的变量。让我们来解释一下:
-
for x in architecture
: 这里的x
是一个迭代变量,用于遍历architecture
列表中的元素。在这个上下文中,x
只是一个占位符,用来表示architecture
列表中的每个元素。 -
x
张量:在上面的代码中,x
张量是一个具体的输入张量,表示一个4维的图像张量。这个张量在模型的前向传播过程中被用作输入数据,经过模型的各个层处理后得到输出结果。
因此,for x in architecture
中的x
只是一个迭代变量,用于遍历列表中的元素;而x
张量是一个具体的输入数据张量,用于模型的前向传播计算。这两个x
是不同的变量,代表不同的含义。
问题四:所以python语法里不在乎变量的同名吗?
是的,Python语法允许在不同的作用域中使用同名的变量,因为Python是一种动态类型语言,变量的作用域和类型是在运行时确定的。在不同的作用域中可以使用相同的变量名,但它们实际上是不同的变量。
在代码中,变量的作用域由变量所在的代码块决定。在for x in architecture
循环中,x
是一个循环变量,它只在循环体内部有效,循环结束后就会被销毁。而在模型前向传播的过程中,x
是一个张量变量,它代表输入数据张量,用于模型计算。
因此,尽管这两个变量名字相同,但它们处于不同的作用域,因此不会发生冲突。这种灵活性使得Python代码更容易编写和理解。