NMF学习练习:做电影推荐

NMF是很久以前学的,基本快忘没了,昨天YX提出来一个关于NMF(同音同字不同义)的问题,才又想起来。 自己的学习笔记写的比较乱,好在网上资料多,摘了一篇,补充上自己笔记的内容,留此助记。

NMF概念出现的比较早,差不多在电脑还没有开始繁荣起来,NMF及相关的一些算法已经很成熟了。NMF用在电影推荐、商品推荐也并不是很适合,现在大多使用SVD之类的算法。不过这篇只是学习的记录,有个例子总比枯燥的啃概念好的多。

场景

让我们假设一个场景。 相像当前这个档期,有10部电影正在上映,我们把它们放到一个数组中:

item = [
    '希特勒回来了', '死侍', '房间', '龙虾', '大空头',
    '极盗者', '裁缝', '八恶人', '实习生', '间谍之桥',
]

放入数组这个动作,等于也把这些电影编了号,从0到9,比如电影《实习生》,编号就是8。 随后我们继续假设我们影院有15个老顾客,同样把它们放置到一个数组:

user = ['五柳君', '帕格尼六', '木村静香', 'WTF', 'airyyouth',
        '橙子c', '秋月白', 'clavin_kong', 'olit', 'You_某人',
        '凛冬将至', 'Rusty', '噢!你看!', 'Aron', 'ErDong Chen']

他们的编号是0-14。 接着从用户的观影记录中,我们提取每个用户,对每部电影的打分记录。以电影序号为行号,以用户编号为列号,形成一个矩阵:

