《用Python进行自然语言处理》第6章 学习分类文本

1. 我们怎样才能识别语言数据中能明显用于对其分类的特征?
2. 我们怎样才能构建语言模型,用于自动执行语言处理任务?

3. 从这些模型中我们可以学到哪些关于语言的知识?

6.1 有监督分类

性别鉴定

#创建一个分类器的第一步是决定输入的什么样的特征是相关的,以及如何为那些特征编码

#以下特征提取器 函数建立一个字典,包含有关给定名称的相关信息:
def gender_features(word):
    return {'last_letter': word[-1]}
print(gender_features('Shrek'))
#这个函数返回的字典被称为特征集,映射特征名称到它们的值


#现在,我们已经定义了一个特征提取器,我们需要准备一个例子和对应类标签的链表:
import nltk
from nltk.corpus import names
import random
labeled_names = ([(name, 'male') for name in names.words('male.txt')] + 
                 [(name, 'female') for name in names.words('female.txt')])
random.shuffle(labeled_names)
print(labeled_names[:5])


#接下来,我们使用特征提取器处理名称数据,并划分特征集的结果链表为一个训练集和一个测试集。
#训练集用于训练一个新的“朴素贝叶斯”分类器。
featuresets = [(gender_features(n), gender) for (n, gender) in labeled_names]
print(featuresets[:5])#看一下featuresets长什么样子。。。
train_set, test_set = featuresets[500:], featuresets[:500]
classifier = nltk.NaiveBayesClassifier.train(train_set)


#测试一些没有出现在训练数据中的名字
print(classifier.classify(gender_features('Neo')))
print(classifier.classify(gender_features('Trinity')))

#评估
print(nltk.classify.accuracy(classifier, test_set))

#检查分类器,确定哪些特征对于区分名字的性别是最有效的。
print(classifier.show_most_informative_features(5))#比率称为似然比,可以用于比较不同特征-结果关系

选择正确的特征

#建立一个分类器的很多有趣的工作之一是找出哪些特征可能是相关 的,以及我们如何能够表示它们。
#虽然使用相当简单而明显的特征集往往可以得到像样的性 能,
#但是使用精心构建的基于对当前任务的透彻理解的特征,通常会显著提高收益。


#一个特征提取器,过拟合性别特征。
#这个特征提取器返回的特征集包括大量指 定的特征,从而导致对于相对较小的名字语料库过拟合。
def gender_features2(name):
    features = {}
    features['firstletter'] = name[0].lower()
    features['lastletter'] = name[-1].lower()
    for letter in 'abcdefghijklmnopqistuvwxyz':
        features["count(%s)"%letter] = name.lower().count(letter)
        features["has(%s)"%letter] = (letter in name.lower())
    return features

print(gender_features2('John'))

#如果你提供太多的特征,那么该算法将高度依赖你的训练数据的特性而,一般化到新的例子的效果不会很好。
#这 个问题被称为过拟合,当运作在小训练集上时尤其会有问题。
featuresets = [(gender_features2(n), gender) for (n, gender) in labeled_names]
train_set, test_set = featuresets[500:], featuresets[:500]
classifier = nltk.NaiveBayesClassifier.train(train_set)
print(nltk.classify.accuracy(classifier, test_set))


#旦初始特征集被选定,完善特征集的一个非常有成效的方法是错误分析。
#首先,我们选择一个开发集,包含用于创建模型的语料数据。
#然后将这种开发集分为训练集和开发测试集。
train_names = labeled_names[1500:]
devtest_names = labeled_names[500:1500]
test_names = labeled_names[:500]


#训练集用于训练模型,开发测试集用于进行错误分析,测试集用于系统的最终评估。
train_set = [(gender_features2(n), g) for (n, g) in train_names]
devtest_set = [(gender_features2(n), g) for (n, g) in devtest_names]
test_set = [(gender_features2(n), g) for (n, g) in test_names]
classifier = nltk.NaiveBayesClassifier.train(train_set)
print(nltk.classify.accuracy(classifier, devtest_set))



#用于训练有监督分类器的语料数据组织图。语料数据分为两类:开发集和测试集。 开发集通常被进一步分为训练集和开发测试集。
error = []
for (name, tag) in devtest_names:
    guess = classifier.classify(gender_features(name))
    if guess != tag:
        error.append((tag, guess, name))

