結論:維基百科有 ⟦R:n_articles⟧ 篇文章,太多了,所以隨機抽 20% 來當教材。重點是一篇一篇讀、讀到就決定要不要留,不要整份讀進記憶體。
助教已經把維基百科「洗乾淨」了:拿掉網頁語法、標點,全部轉小寫,字和字之間用空白隔開。每一行就是一整篇文章。
⟦R:wiki_head⟧
分成 11 個壓縮檔,加起來 6.93 GB(筆記本 In [18] 那一格印的)。前 10 個檔各 562,365 篇(模板註解寫的),最後一個只有 5 篇(推算:5,623,655 − 10 × 562,365 = 5)。
注意上面第 5 行(abraham lincoln):「february april was」「the th president」中間的日期和數字被拿掉了;第 0 行「anarchism is political philosophy」、第 3 行「alabama is state」少了單一字母的 a。原因是助教用 gensim WikiCorpus 的預設參數清資料:模板在 TODO4 前面寫「words with one character were discarded」(只有一個字母的字被丟掉);筆記本 In [27] 的註解也寫了這個預設「只留 2~15 個字母的英文字」,所以數字(不是字母)也會被拿掉。
SAMPLE_RATIO = 0.20
random.seed(42) # ① 固定亂數種子
...
for part in wiki_parts: # ② 11 個壓縮檔依序讀
with gzip.open(part, "rt", encoding="utf-8") as f:
for line in f: # ③ 一次只讀一行(一篇文章)
n_total += 1
r = random.random() # ④ 擲一個 0~1 的亂數
if r < SAMPLE_RATIO: # 小於 0.2 就留下
output_file.write(line)
for ratio, fh in extra_outputs.items(): # ⑤ 同一個亂數,順便做 5%、10%
if r < ratio:
fh.write(line)
| # | 在做什麼 | 為什麼要這樣 |
|---|---|---|
| ① | 固定亂數種子 | 亂數其實是「照公式算出來的」,起點(種子)固定,每次跑抽到的文章就一樣。結果才能重現,報告的數字才不會每次跑都不同。 |
| ② | 直接讀壓縮檔 | 不用先解壓縮、合併,省下大量硬碟空間。解壓後約 3 倍大(推算:20% 樣本解壓後 4.16 GB,全部約 4.16 ÷ 0.2 ≈ 20.8 GB,是壓縮檔 6.93 GB 的約 3 倍)。 |
| ③ | 一行一行讀 | 老師在課堂上特別提醒過:以前有同學說 Colab 記憶體不夠,通常是程式寫法有問題——「你不要把百分之百的 wikidump 都載入,然後自己再選」(W3 01:55:49–01:56:07)。一行一行讀,記憶體裡永遠只有一篇文章。 |
| ④ | 擲亂數決定留不留 | 每篇文章有 20% 的機會被留下,所以最後大約留下 20%。是「以篇為單位」抽(助教影片 00:13:26「每一行代表一篇 Wikipedia 的文章,因此我們是以文章為單位進行取樣」)。 |
| ⑤ | 同一個亂數順便做 5%、10% | 亂數小於 0.05 的文章,一定也小於 0.10、0.20。所以 5% 的文章一定包含在 10% 裡、10% 一定包含在 20% 裡。這樣三份教材只差在「多少」,內容種類一樣,報告第 2 題的比較才公平。而且讀一趟 11 個壓縮檔要幾分鐘,這樣省掉兩趟。 |
每篇文章只擲一次亂數 r。下圖四顆星是四篇文章的例子(r 是我編的):
⟦IMG:sampling_line⟧
所以「5% 裡的每一篇,一定也在 10% 裡」,不可能有例外。
⟦R:todo4_out⟧
花了 ⟦R:todo4_time⟧。
結論:前處理只做一件事(只留純英文字母的字),而且刻意不做「移除停用詞」和「詞形還原」,因為它們會把考卷的答案弄不見。訓練用 Skip-gram,參數盡量跟 GloVe 一樣,比較才公平。
| 作業規格建議的做法(PDF p28) | 我有沒有做 | 為什麼 |
|---|---|---|
| 移除非英文字 | 有(只留 a~z 組成的字) | 不過效果很小:筆記本 TODO5 的輸出顯示只刪掉 ⟦R:nonen_pct⟧ 的字,因為助教已經清過了。 |
| 移除停用詞(the、of、he、she…) | 刻意不做 | 考卷裡有 ⟦R:n_stop_q⟧ 題含有停用詞,例如家人/性別小類(筆記本上叫 family)的 he she his her,刪掉之後這些題目必錯。老師上課也說停用詞「現在一點都不重要也不會去做」(W2 01:39:50)(老師是在講搜尋引擎建索引時說的;本作業的停用詞實驗也支持同樣的結論)。第 12 節有實驗。 |
| 詞形還原(rocks → rock) | 刻意不做 | 語法類的考題就是在考字形變化(bigger、biggest、cars、danced)。還原成原形,答案就不見了。 |
| 更好的斷詞 | 不需要 | 助教的資料已經斷好字了。 |
| 只保留常見字 | 有(最多 30 萬字) | 見下面參數表的 max_final_vocab。 |
W2V_PARAMS = dict(vector_size=100, window=5, sg=1, negative=5, sample=1e-3,
min_count=5, max_final_vocab=300_000, epochs=5, workers=15, seed=42)
| 參數 | 我設多少 | 白話 | 為什麼這樣設 |
|---|---|---|---|
vector_size | 100 | 每個字用幾個數字表示(詞向量有幾個數字) | 跟 GloVe-100 一樣,比較才公平。第 12 節有試 300。 |
window | 5 | 「前後文的字」算多遠:最多前後各 5 個字(每次實際在 1~5 之間隨機取,所以近的字被看到的次數比較多;外部知識:gensim 的預設做法) | 常見預設。第 12 節有試 10。 |
sg | 1 | 1 = Skip-gram,0 = CBOW | 老師投影片的重點是 Skip-gram。第 12 節有比較 CBOW。 |
negative | 5 | 每一組真的(中心字, 前後文的字)配 5 個隨機抽的假字,讓電腦練習分辨「這一對是不是真的前後文」 | 這叫 negative sampling(負取樣):取代「對 30 萬字算機率」,所以快很多。預設值。手算例子見課堂補充第 5 點。 |
sample | 0.001 | 太常見的字(the、of)隨機跳過一些 | 加速,也讓少見字學得比較好(外部知識:Mikolov 2013 原論文)。預設值。注意:它的效果有點像「部分刪掉停用詞」,第 12 節停用詞實驗要考慮這點。 |
min_count | 5(實際被調成 ⟦R:eff_min_count⟧) | 收錄門檻:出現次數低於門檻的字不收進字典 | 出現太少,詞向量學不準。實際被自動調高,見下面例外 1。 |
max_final_vocab | 300,000 | 字典最多收 30 萬個字 | 作業規格 PDF p28 建議的「只保留最高頻的字」(Retain the most frequent words)。字典太大會讓答題變很慢、也比較容易猜到冷門字。 |
epochs | 5 | 整份教材讀幾遍 | 預設值。讀越多遍越準,但越久。 |
workers | 15 | 同時用幾個 CPU 執行緒 | 你的電腦有 16 個,留 1 個給系統。Colab 要改成 2。 |
seed | 42 | 亂數種子 | 固定初始值。但結果仍會小幅變動,見下面例外 2。 |
因為 max_final_vocab 規定字典最多收 30 萬字,出現 5 次以上的字卻超過 30 萬個。gensim 就自動把門檻往上調,直到字數壓到 30 萬以內。
結果:20% 維基實際是出現 ⟦R:eff_min_count⟧ 次以上才收(TODO5 輸出有印);10% 是 16 次、5% 是 9 次(In [31])。教材越大,門檻被調得越高。
用 15 個執行緒同時訓練時,大家更新詞向量的先後順序每次不同,所以就算種子一樣,結果也會差一點點。差多少,第 12 節實驗 ① 有量(整體約 1 個百分點)。
抽樣用的 random.seed(42) 只有一條執行緒,可以完全重現。
⟦R:todo5_out⟧