干货|深入理解神经网络

栏目: 数据库 · 发布时间: 5年前

内容简介:本文将介绍用于解决实际问题的深度学习架构的不同模块。前一章使用PyTorch的低级操作构建了如网络架构、损失函数和优化器这些模块。本章将介绍用于解决真实问题的神经网络的一些重要组件,以及PyTorch如何通过提供大量高级函数来抽象出复杂度。本章还将介绍用于解决真实问题的算法,如回归、二分类、多类别分类等。本文将讨论如下主题:上一章已经介绍了训练深度学习算法需要的几个步骤。

本文将介绍用于解决实际问题的深度学习架构的不同模块。前一章使用PyTorch的低级操作构建了如网络架构、损失函数和优化器这些模块。本章将介绍用于解决真实问题的神经网络的一些重要组件,以及PyTorch如何通过提供大量高级函数来抽象出复杂度。本章还将介绍用于解决真实问题的算法,如回归、二分类、多类别分类等。

本文将讨论如下主题:

    • 详解神经网络的不同构成组件;
    • 探究PyTorch中用于构建深度学习架构的高级功能;
    • 应用深度学习解决实际的图像分类问题。

1详解神经网络的组成部分

上一章已经介绍了训练深度学习算法需要的几个步骤。

1.构建数据管道。

2.构建网络架构。

3.使用损失函数评估架构。

4.使用优化算法优化网络架构的权重。

上一章中的网络由使用PyTorch数值运算构建的简单线性模型组成。尽管使用数值运算为玩具性质的问题搭建神经架构很简单,但当需要构建解决不同领域的复杂问题时,如计算机视觉和自然语言处理,构建一个架构就迅速变得复杂起来。大多数深度学习框架,如PyTorch、TensorFlow和Apache MXNet,都提供了抽象出很多复杂度的高级功能。这些深度学习框架的高级功能称为层(layer)。它们接收输入数据,进行如同在前面一章看到的各种变换,并输出数据。解决真实问题的深度学习架构通常由1~150个层组成,有时甚至更多。抽象出低层的运算并训练深度学习算法的过程如图3.1所示。

干货|深入理解神经网络

图3.1

1.1层——神经网络的基本组成

在本章的剩余部分,我们会见到各种不同类型的层。首先,先了解其中最重要的一种层:线性层,它就是我们前面讲过的网络层结构。线性层应用了线性变换:

Y

= Wx

+ b

线性层之所以强大,是因为前一章所讲的功能都可以写成单一的代码行,如下所示。

from torch.nn import Linear
myLayer= Linear(in_features=10,out_features=5,bias=True)

上述代码中的 myLayer 层,接受大小为 10 的张量作为输入,并在应用线性变换后输出一个大小为 5 的张量。下面是一个简单例子的实现:

inp = Variable(torch.randn(1,10))
myLayer = Linear(in_features=10,out_features=5,bias=True)
myLayer(inp)

可以使用属性 weightsbias 访问层的可训练参数:

myLayer.weight  


Parameter containing:
-0.2386 0.0828 0.2904 0.3133 0.2037 0.1858 -0.2642 0.2862 0.2874 0.1141
 0.0512 -0.2286 -0.1717 0.0554 0.1766 -0.0517 0.3112 0.0980 -0.2364 -0.0442
 0.0776 -0.2169 0.0183 -0.0384 0.0606 0.2890 -0.0068 0.2344 0.2711 -0.3039
 0.1055 0.0224 0.2044 0.0782 0.0790 0.2744 -0.1785 -0.1681 -0.0681 0.3141
 0.2715 0.2606 -0.0362 0.0113 0.1299 -0.1112 -0.1652 0.2276 0.3082 -0.2745
[torch.FloatTensor of size 5x10]  

myLayer.bias


Parameter containing:
-0.2646
-0.2232
 0.2444
 0.2177
 0.0897
