在处理更复杂的问题之前,我知道我需要实现自己的 backward
传递,所以我想先尝试一个简单且易于操作的例子。因此,我尝试使用 PyTorch 进行线性回归,并使用均方误差损失。然而,当我定义了自己的 backward
方法时,事情出了问题(见下面的第三种实现选项),我怀疑这是因为我没有清楚地思考应该向 PyTorch 提供什么样的梯度。所以,我认为我需要一些关于 PyTorch 期望我以何种形式提供什么样的解释/澄清/建议。
我使用的是 PyTorch 1.7.0,所以许多旧的例子不再适用(处理用户定义的自动梯度函数的方式与文档中描述的不同)。
第一种方法(标准的 PyTorch MSE 损失函数)
让我们先用标准的方式,不使用自定义损失函数来做这件事:
import torchimport torch.nn as nnimport torch.nn.functional as F# 让我们生成一些假数据torch.manual_seed(42)resid = torch.rand(100) inputs = torch.tensor([ [ xx ] for xx in range(100)] , dtype=torch.float32)labels = torch.tensor([ (2 + 0.5*yy + resid[yy]) for yy in range(100)], dtype=torch.float32)# 现在我们定义一个线性回归模型class linearRegression(torch.nn.Module): def __init__(self, inputSize, outputSize): super(linearRegression, self).__init__() self.bn = torch.nn.BatchNorm1d(num_features=1) self.linear = torch.nn.Linear(inputSize, outputSize) def forward(self, inx): x = self.bn(inx) # 添加BN来标准化输入,有助于我们使用更高的学习率 x = self.linear(x) return x model = linearRegression(1, 1) # 使用PyTorch的标准mse_lossepochs = 25 mseloss = F.mse_lossoptimizer = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=1e-3)scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.1)for epoch in range(epochs): model.train() optimizer.zero_grad() outputs = model(inputs) loss = mseloss(outputs.view(-1), labels) loss.backward() optimizer.step() scheduler.step() print(f'epoch {epoch}, loss {loss}')
这个训练过程非常顺利,最终损失约为0.0824,拟合的图看起来也很好。
第二种方法(自定义损失函数,但依赖于PyTorch的自动梯度计算)
那么,现在我用自己的MSE损失实现替换了损失函数,但我仍然依赖PyTorch的自动梯度计算。这里我唯一改变的是定义了自定义损失函数,相应地定义了基于此的损失,以及我将预测和真实标签传递给损失函数的细微细节。
#######################################################3class MyMSELoss(nn.Module): def __init__(self): super(MyMSELoss, self).__init__() def forward(self, inputs, targets): tmp = (inputs-targets)**2 loss = torch.mean(tmp) return loss#######################################################3model = linearRegression(1, 1) mseloss = MyMSELoss()optimizer = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=1e-3)scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.1)for epoch in range(epochs): model.train() outputs = model(inputs) loss = mseloss(outputs.view(-1), labels) loss.backward() optimizer.step() optimizer.zero_grad() scheduler.step() print(f'epoch {epoch}, loss {loss}')
这给出的结果与使用标准MSE损失函数完全相同。各轮次的损失看起来像这样:
epoch 0, loss 884.2006225585938epoch 1, loss 821.930908203125epoch 2, loss 718.7732543945312epoch 3, loss 538.1835327148438epoch 4, loss 274.50909423828125epoch 5, loss 55.115299224853516epoch 6, loss 2.405021905899048epoch 7, loss 0.47621214389801025epoch 8, loss 0.1584305614233017epoch 9, loss 0.09725229442119598epoch 10, loss 0.0853077694773674epoch 11, loss 0.08297089487314224epoch 12, loss 0.08251354098320007epoch 13, loss 0.08242412656545639epoch 14, loss 0.08240655809640884epoch 15, loss 0.08240310847759247epoch 16, loss 0.08240246027708054epoch 17, loss 0.08240233361721039epoch 18, loss 0.08240240067243576epoch 19, loss 0.08240223675966263epoch 20, loss 0.08240225911140442epoch 21, loss 0.08240220695734024epoch 22, loss 0.08240220695734024epoch 23, loss 0.08240220695734024epoch 24, loss 0.08240220695734024
第三种方法(自定义损失函数并使用我自己的backward方法)
现在,最后一个版本,我为MSE实现了自己的梯度。为此,我在损失函数类中定义了自己的 backward
方法,显然需要执行 mseloss = MyMSELoss.apply
。
from torch.autograd import Function#######################################################class MyMSELoss(Function): @staticmethod def forward(ctx, y_pred, y): ctx.save_for_backward(y_pred, y) return ( (y - y_pred)**2 ).mean() @staticmethod def backward(ctx, grad_output): y_pred, y = ctx.saved_tensors grad_input = torch.mean( -2.0 * (y - y_pred)).repeat(y_pred.shape[0]) # 这会失败,就像 grad_input = -2.0 * (y-y_pred) 一样 # 我还尝试过改变符号,但这不是唯一的问题。 return grad_input, None ####################################################### model = linearRegression(1, 1) mseloss = MyMSELoss.applyoptimizer = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=1e-3)scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.1)for epoch in range(epochs): model.train() outputs = model(inputs) loss = mseloss(outputs.view(-1), labels) loss.backward() optimizer.step() optimizer.zero_grad() scheduler.step() print(f'epoch {epoch}, loss {loss}')
这就是事情出错的地方,而不是训练损失减少,我得到的是增加的训练损失。现在看起来像这样:
epoch 0, loss 884.2006225585938epoch 1, loss 3471.384033203125epoch 2, loss 47768555520.0epoch 3, loss 1.7422577779621402e+33epoch 4, loss infepoch 5, loss nanepoch 6, loss nanepoch 7, loss nanepoch 8, loss nanepoch 9, loss nanepoch 10, loss nanepoch 11, loss nanepoch 12, loss nanepoch 13, loss nanepoch 14, loss nanepoch 15, loss nanepoch 16, loss nanepoch 17, loss nanepoch 18, loss nanepoch 19, loss nanepoch 20, loss nanepoch 21, loss nanepoch 22, loss nanepoch 23, loss nanepoch 24, loss nan
回答: