目录
第2章 自然语言处理基础
2.1 文本的表示
2.1.1 词的独热表示
2.1.2 词的分布式表示
2.1.3 词嵌入表示
2.1.4 文本的词袋表示
2.2 自然语言处理任务
2.2.1 语言模型
2.2.2 自然语言处理基础任务
2.2.3 自然语言处理应用任务
2.3 基本问题
2.3.1 文本分类问题
2.3.2 结构预测问题
2.3.3 序列到序列问题
2.4 评价指标
字符串是文本最自然,也是最常用的机内存储形式。所谓字符串,即字符序列,而其中的一个字符本质上就是一个整数。基于字符串的文本表示方式可以实现简单的字符串增删改查等编辑任务,并能够通过编辑距离等算法计算两个字符串之间的字面相似度。
基于规则的方法存在很多问题。首先,规则的归纳依赖专家的经验,需要花费大量的人力、物力和财力;其次,规则的表达能力有限,很多语言现象无法用简单的规则描述;最后,随着规则的增多,规则之间可能存在矛盾和冲突的情况,导致最终无法做出决策。
基于机器学习的自然语言处理技术最本质的思想是将文本表示为向量,其中的每一维代表一个特征。在进行决策的时候,只要对这些特征的相应值进行加权求和,就可以得到一个分数用于最终的判断。
除了可以应用于基于机器学习的方法,文本向量表示还可以用于计算两个文本之间的相似度,即使用余弦函数等度量函数表示两个向量之间的相似度,并应用于信息检索任务。
所谓词的独热表示,即使用一个词表大小的向量表示一个词,然后将词表中的第i个词wi表示为向量:
在该向量中,词表中第i个词在第i维上被设置为1,其余维均为0。这种表示被称为词的独热表示或独热编码。
独热表示的一个主要问题就是不同词使用完全不同的向量进行表示,这会导致即使两个词在语义上很相似,但是通过余弦函数来度量它们之间的相似度时值却为0。另外,当应用于基于机器学习的方法时,独热模型会导致数据稀疏问题。
为了缓解数据稀疏问题,传统的做法是除了词自身,再提取更多和词相关的泛化特征,如词性特征、词义特征和词聚类特征等。
1.分布式语义假设
词的含义可由其上下文的分布进行表示。
基于该思想,可以利用大规模的未标注文本数据,根据每个词的上下文分布对此进行表示。
除了词,上下文的选择有很多种方式,而选择不同的上下文得到的词向量表示性质会有所不同。
2.点互信息
如果一个词语很多词共现,则降低其权重;反之,如果一个词只与个别词共现,则提高其权重。信息论中的点互信息恰好能做到这一点。对于词w和上下文c是,其PMI为:
可以通过最大似然估计分别计算相关的概率值。
import numpy as np
M = np.array([[0, 2, 1, 1, 1, 1, 1, 2, 1, 3],
[2, 0, 1, 1, 1, 0, 0, 1, 1, 2],
[1, 1, 0, 1, 1, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 1, 0, 1],
[2, 1, 0, 0, 0, 1, 1, 0, 1, 1],
[1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[3, 2, 1, 1, 1, 1, 1, 2, 1, 0]])
def pmi(M, positive=True):
col_totals = M.sum(axis=0) # 按列求和
row_totals = M.sum(axis=1) # 按行求和
total = col_totals.sum() # 总频次
expected = np.outer(row_totals, col_totals) / total # 获得每个元素的分子
M = M / expected
with np.errstate(divide='ignore'): # 不显示log(0)的警告:
M = np.log(M)
M[np.isinf(M)] = 0.0 # 将log(0)置为0
if positive:
M[M < 0] = 0.0
return M
M_pmi = pmi(M)
np.set_printoptions(precision=2) # 打印结果保留两位小数
print(M_pmi)
[[0. 0.17 0.06 0.06 0.06 0.28 0.28 0.28 0.28 0.28]
[0.17 0. 0.43 0.43 0.43 0. 0. 0. 0.65 0.25]
[0.06 0.43 0. 1.02 1.02 0. 0. 0. 0. 0.14]
[0.06 0.43 1.02 0. 1.02 0. 0. 0. 0. 0.14]
[0.06 0.43 1.02 1.02 0. 0. 0. 0. 0. 0.14]
[0.28 0. 0. 0. 0. 0. 1.46 0.77 0. 0.36]
[0.28 0. 0. 0. 0. 1.46 0. 0.77 0. 0.36]
[0.42 0.09 0. 0. 0. 0.9 0.9 0. 0.9 0. ]
[0.28 0.65 0. 0. 0. 0. 0. 0.77 0. 0.36]
[0.2 0.17 0.06 0.06 0.06 0.28 0.28 0.28 0.28 0. ]]
3.奇异值分解
import numpy as np
import matplotlib.pyplot as plt
from pylab import mpl
# 设置显示中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
M = np.array([[0, 2, 1, 1, 1, 1, 1, 2, 1, 3],
[2, 0, 1, 1, 1, 0, 0, 1, 1, 2],
[1, 1, 0, 1, 1, 0, 0, 0, 0, 1],
[1, 1, 1, 0, 1, 0, 0, 0, 0, 1],
[1, 1, 1, 1, 0, 0, 0, 0, 0, 1],
[1, 0, 0, 0, 0, 0, 1, 1, 0, 1],
[1, 0, 0, 0, 0, 1, 0, 1, 0, 1],
[2, 1, 0, 0, 0, 1, 1, 0, 1, 1],
[1, 1, 0, 0, 0, 0, 0, 1, 0, 1],
[3, 2, 1, 1, 1, 1, 1, 2, 1, 0]])
def pmi(M, positive=True):
col_totals = M.sum(axis=0) # 按列求和
row_totals = M.sum(axis=1) # 按行求和
total = col_totals.sum() # 总频次
expected = np.outer(row_totals, col_totals) / total # 获得每个元素的分子
M = M / expected
with np.errstate(divide='ignore'): # 不显示log(0)的警告:
M = np.log(M)
M[np.isinf(M)] = 0.0 # 将log(0)置为0
if positive:
M[M < 0] = 0.0
return M
M_pmi = pmi(M)
np.set_printoptions(precision=2) # 打印结果保留两位小数
print(M_pmi)
U, s, Vh = np.linalg.svd(M_pmi)
words = ['我', '喜欢', '自然', '语言', '处理', '爱', '深度', '学习', '机器', '。']
for i in range(len(words)):
plt.text(U[i, 0], U[i, 1], words[i])
plt.show()
可见:上下文比较相近的词在空间上的距离比较近,如“深度” “学习”等;而“我”和“。”等高频词则与其他词语距离比较远。
虽然在基于传统机器学习的方法中,词的分布式表示取得了不错的效果,但是其仍然存在一些问题。首先,当共现矩阵规模较大时,奇异值分解的运行速度非常慢;其次,如果想在原来语料库的基础上增加更多的数据,则需要重新运行奇异值分解算法,代价非常高;另外,分布式表示只能用于表示比较短的单元,如词或短语等,如果待表示的单元比较长,如段落、句子等,由于与其共现的上下文会非常少,则无法获得有效的分布式表示;最后,分布式表示一旦训练完成,则无法修改,也就是说,无法根据具体的任务调整其表示方式。为了解决这些问题,可引入一种新的词表示方式——词嵌入表示。
与词的分布式表示相类似,词嵌入表示也使用一个连续、低维、稠密的向量来表示词,经常直接简称为词向量,但于分布式表示不同之处在于其赋值方式。在词的分布式表示中,向量值是通过对语料库进行统计得到的,然后再经过点互信息、奇异值分解等变换,一旦确定则无法修改。而词向量中的向量值,是随着目标任务的优化过程自动调整的,也就是说,可以将词向量中的向量值看作模型的参数。
所谓词袋表示,就是假设文本中的词语是没有顺序的集合,将文本中的全部词所对应的向量表示(既可以是独热表示,也可以是分布式表示或词向量)相加,即构成了文本的向量表示。
语言模型是描述自然语言概率分布的模型,是一个非常基础和重要的自然语言处理任务。
1.N元语言模型
语言模型的基本任务是在给定次序列w1w2...wt-1的条件下,对下一时刻t可能出现的词wt的条件概率进行估计。
马尔可夫假设:“下一个词出现的概率只依赖于它前面n-1个词”, 即
满足这种假设的模型,被称为N元语法或N元文法模型。特别地,当n=1时,下一个词的出现独立于其历史,相应的一元语法通常记作unigram。当n=2时,下一个词只依赖于前1个词,对应的二元语法记作bigram。二元语法模型也被称为一阶马尔可夫链。类似地,三元语法假设(n=3)也被称为二阶马尔可夫假设,相应的三元语法记作trigram。n的取值越大,考虑的历史越完整,在unigram模型中,由于词与词之间相互独立,因此它是与语序无关的。
2.平滑
为了避免“零概率”问题,需要使用平滑技术调整概率估计的结果。
折扣法平滑的基本思想是“损有余而补不足”,即从频繁出现的N-gram中匀出一部分概率并分配给低频次(含零频次)的N-gram,从而使得整体概率分布趋于均匀。
加1平滑是一种典型的折扣法,也被称为拉普拉斯平滑,它假设所有N-gram的频次比实际出现的频次多一次。
对于unigram模型来说,平滑之后的概率可由以下公式计算:
对于bigram模型,则有:
以bigram语言模型为例,使用加平滑之后的条件概率为:
由于引入了马尔可夫假设,导致N元语言模型无法对长度超过N的长距离词语依赖关系进行建模,如果将N扩大,又会带来更严重的数据稀疏问题,同时还会急剧增加模型的参数量,为存储和计算都带来极大的挑战。
3.语言模型性能评价
如何衡量一个语言模型的好坏?一种方法是将其应用于具体的外部任务,并根据该任务上指标的高低对语言模型进行评价。这种方法也被称为“外部任务评价”。
目前最为常用的是基于困惑度的“内部评价”方式。
测试集的概率:
困惑度则为模型分配给测试集中每一个词的概率的几何平均值的倒数:
困惑度越小,意味着单词序列的概率越大,也意味着模型能够更好地解释测试集中的数据。困惑度可以作为一种快速评价语言模型性能的指标。
自然语言处理的一大特点是任务种类纷繁复杂,有多种划分的方式。从处理顺序的角度,可以分为底层的基础任务以及上层的应用任务。
1.中文分词
词是最小的能独立使用的音义结合体,是能够独立运用并能够表达语义或语用内容的最基本单元。为了进行后续的自然语言处理,通常需要首先对不含分隔符的语言进行分词操作。
中文分词就是将一串连续的字符构成的句子分割成词语序列,最简单的分词算法叫作正向最大匹配分词算法,即从前向后扫描句子中的字符串,尽量找到词典中较长的单词作为分词的结果。
def fmm_word_seg(sentence, lexicon, max_len):
"""
sentence: 待分词的句子
lexicon: 词典(所有单词集合)
max_len: 词典中最长单词长度
"""
begin = 0
end = min(begin + max_len, len(sentence))
words = []
while begin < end:
word = sentence[begin:end]
if word in lexicon or end - begin == 1:
words.append(word)
begin = end
end = min(begin + max_len, len(sentence))
else:
end -= 1
return words
def load_dict():
f = open("lexicon.txt") # 词典文件,每行存储一个单词
lexicon = set()
max_len = 0
for line in f:
word = line.strip()
lexicon.add(word)
if len(word) > max_len:
max_len = len(word)
f.close()
return lexicon, max_len
lexicon, max_len = load_dict()
words = fmm_word_seg(input('请输入句子:'), lexicon, max_len)
for word in words:
print(word)
2.子词切分
词形还原或词干提取虽然在一定程度上解决了数据稀疏问题,但是需要人工撰写大量的规则,这种基于规则的方法既不容易扩展到新的领域,也不容易扩展到新的语言上。因此,基于统计的无监督子词切分任务应运而生,并在现代的预训练模型中使用。
所谓子词切分,就是将一个单词切分为若干连续的片段。目前子词切分算法基本的原理都是使用尽量长且频次高的子词对单词进行切分。常用的有字节对编码算法(BPE)。
3.词性标注
词性是词语在句子中扮演的语法角色,也被称为词类。词性可为句法分析、语义理解等提供帮助。词性标注任务是指给定一个句子,输出句子中每个词相应的词性。
词性标注的主要难点在于歧义性,即一个词在不同的上下文可能有不同的词性。
4.句法分析
句法分析的主要目标是给定一个句子,分析句子的句法成分信息。最终的目标是将词序列表示的句子转换成树状结构,从而有助于更准确地理解句子的含义,并辅助下游自然语言处理任务。
典型的句法结构表示方法包含两种——短语结构句法表示和依存结构句法表示。它们的不同点在于依托的文法规则不一样。其中,短语结构句法表示依托上下文无关文法,属于一种层次性的表示方法,而依存结构句法表示依托依存文法。
5.语义分析
自然语言处理的核心任务即是让计算机“理解”自然语言所蕴含的意义,即语义。一般意义上的语义分析指的是通过离散的符号及结构显性地表示语义。根据待表示语言单元粒度以及语义表示方法的不同,语义分析又可以被分为多种形式。
根据词出现的不同上下文,确定其具体含义的自然语言处理任务被称为词义消歧。
由于语言的语义组合性和进化性,无法像词语一样使用词典定义句子、段落或篇章的语义,因此很难用统一的形式对句子等语言单元的语义进行表示。众多的语言学流派提出了各自不同的语义表示形式,如语义角色标注、语义依存分析等。
语义角色标注也称谓词论元结构,即首先识别句子中可能的谓词,然后为每个谓词确定所携带的语义角色。
语义依存分析则利用通用图表示更丰富的语义信息。根据图中节点类型的不同,又可分为两种表示——语义依存图表示和概念语义图表示。语义依存图中的节点是句子中实际存在的词语,在词与词之间创建语义关系边,而概念语义图首先将句子转化为虚拟的概念节点,然后在概念节点之间创建语义关系边。
1.信息抽取
信息抽取是从非结构化的文本中自动提取结构化信息的过程,这种结构化的信息方便计算机进行后续的处理。另外,抽取的结果还可以作为新的知识加入知识库。
信息抽取一般包含一下几个子任务:
命名实体识别是在文本中抽取每个提及的命名实体并标注其类型,一般包括人名、地名和机构名等,也包括专有名称等。在文本中找到提及的命名实体后,往往还需要将这些命名实体链接接到知识库或知识图谱中的具体实体,这一过程被称作实体链接。
关系抽取用于识别和分类文本中提及的实体之间的语义关系。
事件抽取的任务是从文本中识别人们感兴趣的事件以及事件所涉及的时间、地点和人物等关键元素。其中,事件往往使用文本中提及的具体触发词。
事件的发生时间往往比较关键,因此时间表达式识别也被认为是重要的信息抽取子任务,一般包括两种类型的时间:绝对时间和相对时间。使用时间表达归一化将这些时间表达式映射到特定的日期或一天中的时间。
2.情感分析
情感是人类重要的心理认知能力,使用计算机自动感知和处理人类情感已经成为人工智能领域重要的研究内容之一。自然语言处理中的情感分析主要研究人类通过文字表达的情感,因此也称为文本情感分析。
情感分析可以从任务角度分为两个主要的子任务,即情感分类和情感信息抽取。
3.问答系统
问答系统是指系统接受用户以自然语言形式描述的问题,并从异构数据中通过检索、匹配和推理等技术获得答案的自然语言处理系统。根据数据来源的不同,问答系统可以分为4种主要的类型:1)检索式问答系统;2)知识库问答系统;3)常问问题集问答系统;4)阅读理解式问答系统。
4.机器翻译
机器翻译是指利用计算机实现从一种自然语言(源语言)到另外一种自然语言(目标语言)的自动翻译。机器翻译的目标是建立自动翻译方法、模型和系统,打破语言壁垒,最终实现任意时间、任意地点和任意语言之间的自动翻译,完成人们无障碍自由交流的梦想。
机器翻译方法一般以句子为基本输入单位,研究从源语言句子到目标语言句子的映射函数。
5.对话系统
对话系统是指以自然语言为载体,用户与计算机通过多轮交互的方式实现特定目标的智能系统。对话系统主要分为任务型对话系统和开放域对话系统。
任务型对话系统一般由顺序执行的三个模块构成,即自然语言理解、对话管理和自然语言生成。
其中,自然语言理解模块的主要功能是分析用户话语的语义,通常的表示形式为该话语的领域、意图以及相应的槽值等。
对话管理模块包括对话状态跟踪和对话策略优化两个子模块。对话状态一般表示为语义槽和值的列表。
在任务型对话系统里,自然语言生成模块工作相对比较简单,通常通过写模板即可实现。
文本分类是最简单也最基础的自然语言处理问题,即针对一段文本输入,输出该文本所属的类别。其中,类别是事先定义好的一个封闭的集合。
文本匹配是判断两段输入文本之间的匹配关系,包括复述关系、蕴含关系等。
1.序列标注
所谓序列标注,指的是为输入文本序列中的每个词标注相应的标签。条件随机场是一种被广泛应用的序列标注模型,其不但考虑了每个词属于某一标签的概率,还考虑了标签之间的相互关系。
2.序列分割
序列分割问题,就是将字符序列切分成若干连续的子序列;命名实体识别问题,也是在文本序列中切分出子序列,并为每个子序列赋予一个实体的类别。
3.图结构生成
图结构生成输入是自然语言,输出结果是一个以图表示的结构。图中的节点既可以来自原始输入,也可以是新生成的;边连接了两个节点,并可以赋予相应的类型。
图结构生成算法主要包括两大类:基于图的算法和基于转移的算法。
基于图的算法首先为图中任意两个节点构成的边赋予一定的分数,算法的目标是求解出一个满足约束的分数最大的子图。
基于转移的算法将图结构的构建过程转化为一个状态转移序列。
序列到序列问题,输入是一个由若干词组成的序列,输出则是一个新的序列,其中,输入和输出的序列不要求等长,同时也不要求词表一致。
由于序列到序列模型具备强大的建模能力,其已成为自然语言处理的大一统框架,越来越多的问题都可以尝试使用该模型加以解决。也就是说,可以将复杂的自然语言处理问题转化为编码、解码两个子问题,然后就可以分别使用独立的模型建模了。
由于自然语言处理任务的多样性以及评价的主观性,因此很难使用单一的评价指标衡量所有任务的性能,所以针对不同类型的任务,往往采用不同的评价方法。
准确率是最简单、直观的评价指标,经常被应用于文本分类等问题。
F值是精确率和召回率的加权调和平均,具体公式为:
对人机对话系统的评价,虽然也可以利用历史上人人对话数据,采用BLEU值等指标,但是由于回复的开放性,这种自动评价的结果很难保证公正、客观。
人工评价的代价往往非常高,很难在系统开发的过程中多次进行。
人机对话系统的评价方法仍是目前自然语言处理领域一个非常棘手的开放性问题,并没有很好地被解决。