#然后,可以检查个别错误案例,在那里该模型预测了错误的标签,尝试确定什么额外信息将使其能够作出正确的决定
#然后可以 相应的调整特征集。我们已经建立的名字分类器在开发测试语料上产生约 100 个错误
for (tag, guess, name) in sorted(error):
    print('correct=%-8s guess=%-8s name=%-30s'%(tag, guess, name))
    break
    
    
#调整我们的特征提取器包括两个字母后缀的特征:    
def gender_features(word):
    return {'suffix1': word[-1:], 
            'suffix1': word[-2]}
#使用新的特征提取器重建分类器,我们看到测试数据集上的性能提高了近 3 个百分点
classifier = nltk.NaiveBayesClassifier.train(train_set)
print(nltk.classify.accuracy(classifier, devtest_set))

文档分类

#建立分类器,自动给新文档添加适当的类别标签
#首先,我们构造一个标记了相应 类别的文档清单。
#对于这个例子,我们选择电影评论语料库,将每个评论归类为正面或负面。
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)


#对于文档主题识别,我们可以为每个词定义一个特性表示该文档是否包含这个词。
#为了限制分类器需要处理的特征的数目,我们一开始构建一个整个语料库中前 2000 个最频繁词的链表。
#然后,定义一个特征提取器,简单地检查这些词是否在一个给定的文档中。


#一个文档分类的特征提取器,其特征表示每个词是否在一个给定的文档中
all_words = nltk.FreqDist(w.lower() for w in movie_reviews.words())
word_features = list(all_words)[: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')))


#已经定义了我们的特征提取器,可以用它来训练一个分类器,为新的电影评 论加标签
#为了检查产生的分类器可靠性如何,我们在测试集上计算其准确性
#使用 show_most_informative_features()来找出哪些特征是分类器发现最有信息量的

#训练和测试一个分类器进行文档分类。
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))
print(classifier.show_most_informative_features(5))


#练一个分类器来 算出哪个后缀最有信息量。首先,让我们找出最常见的后缀:
from nltk.corpus import brown
suffix_fdist = nltk.FreqDist()
for word in brown.words():
    word = word.lower()
    suffix_fdist[word[-1:]] += 1
    suffix_fdist[word[-2:]] += 1
    suffix_fdist[word[-3:]] += 1
common_suffixes = [suffix for (suffix, count) in suffix_fdist.most_common(100)]
print(common_suffixes[:5])



#现在,我们已经定义了我们的特征提取器,可以用它来训练一个新的“决策树”的分类器
def pos_features(word):
    features = {}
    for suffix in common_suffixes:
        features['endswith({})'.format(suffix)] = word.lower().endswith(suffix)
    return features

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

探索上下文语境

#了采取基于词的上下文的特征,我们必须修改以前为我们的特征提取器定义的模式。
#不是只传递已标注的词,我们将传递整个(未标注的)句子,以及目标词的索引

#一个词性分类器,它的特征检测器检查一个词出现的上下文以便决定应该分配 的词性标记。特别的,前面的词被作为一个特征。

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')
#print(tagged_sents)


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

序列分类

#为了捕捉相关的分类任务之间的依赖关系,我们可以使用联合分类器模型,收集有关输 入,选择适当的标签。
#在词性标注的例子中,各种不同的序列分类器 模型可以被用来为一个 给定的句子中的所有的词共同选择词性标签。


#一种序列分类器策略,称为连续分类或贪婪序列分类,是为第一个输入找到最有可能的 类标签,
#然后使用这个问题的答案帮助找到下一个输入的最佳的标签。
#这个过程可以不断重 复直到所有的输入都被贴上标签


#使用连续分类器进行词性标注。
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))

其他序列分类方法

#隐马尔可夫模型类似于连续分类器,它不光看输入也看已预测标记的历史
#不是简单地找出一个给定的词的单个最好的标签,而是为标记产生一个概率分布。
#然后将这些概率结合起来计算标记序列的概率得分,最高概率的标记序列会被选中。

#最大熵马尔可夫模型和线性链条件随机场模型 

