python自然语言处理-读书笔记9

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zlp_zky/article/details/83110511
# -*- coding:utf-8 -*-
# __author__ = 'lipzhang'
#第六章 学习分类文本
# 1. 我们怎样才能识别语言数据中能明显用于对其分类的特征? 2. 我们怎样才能构建语言模型,用于自动执行语言处理任务? 3. 从这些模型中我们可以学到哪些关于语言的知识?
#6.1  有监督分类  如果分类的建立基于包含每个输入的正确标签的训练语料,被称为有监督分类。
#性别鉴定  男性和女性的名字有一些鲜明的特点。以a,e 和i 结尾的很可 能是女性,而以k,o,r,s 结尾的很可能是男性。让我们建立一个分类器更精确地模拟这 些差异。
# def gender_features(word): #特征提取器函数
#     return {'suffix1': word[-1:], 'suffix2': word[-2:]}
#     print(gender_features('Shrek'))
#现在,我们已经定义了一个特征提取器,我们需要准备一个例子和对应类标签的链表:
import nltk
from nltk.corpus import names
import random
# names = ([(name, 'male') for name in names.words('male.txt')] + [(name, 'female') for name in names.words('female.txt')])
# random.shuffle(names)
# print(random.shuffle(names))
# #接下来,我们使用特征提取器处理名称数据,并划分特征集的结果链表为一个训练集和 一个测试集。训练集用于训练一个新的“朴素贝叶斯”分类器。
# featuresets = [(gender_features(n), g) for (n, g) in names]
# train_set, test_set = featuresets[500:], featuresets[:500]
# classifier = nltk.NaiveBayesClassifier.train(train_set)
# #测试一些没有出现在训练数据中的名字:
# print(classifier.classify(gender_features('Trinity')))
# #评估这个分类器
# print( nltk.classify.accuracy(classifier, test_set) )
# #最后,我们可以检查分类器,确定哪些特征对于区分名字的性别是最有效的。
# print(classifier.show_most_informative_features(5) )
# Most Informative Features
#              last_letter = 'a'            female : male   =     33.1 : 1.0
#              last_letter = 'k'              male : female =     31.2 : 1.0
#              last_letter = 'f'              male : female =     14.1 : 1.0
#              last_letter = 'p'              male : female =     12.7 : 1.0
#              last_letter = 'v'              male : female =     10.0 : 1.0
# 此列表显示训练集中以a 结尾的名字中女性是男性的 33倍,而以 k结尾名字中男性是 女性的31 倍。这些比率称为似然比,可以用于比较不同特征-结果关系。

#在处理大型语料库时,构建一个包含每一个实例的特征的单独的链表会使用大量的内 存。在这些情况下,使用函数nltk.classify.apply_features,返回一个行为像一个链表而 不会在内存存储所有特征集的对象:
from nltk.classify import apply_features

# train_set = apply_features(gender_features, names[500:])
# test_set = apply_features(gender_features, names[:500])
# print(test_set)

#选择正确的特征
#特征提取通过反复试验和错误的过程建立的,由哪些信息是与问题相关的直觉 指引的。它通常以“厨房水槽”的方法开始,包括你能想到的所有特征,然后检查哪些特征 是实际有用的
# def gender_features2(name):#一个特征提取器,过拟合性别特征。这个特征提取器返回的特征集包括大量指 定的特征,从而导致对于相对较小的名字语料库过拟合。
#     features = {}
#     features["firstletter"] = name[0].lower()
#     features["lastletter"] = name[-1].lower()
#     for letter in 'abcdefghijklmnopqrstuvwxyz':
#         features["count(%s)" % letter] = name.lower().count(letter)
#         features["has(%s)" % letter] = (letter in name.lower())
#         return features
# print(gender_features2('John'))

#一旦初始特征集被选定,完善特征集的一个非常有成效的方法是错误分析。首先,我们 选择一个开发集,包含用于创建模型的语料数据。然后将这种开发集分为训练集和开发测试 集。
# train_names = names[1500:]
# devtest_names = names[500:1500]
# test_names = names[:500]
#训练集用于训练模型,开发测试集用于进行错误分析,测试集用于系统的最终评估。由 于下面讨论的原因,我们将一个单独的开发测试集用于错误分析而不是使用测试集是很重要 的
#已经将语料分为适当的数据集,我们使用训练集训练一个模型,然后在开发测试集上 运行。
# train_set = [(gender_features(n), g) for (n,g) in train_names]
# devtest_set = [(gender_features(n), g) for (n,g) in devtest_names]
# test_set = [(gender_features(n), g) for (n, g) in test_names]
# classifier = nltk.NaiveBayesClassifier.train(train_set)
# print(nltk.classify.accuracy(classifier, devtest_set))
#使用开发测试集,我们可以生成一个分类器预测名字性别时的错误列表。
# errors = []
# for (name, tag) in devtest_names:
#     guess = classifier.classify(gender_features(name))
#     if guess != tag:
#         errors.append((tag, guess, name))
# for (tag, guess, name) in sorted(errors):
#     # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE ...
#     print ('correct=%-8s guess=%-8s name=%-30s' % (tag, guess, name))

