NLP神经网络基础组件

Posted ZSYL

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了NLP神经网络基础组件相关的知识,希望对你有一定的参考价值。

神经网络基础组件

本文标题:Natural-Language-Processing-with-PyTorch(三)

文章作者:Yif Du

发布时间:2018 年 12 月 19 日 - 14:12

最后更新:2018 年 12 月 28 日 - 11:12

原始链接:http://yifdu.github.io/2018/12/19/Natural-Language-Processing-with-PyTorch(三)/

许可协议:署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

本章通过介绍构建神经网络的基本思想,如激活函数、损失函数、优化器和监督训练设置,为后面的章节奠定了基础。

我们从感知器开始,这是一个将不同概念联系在一起的一个单元的神经网络。感知器本身是更复杂的神经网络的组成部分。这是一种贯穿全书的常见模式,我们讨论的每个架构或网络都可以单独使用,也可以在其他复杂的网络中组合使用。

当我们讨论计算图形和本书的其余部分时,这种组合性将变得清晰起来。

感知机:最简单的神经网络

最简单的神经网络单元是感知器。感知器在历史上是非常松散地模仿生物神经元的。就像生物神经元一样,有输入和输出,“信号”从输入流向输出,如图 3-1 所示。

每个感知器单元有一个输入(x),一个输出(y),和三个“旋钮”(knobs):一组权重(w),偏量(b),和一个激活函数(f)。权重和偏量都从数据学习,激活函数是精心挑选的取决于网络的网络设计师的直觉和目标输出。数学上,我们可以这样表示:

通常情况下感知器有不止一个输入。我们可以用向量表示这个一般情况;即,xw是向量,wx的乘积替换为点积:

激活函数,这里用f表示,通常是一个非线性函数。示例 3-1 展示了 PyTorch 中的感知器实现,它接受任意数量的输入、执行仿射转换、应用激活函数并生成单个输出。

示例 3-1:使用 PyTorch 实现感知机

import torch
import torch.nn as nn

class Perceptron(nn.Module):
    """ A Perceptron is one Linear layer """
    def __init__(self, input_dim):
        """
        Args:
            input_dim (int): size of the input features
        """
        super(Perceptron, self).__init__()
        self.fc1 = nn.Linear(input_dim, 1)

    def forward(self, x_in):
        """The forward pass of the Perceptron

        Args:
            x_in (torch.Tensor): an input data tensor.
                x_in.shape should be (batch, num_features)
        Returns:
            the resulting tensor. tensor.shape should be (batch,)
        """
        return torch.sigmoid(self.fc1(x_in)).squeeze()

线性运算w_vec^T x_vec + b称为仿射变换。PyTorch 方便地在torch中提供了一个Linear()类。

nn模块,它做权值和偏差所需的簿记,并做所需的仿射变换。在“深入到有监督的训练”中,您将看到如何从数据中“学习”权重wb的值。

前面示例中使用的激活函数是 sigmoid 函数。在下一节中,我们将回顾一些常见的激活函数,包括 sigmoid 函数。

激活函数

激活函数是神经网络中引入的非线性函数,用于捕获数据中的复杂关系。在“深入到有监督的训练”和“多层感知器”中,我们深入研究了为什么学习中需要非线性,但首先,让我们看看一些常用的激活函数。

Sigmoid

sigmoid 是神经网络历史上最早使用的激活函数之一。它取任何实值并将其压缩在 0 和 1 之间。数学上,sigmoid 的表达式如下:

从表达式中很容易看出,sigmoid 是一个光滑的、可微的函数。Torch 将 sigmoid 实现为Torch.sigmoid(),如示例 3-2 所示。

示例 3-2:Sigmoid 激活

import torch
import matplotlib.pyplot as plt

x = torch.range(-5., 5., 0.1)
y = torch.sigmoid(x)
plt.plot(x.numpy(), y.numpy())
plt.show()

从图中可以看出,sigmoid 函数饱和(即,产生极值输出)非常快,对于大多数输入。这可能成为一个问题,因为它可能导致梯度变为零或发散到溢出的浮点值。这些现象分别被称为消失梯度问题和爆炸梯度问题。因此,在神经网络中,除了在输出端使用 sigmoid 单元外,很少看到其他使用 sigmoid 单元的情况,在输出端,压缩属性允许将输出解释为概率。

Tanh

如例 3-3 所示,tanh 激活函数是 sigmoid 在外观上的不同变体。当你写下 tanh 的表达式时,这就变得很清楚了:

通过一些争论(我们留作练习),您可以确信 tanh 只是 sigmoid 的一个线性变换。当您为tanh()写下 PyTorch 代码并绘制曲线时,这一点也很明显。注意双曲正切,像 sigmoid,也是一个“压缩”函数,除了它映射一个实值集合从(-∞,+∞)(-1,+1)范围。

示例 3-3:Tanh 激活

import torch
import matplotlib.pyplot as plt

x = torch.range(-5., 5., 0.1)
y = torch.tanh(x)
plt.plot(x.numpy(), y.numpy())
plt.show()

ReLU

ReLU(发音为 ray-luh)代表线性整流单元。这可以说是最重要的激活函数。事实上,我们可以大胆地说,如果没有使用 ReLU,许多最近在深度学习方面的创新都是不可能实现的。对于一些如此基础的东西来说,神经网络激活函数的出现也是令人惊讶的。它的形式也出奇的简单: f(x)=max(0,x)因此,ReLU 单元所做的就是将负值裁剪为零,如示例 3-4 所示。

示例 3-4:ReLU 激活

import torch
import matplotlib.pyplot as plt

relu = torch.nn.ReLU()
x = torch.range(-5., 5., 0.1)
y = relu(x)

plt.plot(x.numpy(), y.numpy())
plt.show()

ReLU 的裁剪效果有助于消除梯度问题,随着时间的推移,网络中的某些输出可能会变成零,再也不会恢复。这就是所谓的“ReLU 死亡”问题。为了减轻这种影响,提出了 Leaky ReLU 或 Parametric ReLU (PReLU)等变体,其中泄漏系数a是一个可学习参数: f(x)=max(x,ax)

import torch
import matplotlib.pyplot as plt

prelu = torch.nn.PReLU(num_parameters=1)
x = torch.range(-5., 5., 0.1)
y = prelu(x)

plt.plot(x.numpy(), y.numpy())
plt.show()

Softmax

激活函数的另一个选择是 softmax。与 sigmoid 函数类似,softmax 函数将每个单元的输出压缩为 0 到 1 之间。然而,softmax 操作还将每个输出除以所有输出的和,从而得到一个离散概率分布,除以k个可能的类。结果分布中的概率总和为 1。这对于解释分类任务的输出非常有用,因此这种转换通常与概率训练目标配对,例如分类交叉熵,它在“深入研究监督训练”中介绍

Input[0]
import torch.nn as nn
import torch

softmax = nn.Softmax(dim=1)
x_input = torch.randn(1, 3)
y_output = softmax(x_input)
print(x_input)
print(y_output)
print(torch.sum(y_output, dim=1))
Output[0]
tensor([[ 0.5836, -1.3749, -1.1229]])
tensor([[ 0.7561,  0.1067,  0.1372]])
tensor([ 1.])

在本节中,我们研究了四个重要的激活函数:Sigmoid、Tanh、ReLU 和 softmax。这些只是你在构建神经网络时可能用到的四种激活方式。随着本书的深入,我们将会清楚地看到应该使用哪些激活函数以及在哪里使用,但是一般的指南只是简单地遵循过去的工作原理。

损失函数

在第 1 章中,我们看到了通用的监督机器学习架构,以及损失函数或目标函数如何通过查看数据来帮助指导训练算法选择正确的参数。回想一下,一个损失函数将真相(y)和预测(ŷ)作为输入,产生一个实值的分数。这个分数越高,模型的预测就越差。PyTorch 在它的nn包中实现了许多损失函数,这些函数太过全面,这里就不介绍了,但是我们将介绍一些常用的损失函数。

均方误差损失

回归问题的网络的输出(ŷ)和目标(y)是连续值,一个常用的损失函数的均方误差(MSE)。

MSE 就是预测值与目标值之差的平方的平均值。还有一些其他的损失函数可以用于回归问题,例如平均绝对误差(MAE)和均方根误差(RMSE),但是它们都涉及到计算输出和目标之间的实值距离。示例 3-6 展示了如何使用 PyTorch 实现 MSE 损失。

示例 3-6:MSE 损失

Input[0]
import torch
import torch.nn as nn

mse_loss = nn.MSELoss()
outputs = torch.randn(3, 5, requires_grad=True)
targets = torch.randn(3, 5)
loss = mse_loss(outputs, targets)
print(loss)
Output[0]
tensor(3.8618)

