确定神经网络中的参数需要两个步骤:
- Forward Propagation:正向传播,就是神经网络利用自己“猜测”最好的参数,根据input data得到output(包括各个node的值)。
- Backward Propagation: 反向传播,神经网络通过正向传播的output得到的error适应自己的参数。更新权重参数的过程是在BP阶段完成的。
模型中的参数根据损失函数的梯度来确定。为了计算这些梯度,Pytorch有一些内置的引擎,叫torch.autograd。(模型中具体如何通过BP来确定参数,可以去看一哈3Blue1Brown的视频)
另外:pytorch官网上有两个关于aotograd的tutorial,个人建议看第二个。
https://pytorch.org/tutorials/beginner/blitz/autograd_tutorial.html#sphx-glr-beginner-blitz-autograd-tutorial-py
梯度计算:
一个最简单的一层网络模型。
import torch
x = torch.ones(5) # input tensor
y = torch.zeros(3) # expected output
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)
将上述代码转化成计算图如下:
其中w和b是网络中的待求参数,需要计算每个w和b的损失函数的梯度来确定他们的取值。为了完成这些,我们需要设置requires_grad
属性。(原文中是property,不是attribute,查了以下是有一点区别的)。只有当该属性为True时,梯度才能被计算。
其实我们对tensors构建计算图的方法是一个类的对象,这个对象知道如何进行直接的正向计算也能计算反向传播中的导数。
当然,为了寻找神经网络中最合适的参数,我们需要计算每一个w和b对应的损失函数的导数。为了计算这些导数,我们调用 loss.backward()
,再从w.grad
和b.grad
中获得这些导数值。
loss.backward()
print(w.grad) //这个就是y对w的导数。
print(b.grad)
结果为:
tensor([[0.2183, 0.0097, 0.1539],
[0.2183, 0.0097, 0.1539],
[0.2183, 0.0097, 0.1539],
[0.2183, 0.0097, 0.1539],
[0.2183, 0.0097, 0.1539]])
tensor([0.2183, 0.0097, 0.1539])
注:只有当requires_grad的属性为Trure时,grad的属性才能被访问。而在网格中所有节点中,只有叶子节点的该属性可以被设为True。
为了保证性能,我们只能再使用给定网格中使用backward才能进行梯度计算。如果我们要对同一个网格的backward做更多操作,可以通过retain_graph=True
对backward进行调用。
取消Gradient Tracking
默认情况下,tensor设置中的requires_grad=True
,这样可以追溯之前的计算历史,支持梯度计算。但是在一些情况下我们需要忽略一些历史信息。例如,我们有一个已经训练好的模型,只需要它来计算input data然后产生一个输出就可以了。用torch.no_grad()
方法可以实现:
z = torch.matmul(x, w)+b
print(z.requires_grad)
with torch.no_grad():
z = torch.matmul(x, w)+b
print(z.requires_grad)
输出为:
True
False
还有一种方法,是detach()
方法。
z = torch.matmul(x, w)+b
z_det = z.detach()
print(z_det.requires_grad)
一些情况下需要disable gradient tracking:
- 使一些参数为“冻结参数”,这经常在微调预训练网络中被使用。
- 加速计算,在只有前馈计算的情况下。
优化模型参数:
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
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(NeuralNetwork, self).__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),
nn.ReLU()
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
model = NeuralNetwork()
重要参数:
超参数:是人为设置的,控制模型优化过程的参数。不同的超参数影响着模型训练和收敛速度。有以下超参数:
· Number of Epochs - 整个数据集迭代的次数。
· Batch Size - 在参数更新前,数据样本传递给网络的次数。
· Learning Rate - 被一batch和ephoch的“更新力度”。
其中,epochs主要包含以下两个主要部分:
Train Loop 和 Validation/Test Loop。 从名字中就可以看出端倪,前者是训练循环,后者是验证循环。
损失函数:常用的损失函数有 nn.MSELoss(拟合任务), nn.NLLLoss(分类任务),nn.CrossEntropyLoss(结合了nn.LogSoftmas和nn.NLLLoss)
优化器:优化算法定义了模型参数的计算过程。所有优化方法都被封装在一个 optimizer 对象里面。这里,我们使用SGD optimizer,
在整个training loop中,优化发生在三个步骤中。
- 调用 optimizer.zero_grad()去重新设置模型参数的梯度。梯度默认设置是增加的,为了防止重复计算,我们在每个迭代开始时将初值设为0。
- 反向传播,预测loss时调用 loss.backwards()。
- 一旦我们拥有了梯度,我们就会调用optimizer.step(),通过反向计算中的梯度来更新新梯度。
这些重要参数的设置代码:
learning_rate = 1e-3
batch_size = 64
epochs = 5
loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)
部署:
先放一个完整代码:
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda
class NeuralNetwork(nn.Module):
def __init__(self):
super(NeuralNetwork, self).__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),
nn.ReLU()
)
def forward(self, x):
x = self.flatten(x)
logits = self.linear_relu_stack(x)
return logits
def train_loop(dataloader, model, loss_fn, optimizer):
size = len(dataloader.dataset)
for batch, (X, y) in enumerate(dataloader):
# Compute prediction and loss
pred = model(X)
loss = loss_fn(pred, y)
# Backpropagation
optimizer.zero_grad()
loss.backward()
optimizer.step()
if batch % 100 == 0:
loss, current = loss.item(), batch * len(X)
print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]")
def test_loop(dataloader, model, loss_fn):
size = len(dataloader.dataset)
num_batches = len(dataloader)
test_loss, correct = 0, 0
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")
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)
model = NeuralNetwork()
learning_rate = 1e-3
batch_size = 64
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!")
具体解释:
- 首先来看一下只有一个训练步骤的模型。举个例子:我们将一个已经训练好了的resnet18作为我们的模型。创造一个random tensor,代表一个三通道的图像,并且它的长和宽都是64。(36464)同样,labels也给随机掉。
import torch, torchvision
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
- 用前馈 forward pass来计算结果。
prediction = model(data) # forward pass
- 定义损失函数
loss
,利用反向传播 backpropagation来将error传播到整个网络。反向传播在我们对loss
调用.backward()
的时候就被使用,自动计算梯度并且将求得的结果存在模型参数的.grad
属性中。
loss = (prediction - labels).sum()
loss.backward() # backward pass
- 选用SGD优化方法,learning rate设置为0.01,momentum为0.9
(momentum:动量,来源于牛顿定律,基本思想是为了找到最优加入“惯性”的影响,当误差曲面中存在平坦区域,SGD就可以更快的学习。)
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
- 调用 .step()来启动gradient descent,optimizer就会对之前存在.grad里的参数来进行调整。
optim.step() #gradient descent