基础工具集与常用工具集
本篇首先介绍两种常用的自然语言处理基础工具集,即英文处理工具集NLTK和中文工具集LTP。其次,介绍本书所使用的深度学习框架(PyTorch)。最后,介绍常用的大规模预训练数据集以及更多自然语言处理数据集的获取方法。通过本章的学习,读者将对基础自然语言处理技术、深度学习工具以及大规模数据集有一个更直观的感受,并为后续章节的学习做好准备。
NLTK工具集
NLTK(Natural Language Toolkit)是一个Python模块块,提供了多种语料库(Corpora)和词典(Lexicon)资源,如WordNet等,以及一系列基本的自然语言处理工具集,包括:分句、标记解析(Tokenization)、词干提取(Stemming)、词性标注(POS Tagging)和句法分析(Syntactic Parsing)等,是对英文文本数据进行处理的常用工具。
为了使用NLTK,需要对其进行安装,可以直接使用pip
包管理工具安装,具
体方法为,首先进入操作系统的控制台,然后执行以下命令。
接下来简要介绍NLTK提供的常用语料库、词典资源及自然语言处理工具。
常用语料库和词典资源
为了使用NLTK提供的语料库和词典资源,首先需要进行下载。具体方法为, 进入Python的控制台(在操作系统控制台下,执行python命令),然后执行以下两行命令。
1
2
|
import nltk
nltk.download()
|
此时会弹出一个对话框,允许用户选择所需下载的数据资源,可以简单地选
择“All”,然后单击“Download”。同时,还可以选择数据存储的目录。
停用词
在进行自然语言处理时,有一些词对于表达语言的含义并不重要,如英文中 的冠词“a”“the”,介词“of”“to”等。因此,在对语言进行更深入的处理之前,可以将它们删除,从而加快处理的速度,减小模型的规模。这些词又被称为停用词(Stop words)。NLTK提供了多种语言的停用词词表,可以通过下面语句引入停用词词表。
1
|
from nltk.corpus import stopwords
|
然后,使用下面的语句查看一种语言的停用词词表(如英文)。
1
|
print(stopwords.words('english'))
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
['i', 'me', 'my', 'myself', 'we', 'our', 'ours', 'ourselves', 'you', "you're",
"you've", "you'll", "you'd", 'your', 'yours', 'yourself','yourselves', 'he',
'him', 'his', 'himself', 'she', "she's", 'her', 'hers', 'herself', 'it', "it's",
'its', 'itself', 'they', 'them', 'their', 'theirs', 'themselves', 'what',
'which', 'who', 'whom', 'this', 'that', "that'll", 'these', 'those', 'am', 'is',
'are', 'was', 'were', 'be', 'been', 'being', 'have', 'has', 'had', 'having',
'do', 'does', 'did', 'doing', 'a', 'an', 'the', 'and', 'but', 'if', 'or',
'because', 'as', 'until', 'while', 'of', 'at', 'by', 'for', 'with', 'about',
'against', 'between', 'into', 'through', 'during', 'before', 'after', 'above',
'below', 'to', 'from', 'up', 'down', 'in', 'out', 'on', 'off', 'over', 'under',
'again', 'further', 'then', 'once', 'here', 'there', 'when', 'where', 'why',
'how', 'all', 'any', 'both', 'each', 'few', 'more', 'most', 'other', 'some',
'such', 'no', 'nor', 'not', 'only', 'own', 'same', 'so', 'than', 'too', 'very',
's', 't', 'can', 'will', 'just', 'don', "don't", 'should', "should've", 'now',
'd', 'll', 'm', 'o', 're', 've', 'y', 'ain', 'aren', "aren't", 'couldn',
"couldn't", 'didn', "didn't", 'doesn', "doesn't", 'hadn', "hadn't", 'hasn',
"hasn't", 'haven', "haven't", 'isn', "isn't", 'ma', 'mightn', "mightn't",
'mustn', "mustn't", 'needn', "needn't", 'shan', "shan't", 'shouldn',
"shouldn't", 'wasn', "wasn't", 'weren', "weren't", 'won', "won't", 'wouldn',
"wouldn't"]
|
常用语料库
NLTK提供了多种语料库(文本数据集),如图书、电影评论和聊天记录等, 它们可以被分为两类,即未标注语料库(又称生语料库或生文本,Raw text)和人工标注语料库(Annotated corpus)。下面就其中的典型语料库加以简要介绍,关于全部语料库的详细信息,可以通过NLTK的网站了解。
(1)未标注语料库。可以使用两种方式访问之前下载的语料库,第一种是直接访问语料库的原始文本文件(目录为下载数据时选择的存储目录);另一种是调用NLTK提供的相应功能。例如,通过以下方式,可以获得古腾堡(Gutenberg)语料库(目录为:nltk_data/corpora/gutenberg )中简•奥斯汀(Jane Austen)所著的小说Emma原文。
1
2
|
from nltk.corpus import gutenberg
print(gutenberg.raw("austen-emma.txt"))
|
(2)人工标注语料库。人工标注的关于某项任务的结果。如在句子极性语料库(sentence_polarity
)中,包含了10662条来自电影领域的用户评论句子以及相应的极性信息(褒义或贬义)。通过以下命令,可以获得该语料库,其中,褒贬各5331句(经过了小写转换、简单的标记解析等预处理后)。
1
|
from nltk.corpus import sentence_polarity
|
sentence_polarity
提供了基本的数据访问方法,如sentence_polarity.categories()
返回褒贬类别列表,即['neg', 'pos']
; sentence_polarity.words()
返回语料库中全部单词的列表,如果调用时提供类别参数(categories ="pos"或"neg"
),则会返回相应类别的全部单词列表;sentence_polarity.sents()
返回语料库中全部句子的列表,调用时同样可以提供类别参数。可以使用以上方法的组合,构造出一个大列表,其中每个元素为一个句子的单词列表及其对应的褒贬类别构成的元组。
1
2
3
|
[(sentence, category)
for category in sentence_polarity.categories()
for sentence in sentence.polarity.sents(categories=category)]
|
常用词典
(1)WordNet。WordNet是普林斯顿大学构建的英文语义词典(也称作辞典,Thesaurus),其主要特色是定义了同义词集合(Synset),每个同义词集合由具 有相同意义的词义组成。此外,WordNet为每一个同义词集合提供了简短的释义 (Gloss),同时,不同同义词集合之间还具有一定的语义关系。下面演示WordNet的简单使用示例。
1
2
3
|
from nltk.corpus import wordnet
syns = wordnet.synsets("bank") # 返回“bank”的全部18个词义的synset
print(syns[0].name()) #返回“bank”第1个词义的名称,其中“n”表示名词(Noun)
|
1
|
print(syns[0].definition) #返回“bank”第一个词词义的定义,即“河岸”的定义
|
1
|
sloping land (especially the slope beside a body of water)
|
1
|
print(syns[1].definition()) #返回“bank”第2个词义的定义,即“银行”的定义
|
1
2
|
a financial institution that accepts deposits and channels the money into lending
activities
|
1
|
print(syns[0].examples()) #返回“bank”第1个词义的使用示例
|
1
2
|
['they pulled the canoe up on the bank', 'he sat on the bank of the river and
watched the currents']
|
1
|
print(syns[0].hypernyms()) #返回“bank”第1个词义的上位同义词集合
|
1
2
3
|
dog = wordnet.synset ('dog.n.01')
cat = wordnet.synset ('cat.n.01')
print(dog.wup_similarity(cat)) #计算两个同义词集合之间的Wu-Palmer相似度
|
(2)SentiWordNet。SentiWordNet (Sentiment WordNet)是基于WordNet标注的词语(更准确地说是同义词集合)情感倾向性词典,它为WordNet中每个同义词集合人工标注了三个情感值,依次是褒义、贬义和中性。通过该词典,可以实现一个简单的情感分析系统。仍然通过一个例子演示SentiWordNet的使用方法。
1
2
3
|
from nltk.corpus import sentiwordnet
print(sentiwordnet.senti_synset('good.a.01'))
#词good在形容词(Adjective)下的第1号语义
|
1
|
<good.a.01: PosScore=0.75 NegScore=0.0>
|
常用自然语言处理工具集
NLTK提供了多种常用的自然语言处理基础工具,如分句、标记解析和词性
标注等,下面简要介绍这些工具的使用方法。
分句
通常一个句子能够表达完整的语义信息,因此在进行更深入的自然语言处理之前,往往需要将较长的文档切分成若干句子,这一过程被称为分句。一般来讲,一个句子结尾具有明显的标志,如句号、问号和感叹号等,因此可以使用简单的规则进行分句。然而,往往存在大量的例外情况,如在英文中,句号除了可以作为句尾标志,还可以作为单词的一部分(如“Mr.”)。NLTK提供的分句功能可以较好地解决此问题。下面演示如何使用该功能。
1
2
3
4
5
|
from nltk.corpus import gutenberg
from nltk.tokenize import sent_tokenize
text = gutenberg.raw("austen-emma.txt")
sentences = sent_tokenize(text) # 对Emma小说全文进行分句
print(sentences[100]) # 显示其中一个句子
|
1
|
Mr. Knightley loves to find fault with me, you know--in a joke--it is all a joke.
|
标记解析
一个句子是由若干标记(Token)按顺序构成的,其中标记既可以是一个词,
也可以是标点符号等,这些标记是自然语言处理最基本的输入单元。将句子分割为标记的过程叫作标记解析(Tokenization)。英文中的单词之间通常使用空格进行分割,不过标点符号通常和前面的单词连在一起,因此标记解析的一项主要工 作是将标点符号和前面的单词进行拆分。和分句一样,也无法使用简单的规则进 行标记解析,仍以符号为例,它既可作为句号,也可以作为标记的一部分,如不能简单地将“Mr.”分成两个标记。同样,NLTK提供了标记解析功能,也称作标记解析器(Tokenizer)。下面演示如何使用该功能。
1
2
|
from nltk.tokenize import word_tokenize
word_tokenize(sentences[100])
|
1
2
|
['Mr.', 'Knightley', 'loves', 'to', 'find', 'fault', 'with', 'me', ',', 'you',
'know', '--', 'in', 'a', 'joke', '--', 'it', 'is', 'all', 'a', 'joke', '.']
|
词性标注
词性是词语所承担的语法功能类别,如名词、动词和形容词等,因此词性也
被称为词类。很多词语往往具有多种词性,如“fire”,即可以作名词(“火”),也可
以作动词(“开火”)词性标注就是根据词语所处的上下文,确定其具体的词性。如在“They sat by the fire.”中,“fire”是名词,而在“They fire a gun.”中,“fire”就是动词。NLTK提供了词性标注器(POS Tagger),下面演示其使用方法。
1
2
3
|
from nltk import pos_tag
print(pos_tag(word_tokenize("They sat by the fire.")))
#对句子标记解析后再进行词性标注
|
1
2
|
[('They', 'PRP'), ('sat', 'VBP'), ('by', 'IN'), ('the', 'DT'), ('fire', 'NN'),
('.', '.')]
|
1
|
print(pos_tag(word_tokenize("They fire a gun.")))
|
1
|
[('They', 'PRP'), ('fire', 'VBP'), ('a', 'DT'), ('gun', 'NN'), ('.', '.')]
|
其它工具
除了以上介绍的分句、标记解析和词性标注,NLTK还提供了其他丰富的自 然语言处理工具,包括命名实体识别、组块分析(Chunking)和句法分析等。
另外,除了NLTK,还有很多其他优秀的自然语言处理基础工具集可供使用, 如斯坦福大学使用Java开发的CoreNLP、基于Python/Cython开发的spaCy等,它们的使用方法本书不再进行详细的介绍,感兴趣的读者可以自行查阅相关的参考资料。
LTP工具集
以上介绍的工具集主要用于英文的处理,而以中文为代表的汉藏语系与以英
语为代表的印欧语系不同,一个显著的区别在于词语之间不存在明显的分隔符,句子一般是由一串连续的字符构成的,因此在处理中文时,需要使用更有针对性的分析工具。
语言技术平台(Language Technology Platform, LTP)是哈尔滨工业大学社会计算与信息检索研究中心(HIT-SCIR)历时多年研发的一整套高效、高精度的
中文自然语言处理开源基础技术平台。该平台集词法分析(分词、词性标注和命名实体识别)、句法分析(依存句法分析)和语义分析(语义角色标注和语义依存分析)等多项自然语言处理技术于一体。最新发布的LTP4.0版本使用Python语言编写,采用预训练模型以及多任务学习机制,能够以较小的模型获得非常高的分析精度。
LTP的安装也非常简单,可以直接使用pip包管理工具,具体方法为,首先进入操作系统的控制台,然后执行以下命令。
下面对LTP的使用方法进行简要的介绍。
中文分词
如上所述,由于中文词语之间没有空格进行分割,而自然语言处理中通常以
词为最小的处理单位,因此需要对中文进行分词处理。中文的分词与英文的标记解析功能类似,只是中文分词更强调识别句子中的词语信息,因此往往不被称为标记解析。另外,与标记解析相比,由于一个句子往往有多种可能的分词结果,因分词任务的难度更高,精度也更低。使用LTP进行分词非常容易,具体示例如下。
1
2
3
4
|
from ltp import LTP
ltp = LTP()
words = ltp.pipeline(["南京市长江大桥。"], tasks = ["cws"], return_dict = False)
print(words)
|
1
|
[['南京市', '长江', '大桥', '。']]
|
其他自然语言处理功能
除了分词功能,LTP还提供了分句、词性标注、命名实体识别、依存句法分析和语义角色标注等功能。与NLTK类似,在此只演示如何使用LTP进行分句和词性标注,关于更多其他功能的使用方法,请参见LTP的官方文档。
1
2
3
|
from ltp import StnSplit
sents = StnSplit().batch_split(["南京市长江大桥。", "汤姆生病了。他去了医院。"])
print(sents)
|
1
|
['南京市长江大桥。', '汤姆生病了。', '他去了医院。']
|
1
2
3
4
|
from ltp import LTP
ltp = LTP()
words = ltp.pipeline(sents, tasks = ["cws"], return_dict = False)
print(words)
|
1
2
|
([['南京市', '长江', '大桥', '。'], ['汤姆', '生病', '了', '。'], ['他', '去', '了',
'医院', '。']],)
|
1
2
|
result = ltp.pipeline(sents, tasks = ["cws","pos"])
print(result.pos)
|
1
|
[['ns', 'ns', 'n', 'wp'], ['nh', 'v', 'u', 'wp'], ['r', 'v', 'u', 'n', 'wp']]
|
Pytorch基础
现代深度学习系统的模型结构变得越来越复杂,若要从头开始搭建则极其耗时耗力,而且非常容易出错。幸好,看似纷繁复杂的深度学习模型,都可以分解为一些同构的简单网络结构,通过将这些简单网络结构连接在一起,就可构成复杂的模型。因此,很多深度学习库应运而生,它们可以帮助用户快速搭建一个深度学习模型,并完成模型的训练(也称学习或优化)、预测和部署等功能。
本书使用的是PyTorch开源深度学习库,它由Facebook人工智能研究院(Facebook AI Research, FAIR)于2017年推出,可以使用Python语言调用。严格来 讲,PyTorch是一个基于张量(Tensor)的数学运算工具包,提供了两个高级功能:(1)具有强大的GPU(图形处理单元,也叫显卡)加速的张量计算功能;(2)能够自动进行微分计算,从而可以使用基于梯度的方法对模型参数进行优化。基于这些特点,它特别适合作为一个灵活、高效的深度学习平台。与其他深度学习库相比,PyTorch具有如下优点:
◦框架简洁;
◦入门简单,容易上手;
◦支持动态神经网络构建;
◦与Python语言无缝结合;
◦调试方便。
因此,PyTorch获得了越来越多的用户,尤其是研究人员的青睐。本节将简要介绍PyTorch的基本功能,主要包括基本的数据存储结构——张量,张量的基本操作以及通过反向传播技术自动计算梯度。
首先,仍然可以使用pip包管理工具安装PyTorch,具体方法为,首先进入操
作系统的控制台,然后执行以下命令。
本站更推荐使用Conda虚拟环境安装和运行PyTorch,具体安装方法可以参 见PyTorch官网。
张量的基本概念
所谓张量(Tensor),就是多维数组。当维度小于或等于2时,张量又有一些更熟悉的名字,例如,2维张量又被称为矩阵(Matrix),1维张量又被称为向量(Vector),而0维张量又被称为标量(Scalar),其实就是一个数值。使用张量,可以方便地存储各种各样的数据,如2维表格数据可以使用2维张量,即矩阵存储,而多张表格就可以使用3维张量表示和存储。一幅灰度图像(每个像素使用一个整数灰度值表示)也可以使用矩阵存储,而通常一副彩色图像(每个像素使用三个整数表示,分别代表红、绿、蓝的值)就可以使用3维张量表示和存储。
PyTorch提供了多种方式创建张量,如下所示。
1
2
|
import torch
print(torch.empty(2, 3)) #创建一个形状(Shape)为(2, 3)的空张量(未初始化)
|
1
2
|
tensor([[0.0000e+00, 3.6893e+19, 0.0000e+00],
[3.6893e+19, 6.3424e-28, 1.4013e-45]])
|
1
2
|
print(torch.rand(2, 3)) #创建一个形状为(2, 3)的随机张量,
# 每个值从[0,1)之间的均匀分布中采用
|
1
2
|
tensor([[0.4181, 0.3817, 0.6418],
[0.7468, 0.4991, 0.2972]])
|
1
2
|
print(torch.randn(2, 3)) #创建一个形状为(2, 3)的随机张量,每个值从标准正态分布
#(均值为0,方差为1)中采用
|
1
2
|
tensor([[ 1.2760, 0.4784, -0.9421],
[0.0435, -0.2632, -0.7315]])
|
1
2
|
print(torch.zeros(2, 3, dtype=torch.long)) # 创建一个形状为(2, 3)的0张量,
#其中dtype设置张量的数据类型,此处为整数
|
1
2
|
tensor([[0, 0, 0],
[0, 0, 0]])
|
1
2
|
print(torch.zeros(2, 3, dtype=torch.double)) #创建一个形状为(2, 3)的0张量,
#类型为双精度浮点数
|
1
2
|
tensor([[0., 0., 0.],
[0., 0., 0.]], dtype=torch.float64)
|
1
|
print(torch.tensor([[1.0, 3.8, 2.1], [8.6, 4.0, 2.4]])) #通过Python列表创建张量
|
1
2
|
tensor([[1.0000, 3.8000, 2.1000],
[8.6000, 4.0000, 2.4000]])
|
1
|
print(torch.arange(10)) #生成包含0至9,共1O个数字的张量
|
1
|
tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
|
以上张量都存储在内存中,并使用CPU进行运算。若要在GPU中创建和计算张量,则需要显式地将其存入GPU中,具体可以采用下列方法之一(前提是本机已经配置了 NVIDIA的GPU并且正确地安装了相应的CUDA库)。
1
2
3
|
torch.rand(2, 3).cuda()
torch.rand(2, 3).to("cuda")
torch.rand(2, 3, device="cuda")
|
张量的基本运算
创建了张量后,即可以对其进行运算或操作,如加减乘除四则混合运算等。PyTorch中的加减乘除是按元素进行运算的,即将参与运算的两个张量按对应的元素进行加减乘除,如下所示。
1
2
3
|
x = torch.tensor( [1, 2, 3] , dtype=torch.double)
y = torch.tensor( [4, 5, 6] , dtype=torch.double)
print(x + y)
|
1
|
tensor([5., 7., 9.], dtype=torch.float64)
|
1
|
tensor([-3., -3., -3.], dtype=torch.float64)
|
1
|
tensor([ 4., 10., 18.], dtype=torch.float64)
|
1
|
tensor([0.2500, 0.4000, 0.5000], dtype=torch.float64)
|
更多的运算方式可以通过torch
中的函数实现,如向量点积(torch.dot
)、矩阵相乘(torch.mm
)、三角函数和各种数学函数等。具体示例如下。
1
|
print(x.dot(y)) #向量x和y的点积
|
1
|
tensor(32., dtype=torch.float64)
|
1
|
print(x.sin()) #对x按元素求正弦值
|
1
|
tensor([0.8415, 0.9093, 0.1411], dtype=torch.float64)
|
1
|
print(x.exp()) #对x按元素求e^x
|
1
|
tensor([2.7183, 7.3891, 20.0855], dtype=torch.float64)
|
除了以上常用的数学运算,PyTorch还提供了更多的张量操作功能,如聚合操 作(Aggregation)、拼接(Concatenation)操作、比较操作、随机采样和序列化等, 详细的功能列表和使用方法可以参考PyTorch官方文档。
其中,当对张量进行聚合(如求平均、求和、最大值和最小值等)或拼接操作时,还涉及一个非常重要的概念,即维(Dim)或轴(Axis)。如对于一个张量, 可以直接使用mean
函数求其平均值。
1
2
|
x = torch.tensor([[1, 2, 3] , [4, 5, 6]])
print(x.mean())
|
可见,直接调用mean
函数获得的是全部6个数字的平均值。然而,有时需要
对某一行或某一列求平均值,此时就需要使用维的概念。对于一个九维张量,其维分别是dim=0,dim=1,···,dim=n-1。在做张量的运算操作时,dim设定了哪个维,就会遍历这个维去做运算(也叫作沿着该维运算),其他维顺序不变。仍然是调用mean
函数,当设定的维不同时,其结果也是不同的。
1
2
|
x = torch.tensor( [[1, 2, 3] , [4, 5, 6]])
x.mean(dim=0) #按第i维(列)求平均
|
1
|
tensor([2.5000, 3.5000, 4.5000])
|
1
|
x.mean(dim=1) #按第2维(行)求平均
|
实际上,这个平均聚合运算可以用数学直观表示为
\[
\begin{aligned}
&x.\text{mean}(\text{dim}=k)[i_1][i_2]\cdots[i_{k-1}][i_{k+1}]\cdots[i_{n-1}][i_n]\\[5pt]
=&\frac{1}{x.\text{shape}[k]}\sum_{l=0}^{x.\text{shape}[k]}x[i_1][i_2]\cdots[i_{k-1}][l][i_{k+1}]\cdots[i_{n-1}][i_n]\quad\forall i_1,\cdots,i_{k-1},i_{k+1},\cdots,i_n
\end{aligned}
\tag{1}
\]
因此,对于一个形状为 \((i_1,i_2,\cdots,i_n)\) 的张量,对第 \(k\) 维进行聚合之后,可得到一个形状为 \((i_1,i_2,\cdots,i_{k-1},i_{k+1},\cdots,i_{n-1},i_n)\) 的张量。
某些时候我们希望结果保持正确的维度,聚合操作还提供了keepdim参数,默认设置为False,需要显式地设为True。
1
2
|
x = torch.tensor( [ [1, 2, 3] , [4, 5, 6]])
x.mean(dim=0, keepdim=True)
|
1
|
tensor([[2.5000, 3.5000, 4.5000]])
|
1
|
x.mean(dim=1, keepdim=True)
|
此时的聚合运算可用数学直观表示为
\[
\begin{aligned}
&x.\text{mean}(\text{dim}=k)[i_1][i_2]\cdots[i_{k-1}][0][i_{k+1}]\cdots[i_{n-1}][i_n]\\[5pt]
=&\frac{1}{x.\text{shape}[k]}\sum_{l=0}^{x.\text{shape}[k]}x[i_1][i_2]\cdots[i_{k-1}][l][i_{k+1}]\cdots[i_{n-1}][i_n]\quad\forall i_1,\cdots,i_{k-1},i_{k+1},\cdots,i_n
\end{aligned}
\tag{2}
\]
因此,对于一个形状为 \((i_1,i_2,\cdots,i_n)\) 的张量,对第 \(k\) 维进行聚合之后,可得到一个形状为 \((i_1,i_2,\cdots,i_{k-1},i_{k+1},\cdots,i_{n-1},i_n)\) 的张量。
拼接(torch.cat
)操作也是类似的,通过指定维,获得不同的拼接结果。如:
1
2
3
|
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
y = torch.tensor ([[7, 8, 9], [10, 11, 12]])
torch.cat((x, y) , dim=0)
|
1
2
3
4
|
tensor([[1. ,2. , 3.],
[4. ,5. , 6.],
[7. ,8. , 9.],
[10. , 11., 12.]])
|
1
|
torch.cat((x, y), dim=1)
|
1
2
|
tensor( [[1., 2., 3., 7., 8., 9.],
[4., 5., 6., 10., 11., 12.]])
|
拼接操作的数学直观表示为
\[
\begin{aligned}
&\text{torch.cat}((x_1,x_2,\cdots),\text{dim}=k)[i_1][i_2]\cdots[i_k]\cdots[i_n]\\[5pt]
=&\begin{cases}
x_1[i_1][i_2]\cdots[i_k]\cdots[i_n] & \text{if } 0 \le i_k < x_1.\text{shape}[k] \\[5pt]
x_2[i_1][i_2]\cdots[i_k]\cdots[i_n] & \text{if } x_1.\text{shape}[k] \le i_k < x_1.\text{shape}[k] + x_2.\text{shape}[k] \\[5pt]
\hspace{5em}\vdots
\end{cases}
\end{aligned}
\tag{3}
\]
通过对以上多种操作的组合使用,就可以写出复杂的数学计算表达式。如对
于数学表达式
\[
z=(x+y)\times(y-2)
\tag{4}
\]
当 \(x=2,y=3\) 时,可以手动计算出 \(z=5\) ,当然也可以写一段简单的Python进行计算。
1
2
3
4
|
x = 2.
y = 3.
z = (x + y) * (y - 2)
print(z)
|
那么,使用PyTorch如何计算 \(z\) 的值呢?其实PyTorch程序和Python非常类似,唯一不同之处在于数据使用张量进行保存。具体代码如下所示。
1
2
3
4
|
x = torch.tensor([2.])
y = torch.tensor([3.])
z = (x + y) * (y - 2)
print (z)
|
通过上面的例子可以看到,PyTorch的编程方式与Python非常相似,因此,当具备了Python编程基础后,学习和使用PyTorch都非常容易。而PyTorch带来的一个好处是更高效的执行速度,尤其是当张量存储的数据比较多,同时机器还装有GPU时,效率的提升是极其显著的。
自动微分
除了能显著提高执行速度,PyTorch还提供了自动计算梯度的功能(也叫自动
微分),使得无须人工参与,即可自动计算一个函数关于一个变量在某一取值下的导数。通过该功能,就可以使用基于梯度的方法对参数(变量)进行优化(也叫学习或训练)。使用PyTorch计算梯度非常容易,仅需要执行tensor.backward()
函数,就可以通过反向传播算法(Back Propogation)自动完成。
需要注意的一点是,为了计算一个函数关于某一变量的导数,PyTorch要求显式地设置该变量(张量)是可求导的,否则默认不能对该变量求导。具体设置方法是在张量生成时,设置requires_grad=True
。
因此,计算 \(z=(x+y)\times(y-2)\) 的代码经过简单修改,就可以计算当 \(x=2,y=3\) 时, \(\displaystyle\frac{\partial z}{\partial x}\) 和 \(\displaystyle\frac{\partial z}{\partial y}\) 的值。
1
2
3
4
|
x = torch.tensor([2.], requires_grad=True)
y = torch.tensor([3.], requires_grad=True)
z = (x + y) * (y - 2)
print (z)
|
1
|
tensor([5.], grad_fn=<MulBackwardO>)
|
1
2
|
z.backward() #自动调用反向转播算法计算梯度
print(x.grad, y.grad) # 输出 dz/dx 和 dz/dy 的值tensor([1.]) tensor([6.])
|
1
|
tensor([1.]) tensor([6.])
|
也可手工求解,即: \(\displaystyle\frac{\partial z}{\partial
x}=y-2,\frac{\partial z}{\partial y}=x+2y-2\) ,则当 \(x=2,y=3\) 时, \(\displaystyle\frac{\partial z}{\partial x}\) 和 \(\displaystyle\frac{\partial z}{\partial y}\) 的值分别为 \(1\) 和 \(6\) ,与以上PyTorch代码计算的结果一致。
调整张量形状
参与运算的张量需要满足一定的形状,比如两个矩阵相乘,前一个矩阵的第二维应该和后一个矩阵的第一维相同。为了做到这一点,有时需要对张量的形状进行调整。PyTorch一共提供了4种调整张量形状的函数,分别为view
、reshape
、transpose
和permute
。下面分别加以介绍。
view
函数的参数用于设置新的张量形状,因此需要保证张量总的元素个数不变。示例如下。
1
2
|
x = torch.tensor([1, 2, 3, 4, 5, 6])
print(x, x.shape) #打印x的内容和形状(6)
|
1
|
tensor([1., 2., 3., 4., 5., 6.]) torch.Size([6])
|
1
|
print(x.view(2,3)) #将x的形状调整为(2, 3)
|
1
2
|
tensor([[1., 2., 3.],
[4., 5., 6.]])
|
1
|
print(x.view(3,2)) #将x的形状调整为(3, 2)
|
1
2
3
|
tensor([[1., 2.],
[3., 4.],
[5., 6.]])
|
1
|
print(x.view(-1, 3)) #-1位置的大小可以通过其他维的大小推断出,此处为2
|
1
2
|
tensor([[1., 2., 3.],
[4., 5., 6.]])
|
view
操作的本质是将原来的张量的内容按照如下可数的方式进行排列:
\[
\begin{gather}
x[0]\cdots[0][0],x[0]\cdots[0][1],x[0]\cdots[0][2],\cdots,x[0]\cdots[0][m_n],\\[5pt]
x[0]\cdots[1][0],x[0]\cdots[1][1],x[0]\cdots[1][2],\cdots,x[0]\cdots[1][m_n],\\[5pt]
x[0]\cdots[2][0],x[0]\cdots[2][1],x[0]\cdots[2][2],\cdots,x[0]\cdots[2][m_n],\\[5pt]
\vdots \\[5pt]
x[m_1]\cdots[m_{n-1}][0],x[m_1]\cdots[m_{n-1}][1],x[m_1]\cdots[m_{n-1}][2],\cdots,x[m_1]\cdots[m_{n-1}][m_n]
\end{gather}
\tag{5}
\]
其中 \((m_1,m_2,\cdots,m_n)\) 是原张量的形状。然后,按照上面的排列填入如下新的张量中
\[
\begin{gather}
x[0]\cdots[0][0],x[0]\cdots[0][1],x[0]\cdots[0][2],\cdots,x[0]\cdots[0][m'_n],\\[5pt]
x[0]\cdots[1][0],x[0]\cdots[1][1],x[0]\cdots[1][2],\cdots,x[0]\cdots[1][m'_n],\\[5pt]
x[0]\cdots[2][0],x[0]\cdots[2][1],x[0]\cdots[2][2],\cdots,x[0]\cdots[2][m'_n],\\[5pt]
\vdots \\[5pt]
x[m'_1]\cdots[m'_{n-1}][0],x[m'_1]\cdots[m'_{n-1}][1],x[m'_1]\cdots[m'_{n-1}][2],\cdots,x[m'_1]\cdots[m'_{n-1}][m'_n]
\end{gather}
\tag{6}
\]
其中 \((m'_1,m'_2,\cdots,m'_n)\) 是新张量的形状。要求必须保证
\[
m_1\times m_2 \times \cdots \times m_n = m'_1\times m'_2 \times \cdots \times m'_n
\tag{7}
\]
进行view
操作的张量要求是连续的(Contiguous),可以调用is_contiguous
函数判断一个张量是否为连续的。如果张量非连续,则需要先调用contiguous
函数将其变为连续的,才能调用view
函数。好在PyTorch提供了新的reshape
函数,可以直接对非连续张量进行形状调整。除此之外,reshape
函数与view
函数功能一致。在此不再赘述。
transpose
(转置)函数用于交换张量中的两个维度,参数分别为相应的维。如下所示。
1
2
|
x = torch.tensor([[1, 2, 3], [4, 5, 6]])
print(x)
|
1
2
|
tensor([[1, 2, 3],
[4, 5, 6]])
|
1
|
x.transpose(0, 1) #交换第1维和第2维
|
1
2
3
|
tensor([[1, 4],
[2, 5],
[3, 6]])
|
转置的数学直观表示为
\[
x.\text{transpose}(j,k)[i_1]\cdots[i_j]\cdots[i_k]\cdots[i_n]=x[i_1]\cdots[i_k]\cdots[i_j]\cdots[i_n]
\tag{8}
\]
不过,transpose
函数只能同时交换两个维度,若要交换更多的维度,需要多次调用该函数。更便捷的实现方式是直接调用permute
函数,其需要提供全部的维度信息作为参数,即便有些维度无须交换也需要提供。示例如下所示。
1
2
|
x = torch.tensor([[[1, 2, 3], [4, 5, 6]]])
print(x, x.shape)
|
1
2
|
tensor([[[1, 2, 3],
[4, 5, 6]]]) torch.Size( [1, 2, 3])
|
1
2
3
|
tensor([[[1, 4]],
[[2, 5]],
[[3, 6]]]) torch.Size([3, 1, 2])
|
permute
的直观数学表示为
\[
x.\text{permute}(a_1,a_2,\cdots,a_n)[i_1]\cdots[i_n]=x[i_{a_1}][i_{a_2}]\cdots[i_{a_n}]
\tag{9}
\]
广播机制
在上面介绍的张量运算中,都是假设两个参与运算的张量形状相同。在有些情
况下,即使两个张量的形状不同,也可以通过广播机制(Broadcasting Mechanism)执行按元素计算。具体的执行规则是,首先,对其中一个或同时对两个张量的元素进行复制,使得这两个张量的形状相同;然后,在扩展之后的张量上再执行按元素运算。通常是沿着长度为1的维度进行扩展,下面通过一个具体的例子进行说明。
1
2
3
|
x = torch.arange(1, 4).view(3, 1)
y = torch.aremge(4, 6).view(1, 2)
print(x)
|
1
2
3
|
tensor([[1],
[2],
[3]])
|
生成两个张量,形状分别为(3,1)和(1,2),显然,它们不能直接执行按元素 运算。因此,在执行按元素运算之前,需要将它们扩展(广播)为形状(3, 2)的张量,具体扩展的方法为将x的第1列复制到第2列,将y的第1行复制到第2、3行。如下所示,可以直接进行加法运算,PyTorch会自动执行广播和按元素相加。
1
2
3
|
tensor([[5, 6],
[6, 7],
[7, 8]])
|
索引与切片
与Python的列表类似,PyTorch中也可以对张量进行索引和切片操作,规则
也与Python语言基本一致,即索引值是从0开始的,切片的范围是从m开始,至n的前一个元素结束。与Python语言不同的是,PyTorch可以对张量的任意一个维度进行索引或切片。下面演示一些简单的示例。
1
2
|
x = torch.arange(12).view(3, 4)
print(x)
|
1
2
3
|
tensor([[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11]])
|
1
|
print(x[1, 3]) #x的第2行,第4列的元素(7)
|
1
|
print(x[1:3]) #第2、3两行元素
|
1
2
|
tensor([[ 4, 5, 6, 7],
[8, 9, 10, 11]])
|
1
|
print(x[:, 2]) #第3列全部元素
|
1
|
print(x[:, 2:4]) #第3、4两列元素
|
1
2
3
|
tensor([[2, 3],
[6, 7],
[10, 11]])
|
1
2
|
x[:, 2:4] = 100 #第3、4两列元素全部赋值为100
print(x)
|
1
2
3
|
tensor([[0, 1, 100, 100],
[4, 5, 100, 100],
[8, 9, 100, 100]])
|
降维与升维
有时为了适配某些运算,需要对一个张量进行降维或升维。如很多神经网络模块在调用时,需要同时输入一个批次,即多个样例,如果此时只输入1个输入样例,则需要将某一个维度提升,以适配该模块的调用要求。
具体来讲,所谓升维,就是通过调用torch.unsqueeze(input, dim, out=None)
函数,对输入张量的dim
位置后面插入维度1,并返回一个新的张量。与索引相同,dim
的值也可以为负数。
降维恰好相反,使用torch.squeeze(input, dim=None, out=None)
函数,在不指定dim
时,张量中形状为1的所有维都将被除去。如输入形状为(A, 1, B, 1, C, 1, D)
的张量,那么输出形状就为(A, B, C, D)
。当给定dim
时,那么降维操作只在给定维度上。例如,输入形状为(A, 1, B)
,squeeze(input, dim=0)
函数将会保持张量不变,只有用squeeze(input, dim=1)
函数时,形状才会变成(A, B)
。下面给出调用示例。
1
2
3
|
import torch
a = torch.tensor([1, 2, 3, 4])
print(a.shape)
|
1
2
|
b = torch.unsqueeze(a, dim=0) #将a的第1维升高
print(b, b.shape) #打印b以及b的形状
|
1
|
tensor( [[1., 2., 3., 4.]]) torch.Size([1, 4])
|
1
2
|
b = a.unsqueeze(dim=0) #unsqueeze函数的另一种等价调用方式
print(b, b.shape)
|
1
|
tensor([[1., 2., 3., 4.]]) torch.Size([1, 4])
|
1
2
|
c = b.squeeze() #对b进行降维,去掉所有形状中为1的维
print(c, c.shape)
|
1
|
tensor([1., 2., 3., 4.]) torch.Size([4])
|
大规模预训练数据
预训练语言模型需要通过海量文本学习语义信息,随着语料规模的增大,得到的统计信息将更加精准,更利于文本表示的学习。例如,在小型语料库中,单词“包袱”只出现在“他背着包袱就走了”这句话中,则模型只能学习到“包袱”作为一种“布包起来的衣物包裹”的含义。而随着语料库的增大,单词“包袱”可能出现在更多不同的上下文中,如“你不要有太大的思想包袱”“那位相声演员 的包袱很有趣”,则能够赋予“包袱”更多不同的含义。因此,为了训练效果更好的预训练模型,高质量、大规模的预训练数据是必不可少的。在本节中,将主要介绍典型的语料资源——维基百科数据的获取和基本处理方法。
维基百科数据
维基百科(Wikipedia)是一部用不同语言写成的网络百科全书,由吉米•威尔士与拉里•桑格两人合作创建,于2001年1月13日在互联网上推出网站服务,并在2001年1月15日正式展开网络百科全书的项目。维基百科内容由人工编辑,因此作为预训练的原始数据非常适合。接下来,将介绍维基百科数据的获取以及原始数据的处理方法。
原始数据的获取
维基百科官方会以一定的时间间隔,对整个维基百科的内容进行快照并压缩,用户可以直接下载相应的压缩包,获取到某一时刻的维基百科数据。如以中文维基百科数据为例,存在比较重要的几个文件,如表1所示。
文件名 |
内容 |
大小/MB |
zhwiki-latest-abstract.xml.gz |
所有词条摘要 |
≈147 |
zhwiki-latest-all-titles.gz |
所有词条标题 |
≈33 |
zhwiki-latest-page.sql.gz |
所有词条标题及摘要 |
≈204 |
zhwiki-latest-pagelinks.sql.gz |
所有词条外链 |
≈890 |
zhwiki-latest-pages-articles.xml.bz2 |
所有词条正文 |
≈1952 |
表1 中文维基百科快照内容
预训练语言模型主要使用的是维基百科的正文内容,因此这里选择"zhwiki-latest-pages-articles.xml.bz2",以下载最新快照的词条正文压缩包。以2020年10月23日的快照为例,该压缩包的大小约为1.95GB。由于后续进行处理时会直接对压缩包进行处理,这里不再进行解压缩操作。
语料处理方法
纯文本语料抽取
处理维基百科快照的方法相对比较成熟,这里以WikiExtractor为例进行介绍。WikiExtractor是一款基于Python的工具包,专门用于处理维基百科的快照。为了方便安装工具包的相关依赖程序,这里推荐使用pip命令安装WikiExtractor。
1
|
pip install wikiextractor
|
接下来,直接通过一行命令即可对维基百科的快照压缩包进行处理,去除其中的图片、表格、引用和列表等非常规文本信息,最终得到纯文本的语料。需要注意的是,这一部分的处理需要花费一定处理时间,视系统配置不同可能耗费几十分钟至数小时不等。
1
|
python -m wikiextractor.WikiExtractor zhwiki-latest-pages-articles.xml.bz2
|
对于WikiExtractor工具包的使用参数,可通过如下命令获取(普通用户使用默认参数即可)。
1
|
python -m wikiextractor.WikiExtractor -h
|
处理完毕后,可以获得纯文本语料文件,其目录结构如下所示。
1
2
3
4
5
6
7
8
9
|
./text
|- AA
|- wiki_00
|- wiki_01
|-...
|- wiki_99
|- AB
|- ...
|- AO
|
text文件夹由AA到AO子文件夹构成,而每个子文件夹包含了wiki_00至 wiki_99共100个文件。每个文件包含多个维基百科词条,其内容如下所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
<doc id="13" url="https://zh.wikipedia.org/wiki?curid=13" title="数学">
数学
数学是利用符号语言研究數量、结构、变化以及空间等概念的一門学科,从某种角度看屬於形式科學
的一種。數學透過抽象化和邏輯推理的使用,由計數、計算、量度和對物體形狀及運動的觀察而产生
。數學家們拓展這些概念,为了公式化新的猜想以及從選定的公理及定義中建立起嚴謹推導出的定理
。
......
</doc>
<doc id="18" url="https://zh.wikipedia.org/wiki?curid=18" title="哲学">
哲学
......
</doc>
|
可见,每个词条均由<doc>
标签开始并以</doc>
结尾。
中文繁简体切换
中文维基百科中同时包含了简体中文和繁体中文的数据,如果使用者只需要 获得简体中文数据,需要将纯文本语料中的繁体中文内容转换为简体中文。这里使用一款较为成熟的中文繁简体转换工具——OpenCC。OpenCC工具可将简体中文、繁体中文(其中包括中国香港地区、中国台湾地区使用的繁体)和日本新字体等中文进行互转。OpenCC工具同样可以通过pip命令安装。
安装完毕后,可以通过如下Python脚本进行中文繁简转换。
1
|
python convert_t2s.py input_file > output_file
|
其中,转换脚本convert_t2s.py
的内容如下所示。
1
2
3
4
5
6
7
8
9
10
|
import sys
import opencc
converter = opencc.OpenCC("t2s.json")
f_in = open(sys.argv[1], "r")
for line in f_in.readlines():
line = line.strip()
line_t2s = converter.convert(line)
print(line_t2s)
|
其中要用到的配置文件t2s.json
的内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
{
"name": "Traditional Chinese to Simplified Chinese",
"segmentation": {
"type": "mmseg",
"dict": {
"type": "ocd2",
"file": "TSPhrases.ocd2"
}
},
"conversion_chain": [{
"dict": {
"type": "group",
"dicts": [{
"type": "ocd2",
"file": "TSPhrases.ocd2"
}, {
"type": "ocd2",
"file": "TSCharacters.ocd2"
}]
}
}]
}
|
经过处理后,原始语料中的繁体中文将全部转换为简体中文。读者可根据实 际情况进行简繁体或繁简体的转换。
数据清洗
经过上述处理后,可以得到包含简体中文的纯文本语料。然而,在从维基百科快照里抽取纯文本数据的过程中可能因文本编码、损坏的HTML标签等问题导致纯文本中包含一些乱码或机器字符。因此,在最后需要通过一个简单的后处理操作对纯文本语料进行二次过滤,进一步提升预训练语料的质量。需要注意的是,这里仅处理语料中的一些明显错误,而对于一般类型的错误则不会处理(如标点不统一等问题),因为一般类型的错误在日常的文本中也会出现。这里的处理方式主要包括如下几类:
◦删除空的成对符号,例如“()”“《》”“【】”“[]”等;
◦删除<br>等残留的HTML标签。需要注意的是,这里不删除以“<doc id”和“</doc>”为开始的行,因其表示文档的开始和结束,能为某些预训练语言模型的数据处理提供至关重要的信息;
◦删除不可见控制字符,避免意外导致数据处理中断。
所以,数据清洗将最大限度地保留自然文本的统计特征,对于其中的“对”与“错”二则交由模型来进行学习,而非通过人工进行过多干预。
通过如下脚本启动数据清洗过程。
1
|
python wikidata.cleaning.py input.file > output_file
|
其中,数据清洗脚本wikidata_cleaning.py
的内容如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import sys
import re
def remove_empty_paired_punc(in_str):
return in_str.replace('()', '').replace('《》', '').replace('【】', '').replace('[]', '')
def remove_html_tags(in_str):
html_pattern = re.compile(r'<[^>]+>', re.S)
return html_pattern.sub('', in_str)
def remove_control_chars(in_str):
control_chars = ''.join(map(chr, list(range(0, 32)) + list(range(127, 160))))
control_chars = re.compile('[%s]' % re.escape(control_chars))
return control_chars.sub('', in_str)
f_in = open(sys.argv[1], 'r')
for line in f_in.readlines():
line = line.strip()
if re.search(r'^(<doc id)|(</doc>)', line):
print(line)
continue
line = remove_empty_paired_punc(line)
line = remove_html_tags(line)
line = remove_control_chars(line)
print(line)
|
CommonCrawl数据
Common Crawl包含了超过7年的网络爬虫数据集,包含原始网页数据、元数据提取和文本提取。数据存储在Amazon Web服务的公共数据集和遍布全球的 多个学术云平台上,拥有PB级规模。Common Crawl的数据非常庞大,因此想处理好如此庞大的数据并不是一件容易的事情。Facebook提出的CC-Net工具可用于获取Common Crawl数据,并且提供了一套相对完整的数据处理流程。其应用方法较为简单。
更多数据集
本节介绍的NLTK工具集提供了少量的自然语言处理数据集,可用于模型演示和简单的系统测试。近期,HuggingFace公司发布了更大规模的语料库集合——HuggingFace Datasets,与其他自然语言处理数据集相比,具有如下的特点:
◦数据集数目多:截至2021年3月,共收录了近200种语言的700多个数据集,涵盖了文本分类、机器翻译和阅读理解等众多自然语言处理任务。之所以能有如此多的数据,主要依赖于社区的贡献,任何用户都可以共享相关的数据集。除了支持用户可以直接使用这些公开的数据集,还支持其方便地调用自己私有的数据集。
◦兼容性好:可以直接被PyTorch、TensorFlow等深度学习框架,以及pandas、NumPy等数据处理工具调用,同时支持CSV、JSON等数据格式的读取,并提供了丰富、灵活的调用接口和数据处理接口。
◦数据读取效率高:可以在仅占用少量内存的条件下,高速地读取大量的丰富的评价方法:如2.4节介绍的,由于自然语言处理任务类型众多,需要多种不同的评价指标对它们进行评价。为此,HuggingFace Datasets除了提供多种通用的评价方法,还针对不同的数据集提供了更有针对性的评价方法。
在使用HuggingFace Datasets之前,首先需要使用以下命令安装datasets包。
下面通过一些示例,演示如何调用datasets提供的数据集以及评价方法。
1
2
3
4
5
6
|
from pprint import pprint
from datasets import load_dataset
dataset = load_dataset('sst', split='train', trust_remote_code=True) # 加载SST(Stanford Sentiment
# Treebank)数据集(训练数据部分)。在第一次执行时,程序会自动下载相应的
# 数据集并放入本地的缓存目录中,当下次再运行时,会直接从本地加载
print(len(dataset)) #数据集中的样本数目
|
1
2
3
|
pprint(dataset[0]) #打印以字典对象存储的第1个样本,字典中存储了4个键值对
# 分别为标签(label: 0〜1的实数值,指示属于正例的可能性)、原始句子
# (sentence)、标记序列(tokens:各标记之间使用|分隔)以及句法分析树(tree)
|
1
2
3
4
5
6
7
8
9
10
|
{'label': 0.6944400072097778,
'sentence': "The Rock is destined to be the 21st Century 's new `` Conan '' "
"and that he 's going to make a splash even greater than Arnold "
'Schwarzenegger , Jean-Claud Van Damme or Steven Segal .',
'tokens': "The|Rock|is|destined|to|be|the|21st|Century|'s|new|``|Conan|''|and
|that|he|'s|going|to|make|a|splash|even|greater|than|Arnold|Schwarzenegger|,
|Jean-Claud|Van|Damme|or|Steven|Segal|.",
'tree': '70|70|68|67|63|62|61|60|58|58|57|56|56|64|65|55|54|53|52|51|49|47|47
|46|46|45|40|40|41|39|38|38|43|37|37|69|44|39|42|41|42|43|44|45|50|48|48|49|50
|51|52|53|54|55|66|57|59|59|60|61|62|63|64|65|66|67|68|69|71|71|0'}
|
datasets还提供了一些额外的函数,用于对数据进行处理或转换为PyTorch、TensorFlow等工具集能够处理的格式,具体调用方法见相应的使用文档。
Hugging Face还提供了evaluate库以提供评价方法。通过如下命令安装
使用方法如下。
1
2
3
4
|
import evaluate
accuracy = evaluate.load("accuracy")
results = accuracy.compute(references=[0,1,0], predictions=[1,1,0])
print(results)
|
1
|
{'accuracy': 0.6666666666666666}
|
小结
本章介绍了三种常用的自然语言处理基础以及神经网络工具集,分别为:英文自然语言处理基础工具NLTK、中文自然语言处理基础工具LTP,以及本书所使用的深度学习框架PyTorch。另外,还介绍了预训练模型的基础之——大规模文本数据的获取和简单处理方式,以及使用HuggingFace Datasets获取更多数据集的方法。本部分后续章节内容都紧密依赖这些工具和数据。
靠近你就靠近了痛苦,远离你就远离了幸福。
― 安德烈·纪德, 《窄门》