类别交叉熵损失

分类交叉熵损失(categorical cross-entropy loss)通常用于多类分类设置,其中输出被解释为类隶属度概率的预测。目标(y)是n个元素的向量,表示所有类的真正多项分布。如果只有一个类是正确的,那么这个向量就是单热向量。网络的输出(ŷ)也是一个向量n个元素,但代表了网络的多项分布的预测。分类交叉熵将比较这两个向量(y, ŷ)来衡量损失:

交叉熵和它的表达式起源于信息论,但是为了本节的目的,把它看作一种计算两个分布有多不同的方法是有帮助的。我们希望正确的类的概率接近 1,而其他类的概率接近 0。

为了正确地使用 PyTorch 的交叉熵损失,一定程度上理解网络输出、损失函数的计算方法和来自真正表示浮点数的各种计算约束之间的关系是很重要的。具体来说,有四条信息决定了网络输出和损失函数之间微妙的关系。

首先,一个数字的大小是有限制的。其次,如果 softmax 公式中使用的指数函数的输入是负数,则结果是一个指数小的数,如果是正数,则结果是一个指数大的数。接下来,假定网络的输出是应用 softmax 函数之前的向量。最后,对数函数是指数函数的倒数,和log(exp (x))就等于x

因这四个信息,数学简化假设 指数函数和对数函数是为了更稳定的数值计算和避免很小或很大的数字。这些简化的结果是,不使用 softmax 函数的网络输出可以与 PyTorch 的交叉熵损失一起使用,从而优化概率分布。然后,当网络经过训练后,可以使用 softmax 函数创建概率分布,如例 3-7 所示。

示例 3-7:交叉熵损失

Input[0]
import torch
import torch.nn as nn

ce_loss = nn.CrossEntropyLoss()
outputs = torch.randn(3, 5, requires_grad=True)
targets = torch.tensor([1, 0, 3], dtype=torch.int64)
loss = ce_loss(outputs, targets)
print(loss)
Output[0]
tensor(2.7256)

二元交叉熵

我们在上一节看到的分类交叉熵损失函数在我们有多个类的分类问题中非常有用。有时,我们的任务包括区分两个类——也称为二元分类。在这种情况下,利用二元交叉熵损失是有效的。我们将在示例任务的“示例:对餐馆评论的情绪进行分类”中研究这个损失函数。

在示例 3-8 中,我们使用表示网络输出的随机向量上的 sigmoid 激活函数创建二进制概率输出向量。接下来,真实情况被实例化为一个 0 和 1 的向量。最后,利用二元概率向量和基真值向量计算二元交叉熵损失。

示例 3-8:二元交叉熵损失

Input[0]
bce_loss = nn.BCELoss()
sigmoid = nn.Sigmoid()
probabilities = sigmoid(torch.randn(4, 1, requires_grad=True))
targets = torch.tensor([1, 0, 1, 0],  dtype=torch.float32).view(4, 1)
loss = bce_loss(probabilities, targets)
print(probabilities)
print(loss)
Output[0]
tensor([[ 0.1625],
        [ 0.5546],
        [ 0.6596],
        [ 0.4284]])
tensor(0.9003)

深入监督学习

监督学习是学习如何将观察结果映射到特定目标的问题。在这一节中,我们将更详细地讨论。具体地说,我们明确地描述了如何使用模型预测和损失函数对模型参数进行基于梯度的优化。这是一个重要的部分,因为本书的其余部分都依赖于它,所以即使您对监督学习有些熟悉,也值得详细阅读它。

回顾第 1 章,有监督学习需要以下内容:模型、损失函数、训练数据和优化算法。监督学习的训练数据是观察和目标对,模型从观察中计算预测,损失衡量预测相对于目标的误差。训练的目的是利用基于梯度的优化算法来调整模型的参数,使损失尽可能小。

在本节的其余部分中,我们将讨论一个经典的玩具问题:将二维点划分为两个类中的一个。直观上,这意味着学习一条直线(称为决策边界或超平面)来区分类之间的点。我们一步一步地描述数据结构,选择模型,选择一个损失,建立优化算法,最后,一起运行它。

构造玩具数据

