【从入门到放弃】PyTorch入门(2)

Neural networks with PyTorch 使用Pytorch实现神经网络

深度学习网络就是有成百上千的神经层构成了,因为有很多很多层,所以才称之为<深度>。你能够像上文一样,只用一个权重矩阵就能够实现构建网络,但是在现实中,这么做非常的困难。在Pytorch中,nn模块提供了一个高效构建网络的接口。

1
2
3
# 下载必要数据
import urllib.request
urllib.request.urlretrieve('https://raw.githubusercontent.com/udacity/deep-learning-v2-pytorch/master/intro-to-pytorch/helper.py', 'helper.py')
1
2
3
4
5
6
7
8
9
10
# 导入必要的模块

%matplotlib inline
%config InlineBackend.figure_format = 'retina'
import numpy as np
import torch

import helper

import matplotlib.pyplot as plt

现在我们来构建一个大型网络来解决手写输入的识别问题。这里我们使用的MNIST数据库,里面的图片为28x28灰度手写数字,如下图所示。

mnist 我们的目标就是构建一个神经网络,进而完成手写的识别。

第一,我们需要下载数据,下面的代码可能有点复杂,我们后续会相应的解释,请不要放弃。

1
2
3
4
5
6
7
8
9
10
11
12
### 先下载数据

from torchvision import datasets, transforms

# 定义一个tranform(变换)来对数据进行归一化

transform = transforms.Compose([transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,)),
])
# 下载数据
trainset = datasets.MNIST('~/.pytorch/MNIST_data/', download=True, train=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64, shuffle=True)

我们先将训练数据加载到trainloader,并设置一个迭代器iter(trainloader),随后我们用这个迭代器来遍历训练数据,这个过程有点类似于,

1
2
3
for image, label in trainloader:
## do things with images and
labels

注意,trainloader的batch(批量)大小为64且shuffle=True。 batch 大小:每次循环我们加载图片的数目。每次循环(运行一次网络)被称为一个batchshuffle=True:每次加载数据,都会对数据进行打乱操作,这样有助于增加鲁棒性。 这里为了演示方便,我们先知进行一次迭代,我们可以看到images张量的size为(64, 1, 28, 28),其中 64:每batch的加载64images。 1: 一个颜色通道(灰度)。 28x28:图像大小。

1
2
3
4
5
dataiter = iter(trainloader)
images, labels = dataiter.next()
print(type(images))
print(images.shape)
print(labels.shape)

我们加载一副图像看一看他的样子。

1
plt.imshow(images[1].numpy().squeeze(), cmap='Greys_r'); # squeeze()函数将表示向量的数组转换为秩为1的数组

我们先尝试使用权重矩阵和矩阵乘法构建一个简单的神经网路。在本文中,我们使用方便且强大的nn模块。 到现在为止,我们构建的网络都是全链接网络或称之为稠密网路。也就是说,每个神经元(单元)都和下一层的每一个神经元都连接(有权非0)。在全连接网络中,每层输入必需是一维向量(因为会迭代多次,所以输入可以拼接成一个2D矩阵)。但是,因为我们的图像是一个28x28 二维张量,所以我们需要将他转化为一个一维向量。考虑到尺寸问题,我们将一个batch的图像由(64, 1, 28, 28)转换为(64, 784)(注:784= 28 * 28)。截止到目前为止我们完成了展平操作。 现在我们已经有了一个完整的学习网络,这里我们要区分10个数字,所以网络输出为一个 1* 10的向量,每个元素的值为每个数字的概率。拥有最高概率的数字就是我们预测的数字。

练习: 展平一批图像images。然后使用随机权重和偏移量来构建一个神经网络。它是一个具有784个输入单元,256个隐藏单元和10个输出单元的多层网络。对于隐藏层,我们使用sigmoid激活。为了让输出层输出概率值,我们不对输出进行激活操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
## 解
def activation(x):
return 1/(1+torch.exp(-x))

# 展平数据
inputs = images.view(images.shape[0], -1)

# 创建参数
w1 = torch.randn(784, 256)
b1 = torch.randn(256)

w2 = torch.randn(256, 10)
b2 = torch.randn(10)

