从基础概念到实现:入门PyTorch深度神经网络框架

PyTorch 是一个有潜力能改变深度学习实现面貌的 Python 库,它的使用非常灵活与轻松。在本文中,我们将以更实用的方式探索 PyTorch,包括基础知识和案例研究等。此外,本文还将比较使用 NumPy 和 PyTorch 从头构建神经网络的方式,以了解它们在实现中的相似之处。


PyTorch 的构建者表明,PyTorch 的哲学是解决当务之急,也就是说即时构建和运行我们的计算图。这恰好适合 Python 的编程方法,因为我们不需等待整个代码都被写入才能知道是否起作用。我们很容易运行部分代码,并实时检查它。

PyTorch 是一个基于 Python 的库,旨在为深度学习提供一个灵活的开发平台。PyTorch 的工作流程非常接近于 Python 的科学计算库 NumPy。那么为什么我们需要使用 PyTorch 构建深度学习模型?以下作者根据实际经验提供了三个理由:

  • 便于使用的 API:它的使用如同 Python 那样简单。

  • 支持 Python:正如上文所述,PyTorch 可以平滑地与 Python 数据科学栈相结合。它与 NumPy 一样简单,甚至我们都感觉不出它们的区别。

  • 动态计算图:PyTorch 不再采用特定的函数预定义计算图,而是提供构建动态计算图的框架,甚至我们可以在运行时修正它们。这种动态框架在我们不知道所构建的神经网络需要多少内存时非常有用。

其它一些使用 PyTorch 的优点还有多 GPU 支持、自定义数据加载器和极简的预处理过程等。自从它在 2016 年 1 月份发布以来,许多研究者将其采用为标准的实现库,因为它构建新颖的、极其复杂的计算图同样非常简单。即使这样,PyTorch 被主流数据科学家和研究员接收还是花了很长时间,因为它目前仍然是新的项目,且还有很多地方需要构建与完善。

PyTorch 基础

在讨论 PyTorch 的各个组件前,我们需要了解它的工作流。PyTorch 使用一种称之为 imperative / eager 的范式,即每一行代码都要求构建一个图以定义完整计算图的一个部分。即使完整的计算图还没有完成构建,我们也可以独立地执行这些作为组件的小计算图,这种动态计算图被称为「define-by-run」方法。

更多介绍请查看:http://pytorch.org/about/

安装 PyTorch 非常简单,我们可以按照自己的系统跟随官方文档的步骤轻松完成。例如以下选择在 Linux、Python 3.5 和 CUDA 9.1 的环境下安装 PyTorch:

conda install pytorch torchvision cuda91 -c pytorch

我们在基础部分主要需要了解的 PyTorch 元素有 PyTorch 张量、数学运算、自动求导模块、最优化模块和神经网络模块。下面本文会依次对这些模块进行简要的介绍:

PyTorch 张量

张量其实就是多维数组,PyTorch 中的张量非常类似于 NumPy 中的 Ndarry,只不过张量可以用于 GPU。PyTorch 支持多种类型的张量,我们可以如下简单地定义一个一维矩阵:

# import pytorch
import torch

# define a tensor
torch.FloatTensor([2])
 2
[torch.FloatTensor of size 1]

数学运算

如 NumPy 一样,高效地实现数学函数对于科学计算库至关重要。PyTorch 提供了一个简单的接口,并支持 200 多种数学运算,以下是 PyTorch 实现简单加运算的过程:

a = torch.FloatTensor([2])
b = torch.FloatTensor([3])

a + b
 5
[torch.FloatTensor of size 1]

这种运算与 Python 非常像,我们可以在定义的 PyTorch 张量上执行多种矩阵运算。例如我们可以转置二维张量:

matrix = torch.randn(3, 3)
matrix
-1.3531 -0.5394  0.8934
 1.7457 -0.6291 -0.0484
-1.3502 -0.6439 -1.5652
[torch.FloatTensor of size 3x3]
matrix.t()
-2.1139  1.8278  0.1976
 0.6236  0.3525  0.2660
-1.4604  0.8982  0.0428
[torch.FloatTensor of size 3x3]

AutoGrad 模块

PyTorch 使用的技术为自动微分(automatic differentiation)。在这种机制下,系统会有一个 Recorder 来记录我们执行的运算,然后再反向计算对应的梯度。这种技术在构建神经网络的过程中十分强大,因为我们可以通过计算前向传播过程中参数的微分来节省时间。