在机器学习中,当试图理解一个算法时,创建具有易于理解的属性的合成数据是一种常见的实践。在本节中,我们使用“玩具”任务的合成数据——将二维点分类为两个类中的一个。为了构建数据,我们从 xy 平面的两个不同部分采样点,为模型创建了一个易于学习的环境。示例如图 3-2 所示。模型的目标是将星星()作为一个类,圆圈()作为另一个类。这可以在图的右边看到,线上面的东西和线下面的东西分类不同。生成数据的代码位于本章附带的 Python 笔记本中名为get_toy_data()的函数中。

选择模型

我们在这里使用的模型是在本章开头介绍的:感知器。感知器是灵活的,因为它允许任何大小的输入。在典型的建模情况下,输入大小由任务和数据决定。在这个玩具示例中,输入大小为 2,因为我们显式地将数据构造为二维平面。对于这个两类问题,我们为类指定一个数字索引:0 和 1。字符串的映射标签类指数是任意的,只要它在数据预处理是一致的,训练,评估和测试。该模型的另一个重要属性是其输出的性质。由于感知器的激活函数是一个 sigmoid,感知器的输出为数据点(x)为类 1 的概率;即P(y = 1 | x)

转换概率到具体类

对于二元分类问题,我们可以输出概率转换成两个离散类通过利用决策边界δ。如果预测的概率P(y = 1 | x) > δ,预测类是 1,其它类是 0。通常,这个决策边界被设置为 0.5,但是在实践中,您可能需要优化这个超参数(使用一个评估数据集),以便在分类中获得所需的精度。

选择损失函数

在准备好数据并选择了模型体系结构之后,在有监督的训练中还可以选择另外两个重要组件:损失函数和优化器。在模型输出为概率的情况下,最合适的损失函数是基于熵的交叉损失。对于这个玩具数据示例,由于模型产生二进制结果,我们特别使用 BCE 损失。

选择优化器

在这个简化的监督训练示例中,最后的选择点是优化器。当模型产生预测,损失函数测量预测和目标之间的误差时,优化器使用错误信号更新模型的权重。最简单的形式是,有一个超参数控制优化器的更新行为。这个超参数称为学习率,它控制错误信号对更新权重的影响。学习速率是一个关键的超参数,你应该尝试几种不同的学习速率并进行比较。较大的学习率会对参数产生较大的变化,并会影响收敛性。学习率过低会导致在训练过程中进展甚微。

PyTorch 库为优化器提供了几种选择。随机梯度下降法(SGD)是一种经典的选择算法,但对于复杂的优化问题,SGD 存在收敛性问题,往往导致模型较差。当前首选的替代方案是自适应优化器,例如 Adagrad 或 Adam,它们使用关于更新的信息。在下面的例子中,我们使用 Adam,但是它总是值得查看几个优化器。对于 Adam,默认的学习率是 0.001。对于学习率之类的超参数,总是建议首先使用默认值,除非您从论文中获得了需要特定值的秘诀。

示例 3-9:实例化 Adam 优化器

Input[0]
import torch.nn as nn
import torch.optim as optim

input_dim = 2
lr = 0.001

perceptron = Perceptron(input_dim=input_dim)
bce_loss = nn.BCELoss()
optimizer = optim.Adam(params=perceptron.parameters(), lr=lr)

放到一起:基于梯度的监督学习

学习从计算损失开始;也就是说,模型预测离目标有多远。损失函数的梯度,反过来,是参数应该改变多少的信号。每个参数的梯度表示给定参数的损失值的瞬时变化率。实际上,这意味着您可以知道每个参数对损失函数的贡献有多大。直观上,这是一个斜率,你可以想象每个参数都站在它自己的山上,想要向上或向下移动一步。基于梯度的模型训练所涉及的最简单的形式就是迭代地更新每个参数,并使用与该参数相关的损失函数的梯度。

让我们看看这个梯度步进(gradient-steeping)算法是什么样子的。首先,使用名为zero_grad()的函数清除当前存储在模型(感知器)对象中的所有记帐信息,例如梯度。然后,模型计算给定输入数据(x_data)的输出(y_pred)。接下来,通过比较模型输出(y_pred)和预期目标(y_target)来计算损失。这正是有监督训练信号的有监督部分。PyTorch 损失对象(criteria)具有一个名为bcakward()的函数,该函数迭代地通过计算图向后传播损失,并将其梯度通知每个参数。最后,优化器(opt)用一个名为step()的函数指示参数如何在知道梯度的情况下更新它们的值。

