基於深度學習的推薦系統

做者|James Loy 編譯|VK 來源|Towards Data Sciencehtml

傳統的推薦系統基於聚類、最近鄰和矩陣分解等方法。然而,近年來,深度學習在從圖像識別到天然語言處理等多個領域取得了巨大的成功。推薦系統也得益於深度學習的成功。事實上,現在最早進的推薦系統,好比Youtube和Amazon的推薦系統,都是由複雜的深度學習系統驅動的,而不是傳統方法。python

本教程

在閱讀了許多有用的教程,這些教程介紹了使用諸如矩陣分解等傳統方法的推薦系統的基礎知識,但我注意到,缺少介紹基於深度學習的推薦系統的教程。在本教程中,咱們將介紹如下內容:git

  • 如何使用PyTorch Lightning建立本身的基於深度學習的推薦系統github

  • 推薦系統中隱式反饋與顯式反饋的區別架構

  • 如何在不引入誤差和數據泄漏的狀況下訓練測試分割數據集以訓練推薦系統app

  • 評估推薦系統的指標(提示:準確度或RMSE不合適!)框架

數據集

本教程使用MovieLens 20M數據集提供的電影評論,這是一個流行的電影評分數據集,包含1995年至2015年收集的2000萬部電影評論。dom

若是你想查看本教程中的代碼,能夠查看個人Kaggle Notebook,在這裏你能夠運行代碼,並在本教程中查看輸出:https://www.kaggle.com/jamesloy/deep-learning-based-recommender-systems機器學習

利用隱式反饋構建推薦系統

在咱們創建模型以前,重要的是要理解隱式反饋和顯式反饋之間的區別,以及爲何現代推薦系統是創建在隱式反饋的基礎上的。ide

顯式反饋

在推薦系統中,顯式反饋是從用戶那裏收集的直接的、定量的數據。例如,亞馬遜容許用戶對購買的商品進行1-10的評分。這些評分是直接由用戶提供的,這個評分標準容許亞馬遜量化用戶的偏好。另外一個明確反饋的例子包括YouTube上的贊/踩按鈕,它捕捉用戶對特定視頻的明確偏好(即喜歡或不喜歡)。

然而,顯式反饋的問題是它們不多。若是你仔細想一想,你上一次點擊YouTube視頻上的「喜歡」按鈕,或者對你的網上購物進行評級是何時?極可能你在YouTube上觀看的視頻數量遠遠大於你明確評級的視頻數量。

隱性反饋

另外一方面,隱式反饋是從用戶交互中間接收集的,它們充當用戶偏好的代理。例如。你在YouTube上觀看的視頻被用做隱式反饋,爲你量身定作推薦,即便你沒有明確地給視頻打分。另外一個隱含反饋的例子包括你在亞馬遜上瀏覽過的商品,這些商品用來爲你推薦其餘相似的項目。

隱性反的優勢在於它是豐富的。使用隱式反饋構建的推薦系統還容許咱們經過每次點擊和交互實時定製推薦。今天,在線推薦系統是使用隱式反饋構建的,它容許系統在每次用戶交互時實時調整其推薦。

數據預處理

在開始構建和訓練咱們的模型以前,讓咱們作一些預處理,以得到所需格式的MovieLens數據。

爲了保持30%的數據在用戶可管理的範圍內使用,咱們將只使用30%的數據集。讓咱們隨機選擇30%的用戶,而且只使用所選用戶的數據。

import pandas as pd
import numpy as np
np.random.seed(123)

ratings = pd.read_csv('rating.csv', parse_dates=['timestamp'])

rand_userIds = np.random.choice(ratings['userId'].unique(), 
                                size=int(len(ratings['userId'].unique())*0.3), 
                                replace=False)

ratings = ratings.loc[ratings['userId'].isin(rand_userIds)]

過濾數據集以後,如今有來自41547個用戶的6027314行數據(這仍然是大量數據!)。數據幀中的每一行都對應於單個用戶的電影評論,以下所示。

訓練測試拆分

除了評級以外,還有一個時間戳列,顯示提交評審的日期和時間。使用timestamp列,咱們將使用留一法實現咱們的訓練測試分割策略。對於每一個用戶,最新的評分被用做測試集(即,測試集樣本數爲1),而其他的將用做訓練數據。

爲了說明這一點,用戶39849審查的電影以下所示。用戶評論的最後一部電影是2014年熱映的《銀河守護者》。咱們將使用這部電影做爲該用戶的測試數據,並將其他已審查的影片用做訓練數據。