from torch.autograd import Variable

x = Variable(train_x)
y = Variable(train_y, requires_grad=False)

最优化模块

torch.optim 是实现神经网络中多种优化算法的模块,它目前已经支持大多数一般的方法,所以我们不需要从头构建优化算法。以下展示了使用 Adam 优化器的基本代码:

optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)

神经网络模块


PyTorch AutoGrad 使得计算图的定义和梯度的计算十分简单,但原版的 AutoGrad 可能对定义复杂的神经网络显得太底层,因此我们需要神经网络模块帮助简化工作。该 nn 包定义了一组函数,我们可以将其视为有一些可训练权重的神经网络层级。我们也可以将该神经网络模块视为类似于 Keras 的 PyTorch 组件。

import torch

# define model
model = torch.nn.Sequential(
 torch.nn.Linear(input_num_units, hidden_num_units),
 torch.nn.ReLU(),
 torch.nn.Linear(hidden_num_units, output_num_units),
)
loss_fn = torch.nn.CrossEntropyLoss()

以上就是 PyTorch 的基本组件,我们可以使用它们快速构建神经网络。当然以上只是简单的概念介绍,每一个模块都有非常多的函数与方法,读者可详细查阅 PyTorch 文档了解更多。

构建神经网络(NumPy vs. PyTorch)

在这一部分中,我们分别使用 NumPy 和 PyTorch 构建简单的神经网络以实现二元分类问题,本文的后面会对这一部分的代码进行解释。

## Neural network in numpy

import numpy as np

#Input array
X=np.array([[1,0,1,0],[1,0,1,1],[0,1,0,1]])

#Output
y=np.array([[1],[1],[0]])

#Sigmoid Function
def sigmoid (x):
 return 1/(1 + np.exp(-x))

#Derivative of Sigmoid Function
def derivatives_sigmoid(x):
 return x * (1 - x)

#Variable initialization
epoch=5000 #Setting training iterations
lr=0.1 #Setting learning rate
inputlayer_neurons = X.shape[1] #number of features in data set
hiddenlayer_neurons = 3 #number of hidden layers neurons
output_neurons = 1 #number of neurons at output layer

#weight and bias initialization
wh=np.random.uniform(size=(inputlayer_neurons,hiddenlayer_neurons))
bh=np.random.uniform(size=(1,hiddenlayer_neurons))
wout=np.random.uniform(size=(hiddenlayer_neurons,output_neurons))
bout=np.random.uniform(size=(1,output_neurons))

for i in range(epoch):
  #Forward Propogation
  hidden_layer_input1=np.dot(X,wh)
  hidden_layer_input=hidden_layer_input1 + bh
  hiddenlayer_activations = sigmoid(hidden_layer_input)
  output_layer_input1=np.dot(hiddenlayer_activations,wout)
  output_layer_input= output_layer_input1+ bout
  output = sigmoid(output_layer_input)

  #Backpropagation
  E = y-output
  slope_output_layer = derivatives_sigmoid(output)
  slope_hidden_layer = derivatives_sigmoid(hiddenlayer_activations)
  d_output = E * slope_output_layer
  Error_at_hidden_layer = d_output.dot(wout.T)
  d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
  wout += hiddenlayer_activations.T.dot(d_output) *lr
  bout += np.sum(d_output, axis=0,keepdims=True) *lr
  wh += X.T.dot(d_hiddenlayer) *lr
  bh += np.sum(d_hiddenlayer, axis=0,keepdims=True) *lr

print('actual :\n', y, '\n')
print('predicted :\n', output)

现在,我们会发现使用 PyTorch 实现相同的网络会非常简单。以下的代码同样也用粗体表示出它与 NumPy 的不同之处:

## neural network in pytorch*import torch*

#Input array
X = *torch.Tensor*([[1,0,1,0],[1,0,1,1],[0,1,0,1]])

#Output
y = *torch.Tensor*([[1],[1],[0]])

#Sigmoid Function
def sigmoid (x):
  return 1/(1 + *torch.exp*(-x))

#Derivative of Sigmoid Function
def derivatives_sigmoid(x):
  return x * (1 - x)

#Variable initialization
epoch=5000 #Setting training iterations
lr=0.1 #Setting learning rate
inputlayer_neurons = X.shape[1] #number of features in data set
hiddenlayer_neurons = 3 #number of hidden layers neurons
output_neurons = 1 #number of neurons at output layer

