使用Doc2Vec模型参加Kaggle的NLP比赛,最终score达到0.97,前2%。
本文所有的代码都可以在我的github上找到。
在上一篇博文中,我们使用了TF-IDF
,准确率达到了0.95
,已经进入前100,但还不够,我们试试使用更加高大上的Doc2Vec
结合神经网络模型,其准确率能否再次提升。
word2vec
进行向量化,本文主要介绍Doc2Vec
模型的使用,并使用神经网络模型提高准确率。doc2vect
模型的语料pandas
、nltk
、gensim
、TensorFlow
库进行数据清理和分析。ROC
曲线(AUC) ,并不是硬分类,参考Wikipedia。此部分的JupyterNotebook
可参考这里。
对于拿到的电影评论数据,我们需要进行数据清理以后才能使用doc2vec
进行向量化。
本文采用pandas
库进行数据预处理,好处是可以使用apply
函数对其进行并发操作,提高效率。
NLP的数据预处理一般包括以下几个部分:
Html tag
Lemmatizer
、stemming
本项目没有纠正拼写错误(口语化词语太多)、没有去处数字(实测会提高精度)最后将训练集分开(pos
和neg
)。
此部分的JupyterNotebook
可参考这里。
Doc2Vec
模型比较复杂,相对于word2vec
模型,它可以直接得到每个文档的向量,省略了将词向量转换为段向量的过程。
由于其直接是段向量,因此考虑了词之间的顺序,具有较好的语义信息。
而传统的word2vec
模型,使用平均词向量或聚类的方式得到的段向量没有考虑词的顺序。
test.txt
:25000 条评论用于测试train-neg.txt
: 12500 条negative 评论用于训练train-pos.txt
: 12500 条positive 评论用于训练train-unsup.txt
: 50000 条未标注的评论用于训练doc2vec
模型我们需要将每句话以LabeledSentence
类的形式传入gensim
的Doc2Vec
模型中:
[['word1', 'word2', 'word3',..., 'lastword'], ['label1']]
其中,label
标签是指这一段话的唯一标签,在官方文档中这个标签可以有多个(本文只使用一个),可以用标签提取当前模型训练出来的段向量。
传统的LabeledSentence
类只能接受一个文件,而我们需要将多个文件一起进行训练,因此我们对其进行简单的封装,生成我们的LabeledLineSentence
类,再使用gensim
的TaggedDocument
函数转化为合适的格式。
其中,LabeledSentence.sentences_perm()
是为了我们之后训练时能够随机打乱段落顺序,这能有效提高训练准确率。
现在,我们只需要传入一个字典(数据路径+数据前缀)即可,请记住,数据的前缀必须要唯一,例如:
sources = {'CleanData\\test.txt': 'TEST', 'CleanData\\train-neg.txt': 'TRAIN_NEG','CleanData\\train-pos.txt': 'TRAIN_POS', 'CleanData\\train-unsup.txt': 'TRAIN_UNS'}
sentences = LabeledLineSentence(sources)
训练模型的过程比较简单,分为模型构建
、构建词汇
、模型训练
、模型序列化
,主要代码为:
self.model = Doc2Vec(min_count=40, window=15, size=400, sample=1e-4, negative=5, workers=7)
self.model.build_vocab(self.sentences.to_array())
self.model.train(self.sentences.shuffle(),total_examples=self.model.corpus_count, epochs=self.model.iter)
self.model.save(filename)
mini cout
设置为40,window
为10-20,size
为400或者更大,训练出来的模型会比较准确(具体训练参数参考代码Doc2Vec.py
)。查找给定词语中最相似的词语:
python
model.wv.most_similar('good')
[('decent', 0.7674194574356079),
('great', 0.7440007328987122),
('fine', 0.7338722944259644),
('bad', 0.7250358462333679),
('solid', 0.6936144232749939),
('nice', 0.6750378012657166),
('well', 0.6653556823730469),
('fantastic', 0.6441545486450195),
('terrible', 0.6301878690719604),
('excellent', 0.6274154186248779)]
对给定词语中最不匹配的词:
python
model.wv.doesnt_match("man woman child kitchen".split())
'kitchen'
我们也可以使用label
标签查看训练的段落向量:
python
model["TEST_24810"]
我们可以利用google提供的TensorBoard很方便的对模型进行可视化,来帮助查看我们训练的好坏。
其可视化代码可参考我的Github上Visualize.py
。
我们想观察与suck
关联度最高的词
至此,我们已经成功将文本向量化,只需要从模型中把需要的向量取出,即可进行常规的数据分析:
train_arrays = np.zeros((25000, 100))
train_labels = np.zeros(25000)
for i in range(12500):
prefix_train_pos = 'TRAIN_POS_' + str(i)
prefix_train_neg = 'TRAIN_NEG_' + str(i)
train_arrays[i] = model[prefix_train_pos]
train_arrays[12500 + i] = model[prefix_train_neg]
train_labels[i] = 1
train_labels[12500 + i] = 0
这里只显示了训练数据的向量,实际上,我们需要将所有数据取出,为了方便,可使用pickle
保存到磁盘。
本节代码见这里。
首先我们用传统的机器学习算法进行训练,使用GridSearchCV
调参,查看在训练集上的ROC
,与使用TF-IDF中的训练方式类似,不再赘述。
最后,在逻辑回归上的训练集准确率有0.92
,在SVM上准确率有0.94
,还不错。
此部分的JupyterNotebook可参考这里。
对于NLP
领域来说,最常用的神经网络模型是LSTM,因为它能很好的考虑到词向量之间的关联。
这里需要对输入向量进行修改,我们要将每个文档提取feature
,每个feature
用一个向量表示,我使用了文档中前500个词作为feature
,每个词拿到之前训练好的Doc2Vec
中提取向量,因此每个文档是(500,400)
的二维向量(400是训练的模型向量长度)。
由于矩阵过于庞大,我们需要先保存到磁盘,而传统的使用pickle
库效率很低,我们使用了numpy
自带的np.save
保存,避免内存不够用。
LSTM
具体原理可见附录,这里为了方便起见,使用Keras
提供的high level API进行训练。其中,有4层卷积层,一层LSTM,最后是全连接层。
最后训练出来,在测试集上有0.97
左右的准确率,挺不错的。
Submissions are judged on area under the ROC curve.
,所以这个题并不是要求我们硬分类,只是需要我们提供概率即可(衡量分类器的好坏)。关于ROC
的介绍和讨论参考附录。kears
没有提供ROC
的性能评价,需要我们自己实现,可以参考这里,Keras上也有简短的介绍(使用Tensorflow
的评价指标)。np.save()
性能好于pickle
。keras
框架和LSTM的使用,锻炼了调参过程。Doc2Vec
部分主要参考:Sentiment Analysis Using Doc2Vec
word2vec
可以参考kaggle上的toturial:kaggle tutorial
LSTM
可参考The Unreasonable Effectiveness of Recurrent Neural Networks
How to compute Receiving Operating Characteristic (ROC) and AUC in keras?
机器学习和统计里面的auc怎么理解?
negative sampling
TFlearn