1. 程式人生 > >Gensim官方教程翻譯(二)——語料庫與向量空間(Corpora and Vector Spaces)

Gensim官方教程翻譯(二)——語料庫與向量空間(Corpora and Vector Spaces)

====================正==========文====================

如果你想記錄日誌,請不要忘記設定:

>>> import logging
>>> logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

從字串到向量

這次,讓我們從用字串表示的文件開始

>>> from gensim import corpora, models, similarities
>>>
>>> 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"]

這是一個由9篇文件組成的微型語料庫,每個文件僅有一個句子組成。

(記號化 or tokenize)

首先,讓我們對這些文件進行記號化(tokenize,或稱標記化等)處理,遮蔽常用詞(利用停用詞表)和整個語料庫中僅僅出現一次的詞:

>>> # 去除停用詞並分詞
>>> # 譯者注:這裡只是例子,實際上還有其他停用詞
>>> #         處理中文時,請藉助 Py結巴分詞 https://github.com/fxsjy/jieba
>>> 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   # pretty-printer
>>> pprint(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']]

在這裡我僅利用空格切分字串來記號化,並將它們都轉成小寫;而你處理文件的方式很可能會有所不同。實際上,我是在用這個特殊(簡單且效率低)的設定來模仿Deerwester等人的原始LSA文章中的實驗。[1]

(總結屬性字典)

處理文件的方法應該視應用情形、語言而定,因此我決定不對處理方法做任何的限制。取而代之的是,一個文件必須由其中提取出來的屬性表示,而不僅僅是其字面形式;如何提取這些屬性由你來決定(可以是單詞、文件長度數量等)。接下來,我將描述一個通用的、常規目的的方法(稱為詞袋),但是請記住不同應用領域應使用不同的屬性。如若不然,渣進滓出(garbage in, garbage out)。

為了將文件轉換為向量,我們將會用一種稱為詞袋的文件表示方法。這種表示方法,每個文件由一個向量表示,該向量的每個元素都代表這樣一個問-答對:

“‘系統’這個單詞出現了多少次?1次。”

我們最好用這些問題的(整數)編號來代替這些問題。問題與編號之間的對映,我們稱其為字典(Dictionary)。

>>> dictionary = corpora.Dictionary(texts)
>>> dictionary.save('/tmp/deerwester.dict') # 把字典儲存起來,方便以後使用
>>> print(dictionary)
Dictionary(12 unique tokens)

上面這些步驟,我們利用gensim.corpora.dictionary.Dictionary類為每個出現在語料庫中的單詞分配了一個獨一無二的整數編號。這個操作收集了單詞計數及其他相關的統計資訊。在結尾,我們看到語料庫中有12個不同的單詞,這表明每個文件將會用12個數字表示(即12維向量)。如果想要檢視單詞與編號之間的對映關係:

>>> print(dictionary.token2id)
{'minors': 11, 'graph': 10, 'system': 5, 'trees': 9, 'eps': 8, 'computer': 0,
'survey': 4, 'user': 7, 'human': 1, 'time': 6, 'interface': 2, 'response': 3}

(產生稀疏文件向量)

為了真正將記號化的文件轉換為向量,需要:

>>> new_doc = "Human computer interaction"
>>> new_vec = dictionary.doc2bow(new_doc.lower().split())
>>> print(new_vec) # "interaction"沒有在dictionary中出現,因此忽略
[(0, 1), (1, 1)]

函式doc2bow()簡單地對每個不同單詞的出現次數進行了計數,並將單詞轉換為其編號,然後以稀疏向量的形式返回結果。因此,稀疏向量[(0, 1), (1, 1)]表示:在“Human computer interaction”中“computer”(id 0) 和“human”(id 1)各出現一次;其他10個dictionary中的單詞沒有出現過(隱含的)。

>>> corpus = [dictionary.doc2bow(text) for text in texts]
>>> corpora.MmCorpus.serialize('/tmp/deerwester.mm', corpus) # 存入硬碟,以備後需
>>> print(corpus)
[(0, 1), (1, 1), (2, 1)]
[(0, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1)]
[(2, 1), (5, 1), (7, 1), (8, 1)]
[(1, 1), (5, 2), (8, 1)]
[(3, 1), (6, 1), (7, 1)]
[(9, 1)]
[(9, 1), (10, 1)]
[(9, 1), (10, 1), (11, 1)]
[(4, 1), (10, 1), (11, 1)]

(通過上面的操作,我們看到了這次我們得到的語料庫。)到現在為止,我們應該明確,上面的輸出表明:對於前六個文件來說,編號為10的屬性值為0表示問題“文件中‘graph’出現了幾次”的答案是“0”;而其他文件的答案是1。事實上,我們得到了《快速入門》中的示例語料庫。

語料庫流——一次一個文件

需要注意的是,上面的語料庫整個作為一個Python List存在了記憶體中。在這個簡單的例子中,這當然無關緊要。但是我們因該清楚,假設我們有一個百萬數量級文件的語料庫,我們不可能將整個語料庫全部存入記憶體。假設這些文件存在一個硬碟上的檔案中,每行一篇文件。Gemsim僅要求一個語料庫可以每次返回一個文件向量:
>>> 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())
請在這裡下載示例檔案mycorpus.txt。 這裡假設的在一個單獨的檔案中每個文件佔一行不是十分重要;你可以改造 __iter__ 函式來適應你的輸入格式,無論你的輸入格式是什麼樣的,例如遍歷資料夾、解析XML、訪問網路等等。你僅需在每個文件中解析出一個由記號(tokens)組成的乾淨列表,然後利用dictionary將這些符號轉換為其id,最後在__iter__函式中產生一個稀疏向量即可。
>>> corpus_memory_friendly = MyCorpus() # 沒有將整個語料庫載入記憶體
>>> print(corpus_memory_friendly)
<__main__.MyCorpus object at 0x10d5690>
現在的語料庫是一個物件。我們沒有定義任何列印它的方法,所以僅能列印該物件在記憶體中的地址,對我們沒什麼幫助。為了查看向量的組成,讓我們通過迭代的方式取出語料庫中的每個文件向量(一次一個)並列印:
>>> for vector in corpus_memory_friendly: # 一次讀入記憶體一個向量
...     print(vector)
[(0, 1), (1, 1), (2, 1)]
[(0, 1), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1)]
[(2, 1), (5, 1), (7, 1), (8, 1)]
[(1, 1), (5, 2), (8, 1)]
[(3, 1), (6, 1), (7, 1)]
[(9, 1)]
[(9, 1), (10, 1)]
[(9, 1), (10, 1), (11, 1)]
[(4, 1), (10, 1), (11, 1)]
雖然輸出與普通的Python List一樣,但是現在的語料庫對記憶體更加友好,因為一次最多隻有一個向量寄存於記憶體中。你的語料庫現在可以想多大就多大啦!
相似的,為了構造dictionary我們也不必將全部文件讀入記憶體:
>>> # 收集所有符號的統計資訊
>>> dictionary = corpora.Dictionary(line.lower().split() for line in open('mycorpus.txt'))
>>> # 收集停用詞和僅出現一次的詞的id
>>> stop_ids = [dictionary.token2id[stopword] for stopword in stoplist
>>>             if stopword in dictionary.token2id]
>>> once_ids = [tokenid for tokenid, docfreq in dictionary.dfs.iteritems() if docfreq == 1]
>>> dictionary.filter_tokens(stop_ids + once_ids) # 刪除停用詞和僅出現一次的詞
>>> dictionary.compactify() # 消除id序列在刪除詞後產生的不連續的缺口
>>> print(dictionary)
Dictionary(12 unique tokens)
這就是你需要為他準備的所有,至少從詞袋模型的角度考慮是這樣的。當然,我們用該語料庫做什麼事另外一個問題,我們並不清楚計算不同單詞的詞頻是否真的有用。事實證明,它確實也沒有什麼用,我們將需要首先對這種簡單的表示方法進行一個轉換,才能計算出一些有意義的文件及文件相似性。
轉換的內容將會在下個教程講解,在這之前,讓我們暫時將注意力集中到語料庫持久上來。

