本文翻译自:https:pytorch.orgtutorialsbeginnernn_tutorial.html这是第一部分:如何用纯Python构造一个神经网络。Pytorch提
本文翻译自:https://pytorch.org/tutorials/beginner/nn_tutorial.html 这是第一部分:如何用纯Python构造一个神经网络。
Pytorch提供了几个设计得非常棒的模块和类,比如 torch.nn,torch.optim,Dataset 以及 DataLoader,来帮助你设计和训练神经网络。为了充分利用他们来解决你的问题,你需要明白他们具体是做什么的。为了帮助大家理解这些内容,我们首先基于MNIST数据集,不用以上提到的模块和类来训练一个基础的神经网络,只用到基本的PyTorch tensor 函数。然后我们会逐渐地使用来自torch.nn,torch.optim,Dataset 以及 DataLoader的功能。展示每个模块具体的功能,他的运作过程。这样来使得代码逐渐简洁和灵活。
MNIST数据集
我们使用经典的MNIST数据集。这个数据集由手写数字0~9的黑白照片组成。
我们使用pathlib包来解决文件路径问题(这是python3的一个标准包),我们使用requests函数来下载数据。我们只导入我们需要用到的模块,所以你可以清晰地看到每一步具体用了什么。
from pathlib import Path
import requests
DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"
PATH.mkdir(parents=True, exist_ok=True)
URL = "http://deeplearning.net/data/mnist/"
FILENAME = "mnist.pkl.gz"
if not (PATH / FILENAME).exists():
content = requests.get(URL + FILENAME).content
(PATH / FILENAME).open("wb").write(content)
数据现在是numpy array格式,用pickle存储起来了。这是Python特有的串行数据格式。
import pickle
import gzip
with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:
((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")
每一张照片都是 28*28大小的一个数组,但是被存成了一个长度为784(28*28)的一行。现在给大家展示其中一张图片,首先我们要把它重新组织成28*28的一个二维形状。
from matplotlib import pyplot
import numpy as np
pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)
输出:
(50000, 784)
Pytorch使用 torch.tensor,而不是 numpy arrays。所以我们首先需要转换我们的数据。
import torch
x_train, y_train, x_valid, y_valid = map(
torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())
输出:
tensor([[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
...,
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.],
[0., 0., 0., ..., 0., 0., 0.]]) tensor([5, 0, 4, ..., 8, 4, 8])
torch.Size([50000, 784])
tensor(0) tensor(9)
利用scratch(不是 torch.nn)构建神经网络
让我们首先只用PyTorch tensor的基本操作来构造我们的神经网络。首先假设你已经有一定的神经网络知识基础。如果没有,可以在 course.fast.ai 里面学习。(译者:或者可以通过《神经网络设计》这本书学习,这本书深入浅出,从基本的神经元讲起,很适合初学者)
PyTorch提供了一些创建随机或者零张量(tensor)的方法。我们可以利用他们去构建线性模型(linear model)所用到的权值(weights)和偏移(bias)。这些都是常规的张量,除了一个方面,他们需要一个梯度(gradient)。这样会使得PyTorch会记录所有对这个张量所进行的操作,所以这样可以自动计算关于这些权值和偏移的梯度。(关于pytorch gradient这个问题具体可以看https://www.jianshu.com/p/cbce2dd60120 我也会马上写一篇关于介绍这个的文章)
对于权值张量,我们在初始化它的时候设置 requires_grad
import math
weights = torch.randn(784, 10) / math.sqrt(784)
weights.requires_grad_()
bias = torch.zeros(10, requires_grad=True)
多亏了PyTorch自动计算梯度的这个功能,所以我们可以用任何标准的PyTorch函数。所以让我们来构建一个包含矩阵乘法和张量加法的简单线性模型。我们还需要一个激活函数(activation function)。所以我们写一个 log_softmax 函数。记住:虽然PyTorch提供了大量写好的损失函数(loss function)和激活函数(activation function)。但是你也可以用简单基本的Python语言实现。
def log_softmax(x):
return x - x.exp().sum(-1).log().unsqueeze(-1)
def model(xb):
return log_softmax(xb @ weights + bias)
以上代码中“@”表示点积(dot production)操作,我们将会使他作用于每一批量(batch)的数据(这个例子是64张图片的数据)。这是一次向前过程(forward pass)。注意我们这样的预测并不会比随机判断好多少,因为我们的权值刚开始是随机产生的。
bs = 64 # batch size
xb = x_train[0:bs] # a mini-batch from x
preds = model(xb) # predictions
preds[0], preds.shape
print(preds[0], preds.shape)
输出:
tensor([-1.5058, -2.1150, -2.4652, -2.4982, -2.4030, -3.0075, -2.2017, -2.8193,
-2.6561, -2.2075], grad_fn=) torch.Size([64, 10])
正如你所看到的一样,张量 preds 不仅包含了他的数值,也包含一个梯度函数。我们等下会用到它来做反向传播。
然我们来实现一个 negative log_likehood 函数作为他的损失函数。仍然用的是Python 标准函数,没有用PyTorch集成的函数。
def nll(input, target):
return -input[range(target.shape[0]), target].mean()
loss_func = nll
让我们来检测一下我们模型的损失值,这样我们在下一个回合就可以比较损失值的变化了。
yb = y_train[0:bs]
print(loss_func(preds, yb))
输出:
tensor(2.2584, grad_fn=)
让我们再来实现一个函数计算我们模型预测出来的结果的正确性。在每次预测中,输出向量最大值得下标索引如果和目标值(标签)相同,则认为预测结果是对的。
def accuracy(out, yb):
preds = torch.argmax(out, dim=1)
return (preds == yb).float().mean()
让我们来检测我们模型的正确率,这样我们就可以看到随着损失函数的提升,正确率也随之提升。
print(accuracy(preds, yb))
输出:
tensor(0.1094)
现在我们可以运行整个训练过程。在每次迭代中,我们将会:
- 选择一个最小批量的数据(代码中表示为 bs,即每次进入神经网络的数据单元)
- 用模型去得到预测结果
- 计算损失函数值
- loss.backward()更新模型的梯度,在这里是权值(weights)和偏移(bias)。
现在我们用梯度去更新权值和偏移。我们在更新时设置 torch.no_grad() ,因为我们不想把这个操作也记录到梯度中。
然后我们把梯度清零。这样我们就可以进行下一个循环过程。不然的话梯度会记录所有的操作。
from IPython.core.debugger import set_trace
lr = 0.5 # learning rate
epochs = 2 # how many epochs to train for
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
weights.grad.zero_()
bias.grad.zero_()
这就是我们用纯Python构建的神经网络。(这个例子中,这是一个逻辑回归,就两层,没有隐含层)。
让我们来检测这个模型的损失值和正确率。我们期望的是损失值能够降下来,正确率能够上升。
print(loss_func(model(xb), yb), accuracy(model(xb), yb))
输出:
tensor(0.0849, grad_fn=) tensor(1.)
正确率是1.