1. 问题定义
在这个项目中会采用20Newgroups的数据,这是在网上非常流行的对文本进行分类和聚类的数据集。数据集中的数据分为两部分,一部分是用来训练算法模型的数据,一部分是用来评估算法的新数据。网上还提供了3个数据集,这里采用了20new-bydate这个数据集进行项目研究。这个数据集是按照日期进行排序的,并去掉了部分重复数据和header,共包含18846个文档。
2.导入数据
这里使用scikit-learn的loadfiles导入文档数据,文档是按照不同的分类分目录来保存的,文件目录 名称即所属类别。
在导入文档数据之前,要导入项目中所需的类库。
# 导入类库
from sklearn.datasets import load_files
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.naive_bayes import MultinomialNB
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import classification_report
from sklearn.metrics import accuracy_score
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import KFold
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import AdaBoostClassifier
from sklearn.ensemble import RandomForestClassifier
from matplotlib import pyplot as plt
import warnings
warnings.filterwarnings('ignore')
利用机器学习对文本进行分类,与对数值特征进行分类最大的区别是,对文本进行分类是要先提取文本特征,提取到的文本特征属性的个数是巨大的,会有超万个的特征属性。
3. 文本特征提取
文本数据属于非结构化的数据,一般要转换成结构化的数据才能够通过机器学习算法进行分类。常见的做法是将文本转换成“文档-词项矩阵”,矩阵中的元素可以使用词频或TF-IDF值等。
TF-IDF值是一种用于信息检索与数据挖掘的常用加权技术。TF的意思是词频(Term Frequency),IDF的意思是逆向文件频率(Inverse Document Frequency)。TF-IDF的主要思想是:如果某一个词或短语在一篇文章中出现的频率高,并且在其他文章中很少出现,则认为此词或短语具有很好的类别区分能力,时适合用来分类。TF-IDF实际上是TF*IDF。
IDF的主要思想是:如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能力,如果某一类文档C中包含词条t的文档数为m,而其他类包含t的文档总数为k,显然所有包含t的文档数n = m + k,当m打的时候,n也大,按照IDF公式得到的IDF的值小,这说明该词条t的类别区分能力不强。但是实际上,如果一个词条在一个类的文档中频繁出现,这说明该词条能够很好地代表这个类的文本特征,这样的词条应该被赋予较高的权重,并将其作为该类文本的特征词,以区别于其他类文档。这就是IDF的不足之处,在一份给定的文件里,TF指的是某一个给定的词语在该文件中出现的频率,这是对词数(Term Count)的归一化,乙方他偏向长的文件。IDF是一个词语普遍重要性的度量,某一特定词语的IDF,可以由总文件数目除以包含该词语的文件的数目,再将得到的商取对数得到。
在scikit-learn中提供了词频和TF-IDF来进行文本特征提取的实现,分别是CountVectorizer和TfidTransformer。下面对训练数据集分别进行词频和TF-IDF的计算。
# 数据准备与理解
# 计算词频
count_vect = CountVectorizer(stop_words= 'english',decode_error ='ignore')
X_train_counts = count_vect.fit_transform(dataset_train.data)
# 查看数据维度
print(X_train_counts.shape)
词频统计结果:
然后计算TF-IDF:
# 计算TF-IDF
tf_transformer = TfidfVectorizer(stop_words='english',decode_error='ignore')
X_train_counts_tf = tf_transformer.fit_transform(dataset_train.data)
# 查看数据维度
print(X_train_counts_tf.shape)
TF-IDF的计算结果:
这里通过两种方法进行了文本特征的提取,并且查看了数据维度,得到的数据维度还是非常巨大的。
4. 评估算法
通过简单地查看数据维度,不能确定那个算法对和这个问题比较有效。下面将采用10折交叉验证的方式来比较算法的准确度,一遍找到处理问题最有效的两三种算法,然后进行下一步的处理。
# 设置评估算法的基准
num_folds = 10
seed = 7
scoring = 'accuracy'
接下来将会利用提取到的文本特征TF-IDF来对算法进行审查,审查的算法如下:
线性算法:逻辑回归(LR)
非线性算法:分类与回归树(CART)、支持向量机(SVM)、朴素贝叶斯分类器(MNB)和K近邻(KNN)。
# 评估算法
# 生成算法模型
models = {}
models['LR'] = LogisticRegression()
models['SVM'] = SVC()
models['CART'] = DecisionTreeClassifier()
models['MNB'] = MultinomialNB()
models['KNN'] = KNeighborsClassifier()
所有的算法使用默认参数,比较算法的准确度和标准方差,以便从中选择两三种可以进一步进行研究的算法。
# 比较算法
results = []
for key in models:
kfold = KFold(n_splits= num_folds,random_state= seed)
cv_results = cross_val_score(models[key],X_train_counts_tf,dataset_train.target,cv = kfold,scoring=scoring)
results.append(cv_results)
print('%s :%f(%f)'%(key,cv_results.mean(),cv_results.std()))
执行结果显示,逻辑回归(LR)具有最后的准确度,朴素贝叶斯分类器(MNB)和K近邻也值得进一步研究。
接下来看一下算法每次执行结果的分布情况。
5.算法调参
通过上面的分析,逻辑回归(LR)和朴素贝叶斯分类器(MNB)算法值得进一步优化。下面对这两个算法的参数进行调参,进一步提高算法的准确度。
5.1 逻辑回归调参
在逻辑回归中的超参数是C。C是目标的约束函数,C值越小则正则化强度越大。对C进行设定一定数量的值,如果临界值是最优参数,重复这个步骤,直到找到最优值。
# 调参LR
param_grid = {}
param_grid['C'] = [0.1,5,13,15]
model = LogisticRegression()
kfold = KFold(n_splits=num_folds,random_state=seed)
grid = GridSearchCV(estimator=model,param_grid = param_grid, scoring = scoring,cv=kfold)
grid_result = grid.fit(X=X_train_counts_tf,y=dataset_train.target)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
可以看到C的最优参数是15。
5.2 朴素贝叶斯分类器调参
通过对逻辑回归调参,准确度提升到大概0.92,提升还是比较大的。朴素贝叶斯分类器有一个alpha参数,该参数是一个平滑参数,默认值为1.0。我们也可以对这个参数进行调参,以提高算法的准确度。
# 调参 MNB
param_grid ={}
param_grid['alpha'] = [0.001,0.01,0.1,1.5]
model = MultinomialNB()
kfold = KFold(n_splits=num_folds,random_state=seed)
grid = GridSearchCV(estimator= model, param_grid = param_grid, scoring=scoring, cv=kfold)
grid_result = grid.fit(X = X_train_counts_tf, y = dataset_train.target)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
同样,通过多次调整param_grid,得到的朴素贝叶斯分类器的alpha参数的最优值是0.01。
通过调参发现,逻辑回归在C = 15 是具有最好的准确度。接下来进行审查集成算法。
6. 集成算法
除了调参,提高算法准确度的方法是使用集成算法。下面对两种集成算法进行比较,看看能否进一步提高模型的准确度。
随机森林(RF)
AdaBoost(AB)
# 集成算法
ensembles = {}
ensembles['RF'] = RandomForestClassifier()
ensembles['AB'] = AdaBoostClassifier()
# 比较集成算法
results = []
for key in ensembles:
kfold = KFold(n_splits= num_folds,random_state= seed)
cv_results = cross_val_score(ensembles[key],X_train_counts_tf,dataset_train.target,cv = kfold,scoring = scoring)
results.append(cv_results)
print('%s : %f(%f)'%(key,cv_results.mean(),cv_results.std()))
评估结果:
7. 集成算法调参
通过对集成算法的分析,发现随机森林算法具有较高的准确度和非常稳定的数据分布,非常值得进行进一步的研究。下面通过调参对随机森林算法进行优化。随机森林有一个很重要的参数n_estimators,下面对n_estimators进行调参优化,争取找到最优解。
# 调参RF
param_grid = {}
param_grid['n_estimators'] = [10,100,150,200]
model = RandomForestClassifier()
kfold = KFold(n_splits=num_folds,random_state= seed)
grid = GridSearchCV(estimator=model,param_grid = param_grid, scoring = scoring,cv = kfold)
grid_result = grid.fit(X =X_train_counts_tf,y = dataset_train.target)
print('最优:%s 使用%s'%(grid_result.best_score_,grid_result.best_params_))
调参之后的最优结果如下:
8. 确定最终模型
通过对算法的比较和调参发现,逻辑回归算法具有最高的准确度,因此使用逻辑回归算法生成算法模型。接下来会利用评估数据集对生成的模型进行验证,以确认模型的准确度。需要注意的是,为了保持数据特征的一致性,对新数据进行文本特征提取是应进行特征扩充,下面使用之前生成的tf_transformer的transform方法来处理评估数据集。
# 生成模型
model = LogisticRegression(C = 15)
model.fit(X_train_counts_tf,dataset_train.target)
X_test_counts = tf_transformer.transform(dataset_test.data)
predictions = model.predict(X_test_counts)
print(accuracy_score(dataset_test.target, predictions))
print(classification_report(dataset_test.target, predictions))
从结果可以看到,准确度大概达到了85%,与期待的结果比较一致。执行结果:
参考从书:《机器学习Python实践》 魏贞原