集體智慧編程——協同過濾推薦算法-Python實現

本系列文章爲集體智慧編程讀書筆記,本人將自己讀書的心得體會和根據書中內容編寫的代碼放在博客中,供大家參考。代碼中根據個人體會寫了較爲詳細的中文註釋,僅供大家參考。代碼本人都運行過,如有問題歡迎交流。

首先介紹幾個知識點:

  • 相似性度量方法
    1.歐幾里得距離:該距離只有當兩者特徵向量中每個特徵都較小時,特徵向量間距離才比較小。
    2.皮爾森相關係數:該係數度量兩個特徵向量之間的線性相關性,二者線性相關程度越強,該度量值越高。該度量適用於不同特徵的範圍不一樣的情況。

  • 基於用戶相似度的推薦
    爲用戶A提供推薦,首先選擇皮爾森相關係數進行度量,計算與A最相似的N個人,然後計算每部電影在相似度加權平均下的加權平均值,以該分值作爲每部電影的推薦值。

  • 基於物品的協作型過濾
    首先將原始字典的鍵和值進行調換,此時鍵爲物品,值爲一個字典,字典的鍵爲用戶,值爲分值。根據此字典運行上述的算法,先求物品之間的相似度。比如要給用戶A提供推薦,A做出評價的物品有1,2,3.未作出評價的物品有4,5,6。計算物品4的分值,需要先計算物品4與物品1,2,3的相似度,以相似度作爲權值,對物品1,2,3的評分進行加權平均,以此作爲對物品4 評分。

相關代碼如下(具體參看註釋):

# -*- coding: utf-8 -*-
__author__ = 'Bai Chenjia'
from math import *