在訓練和評估推薦系統時,常用這種訓練-測試分割策略。作一個隨機的分割是不公平的,由於咱們可能會使用用戶最近的評論進行訓練,而使用早期的評論進行測試。這就引入了具備前瞻性誤差的數據泄漏,而且訓練後的模型的性能不能歸納爲真實世界的性能。

下面的代碼將使用留一法將咱們的評分數據集分割爲一個訓練和測試集。

ratings['rank_latest'] = ratings.groupby(['userId'])['timestamp'].rank(method='first', ascending=False)

train_ratings = ratings[ratings['rank_latest'] != 1]
test_ratings = ratings[ratings['rank_latest'] == 1]

# 刪除咱們再也不須要的列
train_ratings = train_ratings[['userId', 'movieId', 'rating']]
test_ratings = test_ratings[['userId', 'movieId', 'rating']]

將數據集轉換爲隱式反饋數據集

如前所述,咱們將使用隱式反饋來訓練推薦系統。然而,咱們使用的MovieLens數據集是基於顯式反饋的。要將此數據集轉換爲隱式反饋數據集,咱們只需將評級進行二進制化並將其轉換爲「1」(即正類)。值「1」表示用戶已與該項交互。

須要注意的是,使用隱式反饋能夠從新定義咱們的推薦者試圖解決的問題。咱們不是試圖在使用顯時反饋時預測電影收視率,而是試圖預測用戶是否會與每部電影互動(即點擊/購買/觀看),目的是向用戶展現具備最高交互可能性的電影。

train_ratings.loc[:, 'rating'] = 1

不過,咱們如今確實有問題。在對數據集進行二進制化以後,咱們看到數據集中的每一個樣本如今都屬於正類。咱們假設其他的電影是那些用戶不感興趣的電影-即便這是一個普遍的假設,可能不是真的,它一般是至關好的實踐。

下面的代碼爲每行數據生成4個負樣本。換句話說,陰性樣本與陽性樣本的比率是4:1。這個比例是任意選擇的,但我發現它在實踐中運行得至關好(你能夠本身找到最好的比率!)。

# 獲取全部電影id的列表
all_movieIds = ratings['movieId'].unique()

# 用於保存訓練數據的佔位符
users, items, labels = [], [], []

# 這是每一個用戶都與之交互的項目集
user_item_set = set(zip(train_ratings['userId'], train_ratings['movieId']))

# 4:1 
num_negatives = 4

for (u, i) in user_item_set:
    users.append(u)
    items.append(i)
    labels.append(1) # 用戶與項目有交互
    for _ in range(num_negatives):
        # 隨機選擇一個項目
        negative_item = np.random.choice(all_movieIds) 
        # 檢查用戶是否與該項目進行了交互
        while (u, negative_item) in user_item_set:
            negative_item = np.random.choice(all_movieIds)
        users.append(u)
        items.append(negative_item)
        labels.append(0) # 表明沒有交互

太好了!咱們如今有了模型所需格式的數據。在繼續以前,讓咱們定義一個PyTorch數據集,以便於訓練。下面的類簡單地將上面編寫的代碼封裝到PyTorch數據集類中。

import torch
from torch.utils.data import Dataset

class MovieLensTrainDataset(Dataset):
    """MovieLens PyTorch數據集用於訓練
    
    Args:
        ratings (pd.DataFrame): 包含電影評級的DataFrame
        all_movieIds (list): 包含全部電影id的列表
    
    """

    def __init__(self, ratings, all_movieIds):
        self.users, self.items, self.labels = self.get_dataset(ratings, all_movieIds)

    def __len__(self):
        return len(self.users)
  
    def __getitem__(self, idx):
        return self.users[idx], self.items[idx], self.labels[idx]

    def get_dataset(self, ratings, all_movieIds):
        users, items, labels = [], [], []
        user_item_set = set(zip(ratings['userId'], ratings['movieId']))

        num_negatives = 4
        for u, i in user_item_set:
            users.append(u)
            items.append(i)
            labels.append(1)
            for _ in range(num_negatives):
                negative_item = np.random.choice(all_movieIds)
                while (u, negative_item) in user_item_set:
                    negative_item = np.random.choice(all_movieIds)
                users.append(u)
                items.append(negative_item)
                labels.append(0)

        return torch.tensor(users), torch.tensor(items), torch.tensor(labels)

咱們的模型-神經協同過濾(NCF)

