用tensorflow學習貝葉斯個性化排序(BPR)

劉建平Pinard發表於2018-06-10

    在貝葉斯個性化排序(BPR)演算法小結中,我們對貝葉斯個性化排序(Bayesian Personalized Ranking, 以下簡稱BPR)的原理做了討論,本文我們將從實踐的角度來使用BPR做一個簡單的推薦。由於現有主流開源類庫都沒有BPR,同時它又比較簡單,因此用tensorflow自己實現一個簡單的BPR的演算法,下面我們開始吧。

1. BPR演算法回顧

    BPR演算法是基於矩陣分解的排序演算法,它的演算法訓練集是一個個的三元組$<u,i,j>$,表示對使用者u來說,商品i的優先順序要高於商品j。訓練成果是兩個分解後的矩陣$W$和$H$,假設有m個使用者,n個物品,那麼$W$的維度是$m \times k$, $H$的維度是$n \times k$。其中k是一個需要自己定義的較小的維度。對於任意一個使用者u,我們可以計算出它對商品i的排序評分為$\overline{x}_{ui} = w_u \bullet h_i$。將u對所有物品的排序評分中找出最大的若干個,就是我們對使用者u的真正的推薦集合。

2. 基於movieLens 100K做BPR推薦

    本文我們基於MovieLens 100K的資料做BPR推薦示例,資料下載連結在這。這個資料集有943個使用者對1682部電影的打分。由於BPR是排序演算法,因此資料集裡的打分會被我們忽略,主要是假設使用者看過的電影會比使用者滿意看的電影的排序評分高。由於tensorflow需要批量梯度下降,因此我們需要自己劃分若干批訓練集和測試集。

3. 演算法流程

    下面我們開始演算法的流程,參考了github上一個較舊的BPR程式碼於此,有刪改和增強。

    完整程式碼參見我的github:https://github.com/ljpzzz/machinelearning/blob/master/classic-machine-learning/bpr.ipynb

    首先是載入類庫和資料,程式碼如下:

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)

    輸出為資料集裡面的使用者數和電影數。同時,每個使用者看過的電影都儲存在user_ratings中。

max_u_id: 943
max_i_id: 1682

    下面我們會對每一個使用者u,在user_ratings中隨機找到他評分過的一部電影i,儲存在user_ratings_test,後面構造訓練集和測試集需要用到。

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)

    接著我們需要得到TensorFlow迭代用的若干批訓練集,獲取訓練集的程式碼如下,主要是根據user_ratings找到若干訓練用的三元組$<u,i,j>$,對於隨機抽出的使用者u,i可以從user_ratings隨機抽出,而j也是從總的電影集中隨機抽出,當然j必須保證$(u,j)$不出現在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)

    下一步是產生測試集三元組$<u,i,j>$。對於每個使用者u,它的評分電影i是我們在user_ratings_test中隨機抽取的,它的j是使用者u所有沒有評分過的電影集合,比如使用者u有1000部電影沒有評分,那麼這裡該使用者的測試集樣本就有1000個。

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)

    有了訓練集和測試集,下面是用TensorFlow構建BPR演算法的資料流,程式碼如下,其中hidden_dim就是我們矩陣分解的隱含維度k。user_emb_w對應矩陣$W$, item_emb_w對應矩陣$H$。如果大家看過之前寫的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

    有了演算法的資料流圖,訓練集和測試集也有了,現在我們可以訓練模型求解$W,H$這兩個矩陣了,注意我們在原理篇是最大化對數後驗估計函式, 而這裡是最小化取了負號後對應的對數後驗估計函式,實際是一樣的。程式碼如下:

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)

    這裡我k取了20, 迭代次數3, 主要是為了快速輸出結果。如果要做一個較好的BPR演算法,需要對k值進行選擇迭代,並且迭代次數也要更多一些。這裡我的輸出如下,供參考。

epoch:  1
bpr_loss:  0.7236263042427249
_train_op
test_loss:  0.76150036 test_auc:  0.4852939894020929

epoch:  2
bpr_loss:  0.7229681559433149
_train_op
test_loss:  0.76061743 test_auc:  0.48528061393838007

epoch:  3
bpr_loss:  0.7223725006756341
_train_op
test_loss:  0.7597519 test_auc:  0.4852617720521252

Variable:  user_emb_w:0
Shape:  (944, 20)
[[ 0.08105529  0.04270628 -0.12196594 ...  0.02729403  0.1556453
  -0.07148876]
 [ 0.0729574   0.01720054 -0.08198593 ...  0.05565814 -0.0372898
   0.11935959]
 [ 0.03591165 -0.11786834  0.04123168 ...  0.06533947  0.11889934
  -0.19697346]
 ...
 [-0.05796075 -0.00695129  0.07784595 ... -0.03869986  0.10723818
   0.01293885]
 [ 0.13237114 -0.07055715 -0.05505611 ...  0.16433473  0.04535925
   0.0701588 ]
 [-0.2069717   0.04607181  0.07822093 ...  0.03704183  0.07326393
   0.06110878]]
Variable:  item_emb_w:0
Shape:  (1683, 20)
[[ 0.09130769 -0.16516572  0.06490657 ...  0.03657753 -0.02265425
   0.1437734 ]
 [ 0.02463264  0.13691436 -0.01713235 ...  0.02811887  0.00262074
   0.08854961]
 [ 0.00643777  0.02678963  0.04300125 ...  0.03529688 -0.11161
   0.11927075]
 ...
 [ 0.05260892 -0.03204868 -0.06910443 ...  0.03732759 -0.03459863
  -0.05798787]
 [-0.07953933 -0.10924194  0.11368059 ...  0.06346208 -0.03269136
  -0.03078123]
 [ 0.03460099 -0.10591184 -0.1008586  ... -0.07162578  0.00252131
   0.06791534]]

    現在我們已經得到了$W,H$矩陣,就可以對任意一個使用者u的評分排序了。注意輸出的$W,H$矩陣分別在values[0]和values[1]中。

    那麼我們如何才能對某個使用者推薦呢?這裡我們以第一個使用者為例,它在$W$中對應的$w_0$向量為value[0][0],那麼我們很容易求出這個使用者對所有電影的預測評分, 程式碼如下:

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)

    輸出為一個評分向量:

[[-0.01707731  0.06217583 -0.01760234 ...  0.067231    0.08989487
  -0.05628442]]

    現在給第一個使用者推薦5部電影,程式碼如下:

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])

    輸出如下:

以下是給使用者0的推薦:
54 0.1907271
77 0.17746378
828 0.17181025
1043 0.16989286
1113 0.17458326

4. 小結

    以上就是用tensorflow來構建BPR演算法模型,並用該演算法模型做movieLens 100K推薦的過程。實際做產品專案中,如果要用到BPR演算法,一是要注意對隱藏維度k的調參,另外儘量多迭代一些輪數。

    另外我們可以在BPR損失函式那一塊做文章。比如我們可以對$\overline{x}_{uij} = \overline{x}_{ui} - \overline{x}_{uj}$這個式子做改進,加上一個基於評分時間的衰減係數,這樣我們的排序推薦還可以考慮時間等其他因素。

    以上就是用tensorflow學習BPR的全部內容。

 

(歡迎轉載,轉載請註明出處。歡迎溝通交流: liujianping-ok@163.com)    

相關文章