学习协同过滤推荐 w 100行Python代码
引言
用一百行 Python 代码,入门协同过滤推荐。
数据准备
用户对物品的喜好记录,第一列是用户,第二列是物品。在终端输入:
python3
import operator
prefs_str = '''
david 百年孤独
david 霍乱时期的爱情
david 从0到1
andy 霍乱时期的爱情
jack 浪潮之巅
jack 失控
jack 创业维艰
michale 寻路中国:从乡村到工厂的自驾之旅
michale 背包十年:我的职业是旅行
ann 活着
ann 霍乱时期的爱情
ann 迟到的间隔年
joel 巨人的陨落:世纪三部曲
joel 中国历代政治得失
joel 人类简史:从动物到上帝
joel 失控
jim 背包十年:我的职业是旅行
jim 迟到的间隔年
ray 霍乱时期的爱情
ray 迟到的间隔年
ray 枪炮、病菌与钢铁:人类社会的命运
'''
基本概念
偏好矩阵
偏好记录可以转化成偏好矩阵,在 Python 中用 dict 保存:
# {'andy': {'霍乱时期的爱情': 1},...}
def read_prefs(prefs_str):
prefs = {}
for line in prefs_str.split('n'):
parts = line.rstrip().split()
if len(parts) == 2:
userId, itemId = parts
prefs.setdefault(userId, {})
prefs[userId].update({itemId:1})
return prefs
prefs = read_prefs(prefs_str)
偏好相似度(距离函数)
给定两个用户 user1
user2
和偏好向量 [1,0,0,0,0,0]
[1,1,0,0,0,0]
,我们需要定义一个距离函数,返回 0.0~1.0,衡量他们的相似度。距离函数可以选择余弦距离、欧几里得距离、棋盘距离等等,定义不同的距离函数有不同的推荐效果。
这里简单地用 Jaccard 相似性系数,即两个用户感兴趣的书的交集除并集:
def jaccard_distance(prefs, user1, user2):
s1 = set(prefs[user1].keys())
s2 = set(prefs[user2].keys())
return 1.0 * len(s1.intersection(s2)) / len(s1.union(s2))
举个例子,item5 和 item6 的用户评价向量的 Jaccard 距离 = 1 / 4 = 0.25。
prefs |
user1 |
user2 |
user3 |
user4 |
user5 |
user6 |
---|---|---|---|---|---|---|
item5 |
1 |
1 |
1 |
|||
item6 |
1 |
1 |
基于用户的协同过滤(User-CF)
现在我们有了用户的偏好信息,能够衡量任意用户间的相似度,那么,对于 user1
,如何给他推荐物品?
首先,要找出与这个 user1
兴趣相近的用户们,即与 user1
对偏好向量距离相近的用户。然后,找出兴趣相近用户中,最受欢迎的书,推荐给 user1
。
比如对 user4
,发现 user5
的评价距离相近,可以推荐他在读的 item4
给他。
# 找出与 user1 兴趣最相近的用户 user1 -> [(1.0, user3), (0.8, user4), ...]
def top_matches(prefs, user1, similarity, n = 5):
scores = [(similarity(prefs, user1, user2), user2) for user2 in prefs if user1 != user2]
scores.sort()
scores.reverse()
return scores[0:n]
# prefs -> "用户 xx 根据 xx 推荐 xx 书籍 xxx"
def calculate_user_cf(prefs, similarity, n = 10):
ret = {}
for user in prefs.keys():
scores = top_matches(prefs, user, similarity, n)
ret[user] = scores
return ret
def print_recomendation(prefs, similiar_users, min_score = 0.1):
# 对每个用户
for target_user in similiar_users:
itemId_cnt = {}
# 找出兴趣相近的用户
for score, similiar_user in similiar_users[target_user]:
if score > min_score:
# 统计兴趣相近用户最喜欢的书,排序
for itemId in set(prefs[similiar_user]) - set(prefs[target_user]):
itemId_cnt.setdefault(itemId, 0)
itemId_cnt[itemId] += 1
recommends_itemId_cnt = sorted(itemId_cnt.items(), key=operator.itemgetter(1), reverse=True)
print('n用户:%snt喜欢:%snt相似用户:%snt推荐:%s' % (target_user, list(prefs[target_user].keys()), list(filter(lambda score_user:score_user[0] > min_score, similiar_users[target_user])), recommends_itemId_cnt))
print('n基于用户的协同过滤推荐')
similiar_users = calculate_user_cf(prefs, jaccard_distance, n = 10)
print_recomendation(prefs, similiar_users)
输出结果
用户:jim
喜欢:['背包十年:我的职业是旅行', '迟到的间隔年']
相似用户:[(0.3333333333333333, 'michale'), (0.25, 'ray'), (0.25, 'ann')]
推荐:[('霍乱时期的爱情', 2), ('活着', 1), ('寻路中国:从乡村到工厂的自驾之旅', 1), ('枪炮、病菌与钢铁:人类社会的命运', 1)]
...
基于物品的协同过滤(Item-CF)
在神奇的数学世界里,我们把偏好矩阵转置,即行列互换,用相同的思想,可以得到一种新的推荐方法 —— 基于物品的协同过滤。
用 User-CF 方法,对于给定用户,根据偏好矩阵,算出兴趣相近的用户;用 Item-CF 方法,对于给定物品,根据偏好矩阵,算出用户评价向量相近的物品,作为推荐。
# prefs[userId][itemId] -> prefs[itemId][userId]
def transpose_prefs(prefs):
ret = {}
for userId in prefs:
for itemId in prefs[userId]:
ret.setdefault(itemId, {})
ret[itemId][userId] = prefs[userId][itemId]
return ret
得到转置后的偏好矩阵:
prefs |
user1 |
user2 |
user3 |
user4 |
user5 |
user6 |
---|---|---|---|---|---|---|
item1 |
1 |
1 |
||||
item2 |
1 |
|||||
item3 |
||||||
item4 |
1 |
|||||
item5 |
1 |
1 |
1 |
|||
item6 |
1 |
1 |
如果一个用户喜欢物品 item5
,可以推荐用户偏好向量距离相近的物品如 item4
和 item6
给这位用户。
def calculate_item_cf(prefs, similarity, n = 10):
ret = {}
itemPrefs = transpose_prefs(prefs)
# 对于每个物品,找出用户评价向量距离相近的物品
for item in itemPrefs:
scores = top_matches(itemPrefs, item, similarity, n)
ret[item] = scores
return ret
def print_similiar_items(similiar_items, min_score = 0.1):
for target_itemId in similiar_items:
print('n根据 %s 推荐:' % target_itemId)
score_items = similiar_items[target_itemId]
for score_item in score_items:
score, itemId = score_item
if score > min_score: print('t%s %f' % (itemId, score))
print('n基于书籍的协同过滤推荐')
similiar_items = calculate_item_cf(prefs, jaccard_distance, n = 10)
print_similiar_items(similiar_items)
输出结果
根据 创业维艰 推荐:
浪潮之巅 1.000000
失控 1.000000
...
延伸阅读
《集体智慧编程》—— 协同过滤
推荐算法综述1
推荐算法综述2
推荐算法综述3
推荐算法综述4
推荐算法综述5
Amazon Item-CF Patent 1998
- 运维必备技能 WEB 日志分析
- Elasticsearch 一键安装含中文分词
- Session 的 Cookie 域处理(多域名虚拟主机)
- ElasticSearch + Logstash + Kibana 日志采集
- ElasticSearch + Logstash + Kibana 一键安装
- Oracle 表空间管理
- 数据加密字段加密
- 《Netkiller Virtualization 手札》Docker 卷管理
- PHP高级编程之守护进程
- Spring boot with Docker
- Spring boot with Service
- Spring boot with PostgreSQL
- Struts2 S2-046, S2-045 Firewall(漏洞防火墙)
- 应用程序的通信成本
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 使用Seq搭建免费的日志服务
- 拜托!这才是分布式系统CAP的正确打开方式!
- 接口管理这下总会了吧?
- 交子杯 - 2020 - AI赛道 - TOP1
- Valine 一款快速、简洁且高效的无后端评论系统
- 两段有趣的C代码
- 算法数据结构 | 三个步骤完成强连通分量分解的Kosaraju算法
- 并查集算法 详解
- SQL 中 EXISTS 用法详解
- Blazor带我重玩前端(六)
- PB 级大规模 Elasticsearch 集群运维与调优实践
- MySQL实时在线备份恢复方案
- Android通过原生请求直接获取网页内容
- matplotlib | Python强大的作图工具,让你从此驾驭图表(二)
- 设计模式 | Catalog设计模式,抵御业务方需求变动