整个训练数据集被划分成多个批(batch)。在文献和本书中,术语小批量也可以互换使用,而不是“批量”来强调每个批量都明显小于训练数据的大小;例如,训练数据可能有数百万个,而小批数据可能只有几百个。梯度步骤的每一次迭代都在一批数据上执行。名为batch_size的超参数指定批次的大小。由于训练数据集是固定的,增加批大小会减少批的数量。在多个批量(通常是有限大小数据集中的批量数量)之后,训练循环完成了一个周期。周期一个完整的训练迭代。如果每个周期的批数量与数据集中的批数量相同,那么周期就是对数据集的完整迭代。模型是为一定数量的周期而训练的。要训练的周期的数量对于选择来说不是复杂的,但是有一些方法可以决定什么时候停止,我们稍后将讨论这些方法。如示例 3-10 所示,受监督的训练循环因此是一个嵌套循环:数据集或批量集合上的内部循环,以及外部循环,后者在固定数量的周期或其他终止条件上重复内部循环。

示例 3-10:感知机和二分类的监督训练循环

# each epoch is a complete pass over the training data
for epoch_i in range(n_epochs):
    # the inner loop is over the batches in the dataset
    for batch_i in range(n_batches):

        # Step 0: Get the data
        x_data, y_target = get_toy_data(batch_size)

        # Step 1: Clear the gradients
        perceptron.zero_grad()

        # Step 2: Compute the forward pass of the model
        y_pred = perceptron(x_data, apply_sigmoid=True)

        # Step 3: Compute the loss value that we wish to optimize
        loss = bce_loss(y_pred, y_target)

        # Step 4: Propagate the loss signal backward
        loss.backward()

        # Step 5: Trigger the optimizer to perform one update
        optimizer.step()

辅助训练概念

基于梯度监督学习的核心概念很简单:定义模型,计算输出,使用损失函数计算梯度,应用优化算法用梯度更新模型参数。然而,在训练过程中有几个重要但辅助的概念。我们将在本节介绍其中的一些。

正确度量模型表现:评估度量

核心监督训练循环之外最重要的部分是使用模型从未训练过的数据来客观衡量性能。模型使用一个或多个评估指标进行评估。在自然语言处理(NLP)中,存在多种评价指标。最常见的,也是我们将在本章使用的,是准确性。准确性仅仅是在训练过程中未见的数据集上预测正确的部分。

正确度量模型表现:分割数据集

一定要记住,最终的目标是很好地概括数据的真实分布。这是什么意思?假设我们能够看到无限数量的数据(“真实/不可见的分布”),那么存在一个全局的数据分布。显然,我们不能那样做。相反,我们用有限的样本作为训练数据。我们观察有限样本中的数据分布这是真实分布的近似或不完全图像。如果一个模型不仅减少了训练数据中样本的误差,而且减少了来自不可见分布的样本的误差,那么这个模型就比另一个模型具有更好的通用性。当模型致力于降低它在训练数据上的损失时,它可以过度适应并适应那些实际上不是真实数据分布一部分的特性。

要实现这一点,标准实践是将数据集分割为三个随机采样的分区,称为训练、验证和测试数据集,或者进行 k 折交叉验证。分成三个分区是两种方法中比较简单的一种,因为它只需要一次计算。您应该采取预防措施,确保在三个分支之间的类分布保持相同。换句话说,通过类标签聚合数据集,然后将每个由类标签分隔的集合随机拆分为训练、验证和测试数据集,这是一种很好的实践。一个常见的分割百分比是预留 70% 用于训练,15% 用于验证,15% 用于测试。不过,这不是一个硬编码的约定。

在某些情况下,可能存在预定义的训练、验证和测试分离;这在用于基准测试任务的数据集中很常见。在这种情况下,重要的是只使用训练数据更新模型参数,在每个周期结束时使用验证数据测量模型性能,在所有的建模选择被探索并需要报告最终结果之后,只使用测试数据一次。这最后一部分是极其重要的,因为更多的机器学习工程师在玩模型的性能测试数据集,他们是偏向选择测试集上表现得更好。当这种情况发生时,它是不可能知道该模型性能上看不见的数据没有收集更多的数据。

