×

GloVe向量化做文本分类

96
slade_sal
2018.09.25 10:29* 字数 1729

向量化

在之前,我对向量化的方法一直局限在两个点,

第一种是常规方法的one-hot-encoding的方法,常见的比如tf-idf生成的0-1的稀疏矩阵来代表原文本:


这种方法简单暴力,直接根据文本中的单词进行one-hot-encoding,但是数据量一但大了,这个单句话的one-hot-encoding结果会异常的长,而且没办法得到词与词之间的关系。

第二种是基于神经网络的方法,常见的比如word2vec,YouTubeNet:


这种方法(这边以CBOW为例子)都是初始一个固定长度的随机向量作为每个单词的向量,制定一个目标词的向量,以上下文词向量的sum结果作为input进行前向传递,使得传递的结果和目标词向量尽可能一致,以修正初始的随机向量。
换句话说,就是刚开始,我随意定义生成一个vector代表一个词,然后通过上下文的联系去修正这个随机的vector。好处就是我们可以得到词与词之间的联系,而且单个词的表示不复杂,坏处就是需要大量的训练样本,毕竟涉及到了神经网络。

最近,我们突然发现了第三种方法,GloVe向量化。它也是开始的时候随机一个vector作为单词的表示,但是它不利用神经网络去修正,而是利用了一个自己构造的损失函数:


通过我们已有的文章内容,去是的这个损失函数最小,这就变成了一个机器学习的方法了,相比较暴力的前馈传递,这也高快速和高效的多。同时,它还兼具了word2vec最后结果里面vector方法的优点,得到词与词之间的联系,而且单个词的表示不复杂。

这边就不展开GloVe算法的细节了,后面有空和大家补充,这个算法的构造非常巧妙,值得大家借鉴一下。

文本分类

刚才开门见山的聊了蛮久向量化,看起来和文本分类没什么关系,确实在通常意义上来讲,我们的最简单最常用的方法并不是向量化的方法,比如通过朴素贝叶斯,N-Grams这些方法来做分类识别。

tfidf+N-grams

1.其实很简单,首先对语料库进行切词,维护自己的词典,做高频词的人工复审,将无意词进行stop_words归总


对公司内部信息进行了一下处理,主要看分布趋势

可以看到,高频词其实是非常非常少的,而且如果你真的去做了,你就会发现,"了"、“的”、“啊”这种语气词,和一些你公司相关的领域词汇会非常靠前,这些词作为stop_words会有效的降低训练成本、提高模型效果。

2.进行tf-idf,将词进行重赋权,字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降,有效的将向量化中的one hot encoding结果进行了修正。但是依然存在问题:在TFIDF算法中并没有体现出单词的位置信息。

# sublinear_tf:replace tf with 1 + log(tf)
# max_df:用来剔出高于词频0.5的词
# token_pattern:(?u)\b\w+\b是为了匹配出长度为1及以上的词,默认的至少需要词长度为2
# ngram_range:这边我做了3-grams处理,如果只想朴素计算的话(1,1)即可
# max_features:随着我做了各种宽松的条件,最后生成的词维度会异常大,这边限制了前3万
vectorizer = TfidfVectorizer(stop_words=stpwrdlst, sublinear_tf=True, max_df=0.5, token_pattern=r"(?u)\b\w+\b",ngram_range=(1, 3), max_features=30000)

不得不说,python处理机器学习,深度学习的便捷程度是异常的高。

3.在经过TfidfVectorizer处理之后的结果是以稀疏矩阵的形式来存的,如果想看内容的话,可以用todense()转化为matrix来看。接下来,用贝叶斯来训练刚才得到的矩阵结果就可以了。

mnb_tri = MultinomialNB(alpha=0.001)
mnb_tri.fit(tri_train_set.tdm, tri_train_set.label)

tf-idf + n-grams + naive-bayes + lr

这种方法是上面方法的升级版本,我们先看下架构:


对公司内部信息进行了一下处理,主要看算法架构

其实主要差异在于右侧的算法模型详细部分,我们做了一个由3-grams到3-grams+naive-bayes+lr的扩充,提升精度。

