協同過濾在推薦系統中的應用

1.概述

前面的博客介紹過如何構建一個推薦系統,以及簡要的介紹了協同過濾的實現。本篇博客,筆者將介紹協同過濾在推薦系統的應用。推薦系統是大數據和機器學習中最多見、最容易理解的應用之一。其實,在平常的生活當中,咱們會頻繁的遇到推薦的場景 ,好比你在電商網站購買商品、使用視頻App觀看視頻、在手機上下載各類遊戲等,這些都是使用了推薦技術來個性化你想要的內容和物品。html

2.內容

本篇博客將經過如下方式來介紹,經過創建協同過濾模型,利用訂單數據來想用戶推薦預期的物品。步驟以下:算法

  • 轉換和規範化數據
  • 訓練模型
  • 評估模型性能
  • 選擇最佳模型

2.1 技術選型

完成本篇博客所須要的技術使用Python和機器學習Turicreate來實現。Python所須要的依賴庫以下:app

  • pandas和numpy:用於操做數據
  • turicreate:用於進行模型選擇與評估
  • sklearn:用於對數據進行封裝,包括迴歸、降維、分類、聚類等。

2.2 加載數據

本次演示的數據源,包含以下:機器學習

  • customer_id.csv:列出1000個客戶ID做爲輸出推薦;
  • customer_data.csv:物品數據源集。

加載Python依賴庫,實現代碼以下:ide

import pandas as pd
import numpy as np
import time
import turicreate as tc
from sklearn.model_selection import train_test_split

查看數據集,實現代碼以下:函數

customers = pd.read_csv('customer_id.csv') 
transactions = pd.read_csv('customer_data.csv')
print(customers.head())
print(transactions.head())

預覽結果以下:oop

2.3 數據準備

將上述csv中的數據集中,將products列中的每一個物品列表分解成行,並計算用戶購買的產品數量。性能

2.3.1 使用用戶、物品和目標字段建立數據

  • 此表將做爲稍後建模的輸入
  • 在本次案例中,使用customerId、productId和purchase_count字段

實現代碼以下:學習

transactions['products'] = transactions['products'].apply(lambda x: [int(i) for i in x.split('|')])
data = pd.melt(transactions.set_index('customerId')['products'].apply(pd.Series).reset_index(), 
             id_vars=['customerId'],
             value_name='products') \
    .dropna().drop(['variable'], axis=1) \
    .groupby(['customerId', 'products']) \
    .agg({'products': 'count'}) \
    .rename(columns={'products': 'purchase_count'}) \
    .reset_index() \
    .rename(columns={'products': 'productId'})
data['productId'] = data['productId'].astype(np.int64)
print(data.shape)
print(data.head())

預覽截圖以下:測試

2.3.1 建立虛擬對象

  • 標識用戶是否購買該商品的虛擬人;
  • 若是一我的購買了一個物品,那麼標記purchase_dummy字段爲值爲1;
  • 可能會有疑問,爲何須要建立一個虛擬人而不是將其規範化,對每一個用戶的購買數量進行規範化是不可行的,由於用戶的購買頻率在現實狀況中可能不同;可是,咱們能夠根據全部用戶的購買頻率對商品進行規範化。

實現代碼以下:

def create_data_dummy(data):
    data_dummy = data.copy()
    data_dummy['purchase_dummy'] = 1
    return data_dummy
data_dummy = create_data_dummy(data)
print(data_dummy.head())

預覽結果以下:

 2.3.2 規範化物品

  • 咱們經過首先建立一個用戶矩陣來規範每一個用戶的購買頻率。

實現代碼以下:

df_matrix = pd.pivot_table(data, values='purchase_count', index='customerId', columns='productId')
print(df_matrix.head())

預覽結果以下:

 矩陣規範化實現代碼以下:

df_matrix_norm = (df_matrix-df_matrix.min())/(df_matrix.max()-df_matrix.min())
print(df_matrix_norm.head())

預覽結果以下:

建立一個表做爲模型的輸入,實現代碼以下:

