天池-新闻文本分类-task2 fasttext

FastText:快速的文本分类器

文章目录

    • 一、word2vec
      • 1.1 word2vec为什么 不用现成的DNN模型
      • 1.2 word2vec两种模型:CBOW和Skip-gram
      • 1.2 word2vec两种优化解法:霍夫曼树和负采样
        • 1.2.2 基于Hierarchical Softmax的CBOW模型算法流程:
        • 1.2.3 负采样方法
      • 1.3 总结:
    • 二、fasttext
      • 2.1、简介
      • 2.2 FastText原理
        • 2.2.1 模型架构
        • 2.2.2 层次SoftMax
        • 2.2.3 N-gram特征
        • 2.2.4 subword
        • 2.2.5 fasttext文本分类总结
    • 三、fastText和word2vec对比总结
      • 3.1 fastText和word2vec的区别
      • 3.2 小结
        • 3.2.1 fasttext适用范围
        • 3.2.2 fasttext应用场景
        • 3.2.3 fastText优点
    • 四、用gensim学习word2vec
      • 4.1 使用技巧
      • 4.2 推荐系统中的Word2vec
    • 五、 基于fastText实现文本分类
      • 5.1 fasttext参数:
      • 5.2 基本使用
      • 5.3 bin格式词向量转换为vec格式
    • 六、新闻文本分类——fasttext
      • 6.1 正常fasttext分类
      • 6.2 小数据集:word2vec+fasttext+首尾截断
      • 6.3 全数据集:word2vec+fasttext+首尾截断

一、word2vec

参考文档《word2vec原理和gensim实现》、《深入浅出Word2Vec原理解析》

1.1 word2vec为什么 不用现成的DNN模型

  1. 最主要的问题是DNN模型的这个处理过程非常耗时。我们的词汇表一般在百万级别以上,从隐藏层到输出的softmax层的计算量很大,因为要计算所有词的softmax概率,再去找概率最大的值。解决办法有两个:霍夫曼树和负采样。
  2. 对于从输入层到隐藏层的映射,没有采取神经网络的线性变换加激活函数的方法,而是采用简单的对所有输入词向量求和并取平均的方法。输入从多个词向量变成了一个词向量
  3. 在word2vec中,由于使用的是随机梯度上升法,所以并没有把所有样本的似然乘起来得到真正的训练集最大似然,仅仅每次只用一个样本更新梯度,这样做的目的是减少梯度计算量

1.2 word2vec两种模型:CBOW和Skip-gram

  Word2Vec是轻量级的神经网络,其模型仅仅包括输入层、隐藏层和输出层,模型框架根据输入输出的不同,主要包括CBOW和Skip-gram模型。

  • CBOW的方式是在知道词 w t w_{t} wt的上下文 w t − 2 w_{t-2} wt2 w t − 1 w_{t-1} wt1 w t + 1 w_{t+1} wt+1 w t + 2 w_{t+2} wt+2的情况下预测当前词 w t w_{t} wt
  • Skip-gram是在知道了词 w t w_{t} wt的情况下,对词的上下文进行预测,如下图所示:
    天池-新闻文本分类-task2 fasttext_第1张图片

1.2 word2vec两种优化解法:霍夫曼树和负采样

  1. 霍夫曼树解法:

    • 采用霍夫曼树来代替隐藏层和输出层的神经元,霍夫曼树的叶子节点起到输出层神经元的作用,叶子节点的个数即为词汇表的小大。 而内部节点则起到隐藏层神经元的作用。
    • 把之前计算所有词的softmax概率变成了查找二叉霍夫曼树。那么我们的softmax概率计算只需要沿着树形结构进行,从根节点一直走到我们的叶子节点的词。将每个节点向左或向右走的概率连乘就是最终预测的概率。训练时只更新对应通路的w,与全连接W相比大大减少。
    • 因为涉及连乘,每次乘的概率都是小于1,所以越到深层概率越低。所以其实存在一个词与词之间概率不对等的问题。
    • 霍夫曼编码:由于权重高的叶子节点越靠近根节点,编码值较短。而权重低的叶子节点会远离根节点,编码值较长。这保证的树的带权路径最短,也符合我们的信息论,即我们希望越常用的词拥有更短的编码,查找就更快。如何编码呢?参见上面提的文档
  2. 负采样:

    • 使用霍夫曼树可以提高模型训练的效率。但是如果我们的训练样本里的中心词是一个很生僻的词,那么就得在霍夫曼树中辛苦的向下走很久了。
    • Negative Sampling:word2vec用神经网络解法时,输出是计算V类的概率,其中1类是中心词,概率往大的方向走,剩下一类是V-1个其它词,概率往小的方向走。真正计算复杂的就是负类别。负采样法就是从V-1个负样本中随机挑几个词做负样本。每个词被选为负样本的概率和其词频正相关00
    • Negative Sampling由于没有采用霍夫曼树,每次只是通过采样neg个不同的中心词做负例,利用这一个正例和neg个负例,我们进行二元逻辑回归,就可以训练模型,因此整个过程要比Hierarchical Softmax简单。二元逻辑回归算法见文档。
    • 负采样中每个词有两套向量,分别作为输入和预测时使用。
  3. 两种解法进行一定优化,牺牲了一定的分类的准确度。比如负采样的负样本是随机选取的,所以相对已经没那么准了。

