《用Python进行自然语言处理》代码笔记(四):第五章 分类和标注词

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author  : Peidong
# @Site    : 
# @File    : eg5.py
# @Software: PyCharm
"""
分类和标注词
"""
# 将词汇按它们的词性(parts-of-speech,POS)分类以及相应的标注它们的过程被称为词
# 性标注(part-of-speech tagging, POS tagging)或干脆简称标注。词性也称为词类或词汇范
# 畴。用于特定任务的标记的集合被称为一个标记集。
import nltk

text = nltk.word_tokenize("And now for something completely different")
print(nltk.pos_tag(text))
and 是CC,并列连词;now 和completely 是RB,副词;for 是IN,介
词;something 是NN,名词;different 是JJ,形容词。

# 查看标记的解释文档
print(nltk.help.upenn_tagset('RB'))

text = nltk.word_tokenize("They refuse to permit us to obtain the refuse permit")
print(nltk.pos_tag(text))

text = nltk.Text(word.lower() for word in nltk.corpus.brown.words())
print(text.similar('woman'))
text.similar()方法为一个词w 找出所有上下文w1ww2,然
# 后找出所有出现在相同上下文中的词w',即w1w'w2。
print(text.similar('bought'))

# 一个已标注的标识符使用一个由标识符和标记组成的元组来表示。可以使用函数str2tuple()从
# 表示一个已标注的标识符的标准字符串创建一个这样的特殊元组
tagged_token = nltk.tag.str2tuple('fly/NN')
print(tagged_token)
print(tagged_token[0])
print(tagged_token[1])

# 读取已标注的语料库
print(nltk.corpus.brown.tagged_words())
用tagset = 'universal'替换里面的simplify_tags=True就可以运行了
print(nltk.corpus.nps_chat.tagged_words(tagset='universal'))

# 查看布朗语料库中新闻类最常见的标注
from nltk.corpus import brown
brown_news_tagged = brown.tagged_words(categories='news', tagset='universal')
tag_fd = nltk.FreqDist(tag for (word, tag) in brown_news_tagged)
print(tag_fd.keys())
tag_fd.plot(cumulative=True)

word_tag_pairs = nltk.bigrams(brown_news_tagged)
print(list(nltk.FreqDist(a[1] for (a, b) in word_tag_pairs if b[1] == 'N')))

# 新闻文本中最常见的动词是什么
wsj = nltk.corpus.treebank.tagged_words()
word_tag_fd = nltk.FreqDist(wsj)
print([word + "/" + tag for (word, tag) in word_tag_fd if tag.startswith('V')])
cfd1 = nltk.ConditionalFreqDist(wsj)
print(cfd1['yield'].keys())
print(cfd1['cut'].keys())

# 简单字典操作
pos = {'colorless': 'ADJ', 'ideas': 'N', 'sleep': 'V', 'furiously': 'ADV'}
print(pos.keys())
print(pos.values())
print(pos.items())
for key, val in sorted(pos.items()):
    print(key + ":", val)

# 利用字典计数出现的次数
# 首先初始化一个空的defaultdict,然后处理文本中每个词性标注,如果标记以前没见过,,
# 就默认计数为零,每次遇到一个标记,就使用+=运算符递增它的计数
counts = nltk.defaultdict(int)
from nltk.corpus import brown
for (word, tag) in brown.tagged_words(categories='news'):
    counts[tag] += 1

print(counts['NN'])
print(list(counts))

from operator import itemgetter
print(sorted(counts.items(), key=itemgetter(1), reverse=True))
print([t for t, c in sorted(counts.items(), key=itemgetter(1), reverse=True)])
# sorted()的第一个参数是要排序的项目,它是由一个POS 标记和一个频率组成的元组的链表。
# 第二个参数使用函数itemgetter()指定排序键。在一般情况下,itemgetter(n)返回一个函数,
# 这个函数可以在一些其他序列对象上被调用获得这个序列的第n 个元素的。
# sorted()的最后一个参数指定项目是否应被按相反的顺序返回,即频率值递减。


# 根据最后两个字母索引词汇
last_letters = nltk.defaultdict(list)
words = nltk.corpus.words.words('en')
for word in words:
    key = word[-2:]
    last_letters[key].append(word)

