import nltk
# nltk.download('averaged_perceptron_tagger')
# nltk.download('tagsets')
nltk.download('universal_tagset')
# 一个词性标注器处理一个词序列,为每个词附加一个词性标记。
text = nltk.word_tokenize("And now for something completely different") #分词
nltk.pos_tag(text) #词性标注器
NLTK中提供了每个标记的文档,可以使用标记来查询,如: nltk.help.upenn_tagset(‘RB’),或正则表达式,如:nltk.help.upenn_brown_tagset(‘NN.*’)。一些语料库有标记集文档的README文件;见 nltk.name.readme(),用语料库的名称替换name。
nltk.help.upenn_tagset('RB') # RB: adverb
# 考虑下面的分析,涉及woman(名词),bought(动词)、over(介词〉和 the(限定词)。
# text.similar()方法为一个词w找出所有上下文w1,w2,然后找出所有出现在相同上下文中的词 即w1,w2。
text = nltk.Text(word.lower() for word in nltk.corpus.brown.words())
print(text.similar("woman"),"\n") # 找到的大部分是名词
print(text.similar("bought"),"\n") # 找到的大部分是动词
print(text.similar("over"),"\n") # 找到大部分是介词
print(text.similar("the"),"\n") # 找到大部分是限定词
tagged_token = nltk.tag.str2tuple("gly/NN")
print(tagged_token,"\n",tagged_token[0])
我们可以直接从一个字符串构造一个已标注的词符的列表。
sent = '''
The/AT grand/JJ jury/NN commented/VBD on/IN a/AT number/NN of/IN
other/AP topics/NNS ,/, AMONG/IN them/PPO the/AT Atlanta/NP and/CC
Fulton/NP-tl County/NN-tl purchasing/VBG departments/NNS which/WDT it/PPS
said/VBD ``/`` ARE/BER well/QL operated/VBN and/CC follow/VB generally/RB
accepted/VBN practices/NNS which/WDT inure/VB to/IN the/AT best/JJT
interest/NN of/IN both/ABX governments/NNS ''/'' ./.
'''
print([nltk.tag.str2tuple(t) for t in sent.split()]) # [('The', 'AT'), ('grand', 'JJ'),...]
print(nltk.corpus.brown.tagged_words()) # [('The', 'AT'), ('Fulton', 'NP-TL'), ...]
# simplify _tags=True --> 避免这些标记集复杂化
print(nltk.corpus.brown.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.most_common())
tag_fd.plot(cumulative = True)
word_tag_pairs = nltk.bigrams(brown_news_tagged) # [('The', 'DET'),('Fulton', 'NOUN'),('Fulton', 'NOUN'),('County', 'NOUN')...]
# for (a,b) in word_tag_pairs:
# print(a,b)
# print(a[1],b[1])
# 输出结果: ('The', 'DET') ('Fulton', 'NOUN') | DET NOUN
noun_preceders = [a[1] for (a,b) in word_tag_pairs if b[1] == "NOUN"]
fdist = nltk.FreqDist(noun_preceders)
print([tag for (tag, _) in fdist.most_common()])
# 名词出现在限定词和形容词之后,包括数字形容词(数词,标注为NUM)。
动词是用来描述事件和行动的词,例如2.3中的fall, eat。在一个句子中,动词通常表示涉及一个或多个名词短语所指示物的关系
wsj = nltk.corpus.treebank.tagged_words(tagset = "universal")
word_tag_fd = nltk.FreqDist(wsj)
print([wt[0] for (wt, _) in word_tag_fd.most_common() if wt[1] == "VERB"][:10]) # 找出使用频率最高的10个词
"""
频率分布中计算的项目是词-标记对。
由于词汇和标记是成对的,我们可以把词作作为条件,标记作为事件,
使用条件-事件对的链表初始化一个条件频率分布。这让我们看到了一个给定的词的标记的频率顺序列表:
"""
cfd1 = nltk.ConditionalFreqDist(wsj)
print(cfd1["yield"].most_common()) # 词为条件,标记为事件 [('VERB', 28), ('NOUN', 20)]
print(cfd1["cut"].most_common())
"""
颠倒配对的顺序,这样标记作为条件,词汇作为事件。
现在我们可以看到对于一个给定的标记可能的词。
我们将用《华尔街日报 》的标记集而不是通用的标记集来这样做
"""
wsj = nltk.corpus.treebank.tagged_words()
cfd2 = nltk.ConditionalFreqDist((tag, word) for (word, tag) in wsj)
print(list(cfd2["VBN"])[:10])
"""
要弄清VBD(过去式)和VBN(过去分词)之间的区别,让我们找到可以同是VD和VBN的词汇,看看一些它们周围的文字
"""
print([w for w in cfd1.conditions() if "VBD" in cfd1[w] and "VBN" in cfd2[w]])
形容词:修饰名词,可以作为修饰语(如the large pizza中的large),或者谓语(如the pizza is large)。英语形容词可以有内部结构(如the falling stocks中的fall+ing)。
副词: 修饰动词,指定动词描述的事件的时间、方式、地点或方向(如the stocks fell quickly中的quickly)。副词也可以修饰的形容词(如Mary’s teacher was really nice中的really)。
其他词类:英语中还有几个封闭的词类,如介词,冠词(也常称为限定词)(如the、a),情态动词(如should、may)和人称代词(如she、they)。每个词典和语法对这些词的分类都不同。
# 找出每个名词类型中最频繁的名词 函数定义的是
def findtags(tag_prefix, tagged_text):
"""
tag_prefix: 要找出的标记
找出的含有XX的标记中,在tagged_text内,前5最频繁的词语
"""
cfd = nltk.ConditionalFreqDist((tag, word) for (word, tag) in tagged_text if tag.startswith(tag_prefix))
return dict((tag, cfd[tag].most_common(5)) for tag in cfd.conditions())
tagdict = findtags("NN", nltk.corpus.brown.tagged_words(categories = "news"))
for tag in sorted(tagdict):
print(tag, tagdict[tag])
"""
有许多NN的变种;最重要有$表示所有格名词,S表示复数名词(因为复数名词通常以s结尾),以及P表示专有名词。
此外,大多数的标记都有后缀修饰符:-NC表示引用,-HL表示标题中的词,-TL表示标题(布朗标记的特征)。
"""
# 假设我们正在研究词often,想看看它是如何在文本中使用的。我们可以试着看看跟在often后面的词汇
brown_learned_text = brown.words(categories = "learned")
print(sorted(set(b for (a, b) in nltk.bigrams(brown_learned_text) if a == "often")))
# 使用tagged_words()方法查看跟随词的词性标记可能更有指导性
brown_lrnd_tagged = brown.tagged_words(categories = "learned", tagset = "universal")
tags = [b[1] for (a, b) in nltk.bigrams(brown_lrnd_tagged) if a[0] == "often"]
fd = nltk.FreqDist(tags)
fd.tabulate()
# often后面最高频率的词性是动词。名词从来没有在这个位置出现(在这个特别的语料中)
"""
VERB ADV ADP ADJ . PRT
37 8 7 6 4 2
"""
# 使用tagged_words()方法查看跟随词的词性标记可能更有指导性
brown_lrnd_tagged = brown.tagged_words(categories = "learned", tagset = "universal")
tags = [b[1] for (a, b) in nltk.bigrams(brown_lrnd_tagged) if a[0] == "often"]
fd = nltk.FreqDist(tags)
fd.tabulate()
# often后面最高频率的词性是动词。名词从来没有在这个位置出现(在这个特别的语料中)
"""
VERB ADV ADP ADJ . PRT
37 8 7 6 4 2
"""
"""
看看与它们的标记关系高度模糊不清的词。为什么要标注这样的词-->因为它们各自的上下文可以帮助我们弄清楚标记之间的区别
"""
brown_news_tagged = brown.tagged_words(categories = "news", tagset = "universal")
data = nltk.ConditionalFreqDist((word.lower(), tag) for (word, tag) in brown_news_tagged)
for word in sorted(data.conditions()):
if len(data[word]) > 3:
# 标记超过3个
tags = [tag for (tag, _) in data[word].most_common()]
print(word, " ".join(tags))
python之字典(dict)详细介绍
# 两种方式
pos = {
"colorless": "ADJ", "ideas": "N", "sleep": "V", "furiously": "ADV"}
pos = dict(colorless = "ADJ", ideas = "N", sleep = "V", furiously = "ADV")
# 字典的键必须是不可改变的类型,如字符串和元组。如果我们尝试使用可变键定义字典会得到一个TypeError:
pos = {
["ideas", "blogs", "adventures"]: "N"} # 报错:TypeError
# 导入包
from collections import defaultdict
#默认字典中的value为int,初始值为0
frequency = defaultdict(int)
frequency["colorless"] = 4
print(frequency["colorless"])
print(frequency["new"]) # 默认为0
#默认字典中的value为list,初始值为[]
pos = defaultdict(list)
pos["sleep"] = ["NOUN", "VERB"]
print(pos["new"]) # 默认为[]
pos = defaultdict(lambda: "NOUN")
pos["colorless"] = "ADJ"
print(pos["blog"])
print([pos])
"""
应用:替换掉低频汇的词语
%%javascript体而言:
1. 许多语言处理任务——包括标注——费很大力气来正确处理文本中只出现过一次的词。
2. 如果有一个固定的词汇和没有新词会出现的保证,它们会有更好的表现。
3. 在一个默认字典的帮助下,我们可以预处理一个文本,替换低频词汇为一个特殊的“超出词汇表”词符UNK。
"""
alice = nltk.corpus.gutenberg.words("carroll-alice.txt") # 文本
vocab = nltk.FreqDist(alice) # 每个文本中每个词的词频的字典
v1000 = [word for (word, _) in vocab.most_common(1000)] # 找出使用频率最高的1000个词
mapping = defaultdict(lambda: "UNK") # 定义默认标记字典为UNK
for v in v1000: # 定义高频的1000个词的就是它本身
mapping[v] = v
alice2 = [mapping[v] for v in alice] # 其他低频的为UNK
print(alice2[:10])
print(len(alice))
print(len(alice2))
from collections import defaultdict
counts = defaultdict(int)
from nltk.corpus import brown
# 定义tag的计数
for (word, tag) in brown.tagged_words(categories = "news", tagset = "universal"):
counts[tag] += 1
print(counts["NOUN"])
print(sorted(counts))
# 例5-3的列表演示了一个重要的按值排序一个字典的习惯用法,按频率递减顺序显示词汇。
# sorted()的第一个参数是要排序的项目,它是由一个POS标记和一个频率组成的元组的链表。
# 第二个参数使用函数 itemgetter()指定排序键。在一般情况下,itemgetter(n)返回一个函数,这个函数可以在一些其他序列对象上被调用获得这个序列的第n个元素的。
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)]) # t是key,c是value,key = itemgetter(0)是从小到大,key = itemgetter(1)是从大到小
"""
在3.3的开头还有第二个有用的习惯用法,那里我们初始化一个defaultdict,然后使用for循环来更新其值。
下面是这种模式的另一个示例,我们按它们最后两个字母索引词汇
"""
last_letters = defaultdict(list)
words = nltk.corpus.words.words("en") # 英文单词
for word in words:
key = word[-2:]
last_letters[key].append(word)
print(last_letters["ly"][:10]) # ly结尾
print(last_letters["zy"][:10]) # zy结尾
# 下面的例子使用相同的模式创建一个颠倒顺序的词字典。(你可能会试验第3行来弄清楚为什么这个程序能运行。)
anagrams = defaultdict(list)
for word in words:
key = "".join(sorted(word))
anagrams[key].append(word)
print(anagrams["aeilnrt"]) # 找出这种组合的单词 ['entrail', 'latrine', 'ratline', 'reliant', 'retinal', 'trenail']
# 由于积累这样的词是如此常用的任务,NLTK提供一个创建defaultdict(list)更方便的方式,形式为nltk.Index()。
anagrams = nltk.Index((''.join(sorted(w)), w) for w in words)
print(anagrams["aeilnrt"])
print(anagrams["eilnrt"])
"""
nltk.Index是一个支持额外初始化的defaultdict(list)。
类似地,nltk.FreqDist本质上是一个额外支持初始化的defaultdict(int)(附带排序和绘图方法)。
"""
我们可以使用具有复杂的键和值的默认字典。让我们研究一个词可能的标记的范围,给定词本身和它前一个词的标记。
我们将看到这些信息如何被一个词性标注器使用。
pos = defaultdict(lambda: defaultdict(int))
brown_news_tagged = brown.tagged_words(categories = "news", tagset = "universal")
for ((w1, t1), (w2, t2)) in nltk.bigrams(brown_news_tagged): # (w1, t1), (w2, t2) w1,w2 是单词, t1,t2是标记
pos[(t1, w2)][t2] += 1
print(pos[("DET", "right")])
字典支持高效查找,只要你想获得任意键的值。如果d是一个字典,k是一个键,输入d[k],就立即获得值。
给定一个值查找对应的键要慢一些和麻烦一些:
counts = defaultdict(int)
for word in nltk.corpus.gutenberg.words("milton-paradise.txt"):
counts[word] +=1
print([key for (key, value) in counts.items() if value == 32])
# 经常做这样的一种“反向查找”,建立一个映射值到键的字典是有用的
pos = {
"colorless": "ADj", "ideas": "N", "sleep": "V", "furiously": "ADV"}
pos2 = dict((value, key) for (key, value) in pos.items())
print(pos2["N"])
# 首先让我们将我们的词性字典做的更实用些,使用字典的update()方法加入再一些词到pos中,创建多个键具有相同的值的情况。这样一来,刚才看到的反向查找技术就将不再起作用(为什么不?)作为替代,我们不得不使用append()积累词和每个词性,如下所示
pos.update({
'cats': 'N', 'scratch': 'V', 'peacefully': 'ADV', 'old': 'ADJ'})
pos2 = defaultdict(list) # 建立默认字典
for key, value in pos.items():
print(pos2[value])
pos2[value].append(key)
print(pos2[value])
print(pos2["N"])
# 反转字典pos:可以使用NLTK中的索引支持更容易的做同样的事
pos2 = nltk.Index((value, key) for (key, value) in pos.items())
print(pos2["ADV"])
# 加载数据
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_sents = brown.sents(categories='news')
默认的标注器给每一个单独的词分配标记,即使是之前从未遇到过的词。碰巧的是,一旦我们处理了几千词的英文文本之后,大多数新词都将是名词。正如我们将看到的,这意味着,默认标注器可以帮助我们提高语言处理系统的稳定性。
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)[:10])
# 评估标注器
default_tagger.evaluate(brown_tagged_sents) # 在一个典型的语料库中,它只标注正确了八分之一的标识符
正则表达式标注器基于匹配模式分配标记给词符。例如,我们可能会猜测任一以ed结尾的词都是动词过去分词,任一以’s结尾的词都是名词所有格。
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))
很多高频词没有NN标记。让我们找出100个最频繁的词,存储它们最有可能的标记。然后我们可以使用这个信息作为“查找标注器”(NLTK UnigramTagger)的模型。
fd = nltk.FreqDist(brown.words(categories = "news"))
cfd = nltk.ConditionalFreqDist(brown.tagged_words(categories = "news"))
most_freq_words = fd.most_common(100)
likely_tags = dict((word, cfd[word].max()) for (word, _) in most_freq_words)
print([cfd["the"].max()])
# 构建查询标注器模型
baseline_tagger = nltk.UnigramTagger(model = likely_tags)
# 评估
baseline_tagger.evaluate(brown_tagged_sents)
# 看看它在一些未标注的输入文本上做的如何
sent = brown.sents(categories='news')[3]
print(baseline_tagger.tag(sent))
许多词都被分配了一个None标签,因为它们不在100个最频繁的词之中。在这些情况下,我们想分配默认标记NN。换句话说,我们要先使用查找表,如果它不能指定一个标记就使用默认标注器,这个过程叫做回退(5)。我们可以做到这个,通过指定一个标注器作为另一个标注器的参数,如下所示。现在查找标注器将只存储名词以外的词的词-标记对,只要它不能给一个词分配标记,它将会调用默认标注器。
baseline_tagger = nltk.UnigramTagger(model = likely_tags, backoff = nltk.DefaultTagger("NN")) # backoff --》 回退器
"""
写一个程序来创建和评估具有一定范围的查找标注器
"""
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
word_freqs = nltk.FreqDist(brown.words(categories = "news")).most_common()
words_by_freq = [w for (w, _) in word_freqs]
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 Performancce with Varying Model Size")
pylab.xlabel("Model Size")
pylab.ylabel("Performance")
pylab.show()
# 随着模型规模的增长,最初的性能增加迅速,最终达到一个稳定水平,这时模型的规模大量增加性能的提高很小
display()
"""
我们训练一个UnigramTagger,通过在我们初始化标注器时指定已标注的句子数据作为参数。
训练过程中涉及检查每个词的标记,将所有词的最可能的标记存储在一个字典里面,这个字典存储在标注器内部。
"""
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))
# 应该分割数据,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))
"""
NgramTagger类使用一个已标注的训练语料库来确定对每个上下文哪个词性标记最有可能。
n-gram标注器的一个特殊情况,二元标注器。首先,我们训练它,然后用它来标注未标注的句子
"""
bigram_tagger = nltk.BigramTagger(train_sents)
print(bigram_tagger.tag(brown_sents[2007]))
unseen_sent = brown_sents[4203]
print(bigram_tagger.tag(unseen_sent))
print(bigram_tagger.evaluate(test_sents))
解决精度和覆盖范围之间的权衡的一个办法是尽可能的使用更精确的算法,但却在很多时候落后于具有更广覆盖范围的算法.方法 --> 可以按如下方式组合二元标注器、一元注器和一个默认标注器:
t0 = nltk.DefaultTagger("NN")
t1 = nltk.UnigramTagger(train_sents, backoff = t0)
t2 = nltk.BigramTagger(train_sents, backoff = t1)
print(t2.evaluate(test_sents))
将一个训练好的标注器保存到一个文件以后重复使用
# 保存我们的标注器t2到文件t2.pkl
from pickle import dump
output = open('5.t2.pkl', 'wb')
dump(t2, output, -1)
output.close()
# 在一个单独的Python进程中,我们可以载入保存的标注器
from pickle import load
input = open("5.t2.pkl", "rb")
tagger = load(input)
input.close()
# 检查它是否可以用来标注.
text = """The board's action shows what free enterprise
... is up against in our complex maze of regulatory laws ."""
tokens = text.split()
print(tagger.tag(tokens))
一个n-gram标注器准确性的上限是什么?考虑一个三元标注器的情况。它遇到多少词性歧义的情况?我们可以根据经验决定这个问题的答案
cfd = nltk.ConditionalFreqDist(
((x[1], y[1], z[0]), z[1])
for sent in brown_tagged_sents
for x, y, z in nltk.trigrams(sent))
ambiguous_contexts = [c for c in cfd.conditions() if len(cfd[c]) > 1]
print(sum(cfd[c].N() for c in ambiguous_contexts) / cfd.N())
给定当前单词及其前两个标记,根据训练数据,在5%的情况中,有一个以上的标记可能合理地分配给当前词。假设我们总是挑选在这种含糊不清的上下文中最有可能的标记,可以得出三元标注器准确性的一个下界。
调查标注器准确性的另一种方法是研究它的错误。有些标记可能会比别的更难分配,可能需要专门对这些数据进行预处理或后处理。一个方便的方式查看标注错误是混淆矩阵。它用图表表示期望的标记(黄金标准)与实际由标注器产生的标记:如下代码
test_tags = [tag for sent in brown.sents(categories = "editorial") for (word, tag) in t2.tag(sent)]
gold_tags = [tag for (word, tag) in brown.tagged_words(categories='editorial')]
print(nltk.ConfusionMatrix(gold_tags, test_tags))
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))
# brill标注器演示 基于转换的标注学习一系列“改变标记s 为标记t 在上下文c 中”形式的修复规则
nltk.tag.brill.demo() # 报错了? AttributeError: module 'nltk.tag.brill' has no attribute 'demo'
https://blog.csdn.net/qq_34505594/article/details/79495999