PyTorch的损失函数和优化器

文章目录

  • PyTorch的损失函数和优化器
    • 损失函数
    • 优化器
    • 总结

PyTorch的损失函数和优化器

损失函数

一般来说,PyTorch的损失函数有两种形式:函数形式和模块形式。前者调用的是torch.nn.functional库中的函数,通过传入神经网络预测值和目标值来计算损失函数。后者是torch.nn库里的模块,通过建立一个模块的实例,然后通过调用模块方法来计算最终的损失函数。

对于回归问题,一般情况下使用的是torch.nn.MSELoss模块,即平方损失函数,在实例中传入神经网络的预测值和目标值,能够计算得到最终的损失函数。

mse = nn.MSELoss() # 初始化平方损失函数模块

t1 = torch.randn(5, requires_grad=True)
t2 = torch.randn(5, requires_grad=True)

print(mse(t1, t2)) # 计算t1和t2之间的平方损失函数

得到的结果为:

tensor(2.2568, grad_fn=<MseLossBackward>)

对于分类问题,如果是二分类用到的交叉熵损失函数,可以使用torch.nn.BCELoss模块实现。同样,在初始化这个模块的时候可以用默认参数,输出所有损失函数的平均。该模块一般接受的是Sigmoid函数的输出。注意这个损失函数接受两个张量,第一个张量是正分类标签的概率值,第二个张量是以0为负分类标签、1为正分类标签的目标数据值,这两个值必须是浮点类型。

bce = nn.BCELoss()

t1 = torch.randn(5, requires_grad=True)
t1s = torch.sigmoid(t1) # 对张量求Sigmoid函数,转换为(0, 1)之间的概率
t2 = torch.randint(0, 2, (5, )).float() # 随机生成(0, 1)之间的整数序列并转换为浮点数

print(bce(t1s, t2)) # 计算交叉熵

得到的结果为:

tensor(0.8664, grad_fn=<BinaryCrossEntropyBackward>)

另外在二分类问题中也经常用到对数交叉熵损失函数torch.nn.BECWithLogitsLoss,这个函数和前面的交叉熵损失函数的区别在于可以直接省略Sigmoid函数部分的计算。这是因为该函数会自动在损失函数内部的实现部分添加Sigmoid激活函数,在训练时可以增加计算的数值稳定性。

bce_logits = nn.BCEWithLogitsLoss() # 使用交叉熵对数损失函数

print(bce_logits(t1, t2)) # 计算交叉熵,结果应和前面的一致

得到的结果为:

tensor(0.8664, grad_fn=<BinaryCrossEntropyWithLogitsBackward>)

在多分类的情况下,也可以使用两个模块。第一个模块是torch.nn.NLLLoss,即负对数似然函数,这个损失函数的运算过程是根据预测值和目标值计算这两个值按照元素值一一对应的乘积,然后对乘积求和,并去负值。注意这里的预测值是经过Softmax的计算和对数计算的,目标值使用one-hot编码。 因此这个损失函数在使用之前必须先计算Softmax函数去对数的结果。可以用PyTorch中的torch.nn.functional.log_softmax实现。

N = 10 # 定义分类数目

t1 = torch.randn(5, N, requires_grad=True) # 随机产生预测张量
t1s = torch.nn.functional.log_softmax(t1, -1) # 计算预测张量的LogSoftmax
t2 = torch.randint(0, N, (5, )) # 随机产生目标张量

nll = nn.NLLLoss() # 定义损失函数

print(nll(t1s, t2)) # 计算损失函数

得到的结果为:

tensor(2.4824, grad_fn=<NllLossBackward>)

第二个模块是torch.nn.CrossEntroyLoss,用于构建目标损失函数,这个损失函数可以避免LogSoftmax的计算,在损失函数里整合Softmax输出概率,以及对概率取对数输出损失函数。

ce = nn.CrossEntropyLoss()

print(ce(t1, t2))

此时得到的结果与NLL损失函数的结果一致。

优化器

有了损失函数,就可以对模型进行优化。

接下来会演示如何拟合一个线性模型。

首先构建一个有13个参数的线性回归模型,然后构建损失函数的计算模块criterion,并将其设置为MSELoss模块的实例。有了损失函数后就可以构建一个随机梯度下降算法的优化器。关于torch.optim.SGD的第一个参数是线性回归模型的生成器,第二个参数是学习率。接下来构建训练的输入特征和预测目标,传入的参数是载入的波士顿房价的特征和预测目标的Numpy数组。

