Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
OpenDocCN
Ailearning
提交
9f9c11cb
A
Ailearning
项目概览
OpenDocCN
/
Ailearning
大约 1 年 前同步成功
通知
12
Star
36240
Fork
11272
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
A
Ailearning
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
9f9c11cb
编写于
9月 20, 2020
作者:
片刻小哥哥
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
更新推荐系统最新代码和注释
上级
4702b55f
变更
1
隐藏空白更改
内联
并排
Showing
1 changed file
with
259 addition
and
0 deletion
+259
-0
tutorials/RecommenderSystems/rs_rating_demo.py
tutorials/RecommenderSystems/rs_rating_demo.py
+259
-0
未找到文件。
src/py3.x/ml/16.RecommenderSystems/RS-sklearn-rating
.py
→
tutorials/RecommenderSystems/rs_rating_demo
.py
浏览文件 @
9f9c11cb
#!/usr/bin/python
# coding:utf8
from
__future__
import
print_function
import
sys
import
math
...
...
@@ -9,80 +8,126 @@ from operator import itemgetter
import
numpy
as
np
import
pandas
as
pd
from
scipy.sparse.linalg
import
svds
from
sklearn
import
cross_valida
tion
as
cv
from
sklearn
import
model_selec
tion
as
cv
from
sklearn.metrics
import
mean_squared_error
from
sklearn.metrics.pairwise
import
pairwise_distances
from
middleware.utils
import
TimeStat
,
Chart
"""
推荐系统: Item CF/User CF/SVD 对比
"""
def
splitData
(
dataFile
,
test_size
):
# 加载数据集
# 加载数据集
(用户ID, 电影ID, 评分, 时间戳)
header
=
[
'user_id'
,
'item_id'
,
'rating'
,
'timestamp'
]
df
=
pd
.
read_csv
(
dataFile
,
sep
=
'
\t
'
,
names
=
header
)
n_users
=
df
.
user_id
.
unique
().
shape
[
0
]
n_items
=
df
.
item_id
.
unique
().
shape
[
0
]
print
(
'Number of users = '
+
str
(
n_users
)
+
' | Number of movies = '
+
str
(
n_items
))
print
(
'>>> 本数据集包含: 总用户数 = %s | 总电影数 = %s'
%
(
n_users
,
n_items
)
)
train_data
,
test_data
=
cv
.
train_test_split
(
df
,
test_size
=
test_size
)
print
(
"
数据量: "
,
len
(
train_data
),
len
(
test_data
))
print
(
"
>>> 训练:测试 = %s:%s = %s:%s"
%
(
len
(
train_data
),
len
(
test_data
),
1
-
test_size
,
test_size
))
return
df
,
n_users
,
n_items
,
train_data
,
test_data
def
calc_similarity
(
n_users
,
n_items
,
train_data
,
test_data
):
# 创建用户产品矩阵,针对测试数据和训练数据,创建两个矩阵:
"""
line: Pandas(Index=93661, user_id=624, item_id=750, rating=4, timestamp=891961163)
"""
train_data_matrix
=
np
.
zeros
((
n_users
,
n_items
))
for
line
in
train_data
.
itertuples
():
train_data_matrix
[
line
[
1
]
-
1
,
line
[
2
]
-
1
]
=
line
[
3
]
test_data_matrix
=
np
.
zeros
((
n_users
,
n_items
))
for
line
in
test_data
.
itertuples
():
test_data_matrix
[
line
[
1
]
-
1
,
line
[
2
]
-
1
]
=
line
[
3
]
# 使用sklearn的pairwise_distances函数来计算余弦相似性。
print
(
"1:"
,
np
.
shape
(
train_data_matrix
))
# 行: 人,列: 电影
print
(
"2:"
,
np
.
shape
(
train_data_matrix
.
T
))
# 行: 电影,列: 人
print
(
"1:"
,
np
.
shape
(
train_data_matrix
))
# 行: 人 | 列: 电影
print
(
"2:"
,
np
.
shape
(
train_data_matrix
.
T
))
# 行: 电影 | 列: 人
# 使用sklearn的 pairwise_distances 计算向量距离,cosine来计算余弦距离,越小越相似
user_similarity
=
pairwise_distances
(
train_data_matrix
,
metric
=
"cosine"
)
item_similarity
=
pairwise_distances
(
train_data_matrix
.
T
,
metric
=
"cosine"
)
# print("<<< %s \n %s" % (np.shape(user_similarity), user_similarity) )
# print("<<< %s \n %s" % (np.shape(item_similarity), item_similarity) )
print
(
'开始统计流行item的数量...'
,
file
=
sys
.
stderr
)
item_popular
=
{}
# 统计
在所有的用户中,不同电影的总出现次数
# 统计
同一个电影,观看的总人数(也就是所谓的流行度!)
for
i_index
in
range
(
n_items
):
if
np
.
sum
(
train_data_matrix
[:,
i_index
])
!=
0
:
item_popular
[
i_index
]
=
np
.
sum
(
train_data_matrix
[:,
i_index
]
!=
0
)
# print "pop=", i_index, self.item_popular[i_index]
# save the total number of items
item_count
=
len
(
item_popular
)
print
(
'总共流行item数量 = %d'
%
item_count
,
file
=
sys
.
stderr
)
print
(
'总共流行 item 数量 = %d'
%
item_count
,
file
=
sys
.
stderr
)
return
train_data_matrix
,
test_data_matrix
,
user_similarity
,
item_similarity
,
item_popular
def
predict
(
rating
,
similarity
,
type
=
'user'
):
print
(
type
)
print
(
"rating="
,
np
.
shape
(
rating
))
print
(
"similarity="
,
np
.
shape
(
similarity
))
if
type
==
'user'
:
# 求出每一个用户,所有电影的综合评分(axis=0 表示对列操作, 1表示对行操作)
# print "rating=", np.shape(rating)
"""
:param rating: 训练数据
:param similarity: 向量距离
:return:
"""
print
(
"+++ %s"
%
type
)
print
(
" rating="
,
np
.
shape
(
rating
))
print
(
" similarity="
,
np
.
shape
(
similarity
))
if
type
==
'item'
:
"""
综合打分:
rating.dot(similarity) 表示:
某1个人所有的电影组合 X ·电影*电影·距离(第1列都是关于第1部电影和其他的电影的距离)中,计算出 第一个人对第1/2/3部电影的 总评分 1*n
某2个人所有的电影组合 X ·电影*电影·距离(第1列都是关于第1部电影和其他的电影的距离)中,计算出 第一个人对第1/2/3部电影的 总评分 1*n
...
某n个人所有的电影组合 X ·电影*电影·距离(第1列都是关于第1部电影和其他的电影的距离)中,计算出 第一个人对第1/2/3部电影的 总评分 1*n
= 人-电影-评分(943, 1682) * 电影-电影-距离(1682, 1682)
= 人-电影-总评分距离(943, 1682)
np.array([np.abs(similarity).sum(axis=1)]) 表示: 横向求和: 1 表示某一行所有的列求和
第1列表示:某个A电影,对于所有电影计算出A的总距离
第2列表示:某个B电影,对于所有电影的综出B的总距离
...
第n列表示:某个N电影,对于所有电影的综出N的总距离
= 每一个电影的总距离 (1, 1682)
pred = 人-电影-平均评分 (943, 1682)
"""
pred
=
rating
.
dot
(
similarity
)
/
np
.
array
([
np
.
abs
(
similarity
).
sum
(
axis
=
1
)])
elif
type
==
'user'
:
# 每个样本上减去数据的统计平均值可以移除共同的部分,凸显个体差异。
# 求出每一个用户,所有电影的综合评分
# 横向求平均: 1 表示某一行所有的列求平均
mean_user_rating
=
rating
.
mean
(
axis
=
1
)
# np.newaxis参考地址: http://blog.csdn.net/xtingjie/article/details/72510834
# print "mean_user_rating=", np.shape(mean_user_rating)
# print "mean_user_rating.newaxis=", np.shape(mean_user_rating[:, np.newaxis])
# numpy中包含的 newaxis 可以给原数组增加一个维度
rating_diff
=
(
rating
-
mean_user_rating
[:,
np
.
newaxis
])
# print "rating=", rating[:3, :3]
# print "mean_user_rating[:, np.newaxis]=", mean_user_rating[:, np.newaxis][:3, :3]
# print "rating_diff=", rating_diff[:3, :3]
# 均分 + 人-人-距离(943, 943)*人-电影-评分diff(943, 1682)=结果-人-电影(每个人对同一电影的综合得分)(943, 1682) 再除以 个人与其他人总的距离 = 人-电影综合得分
pred
=
mean_user_rating
[:,
np
.
newaxis
]
+
similarity
.
dot
(
rating_diff
)
/
np
.
array
([
np
.
abs
(
similarity
).
sum
(
axis
=
1
)]).
T
elif
type
==
'item'
:
# 综合打分: 人-电影-评分(943, 1682)*电影-电影-距离(1682, 1682)=结果-人-电影(各个电影对同一电影的综合得分)(943, 1682) / 再除以 电影与其他电影总的距离 = 人-电影综合得分
pred
=
rating
.
dot
(
similarity
)
/
np
.
array
(
[
np
.
abs
(
similarity
).
sum
(
axis
=
1
)])
# 均分 +
# 人-人-距离(943, 943)*人-电影-评分diff(943, 1682)=结果-人-电影(每个人对同一电影的综合得分)(943, 1682) 再除以 个人与其他人总的距离 = 人-电影综合得分
"""
综合打分:
similarity.dot(rating_diff) 表示:
第1列:第1个人与其他人的相似度 * 人与电影的相似度,得到 第1个人对第1/2/3列电影的 总得分 1*n
第2列:第2个人与其他人的相似度 * 人与电影的相似度,得到 第2个人对第1/2/3列电影的 总得分 1*n
...
第n列:第n个人与其他人的相似度 * 人与电影的相似度,得到 第n个人对第1/2/3列电影的 总得分 1*n
= 人-人-距离(943, 943) * 人-电影-评分(943, 1682)
= 人-电影-总评分距离(943, 1682)
np.array([np.abs(similarity).sum(axis=1)]) 表示: 横向求和: 1 表示某一行所有的列求和
第1列表示:第A个人,对于所有人计算出A的总距离
第2列表示:第B个人,对于所有人计算出B的总距离
...
第n列表示:第N个人,对于所有人计算出N的总距离
= 每一个电影的总距离 (1, 943)
pred = 均值 + 人-电影-平均评分 (943, 1682)
"""
pred
=
mean_user_rating
[:,
np
.
newaxis
]
+
similarity
.
dot
(
rating_diff
)
/
np
.
array
([
np
.
abs
(
similarity
).
sum
(
axis
=
1
)]).
T
return
pred
...
...
@@ -112,7 +157,7 @@ def evaluate(prediction, item_popular, name):
hit
+=
1
all_rec_items
.
add
(
item
)
#
计算用户对应的电影出现次数log值的sum
加和
#
popular_sum是对所有的item的流行度进行
加和
if
item
in
item_popular
:
popular_sum
+=
math
.
log
(
1
+
item_popular
[
item
])
...
...
@@ -120,10 +165,12 @@ def evaluate(prediction, item_popular, name):
test_count
+=
len
(
test_items
)
precision
=
hit
/
(
1.0
*
rec_count
)
# 召回率,相对于测试推荐集合的数据
recall
=
hit
/
(
1.0
*
test_count
)
# 覆盖率,相对于训练集合的数据
coverage
=
len
(
all_rec_items
)
/
(
1.0
*
len
(
item_popular
))
popularity
=
popular_sum
/
(
1.0
*
rec_count
)
print
(
'%s: precision=%.4f
\t
recall=%.4f
\t
coverage=%.4f
\t
popularity=%.4f'
%
(
print
(
'
---
%s: precision=%.4f
\t
recall=%.4f
\t
coverage=%.4f
\t
popularity=%.4f'
%
(
name
,
precision
,
recall
,
coverage
,
popularity
),
file
=
sys
.
stderr
)
...
...
@@ -135,19 +182,22 @@ def recommend(u_index, prediction):
reverse
=
True
)[:
10
]
test_items
=
np
.
where
(
test_data_matrix
[
u_index
,
:]
!=
0
)[
0
]
print
(
'原始结果: '
,
test_items
)
print
(
'推荐结果: '
,
[
key
for
key
,
value
in
pre_items
])
result
=
[
key
for
key
,
value
in
pre_items
]
result
.
sort
(
reverse
=
False
)
print
(
'原始结果(%s): %s'
%
(
len
(
test_items
),
test_items
)
)
print
(
'推荐结果(%s): %s'
%
(
len
(
result
),
result
)
)
if
__name__
==
"__main__"
:
def
main
()
:
global
n_users
,
train_data_matrix
,
test_data_matrix
# 基于内存的协同过滤
# ...
# 拆分数据集
# http://files.grouplens.org/datasets/movielens/ml-100k.zip
dataFile
=
'data/16.RecommenderSystems/ml-100k/u.data'
df
,
n_users
,
n_items
,
train_data
,
test_data
=
splitData
(
dataFile
,
test_size
=
0.25
)
path_root
=
"/Users/jiangzl/work/data/机器学习"
dataFile
=
'%s/16.RecommenderSystems/ml-100k/u.data'
%
path_root
df
,
n_users
,
n_items
,
train_data
,
test_data
=
splitData
(
dataFile
,
test_size
=
0.25
)
# 计算相似度
train_data_matrix
,
test_data_matrix
,
user_similarity
,
item_similarity
,
item_popular
=
calc_similarity
(
...
...
@@ -156,35 +206,54 @@ if __name__ == "__main__":
item_prediction
=
predict
(
train_data_matrix
,
item_similarity
,
type
=
'item'
)
user_prediction
=
predict
(
train_data_matrix
,
user_similarity
,
type
=
'user'
)
# 评估: 均方根误差
print
(
'Item based CF RMSE: '
+
str
(
rmse
(
item_prediction
,
test_data_matrix
)))
print
(
'User based CF RMSE: '
+
str
(
rmse
(
user_prediction
,
test_data_matrix
)))
# # 评估: 均方根误差
print
(
'>>> Item based CF RMSE: '
+
str
(
rmse
(
item_prediction
,
test_data_matrix
)))
print
(
'>>> User based CF RMSE: '
+
str
(
rmse
(
user_prediction
,
test_data_matrix
)))
# 基于模型的协同过滤
# ...
# 计算MovieLens数据集的稀疏度 (n_users,n_items 是常量,所以,用户行为数据越少,意味着信息量少;越稀疏,优化的空间也越大)
sparsity
=
round
(
1.0
-
len
(
df
)
/
float
(
n_users
*
n_items
),
3
)
print
(
'The sparsity level of MovieLen100K is '
+
str
(
sparsity
*
100
)
+
'%'
)
# 计算稀疏矩阵的最大k个奇异值/向量
u
,
s
,
vt
=
svds
(
train_data_matrix
,
k
=
15
)
print
(
'
\n
MovieLen100K的稀疏度: %s%%
\n
'
%
(
sparsity
*
100
))
# # 计算稀疏矩阵的最大k个奇异值/向量
# minrmse = math.inf
# index = 1
# for k in range(1, 30, 1):
# u, s, vt = svds(train_data_matrix, k=k)
# # print(">>> ", s)
# s_diag_matrix = np.diag(s)
# svd_prediction = np.dot(np.dot(u, s_diag_matrix), vt)
# r_rmse = rmse(svd_prediction, test_data_matrix)
# if r_rmse < minrmse:
# index = k
# minrmse = r_rmse
index
=
11
minrmse
=
2.6717213264389765
u
,
s
,
vt
=
svds
(
train_data_matrix
,
k
=
index
)
# print(">>> ", s)
s_diag_matrix
=
np
.
diag
(
s
)
svd_prediction
=
np
.
dot
(
np
.
dot
(
u
,
s_diag_matrix
),
vt
)
print
(
"svd-shape:"
,
np
.
shape
(
svd_prediction
)
)
print
(
'Model based CF RMSE: '
+
str
(
rmse
(
svd_prediction
,
test_data_matrix
))
)
"""
在信息量相同的情况下,矩阵越小,那么携带的信息越可靠。
所以: user-cf 推荐效果高于 item-cf; 而svd分解后,发现15个维度效果就能达到90%以上,所以信息更可靠,效果也更好。
item-cf: 1682
user-cf: 943
svd: 15
"""
r_rmse
=
rmse
(
svd_prediction
,
test_data_matrix
)
print
(
"+++ k=%s, svd-shape: %s"
%
(
index
,
np
.
shape
(
svd_prediction
))
)
print
(
'>>> Model based CF RMSE: %s
\n
'
%
minrmse
)
#
"""
#
在信息量相同的情况下,矩阵越小,那么携带的信息越可靠。
#
所以: user-cf 推荐效果高于 item-cf; 而svd分解后,发现15个维度效果就能达到90%以上,所以信息更可靠,效果也更好。
#
item-cf: 1682
#
user-cf: 943
#
svd: 15
#
"""
evaluate
(
item_prediction
,
item_popular
,
'item'
)
evaluate
(
user_prediction
,
item_popular
,
'user'
)
evaluate
(
svd_prediction
,
item_popular
,
'svd'
)
evaluate
(
svd_prediction
,
item_popular
,
'svd'
)
# 推荐结果
# recommend(1, item_prediction)
# recommend(1, user_prediction)
recommend
(
1
,
svd_prediction
)
if
__name__
==
"__main__"
:
main
()
\ No newline at end of file
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录