d = df_matrix_norm.reset_index() 
d.index.names = ['scaled_purchase_freq']
data_norm = pd.melt(d, id_vars=['customerId'], value_name='scaled_purchase_freq').dropna()
print(data_norm.shape)
print(data_norm.head())

預覽結果以下:

上述步驟能夠組合成下面定義的函數,實現代碼以下 :

def normalize_data(data):
    df_matrix = pd.pivot_table(data, values='purchase_count', index='customerId', columns='productId')
    df_matrix_norm = (df_matrix-df_matrix.min())/(df_matrix.max()-df_matrix.min())
    d = df_matrix_norm.reset_index()
    d.index.names = ['scaled_purchase_freq']
    return pd.melt(d, id_vars=['customerId'], value_name='scaled_purchase_freq').dropna()

上面,咱們規範化了用戶的購買歷史記錄,從0到1(1是一個物品的最多購買次數,0是該物品的0個購買計數)。

2.4 拆分用於訓練用的數據集

  • 將數據分割成訓練集和測試集是評估預測建模的一個重要部分,在這種狀況下使一個協做過濾模型。經過,咱們使用較大部分的數據用於訓練,而較小的部分用於測試;
  • 咱們將訓練集和測試集佔比拆分爲80% : 20%;
  • 訓練部分將用於開發預測模型,而另一部分用於評估模型的性能。

拆分函數實現以下:

def split_data(data):
    '''
    Splits dataset into training and test set.
    
    Args:
        data (pandas.DataFrame)
        
    Returns
        train_data (tc.SFrame)
        test_data (tc.SFrame)
    '''
    train, test = train_test_split(data, test_size = .2)
    train_data = tc.SFrame(train)
    test_data = tc.SFrame(test)
    return train_data, test_data

如今咱們有了是三個數據集,分別是購買計數、購買虛擬數據和按比例的購買計數,這裏咱們將每一個數據集分開進行建模,實現代碼以下:

train_data, test_data = split_data(data)
train_data_dummy, test_data_dummy = split_data(data_dummy)
train_data_norm, test_data_norm = split_data(data_norm)
print(train_data)

這裏打印訓練結果數據,預覽結果以下:

2.5 使用Turicreate庫來構建模型

在運行更加複雜的方法(好比協同過濾)以前,咱們應該運行一個基線模型來比較和評估模型。因爲基線一般使用一種很是簡單的方法,所以若是在這種方法以外使用的技術顯示出相對較好的準確性和複雜性,則應該選擇這些技術。

Baseline Model是機器學習領域的一個術語,簡而言之,就是使用最廣泛的狀況來作結果預測。好比,猜硬幣遊戲,最簡單的策略就是一直選擇正面或者反面,這樣從預測的模型結果來看,你是有50%的準確率的。

一種更復雜可是更常見的預測購買商品的方法就是協同過濾。下面,咱們首先定義要在模型中使用的變量,代碼以下:

# constant variables to define field names include:
user_id = 'customerId'
item_id = 'productId'
users_to_recommend = list(customers[user_id])
n_rec = 10 # number of items to recommend
n_display = 30 # to display the first few rows in an output dataset

Turicreate使咱們很是容易去調用建模技術,所以,定義全部模型的函數以下:

def model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display):
    if name == 'popularity':
        model = tc.popularity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target)
    elif name == 'cosine':
        model = tc.item_similarity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target, 
                                                    similarity_type='cosine')
    elif name == 'pearson':
        model = tc.item_similarity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target, 
                                                    similarity_type='pearson')
    recom = model.recommend(users=users_to_recommend, k=n_rec)
    recom.print_rows(n_display)
    return model

2.5.1 使用Popularity Model做爲Baseline

  • Popularity Model採用最受歡迎的物品進行推薦,這些物品在用戶中銷量是最高的;
  • 訓練數據用於模型選擇。

購買計數實現代碼以下:

name = 'popularity'
target = 'purchase_count'
popularity = model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(popularity)

截圖以下:

購買虛擬人代碼以下:

name = 'popularity'
target = 'purchase_dummy'
pop_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(pop_dummy)

截圖以下:

 按比例購買計數實現代碼以下:

name = 'popularity'
target = 'scaled_purchase_freq'
pop_norm = model(train_data_norm, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(pop_norm)

截圖以下:

2.6 協同過濾模型

根據用戶如何在協做購買物品的基礎上推薦類似的物品。例如,若是用戶1和用戶2購買了相似的物品,好比用戶1購買的X、Y、Z,用戶2購買了X、Y、Y,那麼咱們能夠向用戶2推薦物品Z。

2.6.1 原理

  • 建立一個用戶-物品矩陣,其中索引值表示惟一的用戶ID,列值表示惟一的物品ID;
  • 建立類似矩陣,這個做用是用於計算一個物品和另一個物品的類似度,這裏咱們使用餘弦類似度或者皮爾森類似度。
  1. 要計算物品X和物品Y之間的類似性,須要查看對這兩個物品進行評級的全部用戶,例如,用戶1和用戶2都對物品X和Y進行了評級
  2. 而後,咱們在(用戶1,用戶2)的用戶空間中建立兩個物品向量,V1表示物品X,V2表示物品Y,而後找出這些向量之間的餘弦值。餘弦值爲1的零角度或者重疊向量表示徹底類似(或者每一個用戶,全部物品都有相同的評級),90度的角度意味着餘弦爲0或者沒有類似性。

2.6.2 餘弦類似度

公式以下:

 

 

 購買計數代碼以下:

name = 'cosine'
target = 'purchase_count'
cos = model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(cos)

截圖以下:

 

 購買虛擬人代碼以下:

name = 'cosine'
target = 'purchase_dummy'
cos_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(cos_dummy)

截圖以下:

 

 按比例購買計數,實現代碼以下:

name = 'cosine' 
target = 'scaled_purchase_freq' 
cos_norm = model(train_data_norm, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(cos_norm)

截圖以下:

 2.6.3 皮爾森類似度

  • 類似性是兩個向量之間的皮爾遜係數。

  • 計算公式以下:

 購買計數實現代碼:

name = 'pearson'
target = 'purchase_count'
pear = model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(pear)

截圖以下:

 購買虛擬人實現代碼:

name = 'pearson'
target = 'purchase_dummy'
pear_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(pear_dummy)

截圖以下:

 按比例購買計數:

name = 'pearson'
target = 'scaled_purchase_freq'
pear_norm = model(train_data_norm, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
print(pear_norm)

截圖以下:

 2.7 模型訓練

在評價推薦引擎時,咱們可使用RMSE和精準召回的概念。

  • RMSE(Root Mean Squared Errors)
  1. 測量預測值的偏差;
  2. RMSE值越小,結果越好。
  • 召回
  1. 用戶購買的物品中實際推薦的比例是多少;
  2. 若是一個用戶購買了5種物品,而推薦列表決定展現其中的3種,那麼召回率爲60%。
  • 準確率
  1. 在全部推薦的物品中,有多少用戶真正喜歡;
  2. 若是向用戶推薦了5種物品,而用戶購買了其中的4種,那麼準確率爲80%。

爲什麼召回和準確度如此重要呢?

  • 考慮一個案例,咱們推薦全部的物品。這樣咱們的用戶必定會涵蓋他們喜歡和購買的物品。這種狀況下,咱們的召回率爲100%,這樣是否意味着咱們的模型是最好的呢?
  • 咱們必須考慮準確率,若是咱們推薦300件物品,但用戶喜歡,並且購買了3件,那麼準確率是1%,這個很是低的準確率代表,儘管他們的召回率很高,可是這個模型並非很好。
  • 所以,咱們最終的目標是優化召回率和準確率,讓他們儘量的接近1。

下面,咱們爲模型求值建立初識可調用變量,實現代碼以下:

models_w_counts = [popularity, cos, pear]
models_w_dummy = [pop_dummy, cos_dummy, pear_dummy]
models_w_norm = [pop_norm, cos_norm, pear_norm]
names_w_counts = ['Popularity Model on Purchase Counts', 'Cosine Similarity on Purchase Counts', 'Pearson Similarity on Purchase Counts']
names_w_dummy = ['Popularity Model on Purchase Dummy', 'Cosine Similarity on Purchase Dummy', 'Pearson Similarity on Purchase Dummy']
names_w_norm = ['Popularity Model on Scaled Purchase Counts', 'Cosine Similarity on Scaled Purchase Counts', 'Pearson Similarity on Scaled Purchase Counts']

而後,讓咱們比較一下咱們基於RMSE和精準召回特性構建的全部模型,代碼以下:

eval_counts = tc.recommender.util.compare_models(test_data, models_w_counts, model_names=names_w_counts)
eval_dummy = tc.recommender.util.compare_models(test_data_dummy, models_w_dummy, model_names=names_w_dummy)
eval_norm = tc.recommender.util.compare_models(test_data_norm, models_w_norm, model_names=names_w_norm)

評估結果輸出以下:

3.總結

  • 協同過濾:咱們能夠看到,協同過濾算法比Popularity Model更加適合購買數量。實際上,Popularity Model並無提供任何個性化設置,由於它只向每一個用戶提供相同的推薦項目列表;
  • 精準召回:綜上所述,咱們能夠看到購買數量 > 購買虛擬 > 標準化購買計數的精準率和召回率。然而,因爲標準化購買數據的推薦分數爲0且不變,因此咱們選擇了虛擬的,實際上,虛擬模型和標準模型化數據模型的RMSE差異不大;
  • RMSE:因爲使用皮爾森類似度的RMSE比餘弦類似度結果高,因此咱們選擇較小的均方偏差模型,在這種狀況下,就是選擇餘弦類似度模型。

完成實例代碼以下:

import pandas as pd
import numpy as np
import time
import turicreate as tc
from sklearn.model_selection import train_test_split

customers = pd.read_csv('customer_id.csv') 
transactions = pd.read_csv('customer_data.csv')
# print(customers.head())
# print(transactions.head())
transactions['products'] = transactions['products'].apply(lambda x: [int(i) for i in x.split('|')])
data = pd.melt(transactions.set_index('customerId')['products'].apply(pd.Series).reset_index(), 
             id_vars=['customerId'],
             value_name='products') \
    .dropna().drop(['variable'], axis=1) \
    .groupby(['customerId', 'products']) \
    .agg({'products': 'count'}) \
    .rename(columns={'products': 'purchase_count'}) \
    .reset_index() \
    .rename(columns={'products': 'productId'})
data['productId'] = data['productId'].astype(np.int64)
# print(data.shape)
# print(data.head())

def create_data_dummy(data):
    data_dummy = data.copy()
    data_dummy['purchase_dummy'] = 1
    return data_dummy
data_dummy = create_data_dummy(data)
# print(data_dummy.head())

df_matrix = pd.pivot_table(data, values='purchase_count', index='customerId', columns='productId')
# print(df_matrix.head())

df_matrix_norm = (df_matrix-df_matrix.min())/(df_matrix.max()-df_matrix.min())
# print(df_matrix_norm.head())

# create a table for input to the modeling  
d = df_matrix_norm.reset_index() 
d.index.names = ['scaled_purchase_freq']
data_norm = pd.melt(d, id_vars=['customerId'], value_name='scaled_purchase_freq').dropna()
# print(data_norm.shape)
# print(data_norm.head())

def normalize_data(data):
    df_matrix = pd.pivot_table(data, values='purchase_count', index='customerId', columns='productId')
    df_matrix_norm = (df_matrix-df_matrix.min())/(df_matrix.max()-df_matrix.min())
    d = df_matrix_norm.reset_index()
    d.index.names = ['scaled_purchase_freq']
    return pd.melt(d, id_vars=['customerId'], value_name='scaled_purchase_freq').dropna()

def split_data(data):
    '''
    Splits dataset into training and test set.
    
    Args:
        data (pandas.DataFrame)
        
    Returns
        train_data (tc.SFrame)
        test_data (tc.SFrame)
    '''
    train, test = train_test_split(data, test_size = .2)
    train_data = tc.SFrame(train)
    test_data = tc.SFrame(test)
    return train_data, test_data

train_data, test_data = split_data(data)
train_data_dummy, test_data_dummy = split_data(data_dummy)
train_data_norm, test_data_norm = split_data(data_norm)
# print(train_data)

# constant variables to define field names include:
user_id = 'customerId'
item_id = 'productId'
users_to_recommend = list(customers[user_id])
n_rec = 10 # number of items to recommend
n_display = 30 # to display the first few rows in an output dataset

def model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display):
    if name == 'popularity':
        model = tc.popularity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target)
    elif name == 'cosine':
        model = tc.item_similarity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target, 
                                                    similarity_type='cosine')
    elif name == 'pearson':
        model = tc.item_similarity_recommender.create(train_data, 
                                                    user_id=user_id, 
                                                    item_id=item_id, 
                                                    target=target, 
                                                    similarity_type='pearson')
    recom = model.recommend(users=users_to_recommend, k=n_rec)
    recom.print_rows(n_display)
    return model

name = 'popularity'
target = 'purchase_count'
popularity = model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(popularity)

name = 'popularity'
target = 'purchase_dummy'
pop_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(pop_dummy)

name = 'popularity'
target = 'scaled_purchase_freq'
pop_norm = model(train_data_norm, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(pop_norm)

name = 'cosine'
target = 'purchase_count'
cos = model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(cos)

name = 'cosine'
target = 'purchase_dummy'
cos_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(cos_dummy)

name = 'cosine' 
target = 'scaled_purchase_freq' 
cos_norm = model(train_data_norm, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(cos_norm)

name = 'pearson'
target = 'purchase_count'
pear = model(train_data, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(pear)

name = 'pearson'
target = 'purchase_dummy'
pear_dummy = model(train_data_dummy, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(pear_dummy)

name = 'pearson'
target = 'scaled_purchase_freq'
pear_norm = model(train_data_norm, name, user_id, item_id, target, users_to_recommend, n_rec, n_display)
# print(pear_norm)

models_w_counts = [popularity, cos, pear]
models_w_dummy = [pop_dummy, cos_dummy, pear_dummy]
models_w_norm = [pop_norm, cos_norm, pear_norm]
names_w_counts = ['Popularity Model on Purchase Counts', 'Cosine Similarity on Purchase Counts', 'Pearson Similarity on Purchase Counts']
names_w_dummy = ['Popularity Model on Purchase Dummy', 'Cosine Similarity on Purchase Dummy', 'Pearson Similarity on Purchase Dummy']
names_w_norm = ['Popularity Model on Scaled Purchase Counts', 'Cosine Similarity on Scaled Purchase Counts', 'Pearson Similarity on Scaled Purchase Counts']

eval_counts = tc.recommender.util.compare_models(test_data, models_w_counts, model_names=names_w_counts)
eval_dummy = tc.recommender.util.compare_models(test_data_dummy, models_w_dummy, model_names=names_w_dummy)
eval_norm = tc.recommender.util.compare_models(test_data_norm, models_w_norm, model_names=names_w_norm)

# Final Output Result 
# final_model = tc.item_similarity_recommender.create(tc.SFrame(data_dummy), user_id=user_id, item_id=item_id, target='purchase_dummy', similarity_type='cosine')
# recom = final_model.recommend(users=users_to_recommend, k=n_rec)
# recom.print_rows(n_display)

# df_rec = recom.to_dataframe()
# print(df_rec.shape)
# print(df_rec.head())
View Code

4.結束語

這篇博客就和你們分享到這裏,若是你們在研究學習的過程中有什麼問題,能夠加羣進行討論或發送郵件給我,我會盡我所能爲您解答,與君共勉!

另外,博主出書了《Kafka並不難學》和《Hadoop大數據挖掘從入門到進階實戰》,喜歡的朋友或同窗, 能夠在公告欄那裏點擊購買連接購買博主的書進行學習,在此感謝你們的支持。關注下面公衆號,根據提示,可免費獲取書籍的教學視頻。

相關文章
相關標籤/搜索