Gensim介紹

參考:https://blog.csdn.net/duinodu/article/details/76618638

https://www.cnblogs.com/iloveai/p/gensim_tutorial.html

Gensim是一款開源的第三方Python工具包,用於從原始的非結構化的文本中,無監督地學習到文本隱層的主題向量表達。它支持包括TF-IDF,LSA,LDA,和word2vec在內的多種主題模型算法,支持流式訓練,並提供了諸如相似度計算,信息檢索等一些常用任務的API接口。

基本概念

  • 語料(Corpus):一組原始文本的集合,用於無監督地訓練文本主題的隱層結構。語料中不需要人工標註的附加信息。在Gensim中,Corpus通常是一個可迭代的對象(比如列表)。每一次迭代返回一個可用於表達文本對象的稀疏向量。
  • 向量(Vector):由一組文本特徵構成的列表。是一段文本在Gensim中的內部表達。
  • 稀疏向量(Sparse Vector):通常,我們可以略去向量中多餘的0元素。此時,向量中的每一個元素是一個(key, value)的tuple。
  • 模型(Model):是一個抽象的術語。定義了兩個向量空間的變換(即從文本的一種向量表達變換爲另一種向量表達)。

Step 1. 訓練語料的預處理

訓練語料的預處理指的是將文檔中原始的字符文本轉換成Gensim模型所能理解的稀疏向量的過程。

通常,我們要處理的原生語料是一堆文檔的集合,每一篇文檔又是一些原生字符的集合。在交給Gensim的模型訓練之前,我們需要將這些原生字符解析成Gensim能處理的稀疏向量的格式。

由於語言和應用的多樣性,Gensim沒有對預處理的接口做出任何強制性的限定。通常,我們需要先對原始的文本進行分詞、去除停用詞等操作,得到每一篇文檔的特徵列表。例如,在詞袋模型中,文檔的特徵就是其包含的word:

texts = [['human', 'interface', 'computer'],
 ['survey', 'user', 'computer', 'system', 'response', 'time'],
 ['eps', 'user', 'interface', 'system'],
 ['system', 'human', 'system', 'eps'],
 ['user', 'response', 'time'],
 ['trees'],
 ['graph', 'trees'],
 ['graph', 'minors', 'trees'],
 ['graph', 'minors', 'survey']]

其中,corpus的每一個元素對應一篇文檔。

接下來,我們可以調用Gensim提供的API建立語料特徵(此處即是word)的索引字典,並將文本特徵的原始表達轉化成詞袋模型對應的稀疏向量的表達。依然以詞袋模型爲例:

from gensim import corpora
dictionary = corpora.Dictionary(texts)
corpus = [dictionary.doc2bow(text) for text in texts]
print corpus[0] # [(0, 1), (1, 1), (2, 1)]

到這裏,訓練語料的預處理工作就完成了。我們得到了語料中每一篇文檔對應的稀疏向量(這裏是bow向量);向量的每一個元素代表了一個word在這篇文檔中出現的次數。值得注意的是,雖然詞袋模型是很多主題模型的基本假設,這裏介紹的doc2bow函數並不是將文本轉化成稀疏向量的唯一途徑。在下一小節裏我們將介紹更多的向量變換函數。

最後,出於內存優化的考慮,Gensim支持文檔的流式處理。我們需要做的,只是將上面的列表封裝成一個Python迭代器;每一次迭代都返回一個稀疏向量即可。

class MyCorpus(object):
    def __iter__(self):
        for line in open('mycorpus.txt'):
            # assume there's one document per line, tokens separated by whitespace
            yield dictionary.doc2bow(line.lower().split())

Step 2. 主題向量的變換

對文本向量的變換是Gensim的核心。通過挖掘語料中隱藏的語義結構特徵,我們最終可以變換出一個簡潔高效的文本向量。

在Gensim中,每一個向量變換的操作都對應着一個主題模型,例如上一小節提到的對應着詞袋模型的doc2bow變換。每一個模型又都是一個標準的Python對象。下面以TF-IDF模型爲例,介紹Gensim模型的一般使用方法。

首先是模型對象的初始化。通常,Gensim模型都接受一段訓練語料(注意在Gensim中,語料對應着一個稀疏向量的迭代器)作爲初始化的參數。顯然,越複雜的模型需要配置的參數越多。

from gensim import models
tfidf = models.TfidfModel(corpus)

其中,corpus是一個返回bow向量的迭代器。這兩行代碼將完成對corpus中出現的每一個特徵的IDF值的統計工作。

接下來,我們可以調用這個模型將任意一段語料(依然是bow向量的迭代器)轉化成TFIDF向量(的迭代器)。需要注意的是,這裏的bow向量必須與訓練語料的bow向量共享同一個特徵字典(即共享同一個向量空間)。

doc_bow = [(0, 1), (1, 1)]
print tfidf[doc_bow] # [(0, 0.70710678), (1, 0.70710678)]

注意,同樣是出於內存的考慮,model[corpus]方法返回的是一個迭代器。如果要多次訪問model[corpus]的返回結果,可以先講結果向量序列化到磁盤上。

我們也可以將訓練好的模型持久化到磁盤上,以便下一次使用:

tfidf.save("./model.tfidf")
tfidf = models.TfidfModel.load("./model.tfidf")