雖然有許多基於深度學習的推薦系統架構,可是我發現由He等人(https://arxiv.org/abs/1708.05031)提出的框架。是最直接的,它很是簡單,能夠在這樣的教程中實現。

用戶嵌入

在深刻研究模型的體系結構以前,讓咱們先熟悉一下嵌入的概念。嵌入是一個低維空間,它從高維空間捕獲向量之間的關係。爲了更好地理解這個概念,讓咱們更仔細地研究一下用戶嵌入。

假設咱們想根據用戶對兩種類型電影的偏好來表明他們——動做片和浪漫片。讓第一個維度是用戶對動做電影的喜好程度,第二個維度是用戶對浪漫電影的喜好程度。

如今,假設Bob是咱們的第一個用戶。鮑勃喜歡動做片,但不喜歡愛情片。爲了將Bob表示爲二維向量,咱們根據Bob的偏好將其放置在圖中。

咱們的下一個用戶是喬。喬是動做片和愛情片的超級粉絲。咱們用一個二維向量來表示Joe,就像Bob同樣。

這個二維空間被稱爲嵌入。本質上,嵌入減小了咱們的用戶,使他們能夠在一個低維空間中以有意義的方式表示。在這種嵌入中,具備類似電影偏好的用戶彼此靠近,反之亦然。

固然,咱們並不侷限於僅使用二維來表示咱們的用戶。咱們可使用任意數量的維度來表示咱們的用戶。更大數量的維度將容許咱們更準確地捕捉每一個用戶的特徵,而代價是模型的複雜性。在咱們的代碼中,咱們將使用8個維度(稍後將看到)。

學習嵌入

相似地,咱們將使用一個單獨的項目嵌入層來表示項目(即電影)在低維空間中的特徵。

你可能會想知道,咱們如何瞭解嵌入層的權重,以便它提供用戶和項目的準確表示?在前面的示例中,咱們使用了Bob和Joe對動做和浪漫電影的偏好來手動建立嵌入。有沒有辦法自動學習這種嵌入?

答案是協同過濾——經過使用分級數據集,咱們能夠識別類似的用戶和電影,建立從現有評級中學習到的用戶和項目嵌入。

模型體系結構

既然咱們對嵌入有了更好的理解,咱們就能夠定義模型體系結構了。正如你將看到的,用戶和項嵌入是模型的關鍵。

讓咱們使用如下訓練示例來瀏覽模型體系結構:

模型的輸入是userId=3和movieId=1的one-hot編碼用戶和項向量。由於這是一個正樣本(用戶實際評級的電影),因此標籤是1。

用戶向量和項目向量分別被輸入到用戶嵌入和項目嵌入中,從而獲得更小、更密集的用戶和項目向量。

嵌入的用戶和項目向量在經過一系列徹底鏈接的層以前被鏈接起來,這些層將鏈接的嵌入映射到一個預測向量中做爲輸出。在輸出層,咱們應用一個Sigmoid函數來得到最可能類。在上面的例子中,因爲0.8>0.2,最有可能的類是1(正類)。

如今,讓咱們用PyTorch Lightning來定義這個NCF模型!

import torch.nn as nn
import pytorch_lightning as pl
from torch.utils.data import DataLoader

class NCF(pl.LightningModule):
    """ 神經協同過濾(NCF)
    
        Args:
            num_users (int): 惟一用戶的數量
            num_items (int): 惟一項的數量
            ratings (pd.DataFrame): 包含用於訓練的電影評級
            all_movieIds (list): 包含全部movieIds的列表(訓練+測試)
    """
    
    def __init__(self, num_users, num_items, ratings, all_movieIds):
        super().__init__()
        self.user_embedding = nn.Embedding(num_embeddings=num_users, embedding_dim=8)
        self.item_embedding = nn.Embedding(num_embeddings=num_items, embedding_dim=8)
        self.fc1 = nn.Linear(in_features=16, out_features=64)
        self.fc2 = nn.Linear(in_features=64, out_features=32)
        self.output = nn.Linear(in_features=32, out_features=1)
        self.ratings = ratings
        self.all_movieIds = all_movieIds
        
    def forward(self, user_input, item_input):
        
        # 經過嵌入層
        user_embedded = self.user_embedding(user_input)
        item_embedded = self.item_embedding(item_input)

        # Concat兩個嵌入層
        vector = torch.cat([user_embedded, item_embedded], dim=-1)

        # 經過全鏈接層
        vector = nn.ReLU()(self.fc1(vector))
        vector = nn.ReLU()(self.fc2(vector))

        # 輸出層
        pred = nn.Sigmoid()(self.output(vector))

        return pred
    
    def training_step(self, batch, batch_idx):
        user_input, item_input, labels = batch
        predicted_labels = self(user_input, item_input)
        loss = nn.BCELoss()(predicted_labels, labels.view(-1, 1).float())
        return loss

    def configure_optimizers(self):
        return torch.optim.Adam(self.parameters())

    def train_dataloader(self):
        return DataLoader(MovieLensTrainDataset(self.ratings, self.all_movieIds),
                          batch_size=512, num_workers=4)

讓咱們用GPU訓練咱們的NCF模型,epoch=5

注意:PyTorch Lightning與vanilla PyTorch相比的一個優點是,你不須要編寫本身的訓練代碼。注意Trainer類是如何讓咱們只須要幾行代碼就能夠訓練咱們的模型。

num_users = ratings['userId'].max()+1
num_items = ratings['movieId'].max()+1
all_movieIds = ratings['movieId'].unique()

model = NCF(num_users, num_items, train_ratings, all_movieIds)

trainer = pl.Trainer(max_epochs=5, gpus=1, reload_dataloaders_every_epoch=True,
                     progress_bar_refresh_rate=50, logger=False, checkpoint_callback=False)

trainer.fit(model)

評估咱們的推薦系統

如今咱們已經訓練出了模型,咱們準備使用測試數據來評估它。在傳統的機器學習項目中,咱們使用諸如準確性(對於分類問題)和RMSE(對於迴歸問題)這樣的度量來評估咱們的模型。然而,這樣的度量對於評估推薦系統來講過於簡單。

爲了設計一個好的評價推薦系統的指標,咱們首先須要瞭解現代推薦系統是如何使用的。

看看Netflix,咱們能夠看到以下推薦列表:

一樣,亞馬遜給出:

這裏的關鍵是咱們不須要用戶與推薦列表中的每一項進行交互。至少咱們須要用戶與列表中的一個項目進行交互,至少咱們須要與該項目進行交互。

爲了模擬這一點,讓咱們運行下面的評估協議,爲每一個用戶生成一個前10個推薦項的列表。

  • 對於每一個用戶,隨機選擇99個用戶沒有交互的項目。

  • 將這99個項目與測試項目(用戶最後一次交互的實際項目)結合起來。咱們如今有100件。

  • 對這100個項目運行模型,並根據它們的預測機率對它們進行排序。

  • 從100個項目列表中選擇前10個項目。若是測試項出如今前10項中,那麼咱們認爲這是命中。

  • 對全部用戶重複此過程。命中率就是平均命中率。

這種評估協議稱爲命中率@10Hit Ratio @ 10),一般用於評估推薦系統。

