gitbook/PyTorch深度学习实战/docs/460504.md
2022-09-03 22:05:03 +08:00

18 KiB
Raw Blame History

21 | NLP基础详解自然语言处理原理与常用算法

你好,我是方远。

在之前的课程中,我们一同学习了图像分类、图像分割的相关方法,还通过实战项目小试牛刀,学完这部分内容,相信你已经对深度学习图像算法有了一个较为深入的理解。

然而在实际的项目中除了图像算法还有一个大的问题类型就是文字或者说语言相关的算法。这一类让程序理解人类语言表达的算法或处理方法我们统称为自然语言处理Natural Language Processing, NLP

这节课我们先来学习自然语言处理的原理和常用算法通过这一部分的学习以后你遇到一些常见的NLP问题很容易就能想出自己的解决办法。不必担心原理、算法的内容太过理论化我会结合自己的经验从实际应用的角度为你建立对NLP的整体认知。

NLP的应用无处不在

NLP研究的领域非常广泛凡是跟语言学有关的内容都属于NLP的范畴。一般来说较为多见的语言学的方向包括词干提取、词形还原、分词、词性标注、命名实体识别、语义消歧、句法分析、指代消解、篇章分析等方面。

看到这里你可能感觉这些似乎有点太学术、太专业了涉及语言的结构、构成甚至是性质方面的研究了。没错这些都是NLP研究在语言学中的应用方面就会给人一种比较偏研究的感觉。

实际上NLP还有很多的研究内容是侧重“处理”和“应用”方面的比如我们常见的就有机器翻译、文本分类、问答系统、知识图谱、信息检索等等。

图片

我举一个例子,你就知道自然语言处理有多么重要了。平时我们经常会用搜索引擎,当你打开网页、在搜索框中输入自己想要了解的关键词之后,搜索引擎的后台算法逻辑就要开始一整套非常复杂的算法逻辑,这其中包括几个比较重要的方面,我们不妨结合例子来看看。

在搜索引擎的输入框中输入“亚洲的动wu”文本显示的内容如下图所示。别看只是一次简单的检索动作搜索系统要完成的工作可不少。

图片

首先搜索引擎要对你输入的内容query进行解析这就涉及到了之前提到的分词、命名实体识别、语义消歧等内容当然还涉及到了query纠错因为你错误地输入了拼音而非汉字需要改写成正确的形式。

通过一系列的算法之后,系统识别出你的需求是:寻找动物相关的搜索结果,这些结果的限定条件是它们要生活在亚洲。

接着,系统就开始在数据库(或者是存储的集群中)搜索相关的实体,这些实体的查询和限制条件的过滤,就涉及信息检索、知识图谱等内容。

最后,细心的同学对照搜索结果会发现,有的时候搜索引擎除了提供严格匹配的检索结果之外,还会提供一些相关内容的扩展结果,比如广告、新闻、视频等。而且很多搜索引擎的扩展搜索结果页都是个性化的,也就是根据用户的特点行为提供推荐,这些让我们的搜索结果更加丰富,体验更好。

仅仅只有这些了么?不,远远没有,因为刚才的这个过程,只是针对你这一个用户的一次检索所需要完成的一部分工作而已。更多的工作,实际是用户开始使用搜索引擎之前的构建准备阶段。

为了构建搜索引擎,就需要对存储的内容进行解析,这就包括了篇章理解、文本处理、图片识别、音视频算法等环节,对每一个网页(内容)进行特征的提取,构建检索库、知识库等,这个工作量就会非常的大,涉及的面也非常广泛。

由此可见NLP的应用真的深入到了互联网业务的方方面面掌握了NLP的相关算法将会使我们的竞争力变得更强。接下来针对自然语言处理的“应用”方面我们一起聊聊NLP中文场景下的一些重要内容。

NLP的几个重要内容

想要让程序对文本内容进行理解,我们需要解决几个非常基础和重要的内容,分别是分词、文本表示以及关键词提取。

分词

中文跟英文最大的不同在于,英文是由一个个单词构成的,单词与单词之间有空格隔断。但是中文不一样,中文单词和单词之间除了标点符号没有别的隔断。这就给程序理解文本带来了一定的难度,分词的需求也应运而生。

尽管现在的深度学习已经对分词的依赖越来越小可以通过Word Embedding等方式对字符token级的文字进行表示但是分词的地位不会降低单词、词组级别的文本表示仍旧有非常多的应用场景。

因为我们的学习重在快速上手和实战应用,所以为了降低你的学习成本,这个专栏里我不会专门深入讲解各种分词算法细节,而是侧重于带你理解其特点,并教你学习如何用相应的工具包实现分词过程。

目前网络上已经有了很多的开源或者免费的NLP分词工具比如jieba、HanLP、THULAC等包括腾讯、百度、阿里等公司也有相应的商业付费工具。

贫穷使人理智我们今天使用免费的jieba分词来做一个分词的例子链接你可以从这里获取。安装这个工具非常简单只需要使用pip即可。

pip install jieba

jieba的使用也很方便我来演示一下

import jieba
text = "极客时间棒呆啦"
# jieba.cut得到的是generator形式的结果
seg = jieba.cut(text)  
print(' '.join(seg)) 

# Get 极客 时间 棒呆 啦

其实除了分词jieba还提供了词性标注的结果pos

import jieba.posseg as posseg
text = "一天不看极客时间我就浑身难受"
# 形如pair('word, 'pos')的结果
seg = posseg.cut(text)  
print([se for se in seg]) 
# Get [pair('一天', 'm'), pair('不', 'd'), pair('看', 'v'), pair('极客', 'n'), pair('时间', 'n'), pair('我', 'r'), pair('就', 'd'), pair('浑身', 'n'), pair('难受', 'v')]

是不是非常简单?搞定了分词,我们接下来就要开始对文本进行表示了。

文本表示的方法

在深度学习被广泛采用之前,很多传统机器学习算法结合自身的特点,使用了各种各样的文本表示。

最经典的就是独热One-hot表示法了。在这种方法中假定所有的文字一共有N个单词也可以是字符我们可以将每个单词赋予一个单独的序号id那么对于任意一个单词我们都可以采用一个N位的列表向量对其进行表示。在这个表示中只需要将这个单词对应序号id的位置为1其他位置为0即可。

我还是举个例子来帮你加深理解。比方说我们词典大小为10000“极客”这个单词的序号id为666那么我们就需要建立一个10000长度的向量并将其中的第666位置为1其余位为0。如下

图片

这时候你就会发现在UTF-8编码中中文字符有两万多个词语数量更是天文数字那么我们仅用字符的方式每个字就需要两万多维度的数据来表示。推算一下如果有一篇一万字的文章这个数据量就很可怕了。

为了进一步的压缩数据的体积可以只使用一个向量表示文章中所有的单词例如前面的例子我们仍旧建立一个10000维的向量把文章中出现过的所有单词的对应位置置为1其余为0。

图片

这样看上去数据体积就少了很多还有没有其他办法进一步缩减空间的占用呢有的例如count-based表示方法。

在这种方法中我们采用v={index1: count1, index2: count2,…, index n: count n}的形式对每一个出现的单词的序号id以及出现过的次数进行统计这样一来“极客时间”我们只需要两个k-v对的dict即可表示: {3:1, 665:1}。

这种表示方法在SVM、树模型等多个算法包中被广泛采用因为客观来说它确实能够大幅度地压缩空间的占用,生成起来也非常方便。但是你会发现前面这几种方法,不能表述单词的语序信息。

举个例子,“我/喜欢/你”和“你/喜欢/我”两个截然不同的意思,用前面的方法做分词的话,却会得到相同的表示结果。这时候如果搞错了,其实却是单相思的话,那岂不是很苦涩?

