注:本文为学习 DataWhale 开源教程《深入浅出 Pytorch》第三章所做学习笔记,仅记录笔者认为价值较高且之前接触较少的知识点
Pytorch 的数据读取通过 Dataset + DataLoader 实现,Dataset 定义了数据的格式和数据变换方式,DataLoader 定义了如何从数据集中加载每一批数据。
定义 Dataset 类需要继承 Pytorch 自身的 Dataset 类,并相应定义自己的类方法,主要包括三个函数:
1. init:构造函数,定义样本集、构造数据集
1. getitem:定义如何读取数据集中的样本
1. len:返回数据集样本数
可以根据本地数据所在的 csv 文件,定义自己的 Dataset 并定义相关方法,此处定义一个构建头条新闻文本的 Dataset:
class MyDataset(Dataset):
def __init__(self, data_base):
"""
Args:
data_base: 读取的原始数据,list类型。
"""
self.data, self.label = self.pre_change(data_base)
def pre_change(self, data_base):
# 用于对读入的数据进行预处理成DataFrame类型
label_list = []
# 存标签数据
text_list = []
# 存文本数据
key_words_list = []
# 存关键词文本
for one_item in data_base:
# 每一条数据是一个以_!_分割的字符串
info_list = one_item.split("_!_")
label_list.append(int(info_list[1]))
text_list.append(info_list[3] + info_list[4])
return pd.DataFrame(text_list, columns=["text"]), pd.DataFrame(label_list, columns=["label"])
def __getitem__(self, index):
"""
Args:
index: the index of item
Returns:
text and its labels
"""
text = self.data.iloc[index, 0]
label = self.label.iloc[index, 0]
return text, label
def __len__(self):
return self.data.shape[0]
此处我们的源数据为 txt 类型,读入成列表之后进行了训练集、测试集与验证集的拆分。
Pytorch 在训练模型时,将使用 DataLoader 从 Dataset 中依次迭代一批数据(一个 batch_size),因此需要基于三个 Dataset 构造三个 DataLoader:
# 具体构建三种数据的数据集
train_dataset = MyDataset(train_data)
test_dataset = MyDataset(test_data)
eval_dataset = MyDataset(eval_data)
# 构建Dataloader
train_dataloader = DataLoader(train_dataset, batch_size=batch_size, num_workers=5, shuffle=True, drop_last=True)
# batch_size为批量;num_workers为读取数据的进程数;shuffle为是否对数据进行洗牌;drop_last为对最后不足一批的数据是否进行丢弃
test_dataloader = DataLoader(test_dataset, batch_size=batch_size, num_workers=5, shuffle=False)
eval_dataloader = DataLoader(eval_dataset, batch_size=batch_size, num_workers=5, shuffle=False)
Pytorch 基于 Module 类构造自己的神经网络模型,一个模型可以构建多个层,需要定义构造函数以及前向计算函数。
首先我们定义几个常见的层。
自定义无参数层:
class MyLayer(nn.Module):
def __init__(self, **kwargs):
# 构造函数
super(MyLayer, self).__init__(**kwargs)
def forward(self, x):
# 前向计算函数,此处为减去向量均值
return x - x.mean()
注:定义一个自定义层,主要需要定义构造函数、前向计算函数,在构造函数中实现该层的初始化,在前向计算函数中实现该层的计算功能。
自定义含参数层:
class MyDictDense(nn.Module):
def __init__(self):
super(MyDictDense, self).__init__()
self.params = nn.ParameterDict({
'linear1': nn.Parameter(torch.randn(4, 4)),
'linear2': nn.Parameter(torch.randn(4, 1))
})
self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增
def forward(self, x, choice='linear1'):
# 默认使用 linear1 的参数
return torch.mm(x, self.params[choice])
# torch.mm 为矩阵相乘
注:定义一个含参数层,可以使用 ParameterDict 构造参数字典,也可以使用 ParamterList 构造参数列表
卷积层
# 卷积运算(二维互相关)
def corr2d(X, K):
h, w = K.shape
X, K = X.float(), K.float()
Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
return Y
# 二维卷积层
class Conv2D(nn.Module):
def __init__(self, kernel_size):
super(Conv2D, self).__init__()
self.weight = nn.Parameter(torch.randn(kernel_size))
self.bias = nn.Parameter(torch.randn(1))
# 二维卷积有两个参数
def forward(self, x):
return corr2d(x, self.weight) + self.bias
注:卷积层是卷积神经网络的重要搭建部件,实现卷积层重点在于卷积操作的定义
池化层
def pool2d(X, pool_size, mode='max'):
p_h, p_w = pool_size
Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
for i in range(Y.shape[0]):
for j in range(Y.shape[1]):
if mode == 'max':
Y[i, j] = X[i: i + p_h, j: j + p_w].max()
elif mode == 'avg':
Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
return Y
注:池化是用于缓解过拟合的重要操作,可以选择最大池化、平均池化或其他
Pytorch 性能的一个重要影响因素是参数初始化范围,Pytorch 提供了多种参数初始化的方法:
1. uniform_:均匀分布初始化
1. normal_:正态分布初始化
1. constant_:常数初始化
1. eye_:初始化为单位矩阵
1. sparse_:初始化为稀疏矩阵
注:可以使用 isinstance 判断该层属于什么模块,然后对于不同的模块需要进行不同的初始化
可以基于上述方法封装一个初始化函数:
def initialize_weights(self):
for m in self.modules():
# 判断是否属于Conv2d
if isinstance(m, nn.Conv2d):
torch.nn.init.xavier_normal_(m.weight.data)
# 判断是否有偏置
if m.bias is not None:
torch.nn.init.constant_(m.bias.data,0.3)
elif isinstance(m, nn.Linear):
torch.nn.init.normal_(m.weight.data, 0.1)
if m.bias is not None:
torch.nn.init.zeros_(m.bias.data)
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zeros_()
损失函数是 Pytorch 训练的一个重要组成部分,此处仅列出各种常用的损失函数方法及名称,具体原理可参见《NNDL》书籍:
二分类交叉熵损失函数:weight 为每个类别 loss 的权重,size_average 为是否对 loss 取平均值,reduce 为是否返回标量,
torch.nn.BCELoss(weight=None, size_average=None, reduce=None, reduction='mean')
交叉熵损失函数:ignore_index 为忽略某个类的损失
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
L1 损失函数:即 MAE
torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
MSE 损失函数
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
平滑 L1 损失函数:即平滑之后的 L1 损失,beta 为平滑系数
torch.nn.SmoothL1Loss(size_average=None, reduce=None, reduction='mean', beta=1.0)
多标签边界损失函数:用于多标签分类
torch.nn.MultiLabelMarginLoss(size_average=None, reduce=None, reduction='mean')
二分类逻辑损失函数:
torch.nn.SoftMarginLoss(size_average=None, reduce=None, reduction='mean')
HingeEmbedding损失:专用于 Embedding,margin 为边界值
torch.nn.HingeEmbeddingLoss(margin=1.0, size_average=None, reduce=None, reduction='mean')
Pytorch 中的训练有一个统一的流程:
def train(epoch):
model.train()
# 打开训练模式
train_loss = 0
# 训练损失
for data, label in train_loader:
# 一次训练一批样本
data, label = data.cuda(), label.cuda()
# 将样本和标签都加载到GPU
optimizer.zero_grad()
# 清零梯度值
output = model(data)
# 根据样本输出预测标签
loss = criterion(label, output)
# 根据预测标签计算损失函数
loss.backward()
# 反向传播
optimizer.step()
# 梯度下降
train_loss += loss.item()*data.size(0)
# 加上损失
train_loss = train_loss/len(train_loader.dataset)
# 计算这一个 epoch 的整体损失
print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
训练和测试不同的地方在于:
1. 测试不需要计算梯度、反向传播
2. 测试不需要优化器置零
3. 测试不需要更新优化器
优化器会在一定程度上影响模型的收敛效果以及收敛速度,Pytorch 提供的常见优化器包括:ASGD、Adam、Adagrad、LBFGS、SGD等。
常用的优化器方法:
1. zero_grad:梯度清零
2. step:执行一步梯度更新
3. load_state_dict:加载状态参数字典,可以断点续传
4. state_dict:获取当前状态参数字典