机器学习文档Scikit-learn翻译(第三章)

3.1 加载20新闻组数据集

这些文件保存在对象的属性data中,执行tweety.data命令这些数据就会被加载进来,同样引用文件名称filenames也具有同样的效果,如

>>> len(twenty_train.data)
2257
>>> len(twenty_train.filenames)
2257

现在,让我们看看第一个被加载进的文件的第一行内容:

>>> print("\n".join(twenty_train.data[0].split("\n")[:3]))
From: sd345@city.ac.uk (Michael Collier)
Subject: Converting images to HP LaserJet III?
Nntp-Posting-Host: hampton
>>> print(twenty_train.target_names[twenty_train.target[0]])
comp.graphics

监督学习算法要求在训练集中的每篇文档都有对应的类别号。在20newsgroups案例中,类别标号是新闻组 newsgroup的名称,同时,它还与存放该文件的文件名称一样。
出于计算运算速率和空间开销的考虑,scikit-learn以整数数组的形式加载目标属性,该数组中元素值与target_names列表中类标名称的索引是一一对应的关系。每一个样本的类标都储存在tweety_train对象的target属性中。

>>> twenty_train.target[:10]
array([1, 1, 3, 3, 3, 3, 3, 2, 2, 2])

我们通过类标号也可以反向获取类别名称:

>>> for t in twenty_train.target[:10]:
... print(twenty_train.target_names[t])
...
comp.graphics
comp.graphics
soc.religion.christian
soc.religion.christian
soc.religion.christian
soc.religion.christian
soc.religion.christian
sci.med
sci.med
sci.med

你可能注意到了,样本已经被随机混洗(shuffle)了(伪随机种子方式),这极大地方便了你选择第一个样本集进行快速地训练数据模型,同时也方便了在未使用所有数据集训练模型情况下对数据模型总体的认识。

3.2 从文本文件中析取特征变量

为了使机器学习能在文本文档上工作,我们必须把文本内容转换成数值特征向量。

3.2.1 词袋模型

词袋模型最直观表现:

  1. 只要训练数据集的任意文档中任意单词出现,就为其分配一个固定值。
  2. 对于每一篇文档 #i,统计每个单词W出现的次数,并保存在X[i, j],作为特征 #j的值,j为单词W在字典中索引。

词袋中的n_features是语料库中不同单词的个数,该数值常常大于100,000。
如果n_samples=10000,那么以float32类型储存X数组需要4GB的内存空间,这在现代计算机上是不可忍受的。
幸运的是,在X中大部分值是0,因为对于给定的任意一篇文档所使用的不同词都不会高于上千个。鉴于这个原因,我们有把握说,词袋是高维稀疏数据集,我们可以仅仅储存向量中非0特征值。
scipy.sparse 就是处理高维稀疏矩阵的非常好的数据结构,并且scikit-learn也有对此结构的内建支持。

3.2.2使用scikit-learn标记文本

高水平组件的操作包含了文本处理、标记和过滤停用词等操作,这使得很容易构建一个特征字典及把文本转成特征向量。

>>> from sklearn.feature_extraction.text import CountVectorizer
>>> count_vect = CountVectorizer()
>>> X_train_counts = count_vect.fit_transform(twenty_train.data)
>>> X_train_counts.shape
(2257, 35788)

CountVectorizer支持对使用N元分词语法分成词数的统计(N-grams)或对序列字符数计数。一旦对模型拟合后,我们便成功地便构建了特征字典的索引切片。

>>> count_vect.vocabulary_.get(u'algorithm')
4690

词汇表单词的索引值与该词在整个训练语料库中出现频数相关。

3.2.3 从出现与否到出现频数

对单词作出现与否的统计是一件很好的开始,但是这存在一个问题:对应一篇更长的文档词出现次数的均值总比短文档要高,即便是在讨论同一个话题。
为了避免潜在的差异性,我们统计每个单词出现次数与语料库文档中单词总个数之比,便会生成一个新的特征变量,即频繁项集:tf
还需要考虑的问题是,对于一个很高的tf值我们需要降低它在语料库文档中出现的权重,tf大代表的信息可能不如仅在语料库部分文档中出现次数较少的单词代表的信息多,如,“是”、“的”、“我”等词。
这种操作称为逆文档率tf-idf
tftf-idf计算如下:

