Last Updated on 2021-06-08 by Clay
Gensim 介紹
在自然語言處理 (NLP) 的任務中,純文字的資料型態本身是相當難以進行處理的,尤其是在機器學習的任務當中。
試想:今天我們輸入圖像進行分類的訓練,我們可以使用『像素值』代表圖片的特徵進入模型當中進行訓練。但是文字呢?
文字型態的資料,是沒有辦法如同數值資料一樣進行 Forward Propagation 以及 Backward Propagation 的、是沒有辦法微分的,簡單來講,是沒辦法進行運算並且訓練權重網路的。
為了要解決這個問題,我們需要將『文字』轉換成『數值』。
最簡單的方法,就像我在《[PyTorch] 旅館評論情感分析實戰紀錄 (0)》這篇文章中所做的一樣,將每個『相異字』(Character) 轉換成一個特定的數字。這樣一來,我們便可以將機器學習相關的技術應用在文字領域了。
不過,只是單純地轉換 Character,有時是得不到較好的結果的。對中文而言,有時使用『詞』作為句子裡的基本元件單位會更恰當;另外,只是轉成一個『數字』也很難表現出中文詞彙的多樣性,轉換成『向量』通常效果會更好一點。
Word2Vec 就是這樣的一個工具,其最早是為 Google 開發的一個工具;而今天本文的主角 Gensim 則是它的 Python 實現 (不過只有最高層是 Python、內部還是封裝了 Word2Vec 的 C 接口)。
以下就來簡單地介紹該如何使用 Gensim 這項工具來完成將『文字轉換成向量』這項工作吧!
安裝 Gensim
如果你的電腦裡還沒有 Gensim,可以透過以下指令下載:
pip3 install gensim
下載 Wiki 語料
這裡以 Wiki 上的中文資料為訓練語料,Wiki 上的資料可是相當優秀的。不僅量多、而且詞彙涵蓋範圍廣,可以應用在許多不同的任務上。
可以從這個網址找到下載處:https://zh.wikipedia.org/wiki/Wikipedia:%E6%95%B0%E6%8D%AE%E5%BA%93%E4%B8%8B%E8%BD%BD
進入之後可以直接選擇一個日期,那是不同時間點的資料備份。
比如說我選擇了 20200101 這個時間點的資料,那麼,我要下載的資料則為:zhwiki-20200101-pages-articles-multistream.xml.bz2
要注意的是,你的日期可能與我不同,不過同樣都是這份檔案。
下載之後不用解壓縮,可以使用 Gensim 裡的工具直接提取文章。
WikiCorpus
WikiCorpus 是 Gensim 裡面的一個函式,可以幫助我們從 Wiki 的資料中提取乾淨的文章。
程式碼如下:
from gensim.corpora import WikiCorpus wiki_corpus = WikiCorpus('zhwiki-20200101-pages-articles-multistream.xml.bz2', dictionary={}) text_num = 0 with open('wiki_text.txt', 'w', encoding='utf-8') as f: for text in wiki_corpus.get_texts(): f.write(' '.join(text)+'\n') text_num += 1 if text_num % 10000 == 0: print('{} articles processed.'.format(text_num)) print('{} articles processed.'.format(text_num))
這裡我將乾淨的文本儲存成 wiki_text.txt 這份檔案,檔案的內容應如下:
歐幾里得 西元前三世紀的古希臘數學家 現在被認為是幾何之父 此畫為拉斐爾的作品 雅典學院 数学 是利用符号语言研究數量 结构 变化以及空间等概念的一門学科 ...
我將原本應是標點符號的地方以空白隔開,畢竟這可能會影響到『斷詞結果』。
斷詞
『斷詞』這項工作指的是文本分成以『詞』為單位的句子。斷詞的工具有很多種,基本上可以參考我之前寫過的:
如果想要對繁體中文斷詞效果最好,首推 CKIP;如果是要斷詞速度的話,那非 Jieba 莫屬。在這裡以 Jieba 示範。
(註:Wiki 的資料當中許多地方是簡繁混雜的,可以考慮使用 OpenCC 這項工具來轉換。詳細的作法可以參閱我之前撰寫過的《中文繁簡轉換的便利工具 —— OpenCC》)
# coding: utf-8 import jieba from opencc import OpenCC # Initial cc = OpenCC('s2t') # Tokenize with open('wiki_text_seg.txt', 'w', encoding='utf-8') as new_f: with open('wiki_text.txt', 'r', encoding='utf-8') as f: for times, data in enumerate(f, 1): print('data num:', times) data = cc.convert(data) data = jieba.cut(data) data = [word for word in data if word != ' '] data = ' '.join(data) new_f.write(data)
這裡我使用 OpenCC 將所有簡體轉換成繁體、再使用 Jieba 進行斷詞。
處理好之後的文本 seg.txt 應如下 (為了展示斷詞效果,我將詞彙斷開的地方使用換行示意):
歐幾
裏
得
西元前
三世
紀的
古希臘
數學家
現在
被
認為
是
幾何
之父
此畫
為拉斐爾
的
作品
雅典
學院
數學
是
利用
...
最後,就只需要使用 Gensim 訓練 Word2Vec 模型即可。
使用 Gensim 訓練模型
from gensim.models import word2vec # Settings seed = 666 sg = 0 window_size = 10 vector_size = 100 min_count = 1 workers = 8 epochs = 5 batch_words = 10000 train_data = word2vec.LineSentence('wiki_text_seg.txt') model = word2vec.Word2Vec( train_data, min_count=min_count, vector_size=vector_size, workers=workers, epochs=epochs, window=window_size, sg=sg, seed=seed, batch_words=batch_words ) model.save('word2vec.model')
這裡要解釋一下 Settings 的參數(2021/05/31 更新:下方網友有留言告知新版 Gensim 似乎有調整過參數名稱):
- seed: 亂數種子
- sg: Word2Vec 有兩種算法,CBOW 以及 Skip-gram,這裡選擇了訓練比較快的 CBOW
- window_size: 周圍詞彙要看多少範圍
- vector_size: 轉成向量的維度
- min_count: 詞頻少於 min_count 之詞彙不會參與訓練
- workers: 訓練的並行數量
- epochs: 訓練的迭代次數
- batch_words:每次給予多少詞彙量訓練
訓練完之後,我將模型儲存為 "word2vec.model"。
測試模型效果
from gensim.models import word2vec model = word2vec.Word2Vec.load('word2vec.model') print(model.wv['生物'].shape) for item in model.wv.most_similar('生物'): print(item)
Output:
(100,)
('微生物', 0.7986701726913452)
('昆蟲', 0.7413981556892395)
('藻類', 0.7325523495674133)
('人類', 0.7236852049827576)
('動物', 0.7195628881454468)
('海洋生物', 0.7128344774246216)
('細菌', 0.7099639177322388)
('爬蟲', 0.7015881538391113)
('動物學', 0.6957031488418579)
('甲藻', 0.6929513216018677)
首先,我們將剛才訓練好的模型讀入。然後我們隨便選了個詞『生物』。
我們可以看到詞彙的維度的確是 100 維,跟我們剛才訓練時設定的一致。
然後我們可以使用 most_similar 看到這個向量最相近的詞彙 (基本上是將兩向量進行 Cosine Similarity 計算) —— 這是在只使用『數字』代表一個詞彙時所做不到的事情。
有了將文字轉向量之後,大部分機器學習的模型都能跑了,非常方便。
遇到『未知詞』的處理方法
我們所訓練的模型自然不可能涵蓋這個世界上所有的詞彙;也就是說,我們需要考慮在碰到不存在模型詞表(corpus)中的詞彙時,應該如何去處理未知詞。
目前就我所熟悉的處理方式可以分為兩類:
- 未知詞使用『模型平均向量』填充
- 未知詞使用『全零向量』填充
『模型平均向量』我們可以事先計算好、而『全零向量』也可先設置好。接著,再使用 try-except 針對『詞彙』不在『詞表』中的情況進行錯誤處理。
比方說像以下的範例程式碼:
# coding: utf-8 import numpy as np from gensim.models import word2vec # Load model print("loading...") model = word2vec.Word2Vec.load("word2vec.model") # Zero padding padding = np.zeros((model.vector_size,), dtype=np.float32) # Error process try: print(model.wv["哥吉拉大怪獸"].shape) except: print("OOV") print(padding.shape)
這樣一來就能在詞彙不在詞表中的情況,迅速指定使用全零向量來當作未知詞的向量。
Pingback: [PyTorch] 旅館評論情感分析實戰紀錄 (1) - Clay-Technology World
作者您好,我在練習這個範例時,到了最後測試模型效果,輸入文字時,他跳出
Gensim: KeyError: “word not in vocabulary”
請問是因為”train_data = word2vec.LineSentence(‘seg.txt’)”這裡我們讀檔的type所導致的嗎?
您好,請問是輸入什麼樣的詞彙呢?
因為這個模型是使用 Wiki 中文資料訓練而成,並使用 Jieba 斷詞,故可能有些詞彙並沒有在模型中。
所以,如果有些詞彙無法轉換是很正常的,可以換個詞彙試試看輸出的向量。
另外,在實際任務中,的確可能碰到這種所謂的『未知詞』,通常是取全零向量或是模型的平均向量來填充。
希望有回答到您的問題。
我輸入的詞彙跟作者您一樣都是”生物”,
我的code都跟您的一樣,但到最後測試時就跟我上面說的一樣,出現那個error。
哈哈,暫時沒有想到是發生了什麼樣的問題。
或許晚點我來重跑看看這篇的程式。
也許您可以使用
for word in model.wv.vocab: print(word)
來查看模型的詞表,看看模型裡到底有哪些詞彙。好的,我去跑跑看,謝謝您!!
作者你好,想請問如果遇到KeyError: “word ‘新電腦’ not in vocabulary”時,若要將這個”新電腦”取成零向量的話
實際上要如何實作呢?
傍晚我重新跑了一次程式,發現了一個可能存在的問題:
我當初是使用記憶體非常大的伺服器下去跑,可能在『斷詞』那個小節的程式有些問題(使用 Jieba 套件斷詞的程式),可能會導致記憶體溢位,不知道是否導致您的 seg.txt 這份檔案有點問題呢?
我修改了程式碼,可能要麻煩您再試試看重新執行從斷詞那小節開始的程式碼。
造成您的困擾,十分抱歉。
不好意思過了兩天才回覆,
我後來去看了我模型裡面的字,發現真的怪怪的,後來去看set.txt的內容,發現真的有點問題!!
好的我會再去重新跑斷詞那邊看看,應該就沒有問題了!
感謝作者回覆!
作者你好,想請問遇到未知詞時要取零向量的話,是要如何實做呢?
遇到的問題為KeyError: “word ‘新電腦’ not in vocabulary”,想要把”新電腦”這個詞設為零向量
你好,感謝詢問。
目前已經將相關的處理方法,新增在本文的最下方。
希望能提供一些參考,謝謝。
謝謝作者的回覆,這個方法有解決到我的問題!
哈哈,有幫上忙就好。
Gensim更新了很參數,
model = word2vec.Word2Vec(
train_data,
min_count=min_count,
vector_size=vector_size,
workers=workers,
epochs=epochs,
window=window_size,
sg=sg,
seed=seed,
batch_words=batch_words
)
size=>vector_size
iter=>epochs
如果有不能跑得可以更改這邊看看
作者你好 請問上面的seg.text和wiki_text_seg.txt 是同一個檔案嗎
我跑了件模型的程式之後
點了模型後出現
Error! c:\Users\Yvonne\word2vec.model is not UTF-8 encoded
Saving disabled.
See Console for more details
後來跑了尋找”生物”相關的字出現這個錯誤
TypeError Traceback (most recent call last)
in
2
3 model = word2vec.Word2Vec.load(‘word2vec.model’)
—-> 4 print(model[‘生物’].shape)
5
6 for item in model.most_similar(‘生物’):
TypeError: ‘Word2Vec’ object is not subscriptable
可以問說哪邊出了問題嗎? 謝謝
你好,謝謝你的提醒。
下方讀取 seg.txt 文件訓練模型的程式,實際上應該是要使用 wiki_text_seg.txt 這份文件才對。
過去曾經改寫過一次本篇文章,前後的程式碼沒有對應起來,實在抱歉。
另外,”TypeError: ‘Word2Vec’ object is not subscriptable” 這個錯誤似乎是由於 Word2Vec 物件(也就是我們訓練的 W2V 模型)沒有可索引的物件,可是我們還是嘗試使用 Word2Vec[i] 的方式呼叫的緣故。
就如同上圖般這樣的報錯。
參考自:https://stackoverflow.com/questions/61136557/getting-error-on-averaging-word2vec-crerated-vectors
我會猜有兩種可能:
1. 模型沒有訓練起來(不過這樣在讀取模型時沒有報錯很奇怪)
2. Gensim 改動了讀取模型詞向量的方法(我很久沒用 Gensim 了,有些不確定)
抱歉無法完全解答你的問題,我有空會再用最新版本的 Gensim 跑一遍我這篇文章的程式碼,檢查看看是否哪裡有問題。
好 謝謝作者
抱歉一直到今天才有時間做個完整的測試。
實際跑過之後,發現現在 Gensim 訓練的 Word2Vec 模型提取向量不能直接使用詞彙當作索引,需要使用 model.wv[“xxx”] 這樣的形式來取得 xxx 的向量。
文章中的程式碼已經更新,希望能順利執行。
再次謝謝你的留言,若不是你的留言,我還沒注意到舊的程式碼已經無法跑新版的 Gensim。
不會
我之前有試著跑出來了 謝謝作者
哈哈 能順利執行就好
Pingback: 2023/6/5 每日筆記 - Arfiligol Blog & CV