Skip to content

[Python] 使用 Gensim 套件將文字轉成向量(Word2Vec)

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)


這樣一來就能在詞彙不在詞表中的情況,迅速指定使用全零向量來當作未知詞的向量。

21 thoughts on “[Python] 使用 Gensim 套件將文字轉成向量(Word2Vec)”

  1. Pingback: [PyTorch] 旅館評論情感分析實戰紀錄 (1) - Clay-Technology World

  2. 作者您好,我在練習這個範例時,到了最後測試模型效果,輸入文字時,他跳出
    Gensim: KeyError: “word not in vocabulary”
    請問是因為”train_data = word2vec.LineSentence(‘seg.txt’)”這裡我們讀檔的type所導致的嗎?

  3. 您好,請問是輸入什麼樣的詞彙呢?
    因為這個模型是使用 Wiki 中文資料訓練而成,並使用 Jieba 斷詞,故可能有些詞彙並沒有在模型中。
    所以,如果有些詞彙無法轉換是很正常的,可以換個詞彙試試看輸出的向量。
    另外,在實際任務中,的確可能碰到這種所謂的『未知詞』,通常是取全零向量或是模型的平均向量來填充。

    希望有回答到您的問題。

    1. 我輸入的詞彙跟作者您一樣都是”生物”,
      我的code都跟您的一樣,但到最後測試時就跟我上面說的一樣,出現那個error。

      1. 哈哈,暫時沒有想到是發生了什麼樣的問題。
        或許晚點我來重跑看看這篇的程式。

        也許您可以使用 for word in model.wv.vocab: print(word) 來查看模型的詞表,看看模型裡到底有哪些詞彙。

    2. 作者你好,想請問如果遇到KeyError: “word ‘新電腦’ not in vocabulary”時,若要將這個”新電腦”取成零向量的話
      實際上要如何實作呢?

  4. 傍晚我重新跑了一次程式,發現了一個可能存在的問題:
    我當初是使用記憶體非常大的伺服器下去跑,可能在『斷詞』那個小節的程式有些問題(使用 Jieba 套件斷詞的程式),可能會導致記憶體溢位,不知道是否導致您的 seg.txt 這份檔案有點問題呢?

    我修改了程式碼,可能要麻煩您再試試看重新執行從斷詞那小節開始的程式碼。
    造成您的困擾,十分抱歉。

    1. 不好意思過了兩天才回覆,
      我後來去看了我模型裡面的字,發現真的怪怪的,後來去看set.txt的內容,發現真的有點問題!!
      好的我會再去重新跑斷詞那邊看看,應該就沒有問題了!
      感謝作者回覆!

  5. 作者你好,想請問遇到未知詞時要取零向量的話,是要如何實做呢?
    遇到的問題為KeyError: “word ‘新電腦’ not in vocabulary”,想要把”新電腦”這個詞設為零向量

  6. 你好,感謝詢問。
    目前已經將相關的處理方法,新增在本文的最下方。
    希望能提供一些參考,謝謝。

  7. 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
    如果有不能跑得可以更改這邊看看

  8. 作者你好 請問上面的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

    可以問說哪邊出了問題嗎? 謝謝

    1. 你好,謝謝你的提醒。
      下方讀取 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 跑一遍我這篇文章的程式碼,檢查看看是否哪裡有問題。

        1. 抱歉一直到今天才有時間做個完整的測試。
          實際跑過之後,發現現在 Gensim 訓練的 Word2Vec 模型提取向量不能直接使用詞彙當作索引,需要使用 model.wv[“xxx”] 這樣的形式來取得 xxx 的向量。

          文章中的程式碼已經更新,希望能順利執行。

          再次謝謝你的留言,若不是你的留言,我還沒注意到舊的程式碼已經無法跑新版的 Gensim。

    2. Pingback: 2023/6/5 每日筆記 - Arfiligol Blog & CV

    Leave a Reply