残差连接和层归一化详解(Python实现)
在深层神经网络中,残差连接和层归一化是提高训练稳定性和优化性能的关键组件。
本节将首先详细介绍残差连接的实现方法及其在深层网络中的作用,接着探讨层归一化的工作原理,分析其如何稳定训练过程。
在实现中,残差连接会将输入与经过若干层变换的输出相加,使模型在增加层数的同时不会过度影响梯度传播。残差学习结构如下图所示:

图 1 残差学习结构图
权重层(weight layer)会经过两次 relu 进行非线性激活,以便更好地学习到输入信息的深层特征。
以下代码将展示残差连接的实现,并结合卷积和激活函数构建一个带残差连接的网络层结构。
1) ResidualBlock:定义了一个残差块,包含两个卷积层和批归一化层。在前向传播中,将输入添加到卷积层输出中,实现残差连接。如果输入和输出的维度不同,使用下采样层调整输入以匹配输出维度。
2) ResNetLike:搭建一个简单的网络模型,包含多个残差块。在模型结构中,前几层为卷积层,用于特征提取,中间层使用多个残差块来增强特征表达能力,最后通过全局平均池化和全连接层实现分类。
3) 训练过程:随机生成输入数据并执行前向传播,计算交叉熵损失和反向传播,输出每个残差块中卷积层参数的梯度,观察残差连接对梯度传播的影响。
代码运行结果如下:
层归一化将输入在特征维度上进行标准化,并使用可学习的缩放参数和偏置参数进行调整,使得网络能够更灵活地适应不同任务。相比于批归一化,层归一化在序列建模任务和小批量数据训练中更加适用。
以下代码将展示层归一化的实现及其在神经网络中的应用。
1) LayerNormBlock:实现带层归一化的基本模块,包含层归一化、全连接和激活函数。每次前向传播时,输入会先经过层归一化,使得各特征在标准化后更加稳定,避免分布偏移对训练的影响。
2) SimpleLayerNormModel:构建一个简单的神经网络结构,包含3个层归一化模块。每层对输入进行层归一化,再通过全连接和激活层处理,使得输出更具有可训练性和稳定性。
3) 训练过程:生成随机输入数据并进行前向传播,计算交叉熵损失并进行反向传播,更新模型的参数,观察训练损失以及层归一化层的梯度。
4) 测试模型在不同输入分布下的稳定性:模拟不同分布的输入数据,以验证层归一化在保持模型输出稳定性方面的作用。
代码运行结果如下:
本节将首先详细介绍残差连接的实现方法及其在深层网络中的作用,接着探讨层归一化的工作原理,分析其如何稳定训练过程。
残差连接层的实现
残差连接是一种将输入直接添加到输出的机制,它通过构建“捷径”路径缓解了深层网络中的梯度消失问题,使得信息可以在不经过所有层的情况下流动,从而提高深层神经网络的训练效率。在实现中,残差连接会将输入与经过若干层变换的输出相加,使模型在增加层数的同时不会过度影响梯度传播。残差学习结构如下图所示:

