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

基于Pytorch深度学习

在本文中,我将介绍一下PyTorch,PyTorch 是一个训练神经网络的有利助手。同时,Pytorch基本可以无缝衔接Numpy,对于熟悉Numpy的你,应该很容易就能转型到PyTorch。同时PyTorch能够调用GPU的API实现硬件加速,并提供诸如自动计算梯度和模型定制化等诸多方便功能。另一方面,PyTorch相较于 Tensorflow 有更好的兼容性。

神经网络

深度学习是一套基于人工神经网络的函数拟合方法。网络有多个“神经元”构成。每个神经元又有多个输入,每个输入又有自己的权重。这些输入将会权重加成后,输入激活函数得到输出值。 simple_neuron 数学表达式为:

\[ \begin{align} y &= f(w_1 x_1 + w_2 x_2 + b) \\ y &= f\left(\sum_i w_i x_i +b \right) \end{align} \]

这里的向量乘法为点乘。

\[ h = \begin{bmatrix} x_1 \, x_2 \cdots x_n \end{bmatrix} \cdot \begin{bmatrix} w_1 \\ w_2 \\ \vdots \\ w_n \end{bmatrix} \]

张量

通过对张量的线性运算,我们就能够得出各种各样的神经网络。张量其实是一种,矩阵的扩展表达形式,一维张量是标量,二维张量是向量,三维张量是矩阵(如下图所示)。 tensor_examples

下面我们看一下如何使用PyTorch来构建一个简单的神经网络。

1
2
# 首先引入 PyTorch
import torch
1
2
3
4
5
6
7
8
def activation(x):
""" Sigmoid 激活函数

变量定义
---------
x: torch.Tensor
"""
return 1/(1+torch.exp(-x))

Sigmoid 函数:

sigmoid_function
1
2
3
4
5
6
7
8
9
10
### 生成数据
torch.manual_seed(7) # 设置随机种子数

# 基于标注正态分布,获得5个随机数, size 1*5,均值为0,方差为1。
features = torch.randn((1, 5))
print(features)
# 设置Ground-Truth(GT)权重,size 同 features。
weights = torch.randn_like(features)
# 设置GT的偏移量。
bias = torch.randn((1, 1))

以上我们就完成了,训练简单神经网络的准备数据。现在他们还都是基于正态随机分布的随机取值,但是随着训练过程的进行,他们将收敛于GT。

Pytorch 的张量可以进行,相加,相乘,相减等操作,和你平常使用的Numpy的array用法一样。现在,我们将用生成的随机数据计算这个简单神经网络的输出值。

练习: 通过特征 features,权重 weights,和偏移量bias计算网络的输出值。类似与Numpy,在Pytorch中可以使用torch.sum()函数进行求和,然后使用我们定义的激活函数来计算输出值。

1
2
3
4
### 解

y = activation(torch.sum(features * weights) + bias)
y = activation((features * weights).sum() + bias)

你也可以用采用矩阵相乘的办法来一次完成相乘和求和的操作。通常来说,我更建议采用矩阵相乘的方式来进行计算,因为这样做更高效。PyTorch提供了大量的库函数和GPU接口,来加速矩阵运算。 如果我们想使用矩阵乘法,我们就需要调用函数torch.mm() 或者 torch.matmul()。我个人更建议使用后者,因为它有更多的特性。

1
torch.matmul(features, weights)

但是当我们运行时,就会出现如下错误

1
2
3
4
5
6
7
8
9
>> torch.matmul(features, weights)
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-13-15d592eb5279> in <module>()
----> 1 torch.matmul(features,
weights)

RuntimeError: size mismatch, m1: [1 x 5], m2: [1 x 5] at
/pytorch/aten/src/TH/generic/THTensorMath.cpp:961

这是因为张量的大小(shape)不正确,造成的矩阵不能相乘。这是一个非常常见的问题。 解决办法也很简单,就是直接调整weights的大小,来适应矩阵乘法运算。

注意: 张量的大小标示为tensor.shape。这是一个非常常见的运算函数,请记住它。

