python自然语言处理|分类和标注词汇

  • 本章解决问题
  1. 什么是词汇分类,在自然语言处理中它们是如何使用?
  2. 一个好的存储词汇和它们的分类的Python数据结构是什么?
  3. 我们如何自动标注文本中词汇的词类?
  • 词性标注:将词汇按它们的词性( parts-of-speech,POS)分类以及相应的标注它们的过程被称为词性标注(part-of-speech tagging,POS tagging)或干脆简称标注。词性也称为词类或词汇范畴。
  • 标记集:用于特定任务的标记的集合。

这里写目录标题

  • 1 使用词性标注器
  • 2 标注语料库
    • 2.1 表示已标注的标识符
    • 2.2 读取已标注的语料库
    • 2.3 通用词性标记集
    • 2.4 名词
    • 2.5 动词
    • 2.6 形容词和副词
    • 2.7 未简化的标记
    • 2.8 探索已标注的语料库
  • 3 使用Python字典映射单词到其属性
    • 3.1 索引链表 VS 字典
    • 3.2 Python字典
    • 3.3 定义字典
    • 3.4 默认字典
    • 3.5 递增地更新字典
    • 3.6 复杂的键和值
    • 3.7 反转字典
  • 4 自动标注
    • 4.1 默认标注器
    • 4.2 正则表达式标注器
    • 4.3 查询标注器
    • 4.4 评估
  • 5 N-gram标注
    • 5.1 一元标注
    • 5.2 分离训练和测试数据
    • 5.3 一般的N-gram标注
    • 5.4 组合标注器
    • 5.5 标注生词
    • 5.6 存储标注器
    • 5.7 性能限制
    • 5.8 跨句子边界标注
  • 6 基于转换的标注
  • 7 如何确定一个词的分类
    • 7.1 形态学线索
    • 7.2 句法线索
    • 7.3 语义线索
    • 7.4 词性标记集中的形态学
  • 8 小结
  • 9 练习

1 使用词性标注器

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")   # 找到大部分是限定词

2 标注语料库

2.1 表示已标注的标识符

  • 按照NLTK的约定,一个已标注的词符使用一个由词符和标记组成的元组来表示
  • 我们可以使用**函数str2tuple()**从表示一个已标注的词符的标准字符串创建一个这样的特殊元组:
tagged_token = nltk.tag.str2tuple("gly/NN")
print(tagged_token,"\n",tagged_token[0])

我们可以直接从一个字符串构造一个已标注的词符的列表。

  1. 第一步是对字符串分词以便能访问单独的单词/标记字符串;
  2. 然后将每一个转换成一个元组(使用str2tuple())。
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'),...]

2.2 读取已标注的语料库

python自然语言处理|分类和标注词汇_第1张图片

print(nltk.corpus.brown.tagged_words())  # [('The', 'AT'), ('Fulton', 'NP-TL'), ...]
# simplify _tags=True  --> 避免这些标记集复杂化
print(nltk.corpus.brown.tagged_words(tagset = "universal"))

2.3 通用词性标记集

python自然语言处理|分类和标注词汇_第2张图片
python自然语言处理|分类和标注词汇_第3张图片

# 哪些是布朗语料库的新闻类中最常见的
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)

python自然语言处理|分类和标注词汇_第4张图片

2.4 名词

  • 名词一般指的是人、地点、事情或概念,例如: woman, Scotland, book, intelligence。
  • 名词可能出现在限定词和形容词之后,可以是动词的主语或宾语。
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.5 动词

动词是用来描述事件和行动的词,例如2.3中的fall, eat。在一个句子中,动词通常表示涉及一个或多个名词短语所指示物的关系
python自然语言处理|分类和标注词汇_第5张图片

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]])

2.6 形容词和副词

  • 形容词:修饰名词,可以作为修饰语(如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)。每个词典和语法对这些词的分类都不同。

2.7 未简化的标记

# 找出每个名词类型中最频繁的名词   函数定义的是
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表示标题(布朗标记的特征)。
 """

2.8 探索已标注的语料库

# 假设我们正在研究词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))

3 使用Python字典映射单词到其属性

python自然语言处理|分类和标注词汇_第6张图片

3.1 索引链表 VS 字典

python自然语言处理|分类和标注词汇_第7张图片

3.2 Python字典

python之字典(dict)详细介绍

3.3 定义字典

# 两种方式
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  

3.4 默认字典

  • 试图访问一个不在字典中的键,会得到一个错误。然而,如果一个字典能为这个新键自动创建一个条目并给它一个默认值,如0或者一个空链表,将是有用的
  • defaultdict的字典的应用:提供一个参数,用来创建默认值,如int, float, str, list, dict, tuple。
# 导入包
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"])  # 默认为[]
  • 指定任何我们喜欢的默认值,只要提供可以无参数的被调用产生所需值的函数的名字,采用lambda
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))

3.5 递增地更新字典

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)(附带排序和绘图方法)。
"""

3.6 复杂的键和值

我们可以使用具有复杂的键和值的默认字典。让我们研究一个词可能的标记的范围,给定词本身和它前一个词的标记
我们将看到这些信息如何被一个词性标注器使用。

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")])

3.7 反转字典

字典支持高效查找,只要你想获得任意键的值。如果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"])

4 自动标注

# 加载数据
from nltk.corpus import brown
brown_tagged_sents = brown.tagged_sents(categories='news')
brown_sents = brown.sents(categories='news')

4.1 默认标注器

