Press "Enter" to skip to content

PyTorch深度学习之神经网络合成

Last updated on 2019年4月13日

理解神经网络的基本组成部分,如张量、张量运算和梯度递减等,对构造复杂的神经网络至关重要。本章将构建首个神经网络的Hello world程序,并涵盖以下主题:

安装PyTorch;
实现第一个神经网络;
划分神经网络的功能模块;
介绍张量、变量、Autograd、梯度和优化器等基本构造模块;
使用PyTorch加载数据。

2.1安装PyTorch

 

PyTorch可以作为Python包使用,用户可以使用 pipconda 来构建,或者从源代码构建。本书推荐使用Anaconda Python 3发行版。要安装Anaconda,请参考Anaconda官方文档。所有示例将在本书的GitHub存储库中以Jupyter Notebook的形式提供。强烈建议使用Jupyter Notebook,因为它允许进行交互。如果已经安装了Anaconda Python,那幺可以继续PyTorch安装的后续步骤。

 

基于GPU的Cuda 8版安装:

 

conda install pytorch torchvision cuda80 -c soumith

 

基于GPU的Cuda 7.5版安装:

 

conda install pytorch torchvision -c soumith

 

非GPU版的安装:

 

conda install pytorch torchvision -c soumith

 

在本书写作时,PyTorch还不支持Windows,所以可以尝试使用虚拟机或Docker镜像。

 

2.2实现第一个神经网络

 

下面给出本书介绍的第一个神经网络,它将学习如何将训练示例(即输入数组)映射成目标(即输出数组)。假设我们为最大的在线公司之一Wondermovies工作(该公司按需提供视频服务),训练数据集包含了表示用户在平台上观看电影的平均时间的特征,网络将据此预测每个用户下周使用平台的时间。这是个假想出来的用例,不需要深入考虑。构建解决方案的主要分解活动如下。

准备数据: get_data 函数准备输入和输出张量(数组)。
创建学习参数: get_weights 函数提供以随机值初始化的张量,网络通过优化这些参数来解决问题。
网络模型: simple_network 函数应用线性规则为输入数据生成输出,计算时先用权重乘以输入数据,再加上偏差( y
= wx
+ b
)。
损失: loss_fn 函数提供了评估模型优劣的信息。
优化器: optimize 函数用于调整初始的随机权重,并帮助模型更准确地计算目标值。

如果大家刚接触机器学习,不用着急,到本章结束时将会真正理解每个函数的作用。下面这些从PyTorch代码抽取出来的函数,有助于更容易理解神经网络。我们将逐个详细讨论这些函数。前面提到的分解活动对大多数机器学习和深度学习问题而言都是相同的。接下来的章节会探讨为改进各项功能从而构建实际应用的各类技术。

 

神经网络的线性回归模型如下:

 

y

 

= w x

 

+ b

 

用PyTorch编码如下:

 

x,y = get_data() #x – 表示训练数据,y – 表示目标变量
w,b = get_weights() #w,b – 学习参数
for i in range(500):
    y_pred = simple_network(x) # 计算wx + b的函数
    loss = loss_fn(y,y_pred) # 计算y和y_pred平方差的和
if i % 50 == 0:
        print(loss)
    optimize(learning_rate) # 调整 w,b,将损失最小化

 

到本章结束时,你会了解到每个函数的作用。

 

2.2.1准备数据

 

PyTorch提供了两种类型的数据抽象,称为张量和变量。张量类似于 numpy 中的数组,它们也可以在GPU上使用,并能够改善性能。数据抽象提供了GPU和CPU的简易切换。对某些运算,我们会注意到性能的提高,以及只有当数据被表示成数字的张量时,机器学习算法才能理解不同格式的数据。张量类似Python数组,并可以改变大小。例如,图像可以表示成三维数组(高,宽,通道(RGB)),深度学习中使用最多5个维度的张量表示也很常见。一些常见的张量如下:

标量(0维张量);
向量(1维张量);
矩阵(2维张量);
3维张量;
切片张量;
4维张量;
5维张量;
GPU张量。

1.标量(0维张量)

 

包含一个元素的张量称为标量。标量的类型通常是 FloatTensorLongTensor 。在本书写作时,PyTorch还没有特别的0维张量。因此,我们使用包含一个元素的一维张量表示:

 

x = torch.rand(10)
x.size()
Output - torch.Size([10])

 

2.向量(1维张量)

 

向量只不过是一个元素序列的数组。例如,可以使用向量存储上周的平均温度:

 

