FastText进行文本分类实践

0、内容介绍

本文主要介绍如何使用利用fastText进行文本分类任务,包括如何准备、处理数据,训练及测试过程。

最近用到fastText进行文本分类任务,其不用训练好的词向量,训练简单又快速,尝试了一下,效果还不错。本文旨在记录测试的过程。

本文不涉及算法原理部分,具体的原理可参考下面这篇博客:
原理参考
fastText原理和文本分类实战,看这一篇就够了

1、FastText是什么?

fasttextfacebook开源的一个词向量与文本分类工具,于2016年开源,典型应用场景是 带监督的文本分类问题 。提供简单而高效的文本分类和表征学习的方法,性能比肩深度学习而且速度更快。

github地址:FastText之github

fastText方法包含三部分,模型架构层次SoftmaxN-gram特征

  • 文本分类: 有监督学习
  • 词向量表征:无监督学习

1.1 安装

安装需求如下:

  • 通常,fastText建立在Mac OS和Linux发行版上。由于它使用了一些C++ 11特性,因此需要具有良好C++ 11支持的编译器。这些包括 :
    • (g+±4.7.2 or newer) or (clang-3.3 or newer)

使用Makefile进行编译,因此您需要有一个有效的make。如果你想使用cmake,你至少需要2.8.9版本。

  • 对于python bindings来讲,需要满足如下条件:
    • Python version 2.7 or >=3.4
    • NumPy & SciPy
    • pybind11

编译

github上获取源码,进行编译,包含使用make编译,使用cmake编译,使用Python编译。

  1. 使用make编译(首选)
    $ wget https://github.com/facebookresearch/fastText/archive/v0.9.1.zip
    $ unzip v0.9.1.zip
    $ cd fastText-0.9.1
    $ make
    
  2. 使用cmake构建fastText

    目前,这不是release版本的一部分,因此您需要克隆主分支。

    $ git clone https://github.com/facebookresearch/fastText.git
    $ cd fastText
    $ mkdir build && cd build && cmake ..
    $ make && make install
    
    这将创建fasttext二进制文件以及所有相关库(shared, static, PIC)。
  3. 使用python编译

    目前,这不是release版本的一部分,因此您需要克隆主分支。

    $ git clone https://github.com/facebookresearch/fastText.git
    $ cd fastText
    $ pip install .
    
    如果想要安装最新的release版本,可以使用pip命令直接安装:
    $ pip install fasttext
    

1.2 如何使用?

下载的源代码中给出了一系列的说明文档,位置在fastText/docs

A:单词表达模型

为了学习词向量,我们可以使用fasttext.train_unsupervised函数,像下面这样:

import fasttext

# Skipgram model :
model = fasttext.train_unsupervised('data.txt', model='skipgram')

# or, cbow model :
model = fasttext.train_unsupervised('data.txt', model='cbow')

其中,data.txt是使用utf-8编码的用于训练的文本文件。

  • 保存和加载模型对象
      model.save_model("model_filename.bin")
      model = fasttext.load_model('model_filename.bin')
    

B:文本分类模型

import fasttext

model = fasttext.train_supervised('data.train.txt')

其中,data.train.txt是一个文本文件,每行包含一个训练语句以及标签。默认情况下,我们假设标签是以字符串__label__为前缀的词。
模型训练完成后,我们可以检索单词和标签列表:

print(model.words)
print(model.labels)

验证模型

通过在测试集上计算在P@1的准确度和召回率,(P@1 表示top1精确率,R@1表示top1召回率),使用如下测试函数

def print_results(N, p, r):
    print("N\t" + str(N))
    print("P@{}\t{:.3f}".format(1, p))
    print("R@{}\t{:.3f}".format(1, r))

print_results(*model.test('test.txt'))

可以使用preidict函数来预测指定的文本:

model.predict("Which baking dish is best to bake a banana bread ?", k=3)

其中,k=3用来指定获取前3个概率最高的结果,默认k=1
如果想要预测多个句子,可以传入一个字符串数组,如下:

