NLP是指一组技术,涉及统计方法的应用,无论是否有语言学的见解,都是为了解决现实世界的任务而理解文本。对文本的这种“理解”主要是通过将文本转换为可用的计算而得到的表示,是离散的或连续的组合结构,如矢量或张量,图形和树。
从数据(在这种情况下是文本)中学习适合于任务的表示是主题 机器学习。机器学习在文本数据中的应用已有三十多年的历史,但最近从2008年到2010年一套机器学习技术,被称为深度学习,不断发展,并证明对NLP,语音和计算机视觉中的各种人工智能(AI)任务非常有效。深度学习是我们涵盖的另一个主要课题; 因此,本书是对NLP和深度学习的研究。
简单地说, 深度学习使人们能够使用数据的抽象来有效地从数据中学习表示 计算图和数值优化技术。这就是深度学习和计算图表的成功,谷歌,Facebook和亚马逊等主要科技公司已经发布了基于它们的计算图框架和库的实现,以捕捉研究人员和工程师的思想共享。在本书中,我们考虑PyTorch,一种越来越流行的基于Python的计算图框架库,用于实现深度学习算法。在本章中,我们将解释计算图是什么以及我们选择使用PyTorch作为框架。机器学习和深度学习领域是巨大的。在本章中,对于本书的大部分内容,我们主要涉及的内容是监督学习 ; 也就是说,用标记的训练样例学习。我们解释了监督学习范式,它将成为本书的基础。如果您到目前为止还不熟悉这些术语,那么您来对地方了。本章以及未来的章节不仅阐明了这一点,而且深入研究了这些章节。如果您已经熟悉这里提到的一些术语和概念,我们仍然鼓励您遵循以下两个原因:为本书的其余部分建立共享词汇表,并填补理解未来章节所需的任何空白。
本章的目标是:
- 清楚地理解监督学习范式,理解术语,并制定概念框架,以便为将来的章节处理学习任务
- 了解如何为学习任务编码输入
- 了解计算图是什么
- 掌握PyTorch的基础知识
让我们开始吧!
监督学习范式
监理在机器学习,或简单的监督学习,指的是当事实真相的情况 目标(正在预测的是)可用于观察。例如,在文档分类中,目标是分类标签,和观察是一份文件。例如,在机器翻译中,观察是一种语言的句子,而目标是另一种语言的句子。通过对输入数据的这种理解,我们在[图1-1中]说明了监督学习范例。
我们可以将监督学习范式分解为六个主要概念,如图1-1所示:
- 观察(Observations) 观察是我们想要预测某些事物的项目。我们用x表示观察。*我们有时将观察结果称为“输入”。
- 目标(Targets) 目标是与观察相对应的标签。通常是预测的事情。遵循机器学习/深度学习中的标准符号,我们使用y来指代这些。有时,这被称为基本事实。
- 模型(Model) 一个模型是一个数学表达式或函数,它接受观察x,并预测其目标标签的值。参数(Parameters) 有时也称为权重,这些参数化模型。使用它是标准的符号w(用于权重)或ŵ。
- 预测(Predictions), 也称为估计,是模型猜测的目标值,给出观察结果。我们用“帽子”形状的符号表示法表示这些。因此,目标y的预测表示为ŷ。
- 损失函数(Loss function) 是一种函数,用于比较预测与训练数据中的观察目标的距离。给定目标及其预测,损失函数分配称为损失的标量实数值。损失值越低,模型在预测目标方面越好。我们用L来表示损失函数。
虽然在NLP /深度学习建模或编写本书时,在数学上有正规表述,但我们还是重新叙述监督学习范例,以便为那些对该领域不熟悉的读者提供标准术语,以便他们拥有熟悉arXiv研究论文中的符号和写作风格。
考虑一个数据集 D={Xi,yi}i=1n有n个例子。给定该数据集,我们想要学习由权重 w 参数化的函数(模型)f。也就是说,我们对f的结构做出假设,并且给定该结构,权重w的学习值将完全表征模型。对于给定的输入X,模型预测ŷ为目标:
y^=f(X; W)
在监督学习中,对于训练样例,我们已知观察的真实目标y。此实例的损失将为L(y,ŷ)。然后,监督学习就是找到最佳参数/权重w的过程,将使所有n个示例的累积损失最小化。
例子
使用(随机)梯度下降训练
监督学习的目标是选择最小化给定数据集的损失函数的参数值。换句话说,这相当于在等式中找到根。我们知道梯度下降是找到方程根的常用技术。回想一下,在传统的梯度下降中,我们猜测根(参数)的一些初始值并迭代地更新参数,直到目标函数(损失函数)评估为低于可接受阈值的值(aka收敛标准)。对于大型数据集,由于存储器限制,通常不可能在整个数据集上实现传统的梯度下降,并且由于计算开销而非常慢。相反,一个通常采用称为随机梯度下降(SGD)的梯度下降近似。在随机情况下,随机选取数据点或数据点子集,并为该子集计算梯度。当使用单个数据点时,该方法称为纯SGD,当使用(多个)数据点的子集时,我们将其称为minibatch SGD。通常是单词“pure”和“minibatch”在根据上下文变得清晰时被删除。在实践中,很少使用纯SGD,因为它会因噪声更新而导致收敛非常慢。一般SGD算法有不同的变体,都旨在加快收敛速度。在后面的章节中,我们将探讨其中一些变体,以及如何在更新参数时使用渐变。调用迭代更新参数的过程反向传播。反向传播的每个步骤(也称为纪元)由a组成向前 传球和向后 传球。正向传递使用参数的当前值评估输入并计算损失函数。向后传递使用损失的梯度更新参数。
观察到目前为止,这里没有任何内容专门针对深度学习或神经网络。[图1-1中]箭头的方向表示训练系统时数据的“流量”。我们对“计算图”中的“流程”概念有更多的说法,但首先,我们来看看我们如何用数字方式表示我们在NLP问题中的输入和目标,以便我们可以训练模型并预测结果。
观察和目标编码
我们 将 需要以数字方式表示观察(文本)以与机器学习算法结合使用。图1-2显示了视觉描绘。
图1-2。观察和目标编码:注意图1-1)中的目标和观察如何用数字表示为矢量或张量。这统称为输入“编码”
一个表示文本的简单方法是作为数字向量。有无数种方法可以执行此映射/表示。实际上,本书的大部分内容都致力于从数据中学习这样的任务表示。但是,我们从一些基于启发式的基于计数的简单表示开始。虽然简单,但它们本身就非常强大,或者作为更丰富的表征学习的起点。所有这些基于计数的表示都以固定维度的向量开始。
独热表示one-hot representation
独热表示, 正如其名,由一个零向量,其中1表示这个单词在文章的这个位置出现,如下面两句话
Time flies like an arrow.
Fruit flies like a banana.
忽略标点符号,忽略大小写,我们得到一个字典{time,fruit,flies,like,a,an,arrow,banana},字典大小是8,因此我们使用个8维向量来表示,例如“like a banana” 表示为: [0, 0, 0, 1, 1, 0, 0, 1].
Figure 1-3. “Time flies like an arrow” 和 “Fruit flies like a banana.”独热编码
虽然我们很少会在本书的输入中使用除了独热表示之外的任何内容,但我们现在引入术语 - Term-Frequency(TF)和术语 - Term-Frequency-Inverse-Document-Frequency(TF-IDF)表示其在NLP,由于历史原因,并且为了完成。这些表示在信息检索(IR)中具有悠久的历史,并且甚至在生产NLP系统中也被积极地使用。
TF表示
TF 短语、句子或文档的表示仅仅是组成单词的一个频率表示的总和。继续我们简单的例子,使用前面提到的独热编码,句子“Fruit flies like time flies a fruit”有以下TF表示:[1, 2, 2, 1, 1, 0, 0, 0]
。请注意,每个条目都是相应单词出现次数的计数在句子(语料库)。让我们用TF(w)表示w出现的频率。
from sklearn.feature_extraction.text import CountVectorizer
import seaborn as sns
corpus = ['Time flies flies like an arrow.',
'Fruit flies like a banana.']
one_hot_vectorizer = CountVectorizer(binary=True)
one_hot = one_hot_vectorizer.fit_transform(corpus).toarray()
sns.heatmap(one_hot, annot=True,
cbar=False, xticklabels=vocab,
yticklabels=['Sentence 1', 'Sentence 2'])
TF
TF-IDF
在专利文档的集合中,最常见的单词是 claim, system, method, procedure等,这些单词的重复次数很多,如果使用TF表示,这些单词的频率最高。然而这些单词对于理解一个具体的专利没有太大作用,反而如“tetrafluoroethylene”这些频率特别低的单词更具有代表性,能反应专利。为了给这些单词更高的权值我们引入TF-IDF。公式为:
IDF(w)=log (N/nw)
其中nw是文档中单词word的数量,N是文档个数
TF-IDF = TF(w) * IDF(w).
首先,如果单词在所有文档中经常出现,则nw等于N,IDF等于0,TF-IDF为0
其次,如果单词出现次数比较少,IDF的值将比较大,相应TF-IDF也比较大。
from sklearn.feature_extraction.text import TfidfVectorizer
import seaborn as sns
tfidf_vectorizer = TfidfVectorizer()
tfidf = tfidf_vectorizer.fit_transform(corpus).toarray()
sns.heatmap(tfidf, annot=True, cbar=False, xticklabels=vocab,
yticklabels= ['Sentence 1', 'Sentence 2'])
TF-IDF
在深度学习中,很少直接使用TF-IDF。因为我们的目标是学习一个表示,但是有时却使用独热编码
目标编码
如在“监督学习范例”中指出,目标变量如何编码取决于正在解决的NLP任务。例如,在机器翻译,摘要和问题回答的情况下,目标也是文本并且使用诸如先前描述的独热编码的方法来编码。
许多NLP任务实际上使用分类标签,其中模型必须预测一组固定标签。对此进行编码的常用方法是对每个标签使用唯一索引。当输出标签的数量太大时,这种简单的表示可能会成为问题。一个例子是语言建模问题,其中任务是根据过去看到的单词预测下一个单词。标签空间是一种语言的整个词汇表,可以轻松增长到几十万种,包括特殊字符,名称等。我们将在后面的章节中重新讨论这个问题以及如何解决这个问题。
一些NLP问题涉及从给定文本预测数值。例如,英语论文,我们可能需要指定数字成绩或可读性分数。餐厅评论,我们可能需要预测直到第一个小数的数字星级。根据用户的推文,我们可能需要预测用户的年龄组。存在几种编码数字目标的方法,但是将目标简单地分类为分类“箱” - 例如,“ 0-18
”,“ 19-25
”,“ 25-30
”等等 - 并将其视为一个序数分类问题是一种合理的方法。分箱可以是均匀的或非均匀的,也可以是数据驱动的。虽然对此书的详细讨论超出了本书的范围,但我们会提请您注意这些问题,因为在这种情况下,目标编码会显着影响性能,我们建议您看看Dougherty等人(1995)及其中的参考文献。
图1-1总结监督学习范例作为数据流结构,其中输入由模型(数学表达式)转换以获得预测,并且损失函数(另一表达式)提供反馈信号以调整模型的参数。可以使用计算图数据结构方便地实现该数据流。从技术上讲,计算图是一种模拟数学表达式的抽象。在深度学习的背景下,计算图的实现(例如Theano,TensorFlow和PyTorch)进行额外的簿记以实现在监督学习范例的训练期间获得参数梯度所需的自动区分。我们在“PyTorch Basics”中进一步探讨了这一点。推断(或预测)仅仅是表达式评估(计算图上的前向流)。让我们看看计算图如何模拟表达式。考虑表达式:
ÿ=w ^X+b
这可以写成两个子表达式,z = wx和y = z + b。然后,我们可以使用有向无环图(DAG)表示原始表达式,其中节点是乘法和加法等数学运算。操作的输入是节点的输入边缘,操作的输出是输出边缘。因此,对于表达式y = wx + b,计算图如图1-6所示。在下一节中,我们将看到PyTorch如何允许我们以简单的方式创建计算图形,
PyTorch基础知识
在本书中,我们广泛使用PyTorch来实现我们的深度学习模型。PyTorch是一个开源的,社区驱动的深度学习框架。与Theano,Caffe和TensorFlow不同,PyTorch实现了一种“基于自动微分”方法,允许我们动态定义和执行计算图。这对于调试以及最快构建复杂模型非常有用。
张量表示多维数字
在以下部分中,我们将使用PyTorch学习以下内容:
- 创建张量
- 张量操作
- 索引,切片和与张量连接
- 用张量计算梯度
- 使用带GPU的CUDA张量
在本节的其余部分中,我们将使用PyTorch的首先来熟悉各种PyTorch操作。我们建议您在此时安装Python 3.5+jupyter,并安装PyTorch,并按照本节中的示例进行操作。我们还建议您在本节后面的练习中完成练习。
安装PyTorch
第一步是在您的机器上安装PyTorch,方法是选择系统首选项pytorch.org
。选择您的操作系统,然后选择包管理器(我们建议conda
/ pip
),然后选择您正在使用的Python版本(我们建议使用3.5+)。这将生成执行以安装PyTorch的命令。在撰写本文时,例如,conda环境的install命令如下:
conda install pytorch torchvision -c pytorch
创建张量
首先,我们定义辅助函数,它将汇总张量的各种属性,例如张量的类型,张量的维数和张量的内容:describe(*x*)``*x*
def describe (x):
print(“Type:{}”。 format(x.type()))
print(“Shape / size:{}”。 format(x.shape))
print(“Values:\ n { } .format(x))
PyTorch允许我们使用多种不同的方式创建张量 该torch
软件包。创建张量的一种方法是通过指定其尺寸来初始化随机张量,如例1-3所示。
import torch
describe(torch.Tensor(2, 3))
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[0.0243, 0.0000, 0.9586],
[0.0000, 0.0000, 0.0000]])
我们还可以通过使用interval [ 0, 1
)或者中的均匀分布的值随机初始化来创建张量标准正态分布(随机初始化张量,从均匀分布说,是重要的,因为你将看到在第3章以及第4章,如在示出的实施例1-4。
import torch
describe(torch.rand(2, 3)) # uniform random
describe(torch.randn(2, 3)) # random normal
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0.0242, 0.6630, 0.9787],
[ 0.1037, 0.3920, 0.6084]])
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[-0.1330, -2.9222, -1.3649],
[ 2.3648, 1.1561, 1.5042]])
我们还可以创建一个填充了相同标量的张量。为了创建一个零或一个的张量,我们有内置函数,并且为了填充特定值,我们可以使用的fill_()
方法。任何PyTorch方法用 下划线(_)指的是 就地操作; 也就是说,它在不创建新对象的情况下修改了内容,如例1-5所示。
import torch
describe(torch.zeros(2, 3))
x = torch.ones(2, 3)
describe(x)
x.fill_(5)
describe(x)
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 0., 0., 0.],
[ 0., 0., 0.]])
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 1., 1., 1.],
[ 1., 1., 1.]])
Type: torch.FloatTensor
Shape/size: torch.Size([2, 3])
Values:
tensor([[ 5., 5., 5.],
[ 5., 5., 5.]])
从列表创建和初始化张量
x = torch.Tensor([[1, 2, 3],
[4, 5, 6]])
describe(x)
值可以来自列表,如前面的示例,也可以来自NumPy数组。当然,我们也可以从PyTorch张量到NumPy阵列。请注意,张量的类型是一个DoubleTensor
而不是默认值FloatTensor
。这对应于NumPy随机矩阵a的数据类型float64
,如例1-7所示。
import torch
import numpy as np
npy = np.random.rand(2, 3)
describe(torch.from_numpy(npy))
张量类型和尺寸
每个张量都有相关的类型和大小。默认值使用torch.Tensor
构造函数时的张量类型torch.FloatTensor
。然而,可以将张量转换成不同的类型(float
,long
,double
通过在初始化任一指定或稍后使用的类型转换方法的一个,等等)。有两种方法可以指定初始化类型,方法是直接调用特定张量类型的构造函数,例如FloatTensor
和LongTensor
,或使用特殊方法torch.tensor
,并提供dtype
,如例1-8所示。
x = torch.FloatTensor([[1, 2, 3],
[4, 5, 6]])
describe(x)
x = x.long()
describe(x)
我们使用张量对象的形状属性和大小方法来访问其尺寸的测量值。访问这些测量的两种方式大多是同义词。检查张量的形状成为调试PyTorch代码不可或缺的工具。
张量操作
已经创建了的张量,你可以像使用传统的编程语言类型一样操作它们,比如“+”,“ - ”,“*”,“/”。我们也可以使用一些符号例如.add()
,如例1-9所示的函数,对应于符号运算符。
import torch
x = torch.randn(2, 3)
describe(x)
describe(torch.add(x, x))
describe(x + x)
您还可以将操作应用于张量的特定维度。您可能已经注意到,对于2D张量,我们将行表示为维度0
,将列表示为维度1
,如示例1-10中所示。
import torch
x = torch.arange(6)
describe(x)
x = x.view(2, 3)
describe(x)
describe(torch.sum(x, dim=0))
describe(torch.sum(x, dim=1))
describe(torch.transpose(x, 0, 1))
通常,我们需要执行更复杂的操作,这些操作涉及索引,切片,连接和突变的组合。与NumPy和其他数字库一样,PyTorch具有内置函数,可以使这种张量操作变得非常简单。
索引,切片和连接
如果如果您是NumPy用户,PyTorch的索引和切片方案如例1-11可能对您非常熟悉。
import torch
x = torch.arange(6).view(2, 3)
describe(x)
describe(x[:1, :2])
describe(x[0, 1])
例1-12演示了PyTorch还具有复杂索引和切片操作的功能,您可能有兴趣有效地访问张量的非连续位置。
indices = torch.LongTensor([0, 2])
describe(torch.index_select(x, dim=1, index=indices))
row_indices = torch.arange(2).long()
col_indices = torch.LongTensor([0, 1])
describe(x[row_indices, col_indices])
连接
describe(torch.cat([x, x], dim=0))
describe(torch.cat([x, x], dim=1))
PyTorch还在张量上实现了高效的线性代数运算,例如乘法,逆和迹,您可以在例1-14中看到。
import torch
x1 = torch.arange(6).view(2, 3)
describe(x1)
x2 = torch.ones(3, 2)
x2[:, 1] += 1
describe(x2)
到目前为止,我们已经研究了创建和操作常量PyTorch张量对象的方法。正如编程语言(例如Python)具有封装一段数据的变量并且具有关于该数据的附加信息(例如,存储它的存储器地址),PyTorch张量器处理构建计算图形所需的梯度。机器学习只需在实例化时启用布尔标志即可。
张量和计算图
PyTorch张量class封装数据(张量本身)和一系列操作,例如代数运算,索引和整形操作。但是,如例1-15所示,当requires_grad
布尔标志设置为True
张量时,启用梯度操作,可以跟踪张量的梯度以及梯度函数,这两者都是促进梯度所需的 - “监督学习范式”中讨论的基础学习
import torch
x = torch.ones(2, 2, requires_grad=True)
describe(x)
print(x.grad is None)
当您创建张量时requires_grad=True
,您需要PyTorch来管理计算渐变的梯度信息。首先,PyTorch将跟踪前向传递的值。然后,在计算结束时,使用单个标量来计算后向传递。通过使用,启动向后传递backward()
由损失函数的评估产生的张量方法。向后传递计算参与正向传递的张量对象的梯度值。
通常,梯度是表示函数输出相对于函数输入的斜率的值。在计算图设置中,模型中的每个参数都存在梯度,可以将其视为参数对误差信号的贡献。在PyTorch中,您可以使用.``grad
成员变量访问计算图中节点的渐变。优化器使用该.``grad
变量来更新参数的值。
到目前为止,我们一直在CPU内存上分配我们的张量。在进行线性代数运算时,如果你有GPU,那么使用GPU可能是有意义的。要利用GPU,您需要首先在GPU的内存上分配张量。通过专用API访问GPU叫做CUDA。CUDA API由NVIDIA创建,仅限于在NVIDIA GPU上使用。PyTorch提供的CUDA张量对象在使用时与常规的CPU绑定张量无法区分,除了它在内部分配的方式。
CUDA Tensors
PyTorch制作创建这些CUDA张量非常容易(示例1-16),它将张量从CPU传输到GPU,同时保持其基础类型。PyTorch中的首选方法是设备不可知并编写无论是在GPU还是CPU上都能正常工作的代码。在下面的代码片段中,我们首先使用检查GPU是否可用torch.cuda.is_available()
,然后检索设备名称torch.device
。然后,通过使用该.to(device)
方法实例化所有未来的Tensors并将其移动到目标设备。
import torch
print (torch.cuda.is_available())
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print (device)
x = torch.rand(3, 3).to(device)
describe(x)
要对CUDA和非CUDA对象进行操作,我们需要确保它们位于同一设备上。如果我们不这样做,计算将会中断,如下面的代码片段所示。例如,当计算不属于计算图的监视度量时,会出现这种情况。在两个张量对象上操作时,请确保它们都在同一设备上。例1-17演示了。
请记住,从GPU来回移动数据是很昂贵的。因此,典型的过程涉及在GPU上进行许多可并行化的计算,然后将最终结果传回CPU。这将允许您充分利用GPU。如果您有多个CUDA可见设备(即多个GPU),最佳做法是CUDA_VISIBLE_DEVICES在执行程序时使用环境变量,如下所示:
CUDA_VISIBLE_DEVICES = 0,1,2,3 python main.py
Exercises
摘要
在本章中,我们介绍了本书的目标 - 自然语言处理(NLP)和深度学习 - 并对监督学习范式进行了详细的理解。在本章的最后,您现在应该熟悉或至少知道各种术语,例如观察,目标,模型,参数,预测,损失函数,表示,学习/训练和推理。您还了解了如何使用独热编码对学习任务的输入(观察和目标)进行编码。我们还检查了基于计数的表示,如TF和TF-IDF。我们通过首先了解计算图是什么,静态与动态计算图以及PyTorch张量操作操作之旅来开始我们的PyTorch之旅。在第2章,我们提供传统NLP的概述。第2章和本章应该为您奠定必要的基础,如果您对本书的主题不熟悉并为本书的其余部分做好准备。
网友评论