temp = torch.FloatTensor([23,24,24.5,26,27.2,23.0])
temp.size()
Output - torch.Size([6])

 

3.矩阵(2维向量)

 

大多数结构化数据都可以表示成表或矩阵。我们使用波士顿房价(Boston House Prices)的数据集,它包含在Python的机器学习包scikit-learn中。数据集是一个包含了 506 个样本或行的 numpy 数组,其中每个样本用 13 个特征表示。 Torch 提供了一个工具函数 from_numpy() ,它将 numpy 数组转换成 torch 张量,其结果张量的形状为 506 行× 13 列:

 

boston_tensor = torch.from_numpy(boston.data)
boston_tensor.size()
Output: torch.Size([506, 13])
boston_tensor[:2]
Output:
Columns 0 to 7
   0.0063 18.0000 2.3100 0.0000 0.5380 6.5750 65.2000 4.0900
   0.0273 0.0000 7.0700 0.0000 0.4690 6.4210 78.9000 4.9671
Columns 8 to 12
   1.0000 296.0000 15.3000 396.9000 4.9800
   2.0000 242.0000 17.8000 396.9000 9.1400
[torch.DoubleTensor of size 2x13]

 

4.3维张量

 

当把多个矩阵累加到一起时,就得到了一个3维张量。3维张量可以用来表示类似图像这样的数据。图像可以表示成堆叠到一起的矩阵中的数字。一个图像形状的例子是( 224,224,3 ),其中第一个数字表示高度,第二个数字表示宽度,第三个表示通道数(RGB)。我们来看看计算机是如何识别大熊猫的,代码如下:

 

from PIL import Image
# 使用PIL包从磁盘读入熊猫图像并转成numpy数组
panda = np.array(Image.open('panda.jpg').resize((224,224)))
panda_tensor = torch.from_numpy(panda)
panda_tensor.size()
Output - torch.Size([224, 224, 3])
# 显示熊猫
plt.imshow(panda)

 

由于显示大小为( 224,224,3 )的张量会占用本书的很多篇幅,因此将把图2.1所示的图像切片成较小的张量来显示。

 

 

图2.1显示的图像

 

5.切片张量

 

张量的一个常见操作是切片。举个简单的例子,我们可能选择一个张量的前5个元素,其中张量名称为 sales 。我们使用一种简单的记号 sales[:slice_index] ,其中 slice_index 表示要进行切片的张量位置:

 

sales = 
torch.FloatTensor([1000.0,323.2,333.4,444.5,1000.0,323.2,333.4,444.5])
sales[:5]
 1000.0000
  323.2000
  333.4000
  444.5000
 1000.0000
[torch.FloatTensor of size 5]
sales[:-5]
 1000.0000
  323.2000
  333.4000
[torch.FloatTensor of size 3]

 

对熊猫图像做些更有趣的处理,比如只选择一个通道时熊猫图像的样子,以及如何选择熊猫的面部。

 

下面,只选择熊猫图像的一个通道:

 

plt.imshow(panda_tensor[:,:,0].numpy()) #0表示RGB中的第一个通道

 

输出如图2.2所示。

 

 

图2.2

 

现在裁剪图像,假设要构造一个熊猫的面部检测器,我们只需要熊猫图像的面部部分。我们来裁剪张量图像,让它只包含熊猫面部。

 

plt.imshow(panda_tensor[25:175,60:130,0].numpy())

 

输出如图2.3所示。

 

另一个常见的例子是需要获取张量的某个特定元素:

 

# torch.eye(shape)生成一个对角线元素为1的对角矩阵
sales = torch.eye(3,3)
sales[0,1]
Output- 0.00.0

 

第5章在讨论使用卷积神经网络构建图像分类器时,将再次用到图像数据。

 

 

图2.3

 

 

PyTorch的大多数张量运算都和 NumPy 运算非常类似。

 

6.4维张量

 

4维张量类型的一个常见例子是批图像。为了可以更快地在多样例上执行相同的操作,现代的CPU和GPU都进行了专门优化,因此,处理一张或多张图像的时间相差并不大。因而,同时使用一批样例比使用单样例更加常见。对批大小的选择并不一目了然,它取决于多个因素。不使用更大批尺寸或完整数据集的主要因素是GPU的内存限制,16、32和64是通常使用的批尺寸。

 

举例来说,加载一批 64×224×224×3 的猫咪图片,其中64表示批尺寸或图片数量,两个224分别表示高和宽,3表示通道数:

 

