個性化排序的神經協同過濾

個性化排序的神經協同過濾

Neural Collaborative Filtering for Personalized Ranking

這一部分將超越顯式反饋,介紹神經協作過濾(NCF)框架,用於推薦具有隱式反饋。隱式反饋在推薦系統中普遍存在。諸如點擊、購買和觀看等行爲都是常見的隱性反饋,這些反饋很容易收集並指示用戶的偏好。我們將介紹的模型名爲NeuMF[He et al.,2017b],是神經矩陣分解的縮寫,旨在解決帶有隱式反饋的個性化排名任務。該模型利用神經網絡的靈活性和非線性,代替矩陣分解的點積,提高模型的表達能力。具體地說,該模型由廣義矩陣分解(GMF)和多層感知器(MLP)兩個子網絡構成,用兩條路徑來模擬相互作用,而不是簡單的內積。將這兩個網絡的輸出連接起來,以計算最終的預測分數。與AutoRec中的評分預測任務不同,該模型基於隱含反饋爲每個用戶生成一個排名推薦列表。我們將使用最後一節介紹的個性化排名損失來訓練這個模型。

  1. The NeuMF model

如上所述,NeuMF融合了兩個子網。GMF是矩陣分解的一種神經網絡模型,輸入是用戶和項目潛在因素的元素乘積。它由兩個神經層組成:
在這裏插入圖片描述
這個模型的另一個組件是MLP。爲了增加模型的靈活性,MLP子網不與GMF共享用戶和項嵌入。它使用用戶和項嵌入的連接作爲輸入。通過複雜的連接和非線性變換,它能夠模擬用戶和物品之間的複雜交互。更準確地說,MLP子網定義爲:
在這裏插入圖片描述
在這裏插入圖片描述
Fig. 1 Illustration of the NeuMF model

from d2l import mxnet as d2l

from mxnet import autograd, gluon, np, npx

from mxnet.gluon import nn

import mxnet as mx

import random

import sys

npx.set_np()

  1. Model
    Implementation

下面的代碼實現了NeuMF模型。它由一個廣義矩陣分解模型和一個具有不同用戶和項目嵌入向量的多層感知器組成。MLP的結構由參數nums_hiddens控制。ReLU用作默認激活功能。

class NeuMF(nn.Block):

def __init__(self, num_factors, num_users, num_items, nums_hiddens,

             **kwargs):

    super(NeuMF, self).__init__(**kwargs)

    self.P = nn.Embedding(num_users, num_factors)

    self.Q = nn.Embedding(num_items, num_factors)

    self.U = nn.Embedding(num_users, num_factors)

    self.V = nn.Embedding(num_items, num_factors)

    self.mlp = nn.Sequential()

    for num_hiddens in nums_hiddens:

        self.mlp.add(nn.Dense(num_hiddens, activation='relu',

                              use_bias=True))

def forward(self, user_id, item_id):

    p_mf = self.P(user_id)

    q_mf = self.Q(item_id)

    gmf = p_mf * q_mf

    p_mlp = self.U(user_id)

    q_mlp = self.V(item_id)

    mlp = self.mlp(np.concatenate([p_mlp, q_mlp], axis=1))

    con_res = np.concatenate([gmf, mlp], axis=1)

    return np.sum(con_res, axis=-1)
  1. Customized Dataset with Negative Sampling

對於成對排序損失,一個重要的步驟是負採樣。對於每個用戶,用戶未與之交互的項是候選項(未觀察到的條目)。下面的函數以用戶身份和候選項作爲輸入,並從每個用戶的候選項集中隨機抽取負項。在訓練階段,該模型確保用戶喜歡的項目的排名高於其不喜歡或未與之交互的項目。

class PRDataset(gluon.data.Dataset):

def __init__(self, users, items, candidates, num_items):

    self.users = users

    self.items = items

    self.cand = candidates

    self.all = set([i for i in range(num_items)])

def __len__(self):

    return len(self.users)

def __getitem__(self, idx):

    neg_items = list(self.all - set(self.cand[int(self.users[idx])]))

    indices = random.randint(0, len(neg_items) - 1)

    return self.users[idx], self.items[idx], neg_items[indices]
  1. Evaluator

在這一部分中,我們採用按時間分割的策略來構造訓練集和測試集。給定截線命中率的兩個評價指標ℓℓ (Hit@ℓHit@ℓ) and area
under the ROC curve (AUC),用ROC曲線下面積(AUC)評價模型的有效性。給定位置命中率ℓ,對於每個用戶,指示建議的項目是否包含在頂部ℓ排行榜。正式定義如下:
在這裏插入圖片描述
#@save

def hit_and_auc(rankedlist, test_matrix, k):

hits_k = [(idx, val) for idx, val in enumerate(rankedlist[:k])

          if val in set(test_matrix)]

hits_all = [(idx, val) for idx, val in enumerate(rankedlist)

            if val in set(test_matrix)]

max = len(rankedlist) - 1

auc = 1.0 * (max - hits_all[0][0]) / max if len(hits_all) > 0 else 0

return len(hits_k), auc

然後,總體命中率和AUC計算如下。

#@save

def evaluate_ranking(net, test_input, seq, candidates, num_users, num_items,

                 ctx):

ranked_list, ranked_items, hit_rate, auc = {}, {}, [], []

all_items = set([i for i in range(num_users)])

