歡迎來到AI入門 - 集體智慧編程的第一章,這一章節(jié)里Pan會帶著你制作一個簡單的推薦系統(tǒng)。

學(xué)習(xí)成果
- 基于電影影評推薦和你喜好相同的人(交友)
- 基于電影影評推薦和自己喜好相同的電影(愛好)

編程小技巧
# 列表解析式
>>> a = [i for i in range(3)]
>>> a
>>>[1, 2, 3]
# 字典設(shè)置默認(rèn)鍵
>>> a = {}
>>> a.setdefault("A", 1)
>>> a.setdefault("A", 2)
>>> a
# 猜猜A的值是幾?
>>> {"A":1}
# setdefault設(shè)置默認(rèn)鍵,如果有的話不會更改原來的值
什么是推薦?
試想你有沒有這樣的經(jīng)歷,每次看電影都需要在找電影上花費好多時間,好不容易找到了卻發(fā)現(xiàn)內(nèi)容爛如**,最穩(wěn)妥的方法就是查看影評,這就和我們早淘寶買東西首先查看銷量第一是一個道理,畢竟群眾的眼睛是雪亮的。如果一個一個地去查看影評還是會花費很長的時間,聰明的人就想電腦似乎可以幫助我們識別哪些影評更適合我們,其中運用了一些數(shù)學(xué)原理進(jìn)行一些計算從而得出哪部電影最適合我們,所以這就是最基本的推薦系統(tǒng)。
基本數(shù)據(jù)
首先我們得掌握一些數(shù)據(jù),不然巧婦難為無米之炊啊~ ,可以看到這只是一些小型數(shù)據(jù),但是對于數(shù)據(jù)集也是管用的。 請創(chuàng)建一個叫做recommend.py的文件并把下面的數(shù)據(jù)粘貼進(jìn)去。
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},
'櫻花之眼': {'養(yǎng)家之人': 3.6,
'小蘿莉的猴神大叔': 2.3,
'無巧不成婚': 1.1,
'無手的少女': 0.4,
'比得兔': 1.2,
'那就是我的世界': 3.1},
'浮華': {'養(yǎng)家之人': 1.1,
'天上再見': 3.6,
'怪奇秀': 3.0,
'無巧不成婚': 4.9,
'比得兔': 4.4,
'金錢世界': 1.1}
}
基于電影評價推薦相似的用戶

人以類聚,物以群分,人們的喜好都是有聚群現(xiàn)象的,你喜歡的物品肯定會有一群人也喜歡,電影也是如此。以上述數(shù)據(jù)為例子,吧唧吧唧和小島木看過的電影相似。但是,最關(guān)鍵的是影評不盡相同,可以從上圖看到擬合出的直線的斜率已經(jīng)為負(fù)數(shù),這代表兩人完全不相似,因此他們不是同一類人,所以我們需要智能識別那些人是一類的。

怎么判斷兩個人是相似的?
提起這個問題首先會想到肉眼識別唄,兩個人之間同一部電影之間的分?jǐn)?shù)差值越小越相似唄,答對了,不過有更佳科學(xué)的計算方法:
- 歐幾里得距離(Euclid Distance Score)
什么鬼?哪來的鬼名字?別害怕~在這裝X名字的背后其實是你小學(xué)已經(jīng)學(xué)過的數(shù)學(xué)方法,就是平面間兩點間的距離公式。嘿嘿簡單吧,我決定為了讓看過此專題的同學(xué)也能裝X,我會把所有數(shù)學(xué)方法的英文名字都寫出來.. 請把以下代碼加入到你的recommend.py中
def sim_distance(prefs, person1, person2):
# 創(chuàng)建一個字典,此處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))
- 皮爾遜相關(guān)度評價(Pearson Correlation Score)
這和上面的有什么不同?當(dāng)然不同了,不然我寫出來干什么?
簡單的說如果兩個人對于相同電影的評價總是保持固定差值時,用皮爾遜系數(shù)最好,這時候使用歐幾里得距離會相似值偏小,請把以下代碼加入到你的recommend.py中
既然這讓不妨把數(shù)學(xué)公式展示出來,(抱歉懶得打latex公式了),書上的是把n除以在了分母,所以無所謂了??梢钥吹叫枰嬎愕木褪莤的求和,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
# 對應(yīng)上面公式x的求和
sum1 = sum([prefs[person1][it] for it in si])
sum2 = sum([prefs[person2][it] for it in si])
# 對應(yīng)上面公式y(tǒng)的求和
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])
# 對應(yīng)上面公式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
好友推薦
主要記住的是前面的代碼只會返回兩個人之間的相似指數(shù),并不會返回這個人與其他所有的相似程度,所以需要做點小工作就可以同城交友啦
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, '吧唧吧唧')]

電影推薦給個人
好了好了別忘了我們的最終目的,尋找自己喜歡的電影,節(jié)省自己的時間。那么如何根據(jù)自己已有的數(shù)據(jù)向一個用戶推薦有哪些感興趣的電影呢?其中需要注意幾個問題:
- 電影都是來源于數(shù)據(jù),所以我們推薦的電影都是此用戶沒有看過但是有人評價過的
- 不同人對于電影的評價不盡相同,所以相似度大的用戶推薦的電影更有意義
- 此用戶沒看過的電影可能評價人數(shù)是不一樣的,不能因為人數(shù)的差異造成結(jié)果失精
怎么解決這三個問題呢?
- 第一個很好解決就是篩選哪些自己沒看過的電影
- 第二個問題的解決方法是我們已經(jīng)求出了每個人的相似度,所以拿每個人的相似度乘以自己看過的待評測電影得到每部電影的權(quán)重評分,相似度高的人推薦的電影要比相似度低的人分?jǐn)?shù)要高。
- 第三個問題的解決方法如下:
現(xiàn)算出每個人的所有電影權(quán)重評分總值Sim,再算出所有人評價過此電影的相似度總和Sim_Sum,結(jié)果就是拿Sim / (Sim_Sum)。 可以看到結(jié)果是拿評論過的電影權(quán)重除以評論過的人的到的值,所以人數(shù)并不會影響最終的結(jié)果。
請把下面的代碼加入到recommond.py之中
def get_recommendation(prefs, person, similarity=sim_person):
totals = {}
sim_sum = {}
for other in prefs:
if other == person:
continue
# 平價值小于0的不要,因為沒有意義已經(jīng)道不同了..
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, '養(yǎng)家之人')]
電影推薦給用戶
上面我們已經(jīng)成功地推薦給一個人的電影了,不要忘記我們是程序員,我們的工作是給所有用戶推薦,所以最后一步是推薦給所有的電影,請把下面的代碼加入到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, '養(yǎng)家之人'), (2.5, '樂高DC超級英雄:閃電俠')]
mypd: [(3.6, '養(yǎng)家之人'), (2.620729210755176, '那就是我的世界'), (2.034961466845575, '比得兔')]
Pan: [(4.169491235277953, '無巧不成婚'), (3.6, '天上再見'), (2.2035185653797735, '那就是我的世界'), (1.592271386904664, '養(yǎng)家之人')]
小島木: [(4.026438603356924, '比得兔'), (3.6, '天上再見'), (2.3, '無手的少女'), (1.1, '養(yǎng)家之人')]
小草: [(2.5, '樂高DC超級英雄:閃電俠'), (1.9, '無手的少女')]
太好了我們成功給所有人推薦了電影,現(xiàn)在你可以享受AI自動找片的快感了!

附加
- 本文章中完整代碼在這里
- 未經(jīng)允許,不得轉(zhuǎn)載~