Task4 基于深度学习的文本分类1-FastText
与传统机器学习不同,深度学习既提供特征提取功能,也可以完成分类的功能。
学习目标
- 学习FastText的使用和基础原理
- 学会使用验证集进行调参
文本表示方法 Part2-1
现有文本表示方法的缺陷
之前介绍的几种文本表示方法(One-hot、Bag of Words、N-gram、TF-IDF)都或多或少存在一定的问题:转换得到的向量维度很高,需要较长的训练实践;没有考虑单词与单词之间的关系,只是进行了统计。
与这些表示方法不同,深度学习也可以用于文本表示,还可以将其映射到一个低纬空间。其中比较典型的例子有:FastText、Word2Vec和Bert。
FastText
FastText是一种典型的深度学习词向量的表示方法,它非常简单通过Embedding层将单词映射到稠密空间,然后将句子中所有的单词在Embedding空间中进行平均,进而完成分类操作。
所以FastText是一个三层的神经网络,输入层、隐含层和输出层。
下图是使用keras实现的FastText网络结构:
FastText在文本分类任务上,是优于TF-IDF的:
- FastText用单词的Embedding叠加获得的文档向量,将相似的句子分为一类
- FastText学习到的Embedding空间维度比较低,可以快速进行训练
参考论文:Bag of Tricks for Efficient Text Classification
基于FastText的文本分类
安装FastText
FastText可以快速的在CPU上进行训练,最好的实践方法就是官方开源的版本: https://github.com/facebookresearch/fastText/tree/master/python
这里建议在这个网站直接下载fasttext的库,然后进入下载路径,用下面的语句直接安装:
cd 文件所在路径
pip install fasttext-0.9.2-cp38-cp38-win_amd64.whl
数据加载、预处理和划分
这里的操作和之前一致,不再赘述。
#数据加载、预处理
import pandas as pd
import joblib
data_file = 'train_set.csv'
rawdata = pd.read_csv(data_file, sep='\t', encoding='UTF-8')
#用正则表达式按标点替换文本
import re
rawdata['words']=rawdata['text'].apply(lambda x: re.sub('3750|900|648',"",x))
del rawdata['text']
#数据划分
from sklearn.model_selection import train_test_split
import joblib
rawdata.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) #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']]
train_y=rawdata.loc[train_data['X_train']]['label'].values
test_x=rawdata.loc[test_data['X_test']]
test_y=rawdata.loc[test_data['X_test']]['label'].values
# 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))
FastText训练和预测
import pandas as pd
from sklearn.metrics import f1_score
# 转换为FastText需要的格式
train_x['label_ft'] = '__label__' + train_x['label'].astype(str)
train_x[['words','label_ft']].to_csv('fasttext_need_train.csv', index=None, header=None, sep='\t')
import fasttext
model = fasttext.train_supervised('fasttext_need_train.csv', lr=1.0, wordNgrams=2, verbose=2, minCount=1, epoch=25, loss="hs")
val_pred = [model.predict(x)[0][0].split('__')[-1] for x in test_x['words']]
print(f1_score(test_y.astype(str), val_pred, average='macro'))
#验证集得分:0.9198590651705543
FastText+交叉验证+网格搜索参数
这里主要参考了博文FastText调参:GridSearch+CV
# 将各个参数的取值进行排列组合
def get_gridsearch_params(param_grid):
params_combination = [dict()] # 用于存放所有可能的参数组合
for k, v_list in param_grid.items():
tmp = [{k: v} for v in v_list]
n = len(params_combination)
# params_combination = params_combination*len(tmp) # 浅拷贝,有问题
copy_params = [copy.deepcopy(params_combination) for _ in range(len(tmp))]
params_combination = sum(copy_params, [])
_ = [params_combination[i*n+k].update(tmp[i]) for k in range(n) for i in range(len(tmp))]
return params_combination
# 使用k折交叉验证,得到最后的score,保存最佳score以及其对应的那组参数
# 输入分别是训练数据帧,要搜索的参数,用于交叉验证的KFold对象,最佳score评价指标,几分类
def get_KFold_scores(df, params, kf, metric, n_classes):
metric_score = 0.0
for train_idx, val_idx in kf.split(df['words'],df['label']):
df_train = df.iloc[train_idx]
df_val = df.iloc[val_idx]
tmpdir = tempfile.mkdtemp() #因为fasttext的训练时读入一个训练数据所在的目录或文件,所以这里用交叉验证集开一个临时目录/文件
tmp_train_file = tmpdir + '/train.txt'
df_train.to_csv(tmp_train_file, sep='\t', index=False, header=None, encoding='UTF-8') # 不要表头
fast_model = fasttext.train_supervised(tmp_train_file, label_prefix='__label__', thread=3, **params) #训练,传入参数
#用训练好的模型做评估预测
predicted = fast_model.predict(df_val['words'].tolist()) # ([label...], [probs...])
y_val_pred = [int(label[0][-1:]) for label in predicted[0]] # label[0] __label__0
y_val = df_val['label'].values
score = get_metrics(y_val, y_val_pred, n_classes)[metric]
metric_score += score #累计在不同的训练集上的score,用于计算在整个交叉验证集上平均分
shutil.rmtree(tmpdir, ignore_errors=True) #删除临时训练数据文件
print('平均分:', metric_score / kf.n_splits)
return metric_score / kf.n_splits
# 网格搜索+交叉验证
# 输入分别是训练数据帧,要搜索的参数,最佳score评价指标,交叉验证要做几折
def my_gridsearch_cv(df, param_grid, metrics, kfold=10):
n_classes = len(np.unique(df['label']))
print('n_classes', n_classes)
#kf = KFold(n_splits=kfold) # k折交叉验证
skf = StratifiedKFold(n_splits=kfold,shuffle=True,random_state=1) #k折分层采样交叉验证
params_combination = get_gridsearch_params(param_grid) # 获取参数的各种排列组合
best_score = 0.0
best_params = dict()
for params in params_combination:
avg_score = get_KFold_scores(df, params, skf, metrics, n_classes)
if avg_score > best_score:
best_score = avg_score
best_params = copy.deepcopy(params)
return best_score, best_params
import fasttext
from sklearn.model_selection import KFold, StratifiedKFold
import numpy as np
import pandas as pd
import copy
import tempfile
import shutil
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report
#计算分类评价指标
def get_metrics(y_true, y_pred, n_classes):
metrics = {}
if n_classes==2:
#二分类
metrics['precision'] = precision_score(y_true, y_pred, pos_label=1)
metrics['recall'] = recall_score(y_true, y_pred, pos_label=1)
metrics['f1'] = f1_score(y_true, y_pred, pos_label=1)
else:#多分类
average = 'macro'
metrics[average+'_precision'] = precision_score(y_true, y_pred, average=average)
metrics[average+'_recall'] = recall_score(y_true, y_pred, average=average)
metrics[average+'_f1'] = f1_score(y_true, y_pred, average=average)
metrics['accuracy'] = accuracy_score(y_true, y_pred)
metrics['confusion_matrix'] = confusion_matrix(y_true, y_pred)
metrics['classification_report'] = classification_report(y_true, y_pred)
return metrics
#DATA_PATH = '../data/'
# 要调试的参数
tuned_parameters = {
'lr': [1.0, 0.85, 0.5],
'epoch': [30,50],
'dim': [ 200],
'wordNgrams': [2, 3],
}
# 这里引入上述3个方法
if __name__ == '__main__':
#filepath = DATA_PATH + 'fast/augmented/js_pd_tagged_train.txt'
#df = pd.read_csv(filepath, encoding='UTF-8', sep='\t', header=None, index_col=False, usecols=[0, 1])
print(train_x.head())
print(train_x.shape)
best_score, best_params = my_gridsearch_cv(train_x, tuned_parameters, 'accuracy', kfold=10)
print('best_score', best_score)
print('best_params', best_params)
参考资料
Datawhale零基础入门NLP赛事 - Task4 基于深度学习的文本分类1-fastText