word2vec是谷歌2013年開源的語言工具。 兩層網(wǎng)絡(luò),就能把詞變成向量,在NLP領(lǐng)域舉足輕重,是許多功能實(shí)現(xiàn)的基礎(chǔ)。 可是現(xiàn)在,有一位叫做bollu (簡(jiǎn)稱菠蘿) 的程序員,大聲對(duì)世界說: “關(guān)于word2vec,你所知道的一切都是錯(cuò)的。” 
在他看來,論文里的算法解釋,和代碼實(shí)現(xiàn)一比,講的根本是兩回事。
是不是只要開源了代碼,論文寫不寫清楚都沒關(guān)系? 一番仔細(xì)的論述,引起了許多人的討論和共鳴,不出半日Hacker News熱度已近300點(diǎn): 
那么,菠蘿的世界觀是怎樣崩塌的,他眼里真實(shí)的word2vec是什么樣子呢? 不一樣的天空word2vec有種經(jīng)典解釋 (在Skip-Gram里、帶負(fù)采樣的那種) ,論文和數(shù)不勝數(shù)的博客都是這樣寫的: 
只能看出有兩個(gè)向量。
可程序員說,看了word2vec最原本的C語言實(shí)現(xiàn)代碼,就會(huì)發(fā)現(xiàn)完全不一樣。 (多數(shù)用word2vec做詞嵌入的人類,要么是直接調(diào)用C實(shí)現(xiàn),要么是調(diào)用gensim實(shí)現(xiàn)。gensim是從C實(shí)現(xiàn)上翻譯過來的,連變量的名字都不變。) C實(shí)現(xiàn)長這樣每個(gè)單詞有兩個(gè)向量,分別有不同的角色: 一個(gè)表示這個(gè)詞作為中心詞 (Focus Word) 時(shí)的樣子。 一個(gè)表示它作為另一個(gè)中心詞的上下文 (Context Word) 時(shí)的樣子。
菠蘿說:耳熟吧,GloVe就是借用了這里的思路,只是沒有誰明確說出來而已。 在C語言的源代碼里,設(shè)定已經(jīng)非常完好,這些向量由兩個(gè)數(shù)組 (Array) 分別負(fù)責(zé): syn0數(shù)組,負(fù)責(zé)某個(gè)詞作為中心詞時(shí)的向量。是隨機(jī)初始化的。
1https://github.com/tmikolov/word2vec/blob/20c129af10659f7c50e86e3be406df663beff438/word2vec.c#L369 2 for (a = 0; a <>for (b = 0; b <> 3 next_random = next_random * (unsigned long long)25214903917 + 11; 4 syn0[a * layer1_size + b] = 5 (((next_random & 0xFFFF) / (real)65536) - 0.5) / layer1_size; 6 }
syn1neg數(shù)組,負(fù)責(zé)這個(gè)詞作為上下文時(shí)的向量。是零初始化的。 1https://github.com/tmikolov/word2vec/blob/20c129af10659f7c50e86e3be406df663beff438/word2vec.c#L365 2for (a = 0; a <>for (b = 0; b <> 3 syn1neg[a * layer1_size + b] = 0;
訓(xùn)練的話,要先選出一個(gè)中心詞。在正、負(fù)樣本訓(xùn)練的時(shí)候,這個(gè)中心詞就保持不變 (Constant) 了。 中心詞向量的梯度 (Gradients) ,會(huì)在緩沖器 (Buffer) 里累積起來。經(jīng)過正、負(fù)樣本的作用之后,這些梯度會(huì)被應(yīng)用到中心詞上: 1if (negative > 0) for (d = 0; d <>1; d++) { 2 // if we are performing negative sampling, in the 1st iteration, 3 // pick a word from the context and set the dot product target to 1 4 if (d == 0) { 5 target = word; 6 label = 1; 7 } else { 8 // for all other iterations, pick a word randomly and set the dot 9 //product target to 0 10 next_random = next_random * (unsigned long long)25214903917 + 11; 11 target = table[(next_random >> 16) % table_size]; 12 if (target == 0) target = next_random % (vocab_size - 1) + 1; 13 if (target == word) continue; 14 label = 0; 15 } 16 l2 = target * layer1_size; 17 f = 0; 18 19 // find dot product of original vector with negative sample vector 20 // store in f 21 for (c = 0; c <> 22 23 // set g = sigmoid(f) (roughly, the actual formula is slightly more complex) 24 if (f > MAX_EXP) g = (label - 1) * alpha; 25 else if (f <>0) * alpha; 26 else g = (label - expTable[(int)((f + MAX_EXP) * (EXP_TABLE_SIZE / MAX_EXP / 2))]) * alpha; 27 28 // 1. update the vector syn1neg, 29 // 2. DO NOT UPDATE syn0 30 // 3. STORE THE syn0 gradient in a temporary buffer neu1e 31 for (c = 0; c <> 32 for (c = 0; c <> 33} 34// Finally, after all samples, update syn1 from neu1e 35https://github.com/tmikolov/word2vec/blob/20c129af10659f7c50e86e3be406df663beff438/word2vec.c#L541 36// Learn weights input -> hidden 37for (c = 0; c <>
那么問題來了,為什么是隨機(jī)初始化,為什么是零初始化?
關(guān)于初始化這些東西,也沒見論文和博客里講過,菠蘿只能自己推測(cè)了一下: 因?yàn)?strong>負(fù)樣本 (Negative Sample) 來自全文上下,并沒太根據(jù)詞頻來定權(quán)重,這樣選哪個(gè)單詞都可以,通常這個(gè)詞的向量還沒經(jīng)過多少訓(xùn)練。 而如果這個(gè)向量已經(jīng)有了一個(gè)值,那么它就可以隨意移動(dòng) (Move Randomly) 中心詞了。 解決方法是,把所有負(fù)樣本設(shè)為零,這樣依賴只有那些比較高頻出現(xiàn)的向量,才會(huì)影響到另外一個(gè)向量的表征。
程序員說,如果是這樣,真的很巧妙。他也從來沒想過,初始化策略能有這么重要,讀論文也看不出。 直接看代碼,不相信論文了
在這之前,菠蘿已經(jīng)花了兩個(gè)月來復(fù)現(xiàn)word2vec,也讀了無數(shù)文章,就是不成功。 不管試了多少次,還是得不到論文說的分?jǐn)?shù)。又不能認(rèn)為分?jǐn)?shù)是論文作者編的。 最后,他決定去仔細(xì)讀源代碼。初讀還以為打開方式錯(cuò)了,因?yàn)楹椭翱催^的資料都不一樣: 我不明白,為什么原始論文和網(wǎng)上的博客,都不去寫word2vec真正是怎么工作的。所以就想自己寫出來。
也是在這個(gè)過程中,他才像上文提到的那樣,發(fā)現(xiàn)GloVe給上下文 (Context) 一個(gè)單獨(dú)的向量這種做法,是從word2vec那里來的。 而GloVe的作者并沒有提到過這一點(diǎn)。 想到這里,程序員又有了新的質(zhì)疑: 這樣不算學(xué)術(shù)不誠實(shí) (Academic Dishonesty) 么?我也不知道算不算,但覺得至少是個(gè)很嚴(yán)重的問題。
傷感之余,菠蘿作出了一個(gè)機(jī)智的決定:以后先不看論文對(duì)算法的解釋,直接去讀源代碼。 都是這種習(xí)慣么?探討起論文和實(shí)現(xiàn)不一致的情況,一個(gè)用編譯器讀了40年論文的資深程序員 (DannyBee) ,占據(jù)了Hacker News評(píng)論區(qū)的頂樓。 他細(xì)數(shù)了這些年來,論文作者的習(xí)慣變化: 早期許多算法的實(shí)現(xiàn),原理都和描述相符,性能也和描述相符。只是論文會(huì)用偽代碼 (Pseudocode) ,用偽代碼的部分,和實(shí)現(xiàn)的差別到底在哪,也會(huì)詳細(xì)說明。 后來,人們便開始走遠(yuǎn)了。有些論文的算法,要么是工作原理不像描述那樣,要么是效率低到?jīng)]法用??丛创a的時(shí)候也會(huì)發(fā)現(xiàn),不是論文說的那回事。 SSAPRE就是一個(gè)典型。時(shí)至今日,大家讀起它的論文還是會(huì)覺得難懂。把源碼放進(jìn)Open64編譯器去讀,也發(fā)現(xiàn)和論文大相徑庭 (Wildly Different) 。 再后來,有了github這類社區(qū),事情好像又朝著早期的健康方向發(fā)展了。 在這樣的環(huán)境里,word2vec算個(gè)反例吧,可能他們覺得已經(jīng)把代碼開源了,論文里寫不清也沒關(guān)系。
緊接著,樓下便有人 (nullwasamistake) 表示,反例不止這一個(gè): 我在實(shí)現(xiàn)一個(gè)哈希表排序算法的時(shí)候,發(fā)現(xiàn)一篇近期的論文也有類似的問題。 論文里從來沒提到過,表格尺寸必須是2的n次方。 而這篇研究的全部意義,似乎就是比現(xiàn)有的其他算法,內(nèi)存效率更高。 我做了2/3才發(fā)現(xiàn),根本沒有比現(xiàn)有方法更高效,反而更差了,除非把表的尺寸調(diào)成2^n。 雖然不是徹頭徹尾的騙人,但這個(gè)疏漏算是很有創(chuàng)意了。
不過,當(dāng)有人勸ta把那篇論文掛出來,這位吐槽的網(wǎng)友也實(shí)誠地表示: 現(xiàn)在批評(píng)科技巨頭有風(fēng)險(xiǎn),以后可能還想去工作呢。
由此觀之,菠蘿是個(gè)有勇氣的少年。 傳送門
菠蘿對(duì)word2vec的完整意見發(fā)表在github上,有興趣可前去觀賞: https://github.com/bollu/bollu.github.io 另外,還有Hacker News評(píng)論區(qū),便于尋找更多同感: https://news./item?id=20089515
|