0%

[word2vec] Word2vec 實作

word2vec

Word2vec

簡介

  • 依照輸入的詞的集合計算出詞與詞之間的距離
  • 轉為向量,把對文章內容的處理簡化為向量空間中的向量運算,計算出向量空間上的相似度
  • 計算的是 cosine 值(餘弦值),cosine 值越大,代表兩個詞關聯度越高
  • CBOW vs. Skip-gram 算法
    • CBOW(Continues Bag of Words): 給定上下文,預測 input word
    • Skip-gram: 給定 input word,預測上下文

實作

Requirements

在這次的實作中,我們使用的 Python 版本是 3.5.2,另外也會需要以下幾個 package:

  • gensim
  • jieba
1
2
3
4
5
6
7
8
# create virtual environment
$ virtualenv ./env

# change to virtual environment
$ source ./env/bin/activate

# install required packages
$ pip3 install gensim jieba

資料前處理

首先,我們需要先將資料進行前處理,在這次的實作中,我們是以 Dcard 的資料來訓練 word2vec model.
資料已經先處理為每行都是一篇文章的內容,將每篇文章進行斷詞,並過濾 stopwords,最後將斷詞後的結果存為檔案以便後續訓練 word2vec model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# preprocessing.py

import re
import sys
import jieba
import logging
from optparse import OptionParser

class Preprocessing(object):
def __init__(self):
# logging config
logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

# 載入自定義詞典
jieba.set_dictionary('./dict/dict.txt.big')
jieba.load_userdict('./dict/user_dict.dict')
self.stoplist = self.load_dict('./dict/stoplist.dict')

def load_dict(self, fileName):
""" 讀取stoplist """
dicts = []
with open(fileName, 'r', encoding='utf-8') as fp:
for line in fp:
line = line.strip('\n')
if line:
dicts.append(line)
return dicts

def cut(self, content):
""" 使用 jieba 斷詞, 並過濾 stopwords """
word_list = jieba.cut(content, cut_all=False, HMM=False)

# 過濾stopwords
terms = []
for word in word_list:
word = word.strip()
if word and word.isalnum() and word not in self.stoplist:
terms.append(word)
return ' '.join(terms)

def execute(self, input_file):
"""
1. 讀取檔案內容,每行已處理為每篇文章的內容
2. 將每篇文章內容進行斷詞後輸出為 {file}.out, 每行為該文章的詞的集合
"""

output = open(input_file + '.out', 'w', encoding='utf-8', errors='replace')

cnt = 0
with open(input_file, 'r', encoding='utf-8', errors='replace') as fp:
for line in fp:
line = line.strip('\n')
terms = self.cut(line)
output.write(terms + '\n')

cnt += 1
if (cnt % 1000) == 0:
logging.info('cnt: %d' % cnt)

output.close()

if __name__ == '__main__':

# Option parser
parser = OptionParser()
parser.add_option("-f", "--file", dest="input_file", help="Input file.")
(options, args) = parser.parse_args()

if not options.file:
parser.print_help()
parser.error("Must have -f option.")

preprocessing = Preprocessing()
preprocessing.execute(options.input_file)

1
$ python3 preprocessing.py -f './data/data.rec'

資料處理完之後,會輸出至data/data.rec.out,檔案內容如下:

1
2
發問 淘寶 新手 付款 集運 疑問 聽 玩 模型 前輩 ...
退款 下訂 私下 轉錢 麻煩 淘寶 退款 付款 帳戶 百度 查 物流 帳號 ...

每篇文章都被轉換為多個詞的集合,接著我們就可以用這些處理過的資料來訓練 word2vec model.

訓練 word2vec model

處理完資料之後,利用 gensimword2vec 來訓練 word2vec model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# train.py

import sys
import logging
from gensim.models import word2vec
from optparse import OptionParser

def train(options):

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

sentences = word2vec.LineSentence(options.input_file)
model = word2vec.Word2Vec(sentences, size=300, min_count=15)

# 儲存model
model.save(options.output_file)

if __name__ == '__main__':

# Option parser
parser = OptionParser()
parser.add_option("-f", "--file", dest="input_file", help="Input file for training wrod2vec model.", metavar="FILE")
parser.add_option("-o", "--output", dest="output_file", help="Output word2vec model. (Default=\"data/word2vec.mdl\")", metavar="FILE", default="./data/word2vec.mdl")
(options, args) = parser.parse_args()

if not options.input_file:
parser.print_help()
parser.error("Must have -f option.")

train(options)

1
$ python3 train.py -f './data/data.rec.out' -o './data/word2vec.mdl'

這裡的程式碼最主要其實只有三行 (分別是第11、12和15行),就能夠訓練出 word2vec model 並將 model 儲存。最重要的是第12行: model = word2vec.Word2Vec(sentences, size=300, min_count=15),我們先看一下word2vec.Word2Vec()的原型:

1
2
3
4
class gensim.models.word2vec.Word2Vec(sentences=None, size=100, alpha=0.025, window=5, \
min_count=5, max_vocab_size=None, sample=0.001, seed=1, workers=3, min_alpha=0.0001, \
sg=0, hs=0, negative=5, cbow_mean=1, hashfxn=<built-in function hash>, iter=5, \
null_word=0, trim_rule=None, sorted_vocab=1, batch_words=10000)