print(last_letters['ly'])
print(last_letters['zy'])

# 创建一个颠倒顺序的词字典
words = nltk.corpus.words.words('en')
anagrams = nltk.Index((''.join(sorted(w)), w) for w in words)   # 搞清楚join的用法
print(anagrams['aeilnrt'])


# 自动标注词汇
# 加载数据
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_sents = brown.sents(categories='news')

# 输出最有可能的标记词性
tags = [tag for (word, tag) in brown.tagged_words(categories='news')]
print(nltk.FreqDist(tags).max())

# 创建标注器,将所有的词都标注为NN
raw = 'I do not like green eggs and ham, I do not like them Sam I am!'
tokens = nltk.word_tokenize(raw)
default_tagger = nltk.DefaultTagger('NN')
print(default_tagger.tag(tokens))
# # 计算标注的正确率
print(default_tagger.evaluate(brown_tagged_sents))

# 正则表达式标注器
patterns = [
 (r'.*ing$', 'VBG'), # gerunds
 (r'.*ed$', 'VBD'), # simple past
 (r'.*es$', 'VBZ'), # 3rd singular present
 (r'.*ould$', 'MD'), # modals
 (r'.*\'s$', 'NN$'), # possessive nouns
 (r'.*s$', 'NNS'), # plural nouns
 (r'^-?[0-9]+(.[0-9]+)?$', 'CD'), # cardinal numbers
 (r'.*', 'NN') # nouns (default)
]
regexp_tagger = nltk.RegexpTagger(patterns)
print(regexp_tagger.tag(brown_sents[3]))
print(regexp_tagger.evaluate(brown_tagged_sents))  # 20%

# 查询标注器
# 找出100 个最频繁的词,存储它们最有可能的标记,构建查找标注器模型
fd = nltk.FreqDist(brown.words(categories='news'))
cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories='news'))
most_freq_words = fd.keys()[:100]     # 此处有问题尚未解决
likely_tags = dict((word, cfd[word].max()) for word in most_freq_words)
baseline_tagger = nltk.UnigramTagger(model=likely_tags)
print(baseline_tagger.evaluate(brown_tagged_sents))

# 查找标注器的性能,使用不同大小的模型
def performance(cfd, wordlist):
    lt = dict((word, cfd[word].max()) for word in wordlist)
    baseline_tagger = nltk.UnigramTagger(model=lt, backoff=nltk.DefaultTagger('NN'))
    return baseline_tagger.evaluate(brown.tagged_sents(categories='news'))
def display():
    import pylab
    words_by_freq = list(nltk.FreqDist(brown.words(categories='news')))
    cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories='news'))
    sizes = 2 ** pylab.arange(15)
    perfs = [performance(cfd, words_by_freq[:size]) for size in sizes]
    pylab.plot(sizes, perfs, '-bo')
    pylab.title('Lookup Tagger Performance with Varying Model Size')
    pylab.xlabel('Model Size')
    pylab.ylabel('Performance')
    pylab.show()

display()

# 一元标注模型(Unigram)
# 一元标注器基于一个简单的统计算法:对每个标识符分配这个独特的标识符最有可能的标记
# 训练一个一元标注器,用它来标注一个句子,然后评估
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_sents = brown.sents(categories='news')   # 获取数据
unigram_tagger = nltk.UnigramTagger(brown_tagged_sents)  # 训练模型
print(unigram_tagger.tag(brown_sents[2007]))   # 评估
print(unigram_tagger.evaluate(brown_tagged_sents)) # 获取模型准确率 93.39%
# 训练一个UnigramTagger,通过在我们初始化标注器时指定已标注的句子数据作为参数。训练
# 过程中涉及检查每个词的标记,将所有词的最可能的标记存储在一个字典里面,这个字典存储在
# 标注器内部。


# 分离数据集,90%为训练数据,10%为测试数据
size = int(len(brown_tagged_sents) * 0.9)
print(size)
train_sents = brown_tagged_sents[:size]
test_sents = brown_tagged_sents[size:]
unigram_tagger = nltk.UnigramTagger(train_sents)  # 训练模型
print(unigram_tagger.evaluate(test_sents))        # 测试评估 81.2%

