1.基本概念
梯度下降(gradient descent):计算损失函数关于参数的导数(也称梯度),如下图更新参数。
batch:计算损失函数最简单的做法是针对数据集中所有样本的损失均值求关于参数的导数,再进行参数更新;但遍历整个数据集使实际中的执行可能会非常慢。因此最常用的用法是随机抽取一小批样本, batch_size即这个小批量的训练样本数量,计算小批量的平均损失关于模型参数的导数,再进行更新。
epoch: 在每个迭代周期(epoch)中,遍历整个数据集, 并将训练数据集中所有样本都使用一次。num_epochs是超参数,自己设定。
2.训练过程
设y = wx+b
2.1定义data_iter来读取数据集
定义一个data_iter函数
输入:batch_size、features、labels
输出:生成大小为batch_size的小批量(每一个为feature和对应的label)
纯手写
def data_iter(batch_size, features, labels):
num_examples = len(features) #样本数量
indices = list(range(num_examples)) #取batch的下标
# 这些样本是随机读取的,没有特定的顺序
random.shuffle(indices) #通过shuffle打乱下标
for i in range(0, num_examples, batch_size):
batch_indices = torch.tensor(
indices[i: min(i + batch_size, num_examples)])
yield features[batch_indices], labels[batch_indices]
使用torch
torch.utils.data包含相关处理接口
from torch.utils import data
def load_array(data_arrays, batch_size, is_train=True): #
"""构造一个PyTorch数据迭代器"""
dataset = data.TensorDataset(*data_arrays) #包装数据
return data.DataLoader(dataset, batch_size, shuffle=is_train) #用dataloader进行封装
batch_size = 10
data_iter = load_array((features, labels), batch_size)
2.2定义模型
假设定义为线性模型
def linreg(X, w, b):
"""线性回归模型"""
return torch.matmul(X, w) + b
使用torch
简单的模型,直接使用torch.nn的接口,模型1如下:
from torch import nn
net = nn.Sequential(nn.Linear(2, 1))
复杂的模型,可继承 nn.Module(它本身是一个类并且能够跟踪状态)建立子类,并实例化模型,模型2如下是一个基于bert的多标签模型,其中也用到了nn.Linear()建立线性层:
class Model(nn.Module):
def __init__(self, config):
super(Model, self).__init__() #调用父类方法
self.bert = BertModel.from_pretrained(config.bert_path)
for param in self.bert.parameters():
param.requires_grad = True #True代表需要forward train ,false则参数固定不微调
self.fc = nn.Linear(config.hidden_size, config.num_classes)
self.drop = nn.Dropout(config.dropout)
def forward(self, x):
#x[ids,seq_len,mask]
context = x[0] # 输入的句子 shape[batch_size,pad_size]
mask = x[2] # 对padding部分进行mask,和句子一个size,padding部分用0表示,如:[1, 1, 1, 1, 0, 0]
_, pooled = self.bert(context, attention_mask=mask, output_all_encoded_layers=False) #pooled shape[batch_size,hidden_size]
out = self.fc(pooled) #shape[batch_size,num_classes]
out = torch.sigmoid(out)
return out
2.3初始化模型参数
纯手写
w = torch.normal(0, 0.01, size=(2,1), requires_grad=True)
b = torch.zeros(1, requires_grad=True)
使用torch
正如构造nn.Linear时可指定输入和输出尺寸, 也能直接访问参数以设定它们的初始值。 通过net[0]选择网络中的第一个图层, 然后使用weight.data和bias.data方法访问参数。 还可以使用替换方法normal_和fill_来重写参数值。
针对模型1
net[0].weight.data.normal_(0, 0.01)
net[0].bias.data.fill_(0)
2.4定义loss function
纯手写
假设定义为均方误差
def squared_loss(y_hat, y): #
"""均方损失"""
return (y_hat - y.reshape(y_hat.shape)) ** 2 / 2
使用torch
torch.nn和torch.nn.functional中都定义了多种loss functions(交叉熵、均方差等)、activate functions,还定义了Convolution functions、Pooling functions等,可直接调用loss functions接口。但torch.nn和torch.nn.functional使用方法略有不同
官网地址:https://pytorch.org/docs/stable/nn.html
torch.nn调用代码实例:
loss = nn.MSELoss() #定义loss function
l = loss(net(X) ,y) #计算
torch.nn.functional调用代码实例:
import torch.nn.functional as F
loss = F.mse_loss(net(X) ,y) # 不用定义,直接计算mse_loss
2.5定义优化算法
纯手写
def sgd(params, lr, batch_size):
"""小批量随机梯度下降"""
with torch.no_grad():
#遍历params数组,对里面的每一个参数进行更新
for param in params:
param -= lr * param.grad / batch_size
param.grad.zero_()
使用torch
直接调用接口,调用代码如下:
from torch import optim
trainer =optim.SGD(net.parameters(), lr=0.03)
2.6训练
纯手写
lr = 0.03
num_epochs = 3
net = linreg
loss = squared_loss
for epoch in range(num_epochs):
for X, y in data_iter(batch_size, features, labels):
l = loss(net(X, w, b), y) # X和y的小批量损失
# 因为l形状是(batch_size,1),而不是一个标量。l中的所有元素被加到一起,
# 并以此计算关于[w,b]的梯度
l.sum().backward()
sgd([w, b], lr, batch_size) # 使用参数的梯度更新参数
with torch.no_grad():
train_l = loss(net(features, w, b), labels)
print(f'epoch {epoch + 1}, loss {float(train_l.mean()):f}')
使用torch
num_epochs = 3
for epoch in range(num_epochs):
for X, y in data_iter:
l = loss(net(X) ,y)
trainer.zero_grad()
l.backward()
trainer.step()
l = loss(net(features), labels)
print(f'epoch {epoch + 1}, loss {l:f}')