Task3 基于机器学习的文本分类
实操主要包括以下几个任务:
基于文本统计特征的特征提取(包括词频特征、TF-IDF特征等)
如何划分训练集(用于参数选择、交叉验证)
结合提取的不同特征和不同模型(线性模型、集成学习模型)完成训练和预测
文本表示方法
在机器学习算法的训练过程中,假设给定\(N\) 个样本,每个样本有\(M\) 个特征,这样组成了\(N×M\) 的样本矩阵,然后完成算法的训练和预测。文本表示成计算机能够运算的数字或向量的方法一般称为词嵌入(Word Embedding)方法。词嵌入将不定长的文本转换到定长的空间内,是文本分类的第一步。
One-hot
将每一个单词使用一个离散的向量表示。具体将每个字/词编码一个索引,然后根据索引进行赋值。
One-hot表示方法的例子如下:
句子1 :我 爱 北 京 天 安 门 句子2 :我 喜 欢 上 海
首先对所有句子的字进行索引,即将每个字确定一个编号:
{ '我' : 1 , '爱' : 2 , '北' : 3 , '京' : 4 , '天' : 5 , '安' : 6 , '门' : 7 , '喜' : 8 , '欢' : 9 , '上' : 10 , '海' : 11 }
在这里共包括11个字,因此每个字可以转换为一个11维度稀疏向量:
我:[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 爱:[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] ... 海:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
Bag of Words
Bag of Words(词袋表示),也称为Count Vectors,每个文档的字/词可以使用其出现次数(词频)来进行表示。
句子1 :我 爱 北 京 天 安 门 句子2 :我 喜 欢 上 海
直接统计每个字出现的次数,并进行赋值:
句子1 :我 爱 北 京 天 安 门 转换为 [1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 ] 句子2 :我 喜 欢 上 海 转换为 [1 , 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 , 1 ]
在sklearn中可以直接CountVectorizer
来实现这一步骤:
from sklearn.feature_extraction.text import CountVectorizercorpus = [ 'This is the first document.' , 'This document is the second document.' , 'And this is the third one.' , 'Is this the first document?' , ] vectorizer = CountVectorizer() vectorizer.fit_transform(corpus).toarray()
N-gram
N-gram与Count Vectors类似,不过加入了相邻单词组合成为新的单词,并进行计数。
如果N取值为2,则句子1和句子2就变为:
句子1:我爱 爱北 北京 京天 天安 安门 句子2:我喜 喜欢 欢上 上海
TF-IDF
TF-IDF 分数由两部分组成:第一部分是词语频率 (Term Frequency),第二部分是逆文档频率 (Inverse Document Frequency)。其中计算语料库中文档总数除以含有该词语的文档数量,然后再取对数就是逆文档频率。
TF(t)= 该词语在当前文档出现的次数 / 当前文档中词语的总数 IDF(t)= log_e(文档总数 / 出现该词语的文档总数)
机器学习模型
机器学习是对能通过经验自动改进的计算机算法的研究。机器学习通过历史数据训练 出模型 对应于人类对经验进行归纳 的过程,机器学习利用模型 对新数据进行预测 对应于人类利用总结的规律 对新问题进行预测 的过程。
机器学习能解决一定的问题,但不能奢求机器学习是万能的;
机器学习算法有很多种,看具体问题需要什么,再来进行选择;
每种机器学习算法有一定的偏好,需要具体问题具体分析;
本文主要为利用python进行实操,使用到的模型理论部分可以跳转相应的链接学习。
线性模型
Adaboost
XGBoost
随机森林
LightGBM
值得注意的是,XGBoost、LightGBM并不是指某个算法,而是机器学习算法GBDT的算法实现,高效地实现了GBDT算法并进行了算法和工程上的许多改进,具体区别和联系可参考:GBDT、XGBoost、LightGBM的区别和联系
基于机器学习的文本分类实验
数据加载及预处理
根据数据分析结果,我们这里可以去掉可能是标点符号的字符(字符3750,字符900和字符648)。
import pandas as pdimport joblibdata_file = 'train_set.csv' rawdata = pd.read_csv(data_file, sep='\t' , encoding='UTF-8' ) import rerawdata['words' ]=rawdata['text' ].apply(lambda x: re.sub('3750|900|648' ,"" ,x)) del rawdata['text' ]
数据集划分
一般策略是:
在选择模型、确定模型参数时 :
先把当前竞赛给的训练集划分为三个部分:训练集、验证集、测试集 。其中,训练集用于训练,验证集用于调参,测试集用于评估线下和线上的模型效果。
特别的,为了进行交叉验证,划分后的训练集留10%用于测试;剩下90%输入交叉验证。
注意:测试集最好固定下来,可以先打乱然后记录下索引值,之后直接取用。
有一些模型的参数需要选择,这些参数会在一定程度上影响模型的精度,那么如何选择这些参数呢?
通过阅读文档,要弄清楚这些参数的大致含义,那些参数会增加模型的复杂度
通过在验证集上进行验证模型精度,找到模型在是否过拟合还是欠拟合
Image
在确定最佳模型参数(best parameters)后 :可以把原始的完整训练集全部扔给设置为best parameters的模型进行训练,得到最终的模型(final model),然后利用final model对真正的测试集进行预测。
注意:由于训练集中,不同类别的样本个数不同,在进行数据集划分时可以考虑使用分层抽样,根据不同类别的样本占比进行抽样,以保证标签的分布与整个数据集的分布一致。
from sklearn.model_selection import train_test_splitimport joblibrawdata.reset_index(inplace=True ,drop=True ) test_data=joblib.load('test_index.pkl' ) train_data=joblib.load('train_index.pkl' ) X=list (rawdata.index) y=rawdata['label' ] X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=0.1 ,stratify=y) test_data={'X_test' :X_test,'y_test' :y_test} joblib.dump(test_data,'test_index.pkl' ) train_data={'X_train' :X_train,'y_train' :y_train} joblib.dump(train_data,'train_index.pkl' ) train_x=rawdata.loc[train_data['X_train' ]]['words' ] train_y=rawdata.loc[train_data['X_train' ]]['label' ].values test_x=rawdata.loc[test_data['X_test' ]]['words' ] test_y=rawdata.loc[test_data['X_test' ]]['label' ].values
利用StratifiedKFold对划分后的train_data分层抽样:
sfolder=sklearn.model_selection.StratifiedKFold(n_splits=10 ,shuffle=True ,random_state=1 ) for train, test in sfolder.split(X,y): print ('Train: %s | test: %s' % (train, test))
test_data_file = 'test_a.csv' f = pd.read_csv(test_data_file, sep='\t' , encoding='UTF-8' ) test_data = f['text' ].apply(lambda x: re.sub('3750|900|648' ,"" ,x))
特征提取
使用sklearn库进行词频特征和TFIDF特征提取,注意,这里使用划分后的训练集提取各个词的特征(fit),而由训练集划分出的测试集的特征是由该特征映射的,而不是用全部训练集提取的特征映射来的;而最终测试集的特征由全部训练集提取的特征映射得到。
max_features表示特征维度,该值也算是超参数,这里只设置了300维。
词频特征
from sklearn.feature_extraction.text import CountVectorizervectorizer = CountVectorizer(max_features=300 ) vectorizer = vectorizer.fit(train_x) train_text = vectorizer.transform(train_x) test_text = vectorizer.transform(test_x) vectorizer = CountVectorizer(max_features=300 ) vectorizer = vectorizer.fit(rawdata['words' ]) final_test_text=vectorizer.transform(test_data)
TF-IDF特征
from sklearn.feature_extraction.text import TfidfVectorizertfidf = TfidfVectorizer(ngram_range=(1 ,3 ), max_features=300 ) tfidf = tfidf.fit(train_x) train_text_tfidf = tfidf.transform(train_x) test_text_tfidf = tfidf.transform(test_x) final_test_text_tfidf=tfidf.transform(test_data) tfidf = TfidfVectorizer(ngram_range=(1 ,3 ), max_features=300 ) tfidf = tfidf.fit(rawdata['words' ]) final_test_text=tfidf.transform(test_data)
词频特征+线性模型
通过本地构建验证集计算F1得分。
from sklearn.linear_model import RidgeClassifierfrom sklearn.metrics import f1_scoreclf = RidgeClassifier() clf.fit(train_text[:10000 ], train_y[:10000 ]) val_pred = clf.predict(train_text[10000 :]) print (f1_score(train_y[10000 :], val_pred, average='macro' ))
测试集精度:0.5948335699700965
TFIDF 特征+线性模型
import pandas as pdfrom sklearn.linear_model import RidgeClassifierfrom sklearn.metrics import f1_scoreclf = RidgeClassifier() clf.fit(train_text_tfidf[:10000 ], train_y.values[:10000 ]) val_pred = clf.predict(train_text_tfidf[10000 :]) print (f1_score(train_y.values[10000 :], val_pred, average='macro' ))
0.6928858913248898
TFIDF 特征+Adaboost
利用sklearn内置的Adaboost模块可以直接进行模型训练,Adaboost是集成学习模型,可以选择不同的个体学习器,这里选择决策树分类器作为基学习器。
import numpy as npfrom sklearn.ensemble import AdaBoostClassifierfrom sklearn.tree import DecisionTreeClassifierfrom sklearn.datasets import make_gaussian_quantilesbdt = AdaBoostClassifier(DecisionTreeClassifier(max_depth=2 , min_samples_split=20 , min_samples_leaf=5 ), algorithm="SAMME" , n_estimators=200 , learning_rate=0.8 ) bdt.fit(train_text_tfidf, train_y) Z = bdt.predict(test_text_tfidf) print "Score:" , bdt.score(train_text_tfidf,train_y)
TFIDF 特征+XGBoost
额外安装依赖库xgboost:
由两种方式可以利用XGBoost库进行模型训练和预测,第一种基于XGBoost的sklearn API(推荐),第二种是基于XGBoost自带接口。
import pandas as pdfrom sklearn import metricsfrom sklearn.model_selection import train_test_splitimport xgboost as xgbimport matplotlib.pyplot as pltfrom xgboost.sklearn import XGBClassifierclf = XGBClassifier( n_estimators=20 , learning_rate=0.1 , max_depth=5 , min_child_weight=1 , silent=1 , subsample=0.8 , colsample_bytree=0.8 , objective='multi:softmax' , num_class=3 , nthread=4 , seed=27 ) print "training..." clf.fit(train_text_tfidf, train_y, verbose=True ) fit_pred = clf.predict(test_text_tfidf) dtrain=xgb.DMatrix(train_text_tfidf,label=train_y) dtest=xgb.DMatrix(test_text_tfidf) watchlist = [(dtrain,'train' )] params={'booster' :'gbtree' , 'objective' : 'multi:softmax' , 'num_class' :14 , 'eval_metric' : 'auc' , 'max_depth' :5 , 'lambda' :10 , 'subsample' :0.75 , 'colsample_bytree' :0.75 , 'min_child_weight' :2 , 'eta' : 0.025 , 'seed' :0 , 'nthread' :8 , 'gamma' :0.15 , 'learning_rate' : 0.01 } bst=xgb.train(params,dtrain,num_boost_round=50 ,evals=watchlist) ypred=bst.predict(dtest) y_pred = (ypred >= 0.5 )*1 print ('Precesion: %.4f' %metrics.precision_score(test_y,ypred,average='macro' ))print ('Recall: %.4f' % metrics.recall_score(test_y,ypred,average='macro' ))print ('F1-score: %.4f' %metrics.f1_score(test_y,ypred,average='macro' ))print ('Accuracy: %.4f' % metrics.accuracy_score(test_y,ypred,average='macro' ))print ('AUC: %.4f' % metrics.roc_auc_score(test_y,ypred,average='macro' ))
TFIDF 特征+随机森林
import pandas as pdimport numpy as npfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.model_selection import GridSearchCVfrom sklearn import cross_validation, metricsrf0 = RandomForestClassifier(oob_score=True , random_state=10 ) rf0.fit(train_text_tfidf,train_y) print rf0.oob_score_y_predprob = rf0.predict_proba(train_text_tfidf)[:,1 ]
TFIDF+LightGBM
需要安装依赖库lightgbm。
lightgbm库和XGBoost库的使用方式类似,相比XGBoost的优点可参考LightGBM核心解析与调参 - 掘金 (juejin.cn) 。
这里给出推荐的使用方法:
from lightgbm import LGBMClassifierfrom sklearn.datasets import load_irisfrom lightgbm import plot_importanceimport matplotlib.pyplot as pltfrom sklearn.model_selection import train_test_splitfrom sklearn.metrics import accuracy_scoremodel = LGBMClassifier( max_depth=3 , learning_rate=0.1 , n_estimators=200 , objective='multiclass' , num_class=3 , booster='gbtree' , min_child_weight=2 , subsample=0.8 , colsample_bytree=0.8 , reg_alpha=0 , reg_lambda=1 , seed=0 ) model.fit(train_text_tfidf,train_y, eval_set=[(train_text_tfidf, train_y), (test_text_tfidf, test_y)], verbose=100 , early_stopping_rounds=50 ) y_pred = model.predict(test_text_tfidf) accuracy = accuracy_score(test_y,y_pred) print ('accuracy:%3.f%%' %(accuracy*100 ))plot_importance(model) plt.show()
交叉验证模型效果
score = cross_val_score(clf,X,y,cv=skf,scoring='f1' ) print ('f1 score:' +str (score)+'\n' )
交叉验证+网格搜索选择参数
结合分层抽样和网格搜索超参数来确定最优模型:
from sklearn.model_selection import GridSearchCVfrom sklearn.model_selection import StratifiedKFold decision_tree_classifier = DecisionTreeClassifier() parameter_grid = {'max_depth' : [1 , 2 , 3 , 4 , 5 ], 'max_features' : [1 , 2 , 3 , 4 ]} skf = StratifiedKFold(n_splits=10 ,shuffle=True ,random_state=1 ) skf = skf.get_n_splits(train_text_tfidf, train_y) grid_search = GridSearchCV(decision_tree_classifier, param_grid=parameter_grid,cv=skf) grid_search.fit(all_inputs, all_classes)
这里以随机森林和LightGBM的交叉验证+网格搜索为例进行实验。
TFIDF+随机森林+交叉验证+网格搜索
param_test1 = {'n_estimators' :range (10 ,71 ,10 )} gsearch1 = GridSearchCV(estimator = RandomForestClassifier(min_samples_split=100 ,min_samples_leaf=20 ,max_depth=8 ,max_features='sqrt' ,random_state=10 ), param_grid = param_test1,cv=skf) gsearch1.fit(train_text_tfidf,train_y) gsearch2.cv_results_,gsearch1.best_params_, gsearch1.best_score_ param_test2 = {'max_depth' :range (3 ,14 ,2 ), 'min_samples_split' :range (50 ,201 ,20 )} gsearch2 = GridSearchCV(estimator = RandomForestClassifier(n_estimators= 70 , min_samples_leaf=20 ,max_features='sqrt' ,oob_score=True , random_state=10 ), param_grid = param_test2, cv=skf) gsearch2.fit(train_text_tfidf,train_y) gsearch2.cv_results_,gsearch2.best_params_, gsearch2.best_score_
TFIDF+LightGBM+交叉验证+网格搜索
from sklearn.datasets import load_irisimport lightgbm as lgbfrom sklearn.model_selection import GridSearchCV from sklearn.model_selection import train_test_splitparameters = { 'max_depth' : [15 , 20 , 25 , 30 , 35 ], 'learning_rate' : [0.01 , 0.02 , 0.05 , 0.1 , 0.15 ], 'feature_fraction' : [0.6 , 0.7 , 0.8 , 0.9 , 0.95 ], 'bagging_fraction' : [0.6 , 0.7 , 0.8 , 0.9 , 0.95 ], 'bagging_freq' : [2 , 4 , 5 , 6 , 8 ], 'lambda_l1' : [0 , 0.1 , 0.4 , 0.5 , 0.6 ], 'lambda_l2' : [0 , 10 , 15 , 35 , 40 ], 'cat_smooth' : [1 , 10 , 15 , 20 , 35 ] } gbm = LGBMClassifier(max_depth=3 , learning_rate=0.1 , n_estimators=200 , objective='multiclass' , num_class=3 , booster='gbtree' , min_child_weight=2 , subsample=0.8 , colsample_bytree=0.8 , reg_alpha=0 , reg_lambda=1 , seed=0 ) gsearch = GridSearchCV(gbm, param_grid=parameters, scoring='accuracy' , cv=skf) gsearch.fit(train_text_tfidf,train_y)
参考资料
Datawhale零基础入门NLP赛事 - Task3 基于机器学习的文本分类
GBDT、XGBoost、LightGBM的区别和联系
集成学习(五)LightGBM
西瓜书阅读笔记——第8章-集成学习
西瓜书阅读笔记——第3章-线性回归(3.1-3.2)
LightGBM 重要参数、方法、函数理解及调参思路、网格搜索(附例子)
LightGBM核心解析与调参
Parameters — LightGBM 3.3.0.99 documentation