[Scikit-learn教程] 03.02 文本处理:分类与优化
欢迎访问集智主站:集智,通向智能时代的引擎
回顾
上一节我们通过Scikit-learn提供的多种方法从网络以及硬盘获取到了原始的文本数据,并采用tf-idf
方法成功地提取了文本特征,你可以从下面的例子中再次复习这一过程。
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer # 选取参与分析的文本类别 categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] # 从硬盘获取原始数据 twenty_train=load_files("/mnt/vol0/sklearn/20news-bydate-train", categories=categories, load_content = True, encoding="latin1", decode_error="strict", shuffle=True, random_state=42) # 统计词语出现次数 count_vect = CountVectorizer() X_train_counts = count_vect.fit_transform(twenty_train.data) # 使用tf-idf方法提取文本特征 tfidf_transformer = TfidfTransformer() X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts) # 打印特征矩阵规格 print(X_train_tfidf.shape)
本节我们将在完成特征提取工作的基础上,继续完成文本信息挖掘的下一步——训练并优化分类器。
训练分类器
可以用于文本分类的机器学习算法有很多,朴素贝叶斯算法(Naïve Bayes)就是其中一个优秀代表。Scikit-learn包含了朴素贝叶斯算法的多种改进模型,最适于文本词数统计方面的模型叫做多项式朴素贝叶斯(Multinomial Naïve Bayes),它可以通过以下的方式来调用。
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer categories = ["alt.atheism", "soc.religion.christian", "comp.graphics", "sci.med"] twenty_train=load_files("/mnt/vol0/sklearn/20news-bydate-train", categories=categories, load_content = True, encoding="latin1", decode_error="strict", shuffle=True, random_state=42) count_vect = CountVectorizer() X_train_counts = count_vect.fit_transform(twenty_train.data) tfidf_transformer = TfidfTransformer() X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts)[/amalthea_pre_exercise_code] [amalthea_sample_code] from sklearn.naive_bayes import MultinomialNB clf = MultinomialNB().fit(X_train_tfidf, twenty_train.target) print("分类器的相关信息:") print(clf)
这样就完成了一个分类器的训练过程。为了使用一个新文档来进行分类器的分类预测工作,我们必须使用同样的数据处理手段处理我们的新文档。如下面的例子所示,我们使用了一组自定义的字符串,用来判断它们的分类情况。字符串组必须经过transform
方法的处理才能进行预测。
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer categories = ["alt.atheism", "soc.religion.christian", "comp.graphics", "sci.med"] twenty_train=load_files("/mnt/vol0/sklearn/20news-bydate-train", categories=categories, load_content = True, encoding="latin1", decode_error="strict", shuffle=True, random_state=42) count_vect = CountVectorizer() X_train_counts = count_vect.fit_transform(twenty_train.data) tfidf_transformer = TfidfTransformer() X_train_tfidf = tfidf_transformer.fit_transform(X_train_counts) from sklearn.naive_bayes import MultinomialNB clf = MultinomialNB().fit(X_train_tfidf, twenty_train.target) # 预测用的新字符串,你可以将其替换为任意英文句子 docs_new = ["Nvidia is awesome!"] # 字符串处理 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]))
作为受西方理论指导的一种基础的机器学习算法,朴素贝叶斯虽然很简单,有时候很朴素,但是它的运行速度非常的快,效果也非常的理想,能够跟很多更复杂的算法相提并论。
建立Pipeline
为了简化对于原始数据的清洗、特征提取以及分类过程,Scikit-learn提供了Pipeline
类来实现一个整合式的分类器建立过程。分类器可以通过建立一个Pipeline
的方式来实现,而各种特征提取、分类方法都可以在建立Pipeline
的时候直接指定,从而大大提高编码和调试的效率,如下所示:
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer, TfidfTransformer from sklearn.naive_bayes import MultinomialNB categories = ["alt.atheism", "soc.religion.christian", "comp.graphics", "sci.med"] twenty_train=load_files("/mnt/vol0/sklearn/20news-bydate-train", categories=categories, load_content = True, encoding="latin1", decode_error="strict", shuffle=True, random_state=42)[/amalthea_pre_exercise_code] [amalthea_sample_code] from sklearn.pipeline import Pipeline # 建立Pipeline text_clf = Pipeline([("vect", CountVectorizer()), ("tfidf", TfidfTransformer()), ("clf", MultinomialNB()), ]) # 训练分类器 text_clf = text_clf.fit(twenty_train.data, twenty_train.target) # 打印分类器信息 print(text_clf)
使用测试数据评估分类器性能
我们可以采用上述的方法对测试数据集进行预测,然后使用Numpy
所提供的函数得到评测结果:
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer,TfidfTransformer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline if "text_clf" not in dir() : categories = ["alt.atheism", "soc.religion.christian", "comp.graphics", "sci.med"] twenty_train=load_files("/mnt/vol0/sklearn/20news-bydate-train",categories=categories, load_content = True, encoding="latin1", decode_error="strict",shuffle=True, random_state=42) text_clf = Pipeline([("vect", CountVectorizer()), ("tfidf", TfidfTransformer()), ("clf", MultinomialNB()), ]) text_clf = text_clf.fit(twenty_train.data, twenty_train.target) import numpy as np # 获取测试数据 twenty_test=load_files('/mnt/vol0/sklearn/20news-bydate-test', categories=categories, load_content = True, encoding='latin1', decode_error='strict', shuffle=True, random_state=42) docs_test = twenty_test.data # 使用测试数据进行分类预测 predicted = text_clf.predict(docs_test) # 计算预测结果的准确率 print("准确率为:") print(np.mean(predicted == twenty_test.target))
如果正常运行上述代码,我们应该可以得到83.4%的准确率。我们有很多办法来改进这个成绩,使用业界公认的最适于文本分类的算法——支持向量机(SVM,Support Vector Machine)就是一个很好的方向(虽然它会比朴素贝叶斯稍微慢一点)。我们可以通过改变Pipeline中分类器所指定的对象轻松地实现这一点:
import numpy as np from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer,TfidfTransformer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] if 'twenty_train' not in dir() : twenty_train=load_files('/mnt/vol0/sklearn/20news-bydate-train',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) if 'twenty_test' not in dir() : twenty_test=load_files('/mnt/vol0/sklearn/20news-bydate-test',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) docs_test = twenty_test.data 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) print("准确率:") print(np.mean(predicted == twenty_test.target))
我们可以看到,相对于朴素贝叶斯,SVM方法得到的准确率有了很大的进步。
Scikit-learn提供了更多的评测工具来更好地帮助我们进行分类器的性能分析,如下所示,我们可以得到预测结果中关于每一种分类的准确率、召回率、F值等等以及它们的混淆矩阵。
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer,TfidfTransformer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] if 'predicted' not in dir() : twenty_train=load_files('/mnt/vol0/sklearn/20news-bydate-train',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) twenty_test=load_files('/mnt/vol0/sklearn/20news-bydate-test',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) docs_test = twenty_test.data 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) from sklearn import metrics print("打印分类性能指标:") print(metrics.classification_report(twenty_test.target, predicted, target_names=twenty_test.target_names)) print("打印混淆矩阵:") metrics.confusion_matrix(twenty_test.target, predicted)
不出所料,通过混淆矩阵我们可以发现,相对于计算机图形学(comp.graphics),与无神论(alt.atheism)以及基督教(soc.religion.christian)相关的两种分类更难以被区分出来。
使用网格搜索来进行参数优化
我们已经了解了很多机器学习过程中所遇到的参数,比如TfidfTransformer
中的use_idf
。分类器往往会拥有很多的参数,比如说朴素贝叶斯算法中包含平滑参数alpha
,SVM算法会包含惩罚参数alpha
以及其他一些可以设置的函数。
为了避免调整这一系列参数而带来的繁杂工作,我们可以使用网格搜索方法来寻找各个参数的最优值。如下面的例子所示,我们可以在采用SVM算法建立分类器时尝试设置如下参数:使用单词或是使用词组、使用IDF或是不使用IDF、惩罚参数为0.01或是0.001。
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer,TfidfTransformer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] if 'text_clf' not in dir() : twenty_train=load_files('/mnt/vol0/sklearn/20news-bydate-train',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) twenty_test=load_files('/mnt/vol0/sklearn/20news-bydate-test',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) docs_test = twenty_test.data 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)), ]) from sklearn.grid_search import GridSearchCV # sklearn 0.18.1 版本请使用以下方式导入网格搜索库 # from sklearn.model_selection import GridSearchCV # 设置参与搜索的参数 parameters = {'vect__ngram_range': [(1, 1), (1, 2)], 'tfidf__use_idf': (True, False), 'clf__alpha': (1e-2, 1e-3), } # 构建分类器 gs_clf = GridSearchCV(text_clf, parameters, n_jobs=-1) print(gs_clf)
很明显,逐个进行这样一个搜索过程会消耗较大的计算资源。如果我们拥有一个多核CPU平台,我们就可以并行计算这8个任务(每个参数有两种取值,三个参数共有个参数组合),这需要我们修改n_jobs
这个参数。如果我们设置这个参数的值为-1
,网格搜索过程将会自动检测计算环境所存在的CPU核心数量,并使用全部核心进行并行工作。
一个具体的网格搜索模型与普通的分类器模型一致,我们可以使用一个较小的子数据块来加快模型的训练过程。对GridSearchCV
对象调用fit
方法之后将得到一个与之前案例类似的分类器,我们可以使用这个分类器来进行预测。
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer,TfidfTransformer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline from sklearn.grid_search import GridSearchCV categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] if 'gs_clf' not in dir() : twenty_train=load_files('/mnt/vol0/sklearn/20news-bydate-train',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) twenty_test=load_files('/mnt/vol0/sklearn/20news-bydate-test',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) docs_test = twenty_test.data 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)), ]) parameters = {'vect__ngram_range': [(1, 1), (1, 2)], 'tfidf__use_idf': (True, False), 'clf__alpha': (1e-2, 1e-3), } gs_clf = GridSearchCV(text_clf, parameters, n_jobs=-1) # 使用部分训练数据训练分类器 gs_clf = gs_clf.fit(twenty_train.data[:400], twenty_train.target[:400]) # 查看分类器对于新文本的预测结果,你可以自行改变下方的字符串来观察分类效果 twenty_train.target_names[gs_clf.predict(['An apple a day keeps doctor away'])[0]]
分类器同时包含best_score_
和best_params_
两个属性,这两个属性包含了最佳预测结果以及取得最佳预测结果时的参数配置。当然,我们也可以浏览gs_clf.cv_results_
来获取更详细的搜索结果(这是sklearn 0.18.1版本新加入的特性),这个参数可以很容易地导入到pandas中进行更为深入的研究。
from sklearn.datasets import load_files from sklearn.feature_extraction.text import CountVectorizer,TfidfTransformer from sklearn.naive_bayes import MultinomialNB from sklearn.pipeline import Pipeline from sklearn.grid_search import GridSearchCV categories = ['alt.atheism', 'soc.religion.christian', 'comp.graphics', 'sci.med'] if 'gs_clf' not in dir() : twenty_train=load_files('/mnt/vol0/sklearn/20news-bydate-train',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) twenty_test=load_files('/mnt/vol0/sklearn/20news-bydate-test',categories=categories, load_content = True, encoding='latin1', decode_error='strict',shuffle=True, random_state=42) docs_test = twenty_test.data 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)), ]) parameters = {'vect__ngram_range': [(1, 1), (1, 2)], 'tfidf__use_idf': (True, False), 'clf__alpha': (1e-2, 1e-3), } gs_clf = GridSearchCV(text_clf, parameters, n_jobs=-1) gs_clf = gs_clf.fit(twenty_train.data[:400], twenty_train.target[:400]) print("最佳准确率:%r" % (gs_clf.best_score_)) for param_name in sorted(parameters.keys()): print("%s: %r" % (param_name, gs_clf.best_params_[param_name]))
小结
至此,我们已经完整实践了一个使用机器学习方法进行文本分类工作的全过程,我们了解了从网络获取数据并进行读取、清洗原始数据并提取特征向量、使用不同算法来构建分类器、并使用网格搜索方法来进行参数调优等有监督机器学习中较为常见的各个知识点。关于更为复杂的一些问题,比如中文文本处理、文本聚类分析等等,我们将在之后的文章中进行讨论。
(本篇课程内容来自于Scikit-Learn - Working With Text Data,转载请注明来源。)