使用 k 折交叉验证的模型评估与使用预定义分割的评估非常相似,但是在此之前还有一个额外的步骤,将整个数据集分割为 k 个大小相同的折。其中一折保留用于评估,剩下的k-1折用于训练。通过交换出计算中的哪些折,可以重复执行此操作。因为有 k 折,每一折都有机会成为一个评价折,并产生一个特定于折的精度,从而产生 k 个精度值。最终报告的准确性只是具有标准差的平均值。k 折评估在计算上是昂贵的,但是对于较小的数据集来说是非常必要的,对于较小的数据集来说,错误的分割可能导致过于乐观(因为测试数据太容易了)或过于悲观(因为测试数据太困难了)。

了解什么时候停止训练

之前的例子训练了固定次数的模型。虽然这是最简单的方法,但它是任意的和不必要的。正确度量模型性能的一个关键功能是使用该度量来知道何时应该停止训练。最常用的方法是使用启发式方法,称为早期停止(early stopping)。早期停止通过跟踪验证数据集上从一个周期到另一个周期的性能并注意性能何时不再改进来的工作。然后,如果业绩继续没有改善,训练将终止。在结束训练之前需要等待的时间称为耐心。一般来说,模型停止改进某些数据集的时间点称为模型收敛的时间点。在实际应用中,我们很少等待模型完全收敛,因为收敛是耗时的,而且会导致过拟合。

查找正确的超参数

我们在前面了解到,参数(或权重)采用优化器针对称为小批量的固定训练数据子集调整的实际值。超参数是影响模型中参数数量和参数所取值的任何模型设置。有许多不同的选择来决定如何训练模型。这些选择包括选择一个损失函数;优化器;优化器的学习率,如层大小(在第 4 章中介绍);有等到早停止(early stopping)的耐心;和各种正规化决策(也在第 4 章讨论)。需要注意的是,这些决策会对模型是否收敛及其性能产生很大影响,你应该系统地探索各种选择点。

正则化

深度学习(以及机器学习)中最重要的概念之一是正则化。正则化的概念来源于数值优化理论。回想一下,大多数机器学习算法都在优化损失函数,以找到最可能解释观测结果(即,产生的损失最少)。对于大多数数据集和任务,这个优化问题可能有多个解决方案(可能的模型)。那么我们(或优化器)应该选择哪一个呢?为了形成直观的理解,请考虑图 3-3 通过一组点拟合曲线的任务。

两条曲线都“拟合”这些点,但哪一条是不太可能的解释呢?通过求助于奥卡姆剃刀,我们凭直觉知道一个简单的解释比复杂的解释更好。这种机器学习中的平滑约束称为 L2 正则化。在 PyTorch 中,您可以通过在优化器中设置weight_decay参数来控制这一点。weight_decay值越大,优化器选择的解释就越流畅;也就是说,L2 正则化越强。

除了 L2,另一种流行的正则化是 L1 正则化。L1 通常用来鼓励稀疏解;换句话说,大多数模型参数值都接近于零。在第 4 章中,您将看到一种结构正则化技术,称为“丢弃”。模型正则化是一个活跃的研究领域,PyTorch 是实现自定义正则化的灵活框架。

示例:分类餐馆评论的情感

在上一节中,我们通过一个玩具示例深入研究了有监督的训练,并阐述了许多基本概念。在本节中,我们将重复上述练习,但这次使用的是一个真实的任务和数据集:使用感知器和监督训练对 Yelp 上的餐馆评论进行分类,判断它们是正面的还是负面的。因为这是本书中第一个完整的 NLP 示例,所以我们将极其详细地描述辅助数据结构和训练例程。后面几章中的示例将遵循非常相似的模式,因此我们鼓励您仔细遵循本节,并在需要复习时参考它。

在本书的每个示例的开头,我们将描述正在使用的数据集和任务。在这个例子中,我们使用 Yelp 数据集,它将评论与它们的情感标签(正面或负面)配对。此外,我们还描述了一些数据集操作步骤,这些步骤用于清理数据集并将其划分为训练、验证和测试集。

在理解数据集之后,您将看到定义三个辅助类的模式,这三个类在本书中反复出现,用于将文本数据转换为向量化的形式:词汇表(the Vocabulary)、向量化器(Vectorizer)和 PyTorch 的DataLoader。词汇表协调我们在“观察和目标编码”中讨论的整数到标记(token)映射。我们使用一个词汇表将文本标记(text tokens)映射到整数,并将类标签映射到整数。接下来,向量化器(vectorizer)封装词汇表,并负责接收字符串数据,如审阅文本,并将其转换为将在训练例程中使用的数字向量。我们使用最后一个辅助类,PyTorch 的DataLoader,将单个向量化数据点分组并整理成小批量。