各種語料庫格式

(儲存語料庫)

我們有幾種檔案格式來序列化一個向量空間語料庫(~向量序列),並存到硬碟上。Gemsim通過之前提到的語料庫流介面實現了這些方法,用一個惰性方式來將文件從硬碟中讀出(或寫入)。一次一個文件,不會將整個語料庫讀入主記憶體。
所有的語料庫格式中,一種非常出名的檔案格式就是 Market Matrix格式。想要將語料庫儲存為這種格式:

>>> from gensim import corpora
>>> # 建立一個玩具級的語料庫
>>> corpus = [[(1, 0.5)], []]  # 讓一個文件為空,作為它的heck
>>>
>>> corpora.MmCorpus.serialize('/tmp/corpus.mm', corpus)
>>> corpora.SvmLightCorpus.serialize('/tmp/corpus.svmlight', corpus)
>>> corpora.BleiCorpus.serialize('/tmp/corpus.lda-c', corpus)
>>> corpora.LowCorpus.serialize('/tmp/corpus.low', corpus)

(載入語料庫)

相反地,從一個Matrix Market檔案載入語料庫:

>>> corpus = corpora.MmCorpus('/tmp/corpus.mm')

語料庫物件是流式的,因此你不能直接將其打印出來

>>> print(corpus)
MmCorpus(2 documents, 2 features, 1 non-zero entries)

如果你真的特別想看看語料庫的內容,也不是沒有辦法:

>>> # 將語料庫全部匯入記憶體的方法
>>> print(list(corpus)) # 呼叫list()將會把所有的序列轉換為普通Python List
[[(1, 0.5)], []]

或者

>>> # 另一種利用流介面,一次只打印一個文件
>>> for doc in corpus:
...     print(doc)
[(1, 0.5)]
[]

第二種方法顯然更加記憶體友好,但是如果只是為了測試與開發,沒有什麼比呼叫list()更簡單了。(*^_^*)

(轉存語料庫)

想將這個 Matrix Market格式的語料庫存為Blei’s LDA-C格式,你只需:

>>> corpora.BleiCorpus.serialize('/tmp/corpus.lda-c', corpus)

這種方式,gensim可以被用作一個記憶體節約型的I/O格式轉換器:你只要用一種檔案格式流載入語料庫,然後直接儲存成其他格式就好了。增加一種新的格式簡直是太容易了,請參照我們為SVMlight語料庫設計的程式碼

與NumPy和SciPy的相容性

Gensim包含了許多高效的工具函式來幫你實現語料庫與numpy矩陣之間互相轉換:

>>> corpus = gensim.matutils.Dense2Corpus(numpy_matrix)
>>> numpy_matrix = gensim.matutils.corpus2dense(corpus, num_terms=number_of_corpus_features)

以及語料庫與scipy稀疏矩陣之間的轉換:

>>> corpus = gensim.matutils.Sparse2Corpus(scipy_sparse_matrix)
>>> scipy_csc_matrix = gensim.matutils.corpus2csc(corpus)

想要更全面的參考(例如,壓縮詞典的大小、優化語料庫與NumPy/SciPy陣列的轉換),參見API文件,或者繼續閱讀下一篇《主題與轉換》教程。


==================================================