h = activation(torch.mm(inputs, w1) + b1)

out = torch.mm(h, w2) + b2

Now 现在我们网络有了一个10个输入。每个输入网络的图像都能得到一个概率分布来告诉我们图像里的数字是什么。我们来举个例子:

image_distribution 这里我们会发现每个类别的概率似乎差的不多,这是因为我们的网络还没有训练,而且起始的参数是随机的分布值。这里计算概率我们用的是softmax function数学表达式为: \[ \Large \sigma(x_i) = \cfrac{e^{x_i}}{\sum_k^K{e^{x_k}}} \]

这里, \(x_i\)取值范围为0到1,这个公式其实还完成了归一化的操作,使所有概率的和为一。

练习: 实现一个softmax函数,来算并返回批处理中每个例子的概率分布。你需要注意,执行此操作时张量的大小变化。如果你有一个张量a大小为(64, 10),还有一个张量b大小为(64,),那么当你执行a/b时,就会报错,因为你的张量大小不匹配。您只想将a值除以一个值,你需要确保b的大小为(64,1)。 这样PyTorch会将a每行中的10个值除以'b每行中的一个值。 你还需要为torch.sumdim关键字赋值。 设置dim = 0取整行的总和,而'dim = 1取整列的总和。

1
2
3
4
5
6
7
8
9
10
## 解
def softmax(x):
return torch.exp(x)/torch.sum(torch.exp(x), dim=1).view(-1, 1)

probabilities = softmax(out)

# 检查一下大小,应为(64, 10)
print(probabilities.shape)
# 他们的和为一?
print(probabilities.sum(dim=1))

采用PyTorch构建网路

我们已经知道如何构建输出的分布了,现在我们使用PyTorch的nn模块来构建一个简单的网络。这里我们的网路有 784 个输入, 256 个隐藏单元, 10 个输出单元和 1 个 softmax 输出。

1
from torch import nn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Network(nn.Module):
def __init__(self):
super().__init__()

# 输入和隐藏层为线性传递
self.hidden = nn.Linear(784, 256)
# 隐藏层到输出层也为线性
self.output = nn.Linear(256, 10)

# 定义 sigmoid 激活函数和softmax输出函数
self.sigmoid = nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)

def forward(self, x):
# 定义前向网络的每一步操作
x = self.hidden(x)
x = self.sigmoid(x)
x = self.output(x)
x = self.softmax(x)

return x

现在我们来对每一步进行一下讲解:

1
class Network(nn.Module):

我们构建一个网路Network,它的父类为 nn.Module。使用super().__init__()来对父类进行初始化,这里包含了很多有用的参量(我们后续会讲)。当然这个网络你叫什么名字都可以。

1
self.hidden = nn.Linear(784, 256)

定义了一个线性传递函数,\(x\mathbf{W} + b\) 其中,\(W\)\(b\) 的张量由函数自动创建,你只需要定义一个输入和输出的大小就行了。如果你想访问其中的权重和方差,你可以使用net.hidden.weightnet.hidden.bias

1
self.output = nn.Linear(256, 10)

同样,我们设置一个256输入,10输出的线性传递函数。 Similarly, this creates another linear transformation with 256 inputs and 10 outputs.

1
2
3
self.sigmoid =
nn.Sigmoid()
self.softmax = nn.Softmax(dim=1)

我们定义一下sigmoid 激活函数和softmax输出函数,并设置dim=1来对tensor每一列求和。

1
def forward(self, x):

nn.Module有一个前向的函数函数,张量 x 会顺次执行__init__的中定义的操作。

1
2
3
4
5
x =
self.hidden(x)
x = self.sigmoid(x)
x = self.output(x)
x = self.softmax(x)

每一步的顺序要和你构建的网络相匹配,至于变量x的命名无关紧要。注意,网络的顺序以forward为准而不是以softmax为准,现在我们可以创建一个Network对象。

1
2
3
# 现在我们来定义一下这个网络
model = Network()
model

您可以使用torch.nn.functional模块更简洁明了地定义网络。为了简便,我们通常将此模块命名为F

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import torch.nn.functional as F

