计算性能+提升模型性能的关键技术
计算性能
单机多卡并行
一台机器可以安装多个GPU,在训练和预测时,将一个小批量计算切分到到多个GPU来达到加速目的。常用的切分方案有:数据并行,模型并行,通道并行(数据+模型并行)。
数据并行
将小批量分成n块,每个GPU拿到完整参数计算的一块数据的梯度,通常性能更好
模型并行
将模型分成n块,每个GPU拿到一块模型计算它的前向和方向的结果,通常用于模型过大导致单GPU无法放下
总结
- 当一个模型能用单卡计算时,通常使用数据并行拓展到多卡上
- 模型并行则用在超大模型上
分布式计算
在本质上,分布式计算和数据并行并没有太多的差别。
数据并行:数据并行是一种并行计算的策略,通常应用于处理大量数据时。它的基本思想是将数据集划分成多个子集,然后在多个计算单元上并行处理这些子集。
分布式计算:指将计算任务分散到多个计算节点(如服务器、计算机等)上进行处理,这些节点通常通过网络相互连接。分布式计算的主要目的是提高计算能力、容错性和可扩展性。
区别:两者也可以结合使用
-
分布式计算侧重于将计算任务分散到多个节点,强调系统的扩展性和容错性。
-
数据并行则强调在多个处理单元上并行执行相同的操作,主要用于加速数据处理。
在计算机视觉中提升模型性能的手段
数据增广
图片,文本,语音处理,本节针对于图片的处理。从去除像素,颜色变换,亮度变化几个方面对数据进行处理。
为什么要进行数据增广:助于模型更好地学习特征,提高对不同场景的泛化能力。
数据增强:增加一个已有数据集的基础上进行变换,使其具有更多的多样性。例如:在语音里加入背景噪音,改变图片的颜色和形状。
数据增强的处理:== (在线生成) ==在训练中,读取数据时,一定概率下对数据进行增强处理。(例如上一篇文章中的旋转裁剪)->训练中
处理方式:左右翻转,上下翻转(不总是可行,取决于数据集的情况),
切割(从图片中切割一块,然后变形到固定形状):随机高宽比,随机大小,随机位置(图片切法)取出图片再变形到固定形状
颜色:改变色调,饱和度,明亮度(当前值为0.5)
几十种变化手段:https://github.com/aleju/imgaug
定义辅助函数apply,在函数在输入图像img上多次运行图像增广方法aug并显示所有结果:
def apply(img, aug, num_rows=2, num_cols=4, scale=1.5):
Y = [aug(img) for _ in range(num_rows * num_cols)]
d2l.show_images(Y, num_rows, num_cols, scale=scale)
# 旋转和裁剪
# 水平翻转
apply(img, torchvision.transforms.RandomHorizontalFlip())
# 垂直翻转
apply(img, torchxision.transforms.RandomVerticalFlip())
# 裁剪
shape_aug = torchvision.transforms.RandomResizedCrop(
(200, 200), scale=(0.1, 1), ratio=(0.5, 2))
apply(img, shape_aug)
# 更改色调
# 创建一个RandomColorJitter实例,并设置如何同时随机更改图像的亮度(brightness)、对比度(contrast)、饱和度(saturation)和色调(hue)
color_aug = torchvision.transforms.ColorJitter(
brightness=0.5, contrast=0.5, saturation=0.5, hue=0.5)
apply(img, color_aug)
# 结合多种图像增广
augs = torchvision.transforms.Compose([
torchvision.transforms.RandomHorizontalFlip(), color_aug, shape_aug])
apply(img, augs)
微调
微调包括以下四个步骤:(通常运用在迁移学习中,迁移学习里的一类算法)
- 在源数据集(例如ImageNet数据集)上预训练神经网络模型,即源模型。
- 创建一个新的神经网络模型,即目标模型。这将复制源模型上的所有模型设计及其参数(输出层除外)。我们假定这些模型参数包含从源数据集中学到的知识,这些知识也将适用于目标数据集。我们还假设源模型的输出层与源数据集的标签密切相关;因此不在目标模型中使用该层。
- 向目标模型添加输出层,其输出数是目标数据集中的类别数然后随机初始化该层的模型参数。
- 在目标数据集(如椅子数据集)上训练目标模型。输出层将从头开始进行训练,而所有其他层的参数将根据源模型的参数进行微调。
训练:一个目标数据集上的正常训练任务,但使用更强的正则化,使用更小的学习率,使用更少的数据迭代。
源数据集远复杂于目标数据集,通常微调的效果更好。
重用分类器权重:当你利用原有模型对现有模型进行训练时,若源数据集和目标数据中的种类相同,可以使用预训练好的模型分类器中对应标号对应的向量来做初始化。
固定一些层:神经网络通常学习有层次的特征表示,低层次的特征更加通用,高层次的特征则更加跟数据集相关。
根据上述的性质,发现高层次的层对数据分类的影响更强,故可以固定底部一些层的参数,不参与更新,只更新上层容易分类的层。等价于相当于把底层的权重固定,只改变上层模型的参数,相当预把原有的模型缩小,相当于一个更强的正则。
# 代码实现
%matplotlib inline
import os
import torch
import torchvision
from torch import nn
from d2l import torch as d2l
d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip','fba480ffa8aa7e0febbb511d181409f899b9baa5')
data_dir = d2l.download_extract('hotdog')
train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))
hotdogs = [train_imgs[i][0] for i in range(8)]
not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4);
# 使用RGB通道的均值和标准差,以标准化每个通道
normalize = torchvision.transforms.Normalize(
[0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
train_augs = torchvision.transforms.Compose([
torchvision.transforms.RandomResizedCrop(224),
torchvision.transforms.RandomHorizontalFlip(),
torchvision.transforms.ToTensor(),
normalize])
test_augs = torchvision.transforms.Compose([
torchvision.transforms.Resize([256, 256]),
torchvision.transforms.CenterCrop(224),
torchvision.transforms.ToTensor(),
normalize])
# 加载预训练模型,fc为输出层
finetune_net = torchvision.models.resnet18(pretrained=True)
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2)
nn.init.xavier_uniform_(finetune_net.fc.weight);
# 如果param_group=True,输出层中的模型参数将使用十倍的学习率
def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5,
param_group=True):
train_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(
os.path.join(data_dir, 'train'), transform=train_augs),
batch_size=batch_size, shuffle=True)
test_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(
os.path.join(data_dir, 'test'), transform=test_augs),
batch_size=batch_size)
devices = d2l.try_all_gpus()
loss = nn.CrossEntropyLoss(reduction="none")
if param_group:
# 将训练好的模型参数带入
params_1x = [param for name, param in net.named_parameters()
if name not in ["fc.weight", "fc.bias"]]
# 将网络中输出层fc的参数单独设置更高的学习率(原学习率的10倍),其它层的学习率不变
trainer = torch.optim.SGD([{'params': params_1x},
{'params': net.fc.parameters(),
'lr': learning_rate * 10}],
lr=learning_rate, weight_decay=0.001)
else:
trainer = torch.optim.SGD(net.parameters(), lr=learning_rate,
weight_decay=0.001)
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,
devices)
# 比较微调和重新定义模型的区别
# 微调模型训练,小学习率
train_fine_tuning(finetune_net, 5e-5)
# 从头训练一个模型,可以使用大学习率
scratch_net = torchvision.models.resnet18()
scratch_net.fc = nn.Linear(scratch_net.fc.in_features, 2)
train_fine_tuning(scratch_net, 5e-4, param_group=False)
- 练习(复用原有模型与现有模型的相同类别)
# 复用模型类别
# 获取预训练模型“热狗”类别输出层的参数
pretrained_net = torchvision.models.resnet18(pretrained=True)
weight = pretrained_net.fc.weight
# 获取(1000,512)的权值,取出第934号的512权值
hotdog_w = torch.split(weight.data, 1, dim=0)[934]
finetune_net = torchvision.models.resnet18(pretrained=True)
# 二分类
finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2)
nn.init.xavier_uniform_(finetune_net.fc.weight)
# 把热狗当做新分类网络的第1类
finetuning_net.fc.weight.data[0]= hotdogs_w.data.reshape(-1)#关键代码
train_fine_tuning(finetune_net, 5e-5)
总结
- 微调通过使用在大数据上的到的预训练好的模型来初始化模型权重来完成提升精度
- 预训练模型质量很重要
- 微调通常速度更快,精度更高;可能会出现训练精度比测试精度更高
Q1:为什么fc层的学习率要高于其他层的学习率
A1:因为输出层的参数与任务的类别数直接相关,需要较大幅度的调整,而卷积层通常已经学习到较好的特征,不需要调整
Q2:测试中一般做什么样的增广,测试增广在比赛中能提高精度?
A2:以图片中心为基点,裁剪出图片所需大小;或者以四个角为边界,裁剪出图片所需大小,每次进行预测,将得出的结果进行average。(在竞赛中可以使用,一般在实际中很少使用)
- 训练集与测试集对应种类的比例应该相同
- mix-up增广对图像识别有效;标签叠加,先转化成ONE-HOT编码,再将出现的概率叠加