好在现在深度学习的使用推动了Word Embedding的发展基本上我们都会采用该方法进行文本表示。但是还是刚才的话这并不意味着传统的文本表示方法就过时了。在一些小规模、轻量级的文本处理场景中它们的作用仍旧非常大。

关于文本表示中Word Embedding的部分咱们在后续课程再展开讲解这也是NLP深度学习的核心内容之一。

让我们回到刚才的传统文本表示方法为了实现对单词顺序信息的记录该怎么办呢这时我们要解决NLP中的一个重要问题关键词的提取。

关键词的提取

关键词,顾名思义,就是能够表达文本中心内容的词语。关键词提取在检索系统、推荐系统等应用中有着极重要的地位。它是文本数据挖掘领域的一个分支,所以在摘要生成、文本分类/聚类等领域中也是非常基础的环节。

关键词提取,主要分为有监督和无监督的方法,一般来说,我们采用无监督的方法较多一些,这是因为它不需要人工标注的语料,只需要对文本中的单词按照位置、频率、依存关系等信息进行判断,就可以实现关键词的识别和提取。

无监督方法一般有三种类型,基于统计特征的方法、基于词图模型的方法,以及基于主题模型的方法,我们分别来看看。

基于统计特征的方法

这种类型的方法最为经典的就是TF-IDFterm frequencyinverse document frequency词频-逆向文件频率)。该方法最核心的思想非常简单:一个单词在文件中出现的次数越多,它的重要性越高;但它在语料库中出现的频率越高,它的重要性反而越小。

什么意思呢就比如说我们有10篇文章其中有2篇财经文章、5篇科技、3篇娱乐对于单词“股票”它在财经文章中的次数肯定非常多但是在娱乐和科技中就非常少这就意味着“股票”这个词就能够更好的“区分”文章的类别那它的重要性自然也就非常高了。

在TF-IDF中词频TF表示关键字在文本中出现的频率。而逆向文件频率 (IDF) 是由包含该词语的文件的数目除以总文件数目得到的,一般情况下还会取对数对结果进行缩放。

图片
图片

你可以自己先想想这里为什么分母要加1呢这是为了避免分母为0的情况。得到了TF和IDF之后我们将两者相乘就得到了TF-IDF了。

通过TF-IDF不难看出基于统计的方法的特点在于对单词出现的次数以及分布进行数学上的统计,从而发现相应的规律和重要性(权重),并以此作为关键词提取的依据

跟分词一样关键词的提取目前也有很多集成工具包比如NLTKNatural Language Toolkit它是一个非常著名的自然语言处理工具包是NLP研究领域常用的Python库。我们仍旧可以使用pip install nltk命令来进行安装。

使用NLTK来计算TF-IDF非常简单代码如下

from nltk import word_tokenize
from nltk import TextCollection

sents=['i like jike','i want to eat apple','i like lady gaga']
# 首先进行分词
sents=[word_tokenize(sent) for sent in sents]

# 构建语料库
corpus=TextCollection(sents)

# 计算TF
tf=corpus.tf('one',corpus)

# 计算IDF
idf=corpus.idf('one')

# 计算任意一个单词的TF-IDF
tf_idf=corpus.tf_idf('one',corpus)

你可以执行前面这段代码看看tf_idf等于多少

基于词图模型的关键词提取

前面基于统计的方法采用的是对词语的频率计算的方式,但我们还可以有其他的提取思路,那就是基于词图模型的关键词提取。

在这种方法中,我们首先要构建文本一个图结构,用来表示语言的词语网络。然后对语言进行网络图分析,在这个图上寻找具有重要作用的词或者短语,即关键词。

