07-02 基於協同過濾的推薦算法

更新、更全的《機器學習》的更新網站,更有python、go、數據結構與算法、爬蟲、人工智能教學等着你:http://www.javashuo.com/article/p-vozphyqp-cm.htmlpython

基於協同過濾的推薦算法

本推薦系統採用中等大小的MovieLens數據集,該數據集包含6000多用戶對4000多部電影的100萬條評分。該數據集是一個評分數據集,用戶能夠給電影評5個不一樣等級的分數(1~5分)。本篇文章主要研究隱反饋數據集中的TopN推薦問題,所以將會忽略數據集中的評分記錄。也就是說,TopN推薦的任務是預測用戶會不會對某部電影評分,而不是預測用戶在準備對某部電影評分的前提下對電影評多少分。算法

1、實驗設計——訓練集M折交叉驗證

from numpy import random


def split_data(data, M, k, seed):
    """
    切割訓練集,防止過擬合
    data: 訓練集
    M: 切割訓練集的份數 
    k: 測試集的索引
    seed: 隨機數種子
    """
    test = []
    train = []
    random.seed(seed)

    for user, item in data:
        if random.randint(0, M) == k:
            test.append([user, item])
        else:
            train.append([user, item])

    return train, test

2、評測指標

2.1 準確率/召回率

對用戶\(u\)推薦\(N\)個物品,記做\(R(u)\),用戶\(u\)在測試集上喜歡的物品集合爲\(T(u)\),可使用準確率/召回率評測推薦算法的精度。數據結構

準確率公式爲:
\[ \text{Precision}=\frac{\sum_{u\in{U}}|R(u)\bigcap{T(u)}|}{\sum_{u\in{U}}|R(u)|} \]
其中\(R(u)\)是用戶在訓練集上的行爲給用戶做出的推薦列表。app

召回率公式爲:
\[ \text{Recall}=\frac{\sum_{u\in{U}}|R(u)\bigcap{T(u)}|}{\sum_{u\in{U}}|T(u)|} \]
其中\(T(u)\)是用戶在測試集上的行爲給用戶做出的推薦列表。dom

準確率描述最終的推薦列表中有多少比例是發生過的用戶-物品評分記錄;召回率描述有多少比例的用戶-物品評分記錄包含在最終的推薦列表中。機器學習

def recall_(train, test, N):
    """計算召回率"""
    hit = 0
    all_ = 0
    
    for user in train.keys():
        tu = test[user]
        rank = get_recommendation(user, N) 
        for item, pui in rank:
            if item in tu:
                hit += 1
        all_ += len(tu)

    return hit/(all_*1.)


def precision(train, test, N):
    """計算準確率"""
    hit = 0
    all_ = 0 
    for user in train.keys():
        tu = test[user]     
        rank = get_recommendation(user, N)     
        for item, pui in rank:
            if item in tu:
                hit += 1
        all_ += N

    return hit/(all_*1.)

2.2 覆蓋率

覆蓋率反映了推薦算法發掘長尾的能力,覆蓋率越高,說明推薦算法越可以將長尾中的物品推薦給用戶。學習

\[ \text{Coverate}=\frac{|\bigcup_{u\in{U}}R(u)|}{|I|} \]測試

def coverage(train, test, N):
    """計算覆蓋率"""
    recommend_items = set()
    all_items = set()

    for user in train.keys():
        for item in train[user].keys():
            all_items.add(item)
            
        rank = get_recommendation(user, N)
        for item, pui in rank:
            recommend_items.add(item)

    return len(recommend_items)/len((all_items)*1.)

2.3 新穎度

若是推薦出的物品都很熱門,說明推薦的新穎度較低;不然說明推薦結果比較新穎。網站

def popularity(train, test, N):
    """計算新穎度"""
    item_popularity = dict()
    for user, items in train.items():
        for item in items.keys():
            if item not in item_popularity:
                item_popularity[item] = 0
            item_popularity[item] += 1

    ret = 0
    n = 0
    for user in train.keys():
        rank = get_recommendation(user, N)
        for item, pui in rank:
            # 物品的流行度分佈知足長尾分佈,對流行度取對數後,流行度的平均值更穩定
            ret += math.log(1+item_popularity[item])
            n += 1

    ret /= n*1.

    return ret

3、基於領域的算法

基於領域的算法分爲兩大類,一類是基於用戶的協同過濾算法,另外一類是基於物品的協同過濾算法。

3.1 基於用戶的協同過濾算法

在一個基於用戶的協同過濾算法的在線個性化推薦系統中,當一個用戶A須要個性化推薦時,通常須要如下兩個步驟:

  1. 先找到和他有類似興趣的其餘用戶(找到和目標用戶興趣類似的用戶集合)
  2. 把其餘用戶喜歡的且用戶A沒有據說過的物品推薦給用戶A(找到這個集合中的用戶喜歡的且目標用戶沒有據說過的物品推薦給目標用戶)

