文本分类模型第三弹:BoW(Bag of Words) + TF-IDF + LightGBM

万俟均
2023-12-01

一、前言

本文是文本分类模型的第三弹,利用词袋模型(BoW),词频逆文档频率(TF-IDF)与 LightGBM 模型进行文本分类。

原本计划的第三弹内容为 TextCNN 网络,介于最近刚刚利用 LightGBM 完成了一个简单的文本分类的任务,就趁热记录一下,作为第三弹的内容。

这里是文本分类系列:

文本分类模型第一弹:关于Fasttext,看这一篇就够了

文本分类模型第二弹:HAN(Hierarchy Attention Network)

文本分类模型第三弹:BoW(Bag of Words) + TF-IDF + LightGBM


二、相关论文及理论

1.LightGBM

这里是 LightGBM 提出的论文《LightGBM: A Highly Efficient Gradient Boosting Decision Tree》,LightGBM 本质上也是 梯度提升树(GBDT)的优化模型,GBDT 的本质是回归树模型,在做分类任务时,通过“回归相应类别”的概率值,来曲线完成分类任务。LightGBM 与上一代 kaggle 神器 Xgboost 相比,由于采用了直方图算法(用于特征处理),和 Leaf-wise 的树分裂方法(用于模型构建),模型准确度更高,训练耗时更低。其不仅应用于传统的机器学习回归及二分类,多分类任务,在 CTR 预估,推荐系统中也有着广泛的应用。

2.词袋模型(BoW)

再啰嗦一句词袋模型,Bag of Words,这是我学习的第一个 NLP 语言模型。Bag of Words 会将词典里面的每一个单词编一个序号,这个序号从 0 开始累加,这样当构造一个长度等于词典的 list 时,它其中的每一维就对应了词典中的每一个字。

当需要表示某一个词时,这个向量对应这个单词的那一维的值为1,其余为0,这就是词的 one-hot 表示方法。

当需要表示一篇文章时,文章中的每一个词对应的那一维的值为单词出现的次数,也叫做词频(TF),这就是最简单的文本表示方法。

3.TF-IDF

理论上来说,当我们构造出了基于 BoW 的文档向量后,只要对向量稍加处理(归一化),就可以使用这个文档进行文本分类了,但这样的效果并不是很好,因为 BoW + TF 构造的向量只是对单个文档进行了信息统计,并没有考虑到所有文档的统计信息,以及文档之间究竟以什么来区分。所以需要对文档中出现的词的 TF 值稍加处理,那就是计算TF-IDF。

网上搜一下 TF-IDF 的原理,大概可以搜到上万篇博客文章,所以这里就不再进行赘述。只用一句话来解释它的作用就是:找出自己有什么,而别人没有。


三、代码

代码分为两部分来介绍,分别是向量构建及模型构建部分。

文本特征向量构建其实就是机器学习过程中构造特征工程的过程。这里使用了 gensim 和 LightGBM 包进行构建,sklearn 中也集成有调用 BoW, TF-IDF 和 LightGBM 的方法,你也可以通过 sklearn 一站式搞定它们。

1.词袋模型及文本特征向量构建

from gensim import corpora, models

# bulid dictionay and tfidf model
all_corporas = []

for data in raw_data:
    text = clean_data(data)
    all_corporas.append(text)
    
    
dictionary = corpora.Dictionary(all_corporas)
corpus = [dictionary.doc2bow(text) for text in all_corporas]
tfidf = models.TfidfModel(corpus, id2word = dictionary)


# bulid one hot doc vector
def bulid_onehot_vector(data):
    tfidf_vec = tfidf[dictionary.doc2bow(clean_data(data))]
    one_hot = [0] * len(dictionary)
    for x in tfidf_vec:
        one_hot[x[0]] = x[1]
    return one_hot

clean_data() 中完成了对原始语料的分词以及去停用词操作,去停用词的操作。特别要说明的是,由于是基于文本中词的统计信息来分类,所以去停用词很重要,可以去掉一些不重要词的干扰。

接下来就是构造词典,词典中可以去除一些词频过低的词语,这里可以自己查阅 gensim 官方文档对一些参数进行设置。最后建立 TF-IDF 模型,对特征向量进行构造。

2.LightGBM

import lightgbm as lgb

lgb_train = lgb.Dataset(np.array(train_data), np.array(train_label))
lgb_val = lgb.Dataset(np.array(test_data), np.array(test_label))

params = {'max_depth': 15, 'min_data_in_leaf': 55, 'num_leaves': 80, 'learning_rate': 0.1, 'lambda_l1': 0.1,
          'lambda_l2': 0.2, 'objective': 'multiclass', 'num_class': 3, 'verbose': -1}

num_boost_round = 200

gbm = lgb.train(params, lgb_train, num_boost_round, verbose_eval=50, valid_sets=lgb_val)
result = gbm.predict(np.array(test_data), num_iteration=gbm.best_iteration)

LightGBM 的构建就更加简单了,首先将训练数据封装成 Dataset 格式,设置好参数,直接 train 就ok,这里主要说一下参数的设置吧。

LightGBM 调参中,最重要的三个参数分别是 max_depth, min_data_in_leaf, num_leaves。

LightGBM 采用的分裂方式是 Leaf-wise,这样每一棵 LightGBM 能够更好的对上一棵树预测残差的负梯度进行拟合,这使得 LightGBM 的精度更高,但也更容易过拟合,这三个参数是控制 LightGBM 模型拟合程度的关键所在。

首先,max_depth 代表树的深度,树越深越容易过拟合。

min_data_in_leaf 代表每个叶子节点上的最小样本数量。样本过少容易过拟合。样本过多容易欠拟合,何以理解为预剪枝的操作。

num_leaves 代表树的叶子节点个数。节点数过多容易过拟合,节点数过少容易欠拟合。

也是由于 LightGBM 采用的分裂方式是 Leaf-wise,因此 max_depth 与 num_leaves 并不存在任何关联,需要在 num_leaves 小于最大叶子节点数的前提下分别独立调参。


四、结论

由于数据来源于公司,所以这里训练过程和分类结果就只有跳票,直接来下结论吧。

BoW + TF-IDF + LightGBM 的文本分类原理,表面来说,是基于关键词。但实际上则是基于文档中词的统计信息,这种分类方法完全不会对文章的语义进行理解和分析,所以只适用于一些类别之间特征明显的简单文本分类任务,当遇到未登录词或需要依靠复杂语义分析和语义特征提取的任务时,就无法正确的进行分类判断。当然也可通过分析 bad case,针对性的增加相应语料来提升分类性能。

当文本各个类别的样本数量不均衡时,可以考虑使用 focal loss 来作为 loss function,具体的方法后面单独再写一篇来记录。

另外,当词典过长时,构造的文本向量长度也就较长,这样会造成 LightGBM 推理时间过长。所以需要对词典中的一些词频较低的词进行舍弃,但这样也会造成一定的分类精度降低。

最后的一个 trick 是,在用这种方法分类中文文本时,分类的准确程度就变得更为重要,因此可以在分词的基础上,在词典和文本向量中加入单个汉字,对分类的性能也会有一定的提升。

 

 

如有错误遗漏欢迎交流指正,转载请注明出处。

 类似资料: