第16单元 处理自然语言中的文本
根据经验,所有潜在的可用数据中,约80%的数据是非结构化的,其中包括音频、视频、图像(这些内容超出了本书的范围)和用自然语言编写的文本6。自然语言中的文本没有标签、分隔符和数据类型,但它仍然是丰富的信息源。有时我们想知道某些词是否出现在文本中及出现的频率(词句标记),文本属于什么类别(文本分类),它传达了正面的还是负面的消息(情感分析),文本中提到的人和物(实体提取),等等。如果只是一两个文本,我们尚且可以靠肉眼来读取和处理,但对于大规模的文本分析,就必须借助于自然语言处理(NLP)。
6www.informationweek.com/software/information-management/structure-models-and-meaning/d/d-id/1030187
Python的nltk模块(自然语言工具包)中实现了很多NLP的功能。该模块围绕语料库(字词和表达式的集合)、函数和算法进行组织。
NLTK语料库
语料库是单词或表达式的结构化或非结构化集合。所有NLTK语料库都存储在模块nltk.corpus中。下面是一些例子。
gutenberg——包含来自古登堡计划的十八个英语文本,例如《白鲸》和《圣经》。
names——8000名男性和女性名字的列表。
words——235 000个最常用的英语单词和表单的列表。
stopwords——以十四种语言列出的停用词(即最常用的词)列表。英文列表位于stopwords.words("english")中。大多数的文本分析都要删除停用词,因为通常它们对于文本的理解不会带来新的信息。
cmudict——诞生在卡内基梅隆大学的发音字典(也因此而得名),有134 000多个条目。cmudict.entries()中的每个条目都是一个单词和一个音节列表组成的元组,同一个词可能有几种不同的发音。这个语料库可用于识别同音词(soundalikes)。
nltk.corpus.wordnet对象是另一个语料库的接口——一个在线语义词网络WordNet(需要访问互联网)。该网络是用词性和序列号标记的同义词集合:
wn = nltk.corpus.wordnet # 语料库读取器 wn.synsets("cat") ➾ [Synset('cat.n.01'), Synset('guy.n.01'), «more synsets»]
可以查找每个同义词的定义,这可能是一个意想不到的功能:
wn.synset("cat.n.01").definition() wn.synset("cat.n.02").definition() ➾ 'feline mammal usually having thick soft fur «...»' ➾ 'an informal term for a youth or man'
同义词可以具有上义词(含义较为抽象的同义词)和下义词(含义较为具体的同义词),这些功能使得同义词看起来像具有子类和超类的面向对象(OOP)类。
wn.synset("cat.n.01").hypernyms() wn.synset("cat.n.01").hyponyms() ➾ [Synset('feline.n.01')] ➾ [Synset('domestic_cat.n.01'), Synset('wildcat.n.03')]
最后,可以使用WordNet来计算两个同义词组之间的语义相似性。相似度是范围[0...1]中的双精度数。如果相似性为0,则同义词是无关的;如果相似性为1,则同义词是完全同义词。
x = wn.synset("cat.n.01") y = wn.synset("lynx.n.01") x.path_similarity(y) ➾ 0.04
上面提到的是两个词之间的相似性,那么任意两个词之间的相似性如何分析呢?让我们来看看“dog”和“cat”的所有同义词,并找到语义最接近的定义:
[simxy.definition() for simxy in max( (x.path_similarity(y), x, y) for x in wn.synsets('cat') for y in wn.synsets('dog') if x.path_similarity(y) # 确保synsets是相关的 )[1:]] ➾ ['an informal term for a youth or man', 'informal term for a man']
真是太神奇了!
除了使用标准语料库,还可以通过PlaintextCorpusReader创建自己的语料库。PlaintextCorpusReader会在根目录中查找与glob模式匹配的文件。
myCorpus = nltk.corpus.PlaintextCorpusReader(root, glob)
函数fileids()返回包含在新创建的语料库中的文件列表。函数raw()返回语料库中原始的“原始”文本。函数sents()返回所有句子的列表。函数words()返回所有单词的列表。在下一节中,你将领略到把原始文本转换为句子和单词的神奇魔法。
myCorpus.fileids() myCorpus.raw() myCorpus.sents() myCorpus.words()
使用下一小节介绍的函数并结合Counter对象(参考第7单元),可以计算单词出现的频率并识别最常用的单词。
NLTK是一个空模块
安装模块NLTK时,实际上只安装了类,而未安装语料库。该语料库太大,以至于被认为无法包含在发行版中。第一次导入模块时,需要调用download()函数(需要连接互联网),并根据需求安装缺失的部件。
规范化
规范化是用自然语言的表达形式对文本进行整理,以便进一步处理的过程。规范化通常包括以下步骤(不一定严格参照这些步骤,但基本上是按此顺序处理的)。
(1) 分词(将文本切分成单词)。NLTK提供了两个简单的和两个较为高级的分词器。句子切分器返回一个列表,列表元素为句子字符串。其他所有分词器返回的都是一个单词列表:
word_tokenize(text)——切分单词
sent_tokenize(text)——切分句子
regexp_tokenize(text, re)——基于正则表达式的分词,参数re是描述有效字的正则表达式
根据分词器的质量和句子的标点结构,有些“词”可以包含非字母字符。对于严重依赖于标点符号的分析任务(例如通过表情符号的情感分析),需要用到更为高级的WordPunctcTokenizer。下面是WordPunctTokenizer.tokenize()和word_tokenize()对相同文本解析结果的对比:
from nltk.tokenize import WordPunctTokenizer word_punct = WordPunctTokenizer() text = "}Help! :))) :[ ..... :D{" word_punct.tokenize(text) ➾ ["}", "Help", "!", ":)))", ":[", ".....", ":", "D", "{"] nltk.word_tokenize(text) ➾ ["}", "Help", "!", ":", ")", ")", ")", ":", "[", "...", ➾ "..", ":", "D", "{"]
(2) 将单词转换为大小写相同的字符(全部大写或小写)。
(3) 删除停用词。使用语料库中的停用词和其他特定的停用词列表作为参考。需要注意的是,停用词使用的是小写字母。语料库中并不存在“THE”这个停用词(虽然它肯定是停用词)。
(4) 词干化处理(将各种词形转换为词干)7。NLTK提供两个基本的词干分析器:较为保守的Porter和较为激进的Lancaster。由于其激进的规则,Lancaster会产生更多的音形相近但含义不同的词干。两个分析器都有stem(word),这个函数会返回所谓的词干:
7对于英文而言,所谓的词干化处理主要包括把名词的复数形式变成单数,将其他时态变成基本形态等类似的处理。——译者注
pstemmer = nltk.PorterStemmer() pstemmer.stem("wonderful") ➾ 'wonder' lstemmer = nltk.LancasterStemmer() lstemmer.stem("wonderful") ➾ 'wond'
两个词干分析器的特点在于:仅将词干分析应用于单个单词,而不是完整短语。
(5) 词形还原——一种速度更慢也更保守的词干分析机制。WordNetLemmatizer查找WordNet中算出的词干,且仅接受单词或表格形式的词干。(要使用lemmatizer,需要访问互联网。)函数lemmatize(word)返回word表示的词的词元。
lemmatizer = nltk.WordNetLemmatizer() lemmatizer.lemmatize("wonderful") ➾ 'wonderful'
另外,虽然在技术层面上,词性标注(POS标注)并不属于规范化的范畴,但它也是文本预处理中的重要步骤。函数nltk.pos_tag(text)为文本中的每个单词分配一个词性标签,其中文本是一个单词列表。函数的返回值是一个元组列表,其中元组的第一个元素是原始单词,第二个元素是标签。
nltk.pos_tag(["beautiful", "world"]) # 一个形容词和一个名词 ➾ [('beautiful', 'JJ'), ('world', 'NN')]
下面的代码从文件index.html中找出了10个最常用的非停用词的词干,这个例子涵盖了上面提到的知识点,以及在第13单元介绍的BeautifulSoup。
from bs4 import BeautifulSoup from collections import Counter from nltk.corpus import stopwords from nltk import LancasterStemmer # 创建一个新词干 ls = nltk.LancasterStemmer() # 读取文件并生成soup with open("index.html") as infile: soup = BeautifulSoup(infile) # 提取并标记文本 words = nltk.word_tokenize(soup.text) # 转换为小写 words = [w.lower() for w in words] # 删除停止单词,并分析剩余部分的词干 words = [ls.stem(w) for w in text if w not in stopwords.words("english") and w.isalnum()] # 对词进行计数 freqs = Counter(words) print(freqs.most_common(10))
本段代码可以作为实现主题抽取的第一步。
其他文本处理程序
关于其他高级NLP程序的讨论超出了本书范围,此处简要地罗列几项。
分段——在没有特定的单词边界语法的文本中(例如,中文文本中)对“单词”的边界进行识别。可以将分段方法应用于任何字符或数字序列(例如,购物清单、DNA片段等)。
文本分类——根据预设的条件将文本划分到某一已知类别中。情绪分析是文本分类的一种特殊情况,通常以情绪词的出现频率作为分析的基础。
实体提取——检测具有预定属性的单词或短语,通常指实体,例如人、地理位置、公司、产品和品牌。
潜在语义索引——使用奇异值分解(SVD)在非结构化文本集合中识别出术语与概念之间关系的模式。顺便指出,SVD在统计学上又被称为主成分分析(PCA)。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论