#文档分类
from nltk.corpus import movie_reviews
# documents = [(list(movie_reviews.words(fileid)), category)  for category in movie_reviews.categories()  for fileid in movie_reviews.fileids(category)]
# random.shuffle(documents)#随机排序

#一个文档分类的特征提取器,其特征表示每个词是否在一个给定的文档中。
# all_words = nltk.FreqDist(w.lower() for w in movie_reviews.words())
# word_features = list(all_words.keys())[:2000]
# def document_features(document):
#     document_words = set(document)
#     features = {}
#     for word in word_features:
#         features['contains(%s)' % word] = (word in document_words)
#     return features
# print(document_features(movie_reviews.words('pos/cv957_8737.txt')))

#训练和测试一个分类器进行文档分类。
# featuresets = [(document_features(d), c) for (d,c) in documents]
# train_set, test_set = featuresets[100:], featuresets[:100]
# classifier = nltk.NaiveBayesClassifier.train(train_set)
# print(nltk.classify.accuracy(classifier, test_set))
# classifier.show_most_informative_features(5)
# help(nltk.FreqDist)
# dir(nltk.FreqDist)
#第5 章中,我们建立了一个正则表达式标注器,通过查找词内部的组成,为词选择词性 标记。然而,这个正则表达式标注器是手工制作的。作为替代,我们可以训练一个分类器来 算出哪个后缀最有信息量。首先,让我们找出最常见的后缀
from nltk.corpus import brown
# suffix_fdist = nltk.FreqDist()
# for word in brown.words():
#     word = word.lower()
#     #suffix_fdist.inc(word[-1:]) python2
#     suffix_fdist[word[-1:]] += 1 #python3
#     suffix_fdist[word[-2:]] += 1
#     suffix_fdist[word[-3:]] += 1
# common_suffixes = list(suffix_fdist.keys())[:100]
# print(common_suffixes)
#接下来,我们将定义一个特征提取器函数,检查给定的单词的这些后缀:
# def pos_features(word):
#     features = {}
#     for suffix in common_suffixes:
#         features['endswith(%s)' % suffix] = word.lower().endswith(suffix)
#     return features
# #现在,我们已经定义了我们的特征提取器,可以用它来训练一个新的“决策树”的分类 器(将在6.4节讨论):
# tagged_words = brown.tagged_words(categories='news')
# featuresets = [(pos_features(n), g) for (n,g) in tagged_words]
# size = int(len(featuresets) * 0.1)
# train_set, test_set = featuresets[size:], featuresets[:size]
# classifier = nltk.DecisionTreeClassifier.train(train_set)
# print(nltk.classify.accuracy(classifier, test_set))
# print(classifier.classify(pos_features('cats')))
# print(classifier.pseudocode(depth=4))

#探索上下文语境
#为了采取基于词的上下文的特征,我们必须修改以前为我们的特征提取器定义的模式。 不是只传递已标注的词,我们将传递整个(未标注的)句子,以及目标词的索引。
# def pos_features(sentence, i):
#     features = {"suffix(1)": sentence[i][-1:],
#                 "suffix(2)": sentence[i][-2:],
#                 "suffix(3)": sentence[i][-3:]}
#     if i == 0:
#         features["prev-word"] = "<START>"
#     else:
#         features["prev-word"] = sentence[i-1]
#     return features
# print(pos_features(brown.sents()[0], 8))
# tagged_sents = brown.tagged_sents(categories='news')
# featuresets = []
# for tagged_sent in tagged_sents:
#     untagged_sent = nltk.tag.untag(tagged_sent)
#     for i, (word, tag) in enumerate(tagged_sent):
#         featuresets.append((pos_features(untagged_sent, i), tag))
# size = int(len(featuresets) * 0.1)
# train_set, test_set = featuresets[size:], featuresets[:size]
# classifier = nltk.NaiveBayesClassifier.train(train_set)
# print(nltk.classify.accuracy(classifier, test_set))

