目录
大小写转换
词干还原
词形归并
使用场景
词汇表大小对NLP流水线的性能有很大的影响,有一种减少词汇表大小的方法是将词汇表归一化以便意义相似的词条归并成单个归一化的形式。这样做一方面可以减少需要再词汇表中保留的词条数,另一方面也会提高语料库中意义相似但是拼写不同的词条或者n-gram之间的语义关联。
大小写转换
当两个单词只有大小写形式不同时,大小写转换会用来把笔不同的大小写形式进行统一处理,(英文中)当单词出现在了句首或者为了表示强调均采用大写形式来表示时,某个单词的大小写变得不太统一。将这种不统一的大小写形式统一化则称为大小写归一化,或是大小写转换。将单词或字符的大小写统一是一种减小词汇表规模的方法,可以推广到NLP的流水线。它有助于将意义相同的单词统一化为单个词条。
但是,单词的大写有时也包含了一些特定的含义,例如“doctor”和“Doctor”往往具有不同的含义。大写单词有时也表示其是一个专有名词,比如人名、地名或是事物的名称。如果命名实体识别对NLP流水线而言很重要的话,我们就希望能够识别出上面那些不同于其他单词的专有名词。然而如果词条不进行大小写归一化,那么词汇表的规模就大约是原来的两倍,需要消耗的内存和处理时间也大约是原来的两倍,这样可能会增加需要标注的训练数据的数量以保证机器学习流水线收敛到精确地通用解。在机器学习流水线中,标注的用于训练的数据集必须能够代表模型需要处理的所有可能的特征向量所处的空间,包括能够处理大小写的变化情况。对于10000维的词袋向量,通常必须要有100000条甚至更多的标注数据,才能训练出一个不太会发生过拟合的有监督机器学习流水线。在某些情况下,将词汇表规模缩小一般比丢弃部分信息更值当。
在Python中,利用列表解析式能够很方便地对词条进行大小写归一化处理:
tokens=['House','Visitor','Center','center']
normalized_tokens=[x.lower() for x in tokens]
print(normalized_tokens)
如果确信要对整篇文档进行大小写归一化处理,可以在分词前就对文本字符串使用lower()函数进行处理。但是如果这样的话,可能会干扰一些更高级的分词器,这些分词器可以将驼峰式大小写的单词进行分割,比如“WordPerfect”等。到底何时以及如何使用大小写转换,取决于开发者自己。
通过大小写归一化,我们试图在语法规则和词条在句子中的位置影响其大小写之前,将这些词条还原成归一化形式。一种最简单也最常见的文本字符串大小写归一化方法是:利用诸如Python内置的str.lower()函数将所有字符转成小写形式。不幸的是,这种做法除了会将我们希望的那些意义不大的句首大写字母归一化,也会将很多有意义的大小写形式给归一化掉。一个更高的大小写归一化方法是只将句首大写字母转成小写,其他单词保持原有形式。
只将句首字母转成小写可以保留句子中专有名词的含义,如Joe和Smith在句子“Joe Smith”中的情况。这种做法能够正确地将本该在一起的词分成一组,这是因为它们不是专有名字而只在句首时才首字母大写。这种做法可以在分词时将“joe”和“coffee”区分开来。这种做法也能防止一句话当中有“铁匠”含义的“smith”和专有名词“Smith”换在一起。即使采用这种小心谨慎的大小写处理方法,即只将句首的单词转化成小写形式,也会遇到某些情况下专有名词出现在句首而导致的错误。
为了避免上述例子中可能的信息损失,很多NLP流水线根本不进行大小写归一化处理。在很多应用中,将词汇表规模减小一半带来的效率提升会大于专有名词的信息损失。但是,即使不进行大小写归一化处理,有些信息也会损失。如果不将句首的“The”识别为停用词,对有些应用来说可能会带来问题。拥有真正完善手段的流水线会在选择性地归一化那些出现在句首但明显不是专有名词的词之前,先检测出专有名词。我们可以使用任何对应用有意义的大小写处理方法。比如语料库中的“Smith's”和“word smiths”不太多,我们也不关系它们是否要归一化成一个词条,那么就可以将所有文本都转成小写形式。最好的方法就是尝试多种不同做法,看看到底哪一种做法在NLP项目中能获得最高性能。
为了让模型能够处理那些出现古怪大小写形式的文本,大小写归一化可以减少对机器学习流水线的过拟合情况。大小写归一化对搜索引擎来说尤为有用。对搜索而言,归一化能够增加对特定查询找到的匹配数,这也称为搜索引擎的召回率。
对于一个没有进行大小写归一化的搜索引擎,如果搜索“Age”会得到和搜索“age”不一样的文档集合。通过将搜索索引中的词汇表归一化,无论输入查询的大小写如何,都可以保证两类有关“age”的文档均被返回。
但是,上述召回率的额外升高会造成正确率降低,此时对于返回的很多文档,用户并不感兴趣。基于这个原因,现代搜索引擎一般允许用户关闭查询的大小写归一化选项,通常的做法是将需要精确匹配的词用双银号引起来。如果要构建这样的搜索引擎流水线,以便处理上述两种查询,就需要为文档建立两个索引:一个索引将n-gram进行大小写归一化处理,而另一个则采用原始的大小写形式。
词干还原
另一种常用的词汇表归一化技术是消除词的复数形式、所有格的词尾甚至不同的动词形式等带来意义上的微小差别。这种识别词的不同形式背后的公共词干的归一化方法称为词干还原。例如,housing和houses的公共词干是house。词干还原过程会去掉词的后缀,从而试图将具有相似意义的词并到其公共词干。不一定要求词干必须是一个拼写正确的词,而只需要是一个能够代表词的多种可能拼写形式的词条或者标签。
对机器而言,需要某种条件来告诉它“house”和“houses”分别是同一名词的单数和复数形式。词干还原的主要好处之一就是:机器中的软件或者语言模型所需记录其意义的词的个数得以压缩。它在限制信息或意义损失的同时,会尽可能减小词汇表的规模,这在机器学习中称为降维。它能够帮助泛化语言模型,使模型能够在属于同一词干的词上表现相同。因此,只要我们的应用中不需要机器区分house和houses,词干还原就可以将程序或数据集的规模减小一半甚至更多,减小的程度依赖所选词干还原工具的激进程度。
词干还原对关键词搜索或信息检索十分重要。通过词干还原可以拓宽搜索结果,这样可以确保丢失相关文档或者网页的可能性减小。这种拓宽搜素结果的方法会极大地提高搜索的召回率得分,召回率是度量搜索引擎返回所有相关文档的程度的一个指标。
然而,词干还原可能会大幅度降低搜索引擎的正确率得分,这是因为在返回相关文档的同时可能反悔了大量不相关文档。在一些应用中,假阳率会是一个问题。因此,大部分搜索引擎可以通过对词或短语加双引号的方式关闭词干还原甚至大小写转换这些选项。加双引号意味着返回页面必须包含短语的精确拼写形式。
下面是一个使用纯Python实现的词干还原的简单示例,该示例可以处理词尾的s:
def stem(phrase):
return ' '.join([re.findall('^(.*ss|.*?)(s)?$',word)[0][0].strip("'") for word in phrase.lower().split()])
print(stem('house'))
print(stem("Doctor House's calls"))
上面的词干还原函数使用了一个短的正则表达式来遵守如下的一些简单规则:
- 如果词结尾不止一个s,那么词干词本身,后缀是空字符串;
- 如果词结尾只有一个s,那么词干就是去掉s后的词,后缀是字符s;
- 如果词结尾不是s,那么词干就是词本身,不返回任何后缀。
上面的strip方法能够确保一些词的所有格和复数形式能够被词干还原。
上述函数可以处理常规函数,但是无法处理更复杂的情况。例如,上述规则遇到dishes或者herose就会失效。针对这种更复杂的情况,NLTK包提供了其他词干还原工具。
两种最流行的词干还原工具分别是Porter和Snowball。这些词干还原工具使用了比单个正则表达式更复杂的规则,这样就能够处理更复杂的英文拼写和词尾情况:
from nltk.stem.porter import PorterStemmer
stemmer=PorterStemmer()
s=' '.join([stemmer.stem(w).strip("'") for w in "dish washer's washes dishes".split()])
print(s)
需要注意的是,像上面的正则表达式词干还原工具一样,Porter保留了词尾的撇号('),这样就能把所有格形式和非所有格形式的词区分开来。所有格名词往往都是专有名词,因此这个特性对于那些要将人名和其他名词区分开来的应用来说非常重要。
词形归并
如果知道词义之间可以互相关联,那么可能就能将一些词关联起来,即使它们的拼写完全不一样。这种更粗放的将词归一化成语义词根即词元的方式称为词形归并。
对于任何一个NLP流水线,如果想要对相同语义词根的不同拼写形式都做出统一回复的话,那么词形归并工具就很有用,它会减少必须要回复的词的数目,即语言模型的维度。利用词形归并工具,可以让模型更一般化,当然也可能带来模型精确率的降低,因为它会对同一词根的不同拼写形式一视同仁。例如,即使它们的意义不同,在NLP流水线中使用词形归并的情况下,“chat”、“chatter”、“chatty”甚至“chatbot”可能也会被同等对待。与此类似的是,尽管“bank”、“banked”和“banking”分别和河岸、汽车和金融有关,但是如果使用了词干还原工具,它们会被同等对待。
如果有一个词,经过词形归并处理之后,可能会彻底改变该词的意思,甚至可能得到意义完全相反的词,从而导致与期望回复相反的结果。这种情形称为“刻意欺骗”,即通过精心构造难以处理的输入,有意使机器学习流水线产生错误的响应。
由于考虑了词义,相对于词干还原和大小写归一化,词形归并是一种潜在的更具精确性的词的归一化方法。通过使用同义词表和词尾相关的知识库,词形归并工具可以确保只有那些具有相似意义的词才会被归并成同一词条。
有些词形归并工具除拼写之外还使用词的词性(POS)标签来提高精准率。词的POS标签代表了该词在短语或句子中的语法角色。例如,名词一般是代表人物、地点、事物的词;形容词常常代表了修饰或者描述名词的词;动词代表动作。只孤立地考虑词本身是无法判断词性的,判断词性要考虑该词的上下文。因此,一些高级的词形归并工具无法在孤立的词上运行。
在很多应用中,词形归并比词干还原有效。词干还原工具实际上仅仅用于大规模信息检索应用(关键词搜索)中。如果我们真的希望在信息检索流水线中通过词干还原工具进行降维和提高召回率,那么可能需要在使用词干还原工具前,先使用词形归并工具。由于词元本身是一个有效的英文词,词干还原工具作为词形归并的输出会很奏效。这种技巧会比单独使用词干还原工具能更好地降维和提高信息检索的召回率。
NLTK包提供了识别词元的相关函数:
#nltk.download('wordnet')
from nltk.stem import WordNetLemmatizer
lemmatizer=WordNetLemmatizer()
print(lemmatizer.lemmatize('better'))
print(lemmatizer.lemmatize('better',pos='a'))
print(lemmatizer.lemmatize('good',pos='a'))
print(lemmatizer.lemmatize('goods',pos='a'))
print(lemmatizer.lemmatize('goods',pos='n'))
print(lemmatizer.lemmatize('goodness',pos='n'))
print(lemmatizer.lemmatize('best',pos='a'))
如果没有给定某个词的词性,NLTK词形归并工具会默认其为名词。
使用场景
词干还原工具通常计算速度比较快,所需要的代码和数据集也更简单。但是,相对于词形归并,词干还原会犯更多错误,会对更多的词进行处理,从而对文本的信息内容及意义的缩减量也更大。无论是词干还是词形归并,都会减小词汇表的规模,同时增加文本的歧义性。但是词形归并工具基于词在文本中的用法和目标词义,能够尽可能地保留文本的信息内容。因此,有些NLP包(比如spaCy)不提供词干还原工具,而只提供词形归并工具。
如果应用中包含搜索过程,那么词干还原和词形归并能够通过将查询词关联到更多文档而提高搜索的召回率。但是,词干还原、词形归并甚至大小写转换将显著降低搜索结果的正确率和精确率。上述词汇表压缩方法会导致信息检索系统(搜索引擎)返回更多与词的原本意义不相关的文档。由于搜索结果可以按照相关度排序,搜索引擎和文档索引常常使用词干还原或词形归并来提高所需文档在搜索结果中出现的可能性。但是,最终搜索引擎会将词干还原前和还原后的检索结果混在一起,通过排序展示给用户。
而对基于搜索的聊天机器人来说,精确率更为重要。因此,聊天机器人会先基于未进行词干还原、未进行词形归并的词来搜索最相近的匹配,只有失败了才转向词干还原或者过滤掉的词条匹配来寻找可能的结果。而词条归一化前的匹配结果的级别高于归一化后的匹配结果。