在模型的过程中,上面的第一步,都是一样的,在第二、三步有所差异:
2.在第二步中,我们除了要构造出一个3-grams的sparse matrix也需要构造出一个朴素的sparse matrix

# 朴素结果
vectorizerby = TfidfVectorizer(stop_words=stpwrdlst, token_pattern=r"(?u)\b\w+\b", max_df=0.5, sublinear_tf=True,ngram_range=(1, 1), max_features=100000)

3.不仅仅用bayes进行一次分类,而是根据3-grams和朴素情况下的sparse matrix进行预测,再用logistics regression来合并两个的结果做个stack进行0-1压缩。

# 构造出一个3-grams的sparse matrix也需要构造出一个朴素的sparse matrix
mnb_tri = MultinomialNB(alpha=0.001)
mnb_tri.fit(tri_train_set.tdm, tri_train_set.label)
mnb_by = MultinomialNB(alpha=0.001)
mnb_by.fit(by_train_set.tdm, by_train_set.label)
# 加bias,cv选择最优正则结果,lbfgs配合l2正则
lr = LogisticRegressionCV(multi_class="ovr", fit_intercept=True, Cs=np.logspace(-2, 2, 20), cv=2, penalty="l2",solver="lbfgs", tol=0.01)
re = lr.fit(adv_data[['f1', 'f2']], adv_data['rep_label'])

总结一下上面两种方法,我觉得是入门快,效果也不错的小练手,也是完全可以作为我们开始一个项目的时候,用来做baseline的方法,主要是快啊~/斜眼笑

GloVe+lr

因为我目前的带标签数据比较少,所以之前一直没有敢用word2vec去向量化作死,但是GloVe不存在这个问题啊,我就美滋滋的进行了一波。
首先,先讲下GloVe的使用:

  • https://github.com/stanfordnlp/GloVe 在最大的代码抄袭网站下载(git clone)坦福大佬的代码,友情提醒,不要作死自己看了理论就觉得自己会写,自己搞个GloVe。(别问我是怎么知道的)
  • cd到对应目录下,vim demo.sh这个文件
#!/bin/bash
set -e

# Makes programs, downloads sample data, trains a GloVe model, and then evaluates it.
# One optional argument can specify the language used for eval script: matlab, octave or [default] python

# 请把make这边注释掉,这个是让你去下个demo,我们直接改成自己的数据
# make
# if [ ! -e text8 ]; then
#   if hash wget 2>/dev/null; then
#     wget http://mattmahoney.net/dc/text8.zip
#   else
#     curl -O http://mattmahoney.net/dc/text8.zip
#   fi
#   unzip text8.zip
#   rm text8.zip
# fi

# CORPUS需要对应自己的欲训练的文档
CORPUS=content.txt
VOCAB_FILE=vocab.txt
COOCCURRENCE_FILE=cooccurrence.bin
COOCCURRENCE_SHUF_FILE=cooccurrence.shuf.bin
BUILDDIR=build
SAVE_FILE=vectors
VERBOSE=2
MEMORY=4.0
# 单词至少出现几次
VOCAB_MIN_COUNT=3
# 向量长度
VECTOR_SIZE=128
# 迭代次数
MAX_ITER=30
# 窗口长度
WINDOW_SIZE=15
BINARY=2
NUM_THREADS=8
X_MAX=10