class Network(nn.Module):
def __init__(self):
super().__init__()
# 输入层到隐藏层的线性连接
self.hidden = nn.Linear(784, 256)
# 隐藏层到输出层的线性连接
self.output = nn.Linear(256, 10)

def forward(self, x):
# 调用F的sigmoid函数
x = F.sigmoid(self.hidden(x))
# 调用softmax激活函数
x = F.softmax(self.output(x), dim=1)

return x

激活函数

到目前为止,我们只关注softmax激活,但从理论上来讲,我们可以使用任意非线性函数来作为激活函数。常见的激活函数为: Tanh(双曲正切函数)和ReLU(整流线性函数)。

activation 在实践中,ReLU用的最为普遍。

构建你的网络

mlp_mnist

练习: 构建一个更复杂的网络,如上图所示,它经过,784个单元的输入层,128个单元的隐藏层,一个ReLU激活,64个单元的隐藏层,一个ReLU激活和一个带softmax激活的输出层。 'fc'表示完全连接的图层。 在编写解决方案时,使用fc1fc2fc3作为图层名称。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## 解

class Network(nn.Module):
def __init__(self):
super().__init__()
# 按图定义各层的结构
self.fc1 = nn.Linear(784, 128)
self.fc2 = nn.Linear(128, 64)
self.fc3 = nn.Linear(64, 10)

def forward(self, x):

x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
x = F.relu(x)
x = self.fc3(x)
x = F.softmax(x, dim=1)

return x

model = Network()
model

初始化权重和偏移量

权重和偏移量的初始化是自动完成的,当然你也可以对他进行订制化,可通过model.fc1.weight来进行取值。

1
2
print(model.fc1.weight)
print(model.fc1.bias)

现在我们来举几个例子。

1
2
# 将偏移量全部赋为0
model.fc1.bias.data.fill_(0)
1
2
# 设为方差为1,均值为0的正态随机数
model.fc1.weight.data.normal_(std=0.01)

前相传递

我们来看一下,一幅图像通过网络有什么变化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 加载数据
dataiter = iter(trainloader)
images, labels = dataiter.next()

# 转化为1维向量 (batch size, color channels, image pixels)
images.resize_(64, 1, 784)
# 或者 images.resize_(images.shape[0], 1, 784) 来自动获取 batch size

# 前向通过网络
img_idx = 0
ps = model.forward(images[img_idx,:])

img = images[img_idx]
helper.view_classify(img.view(1, 28, 28), ps)

截止到目前为止,网络还是不好用,因为所有权重都是随机地!

使用 nn.Sequential

PyTorch提供了一种方便的方法来构建网络,它叫做`nn.Sequential`` (documentation)。我们来看一下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 网络的超变量
input_size = 784
hidden_sizes = [128, 64]
output_size = 10

# 建立网络
model = nn.Sequential(nn.Linear(input_size, hidden_sizes[0]),
nn.ReLU(),
nn.Linear(hidden_sizes[0], hidden_sizes[1]),
nn.ReLU(),
nn.Linear(hidden_sizes[1], output_size),
nn.Softmax(dim=1))
print(model)

# 使数据通过网络
images, labels = next(iter(trainloader))
images.resize_(images.shape[0], 1, 784)
ps = model.forward(images[0,:])
helper.view_classify(images[0].view(1, 28, 28), ps)

如果你想进行一次线性运算并查看权重,你可以使用model[0]

1
2
print(model[0])
model[0].weight

你甚至还可以使用一个字典OrderedDict来存储这些变量,这样的好处就是保证你的变量不会重名。

1
2
3
4
5
6
7
8
9
from collections import OrderedDict
model = nn.Sequential(OrderedDict([
('fc1', nn.Linear(input_size, hidden_sizes[0])),
('relu1', nn.ReLU()),
('fc2', nn.Linear(hidden_sizes[0], hidden_sizes[1])),
('relu2', nn.ReLU()),
('output', nn.Linear(hidden_sizes[1], output_size)),
('softmax', nn.Softmax(dim=1))]))
model

你可以使用每层的序号或者名字来快速访问他们。

1
2
print(model[0])
print(model.fc1)

下一篇文章我们来讲一下如何训练这个网络。