PyTorch提供了方便漂亮的类和模块,来帮助我们创建和训练神经网络,例如 torch.nn, torch.optim 等。
为了更好地理解这些模块的功能和原理,我们在手动搭建的神经网络上,逐步添加这些模块,以显示每部分模块的功能,以及每部分是如何让代码更加灵活简洁的。
使用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.)
#准确度明显提高
现在已经手动定义了一个小型的神经网络,每次迭代,将会进行以下几件事情:
我们现在来重构代码,代码的功能和前边的一样,我们只是利用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.)
可以通过继承 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()
使用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)
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