默认的标注器给每一个单独的词分配标记,即使是之前从未遇到过的词。碰巧的是,一旦我们处理了几千词的英文文本之后,大多数新词都将是名词。正如我们将看到的,这意味着,默认标注器可以帮助我们提高语言处理系统的稳定性。

  1. 最简单的标注器是为每个词符分配同样的标记。这似乎是一个相当平庸的一步,但它建立了标注器性能的一个重要的底线。
  2. 为了得到最好的效果,我们用最有可能的标记标注每个词。让我们找出哪个标记是最有可能的(现在使用未简化标记集)
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)  # 在一个典型的语料库中,它只标注正确了八分之一的标识符

4.2 正则表达式标注器

正则表达式标注器基于匹配模式分配标记给词符。例如,我们可能会猜测任一以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))

4.3 查询标注器

很多高频词没有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()

python自然语言处理|分类和标注词汇_第8张图片

4.4 评估

python自然语言处理|分类和标注词汇_第9张图片

5 N-gram标注

5.1 一元标注

  1. 一元标注器基于一个简单的统计算法:对每个标识符分配这个独特的标识符最有可能的标记。
  2. 例如,它将分配标记JJ给词frequent的所有出现,因为frequent用作一个形容词(例如a frequent word)比用作一个动词(例如I frequent this cafe)更常见。一个一元标注器的行为就像一个查找标注器(4),除了有一个更方便的建立它的技术,称为训练。
  3. 在下面的代码例子中,我们训练一个一元标注器,用它来标注一个句子,然后评估:
"""
我们训练一个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))

5.2 分离训练和测试数据

# 应该分割数据,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))

5.3 一般的N-gram标注

  1. 在基于一元处理一个语言处理任务时,我们使用上下文中的一个项目。标注的时候,我们只考虑当前的词符,与更大的上下文隔离。给定一个模型,我们能做的最好的是为每个词标注其先验的最可能的标记。这意味着我们将使用相同的标记标注一个词,如wind,不论它出现的上下文是the wind还是to wind。
  2. 一个n-gram tagger标注器是一个一元标注器的一般化,它的上下文是当前词和它前面n-1个标识符的词性标记,如图5.1所示。要选择的标记是圆圈里的tn,灰色阴影的是上下文。在5.1所示的n-gram标注器的例子中,我们让n=3;也就是说,我们考虑当前词的前两个词的标记。一个n-gram标注器挑选在给定的上下文中最有可能的标记
    python自然语言处理|分类和标注词汇_第10张图片
"""
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))
  1. 二元标注器能够标注训练中它看到过的句子中的所有词,但对一个没见过的句子表现很差。只要遇到一个新词(如13.5),就无法给它分配标记。
  2. 它不能标注下面的词(如million),即使是在训练过程中看到过的,只是因为在训练过程中从来没有见过它前面有一个None标记的词。因此,标注器标注句子的其余部分也失败了。它的整体准确度得分非常低:
  3. 当n越大,上下文的特异性就会增加,我们要标注的数据中包含训练数据中不存在的上下文的几率也增大。这被称为数据稀疏问题,在NLP中是相当普遍的。因此,我们的研究结果的精度和覆盖范围之间需要有一个权衡(这与信息检索中的精度/召回权衡有关)。
  4. N-gram标注器不应考虑跨越句子边界的上下文。因此,NLTK的标注器被设计用于句子列表,其中一个句子是一个词列表。在一个句子的开始,tn-1和前面的标记被设置为None。

5.4 组合标注器

解决精度和覆盖范围之间的权衡的一个办法是尽可能的使用更精确的算法,但却在很多时候落后于具有更广覆盖范围的算法.方法 --> 可以按如下方式组合二元标注器、一元注器和一个默认标注器:

  1. 尝试使用二元标注器标注标识符。
  2. 如果二元标注器无法找到一个标记,尝试一元标注器。
  3. 如果一元标注器也无法找到一个标记,使用默认标注器。
t0 = nltk.DefaultTagger("NN")
t1 = nltk.UnigramTagger(train_sents, backoff = t0)
t2 = nltk.BigramTagger(train_sents, backoff = t1)
print(t2.evaluate(test_sents))

5.5 标注生词

  1. 标注生词常用方法是回退到一个正则表达式标注器或一个默认标注器,但其缺陷是无法利用上下文;
  2. 一个有用的基于上下文标注生词的方法是限制一个标注器的词汇表为最频繁的n 个词,使用字典中的方法替代每个其他的词为一个特殊的词UNK。训练时,一个一元标注器可能会学到UNK通常是一个名词。然而,n-gram标注器会检测它的一些其他标记中的上下文。例如,如果前面的词是to(标注为TO),那么UNK可能会被标注为一个动词。

5.6 存储标注器

将一个训练好的标注器保存到一个文件以后重复使用

# 保存我们的标注器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))

5.7 性能限制

一个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))

5.8 跨句子边界标注

python自然语言处理|分类和标注词汇_第11张图片

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))

6 基于转换的标注

python自然语言处理|分类和标注词汇_第12张图片

# brill标注器演示 基于转换的标注学习一系列“改变标记s 为标记t 在上下文c 中”形式的修复规则
nltk.tag.brill.demo()  # 报错了? AttributeError: module 'nltk.tag.brill' has no attribute 'demo'

7 如何确定一个词的分类

7.1 形态学线索

python自然语言处理|分类和标注词汇_第13张图片

7.2 句法线索

python自然语言处理|分类和标注词汇_第14张图片

7.3 语义线索

python自然语言处理|分类和标注词汇_第15张图片

7.4 词性标记集中的形态学

python自然语言处理|分类和标注词汇_第16张图片

python自然语言处理|分类和标注词汇_第17张图片

8 小结

python自然语言处理|分类和标注词汇_第18张图片

9 练习

https://blog.csdn.net/qq_34505594/article/details/79495999

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