图 1 残差学习结构图
权重层(weight layer)会经过两次 relu 进行非线性激活,以便更好地学习到输入信息的深层特征。
以下代码将展示残差连接的实现,并结合卷积和激活函数构建一个带残差连接的网络层结构。
import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F # 设置随机种子 torch.manual_seed(42) # 定义残差块 class ResidualBlock(nn.Module): def __init__(self, in_channels, out_channels, stride=1, downsample=None): super(ResidualBlock, self).__init__() # 第一个卷积层 self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels) # 第二个卷积层 self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels) self.downsample = downsample self.relu = nn.ReLU(inplace=True) def forward(self, x): identity = x # 如果需要对维度进行调整 if self.downsample is not None: identity = self.downsample(x) # 卷积操作并激活 out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) # 将输入添加到输出(残差连接) out += identity out = self.relu(out) return out # 构建简单的网络模型,包含多个残差块 class ResNetLike(nn.Module): def __init__(self, num_classes=10): super(ResNetLike, self).__init__() self.layer1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(64) self.relu = nn.ReLU(inplace=True) # 使用残差块构建网络 self.layer2 = ResidualBlock(64, 64) self.layer3 = ResidualBlock(64, 128, stride=2, downsample=nn.Sequential( nn.Conv2d(64, 128, kernel_size=1, stride=2, bias=False), nn.BatchNorm2d(128) )) self.layer4 = ResidualBlock(128, 128) # 全局平均池化和全连接层 self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) self.fc = nn.Linear(128, num_classes) def forward(self, x): x = self.layer1(x) x = self.bn1(x) x = self.relu(x) x = self.layer2(x) x = self.layer3(x) x = self.layer4(x) x = self.avgpool(x) x = torch.flatten(x, 1) # 展平 x = self.fc(x) return x # 模拟输入 # Batch size of 1, 3 channels (RGB), 32×32 image size input_data = torch.randn(1, 3, 32, 32) # 创建模型 model = ResNetLike(num_classes=10) # 前向传播 output = model(input_data) print("网络输出:", output) # 损失和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 模拟训练步骤 target = torch.tensor([1]) # 假设的目标类别 optimizer.zero_grad() loss = criterion(output, target) loss.backward() optimizer.step() print("训练损失:", loss.item()) # 检查残差块中的参数更新情况 for name, param in model.layer2.named_parameters(): print(f"{name}: {param.grad}")代码解析如下:
1) ResidualBlock:定义了一个残差块,包含两个卷积层和批归一化层。在前向传播中,将输入添加到卷积层输出中,实现残差连接。如果输入和输出的维度不同,使用下采样层调整输入以匹配输出维度。
2) ResNetLike:搭建一个简单的网络模型,包含多个残差块。在模型结构中,前几层为卷积层,用于特征提取,中间层使用多个残差块来增强特征表达能力,最后通过全局平均池化和全连接层实现分类。
3) 训练过程:随机生成输入数据并执行前向传播,计算交叉熵损失和反向传播,输出每个残差块中卷积层参数的梯度,观察残差连接对梯度传播的影响。
代码运行结果如下:
网络输出: tensor([[ 0.1210, -0.3456, ..., 0.7645]]) 训练损失: 2.5308 layer2.conv1.weight: tensor([...], grad_fn=<SubBackward0>) layer2.bn1.weight: tensor([...], grad_fn=<SubBackward0>) ...结果解析如下:
- 网络输出:展示了经过残差网络处理后的输出,表明该网络可以通过残差连接捕捉到有效特征;
- 训练损失:表示网络在一次训练迭代中的损失值;
- 梯度检查:显示残差块中各参数的梯度,表明梯度在深层网络中传播顺畅,证实了残差连接能缓解梯度消失问题。
层归一化与训练稳定性
层归一化是一种提高神经网络训练稳定性的正则化方法。通过对每一层的输入进行标准化,使得网络中的每一层在训练过程中保持相对一致的分布,从而加速收敛并缓解梯度消失问题。层归一化将输入在特征维度上进行标准化,并使用可学习的缩放参数和偏置参数进行调整,使得网络能够更灵活地适应不同任务。相比于批归一化,层归一化在序列建模任务和小批量数据训练中更加适用。
以下代码将展示层归一化的实现及其在神经网络中的应用。
import torch import torch.nn as nn import torch.optim as optim import torch.nn.functional as F # 设置随机种子 torch.manual_seed(42) # 定义带层归一化的神经网络层 class LayerNormBlock(nn.Module): def __init__(self, embed_size): super(LayerNormBlock, self).__init__() self.layer_norm = nn.LayerNorm(embed_size) self.fc = nn.Linear(embed_size, embed_size) self.relu = nn.ReLU() def forward(self, x): # 层归一化后进行全连接和激活操作 out = self.layer_norm(x) out = self.fc(out) out = self.relu(out) return out # 构建包含层归一化的简单网络模型 class SimpleLayerNormModel(nn.Module): def __init__(self, input_dim, hidden_dim, num_classes=10): super(SimpleLayerNormModel, self).__init__() self.layer1 = LayerNormBlock(input_dim) self.layer2 = LayerNormBlock(hidden_dim) self.layer3 = LayerNormBlock(hidden_dim) self.fc_out = nn.Linear(hidden_dim, num_classes) def forward(self, x): x = self.layer1(x) x = self.layer2(x) x = self.layer3(x) x = self.fc_out(x) return x # 模拟输入数据 input_data = torch.randn(4, 128) # Batch size of 4, feature size 128 # 创建模型 model = SimpleLayerNormModel(input_dim=128, hidden_dim=128, num_classes=10) # 前向传播 output = model(input_data) print("网络输出:", output) # 损失和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # 模拟训练步骤 target = torch.randint(0, 10, (4,)) # 随机生成目标类别 optimizer.zero_grad() loss = criterion(output, target) loss.backward() optimizer.step() print("训练损失:", loss.item()) # 检查层归一化块中的参数更新情况 for name, param in model.layer1.named_parameters(): print(f"{name}: {param.grad}") # 测试模型在不同输入分布下的稳定性 test_input = torch.randn(4, 128) * 2 + 5 # 生成不同分布的输入数据 test_output = model(test_input) print("测试数据的网络输出:", test_output)代码解析如下:
1) LayerNormBlock:实现带层归一化的基本模块,包含层归一化、全连接和激活函数。每次前向传播时,输入会先经过层归一化,使得各特征在标准化后更加稳定,避免分布偏移对训练的影响。
2) SimpleLayerNormModel:构建一个简单的神经网络结构,包含3个层归一化模块。每层对输入进行层归一化,再通过全连接和激活层处理,使得输出更具有可训练性和稳定性。
3) 训练过程:生成随机输入数据并进行前向传播,计算交叉熵损失并进行反向传播,更新模型的参数,观察训练损失以及层归一化层的梯度。
4) 测试模型在不同输入分布下的稳定性:模拟不同分布的输入数据,以验证层归一化在保持模型输出稳定性方面的作用。
代码运行结果如下:
网络输出: tensor([[ 0.1210, -0.3456, ..., 0.7645]]) 训练损失: 2.5308 layer1.layer_norm.weight: tensor([...], grad_fn=<SubBackward0>) layer1.layer_norm.bias: tensor([...], grad_fn=<SubBackward0>) 测试数据的网络输出: tensor([[0.6543, -0.2345, ..., 1.1245]])结果解析如下:
- 网络输出:表示模型在前向传播后的输出,表明经过层归一化后的模型能够产生稳定的特征;
- 训练损失:表示训练过程中计算的损失值,表明模型能够有效地进行学习;
- 梯度检查:显示层归一化层的参数梯度,表明在训练中层归一化层的可学习参数更新正常;
- 测试数据的网络输出:验证层归一化在不同输入分布下的稳定性,使模型在输入变化时依然保持输出的稳定性和一致性。