對於步驟1,咱們須要計算兩個用戶的興趣類似程度,協同過濾算法主要利用用戶的行爲的類似度計算用戶間興趣的類似度,可使用如下兩種方法計算用戶間的興趣類似度(其中\(u\)\(v\)表示不一樣的用戶,\(N(u)\)表示用戶\(u\)曾經擁有正反饋的物品集合;\(N(v)\)表示用戶\(v\)曾經擁有正反饋的物品集合。):

  1. Jaccard公式:
    \[ w_{uv}=\frac{|N(u)\bigcap{N(v)}|}{|N(u)\bigcup{N(v)}|} \]

  2. 餘弦類似度:
    \[ w_{uv}=\frac{|N(u)\bigcap{N(v)}|}{\sqrt{|N(u)||{N(v)}|}} \]

3.1.1 UserCF推薦算法

用戶行爲記錄舉例

假設上圖爲某個網站用戶行爲記錄,UserCF(基於用戶的協同過濾算法)時使用餘弦類似度計算用戶A和用戶B的興趣類似度爲:
\[ w_{AB} = \frac{|\{a,b,d\}\bigcap{\{a,c\}|}}{\sqrt{|\{a,b,d\}||\{a,c\}|}} = \frac{1}{\sqrt{6}} \]

同理咱們能夠計算出用戶A和用戶C、D的類似度爲:
\[ \begin{aligned} & w_{AC} = \frac{|\{a,b,d\}\bigcap{\{b,e\}|}}{\sqrt{|\{a,b,d\}||\{b,e\}|}} = \frac{1}{\sqrt{6}} \\ & w_{AD} = \frac{|\{a,b,d\}\bigcap{\{c,d,e\}|}}{\sqrt{|\{a,b,d\}||\{c,d,e\}|}} = \frac{1}{\sqrt{9}} = \frac{1}{3} \end{aligned} \]

def user_similarity(train):
    """計算用戶間的餘弦類似度"""   
    W = dict()
    for u in train.keys():
        for v in train.keys():
            if u == v:
                continue
            W[u][v] = len(train[u] & train[v])
            W[u][v] /= math.sqrt(len(train[u])*len(train[v])*1.)

    return W

對於上面的餘弦類似度計算,有着很大的計算開銷。由於不少用戶之間並無對一樣的物品產生過行爲,即有時候\(|N(u)\bigcap{N(v)}|=0\).所以咱們能夠考慮首先計算\(|N(u)\bigcap{N(v)}|\neq0\)的用戶對\((u,v)\),而後再對這些用戶進行餘弦類似度計算,即下面改進的餘弦類似度計算。

改進的餘弦類似度計算,須要如下三個步驟:

  1. 構建物品到用戶的倒排表,即鍵值對爲{物品:對該物品產生行爲的用戶列表}。
  2. 令稀疏矩陣\(C[u][v]=|N(u)\bigcap{N(v)}|\)。假設用戶\(u\)和用戶\(v\)同時屬於倒排表中\(K\)個物品對應的用戶列表,則會有\(C[u][v]=K\)。所以能夠掃描倒排表中每一個物品對應你的用戶列表,將用戶表中的兩兩用戶對應的\(C[u][v]\)加1.
  3. 計算\(|N(u)\bigcap{N(v)}|\neq0\)用戶\(u\)和用戶\(v\)之間的餘弦類似度。

物品-用戶倒排表

def user_similarity(train):
    """計算用戶之間的類似度"""
    # 構建物品-用戶的倒排表
    item_users = dict()
    for u, items in train.items():
        for i in items.keys():
            if i not in item_users:
                item_users[i] = set()
            item_users[i].add(u)

    # 計算用戶之間的共同有過行爲的物品
    C = dict()
    N = dict()
    for i, users in item_users.items():
        for u in users:
            N[u] += 1
            for v in users:
                if u == v:
                    continue
                C[u][v] = +=1

    # 計算最後的餘弦類似度矩陣W
    W = dict()
    for u, related_users in C.items():
        for v, cuv in related_users.items():
            W[u][v] = cuv/math.sqrt(N[u]*N[v])

    return W
File "<ipython-input-8-8d19b8833b0f>", line 19
    C[u][v] = +=1
               ^
SyntaxError: invalid syntax

經過上述獲得用戶之間的興趣類似度以後,UserCF算法會給用戶推薦和他興趣最類似的K個用戶喜歡的物品。而且能夠經過下述公式計算用戶\(u\)對物品\(i\)的感興趣程度:
\[ p(u,i) = \sum_{v\in{S}(u,K)\bigcap{N(i)}}w_{uv}r_{vi} \]
其中\(S(u,K)\)包含和用戶\(u\)興趣最接近的\(K\)個用戶,\(N(i)\)是對物品\(i\)有過行爲的用戶集合,\(w_{uv}\)是用戶\(u\)和用戶\(v\)的興趣類似度,\(r_{vi}\)表明用戶\(v\)對物品\(i\)的興趣,由於使用的是單一行爲的隱反饋數據,所以全部的\(r_{vi}=1\)

