fastText算法

一、简介

1.1:fastText背景

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

fastText结合了自然语言处理和机器学习中最成功的理念。这些包括了使用词袋以及n-gram袋表征语句,还有使用子字(subword)信息,并通过隐藏表征在类别间共享信息。我们另外采用了一个softmax层级(利用了类别不均衡分布的优势)来加速运算过程。

这些不同概念被用于两个不同任务:

  • 有效文本分类 :有监督学习

  • 学习词向量表征:无监督学习

1.2:fastText优点:

fastText是一个快速文本分类算法,与基于神经网络的分类算法相比有两大优点:
1、fastText在保持高精度的情况下加快了训练速度和测试速度
2、fastText不需要预训练好的词向量,fastText会自己训练词向量
3、fastText两个重要的优化:Hierarchical Softmax、N-gram

二、原理

2.1:fastText模型架构

fastText模型架构和word2vec中的CBOW很相似, 不同之处是fastText预测标签而CBOW预测的是中间词,即模型架构类似但是模型的任务不同。下面我们先看一下两者的架构:

在这里插入图片描述

两者不同:

· 输入层:CBOW的输入是目标单词的上下文并进行one-hot编码,fastText的输入是多个单词embedding向量,并将单词的字符级别的n-gram向量作为额外的特征

· 从输入层到隐藏层,CBOW会将经过一次矩阵乘法并上下文单词向量叠加起来(线性变化)并应用激活函数,而fastText省略了这一过程,直接将embedding过的向量特征求和取平均

· 输出层,一般的CBOW模型会采用Softmax作为输出,而fastText则采用了Hierarchical Softmax,大大降低了模型训练时间;

· CBOW的输出是目标词汇,fastText的输出是文档对应的类标。

2.2:Subword n-gramlnformation

在fastText的工作之前,大部分的文本向量化的工作,都是以词汇表中的独立单词作为基本单元来进行训练学习的。这种想法非常自然,但也会带来如下的问题:

· 低频词、罕见词,由于在语料中本身出现的次数就少,得不到足够的训练,效果不佳;

· 未登录词,如果出现了一些在词典中都没有出现过的词,或者带有某些拼写错误的词,传统模型更加无能为力。

fastText引入了subword n-gram的概念来解决词形变化(morphology)的问题。直观上,它将一个单词打散到字符级别,并且利用字符级别的n-gram信息来捕捉字符间的顺序关系,希望能够以此丰富单词内部更细微的语义。我们知道,西方语言文字常常通过前缀、后缀、字根来构词,汉语也有单字表义的传统,所以这样的做法听起来还是有一定的道理。

举个例子。对于一个单词“google”,为了表达单词前后边界,我们加入<>两个字符,即变形为“<google>”。假设我们希望抽取所有的tri-gram信息,可以得到如下集合:G = { <go, goo, oog,ogl, gle, le>}。在实践中,我们往往会同时提取单词的多种n-gram信息,如2/3/4/5-gram。这样,原始的一个单词google,就被一个字符级别的n-gram集合所表达。

在训练过程中,每个n-gram都会对应训练一个向量,而原来完整单词的词向量就由它对应的所有n-gram的向量求和得到。所有的单词向量以及字符级别的n-gram向量会同时相加求平均作为训练模型的输入。

从实验效果来看,subword n-gram信息的加入,不但解决了低频词未登录词的表达的问题,而且对于最终任务精度一般会有几个百分点的提升。唯一的问题就是由于需要估计的参数多,模型可能会比较膨胀。

2.3:压缩模型的建议:

· 采用hash-trick。由于n-gram原始的空间太大,可以用某种hash函数将其映射到固定大小的buckets中去,从而实现内存可控;

· 采用quantize命令,对生成的模型进行参数量化和压缩;

· 减小最终向量的维度

需要注意的是以上几种方法都会以一定的精度损失为代价,尤其是维度的压缩,具体可以实践中再权衡。

2.4:Hierarchical Softmax

层次化的Softmax的思想实质上是将一个全局多分类的问题,转化成为了若干个二元分类问题,从而将计算复杂度从O(V)降到O(logV)。

