PyTorch动态神经网络
好的 PyTorch 是由 Facebook 推出的一个开源项目. 基于 Torch 的神经网络库使用 torch 语言时面临挑战, 虽然 Torch 非常强大, 但Lua 并不十分普及, 开发团队因此决定将Lua版本迁移至更流行的Python. 显然 PyTorch自诞生以来就引起了强烈的关注. 那么为何会如此呢?
此外如果你熟悉 Numpy, PyTorch指出,在神经网络领域中它是替代numpy的工具
PyTorch 和 Tensorflow
据 PyTorch 自己介绍, 他们家的最大优点就是建立的神经网络是动态 的, 对比静态的 Tensorflow, 他能更有效地处理一些问题, 比如说 RNN 变化时间长度的输出. 而我认为, 各家有各家的优势和劣势, 所以我们要以中立的态度. 两者都是大公司, Tensorflow 自己说自己在分布式训练上下了很大的功夫, 那我就默认 Tensorflow 在这一点上要超出 PyTorch, 但是 Tensorflow 的静态计算图使得他在 RNN 上有一点点被动 (虽然它用其他途径解决了), 不过用 PyTorch 的时候, 你会对这种动态的 RNN 有更好的理解.
而且 TensorFlow 的高度集成功能使其基础代码难以让人理解. PyTorch 稍逊一筹 如果你深入探索其 API 接口你至少能比直观看懂一些 PyTorch 的运行机制.
最后我的建议就是:
- 如果是学生的话,可以选择任何一个学校或者稍微偏好PyTorch框架学习。掌握了某个模块的知识后,迁移至其他框架如TensorFlow或其他框架相对容易。
- 如果你已经进入职场了,请依照公司提供的资源和工具进行开发工作。不要偏离轨道或不适应公司的技术栈。
PyTorch安装
学习资料:
安装
PyTorch 安装起来很简单, 它自家网页上就有很方便的选择方式 (网页升级改版后可能和下图有点不同):
按照上面的提示, 我只需在终端中输入以下指令就可以了:
$ pip install http://download.pytorch.org/whl/torch-0.1.11.post5-cp35-cp35m-macosx_10_7_x86_64.whl
$ pip install torchvision
Torch 或 Numpy
PyTorch 被公认为神经网络界的 Numpy 替代品, 因为其能够在 GPU 上加速张量运算(前提是具备相应的 GPU 设备), 类似于 Numpy 在 CPU 上处理数组运算. 因此, 对于神经网络建模来说, 使用 PyTorch 的张量形式是最理想的选择. 比如说, 和 TensorFlow 中的张量一样.
自然地, 我们对Numpy始终心怀感激, 因为我们已经习惯了它的使用模式. 然而,Torch察觉到我们的情谊, 它将Torch设计得与Numpy能够完美兼容. 比如说吧, 这样就可以轻松地将numpy数组转换为Torch张量了:
import torch
import numpy as np
np_data = np.arange(6).reshape((2, 3))
torch_data = torch.from_numpy(np_data)
tensor2array = torch_data.numpy()
print(
'\nnumpy array:', np_data, # [[0 1 2], [3 4 5]]
'\ntorch tensor:', torch_data, # 0 1 2 \n 3 4 5 [torch.LongTensor of size 2x3]
'\ntensor to array:', tensor2array, # [[0 1 2], [3 4 5]]
Torch 中的数学运算
事实上,在TensorFlow框架中张量运算与NumPy数组的表现极其相似, 我们可以通过对比分析的方式来深入理解这一特性. 如果您对TensorFlow中更多的实用运算符感兴趣, 官方文档将是您最佳的学习资源.
# abs 绝对值计算
data = [-1, -2, 1, 2]
tensor = torch.FloatTensor(data) # 转换成32位浮点 tensor
print(
'\nabs',
'\nnumpy: ', np.abs(data), # [1 2 1 2]
'\ntorch: ', torch.abs(tensor) # [1 2 1 2]
)
# sin 三角函数 sin
print(
'\nsin',
'\nnumpy: ', np.sin(data), # [-0.84147098 -0.90929743 0.84147098 0.90929743]
'\ntorch: ', torch.sin(tensor) # [-0.8415 -0.9093 0.8415 0.9093]
)
# mean 均值
print(
'\nmean',
'\nnumpy: ', np.mean(data), # 0.0
'\ntorch: ', torch.mean(tensor) # 0.0
除了一些基本的计算外, 矩阵运算才是神经网络领域中占据核心地位的内容. 因此, 我们重点讲解了矩阵乘法的相关知识. 其中, 我们介绍了一种能够在numpy环境下高效执行的方法, 但在PyTorch框架中则不具备这样的特性.
# matrix multiplication 矩阵点乘
data = [[1,2], [3,4]]
tensor = torch.FloatTensor(data) # 转换成32位浮点 tensor
# correct method
print(
'\nmatrix multiplication (matmul)',
'\nnumpy: ', np.matmul(data, data), # [[7, 10], [15, 22]]
'\ntorch: ', torch.mm(tensor, tensor) # [[7, 10], [15, 22]]
)
# !!!! 下面是错误的方法 !!!!
data = np.array(data)
print(
'\nmatrix multiplication (dot)',
'\nnumpy: ', data.dot(data), # [[7, 10], [15, 22]] 在numpy 中可行
'\ntorch: ', tensor.dot(tensor) # torch 会转换成 [1,2,3,4].dot([1,2,3,4) = 30.0
)
自新版本起(≥0.3.0),对tensor.dot()功能进行了更新。该函数仅限于一维数组的情况,在此前提下做出了相应调整。
tensor.dot(tensor) # torch 会转换成 [1,2,3,4].dot([1,2,3,4) = 30.0
# 变为
torch.dot(tensor.dot(tensor)
变量
什么是 Variable
在 Torch 中是一个存储可变值的位置,在这个位置上可以存放不同的数值并随时更新这些数值;这些数值会发生不断的变化,在编程中可以用一种形象的方式来比喻这一过程——就像一个装满不同物品的购物袋一样;那么具体来说这个购物袋里的物品到底是什么呢它自然是属于 Torch 的 Tensor 这种数据结构;当我们在程序中使用一个 Variable 来执行运算时也会生成一个新的相同类型的 Variable 这就是 Torch 中的基本操作方式
我们定义一个 Variable:
import torch
from torch.autograd import Variable # torch 中 Variable 模块
# 先生鸡蛋
tensor = torch.FloatTensor([[1,2],[3,4]])
# 把鸡蛋放到篮子里, requires_grad是参不参与误差反向传播, 要不要计算梯度
variable = Variable(tensor, requires_grad=True)
print(tensor)
"""
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
print(variable)
"""
Variable containing:
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
Variable 计算, 梯度
我们再对比一下 tensor 的计算和 variable 的计算.
t_out = torch.mean(tensor*tensor) # x^2
v_out = torch.mean(variable*variable) # x^2
print(t_out)
print(v_out) # 7.5
到目前为止为止我们尚未发现明显的区别 但请时刻牢记 Variable 在运算过程中默默构建了一个复杂的运算网络 被称作 computational graph. 这个图的作用是什么?它是为了整合所有的运算步骤(节点)以便在反向传播过程中 Aided by the backward propagation of errors during this process 能够高效地完成对所有变量 gradient 的一次性计算 而 tensor 系统不具备这种能力.
在构建计算图的过程中新增了一个运算步骤`v_out = torch.mean(variable*variable)`;在反向传播过程中为减少误差贡献了这一项运算;我们可以通过以下示例来更好地理解这一过程:
v_out.backward() # 模拟 v_out 的误差反向传递
# 下面两步看不懂没关系, 只要知道 Variable 是计算图的一部分, 可以用来传递误差就好.
# v_out = 1/4 * sum(variable*variable) 这是计算图中的 v_out 计算步骤
# 针对于 v_out 的梯度就是, d(v_out)/d(variable) = 1/4*2*variable = variable/2
print(variable.grad) # 初始 Variable 的梯度
'''
0.5000 1.0000
1.5000 2.0000
'''
访问Variable中的数据信息
直接运行print(variable)命令只会输出 Variable 类型的数据,在许多情况下无法实现目标(例如尝试使用plt绘图)。因此我们需要将该变量转换为 tensor 类型。
print(variable) # Variable 形式
"""
Variable containing:
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
print(variable.data) # tensor 形式
"""
1 2
3 4
[torch.FloatTensor of size 2x2]
"""
print(variable.data.numpy()) # numpy 形式
"""
[[ 1. 2.]
[ 3. 4.]]
"""
激励函数
通常情况下,在具体的例子中,默认选择作为优先级的第一位的是哪些激励函数。当遇到较少层数时,在少量层结构中,我们可以探索尝试多种不同的激励函数。对于卷积神经网络中的卷积层来说,在卷积神经网络 Convolutional neural networks 的框架内,默认情况下常用的激励函数通常是 ReLU 函数。在循环神经网络 recurrent neural networks 中,默认情况下常用的激活函数通常是 tanh 或者 ReLU 函数。至于如何选择 tanh 和 ReLU 中哪一个更适合特定的任务,则会在后续专门介绍循环神经网络的文章中进行详细讲解。
其核心在于通过数学方式描述复杂的非线性关系,在提升模型处理复杂数据能力方面发挥着关键作用. 对于尚未完全掌握其中奥秘的读者, 我们推荐查看这个动画短片以更好地理解这一概念. 通过生动有趣的视觉化展示激励函数的工作原理, 这个资源能够帮助你更直观地 grasp 激活函数的本质.
Torch 中的激励函数
Torch中的激励函数种类繁多, 实际上我们常用到的也就这几个relu, sigmoid, tanh, softplus. 为了更好地理解这些激活函数的特点, 让我们深入了解这些激活函数的具体表现形式吧.
import torch
import torch.nn.functional as F # 激励函数都在这
from torch.autograd import Variable
# 做一些假数据来观看图像
x = torch.linspace(-5, 5, 200) # x data (tensor), shape=(100, 1)
x = Variable(x)
接着就是做生成不同的激励函数数据:
x_np = x.data.numpy() # 换成 numpy array, 出图时用
# 几种常用的 激励函数
y_relu = F.relu(x).data.numpy()
y_sigmoid = F.sigmoid(x).data.numpy()
y_tanh = F.tanh(x).data.numpy()
y_softplus = F.softplus(x).data.numpy()
# y_softmax = F.softmax(x) softmax 比较特殊, 不能直接显示, 不过他是关于概率的, 用于分类
接着我们开始画图, 画图的代码也在下面:

import matplotlib.pyplot as plt # python 的可视化模块, 我有教程 (https://morvanzhou.github.io/tutorials/data-manipulation/plt/)
plt.figure(1, figsize=(8, 6))
plt.subplot(221)
plt.plot(x_np, y_relu, c='red', label='relu')
plt.ylim((-1, 5))
plt.legend(loc='best')
plt.subplot(222)
plt.plot(x_np, y_sigmoid, c='red', label='sigmoid')
plt.ylim((-0.2, 1.2))
plt.legend(loc='best')
plt.subplot(223)
plt.plot(x_np, y_tanh, c='red', label='tanh')
plt.ylim((-1.2, 1.2))
plt.legend(loc='best')
plt.subplot(224)
plt.plot(x_np, y_softplus, c='red', label='softplus')
plt.ylim((-0.2, 6))
plt.legend(loc='best')
plt.show()
关系拟合(回归)
我将会参加此次会议,以观察神经网络如何利用简洁的形式将一群数据转化为一条直线。或者具体来说,是关于如何在数据中发现它们之间的联系,并运用神经网络模型构建能够代表这些联系的直线。
建立数据集
我们生成一些模拟数据以还原真实场景. 例如,在一个一元二次函数模型中:y = a \cdot x^2 + b,我们向响应变量添加轻微的随机扰动以更接近实际情况.
import torch
import matplotlib.pyplot as plt
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1) # x data (tensor), shape=(100, 1)
y = x.pow(2) + 0.2*torch.rand(x.size()) # noisy y data (tensor), shape=(100, 1)
# 画图
plt.scatter(x.data.numpy(), y.data.numpy())
plt.show()
建立神经网络
我们可以直接使用 torch 里的神经网络构建体系。在初始化阶段定义所有层属性后,在前向传播阶段逐步构建各层之间的关系。在搭建各层关系时,激励函数起到关键作用。如果还不清楚激励函数用途的同学,请参考这篇非常好的动画教程。
import torch
import torch.nn.functional as F # 激励函数都在这
class Net(torch.nn.Module): # 继承 torch 的 Module
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__() # 继承 __init__ 功能
# 定义每层用什么样的形式
self.hidden = torch.nn.Linear(n_feature, n_hidden) # 隐藏层线性输出
self.predict = torch.nn.Linear(n_hidden, n_output) # 输出层线性输出
def forward(self, x): # 这同时也是 Module 中的 forward 功能
# 正向传播输入值, 神经网络分析出输出值
x = F.relu(self.hidden(x)) # 激励函数(隐藏层的线性值)
x = self.predict(x) # 输出值
return x
net = Net(n_feature=1, n_hidden=10, n_output=1)
print(net) # net 的结构
"""
Net (
(hidden): Linear (1 -> 10)
(predict): Linear (10 -> 1)
)
"""
训练网络
训练的步骤很简单, 如下:
# optimizer 是训练的工具
optimizer = torch.optim.SGD(net.parameters(), lr=0.2) # 传入 net 的所有参数, 学习率
loss_func = torch.nn.MSELoss() # 预测值和真实值的误差计算公式 (均方差)
for t in range(100):
prediction = net(x) # 喂给 net 训练数据 x, 输出预测值
loss = loss_func(prediction, y) # 计算两者的误差
optimizer.zero_grad() # 清空上一步的残余更新参数值
loss.backward() # 误差反向传播, 计算参数更新值
optimizer.step() # 将参数更新值施加到 net 的 parameters 上
可视化训练过程
为了可视化整个训练的过程, 更好的理解是如何训练, 我们如下操作:
import matplotlib.pyplot as plt
plt.ion() # 画图
plt.show()
for t in range(200):
...
loss.backward()
optimizer.step()
# 接着上面来
if t % 5 == 0:
# plot and show learning process
plt.cla()
plt.scatter(x.data.numpy(), y.data.numpy())
plt.plot(x.data.numpy(), prediction.data.numpy(), 'r-', lw=5)
plt.text(0.5, 0, 'Loss=%.4f' % loss.data.numpy(), fontdict={'size': 20, 'color': 'red'})
plt.pause(0.1)
