《用Python进行自然语言处理》第2章 获得文本语料和词汇资源

1. 什么是有用的文本语料和词汇资源,我们如何使用 Python 获取它们?
2. 哪些 Python 结构最适合这项工作?
3. 编写 Python 代码时我们如何避免重复的工作?

2.1 获取文本语料库

古腾堡语料库

import nltk
print(nltk.corpus.gutenberg.fileids()[:5])


#挑选这些文本的第一个 ——简·奥斯丁的《爱玛》——并给它一个简短的名称 emma,然后找出它包含多少个词
emma = nltk.corpus.gutenberg.words('austen-emma.txt')
print(len(emma))


#通过循环遍历前面列出的 gutenberg 文件标识符链表相应的 fileid,然后计算统计每个文本
from nltk.corpus import gutenberg
for fileid in gutenberg.fileids():
    #raw() 函数给我们没有进行过任何语言学处理的文件的内容。告诉我们文本中出现的词汇个数,包括词之间的空格。
    num_chars = len(gutenberg.raw(fileid))
    #print(gutenberg.raw(fileid))
    print(num_chars)
    num_words = len(gutenberg.words(fileid))
    print(gutenberg.words(fileid))
    print(num_words)
    #sents()函数把 文本划分成句子,其中每一个句子是一个词链表。
    print(gutenberg.sents(fileid))
    num_sents = len(gutenberg.sents(fileid))
    num_vocab = len(set([w.lower() for w in gutenberg.words(fileid)]))
    #平均词长、平均句子长度和本文中每个词出现的平均次数
    print(int(num_chars/num_words), int(num_words/num_sents), int(num_words/num_vocab), fileid)
    break

网络和聊天文本

#虽然古腾堡项目包含成千上万的书籍,它代表既定的文学。考虑较不正式的语言也是 很重要的。
#NLTK的网络文本小集合的内容包括 Firefox交流论坛,在纽约无意听到的对话, 《加勒比海盗》的电影剧本,个人广告和葡萄酒的评论
from nltk.corpus import webtext
for fileid in webtext.fileids():
    print(fileid,"内容:" + webtext.raw(fileid)[:10], '...', '\n')
    
    
#即时消息聊天会话语料库
#例如: 10-19- 20s_706posts.xml 包含 2006 年 10 月 19 日从 20 多岁聊天室收集的 706 个帖子。
from nltk.corpus import nps_chat
chatroom = nps_chat.posts('10-19-20s_706posts.xml')
print(chatroom[123])    

布朗语料库

#布朗语料库是第一个百万词级的英语电子语料库,由布朗大学于 1961 年创建。
#这个 语料库包含 500 个不同来源的文本,按照文体分类,如:新闻、社论等。
#以将语料库作为词链表或者句子链表来访问(每个句子本身也是一个词链表 )。 我们可以指定特定的类别或文件阅读
from nltk.corpus import brown
print(brown.categories()[:5])
print(brown.words(categories='news'))
print(brown.words(fileids=['cg22']))



#布朗语料库是一个研究文体之间的系统性差异 ——一种叫做文体学的语言学研究——很方便的资源。
#让我们来比较不同文体中的情态动词的用法

#第一步:产生特定文体的计数
from nltk.corpus import brown
news_text = brown.words(categories='news')
fdist = nltk.FreqDist([w.lower() for w in news_text]) 
modals = ['can', 'could', 'may', 'might', 'must', 'will']
for m in modals:
    print(m + ':', fdist[m], end=" " )
    
#下面,我们来统计每一个感兴趣的文体。我们使用 NLTK 提供的带条件的频率分布函数
#fdist.tabulate()  绘制频率分布表
print()
cfd = nltk.ConditionalFreqDist(
    (genre, word)
    for genre in brown.categories()
    for word in brown.words(categories=genre))
genres = ['news', 'religion', 'hobbies', 'science_fiction', 'romance', 'humor']
modals = ['can', 'could', 'may', 'might', 'must', 'will']
cfd.tabulate(conditions=genres, samples=modals)
#新闻文体中最常见的情态动词是 will,而言情文体中最常见的情态动词是 could

路透社语料库

