受限玻爾茲曼機(jī)的實(shí)現(xiàn)及其在推薦系統(tǒng)中的應(yīng)用

受限玻爾茲曼機(jī)(restricted Boltzmann machine, RBM)是一種可通過(guò)輸入數(shù)據(jù)集學(xué)習(xí)概率分布的隨機(jī)生成神經(jīng)網(wǎng)絡(luò),在降維、分類(lèi)、協(xié)同過(guò)濾、特征學(xué)習(xí)和主題建模等領(lǐng)域中有著廣泛應(yīng)用。

在Netflix Prize后半程,有選手將RBM應(yīng)用在該預(yù)測(cè)電影評(píng)分問(wèn)題上并取得了不錯(cuò)的效果。后來(lái)Edwin Chen的文章《Introduction to Restricted Boltzmann Machines》使用詳細(xì)而易懂的方式(沒(méi)什么數(shù)學(xué)公式與推導(dǎo))描述了RBM的運(yùn)作機(jī)理,并使用Python的numpy寫(xiě)了一個(gè)簡(jiǎn)易實(shí)現(xiàn)。

這篇文章通過(guò)逐行閱讀并運(yùn)行Edwin Chen的開(kāi)源代碼,觀看其中用到的數(shù)據(jù)結(jié)構(gòu)、值的變化來(lái)展現(xiàn)RBM的運(yùn)作原理及實(shí)現(xiàn)技巧。

這里仍然把背景放到音樂(lè)這里來(lái),使用六首歌曲來(lái)訓(xùn)練RBM,其中三首為Disco歌曲:ABBA的Dancing Queen,Bee Gees的Stayin' Alive,新褲子的別再問(wèn)我什么是迪斯科,另外三首是吉他英雄的solo:Dire Straits的Sultans Of Swing,Yngwie Malmsteen的Black Star和桶哥Buckethead的Thorne Room。

作為一個(gè)神經(jīng)網(wǎng)絡(luò),RBM有可見(jiàn)層和隱藏層兩層,其中可見(jiàn)層每個(gè)節(jié)點(diǎn)對(duì)應(yīng)一首歌曲,而隱藏層的每個(gè)節(jié)點(diǎn)我們則希望它對(duì)應(yīng)于一種音樂(lè)類(lèi)型,故對(duì)應(yīng)上述歌曲的特點(diǎn)在說(shuō)明中使隱藏層為兩個(gè)節(jié)點(diǎn)。同時(shí)再加一個(gè)bias unit來(lái)控制太過(guò)熱門(mén)的item對(duì)該模型造成的影響,此神經(jīng)網(wǎng)絡(luò)各節(jié)點(diǎn)的連接情況是這樣的:

image

可見(jiàn)層的每個(gè)節(jié)點(diǎn)與所有隱藏層的節(jié)點(diǎn)相連,bias unit與兩層所有的節(jié)點(diǎn)相連,每個(gè)連接對(duì)應(yīng)一個(gè)weight,首先使用矩陣來(lái)表示可見(jiàn)層與隱藏層節(jié)點(diǎn)的所有weight,比如該矩陣的第一行的第一列的數(shù)據(jù)對(duì)應(yīng)于可見(jiàn)層節(jié)點(diǎn)Dancing Queen與hidden unit1相連的weight,然后在此基礎(chǔ)上,插入bias unit的weight,因?yàn)樗c可見(jiàn)層、隱藏層皆相連,故此矩陣的行與列各加1。給兩層節(jié)點(diǎn)之間的所有weight賦予一個(gè)范圍內(nèi)的隨機(jī)值以初始化該矩陣,與bias unit相連的節(jié)點(diǎn)暫全置為0。

import numpy as np


num_hidden = 2
num_visible = 6
np_rng = np.random.RandomState(1234)

weights = np.asarray(np_rng.uniform(
                low=-0.1 * np.sqrt(6. / (num_hidden + num_visible)),
                high=0.1 * np.sqrt(6. / (num_hidden + num_visible)),
                size=(num_visible, num_hidden)))
weights
array([[-0.0534304 ,  0.02114986],
       [-0.01078587,  0.04942556],
       [ 0.04849323, -0.03938812],
       [-0.03871753,  0.05228579],
       [ 0.07935206,  0.06511344],
       [-0.02462677,  0.00017236]])
