打算從這篇開始,一邊學習一邊寫些資料採礦的東西,主要是督促自己學習和總結。
我最開始的網購是從china-pub買了一本《Unix/Linux編程實踐教程》,書好,便宜,並且可以貨到付款,很是吸引我這種懶窮學生,於是一發不可收拾買了很多書,後來轉戰dangdang,再後來就是amazon,現在基本都在jd買了,除了書,還會在yihaodian買一些日用品。後來發現這幾家都會有推薦,dangdang和china-pub的推薦沒什麼印象,jd的推薦離我的興趣點差的挺遠,印象深刻的就是amazon,有次推送的郵件真是推到我心坎坎了。這次我也來做一個木偶推薦系統玩玩。
好吧,今晚閑來無聊,想找本書來消遣。現在書籍甚是豐富,買哪本書呢?
1)我會打電話問問朋友,請求推薦;
2)還會登陸網站看暢銷top100,選一本自己感興趣的;
慢慢就會發現問題,第一:朋友推薦和top100的書籍比較穩定,新意不多,看完了想看新的就沒了,第二:這裡面很合自己脾氣的書不多,自己很厭倦太巨頭或太學府派的書,還有就是現在的top100很多孕育和養生的東東,目前我就先忍忍吧。
好了,既然目前都在說大資料,怎麼用大資料進行推薦呢?我首先想到的是,我喜歡《Unix/Linux編程實踐教程》,和這本內容寫作風格近似的也應該是我喜歡的,我喜歡packt出版社的一本書,可能這個出版社出版的其他書我也喜歡(事實也是我非常喜歡這個出版社的書),我喜歡《Think
in C++》,我可能還會喜歡bruce eckel出的其他書(事實也是這樣)。還有一種情況,我是碼農,我有個碼農朋友,我兩興趣愛好相投,他喜歡的書可能也是我喜歡的書,我喜歡的書也可能是他喜歡的書(事實也是這樣)。好,總結一下,第一種基於物品的推薦,我買了A,很可能會喜歡和A相似的B,第二種基於使用者推薦,我和C志趣相投,他喜歡D,我可能也喜歡D。
我們先來說說評價,最先想到的評價有兩種,買與不買(1和0);買了打幾顆星(通常滿星是五顆,0,1,2,3,4,5),還有容許打半顆星的,這樣就有(0,
0.5,1,1.5,2,2.5 。。。5)。下面我們用0--5之間的小數表示評價,值越打表示評價越高,比如,我喜歡A,我給他5星,一般喜歡B,我給他3.8星,我很討厭C,我給他0星;於是有了下面一個變數ctitics:
critics = {'user1': {'goods1': 2.5, 'goods2': 3.5, 'goods3': 3.0, 'goods4': 3.5, 'goods5': 2.5, 'goods6': 3.0},'user2': {'goods1': 3.0, 'goods2': 3.5, 'goods3': 1.5, 'goods4': 5.0, 'goods6': 3.0, 'goods5': 3.5}, 'user3': {'goods1': 2.5, 'goods2': 3.0, 'goods4': 3.5, 'goods6': 4.0},'user4': {'goods2': 3.5, 'goods3': 3.0, 'goods6': 4.5, 'goods4': 4.0, 'goods5': 2.5},'user5': {'goods1': 3.0, 'goods2': 4.0, 'goods3': 2.0, 'goods4': 3.0, 'goods6': 3.0, 'goods5': 2.0}, 'user6': {'goods1': 3.0, 'goods2': 4.0, 'goods6': 3.0, 'goods4': 5.0, 'goods5': 3.5},'user7': {'goods2': 4.5, 'goods5': 1.0, 'goods4': 4.0}}
下面來定義“相似”,“相似”就是在某些方面很接近,怎麼考量相似呢,對上面的變數ctitics,怎麼判斷user1和那個其他user*相似,然後給出推薦呢,首先想到的是歐幾裡得距離評價,這就是最簡單的求兩點距離的公式,
下面來計算任意兩個使用者之間的“相似性”:
from math import sqrtdef sim_distance(prefs,person1,person2): si={} for item in prefs[person1]: if item in prefs[person2]: si[item]=1 if len(si)==0: return 0 sum_of_squares=sum([pow(prefs[person1][item]-prefs[person2][item],2) for item in prefs[person1] if item in prefs[person2]]) return 1/(1+sqrt(sum_of_squares))
注意最後一行,按照基本的歐幾裡得距離,兩個越相近的人距離值越小,通常我們的做法是近似度越大會給出越大的值,所以這個修正總會返回0-1之間的值,返回1表示兩人居右完全相同的愛好。
好吧,我們中總會有一些人很挑剔,總有一些人不那麼挑剔,這些挑剔的人總趨向於給出整體偏低的評價(3,2,1),有些不那麼挑剔的人總趨向與給出整體偏高的評價(5,4,3),但是這兩種人的整體偏好相似,都會給goods1一個在他們認為較高的星星數(3和5),都會給goods2一個在他們認為較差的星星數(1和3)。這種情況下,用歐幾裡得距離計算的結果就不那麼具有競爭力了,如何修正這種偏差呢?我們來看看皮爾遜相關度評價。
下面來計算使用者之間的皮爾遜相關度:
def sim_pearson(prefs,p1,p2): si={} for item in prefs[p1]: if item in prefs[p2]: si[item]=1 if len(si)==0: return 0 n=len(si) sum1=sum([prefs[p1][it] for it in si]) sum2=sum([prefs[p2][it] for it in si]) sum1Sq=sum([pow(prefs[p1][it],2) for it in si]) sum2Sq=sum([pow(prefs[p2][it],2) for it in si]) pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si]) num=pSum-(sum1*sum2/n) den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n)) if den==0: return 0 r=num/den return r
還有很多計算相似性的方式,等用到時再學習吧。
下面我們就可以判斷任意使用者的相似性了:
def topMatches(prefs,person,n=5,similarity=sim_pearson): scores=[(similarity(prefs,person,other),other) for other in prefs if other!=person] scores.sort() scores.reverse() return scores[0:n]
注意topMatches的最後一個參數是一個函數名,是我們上面說的計算相似性的任意一種方法,只要他們有相同的函數簽名即可,這樣我們可以隨時換用我們想用的相似性計算方式。
來看看在我機器上計算出來的效果吧:
>>> import test>>> test.topMatches(test.critics, 'user1')[(0.9912407071619299, 'user7'), (0.7470178808339965, 'user6'), (0.5940885257860044, 'user5'), (0.5669467095138396, 'user4'), (0.40451991747794525, 'user3')]>>> test.topMatches(test.critics, 'user2')[(0.963795681875635, 'user6'), (0.41176470588235276, 'user5'), (0.39605901719066977, 'user1'), (0.38124642583151164, 'user7'), (0.31497039417435607, 'user4')]>>> test.topMatches(test.critics, 'user3')[(1.0, 'user4'), (0.40451991747794525, 'user1'), (0.20459830184114206, 'user2'), (0.13483997249264842, 'user6'), (-0.2581988897471611, 'user5')]>>> test.topMatches(test.critics, 'user4')[(1.0, 'user3'), (0.8934051474415647, 'user7'), (0.5669467095138411, 'user5'), (0.5669467095138396, 'user1'), (0.31497039417435607, 'user2')]>>> test.topMatches(test.critics, 'user5')[(0.9244734516419049, 'user7'), (0.5940885257860044, 'user1'), (0.5669467095138411, 'user4'), (0.41176470588235276, 'user2'), (0.21128856368212925, 'user6')]>>> test.topMatches(test.critics, 'user6')[(0.963795681875635, 'user2'), (0.7470178808339965, 'user1'), (0.66284898035987, 'user7'), (0.21128856368212925, 'user5'), (0.13483997249264842, 'user3')]>>> test.topMatches(test.critics, 'user7')[(0.9912407071619299, 'user1'), (0.9244734516419049, 'user5'), (0.8934051474415647, 'user4'), (0.66284898035987, 'user6'), (0.38124642583151164, 'user2')]
好了,終於找到了臭味相投的同志,給出一個推薦呢?
(1)我們可以從志投道和的user中,找一個他評價很高而我們沒有看過的一個goods,
(2)我們在所有志同道合的user中,用相似性和評價的一個加權評價值來給googs打分,從而形成一個排名進而推薦。
很顯然,我們採用第二種,為此,我們需要取得其他評論者相似性後,再乘以評論者為每個goods的評價值,就會得到我們想要的排名。
這中間會有一個問題,就是如果一個goods的評論user很多,那麼最終排名會比較靠前,而較少評論的goods最最終的影響會比較小,通常這樣沒什麼問題,我們在這裡稍微做一些修正,就是用對這個goods評價的所有其他users的相似性之和除以最終的排名值,這樣會公平一些,哈哈哈,來看代碼:
def getRecommendations(prefs,person,similarity=sim_pearson): totals={} simSums={} for other in prefs: if other==person: continue 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 simSums.setdefault(item,0) simSums[item]+=sim rankings=[(total/simSums[item],item) for item,total in totals.items()] rankings.sort() rankings.reverse() return rankings
下面,我們可以進行推薦啦,來看看我計算的結果。user1,user2,user5評價全了所有goods,所以給不出新推薦。
>>> import test>>> test.getRecommendations(test.critics, 'user1')[]>>> test.getRecommendations(test.critics, 'user2')[]>>> test.getRecommendations(test.critics, 'user3')[(2.8092760065251268, 'goods3'), (2.694636703980363, 'goods5')]>>> test.getRecommendations(test.critics, 'user4')[(2.683756272799255, 'goods1')]>>> test.getRecommendations(test.critics, 'user5')[]>>> test.getRecommendations(test.critics, 'user6')[(2.1505590044630245, 'goods3')]>>> test.getRecommendations(test.critics, 'user7')[(3.3477895267131013, 'goods6'), (2.832549918264162, 'goods1'), (2.5309807037655645, 'goods3')]
上面我們不僅計算出了相似性,還給出了推薦。值得一提的是,計算相似性不僅可以計算user之間的相似性,還可以計算goods之間的相似性,根據user已買的物品推薦相似性較高的物品在jd和amazon也很常見。