#序列分类
#一种序列分类器策略,称为连续分类或贪婪序列分类,是为第一个输入找到最有可能的 类标签,然后使用这个问题的答案帮助找到下一个输入的最佳的标签
# 首先,我们必须扩展我们的特征提取函数使其具有参数his tory,它提供一个我们到目前为止已经为句子预测的标记的链表。
# history 中的每个标记 对应句子中的一个词。但是请注意,history将只包含我们已经归类的词的标记,也就是目 标词左侧的词。
# 因此,虽然是有可能查看目标词右边的词的某些特征,但查看那些词的标记 是不可能的(因为我们还未产生它们)。
#使用连续分类器进行词性标注。
# def pos_features(sentence, i, history):
#     features = {"suffix(1)": sentence[i][-1:],
#                 "suffix(2)": sentence[i][-2:],
#                 "suffix(3)": sentence[i][-3:]}
#     if i == 0:
#         features["prev-word"] = "<START>"
#         features["prev-tag"] = "<START>"
#     else:
#         features["prev-word"] = sentence[i-1]
#         features["prev-tag"] = history[i - 1]
#     return features
# class ConsecutivePosTagger(nltk.TaggerI):#已经定义了特征提取器,我们可以继续建立我们的序列分类器。在训练中,我们使用 已标注的标记为征提取器提供适当的历史信息,但标注新的句子时,我们基于标注器本身的 输出产生历史信息。
#     def __init__(self, train_sents):
#         train_set = []
#         for tagged_sent in train_sents:
#             untagged_sent = nltk.tag.untag(tagged_sent)
#             history = []
#             for i, (word, tag) in enumerate(tagged_sent):
#                 featureset = pos_features(untagged_sent, i, history)
#                 train_set.append((featureset, tag))
#                 history.append(tag)
#         self.classifier = nltk.NaiveBayesClassifier.train(train_set)
#
#     def tag(self, sentence):
#         history = []
#         for i, word in enumerate(sentence):
#             featureset = pos_features(sentence, i, history)
#             tag = self.classifier.classify(featureset)
#             history.append(tag)
#         return zip(sentence, history)
#
# tagged_sents = brown.tagged_sents(categories='news')
# size = int(len(tagged_sents) * 0.1)
# train_sents, test_sents = tagged_sents[size:], tagged_sents[:size]
# tagger = ConsecutivePosTagger(train_sents)
# print(tagger.evaluate(test_sents))

#其他序列分类方法
#:如果我们决定将一个词标 注为名词,但后来发现的证据表明应该是一个动词,就没有办法回去修复我们的错误。这个 问题的一个解决方案是采取转型策略。转型联合分类的工作原理是为输入的标签创建一个初 始值,然后反复提炼那个值,尝试修复相关输入之间的不一致。Brill 标注器,5.6节描述的, 是这种策略的一个很好的例子。
#另一种方案是为词性标记所有可能的序列打分,选择总得分最高的序列。隐马尔可夫模 型就采取这种方法。隐马尔可夫模型类似于连续分类器,它不光看输入也看已预测标记的历 史。然而,不是简单地找出一个给定的词的单个最好的标签,而是为标记产生一个概率分布。 然后将这些概率结合起来计算标记序列的概率得分,最高概率的标记序列会被选中。

#6.2 有监督分类的更多例子
#句子分割   可以看作是一个标点符号的分类任务:每当我们遇到一个可能会结束一个句子 的符号,如句号或问号,我们必须决定它是否终止了当前句子。
#第一步是获得一些已被分割成句子的数据,将它转换成一种适合提取特征的形式:
sents = nltk.corpus.treebank_raw.sents()
tokens = []
boundaries = set()
offset = 0
for sent in nltk.corpus.treebank_raw.sents():
    tokens.extend(sent)
    offset += len(sent)
    boundaries.add(offset - 1)
#在这里,tokens是单独句子标识符的合并链表,boundaries 是一个包含所有句子边 界标识符索引的集合。下一步,我们需要指定用于决定标点是否表示句子边界的数据特征:
def punct_features(tokens, i):
    return {'next-word-capitalized': tokens[i + 1][0].isupper(), 'prevword': tokens[i-1].lower(),'punct': tokens[i],'prev-word-is-one-char': len(tokens[i-1]) == 1}
#基于这一特征提取器,我们可以通过选择所有的标点符号创建一个加标签的特征集的链 表,然后标注它们是否是边界标识符:
featuresets = [(punct_features(tokens, i), (i in boundaries))  for i in range(1, len(tokens)-1)  if tokens[i] in '.?!']
#使用这些特征集,我们可以训练和评估一个标点符号分类器:
size = int(len(featuresets) * 0.1)
train_set, test_set = featuresets[size:], featuresets[:size]
classifier = nltk.NaiveBayesClassifier.train(train_set)
print(nltk.classify.accuracy(classifier, test_set))

