不到100行程式碼實現一個簡單的推薦系統
一個好的推薦系統推薦的精度必然很高,能夠真的發現使用者的潛在需求或喜好,提高購物網詀的銷量,讓視訊網站發現使用者喜歡的收費電影… 可是要實現一個高精度的推薦系統不是那麼容易的,netflix曾經懸賞高額獎金尋找能給其推薦系統的精確度提高10%的人,可見各個公司對推薦系統的重視和一個好的推薦系統確實能帶來經濟效益。
下面咱以電影電視的推薦系統為例,一步一步的來實現一個簡單的推薦系統吧, 由於比較簡單,整個推薦系統原始碼不到100行,大概70-80行吧,應該很容易掌握。 為了快速開發原型,咱採用Python程式碼來演示
1. 推薦系統的第一步,需要想辦法收集資訊
不同的業務,不同的推薦系統需要收集的資訊不一樣 針對咱要做的電影推薦,自然是每個使用者對自己看過的電影的評價了,如下圖所示:
Kai Zhou對Friends打分是4分, 對Bedtime Stories打分是3分,沒有對RoboCop打分 Shuai Ge沒有對Friends打分,對Bedtime Stories打分是3.5分 …… 為簡單,咱將此資料存成csv檔案,形成一個二維的矩陣,假設存在D:\train.csv, 資料如下:
Name,Friends,Bedtime Stories,Dawn of the Planet of the Apes,RoboCop,Fargo,Cougar Town
Kai Zhou,4,3, 5,,1,2
Shuai Ge,,3.5,3,4,2.5,4.5
Mei Nv,3,4,2,3,2,3
xiaoxianrou,2.5,3.5,3,3.5,2.5,3
fengzhi,3,4,,5,3.5,3
meinv,,4.5,,4,1,
mincat,3,3.5,1.5,5,3.5,3
alex,2.5,3,,3.5,,4
先從csv檔案中載入二維矩陣,程式碼如下:
def load_matrix():
matrix = {}
f = open("d:\\train.csv")
columns = f.readline().split(',')
for line in f:
scores = line.split(',')
for i in range(len(scores))[1:]:
matrix[(scores[0], columns[i])] = scores[i].strip("\n")
return matrix
matrix = load_matrix()
print "matrix:", matrix
load_matrix()解析csv檔案,返回一個dictionary, 該dictionary以(行名,列名)為索引
資料有了,下面咱就正式開始幹活了 ,推薦系統要幹些什麼呢?
咱以電影推薦來說,推薦系統需要解決的幾個主要問題:
1. 判斷兩個電影,兩個觀影人之間的相似度
2. 找到和某影片最相似的影片, 或找到和某觀影人有同樣興趣的人
3. 找到某觀影人可能喜歡的電影,或找到對某影片感興趣的人
2. 推薦系統的基礎,判斷相似度
針對咱的電影推薦來說,就是判斷兩個電影,兩個觀影人之間的相似度。 2.1 歐幾里德距離計算相似度最簡單的,最容易理解的就是歐幾里德距離. 那麼,什麼是歐幾里德距離,怎麼用呢? 請對比評價資料,看下圖:
咱用兩個電影Fargo和Cougr Town來取例 圖中X軸代表電影Fargo, Y軸代表電影Cougr Town, Kai Zhou給電影Fargo 打1分,Cougr Town打2分,畫到圖上
同理,咱可以將Shuai Ge和Mei Nv的資料點都畫到圖上 很明顯,咱可以看出Kai Zhou與Mei Nv 離得近,與Shuai Ge離得遠,所以說Kai Zhou與Mei Nv的興趣更相近. 用數學式子表達出來就是:
Kai Zhou與Mei Nv的距離的平方: (2 – 1)^2 + (3 – 2)^2 = 2
Kai Zhou 與Shuai Ge的距離的平方: (2.5 – 1)^2 + (4.5 – 2)^2 = 8.5
2 < 8.5, 所以Kai Zhou與Mei Nv比Shuai Ge興趣更近. 這就是利用歐幾里得距離來判斷相似度 兩個使用者對所有電影的評價相似度的和,就是兩使用者的相似度
2.2 歸一化處理
為了方便比較處理後的資料,一般還需要對計算出來的結果進行歸一化處理。
資料標準化(歸一化)處理是資料探勘的一項基礎工作,不同評價指標往往具有不同的量綱和量綱單位,這樣的情況會影響到資料分析的結果,為了消除指標之間的量綱影響,需要進行資料標準化處理,以解決資料指標之間的可比性。
原始資料經過資料標準化處理後,各指標處於同一數量級,適合進行綜合對比評價。
上面的介紹太學術化了吧,不容易懂,我的理解:歸一化化就是要把你需要處理的資料經過處理後(通過某種演算法)限制在你需要的一 定範圍內。
簡單的說,我們希望,處理後的資料取值範圍在0-1之間. 在數學上有很多歸一化處理的方法 常用的有
一、min-max標準化(Min-Max Normalization)
也稱為離差標準化,是對原始資料的線性變換,使結果值對映到[0 – 1]之間。
二、Z-score標準化方法
這種方法給予原始資料的均值(mean)和標準差(standard deviation)進行資料的標準化。經過處理的資料符合標準正態分佈,即均值為0,標準差為1
咱可以根據需要選擇,不過,針對咱這系統採用的是歐幾里德距離,咱可以用下面的更簡單的公式:
假設計算出來的歐幾里德距離為:n
1 / (1 + n)
當距離為0,歸一化後的值為:1
距離越大,歸一化後的值越接近0
有了上面的基礎知識之後,下面的程式碼就水到渠成了
def sim_distance(matrix, row1, row2):
columns = set(map(lambda l: l[1], matrix.keys()))
si = filter(lambda l: matrix.has_key((row1, l)) and matrix[(row1, l)] != "" and matrix.has_key((row2, l)) and matrix[(row2, l)] != "", columns)
if len(si) == 0: return 0
sum_of_distance = sum([pow(float(matrix[(row1, column)]) - float(matrix[(row2, column)]), 2) for column in si])
return 1 / (1 + sqrt(sum_of_distance))
print sim_distance(matrix, "Kai Zhou", "Shuai Ge")
3. 找到和和某觀影人有同樣興趣的人,某影片最相似的影片
a.有了上面的程式碼,找到和某使用者有同樣興趣的人,就非常簡單了。只要將某使用者和其它所有使用者的相似度計算出來,排下序就行了。
def top_matches(matrix, row, similarity=sim_distance):
rows = set(map(lambda l: l[0], matrix.keys()))
scores = [(similarity(matrix, row, r), r) for r in rows if r != row]
scores.sort()
scores.reverse()
return scores
person = "Kai Zhou"
print "top match for:", person
print top_matches(matrix, person)
b. 找到和某影片相似的影片,這個需要稍微變化下。咱的輸入資料是以使用者為行資料,影片為列資料, 只要改成以影片為行資料,使用者為列資料,一樣的呼叫。 所以需要一個函式,將矩陣轉置
def transform(matrix):
rows = set(map(lambda l: l[0], matrix.keys()))
columns = set(map(lambda l: l[1], matrix.keys()))
transform_matrix = {}
for row in rows:
for column in columns:
transform_matrix[(column, row)] = matrix[(row, column)]
return transform_matrix
找到和Friends 相似的影片:
trans_matrix = transform(matrix)
print "trans:", trans_matrix
film = "Friends"
print "top match for:", film
print top_matches(trans_matrix, film)
4. 找到某觀影人可能喜歡的電影,找到對某影片感興趣的人
最理想的是找到兩個相似度一樣的人,可以認為某個人喜歡的電影,另外那個也喜歡。 但是這樣有它的缺點,比較好的辦法是把所有人的資料都用上,方法如下: 1. 先計算所有人和Kai Zhou的相似度 2. 對於Kai Zhou沒有看過,沒有評分,而其它人有評分的的影片, 將其評分與相似度相乘,得到的值再除以相似度之和 3. 排序 咱先以給Kai Zhou推薦影片為例來說明, Dawn of the Planet of the Apes 和 RoboCop 這兩部影片Kai Zhou都沒有看,我們該推薦他看哪部呢? 假設我們計算出來Kai Zhou與其它人的相似度如下:
[(0.3333333333333333, ‘Mei Nv’),
(0.29429805508554946, ‘xiaoxianrou’),
(0.2857142857142857, ‘alex’),
(0.2553967929896867, ‘mincat’),
(0.252650308587072, ‘Shuai Ge’),
(0.2474401533514073, ‘fengzhi’)]
即Kai Zhou與Mei Nv 相似度為0.3333333333333333, 與xiaoxiaorou相似度為0.29429805508554946, 其它類似… 那麼計算Dawn of the Planet of the Apes對Kai Zhou的推薦值過程如下:
1. 找到Shuai Ge對Dawn of the Planet of the Apes的評價值 乘以Shuai Ge與Kai Zhou的相似度: 3 * 0.252650308587072
2. 找到Mei Nv對Dawn of the Planet of the Apes的評價值 乘以其與Kai Zhou的相似度: 2 * 0.3333333333333333
3. 找到xiaoxianrou 對Dawn of the Planet of the Apes的評價值 乘以其與Kai Zhou的相似度: 3 * 0.29429805508554946
4. fengzhi 沒有對Dawn of the Planet of the Apes評價,不用計算
5. 找到mincat對Dawn of the Planet of the Apes的評價值 乘以其與Kai Zhou的相似度: 1.5 * 0.2553967929896867
6. alex 沒有對Dawn of the Planet of the Apes評價,不用計算
7. 將 1, 2, 3, 5 步的計算結果相加 得到: 3 * 0.252650308587072 + 2 * 0.3333333333333333 + 3 * 0.29429805508554946 + 1.5 * 0.2553967929896867 = 2.6906069471690612
8. 將1,2,3,5步的參與計算的人的相似度相加: 0.252650308587072 + 0.3333333333333333 + 0.29429805508554946 + 0.2553967929896867 = 1.1356784899956416
9. 將第7步結果除以第8步的結果,就是Dawn of the Planet對Kai Zhou的推薦值: 2.6906069471690612 / 1.1356784899956416 = 2.369162549851047
同樣的方法,計算出來RoboCop 對Kai Zhou的推薦值為:3.9277923180363326 所以RoboCop應該對Kai Zhou的吸引力比Dawn of the Planet of the Apes更大. 程式碼如下:
def get_recommendations(matrix, row, similarity=sim_distance):
rows = set(map(lambda l: l[0], matrix.keys()))
columns = set(map(lambda l: l[1], matrix.keys()))
sum_of_column_sim = {}
sum_of_column = {}
for r in rows:
if r == row: continue
sim = similarity(matrix, row, r)
if sim <= 0: continue
for c in columns:
if matrix[(r, c)] == "": continue
sum_of_column_sim.setdefault(c, 0)
sum_of_column_sim[c] += sim
sum_of_column.setdefault(c, 0)
sum_of_column[c] += float(matrix[(r, c)]) * sim
scores = [(sum_of_column[c] / sum_of_column_sim[c], c) for c in sum_of_column]
scores.sort()
scores.reverse()
return scores
print get_recommendations(matrix, person)
找到對某影片感興趣的人和之前類似,需要將矩陣轉置就行了,程式碼如下:
trans_matrix = transform(matrix)
print get_recommendations(trans_matrix, "Friends")
這就是一個簡單的推薦系統的雛型,當然,要實現一個可用的推薦系統,還有很多工作要做。比如推薦的精確度,使用者喜歡打鬥片,咱不可能給他推薦愛情片吧?比如資料量大了之後,效能問題,擴充套件性?是基於使用者推薦還是物品推薦?……
5. 參考資料
我為了瞭解學習推薦系統,找了一些資料,覺得下面的書值得閱讀:
1.《推薦系統實踐》項亮
入門級教材,很薄,可以很快就看完,把很多基礎而簡單的問題講的很詳細。總體來說,此書價效比很高,值得入手一本研讀
我買書喜歡上亞馬遜, 因為亞馬遜上很多都可以試讀,這本書亞馬遜就提供了試讀,推薦大家先去試讀下,再決定有沒有購買價值。
2.《Recommender Systems Handbook》Paul B. Kantor
有這本書就不用其它的了,很細很全,就是英文原版的有點小貴,真有志於做推薦系統的才去買吧,用到哪就翻書查。按人家的說法,所有敢自稱handbook的書都是神書,沒看過這本書出去吹牛逼時你都不好意思說自己是做推薦的。