集体智慧编程——协同过滤推荐算法-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')[:]
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章