#路透社语料库包含 10,788 个新闻文档,共计 130 万字。这些文档分成 90 个主题,按照 “训练”和“测试”分为两组。
#因此,fileid 为“test/14826”的文档属于测试组。这样分割 是为了训练和测试算法
from nltk.corpus import reuters
print(reuters.fileids()[:5])#文档
print(len(reuters.fileids()))

print(reuters.categories()[:5])#主题
print(len(reuters.categories()))



#与布朗语料库不同,路透社语料库的类别是有互相重叠的,只是因为新闻报道往往涉及多个主题。
#我们可以查找由一个或多个文档涵盖的主题,也可以查找包含在一个或多个类别 中的文档。
#为方便起见,语料库方法既接受单个的 fileid 也接受 fileids 列表作为参数。
print(reuters.categories('training/9865'))  #该文档涉及好几个主题
print(reuters.categories(['training/9865', 'training/9880']))
print(reuters.fileids('barley')[:5])
print(reuters.fileids(['barley', 'corn'])[:5])



#类似的,我们可以以文档或类别为单位查找我们想要的词或句子。
#这些文本中最开始的几个词是标题,按照惯例以大写字母存储。
print(reuters.words('training/9865')[:5])
print(reuters.words(['training/9865', 'training/9880']))
print(reuters.words(categories=['barley', 'corn']))

就职演说语料库

from nltk.corpus import inaugural
print(inaugural.fileids()[:5])
#每个文本的年代都出现在它的文件名中。要从文件名中获得年代,我们使用 fileid[:4]提取前四个字符。
print([fileid[:4] for fileid in inaugural.fileids()][:5])



#让我们来看看词汇 america 和 citizen 随时间推移的使用情况
cfd = nltk.ConditionalFreqDist(
            (target, fileid[:4])
            for fileid in inaugural.fileids()         
            for w in inaugural.words(fileid)
            for target in ['america', 'citizen']
            #使用 w.lower()将就职演说语料库中的词汇转换成小写。
            # startswith()检查它们是否以“目 标”词汇 america 或 citizen 开始。
            if w.lower().startswith(target))
cfd.plot()

标注文本语料库

许多文本语料库都包含语言学标注,有词性标注、命名实体、句法结构、语义角色等。

在其他语言的语料库

#NLTK 包含多国语言语料库。某些情况下你在使用这些语料库之前需要学习如何在 Python中处理字符编码
print(nltk.corpus.cess_esp.words())
print(nltk.corpus.floresta.words())
print(nltk.corpus.indian.words('hindi.pos'))
print(nltk.corpus.udhr.fileids()[:5])
print(nltk.corpus.udhr.words('Javanese-Latin1')[11:])
#条件频率分布来研究 “世界人权宣言”(udhr)语料库中不同语言版本中的字长差异
from nltk.corpus import udhr
languages = ['Chickasaw', 'English', 'German_Deutsch', 'Greenlandic_Inuktikut', 'Hungarian_Magyar', 'Ibibio_Efik'] 
cfd = nltk.ConditionalFreqDist(
    (lang, len(word))
    for lang in languages
    for word in udhr.words(lang + '-Latin1'))
cfd.plot(cumulative=True)

文本语料库的结构

到目前为止,我们已经看到了大量的语料库结构。最简单的一种 没有任何结构,仅仅是一个文本集合。
通常,文本会按照其可能对应的文体、来源、作者、 语言等分类。
有时,这些类别会重叠,尤其是在按主题分类的情况下,因为一个文本可能与多个主题相关。
偶尔的,文本集有一个时间结构,新闻集合是最常见的例子。
NLTK中定义的基本语料库函数
fi leids() 语料库中的文件
fileids([categories]) 这些分类对应的语料库中的文件
categories() 语料库中的分类
categories([fileids]) 这些文件对应的语料库中的分类
raw() 语料库的原始内容
raw(fi leids=[f 1,f2, f3] ) 指定文件的原始内容
raw(categori es=[c1,c2]) 指定分类的原始内容
words() 整个语料库中的词汇
wor ds( fi leids=[f1, f2, f3]) 指定文件中的词汇
wor ds( categori es=[c1, c2] ) 指定分类中的词汇
sents() 指定分类中的句子
sents (fileids=[f 1,f2, f3] ) 指定文件中的句子
sents(categories=[c1,c2]) 指定分类中的句子
abspath(fileid) 指定文件在磁盘上的位置
encoding(fileid) 文件的编码(如果知道的话)
open(fileid) 打开指定语料库文件的文件流
root() 到本地安装的语料库根目录的路径

