以上章节介绍了自然语言处理中几种常用的神经网络(深度学习)模型,其中每种模型内部都包含大量的参数,如何恰当地设置这些参数是决定模型准确率的关键,而寻找一组优化参数的过程又叫作模型训练或学习。
1.损失函数
为了评估一组参数的好坏,需要有一个准则,在机器学习中,又被称为损失函数(Loss Function)
(无法直接使用准确率等指标评估,因为这些指标对于参数的微小变化有可能不敏感(导数过小)或过于敏感(不可导)从而无法对参数优化)
简单来讲,损失函数用于衡量在训练数据集上模型的输出与真实输出之间的差异。
因此,损失函数的值越小,模型输出与真实输出越相似,可以认为此时模型表现最好。不过如果损失函数的值过小,那么模型就会与训练数据集过拟合(Overfit),反倒不适用于新的数据。所以,在训练深度学习模型时,要避免产生过拟合的现象,有多种技术可以达到此目的,如正则化(Regularization)、丢弃正则化(Dropout)和早停法(Early Stopping)等。
在此介绍深度学习中两种常用的损失函数:均方误差(Mean Squared Error,MSE)损失和交叉熵(Cross-Entropy,CE)损失。
所谓均方误差损失指的是每个样本的平均平方损失,即:
式中,表示样本的数目;表示第个样本的真实输出结果;表示第个样本的模型预测结果。可见,模型表现越好,即预测结果与真实结果越相似,均方误差损失越小。
以上形式的均方误差适用于回归问题,即一个样本有一个连续输出值作为标准答案。那么如何使用均方误差损失处理分类问题呢?
假设处理的是类分类问题,则均方误差被定义为:
式中,表示第个样本的第类上的真实输出结果,只有正确的类别输出为1,其他类别输出为0;表示模型对第个样本的第类上预测结果,如果使用Softmax函数对结果进行归一化,则表示该类别预测的概率。与回归问题的均方误差损失一样,模型表现越好,其对真实类别预测的概率越趋近于1,对于错误类别预测的概率则趋近于0,因此最终计算的损失也越小。
在处理分类问题时,交叉熵损失是一种更常用的损失函数。与均方误差损失相比,交叉熵损失的学习速度更快。其具体定义为:
式中, 表示第个样本的第类上的真实输出结果,只有正确的类别输出为1,其他类别输出为0;表示模型对第个样本属于第类的预测概率。于是,最终交叉熵损失值取决于模型对正确类别预测概率的对数值。如果模型表现越好,则预测的概率越大,由于公式右侧前面还有一个负号,所以交叉熵损失越小(这符合直觉)。更本质地讲,交叉熵损失函数公式右侧是对多类输出结果的分布(伯努利分布)求极大似然中的对数似然函数(Log-Likehood)。另外,由于交叉熵损失只取决于正确类别的预测结果,所以其还可以进一步简化,即:
式中,表示模型对第个样本在正确类别上的预测概率。所以,交叉熵损失也被称为负对数似然损失(Negative Log Likelihood,NLL)。之所以交叉熵损失的学习速度更高,是因为当模型错误较大时,即对正确类别的预测结果偏小(趋向于0),负对数的值会非常大;当模型错误较小时,即对正确类别的预测结果偏大(趋近于1),负对数的值会趋近于0。这种变化是呈指数形的,即当模型错误较大时,损失函数的梯度较大,因此模型学得更快;而当模型错误较小时,损失函数的梯度较小,此时模型学得更慢。
2.梯度下降
梯度下降(Gradient Descent,GD)是一种非常基础和常用的参数优化方法。梯度(Gradient)即以向量的形式写出的对多元函数各个参数求得的偏导数。
如函数对各个参数求偏导,则梯度向量为,也可以记为
。
梯度的物理意义是函数值增加最快的方向,或者说,沿着梯度的方向更加容易找到函数的极大值;反过来说,沿着梯度相反的方向,更加容易找到函数的极小值。
正是利用了梯度的这一性质,对深度学习模型进行训练时,就可以通过梯度下降法一步步地迭代优化一个事先定义的损失函数,即得到较小的损失函数,并获得相应的模型参数值。
梯度下降算法如下所示:
算法4.1 梯度下降算法 |
Input:学习率;含有个样本的训练数据 |
Output:优化参数 |
1. 设置损失函数为; |
2. 初始化参数 |
3. while 未达到终止条件 do |
4. 计算梯度; |
5. |
6. end |
在算法中,循环的终止条件根据实际情况可以有多种,如给定的循环次数、算法两次循环之间梯度变化的差小于一定的阈值和在开发集上算法的准确率不再提升等,读者可以根据实际情况自行设定。
然而,当训练数据的规模比较大时,如果每次都遍历全部的训练数据计算梯度,算法的运行时间会非常久。为了提高算法的运行速度,每次可以随机采样一定规模的训练数据来估计梯度,此时被称为小批次梯度下降(Mini-batch Gradient Descent,SGD)。
接下来,以多层感知器(《自然语言处理:基于预训练模型的方法》第四章 自然语言处理中的神经网络基础--多层感知器_勇气与行动-CSDN博客)为例,介绍如何使用梯度下降法获得优化的参数,解决异或问题。
算法4.1 小批次梯度下降算法 |
Input:学习率;批次大小;含有个样本的训练数据 |
Output:优化参数 |
1. 设置损失函数为; |
2. 初始化参数 |
3. while 未达到终止条件 do |
4. 从训练数据中采集个样本 |
5. 计算梯度; |
6. |
7. end |
import torch
from torch import nn,optim
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, num_class):
super(MLP, self).__init__()
self.linear1 = nn.Linear(input_dim, hidden_dim)
self.activate = torch.relu
self.linear2 = nn.Linear(hidden_dim, num_class)
def forward(self, inputs):
hidden = self.linear1(inputs)
activation = self.activate(hidden)
outputs = self.linear2(activation)
# 获得每个输入属于某一类别的概率(Softmax),然后再取对数
# 取对数的目的是避免计算Softmax时可能产生的数值溢出问题
log_probs = torch.log_softmax(outputs, dim=1)
return log_probs
# 异或问题的4个输入
x_train = torch.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]])
# 每个输入对应的输出类别
y_train = torch.tensor([0, 1, 1, 0])
# 创建多层感知器模型,输入层大小为2,隐含层大小为5,输出层大小为2(即有2个类别)
model = MLP(input_dim=2, hidden_dim=5, num_class=2)
criterion = nn.NLLLoss()# 当使用log_softmax输出时,需要调用负对数似然损失(Negative Log Likelihood,NLL)
optimizer = optim.SGD(model.parameters(),lr=0.05)
# 使用梯度下降参数优化方法,学习率设置为0.05
for epoch in range(500):
y_pred = model(x_train)# 调用模型,预测输出结果
loss = criterion(y_pred,y_train) # 通过对比预测结果与正确的结果,计算损失
optimizer.zero_grad()# 在调用反向传播算法之前,将优化器的梯度值置为零,否则每次循环的梯度将累加
loss.backward()
optimizer.step()
# 在优化器中更新参数,不同优化器更新的方法不同,但是调用方式相同
print("Parameters:")
for name,param in model.named_parameters():
print(name,param.data)
y_pred = model(x_train)
print("Predicted results:",y_pred.argmax(axis=1))
运行结果:
输出结果如下:首先,输出网络的参数值,包括两个线性映射层的权重和偏置项的值;然后,输出网络对训练数据的预测结果,即[0,1,1,0],其与原训练数据相同,说明该组参数能够正确处理异或问题(即线性不可分问题)
需要注意的是,PyTorch提供了nn.CrossEntropyLoss损失函数(类),不过与一般意义上的交叉熵损失不同,其在计算损失之前自动进行Softmax计算,因此在网络的输出层不需要再调用Softmax运算,直接将输出分数最高的类别作为预测结果即可。
除了nn.NLLLoss和nn.CrossEntropyLoss,PyTorch还定义了很多其他常用的损失函数。
同样地,除了梯度下降,PyTorch还提供了其他的优化器,如Adam、Adagrad和Adadelta等,这些优化器是对原始梯度下降法的改进,改进思路包括动态调整学习率,对梯度累积等。它们的调用方式也非常简单,只要在定义优化器时替换为相应的优化器类,并提供一些必要的参数即可。