參數說明:

  • sentences: 要訓練的句子,可以是 list,對於較大量的訓練資料,建議使用 BrownCorpus, Text8Corpus 或 LineSentence.
  • size: 詞向量的維度,預設是 100,較大的 size 會需要更多的訓練資料,也會需要更多的記憶體空間,但是效果會比較好。
  • alpha: 機器學習中的學習率,會逐漸收斂到 min_alpha.
  • window: 往左右各看幾個字,預設是 5.
  • min_count: 忽略出現的次數小於 min_count 的詞
  • max_vocab_size: 設定詞向量構建期間的RAM限制,如果詞的總數超過 max_vocab_size 的值,則會去除出現頻率最低的。設為 None 表示沒有限制。
  • workers: 執行緒數目
  • sg: sg=1 表示採用 skip-gram, sg=0 表示採用 CBOW, 預設是 0.
  • hs: hs=1 表示採用 hierarchical softmax, hs=0 表示使用 negative sampling, 預設是 0.
  • negative: 如果 > 0, 則會採用 negative sampling. 此值表示 noise words 的數量。
  • cbow_mean: cbow_mean=0 採用上下文詞向量的總和, cbow_mean=1 採用均值,預設是 1。這個值只有在使用 CBOW 時才有作用。
  • hashfxn: hash function 來初始化權重,預設使用 Python 的 hash function.
  • iter: 迭代次數,預設是 5.
  • trim_rule: 用來設定詞的整理規則,指定哪些詞要被刪除、哪些要保留,預設是 word count < min_count 的詞會被刪除。
  • sorted_vocab: 預設是 1, 在分配 word index 時會依照詞頻做降序排序。
  • batch_words: 每次處理的詞的數量。

其中 sentences 是一定要有的參數,其他的可以依照需求再做調整~

如果訓練完 word2vec model 後,在載入 model 或是查詢時出現 MemoryError 的問題,可以試著增加 min_count,忽略出現次數過少的詞,以減少訓練的詞量,降低記憶體需求。

結果

訓練完之後,來測試一下效果如何:

  • 載入模型

    1
    2
    3
    from gensim.models import word2vec

    model = word2vec.Word2Vec.load('./data/word2vec.mdl')
  • 取得前20個相關詞及其機率:

    1
    similar = model.most_similar(positive=query, topn=20)

    執行結果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    query: 蔡英文

    馬英九 0.7758316397666931
    小英 0.7685800194740295
    馬總統 0.741024911403656
    蔡英文總統 0.7397772073745728
    洪秀柱 0.7373905777931213
    朱立倫 0.7315630912780762
    民進黨政府 0.7311826944351196
    民進黨 0.7303115725517273
    林全 0.7250531315803528
    馬英九總統 0.7208837270736694
    總統 0.7105945348739624
    宋楚瑜 0.7093351483345032
    選前 0.7067139148712158
    吳敦義 0.7047908902168274
    新政府 0.7000598311424255
    賴清德 0.6969730854034424
    李登輝 0.6749316453933716
    選後 0.6709515452384949
    國民黨 0.6695643067359924
    綠營 0.668891191482544
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    query: 珍珠奶茶

    珍奶 0.8230817317962646
    波霸奶茶 0.7711058855056763
    木瓜牛奶 0.7707912921905518
    奶綠 0.7704713344573975
    芒果冰沙 0.7543411254882812
    鮮奶茶 0.7471712231636047
    西瓜汁 0.7412415146827698
    芋頭牛奶 0.7387706637382507
    手搖飲料 0.7384251356124878
    水果茶 0.7374613881111145
    四季春 0.736596941947937
    抹茶拿鐵 0.7361791133880615
    綠豆沙 0.7361130714416504
    檸檬紅茶 0.7228580713272095
    五十嵐 0.7211850881576538
    凍檸茶 0.7128199934959412
    迷克夏 0.708733320236206
    手搖 0.7055209875106812
    酪梨牛奶 0.7013069987297058
    冬瓜茶 0.7011166214942932
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    query: 阿里山

    合歡山 0.8092970848083496
    日月潭 0.804085373878479
    清境 0.7589331865310669
    北海岸 0.7464619278907776
    武嶺 0.7462913990020752
    擎天崗 0.7406344413757324
    清境農場 0.7399024367332458
    太平山 0.7287646532058716
    忘憂森林 0.7182701826095581
    高美 0.7161029577255249
    三仙台 0.7141523957252502
    奮起湖 0.7141432762145996
    七星潭 0.710894763469696
    高美濕地 0.7108360528945923
    平溪 0.710108757019043
    南投 0.7075525522232056
    拉拉山 0.7046494483947754
    鵝鑾鼻 0.7046091556549072
    六十石山 0.7044894099235535
    蘇澳 0.6968797445297241
  • 取得兩個詞之間的相關度

    1
    result = model.similarity(word1, word2)

    執行結果:

    1
    2
    3
    4
    5
    6
    7
    8
    珍珠奶茶, 奶茶
    0.698616829611946

    蔡英文, 賴清德
    0.6969730854034424

    電腦, 程式
    0.5202557530895322

從以上幾個例子來看,效果還算OK。

當然,我們還可以再想辦法優化模型訓練的效果:
在訓練的過程中,很重要的一部份是斷詞,透過斷詞來決定哪些詞出現在一起的,所以斷詞的效果好,整體訓練結果才會更好。jieba斷詞其實還有不足的地方,所以也可以試著選擇其他斷詞器,或者是調整辭典中的詞的權重、增加自定義辭典來改善斷詞效果。
另外一部份就是在訓練時的參數調整,如果對於 word2vec 已經很熟悉,也可以試著調整 word2vec model 的參數,讓訓練的效果可以更好~

參考資料