欢迎来到AI入门 - 集体智慧编程的第一章,这一章节里Pan会带着你制作一个简单的推荐系统。
学习成果
- 基于电影影评推荐和你喜好相同的人(交友)
- 基于电影影评推荐和自己喜好相同的电影(爱好)
编程小技巧
# 列表解析式
>>> a = [i for i in range(3)]
>>> a
>>>[1, 2, 3]
# 字典设置默认键
>>> a = {}
>>> a.setdefault("A", 1)
>>> a.setdefault("A", 2)
>>> a
# 猜猜A的值是几?
>>> {"A":1}
# setdefault设置默认键,如果有的话不会更改原来的值
什么是推荐?
试想你有没有这样的经历,每次看电影都需要在找电影上花费好多时间,好不容易找到了却发现内容烂如**,最稳妥的方法就是查看影评,这就和我们早淘宝买东西首先查看销量第一是一个道理,毕竟群众的眼睛是雪亮的。如果一个一个地去查看影评还是会花费很长的时间,聪明的人就想电脑似乎可以帮助我们识别哪些影评更适合我们,其中运用了一些数学原理进行一些计算从而得出哪部电影最适合我们,所以这就是最基本的推荐系统。
基本数据
首先我们得掌握一些数据,不然巧妇难为无米之炊啊~ ,可以看到这只是一些小型数据,但是对于数据集也是管用的。 请创建一个叫做recommend.py
的文件并把下面的数据粘贴进去。
critics = {
'Pan': {'乐高DC超级英雄:闪电侠': 2.9,
'小萝莉的猴神大叔': 2.7,
'怪奇秀': 1.7,
'无手的少女': 2.3,
'比得兔': 3.5,
'金钱世界': 0.5},
'mypd': {'乐高DC超级英雄:闪电侠': 2.5,
'天上再见': 0.6,
'小萝莉的猴神大叔': 4.9,
'无巧不成婚': 0.3,
'无手的少女': 1.9,
'金钱世界': 4.3},
'吧唧吧唧': {'天上再见': 3.7,
'小萝莉的猴神大叔': 4.7,
'无巧不成婚': 1.6,
'无手的少女': 2.0,
'比得兔': 1.9,
'那就是我的世界': 3.1},
'小岛木': {'乐高DC超级英雄:闪电侠': 3.6,
'小萝莉的猴神大叔': 2.1,
'怪奇秀': 2.8,
'无巧不成婚': 4.2,
'那就是我的世界': 1.9,
'金钱世界': 1.5},
'小草': {'天上再见': 1.9,
'小萝莉的猴神大叔': 1.6,
'无巧不成婚': 0.7,
'比得兔': 3.3,
'那就是我的世界': 1.4,
'金钱世界': 3.1},
'樱花之眼': {'养家之人': 3.6,
'小萝莉的猴神大叔': 2.3,
'无巧不成婚': 1.1,
'无手的少女': 0.4,
'比得兔': 1.2,
'那就是我的世界': 3.1},
'浮华': {'养家之人': 1.1,
'天上再见': 3.6,
'怪奇秀': 3.0,
'无巧不成婚': 4.9,
'比得兔': 4.4,
'金钱世界': 1.1}
}
基于电影评价推荐相似的用户
人以类聚,物以群分,人们的喜好都是有聚群现象的,你喜欢的物品肯定会有一群人也喜欢,电影也是如此。以上述数据为例子,吧唧吧唧
和小岛木
看过的电影相似。但是,最关键的是影评不尽相同,可以从上图看到拟合出的直线的斜率已经为负数,这代表两人完全不相似
,因此他们不是同一类人,所以我们需要智能识别那些人是一类的。
怎么判断两个人是相似的?
提起这个问题首先会想到肉眼识别呗,两个人之间同一部电影之间的分数差值越小越相似呗,答对了,不过有更佳科学的计算方法:
- 欧几里得距离(Euclid Distance Score)
什么鬼?哪来的鬼名字?别害怕~在这装X名字的背后其实是你小学已经学过的数学方法,就是平面间两点间的距离公式。嘿嘿简单吧,我决定为了让看过此专题的同学也能装X,我会把所有数学方法的英文名字都写出来.. 请把以下代码加入到你的recommend.py
中
def sim_distance(prefs, person1, person2):
# 创建一个字典,此处person1是需要交友的,所以我们需要
# 判断person1中哪些电影是person2看过的
si = {}
for item in prefs[person1]:
if item in prefs[person2]:
si[item] = 1
# 很不巧person2看过的电影person1一部都没看过,还交啥友,返回0呗
if len(si) == 0:
return 0
# 1 / (1 + math.sqrt(sum_of_squares)),为什么给分母加1呢?因为math.sqrt()可能会返回0,所以程序就会出错
sum_of_squares = sum(math.pow(prefs[person1][item] - prefs[person2][item], 2) for item in si)
return 1 / (1 + math.sqrt(sum_of_squares))
- 皮尔逊相关度评价(Pearson Correlation Score)
这和上面的有什么不同?当然不同了,不然我写出来干什么?
简单的说如果两个人对于相同电影的评价总是保持固定差值时,用皮尔逊系数最好,这时候使用欧几里得距离会相似值偏小,请把以下代码加入到你的recommend.py
中
既然这让不妨把数学公式展示出来,(抱歉懒得打latex公式了),书上的是把n除以在了分母,所以无所谓了。可以看到需要计算的就是x的求和,y的求和,xy的求和,x ^ 2的求和,y ^ 2的求和。
def sim_person(prefs, person1, person2):
si = {}
for item in prefs[person1]:
if item in prefs[person2]:
si[item] = 1
n = len(si)
if len(si) == 0:
return 0
# 对应上面公式x的求和
sum1 = sum([prefs[person1][it] for it in si])
sum2 = sum([prefs[person2][it] for it in si])
# 对应上面公式y的求和
pow_sum1 = sum([math.pow(prefs[person1][it], 2) for it in si])
pow_sum2 = sum([math.pow(prefs[person2][it], 2) for it in si])
# 对应上面公式xy的求和
pSum = sum([prefs[person1][it] * prefs[person2][it] for it in si])
num = pSum - (sum1 * sum2 / n)
den = math.sqrt((pow_sum1 - pow(sum1, 2) / n) * (pow_sum2 - pow(sum2, 2) / n))
if den == 0:
return 0
r = num / den
return r
好友推荐
主要记住的是前面的代码只会返回两个人之间的相似指数,并不会返回这个人与其他所有的相似程度,所以需要做点小工作就可以同城交友啦
def topMatches(prefs, person, n=5, similarity=sim_person):
scores = [(similarity(prefs, person, other), other) for other in prefs if person != other]
scores.sort()
scores.reverse()
return scores[0:n]
# 试试我自己,棒耶,我和'浮华'是真基友!
>>> topMatches(critics, "Pan")
>>> [(0.9796238966216864, '浮华'),
(0.7094388947838616, '小岛木'),
(0.24019223070763174, '樱花之眼'),
(-0.15472327075742795, '小草'),
(-0.21979763892240334, '吧唧吧唧')]
电影推荐给个人
好了好了别忘了我们的最终目的,寻找自己喜欢的电影,节省自己的时间。那么如何根据自己已有的数据向一个用户推荐有哪些感兴趣的电影呢?其中需要注意几个问题:
- 电影都是来源于数据,所以我们推荐的电影都是此用户没有看过但是有人评价过的
- 不同人对于电影的评价不尽相同,所以相似度大的用户推荐的电影更有意义
- 此用户没看过的电影可能评价人数是不一样的,不能因为人数的差异造成结果失精
怎么解决这三个问题呢?
- 第一个很好解决就是筛选哪些自己没看过的电影
- 第二个问题的解决方法是我们已经求出了每个人的相似度,所以拿每个人的相似度乘以自己看过的待评测电影得到每部电影的权重评分,相似度高的人推荐的电影要比相似度低的人分数要高。
- 第三个问题的解决方法如下:
现算出每个人的所有电影权重评分总值Sim,再算出所有人评价过此电影的相似度总和Sim_Sum,结果就是拿Sim / (Sim_Sum)。 可以看到结果是拿评论过的电影权重除以评论过的人的到的值,所以人数并不会影响最终的结果。
请把下面的代码加入到recommond.py
之中
def get_recommendation(prefs, person, similarity=sim_person):
totals = {}
sim_sum = {}
for other in prefs:
if other == person:
continue
# 平价值小于0的不要,因为没有意义已经道不同了..
sim = similarity(prefs, person, other)
if sim < 0:
continue
for item in prefs[other]:
# 只对自己没看过的电影做评价
if item not in prefs[person] or prefs[person][item] == 0:
totals.setdefault(item, 0)
totals[item] += prefs[other][item] * sim
# 相似度之和计算上面的sim_sum
sim_sum.setdefault(item, 0)
sim_sum[item] += sim
# sum / sim_sum解决的是上面第三个问题
ranking = [(total / sim_sum[item], item) for item, total in totals.items() if sim_sum[item] != 0]
ranking.sort()
ranking.reverse()
return ranking
#测试下,好耶我竟然是婆婆剧脑残粉!
>>> get_recommendation(critics, "Pan")
>>> [(4.169491235277953, '无巧不成婚'),
(3.6, '天上再见'),
(2.203518565379774, '那就是我的世界'),
(1.5922713869046639, '养家之人')]
电影推荐给用户
上面我们已经成功地推荐给一个人的电影了,不要忘记我们是程序员,我们的工作是给所有用户推荐,所以最后一步是推荐给所有的电影,请把下面的代码加入到recommond.py
之中
def recommend_all(prefers):
for i in users:
print(i + ":", end="")
print("%5s" % "", end="")
print(get_recommendation(prefers, i))
# 测试下
>>> recommend_all(prefers=critics)
>>>
樱花之眼: [(3.3783874757609436, '金钱世界'), (2.5970118446567425, '乐高DC超级英雄:闪电侠'), (2.0745414665137667, '天上再见'), (1.7, '怪奇秀')]
浮华: [(3.2535625249491025, '乐高DC超级英雄:闪电侠'), (2.396946407186484, '小萝莉的猴神大叔'), (2.3, '无手的少女'), (1.9, '那就是我的世界')]
吧唧吧唧: [(4.3, '金钱世界'), (3.5999999999999996, '养家之人'), (2.5, '乐高DC超级英雄:闪电侠')]
mypd: [(3.6, '养家之人'), (2.620729210755176, '那就是我的世界'), (2.034961466845575, '比得兔')]
Pan: [(4.169491235277953, '无巧不成婚'), (3.6, '天上再见'), (2.2035185653797735, '那就是我的世界'), (1.592271386904664, '养家之人')]
小岛木: [(4.026438603356924, '比得兔'), (3.6, '天上再见'), (2.3, '无手的少女'), (1.1, '养家之人')]
小草: [(2.5, '乐高DC超级英雄:闪电侠'), (1.9, '无手的少女')]
太好了我们成功给所有人推荐了电影,现在你可以享受AI自动找片的快感了!
附加
- 本文章中完整代码在这里
- 未经允许,不得转载~