Pytorch 也提供了诸多适合改变shape(大小)的函数,例如: weights.reshape(), weights.resize_(), 和 weights.view(). * weights.reshape(a, b) 是讲weights的数据拷贝到一个新的内存中,并整形为a*b。

  • weights.resize_(a, b) 也能得到相同的结果,唯一不同的是,他会检查shape。当新tensor比原tensor的元素少时,多余的元素将会在新tensor中剔除(但你依然可以通过原tensor获得这部分数据),如果新tesnor的元素多于原tensor,那么程序将阻止它初始化。同时要注意,不同于reshape,这里的操作都是原地操作,没有拷贝。如果你想对原地操作有更过的了解可以查看 read more about in-place operations in PyTorch.
  • weights.view(a, b) 其实和reshape差不多,但是由于存在时间比较长,所以用的人也比较多。 我个人建议倾向于使用reshape,但是如果你使用另外两个一般也不会影响你的使用结果。

练习: 使用矩阵相乘来计算神经元输出。

1
2
3
## 解

y = activation(torch.mm(features, weights.reshape(5,1)) + bias)

实现第一个网络吧!

现在我们已经学会了如何计算一个神经元。现在我们来试着把这些神经元堆叠在一起,从而试想一个网络,第一层的神经元的输出可以作为第二层神经元的输入。因为每层都有多个神经元,所以我们用矩阵来表示权重。 multilayer_diagram_weights 最底下的一层是神经网络的输入,我们称之为输入层。中间的称为隐藏层,最顶端的称为输出层。下面我们从数学的角度来分析一下网络的运算原理。例如,隐藏层(\(h_1\)\(h_2\))可表示为:

\[ \vec{h} = [h_1 \, h_2] = \begin{bmatrix} x_1 \, x_2 \cdots \, x_n \end{bmatrix} \cdot \begin{bmatrix} w_{11} & w_{12} \\ w_{21} &w_{22} \\ \vdots &\vdots \\ w_{n1} &w_{n2} \end{bmatrix} \] 隐藏层的输出就是输出层的输入。那么整个网络的输出就可以表达为:

\[ y = f_2 \! \left(\, f_1 \! \left(\vec{x} \, \mathbf{W_1}\right) \mathbf{W_2} \right) \]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
### 生成数据
torch.manual_seed(7) # 设置随机种子

# 生成3个正态分布随机数
features = torch.randn((1, 3))

# 定义网络每层的大小
n_input = features.shape[1] # 输入的大小
n_hidden = 2 # 隐藏层神经元数目
n_output = 1 # 输出层神经元数目

# 隐藏层输入的权重
W1 = torch.randn(n_input, n_hidden)
# 隐藏层输出的权重
W2 = torch.randn(n_hidden, n_output)

# 隐藏层和输出层的偏移量
B1 = torch.randn((1, n_hidden))
B2 = torch.randn((1, n_output))

练习: 使用权重W1W2和偏移量B1B2计算神经网络的输出。

1
2
3
4
5
### 解

h = activation(torch.mm(features, W1) + B1)
output = activation(torch.mm(h, W2) + B2)
print(output)

如果计算正确,你的输出为 tensor([[ 0.3171]]). 隐藏单元的数目称为hyperparameter(超参数)。每个权重和偏移量的超参数都不尽相同。同时,有更多层,更多单元的网络在相同数据中有更好的性能,因为他们学习到更多的特征,当然计算量也越大。

Numpy 与 Torch 相互转换

和Numpy的相互转化是PyTorch的主打特性之一。具体操作为: Numpy -> PyTorch torch.from_numpy()

PyTorch -> Numpy

.numpy()

1
2
3
import numpy as np
a = np.random.rand(4,3) # numpy的随机array
a
1
2
b = torch.from_numpy(a) # 转换成torch的张量
b
1
b.numpy() # 转换回 numpy 的array

注意这里所有的操作都是‘in-place’的。因为numpy和pyTorch共享内存。

1
2
# pytorh乘2
b.mul_(2)
1
2
# Numpy array 也会有相应的变化,希望使用时大家能注意
a