基于fasttext的文本分类

274 阅读6分钟

基于fasttext的文本分类

前段时间使用bert参与了一场竞赛,回过头来看fasttext,感觉这个工具,就一个特点,快,很快,非常快,所以回过头来就整理一下。

工具下载,进入链接后选择合适的版本,下载即可。 image.png 进入后点击左侧下载到本地,通过pip install命令安装

数据

image.png

这里使用的是新闻数据,每条格式都是“类别+标题+正文”,训练与测试数据自行下载

数据读取

import pandas as pd
df = pd.read_csv('./cnews/cnews_train.txt',
                 sep='\t',
                 error_bad_lines=False,header=None)
df.columns = ['labels', 'str']

数据展示

df.head(3)

   labels             str
0   体育  商瑞华首战复仇心切 中国玫瑰要用美国方式攻克...
1   体育  冠军球队迎新欢乐派对 黄旭获大奖张军赢下PK赛...
2   体育  辽足签约危机引注册难关 高层威逼利诱合同笑里...

数据标准格式

__label__Shares , 中铁 物资 最快 今年底 A H股 上市 募资 120 亿……

fasttext模型训练时使用的标准数据分为两块,第一块的组成是“__label__”+“类别名称”,这里的类别名称可以是中文也可以是英文,值得一提的是,这个标签其实可以填写多个,但是非常不推荐。

第二块是文章主体,并且主体已经完成分词,并使用空格分割。

第一块与第二块之间使用西文逗号分割。

所以,想要非常简单直接的使用fasttext,首先,得把数据改好。

数据加工

标签加工

# 标签加工
def create_label(x):
    return '__label__' + str(x)

df['label_type'] = df['labels'].apply(lambda x: create_label(x))

效果展示

df['label_type'].head(3)

0    __label__体育
1    __label__体育
2    __label__体育
Name: label_type, dtype: object

文章主体加工

文章主体的加工其实也非常简单,只是相对于标签加工而言多了一个分词的步骤,详细的分析原理可以研究研究结巴分词,不得不说的是结巴真的挺强大的,至于到底应该如何选择分词工具、分词模式,仁者见仁智者见智,文中就简单使用,不做详细甄别。

import jieba

def str_cut(x):
    x = ' '.join(i for i in jieba.cut(x))
    return x

df['text'] = df['str'].apply(lambda x: str_cut(x))

效果展示

df['text'].head(3)

0    商瑞华 首战 复仇 心切 中国 玫瑰 要 用 美国 方式 攻...
1    冠军 球队 迎新 欢乐 派对 黄旭获 大奖 张军 赢 下...
2    辽足 签约 危机 引 注册 难关 高层 威逼利诱 合同 笑里藏刀 新浪...
Name: text, dtype: object

数据合并

df['model_text'] = df['label_type'] + "," + df['text']

效果展示

df['model_text'].head(3)

0    __label__体育,商瑞华 首战 复仇 心切 中国 玫瑰 要 用 美国 方式 攻克 ...
1    __label__体育,冠军 球队 迎新 欢乐 派对 黄旭获 大奖 张军 赢 下 PK ...
2    __label__体育,辽足 签约 危机 引 注册 难关 高层 威逼利诱 合同 笑里藏刀...
Name: model_text, dtype: object

训练数据写出

在这里有个很不解的地方,就是fasttext训练的时候是通过外置文件进行读取训练的,我一直没找到应该怎么样去用脚本内的数据集进行模型的训练,每次都是文件转存好后,通过模型去调用这个文件,离离原上谱……

抽一部分数据作为训练数据进行写出,留一部分数据预测

import numpy as np
from sklearn.model_selection import train_test_split

x_train,x_text = train_test_split(df, train_size=0.1, random_state=18)

np.savetxt('./data_set',
           x_train.values,
           delimiter=' ',
           fmt="%s",
           encoding='utf-8')

此处抽取了10%的数据进行训练,剩下90%都作为预测。

模型

模型构建与训练

import fasttext

model = fasttext.train_supervised('data_set',
                                  epoch = 1000,
                                  lr = 0.9,
                                  dim = 5,
                                  wordNgrams = 1,
                                  loss='softmax')

为了使得模型尽可能简化,我在这里选用了一组计算消耗低的参数用于模型训练,常见参数如下:

参数含义
lr学习率
dim向量维度
wscbow模型时使用
epoch训练轮数
minCount词频阈值, 小于该值在初始化时会过滤掉
minCountLabel类别阈值,类别小于该值初始化时会过滤掉
minn构造subword时最小char个数
maxn构造subword时最大char个数
neg负采样
wordNgramsn-gram个数
loss损失函数类型[softmaxns--负采样, hs--分层softmax]
bucket词扩充大小, [A, B]A语料中包含的词向量, B不在语料中的词向量
thread线程个数, 每个线程处理输入数据的一段, 0号线程负责loss输出
lrUpdateRate学习率更新
t负采样阈值

模型保存

# model.save_model("./model/fasttext.bin")
# model = fasttext.load_model("./model/fasttext.bin")

模型预测

def x_pre(x):
    f = model.predict(x)[0][0].split(',')[0].replace('__label__','')
    return f

df['pred'] = df['text'].apply(lambda x: x_pre(x))

模型检验

acc_lst = []
for i in range(len(df)):
    if df['labels'][i] == df['pred'][i]:
        acc_lst.append(1)
    else:
        acc_lst.append(0)

sum(acc_lst)/len(acc_lst)

效果展示

sum(acc_lst)/len(acc_lst)

0.84718

可以看到,我们使用了新闻数据集10%的数据来进行训练,训练好的模型在整个数据集上的精度是84%,相对而言效果还是不错的。

image.png

说一些题外话

之前有一个省份的项目,是工单相关的,原来的模型用了一种让人很不理解的方法:

1 分词

2 tf-idf提取词汇

3 词汇编码构建词向量

4 词向量使用lightgbm构建分类模型

这个模型很帅,有自定义业务分词,有逆文档词频,有编码,有集成学习。啥都有,就是效果缺了点。值得一提的是,这个模型全程没有注释,里边还掺杂了大量的业务逻辑,我看了一天,人都傻了。在这里恳求大家,一定要养成代码配注释的好习惯。

后来项目组到交付期限了,模型有,但是一直达不到测试要求,于是转了一圈最后到了研发中心,我和项目人员沟通的时候,得到的方案是:能不能优化一下?我痛定思痛后果断拒绝,然后用正文里看起来很小清新的方法,把这个项目的核心部分全部重构了,后来反馈也挺好,顺利交付。

在这里就得说一些项目上的思考,很多时候,项目会在追逐”算法高大上“的路上越走越远,但是如果真的静下心来看一看,就会发现,其实有很多看起来不起眼但是效果真的、不不错的方法可以选择,就像上边的例子里,是有机会和有条件去进行bert训练的,但是为什么最后会选择效果相对偏差的fasttext呢?

主要还是因为一点:算力,工单方面想要实现快速响应,尽管bert的效果辗压群雄,但是慢,今天接到客户需求说,近期整理好了一批数据,想加工测试一下看看模型会不会有提升,明天给个报告。能说做不了嘛?不能。

所以,前沿的技术不一定要用但是要会,并且实际项目中一定要务实,不然,你永远不知道,会不会在什么时候坑到自己,或者是坑到一个素不相识的小倒霉蛋。