Word Embedding原理及實現

自Google? 2013 年開源word2vec算法程序以后,它的簡單、高效、實用,很快引起業界眾人的關注和應用,為搜索引擎、[廣告系統-谷歌的wide & deep learning][2]、[推薦系統][1]等互聯網服務提供新的基礎技術和思路。


何為Embedding?

開篇之前首先需要明白一個概念何為Embedding?Embedding可以看作是數學上的一個空間映射(Mapping):map( lambda y: f(x) ),該映射的特點是:單射(在數學里,單射函數為一函數,其將不同的引數連接至不同的值上。更精確地說,函數f被稱為是單射時,對每一值域內的y,存在至多一個定義域內的x使得f(x) = y。)、映射前后結構不變,對應到word embedding概念中可以理解為尋找一個函數或映射,生成新的空間上的表達,把單詞one-hot所表達的X空間信息映射到Y的多維空間向量。

接下來,將以模型的角度分解embedding映射函數及新空間內表達的建模過程:

非監督的“監督學習”

從應用角度,新空間內映射函數的學習方法不需要大量的人工標記樣本就可以得到質量還不錯的embedding向量,沒有具體的應用任務導向,從這個角度可以看作非監督的學習過程,而從建模角度,向量提取的建模過程是 個分類模型,又可以看做是監督學習,只是這個監督沒有實際的監督意義,當然后來有的應該將word2vec的前段表達方式喂給標注的過文本,形成真正意義上的監督學習,如Facebook的FastText

2、一層隱層神經網絡

帶有一個隱層的神經網絡有以下普遍特性:理論上給定足夠該隱層節點數,這個隱層可以近似擬合任意函數,本質上,隱層是上一層的嵌入(Embedding)函數的近似表示,而且可以被用做lookup表(后面會介紹),word2vec也是基于該層去找到輸入word的嵌入向量表示,然后再建立下一層和當前層的連接(connections),來控制目標函數的誤差。【進一步抽象,如果從統計的角度,其實不同層之間的統計關系是一種遞歸的廣義線性關系(遞歸廣義線性模型),每一層通過線性組合對前一層進行變換,然后以一些非線性連接函數(不同函數對應output label不同的統計分布,比如softmax對應多項目分布,sigmoid對應二項分布等)得到非線性結果喂給下一層,參見圖rglm】

model_net.png
rglm.png

3、Embedding函數

從前面的定義,我們期望在隱層中找到一個/組嵌入函數W(這里采用lookup table的方式),使得![][3]具體的,假設指定固定的向量維度,W("籃球")=(0.2, -0.4, 0.7, ...),W("蘋果")=(0.0, 0.6, -0.1, ...),W初始化時可以賦值給每個維度一個隨機數,并通過與output層連接建立學習模型/任務后得到有意義的向量。

4、建模

接下來來看看如何建立和訓練模型。

數據準備

為給模型準備數據,我們首先需要定義或獲取n個樣本:![][4]

假如我們有一個句子“姚明 的 籃球 打得 很不錯”。常規方式是首先由統計語言模型,由中間詞預測周圍詞(SKIP-GRAM),或由周圍詞預測中間詞(CBOW)等方式,然后以指定的窗口向前推進,以SKIP-GRAM方式為例,假設推進窗口為2,我們可以得到樣本對:("籃球","的"),("籃球","姚明"),("籃球","打得"),("籃球","很不錯"),X skip至"打得"時,得到樣本對 :("打得","籃球"),("打得","的"),("打得","很不錯"),以此類推...我們可以得到用于模型的訓練樣本。

樣本表示

樣本拆解出來了,接下來如何用數值來表達這些樣本對呢?常用的辦法是將所有的訓練數據,即“word”對抽取出唯一不重復的單詞來構建詞典表(vocabulary),然后將樣本數據中的“word”表達成one-hot編碼,編碼時只對有值的位置上為1其他位置均為0,以上面例子為例,“姚明 的 籃球 打得 很不錯”。基于這個句子可以構建維度為5的詞典表:{"姚明":0,"":1,"的":2,"籃球":3,"打得":4,"很不錯":5},那么訓練樣本("籃球","姚明")即可表達為([0,0,1,0,0],0),看起來比較像常規的多分類數據了,這里為了好理解Y表示成了位置編號,后續在模型中仍以one-hot向量表達。

各層條件分布

神經網絡基于這些訓練樣本將會輸出一個概率分布,這個概率代表著我們的詞典中的每個詞是output word的可能性。更一般的,假設隱層有K個節點(即生成word對應vector向量的維度),對每個樣本,我們需要做兩件事情:

給定隱層后預測output word的概率,即需要建個模型來估計![][5]

將觀測到的input word喂給隱層嵌入函數,得到隱層的概率分布,![][6]用連接函數表達即上面提到的(常見的一般會是K個關于x線性組合的方程組,后面會講到為何不用該方式)![][3]