raw = gutenberg.raw('burgess-busterbrown.txt')
print(raw[1:20])


words = gutenberg.words('burgess-busterbrown.txt')
print(words[1:10])


sents = gutenberg.sents('burgess-busterbrown.txt')
print(sents[1:3])

载入你自己的语料库

2.2 条件频率分布


当语料文本被分为几类(文体、主题、作者等)时,我们可以计算每个类别独立的频率分布。
条件频率分布是频率分布的集合,每个频率分布有一个不 同的“条件”。这个条件通常是文本的类别

条件和事件

#频率分布计算观察到的事件,如文本中出现的词汇。
#条件频率分布需要给每个时间关联一个条件,所以不是处理一个词序列
text = ['The', 'Fulton', 'County', 'Grand', 'Jury', 'said', ...] 
#我们必须处理的是一个配对序列
pairs = [('news', 'The'), ('news', 'Fulton'), ('news', 'County'), ...]
#每对的形式是:(条件,事件)。如果我们按文体处理整个布朗语料库,将有 15 个条件(每个文体一个条件)和 1,161,192 个事件(每一个词一个事件)。

按文体计数词汇

# #FreqDist( ) 以一个简单的链表作为输入,ConditionalFreqDist( ) 以一个配对链表作为输入。
from nltk.corpus import brown
cfd = nltk.ConditionalFreqDist(
        (genre, word)
        for genre in brown.categories()
        for word in brown.words(categories=genre))



#只看两个文体:新闻和言情。对于每个文体,我们遍历文体中的每个词以产生文体与词的配对。
genre_word = [(genre, word)
             for genre in ['news', 'romance']
             for word in brown.words(categories=genre)]
print(len(genre_word))

#链表 genre_word 的前几个配对将是('news',word)的形式,而最后几个配对将是('romance', word)的形式。
print(genre_word[:4])
print(genre_word[-4:])


#使用此配对链表创建一个 ConditionalFreqDist,并将它保存在一个变 量 cfd 中。
#像往常一样,我们可以输入变量的名称来检查它,并确认它有两个条件
cfd = nltk.ConditionalFreqDist(genre_word)
print(cfd)
print(cfd.conditions())
#访问这两个条件,它们每一个都只是一个频率分布:
print(cfd['news'])
print(cfd['romance'])
print(list(cfd['romance'])[:5])
print(cfd['romance']['could'])

绘制分布图和分布表

#除了组合两个或两个以上的频率分布和更容易初始化之外,ConditionalFreqDist 还为 制表和绘图提供了一些有用的方法。
#条件是词 america 或 citizen ,被绘图的计数是在特定演讲中出现的词的次数。
#它利用了每个演讲的文件名——例如 1 865-Lincoln.txt——的前4个字符包含年代的事实。
#这段代码为文件 1865-Lincoln.txt 中每 个小写形式以america开头的词——如:Americans——产生一个配对('america', '1865')。
from nltk.corpus import inaugural
cfd = nltk.ConditionalFreqDist(
        (target, fileid[:4])
        for fileid in inaugural.fileids()
        for w in inaugural.words(fileid)
        for target in ['america', 'citizen']
        if w.lower().startswith(target))
cfd.plot()



#图中的计数来源于词长 。它利用了每一种语言的文件名是语言名称后面跟 '-Latin1' (字符编码)的事实。
from nltk.corpus import udhr
languages = ['Chickasaw', 'English', 'German_Deutsch', 'Greenlandic_Inuktikut', 'Hungarian_Magyar', 'Ibibio_Efik'] 
cfd = nltk.ConditionalFreqDist(
    (lang, len(word)) 
    for lang in languages
    for word in udhr.words(lang + '-Latin1'))
cfd.plot()



#为两种语言和长度少于 10 个字符的词汇绘制累计频率数据表
cfd.tabulate(conditions=['English', 'German_Deutsch'],
            samples=range(10), cumlative=True)

使用双连词生成随机文本