#识别对话行为类型
#。识别对话中言语下的对话行为是理 解谈话的重要的第一步。NPS聊天语料库
#第一步是提取基本的消息数据。我们将调用xml_posts()来得到一个数 据结构,表示每个帖子的XML 注释:
# posts = nltk.corpus.nps_chat.xml_posts()[:10000]
# # 下一步,我们将定义一个简单的特征提取器,检查帖子包含什么词:
# def dialogue_act_features(post):
#     features = {}
#     for word in nltk.word_tokenize(post):
#         features['contains(%s)' % word.lower()] = True
#     return features
#最后,我们通过为每个帖子提取特征(使用 post.get('class') 获得一个帖子的对话行 为类型)构造训练和测试数据,并创建一个新的分类器:
# featuresets = [(dialogue_act_features(post.text), post.get('class'))  for post in posts]
# size = int(len(featuresets) * 0.1)
# train_set, test_set = featuresets[size:], featuresets[:size]
# classifier = nltk.NaiveBayesClassifier.train(train_set)
# print(nltk.classify.accuracy(classifier, test_set))

#识别文字蕴含
# def rte_features(rtepair):
#     extractor = nltk.RTEFeatureExtractor(rtepair)
#     features = {}
#     features['word_overlap'] = len(extractor.overlap('word'))
#     features['word_hyp_extra'] = len(extractor.hyp_extra('word'))
#     features['ne_overlap'] = len(extractor.overlap('ne'))
#     features['ne_hyp_extra'] = len(extractor.hyp_extra('ne'))
#     return features
# #为了说明这些特征的内容,我们检查前面显示的文本/假设对34 的一些属性:
# rtepair = nltk.corpus.rte.pairs(['rte3_dev.xml'])[33]
# extractor = nltk.RTEFeatureExtractor(rtepair)
# print(extractor.text_words)
# print(extractor.hyp_words)
#nltk.classify.rte_classify 模块使用这些方法在合并的RTE测试数据上取得了刚刚超 过58%的准确率。

# 6.3  评估
#测试集
#准确度
# 之前我们选择测试集和开发集,都是在一个原有集合下。这样,示例相似程度很大,不利于推广到其他数据集。而评估最简单的度量就是准确度,即: accuracy()
# 函数。除了这个,精确度、召回率和F-度量值也确实影响了准确度。
#
# - 精确度:发现项目中多少是相关的。TP/(TP+FP)
#
# - 召回率:表示相关项目发现了多少。TP(TP+FN)
#
# - F-度量值:精确度和召回率的调和平均数。
#
# 其中,T:true;P:Positive;F:false;N:negative。组合即可。例如TP:真阳性(正确识别为相关的),TN:真阴性(相关项目中错误识别为不想关的)
#混淆矩阵
#当处理有3 个或更多的标签的分类任务时,基于模型错误类型细分模型的错误是有信息 量的。一个混淆矩阵是一个表,其中每个 cells[i,j]表示正确的标签i 被预测为标签j 的次数。 因此,对角线项目(即cells[i,i])表示正确预测的标签,非对角线项目表示错误。
#我们为5.4节中开发的 unigram 标注器生成一个混淆矩阵:
# 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)
# def tag_list(tagged_sents):
#     return [tag for sent in tagged_sents for (word, tag) in sent]
# def apply_tagger(tagger, corpus):
#     return [tagger.tag(nltk.tag.untag(sent)) for sent in corpus]
# gold = tag_list(brown.tagged_sents(categories='editorial'))
# test = tag_list(apply_tagger(t2, brown.tagged_sents(categories='editorial')))
# cm = nltk.ConfusionMatrix(gold, test)
# print(cm)
#交叉验证

#6.4 决策树
#接下来的三节中,我们将仔细看看可用于自动生成分类模型的三种机器学习方法:决策 树、朴素贝叶斯分类器和最大熵分类器。
#熵和信息增益
#正如之前提到的,有几种方法来为决策树桩确定最有信息量的特征。一种流行的替代方 法,被称为信息增益,当我们用给定的特征分割输入值时,衡量它们变得更有序的程度。要 衡量原始输入值集合如何无序,我们计算它们的标签的墒,如果输入值的标签非常不同,墒 就高;如果输入值的标签都相同,墒就低。特别是,熵被定义为每个标签的概率乘以那个标 签的log 概率的总和。
#名字性别预测任务中标签的墒,作为给定名字集合中是男性的百分比的函数。
import math
def entropy(labels):
    freqdist = nltk.FreqDist(labels)
    probs = [freqdist.freq(l) for l in nltk.FreqDist(labels)]
    return -sum([p * math.log(p,2) for p in probs])
