足夠詳細、足夠簡單的 Python 版推薦系統入門級—理論篇(下)|8月更文挑戰

代碼實現

import numpy as np
import pandas as pd
複製代碼

咱們將研究 MovieLens 數據集,基於並建立模型用於向用戶推薦電影。這個數據是由明尼蘇達大學的 GroupLens 研究項目收集的。該數據集能夠從這裏下載。在這個數據集包括了來自 943 個用戶對 1682 部電影的 100,000 個評分(1-5)。用戶的信息包括年齡、性別、職業等信息。html

導入數據集

首先,咱們將導入咱們的標準庫並在 Python 中讀取數據集。這個數據集包含1682部電影的屬性。其中有 24 列,最後 19 列指定了某部電影的類型。這些都是二進制的列,也就是說,數值爲 1 表示電影屬於該類型,不然爲 0。python

數據集已經被 GroupLens 分爲訓練數據集和測試數據集兩部分,其中測試數據數據集,對每一個用戶有10個評分,即總共有9,430行。咱們將把這兩個文件讀入咱們的Python環境。web

r_cols = ['user_id','movie_id','rating','unix_timestamp']
ratings_train = pd.read_csv('data/ml-100k/ua.base',sep='\t',names=r_cols,encoding='latin-1')
複製代碼
ratings_train.head()
複製代碼
user_id movie_id rating unix_timestamp
0 1 1 5 874965758
1 1 2 3 876893171
2 1 3 4 878542960
3 1 4 3 876893119
4 1 5 3 889751712
ratings_test = pd.read_csv('data/ml-100k/ua.test',sep='\t',names=r_cols,encoding='latin-1')
複製代碼
ratings_test.shape,ratings_train.shape
複製代碼
((9430, 4), (90570, 4))
複製代碼
ratings_test.head()
複製代碼
user_id movie_id rating unix_timestamp
0 1 20 4 887431883
1 1 33 4 878542699
2 1 61 4 878542420
3 1 117 3 874965739
4 1 155 2 878542201

建立協同過濾模型

生成評分矩陣

協同過濾計算都是基於評分矩陣接下來咱們就要利用上面 DataFrame 數據來生成而一個 2 維評分矩陣,行是用戶列是電影,數值是用戶對電影評分,沒有評分的用 0 來填充。算法

rating_df = ratings_train.pivot(index='user_id', columns='movie_id', values='rating').fillna(0)
複製代碼
data_matrix = rating_df.to_numpy()
複製代碼
data_matrix.shape
複製代碼
(943, 1680)
複製代碼
計算用戶到用戶、項目到項目的類似性

如今計算出類似度,可使用 sklearn 提供的 pairwise_distance 函數來計算餘弦類似度。數組

from sklearn.metrics.pairwise import pairwise_distances 
user_similarity = pairwise_distances(data_matrix, metric='cosine')
item_similarity = pairwise_distances(data_matrix.T, metric='cosine')
複製代碼
實現預測方法

這就給了咱們數組形式的項目-項目和用戶-用戶的類似性。下一步是根據這些類似性來進行預測。讓咱們定義一個函數來作這件事。markdown

def predict(ratings, similarity, type='user'):
    if type == 'user':
        mean_user_rating = ratings.mean(axis=1)
        #We use np.newaxis so that mean_user_rating has same format as ratings
        ratings_diff = (ratings - mean_user_rating[:, np.newaxis])
        pred = mean_user_rating[:, np.newaxis] + similarity.dot(ratings_diff) / np.array([np.abs(similarity).sum(axis=1)]).T
    elif type == 'item':
        pred = ratings.dot(similarity) / np.array([np.abs(similarity).sum(axis=1)])
    return pred
複製代碼
user_prediction = predict(data_matrix, user_similarity, type='user')
item_prediction = predict(data_matrix, item_similarity, type='item')
複製代碼
user_prediction
複製代碼
array([[ 1.81349209,  0.70700463,  0.61698708, ...,  0.39276591,
         0.39226752,  0.39200766],
       [ 1.49898583,  0.34098239,  0.18310294, ..., -0.08555358,
        -0.08404725, -0.08377072],
       [ 1.51740786,  0.29296796,  0.15029285, ..., -0.12609608,
        -0.12431009, -0.12410346],
       ...,
       [ 1.36707183,  0.23452991,  0.09185339, ..., -0.17162167,
        -0.17056838, -0.17063272],
       [ 1.54450965,  0.36817316,  0.25677137, ..., -0.01115452,
        -0.01046112, -0.01008175],
       [ 1.59370125,  0.45494826,  0.37321426, ...,  0.14561441,
         0.14514214,  0.14528961]])
複製代碼

矩陣分解(MF)

接下來經過一個例子來理解矩陣分解。考慮一個由不一樣用戶給不一樣電影的用戶-電影評分矩陣。因式分解咱們你們都並不陌生,在初中時候我接觸過,app

x 2 1 = 0 ( x 1 ) ( x + 1 ) = 0 x^2 -1 =0 \rightarrow (x-1)(x+1) = 0

因式分解的目的就是便於簡化dom

這裏評分矩陣是一個稀疏的矩陣,所謂稀疏矩陣就是矩陣上大部分位置都是 0 ,其實這是由於一般用戶只會看到一小部分的電影。經過矩陣分解將這個稀疏矩陣爲 0 位置的值進行預測,來填充那些缺失的評分。使用矩陣分解法,能夠找到一些潛在的特徵,咱們一般由於這個潛在的特徵存在於潛在空間,用戶和項目均可以經過隱含特徵來共存在同一個潛在空間,他們具備了共同的隱含特徵,方便咱們來計算他們之間類似性。基於這些潛在的特徵能夠推測用戶如何對電影進行評分。分解後的矩陣相乘須要能夠還原原有矩陣。機器學習

在評分矩陣中,行是一個一個用戶經過 User Id 來區分不一樣用戶,而項目是列,經過 Item Id 來區分不一樣項目。經過矩陣分解將一個用戶-項目的評分矩陣分解爲兩個矩陣,而後經過這兩個矩陣能夠返回一個矩陣,這樣作的好處,獲得矩陣的用戶和項目交叉的位置會有數值,也就是函數

一般潛在特徵 k 維度要遠遠小於用戶維度 M 或者商品維度 N,能夠把咱們的評級矩陣 R ( M × N ) R_{(M \times N)} 分紅 P M × K P_{M \times K} Q ( N × K ) Q_{(N \times K)} ,這樣 P x Q T P x Q^T (這裏 Q T Q^T 是 Q 矩陣的轉置)就接近於R矩陣。

R = P Σ Q T R = P \Sigma Q^T
  • M 用戶的數量
  • N 項目(電影)的數量
  • K 隱含空間特徵數量
  • R M × N R_{M \times N} 用戶-電影的評分矩陣
  • P M × K P_{M \times K} 用戶特徵矩陣表示每一個用戶隱含特徵
  • Q N × K Q_{N \times K} 項目特徵矩陣表示每一個項目隱含特徵
  • Σ k × k \Sigma_{k \times k} 對角特徵權重矩陣表示特徵

經過矩陣分解能夠消除數據中的噪音。一般會移除哪些那些和用戶評價電影關係不大的特徵,其實也就是將如今用戶對物品的評分數值(標量)分解爲兩個向量,這兩個向量分別是用戶 p u k p_{uk} 和項目的向量 q i k q_{ik} 。能夠計算兩個向量的點積就是用戶對項目的具體評分值。

r u i = k = 1 k p u k σ q i k r_{ui} = \sum_{k=1}^k p_{uk}\sigma q_{ik}

當一個新的用戶進入系統,而且給電影進行評分,那麼這些新增用戶行爲數據如何添加到這個矩陣來更新既有的矩陣。當新增用戶都某一個商品進行評價,這是對角線矩陣 Σ \Sigma 不會發生變化,項目-特徵矩陣,惟一變換的是用戶-特徵的變換。

R = P Σ Q T R Q = P Σ Q T Q Q T Q = 1 R Q = P Σ R Q Σ 1 = P R = P\Sigma Q^T\\ RQ = P\Sigma Q^TQ\\ Q^TQ = 1\\ RQ=P\Sigma\\ RQ\Sigma^{-1}=P
  • 在等號兩次都乘以矩陣 Q Q
  • 由於 Q Q 矩陣是正交矩陣,獲得 Q T Q = 1 Q^TQ = 1 因此有 R Q = P Σ RQ = P\Sigma
  • 從而得出 $$

