排序推薦算法大體上可以分為三類秃臣,第一類排序算法類別是點(diǎn)對方法(Pointwise Approach)梳毙,這類算法將排序問題被轉(zhuǎn)化為分類、回歸之類的問題般婆,并使用現(xiàn)有分類到腥、回歸等方法進(jìn)行實(shí)現(xiàn)。第二類排序算法是成對方法(Pairwise Approach)蔚袍,在序列方法中乡范,排序被轉(zhuǎn)化為對序列分類或?qū)π蛄谢貧w。所謂的pair就是成對的排序,比如(a,b)一組表明a比b排的靠前篓足。第三類排序算法是列表方法(Listwise Approach)段誊,它采用更加直接的方法對排序問題進(jìn)行了處理。它在學(xué)習(xí)和預(yù)測過程中都將排序列表作為一個(gè)樣本栈拖。排序的組結(jié)構(gòu)被保持连舍。
之前我們介紹的算法大都是Pointwise的方法,今天我們來介紹一種Pairwise的方法:貝葉斯個(gè)性化排序(Bayesian Personalized Ranking, 以下簡稱BPR)
1涩哟、BPR算法簡介
1.1 基本思路
在BPR算法中索赏,我們將任意用戶u對應(yīng)的物品進(jìn)行標(biāo)記,如果用戶u在同時(shí)有物品i和j的時(shí)候點(diǎn)擊了i贴彼,那么我們就得到了一個(gè)三元組<u,i,j>潜腻,它表示對用戶u來說,i的排序要比j靠前器仗。如果對于用戶u來說我們有m組這樣的反饋融涣,那么我們就可以得到m組用戶u對應(yīng)的訓(xùn)練樣本。
這里精钮,我們做出兩個(gè)假設(shè):
- 每個(gè)用戶之間的偏好行為相互獨(dú)立威鹿,即用戶u在商品i和j之間的偏好和其他用戶無關(guān)。
- 同一用戶對不同物品的偏序相互獨(dú)立轨香,也就是用戶u在商品i和j之間的偏好和其他的商品無關(guān)忽你。
為了便于表述,我們用>u符號表示用戶u的偏好臂容,上面的<u,i,j>可以表示為:i >u j科雳。
在BPR中,我們也用到了類似矩陣分解的思想脓杉,對于用戶集U和物品集I對應(yīng)的U*I的預(yù)測排序矩陣糟秘,我們期望得到兩個(gè)分解后的用戶矩陣W(|U|×k)和物品矩陣H(|I|×k),滿足:
那么對于任意一個(gè)用戶u丽已,對應(yīng)的任意一個(gè)物品i蚌堵,我們預(yù)測得出的用戶對該物品的偏好計(jì)算如下:
而模型的最終目標(biāo)是尋找合適的矩陣W和H,讓X-(公式打不出來沛婴,這里代表的是X上面有一個(gè)橫線,即W和H矩陣相乘后的結(jié)果)和X(實(shí)際的評分矩陣)最相似督赤∴业疲看到這里,也許你會說躲舌,BPR和矩陣分解沒有什區(qū)別呀丑婿?是的,到目前為止的基本思想是一致的,但是具體的算法運(yùn)算思路羹奉,確實(shí)千差萬別的秒旋,我們慢慢道來。
1.2 算法運(yùn)算思路
BPR 基于最大后驗(yàn)估計(jì)P(W,H|>u)來求解模型參數(shù)W,H,這里我們用θ來表示參數(shù)W和H, >u代表用戶u對應(yīng)的所有商品的全序關(guān)系,則優(yōu)化目標(biāo)是P(θ|>u)诀拭。根據(jù)貝葉斯公式迁筛,我們有:
由于我們求解假設(shè)了用戶的排序和其他用戶無關(guān),那么對于任意一個(gè)用戶u來說耕挨,P(>u)對所有的物品一樣细卧,所以有:
這個(gè)優(yōu)化目標(biāo)轉(zhuǎn)化為兩部分。第一部分和樣本數(shù)據(jù)集D有關(guān)筒占,第二部分和樣本數(shù)據(jù)集D無關(guān)贪庙。
第一部分
對于第一部分,由于我們假設(shè)每個(gè)用戶之間的偏好行為相互獨(dú)立翰苫,同一用戶對不同物品的偏序相互獨(dú)立止邮,所以有:
上面的式子類似于極大似然估計(jì),若用戶u相比于j來說更偏向i奏窑,那么我們就希望P(i >u j|θ)出現(xiàn)的概率越大越好导披。
上面的式子可以進(jìn)一步改寫成:
而對于P(i >u j|θ)這個(gè)概率,我們可以使用下面這個(gè)式子來代替:
其中良哲,σ(x)是sigmoid函數(shù)盛卡,σ里面的項(xiàng)我們可以理解為用戶u對i和j偏好程度的差異,我們當(dāng)然希望i和j的差異越大越好筑凫,這種差異如何體現(xiàn)滑沧,最簡單的就是差值:
省略θ我們可以將式子簡略的寫為:
因此優(yōu)化目標(biāo)的第一項(xiàng)可以寫作:
哇,是不是很簡單的思想巍实,對于訓(xùn)練數(shù)據(jù)中的<u,i,j>滓技,用戶更偏好于i,那么我們當(dāng)然希望在X-矩陣中ui對應(yīng)的值比uj對應(yīng)的值大棚潦,而且差距越大越好令漂!
第二部分
回想之前我們通過貝葉斯角度解釋正則化的文章:http://www.reibang.com/p/4d562f2c06b8
當(dāng)θ的先驗(yàn)分布是正態(tài)分布時(shí),其實(shí)就是給損失函數(shù)加入了正則項(xiàng)丸边,因此我們可以假定θ的先驗(yàn)分布是正態(tài)分布:
所以:
因此叠必,最終的最大對數(shù)后驗(yàn)估計(jì)函數(shù)可以寫作:
剩下的我們就可以通過梯度上升法(因?yàn)槭且屔鲜阶畲蠡?來求解了。我們這里就略過了妹窖,BPR的思想已經(jīng)很明白了吧纬朝,哈哈!讓我們來看一看如何實(shí)現(xiàn)吧骄呼。
2共苛、算法實(shí)現(xiàn)
本文的github地址為:https://github.com/princewen/tensorflow_practice/tree/master/recommendation/Basic-BPR-Demo
所用到的數(shù)據(jù)集是movieslen 100k的數(shù)據(jù)集判没,下載地址為:http://grouplens.org/datasets/movielens/
數(shù)據(jù)預(yù)處理
首先,我們需要處理一下數(shù)據(jù)隅茎,得到每個(gè)用戶打分過的電影澄峰,同時(shí),還需要得到用戶的數(shù)量和電影的數(shù)量辟犀。
def load_data():
user_ratings = defaultdict(set)
max_u_id = -1
max_i_id = -1
with open('data/u.data','r') as f:
for line in f.readlines():
u,i,_,_ = line.split("\t")
u = int(u)
i = int(i)
user_ratings[u].add(i)
max_u_id = max(u,max_u_id)
max_i_id = max(i,max_i_id)
print("max_u_id:",max_u_id)
print("max_i_idL",max_i_id)
return max_u_id,max_i_id,user_ratings
下面我們會對每一個(gè)用戶u俏竞,在user_ratings中隨機(jī)找到他評分過的一部電影i,保存在user_ratings_test,后面構(gòu)造訓(xùn)練集和測試集需要用到踪蹬。
def generate_test(user_ratings):
"""
對每一個(gè)用戶u胞此,在user_ratings中隨機(jī)找到他評分過的一部電影i,保存在user_ratings_test,我們?yōu)槊總€(gè)用戶取出的這一個(gè)電影跃捣,是不會在訓(xùn)練集中訓(xùn)練到的漱牵,作為測試集用。
"""
user_test = dict()
for u,i_list in user_ratings.items():
user_test[u] = random.sample(user_ratings[u],1)[0]
return user_test
構(gòu)建訓(xùn)練數(shù)據(jù)
我們構(gòu)造的訓(xùn)練數(shù)據(jù)是<u,i,j>的三元組疚漆,i可以根據(jù)剛才生成的用戶評分字典得到酣胀,j可以利用負(fù)采樣的思想,認(rèn)為用戶沒有看過的電影都是負(fù)樣本:
def generate_train_batch(user_ratings,user_ratings_test,item_count,batch_size=512):
"""
構(gòu)造訓(xùn)練用的三元組
對于隨機(jī)抽出的用戶u娶聘,i可以從user_ratings隨機(jī)抽出闻镶,而j也是從總的電影集中隨機(jī)抽出,當(dāng)然j必須保證(u,j)不在user_ratings中
"""
t = []
for b in range(batch_size):
u = random.sample(user_ratings.keys(),1)[0]
i = random.sample(user_ratings[u],1)[0]
while i==user_ratings_test[u]:
i = random.sample(user_ratings[u],1)[0]
j = random.randint(1,item_count)
while j in user_ratings[u]:
j = random.randint(1,item_count)
t.append([u,i,j])
return np.asarray(t)
構(gòu)造測試數(shù)據(jù)
同樣構(gòu)造三元組丸升,我們剛才給每個(gè)用戶單獨(dú)抽出了一部電影铆农,這個(gè)電影作為i,而用戶所有沒有評分過的電影都是負(fù)樣本j:
def generate_test_batch(user_ratings,user_ratings_test,item_count):
"""
對于每個(gè)用戶u狡耻,它的評分電影i是我們在user_ratings_test中隨機(jī)抽取的墩剖,它的j是用戶u所有沒有評分過的電影集合,
比如用戶u有1000部電影沒有評分夷狰,那么這里該用戶的測試集樣本就有1000個(gè)
"""
for u in user_ratings.keys():
t = []
i = user_ratings_test[u]
for j in range(1,item_count + 1):
if not(j in user_ratings[u]):
t.append([u,i,j])
yield np.asarray(t)
模型構(gòu)建
首先回憶一下我們需要學(xué)習(xí)的參數(shù)θ岭皂,其實(shí)就是用戶矩陣W(|U|×k)和物品矩陣H(|I|×k)對應(yīng)的值,對于我們的模型來說沼头,可以簡單理解為由id到embedding的轉(zhuǎn)化爷绘,因此有:
u = tf.placeholder(tf.int32,[None])
i = tf.placeholder(tf.int32,[None])
j = tf.placeholder(tf.int32,[None])
user_emb_w = tf.get_variable("user_emb_w", [user_count + 1, hidden_dim],
initializer=tf.random_normal_initializer(0, 0.1))
item_emb_w = tf.get_variable("item_emb_w", [item_count + 1, hidden_dim],
initializer=tf.random_normal_initializer(0, 0.1))
u_emb = tf.nn.embedding_lookup(user_emb_w, u)
i_emb = tf.nn.embedding_lookup(item_emb_w, i)
j_emb = tf.nn.embedding_lookup(item_emb_w, j)
回想一下我們要優(yōu)化的目標(biāo),第一部分是ui和uj對應(yīng)的預(yù)測值的評分之差,再經(jīng)由sigmoid變換得到的[0,1]值,我們希望這個(gè)值越大越好,對于損失來說,當(dāng)然是越小越好玉凯。因此,計(jì)算如下:
x = tf.reduce_sum(tf.multiply(u_emb,(i_emb-j_emb)),1,keep_dims=True)
loss1 = - tf.reduce_mean(tf.log(tf.sigmoid(x)))
第二部分是我們的正則項(xiàng),參數(shù)就是我們的embedding值蔚约,所以正則項(xiàng)計(jì)算如下:
l2_norm = tf.add_n([
tf.reduce_sum(tf.multiply(u_emb, u_emb)),
tf.reduce_sum(tf.multiply(i_emb, i_emb)),
tf.reduce_sum(tf.multiply(j_emb, j_emb))
])
因此坑赡,我們模型整個(gè)的優(yōu)化目標(biāo)可以寫作:
regulation_rate = 0.0001
bprloss = regulation_rate * l2_norm - tf.reduce_mean(tf.log(tf.sigmoid(x)))
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(bprloss)
至此,我們整個(gè)模型就介紹完了么抗,如果大家想要了解完整的代碼實(shí)現(xiàn)毅否,可以參考github喲。
3蝇刀、總結(jié)
1.BPR是基于矩陣分解的一種排序算法螟加,它不是做全局的評分優(yōu)化,而是針對每一個(gè)用戶自己的商品喜好分貝做排序優(yōu)化吞琐。
2.它是一種pairwise的排序算法捆探,對于每一個(gè)三元組<u,i,j>,模型希望能夠使用戶u對物品i和j的差異更明顯站粟。
3.同時(shí)黍图,引入了貝葉斯先驗(yàn),假設(shè)參數(shù)服從正態(tài)分布奴烙,在轉(zhuǎn)換后變?yōu)榱薒2正則助被,減小了模型的過擬合。
參考文獻(xiàn)
1切诀、http://www.cnblogs.com/pinard/p/9128682.html
2揩环、http://www.cnblogs.com/pinard/p/9163481.html