#从磁盘读取猫咪图片
cats = glob(data_path+'*.jpg')
#将图片转换成numpy数组
cat_imgs = np.array([np.array(Image.open(cat).resize((224,224))) for cat in
cats[:64]])
cat_imgs = cat_imgs.reshape(-1,224,224,3)
cat_tensors = torch.from_numpy(cat_imgs)
cat_tensors.size()
Output - torch.Size([64, 224, 224, 3])

 

7.5维张量

 

可能必须使用5维张量的一个例子是视频数据。视频可以划分为帧,例如,熊猫玩球的长度为30秒的视频,可能包含30帧,这可以表示成形状为(1×30×224×224×3)的张量。一批这样的视频可以表示成形状为(32×30×224×224×3)的张量——例中的30表示每个视频剪辑中包含的帧数,32表示视频剪辑的个数。

 

8.GPU上的张量

 

我们已学习了如何用张量表示法表示不同形式的数据。有了张量格式的数据后,要进行一些常见运算,比如加、减、乘、点积和矩阵乘法等。所有这些操作都可以在CPU或GPU上执行。PyTorch提供了一个名为 cuda() 的简单函数,将张量从CPU复制到GPU。我们来看一下其中的一些操作,并比较矩阵乘法运算在CPU和GPU上的性能差异。

 

张量的加法运算用如下代码实现:

 

#执行张量加法运算的不同方式
a = torch.rand(2,2)
b = torch.rand(2,2)
c = a + b
d = torch.add(a,b)
#和自身相加
a.add_(5)
#不同张量之间的乘法 
a*b
a.mul(b)
#和自身相乘
a.mul_(b)

 

对于张量矩阵乘法,我们比较一下代码在CPU和GPU上的性能。所有张量都可以通过调用 cuda() 函数转移到GPU上。

 

GPU上的乘法运算运行如下:

 

a = torch.rand(10000,10000)
b = torch.rand(10000,10000)
a.matmul(b)
Time taken: 3.23 s
#将张量转移到GPU
a = a.cuda()
b = b.cuda()
a.matmul(b)
Time taken: 11.2 µs

 

加、减和矩阵乘法这些基础运算可以用于构建复杂运算,如卷积神经网络(CNN)和递归神经网络(RNN),本书稍后的章节将进行相关讲解。

 

9.变量

 

深度学习算法经常可以表示成计算图。图2.4所示为一个在示例中构建的变量计算图的简单例子。

 

 

图2.4变量计算图

 

在图2.4所示的计算图中,每个小圆圈表示一个变量,变量形成了一个轻量封装,将张量对象、梯度,以及创建张量对象的函数引用封装起来。图2.5所示为 Variable 类的组件。

 

 

图2.5Variable类

 

梯度是指 loss 函数相对于各个参数( W

 

, b

 

)的变化率。例如,如果 a

 

的梯度是2,那幺 a

 

值的任何变化都会导致 Y

 

值变为原来的两倍。如果还不清楚,不要着急——大多数数深度学习框架都会为我们代为计算梯度值。本章中,我们将学习如何使用梯度来改善模型的性能。

 

除了梯度,变量还引用了创建它的函数,相应地也就指明了每个变量是如何创建的。例如,变量 a 带有的信息表明它是由 XW 的积生成的。

 

让我们看个例子,创建变量并检查梯度和函数引用:

 

x = Variable(torch.ones(2,2),requires_grad=True)
y = x.mean()
y.backward()
x.grad
Variable containing:
 0.2500 0.2500
 0.2500 0.2500
[torch.FloatTensor of size 2x2]
x.grad_fn
Output – None
x.data
 1 1
 1 1
[torch.FloatTensor of size 2x2]
y.grad_fn
<torch.autograd.function.meanbackward>

 

在上面的例子中,我们在变量上调用了 backward 操作来计算梯度。默认情况下,变量的梯度是none。

 

变量中的 grad_fn 指向了创建它的函数。变量被用户创建后,就像例子中的 x 一样,其函数引用为 None 。对于变量 y ,它指向的函数引用是 MeanBackward

 

属性Data用于获取变量相关的张量。

 

2.2.2为神经网络创建数据

 

第一个神经网络中的 get_data 函数创建了两个变量: xy ,尺寸为 (17, 1)(17) 。我们看函数内部的构造:

 

def get_data():
    train_X =
np.asarray([3.3,4.4,5.5,6.71,6.93,4.168,9.779,6.182,7.59,2.167,
                         7.042,10.791,5.313,7.997,5.654,9.27,3.1])
    train_Y =