[torch.FloatTensor of size 5

线性层在不同的框架中使用的名称有所不同,有的称为dense层,有的称为全连接层(fully connected layer)。用于解决真实问题的深度学习架构通常包含不止一个层。在PyTorch中,可以用多种方式实现。

一个简单的方法是把一层的输出传入给另一层:

myLayer1 = Linear(10,5)
myLayer2 = Linear(5,2)
myLayer2(myLayer1(inp))

每一层都有自己的学习参数,在多个层的架构中,每层都学习出它本层一定的模式,其后的层将基于前一层学习出的模式构建。把线性层简单堆叠在一起是有问题的,因为它们不能学习到简单线性表示以外的新东西。我们通过一个简单的例子看一下,为什么把线性层堆叠在一起的做法并不合理。

假设有具有如下权重的两个线性层:

权重

Layer1

3.0

Layer2

2.0

以上包含两个不同层的架构可以简单表示为带有另一不同层的单层。因此,只是堆叠多个线性层并不能帮助我们的算法学习任何新东西。有时,这可能不太容易理解,我们可以用下面的数学公式对架构进行可视化:

Y

= 2(3 X

1

) -2 Linear layers

Y

= 6( X

1

) -1 Linear layers

为解决这一问题,相较于只是专注于线性关系,我们可以使用不同的非线性函数,帮助学习不同的关系。

深度学习中有很多不同的非线性函数。PyTorch以层的形式提供了这些非线性功能,因为可以采用线性层中相同的方式使用它们。

一些流行的非线性函数如下所示:

    • sigmoid
    • tanh
    • ReLU
    • Leaky ReLU

1.2非线性激活函数

非线性激活函数是获取输入,并对其应用数学变换从而生成输出的函数。我们在实战中可能遇到数个非线性操作。下面会讲解其中几个常用的非线性激活函数。

1.sigmoid

sigmoid激活函数的数学定义很简单,如下:

干货|深入理解神经网络

简单来说,sigmoid函数以实数作为输入,并以一个0到1之间的数值作为输出。对于一个极大的负值,它返回的值接近于0,而对于一个极大的正值,它返回的值接近于1。图3.2所示为sigmoid函数不同的输出。

干货|深入理解神经网络

图3.2

sigmoid函数曾一度被不同的架构使用,但由于存在一个主要弊端,因此最近已经不太常用了。当sigmoid函数的输出值接近于0或1时,sigmoid函数前一层的梯度接近于0,由于前一层的学习参数的梯度接近于0,使得权重不能经常调整,从而产生了无效神经元。

2.tanh

非线性函数tanh将实数值输出为-1到1之间的值。当tanh的输出极值接近-1和1时,也面临梯度饱和的问题。不过,因为tanh的输出是以0为中心的,所以比sigmoid更受偏爱,如图3.3所示。

干货|深入理解神经网络

图3.3

3.ReLU

近年来ReLU变得很受欢迎,我们几乎可以在任意的现代架构中找到ReLU或其某一变体的身影。它的数学公式很简单:

f

( x

) =max

( 0,x

)

简单来说,ReLU把所有负值取作0,正值保持不变。可以对ReLU函数进行可视化,如图3.4所示。

干货|深入理解神经网络

图3.4

使用ReLU函数的一些好处和弊端如下。

    • 有助于优化器更快地找到正确的权重集合。从技术上讲,它使随机梯度下降收敛得更快。
    • 计算成本低,因为只是判断了阈值,并未计算任何类似于sigmoid或tangent函数计算的内容。
    • ReLU有一个缺点,即当一个很大的梯度进行反向传播时,流经的神经元经常会变得无效,这些神经元称为无效神经元,可以通过谨慎选择学习率来控制。我们将在第4章中讨论调整学习率的不同方式时,了解如何选择学习率。

4.Leaky ReLU

Leaky ReLU尝试解决一个问题死角,它不再将饱和度置为0,而是设为一个非常小的数值,如0.001。对某些用例,这一激活函数提供了相较于其他激活函数更优异的性能,但它不是连续的。

1.3PyTorch中的非线性激活函数

PyTorch已为我们实现了大多数常用的非线性激活函数,我们可以像使用任何其他的层那样使用它们。让我们快速看一个在PyTorch中使用 ReLU 激活函数的例子:

sample_data = Variable(torch.Tensor([[1,2,-1,-1]]))
myRelu = ReLU()
myRelu(sample_data)

输出:

Variable containing:
 1 2 0 0
[torch.FloatTensor of size 1x4]

在上面这个例子中,输入是包含两个正值、两个负值的张量,对其调用 ReLU 函数,负值将取为 0 ,正值则保持不变。

现在我们已经了解了构建神经网络架构的大部分细节,我们来构建一个可用于解决真实问题的深度学习架构。上一章中,我们使用了简单的方法,因而可以只关注深度学习算法如何工作。后面将不再使用这种方式构建架构,而是使用PyTorch中正常该用的方式构建。

1.PyTorch构建深度学习算法的方式

PyTorch中所有网络都实现为类,创建PyTorch类的子类要调用 nn.Module ,并实现 __init__forward 方法。在 init 方法中初始化层,这一点已在前一节讲过。在 forward 方法中,把输入数据传给 init 方法中初始化的层,并返回最终的输出。非线性函数经常被 forward 函数直接使用, init 方法也会使用一些。下面的代码片段展示了深度学习架构是如何用PyTrorch实现的:

class MyFirstNetwork(nn.Module):
    def __init__(self,input_size,hidden_size,output_size):
        super(MyFirstNetwork,self).__init__()
        self.layer1 = nn.Linear(input_size,hidden_size)
        self.layer2 = nn.Linear(hidden_size,output_size)
    def __forward__(self,input):
        out = self.layer1(input)
        out = nn.ReLU(out)
        out = self.layer2(out)
        return out

如果你是 Python 新手,上述代码可能会比较难懂,但它全部要做的就是继承一个父类,并实现父类中的两个方法。在Python中,我们通过将父类的名字作为参数传入来创建子类。 init 方法相当于Python中的构造器, super 方法用于将子类的参数传给父类,我们的例子中父类就是 nn.Module

2.不同机器学习问题的模型架构

待解决的问题种类将基本决定我们将要使用的层,处理序列化数据问题的模型从线性层开始,一直到长短期记忆(LSTM)层。基于要解决的问题类别,最后一层是确定的。使用机器学习或深度学习算法解决的问题通常有三类,最后一层的情况通常如下。

    • 对于回归问题,如预测T恤衫的销售价格,最后使用的是有一个输出的线性层,输出值为连续的。
    • 将一张给定的图片归类为T恤衫或衬衫,用到的是sigmoid激活函数,因为它的输出值不是接近1就是接近0,这种问题通常称为二分类问题。
    • 对于多类别分类问题,如必须把给定的图片归类为T恤、牛仔裤、衬衫或连衣裙,网络最后将使用softmax层。让我们抛开数学原理来直观理解softmax的作用。举例来说,它从前一线性层获取输入,并输出给定数量样例上的概率。在我们的例子中,将训练它预测每个图片类别的4种概率。记住,所有概率相加的总和必然为1。

3.损失函数

一旦定义好了网络架构,还剩下最重要的两步。一步是评估网络执行特定的回归或分类任务时表现的优异程度,另一步是优化权重。

优化器(梯度下降)通常接受一个标量值,因而 loss 函数应生成一个标量值,并使其在训练期间最小化。某些用例,如预测道路上障碍物的位置并判断是否为行人,将需要两个或更多损失函数。即使在这样的场景下,我们也需要把损失组合成一个优化器可以最小化的标量。最后一章将详细讨论把多个损失值组合成一个标量的真实例子。

上一章中,我们定义了自己的 loss 函数。PyTorch提供了经常使用的 loss 函数的实现。我们看看回归和分类问题的 loss 函数。

回归问题经常使用的 loss 函数是均方误差(MSE)。它和前面一章实现的 loss 函数相同。可以使用PyTorch中实现的 loss 函数,如下所示:

loss = nn.MSELoss()
input = Variable(torch.randn(3, 5), requires_grad=True)
target = Variable(torch.randn(3, 5))
output = loss(input, target)
output.backward()

对于分类问题,我们使用交叉熵损失函数。在介绍交叉熵的数学原理之前,先了解下交叉熵损失函数做的事情。它计算用于预测概率的分类网络的损失值,损失总和应为1,就像softmax层一样。当预测概率相对正确概率发散时,交叉熵损失增加。例如,如果我们的分类算法对图3.5为猫的预测概率值为0.1,而实际上这是只熊猫,那么交叉熵损失就会更高。如果预测的结果和真实标签相近,那么交叉熵损失就会更低。

干货|深入理解神经网络

图3.5

下面是用Python代码实现这种场景的例子。

def cross_entropy(true_label, prediction):
    if true_label == 1:
        return -log(prediction)
    else:
        return -log(1 - prediction)

为了在分类问题中使用交叉熵损失,我们真的不需要担心内部发生的事情——只要记住,预测差时损失值高,预测好时损失值低。PyTorch提供了 loss 函数的实现,可以按照如下方式使用。

loss = nn.CrossEntropyLoss()
input = Variable(torch.randn(3, 5), requires_grad=True)
target = Variable(torch.LongTensor(3).random_(5))
output = loss(input, target)
output.backward()

PyTorch包含的其他一些 loss 函数如表3.1所示。

表3.1

L1 loss

通常作为正则化器使用;第4章将进一步讲述

MSE loss

均方误差损失,用于回归问题的损失函数

Cross-entropy loss

交叉熵损失,用于二分类和多类别分类问题

NLL Loss

用于分类问题,允许用户使用特定的权重处理不平衡数据集

NLL Loss2d

用于像素级分类,通常和图像分割问题有关

4.优化网络架构

计算出网络的损失值后,需要优化权重以减少损失,并改善算法准确率。简单起见,让我们看看作为黑盒的优化器,它们接受损失函数和所有的学习参数,并微量调整来改善网络性能。PyTorch提供了深度学习中经常用到的大多数优化器。如果大家想研究这些优化器内部的动作,了解其数学原理,强烈建议浏览以下博客:

 ●http://colah.github.io/posts/2015-08-Backprop/

PyTorch提供的一些常用的优化器如下:

    • ADADELTA
    • Adagrad
    • Adam
    • SparseAdam
    • Adamax
    • ASGD
    • LBFGS
    • RMSProp
    • Rprop
    • SGD

第4章中将介绍更多算法细节,以及一些优势和折中方案考虑。让我们看看创建任意 optimizer 的一些重要步骤:

optimizer=optim.SGD(model.parameters(),lr= 0.01)

在上面的例子中,创建了 SGD 优化器,它把网络的所有学习参数作为第一个参数,另外一个参数是学习率,学习率决定了多大比例的变化调整可以作用于学习参数。第4章将深入学习率和动量(momentum)的更多细节,它们是优化器的重要参数。创建了优化器对象后,需要在循环中调用 zero_grad() 方法,以避免参数把上一次 optimizer 调用时创建的梯度累加到一起:

for input, target in dataset:
    optimizer.zero_grad()
    output = model(input)
    loss = loss_fn(output, target)
    loss.backward()
    optimizer.step()

再一次调用 loss 函数的 backward 方法,计算梯度值(学习参数需要改变的量),然后调用 optimizer.step() 方法,用于真正改变调整学习参数。

现在已经讲述了帮助计算机识别图像所需要的大多数组件。我们来构建一个可以区分狗和猫的复杂深度学习模型,以将学到的内容用于实践。

1.4使用深度学习进行图像分类

解决任何真实问题的重要一步是获取数据。Kaggle提供了大量不同数据科学问题的竞赛。我们将挑选一个2014年提出的问题,然后使用这个问题测试本章的深度学习算法,并在第5章中进行改进,我们将基于卷积神经网络(CNN)和一些可以使用的高级技术来改善图像识别模型的性能。大家可以从 https://www.kaggle.com/c/dogs-vs-cats/data 下载数据。数据集包含25,000张猫和狗的图片。在实现算法前,预处理数据,并对训练、验证和测试数据集进行划分是需要执行的重要步骤。数据下载完成后,可以看到对应数据文件夹包含了如图3.6所示的图片。

干货|深入理解神经网络

图3.6

当以图3.7所示的格式提供数据时,大多数框架能够更容易地读取图片并为它们设置标签的附注。也就是说每个类别应该有其所包含图片的独立文件夹。这里,所有猫的图片都应位于 cat 文件夹,所有狗的图片都应位于 dog 文件夹。

干货|深入理解神经网络

图3.7

Python可以很容易地将数据调整成需要的格式。请先快速浏览一下代码,然后,我们将讲述重要的部分。

path = '../chapter3/dogsandcats/'

#读取文件夹内的所有文件
files = glob(os.path.join(path,'*/*.jpg'))

print(f'Total no of images {len(files)}')

no_of_images = len(files)

#创建可用于创建验证数据集的混合索引
shuffle = np.random.permutation(no_of_images)

#创建保存验证图片集的validation目录
os.mkdir(os.path.join(path,'valid'))

#使用标签名称创建目录
for t in ['train','valid']:
     for folder in ['dog/','cat/']:
          os.mkdir(os.path.join(path,t,folder))

#将图片的一小部分子集复制到validation文件夹
for i in shuffle[:2000]:
     folder = files[i].split('/')[-1].split('.')[0]
     image = files[i].split('/')[-1]
     os.rename(files[i],os.path.join(path,'valid',folder,image))

#将图片的一小部分子集复制到training文件夹
for i in shuffle[2000:]:
     folder = files[i].split('/')[-1].split('.')[0]
     image = files[i].split('/')[-1]
     os.rename(files[i],os.path.join(path,'train',folder,image))

上述代码所做的处理,就是获取所有图片文件,并挑选出2,000张用于创建验证数据集。它把图片划分到了cats和dogs这两个类别目录中。创建独立的验证集是通用的重要实践,因为在相同的用于训练的数据集上测试算法并不合理。为了创建 validation 数据集,我们创建了一个图片数量长度范围内的数字列表,并把图像无序排列。在创建 validation 数据集时,我们可使用无序排列的数据来挑选一组图像。让我们详细解释一下每段代码。

下面的代码用于创建文件:

files=glob(os.path.join(path,'*/*.jpg'))

glob 方法返回特定路径的所有文件。当图片数量巨大时,也可以使用 iglob ,它返回一个迭代器,而不是将文件名载入到内存中。在我们的例子中,只有25,000个文件名,可以很容易加载到内存里。

可以使用下面的代码混合排列文件:

shuffle=np.random.permutation(no_of_images)

上述代码返回25,000个0~25,000范围内的无序排列的数字,可以把其作为选择图片子集的索引,用于创建 validation 数据集。

可以创建验证代码,如下所示:

os.mkdir(os.path.join(path,'valid'))
for t in ['train','valid']:
     for folder in ['dog/','cat/']:
          os.mkdir(os.path.join(path,t,folder))

上述代码创建了 validation 文件夹,并在 trainvalid 目录里创建了对应的类别文件夹(cats和dogs)。

可以用下面的代码对索引进行无序排列:

for i in shuffle[:2000]:
     folder = files[i].split('/')[-1].split('.')[0]
     image = files[i].split('/')[-1]
     os.rename(files[i],os.path.join(path,'valid',folder,image))

在上面的代码中,我们使用无序排列后的索引随机抽出 2000 张不同的图片作为验证集。同样地,我们把训练数据用到的图片划分到 train 目录。

现在已经得到了需要格式的数据,我们来快速看一下如何把图片加载成PyTorch张量。

1.把数据加载到PyTorch张量

PyTorch的 torchvision.datasets 包提供了一个名为 ImageFolder工具 类,当数据以前面提到的格式呈现时,它可以用于加载图片以及相应的标签。通常需要进行下面的预处理步骤。

1.把所有图片转换成同等大小。大多数深度学习架构都期望图片具有相同的尺寸。

2.用数据集的均值和标准差把数据集归一化。

3.把图片数据集转换成PyTorch张量。

PyTorch在 transforms 模块中提供了很多工具函数,从而简化了这些预处理步骤。例如,进行如下3种变换:

    • 调整成256 ×256大小的图片;
    • 转换成PyTorch张量;
    • 归一化数据(第5章将探讨如何获得均值和标准差)。

下面的代码演示了如何使用 ImageFolder 类进行变换和加载图片:

simple_transform=transforms.Compose([transforms.Scale((224,224)), 
                             transforms.ToTensor(),
                             transforms.Normalize([0.485, 0.456,
0.406], [0.229, 0.224, 0.225])])
train = ImageFolder('dogsandcats/train/',simple_transform)
valid = ImageFolder('dogsandcats/valid/',simple_transform)

train 对象为数据集保留了所有的图片和相应的标签。它包含两个重要属性:一个给出了类别和相应数据集索引的映射;另一个给出了类别列表。

●train.class_to_idx- {'cat': 0, 'dog': 1}
●train.classes- ['cat', 'dog']

把加载到张量中的数据可视化往往是一个最佳实践。为了可视化张量,必须对张量再次变形并将值反归一化。下面的函数实现了这样的功能:

def imshow(inp):
    """Imshow for Tensor."""
    inp = inp.numpy().transpose((1, 2, 0))
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    inp = std * inp + mean
    inp = np.clip(inp, 0, 1)
    plt.imshow(inp)

现在,可以把张量传入前面的 imshow 函数,将张量转换成图片:

imshow(train[50][0])

上述代码生成的输出如图3.8所示。

干货|深入理解神经网络

图3.8

2.按批加载PyTorch张量

在深度学习或机器学习中把图片进行批取样是一个通用实践,因为当今的图形处理器(GPU)和CPU都为批量图片的操作进行了优化。批尺寸根据我们使用的GPU种类而不同。每个GPU都有自己的内存,可能从2GB到12GB不等,有时商业GPU内存会更大。PyTorch提供了 DataLoader 类,它输入数据集将返回批图片。它抽象出了批处理的很多复杂度,如应用变换时的多worker的使用。下面的代码把前面的 trainvalid 数据集转换到数据加载器(data loader)中:

train_data_gen =
  torch.utils.data.DataLoader(train,batch_size=64,num_workers=3)
valid_data_gen =
  torch.utils.data.DataLoader(valid,batch_size=64,num_workers=3)

DataLoader 类提供了很多选项,其中最常使用的选项如下。

shuffle
num_workers

3.构建网络架构

对于大多的真实用例,特别是在计算机视觉中,我们很少构建自己的架构。可以使用已有的不同架构快速解决我们的真实问题。在我们的例子中,使用了流行的名为ResNet的深度学习算法,它在2015年赢得了不同竞赛的冠军,如与计算机视觉相关的ImageNet。为了更容易理解,我们假设算法是一些仔细连接在一起的不同的PyTorch层,并不关注算法的内部。在第5章学习卷积神经网络(CNN)时,我们将看到一些关键的ResNet算法的构造块。PyTorch通过 torchvision.models 模块提供的现成应用使得用户更容易使用这样的流行算法。因而,对于本例,我们快速看一下如何使用算法,然后再详解每行代码:

model_ft = models.resnet18(pretrained=True)
num_ftrs = model_ft.fc.in_features
model_ft.fc = nn.Linear(num_ftrs, 2)

if is_cuda:
    model_ft = model_ft.cuda()

models.resnet18(pertrained = True) 对象创建了算法的实例,实例是PyTorch层的集合。我们打印出 model_ft ,快速地看一看哪些东西构成了ResNet算法。算法的一小部分看起来如图3.9所示。这里没有包含整个算法,因为这很可能会占用几页内容。

干货|深入理解神经网络

图3.9

可以看出,ResNet架构是一个层的集合,包含的层为 Conv2dBatchNorm2dMaxPool2d ,这些层以一种特有的方式组合在一起。所有这些算法都将接受一个名为 pretrained 的参数。当 pretrainedTrue 时,算法的权重已为特定的ImageNet分类问题微调好。ImageNet预测的类别有1000种,包括汽车、船、鱼、猫和狗等。训练该算法,使其预测1000种ImageNet类别,权重调整到某一点,让算法得到最高的准确率。我们为用例使用这些保存好并与模型共享的权重。与以随机权重开始的情况相比,算法以微调好的权重开始时会趋向于工作得更好。因而,我们的用例将从预训练好的权重开始。

ResNet算法不能直接使用,因为它是用来预测1,000种类别,而对于我们的用例,仅需预测猫和狗这两种类别。为此,我们拿到ResNet模型的最后一层—— linear 层,并把输出特征改成2,如下面的代码所示:

model_ft.fc=nn.Linear(num_ftrs, 2)

如果在基于GPU的机器上运行算法,需要在模型上调用 cuda 方法,让算法在GPU上运行。强烈建议在装备了GPU的机器上运行这些算法;有了GPU后,用很少的钱就可以扩展出一个云实例。下面代码片段的最后一行告知PyTorch在GPU上运行代码:

if is_cuda:
model_ft=model_ft.cuda()

4.训练模型

前一节中,我们已经创建了 DataLoader 实例和算法。现在训练模型。为此我们需要 loss 函数和 optimizer

# 损失函数和优化器
learning_rate = 0.001
criterion = nn.CrossEntropyLoss()
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, 
  gamma=0.1)