model.predict(["Which baking dish is best to bake a banana bread ?", "Why not put knives in the dishwasher?"], k=3)

C:使用量化压缩模型

当您想要保存监督模型文件时,fastText可以通过牺牲一点点性能来压缩它以获得更小的模型文件。

# with the previously trained `model` object, call :
model.quantize(input='data.train.txt', retrain=True)

# then display results and save the new model :
print_results(*model.test(valid_data))
model.save_model("model_filename.ftz")

model_filename.ftz的大小比model_filename.bin小得多。

2、使用fastText训练文本分类模型

如何使用Python代码进行训练和测试呢,源代码中给出了demo
位置在fastText/python/doc/examples/train_supervised.py,具体如下

import os
from fasttext import train_supervised
# 打印结果
def print_results(N, p, r):
    print("N\t" + str(N))
    print("P@{}\t{:.3f}".format(1, p))
    print("R@{}\t{:.3f}".format(1, r))

if __name__ == "__main__":
	# 数据
    train_data = os.path.join(os.getenv("DATADIR", ''), 'cooking.train')
    valid_data = os.path.join(os.getenv("DATADIR", ''), 'cooking.valid')

    # train_supervised uses the same arguments and defaults as the fastText cli
    model = train_supervised(
        input=train_data, epoch=25, lr=1.0, wordNgrams=2, verbose=2, minCount=1
    )
    print_results(*model.test(valid_data))

    model = train_supervised(
        input=train_data, epoch=25, lr=1.0, wordNgrams=2, verbose=2, minCount=1,
        loss="hs"
    )
    print_results(*model.test(valid_data))
    # 保存模型
    model.save_model("cooking.bin")
    
	# 压缩模型
    model.quantize(input=train_data, qnorm=True, retrain=True, cutoff=100000)
    print_results(*model.test(valid_data))
    model.save_model("cooking.ftz") # 保存压缩后的模型

从上图可见,训练模型的基本流程如下:

  • 准备训练数据集
  • 处理数据
  • 调用train_supervised函数进行训练
  • 测试/保存模型

fastText模型输入一个词的序列(一段文本或者一句话),输出这个词序列属于不同类别的概率。

2.1 训练数据准备

输入格式为__label__class word1 word2 word3 ...class表示类别标签,其前缀是__label__,注意是前后各两个下划线。

  • 使用jieba进行中文分词
  • 去除停用词
  • 增加类别标签,构造成标准输入格式

对于英文来讲,空格自然断词,而对于中文,需要进行分词处理。
在这里,我使用jieba进行中文分词,类别标签使用数字表示。将处理后的文本写入到文件中(如写入到txt文件),使用utf-8编码格式。

处理后的数据: 每行代表一个文本,以\n结尾,文本以空格分隔单词,如下所示,文本今天天气真的太好了处理后为:
__label__1 今天 天气 真的 太好 了

一条文本可以有多个标签,以空格隔开即可。

def prepro_text(datas, stopwords):
    sentences = []
    label = 1
    for category in datas:
        for line in category:
            try:
                segs = jieba.lcut(line)
                # segs = filter(lambda x:len(x)>1, segs) # 去掉长度小于等于1的词
                segs = filter(lambda x:x not in stopwords, segs)  #去掉停用词
                sentences.append('__label__'+str(label) + " "+" ".join(segs)) #将类别和文本拼接起来
            except Exception as e:
                print('text: %s is error' %(line))
                continue
        label += 1

    return sentences
    
# 将处理过的数据写入文件,编码格式是utf-8
def write_data(datas, file_name):
    print('writing data to fastText format...')
    with io.open(file_name, 'w', encoding='utf-8') as f:
        for senten in datas:
            # print(senten)
            f.write(senten+'\n')
    print('wirte done!')

2.2 训练模型

训练代码如下:

model = train_supervised(input=save_data_file, epoch=10, lr=0.1, wordNgrams=2, minCount=1, loss="softmax")

函数定义在fasttext/python/fasttext_module/fasttext/FastText.py