for u in range(num_users):

    neg_items = list(all_items - set(candidates[int(u)]))

    user_ids, item_ids, x, scores = [], [], [], []

    [item_ids.append(i) for i in neg_items]

    [user_ids.append(u) for _ in neg_items]

    x.extend([np.array(user_ids)])

    if seq is not None:

        x.append(seq[user_ids, :])

    x.extend([np.array(item_ids)])

    test_data_iter = gluon.data.DataLoader(gluon.data.ArrayDataset(*x),

                                           shuffle=False,

                                           last_batch="keep",

                                           batch_size=1024)

    for index, values in enumerate(test_data_iter):

        x = [gluon.utils.split_and_load(v, ctx, even_split=False)

             for v in values]

        scores.extend([list(net(*t).asnumpy()) for t in zip(*x)])

    scores = [item for sublist in scores for item in sublist]

    item_scores = list(zip(item_ids, scores))

    ranked_list[u] = sorted(item_scores, key=lambda t: t[1], reverse=True)

    ranked_items[u] = [r[0] for r in ranked_list[u]]

    temp = hit_and_auc(ranked_items[u], test_input[u], 50)

    hit_rate.append(temp[0])

    auc.append(temp[1])

return np.mean(np.array(hit_rate)), np.mean(np.array(auc))
  1. Training and Evaluating the Model

培訓功能定義如下。我們以成對的方式訓練模型。

#@save

def train_ranking(net, train_iter, test_iter, loss, trainer, test_seq_iter,

              num_users, num_items, num_epochs, ctx_list, evaluator,

              candidates, eval_step=1):

timer, hit_rate, auc = d2l.Timer(), 0, 0

animator = d2l.Animator(xlabel='epoch', xlim=[1, num_epochs], ylim=[0, 1],

                        legend=['test hit rate', 'test AUC'])

for epoch in range(num_epochs):

    metric, l = d2l.Accumulator(3), 0.

    for i, values in enumerate(train_iter):

        input_data = []

        for v in values:

            input_data.append(gluon.utils.split_and_load(v, ctx_list))

        with autograd.record():

            p_pos = [net(*t) for t in zip(*input_data[0:-1])]

            p_neg = [net(*t) for t in zip(*input_data[0:-2],

                                          input_data[-1])]

            ls = [loss(p, n) for p, n in zip(p_pos, p_neg)]

        [l.backward(retain_graph=False) for l in ls]

l += sum([l.asnumpy() for l in ls]).mean()/len(ctx_list)

        trainer.step(values[0].shape[0])

        metric.add(l, values[0].shape[0], values[0].size)

        timer.stop()

    with autograd.predict_mode():

        if (epoch + 1) % eval_step == 0:

            hit_rate, auc = evaluator(net, test_iter, test_seq_iter,

                                      candidates, num_users, num_items,

                                      ctx_list)

            animator.add(epoch + 1, (hit_rate, auc))

print('train loss %.3f, test hit rate %.3f, test AUC %.3f'

      % (metric[0] / metric[1], hit_rate, auc))

print('%.1f examples/sec on %s'

      % (metric[2] * num_epochs / timer.sum(), ctx_list))

現在,我們可以加載MovieLens
100k數據集並訓練模型。由於在MovieLens數據集中只有評級,但準確性會有所下降,因此我們將這些評級進行二值化,即0和1。如果一個用戶對一個項目進行評分,我們認爲隱含反饋爲1,否則爲零。給一個項目評分的行爲可以看作是一種提供隱性反饋的形式。在這裏,我們在seq感知模式下分割數據集,在這種模式下,用戶的最新交互項被排除在外進行測試。

batch_size = 1024

df, num_users, num_items = d2l.read_data_ml100k()

train_data, test_data = d2l.split_data_ml100k(df, num_users, num_items,

                                          'seq-aware')

users_train, items_train, ratings_train, candidates = d2l.load_data_ml100k(

train_data, num_users, num_items, feedback="implicit")

users_test, items_test, ratings_test, test_iter = d2l.load_data_ml100k(

test_data, num_users, num_items, feedback="implicit")

num_workers = 0 if sys.platform.startswith(“win”) else 4

train_iter = gluon.data.DataLoader(PRDataset(users_train, items_train,

                                         candidates, num_items ),

                               batch_size, True,

                               last_batch="rollover",

                               num_workers=num_workers)

然後我們創建並初始化模型。我們使用一個三層MLP,隱藏大小恆定爲10。

ctx = d2l.try_all_gpus()

net = NeuMF(10, num_users, num_items, nums_hiddens=[10, 10, 10])

net.initialize(ctx=ctx, force_reinit=True, init=mx.init.Normal(0.01))

下面的代碼訓練模型。

The following code trains the model.

lr, num_epochs, wd, optimizer = 0.01, 10, 1e-5, ‘adam’

loss = d2l.BPRLoss()

trainer = gluon.Trainer(net.collect_params(), optimizer,

                    {"learning_rate": lr, 'wd': wd})

train_ranking(net, train_iter, test_iter, loss, trainer, None, num_users,

          num_items, num_epochs, ctx, evaluate_ranking, candidates)

train loss 4.030, test hit rate 0.322, test AUC 0.736

13.1 examples/sec on [gpu(0), gpu(1)]
在這裏插入圖片描述
6. Summary

· Adding nonlinearity to matrix factorization model is beneficial for improving the model capability and effectiveness.

· NeuMF is a combination of matrix factorization and Multilayer perceptron. The multilayer perceptron takes the concatenation of user and item embeddings as the input.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章