6.2 有监督分类的更多例子

句子分割

#句子分割可以看作是一个标点符号的分类任务:
#每当我们遇到一个可能会结束一个句子 的符号,如句号或问号,我们必须决定它是否终止了当前句子。

#第一步是获得一些已被分割成句子的数据,将它转换成一种适合提取特征的形式
#tokens 是单独句子标识符的合并链表,
#boundaries 是一个包含所有句子边界标识符索引的集合。
sents = nltk.corpus.treebank_raw.sents()
print(sents[:2])
tokens = []
boundaries = set()
offset = 0
for sent in sents:
    tokens.extend(sent)
    offset += len(sent)
    boundaries.add(offset-1)

    
    
#下一步,我们需要指定用于决定标点是否表示句子边界的数据特征:
def punct_features(tokens, i):
    return {'next-word-capitalized': tokens[i+1][0].isupper(),
            'prev-word': 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 聊天语料库,包括超过 10,000 个来自即时消息会话的帖子。 
#这些帖子都已经被贴上 15 种对话行为类型中的一种标签,例如:“陈述”,“情感”,“yn 问 题”,“Continuer”。
#因此,我们可以利用这些数据建立一个分类器,识别新的即时消息帖子的对话行为类型。

import nltk
#第一步是提取基本的消息数据。我们将调用 xml_posts()来得到一个数据结构,表示每个帖子的 XML 注释:
posts = nltk.corpus.nps_chat.xml_posts()[:10000]
#print(list(posts))

#下一步,我们将定义一个简单的特征提取器,检查帖子包含什么词:
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))

识别文字蕴含

#识别文字蕴含(Recognizing textual entailment(RTE))是判断文本 T 的一个给定片段
#是否蕴含着另一个叫做“假设”的文本

#认识文字蕴涵”的特征提取器。
#RTEFeatureExtractor 类建立了一个除去一些停用词后在文本和假设中都有的词汇包,然后计算重叠和差异。
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
rtepair = nltk.corpus.rte.pairs(['rte3_dev.xml'])[33]
extractor = nltk.RTEFeatureExtractor(rtepair)
print(extractor.text_words)
print(extractor.hyp_words)
print(extractor.overlap('word'))
print(extractor.overlap('ne'))
print(extractor.hyp_extra('word'))

扩展到大型数据集

#Python 提供了一个良好的环境进行基本的文本处理和特征提取
#如果你尝试在 大型数据集使用纯 Python 的机器学习实现(如 nltk.NaiveBayesClassifier),
#你可能会发 现学习算法会花费大量的时间和内存。

6.3 评估

测试集

#建立测试集时,往往是一个可用于测试的和可用于训练的数据量之间的权衡
#选择测试集时另一个需要考虑的是测试集中实例与开发集中的实例的相似程度

#例如:考虑词性标注任务。
#在一种极端情况,我们可以通过从一个反映单一的文体(如新闻)的数据源随机分配句子,创建训练集和测试集:
import random
from nltk.corpus import brown 
tagged_sents = list(brown.tagged_sents(categories='news'))
random.shuffle(tagged_sents)
size = int(len(tagged_sents) * 0.9)
train_set, test_set = tagged_sents[size:], tagged_sents[:size]
#在这种情况下,我们的测试集和训练集将是非常相似的。
#训练集和测试集均取自同一文体,所以我们不能相信评估结果可以推广到其他文体。
  
    
#确保训练集和测试集来自不同的文件:
file_ids = brown.fileids(categories='news')
size = int(len(file_ids) * 0.1)
train_set = brown.tagged_sents(file_ids[size:])
test_set = brown.tagged_sents(file_ids[:size])

#如果我们要执行更令人信服的评估,可以从与训练集中文档联系更少的文档中获取测试集。
train_set = brown.tagged_sents(categories='news')
test_set = brown.tagged_sents(categories='fiction')

准确度

#用于评估一个分类最简单的度量是准确度,测量测试集上分类器正确标注的输入的比例。
#nltk.classify.accuracy()函数会在给定的测试集上计算分类器 模型的准确度:

精确度和召回率