1.2.2 基于Hierarchical Softmax的CBOW模型算法流程:

  • 输入:根据词向量的维度大小M,以及CBOW的上下文大小2c,步长 η \eta η,得到训练样本。
  • 建立霍夫曼树,整体语料的各个词频 决定 huffman树。
  • 随机初始化所有的模型参数 θ \theta θ,所有的词向量w。这些训练样本所用的huffman树是一棵
  • 随机梯度上升法,对于训练集中的每一个样本 ( c o n t e x t ( w ) , w ) (context(w), w) (context(w),w)中的每一个词向量 x i x_i xi(共2c个)进行迭代更新。
  • 如果梯度收敛,则结束梯度迭代,否则回到上一步继续迭代
    h = ∑ i = 1 2 c e m b e d d i n g i h=\sum_{i=1}^{2c} embedding_{i} h=i=12cembeddingi
    y = s o f t m a x ( d ) = s o f t m a x ( W h ) = 1 ∑ i = 1 V e d i [ e d 1 e d 2 . . . e d V ] y=softmax(d)=softmax(Wh)=\frac{1}{\sum_{i=1}^{V}e^{d_{i}}}\begin{bmatrix} e^{d_{1}}\\ e^{d_{2}}\\ ...\\ e^{d_{V}}\end{bmatrix} y=softmax(d)=softmax(Wh)=i=1Vedi1ed1ed2...edV
    W为全连接层参数,将词向量维度映射为V维(词表大小),表示预测词的概率。

1.2.3 负采样方法

如果词汇表的大小为 V V V,那么我们就将一段长度为1的线段分成 V V V份,每份对应词汇表中的一个词。高频词对应的线段长,低频词对应的线段短(高频词数量多,分子count就大)。每个词 w w w的线段长度由下式决定: l e n ( w ) = c o u n t ( w ) ∑ u ∈ v o c a b c o u n t ( u ) len(w) = \frac{count(w)}{\sum\limits_{u \in vocab} count(u)} len(w)=uvocabcount(u)count(w)

在word2vec中,分子和分母都取了3/4次幂(经验参数,提高低频词被选取的概率)如下: l e n ( w ) = c o u n t ( w ) 3 / 4 ∑ u ∈ v o c a b c o u n t ( u ) 3 / 4 len(w) = \frac{count(w)^{3/4}}{\sum\limits_{u \in vocab} count(u)^{3/4}} len(w)=uvocabcount(u)3/4count(w)3/4

在采样前,我们将这段长度为1的线段划分成 M M M等份,这里 M > > V M >> V M>>V,这样可以保证每个词对应的线段都会划分成对应的小块。而M份中的每一份都会落在某一个词对应的线段上。在采样的时候,我们只需要 M M M个位置中采样出 n e g neg neg个位置就行,此时采样到的每一个位置对应到的线段所属的词就是我们的负例词
天池-新闻文本分类-task2 fasttext_第2张图片
在word2vec中, M M M取值默认为 1 0 8 10^8 108
天池-新闻文本分类-task2 fasttext_第3张图片

