Pytorch 作为深度学习届两大扛把子框架之一(另一个是Tensorflow),一直都受着AI炼丹师们的喜爱。这里将简单介绍一下神经网络参数的手动更新和自动更新。
首先来说手动更新,可能很多初学者随便打开一本pytorch书或者教程,发现他们的梯度更新方式都大同小异,都是那几句话,但他其中的具体步骤究竟是怎样的,还是有必要了解一下。
一般情况下,神经网络都是有隐藏层的,当然也可以像上图左边那样,只有输入输出。
如下:
1,输入进来,前向传播,先用初始化的矩阵将输入转换成和输出类似的形式。
2, 然后计算Loss,也就是计算预测的结果和实际结果差了多少。
3,根据这个Loss反向传播,获取不同矩阵的梯度
4,根据获取的梯度,更新梯度。
整个流程大致就是一个epoch,在实际场景的神经网络训练中,这样的循环可能都是成百万次,千万次级别的。当然,初学的时候用不到这么多。
来看代码:
import torch
import numpy as np
x_data = np.array(
[[0, 0], [1, 0], [1, 1], [0, 0], [0, 0], [0, 1]])
x_data_torch = torch.from_numpy(x_data).float()
y_data = np.array([0,1,2,0,0,2])
y_data_torch = torch.from_numpy(y_data)
num_features = 2
num_classes = 3
n_hidden_1 = 5
以上是数据X,y以及一些参数设定,比如特征有2个,输出的类别有3个,隐藏层的维度是5。具体的数据是没有任何意义的,其实对于神经网络而言,他也不知道究竟在学什么
接下来是手动参数更新, 这里的神经网络包含了一层隐藏层,所以第一层的权重矩阵W的维度应该是(num_features, n_hidden_1),对应特征数和隐藏层维度,即(2,5),然后input的维度是(6,2) 于是x*W1的维度就应该是(6,5)然后再和B1矩阵相加。
import torch.nn as nn
import torch.nn.functional as F
from sklearn.metrics import accuracy_score
W1 = torch.randn(num_features, n_hidden_1, requires_grad=True)
B1 = torch.randn(n_hidden_1, requires_grad=True)
Wout = torch.randn(n_hidden_1, num_classes, requires_grad=True)
Bout = torch.randn(num_classes, requires_grad=True)
learning_rate=0.01
no_of_epochs = 1000
for epoch in range(no_of_epochs): #explicitly defne the forward and backward
z1 = torch.add(torch.matmul(x_data_torch, W1), B1)
Zout = torch.add(torch.matmul(F.relu(z1), Wout), Bout)
log_softmax = F.log_softmax(Zout,dim=1)
loss = F.nll_loss(log_softmax, y_data_torch)
loss.backward()
with torch.no_grad():
W1.data -= learning_rate*W1.grad.data
B1.data -= learning_rate*B1.grad.data
Wout.data -= learning_rate*Wout.grad.data
Bout.data -= learning_rate*Bout.grad.data
W1.grad.data.zero_()
B1.grad.data.zero_()
Wout.grad.data.zero_()
Bout.grad.data.zero_()
if epoch % 200 == 199:
with torch.no_grad():
z1 = torch.add(torch.matmul(x_data_torch ,W1),B1)
Zout = torch.add(torch.matmul(F.relu(z1) ,Wout),Bout)
predicted = torch.argmax(Zout, 1)
train_acc = accuracy_score(predicted.numpy(),y_data)
print('Epoch: %d, loss: %.4f, train_acc: %.3f' %(epoch + 1, loss.item() , train_acc))
print("Finished")
# Result
print('Predicted :', predicted.numpy())
print('Truth :', y_data)
print('Accuracy : %.2f' %train_acc)
这里的整体操作就是先用input X 和W1矩阵相乘,然后给所得矩阵的每一行加上B1矩阵,这个X*W1+B1会经过一个relu层,然后输入到第二层,第二层也是相似的方式,先乘再加。所得到的结果经过log_softmax调整后,和y计算negative log likelihood loss(nll loss),然后再反向传播获取每个矩阵对应的梯度。
然后用with torch.no_grad() 自己设定更新方式, 这里用到的就是梯度下降法。然后为了在对每个矩阵调用zero_()使其参数清零,防止梯度堆积(gradient accumulation)
最后设定一个输出方式,每200epochs输出一次当前的Loss和准确度。
大致结果如下,由于数据很小,并且矩阵是随机初始的,每次结果可能不同。
在大型的深度学习网络中,不可能每次都自定义梯度该如何更新,所以这里就需要自动更新。
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from sklearn.metrics import accuracy_score
class ModelWithHiddenLayer(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(ModelWithHiddenLayer, self).__init__()
self.linear1 = nn.Linear(input_size, hidden_size)
self.linear2 = nn.Linear(hidden_size, output_size)
def forward(self, x):
z1 = self.linear1(x)
Zout = self.linear2(F.relu(z1))
return Zout
model = ModelWithHiddenLayer(num_features, n_hidden_1, num_classes)
learning_rate=0.01
no_of_epochs = 1000
# If you apply Pytorch’s CrossEntropyLoss to your output layer,
# you get the same result as applying Pytorch’s NLLLoss to a LogSoftmax layer added after your original output layer.
criterion = nn.CrossEntropyLoss()
optimiser = optim.SGD(model.parameters(), lr=learning_rate)
首先设定好模型,criterion是用来计算loss的,可以设定不同的loss,这里选的是交叉熵。optimiser是设定梯度更新方式的,这里设定的是SGD随机梯度下降。
for epoch in range(no_of_epochs): # loop over the dataset multiple times
# get the inputs
inputs = x_data_torch
labels = y_data_torch
model.train()
# zero the parameter gradients
optimiser.zero_grad()
# forward + backward + optimize
outputs = model(inputs)
loss = criterion(outputs, labels) # We don't need to calcualte logsoftmax here
loss.backward()
optimiser.step()
# print statistics
if epoch % 200 == 199: # print every 200 epochs
model.eval()
pred_outputs = model(inputs)
predicted = torch.argmax(pred_outputs, 1)
train_acc = accuracy_score(predicted.numpy(),y_data)
print('%d, loss: %.4f, train_acc: %.4f' %(epoch + 1, loss.item(), train_acc))
print('Finished Training')
# Result
pred_outputs = model(inputs)
_, predicted = torch.max(pred_outputs, 1)
print('Predicted :', predicted.numpy())
print('Truth :', y_data)
train_acc = accuracy_score(predicted.numpy(),y_data)
print('Accuracy : %.2f' %train_acc)
这种情况下,只需要简单的写出大致流程即可实现神经网络训练。
首先调用model.train() 进行训练,然后用optimiser将梯度都设为0,然后用模型输出一个预测结果,用criterion计算相应的loss,然后用loss.backward()进行反向传播,再用optimiser.step()更新梯度。
预测结果不再展示,其实更复杂的模型都是从最简单的变出来的,一般的模型,即时再复杂,也都是这几步,知识他们的模型class中会设定很多隐藏层。