在上述代码中,创建了基于 CrossEntropyLossloss 函数和基于 SGD 的优化器。 StepLR 函数帮助动态修改学习率。第4章将讨论用于调优学习率的不同策略。

下面的train_model函数获取模型输入,并通过多轮训练调优算法的权重降低损失:

def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
    since = time.time()

    best_model_wts = model.state_dict()
    best_acc = 0.0

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        #每轮都有训练和验证阶段
        for phase in ['train', 'valid']:
            if phase == 'train':
                scheduler.step()
                model.train(True) # 模型设为训练模式
            else:
                model.train(False) # 模型设为评估模式

            running_loss = 0.0
            running_corrects = 0

            #在数据上迭代
            for data in dataloaders[phase]:
                # 获取输入
                inputs, labels = data

                # 封装成变量
                if is_cuda:
                    inputs = Variable(inputs.cuda())
                    labels = Variable(labels.cuda())
                else:
                    inputs, labels = Variable(inputs), 
Variable(labels)

                #梯度参数清0
                optimizer.zero_grad()

                #前向
                outputs = model(inputs)
                _, preds = torch.max(outputs.data, 1) 
                loss = criterion(outputs, labels)

                #只在训练阶段反向优化
                if phase == 'train':
                    loss.backward()
                    optimizer.step()

                #统计
                running_loss += loss.data[0]
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects / dataset_sizes[phase]

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(
                phase, epoch_loss, epoch_acc))

            #深度复制模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = model.state_dict()

        print()

    time_elapsed = time.time() – since
    print('Training complete in {:.0f}m {:.0f}s'.format(
        time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    #加载最优权重
    model.load_state_dict(best_model_wts)
    return model

上述函数的功能如下。

1.传入流经模型的图片并计算损失。

2.在训练阶段反向传播。在验证/测试阶段,不调整权重。

3.每轮训练中的损失值跨批次累加。

4.存储最优模型并打印验证准确率。

上面的模型在运行 25 轮后,验证准确率达到了87%。下面是前面的 train_model 函数在 Dogs vs. Cats 数据集上训练时生成的日志;为了节省篇幅,本书只包含了最后几轮的结果。

Epoch 18/24
----------
train Loss: 0.0044 Acc: 0.9877
valid Loss: 0.0059 Acc: 0.8740

Epoch 19/24
----------
train Loss: 0.0043 Acc: 0.9914
valid Loss: 0.0059 Acc: 0.8725

Epoch 20/24
----------
train Loss: 0.0041 Acc: 0.9932
valid Loss: 0.0060 Acc: 0.8725

Epoch 21/24
----------
train Loss: 0.0041 Acc: 0.9937
valid Loss: 0.0060 Acc: 0.8725

Epoch 22/24
----------
train Loss: 0.0041 Acc: 0.9938
valid Loss: 0.0060 Acc: 0.8725

Epoch 23/24
----------
train Loss: 0.0041 Acc: 0.9938
valid Loss: 0.0060 Acc: 0.8725

Epoch 24/24
----------
train Loss: 0.0040 Acc: 0.9939
valid Loss: 0.0060 Acc: 0.8725

Training complete in 27m 8s
Best val Acc: 0.874000

接下来的章节中,我们将学习可以以更快的方式训练更高准确率模型的高级技术。前面的模型在Titan X GPU上运行了30分钟的时间,后面将讲述有助于更快训练模型的不同技术。

2小结

本章通过使用SGD优化器调整层权重,讲解了PyTorch中神经网络的全生命周期——从构成不同类型的层,到加入激活函数、计算交叉熵损失,再到优化网络性能(即最小化损失)。

本章还介绍了如何应用流行的ResNet架构解决二分类和多类别分类问题。

同时,我们尝试解决了真实的图像分类问题,把猫的图片归类为cat,把狗的图片归类为dog。这些知识可以用于对不同的实体进行分类,如辨别鱼的种类,识别狗的品种,划分植物种子,将子宫癌归类成Type1、Type2和Type3型等。

本文摘自 《PyTorch深度学习》

作者:[印度]毗湿奴•布拉马尼亚(Vishnu Subramanian)

译者:王海玲, 刘江峰

干货|深入理解神经网络

本书在不深入数学细节的条件下,给出了多个先进深度学习架构的直观解释,如ResNet、DenseNet、Inception和Seq2Seq等,也讲解了如何进行迁移学习,如何使用预计算特征加速迁移学习,以及如何使用词向量、预训练的词向量、LSTM和一维卷积进行文本分类。

阅读完本书后,读者将会成为一个熟练的深度学习人才,能够利用学习到的不同技术解决业务问题。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

豆瓣,流行的秘密

豆瓣,流行的秘密

黄修源 / 机械工业出版社 / 2009-9 / 29.00

380万人为何会齐聚豆瓣? HIN1和SARS是如何传播扩散开的? 贾君鹏何以快速窜红网络? 通过创新扩散的理论的分析和说明,给出了所有这些问题的答案! 这本书从豆瓣的流行现象说开来,应用了创新扩散等传播学道理来解释了豆瓣如何流行起来,同时作者还同时用创新扩散的理论解释了为何会出现世界变平的现象,长尾理论,SARS病毒的高速传播等。 作者以前任豆瓣设计师的身份以自己亲......一起来看看 《豆瓣,流行的秘密》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具