该类方法中最经典的就是TextRank算法了它脱胎于更为经典的网页排序算法PageRank。关于PageRank算法你可以参考这个wiki戳我。戳完PageRank之后你就会知道PageRank算法的核心内容有两点

  • 如果一个网页被很多其他网页链接到的话就说明这个网页比较重要也就是PageRank值会相对较高。

  • 如果一个PageRank值很高的网页链接到一个其他的网页那么被链接到的网页的PageRank值会相应地因此而提高。
    而TextRank就非常好理解了。它跟PageRank的区别在于

  • 用句子代替网页

  • 任意两个句子的相似性可以采用类似网页转换概率的概念计算但是也稍有不同TextRank用归一化的句子相似度代替了PageRank中相等的转移概率所以在TextRank中所有节点的转移概率不会完全相等。

  • 利用矩阵存储相似性的得分类似于PageRank的矩阵M。

  • TextRank的基本流程如下图所示。

图片

看上去蛮复杂的不过没有关系刚才提到的jieba也有了相应的集成算法。在jieba中我们可以使用如下的函数进行提取

jieba.analyse.textrank(sentence, topK=20, withWeight=False, allowPOS=('ns', 'n', 'vn', 'v'), withFlag=False)

其中sentence是待处理的文本topK是选择最重要的K个关键词基本上你用好这两个参数就足够了。

基于主题模型的关键词提取

最后一种关键词提取方法就是基于主题模型的关键词提取。

主题模型,这个名字看起来就高端了很多,实际上它也是一种基于统计的模型,只不过它会“发现”文档集合中出现的抽象的“主题”,并用于挖掘文本中隐藏的语义结构。

LDALatent Dirichlet Allocation文档主题生成模型是最典型的基于主题模型的算法。有关LDA的算法的介绍你随便搜索一下网络资料就能找到我就不展开说了。而咱们在这节课中将会利用已经集成好的工具包gensim来实现使用这个模型代码也非常简单我们一起来看一下

from gensim import corpora, models
import jieba.posseg as jp
import jieba

input_content = [line.strip() for line open ('input.txt', 'r')]
# 老规矩,先分词
words_list = []
for text in texts:
  words = [w.word for w in jp.cut(text)]
  words_list.append(words)

# 构建文本统计信息, 遍历所有的文本为每个不重复的单词分配序列id同时收集该单词出现的次数
dictionary = corpora.Dictionary(words_list)

# 构建语料将dictionary转化为一个词袋。
# corpus是一个向量的列表向量的个数就是文档数。你可以输出看一下它内部的结构是怎样的。
corpus = [dictionary.doc2bow(words) for words in words_list]

# 开始训练LDA模型
lda_model = models.ldamodel.LdaModel(corpus=corpus, num_topics=8, id2word=dictionary, passes=10)

在训练环节中num_topics代表生成的主题的个数。id2word即为dictionary它把id都映射成为字符串。passes相当于深度学习中的epoch表示模型遍历语料库的次数。

小结

在这节课中我带你一同了解了自然语言处理的应用场景以及三个经典的NLP基础问题。

NLP的三大经典问题包括分词、文本表示、关键词提取正是因为这三个问题太过经典和基础所以现在已经有了大量的集成工具供我们直接使用。

但我还是那句话,有了工具,并不意味着我们不需要理解它内部的原理,学习要知其然,更需要知其所以然,这样在实际的工作中遇到问题的时候,我们才能游刃有余地解决。

细心的你可能已经发现了今天的课程里我们针对不同的问题使用了不同的工具包分词我们使用了jieba关键词的提取我们使用了gensim和NLTK所以我希望你在课后有空的时候也去了解一下这三个工具的具体使用和更多功能因为它们真的很强大。

在文本表示方法中我们留了一个小尾巴也就是Word Embedding。随着深度学习的越来越广泛使用词嵌入Word Embedding的方法也有了越来越多的算法和工具来实现。在后续的课程中我会通过BERT的实战开发来向你介绍Word Embedding的训练生成和使用。

每课一练

TF-IDF有哪些缺点呢你不妨结合它的计算过程做个梳理。

期待你在留言区跟我交流互动也推荐你把这节课分享给身边对NLP感兴趣的同事、朋友跟他一起学习进步。