为了实现前向和反向传播计算,在构建输入特征的时候需要设置requires_grad=True,这样就能在计算过程中构建计算图。

接下来就是优化过程,需要先获取当前参数下模型的预测结果,并且使用这个结果计算出损失函数,然后清空梯度,损失函数调用反向传播算法,计算得到内个参数对应的梯度,最后执行一步优化的算法。

import torch
import torch.nn as nn
from sklearn.datasets import load_boston

class LinearModel(nn.Module):
    def __init__(self, ndim):
        super(LinearModel, self).__init__()
        self.ndim = ndim
        self.weight = nn.Parameter(torch.randn(ndim, 1))
        self.bias = nn.Parameter(torch.randn(1))
        
    def forward(self, x):
        
        return x.mm(self.weight) + self.bias

boston = load_boston()

lm = LinearModel(13)
criterion = nn.MSELoss()
optim = torch.optim.SGD(lm.parameters(), lr = 1e-6) # 定义优化器
data = torch.tensor(boston["data"], requires_grad=True, dtype=torch.float32) # 该数据为双精度类型,需要转换为单精度
target = torch.tensor(boston["target"], dtype=torch.float32)

for step in range(10000):
    predict = lm(data) # 输出模型预测结果
    loss = criterion(predict, target) # 输出损失函数

    if step and step % 1000 == 0:
        print("Loss:{:.3f}".format(loss.item()))

    optim.zero_grad() # 清零梯度
    loss.backward() # 反向传播
    optim.step()

上述代码使用波士顿地区房价数据,通过安装scikit-learn库。该数据有13个特征,一共506条数据。为简便起见,直接使用全局数据来进行训练。另外由于数据量比较小,每一次模型的优化都会使用全局数据而不是用Mini-batch。

以下是得到的结果,可以看到损失函数在每一步优化的时候逐渐下降了。

Loss:337.852
Loss:257.781
Loss:228.257
Loss:210.646
Loss:197.336
Loss:186.189
Loss:176.497
Loss:167.958
Loss:160.397

在如上的代码中,可以看到随机梯度下降算法算法优化器torch.optim.SGD构建了一个方法,能够对传入参数生成器中的每个参数进行优化。在优化之前,首先要执行两个步骤,第一个是调用zero_grad方法清空所有的参数前一次反向传播的梯度,第二个是调用损失函数的backward方法来计算所有参数的当前反向传播的梯度。

PyTorch的优化器对于不同的参数可以使用不同的学习率。默认的学习率是10-2,默认动量为0.9,但对于model.classifier子模块来说,默认的学习率是10-3。通过使用字典的列表分别指定学习率,可以达到对不同的参数使用不同的学习率的目的。如下所示:

optim.SGD([
	{'params': model.base.parameters()},
	{'params': model.classifier.parameters(), 'lr': 1e-3}
], lr=1e-2, momentum=0.9)

在优化器以外,torch.optim包还提供了学习衰减的相关类,这些类都在torch.optim的子包torch.optim.lr_scheduler中。

scheduler = StepLR(optimizer, step_size=30, gamma=0.1)

for epoch in range(100):
	train(...)
	validate(...)
	scheduler.step()

如上所示,在使用的时候需要传入具体的优化器,以及隔多少步进行学习率衰减以及衰减的系数。在上述代码中就是每次经过30个迭代期,学习率会变成原来的0.1倍,每次经过一个迭代期都会调用梯度衰减类的step方法,学习率衰减类会记录当前的迭代期,并根据当前的迭代期决定是否发生学习率的衰减。

总结

PyTorch定义了一系列的损失函数,包括回归的损失函数和分类的损失函数以及其他的一些损失函数分别用于不同的深度学习任务。同时在优化器方法提供了大量成熟的实现,包括SGD、RMSProp等等。通过优化器的类,可以很容易地根据模型的参数构建对应优化器的实例,然后通过反向传播计算出模型参数对应的梯度,最后调用优化器相关的优化方法对模型的参数进行优化,整个过程非常方便和直观。同时为了能够在模型训练过程中提供学习率的动态调节方法,PyTorch也提供了相关的类来调整学习率。

你可能感兴趣的:(深度学习,python,深度学习,pytorch)