echo
echo "$ $BUILDDIR/vocab_count -min-count $VOCAB_MIN_COUNT -verbose $VERBOSE < $CORPUS > $VOCAB_FILE"
$BUILDDIR/vocab_count -min-count $VOCAB_MIN_COUNT -verbose $VERBOSE < $CORPUS > $VOCAB_FILE
echo "$ $BUILDDIR/cooccur -memory $MEMORY -vocab-file $VOCAB_FILE -verbose $VERBOSE -window-size $WINDOW_SIZE < $CORPUS > $COOCCURRENCE_FILE"
$BUILDDIR/cooccur -memory $MEMORY -vocab-file $VOCAB_FILE -verbose $VERBOSE -window-size $WINDOW_SIZE < $CORPUS > $COOCCURRENCE_FILE
echo "$ $BUILDDIR/shuffle -memory $MEMORY -verbose $VERBOSE < $COOCCURRENCE_FILE > $COOCCURRENCE_SHUF_FILE"
$BUILDDIR/shuffle -memory $MEMORY -verbose $VERBOSE < $COOCCURRENCE_FILE > $COOCCURRENCE_SHUF_FILE
echo "$ $BUILDDIR/glove -save-file $SAVE_FILE -threads $NUM_THREADS -input-file $COOCCURRENCE_SHUF_FILE -x-max $X_MAX -iter $MAX_ITER -vector-size $VECTOR_SIZE -binary $BINARY -vocab-file $VOCAB_FILE -verbose $VERBOSE"
$BUILDDIR/glove -save-file $SAVE_FILE -threads $NUM_THREADS -input-file $COOCCURRENCE_SHUF_FILE -x-max $X_MAX -iter $MAX_ITER -vector-size $VECTOR_SIZE -binary $BINARY -vocab-file $VOCAB_FILE -verbose $VERBOSE
if [ "$CORPUS" = 'text8' ]; then
   if [ "$1" = 'matlab' ]; then
       matlab -nodisplay -nodesktop -nojvm -nosplash < ./eval/matlab/read_and_evaluate.m 1>&2 
   elif [ "$1" = 'octave' ]; then
       octave < ./eval/octave/read_and_evaluate_octave.m 1>&2
   else
       echo "$ python eval/python/evaluate.py"
       python eval/python/evaluate.py
   fi
fi

这边多说一下,CORPUS=content.txt这边content.txt里面的格式需要按照空格为分隔符进行存储,我之前一直以为是\t

  • 直接sh demo.sh,你会得到vectors.txt,这个里面就对应每个词的向量表示
