NLP自然語言處理庫系列教程——gensim庫
Gensim是一款開源的第三方Python工具包,用於從原始的非結構化的文字中,無監督地學習到文字隱層的主題向量表達。它支援包括TF-IDF,LSA,LDA,和word2vec在內的多種主題模型演算法,支援流式訓練,並提供了諸如相似度計算,資訊檢索等一些常用任務的API介面。
- LSI
- LDA
- HDP
- DTM
- DIM
- TF-IDF
- word2vec、paragraph2vec
基本概念
- 語料(Corpus):一組原始文字的集合,用於無監督地訓練文字主題的隱層結構。語料中不需要人工標註的附加資訊。在Gensim中,Corpus通常是一個可迭代的物件(比如列表)。每一次迭代返回一個可用於表達文字物件的稀疏向量。
- 向量(Vector):由一組文字特徵構成的列表。是一段文字在Gensim中的內部表達。
- 稀疏向量(Sparse Vector):通常,我們可以略去向量中多餘的0元素。此時,向量中的每一個元素是一個(key, value)的tuple。
- 模型(Model):是一個抽象的術語。定義了兩個向量空間的變換(即從文字的一種向量表達變換為另一種向量表達)。
將文件集做成語料庫
1.我們需要從文件集中獲得詞庫:
- 0) 分詞
- 1) 去掉沒有意義的冠詞
- 2) 去低頻詞: 去掉只在出現過一次的單詞(避免我們的語料庫矩陣太稀疏了)
- 3) 將剩下的詞做成詞庫
2.根據詞庫處理文件集,轉化為語料庫
#文件集
documents = ["Human machine interface for lab abc computer applications",
"A survey of user opinion of computer system response time",
"The EPS user interface management system",
"System and human system engineering testing of EPS",
"Relation of user perceived response time to error measurement" ,
"The generation of random binary unordered trees",
"The intersection graph of paths in trees",
"Graph minors IV Widths of trees and well quasi ordering",
"Graph minors A survey"]
#去冠詞,連線詞等
stoplist = set('for a of the and to in'.split())
texts = [[word for word in document.lower().split() if word not in stoplist]
for document in documents]
#統計詞頻
from collections import defaultdict
frequency = defaultdict(int)
for text in texts:
for token in text:
frequency[token] += 1
#去掉低頻詞
texts = [[token for token in text if frequency[token] > 1]
for text in texts]
from pprint import pprint
pprint(texts)
#獲取詞庫dictionary
from gensim import corpora
dictionary = corpora.Dictionary(texts)
dictionary.save('/tmp/deerwester.dict')
print(dictionary)
print(dictionary.token2id)
#將文件轉為語料庫corpus
corpus = [dictionary.doc2bow(text) for text in texts]
print(corpus)
訓練語料的預處理
訓練語料的預處理指的是將文件中原始的字元文字轉換成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) # 綜合獲取所有單詞,作為綜合特徵(語料庫)
print(dictionary)
corpus = [dictionary.doc2bow(text) for text in texts] # 利用語料特徵,為每一句話,建立稀疏向量(這裡是bow向量)。對應特徵(單詞)出現的次數
print(corpus)
到這裡,訓練語料的預處理工作就完成了。我們得到了語料中每一篇文件對應的稀疏向量(這裡是bow向量);向量的每一個元素代表了一個word在這篇文件中出現的次數。值得注意的是,雖然詞袋模型是很多主題模型的基本假設,這裡介紹的doc2bow函式並不是將文字轉化成稀疏向量的唯一途徑。在下一小節裡我們將介紹更多的向量變換函式。
最後,出於記憶體優化的考慮,Gensim支援文件的流式處理。我們需要做的,只是將上面的列表封裝成一個Python迭代器;每一次迭代都返回一個稀疏向量即可。
# 流式處理,記憶體優化
class MyCorpus(object):
def __iter__(self):
for line in open('mycorpus.txt'):
# 假設文件中每行是一個句子,使用空格分割
yield dictionary.doc2bow(line.lower().split(' '))
主題向量的變換
對文字向量的變換是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)] # 表示語料庫中的第0個單詞出現1次,第1個單詞出現1次
print(tfidf[doc_bow]) # 將語料轉化為TFIDF向量
注意,同樣是出於記憶體的考慮,model[corpus]方法返回的是一個迭代器。如果要多次訪問model[corpus]的返回結果,可以先講結果向量序列化到磁碟上。
我們也可以將訓練好的模型持久化到磁碟上,以便下一次使用:
tfidf.save("./model.tfidf") # 儲存模型
tfidf = models.TfidfModel.load("./model.tfidf") # 載入模型
Gensim內建了多種主題模型的向量變換,包括LDA,LSI,RP,HDP等。這些模型通常以bow向量或tfidf向量的語料為輸入,生成相應的主題向量。所有的模型都支援流式計算。關於Gensim模型更多的介紹,可以參考這裡:API Reference
文件相似度的計算
在得到每一篇文件對應的主題向量後,我們就可以計算文件之間的相似度,進而完成如文字聚類、資訊檢索之類的任務。在Gensim中,也提供了這一類任務的API介面。
以資訊檢索為例。對於一篇待檢索的query,我們的目標是從文字集合中檢索出主題相似度最高的文件。
首先,我們需要將待檢索的query和文字放在同一個向量空間裡進行表達(以LSI向量空間為例):
from gensim import similarities
# 構造LSI模型並將待檢索的query和文字轉化為LSI主題向量
# 轉換之前的corpus和query均是BOW向量
lsi_model = models.LsiModel(corpus, id2word=dictionary, num_topics=2)
documents = lsi_model[corpus]
query = dictionary.doc2bow(['search','word'])
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
在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
計算兩個詞之間的餘弦距離
y2=model.similarity(u"好", u"還行") #計算兩個詞之間的餘弦距離
print(y2)
計算與某個詞最相近的詞
for i in model.most_similar(u"滋潤"): #計算餘弦距離最接近“滋潤”的10個詞
print(i[0],i[1])
儲存模型、載入模型
# 儲存模型,以便重用
model.save("書.model")
# 對應的載入方式
# model_2 =word2vec.Word2Vec.load("text8.model")
odel = word2vec.Word2Vec.load_word2vec_format('/tmp/vectors.txt', binary=False) # 載入 .txt檔案
# using gzipped/bz2 input works too, no need to unzip:
model = word2vec.Word2Vec.load_word2vec_format('/tmp/vectors.bin.gz', binary=True) # 載入 .bin檔案
增量訓練
不能對C生成的模型進行再訓練.
# 增量訓練
model = gensim.models.Word2Vec.load(temp_path)
more_sentences = [['Advanced', 'users', 'can', 'load', 'a', 'model', 'and', 'continue', 'training', 'it', 'with', 'more', 'sentences']]
model.build_vocab(more_sentences, update=True)
model.train(more_sentences, total_examples=model.corpus_count, epochs=model.iter)