RATE_MATRIX = np.array(
    [[5, 5, 3, 0, 5, 5, 4, 3, 2, 1, 4, 1, 3, 4, 5],
     [5, 0, 4, 0, 4, 4, 3, 2, 1, 2, 4, 4, 3, 4, 0],
     [0, 3, 0, 5, 4, 5, 0, 4, 4, 5, 3, 0, 0, 0, 0],
     [5, 4, 3, 3, 5, 5, 0, 1, 1, 3, 4, 5, 0, 2, 4],
     [5, 4, 3, 3, 5, 5, 3, 3, 3, 4, 5, 0, 5, 2, 4],
     [5, 4, 2, 2, 0, 5, 3, 3, 3, 4, 4, 4, 5, 2, 5],
     [5, 4, 3, 3, 2, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0],
     [5, 4, 3, 3, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
     [5, 4, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2],
     [5, 4, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]
)

从矩阵中,我们可以得到这样几个信息:

  1. 评分是5分制。
  2. 用户打分是有个人特点的,比如第一列的用户,也就是“五柳君”,喜欢给电影打5分,:),真是个心宽的家伙。
  3. 用户没有打分的电影,给分就是0。(通常不会出现真的给某部电影打0分的情况)。

分类

我们使用NMF为电影进行主题分类。 这是一个非常典型的非监督学习方式,也就是我们并不指定主题是什么,只知道主题一定是存在的,因此一定会有两个典型的倾向:

  1. 任何一部电影,必然倾向某一种或者几种主题。
  2. 任意一位观众,必然喜爱某一种或者几种主题。

这里的理解重点是在非监督学习中,我们并不指定主题是什么,但只要你联想一下实际情况就容易理解了,比如可能是“爱情”主题,或者“枪战”主题。 下面代码将使用NMF设定2个关注主题,并通过分类,将电影分类为倾向主题1或者主题2的两类。同时将用户分为喜爱主题1或者喜爱主题2两个分类。

nmf_model = NMF(n_components=2) # 设有2个主题
item_dis = nmf_model.fit_transform(RATE_MATRIX)
user_dis = nmf_model.components_

print('用户的主题分布:')
print(user_dis)
print('电影的主题分布:')
print(item_dis)

使用上面的数据集,会得到如下结果:

用户的主题分布:
[[0.81240799 0.71153396 0.47062388 0.43807017 1.39456425 2.24323719
  1.02417204 1.25356481 1.10517661 1.47624595 1.84626347 0.97437242
  1.14921406 0.8159644  1.14200028]
 [2.23910382 1.70186882 1.34300272 1.09192602 0.68045441 0.
  0.0542231  0.         0.         0.         0.04426552 0.12260418
  0.34109613 0.51642843 0.6157604 ]]
电影的主题分布:
[[2.20401687 1.53852775]
 [1.9083879  0.83214869]
 [1.95596132 0.        ]
 [1.87637018 1.65573674]
 [2.48959328 1.41632377]
 [2.38108536 1.08460665]
 [0.         2.29342959]
 [0.         2.27353353]
 [0.         2.32513876]
 [0.         2.23196277]]

这些数据非常不利于观察理解。它们代表的概念是,比较接近的值,代表该影片或者该观众属于(或说喜爱)比较接近的主题。

数据可视化

为了观察起来更直观,可以使用绘图的代码把数据显示出来,从而更形象的理解“聚类”。

 #显示电影的坐标分布
plt1 = plt
plt1.plot(item_dis[:, 0], item_dis[:, 1], 'ro')
plt1.draw()#直接画出矩阵,只打了点,下面对图plt1进行一些设置

plt1.xlim((-1, 3))
plt1.ylim((-1, 3))
plt1.title(u'the distribution of items (NMF)')#设置图的标题

count = 1
zipitem = zip(item, item_dis)#把电影标题和电影的坐标联系在一起

for item in zipitem:
    item_name = item[0]
    data = item[1]
    plt1.text(data[0], data[1], item_name,
              fontproperties=fontP, 
              horizontalalignment='center',
              verticalalignment='top')
plt1.show()

 #显示用户的坐标分布
user_dis = user_dis.T #把转置用户分布矩阵
plt2 = plt
plt2.plot(user_dis[:, 0], user_dis[:, 1], 'ro')
plt2.xlim((-1, 3))
plt2.ylim((-1, 3))
plt2.title(u'the distribution of user (NMF)')#设置图的标题

zipuser = zip(user, user_dis)#把电影标题和电影的坐标联系在一起
for user in zipuser:
    user_name = user[0]
    data = user[1]
    plt2.text(data[0], data[1], user_name,
              fontproperties=fontP, 
              horizontalalignment='center',
              verticalalignment='top')
plt2.show()#直接画出矩阵,只打了点,下面对图plt1进行一些设置

以上代码会得到两张图,电影主题分布:

用户倾向主题分布:

从图中可以看到,我们分类比较少,数据也不是很准确,导致分布偏差比较大,但基本上是分成两类的。

电影推荐

采用这种方式,我们指定一个用户名,则可以为该用户推荐他倾向主题的电影。 本例中的数据偏差比较大,所以计算的结果有点没有说服力,仅供参考。

filter_matrix = RATE_MATRIX < 1e-8
rec_mat = np.dot(item_dis, user_dis)
print('重建矩阵,并过滤掉已经看过的电影')
rec_filter_mat = (filter_matrix * rec_mat).T
print(rec_filter_mat)

rec_user = 'Rusty'  # 需要进行推荐的用户
rec_userid = user.index(rec_user)  # 推荐用户ID
rec_list = rec_filter_mat[rec_userid, :]  # 推荐用户的电影列表

print('推荐用户的电影:')
print(np.nonzero(rec_list))

执行结果:

(array([2, 4, 6, 7, 8, 9]),)

完整代码

上面为了叙述方便,打散、简化并且省略了一些代码。下面是完整的代码,并且因为XJ同学的课程要求,使用了python3代码。嗯,python3对于中文的支持的确好了很多哈。

#!/usr/bin/env python3

#pip3 install sklearn scipy numpy matplotlib

from sklearn.decomposition import NMF
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

item = [
    '希特勒回来了', '死侍', '房间', '龙虾', '大空头',
    '极盗者', '裁缝', '八恶人', '实习生', '间谍之桥',
]
user = ['五柳君', '帕格尼六', '木村静香', 'WTF', 'airyyouth',
        '橙子c', '秋月白', 'clavin_kong', 'olit', 'You_某人',
        '凛冬将至', 'Rusty', '噢!你看!', 'Aron', 'ErDong Chen']
RATE_MATRIX = np.array(
    [[5, 5, 3, 0, 5, 5, 4, 3, 2, 1, 4, 1, 3, 4, 5],
     [5, 0, 4, 0, 4, 4, 3, 2, 1, 2, 4, 4, 3, 4, 0],
     [0, 3, 0, 5, 4, 5, 0, 4, 4, 5, 3, 0, 0, 0, 0],
     [5, 4, 3, 3, 5, 5, 0, 1, 1, 3, 4, 5, 0, 2, 4],
     [5, 4, 3, 3, 5, 5, 3, 3, 3, 4, 5, 0, 5, 2, 4],
     [5, 4, 2, 2, 0, 5, 3, 3, 3, 4, 4, 4, 5, 2, 5],
     [5, 4, 3, 3, 2, 0, 0, 0, 0, 0, 0, 0, 2, 1, 0],
     [5, 4, 3, 3, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
     [5, 4, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2],
     [5, 4, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]]
)

nmf_model = NMF(n_components=2) # 设有2个主题
item_dis = nmf_model.fit_transform(RATE_MATRIX)
user_dis = nmf_model.components_

print('用户的主题分布:')
print(user_dis)
print('电影的主题分布:')
print(item_dis)

filter_matrix = RATE_MATRIX < 1e-8
rec_mat = np.dot(item_dis, user_dis)
print('重建矩阵,并过滤掉已经评分的物品:')
rec_filter_mat = (filter_matrix * rec_mat).T
print(rec_filter_mat)

rec_user = 'Rusty'  # 需要进行推荐的用户
#rec_user = '凛冬将至'  # 需要进行推荐的用户
rec_userid = user.index(rec_user)  # 推荐用户ID
rec_list = rec_filter_mat[rec_userid, :]  # 推荐用户的电影列表

print('推荐用户的电影:')
print(np.nonzero(rec_list))


######################################################################
fontP = FontProperties(fname="/System/Library/Fonts/STHeiti Light.ttc")
fontP.set_size('small')

plt1 = plt
plt1.plot(item_dis[:, 0], item_dis[:, 1], 'ro')
plt1.draw()#直接画出矩阵,只打了点,下面对图plt1进行一些设置

plt1.xlim((-1, 3))
plt1.ylim((-1, 3))
plt1.title(u'the distribution of items (NMF)')#设置图的标题

count = 1
zipitem = zip(item, item_dis)#把电影标题和电影的坐标联系在一起

for item in zipitem:
    item_name = item[0]
    data = item[1]
    plt1.text(data[0], data[1], item_name,
              fontproperties=fontP, 
              horizontalalignment='center',
              verticalalignment='top')
plt1.show()

user_dis = user_dis.T #把转置用户分布矩阵
plt2 = plt
plt2.plot(user_dis[:, 0], user_dis[:, 1], 'ro')
plt2.xlim((-1, 3))
plt2.ylim((-1, 3))
plt2.title(u'the distribution of user (NMF)')#设置图的标题

zipuser = zip(user, user_dis)#把电影标题和电影的坐标联系在一起
for user in zipuser:
    user_name = user[0]
    data = user[1]
    plt2.text(data[0], data[1], user_name,
              fontproperties=fontP, 
              horizontalalignment='center',
              verticalalignment='top')
plt2.show()#直接画出矩阵,只打了点,下面对图plt1进行一些设置

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏灯塔大数据

每周学点大数据 | No.57基于内容的推荐方法

NO.57 基于内容的推荐方法 Mr. 王:最常见的一种方法就是基于内容的推荐。基于内容的推荐思想非常的清晰、简单,就是向用户推荐与他评分高(喜欢)项目相类似的...

34110
来自专栏人工智能头条

Google发布最新「语音命令」数据集,可有效提高关键词识别系统性能

932
来自专栏iOSDevLog

人工智能-总结

1796

度量采集军备竞赛中搭救的采样方法

MarketsAndMarkets在去年的一份报告中,预测IT运营分析(ITOA)市场将从2015年的21.7亿美元增长到2020年的9.79亿美元,2015年...

2376
来自专栏大数据挖掘DT机器学习

做股票数据挖掘的一些日志

首先说说数据挖掘吧,接触这东西也是机缘巧合,上学期听说ZYN学姐在做科创,于是问了问具体情况,她说跟数据挖掘有关,这词我还是第一次听说,听起来很高级啊,我看了些...

3895
来自专栏PPV课数据科学社区

R语言与数据分析之二:绘图

? 图形展示是最高效且形象的数据描述手段,因此巧妙的图像展示是高质量数据分析报告的必备内容,因此强大的图形展示功能也是统计分析软件的必备功能。R语言提供了强大...

39715
来自专栏月色的自留地

NMF学习练习:做电影推荐

1763
来自专栏量子位

DeepMind推出更难的机器阅读理解数据集,要让AI读懂整本书

夏乙 发自 凹非寺 量子位 出品 | 公众号 QbitAI 在机器阅读理解界的ImageNet——SQuAD挑战赛中,排在前几名的算法,都能拿到八十多分的成绩。...

3294
来自专栏FD的专栏

从人脸识别到机器翻译:52个有用的机器学习和预测API

人工智能正在成为新一代技术变革的基础技术,但从头开始为自己的应用和业务开发人工智能程序既成本高昂,且往往很难达到自己想要的性能表现,但好在我们有大量现成可用的 ...

661
来自专栏新智元

另一种开源:OpenAI 介绍深度学习基础设施

【新智元导读】OpenAI 昨天在博客发文,结合实例,介绍了 OpenAI 进行深度学习研究时采用的基础设施配置,并且提供了相关开源代码。文章激起了很多反响,其...

3745

扫码关注云+社区