代码来源:PyTorch-GAN/implementations/cyclegan/models.py at master · eriklindernoren/PyTorch-GAN · GitHub
在此分析文件models.py
二.models.py
def weights_init_normal(m):
classname = m.__class__.__name__
if classname.find("Conv") != -1:
torch.nn.init.normal_(m.weight.data, 0.0, 0.02)
if hasattr(m, "bias") and m.bias is not None:
torch.nn.init.constant_(m.bias.data, 0.0)
elif classname.find("BatchNorm2d") != -1:
torch.nn.init.normal_(m.weight.data, 1.0, 0.02)
torch.nn.init.constant_(m.bias.data, 0.0)
这段代码定义了一个名为 `weights_init_normal` 的函数,用于对神经网络中的层进行权重初始化。该函数接受一个神经网络层 `m` 作为参数,并根据层的类型对其权重和偏置进行初始化。下面是详细解释:
1. **函数定义**:定义了一个名为 `weights_init_normal` 的函数,该函数用于对传入的神经网络层进行权重初始化。
2. **获取类名**:通过 `m.__class__.__name__` 获取传入层的类名。
3. **卷积层权重初始化**:
- 检查类名中是否包含 "Conv"。如果包含,说明该层是一个卷积层。
- 使用 `torch.nn.init.normal_` 函数将卷积层的权重初始化为均值为0、标准差为0.02的正态分布。
- 检查该层是否有偏置项(bias),如果存在且不为 `None`,则使用 `torch.nn.init.constant_` 函数将偏置初始化为0。
4. **批归一化层权重初始化**:
- 检查类名中是否包含 "BatchNorm2d"。如果包含,说明该层是一个2D批归一化层。
- 使用 `torch.nn.init.normal_` 函数将批归一化层的权重初始化为均值为1、标准差为0.02的正态分布。
- 使用 `torch.nn.init.constant_` 函数将偏置初始化为0。
这段代码的主要目的是根据层的类型(卷积层或批归一化层)对其权重和偏置进行特定的初始化操作,以便在训练神经网络时有一个良好的初始状态,从而提高训练效果和稳定性。
class ResidualBlock(nn.Module):
def __init__(self, in_features):
super(ResidualBlock, self).__init__()
self.block = nn.Sequential(
nn.ReflectionPad2d(1),
nn.Conv2d(in_features, in_features, 3),
nn.InstanceNorm2d(in_features),
nn.ReLU(inplace=True),
nn.ReflectionPad2d(1),
nn.Conv2d(in_features, in_features, 3),
nn.InstanceNorm2d(in_features),
)
def forward(self, x):
return x + self.block(x)
这段代码定义了一个名为 `ResidualBlock` 的类,该类继承自 `nn.Module`,用于创建残差块。残差块是深度神经网络(尤其是 ResNet)中常用的一种模块,它通过短连接(skip connection)使得输入可以绕过卷积层直接加到输出上,从而缓解深层网络训练中的梯度消失问题。下面是对代码的详细解释:
1. **定义残差块中的层**:
使用 `nn.Sequential` 构建一个顺序容器,包含以下层:
- `nn.ReflectionPad2d(1)`:在输入的边界处进行反射填充,填充1个像素。
- `nn.Conv2d(in_features, in_features, 3)`:一个卷积层,输入和输出特征数都是 `in_features`,卷积核大小为 3x3。
- `nn.InstanceNorm2d(in_features)`:实例归一化层,对每个样本独立进行归一化。
- `nn.ReLU(inplace=True)`:ReLU激活函数,设置 `inplace=True` 以节省内存。
- `nn.ReflectionPad2d(1)`:再次进行反射填充。
- `nn.Conv2d(in_features, in_features, 3)`:第二个卷积层。
- `nn.InstanceNorm2d(in_features)`:第二次实例归一化。
2. **前向传播方法**:
`forward` 方法定义了前向传播的过程。输入 `x` 通过残差块 `self.block` 进行处理,然后将输出与原输入 `x` 相加。这种结构实现了所谓的 "short connection" 或 "skip connection",即输入可以绕过卷积层直接加到输出上。
这个 `ResidualBlock` 类实现了一个经典的残差块结构,包含两个卷积层和实例归一化层,并通过短连接将输入直接加到输出上。这种设计有助于缓解深层神经网络训练中的梯度消失问题,并且在许多深度学习模型(如 ResNet)中被广泛使用。
class GeneratorResNet(nn.Module):
def __init__(self, input_shape, num_residual_blocks):
super(GeneratorResNet, self).__init__()
channels = input_shape[0]
# Initial convolution block初始卷积块
out_features = 64
model = [
nn.ReflectionPad2d(channels),
nn.Conv2d(channels, out_features, 7),
nn.InstanceNorm2d(out_features),
nn.ReLU(inplace=True),
]
in_features = out_features
# Downsampling下采样
for _ in range(2):
out_features *= 2
model += [
nn.Conv2d(in_features, out_features, 3, stride=2, padding=1),
nn.InstanceNorm2d(out_features),
nn.ReLU(inplace=True),
]
in_features = out_features
# Residual blocks残差块
for _ in range(num_residual_blocks):
model += [ResidualBlock(out_features)]
# Upsampling上采样
for _ in range(2):
out_features //= 2
model += [
nn.Upsample(scale_factor=2),
nn.Conv2d(in_features, out_features, 3, stride=1, padding=1),
nn.InstanceNorm2d(out_features),
nn.ReLU(inplace=True),
]
in_features = out_features
# Output layer输出层
model += [nn.ReflectionPad2d(channels), nn.Conv2d(out_features, channels, 7), nn.Tanh()]
#顺序容器
self.model = nn.Sequential(*model)
#前向传播
def forward(self, x):
return self.model(x)
这段代码定义了一个名为 `GeneratorResNet` 的类,用于生成基于残差网络(ResNet)的生成器模型。这个生成器模型可以用于图像生成任务,例如图像风格转换、图像超分辨率等。下面是对代码的详细解释:
1. **初始卷积块**:
定义初始卷积块,包括反射填充、卷积层、实例归一化和 ReLU 激活函数。
2. **下采样**:
进行两次下采样操作,每次将特征数翻倍,使用步幅为2的卷积层来减小空间尺寸。
3. **残差块**:
添加指定数量的残差块,每个残差块包含两个卷积层和一个短连接。
4. **上采样**:
进行两次上采样操作,每次将特征数减半,使用上采样层来增大空间尺寸。
5. **输出层**:
定义输出层,包括反射填充、卷积层和 Tanh 激活函数,将输出映射到 (-1, 1) 范围。
6. **顺序容器**:
将 `model` 列表中的所有层连接成一个顺序容器。
7. **前向传播方法**:
定义前向传播过程,输入 `x` 经过模型处理后得到输出。
`GeneratorResNet` 类实现了一个基于残差网络的生成器模型。该模型包括初始卷积块、两次下采样、多层残差块、两次上采样和输出层。通过这种结构,模型可以有效地生成高质量的图像,并且可以用于各种图像生成任务。
class Discriminator(nn.Module):
def __init__(self, input_shape):
super(Discriminator, self).__init__()
channels, height, width = input_shape
# Calculate output shape of image discriminator (PatchGAN)计算判别器的输出形状
self.output_shape = (1, height // 2 ** 4, width // 2 ** 4)
#判别器块
def discriminator_block(in_filters, out_filters, normalize=True):
"""Returns downsampling layers of each discriminator block"""
layers = [nn.Conv2d(in_filters, out_filters, 4, stride=2, padding=1)]
if normalize:
layers.append(nn.InstanceNorm2d(out_filters))
layers.append(nn.LeakyReLU(0.2, inplace=True))
return layers
#构建模型
self.model = nn.Sequential(
*discriminator_block(channels, 64, normalize=False),
*discriminator_block(64, 128),
*discriminator_block(128, 256),
*discriminator_block(256, 512),
nn.ZeroPad2d((1, 0, 1, 0)),
nn.Conv2d(512, 1, 4, padding=1)
)
#前向传播
def forward(self, img):
return self.model(img)
这段代码定义了一个用于图像判别的 `Discriminator` 类。该类用于对输入图像进行判别,以区分真实图像和生成器生成的假图像。这个判别器基于 PatchGAN 结构,通过局部的图像块(patch)进行判别。下面是对代码的详细解释:
1. **计算输出形状**:
计算判别器的输出形状。由于每次下采样尺寸减半,经过 4 次下采样后,高度和宽度分别除以 \(2^4\)(即 16)。
2. **判别器块**:
定义一个函数 `discriminator_block`,用于创建判别器的下采样块。每个块包括一个卷积层、可选的实例归一化层和 LeakyReLU 激活函数。
3. **构建模型**:
使用 `nn.Sequential` 将多个判别器块连接成一个模型。具体包括:
- 一个 `discriminator_block`,将输入通道数转换为 64(不进行归一化)。
- 三个 `discriminator_block`,通道数依次从 64 增加到 128、256 和 512。
- 一个 `nn.ZeroPad2d` 层,进行零填充。
- 最后一个卷积层,将通道数从 512 转换为 1。
4. **前向传播方法**:
定义前向传播过程,输入图像 `img` 经过判别器模型处理后得到输出。