1.3 总结:

  1. one-hot:词表大大时内存不够。且所有词相似度都是一样的没有区别
  2. word embedding:考虑使用使用神经网络语言模型,通过训练,将每个词都映射到一个较短的词向量上来
  3. 神经网络语言模型的输入输出,有连续词袋模型CBOW(Continuous Bag-of-Words) 和Skip-Gram两种模型。
    • CBOW模型的训练输入是某个中心词的上下文词向量,输出是词表所有词的softmax概率,训练的目标是期望中心词对应的softmax概率最大。
    • Skip-Gram模型和CBOW的思路是反着来的,即输入中心词词向量,而输出是中心词对应的上下文词向量。比如窗口大小为4,就是输出softmax概率排前8的8个词。
  4. word2vec有两种解法,霍夫曼树和负采样。负采样用得较多,因为构建霍夫曼树比较麻烦。
  5. 一般来说, Skip-Gram模型比CBOW模型更好,因为:
    • Skip-Gram模型有更多的训练样本。Skip-Gram是一个词预测n个词,而CBOW是n个词预测一个词。
    • 误差反向更新中,CBOW是中心词误差更新n个周边词,这n个周边词被更新的力度是一样的。而Skip-Gram中,每个周边词都可以根据误差更新中心词,所以Skip-Gram是更细粒度的学习方法。
    • Skip-Gram效果更好(默认Skip-Gram模型)但是缺点就是训练次数更多,时间更长。

二、fasttext

2.1、简介

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

  fastText的核心思想:将整篇文档的词及n-gram向量叠加平均得到文档向量,然后使用文档向量做softmax多分类。这中间涉及到两个技巧:字符级n-gram特征的引入以及分层Softmax分类。叠加词向量背后的思想就是传统的词袋法,即将文档看成一个由词构成的集合。

这些不同概念被用于两个不同任务:
• 有效文本分类 :有监督学习(短文本)
• 学习词向量表征:无监督学习

2.2 FastText原理

fastText方法包含三部分,模型架构,层次SoftMax和N-gram特征。用词向量的叠加代表文档向量,全连接之后softmax分类。

2.2.1 模型架构

fastText的架构和word2vec中的CBOW的架构类似,因为它们的作者都是Facebook的科学家Tomas Mikolov,而且确实fastText(2016)也算是words2vec(2014)所衍生出来的。
Continuous Bog-Of-Words:

天池-新闻文本分类-task2 fasttext_第4张图片
天池-新闻文本分类-task2 fasttext_第5张图片
隐藏层就是叠加后的句子(文档)向量
参考《理解文本分类利器fastText》

  • 序列中的词和词组组成特征向量,特征向量通过线性变换映射到中间层,中间层再映射到标签。
  • fastText 模型架构和 Word2Vec 中的 CBOW 模型很类似。不同之处在于,fastText 预测标签,而 CBOW 模型预测中间词。
  • 所以fastText只有CBOW模型,对应fastText.train_supervised 没有model参数。 Word2Vec有两种模型,所以fastText.train_unsupervised可以选择model={cbow, skipgram} ,默认skipgram。

2.2.2 层次SoftMax

层次softmax的基本思想是根据类别的频率构造霍夫曼树来代替扁平化的标准softmax。通过层次softmax,获得概率分布的时间复杂度可以从O(N)降至O(logN)。(多分类转成一系列二分类)

下图为层次softmax的一个具体示例:
天池-新闻文本分类-task2 fasttext_第6张图片
天池-新闻文本分类-task2 fasttext_第7张图片

(见速通一书162页)

2.2.3 N-gram特征

   n-gram解决词袋模型没有词序的问题,Hash解决n-gram膨胀问题。最大问题是有Hash冲突,但是实际中问题不大。

   fastText 本身是词袋模型,为了分类的准确性,所以加入了 N-gram 特征提取词序信息。“我 爱 她”如果加入 2-Ngram,第一句话的特征还有 “我-爱” 和 “爱-她”,这两句话 “我 爱 她” 和 “她 爱 我” 就能区别开来了。当然啦,为了提高效率,我们需要过滤掉低频的 N-gram。
   n-gram的问题是词表会急剧扩大,变为 ∣ V ∣ n |V|^n Vn,没有机器扛得住。所以使用散列法(Hash)对n-gram特征进行压缩。
   Hash:使用Hash函数将字符串映射到某个整数。这样不管n-gram词表有多大,最后整数范围都是函数输出范围(比如4000亿词表。hash函数是对10526取余,最后输出就10526个数值,数值再转成向量)

2.2.4 subword

  • word2vec中每个词都是一个基本信息单元,不可再切分。忽略了词内部特征。fasttext采样子词模型表示词,可以从词的构造上学习词义,解决未登录词的问题。
  • fasttext中子词的n-gram长度在minn和maxn之间。如果模型输入是ID之类的特征,子词没有任何意义,应取消子词。即minn=maxn=0。
  • 中文中子词是两个相邻的字,英文中是词根和词缀。