天气 -0.754142 0.386905 -1.200074 -0.587121 0.758316 0.373824 0.342211 -1.275982 -0.300846 0.374902 -0.548544 0.595310 0.906426 0.029255 0.549932 -0.650563 -0.425185 1.689703 -1.063556 -0.790254 -1.191287 0.841529 1.080641 -0.082830 1.062107 -0.667727 0.573955 -0.604460 -0.601102 0.615299 -0.470923 0.039398 1.110345 1.071094 0.195431 -0.155259 -0.781432 0.457884 1.093532 -0.188207 -0.161646 0.246220 -0.346529 0.525458 0.617904 -0.328059 1.374414 1.020984 -0.959817 0.670894 1.091743 0.941185 0.902730 0.609815 0.752452 1.037880 -1.522382 0.085098 0.152759 -0.562690 -0.405502 0.299390 -1.143145 -0.183861 0.383053 -0.013507 0.421024 0.025664 -0.290757 -1.258696 0.913482 -0.967165 -0.131502 -0.324543 -0.385994 0.711393 1.870067 1.349140 -0.541325 -1.060084 0.078870 0.773146 0.358453 0.610744 0.407547 -0.552853 1.663435 0.120006 0.534927 0.219279 0.682160 -0.631311 1.071941 -0.340337 -0.503272 0.150010 1.347857 -1.024009 -0.181186 0.610240 -0.218312 -1.120266 -0.486539 0.264507 0.266192 0.347005 0.172728 0.613503 -0.131925 -0.727304 -0.504488 1.773406 -0.700505 -0.159963 -0.888025 -1.358476 0.540589 -0.243272 -0.236959 0.391855 -0.133703 -0.071120 1.050547 -1.087613 -0.467604 1.779341 -0.449409 0.949411
好了 1.413075 -0.226177 -2.024229 -0.192003 0.628270 -1.227394 -1.054946 -0.900683 -1.958882 -0.133343 -1.014088 -0.434961 0.026207 -0.066139 0.608682 -0.362021 0.314323 0.261955 -0.571414 1.738899 -1.013223 0.503853 -0.536511 -0.212048 0.611990 -0.627851 0.297657 -0.187690 -0.565871 -0.234922 -0.845875 -0.767733 0.032470 1.508012 -0.204894 -0.495031 -0.159262 0.181380 0.050582 -0.333469 0.454832 -2.091174 0.448453 0.940212 0.882077 -0.617093 0.616782 -0.993445 -0.385087 0.251711 0.259918 -0.222614 -0.595131 0.661472 0.194740 0.619222 -1.253610 -0.838179 0.781428 -0.396697 -0.530109 0.022801 -0.558296 -0.656034 0.842634 -0.105293 0.586823 -0.603681 -0.605727 -0.556468 0.924275 -0.299228 -1.121538 0.237787 0.498935 -0.045423 0.171536 -1.026385 -0.262225 0.390662 1.263240 0.352172 0.261121 0.915840 1.522183 -0.498536 2.046169 0.012683 -0.073264 -0.361662 0.759529 -0.713268 0.281747 -0.811104 -0.002061 -0.802508 0.520559 0.092275 -0.623098 0.199694 -0.134896 -1.390617 0.911266 -0.114067 1.274048 1.108440 -0.266002 1.066987 0.514556 0.144796 -0.606461 0.197114 0.340205 -0.400785 -0.957690 -0.327456 1.529557 -1.182615 0.431229 -0.084865 0.513266 -0.022768 -0.092925 -0.553804 -2.269741 -0.078390 1.376199 -1.163337
随意 0.410436 0.776917 -0.381131 0.969900 -0.804778 -0.785379 -0.887346 -1.463543 -1.574851 0.313285 0.685253 -0.918359 0.199073 -0.305374 -0.642721 0.098114 -0.723331 0.353159 0.042807 0.369208 -1.534930 -0.084871 0.020417 -0.384782 0.276833 -0.160028 1.107051 0.884343 -0.204381 -0.459738 -0.387128 0.125867 0.093569 1.192471 -0.473752 -0.314541 -1.029249 0.481447 1.358753 -1.688778 -0.113080 -0.401443 -0.958206 0.605638 1.083126 0.131617 0.092507 0.476506 0.801755 1.096883 -0.102036 0.461804 0.820297 -0.104053 -0.126638 0.957708 -0.722038 0.223686 0.583582 0.201246 -1.254708 0.770717 -1.271523 -0.584094 -1.142426 1.066567 0.071951 -0.182649 0.014365 -0.577141 0.037340 -0.166832 -0.247827 0.165994 1.143665 -0.258421 -0.335195 0.170218 -0.212838 0.013709 0.088847 0.663238 -0.597439 0.632847 0.370871 0.652707 0.306935 0.195127 -0.252443 0.588479 0.191633 -1.587564 0.564600 -0.306158 -0.648177 -0.488595 1.532795 -0.462473 -0.643878 1.292369 -0.051494 -1.032738 0.453587 0.411327 -0.469373 0.428398 -0.020839 0.307422 0.518331 -0.860913 -2.170098 -0.277532 -0.966210 0.615336 -0.924783 0.042679 1.289640 1.272992 1.367773 0.426600 -0.187254 -0.781009 1.331301 -0.088357 -1.113550 -0.262879 0.300137 0.437905
..
  • 有了每个词的向量,我们这边采取了借鉴YoutubeNet网络的想法:


举个例子:存在一句话"我爱中国",“我”的向量是[0.3,0.2,0.3],"爱"的向量是[0.1,0.2,0.3],“中国”的向量是[0.6,0.6,0.4],那么average后就是[0.33,0.33,0.33],然后这就类似一个特征为三的input。

这种方法的好处就是快捷,预处理的工作代价要小,随着数据量的增多,模型的效果要更加的好,这边给出一下业务数据对比:

experiment date intercepted_recall
3-grams 20180915 79.3%
3-grams 20180917 78.7%
3-grams+bayes+lr 20180915 83.4%
3-grams+bayes+lr 20180917 88.6%
gloVe+lr 20180915 93.1%
gloVe+lr 20180917 93.9%

欢迎大家关注我的个人bolg知乎,更多代码内容欢迎follow我的个人Github,如果有任何算法、代码、转行疑问都欢迎通过公众号发消息给我。

机器学习商业变现
Web note ad 1