np.asarray([1.7,2.76,2.09,3.19,1.694,1.573,3.366,2.596,2.53,1.221, 
                         2.827,3.465,1.65,2.904,2.42,2.94,1.3])
    dtype = torch.FloatTensor
    X =
Variable(torch.from_numpy(train_X).type(dtype),requires_grad=False).view(17,1)
    y = Variable(torch.from_numpy(train_Y).type(dtype),requires_grad=False)
    return X,y

 

1.创建学习参数

 

在前面神经网络的例子中,共有两个学习参数: wb ,还有两个不变的参数: xy 。我们已在 get_data 函数中创建了变量 xy 。学习参数使用随机值初始化并创建,其中参数 require_grad 的值设为 True ,这与变量 xy 不同,变量 xy 创建时 require_grad 的值是 False 。初始化学习参数有不同的方法,我们将在后续章节探索。下面列出的是 get_weights 函数代码:

 

def get_weights():
    w = Variable(torch.randn(1),requires_grad = True)
    b = Variable(torch.randn(1),requires_grad=True)
    return w,b

 

前面的代码大部分是一目了然的,其中 torch.randn 函数为任意给定形状创建随机值。

 

2.神经网络模型

 

使用PyTorch变量定义了输入和输出后,就要构建模型来学习如何将输入映射到输出。在传统的编程中,我们手动编写具有不同逻辑的函数代码,将输入映射到输出。然而,在深度学习和机器学习中,是通过把输入和相关的输出展示给模型,让模型完成函数的学习。我们的例子中,在线性关系的假定下,实现了尝试把输入映射为输出的简单神经网络。线性关系可以表示为 y

 

= wx

b
,其中 w
b
是学习参数。网络要学习 w
b
的值,这样 wx
b
才能更加接近真实的 y
。图2.6是训练集和神经网络要学习的模型的示意图。

 

图2.6输入数据点

 

图2.7表示和输入数据点拟合的线性模型。

 

 

图2.7拟合数据点的线性模型

 

图中的深灰(蓝)色线表示网络学习到的模型。

 

3.网络的实现

 

现在已经有了实现网络所需的所有参数( xwby ),我们对 wx 做矩阵乘法,然后,再把结果与 b 求和,这样就得到了预测值 y 。函数实现如下:

 

def simple_network(x):
    y_pred = torch.matmul(x,w)+b
    return y_pred

 

PyTorch在 torch.nn 中提供了称为层(layer)的高级抽象,层将负责多数常见的技术都需要用到的后台初始化和运算工作。这里使用低级些的操作是为了理解函数内部的构造。在第5章和第6章中,将用PyTorch抽象出来的层来构建复杂的神经网络或函数。前面的模型可以表示为 torch.nn 层,如下:

 

f=nn.Linear(17,1) #简单很多

 

我们已经计算出了 y 值,接下来要了解模型的性能,必须通过 loss 函数评估。

 

4.损失函数

 

由于我们的学习参数 wb 以随机值开始,产生的结果 y_pred ,必和真实值 y 相去甚远。因此,需要定义一个函数,来告知模型预测值和真实值的差距。由于这是一个回归问题,我们使用称为误差平方和(也称为和方差,SSE)的损失函数。我们对 y 的预测值和真实值之差求平方。SSE有助于模型评估预测值和真实值的拟合程度。 torch.nn 库中有不同的损失函数,如均方差(又称方差,MSE)损失和交叉熵损失。但是在本章,我们自己来实现 loss 函数:

 

def loss_fn(y,y_pred):
    loss = (y_pred-y).pow(2).sum()
    for param in [w,b]:
        if not param.grad is None: param.grad.data.zero_()
    loss.backward()
    return loss.data[0]

 

除了计算损失值,我们还进行了 backward 操作,计算出了学习参数w和b的梯度。由于我们会不止一次使用 loss 函数,因此通过调用 grad.data.zero_() 方法来清除前面计算出的梯度值。在第一次调用 backward 函数的时候,梯度是空的,因此只有当梯度不为 None 时才将梯度值设为0。

 

5.优化神经网络

 

前面例子中的算法使用随机的初始权重来预测目标,并计算损失,最后调用 loss 变量上的 backward 函数计算梯度值。每次迭代都在整个样例集合上重复整个过程。在多数的实际应用中,每次迭代都要对整个数据集的一个小子集进行优化操作。损失值计算出来后,用计算出的梯度值进行优化,以让损失值降低。优化器通过下面的函数实现:

 