>>> from sklearn.feature_extraction.text import TfidfTransformer
>>> tf_transformer = TfidfTransformer(use_idf=False).fit(X_train_counts)
>>> X_train_tf = tf_transformer.transform(X_train_counts)
>>> X_train_tf.shape
(2257, 35788)

在上面的实例代码中,我们首先使用fit函数来拟合数据得到估计器,第二,使用transform函数把统计矩阵转换成tf-idf表达式。这两步结合在一起使用也能获取最终相同的结果,而且速度还要快些,即使用fit_transform函数,下面这个例子与上面等价:

>>> tfidf_transformer = TfidfTransformer()
>>> X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)
>>> X_train_tfidf.shape
(2257, 35788)

3.3 训练分类器

现在,我们已经获取到了文档词的特征向量,之后便能训练分类器来预测一个新的文档所属类别。以贝叶斯分类器开始,它提供了很多能够完成下面任务的基本曲线函数,同时scikit-learn还容纳了贝叶斯分类的许多变量参数,其中对文档单词处理最适合是multinomial变量。

>>> from sklearn.naive_bayes import MultinomialNB
>>> clf = MultinomialNB().fit(X_train_tfidf, twenty_train.target)

在尝试预测新文档输出结果前,我们需要把前面从训练样本中析取特征的操作应用在新文档上进行析取特征。
transformers变换对象上调用transform方法与调用fit_transform的区别在于:在前面,我们已经对训练集样本进行了拟合操作,现在也就不再需要fit方法拟合了,即只需调用transform方法。

>>> docs_new = ['God is love', 'OpenGL on the GPU is fast']
>>> X_new_counts = count_vect.transform(docs_new)
>>> X_new_tfidf = tfidf_transformer.transform(X_new_counts)
>>> predicted = clf.predict(X_new_tfidf)
>>> for doc, category in zip(docs_new, predicted):
... print('%r => %s' % (doc, twenty_train.target_names[category]))
...
'God is love' => soc.religion.christian
'OpenGL on the GPU is fast' => comp.graphics

3.4 构建pipline管道

为了能使向量对象、变换对象、分类对象在一起工作,scikit-learn提供了Pipeline管道,它工作起来如多个复合分类器对象一样。

>>> from sklearn.pipeline import Pipeline
>>> text_clf = Pipeline([('vect', CountVectorizer()),
... ('tfidf', TfidfTransformer()),
... ('clf', MultinomialNB()),
... ])

vecttfidfclf(classifer)名称是任意取的,我们还能在下面的网格搜索部分再看到它们的身影。现在,我们使用简单的命令训练模型:

>>> text_clf = text_clf.fit(twenty_train.data, twenty_train.target)

3.5 在测试集上对模型评估

评估模型的预测准确度是相当容易的。

>>> import numpy as np
>>> twenty_test = fetch_20newsgroups(subset='test',
... categories=categories, shuffle=True, random_state=42)
>>> docs_test = twenty_test.data
>>> predicted = text_clf.predict(docs_test)
>>> np.mean(predicted == twenty_test.target)
0.834...

还不错嘛,我们取得了83.4%的准确度。尝试使用线性支持向量机(SVM)模型是否有更好的结果,该模型广受好评,是最好的文本分类算法之一,尽管与贝叶斯分类器相比有些不高效。我们可以通过把一个新的不同的分类器对象追加到Pipeline管道对象中达到修改模型学习对象的目的。

>>> from sklearn.linear_model import SGDClassifier
>>> text_clf = Pipeline([('vect', CountVectorizer()),
... ('tfidf', TfidfTransformer()),
... ('clf', SGDClassifier(loss='hinge', penalty='l2',
... alpha=1e-3, n_iter=5, random_state=42)),
... ])
>>> _ = text_clf.fit(twenty_train.data, twenty_train.target)
>>> predicted = text_clf.predict(docs_test)
>>> np.mean(predicted == twenty_test.target)
0.912...

对于更深次的结果分析,scikit-learn提供了很多实用功能,如:

