1 有监督的分类
1.1 分类相关概念
分类是为给定输入选择正确的类标签的任务。比如判断一封Email是否是垃圾邮件,确定一篇新闻的主题。
如果分类需要人工标准的标签进行训练,则称为有监督分类。有监督的学习使用下图的框架。
在框架中,特征是一个非常重要的概念。使用分类器首先要决定选择什么 样的特征,以及对特征进行编码。如果没有特征直接输入原始数据,则数据会十分离散而难以训练出有用的模型。
1.2 NLTK的分类器介绍
在NLTK中提供了NaiveBayesClassifier,DecisionTreeClassifier,MaxentClassifier三种类型的分类器。分类器都提供了类方法可以训练出一个分类器实例,有了这个实例,便能对新的样本进行分类预测,以及其进行准确度评测。
方法 | 说明 |
---|---|
train(train_set) | 类方法,用于生成一个分类器实例 |
classify(feature) | 实例方法,基于训练的模型对输入特征进行分类 |
show_most_informative_features() | 实例方法,显示训练过程中最有效的特性统计 |
nltk.classify包的工具类提供了下列的方法辅助训练及优化过程
方法 | 说明 |
---|---|
accuracy(classifier,test_set) | 评估分类器在测试集上的准确度 |
apply_feature(func,data) | 将特征函数func应用到data上,类似于map操作 |
1.3 文本分类示例
下面基于nltk的movie_reviews语料库的正负向标注数据训练一个简单的分类器,用来预测评论的正负向。PS:语料库文本被分两类,pos与neg。
代码先统计出最常用2000个词,简单假设这些词的使用情况可以决定一篇评论的正负向情感。针对每篇文档,特征提取器计算其在这2000个词上出现的情况,若出现则在特性中标记为True,否则标记为False。
import random
import nltk
from nltk.corpus import movie_reviews
docs = [(list(movie_reviews.words(fileid)),category)
for category in movie_reviews.categories()
for fileid in movie_reviews.fileids(category)]
random.shuffle(docs)
all_words = nltk.FreqDist(w.lower() for w in movie_reviews.words())
most_common_word = [word for (word,_) in all_words.most_common(2000)]
def doc_feature(doc):
doc_words = set(doc)
feature = {}
for word in most_common_word:
feature[word] = (word in doc_words)
return feature
train_set = nltk.apply_features(doc_feature,docs[:100])
test_set = nltk.apply_features(doc_feature,docs[100:])
classifier = nltk.NaiveBayesClassifier.train(train_set)
print nltk.classify.accuracy(classifier,test_set) #0.735
classifier.show_most_informative_features()
'''
Most Informative Features
hard = True neg : pos = 8.6 : 1.0
worst = True neg : pos = 7.1 : 1.0
giant = True neg : pos = 7.1 : 1.0
unlike = True pos : neg = 6.8 : 1.0
entire = True pos : neg = 5.6 : 1.0
headed = True neg : pos = 5.6 : 1.0
gun = True neg : pos = 5.6 : 1.0
attempt = True neg : pos = 5.2 : 1.0
small = True pos : neg = 5.0 : 1.0
'''
可以看到出现hard,worst,giant词的文档通常是neg的,出现entire,small词的文档通常是正向的。基于特征词的分类器在测试集上达到了73.5%的正确率。精心构造的对于当前任务透彻理解的特性,通常可以显著提高分类的正确性。
nltk.sentiment包提供了两个情感分析器的实现,使用示例见这里。
类名 | 说明 |
---|---|
SentimentAnalyzer | 实现和上面代码的思想一致,只是简单做了些封装,支持传入不同的分类器和使用串联的多个特征提取器。可以直接使用此类进行中文的情感分析。 |
SentimentIntensityAnalyzer | 实现基于《VADER: A Parsimonious Rule-based Model for Sentiment Analysis of Social Media Text》这篇文章,思想是基于词典对词进行打分,得到了个分值来表示句子情感的极性。这个类由于规则词典的词都是英文,如果想使用它处理中文,需要自己统计提取中文极性词并打分,以替换掉英文词典。 |
1.4 过拟合和欠拟合
特性如果选择的太多则会导致算法在训练集上正确率很高,而在测试集上较差的情况,此时就出现了过拟合。尤其是训练数据集较小的情况下更容易出现。因此要平衡特征的选取,以取得算法在未知数据上更好泛化能力。
过拟合的反面是欠拟合,即特征不足。此时通过对失败情况进行分析,改进特征提取器以重建分类器是提升模型准确性的一种方法。但要注意防止出现过拟合。
2 基于上下文的词性标注器
在上一篇文章中,主要介绍了N-gramTagger的词性标注器的使用方法,其主要思想是基于词的词性的历史出现次数进行推测。下面介绍实现一种基于分类器的词性标注器,它借助词本身,词的上下文,标注信息的上下文等特征来训练一个词性分类器,从而实现词性标注。
import nltk
from nltk.corpus import brown
def pos_feature_use_hist(sentence,i,history):
features = {
'suffix-1': sentence[i][-1:],
'suffix-2': sentence[i][-2:],
'suffix-3': sentence[i][-3:],
'pre-word': 'START',
'prev-tag': 'START'
}
if i>0:
features['prev-word'] = sentence[i-1],
features['prev-tag'] = history[i-1]
return features
class ContextPosTagger(nltk.TaggerI):
def __init__(self,train):
train_set = []
for tagged_sent in train:
untagged_sent = nltk.tag.untag(tagged_sent)
history = []
for i,(word,tag) in enumerate(tagged_sent):
features = pos_feature_use_hist(untagged_sent,i,history)
train_set.append((features,tag))
history.append(tag)
print train_set[:10]
self.classifier = nltk.NaiveBayesClassifier.train(train_set)
def tag(self,sent):
history = []
for i,word in enumerate(sent):
features = pos_feature_use_hist(sent,i,history)
tag = self.classifier.classify(features)
history.append(tag)
return zip(sent,history)
tagged_sents = brown.tagged_sents(categories='news')
size = int(len(tagged_sents)*0.8)
train_sents,test_sents = tagged_sents[0:size],tagged_sents[size:]
#tagger = nltk.ClassifierBasedPOSTagger(train=train_sents) # 0.881
tagger = ContextPosTagger(train_sents) #0.78
tagger.classifier.show_most_informative_features()
print tagger.evaluate(test_sents)
这个简单只考虑前一个词及前一个词的词性的标注器,在测试集上有约78%的准确度,尚没有上节的组合N-gram标注器的正确率高,这主要是例子中的特征提取器考虑十分简单的缘故。nltk默认实现了一个ClassifierBasedPOSTagger类,可以实现约88%的标注准确度。
例子中实现存在的问题是一旦一个词的词定下来,那么后面就无法修改,即使是后面发现前面判断出了错。解决这个问题有两种方法:一种是采用转换策略,即Brill标注器。另一种对所有的词性标记序列打分,选择总分最高的序列。隐含马尔可夫模型即实现此方法,此外更高级的如最大熵马尔可夫模型,线性条件随机场模型。
3 算法评估方法
3.1 精确度与召回率
对于分类算法在测试集上运行之后,数据会被分为下表的四类
预测\类别 | 正例 | 负例 |
---|---|---|
分类为正 | TP(真正例) | FP(假正例) |
分类为负 | FN(假负例) | TN(真负例) |
准确度A:(TP+TN)/ALL 分类器正确分类的比例。
精确度P:TP/(TP+FP), 预测为正的样本中,有多少真的正样本
召回率R:TP/(TP+FN),测试集中的正样本,有多少被正确分类
F1评分:(2*P*R)/(P+R),R与P的调和平均数
3.2 混淆矩阵
对于多分类任务,可以使用混淆矩阵来分析错误分类的细分信息。混淆矩阵的元素m[i,j],表示正确的类别i,被预测为类别j的次数。
import nltk
gold = [1,2,3,4]
test = [1,3,2,4]
print nltk.ConfusionMatrix(gold,test)
'''output
| 1 2 3 4 |
--+---------+
1 |<1>. . . |
2 | .<.>1 . |
3 | . 1<.>. |
4 | . . .<1>|
--+---------+
'''
4 分类器模型介绍
4.1 决策树分类器
决策树(decision tree)是一个树结构。其每个非叶节点表示一个特征属性上的测试,而每个叶节点存放一个类别。使用决策树进行决策的过程就是从根节点开始,测试待分类项中相应的特征属性,并按照其值选择输出分支,直到到达叶子节点,将叶子节点存放的类别作为决策结果。
下面是一棵形象的决策树:
4.2 朴素贝叶斯分类器
由条件概率和乘法法则: