zl程序教程

您现在的位置是:首页 >  其他

当前栏目

NLTK-006:分类文本(性别鉴定)

2023-03-15 22:04:34 时间

分类是为给定的输入选择正确的类标签的任务,在基本的分类任务中,每个输入被认为是与所有其它输入隔离的,并且标签集是预先定义的。下面是分类任务的一些例子:

  • 判断一封邮件是否是垃圾邮件。
  • 从一个固定的主题领域列表中,如‘体育’、‘技术’、‘政治’,决定新闻报道的主题是什么。

基本的分类任务有许多有趣的变种。 例如:在多类分类中,每个实例可以分配多个标签,在开放性分类中,标签集是没有定义的。在序列分类中,一个输入链表作为一个整体分类。

有监督分类

但如果分类的建立包含每个输入的正确标签的训练语料,被称为 有监督分类 。 框架图:

(a):在训练过程中,特征提取器用来将每一个输入的值转换为特征集,这些特征集捕捉每个输入中应被应用于对其分类的基本信息。特征集与标签的配对被送入机器学习算法,生成模型。 (b):在预测过程中,相同的特征提取器被用来将未见过的输入转换为特征集,之后,这些特征集被送入模型产生预测标签。

性别鉴定:

在我们之间的名字语料库中,包括了8000个按性别分类的名字。

names = nltk.corpus.names
print(names.fileids())
male_names = names.words('male.txt')
female_names = names.words('female.txt')
print([ w for w in male_names if w in female_names ])

输出 : [‘Abbey’, ‘Abbie’, ‘Abby’, ‘Addie’, …‘Willi’, ‘Willie’, ‘Willy’, ‘Winnie’, ‘Winny’, ‘Wynn’]

一般来说,以字母 a 结尾的名字都是女性名字。所以我们可以提取最后一个字母 name[-1] 则:

cfd = nltk.ConditionalFreqDist((fileid,name[-1]) for fileid in names.fileids() for name in names.words(fileid))
cfd.plot()

输出条件频率分布:

可以由此图看到,大多数名字以 a,e,i 结尾的名字是女性,以 k,o,r,s 和 t 结尾的更可能是男性。以 h,l 结尾的男女差不多。

那我们这里就建立一个分类器来更精确的模拟这些差异。

创建一个分类器的第一步是决定输入的什么样的 特征 是能相关的,以及如何为那些特征 编码 。在这个例子中,我们一开始只是寻找一个给定的名称的最后一个字母。以下 特征提取器 函数建立了一个字典,包含有关给定名称的相关信息:

def gender_features(word):
    return {'last_letter':word[-1]}
print(gender_features('Shark'))

这个函数返回的字典被称为 特征集 ,映射特征名称到他们的值。特征名称是简单类型的值,如布尔,数字和字符串。

现在我们已经建立了一个特征提取器,我们需要准备一个例子和一个对应类标签的链表:

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)			# shuffle没有返回值

接下来,我们使用特征提取器处理名称数据,并划分特征集的结果链表为一个 训练集 和一个 测试集。训练集用于训练一个新的"朴素贝叶斯"分类器。

featuresets = [(gender_features(n),g) for (n,g) in names]
train_set , test_set = featuresets[500:],featuresets[:500]  

下面测试下没有出现在训练数据中的名字:

classiffier = nltk.NaiveBayesClassifier.train(train_set)    #朴素贝叶斯分类器
print(classiffier.classify(gender_features('Neo')))         #分类
print(classiffier.classify(gender_features('Trinity')))

那我们可以使用大数据量来系统的评估这个分类器:

print(nltk.classify.accuracy(classiffier,test_set))     #使用测试集
#	accuracy	准确率,对于给定的测试数据集,分类器正确分类的样本数和总样本数之比

输出结果约为: 0.77

最后我们可以检查分类器,确定哪些特征对于区分名字的性别是最有效的。

print(classiffier.show_most_informative_features(10))

通过输出结果可以看到,训练集中以 ‘a’ 结尾的名字中女性是男性的 34 倍,而以 ‘k’ 结尾名字中男性是女性的30倍。 这些比率叫做 似然比,可以用于比较不同特征-结果关系。

ps:我们也可以修改 gender_features()函数,为分类器提供名称的长度、它的第一个字母以及任何其他看起来可能有用的特征。再用这些新特征训练分类器,并测试其准确性。

而且在处理大型语料的时候,构建一个包含每一个实例的特征的单独的链表会使用大量的内存。在这种情况下,使用函数 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])

选择正确的特征:

选择相关的特征,并决定如何用一个学习方法去编码他们,这对学习方法提取一个好的模型可以产生巨大的影响。建立一个分类器的很多有趣的工作之一是找出哪些特征可能是相关的,以及我们如何能够表示他们。虽然使用相当简单而明显的特征集往往可以得到像样的性能,但是使用精心构建的基于对当前任务的透彻理解的特征,通常会显著提高收益。

然而特征提取是通过反复的试验和错误的过程建立的,由哪些信息是与问题想关的直觉指引的。你需要找出所有特征,然后再选出实际有用的。

举个例子: 以上例为基础,一个过拟合性别特征提取器。 (过拟合就是说提取的特征集包含了大量的指定特征,从而导致对于相对较小的名字语料库过拟合)

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

输出:{‘firstletter’: ‘j’, ‘lastletter’: ‘n’, ‘count(a)’: 0, …, ‘has(z)’: False}

当出现过拟合的时候,运作在小训练集上时会出现大问题。我们拿前面的例子来测试下:

featuresets2 = [(gender_features2(n),g) for (n,g) in names]
train_set , test_set = featuresets2[500:],featuresets2[:500]
classiffier = nltk.NaiveBayesClassifier.train(train_set)    #朴素贝叶斯分类器
print(nltk.classify.accuracy(classiffier,test_set))     #使用测试集评估分类器

输出结果0.7多,该系统的精度要比之前的只测最后一个字母的分类器低了不少。

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

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

输出得到的准确率是 0.75

使用开发测试集,我们可以生成一个分类器预测名字性别时的错误列表。然后进行案列检查,看看预测错在了呢,然后进行相应的调整特征集。

errors = []
for (name,tag) in devtest_names:
            guess = classifier.classify(gender_features(name))
            if guess != tag:
                    errors.append((tag,guess,name))
print(errors)

输出大约有200个错误。

for (tag,guess,name) in sorted(errors):
    print('correct=%-8s guess=%-8s name=%-30s'%(tag,guess,name))

输出结果: correct=female guess=male name=Ag correct=female guess=male name=Alis correct=female guess=male name=Allsun correct=female guess=male name=Amargo correct=female guess=male name=Annabell correct=female guess=male name=Ardeen … … 就看上面这几个,尽管以 n 结尾的一般为男性,但是 en,un结尾的确是女性。 所以我们应该进一步的优化特征提取器。

def gender_features(word):
    return {'suffix1':word[-1],
            'suffix2':word[-2]}

再测试下:

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

果然,准确率0.77,要比之前高了一点。 但是,错误分析是需要不断重复检查,确保该分类器不会开始反应开发测试集的特质。