因此當一個矩陣 Q 發生變化了,咱們就能夠經過上面公式來計算出 P 矩陣。同理在 P 矩陣發生變換了同時能夠更新 Q 矩陣。還有就是將R矩陣分解爲P和Q,咱們還須要讓 P 和 Q 矩陣相乘後造成矩陣越接近 R 矩陣就越好。可使用梯度降低算法來作這件事。目標函數最小化實際評分和和 P和Q 估計的評分之間的平方偏差

e u i 2 = ( r u i r ^ u i ) 2 = ( r u i k = 1 K p u k σ k q k i ) 2 e_{ui}^2 = (r_{ui} - \hat{r}_{ui})^2 = (r_{ui} - \sum_{k=1}^K p_{uk}\sigma_k q_{ki})^2
  • e u i e_{ui} 是偏差值,u 表示用戶下標而 i 表示物品的下標
  • r u i r_{ui} 是實際 u 用戶對 i 項目的評分
  • r ^ u i \hat{r}_{ui} 是經過矩陣分解對 u 用戶對 i 商品的預測值
( e u i 2 ) p u k = 2 ( r u i r ^ u i ) q k i = 2 e u i q k i ( e u i 2 ) q k i = 2 ( r u i r ^ u i ) p u k = 2 e u i p u k \frac{\partial (e_{ui}^2)}{\partial p_{uk}} = -2(r_{ui} - \hat{r}_{ui})q_{ki} = -2e_{ui}q_{ki}\\ \frac{\partial (e_{ui}^2)}{\partial q_{ki}} = -2(r_{ui} - \hat{r}_{ui})p_{uk} = -2e_{ui}p_{uk}\\

有了損失函數,咱們就能夠用梯度降低來更新 q k i q_{ki} p u k p_{uk}

p u k = p u k = a ( e u i 2 ) p u k = p u k + 2 e u i q k i q k i = q k i = a ( e u i 2 ) q k i = p u k + 2 e u i p u k p^{\prime}_{uk} = p_{uk} = a^*\frac{\partial (e_{ui}^2)}{\partial p_{uk}} = p_{uk} + 2 e_{ui}q_{ki}\\ q^{\prime}_{ki} = q_{ki} = a^*\frac{\partial (e_{ui}^2)}{\partial q_{ki}} = p_{uk} + 2 e_{ui}p_{uk}\\

這裏 α \alpha 是學習率,用於肯定每次更新參數的步伐大小。上述更新能夠重複進行,直到偏差最小化,來實現訓練過程。

class MF():

    
    # 初始化用戶-電影評分矩陣,這裏隱含特徵,以及 alpha 和 beta 參數 
    def __init__(self, R, K, alpha, beta, iterations):
        self.R = R
        self.num_users, self.num_items = R.shape
        self.K = K
        self.alpha = alpha
        self.beta = beta
        self.iterations = iterations

    # 初始化用戶-特徵和電影-特徵矩陣
    def train(self):
        self.P = np.random.normal(scale=1./self.K, size=(self.num_users, self.K))
        self.Q = np.random.normal(scale=1./self.K, size=(self.num_items, self.K))

        # Initializing the bias terms 初始化偏置
        self.b_u = np.zeros(self.num_users)
        self.b_i = np.zeros(self.num_items)
        self.b = np.mean(self.R[np.where(self.R != 0)])

        # List of training samples 
        self.samples = [
            (i, j, self.R[i, j])
            for i in range(self.num_users)
            for j in range(self.num_items)
            if self.R[i, j] > 0
        ]

        # 在指定迭代過程當中隨機梯度降低
        training_process = []
        for i in range(self.iterations):
            np.random.shuffle(self.samples)
            self.sgd()
            mse = self.mse()
            training_process.append((i, mse))
            if (i+1) % 20 == 0:
                print("Iteration: %d ; error = %.4f" % (i+1, mse))

        return training_process

    # 計算批量的 MSE
    def mse(self):
        xs, ys = self.R.nonzero()
        predicted = self.full_matrix()
        error = 0
        for x, y in zip(xs, ys):
            error += pow(self.R[x, y] - predicted[x, y], 2)
        return np.sqrt(error)

    # 隨機梯度降低來對 P 和 Q 矩陣進行優化
    def sgd(self):
        for i, j, r in self.samples:
            prediction = self.get_rating(i, j)
            e = (r - prediction)

            self.b_u[i] += self.alpha * (e - self.beta * self.b_u[i])
            self.b_i[j] += self.alpha * (e - self.beta * self.b_i[j])

            self.P[i, :] += self.alpha * (e * self.Q[j, :] - self.beta * self.P[i,:])
            self.Q[j, :] += self.alpha * (e * self.P[i, :] - self.beta * self.Q[j,:])

    # j 獲取 i 用戶對 j 電影的評價
    def get_rating(self, i, j):
        prediction = self.b + self.b_u[i] + self.b_i[j] + self.P[i, :].dot(self.Q[j, :].T)
        return prediction

    # 填充用戶-電影評分矩陣
    def full_matrix(self):
        return mf.b + mf.b_u[:,np.newaxis] + mf.b_i[np.newaxis:,] + mf.P.dot(mf.Q.T)