Gensim內置了多種主題模型的向量變換,包括LDA,LSI,RP,HDP等。這些模型通常以bow向量或tfidf向量的語料爲輸入,生成相應的主題向量。所有的模型都支持流式計算。關於Gensim模型更多的介紹,可以參考這裏:API Reference

Step 3. 文檔相似度的計算

在得到每一篇文檔對應的主題向量後,我們就可以計算文檔之間的相似度,進而完成如文本聚類、信息檢索之類的任務。在Gensim中,也提供了這一類任務的API接口。

以信息檢索爲例。對於一篇待檢索的query,我們的目標是從文本集合中檢索出主題相似度最高的文檔。

首先,我們需要將待檢索的query和文本放在同一個向量空間裏進行表達(以LSI向量空間爲例):

# 構造LSI模型並將待檢索的query和文本轉化爲LSI主題向量
# 轉換之前的corpus和query均是BOW向量
lsi_model = models.LsiModel(corpus, id2word=dictionary, num_topics=2)
documents = lsi_model[corpus]
query_vec = lsi_model[query]

接下來,我們用待檢索的文檔向量初始化一個相似度計算的對象:

index = similarities.MatrixSimilarity(documents)

我們也可以通過save()load()方法持久化這個相似度矩陣:

index.save('/tmp/deerwester.index')
index = similarities.MatrixSimilarity.load('/tmp/deerwester.index')

注意,如果待檢索的目標文檔過多,使用similarities.MatrixSimilarity類往往會帶來內存不夠用的問題。此時,可以改用similarities.Similarity類。二者的接口基本保持一致。

最後,我們藉助index對象計算任意一段query和所有文檔的(餘弦)相似度:

sims = index[query_vec] # return: an iterator of tuple (idx, sim)

 

Word2vec

Word2vec並不是一個模型——它其實是2013年Mikolov開源的一款用於計算詞向量的工具。關於Word2vec更多的原理性的介紹,可以參見我的另一篇博客:word2vec前世今生

在Gensim中實現word2vec模型非常簡單。首先,我們需要將原始的訓練語料轉化成一個sentence的迭代器;每一次迭代返回的sentence是一個word(utf8格式)的列表:

class MySentences(object):
    def __init__(self, dirname):
        self.dirname = dirname

    def __iter__(self):
        for fname in os.listdir(self.dirname):
            for line in open(os.path.join(self.dirname, fname)):
                yield line.split()

sentences = MySentences('/some/directory') # a memory-friendly iterator

接下來,我們用這個迭代器作爲輸入,構造一個Gensim內建的word2vec模型的對象(即將原始的one-hot向量轉化爲word2vec向量):

model = gensim.models.Word2Vec(sentences)

如此,便完成了一個word2vec模型的訓練。

我們也可以指定模型訓練的參數,例如採用的模型(Skip-gram或是CBoW);負採樣的個數;embedding向量的維度等。具體的參數列表在這裏

同樣,我們也可以通過調用save()load()方法完成word2vec模型的持久化。此外,word2vec對象也支持原始bin文件格式的讀寫。

Word2vec對象還支持online learning。我們可以將更多的訓練數據傳遞給一個已經訓練好的word2vec對象,繼續更新模型的參數:

model = gensim.models.Word2Vec.load('/tmp/mymodel')
model.train(more_sentences)

若要查看某一個word對應的word2vec向量,可以將這個word作爲索引傳遞給訓練好的模型對象:

model['computer']  # raw NumPy vector of a word

Doc2vec

Doc2vec是Mikolov在word2vec基礎上提出的另一個用於計算長文本向量的工具。它的工作原理與word2vec極爲相似——只是將長文本作爲一個特殊的token id引入訓練語料中。在Gensim中,doc2vec也是繼承於word2vec的一個子類。因此,無論是API的參數接口還是調用文本向量的方式,doc2vec與word2vec都極爲相似。

主要的區別是在對輸入數據的預處理上。Doc2vec接受一個由LabeledSentence對象組成的迭代器作爲其構造函數的輸入參數。其中,LabeledSentence是Gensim內建的一個類,它接受兩個List作爲其初始化的參數:word list和label list。

from gensim.models.doc2vec import LabeledSentence
sentence = LabeledSentence(words=[u'some', u'words', u'here'], tags=[u'SENT_1'])

類似地,可以構造一個迭代器對象,將原始的訓練數據文本轉化成LabeledSentence對象:

class LabeledLineSentence(object):
    def __init__(self, filename):
        self.filename = filename
        
    def __iter__(self):
        for uid, line in enumerate(open(filename)):
            yield LabeledSentence(words=line.split(), labels=['SENT_%s' % uid])

準備好訓練數據,模型的訓練便只是一行命令:

from gensim.models import Doc2Vec
model = Doc2Vec(dm=1, size=100, window=5, negative=5, hs=0, min_count=2, workers=4)

該代碼將同時訓練word和sentence label的語義向量。如果我們只想訓練label向量,可以傳入參數train_words=False以固定詞向量參數。更多參數的含義可以參見這裏的API文檔

注意,在目前版本的doc2vec實現中,每一個Sentence vector都是常駐內存的。因此,模型訓練所需的內存大小同訓練語料的大小正相關。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章