基於用戶最近鄰模型的協同過濾算法的Python代碼實現

          最近看了幾篇研究最大信息係數的文章,最大信息係數是一種度量兩個變量之間關聯關係的新方法。傳統度量兩個變量之間關係的指標包括皮爾遜相關係數和信息論中的互信息。皮爾遜相關係數在度量具有線性相關關係的兩個變量時具有較好的效果,但若兩個變量之間的關係不是線性時,不能進行準確地度量。互信息是信息論的鼻祖香農老先生在研究通信理論時引入的,後來研究者發現互信息這個度量在研究兩個變量之間的關係具有非常強的作用,在統計學界廣受歡迎。

    本文在這裏不去研究以上三個度量之間的區別。受傳統用戶最近鄰模型在計算用戶相似度時通常使用皮爾遜相似度計算方法啓發,我想在我的研究中使用最大信息係數來計算用戶之間的相關度,不過計算最大信息係數的過程還沒完全理解透。幸好已經有前輩大牛在網上公開了各種代碼。但是其中的Matlab代碼一直是有問題的,Python代碼是可以跑通的。因此本人在網上找了找Python做最近鄰模型的協同過濾的代碼也找到了一個前輩的代碼(點擊打開鏈接)。不過感覺這個結果跟我以前做的結果有點不同,仔細研讀了幾天,發現有幾點與我的理解不同,這裏將我的代碼附上。說明:由於我對Python的語法很不熟悉所以在讀取數據和最後輸出格式方面幾乎是複製這位前輩的代碼。不過核心算法部分有兩處改動,下面一一說明(涉及到另外一位作者的代碼時,本文以原代碼代替)。

    1.在利用皮爾遜相關係數計算公式計算用戶相似度時,原代碼在計算用戶的的評分均值是調用計算用戶評分均值公式進行計算;然而計算用戶相似度時針對的是兩個用戶都評價過的項目,故而此時的用戶的評分均值應該是在用戶對評分交集項目上取均值。文獻[1]中的原文爲"All the summations and averages in the formula are computed only over those articles that u1 and u2 both rated."。

    2.在取前N個最近鄰居進行預測評分時,原代碼直接根據相似度大小選擇最相近的N個鄰居;然而常用的做法是將活躍用戶u的最近鄰居按相似度大小進行排序Nu,並獲取訓練集中評價過目標項目m的所有用戶Nm,對NuNm取交集並取前N項得到,若不足N項則直接取交集。

       3.寫此篇博客純爲探討交流,歡迎討論。

    附上代碼。

#-------------------------------------------------------------------------------
# Name:        PearsonUserNeighCF
# Purpose:     Personalized Recommendation
#
# Author:      Jinkun Wang
# Email:       [email protected], if you have any question about the
#              code, please do not hesitate to contact me.
#
# Created:     10/09/2014
# Copyright:   (c) Jinkun Wang 2014
#-------------------------------------------------------------------------------
from math import sqrt
import numpy as np
import matplotlib.pyplot as plt

def loadData():
    trainSet = {}
    testSet = {}
    movieUser = {}
    u2u = {}

    TrainFile = 'ml-100k/u1.base'   #指定訓練集
    TestFile = 'ml-100k/u1.test'    #指定測試集

    #加載訓練集,生成電影用戶的倒排序表 movieUser
    for line in open(TrainFile):
        (userId, itemId, rating, _) = line.strip().split('\t')
        trainSet.setdefault(userId,{})
        trainSet[userId].setdefault(itemId,float(rating))
        movieUser.setdefault(itemId,[])
        movieUser[itemId].append(userId.strip())

    #防止測試集有訓練集中沒有出現過的項目
    item_in_train = []
    for m in movieUser.keys():
        item_in_train.append(m)

    #加載測試集
    for line in open(TestFile):
        (userId, itemId, rating, _) = line.strip().split('\t')
        testSet.setdefault(userId,{})
        testSet[userId].setdefault(itemId,float(rating))

    return trainSet,testSet,movieUser,item_in_train

#計算一個用戶的平均評分
def getAverageRating(user):
    average = (sum(trainSet[user].values()) * 1.0) / len(trainSet[user].keys())
    return average

