import torch.nn.functional as F
import torch.utils.model_zoo as modelzoo
# from modules.bn import InPlaceABNSync as BatchNorm2d
resnet18_url = 'https://download.pytorch.org/models/resnet18-5c106cde.pth'
def conv3x3(in_planes, out_planes, stride=1):
"""3x3 convolution with padding"""
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride,
padding=1, bias=False)
class BasicBlock(nn.Module):
def __init__(self, in_chan, out_chan, stride=1):
super(BasicBlock, self).__init__()
self.conv1 = conv3x3(in_chan, out_chan, stride)
self.bn1 = nn.BatchNorm2d(out_chan)
self.conv2 = conv3x3(out_chan, out_chan)
self.bn2 = nn.BatchNorm2d(out_chan)
self.relu = nn.ReLU(inplace=True)
self.downsample = None
if in_chan != out_chan or stride != 1:
self.downsample = nn.Sequential(
nn.Conv2d(in_chan, out_chan,
kernel_size=1, stride=stride, bias=False),
nn.BatchNorm2d(out_chan),
)
def forward(self, x):
residual = self.conv1(x)
residual = F.relu(self.bn1(residual))
residual = self.conv2(residual)
residual = self.bn2(residual)
shortcut = x
if self.downsample is not None:
shortcut = self.downsample(x)
out = shortcut + residual
out = self.relu(out)
return out
def create_layer_basic(in_chan, out_chan, bnum, stride=1):
layers = [BasicBlock(in_chan, out_chan, stride=stride)]
for i in range(bnum-1):
layers.append(BasicBlock(out_chan, out_chan, stride=1))
return nn.Sequential(*layers)
class Resnet18(nn.Module):
def __init__(self):
super(Resnet18, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3,
bias=False)
self.bn1 = nn.BatchNorm2d(64)
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
self.layer1 = create_layer_basic(64, 64, bnum=2, stride=1)
self.layer2 = create_layer_basic(64, 128, bnum=2, stride=2)
self.layer3 = create_layer_basic(128, 256, bnum=2, stride=2)
self.layer4 = create_layer_basic(256, 512, bnum=2, stride=2)
self.init_weight()
def forward(self, x):
x = self.conv1(x)
x = F.relu(self.bn1(x))
x = self.maxpool(x)
x = self.layer1(x)
feat8 = self.layer2(x) # 1/8
feat16 = self.layer3(feat8) # 1/16
feat32 = self.layer4(feat16) # 1/32
return feat8, feat16, feat32
def init_weight(self):
state_dict = modelzoo.load_url(resnet18_url)
self_state_dict = self.state_dict()
for k, v in state_dict.items():
if 'fc' in k: continue
self_state_dict.update({k: v})
self.load_state_dict(self_state_dict)
def get_params(self):
wd_params, nowd_params = [], []
for name, module in self.named_modules():
if isinstance(module, (nn.Linear, nn.Conv2d)):
wd_params.append(module.weight)
if not module.bias is None:
nowd_params.append(module.bias)
elif isinstance(module, nn.BatchNorm2d):
nowd_params += list(module.parameters())
return wd_params, nowd_params
if __name__ == "__main__":
net = Resnet18()
x = torch.randn(16, 3, 224, 224)
out = net(x)
print(out[0].size())
print(out[1].size())
print(out[2].size())
net.get_params()
这段代码主要定义了一个深度学习模型,这个模型基于ResNet18的网络架构。以下是对每一行代码的解释:
1-2: 导入torch.nn.functional模块和torch.utils.model_zoo模块。前者包含了许多神经网络的函数,后者可以加载预训练的模型。
5: 设置一个字符串变量resnet18_url,这个URL链接指向预训练的ResNet18模型。
7-11: 定义一个函数conv3x3,这个函数返回一个2D卷积层,卷积核大小为3x3,步长和填充可以自定义。
13-34: 定义了一个名为BasicBlock的类,这个类继承了PyTorch中nn.Module的基础类。BasicBlock代表ResNet中的基本块,包含两个3x3卷积层。如果输入和输出的通道数不同,或者步长不为1,那么会在shortcut中使用1x1卷积进行调整。
36-41: 定义了一个函数create_layer_basic,用于创建多个BasicBlock。
43-61: 定义了一个名为Resnet18的类,这个类同样继承了nn.Module类,表示整个ResNet18的网络结构。
63-73: Resnet18类中定义了一个名为forward的方法,它负责定义网络的前向传播过程。
75-82: Resnet18类中定义了一个名为init_weight的方法,它负责从预训练模型中加载权重并更新当前模型的权重。
84-95: Resnet18类中定义了一个名为get_params的方法,这个方法用于获取网络中需要和不需要权重衰减的参数。
97-105: 这部分代码在__name__ == "main"下,意味着只有当这个脚本被直接运行时才会执行。这部分代码实例化了Resnet18类,然后生成一批随机数据,通过Resnet18模型,然后打印出中间层的输出大小。
所以这个脚本的主要目标是定义一个ResNet18模型,然后加载预训练的权重,并在给定输入数据后进行前向传播。
以下是逐行解释的代码:
pythonCopy code
import torch.nn.functional as F
这一行导入了PyTorch的函数模块,该模块包含了很多神经网络的函数,如激活函数,损失函数等。
pythonCopy code
import torch.utils.model_zoo as modelzoo
这一行导入了PyTorch的model_zoo模块,该模块提供了一种方便的方式来加载各种预训练的模型。
pythonCopy code
resnet18_url = 'https://download.pytorch.org/models/resnet18-5c106cde.pth'
设置了预训练的ResNet18模型的下载链接。
pythonCopy code
def conv3x3(in_planes, out_planes, stride=1): """3x3 convolution with padding""" return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)
定义了一个名为conv3x3的函数,该函数返回一个3x3的卷积层,带有padding。输入参数包括输入和输出的通道数量以及步长。
pythonCopy code
class BasicBlock(nn.Module): def __init__(self, in_chan, out_chan, stride=1): super(BasicBlock, self).__init__() self.conv1 = conv3x3(in_chan, out_chan, stride) self.bn1 = nn.BatchNorm2d(out_chan) self.conv2 = conv3x3(out_chan, out_chan) self.bn2 = nn.BatchNorm2d(out_chan) self.relu = nn.ReLU(inplace=True)
定义了一个名为BasicBlock的类,这是ResNet的基础模块。它包括两个卷积层和两个批次归一化层,以及一个ReLU激活函数。
pythonCopy code
self.downsample = None if in_chan != out_chan or stride != 1: self.downsample = nn.Sequential( nn.Conv2d(in_chan, out_chan, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(out_chan), )
这部分代码定义了当输入通道和输出通道不相等,或步长不为1时,为了使维度匹配,需要对输入进行下采样操作。
pythonCopy code
def forward(self, x): residual = self.conv1(x) residual = F.relu(self.bn1(residual)) residual = self.conv2(residual) residual = self.bn2(residual)
在forward函数中,先通过两个卷积-批标准化-激活的流程,处理输入x,生成残差(residual)。
pythonCopy code
shortcut = x if self.downsample is not None: shortcut = self.downsample(x) out = shortcut + residual out = self.relu(out) return out
然后判断是否需要对输入x进行下采样,如果需要则对其进行下采样。接着将下采样后的shortcut与残差相加,然后通过ReLU激活函数,生成输出。
pythonCopy code
def create_layer_basic(in_chan, out_chan, bnum, stride=1): layers = [BasicBlock(in_chan, out_chan, stride=stride)] for i in range(bnum-1): layers.append(BasicBlock(out_chan, out_chan, stride=1)) return nn.Sequential(*layers)
这个函数用于创建多个BasicBlock,创建的数量由bnum参数指定。第一个BasicBlock的输入通道和输出通道由函数的参数决定,后续的BasicBlock的输入和输出通道相同。
pythonCopy code
class Resnet18(nn.Module): def __init__(self): super(Resnet18, self).__init__() self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
定义了Resnet18类,这个类中包含了一个7x7的卷积层,一个批次归一化层,以及一个最大池化层。
pythonCopy code
self.layer1 = create_layer_basic(64, 64, bnum=2, stride=1) self.layer2 = create_layer_basic(64, 128, bnum=2, stride=2) self.layer3 = create_layer_basic(128, 256, bnum=2, stride=2) self.layer4 = create_layer_basic(256, 512, bnum=2, stride=2) self.init_weight()
定义了四个层,每个层都是由两个BasicBlock组成的。同时,调用了init_weight()函数对网络进行权重初始化。
def forward(self, x):
x = self.conv1(x)
x = F.relu(self.bn1(x))
x = self.maxpool(x)
x = self.layer1(x)
feat8 = self.layer2(x) # 1/8
feat16 = self.layer3(feat8) # 1/16
feat32 = self.layer4(feat16) # 1/32
return feat8, feat16, feat32
定义了前向传播的函数。首先输入经过一次卷积,一次批归一化,一次激活函数ReLU和一次最大池化。然后依次经过四个层的处理,分别生成feat8, feat16, feat32三个特征,并返回。
def get_params(self):
wd_params, nowd_params = [], []
for name, module in self.named_modules():
if isinstance(module, (nn.Linear, nn.Conv2d)):
wd_params.append(module.weight)
if not module.bias对不起,这部分代码被切割了。我会继续分析接下来的可能代码。
```python
if not module.bias is None:
nowd_params.append(module.bias)
elif isinstance(module, nn.BatchNorm2d):
nowd_params += list(module.parameters())
return wd_params, nowd_params
定义了权重初始化的函数。首先从model_zoo中加载预训练的ResNet18模型的权重,然后更新当前模型的权重。注意这里忽略了全连接层的权重。
这个函数用于获取模型中的参数,将卷积层和线性层的权重加入到权重衰减参数列表wd_params中,如果这些层有偏置项,那么将偏置项加入到非权重衰减参数列表nowd_params中。对于批量归一化层,将其所有参数加入到非权重衰减参数列表nowd_params中。
if __name__ == "__main__":
net = Resnet18()
fms = net(torch.randn(1, 3, 224, 224))
for fm in fms:
print(fm.size())
这段代码是在直接运行这个脚本时会执行的部分。首先创建一个Resnet18的实例,然后创建一批随机的图像数据,通过这个网络,并打印出每一层的输出尺寸。
以上就是这段代码的详细解释。这段代码定义了一个ResNet18的模型,并提供了加载预训练权重的方法。同时,还包括了如何使用这个模型进行前向传播的例子。