#我们可以使用条件频率分布创建一个双连词表(词对,在 1.3 节介绍过 )。bigrams()函数 接受一个词汇链表,并建立一个连续的词对链表。
sent = ['In', 'the', 'beginning', 'God', 'created', 'the', 'heaven', 'and', 'the', 'earth', '.']
print(list(nltk.bigrams(sent))[:5])


#产生随机文本:此程序获得《创世记》文本中所有的双连词,然后构造一个条件频率分布来记录哪些词汇最有可能跟在给定词的后面 ;
#例如:living 后面最可能的词是 creature;generate_model()函数使用这些数据和种子词随机产生文本。
def generate_model(cfdist, word, num=15):
    for i in range(num):
        print(word, end=" ")
        word = cfdist[word].max()
text = nltk.corpus.genesis.words('english-kjv.txt')
bigrams = nltk.bigrams(text)
cfd = nltk.ConditionalFreqDist(bigrams)

print(cfd['living'])

print(generate_model(cfd, 'living'))
NLTK 中的条件频率分布 :定义、访问和可视化一个计数的条件频率分布的常用方法和习惯用法
cfdist= ConditionalFreqDist(pairs) 从配对链表中创建条件频率分布
cf di st.condi tions() 将条件按字母排序
cfdist[condition] 此条件下的频率分布
cfdist[condition][sample] 此条件下给定样本的频率
cfdist.tabulate() 为条件频率分布制表
cfdist.tabulate(samples, conditions) 指定样本和条件限制下制表
cfdist.plot() 为条件频率分布绘图
cfdist.plot(samples, conditions) 指定样本和条件限制下绘图
cfdist1 < cfdist2 测试样本在cfdist1中出现次数是否小于在cfdist2中出现次数

2.3 更多关于 Python:代码重用

使用文本编辑器创建程序

函数

#个Python函数:这个函数试图生成任何英语名词的复数形式。
def plural(word):
    if word.endswith('y'):
        return word[:-1] + 'ies'
    elif word[-1] in 'sx' or word[-2:] in ['sh', 'ch']:
        return word + 'es' 
    elif word.endswith('an'):
        return word[:-2] + 'en' 
    else:
        return word + 's'
print(plural('fairy'))
print(plural('woman'))

模块

#在一个文件中的变量和函数定义的集合被称为一个Python 模块(module)。相关模块的集合称为一个包(package)。
#处理布朗语料库的NLTK代码是一个模块,处理各种不同的语料库的代码的集合是一个包。NLTK 的本身是包的集合,有时被称为一个库(library)。

2.4 词典资源

词汇列表语料库

#过滤文本:此程序计算文本的词汇表,然后删除所有在现有的词汇列表中出现的元素,只留下罕见或拼写错误的词。
def unusual_words(text):
    text_vocab = set(w.lower() for w in text if w.isalpha())
    english_vocab = set(w.lower() for w in nltk.corpus.words.words())
    unusual = text_vocab.difference(english_vocab)
    return sorted(unusual)

print(unusual_words(nltk.corpus.gutenberg.words('austen-sense.txt'))[:5])
print(unusual_words(nltk.corpus.nps_chat.words())[:5])



#还有一个停用词语料库,就是那些高频词汇,如:the,to,我们有时在进一步的处理之前想要将它们从文档中过滤。
#停用词通常几乎没有什么词汇内容,而它们的出现会使区分文 本变困难。
from nltk.corpus import stopwords
print(stopwords.words('english')[:5])


#让我们定义一个函数来计算文本中没有在停用词列表中的词的比例。
def content_fraction(text):
    stopwords = nltk.corpus.stopwords.words('english')
    content = [w for w in text if w.lower() not in stopwords]
    return len(content) / len(text)
print(content_fraction(nltk.corpus.reuters.words()))



#另一个词汇列表是名字语料库,包括 8000 个按性别分类的名字。
#男性和女性的名字存储在单独的文件中。让我们找出同时出现在两个文件中的名字即性别暧昧的名字:
names = nltk.corpus.names
print(names.fileids())
male_names = names.words('male.txt')
female_names = names.words('female.txt')
print([w for w in male_names if w in female_names][:5])