def optimize(learning_rate):
    w.data -= learning_rate * w.grad.data
    b.data -= learning_rate * b.grad.data

 

学习率是一个超参数,可以让用户通过较小的梯度值变化来调整变量的值,其中梯度指明了每个变量( wb )需要调整的方向。

 

不同的优化器,如Adam、RmsProp和SGD,已在 torch.optim 包中实现好。后面的章节中,我们将使用这些优化器来降低损失或提高准确率。

 

2.2.3加载数据

 

为深度学习算法准备数据本身就可能是件很复杂的事情。PyTorch提供了很多工具类,工具类通过多线程、数据增强和批处理抽象出了如数据并行化等复杂性。本章将介绍两个重要的工具类: Dataset 类和 DataLoader 类。为了理解如何使用这些类,我们从Kaggle网站( https://www.kaggle.com/c/dogs-vs-cats/data )上拿到 Dogs vs. Cats 数据集,并创建可以生成PyTorch张量形式的批图片的数据管道。

 

1.Dataset类

 

任何自定义的数据集类,例如 Dogs 数据集类,都要继承自PyTorch的数据集类。自定义的类必须实现两个函数: __len__(self)__getitem__(self,idx) 。任何和 Dataset 类表现类似的自定义类都应和下面的代码类似:

 

from torch.utils.data import Dataset
class DogsAndCatsDataset(Dataset):
    def __init__(self,):
        pass
    def __len__(self):
        pass
    def __getitem__(self,idx):
        pass

 

init 方法中,将进行任何需要的初始化。例如在本例中,读取表索引和图片的文件名。 __len__(self) 运算负责返回数据集中的最大元素个数。 __getitem__ (self, idx) 运算根据每次调用时的 idx 返回对应元素。下面的代码实现了 DogsAndCatsDataset 类。

 

class DogsAndCatsDataset(Dataset):
    def __init__(self,root_dir,size=(224,224)):
        self.files = glob(root_dir)
        self.size = size
    def __len__(self):
        return len(self.files)
    def __getitem__(self,idx):
        img = np.asarray(Image.open(self.files[idx]).resize(self.size))
        label = self.files[idx].split('/')[-2]
        return img,label

 

在定义了 DogsAndCatsDataset 类后,可以创建一个对象并在其上进行迭代,如下面的代码所示。

 

for image,label in dogsdset:
#在数据集上应用深度学习算法

 

在单个的数据实例上应用深度学习算法并不理想。我们需要一批数据,现代的GPU都对批数据的执行进行了性能优化。 DataLoader 类通过提取出大部分复杂度来帮助创建批数据。

 

2.DataLoader类

 

DataLoader 类位于 PyTorchutils 类中,它将数据集对象和不同的取样器联合,如 SequentialSamplerRandomSampler ,并使用单进程或者多进程的的迭代器,为我们提供批量图片。取样器是为算法提供数据的不同策略。下面是使用 DataLoader 处理 Dogs vs. Cats 数据集的例子。

 

dataloader = DataLoader(dogsdset,batch_size=32,num_workers=2)
for imgs , labels in dataloader:
     #在数据集上应用深度学习算法
     pass

 

imgs 包含一个形状为(32, 224, 224, 3)的张量,其中32表示批尺寸。

 

PyTorch团队也维护了两个有用的库,即 torchvisiontorchtext ,这两个库基于 DatasetDataLoader 类构建。我们将在相关章节使用它们。

 

2.3小结

 

本章中,我们学习了PyTorch提供的多个数据结构和操作,并使用PyTorch的基础组成模块实现了几个组件。在数据准备上,我们创建了供算法使用的张量。我们的网络架构是一个可预测用户使用Wondermovies平台的平均小时数的模型。我们使用 loss 函数检查模型的性能,并使用 optimize 函数调整模型的学习参数,从而改善平台性能。

 

我们也了解了PyTorch如何通过抽象出数据并行化和数据增强的复杂度,让创建数据管道变得更简单。

 

下一章将深入探讨神经网络和深度学习算法的原理。我们将学习PyTorch内置的用于构建网络架构、损失函数和优化器的几个模块,也将演示如何在真实数据集上使用它们。

 

本文节选自 《PyTorch深度学习》第2章 ,更多信息请访问异步社区本书图书详情页: https://www.epubit.com/book/detail/15208

Be First to Comment

发表评论

电子邮件地址不会被公开。 必填项已用*标注