2.2.5 fasttext文本分类总结

  • 一个句子进行分词,每个词进行embedding转换成一个词向量,默认100维。
  • 每个词按位相加成一个新的100维向量。再过一个全连接矩阵,100行(词向量维度)22列(分类数)
  • 经过softmax得到每一类的类别概率。

三、fastText和word2vec对比总结

3.1 fastText和word2vec的区别

相似处:
1.图模型结构很像,都是采用embedding向量的形式,得到word的隐向量表达。
2.都采用很多相似的优化方法,比如使用Hierarchical softmax优化训练和预测中的打分速度。

不同处:
word2vec用词预测词,而且是词袋模型,没有n-gram。fasttext用文章/句子词向量预测类别,加入了n-gram信息。所以有:

  1. 模型的输入层:word2vec的输入层,是 context window 内的词;而fasttext 对应的整个sentence的内容,包括word、n-gram、subword。
  2. 模型的输出层:word2vec的输出层,计算某个词的softmax概率最大;而fasttext的输出层对应的是 分类的label;
  3. 两者本质的不同,体现在 h-softmax的使用:
    • word2vec用的负采样或者霍夫曼树解法(计算所有词概率,类别过大)。
    • fasttext用的softmsx全连接分类(类别少)
  4. word2vec主要目的的得到词向量,该词向量 最终是在输入层得到(不关注预测的结果准不准,因为霍夫曼树和负采样解法虽然优化了训练速度,但是分类结果没那么准了)。fasttext主要是做分类 ,虽然也会生成一系列的向量,但最终都被抛弃,不会使用。
  5. word2vec有两种模型cbow和 skipgram,fasttext只有cbow模型。
  6. word2vec属于监督模型,但是不需要标注样本。fasttext也属于监督模型,但是需要标注样本。

3.2 小结

3.2.1 fasttext适用范围

总的来说,fastText的学习速度比较快,效果还不错。

  1. fastText适用与分类类别比较大而且数据集足够多的情况,当分类类别比较小或者数据集比较少的话,很容易过拟合
  2. 适用于短文本。因为第一步是多个向量相加,文本越长,高频词越多,最后相加结果越趋于相同。(比如关键词只有那么几个,如果长文本词向量相加,关键词就被淹没了)如果非要用于长文本分类,就先去停用词或者干脆提取关键词(这个软件没有分开计算词的权重)

3.2.2 fasttext应用场景

  1. 可以完成无监督的词向量的学习,可以学习出来词向量,来保持住词和词之间,相关词之间是一个距离比较近的情况;
  2. 也可以用于有监督学习的文本分类任务,(新闻文本分类,垃圾邮件分类、情感分析中文本情感分析,电商中用户评论的褒贬分析)
  3. 封装的特别好,用了很多加速模块包括多线程实现。非常简单。Keras可以做模型,定制化,很灵活,但是需要自己搭。Fasttext任务单一,用起来方便。

3.2.3 fastText优点