print(entropy(['male', 'female', 'maale', 'male']))
#一旦我们已经计算了原始输入值的标签集的墒,就可以判断应用了决策树桩之后标签会 变得多么有序。为了这样做,我们计算每个决策树桩的叶子的熵,利用这些叶子熵值的平均 值(加权每片叶子的样本数量)。信息增益等于原来的熵减去这个新的减少的熵。信息增益 越高,将输入值分为相关组的决策树桩就越好,于是我们可以通过选择具有最高信息增益的 决策树桩来建立决策树。
#决策树也有一些缺点。一个问题是,由于决策树的每个分支会划分训练数据,在 训练树的低节点,可用的训练数据量可能会变得非常小。因此,这些较低的决策节点可能过 拟合训练集,学习模式反映训练集的特质而不是问题底层显著的语言学模式。对这个问题的 一个解决方案是当训练数据量变得太小时停止分裂节点。另一种方案是长出一个完整的决策 树,但随后进行剪枝剪去在开发测试集上不能提高性能的决策节点
#决策树的第二个问题是,它们强迫特征按照一个特定的顺序进行检查,即使特征可能是 相对独立的。例如:按主题分类文档(如体育、汽车或谋杀之谜)时,特征如hasword(foot ball),极可能表示一个特定标签,无论其他的特征值是什么。由于决定树顶部附近的空间有 限,大部分这些特征将需要在树中的许多不同的分支中重复。因为越往树的下方,分支的数 量成指数倍增长,重复量可能非常大。
#决策树需要按一个特定的顺序检查特征的事实,限制了它们的利用相对独立的特征的能 力。我们下面将讨论的朴素贝叶斯分类方法克服了这一限制,允许所有特征“并行”的起作 用。

#6.5 朴素贝叶斯分类器
#在朴素贝叶斯分类器中,每个特征都得到发言权,来确定哪个标签应该被分配到一个给 定的输入值。为一个输入值选择标签,朴素贝叶斯分类器以计算每个标签的先验概率开始, 它由在训练集上检查每个标签的频率来确定。之后,每个特征的贡献与它的先验概率组合, 得到每个标签的似然估计。似然估计最高的标签会分配给输入值。
#潜在概率模型
#零计数和平滑
#非二元特征 数字特征可以通过装箱转换为二元特征,装箱是 用特征,如“4<X <6”,替换它们。
# 6.6 最大熵分类器
#最大熵分类器使用了一个与朴素贝叶斯分类器使用的模型非常相似的模型。不是使用概 率设置模型的参数,它使用搜索技术找出一组将最大限度地提高分类器性能的参数。特别的, 它查找使训练语料的整体可能性最大的参数组。
#最大熵分类器模型是一朴素贝叶斯分类器模型的泛化。像朴素贝叶斯模型一样,最大熵 分类器为给定的输入值计算每个标签的可能性,通过将适合于输入值和标签的参数乘在一 起。朴素贝叶斯分类器模型为每个标签定义一个参数,指定其先验概率,为每个(特征,标 签)对定义一个参数,指定独立的特征对一个标签的可能性的贡献。
#生成式分类器对比条件式分类器
# 朴素贝叶斯分类器和最大熵分类器之间的一个重要差异是它们可以被用来回答问题的 类型。
# 朴素贝叶斯分类器是一个生成式分类器的例子,建立一个模型,预测P(input, label), 即(input, label)对的联合概率。
# 因此,生成式模型可以用来回答下列问题:
# 1. 一个给定输入的最可能的标签是什么?
# 2. 对于一个给定输入,一个给定标签有多大可能性?
# 3. 最有可能的输入值是什么?
# 4. 一个给定输入值的可能性有多大?
# 5. 一个给定输入具有一个给定标签的可能性有多大?
# 6. 对于一个可能有两个值中的一个值(但我们不知道是哪个)的输入,最可能的标签 是什么?
# 另一方面,最大熵分类器是条件式分类器的一个例子。条件式分类器建立模型预测 P(l abel|input)——一个给定输入值的标签的概率。
# 因此,条件式模型仍然可以被用来回答问题 1 和2。然而,条件式模型不能用来回答剩下的问题 3-6。

猜你喜欢

转载自blog.csdn.net/zlp_zky/article/details/83110511