#正如大家都知道的,以字母 a 结尾的名字几乎都是女性。我们可以在图中看到这一点以及一些其它的模式,该图是由下面的代码产生的
#条件频率分布:此图显示男性和女性名字的结尾字母;多数以 a,e 或 i 结尾的名字是女性;
#以 h 和 l 结尾的男性和女性同样多;以 k,o,r,s 和 t 结尾的更可能是男性。
cfd = nltk.ConditionalFreqDist(
        (fileid, name[-1])
        for fileid in names.fileids()
        for name in names.words(fileid))
cfd.plot()

发音的词典

#一个稍微丰富的词典资源是一个表格(或电子表格),在每一行中含有一个词加一些性质。
#NLTK 中包括美国英语的 CMU 发音词典,它是为语音合成器使用而设计的。
entries = nltk.corpus.cmudict.entries()
print(len(entries))
for entry in entries[39943:39951]:
    print(entry)
    
    
#每个条目由两部分组成,我们可以用一个复杂的 for 语句来一个一个的处理这些。
#我们没有写 for entry in entries:,而是用两个变量名 word 和 pron 替换 entry。
#现在,每次通过循环时,word 被分配条目的第一部分,pron 被分配条目的第二部分:
for word, pron in entries:
    if len(pron) == 3:
        ph1, ph2, ph3 = pron
        if ph1 == 'P' and ph3 == 'T':
            print(word, ph2)
            
            
#通过指定词典的名字后面跟一个包含在方括号 里的关键字(例如:词 fire)来查词典
prondict = nltk.corpus.cmudict.dict()
prondict['file']

比较词表

#表格词典的另一个例子是比较词表。NLTK中包含了所谓的斯瓦迪士核心词列表(Swa desh wordlists),
#几种语言中约 200 个常用词的列表。语言标识符使用 ISO639 双字母码。
from nltk.corpus import swadesh
print(swadesh.fileids()[:5])
print(swadesh.words('en')[:5])

#可以通过在 entries()方法中指定一个语言链表来访问多语言中的同源词。更进一步,我们可以把它转换成一个简单的词典
fr2en = swadesh.entries(['fr', 'en'])
print(fr2en[:5])
translate = dict(fr2en)
print(translate['chien'])
print(translate['jeter'])

词汇工具: Toolbox 和 Shoebox

# Toolbox文件由一个大量条目的集合组成,其中每个条目由一个或多个字段组成。
#大多数字段都是可选的或重复的,这意味着这个词汇资源不能作为一个表格或电子表格来处 理。
#条目包括一系列的属性-值对,如('ps', 'V'),表示词性是'V'(动词),('ge', 'gag')表示英文注释是'gag'
from nltk.corpus import toolbox
print(toolbox.entries('rotokas.dic')[:1])

2.5 WordNet

WordNet 是面向语义的英语词典,类似与传统辞典,但具有更丰富的结构
NLTK 中包 括英语 WordNet,共有 155,287 个词和 117,659 个同义词集合。
我们将以寻找同义词和它们 在 WordNet 中如何访问开始。

意义与同义词

#如果我们用 automobile 替换掉(1a)中的词 motorcar,变成(1b),句子的意 思几乎保持不变:
# a. Benz is credited with the invention of the motorcar. 
# b. Benz is credited with the invention of the automobile.
#因为句子中所有其他成分都保持不变,我们可以得出结论:motorcar 和 automobile 有相 同的含义即它们是同义词。


#在 WordNet的帮助下,我们可以探索这些词:
#motorcar 只有一个可能的含义,它被定义为 car.n.01,car 的第一个名词意义。car.n.01 被称为 synset 或“同义词集”
from nltk.corpus import wordnet as wn
print(wn.synsets('motorcar'))

print(wn.synset('car.n.01').lemma_names()) #注意原书代码有误

#同义词集中的每个词可以有多种含义,同义词集也有一些一 般的定义和例句:
print(wn.synset('car.n.01').definition())
print(wn.synset('car.n.01').examples())


#虽然定义帮助人们了解一个同义词集的本意,同义词集中的词往往对我们的程序更有用
#为了消除歧义,我们将这些词标注为 car.n.01.automobile,car.n.01.motorcar等。 这种同义词集和词的配对叫做词条。
# 得到指定同义词集的所有词条
print(wn.synset('car.n.01').lemmas())
# 查找特定的 词条
print(wn.lemma('car.n.01.automobile'))
# 得到一个词条对应的同义词集
print(wn.lemma('car.n.01.automobile').synset())
# 得到一个词条的“名字
print(wn.lemma('car.n.01.automobile').name())