#weight and bias initialization
wh=*torch.randn*(inputlayer_neurons, hiddenlayer_neurons)*.type(torch.FloatTensor)*
bh=*torch.randn*(1, hiddenlayer_neurons)*.type(torch.FloatTensor)*
wout=*torch.randn*(hiddenlayer_neurons, output_neurons)
bout=*torch.randn*(1, output_neurons)

for i in range(epoch):

  #Forward Propogation
  hidden_layer_input1 = *torch.mm*(X, wh)
  hidden_layer_input = hidden_layer_input1 + bh
  hidden_layer_activations = sigmoid(hidden_layer_input)

  output_layer_input1 = *torch.mm*(hidden_layer_activations, wout)
  output_layer_input = output_layer_input1 + bout
  output = sigmoid(output_layer_input1)

  #Backpropagation
  E = y-output
  slope_output_layer = derivatives_sigmoid(output)
  slope_hidden_layer = derivatives_sigmoid(hidden_layer_activations)
  d_output = E * slope_output_layer
  Error_at_hidden_layer = *torch.mm*(d_output, wout.t())
  d_hiddenlayer = Error_at_hidden_layer * slope_hidden_layer
  wout += *torch.mm*(hidden_layer_activations.t(), d_output) *lr
  bout += d_output.sum() *lr
  wh += *torch.mm*(X.t(), d_hiddenlayer) *lr
  bh += d_output.sum() *lr

print('actual :\n', y, '\n')
print('predicted :\n', output)

对比其它深度学习库

在一份基准脚本中,它展示出 PyTorch 在训练长短期记忆(LSTM)网络上比其它主要框架的表现都要好,因为它运行一个 Epoch 有最少的中位数时间。

PyTorch 中的数据加载 API 经过了优良的设计,接口是针对特定数据集、采样器和数据加载器而构建的。对比于 TensorFlow 的数据加载工具(readers, queues 等),我发现 PyTorch 的数据加载模块更易于使用。同时它们还能无缝对接神经网络构建模块,所以我们不需要第三方高级库。

然而,我并不推荐使用使用 PyTorch 部署模型,因为 PyTorch 仍然不是那么成熟。正如 PyTorch 开发者所说:「我们经常看到用户首先创建一个 PyTorch 模型来测试是否可行,然后当需要部署模型到生产中时,他们会转化为 Caffe 2 等其他框架,并将其部署到移动端或其它平台。」

案例研究:用 PyTorch 解决图像识别问题

为了进一步熟悉 PyTorch,我们将使用它解决 Analytics Vidhya 的深度学习实践问题:识别手写数字。我们的问题是给定一张 28 x 28 的图像,利用模型识别其所代表的手写数字。

所以首先我们需要下载训练集与测试集,数据集包含了一个压缩文件以储存所有的图像。其中 train.csv 和 test.csv 分别储存了训练和测试图像,且图像的格式为 png。下面我们将一步步构建简单的神经网络以实现手写数字识别功能。

第 0 步:准备工作

a)导入必要的函数库

# import modules
%pylab inline
import os
import numpy as np
import pandas as pd
from scipy.misc import imread
from sklearn.metrics import accuracy_score

b)设置随机的 Seed,因此我们能控制模型产生的随机数基本不变(伪随机数)。

# To stop potential randomness
seed = 128
rng = np.random.RandomState(seed)

c)设置工作目录的路径。

root_dir = os.path.abspath('.')
data_dir = os.path.join(root_dir, 'data')

# check for existence
os.path.exists(root_dir), os.path.exists(data_dir)

第 1 步:加载与预处理数据

a)现在读取 CSV 格式的数据集,并获取文件名与对应的标注。

# load dataset
train = pd.read_csv(os.path.join(data_dir, 'Train', 'train.csv'))
test = pd.read_csv(os.path.join(data_dir, 'Test.csv'))

sample_submission = pd.read_csv(os.path.join(data_dir, 'Sample_Submission.csv'))

train.head()

b)接下来可以打印准备好的图片。

# print an image
img_name = rng.choice(train.filename)
filepath = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)

img = imread(filepath, flatten=True)

pylab.imshow(img, cmap='gray')
pylab.axis('off')
pylab.show()

c)对于更简单的数据操作,我们可以储存所有的图像作为 NumPy 数组。

