PyTorch学习笔记(二)——torch.nn解析

PyTorch提供了方便漂亮的类和模块,来帮助我们创建和训练神经网络,例如 torch.nn, torch.optim 等。
为了更好地理解这些模块的功能和原理,我们在手动搭建的神经网络上,逐步添加这些模块,以显示每部分模块的功能,以及每部分是如何让代码更加灵活简洁的。

1、手动搭建神经网络

使用MNIST数据集,该数据集共有50000个图片,每一图片大小为2828,储存在长度为2828=784的扁平行。

#定义权重和偏执值,需要requires_grad=True,以便自动计算梯度,完成反馈
weights = torch.randn(784, 10,requires_grad=True)
bias = torch.zeros(10, requires_grad=True)

#定义激活函数
def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)
    
#定义神经网络运算过程,其中@表示点乘
def model(xb):
    return log_softmax(xb @ weights + bias)

#定义代价函数
def nll(input, target):
    return -input[range(target.shape[0]), target].mean()
loss_func = nll

#验证结果的准确性
def accuracy(out, yb):
    preds = torch.argmax(out, dim=1)
    return (preds == yb).float().mean()

#获取一批数据,验证预测的结果:
bs = 64                  # batch size
xb = x_train[0:bs]       # a mini-batch from x
yb = y_train[0:bs]
preds = model(xb)        # predictions
print(preds[0], preds.shape)
print(loss_func(preds, yb))
print(accuracy(preds, yb))
输出:
tensor([-1.7022, -3.0342, -2.4138, -2.6452, -2.7764, -2.0892, -2.2945, -2.5480, -2.3732, -1.8915], grad_fn=<SelectBackward>) torch.Size([64, 10])
tensor(2.3783, grad_fn=<NegBackward>)
tensor(0.0938)
#因为现在的权重和偏置值是随即取得的,所以预测结果并不好

#定义更新权值的训练函数
def fit():
	lr = 0.5 
	epochs = 2 
	for epoch in range(epochs):
	  for i in range((n - 1) // bs + 1):
	     #set_trace()
	      start_i = i * bs
	      end_i = start_i + bs
	      xb = x_train[start_i:end_i]
	      yb = y_train[start_i:end_i]
	      pred = model(xb)
	      loss = loss_func(pred, yb)
	
	      loss.backward()
	      with torch.no_grad():
	          weights -= weights.grad * lr
	          bias -= bias.grad * lr
	          #每次迭代之后,需要将梯度还原为零,否则loss.backward() 将梯度增加到已经存在的值上,而不是替代它
	          weights.grad.zero_()
	          bias.grad.zero_()

fit()
print(loss_func(model(xb), yb), accuracy(model(xb), yb))
输出:
tensor(0.0806, grad_fn=<NegBackward>) tensor(1.)
#准确度明显提高

现在已经手动定义了一个小型的神经网络,每次迭代,将会进行以下几件事情:

  • 选择一批数据(mini-batch)
  • 使用模型进行预测
  • 计算损失 loss.backward()
  • 更新模型的梯度,即权重和偏置

2、使用 torch.nn.functional重构代码

我们现在来重构代码,代码的功能和前边的一样,我们只是利用PyTorch 的 nn 类来使得代码更简洁和灵活。
torch.nn.functional 中的函数可以替代我们手工编写的激活函数和损失函数来缩短代码。
该模块包含 torch.nn 库中所有的函数(而库的其他部分还包含类)。
除了各种损失函数和激活函数,在还模块中你还可以发现许多用于创建神经网络的方便的函数,如池化函数等。

如果使用了负对数似然损失函数和 log softnax 激活函数,那么 Pytorch 提供的torch.nn.functional.cross_entropy 结合了两者。
所以我们甚至可以从我们的模型中移除激活函数。

#传统写法:
#定义激活函数
def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)   
#定义神经网络运算过程,其中@表示点乘
def model(xb):
    return log_softmax(xb @ weights + bias)
#定义代价函数
def nll(input, target):
    return -input[range(target.shape[0]), target].mean()
loss_func = nll

#优化写法:
import torch.nn.functional as F
loss_func = F.cross_entropy
def model(xb):
    return xb @ weights + bias

注意,在 model 函数中我们不再需要调用 log_softmax。让我们确认一下,损失和精确度与前边计算的一样:

print(loss_func(model(xb), yb), accuracy(model(xb), yb))
tensor(0.0806, grad_fn=<NllLossBackward>) tensor(1.)

3、使用 nn.Module 重构代码

可以通过继承 nn.Module(它本身是一个类并且能够跟踪状态)建立神经网络。
我们想要建立一个包含权重、偏置和前向传播的方法的类。
nn.Module 拥有许多我们将会使用的属性和方法(例如:.parameters() 和.zero_grad())

#之前写法:
#定义权重和偏执值,需要requires_grad=True,以便自动计算梯度,完成反馈
weights = torch.randn(784, 10,requires_grad=True)
bias = torch.zeros(10, requires_grad=True)
#定义激活函数
def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)   
#定义神经网络运算过程,其中@表示点乘
def model(xb):
    return log_softmax(xb @ weights + bias)
#定义代价函数
def nll(input, target):
    return -input[range(target.shape[0]), target].mean()
loss_func = nll

优化写法:
from torch import nn
import torch.nn.functional as F
loss_func = F.cross_entropy
class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(784, 10))
        self.bias = nn.Parameter(torch.zeros(10))
    def forward(self, xb):
        return xb @ self.weights + self.bias
#之前需要按名字更新每个参数的值,并且手动将每个参数的梯度归零:
loss.backward()
with torch.no_grad():
    weights -= weights.grad * lr
    bias -= bias.grad * lr
    #每次迭代之后,需要将梯度还原为零,否则loss.backward() 将梯度增加到已经存在的值上,而不是替代它
    weights.grad.zero_()
    bias.grad.zero_()

#可以利用 model.paremeters() 和 model.zero_grad() 使得这些步骤更简洁
model = Mnist_Logistic()
loss.backward()
with torch.no_grad():
    for p in model.parameters(): p -= p.grad * lr
    model.zero_grad()

4、使用 nn.Linear 重构代码

使用PyTorch 的 nn.Linear 类建立一个线性层,以替代手动定义和初始化 self.weights 和 self.bias、计算 xb @ self.weights + self.bias 等工作

#之前写法:
class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(784, 10))
        self.bias = nn.Parameter(torch.zeros(10))
    def forward(self, xb):
        return xb @ self.weights + self.bias

#优化写法:
class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.lin = nn.Linear(784, 10)
    def forward(self, xb):
        return self.lin(xb)

5、使用 optim 重构代码

PyTorch还有一个包含各种优化算法的包 torch.optim 。
我们可以使用优化器中的 step 方法来执行训练更新参数的步骤,而不是手动更新参数。

#之前写法:
for epoch in range(epochs):
	for i in range((n - 1) // bs + 1):
	    start_i = i * bs
	    end_i = start_i + bs
	    xb = x_train[start_i:end_i]
	    yb = y_train[start_i:end_i]
	    pred = model(xb)
	    loss = loss_func(pred, yb)
	
	    loss.backward()
	    with torch.no_grad():
	        for p in model.parameters():
	            p -= p.grad * lr
	        model.zero_grad()

优化写法:
for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        start_i = i * bs
        end_i = start_i + bs
        xb = x_train[start_i:end_i]
        yb = y_train[start_i:end_i]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

参考博文https://blog.csdn.net/Spring_24/article/details/100128412

你可能感兴趣的:(PyTorch学习笔记(二)——torch.nn解析)