接下來我們需要構建整體的似然函數進行優化:

目標函數

分別建立input層-隱層及隱層-output層的連接函數(RGLM),input層和隱層的函數上面已給出,如果假設p(y|w)為正態分布,則 log-likelihood loss便是(negative) L2 loss:![][7],如果假設p(y|w)為多項分布,則likelihood loss便是softmax loss:![][8]從訓練樣本可以看出,output層為多分類,即隱層-output可采用softmax loss.

為了準確預測output word,該網絡需要根據上述損失函數學習參數矩陣W和R(output層),實際上,對于我們來說,整個學習任務是為了學習隱層的W函數,即隱層節點參數。當然對于其他任務,比如神經網絡推薦或Fasttext,網絡構造過程類似,只是學習的任務是學習輸出層的參數和結構。

模型訓練

常規優化方法會采用梯度下降和反向傳播,由上面的樣本定義,我們的訓練樣本中input和output均以one-hot表示,向量極其稀疏(通常完整字典表會是幾十萬維,假設200000),僅有一個位置的數值為1,其余均為0,如果input到隱層的嵌入函數采用常見方式的話,假設節點數即嵌入向量維度為200,則隱層參數矩陣每個樣本的迭代將會是1x200000的向量和200000x200矩陣的相乘,顯然會帶來巨大計算資源的消耗,其實每個樣本的隱層參數僅需要根據one-hot向量中數值為1的索引對應的隱層參數參數矩陣的該索引行對應的向量取出即可:

embedding.png

經過抽象后我們可以得到上面定義的Embedding函數/參數矩陣:

embedding-abstract.png

這種方式其實聯系上面提到的lookup table就容易理解了,即模型中的隱層權重矩陣便成了一個”查找表“(lookup table),進行矩陣計算時,只需要直接去查輸入的one-hot向量中提取非零位置的索引,在隱層的對應行輸出就是每個輸入單詞的“嵌入詞向量”,該過程即完成了嵌入的動作。

對于輸出層:

經過隱層的嵌入計算,input word會被映射為1x200的dense向量,再喂給輸出層經過softmax的分類器的計算,對隨機給定任意output word的嵌入向量計算其預測概率:![][8],這樣基于同一input word,替換不同的beta(output word的嵌入向量)得到不同output word的預測概率。

至此,數據的表示及目標損失函數的定義以及模型訓練過程已拆解完畢。接下來,再看看訓練性能提升和優化的方法。

5、抽樣

基于上面的拆解,我們會發現其實訓練過程涉及的參數數量會非常龐大,以上面的200000個單詞的字典表為例,隱層嵌入200維的詞向量,那么每次迭代的輸入-隱層權重矩陣和隱層-輸出層的權重矩陣都會有 200000 x 200 = 4000萬個權重,在如此龐大的神經網絡中進行梯度下降是相當慢的,而且需要大量的訓練數據來調整這些權重并且避免過擬合。所以對性能的要求仍然很高,雖然上面已經采用lookup table的方式簡化了一些計算,針對這個問題,Word2Vec的作者在論文提出了有效的方法,叫“negative sampling”,每個訓練樣本的訓練只會更新一小部分的模型權重,從而降低計算負擔,甚至是詞向量的質量。基于對假設是,我們的數據中存在大量冗余和噪音,舉例:對于“的”這種常用高頻單詞,我們會發現一些問題:當我們得到成對的單詞訓練樣本時,**("的", "籃球") *這樣的訓練樣本并不會給我們提供關于“籃球”更多的語義信息,因為“的”這樣的噪音詞在大部分單詞的上下文中幾乎都會出現。由于在語料中“的”這樣的常用詞出現概率很大,因此我們將會有大量的(”的“,...)這樣的訓練樣本,而這些樣本數量遠遠超過了我們學習“的”這個詞向量所需的訓練樣本數。所以在設計抽樣方法的時候可以對這樣的樣本直接排除在訓練樣本之外,對于其他樣本對隨機抽取少量的負樣本進行參數的更新,而不是對one-hot向量中所有200000個位置對樣本都進行計算,從而大大提高訓練效率。

上面敘述的有點繁雜,總結起來就是在對給定input word計算softmax時,不去更新所有詞表中word的輸出概率,而是從該樣本的output word之外隨機抽樣有限個(比如只抽樣5個word)作為負樣本計算其概率,進一步進行梯度和參數的更新。也就是說通過負樣本抽樣對于每次訓練只更新(5+1)個beta向量對應的參數,也就是2006=1200個參數,這樣與4000萬個相比,需要更新的參數占比僅為0.003%,效率提升可想而知。

6、基于tensorflow的實現

數據加載

import os

def load_w2c_textcn_dataset(path='./data/'):