複製代碼

評估推薦系統引擎

你們可能更專一模型定義,構建過程和其背後的算法,而對如何評價一個模型的好壞還不太瞭解,不瞭解各類評估模型的指標,以及他們都表明什麼以及如何使用。對一些機器學習或者深度學習任務,接受任務後設定一個實現指標顯得比較重要。

其實要訓練出一個好的模型,首先要知道什麼樣模型纔是好的模型。這樣就須要經過一些指標來真正反映模型好壞。也是今天重點的內容。

真實值\預測值 T F
T TP FN
F FP TN
  • TP(True Positive) 真正類 測試集真實標籤爲 T, 預測值也爲 T的總數
  • FN(False Negative) 漏報 測試集真實標籤爲 F,測試集卻爲 F 的總數
  • FP(False Positive) 誤報 測試集真實標籤爲 F,測試集卻爲 T 的總數
  • TN(True Negative) 真負類

準確率

準確率(Accuracy):全部正確分類的樣本與總樣本數比例 準確度是正確預測和總數的比值,從混淆矩陣中,TP 和 TN 之和就是正確的預測數。 A c c = N p r e d N t o t a l Acc = \frac{N_{pred} }{N_{total}}

N p r e d = T P + T N N_{pred} = TP + TN N t o t a l = T P + T N + F P + F N N_{total} = TP + TN + FP + FN

下面的精準度和召回率有點繞,可是並不難只要你們留心而後在本身作點練習就可以很好理解和運用這兩個指標來衡量模型。

精準度(Precision)

精準度(Precision):就是咱們預測爲正樣本中有多少是正確的機率 正確類數和真正類數與漏報數之和的比值,

P r e c i s i o n = T P T P + F P Precision = \frac{TP}{TP + FP}

召回率(recall)

也叫查全率,反映正樣本被預測爲正的比例。

R e c a l l = T P T P + F N Recall = \frac{TP}{TP+FN}

召回率體現了分類模型H對正樣本的識別能力,recall 越高,說明模型對正樣本的識別能力越強。

假設有 20 任務其中 10 個被按時完成,10 個逾期完成

  • 第 1 種
預測完成任務 預測逾期完成任務
實際按時完成任務 2 8
實際逾期完成任務 0 10
名稱
準確率 (2+10)/20 = 0.6
精準度 2/(2+0) = 1
召回率 2/(2+8) = 0.2
  • 第 2 種預測狀況
預測完成任務 預測逾期完成任務
實際按時完成任務 10 0
實際逾期完成任務 10 0
名稱
準確率 10/20 = 0.5
精準率 10/(10+10) = 0.5
召回率 10/(2+8) = 1

咱們看到雖然召回率是 100% 可是精準率確很低只有 50% 也就是咱們在作題時候所有背注一擲壓一個選擇。

R e c a l l = t p t p + f n Recall = \frac{tp}{tp + fn}
相關文章
相關標籤/搜索