fastText是一个快速文本分类算法,与基于神经网络的分类算法相比有两大优点:

  1. fastText在保持高精度的情况下加快了训练速度和测试速度
  2. fastText不需要预训练好的词向量,fastText会自己训练词向量
  3. fastText两个重要的优化:Hierarchical Softmax、N-gram
    训练代码中,如果电脑一开始训练就卡了,可以设置线程thread=2。(卡住只能kill进程ps - aux│grep python,kill – 9 1531(进程数)

fasttext已经嵌入word2vec,可以用它做有监督和无监督(就是word2vec)。涉及到离散特征都可以用fasttext。比如招聘网站预测求职者和职位的匹配度。(求职者和职位分别提取关键词特征,然后用fasttext训练,输出录用和不录用的概率。但是求职者简历写本科就是本科学位,职位要求的本科是指本科及以上。二者还是有些不一样。需要把求职者关键字/标签加P,职位标签加J予以区分。即当数据来源不同纬度时,语义可能不同,前面加一个field予以区分)

四、用gensim学习word2vec

参考文档《word2vec原理和gensim实现》

4.1 使用技巧

  1. 用哪种方法看需求:
          1.使用时需要将多个向量相加(文本向量化) 用cbow
          2.使用时都是单个词向量使用(找近义词) 用skip-gram
         大原则:使用的过程和训练的过程越一致 ,效果一般越好
    如果实在不知道怎么选,一般来说skip-gram+ns负采样效果好一点点。

  2. 同一批词分别进行两次训练,embedding也不在同一语义空间,不同语义空间的向量没有可比性。word2vec不能进行增量更新,有新词只能全量训练,因为语料库变了one-hot也变了,V也变了。

  3. 孤岛效应:有一堆词,明明不相关,训练出来确是显示相似的。

    • 某部分词总是一起出现,另一堆词也是一起出现,但是这两堆词互相没有任何交集,虽然在一起训练是一个向量空间,但实际上是两个向量空间。这两堆词互相比较是没有意义的。
    • 孤岛效应本质是由一些不相关语料或者弱相关语料组成。Word2vec本身不能解决这个问题,这个只能在样本选取上下功夫,让训练样本尽可能相关。所以各领域自己训练自己的,不要把一堆不相关的东西放到一起训练。几个行业几套词向量

4.2 推荐系统中的Word2vec

  • word2vec可以计算向量之间的相似度,所以可以在其它领域广泛使用。比如视频分类
  • nlp和推荐系统中最大区别是nlp的词向量比较固定,而推荐系统中用户不断推陈出新,用户向量变化很快。
  • 可以使用Hash技术,将用户ID(如手机设备号)进行hash作为类别。
  • 将视频ID作为词,用户的点击序列作为句子(一连串视频),用word2vec对点击序列进行训练。最后每个视频ID对应一个embedding,用来计算不同视频的相似度,或者作为视频向量输入后续模型。

五、 基于fastText实现文本分类

直接pip安装报错:“Microsoft Visual C++ 14.0 or greater is required”。在此页面下载fasttext文件,然后安装:pip install C:\Users\LS\Downloads\fasttext-0.9.2-cp38-cp38-win_amd64.whl

FastText可以快速的在CPU上进行训练,最好的实践方法就是github教程,以及官网教程。

5.1 fasttext参数:

参考官方文档《Python模块》、《FastText代码详解》

  • FUNCTIONS
    load_model(path):加载给定文件路径的模型并返回模型对象。
    read_args(arg_list, arg_dict, arg_names, default_values)
    tokenize(text):给定一串文本,对其进行标记并返回一个标记列表
    train_supervised(*kargs, **kwargs):监督训练,样本包含标签,即fasttext
    train_unsupervised(*kargs, **kwargs):无监督训练,样本没有标签,即word2vec。

  • fasttext.train_unsupervised函数:调用此函数学习词向量,即word2vec模型。

    • 维度 ( dim ) :向量维度的大小,defult=100 ,也可以选100-300 。
    • 子词是包含在最小大小 ( minn ) 和最大大小 ( maxn )之间的单词中的所有子字符串。默认minn=3, maxn=6。
    • minn和maxn分别代表subwords的最小长度和最大长度
    • bucket表示可容纳的subwords和wordNgrams的数量,可以理解成是它们存放的表,与word存放的表是分开的。
    • t表示过滤高频词的阈值,像"the","a"这种高频但语义很少的词应该过滤掉。
input             # training file path (required)
model             # unsupervised fasttext model {cbow, skipgram} [skipgram]
lr                # 学习率 [0.05]
dim               # 词向量维度 [100]
ws                # 上下文窗口大小 [5]
epoch             # 训练轮数 [5]
minCount          # 最少单词词频,过滤过少的单词 [5]
minn              # min length of char ngram [3]
maxn              # max length of char ngram [6]
neg               # 负采样个数 [5]
wordNgrams        # 词ngram最大长度 [1]
loss              # loss function {ns, hs, softmax, ova}[ns]
                  #(负采样、霍夫曼树、softmax和多分类采用多个二分类计算,即loss one-vs-all) 
bucket            # number of buckets,放的是subwords [2000000]
thread            # cpu线程 [number of cpus]
lrUpdateRate      # change the rate of updates for the learning rate,实现阶梯动态学习率 [100]
t                 # sampling threshold,过滤高频词,越大被保留的概率越大 [0.0001]
verbose           # verbose [2]

train_supervised 参数:

input             # training file path (required)
lr                # 学习率 [0.05]
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               # 负采样个数 [5]
wordNgrams        # n-gram [1]
loss              # loss function {ns, hs, softmax, ova} [softmax]
bucket            # number of buckets [2000000]
thread            # cpu线程数 [number of cpus]
lrUpdateRate      # change the rate of updates for the learning rate [100]
t                 # sampling threshold [0.0001]
label             # 标签前缀 ['__label__']
verbose           # verbose [2]
pretrainedVectors # 从 (.vec file)加载预训练的词向量,用于监督训练 []

model属性

get_dimension           # 获取向量(隐藏层)的维度(大小).这等价于 `dim` 属性           
get_input_vector        # 给定一个索引,得到输入矩阵对应的向量 
get_input_matrix        # 获取模型的完整输入矩阵的副本
get_labels              # 获取字典的整个标签列表,这相当于 `labels` 属性。
get_line                # 将一行文本拆分为单词和标签
get_output_matrix       # 获取模型的完整输出矩阵的副本。
get_sentence_vector     # 给定一个字符串,获得向量表示。这个函数
                        # assumes to be given a single line of text. We split words on
                        # whitespace (space, newline, tab, vertical tab) and the control
                        # characters carriage return, formfeed and the null character.
get_subword_id          # 给定一个subword,获取字典中的词 id hashes to.
get_subwords            # 给定一个词,获取子词及其索引。
get_word_id             # 给定一个词,获取字典中的词 id
get_word_vector         # 获取训练好的词向量。
get_words               # 获取字典的整个单词列表,这相当于 `words` 属性。
is_quantized            # 模型是否已经量化过
predict                 # 给定一个字符串,得到一个标签列表和一个对应概率列表
quantize                # 量化模型,减少模型的大小和内存占用
save_model              # 保存模型
test                    # Evaluate supervised model using file given by path
test_label              # 返回每个标签的准确率和召回率。

5.2 基本使用

当 fastText 运行时,进度和预计完成时间会显示在您的屏幕上。训练完成后,model变量包含有关训练模型的信息,可用于查询:

import fasttext
model = fasttext.train_unsupervised('data/fil9')#维基百科文件
model.words

[u'the', u'of', u'one', u'zero', u'and', u'in', u'two', u'a', u'nine', u'to', u'is', ...

获得词向量:(它返回词汇表中的所有单词,按频率递减排序。)

model.get_word_vector("the")
array([-0.03087516,  0.09221972,  0.17660329,  0.17308897,  0.12863874,
        0.13912526, -0.09851588,  0.00739991,  0.37038437, -0.00845221,
        ...
       -0.21184735, -0.05048715, -0.34571868,  0.23765688,  0.23726143],
      dtype=float32)

保存模型(二进制),后续加载

 model.save_model("result/fil9.bin")
 model = fasttext.load_model("result/fil9.bin")

cobw和skipgram:

import fasttext
model = fasttext.train_unsupervised('data/fil9', "cbow")

预测结果

#读取测试集,预测模型输出
test_df=pd.read_csv('./train_set.csv',sep='\t',nrows=10000)
results=[model.predict(x)  for x in test_df['text']]
results

[(('__label__2',), array([0.99827653])),
 (('__label__11',), array([0.84706676])),
 (('__label__3',), array([0.99988556])),
 (('__label__2',), array([0.99980879])),

...
(('__label__2',), array([0.9998678])),
 (('__label__1',), array([0.87650901])),
 (('__label__3',), array([1.00001013])),
 ...]

所以输出结果是带前缀的标签和分类概率。想只得到类别,可以这样写:

result=[model.predict(x)[0][0].split('__')[-1] for x in test_df['text']]
result
['2',
 '11',
 '3',
 '2',
 '3',
 '9',
 '3',
 '10',
 '12',
 '3',
 '0',
...]

5.3 bin格式词向量转换为vec格式

参考《fasttext训练的bin格式词向量转换为vec格式词向量》

#加载的fasttext预训练词向量都是vec格式的,但fasttext无监督训练后却是bin格式,因此需要进行转换
# 以下代码为fasttext官方推荐:
# 请将以下代码保存在bin_to_vec.py文件中
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from __future__ import division, absolute_import, print_function

from fasttext import load_model
import argparse
import errno

if __name__ == "__main__":
    # 整个代码逻辑非常简单
    # 以bin格式的模型为输入参数
    # 按照vec格式进行文本写入
    # 可通过head -5 xxx.vec进行文件查看
    parser = argparse.ArgumentParser(
        description=("Print fasttext .vec file to stdout from .bin file")
    )
    parser.add_argument(
        "model",
        help="Model to use",
    )
    args = parser.parse_args()

    f = load_model(args.model)
    words = f.get_words()
    print(str(len(words)) + " " + str(f.get_dimension()))
    for w in words:
        v = f.get_word_vector(w)
        vstr = ""
        for vi in v:
            vstr += " " + str(vi)
        try:
            print(w + vstr)
        except IOError as e:
            if e.errno == errno.EPIPE:
                pass
# 打开cmd,在bin_to_vec.py路径下执行该命令,生成unsupervised_data.vec
python bin_to_vec.py word15000.bin > word15000.vec

在实践中,我们观察到 skipgram 模型在处理子词信息方面比 cbow 更好

六、新闻文本分类——fasttext

  比赛官方链接为:《零基础入门NLP - 新闻文本分类》。讨论区有《数据读取与分析》
  讨论区还有大佬张帆、惊鹊和张贤等人的代码,值得大家仔细阅读。

6.1 正常fasttext分类

单纯的fasttext分类,参数用讨论区默认参数,没有调整。分数0.9151。
fasttext训练很快,大概十来分钟吧。

import pandas as pd
train_df=pd.read_csv('./train_set.csv',sep='\t')
train_df['label_ft']='__label__'+train_df['label'].astype(str)
train_df[['text','label_ft']].to_csv('./train.csv',index=None,header=None,sep='\t')

import fasttext
model=fasttext.train_supervised('./train.csv',lr=1.0,wordNgrams=2, 
verbose=2,minCount=1,epoch=25,loss="hs")

test_df=pd.read_csv('./test_a.csv',sep='\t')
result=[model.predict(x)[0][0].split('__')[-1] for x in test_df['text']]
result[:100]

pd.DataFrame({'label':result}).to_csv('fasttext.csv',index=None)

最终上传,得分0.9151。
调整部分参数后,最终得分0.9358。

model=fasttext.train_supervised('./train.csv',lr=0.8,wordNgrams=3, 
verbose=2,minCount=1,epoch=25,loss="softmax")

6.2 小数据集:word2vec+fasttext+首尾截断

首先拿15000条数据进行试验,前10000条fasttext训练,后5000条测试,代码见讨论区:《Task4 基于深度学习的文本分类1-fastText》(其实就是上面代码改了点数据集):

  1. 试验正常fasttext效果,f1 score=0.8272
import pandas as pd
from sklearn.metrics import f1_score

# 转换为FastText需要的格式
train_df = pd.read_csv('../data/train_set.csv', sep='\t', nrows=15000)
train_df['label_ft'] = '__label__' + train_df['label'].astype(str)
train_df[['text','label_ft']].iloc[:-5000].to_csv('train.csv', index=None, header=None, sep='\t')

import fasttext
model = fasttext.train_supervised('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 train_df.iloc[-5000:]['text']]
print(f1_score(train_df['label'].values[-5000:].astype(str), val_pred, average='macro'))
  1. 试验word2vec+fasttext效果,f1 score=0.8426
#先进行word2vec训练,含全部15000条数据
train_df[['text','label_ft']].to_csv('train15000.csv', index=None, header=None, sep='\t')
model1 = fasttext.train_unsupervised('train15000.csv', lr=0.1, wordNgrams=2, 
                                  verbose=2, minCount=1, epoch=8, loss="hs")
#保存模型转为词向量
model1.save_model("word15000.bin")
#cmd命令行执行python bin_to_vec.py result1000.bin < result1000.vec,转换为vec词向量
#fasttext进行训练,词向量为前一步训练好的词向量,训练数据为10000条
model2 = fasttext.train_supervised('train.csv',pretrainedVectors='word15000.vec',lr=1.0, wordNgrams=2, 
#                                  verbose=2, minCount=1, epoch=16, loss="hs")
#预测结果
val_pred = [model2.predict(x)[0][0].split('__')[-1] for x in train_df.iloc[-5000:]['text']]
print(f1_score(train_df['label'].values[-5000:].astype(str), val_pred, average='macro'))
  1. 试验首尾截断效果,f1 score=0.8222(首尾各50词),0.8304(首尾各100词)
#首尾截断实验效果
#准备将text文本首尾截断,各取100tokens
def slipt2(x):
  ls=x.split(' ')
  le=len(ls)
  if le<201:
    return x
  else:
    return ' '.join(ls[:100]+ls[-100:])
    
trains_df['summary']=trains_df['text'].apply(lambda x:slipt2(x))
train_df[['summary','label_ft']].iloc[:-5000].to_csv('trains_summary10000.csv', index=None, header=None, sep='\t')

model3 = fasttext.train_supervised('trains_summary10000.csv',pretrainedVectors='word15000.vec',lr=1.0, wordNgrams=2, 
                                  verbose=2, minCount=1, epoch=16, loss="hs")
#预测结果
val_pred = [model3.predict(x)[0][0].split('__')[-1] for x in train_df.iloc[-5000:]['text']]
print(f1_score(train_df['label'].values[-5000:].astype(str), val_pred, average='macro'))

6.3 全数据集:word2vec+fasttext+首尾截断

  1. 数据处理
#读取训练测试集数据
import pandas as pd
from sklearn.metrics import f1_score

# 转换为FastText需要的格式
train_df = pd.read_csv('./train_set.csv', sep='\t')
train_df['label_ft'] = '__label__' + train_df['label'].astype(str)
train_df[['text','label_ft']].to_csv('train_20w.csv', index=None, header=None, sep='\t')

test_df = pd.read_csv('./test_a.csv', sep='\t')
df=pd.concat([train_df,test_df])
df[['text']].to_csv('train_25w.csv', index=None, header=None, sep='\t')
  1. 用word2vec进行train+test数据的词向量训练,这一步花了2个小时。
import fasttext

model1 = fasttext.train_unsupervised('train_25w.csv', lr=0.1, wordNgrams=2, 
                                  verbose=2, minCount=1, epoch=8, loss="hs")

model1.save_model("word_25w.bin")
#cmd下运行python bin_to_vec.py word_25w.bin > word_25w.vec
  1. fasttext进行有监督训练,相当于分类微调。最终上传,得分0.9162,吐血。
model2=fasttext.train_supervised('train_20w.csv',pretrainedVectors='word_25w.vec',lr=0.8, wordNgrams=2, verbose=2, minCount=1, epoch=18, loss="hs")

import pandas as pd
test_df = pd.read_csv('./test_a.csv', sep='\t')
test_pred = [model2.predict(x)[0][0].split('__')[-1] for x in test_df['text']]

pd.DataFrame({'label':test_pred}).to_csv('word_fast.csv',index=None)
  1. 接下来进行首尾截断测试:
#首尾截断进行训练
train_df = pd.read_csv('./train_set.csv', sep='\t')
train_df['label_ft'] = '__label__' + train_df['label'].astype(str)
train_df['summary']=train_df['text'].apply(lambda x:slipt2(x))
train_df[['summary','label_ft']].to_csv('train_summary_20w.csv', index=None, header=None, sep='\t')

model3 = fasttext.train_supervised('train_summary_20w.csv',pretrainedVectors='word_25w.vec',lr=0.8, wordNgrams=2, 
                                  verbose=2, minCount=1, epoch=18, loss="hs")
#预测结果
test_df['summary']=test_df['text'].apply(lambda x:slipt2(x))
test_pred = [model3.predict(x)[0][0].split('__')[-1] for x in test_df['summary']]
pd.DataFrame({'label':test_pred}).to_csv('word_fast_cut.csv',index=None)

最终得分0.9203,至少证明了长文本分类,数据集够多的时候,进行部分截断比较好。

数据量 fasttext word2vec+fasttext word2vec+fasttext+首尾截断
10000+5000 0.8272 0.8426 0.8304
20w+5w 0.9151(没调参) 0.9162(没调参) 0.9203(没调参)
20w+5w 0.9358(已调参) 0.9421(已调参)
截断比不截断高0.4-0.6个点。
  1. 下面是部分调参记录
    继续首尾截断试验,训练集前19w为悬链数据,最后1w为测试数据。
首尾截断 f1 loss n-gram
各30,同时epoch=18,lr=0.8,下同 0.9190 hs 2
各30 0.9352 softmax 2
各30 0.9388 softmax 3
各30 0.9382 softmax 4
各30 softmax 5
各30,同时epoch=18,lr=0.5 softmax 4
各30,同时epoch=27,lr=0.5 softmax 4
----- ----- ----- -----
各50 0.9192 hs 2
各80 0.9170 hs 2
各100 0.9200/0.9184 hs 2
各150 0.9226 hs 2
各150 0.9371 softmax 2
各150 0.9436 softmax 3
各150 0.9417 softmax 4
各200 0.9212 hs 2
不截断 0.9158 hs 2
不截断,加和平均的词向量太多,无用信息冲淡了关键信息。
fasttext分类的loss必须选择softmex,不需要hs和ng,因为类别少。
n-gram中,n增大可以表示一部分词序,有利于文本表征。但是太大的话,词向量和n-gram向量太多,分类效果也不好(参数过多学不好或者是无用信息过多)。

初步选择以下参数:

#首尾截断各150个词
model3=fasttext.train_supervised('train_summary_20w.csv',pretrainedVectors='word_25w.vec',
lr=0.8,wordNgrams=3,verbose=2,minCount=1,epoch=18,loss="softmax")

最终分数f1=0.9421。

你可能感兴趣的:(赛事,1024程序员节)