# N-gram标注器
# 一个n-gram标注器是一个 unigram 标注器的一般化,它的上下文是当前词和它前面n-1 个标识符的词性标记
# NgramTagger 类使用一个已标注的训练语料库来确定对每个上下文哪个词性标记最有可能
# 1-gram 标注器是一元标注器(unigram tagger)另一个名称:即用于标注一个标识符的上下文的只是标识符
# 本身。2-gram 标注器也称为二元标注器(bigram taggers),3-gram 标注器也称为三元标注器(trigram taggers)
# 2-gram标注器
# bigram_tagger = nltk.BigramTagger(train_sents)
# print(bigram_tagger.evaluate(test_sents))   # 10.27%
# bigram 标注器能够标注训练中它看到过的句子中的所有词,但对一个没见过
# 的句子表现很差。只要遇到一个新词,就无法给它分配标记。它不能标注下面的
# 词(如:million),即使是在训练过程中看到过的,只是因为在训练过程中从来没有见过它
# 前面有一个None 标记的词。因此,标注器标注句子的其余部分也失败了。它的整体准确度
# 得分非常低
# 当n 越大,上下文的特异性就会增加,我们要标注的数据中包含训练数据中不存在的上
# 下文的几率也增大。这被称为数据稀疏问题,在NLP 中是相当普遍的。因此,我们的研究
# 结果的精度和覆盖范围之间需要有一个权衡
# N-gram 标注器不应考虑跨越句子边界的上下文。因此,NLTK 的标注器被
# 设计用于句子链表,一个句子是一个词链表。在一个句子的开始,tn-1和前
# 面的标记被设置为None。


# 夸句子边界标注
# 一个n-gram 标注器使用最近的标记作为为当前的词选择标记的指导。当标记一个句子
# 的第一个词时,trigram 标注器将使用前面两个标识符的词性标记,这通常会是前面句子的
# 最后一个词和句子结尾的标点符号。然而,在前一句结尾的词的类别与下一句的开头的通常
# 没有关系。
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_sents = brown.sents(categories='news')
size = int(len(brown_tagged_sents) * 0.9)
train_sents = brown_tagged_sents[:size]
test_sents = brown_tagged_sents[size:]
t0 = nltk.DefaultTagger('NN')
t1 = nltk.UnigramTagger(train_sents, backoff=t0)
t2 = nltk.BigramTagger(train_sents, backoff=t1)
print(t2.evaluate(test_sents))   # 84.53%

# n-gram 标注器的一个潜在的问题是它们的n-gram 表的大小(或语言模型)。如果使用各
# 种语言技术的标注器部署在移动计算设备上,在模型大小和标注器性能之间取得平衡是很重
# 要的。使用回退标注器的n-gram 标注器可能存储trigram 和bigram 表,这是很大的稀疏阵
# 列,可能有数亿条条
# 第二个问题是关于上下文的。n-gram 标注器从前面的上下文中获得的唯一的信息是标
# 记,虽然词本身可能是一个有用的信息源。n-gram 模型使用上下文中的词的其他特征为条
# 件是不切实际的





# 基于转换的标注
# Brill 标注是一种基于转换的学习,想法很简单:猜每个词的标记,然后返回和修复错误的。
# 在这种方式中,Brill 标注器陆续将一个不良标注的文本转换成一个更好的。与n-gram 标注
# 一样,这是有监督的学习方法,因为我们需要已标注的训练数据来评估标注器的猜测是否是一
# 个错误。然而,不像n-gram 标注,它不计数观察结果,只编制一个转换修正规则链表。

# Brill 标注器演示:标注器有一个“X→Y 如果前面的词是Z”的形式的模板集合;
# 这些模板中的变量是创建“规则”的特定词和标记的实例;规则的得分是它纠正的错误例子
# 的数目减去正确的情况下它误报的数目;除了训练标注器,演示还显示了剩余的错误。
nltk.tag.brill.demo()               # 本部分代码存在严重问题,无法实现
print(open("errors.out").read())

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