系列文章目录

PyTorch|Dataset与DataLoader使用、构建自定义数据集
PyTorch|搭建分类网络实例、nn.Module源码学习

一、autograd

在训练神经网络时,最常用的算法是反向传播算法。在该算法中,根据损失函数相对于给定参数的梯度来调整参数(模型权重)。为了计算这些梯度,PyTorch有一个内置的微分引擎,叫做torch.autograd,它支持任何计算图的梯度自动计算。

考虑最简单的单层神经网络,输入x,参数w和b,以及一些损失函数。它可以在PyTorch中以以下方式定义:

import torch

x = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output

# 传入requires_grad意味着会计算梯度
w = torch.randn(5, 3, requires_grad=True)
b = torch.randn(3, requires_grad=True)
# 预测输出
z = torch.matmul(x, w)+b
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

(一)设定需要计算梯度的参数

对于一个参数,如果需要计算其梯度,有两种方法:

  • 在创建张量时直接设置requires_grad
  • 创建张量后,调用 x.requires_grad_(True)方法

对于设置了requires_grad=True的参数,都可以打印对应的grad_fn:

print(f"Gradient function for z = {z.grad_fn}")
print(f"Gradient function for loss = {loss.grad_fn}")

在这里插入图片描述

(二)梯度计算

怎么样计算每个参数的梯度?只需要对要求的根节点调用backward函数就好了
在这里插入图片描述backward函数其实是tensor类的一个方法:

loss必须是一个标量,如果是loss是一个元素个数大于1的张量,括号里需要传入一个vector

loss.backward()
print(w.grad)
print(b.grad)

梯度的shape其实跟节点自身的shape是一样的:
在这里插入图片描述

(三)停止梯度计算

默认情况下,所有requires_grad=True的张量都在跟踪它们的计算历史并支持梯度计算。然而,某些情况可能需要我们禁用梯度跟踪:

  • 将神经网络中的某些参数标记为冻结参数。
  • 只做正向传递时,为了加快计算速度,因为在不跟踪梯度的张量上的计算会更有效率。
z = torch.matmul(x, w)+b
print(z.requires_grad)

with torch.no_grad():
    z = torch.matmul(x, w)+b
print(z.requires_grad)

只要父节点中有一个是require_grad=true,那么该节点就会计算梯度,比如这里的z。但是当设置with torch.no_grad()之后就不会计算梯度。
在这里插入图片描述

另外也可以通过调用detach方法达到同样的效果

z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)

在这里插入图片描述

(四)梯度清零

pytorch中默认情况下梯度是会累积的。所以会调用zero_grad()

(五)张量梯度和雅可比积

inp = torch.eye(4, 5, requires_grad=True)
out = (inp+1).pow(2).t() #不是标量

out.backward(torch.ones_like(out), retain_graph=True) #矩阵对矩阵求导的时候需要传入一个vector,vector的大小与out保持一致
print(f"First call\n{inp.grad}")

#传入了 retain_graph=True,原因是下边进行了第二次调用
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nSecond call\n{inp.grad}")

#梯度清零之后调用第三次
inp.grad.zero_()
out.backward(torch.ones_like(out), retain_graph=True)
print(f"\nCall after zeroing gradients\n{inp.grad}")

在这里插入图片描述

二、训练模型

(一)数据准备和模型构建

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor()
)

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)

train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()

(二)定义超参数

常见的需要定义的用于训练的超参数:

  • epoch的次数——在数据上迭代的次数;
  • batch Size——在参数更新之前通过网络传播的数据样本的数量;
  • learning Rate——在每个batch/epoch更新模型参数的次数。
learning_rate = 1e-3
batch_size = 64
epochs = 5

(三)定义目标函数

# Initialize the loss function
loss_fn = nn.CrossEntropyLoss()

(四)定义优化器

优化器一般至少有两个参数:需要更新的模型的参数、学习率

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

在训练循环中,优化分三步进行:

  • 需要先调用optimizer.zero_grad(),对模型参数的梯度进行重置

在pytorch中,参数的梯度会默认进行累计,了防止重复计数,每次迭代时需要显式地将它们归零。

  • 调用loss.backward()反向传播预测损失
  • 计算梯度之后再调用optimizer.step()函数完成对于参数的更新

(五)完整实现

单步训练和单步测试:

def train_loop(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)
    # Set the model to training mode - important for batch normalization and dropout layers
    # Unnecessary in this situation but added for best practices
    model.train()
    for batch, (X, y) in enumerate(dataloader):
        # Compute prediction and loss
        pred = model(X)
        loss = loss_fn(pred, y)

        # Backpropagation
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

        if batch % 100 == 0:
            loss, current = loss.item(), batch * batch_size + len(X)
            print(f"loss: {loss:>7f}  [{current:>5d}/{size:>5d}]")


def test_loop(dataloader, model, loss_fn):
    # Set the model to evaluation mode - important for batch normalization and dropout layers
    # Unnecessary in this situation but added for best practices
    model.eval()
    size = len(dataloader.dataset)
    num_batches = len(dataloader)
    test_loss, correct = 0, 0

    # Evaluating the model with torch.no_grad() ensures that no gradients are computed during test mode
    # also serves to reduce unnecessary gradient computations and memory usage for tensors with requires_grad=True
    with torch.no_grad():
        for X, y in dataloader:
            pred = model(X)
            test_loss += loss_fn(pred, y).item()
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()

    test_loss /= num_batches
    correct /= size
    print(f"Test Error: \n Accuracy: {(100*correct):>0.1f}%, Avg loss: {test_loss:>8f} \n")

周期训练:

loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

epochs = 10
for t in range(epochs):
    print(f"Epoch {t+1}\n-------------------------------")
    train_loop(train_dataloader, model, loss_fn, optimizer)
    test_loop(test_dataloader, model, loss_fn)
print("Done!")

更多推荐