# 加入bias unit.
weights = np.insert(weights, 0, 0, axis=0)
weights = np.insert(weights, 0, 0, axis=1)
weights
array([[ 0.        ,  0.        ,  0.        ],
       [ 0.        , -0.0534304 ,  0.02114986],
       [ 0.        , -0.01078587,  0.04942556],
       [ 0.        ,  0.04849323, -0.03938812],
       [ 0.        , -0.03871753,  0.05228579],
       [ 0.        ,  0.07935206,  0.06511344],
       [ 0.        , -0.02462677,  0.00017236]])

接下來(lái)構(gòu)造一些訓(xùn)練樣本,一個(gè)樣本是關(guān)于這六首歌的收聽(tīng)情況的list(按照上圖從上到下的順序?qū)?yīng)list中的index),填值1表示該user聽(tīng)過(guò)此歌,填值0表示該user未聽(tīng)過(guò)此歌。這里構(gòu)造6個(gè)樣本作為樣本集合,同時(shí)考慮到weight矩陣增加了bias unit,為了后續(xù)線性代數(shù)運(yùn)算的對(duì)應(yīng)性,需要在樣本集合形成的矩陣中再插入一列,值均置為1。

data = np.array([[1,1,1,0,0,0],[1,0,1,0,0,0],[1,1,1,0,0,0],[0,0,1,1,1,0], [0,0,1,1,0,0],[0,0,1,1,1,0]])
num_examples = data.shape[0]
data = np.insert(data, 0, 1, axis=1)

data
array([[1, 1, 1, 1, 0, 0, 0],
       [1, 1, 0, 1, 0, 0, 0],
       [1, 1, 1, 1, 0, 0, 0],
       [1, 0, 0, 1, 1, 1, 0],
       [1, 0, 0, 1, 1, 0, 0],
       [1, 0, 0, 1, 1, 1, 0]])

有了輸入矩陣與weight矩陣,對(duì)于一個(gè)隱藏節(jié)點(diǎn),計(jì)算它的值是1還是0,首先要將所有與它相連的節(jié)點(diǎn)的取值各自乘以相應(yīng)的weight再做加和,比如對(duì)于靠上的隱藏節(jié)點(diǎn),當(dāng)輸入第一個(gè)樣本時(shí),可見(jiàn)層的取值為[1, 1, 1, 1, 0, 0, 0],相應(yīng)的權(quán)重為[0, -0.0534304, -0.01078587, 0.04849323, -0.03871753, 0.07935206, -0.02462677],兩向量做點(diǎn)乘正好對(duì)應(yīng)了上面的過(guò)程,注意兩向量第一個(gè)值對(duì)應(yīng)于bias unit,而bias unit的weight為0,對(duì)結(jié)果并未產(chǎn)生影響。

對(duì)于所有樣本和所有的隱藏節(jié)點(diǎn)都是一樣的處理,那么可將這么許多次點(diǎn)乘化為矩陣相乘,根據(jù)矩陣相乘的規(guī)則(行向量點(diǎn)乘列向量結(jié)果放在相應(yīng)的位置)可以看出將data與weight矩陣相乘剛好表示對(duì)每個(gè)樣本和每個(gè)隱藏節(jié)點(diǎn)將上述處理做了一次,會(huì)產(chǎn)生出一個(gè)新的形狀為(6*3)的矩陣,第一列對(duì)應(yīng)bias unit值全為0,第二列的第一個(gè)元素便對(duì)應(yīng)了Dancing Queen在hidden unit1處生成的值。使用矩陣相乘,效率要比對(duì)樣本和隱藏單元進(jìn)行迭代快得多。

pos_hidden_activations = np.dot(data, weights)

pos_hidden_activations
array([[ 0.        , -0.01572304,  0.0311873 ],
       [ 0.        , -0.00493717, -0.01823826],
       [ 0.        , -0.01572304,  0.0311873 ],
       [ 0.        ,  0.08912777,  0.07801112],
       [ 0.        ,  0.00977571,  0.01289768],
       [ 0.        ,  0.08912777,  0.07801112]])