class recommendation:
    def __init__(self):
        self.critics = {'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
                                      'Superman Returns': 3.5, 'You, Me and Dupree': 2.5, 'The Night Listener': 3.0},
                        'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5, 'Just My Luck': 1.5,
                                         'Superman Returns': 5.0, 'The Night Listener': 3.0, 'You, Me and Dupree': 3.5},
                        'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
                                             'Superman Returns': 3.5, 'The Night Listener': 4.0},
                        'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0, 'The Night Listener': 4.5,
                                         'Superman Returns': 4.0, 'You, Me and Dupree': 2.5},
                        'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0, 'Just My Luck': 2.0,
                                         'Superman Returns': 3.0, 'The Night Listener': 3.0, 'You, Me and Dupree': 2.0},
                        'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0, 'The Night Listener': 3.0,
                                          'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
                        'Toby': {'Snakes on a Plane': 4.5, 'You, Me and Dupree': 1.0, 'Superman Returns': 4.0}}

    def sim_distance(self, person1, person2, new_critics=0):
        """
        使用歐氏距離計算相似度. 返回相似度是一個浮點數值. 返回值爲0代表沒有相似性.
        首先計算兩個人所看相同電影評分的歐氏距離L. 如兩人沒有觀看相同電影則返回0,否則計算相似度公式爲 1/(L+1).
        其中分母使用L+1是爲了防止出現分母爲0的情況. 歐氏距離越大,相似度越小;歐氏距離越小,相似度越大
        """
        if new_critics != 0:
            new_critics = self.transformPrefs()
            movielist1 = new_critics[person1]
            movielist2 = new_critics[person2]   # dict
            commonlist = []        # list
            for item in movielist1:
                if item in movielist2:
                    commonlist.append(item)
            if len(commonlist) == 0:   # 沒有公共元素,相似度爲0
                return 0
            # 計算歐氏距離(使用列表生成式)
            distance = sum([pow(movielist1[item] - movielist2[item], 2) for item in commonlist])
            sim = 1 / (sqrt(distance) + 1)
            return sim
        else:
            movielist1 = self.critics[person1]
            movielist2 = self.critics[person2]   # dict
            commonlist = []        # list
            for item in movielist1:
                if item in movielist2:
                    commonlist.append(item)
            if len(commonlist) == 0:   # 沒有公共元素,相似度爲0
                return 0
            # 計算歐氏距離(使用列表生成式)
            distance = sum([pow(movielist1[item] - movielist2[item], 2) for item in commonlist])
            sim = 1 / (sqrt(distance) + 1)
            return sim

    def sim_pearson(self, person1, person2):
        """
        計算皮爾森相關係數.   度量兩個向量之間的線性相關性.
        如果一個人總是給出比另一個人更高的分支,但二者的分值只差基本保持一致,則他們仍然存在很好的相關性.
        :return:兩個向量之間的皮爾森相關係數,返回浮點數
        """
        movielist1 = self.critics[person1]
        movielist2 = self.critics[person2]
        commonlist = []
        for item in movielist1:
            if item in movielist2:
                commonlist.append(item)  # key
        if len(commonlist) == 0:  # 沒有共同項
            return 0

        sum1 = sum([self.critics[person1][item] for item in commonlist])   # person1評分和
        sum2 = sum([self.critics[person2][item] for item in commonlist])   # person2評分和

        sum1sq = sum([pow(self.critics[person1][item], 2) for item in commonlist])  # person1評分平方和
        sum2sq = sum([pow(self.critics[person2][item], 2) for item in commonlist])  # person2評分平方和

        psum = sum([self.critics[person1][item] * self.critics[person2][item]
                    for item in commonlist])  # 交叉項乘積和

        # calculate perarson
        n = len(commonlist)
        num = (psum - (sum1 * sum2 / n))
        den = sqrt((sum1sq - pow(sum1, 2) / n) * (sum2sq - pow(sum2, 2) / n))
        if den == 0:
            return 0
        r = num / den
        return r

    def topMatches(self, person, n=5, similarity=sim_pearson):
        """
        返回與person最相似的n個人的姓名和相似度. 相似度度量選用 皮爾森相關係數
        :param person: 要計算的人的姓名
        :param n: 與person相似度最高的n個人
        :param similarity: 相似度度量方法
        :return: 返回一個list,list中元組存儲的形式爲(相關性, 姓名)
        """
        if person not in self.critics:
            print "person input error!"
            return 0
        scores = [(similarity(self, person, item), item) for item in self.critics if item != person]
        # print scores[:]
        return sorted(scores, key=lambda it: -it[0])[0:n]  # 取相似度最高的n位

    def getRecommendations(self, person, similarity=sim_pearson):
        """
        用與person的相似度對其他人對person未看過的影片進行加權平均,對person未看影片進行加權打分
        :param person:
        :param similarity: 相似度度量方法
        :return: 返回對 person 未看影片的推薦評分,按評分排序進行推薦
        """
        moviescore_dict = {}  # 影片--影片對應的評分和
        moviesim_dict = {}  # 影片--影片對應的相似度和
        for other in self.critics:
            sim = similarity(self, other, person)  # 人之間的相似度
            if other == person:
                continue
            if sim < 0:
                continue
            for item in self.critics[other]:
                if item not in self.critics[person]:  # 如果person未對該電影進行評價
                    moviescore_dict.setdefault(item, 0)
                    moviescore_dict[item] += self.critics[other][item] * sim

                    moviesim_dict.setdefault(item, 0)
                    moviesim_dict[item] += sim

        result = [(float(moviescore_dict[item] / moviesim_dict[item]), item)
                  for item in moviescore_dict]
        result = sorted(result, key=lambda e1: -e1[0])
        return result

    def transformPrefs(self):
        """
        將self.critics中的 人--電影 的鍵和值進行調換,反向構造字典
        :return: 鍵值調換後的 dict
        """
        result = {}
        for person in self.critics:
            for item in self.critics[person]:
                result.setdefault(item, {})
                result[item][person] = self.critics[person][item]
        return result

    def calculateSimilarItems(self, similarity=sim_distance):
        """
        利用反向構造的 物品--人  字典,構造一個物品相似度字典
        字典的鍵是物品,值是與該物品最相似的n個物品以及物品之間的相似度
        :param n: 與物品最相似的n個物品
        :return: dict
        """
        result = {}
        reverse_critics = self.transformPrefs()
        for movie in reverse_critics:
            scores = [(similarity(self, movie, item, 100), item)
                      for item in reverse_critics if item != movie]
            scores = sorted(scores, key=lambda it: -it[0])       # 取相似度最高的n位
            result[movie] = scores
        return result

    def getRecommendedItems(self, user):
        """
        指定一個名字user,計算對該用戶的電影推薦
        首先利用 calculateSimilarItems方法計算物品之間的相似度dict
        根據該用戶對曾經看過的影片的評分以及該電影與未看過電影的相似度估計用戶對未看電影的評分,根據評分進行推薦
        :param user: 指定的人
        :return:
        """
        userRatings = self.critics[user]  # 返回該用戶所看電影和評分
        itemMatch = self.calculateSimilarItems()  # 返回物品的相似度字典
        scores = {}
        totalsim = {}
        for item, rating in userRatings.items():
            for similarity, item2 in itemMatch[item]:
                if item2 in userRatings:   # 若該電影被用戶評分
                    continue
                scores.setdefault(item2, 0)
                scores[item2] += similarity * rating
                totalsim.setdefault(item2, 0)
                totalsim[item2] += similarity
        ranking = [(score / totalsim[item], item) for item, score in scores.items()]
        ranking = sorted(ranking, key=lambda it: -it[0])
        return ranking


if __name__ == '__main__':
    system = recommendation()

    # 1.歐幾里得距離計算相似度
    print "\n歐幾里得相似度:", system.sim_distance('Lisa Rose', 'Gene Seymour')

    # 2.皮爾森相關係數計算
    print "\n皮爾森相關係數:", system.sim_pearson('Lisa Rose', 'Gene Seymour')

    # 3.計算與某人相似度最高的n個人
    print "\n與 Toby 相似度最高的n個人", system.topMatches('Toby', n=3)[:]

    # 4.輸入人名,對該人未觀看的影片推薦
    print "\n給 Toby 未觀看影片的推薦是", system.getRecommendations('Toby')[:]

    # 5.構建物品相似度字典
    print "\n電影之間的相似度字典是:"
    for key, value in system.calculateSimilarItems().iteritems():
        print key, ":", value[:]

    # 6.根據物品相似度,輸入人名,推薦影片
    print "\n根據電影相似度給 Toby 推薦的影片是:", system.getRecommendedItems('Toby')[:]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章