# 真阳性是相关项目中我们正确识别为相关的。
# 真阴性是不相关项目中我们正确识别为不相关的。
# 假阳性(或 I 型错误)是不相关项目中我们错误识别为相关的。 
# 假阴性(或 II 型错误)是相关项目中我们错误识别为不相关的。


# 精确度(Precision),表示我们发现的项目中有多少是相关的,TP/(TP+ FP)。
# 召回率(Recall),表示相关的项目中我们发现了多少,TP/(TP+FN)。
# F-度量值(F-Measure)(或 F-得分,F-Score),组合精确度和召回率为一个单独的得分,
# 被定义为精确度和召回率的调和平均数(2 × Precision × Recall)/(Precision + R e ca l l )。

混淆矩阵

#一个混淆矩阵是一个表,其中每个 cells[i,j]表示正确的标签 i 被预测为标签 j 的次数。 
#因此,对角线项目(即 cells[i,i])表示正确预测的标签,非对角线项目表示错误

交叉验证

#在不同的测试集上执行多个评估,然后组合这些评估的得 分,这种技术被称为交叉验证。

#特别是,我们将原始语料细分为 N 个子集称为折叠(folds)。 
#对于每一个这些的折叠,我们使用除这个折叠中的数据外其他所有数据训练模型,然后在这个折叠上测试模型。
#即使个别的折叠可能是太小了而不能在其上给出准确的评价分数,综合 评估得分是基于大量的数据,因此是相当可靠的。

#采用交叉验证的优势是,它可以让我们研究不同的训练集上性能变化有多大。
#如果我们从所有 N 个训练集得到非常相似的分数,然后我们可以相当有信心,得分是准确的。
#另一方面,如果 N 个训练集上分数很大不同,那么,我们应该对评估得分 的准确性持怀疑态度。

6.4 决策树

#决策树是一个简单的为输入值选择标签的流程图。
#这个流程图由检查特征值的决策节点 和分配标签的叶节点组成。
#为输入值选择标签,我们以流程图的初始决策节点(称为其根节点)开始。

熵和信息增益


#计算标签链表的墒。
import math
def entropy(labels):
    freqdist = nltk.FreqDist(labels)
    probs = [freqdist.freq(l) for l in freqdist]
    return -sum(p * math.log(p, 2) for p in probs)
print(entropy(['male', 'male', 'male', 'male'])) 
print(entropy(['male', 'female', 'male', 'male']))
print(entropy(['female', 'male', 'female', 'male']))
print(entropy(['female', 'female', 'male', 'female']))
print(entropy(['female', 'female', 'female', 'female'])) 

freqdist = nltk.FreqDist(['male', 'male', 'male', 'male'])
for l in freqdist:
    print(l)
    print(freqdist.freq(l))

6.8 小结


为语料库中的语言数据建模可以帮助我们理解语言模型,也可以用于预测新语言数据。

有监督分类器使用加标签的训练语料库来建立模型,基于输入的特征,预测那个输入的标签。

有监督分类器可以执行多种 NLP 任务,包括文档分类、词性标注、语句分割、对话行为类型识别以及确定蕴含关系和很多其他任务。
训练一个有监督分类器时,你应该把语料分为三个数据集:用于构造分类器模型的训练集,用于帮助选择和调整模型特性的开发测试集,以及用于评估最终模型性能的测试集。

评估一个有监督分类器时,重要的是你要使用新鲜的没有包含在训练集或开发测试集中的数据。否则,你的评估结果可能会不切实际地乐观。

决策树可以自动地构建树结构的流程图,用于为输入变量值基于它们的特征加标签,虽然它们易于解释,但不适合处理特性值在决定合适标签过程中相互影响的情况。

在朴素贝叶斯分类器中,每个特征决定应该使用哪个标签的贡献是独立的。它允许特征值间有关联,但当两个或更多的特征高度相关时将会有问题。

最大熵分类器使用的基本模型与朴素贝叶斯相似;不过,它们使用了迭代优化来寻找使训练集的概率最大化的特征权值集合。

大多数从语料库自动构建的模型都是描述性的,也就是说,它们让我们知道哪些特征与给定的模式或结构相关,但它们没有给出关于这些特征和模式之间的因果关系的任何信 息。

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值