算出激活值后,眾所周知,神經(jīng)網(wǎng)絡(luò)的節(jié)點(diǎn)往往包含一個(gè)激活函數(shù),這里使用Sigmond函數(shù),將上一步算出的激活值控制到0-1之間,來(lái)代表此節(jié)點(diǎn)被激活的可能性,當(dāng)為某節(jié)點(diǎn)計(jì)算出的激活概率越接近1,則其被激活的可能性越大,這里利用numpy的廣播功能,對(duì)上述生成的矩陣中的所有節(jié)點(diǎn)都施加一個(gè)Sigmond函數(shù)。然后將bias unit對(duì)應(yīng)的那一列全改為1,這表示bias unit總是被激活的,具體原因見(jiàn)下文。

def logistic(x):
    return 1.0 / (1 + np.exp(-x))

pos_hidden_probs = logistic(pos_hidden_activations)
pos_hidden_probs[:, 0] = 1
pos_hidden_probs
array([[1.        , 0.49606932, 0.50779619],
       [1.        , 0.49876571, 0.49544056],
       [1.        , 0.49606932, 0.50779619],
       [1.        , 0.5222672 , 0.5194929 ],
       [1.        , 0.50244391, 0.50322437],
       [1.        , 0.5222672 , 0.5194929 ]])

rand函數(shù)會(huì)隨機(jī)生成一個(gè)處于0到1之間的數(shù),將上述算出的節(jié)點(diǎn)激活概率與一個(gè)這樣的隨機(jī)數(shù)比較大小來(lái)決定是否激活此節(jié)點(diǎn),這意味著即使此時(shí)某次訓(xùn)練中某隱藏結(jié)點(diǎn)的激活概率為0.99,也是有可能不被激活的。

pos_hidden_states = pos_hidden_probs > np.random.rand(num_examples, num_hidden + 1)
pos_hidden_states
array([[ True,  True,  True],
       [ True, False, False],
       [ True, False, False],
       [ True,  True,  True],
       [ True, False,  True],
       [ True, False,  True]])

這個(gè)矩陣的含義舉例為其第一行第二列的值表示Dancing Queen是否激活了hidden unit1。

如上述進(jìn)行過(guò)了一次所有樣本對(duì)隱藏層激活情況的計(jì)算,可以得出Dancing Queen與hidden unit1同時(shí)亮起的相關(guān)性,記為Positive(e_{ij}),看上述矩陣datapos_hidden_probs,第一個(gè)樣本在Dancing Queen節(jié)點(diǎn)取值為1,對(duì)hidden unit1激活概率為0.49606932,將兩者相乘得到一個(gè)值,對(duì)所有樣本如此計(jì)算得到的值的加和即為這兩個(gè)節(jié)點(diǎn)的相關(guān)性,這個(gè)過(guò)程同樣可以使用矩陣相乘來(lái)表示如下:

pos_associations = np.dot(data.T, pos_hidden_probs)
pos_associations
array([[6.        , 3.03788267, 3.05324311],
       [3.        , 1.49090435, 1.51103295],
       [2.        , 0.99213864, 1.01559239],
       [6.        , 3.03788267, 3.05324311],
       [3.        , 1.54697831, 1.54221017],
       [2.        , 1.04453441, 1.03898579],
       [0.        , 0.        , 0.        ]])

如此,一次從可見(jiàn)層到隱藏層的計(jì)算便結(jié)束了。之后反過(guò)來(lái),從隱藏層到可見(jiàn)層,將pos_hidden_states作為樣本集合輸入隱藏層,做一遍與上述過(guò)程完全相同的計(jì)算,同樣可以計(jì)算出兩相連節(jié)點(diǎn)之間的相關(guān)性,這次記為Negative(e_{ij})。由于是隨機(jī)取的初始weight,Positive與Negative之間應(yīng)該會(huì)有不小的差別,而RBM的優(yōu)化目標(biāo)便是通過(guò)多個(gè)epoch的訓(xùn)練,使其差別盡可能小。