def train_supervised(*kargs, **kwargs):
    """
    Train a supervised model and return a model object.

    input must be a filepath. The input text does not need to be tokenized
    as per the tokenize function, but it must be preprocessed and encoded
    as UTF-8. You might want to consult standard preprocessing scripts such
    as tokenizer.perl mentioned here: http://www.statmt.org/wmt07/baseline.html

    The input file must must contain at least one label per line. For an
    example consult the example datasets which are part of the fastText
    repository such as the dataset pulled by classification-example.sh.
    """
    supervised_default = unsupervised_default.copy()
    supervised_default.update({
        'lr' : 0.1,
        'minCount' : 1,
        'minn' : 0,
        'maxn' : 0,
        'loss' : "softmax",
        'model' : "supervised"
    })
	# 训练参数
    arg_names = ['input', 'lr', 'dim', 'ws', 'epoch', 'minCount',
        'minCountLabel', 'minn', 'maxn', 'neg', 'wordNgrams', 'loss', 'bucket',
        'thread', 'lrUpdateRate', 't', 'label', 'verbose', 'pretrainedVectors']
    params = read_args(kargs, kwargs, arg_names, supervised_default)
    a = _build_args(params)
    ft = _FastText(args=a)
    fasttext.train(ft.f, a)
    return ft

训练参数

    input             # 训练文件路径 (required)
    lr                # 学习率 [0.1]
    dim               # 词向量维度 [100]
    ws                # 文本窗口大小 [5]
    epoch             # 迭代次数 [5]
    minCount          # 单词出现的最小次数 [1]
    minCountLabel     # minimal number of label occurences [1]
    minn              # min length of char ngram [0]
    maxn              # max length of char ngram [0]
    neg               # number of negatives sampled [5]
    wordNgrams        # max length of word ngram [1]
    loss              # 损失函数 {ns, hs, softmax, ova} [softmax]
    bucket            # number of buckets [2000000]
    thread            # 线程数 [number of cpus]
    lrUpdateRate      # 学习率更新速率 [100]
    t                 # sampling threshold [0.0001]
    label             # 标签前缀 ['__label__']
    verbose           # verbose [2]
    pretrainedVectors # pretrained word vectors (.vec file) for supervised learning []

2.3 模型保存与测试

训练完分类模型,就可以进行测试了。当然,为了以后使用方便,可以先保存一下,使用save_model()函数。

# 保存模型
model = load_model("model_lr%.2f_epoch%d.ftz"%(lr, epoch))

测试中文文本

测试文本类别,需要将测试的文本进行中文分词,然后使用空格连接起来。

segs = jieba.lcut(test_text)
segs = filter(lambda x:x not in stop_words, segs)
test_text = " ".join(segs)
# 测试
lables, proba = model.predict(test_text)
print('%s, %.2f'%(lable_to_cate[int(lables[0][9:])], proba[0]))

调用model.predict()函数,则返回类别类型以及概率值。
注意,此时返回的结果格式如下:

labels:   ('__label__23',) # 类别
proba:    [0.98677748]  # 概率值

获取真实标签直接使用切片操作即可:labels[0][9:]

2.4 模型对象属性

train_supervised,train_unsupervised和load_model函数返回_FastText类的实例,我们通常将其命名为模型对象。

该对象将这些训练参数公开为属性:lr, dim, ws, epoch, minCount, minCountLabel, minn, maxn, neg, wordNgrams, loss, bucket, thread, lrUpdateRate, t, label, verbose, pretrainedVectors.
因此,model.wordNgrams将为您提供用于训练此模型的word gram的最大长度。

