机器学习的基本训练过程:计算一个函数关于一个变量在某一取值下的导数,从而基于梯度对参数进行优化;
Pytorch 提供自动计算梯度的功能,仅需执行tensor.backward(),则会自动通过反向传播算法完成,在训练模型时会用到该函数;
简单的示例:$z=(x+y)\times(y-2)$
在张量生成时,需显示的设置该变量是否可导,requires_grad=True;
1 2 3 4 5 6 7
| x = torch.tensor([2.], requires_grad=True) y = torch.tensor([3.], requires_grad=True) z = (x + y) * (y - 2) z z.backward() x.grad y.grad
|
手工模拟: $\frac{\mathrm{d}z}{\mathrm{d}x}=y-2$ ,$\frac{\mathrm{d}z}{\mathrm{d}y}=x+2y-2$ ,当 $x=2$ ,$y=3$ 时,$\frac{\mathrm{d}z}{\mathrm{d}x}=1$ 和 $\frac{\mathrm{d}z}{\mathrm{d}y}=6$ ;
注:梯度不能自动清零,在每次反向传播中会叠加;这可能会导致得不到正确的结果,需要手动清零;
1 2 3 4 5 6 7 8 9
| z1 = x*y z1.backward() x.grad y.grad
x.grad.zero_() y.grad.zero_()
|
$\frac{\mathrm{d}z1}{\mathrm{d}x}=y=3$,$\frac{\mathrm{d}z1}{\mathrm{d}y}=x=2$;
Pytorch 模型构建
Pytorch 中进行模型构建的基本流程:准备训练数据集、构建将要使用的模型、设置损失函数和优化器、模型训练。
常用模块:
1 2 3 4 5 6
| import torch from torch.utils.data import TensorDataset from torch.utils.data import DataLoader from torch import nn from torch import optim
|
数据相关
以文本多分类任务为例,制作属于自己的数据集:
需要注意的是制作好的数据集要尽可能的均衡,(eg:分为 8 个类别,8 个类别的比例应该是 1:1… 若有一部分比较多,则选择向少量数据对齐)
Dataset
所有数据集必须继承自Dataset或IterableDataset;
映射型(Map-style)数据集
继承自Dataset类,表示一个从索引到样本的映射(索引不一定是整数,可以是自定义的),为了方便通过dataset[idx]来访问指定索引的样本;
必须实现__gititem__()函数,负责根据指定的 key 返回对应的样本;
__gititem__(self, idx),idx由DataLoader自动分配;
当使用DataLoader加载Dataset时,会根据batch_size、shuffle等参数自动生成一组索引(dix)
__len__()返回数据集的大小;
自定义数据集
迭代型(Iterable-style)数据集
……
DataLoader
DataLoader 数据加载器,方便对数据进行批量处理和模型训练,通常用于将数据分批次加载到模型中进行训练;
实际训练模型时,需要先将数据集切分为很多 mini-batches,然后按批(batch)将样本送入模型;每一个完整遍历所有样本的循环即一个 epoch;
训练相关
优化器用于调整模型参数,以最小化损失函数。损失函数用于衡量模型预测值与真实值之间的差异。训练过程中,通过优化器和损失函数的配合,模型不断的调整参数,以提高预测的准确性;
1 2 3 4 5 6 7 8 9 10
| 训练阶段: for each epoch: for each batch: 清空梯度 → 前向传播 → 计算损失 → 反向传播 → 更新参数 → 记录损失 计算epoch平均训练损失 → 记录到TensorBoard
验证阶段: for each batch: 禁用梯度 → 前向传播 → 计算损失 → 记录损失 计算epoch平均验证损失 → 检查是否保存模型 → 检查早停条件
|
示例(一个简单的训练过程):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| def trainer(train_loader, valid_loader, model, config, device): criterion = nn.MSELoss(reduction='mean') optimizer = torch.optim.SGD(model.parameters(), lr=config['learning_rate'], momentum=0.9) writer = SummaryWriter() if not os.path.isdir('./models'): os.mkdir('./models')
n_epochs, best_loss, step, early_stop_count = config['n_epochs'], math.inf, 0, 0 for epoch in range(n_epochs): model.train() loss_record = []
for x, y in train_loader: optimizer.zero_grad() x, y = x.to(device), y.to(device) pred = model(x) loss = criterion(pred, y) loss.backward() optimizer.step() step += 1 loss_record.append(loss.detach().item()) mean_train_loss = sum(loss_record) / len(loss_record) writer.add_scalar('Loss/train', mean_train_loss, step) model.eval() loss_record = [] for x, y in valid_loader: x, y = x.to(device), y.to(device) with torch.no_grad(): pred = model(x) loss = criterion(pred, y) loss_record.append(loss.item()) mean_valid_loss = sum(loss_record) / len(loss_record) print(f'Epoch [{epoch + 1}/{n_epochs}]: Train loss: {mean_train_loss:.4f}, Valid loss: {mean_valid_loss:.4f}') writer.add_scalar('Loss/valid', mean_valid_loss, step) if mean_valid_loss < best_loss: best_loss = mean_valid_loss torch.save(model.state_dict(), config['save_path']) print('Saving model with loss {:.3f}...'.format(best_loss)) early_stop_count = 0 else: early_stop_count += 1 if early_stop_count >= config['early_stop']: print('\nModel is not improving, training stopped.') break
|
transforms.Compose: 将多个变换组合成一个单一的变换管道。
https://transformers.run/c2/2021-12-14-transformers-note-3/
PS
torch.nn.Moudle
torch.nn.functional
Compose()按顺序处理数据
以图像为例:(训练集和测试集略有不同)统一大小,数据增强(旋转、翻转等)增加数据的多样性、随机性,裁剪(图片的某一部分【最终的图片大小】),ToTensor,Normalize
本质:怎样从数据中作特征提取
分词、ID映射 Embedding向量,每个词对应不同的Embedding向量
指定随机种子,因为每次训练模型使用框架的默认随机初始化值,要保证每次训练随机初始化的值相同,从而为进一步对不同训练出来的模型参数进行对比(这样才具有可比性)
模型使用,持续加载模型(模型推理),持续在GPU中,【eg:训练时用的resnet进行训练的模型参数,加载的时候需要同样的加载原始模型结构,再将新的模型参数赋值给随机初始化的原始模型结构】,数据预处理(对用户传入的数据做与模型训练时进行数据预处理同样的操作)