上述一正一反便算完成了一個(gè)epoch,根據(jù)式子w _ { i j } = w _ { i j } + L * \left( \text {Positive} \left( e _ { i j } \right) - N e g a t i v e \left( e _ { i j } \right) \right)算出新的weight值(其中L為學(xué)習(xí)速率需要煉金而得),來(lái)開(kāi)始下一個(gè)epoch的計(jì)算,如此會(huì)使得兩者之間的差值越來(lái)越小,從而得到一個(gè)訓(xùn)練好的RBM模型。

比較巧妙的還是bias unit,可以看到上述有些可見(jiàn)節(jié)點(diǎn)與bias unit的關(guān)聯(lián)值達(dá)到6,而在下一次循環(huán)中,又會(huì)對(duì)bias unit整個(gè)重新賦值,這個(gè)處理可以將那些熱門(mén)的item對(duì)隱藏節(jié)點(diǎn)是否激活的影響引向這個(gè)bias unit,來(lái)稀釋這種影響,盡量防止“the Beatles現(xiàn)象”的出現(xiàn)。同時(shí),由于它與兩層每個(gè)節(jié)點(diǎn)都相連,在從可見(jiàn)層到隱藏層的計(jì)算過(guò)程中,它其實(shí)是作為一個(gè)隱藏層節(jié)點(diǎn)來(lái)一同參與計(jì)算的,而在反向時(shí),它又作為一個(gè)可見(jiàn)層節(jié)點(diǎn)來(lái)發(fā)揮作用,真是妙啊。

使用上述過(guò)程的完整版代碼(見(jiàn)文末參考鏈接),來(lái)看一下結(jié)果:

r = RBM(num_visible = 6, num_hidden = 2)
training_data = np.array([[1,1,1,0,0,0],[1,0,1,0,0,0],[1,1,1,0,0,0],[0,0,1,1,1,0], [0,0,1,1,0,0],[0,0,1,1,1,0]])
r.train(training_data, max_epochs = 5000)
print(r.weights[1:, 1:])
[[-8.09650002  3.95552071]
 [-5.45512759  1.42845858]
 [ 1.74474585  4.06127352]
 [ 7.74906751 -3.54062571]
 [ 3.18686136 -7.33215302]
 [-2.46868951 -2.60826581]]

從訓(xùn)練好的weight中可以看出,hidden unit1傾向于對(duì)應(yīng)rock guitar hero的音樂(lè),而hidden unit2則傾向于對(duì)應(yīng)disco。

聯(lián)系推薦系統(tǒng),顯然該模型可以對(duì)item做降維處理,與Word2vec一樣,使用weight組成的向量表示即可,比如Dancing Queen可表示為[-8.09650002, 3.95552071]。
而要為user推薦item,則需要將其收聽(tīng)歷史向量[1, 1, 1, 0, 0, 0]輸入訓(xùn)練好的模型,激活一些隱藏節(jié)點(diǎn),再將表示隱藏層節(jié)點(diǎn)被激活情況的向量反向輸入模型,可為每個(gè)item得到一個(gè)被激活的概率,去掉用戶(hù)已經(jīng)聽(tīng)過(guò)的item,再對(duì)概率進(jìn)行從大到小排序選取K個(gè)即可做出TopK推薦。該處理只有簡(jiǎn)單的向量計(jì)算非常迅速,可用于在線實(shí)時(shí)生成推薦結(jié)果。

對(duì)于顯示反饋,比如Netflix Prize的情況,Ruslan Salakhutdinov等人對(duì)RBM提出了改進(jìn),可見(jiàn)層使用Softmax神經(jīng)元來(lái)表示打分情況,對(duì)于沒(méi)有被評(píng)分過(guò)的item則使用特殊的神經(jīng)元表示,不與隱藏層相連避免無(wú)謂的計(jì)算;而條件RBM可以在處理顯示反饋時(shí)將用戶(hù)瀏覽過(guò)哪些物品這樣的隱式反饋的影響同時(shí)考慮進(jìn)去。這些改進(jìn)都涉及到對(duì)本文計(jì)算過(guò)程與數(shù)學(xué)公式的改進(jìn),具體可以參考論文。

參考

echen/restricted-boltzmann-machines
Introduction to Restricted Boltzmann Machines
Restricted Boltzmann Machines for Collaborative Filtering

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容