基于用户的协同过滤算法

最近在做一个古诗推荐功能,以前只会根据点击量,收藏量进行排序,给用户展示点击量、排行量前几的数据。

最近新发现一个算法——基于用户的协同过滤算法。

原理:

1、找到与目标用户兴趣相投的用户。

2、根据找到的用户集合中,相似用户喜欢的,而目标用户没有听说过的物品,进行推荐。


一、首先连接数据库获取数据

import pymysql
import json
# 连接database
conn = pymysql.connect(host="127.0.0.1", user="root", password="tian", database="tangshi", charset="utf8")
# 得到一个可以执行SQL语句的光标对象
cursor = conn.cursor()
# 定义要执行的SQL语句
sql = """select user_id,poetry_id from poetry_collection ;"""
# 执行SQL语句
cursor.execute(sql)
data = cursor.fetchall()
res = {}

# 更改格式
for info in data:
    if (info[0] in res.keys()):
        res[info[0]].append(info[1])
    else:
        res [info[0]]=[info[1]]
for k,v in res.items():
    res[k]=tuple(v)
json_str = json.dumps(res, indent=4)#注意这个indent参数
with open('test_data.json', 'w') as json_file:
     json_file.write(json_str)
# 关闭光标对象
cursor.close()
# 关闭数据库连接
conn.close()

 格式如下:key是用户id,vaue是古诗id

二、获取相似的用户

通常用 Jaccard 公式或者余弦相似度计算两个用户之间的相似度。设 N(u) 为用户 u 喜欢的物品集合,N(v) 为用户 v 喜欢的物品集合,那么 u 和 v 的相似度是多少呢:

余弦相似度:

用户47喜欢的古诗有: 85 , 80 ,87

则用户47和用户51的相似度为:2/(\sqrt{3}*\sqrt{3})=2/3

但是要计算所有用户的相似度,时间复杂度为O(n^{2})所以需要建立倒排表:

以下为抄袭:↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

假设目前共有4个用户: A、B、C、D;共有5个物品:a、b、c、d、e。用户与物品的关系(用户喜欢物品)如下图所示:

 

如何一下子计算所有用户之间的相似度呢?为计算方便,通常首先需要建立“物品—用户”的倒排表,如下图所示:

      然后对于每个物品,喜欢他的用户,两两之间相同物品加1。例如喜欢物品 a 的用户有 A 和 B,那么在矩阵中他们两两加1。如下图所示:

      计算用户两两之间的相似度,上面的矩阵仅仅代表的是公式的分子部分。以余弦相似度为例,对上图进行进一步计算:

      到此,计算用户相似度就大功告成,可以很直观的找到与目标用户兴趣较相似的用户。

三、推荐物品

    首先需要从矩阵中找出与目标用户 u 最相似的 K 个用户,用集合 S(u, K) 表示,将 S 中用户喜欢的物品全部提取出来,并去除 u 已经喜欢的物品。对于每个候选物品 i ,用户 u 对它感兴趣的程度用如下公式计算:

      其中 rvi 表示用户 v 对 i 的喜欢程度,在本例中都是为 1,在一些需要用户给予评分的推荐系统中,则要代入用户评分。

      举个例子,假设我们要给 A 推荐物品,选取 K = 3 个相似用户,相似用户则是:B、C、D,那么他们喜欢过并且 A 没有喜欢过的物品有:c、e,那么分别计算 p(A, c) 和 p(A, e):

      看样子用户 A 对 c 和 e 的喜欢程度可能是一样的,在真实的推荐系统中,只要按得分排序,取前几个物品就可以了。

四、代码实现

import math
from operator import *
import json

file = open('test_data.json', 'r')
js = file.read()
dic = json.loads(js)
for k, v in dic.items():
    for i in range(len(v)):
        v[i] = str(v[i])
    v = tuple(set(v))
file.close()


# 计算用户兴趣相似度
def Usersim(dicc):
    item_user = dict()
    for u, items in dicc.items():
        for i in items:
            if i not in item_user.keys():
                item_user[i] = set()  # i键所对应的值是一个集合
            item_user[i].add(u)  # 向集合中添加用户。

    C = dict()
    N = dict()
    for item, users in item_user.items():
        for u in users:
            if u not in N.keys():
                N[u] = 0
            N[u] += 1  # 每个商品下用户出现一次就加一次,就是计算每个用户一共购买的商品个数。

            for v in users:
                if u == v:
                    continue
                if (u, v) not in C.keys():  # 同上,没有初始值不能+=
                    C[u, v] = 0
                C[u, v] += 1
    # 到这里倒排阵就建立好了,下面是计算相似度。
    W = dict()
    for co_user, cuv in C.items():
        W[co_user] = cuv / math.sqrt(N[co_user[0]] * N[co_user[1]])
    # 把相似度为1的变为0,不能删除,否则倒排阵不成立。两者完全相似,也或者是收藏数比较少。这样没有推荐
    for k, v in W.items():
        if v == 1.0:
            W[k] = 0
    return W


def Recommend(user, dicc, W2, K):
    """
    :param user: 要推荐的用户
    :param dicc: 数据
    :param W2: 倒排矩阵
    :param K:取相似用户,前多少个相似的
    :return:
    """
    rvi = 1  # 这里都是1,实际中可能每个用户就不一样了。就像每个人都喜欢beautiful girl,但有的喜欢可爱的多一些,有的喜欢御姐多一些。
    rank = dict()
    related_user = []
    # 要推荐的用户喜欢的
    interacted_items = dicc[user]
    for co_user, item in W2.items():
        if co_user[0] == user:
            related_user.append((co_user[1], item))  # 先建立一个和待推荐用户兴趣相关的所有的用户列表。
    # 找到相似用户喜欢的,而待推荐用户没有喜欢过的
    # print(related_user[:30])
    for v, wuv in sorted(related_user, key=itemgetter(1), reverse=True)[0:K]:
        # 找到K个相关用户以及对应兴趣相似度,按兴趣相似度从大到小排列。
        for i in dicc[v]:
            if i in interacted_items:
                continue
            if i not in rank.keys():
                rank[i] = 0
            rank[i] += wuv * rvi
    return rank


if __name__ == '__main__':
    # 推荐个数
    advice_num = int(input('请输入推荐页数:')) * 12

    W3 = Usersim(dic)
    # 要推荐的用户id'2313'
    Last_Rank = Recommend('2313', dic, W3, 20000)
    porety_num = 12  # 分页,每页古诗12
    for k, v in Last_Rank.items():
        if porety_num % 12 == 0:
            print('************************第%d页***********************' % (porety_num / 12))
        print('古诗id:', k)
        advice_num -= 1
        porety_num += 1
        if advice_num == 0:
            break
    # print('推荐:', Last_Rank)

 

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值