每个二元分类问题,由一个基本的逻辑回归单元来实现。如下图所示,从根结点开始,每个中间结点(标记成灰色)都是一个逻辑回归单元,根据它的输出来选择下一步是向左走还是向右走。下图示例中实际上走了一条“左-左-右”的路线,从而找到单词w₂。而最终输出单词w₂的概率,等于中间若干逻辑回归单元输出概率的连乘积。

逻辑回归单元的参数

每个逻辑回归单元中,sigmoid函数所需的输入实际上由三项构成,如下公式所示:

记号说明如下:

1. ⟦x⟧是一个特殊的函数,如果下一步需要向左走其函数值定义为1,向右则取-1。在训练时,我们知道最终输出叶子结点,并且从根结点到叶子结点的每一步的路径也是确定的。

2. v' 是每个内部结点(逻辑回归单元)对应的一个向量,这个向量可以在训练过程中学习和更新。

3. h 是网络中隐藏层的输出。

因此,我们以隐藏层的输出、中间结点对应向量以及路径取向函数为输入,相乘后再经过sigmoid函数,得到每一步逻辑回归的输出值。

三:代码实战(fastText地名识别)

import random
import fasttext
import config
import os

cate_dic = {'place': 1, 'others': 0}
def loadData():
    """
    函数说明:加载数据
    """
    pos_data, neg_data = [], []
    if (not os.path.exists(config.path['postive_path']) or not os.path.exists(config.path['negative_path'])):
        print("正样本或负样本文件有个不存在")
    fr_one = open(config.path['postive_path'])
    fr_two = open(config.path['negative_path'])
    for line in fr_one:
        ele_list = line.strip().split();
        if (len(ele_list) > 0):
            pos_data.append(ele_list[0])
        else:
            continue
    for line in fr_two:
        ele_list = line.strip().split();
        if (len(ele_list) > 0):
            neg_data.append(ele_list[0])
        else:
            continue
    print('正样本个数是%d' % int(len(pos_data)))
    print('负样本个数是%d' % int(len(neg_data)))
    return pos_data, neg_data


def sprate(x):
    """
    分割字,保存在list中
    :param x:
    :return:
    """
    ele_list = []
    for i in range(len(x)):
        ele_list.append(x[i])
    return ele_list


def preprocess_text(content_line, sentences, category):
    """
    函数说明:按字进行分词,并转化为fastext的文本格式
    参数:
        content_line:文本数据
        sentences:存储的数据
        category:文本类别
    """
    for line in content_line:
        try:
            ele_list = sprate(line)
            sentences.append(
                "__label__" + str(category) + " , " + " ".join(ele_list))  # 把当前的文本和对应的类别拼接起来,组合成fasttext的文本格式
        except Exception as e:
            print(line)
            continue


def writeData(sentences, fileName):
    """
    函数说明:把处理好的写入到文件中,备用
    参数说明:
    """
    print("writing data to fasttext format...")
    out = open(fileName, 'w')
    for sentence in sentences:
        out.write(sentence + "\n")
    print("done!")


def preprocessData(saveDataFile):
    """
    生成数据集,保存训练集
    :param saveDataFile:
    :return:
    """
    place, others = loadData()
    sentences = []
    preprocess_text(place, sentences, cate_dic["place"])
    preprocess_text(others, sentences, cate_dic["others"])
    print(sentences)
    random.shuffle(sentences)  # 做乱序处理,使得同类别的样本不至于扎堆
    writeData(sentences, saveDataFile)


if __name__ == "__main__":
    saveDataFile = config.path['save_data_path']
    preprocessData(saveDataFile)
    model = fasttext.train_supervised(saveDataFile, lr=0.1, dim=100, epoch=10, word_ngrams=3, loss='softmax')
    result = model.test(saveDataFile)
    model.save_model("./model_file.bin")
    print(result)
    lable_to_cate = {'__label__1': 'place', '__label__0': 'others'}
    texts = ['马 家 小 学 镇']
    lables = model.predict(texts)
    print(lables)
    print(lable_to_cate[lables[0][0][0]])

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值