def recommend(user, train, W):
    """實現UserCF算法"""
    rank = dict()
    interacted_items = train[user]
    for v, wuv in sorted(W[u].items, key=itemgetter(1), reverse=True)[0:K]:
        for i, rvi in train[v].items():
            if i in interacted_items:
                # 在繼續以前咱們應該篩選用戶間的行爲
            rank[i] += wuv*rvi

    return rank

3.1.2 User-IIF推薦算法

若是兩個用戶都買過《新華字典》,並不能證實兩我的興趣類似,由於中國絕大多數人都買過《新華字典》,可是兩個用戶都買過冷門商品,如《機器學習》,則能夠認爲兩個用戶的興趣類似。所以咱們能夠經過以下公式計算用戶間的興趣類似度:
\[ w_{uv}=\frac{\sum_{i\in{N(u)}\bigcap{N(v)}}\frac{1}{\log{(1+|N(i)|)}}}{\sqrt{|N(u)||N(v)|}} \]
其中\(\frac{1}{\log{(1+|N(i)|)}}\)懲罰了用戶\(u\)和用戶\(v\)共同興趣列表中熱門物品對他們類似度的影響。

基於上述用戶間興趣類似度則能夠改造UserCF算法爲User-IIF算法。

def user_similarity(train):
    """計算用戶之間的類似度"""
    # 構建物品-用戶的倒排表
    item_users = dict()
    for u, items in train.items():
        for i in items.keys():
            if i not in item_users:
                item_users[i] = set()
            item_users[i].add(u)

    # 計算用戶之間的共同有過行爲的物品
    C = dict()
    N = dict()
    for i, users in item_users.items():
        for u in users:
            N[u] += 1
            for v in users:
                if u == v:
                    continue
                C[u][v] = +=1/math.log(1+len(users))

    # 計算最後的餘弦類似度矩陣W
    W = dict()
    for u, related_users in C.items():
        for v, cuv in related_users.items():
            W[u][v] = cuv/math.sqrt(N[u]*N[v])

    return W

3.2 基於物品的協同過濾算法

基於物品的協同過濾算法的推薦系統主要分爲兩步:

  1. 計算物品之間的類似度
  2. 根據物品的類似度和用戶的歷史行爲給用戶生成推薦列表

因爲基於物品的協同過濾算法的思想是——購買了該商品的用戶也會常常購買其餘的商品。根據這句話,能夠用下面的公式定義物品的類似度:
\[ w_{ij} = \frac{|N(i)\bigcap{N(j)|}}{|N(j)|} \]
其中\(N(i)\)是喜歡物品\(i\)的用戶數,\(|N(i)\bigcap{N(j)|}\)是同時喜歡物品\(i\)和物品\(j\)的用戶數。

對於上述簡單的公式,若是物品\(j\)很熱門,則該公式會形成任何物品都會和熱門的物品有很大的類似度,這對於致力於挖掘長尾信息的推薦系統來講不是一個好的特性,所以能夠把上述公式改形成:
\[ w_{ij} = \frac{|N(i)\bigcap{N(j)}|}{\sqrt{|N(i)||N(j)|}} \]
其中\(\sqrt{|N(i)||N(j)|}\)至關於懲罰了物品\(j\)的權重,減輕了熱門物品會和不少物品類似的可能性。

3.3 ItemCF算法

ItemCF算法的思想是,假設每一個用戶的興趣都侷限在某幾個方面。若是兩個物品屬於一個用戶的興趣列表,那麼這兩個物品可能就屬於有限的幾個領域;若是兩個物品屬於不少用戶的興趣列表,則它們可能就屬於同一個領域,於是有很大的類似度。

ItemCF算法相似於UserCF算法,也須要創建用戶-物品倒排表,創建該表流程以下:

  1. 先對每一個用戶創建一個包含他喜歡物品的列表
  2. 對於每一個用戶,將他物品列表中的物品兩兩在共現矩陣C中加1

計算物品類似度

import math


def item_similarity(train):
    """計算物品間的類似度"""
    # 計算用戶共同擁有的物品
    C = dict()
    N = dict()
    for u, items in train.items():
        for i in items:
            try:
                N[i] += 1
            except:
                N[i] = 1
            for j in items:
                if i == j:
                    continue
                try:
                    try:
                        if not isinstance(C[i], dict):
                            C[i] = dict()
                    except:
                        C[i] = dict()
                    C[i][j] += 1
                except:
                    C[i][j] = 1

    # 計算最後的類似矩陣W
    W = dict()
    for i, related_items in C.items():
        for j, cij in related_items.items():
            W[i] = dict()
            W[i][j] = cij / math.sqrt(N[i]*N[j])

    return W


train = {'A': ['a', 'b', 'd'], 'B': ['b', 'c', 'e'], 'C': [
    'c', 'd'], 'd': ['b', 'c', 'd'], 'e': ['a', 'd']}

W = item_similarity(train)
相關文章
相關標籤/搜索