1.什么是内部协变量偏移问题:
比如1000条数据,batch_size=4,相当于要练250批次,当第一次批次的4条数据进行模型的训练时,此时网络学习动态已经养成,当第二批次进行训练时,极大可能导致差异较大,即参数变化很大,那么下一层的输入就会收到很大的影响,导致整个网络的学习动态发生改变。
这样结果主要归结于前向传播中的变化的累积,每一层的输出都是下一层的输入。如果上一层的参数在训练中发生较大的变化(特别是在训练初期,毕竟样本太少,很难得到一个方差较小、大家都认可的方案),这将直接影响到下一层接收的输入分布。如果每一层都在接收到与前一次迭代时【前一次batch_size】不同分布的输入,它们就需要不断调整自己来适应这种变化,这会使得网络的训练过程变得复杂且低效。
另外,输入分布不断变化这将导致每一层都需要学习不同的学习速率。这使得设置一个全局学习率变得非常困难。
举个例子:
你在驾车时,道路和交通规则每隔几分钟就会改变。即使你已经适应了当前的驾驶条件,突然的变化也会迫使你不断重新学习如何驾驶,这显然会降低你的驾驶效率和安全性。同样地,在神经网络中,如果每层的输入规则(即数据分布)持续变化,网络层就需要不断调整反应,这降低了学习的效率。
2.解决方案:Batch Normalization
批归一化(Batch Normalization)通过在每一层后规范化输入【均值为0,方差为1】,使得输入分布保持相对稳定【总的来说就是规划输入分布,即调整前一层的输出,允许更高的学习效率】,从而缓解了内部协变量偏移的问题。这意味着网络的每一层都可以预期到它将会接收到具有相似分布的输入,从而使训练过程更稳定,加快收敛速度,使得网络可以使用更高的学习率,而不会那么容易发生训练发散的问题。
举个例子: 在原始的模型中增加BN层,并训练该模型;接着,我们将训练一个不使用BN的模型,并对比两者的训练误差和测试准确率。
class ModelBN(nn.Module):def __init__(self):super(ModelBN, self).__init__()# 定义卷积层,添加BN层self.conv1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(64), # 添加批归一化层nn.ReLU(),nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(128), # 添加批归一化层nn.ReLU(),nn.MaxPool2d(stride=2, kernel_size=2))# 定义全连接层,添加BN层self.dense = nn.Sequential(nn.Linear(14 * 14 * 128, 1024),nn.BatchNorm1d(1024), # 添加批归一化层nn.ReLU(),nn.Dropout(p=0.5),nn.Linear(1024, 10))def forward(self, x):x = self.conv1(x)x = x.view(-1, 14 * 14 * 128)x = self.dense(x)return x# 实例化带BN的模型,并移至GPU
model_bn = ModelBN().to(device)
训练和测试:
def train_and_test(model, optimizer, epochs=3):cost = nn.CrossEntropyLoss()for epoch in range(epochs):model.train() # 设置模型为训练模式running_loss = 0.0for data in data_loader_train:inputs, labels = datainputs, labels = inputs.to(device), labels.to(device)optimizer.zero_grad()outputs = model(inputs)loss = cost(outputs, labels)loss.backward()optimizer.step()running_loss += loss.item()model.eval() # 设置模型为评估模式correct = 0total = 0with torch.no_grad():for data in data_loader_test:images, labels = dataimages, labels = images.to(device), labels.to(device)outputs = model(images)_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()print(f'Epoch {epoch+1}, Loss: {running_loss/len(data_loader_train)}, 'f'Accuracy: {100 * correct / total}%')# 定义优化器
optimizer_bn = optim.Adam(model_bn.parameters())# 训练和测试带BN的模型
print("Training with Batch Normalization:")
train_and_test(model_bn, optimizer_bn)# 训练和测试不带BN的模型
print("Training without Batch Normalization:")
optimizer = optim.Adam(model.parameters()) # 不带BN的模型使用同样的优化器设置
train_and_test(model, optimizer)
3.完整代码:
# 导入必要的库
import torch
import torch.nn as nn
from torchvision import datasets, transforms, utils
import torch.optim as optim
import torchvision
import numpy as np
import matplotlib.pyplot as plt# 检查并设置设备,优先使用GPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print('设备状态:', device)# 定义数据转换步骤
transform = transforms.Compose([transforms.ToTensor(), # 将图像转换为Tensortransforms.Lambda(lambda x: x.repeat(3, 1, 1)), # 将单通道图像复制到三通道transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5)) # 标准化图像数据
])# 下载并加载MNIST数据集
data_train = datasets.MNIST(root='./data/', transform=transform, train=True, download=True)
data_test = datasets.MNIST(root='./data/', transform=transform, train=False)# 定义数据加载器
data_loader_train = torch.utils.data.DataLoader(dataset=data_train, batch_size=64, shuffle=True)
data_loader_test = torch.utils.data.DataLoader(dataset=data_test, batch_size=64, shuffle=True)# 预览数据
images, labels = next(iter(data_loader_train))
img = utils.make_grid(images) # 组合图像以便可视化
img = img.numpy().transpose(1, 2, 0) # 调整图像维度以适配matplotlib
img = img * np.array([0.5, 0.5, 0.5]) + np.array([0.5, 0.5, 0.5]) # 反标准化显示图像
print([labels[i] for i in range(64)]) # 打印标签检查
plt.imshow(img) # 显示图像
plt.show() # 确保图像显示# 定义卷积神经网络模型
class Model(nn.Module):def __init__(self):super(Model, self).__init__()self.conv1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),nn.ReLU(),nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),nn.ReLU(),nn.MaxPool2d(stride=2, kernel_size=2))self.dense = nn.Sequential(nn.Linear(14 * 14 * 128, 1024),nn.ReLU(),nn.Dropout(p=0.5),nn.Linear(1024, 10))def forward(self, x):x = self.conv1(x)x = x.view(-1, 14 * 14 * 128)x = self.dense(x)return x# 定义含批归一化的卷积神经网络模型
class ModelBN(nn.Module):def __init__(self):super(ModelBN, self).__init__()self.conv1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(64),nn.ReLU(),nn.Conv2d(64, 128, kernel_size=3, stride=1, padding=1),nn.BatchNorm2d(128),nn.ReLU(),nn.MaxPool2d(stride=2, kernel_size=2))self.dense = nn.Sequential(nn.Linear(14 * 14 * 128, 1024),nn.BatchNorm1d(1024),nn.ReLU(),nn.Dropout(p=0.5),nn.Linear(1024, 10))def forward(self, x):x = self.conv1(x)x = x.view(-1, 14 * 14 * 128)x = self.dense(x)return x# 实例化并设置模型至GPU
model_bn = ModelBN().to(device)
model = Model().to(device)# 设置优化器
optimizer_bn = optim.Adam(model_bn.parameters())
optimizer = optim.Adam(model.parameters())# 训练和测试函数,记录损失和
print(model) # 打印模型结构'''
7.训练模型
'''def train_and_test(model, optimizer, n_epochs=3):cost = nn.CrossEntropyLoss()losses = []accuracies = []for epoch in range(n_epochs):model.train()total_loss = 0correct = 0total = 0for data in data_loader_train:inputs, labels = datainputs, labels = inputs.to(device), labels.to(device)optimizer.zero_grad()outputs = model(inputs)loss = cost(outputs, labels)loss.backward()optimizer.step()total_loss += loss.item()_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()avg_loss = total_loss / len(data_loader_train)accuracy = 100 * correct / totallosses.append(avg_loss)accuracies.append(accuracy)print(f'Epoch {epoch + 1}/{n_epochs}, Loss: {avg_loss:.4f}, Accuracy: {accuracy:.2f}%')return losses, accuracies# 使用带BN和不带BN的模型进行训练
optimizer_bn = optim.Adam(model_bn.parameters())
losses_bn, acc_bn = train_and_test(model_bn, optimizer_bn)model_without_bn = Model().to(device)
optimizer_nobn = optim.Adam(model_without_bn.parameters())
losses_nobn, acc_nobn = train_and_test(model_without_bn, optimizer_nobn)plt.figure(figsize=(10, 5))
plt.plot(losses_bn, label='With BatchNorm')
plt.plot(losses_nobn, label='Without BatchNorm')
plt.title('Training Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()
可能原因:
短期增加,长期收益: BN有一定的正则化
效果,这可能在短期内增加训练损失
,但长期
看有助于提高模型的泛化能力
。
批大小效应: BN通过对每个批量的数据进行归一化,依赖于批内数据的统计特性。如果批量大小不足以提供稳定的统计估计,或者批数据本身的变异性较大,初期的BN表现可能不够稳定。【小的干大的】