#計算用戶相似度
def UserSimPearson(trainSet):
    userSim = {}
    for u1 in trainSet.keys():
        userSim.setdefault(u1,{})
        u1_rated = trainSet[u1].keys()
        for u2 in trainSet.keys():
            userSim[u1].setdefault(u2,0)
            if u1 != u2:
                u2_rated = trainSet[u2].keys()
                co_rated = list(set(u1_rated).intersection(set(u2_rated)))
                if co_rated == []:
                    userSim[u1][u2] = 0
                else:
                    num = 0     #皮爾遜計算公式的分子部分
                    den1 = 0    #皮爾遜計算公式的分母部分1
                    den2 = 0    #皮爾遜計算公式的分母部分2
                    sigma_u1_m = 0  #計算用戶u1對共同評價項目的評分均值
                    sigma_u2_m = 0  #計算用戶u2對共同評價項目的評分均值
                    for m in co_rated:
                        sigma_u1_m += trainSet[u1][m]
                        sigma_u2_m += trainSet[u2][m]
                    ave_u1_m = sigma_u1_m / len(co_rated)
                    ave_u2_m = sigma_u2_m / len(co_rated)

                    for m in co_rated:
                        num += (trainSet[u1][m] - ave_u1_m) * (trainSet[u2][m] - ave_u2_m) * 1.0
                        den1 += pow(trainSet[u1][m] - ave_u1_m, 2) * 1.0
                        den2 += pow(trainSet[u2][m] - ave_u2_m, 2) * 1.0
                    den1 = sqrt(den1)
                    den2 = sqrt(den2)
                    if den1 == 0 or den2 ==0 :
                        userSim[u1][u2] = 0
                    else:
                        userSim[u1][u2] = num / (den1 * den2)
            else:
                userSim[u1][u2] = 0
    return userSim

#對用戶相似度表進行排序處理
def sortSimMatrix(userSim):
    neighSorted = {}
    for u in userSim.keys():
        neigh_sorted = sorted(userSim[u].items(), key = lambda x:x[1], reverse = True)
        for key, value in neigh_sorted:
            neighSorted.setdefault(u,[])
            neighSorted[u].append(key)
    return neighSorted

#尋找用戶最近鄰並生成推薦結果;與測試集比較獲得算法的準確度
def getAccuracyMetric(N,trainSet,testSet,movieUser,neighSorted, userSim, item_in_train):
    #尋找用戶最近鄰並生成推薦結果
    pred = {}
    for user, item in testSet.items():    #對測試集中的每個用戶
        pred.setdefault(user,{})    #生成用戶User的預測空列表
        ave_u_rating = getAverageRating(user)
        neigh_uninterseced = neighSorted[user] #獲取用戶user的鄰居集合(已按相似度大小降序排列)
        for m in item.keys():
            if m not in item_in_train:
                pred[user][m] = ave_u_rating
            else:
                rated_m_user = movieUser[m]         #測試集中評價過電影m的用戶
                neigh_intersected = sorted(rated_m_user,key = lambda x:neigh_uninterseced.index(x))
                if len(neigh_intersected) > N:
                    neigh = neigh_intersected[0:N]
                else:
                    neigh = neigh_intersected
                neighRating = 0
                neighSimSum = 0
                for neighUser in neigh:
                    neighRating += (trainSet[neighUser][m] - getAverageRating(neighUser)) * userSim[user][neighUser]
                    neighSimSum += abs(userSim[user][neighUser])
                if neighSimSum == 0:
                    pred[user][m] = ave_u_rating
                else:
                    pred[user][m] = ave_u_rating + (neighRating * 1.0) / neighSimSum

    #與測試集比較獲得算法的準確度
    mae = 0
    rmse = 0
    error_sum = 0
    sqrError_sum = 0
    setSum = 0
    for user,item in pred.items():
        for m in item.keys():
            error_sum += abs(pred[user][m] - testSet[user][m])
            sqrError_sum += pow(pred[user][m] - testSet[user][m],2)
            setSum += 1
    mae = error_sum / setSum
    rmse = sqrt(sqrError_sum / setSum)
    return mae, rmse

if __name__ == '__main__':

    print '正在加載數據...'
    trainSet,testSet,movieUser,item_in_train = loadData()

    print '正在計算用戶間相似度...'
    userSim = UserSimPearson(trainSet)

    print '對相似度列表按相似度大小進行排列...'
    neighSorted = sortSimMatrix(userSim)

    print '正在尋找最近鄰...'
    NeighborSize = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
    MAE = []
    RMSE = []
    for N in NeighborSize:            #對不同的近鄰數
        mae, rmse = getAccuracyMetric(N,trainSet,testSet,movieUser,neighSorted, userSim, item_in_train)   #獲得算法推薦精度
        MAE.append(mae)
        RMSE.append(rmse)
    plt.subplot(211)
    plt.plot(NeighborSize,MAE)
    plt.xlabel('NeighborSize')
    plt.ylabel('Mean Absolute Error')
    plt.title('Pearson User Neighbor Model Collaborative Filtering')

    plt.subplot(212)
    plt.plot(NeighborSize,RMSE)
    plt.xlabel('NeighborSize')
    plt.ylabel('Root Mean Square Error')
    plt.title('Pearson User Neighbor Model Collaborative Filtering')

    plt.show()
    raw_input('按任意鍵繼續...')


[1] Resnick P, Iacovou N, Suchak M, et al. GroupLens: an open architecture for collaborative filtering of netnews[C]//Proceedings of the 1994 ACM conference on Computer supported cooperative work. ACM, 1994: 175-186.


發佈了9 篇原創文章 · 獲贊 11 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章