目錄
2.貝葉斯個性化排序(Bayesian Personalized Ranking, BPR)
1.“排序算法”分類
全量召回得到粗排結果,再使用更精準但是性能慢的算法進行排序;相當於算法的融合,得到更佳的推薦結果。初期你想直接用協同過濾的結果做推薦也不是不可以,但要考慮實時推薦的性能和優化推薦結果。
2.貝葉斯個性化排序(Bayesian Personalized Ranking, BPR)
2.1.應用場景
傳統的近鄰協同過濾推薦算法的核心思想是基於現有“用戶-商品評分矩陣”計算用戶之間的相似度,並通過評分預測公式對整個矩陣中的缺失評分進行預測,並依據評分的高低對用戶進行推薦,實踐證明使用起來也很有效。但是,在千萬級別的商品中用戶感興趣的商品僅僅是個位數,通過對評分矩陣中的全部缺失評分進行預測的方式耗時耗力(笨方法)。因此,排序推薦更關心的是對於用戶來說哪些極少數商品在用戶心中有更高的優先級,也就是排序更靠前。
【現有基於隱式反饋數據的排序推薦算法問題】
BPR是基於用戶的隱式反饋(傳統處理方法:產生交互行爲的記爲1,未產生交互行爲的記爲0,並將交互行爲矩陣作輸入給模型進行訓練。於是產生的問題是,我們希望模型在以後預測的缺失值,在訓練時卻都被認爲是負類數據),爲用戶提供物品的推薦,並且是直接對排序進行優化
2.2.BPR建模思路
2.2.1.【符號說明】
<u,i,j>:用戶u在同時有物品i和j的情況下點擊了i,也即:對用戶u來說,i的排序要比j靠前
m:用戶反饋<u,i,j>的個數,也即訓練樣本的個數
:表示用戶u的偏好,相比於商品j,用戶u更加偏好於商品i(滿足:完整性、反對稱性和傳遞性),因此便可爲每個用戶構造大小爲I×I的偏好矩陣
:所有用戶偏好對構成的訓練集(也可直接表示爲D)
:表示BRP算法最終得到的預測排序矩陣
和:分別表示分解後期望得到的用戶矩陣和商品矩陣(k爲自定義大小且遠小於用戶或商品的量級,矩陣的理解同SVD)
2.2.2.【BPR算法前提假設】
①每個用戶之間的偏好行爲相互獨立,即用戶u在商品i和j之間的偏好和其他用戶無關
②同一用戶對不同物品的偏序相互獨立,也就是用戶u在商品i和j之間的偏好和其他的商品無關
2.2.3.【BPR算法的目標函數的形式化定義】
總體目標函數如下:
由於BPR是基於用戶維度的,所以對於任意一個用戶u,對應的任意一個物品i有:
其中,表示W矩陣中對應用戶行的第f個元素,表示H矩陣中對應行的第f個元素
2.3.BPR優化目標函數
2.3.1.【總體目標函數求解】
由貝葉斯定理可知:
(1).要解決的問題:求解最優參數W和H(表示爲θ) → 事件 A
(2).已知條件:用戶u對應的所有商品的全序關係 → 事件 B
故優化目標函數可表示爲:
由於假設了用戶的偏好排序和其他用戶無關,那麼對於任意一個用戶u,對所有的物品一樣,所以有“後驗概率∝(正比於)似然概率×先驗概率”:
綜上優化目標可表示爲“似然概率”和“先驗概率”兩部分。
2.3.2.【第一部分:似然概率求解】
由“BPR算法的前提假設”可得:
基於符號說明中所介紹的“用戶偏好的性質(完整性+反對稱性)”上式可簡化爲:
【一】 上式中通過sigmoid函數表示,原因主要有兩個:
① sigmoid函數滿足“BPR算法的用戶偏好性質” → 單調連續函數
② 方便後續進行優化計算,也就是方便後續求導
【二】上式中通過表示(其中和分別表示中對應位置的值,也即“BPR算法的目標函數的形式化定義”中的最後一個公式),原因:
① 當,;反之,
綜上,第一部分的優化結果最終表示爲:
2.3.3.【第二部分: 先驗概率求解】
先驗概率表示我們在不知道B事件(用戶的偏好排序)的前提下,對A事件(θ)概率的主觀判斷。故原作者大膽使用了貝葉斯假設,即這個概率分佈符合正態分佈,且對應的均值是0,協方差矩陣是,即:
而對於上式中假設的多維正態分佈,其取對數之後與成正比(例如:下圖爲標準正態分佈f(x)和取對數後的函數log2(f(x))圖像),故上式可進一步轉化爲:
2.3.4.【綜上:最終的優化目標函數形式】
上式可通過“隨機梯度下降法”或者“牛頓法”等優化算法對其進行優化。以“隨機梯度下降算法”爲例,θ的導數可表示爲:
由於:
故可得:
因此,對應的各模型參數的迭代公式可表示爲(其中α表示學習速率):
接下來則可以迭代更新各個參數了。
2.4.BPR算法流程
輸入:訓練集D三元組<u,i,j,label>,學習速率=α, 正則化參數=λ,分解矩陣維度=k
輸出:模型參數,矩陣W,H
①. 隨機初始化矩陣W,H
②. 迭代更新模型參數
③. 如果W,HW,H收斂,則算法結束,輸出W,H,否則回到步驟②
④. 當得到最優的W,H後,就可以計算出每一個用戶u對應的任意一個商品的排序分:,最終選擇排序
分最高的若干商品輸出。
2.5.總結
BPR是基於矩陣分解的一種排序算法,但是和funkSVD之類的算法比,其不是做全局的評分優化,而是針對每一個用戶自己的商品喜好分貝做排序優化。因此在迭代優化的思路上完全不同。同時對於訓練集的要求也是不一樣的,funkSVD只需要用戶物品對應評分數據二元組做訓練集,而BPR則需要用戶對商品的喜好排序三元組做訓練集。
3.BPR程序(Python-TensorFlow)
# --------------------庫及原始數據的加載(MovieLens 100K)
import numpy
import tensorflow as tf
import os
import random
from collections import defaultdict
def load_data(data_path):
user_ratings = defaultdict(set)
max_u_id = -1
max_i_id = -1
with open(data_path, '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_id:", max_i_id)
return max_u_id, max_i_id, user_ratings
data_path = os.path.join('D:\\tmp\\ml-100k', 'u.data')
user_count, item_count, user_ratings = load_data(data_path)
# --------------------挑選構造測試集三元組的數據
def generate_test(user_ratings):
user_test = dict()
for u, i_list in user_ratings.items():
user_test[u] = random.sample(user_ratings[u], 1)[0]
return user_test
user_ratings_test = generate_test(user_ratings)
# --------------------構造訓練集三元組數據
def generate_train_batch(user_ratings, user_ratings_test, item_count, batch_size=512):
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 numpy.asarray(t)
# --------------------構造測試集三元組數據
def generate_test_batch(user_ratings, user_ratings_test, item_count):
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 numpy.asarray(t)
# --------------------構造BPR數據流
def bpr_mf(user_count, item_count, hidden_dim):
u = tf.placeholder(tf.int32, [None])
i = tf.placeholder(tf.int32, [None])
j = tf.placeholder(tf.int32, [None])
with tf.device("/cpu:0"):
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)
# MF predict: u_i > u_j
x = tf.reduce_sum(tf.multiply(u_emb, (i_emb - j_emb)), 1, keep_dims=True)
# AUC for one user:
# reasonable iff all (u,i,j) pairs are from the same user
#
# average AUC = mean( auc for each user in test set)
mf_auc = tf.reduce_mean(tf.to_float(x > 0))
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))
])
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)
return u, i, j, mf_auc, bprloss, train_op
# --------------------模型求解
with tf.Graph().as_default(), tf.Session() as session:
u, i, j, mf_auc, bprloss, train_op = bpr_mf(user_count, item_count, 20)
session.run(tf.initialize_all_variables())
for epoch in range(1, 4):
_batch_bprloss = 0
for k in range(1, 5000): # uniform samples from training set
uij = generate_train_batch(user_ratings, user_ratings_test, item_count)
_bprloss, _train_op = session.run([bprloss, train_op],
feed_dict={u:uij[:,0], i:uij[:,1], j:uij[:,2]})
_batch_bprloss += _bprloss
print ("epoch: ", epoch)
print ("bpr_loss: ", _batch_bprloss / k)
print ("_train_op")
user_count = 0
_auc_sum = 0.0
# each batch will return only one user's auc
for t_uij in generate_test_batch(user_ratings, user_ratings_test, item_count):
_auc, _test_bprloss = session.run([mf_auc, bprloss],
feed_dict={u:t_uij[:,0], i:t_uij[:,1], j:t_uij[:,2]}
)
user_count += 1
_auc_sum += _auc
print ("test_loss: ", _test_bprloss, "test_auc: ", _auc_sum/user_count)
print ("")
variable_names = [v.name for v in tf.trainable_variables()]
values = session.run(variable_names)
for k,v in zip(variable_names, values):
print("Variable: ", k)
print("Shape: ", v.shape)
print(v)
# --------------------用戶推薦
session1 = tf.Session()
u1_dim = tf.expand_dims(values[0][0], 0)
u1_all = tf.matmul(u1_dim, values[1],transpose_b=True)
result_1 = session1.run(u1_all)
# print (result_1)
print("以下是給用戶0的推薦:")
p = numpy.squeeze(result_1)
p[numpy.argsort(p)[:-5]] = 0
for index in range(len(p)):
if p[index] != 0:
print (index, p[index])