此外,该对象还公开了几个函数:

    get_dimension           # Get the dimension (size) of a lookup vector (hidden layer).
                            # 等同于 `dim` 属性.
    get_input_vector        # Given an index, get the corresponding vector of the Input Matrix.
    get_input_matrix        # Get a copy of the full input matrix of a Model.
    get_labels              # 获取整个词典的标签列表
                            # 等同于 `labels` 属性.
    get_line                # 将一行文本分为 words 和 labels.
    get_output_matrix       # Get a copy of the full output matrix of a Model.
    get_sentence_vector     # 给定一个字符串, 获取单个向量表示. 这个函数假设给定一个单独的文本行,我们
                            # 通过空白(空格,newline,tab,vertical tab)来分隔单词,来控制 
                            # characters carriage return, formfeed and the null character.
    get_subword_id          # Given a subword, return the index (within input matrix) it hashes to.
    get_subwords            # Given a word, get the subwords and their indicies.
    get_word_id             # Given a word, get the word id within the dictionary.
    get_word_vector         # Get the vector representation of word.
    get_words               # 获取整个词典的单词列表
                            # 等同于 `words` 属性.
    is_quantized            # 模型是否被量化
    predict                 # Given a string, get a list of labels and a list of corresponding probabilities.
    quantize                # Quantize the model reducing the size of the model and it's memory footprint.
    save_model              # Save the model to the given path
    test                    # Evaluate supervised model using file given by path
    test_label              # Return the precision and recall score for each label.    

单词,标签属性返回字典中的单词和标签:

model.words         # equivalent to model.get_words()
model.labels        # equivalent to model.get_labels()

该对象会覆盖__getitem____contains__函数,以便返回单词的表示形式并检查单词是否在词汇表中。

model['king']       # equivalent to model.get_word_vector('king')
'king' in model     # equivalent to `'king' in model.get_words()`

2.5 提高模型性能的方法

通过使用默认参数运行fastText获得的模型在分类问题时效果并不太好,我们可以尝试更改默认参数来提高性能。

  1. 预处理数据
    如去除标点符号,或者一些停用词,对于英文来讲,可以对于包含大写字母的单词进行规范化等。
  2. 更多的迭代次数和更高的学习率
    默认参数下,训练数据只迭代5次,可以通过参数-epoch来增加迭代次数;
    另一种方法是增加(或降低)学习率,学习率为0意味着模型根本不会改变,因此不会学到任何东西。良好的学习率值在0.1-1.0的范围内。
  3. word n-grams
    使用word bi​​grams而不仅仅是unigrams来提高模型的性能,这对于词序很重要的分类问题尤其重要,例如情绪分析。

总结一下:

  • 对数据进行预处理 ;
  • 更改迭代次数 (通过选项-epoch,标准范围 [5 - 50]) ;
  • 更改学习速率 (使用选项 -lr,标准范围 [0.1 - 1.0]);
  • 使用 word n-grams (使用选项 -wordNgrams,标准范围 [1 - 5])。

3、几个概念

什么是Bigram
unigram指的是单个不可分割的单元或标记,通常用做模型的输入。例如,一个unigram可以是一个单词或字母。在fastText中,我们作用在单词级别,因此unigrams指的是单词。
类似的,我们用bigram表示连续两个单词或tokens的连接,类似的,我们经常讨论n-gram指的是n个连续的tokens的连接.

例如,在句子Last donut of the nightunigramslast, donut, of, the 以及nightbigram是Last donut,donut of, of the 和 the night

减小规模
对于较小的训练数据(如几千个示例)训练模型,只需要几秒钟。但是,对于较大数据集的训练模型,标签越多,开始就越慢。使训练更快的潜在解决方法是使用hierarchical softmax,来代替常规的softmax

可以使用选项 -loss hs来指定。

什么是hierarchical softmax
hierarchical softmax是一个损失函数,要比softmax计算的更快。
想法是通过构建一个二叉树,其叶子对应于标签,每一个黄总监节点有一个二元决策激活(例如,sigmoid),预测是应该向左还是向右。输出单元的概率由沿着从根到输出单元的路径的中间节点的概率的乘积给出。
fastText中,我们使用Huffman tree,对于更频繁的输出,查找时间更快,因此输出的平均查找时间是最佳的。

多标签分类
当我们想要将文档分配给多个标签时,仍然可以使用softmax作为损失函数来预测。即要预测的标签数量和预测概率的阈值。但是使用这些参数可能很棘手且不直观,因为概率和必须为1。

处理多个标签的便捷方法是为每个标签使用独立的二元分类器。可以通过-loss one-vs-all或者-loss ova实现。

  • 12
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值