批量规范化与ResNet——pytorch与paddle实现批量规范化与ResNet
本文将深入探讨批量规范化与ResNet的理论基础,并通过PyTorch和PaddlePaddle两个深度学习框架来展示如何实现批量规范化与ResNet模型。我们将首先介绍批量规范化与ResNet的基本概念,这些理论基础是理解和实现批量规范化与ResNet的基础。通过PyTorch和PaddlePaddle的代码示例,我们将展示如何设计、训练和评估一个ResNet模型,从而让读者能够直观地理解并掌握这两种框架在计算机视觉问题中的应用。
本文部分为torch框架以及部分理论分析,paddle框架对应代码可见批量规范化与ResNet-paddle
import torch
print("pytorch version:",torch.__version__)
pytorch version: 2.3.0+cu118
批量规范化
批量规范化(Batch Normalization,简称BN)是一种在深度学习中广泛使用的技术,旨在加速深层神经网络的训练过程,同时提高模型的稳定性和泛化能力。其基本原理是在网络训练过程中,对每个小批量(mini-batch)的数据进行标准化处理,使得每一层的输入数据具有固定的均值和方差。这样做可以有效缓解内部协变量偏移问题,即网络层之间输入数据分布的变化,从而帮助网络更容易学习和收敛。
具体来说,批量规范化的过程可以归纳为以下几个步骤:
-
计算均值和方差:在每次训练迭代中,对于当前小批量数据,首先计算其均值和方差。这两个统计量是基于当前小批量中的所有样本计算得到的。
-
标准化处理:接着,利用上一步计算得到的均值和方差,对当前小批量中的每个样本进行标准化处理,即减去均值并除以标准差,使得处理后的数据具有均值为0、方差为1的分布。为了数值稳定性,通常会在分母中加上一个小的常数ε(epsilon)。
-
引入可学习参数:标准化处理后的数据虽然具有固定的均值和方差,但其分布可能与网络的原始输入数据分布相差较大,这可能会限制网络的表示能力。因此,批量规范化还引入了两个可学习的参数:拉伸参数(scale,γ)和偏移参数(shift,β)。这两个参数分别用于对标准化后的数据进行缩放和偏移,以恢复其原始的数据分布特性。
-
训练过程中的调整:在训练过程中,批量规范化层会不断根据反向传播算法更新拉伸参数和偏移参数,同时也会更新网络中的其他参数。同时,为了能够在测试阶段使用批量规范化,通常会维护一组全局的均值和方差,这些全局统计量是在训练过程中通过滑动平均的方式计算得到的。
批量规范化的数学公式可以表示为:
BN ( x i ) = γ ( x i − μ B σ B 2 + ϵ ) + β \text{BN}(x_i) = \gamma \left( \frac{x_i - \mu_\mathcal{B}}{\sqrt{\sigma_\mathcal{B}^2 + \epsilon}} \right) + \beta BN(xi)=γ(σB2+ϵ xi−μB)+β
其中, x i x_i xi表示当前小批量中的第 i i i个样本, μ B \mu_\mathcal{B} μB和 σ B 2 \sigma_\mathcal{B}^2 σB2分别表示当前小批量的均值和方差, γ \gamma γ和 β \beta β分别表示拉伸参数和偏移参数, ϵ \epsilon ϵ是一个小的常数用于数值稳定性。
# 让我们测试一下批量规范化层,它对一个mini-batch的输入进行规范化。
# 测试一下
batch_norm = torch.nn.BatchNorm1d(5) # 创建一个批量规范化层,输入的维度为1维
x1 = torch.randn(3, 5)
y1 = batch_norm(x1) # 对输入进行批量规范化
print(y1) # 输出规范化后的结果
tensor([[-1.3022, 0.6686, 1.3802, 1.3624, -0.4244],
[ 0.1735, -1.4135, -0.9572, -0.3528, 1.3805],
[ 1.1287, 0.7449, -0.4229, -1.0096, -0.9561]],
grad_fn=<NativeBatchNormBackward0>)
观察数据可以发现,batch_norm(x1)的输出结果中,对于batch中的每个样本,其均值接近于0,方差接近于1,这符合批量规范化的预期效果。读者不妨思考,当batch_size为1时,批量规范化会如何工作?
运行后可以发现程序报错:Expected more than 1 value per channel when training, got input size torch.Size([1, 5])
这是因为当 batch_size为1时,批量规范化计算均值为每个数本身,方差则为0,因为此时没有足够的样本来计算这些统计量。因此,在训练是批量规范化通常要求batch_size大于1。同时,在测试时,批量规范化会使用训练过程中维护的全局均值和方差,因此不需要担心batch_size的问题。
接下来,我们再测试一下批量规范化层对一个mini-batch的输入进行规范化,其中batch_size为1。
x2 = torch.randn(1, 5)
batch_norm.eval()
y2 = batch_norm(x2)
print(x2)
print(y2)
tensor([[ 1.1944, 0.8720, 0.3163, -0.3744, -0.9057]])
tensor([[ 1.1998, 0.8934, 0.3530, -0.3752, -0.8741]],
grad_fn=<NativeBatchNormBackward0>)
数据输出发现x2和y2一样,这是因为batch_norm还未参与训练,其全局均值和方差仍为0,因此测试时batch_norm(x2)的输出与x2相同。
让我们看一下批量规范化层对于图像数据的处理吧。
batch_norm = torch.nn.BatchNorm2d(3) # 创建一个批量规范化层,输入的样本通道数为3
x1 = torch.randn(3, 3, 1, 2) # 创建一个随机张量,维度为3x3x1x2
y1 = batch_norm(x1) # 对输入进行批量规范化
print(y1) # 输出规范化后的结果
tensor([[[[ 0.3462, 1.8859]],
[[-0.6709, 1.6354]],
[[ 0.2670, -1.3766]]],
[[[ 0.0114, -1.3232]],
[[-0.6927, -1.0844]],
[[-0.9689, 1.7184]]],
[[[-0.7329, -0.1874]],
[[ 1.0732, -0.2606]],
[[-0.0165, 0.3766]]]], grad_fn=<NativeBatchNormBackward0>)
可以发现,批量规范化层对于图像数据的处理与对于一维数据的处理类似,都是对每个通道进行规范化。对这些通道的“每个”输出执行批量规范化,每个通道都有自己的拉伸(scale)和偏移(shift)参数,这两个参数都是标量。 假设我们的小批量包含 m m m个样本,并且对于每个通道,卷积的输出具有高度 h h h和宽度 w w w。 那么对于卷积层,我们在每个输出通道的 m × h × w m \times h \times w m×h×w个元素上同时执行每个批量规范化。 因此,在计算平均值和方差时,我们会收集所有空间位置的值,然后在给定通道内应用相同的均值和方差,以便在每个空间位置对值进行规范化。
ResNet
ResNet(Residual Network)是一种深度卷积神经网络,它通过引入残差连接(Residual Connection)来解决深度神经网络中的梯度消失和梯度爆炸问题。ResNet的核心思想是让网络中的每一层都学习残差映射,而不是直接学习输出。残差映射是指输入与网络输出的差值,而不是直接学习输出。通过这种方式,网络可以更容易地学习到复杂的特征表示。
ResNet的残差连接结构如下所示:
其中, x x x表示输入, f ( x ) − x f(x)-x f(x)−x表示残差映射, f ( x ) f(x) f(x)表示网络的实际输出。通过这种方式,网络可以更容易地学习到复杂的特征表示。
接下来,我们使用CIFAR-10来训练一个ResNet模型,看看模型效果吧!
CIFAR-10数据集是一个广泛使用的图像数据集,由Hinton的学生Alex Krizhevsky和Ilya Sutskever整理,用于识别普适物体的小型数据集。CIFAR-10数据集是从一个叫做“80 million tiny images dataset”(8000万张小图数据集)中精炼剥离出来的一部分,是该数据集的子集。由于原数据集涉及争议内容,目前已被下架。该数据集主要用于机器学习领域的计算机视觉算法基准测试,特别是在图像分类任务中。
- 数据集内容
- 图像数量:CIFAR-10数据集包含60,000张32x32像素的彩色(3通道)图像。
- 类别分布:分为10个类别,每个类别包含6,000张图像。具体类别包括飞机(airplane)、汽车(automobile)、鸟类(bird)、猫(cat)、鹿(deer)、狗(dog)、蛙类(frog)、马(horse)、船(ship)和卡车(truck)。
- 数据划分:数据集被划分为50,000张训练图片和10,000张测试图片。训练图片被进一步分为5个批次(batches),每个批次包含10,000张图片。
CIFAR-10数据集包含的是现实世界中真实的物体,与手写字符数据集(如MNIST)相比,CIFAR-10的噪声更大,物体的比例、特征都不尽相同,这为识别带来很大困难。直接的线性模型(如Softmax)在CIFAR-10上表现得很差,需要更复杂的模型来实现较高的分类准确率。CIFAR-10数据集是一个经典的图像分类数据集,广泛用于计算机视觉领域的研究和教育中。尽管其识别问题在深度学习模型的帮助下已经得到了较好的解决,但它仍然是初学者和研究者了解图像分类问题的一个良好起点。
import torch
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
# 定义数据预处理
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
# 加载数据集
train_dataset = torchvision.datasets.CIFAR10(root='data/CIFAR10_train', train=True, download=True, transform=transform)
test_dataset = torchvision.datasets.CIFAR10(root='data/CIFAR10_test', train=False, download=True, transform=transform)
Files already downloaded and verified
Files already downloaded and verified
让我们将加载好的数据放入迭代器中,并看看训练集的前几张图片和标签。
# 创建数据迭代器
train_loader = DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=False)
# 查看训练集的前几张图片和标签
import math
import numpy as np
import matplotlib.pyplot as plt
num_toshow = 10
for images, labels in train_loader:
print(labels[0:num_toshow]) # 打印标签
# 创建一个新的figure,尺寸为10x5英寸
plt.figure(figsize=(10, 5))
for i in range(num_toshow):
# 选择第i张图片
img = images[i]
# 将图片数据放缩到[0, 255]
data_min, data_max = torch.min(img), torch.max(img)
img = (img - data_min) / (data_max - data_min)
img = img * 255
# 将张量转换为numpy数组,并确保数据类型是uint8
img = img.numpy().astype(np.uint8)
# 将图片从CHW格式转换为HWC格式
img = img.transpose((1, 2, 0))
# 在subplot中展示图片
cols = round(math.sqrt(num_toshow))
plt.subplot(cols, math.ceil(num_toshow / cols), i + 1)
plt.xticks([])
plt.yticks([])
plt.grid(False)
plt.imshow(img)
plt.xlabel(f'Image {i+1}')
# 展示所有subplot
plt.show()
break
tensor([3, 8, 6, 0, 5, 3, 7, 4, 0, 9])
接下来,让我们使用一个ResNet结构的网络模型,并使用CIFAR-10数据集进行训练。我们首先看一下PyTorch自带的resnet18模型。我们可以使用pytorch可视化工具netron查看YOLO网络模型结构。
在命令行执行:
C:\Users\admin>netron
Serving at http://localhost:8080
即可在网页端打开netron
将网络模型保存为.pt,或者将现有的.pt文件导入网页即可。如果没有安装netron,需要先pip一下~:pip install netron
ResNet18 = torchvision.models.resnet18(weights=torchvision.models.ResNet18_Weights.IMAGENET1K_V1) # 从torchvision.models中导入ResNet18模型,
# 会下载并加载在 ImageNet 数据集上预训练过的 ResNet-18 模型权重
torch.save(ResNet18, 'data/resnet18.pt') # 保存模型
对于ResNet结构,读者不妨参考原论文Deep Residual Learning for Image Recognition中的介绍。如下图所示,对于一个没有残差结构的普通卷积神经网络,当网络层数更多时,网络训练和测试反而会变得更差,这是由于梯度消失和梯度爆炸问题导致的。
然而当采用残差结构时,网络训练和测试的效果会随层数增加而提升。如下图所示,下图使用了CIFAR-10数据集进行了验证。
接下来让我们来训练测试一下ResNet18模型。首先我们看一下模型输入输出尺寸是否正确。
out = ResNet18(images)
print(out.shape)
torch.Size([64, 1000])
可以看到,原网络输出类别个数为1000,我们需要对其进行结构修改。
class ResNet18_CIFAR_10(torch.nn.Module):
def __init__(self, output_size=10):
super(ResNet18_CIFAR_10, self).__init__()
# 定义ResNet18模型
self.resnet18 = torchvision.models.resnet18(weights=torchvision.models.ResNet18_Weights.IMAGENET1K_V1)
# 修改全连接层输出类别个数
num_ftrs = self.resnet18.fc.in_features
self.resnet18.fc = torch.nn.Linear(num_ftrs, output_size)
def forward(self, x):
# 前向传播
x = self.resnet18(x)
return x
net = ResNet18_CIFAR_10()
out = net(images)
print(out.shape)
torch.Size([64, 10])
接下来我们进行训练和测试。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
net = net.to(device)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
num_epochs = 10
for epoch in range(num_epochs):
for i, (images, labels) in enumerate(train_loader):
images = images.to(device)
labels = labels.to(device)
outputs =net(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i+1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}')
Epoch [10/10], Step [700/782], Loss: 0.1032
接下来我们在测试集上对模型进行测试。
# 测试模型
net.eval()
total = 0
correct = 0
for data in test_loader:
imgs, labels = data
outputs = net(imgs.to(device))
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels.to(device)).sum()
break
print('Accuracy: %.2f %%' % (100 * correct / total))
Accuracy: 92.19 %
可以看到模型在测试集上具有较好的准确率。ResNet在工程上主要有以下应用领域:
-
图像分类:
- ResNet在图像分类任务中表现优异,尤其是在大规模数据集(如ImageNet)上取得了卓越的性能。通过在大规模数据集上进行预训练,ResNet可以学习到强大的特征表示,进而在各种图像分类任务中取得良好的结果。
-
目标检测:
- 在目标检测任务中,ResNet常作为骨干网络(backbone network),结合相应的检测框架(如Faster R-CNN、YOLO、SSD等)构建高性能的目标检测系统。这些系统能够准确识别图像中的目标物体,并给出其位置和类别信息。
-
语义分割:
- 语义分割是计算机视觉领域的一个重要任务,旨在将图像中的每个像素划分为相应的类别。ResNet在语义分割任务中也具有广泛应用,通过与全卷积网络(FCN)等结构相结合,实现对图像像素级别的精细分类。
除了计算机视觉领域,ResNet还逐渐渗透到自然语言处理、语音识别等其他领域。例如,在自然语言处理中,ResNet可用于文本分类、情感分析等任务;在语音识别中,ResNet可用于提取音频特征,提高识别率。ResNet作为一种强大的深度学习模型结构,在多个领域取得了显著成果,并展现出广阔的发展前景。未来,随着技术的不断进步和应用场景的不断拓展,ResNet有望在更多领域发挥更大作用,为人类社会的进步贡献更多力量。
ork),结合相应的检测框架(如Faster R-CNN、YOLO、SSD等)构建高性能的目标检测系统。这些系统能够准确识别图像中的目标物体,并给出其位置和类别信息。
- 语义分割:
- 语义分割是计算机视觉领域的一个重要任务,旨在将图像中的每个像素划分为相应的类别。ResNet在语义分割任务中也具有广泛应用,通过与全卷积网络(FCN)等结构相结合,实现对图像像素级别的精细分类。
除了计算机视觉领域,ResNet还逐渐渗透到自然语言处理、语音识别等其他领域。例如,在自然语言处理中,ResNet可用于文本分类、情感分析等任务;在语音识别中,ResNet可用于提取音频特征,提高识别率。ResNet作为一种强大的深度学习模型结构,在多个领域取得了显著成果,并展现出广阔的发展前景。未来,随着技术的不断进步和应用场景的不断拓展,ResNet有望在更多领域发挥更大作用,为人类社会的进步贡献更多力量。