代码在 https://github.com/crownpku/text2vec

文本向量化,顾名思义就是将一段文字(一篇文章,一个段落或者是一个句子)变成一个向量。在尽可能不丢失原始文本信息的情况下,将文本变成可以计算的向量,可以帮助后续的文本聚类、分类、相似度匹配等等的诸多任务。

手头的一个小项目要对英文短文本进行20个标签左右的分类。标注数据量只有几千条,尝试了几种深度学习方法结果都很一般,最后发现简单用TF-IDF做特征和Linear SVM做分类器就给出了最好的结果。

对于这种标注数据量比较少的短文本分类任务,用词向量做输入接BiLSTM之类的深度学习模型就有点大炮打蚊子的感觉了。更好的Baseline方法可能是将这些短文本向量化,然后用一个简单的如Linear SVM的分类器来做分类。对于这种将批量短文本向量化的需求,我在网上找了一圈,有很多博客、Stackoverflow的讲解和介绍,却没有一个简单称手的文本向量化的工具。于是我基于spacy和gensim写了一个文本向量化的小工具,包装起来供自己的短文本分类项目直接调用使用。

这个项目的名字叫做text2vec,其中包含了一系列文本集向量化的方法,以及几种计算向量相似度和距离的方法。

安装

将项目clone下来,核心文件是text2vec.py。

git clone https://github.com/crownpku/text2vec

项目需要安装spacy和gensim这两个包,其中spacy要安装最新的2.0版本,并下载对应的英文语言模型:

python -m spacy download en

文本向量化

短文本向量化的方式,有在gensim的文档中提到的几个:

  • Term Frequency x Inverse Document Frequency, Tf-Idf

  • Latent Semantic Indexing, LSI (or sometimes LSA)

  • Random Projections, RP

  • Latent Dirichlet Allocation, LDA

  • Hierarchical Dirichlet Process, HDP

这5个都是比较传统的向量化方式,这里就不再展开了。有兴趣的读者可以去自行阅读gensim的文档或寻找相关资料研究。

除去这5个之外,我们也不能缺了用word embedding词向量来做文本向量化的方法。最简单又据称效果很好的方法,是直接把文本中出现的所有的词对应的词向量叠加做平均,作为表示该文本的向量。 利用spacy自带的glove embedding,这个任务在代码中很容易实现:

nlp  = spacy.load('en')
def _document_vector(self, doc, docs_dict, nlp):
    doc_vector = [nlp(word).vector for word in doc if word in docs_dict.token2id]
    return np.mean(doc_vector, axis=0)

而另一个方式,是利用TF-IDF作为词的权重,对文本中的词向量进行加权平均的方式来计算文本向量。这种方式会使代表每个文本特性的词汇占据更大权重,从而让加权平均的文本向量更好地代表该文本本身。

def tfidf_weighted_wv(self):
    #tf-idf
    docs_vecs   = self._get_tfidf(self.docs, self.docs_dict)

    #Load glove embedding vector for each TF-IDF term
    tfidf_emb_vecs = np.vstack([self.nlp(self.docs_dict[i]).vector for i in range(len(self.docs_dict))])

    #To get a TF-IDF weighted Glove vector summary of each document, 
    #we just need to matrix multiply docs_vecs with tfidf_emb_vecs
    docs_emb = np.dot(docs_vecs, tfidf_emb_vecs)

    return docs_emb

于是我们可以很方便使用text2vec.text2vec来计算一个代表文本集的文本列表doc_list对应的向量集。

import text2vec

t2v = text2vec.text2vec(doc_list)

# Use TFIDF
docs_tfidf = t2v.get_tfidf()

# Use Latent Semantic Indexing(LSI)
docs_lsi = t2v.get_lsi()

# Use Random Projections(RP)
docs_rp = t2v.get_rp()

# Use Latent Dirichlet Allocation(LDA)
docs_lda = t2v.get_lda()

# Use Hierarchical Dirichlet Process(HDP)
docs_hdp = t2v.get_hdp()

# Use Average of Word Embeddings
docs_avgw2v = t2v.avg_wv()

# Use Weighted Word Embeddings wrt. TFIDF
docs_emb = t2v.tfidf_weighted_wv()

得到的矩阵的每一行即是对应原先文本集中一个文本的向量。由此我们就可以用向量化的文本来做文本聚类、文本分类以及文本相似度的计算了。

文本相似度/距离

这里主要参考taki0112/Vector_Similarity,其中提供了以下几种相似度/距离的计算方式:

  • Cosine Similarity

  • Euclidean Distance

  • Triangle’s Area Similarity (TS)

  • Sector’s Area Similairity (SS)

  • TS-SS

以上几种相似度/距离的计算方式详细介绍可以参考这里.总体来讲原作者推荐使用TS-SS这种方式来计算向量之间的相似度。

text2vec项目中提供了text2vec.simical的方法,可以很方便计算向量间不同种类的相似度/距离。

我们假设要计算上面 Weighted Word Embeddings wrt. TFIDF 即t2v.tfidf_weighted_wv()方法计算出的文本集向量docs_emb中前两个文本的相似度:

# Initialize
import text2vec
sc = text2vec.simical(docs_emb[0], docs_emb[1])

# Use Cosine
simi_cos = sc.Cosine()

# Use Euclidean
simi_euc = sc.Euclidean()

# Use Triangle's Area Similarity (TS)
simi_ts = sc.Triangle()

# Use Sector's Area Similairity (SS)
simi_ss = sc.Sector()

# Use TS-SS
simi_ts_ss = sc.TS_SS()

未来工作

  • 一个显而易见的事情是把text2vec.text2vec推广到中文文本向量化。这里需要做的是将spacy的模型读取替换成中文word embedding的读取,同时要留意中文tokenize的这一步。

  • 现在的text2vec.simical是计算两个向量的相似度。未来可以在这个基础上开发一个多线程的相似度匹配的方法,方便进行聚类或者寻找最相似文档的任务,在chatbot问题匹配或是无标注数据的非监督任务中会有用处。

  • 可以包装进SVM等几个常用分类器,来对有标签的标注数据进行监督学习的文本分类。

  • 现在的方式,用户可以对text2vec.text2vec提供的向量化方法结果进行组合叠加产生新的文本向量;未来期望加入更多的更创新的短文本向量化的方法,如已经实现的词向量的TF-IDF加权平均的方法。