python自然语言处理 | 您所在的位置:网站首页 › 哪个词性最重要 › python自然语言处理 |
本章解决问题
什么是词汇分类,在自然语言处理中它们是如何使用?一个好的存储词汇和它们的分类的Python数据结构是什么?我们如何自动标注文本中词汇的词类?
词性标注:将词汇按它们的词性( 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])我们可以直接从一个字符串构造一个已标注的词符的列表。 第一步是对字符串分词以便能访问单独的单词/标记字符串;然后将每一个转换成一个元组(使用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 读取已标注的语料库
动词是用来描述事件和行动的词,例如2.3中的fall, eat。在一个句子中,动词通常表示涉及一个或多个名词短语所指示物的关系 形容词:修饰名词,可以作为修饰语(如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之字典(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 默认标注器默认的标注器给每一个单独的词分配标记,即使是之前从未遇到过的词。碰巧的是,一旦我们处理了几千词的英文文本之后,大多数新词都将是名词。正如我们将看到的,这意味着,默认标注器可以帮助我们提高语言处理系统的稳定性。 最简单的标注器是为每个词符分配同样的标记。这似乎是一个相当平庸的一步,但它建立了标注器性能的一个重要的底线。为了得到最好的效果,我们用最有可能的标记标注每个词。让我们找出哪个标记是最有可能的(现在使用未简化标记集) 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()![]() 解决精度和覆盖范围之间的权衡的一个办法是尽可能的使用更精确的算法,但却在很多时候落后于具有更广覆盖范围的算法.方法 --> 可以按如下方式组合二元标注器、一元注器和一个默认标注器: 尝试使用二元标注器标注标识符。如果二元标注器无法找到一个标记,尝试一元标注器。如果一元标注器也无法找到一个标记,使用默认标注器。 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 标注生词 标注生词常用方法是回退到一个正则表达式标注器或一个默认标注器,但其缺陷是无法利用上下文;一个有用的基于上下文标注生词的方法是限制一个标注器的词汇表为最频繁的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 跨句子边界标注https://blog.csdn.net/qq_34505594/article/details/79495999 |
CopyRight 2018-2019 实验室设备网 版权所有 |