命中率@10

如今,讓咱們使用所描述的協議來評估咱們的模型。

# 用於測試的用戶-項目對
test_user_item_set = set(zip(test_ratings['userId'], test_ratings['movieId']))

# 每一個用戶與之交互的全部條目
user_interacted_items = ratings.groupby('userId')['movieId'].apply(list).to_dict()

hits = []
for (u,i) in test_user_item_set:
    interacted_items = user_interacted_items[u]
    not_interacted_items = set(all_movieIds) - set(interacted_items)
    selected_not_interacted = list(np.random.choice(list(not_interacted_items), 99))
    test_items = selected_not_interacted + [i]
    
    predicted_labels = np.squeeze(model(torch.tensor([u]*100), 
                                        torch.tensor(test_items)).detach().numpy())
    
    top10_items = [test_items[i] for i in np.argsort(predicted_labels)[::-1][0:10].tolist()]
    
    if i in top10_items:
        hits.append(1)
    else:
        hits.append(0)
        
print("The Hit Ratio @ 10 is {:.2f}".format(np.average(hits)))

咱們有至關不錯的命中率@10!從上下文來看,這意味着86%的用戶被推薦了他們最終交互的實際項目(在10個項目列表中)。不錯!

下一步

我但願這是一個有用的介紹,以建立一個基於深度學習的推薦系統。要了解更多信息,我建議使用如下資源:

原文連接:https://towardsdatascience.com/deep-learning-based-recommender-systems-3d120201db7e

歡迎關注磐創AI博客站: http://panchuang.net/

sklearn機器學習中文官方文檔: http://sklearn123.com/

歡迎關注磐創博客資源彙總站: http://docs.panchuang.net/

相關文章
相關標籤/搜索