读取文件内容以及所属类别
这里使用sklearn.datasets中的fetch_20newsgroups数据集(20个新闻组集合),该数据集常常应用在文本分类和文本聚类等文本应用的试验中。
数据使用方法:
- fetch_20newsgroups().data:特征数据数组,用来查看文本具体内容
- fetch_20newsgroups().filenames:查看文件位置信息
- fetch_20newsgroups().target_names:查看数据集名称,共20个
- fetch_20newsgroups().target:查看所属Categories(种类)的索引,对应位置的target_names就是种类名称
- fetch_20newsgroups().DESCR:查看描述信息
从20个类别数据集中选出4个进行训练:
from sklearn.datasets import fetch_20newsgroups
categories = ['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian']
#subset:加载哪一部分数据集train/test;categoties:选取哪一类数据集;shuffle:将数据随机排序
twenty_train = fetch_20newsgroups(subset='train',categories=categories,shuffle=True,random_state=42)
# print(len(twenty_train.data))
# print(len(twenty_train.filenames))
#打印第一个文件的前几行,并查看该文档所属种类名称
print('\n'.join(twenty_train.data[0].split('\n')[:3]))
print(twenty_train.target_names[twenty_train.target[0]])
From: sd345@city.ac.uk (Michael Collier)
Subject: Converting images to HP LaserJet III?
Nntp-Posting-Host: hampton
comp.graphics
特征提取CountVectorizer
CountVectorizer可以将文本内容转化为数值形式的特征向量
词袋
1、给全部文档中出现的每一个单词(不重复的)分配一个特定的整数id,例如以单词名称为键,将按单词首字母顺序进行排列的索引值作为值,组成一个单词到整数索引的字典。
2、对每个文档(索引为i),计算每个单词w的出现次数,并将其存储在X[i,j]中作为特征#j的值,其中j是字典中单词w的索引。
from sklearn.feature_extraction.text import CountVectorizer
X_test = ['you are good','but you do not fit']
vectorizer = CountVectorizer()
counts = vectorizer.fit_transform(X_test)
print(counts)
print(vectorizer.get_feature_names_out()) #文本中包含的全部单词,按首字母顺序进行排列
print(vectorizer.vocabulary_) #为文档中每个单词定义一个整数id,建立从单词到整数索引的字典
print(counts.toarray()) #词频矩阵,就是上边说的X[i,j]
print(counts.shape) #相当于词频矩阵大小
(0, 6) 1
(0, 0) 1
(0, 4) 1
(1, 6) 1
(1, 1) 1
(1, 2) 1
(1, 5) 1
(1, 3) 1
['are' 'but' 'do' 'fit' 'good' 'not' 'you']
{'you': 6, 'are': 0, 'good': 4, 'but': 1, 'do': 2, 'not': 5, 'fit': 3}
[[1 0 0 0 1 0 1]
[0 1 1 1 0 1 1]]
(2,7)
最后词频矩阵的说明:
词汇顺序 | are | but | do | fit | good | not | you |
---|---|---|---|---|---|---|---|
第一个句子中出现频数 | 1 | 0 | 0 | 0 | 1 | 0 | 1 |
第二个句子中出现频数 | 0 | 1 | 1 | 1 | 0 | 1 | 1 |
从出现次数到出现频率
如果只统计出现次数,那么长的文本相对于短的文本总有更高的出现次数。为避免不同文本间潜在的差异,将每个文档单词出现次数除以该文档中所有的单词总数。这些新特征称之为词频tf(Term Frequencies)。
另一个在词频的基础上改良是,降低在该训练文集中的很多文档中均出现的单词的权重,从而突出那些仅在该训练文集中在一小部分文档中出现的单词的信息量。这种方法称为tf-idf(Term Frequency times Inverse Document Frequency,词频-逆文档频率)。
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
X_test = ['you are good','but you do not fit']
vectorizer = CountVectorizer()
counts = vectorizer.fit_transform(X_test)
tfidf_transformer = TfidfTransformer()
X_test_tfidf = tfidf_transformer.fit_transform(counts)
print(X_test_tfidf) #返回tf-idf逆文档频率
# print(X_test_tfidf.toarray()) 返回矩阵形式
(0, 6) 0.4494364165239821
(0, 4) 0.6316672017376245
(0, 0) 0.6316672017376245
(1, 6) 0.33517574332792605
(1, 5) 0.47107781233161794
(1, 3) 0.47107781233161794
(1, 2) 0.47107781233161794
(1, 1) 0.47107781233161794
训练文本分类器
训练一个分类器来预测一个帖子所属的类别,最好使用MultinomialNB多项式朴素贝叶斯
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
categories = ['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian']
twenty_train = fetch_20newsgroups(subset='train',categories=categories,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)
clf = MultinomialNB().fit(X_train_tfidf,twenty_train.target)
docs_new = ['God is love', 'OpenGL on the GPU is fast']
#抽取特征,调用transform而不是fit_transform,因为这些特征已经在训练集拟合了
X_new_counts = count_vect.transform(docs_new)
print(X_new_counts)
X_new_tfidf = tfidf_transformer.transform(X_new_counts)
predicted = clf.predict(X_new_tfidf) #返回预测类别的索引
for doc,category in zip(docs_new,predicted):
print(f'{doc}=>{twenty_train.target_names[category]}')
(0, 15521) 1
(0, 18474) 1
(0, 20537) 1
(1, 14048) 1
(1, 15628) 1
(1, 18474) 1
(1, 23733) 1
(1, 23790) 1
(1, 32142) 1
God is love=>soc.religion.christian
OpenGL on the GPU is fast=>comp.graphics
构建Pipeline(管道)
sklearn提供了一个Pipeline类,使向量化(vectorizer)=>转换器(transformer)=>分类器(classifier)过程更加简单。使用Pipeline类,操作起来像一个复合分类器,输入可以直接是新文档,而不需要再进行特征提取。
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
categories = ['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian']
twenty_train = fetch_20newsgroups(subset='train',categories=categories,shuffle=True,random_state=42)
docs_new = ['God is love', 'OpenGL on the GPU is fast']
# 下述名称'vect','tfidf','clf'都是任意的。
text_clf = Pipeline([('vect',CountVectorizer()),('tfidf',TfidfTransformer()),('clf',MultinomialNB())])
text_clf.fit(twenty_train.data,twenty_train.target)
predicted = text_clf.predict(docs_new) #[3,1]
for doc,category in zip(docs_new,predicted):
print(f'{doc}=>{twenty_train.target_names[category]}')
God is love=>soc.religion.christian
OpenGL on the GPU is fast=>comp.graphics
在测试集上性能评估
1、朴素贝叶斯分类器,在测试集上进行性能评估
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
import numpy as np
categories = ['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian']
twenty_train = fetch_20newsgroups(subset='train',categories=categories,shuffle=True,random_state=42)
twenty_test = fetch_20newsgroups(subset='test',categories=categories,shuffle=True,random_state=42)
docs_test = twenty_test.data
text_clf = Pipeline([('vect',CountVectorizer()),('tfidf',TfidfTransformer()),('clf',MultinomialNB())])
text_clf.fit(twenty_train.data,twenty_train.target)
predicted = text_clf.predict(docs_test)
print(np.mean(predicted==twenty_test.target))
0.8348868175765646
2、线性分类器模型支持向量机(SVM)
训练速度比朴素贝叶斯慢一点,公认的最好的文本分类器算法之一。只需要在Pipeline类中插接入不同的分类器对象,就可以实现不同的分类器模型。
SGDClassifier中loss参数默认为'hinge',即合页损失函数,表示线性SVM模型。
SDGClassifier根据参数实现不同分类器模型
# 只需要改变Pipeline类中的分类器,其它和上述贝叶斯分类代码相同
from sklearn.linear_model import SGDClassifier
# ...
text_clf = Pipeline([('vect',CountVectorizer()),
('tfidf',TfidfTransformer()),
('clf',SGDClassifier(loss='hinge',penalty='l2',alpha=1e-3,random_state=42,max_iter=5,tol=None))])
# ...
print(np.mean(predicted==twenty_test.target))
0.9101198402130493
使用网格搜素GridSearchCV调参
CountVectorizer中有ngram_range参数,即词组切分的长度范围。参数详解
TfidfTransformer中有use_idf参数,默认True,即是否在词频(tf)的基础上进行改良。
分类器MultinomialNB包含了平滑参数alpha,以及SGDClassifier有惩罚参数alpha和目标函数中的可设置的损失以及惩罚因子。
from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.linear_model import SGDClassifier
from sklearn.pipeline import Pipeline
from sklearn.model_selection import GridSearchCV
import numpy as np
categories = ['alt.atheism', 'comp.graphics', 'sci.med', 'soc.religion.christian']
twenty_train = fetch_20newsgroups(subset='train',categories=categories,shuffle=True,random_state=42)
docs_test = ['God is love']
text_clf = Pipeline([('vect',CountVectorizer()),
('tfidf',TfidfTransformer()),
('clf',SGDClassifier(loss='hinge',penalty='l2',alpha=1e-3,random_state=42,max_iter=5,tol=None))])
#字典里的键由“名称__参数”形式构成,注意是两个下划线
parameters = {'vect__ngram_range':[(1,1),(1,2)],
'tfidf__use_idf':(True,False),
'clf__alpha':(1e-2,1e-3)}
#如果我们有多个cpu,设置n_jobs参数进行并行处理。n_jobs=-1,使用所有的cpu核心
gs_clf = GridSearchCV(text_clf,parameters,n_jobs=-1)
# 为节省时间选择训练集中一部分进行网格搜索
gs_clf = gs_clf.fit(twenty_train.data[:400],twenty_train.target[:400])
#对句子‘God is love’进行分类
print(twenty_train.target_names[gs_clf.predict(['God is love'])[0]])
soc.religion.christian
该对象的 best_score_ 和 best_params_ 属性存放了最佳的平均分以及其所对应的参数配置:
print(gs_clf.best_score_)
for param_name in sorted(parameters.keys()):
print(f'{param_name}:{gs_clf.best_params_[param_name]}')
0.9175000000000001
clf__alpha:0.001
tfidf__use_idf:True
vect__ngram_range:(1, 1)