"""

Returns

--------

word_list_all : a list

a list of string (word).\n

要求:中文語料需要先分詞

"""

print("Load or Download chinese text corpus Dataset> {}".format(path))

filename = 'wiki_cn.cut'

word_list_all=[]

with open(os.path.join(path, filename)) as f:

for line in f:

word_list=line.strip().split()

for idx, word in enumerate(word_list):

word_list[idx] = word_list[idx].decode('utf-8')

#print word_list[idx]

word_list_all.append(word_list[idx])

return word_list_all

words=load_w2c_textcn_dataset(path='./data/')

print len(words)

字典構建

import collections

vocabulary_size = 200000

count = [['UNK', -1]]

count.extend(collections.Counter(words).most_common(vocabulary_size - 1))

dictionary = dict()

for word, _ in count:

dictionary[word] = len(dictionary)

data = list()

unk_count = 0

for word in words:

if word in dictionary:

index = dictionary[word]

else:

index = 0? # dictionary['UNK']

unk_count = unk_count + 1

data.append(index)

count[0][1] = unk_count

reverse_dictionary = dict(zip(dictionary.values(), dictionary.keys()))

del words

batch數據生成器

data_index = 0

def generate_batch(batch_size, num_skips, skip_window):

global data_index

batch = np.ndarray(shape=(batch_size), dtype=np.int32)

labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)

span = 2 * skip_window + 1? # [ skip_window target skip_window ]

buf = collections.deque(maxlen=span)

for _ in xrange(span):

buf.append(data[data_index])

data_index = (data_index + 1) % len(data)

for i in xrange(batch_size // num_skips):

target = skip_window? # target label at the center of the buffer

targets_to_avoid = [ skip_window ]

for j in xrange(num_skips):

while target in targets_to_avoid:

target = random.randint(0, span - 1)

targets_to_avoid.append(target)

batch[i * num_skips + j] = buf[skip_window]

labels[i * num_skips + j, 0] = buf[target]

buf.append(data[data_index])

data_index = (data_index + 1) % len(data)

return batch, labels

模型構建

import tensorflow as tf

import collections

import numpy as np

batch_size = 128

embedding_size = 128? # 生成向量維度.

skip_window = 2? ? ? # 左右窗口.

num_skips = 2? ? ? ? # 同一個keyword產生label的次數.

num_sampled = 64? ? ? # 負樣本抽樣數.

graph = tf.Graph()

with graph.as_default(), tf.device('/cpu:0'):

train_dataset = tf.placeholder(tf.int32, shape=[batch_size])

train_labels? = tf.placeholder(tf.int32, shape=[batch_size, 1])

embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))

softmax_weights = tf.Variable(

tf.truncated_normal([vocabulary_size, embedding_size], stddev=1.0/np.sqrt(embedding_size)))

softmax_biases = tf.Variable(tf.zeros([vocabulary_size]))

embed = tf.nn.embedding_lookup(embeddings, train_dataset)

loss = tf.reduce_mean(

tf.nn.sampled_softmax_loss(weights=softmax_weights, biases=softmax_biases, inputs=embed,

labels=train_labels, num_sampled=num_sampled, num_classes=vocabulary_size))

optimizer = tf.train.AdagradOptimizer(1.0).minimize(loss)

norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))

normalized_embeddings = embeddings / norm

模型訓練

num_steps = 500001

import random

with tf.Session(graph=graph) as session:

tf.global_variables_initializer().run()

average_loss = 0

for step in range(num_steps):

batch_data, batch_labels = generate_batch(batch_size, num_skips, skip_window)

feed_dict = {train_dataset : batch_data, train_labels : batch_labels}

_, l = session.run([optimizer, loss], feed_dict=feed_dict)

average_loss += l

if step % 100000 == 0 and step > 0:

print('Average loss at step %d: %f' % (step, average_loss / 100000))

average_loss = 0

word2vec = normalized_embeddings.eval()

最近鄰

distances = -word2vec[dictionary[u'數據']].reshape((1, -1)).dot(word2vec.T)

inds = np.argsort(distances.ravel())[1:6]

print(' '.join([reverse_dictionary[i] for i in inds]))

----------------------------------------------

資料 統計 顯示 信息 證據


[1]? Peter McCullagh, John A Nelder,Generalized linear models., , 1989

[2] The seminal paper,A Neural Probabilistic Language Model(Bengio,et al.2003)has a great deal of insight about why word embeddings are powerful.

[3]:https://erikbern.com/2014/06/28/recurrent-neural-networks-for-collaborative-filtering.html

[4]:https://research.googleblog.com/2016/06/wide-deep-learning-better-together-with.html?utm_source=tuicool&utm_medium=referral


原文參考:http://www.lxweimin.com/p/d44ce1e3ec2f

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容