在描述了构成文本向量化小批量管道(text-to-vectorized-minibatch pipeline)的数据集和辅助类之后,概述了感知器分类器及其训练例程。需要注意的重要一点是,本书中的每个示例的训练例程基本保持不变。我们会在这个例子中更详细地讨论它,因此,我们再次鼓励您使用这个例子作为未来训练例程的参考。我们通过讨论结果来总结这个例子,并深入了解模型学习到了什么。

Yelp 评论数据集

2015 年,Yelp 举办了一场竞赛,要求参与者根据点评预测一家餐厅的评级。同年,Zhang, Zhao,和 Lecun(2015)将 1 星和 2 星评级转换为“消极”情绪类,将 3 星和 4 星评级转换为“积极”情绪类,从而简化了数据集。该数据集分为 56 万个训练样本和 3.8 万个测试样本。在这个数据集部分的其余部分中,我们将描述最小化清理数据并导出最终数据集的过程。然后,我们概述了利用 PyTorch 的数据集类的实现。

在这个例子中,我们使用了简化的 Yelp 数据集,但是有两个细微的区别。第一个区别是我们使用数据集的“轻量级”版本,它是通过选择 10% 的训练样本作为完整数据集而派生出来的。这有两个结果:首先,使用一个小数据集可以使训练测试循环快速,因此我们可以快速地进行实验。其次,它生成的模型精度低于使用所有数据。这种低精度通常不是主要问题,因为您可以使用从较小数据集子集中获得的知识对整个数据集进行重新训练。在训练深度学习模型时,这是一个非常有用的技巧,因为在许多情况下,训练数据的数量是巨大的。

从这个较小的子集中,我们将数据集分成三个分区:一个用于训练,一个用于验证,一个用于测试。虽然原始数据集只有两个部分,但是有一个验证集是很重要的。在机器学习中,您经常在数据集的训练部分上训练模型,并且需要一个保留部分来评估模型的性能。如果模型决策基于保留部分,那么模型现在不可避免地偏向于更好地执行保留部分。因为度量增量进度是至关重要的,所以这个问题的解决方案是使用第三个部分,它尽可能少地用于评估。

综上所述,您应该使用数据集的训练部分来派生模型参数,使用数据集的验证部分在超参数之间进行选择(进行建模决策),使用数据集的测试分区进行最终评估和报告。在例 3-11 中,我们展示了如何分割数据集。注意,随机种子被设置为一个静态数字,我们首先通过类标签聚合以确保类分布保持不变。

示例 3-11:创建训练,验证和测试分割

# Splitting the subset by rating to create new train, val, and test splits
by_rating = collections.defaultdict(list)
for _, row in review_subset.iterrows():
    by_rating[row.rating].append(row.to_dict())

# Create split data
final_list = []
np.random.seed(args.seed)

for _, item_list in sorted(by_rating.items()):
    np.random.shuffle(item_list)

    n_total = len(item_list)
    n_train = int(args.train_proportion * n_total)
    n_val = int(args.val_proportion * n_total)
    n_test = int(args.test_proportion * n_total)

    # Give data point a split attribute
    for item in item_list[:n_train]:
        item['split'] = 'train'

    for item in item_list[n_train:n_train+n_val]:
        item['split'] = 'val'

    for item in item_list[n_train+n_val:n_train+n_val+n_test]:
        item['split'] = 'test'

    # Add to final list
    final_list.extend(item_list)

final_reviews = pd.DataFrame(final_list)

除了创建一个子集,该子集有三个分区用于训练、验证和测试之外,我们还通过在标点符号周围添加空格和删除并非所有分割都使用标点符号的无关符号来最低限度地清理数据,如示例 3-12 所示。

示例 3-12:最小程度清理数据

def preprocess_text(text):
    text = text.lower()
    text = re.sub(r"([.,!?])", r" \\1 ", text)
    text = re.sub(r"[^a-zA-Z.,!?]+", r" ", text)
    return text

final_reviews.review = final_reviews.review.apply(preprocess_text)

理解 PyTorch 的数据集表示

示例 3-13 中给出的Review

以上是关于NLP神经网络基础组件的主要内容,如果未能解决你的问题,请参考以下文章

NLP注意力机制在神经网络中的应用

NLP循环神经网络

理解NLP中的卷积神经网络(CNN)

NLP循环神经网络基础

深度神经网络在NLP的应用!

NLP教程