# load images to create train and test set
temp = []
for img_name in train.filename:
  image_path = os.path.join(data_dir, 'Train', 'Images', 'train', img_name)
  img = imread(image_path, flatten=True)
  img = img.astype('float32')
  temp.append(img)

train_x = np.stack(temp)

train_x /= 255.0
train_x = train_x.reshape(-1, 784).astype('float32')

temp = []
for img_name in test.filename:
  image_path = os.path.join(data_dir, 'Train', 'Images', 'test', img_name)
  img = imread(image_path, flatten=True)
  img = img.astype('float32')
  temp.append(img)

test_x = np.stack(temp)

test_x /= 255.0
test_x = test_x.reshape(-1, 784).astype('float32')

train_y = train.label.values

d)因为这个是一个典型的机器学习问题,所以我们可以创建验证集以监控模型的运行情况。下面我们以 7:3 的比例分割训练集与验证集。

# create validation set
split_size = int(train_x.shape[0]*0.7)

train_x, val_x = train_x[:split_size], train_x[split_size:]
train_y, val_y = train_y[:split_size], train_y[split_size:]

第 2 步:构建模型

a)下面是模型的主体,我们定义的神经网络共有三层,即输入层、隐藏层和输出层。输入层和输出层的神经元数量是固定的,即 28 x 28 和 10 x 1,它们分别代表了输入图像的像素和类别。我们在隐藏层采用了 50 个神经元,并采用 Adam 作为最优化算法。

import torch
from torch.autograd import Variable
# number of neurons in each layer
input_num_units = 28*28
hidden_num_units = 500
output_num_units = 10

# set remaining variables
epochs = 5
batch_size = 128
learning_rate = 0.001

b)以下将开始训练模型。

# define model
model = torch.nn.Sequential(
  torch.nn.Linear(input_num_units, hidden_num_units),
  torch.nn.ReLU(),
  torch.nn.Linear(hidden_num_units, output_num_units),
)
loss_fn = torch.nn.CrossEntropyLoss()

# define optimization algorithm
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
## helper functions
# preprocess a batch of dataset
def preproc(unclean_batch_x):
  """Convert values to range 0-1"""
  temp_batch = unclean_batch_x / unclean_batch_x.max()

  return temp_batch

# create a batch
def batch_creator(batch_size):
  dataset_name = 'train'
  dataset_length = train_x.shape[0]

  batch_mask = rng.choice(dataset_length, batch_size)

  batch_x = eval(dataset_name + '_x')[batch_mask]
  batch_x = preproc(batch_x)

  if dataset_name == 'train':
    batch_y = eval(dataset_name).ix[batch_mask, 'label'].values

  return batch_x, batch_y
# train network
total_batch = int(train.shape[0]/batch_size)

for epoch in range(epochs):
  avg_cost = 0
  for i in range(total_batch):
    # create batch
    batch_x, batch_y = batch_creator(batch_size)

    # pass that batch for training
    x, y = Variable(torch.from_numpy(batch_x)), Variable(torch.from_numpy(batch_y), requires_grad=False)
    pred = model(x)

    # get loss
    loss = loss_fn(pred, y)

    # perform backpropagation
    loss.backward()
    optimizer.step()
    avg_cost += loss.data[0]/total_batch

  print(epoch, avg_cost)
# get training accuracy
x, y = Variable(torch.from_numpy(preproc(train_x))), Variable(torch.from_numpy(train_y), requires_grad=False)
pred = model(x)

final_pred = np.argmax(pred.data.numpy(), axis=1)

accuracy_score(train_y, final_pred)
# get validation accuracy
x, y = Variable(torch.from_numpy(preproc(val_x))), Variable(torch.from_numpy(val_y), requires_grad=False)
pred = model(x)
final_pred = np.argmax(pred.data.numpy(), axis=1)

accuracy_score(val_y, final_pred)

训练准确度为:0.8779008746355685

测试准确度为:0.867482993197279

这些分数非常令人满意,因为我们只是用简单的神经网络训练了 5 个 Epoch。以上,本文介绍了简单的 PyTorch 入门概念,并利用简单的案例熟悉 PyTorch 的使用。读者可以继续阅读 PyTorch 的文档以了解更多信息。

原文链接:https://www.analyticsvidhya.com/blog/2018/02/pytorch-tutorial/

工程PyTorch深度学习框架神经网络计算图实现
2
返回顶部