#与词 automobile 和 motorcar 这些意义明确的只有一个同义词集的词不同,词 car 是含糊的,有五个同义词集:
print(wn.synsets('car'))
for synset in wn.synsets('car'):
    print(synset.lemma_names())
#为方便起见,我们可以用下面的方式访问所有包含词 car 的词条。
print(wn.lemmas('car'))

WordNet 的层次结构

#WordNet的同义词集对应于抽象的概念,它们并不总是有对应的英语词汇。
#这些概念在 层次结构中相互联系在一起。一些概念也很一般,如实体、状态、事件;这些被称为独一无 二的根同义词集。

#WordNet使在概念之间漫游变的容易。例如:一个如摩托车这样的概念,我们可以看到 它的更加具体(直接)的概念——下位词。
motorcar = wn.synset('car.n.01')
types_of_motorcar = motorcar.hyponyms()
print(types_of_motorcar[26])
print(sorted([lemma.name() for synset in types_of_motorcar for lemma in synset.lemmas()]))

#可以通过访问上位词来浏览层次结构。有些词有多条路径,因为它们可以归类在一个以上的分类中。
#car.n.01与 entity.n.01之间有两条路径,因为 wheeled_vehicle.n. 01 可以同时被归类为车辆和容器。
print(motorcar.hypernyms())
paths = motorcar.hypernym_paths()
print(len(paths))
print([synset.name() for synset in paths[0]][:5]) 
print([synset.name() for synset in paths[1]][:5]) 

更多的词汇关系

#上位词和下位词被称为词汇关系,因为它们是同义集之间的关系,这个关系定位上下为 “是一个”层次。
#例如:一棵树的部分是它的树干,树冠等;这些都是 par t_meronyms()。一棵树的实质是包括心材和边材组成的,即 substance_meronyms()。 树木的集合形成了一个森林,即 member_holonyms():
print(wn.synset('tree.n.01').part_meronyms())
print(wn.synset('tree.n.01').substance_meronyms())
print(wn.synset('tree.n.01').member_holonyms())

语义相似度

#我们已经看到同义词集之间构成复杂的词汇关系网络。给定一个同义词集,我们可以遍 历 WordNet 网络来查找相关含义的同义词集。
#知道哪些词是语义相关的,对索引文本集合 非常有用,当搜索一个一般性的用语——
#例如:车辆——时就可以匹配包含具体用语——例 如豪华轿车——的文档。

#如果两个同义 词集共用一个非常具体的上位词——在上位词层次结构中处于较低层的上位词——它们一 定有密切的联系。
right = wn.synset('right_whale.n.01') 
orca = wn.synset('orca.n.01')
minke = wn.synset('minke_whale.n.01') 
tortoise = wn.synset('tortoise.n.01') 
novel = wn.synset('novel.n.01')
print(right.lowest_common_hypernyms(minke))
print(right.lowest_common_hypernyms(orca))

2.6 小结


文本语料库是一个大型结构化文本的集合。NLTK包含了许多语料库,如:布朗语料库 nltk.corpus.brown。
有些文本语料库是分类的,例如通过文体或者主题分类,有时候语料库的分类会相互重叠。
条件频率分布是一个频率分布的集合,每个分布都有一个不同的条件。它们可以用于通 过给定内容或者文体对词的频率计数。
行数较多的 Python 程序应该使用文本编辑器来输入,保存为.py 后缀的文件,并使用 import 语句来访问。
Python 函数允许你将一段特定的代码块与一个名字联系起来,然后重用这些代码想用多少次就用多少次。
一些被称为“方法”的函数与一个对象联系在起来,我们使用对象名称跟一个点然后跟 方法名称来调用它,就像:x.funct(y)或者 word.isalpha()。
要想找到一些关于变量 v 的信息,可以在 Pyhon 交互式解释器中输入 help(v)来阅读这 一类对象的帮助条目。
WordNet是一个面向语义的英语词典,由同义词的集合—或称为同义词集(synsets)— 组成,并且组织成一个网络。
默认情况下有些函数是不能使用的,必须使用 Python 的 i mport 语句来访问。

你可能感兴趣的:(自然语言处理,python)