>>> from sklearn import metrics
>>> print(metrics.classification_report(twenty_test.target, predicted,
... target_names=twenty_test.target_names))
...
precision recall f1-score support
alt.atheism 0.95 0.81 0.87 319
comp.graphics 0.88 0.97 0.92 389
sci.med 0.94 0.90 0.92 396
soc.religion.christian 0.90 0.95 0.93 398
avg / total 0.92 0.91 0.91 1502
>>> metrics.confusion_matrix(twenty_test.target, predicted)
array([[258, 11, 15, 35],
[ 4, 379, 3, 3],
[ 5, 33, 355, 3],
[ 5, 10, 4, 379]])

正如预期的一样,混淆矩阵显示来自20newsgroupsathesimchristian是更凌乱的与computer graphics相比。

3.6 使用网格搜索优化参数

TfidfTransformer类中内建了很多参数,如use_idf,而且,分类器对象也倾向于使用更多的参数,如,MultinomialNB类包含了一个平滑参数alpha参数,SGDClasifier也有惩罚项(penalty)参数alpha和在对象函数可配置的损失函数及惩罚项(penalty items)
代替多个组件链对参数的调整操作,通过对网格可能值穷举来搜索最佳参数值是可能的。我们使用单一词或二元分词语法,并对每个线性SVM分类器赋予0.01或0.001的惩罚项系数试验所有的分类器。

>>> from sklearn.grid_search import GridSearchCV
>>> parameters = {'vect__ngram_range': [(1, 1), (1, 2)],
... 'tfidf__use_idf': (True, False),
... 'clf__alpha': (1e-2, 1e-3),
... }

很显然,这种搜索的运算开销是巨大的。如果我们有多CPU内核,我们通过对n_jobs参数设置来告诉网格搜索对象能够使用的CPU核数,使它尝试并行计算由八个参数组成的联合体。如果给n_jobs参数赋值-1,那么机器上所有可用CPU内核将全部投入使用。

>>> gs_clf = GridSearchCV(text_clf, parameters, n_jobs=-1)

网格搜索对象工作方式与scikit-learn其它模型一样。让我们看看该网格搜索对象在较小的训练数据集中运算速率大小。

>>> gs_clf = gs_clf.fit(twenty_train.data[:400], twenty_train.target[:400])

GridSearchCV对象上调用fit方法的结果是一个分类器对象,我们能够使用该对象进行预测操作。

>>> twenty_train.target_names[gs_clf.predict(['God is love'])]
'soc.religion.christian'

它是一个相当大、复杂的对象,不过,我们可以通过检查对象的grid_scores_属性来获取最佳的参数,该属性返回一个参数与分数对(parameters/score)的列表。为得到最佳得分的属性,进行如下操作:

>>> best_parameters, score, _ = max(gs_clf.grid_scores_, key=lambda x: x[1])
>>> for param_name in sorted(parameters.keys()):
... print("%s: %r" % (param_name, best_parameters[param_name]))
...
clf__alpha: 0.001
tfidf__use_idf: True
vect__ngram_range: (1, 1)
>>> score
0.900...

3.6.1 练习

为了练习方便而不破坏原有文件,我们复制'skeletons'目录里内容到新目录'workspace'。

% cp -r skeletons workspace

在ipython中执行如下命令:

%run workspace/exercise_XX_script.py arg1 arg2 arg3

如果抛出异常,请使用%debug命令进行调试。

扩展

  1. CountVectorizer类中多次尝试练习analyzer 和1tokennormalisation`操作。
  2. 如果没有类标号,尝试使用Clustering
  3. 如果每篇文档具有多个类标签,那么翻阅Multiclass and multilabel 部分
  4. 对于隐藏的语义分析,请使用Truncated SVD
  5. 好好使用 Out-of-core Classification类从不适合存入计算机内存数据中学习。
  6. 你应该好好看看 与CountVectorizer类相比,储存效率高的Hashing Vectorizer类。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,117评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,963评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,897评论 0 240
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,805评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,208评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,535评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,797评论 2 311
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,493评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,215评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,477评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,988评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,325评论 2 252
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,971评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,807评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,544评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,455评论 2 266

推荐阅读更多精彩内容