集體智慧程式設計 第二章 匹配商品
我們在前面學習瞭如何為指定人員尋找品味相近的人,以及如何向其推薦商品。但是如果我們想了解哪些商品是彼此相近的,應該如何做?
匹配商品
比如我們去淘寶,點選某個商品的時候,側面總會給我們推薦一些類似商品。這是如何做到的呢?
首先我們要將之前的:{'Person':{'movie':score}} 的形式換成 {'Movie':{'person':score}}
用下列程式碼實現:
#這個函式就是將字典裡面的人員和物品對調 def transformPrefs(prefs): result = {} for person in prefs: for item in prefs[person]: result.setdefault(item, {}) #將物品和人員對調 result[item][person] = prefs[person][item] return result
然後執行程式碼中新增:
print '\n=========================================='
print 'topMatches-Superman Returns'
movies = recommendations.transformPrefs(recommendations.critics)
print recommendations.topMatches(movies, 'Superman Returns')
可以看出結果:
現在我們就得到了一組與《Superman Returns》最為相近的電影。但是有一些相關評價值為負數,這說明喜歡《Superman Returns》的人,存在不喜歡《Just My Luck》的傾向。我們還可以為影片推薦評論者。
新增執行程式碼:
print '\n=========================================='
print 'getRecommendations'
movies = recommendations.transformPrefs(recommendations.critics)
print recommendations.getRecommendations(movies, 'Just My Luck')
當然,書上也強調了對調人和物不一定總是有意義的。但是大多數情況下,這都有助於做出對比。
=============================================================================
構建一個基於 del.icio.us 的連結推薦系統
這一節我們將學習如何從線上書籤網站上面獲得資料,如何利用這些資料查詢使用者,並向他們推薦以前沒看過的連結。
通過 API 訪問 del.icio.us 網站獲得的資料是以 XML 格式返回的,我們先要安裝一些東西。
書上給出了兩個連結,下載已經寫好了的 API:
當然我跟我一步一步來更簡單:
2.用winRAR開啟,解壓到桌面,在 PowerShell 裡面各種 cd 進入解壓資料夾,輸入
python setup.py install
feedparser安裝完畢3.進入網站 https://pypi.python.org/pypi/pydelicious 下載 pydelicious 0.6.1。下載檔案為:pydelicious-0.6.1.tar.gz
4.用winRAR開啟,解壓到桌面,進去再次解壓 pydelicious-0.6.1.tar ,在 PowerShell 裡面各種 cd 進入解壓資料夾,輸入
python setup.py install
pydelicious安裝完畢好了,安裝完畢我們繼續下一步走起。
跟著教材走,建立一個 run2.py:
import pydelicious
print pydelicious.get_popular(tag = 'programming')
執行發現:
吃屎啊...怎麼破?
書上還給了兩個選擇,這裡選擇用 Dorisa 使用者為例:
print pydelicious.get_userposts('dorisa')
然後還是不行。delicious.com/rss 破網站真的有毒,十有八九被牆了。
於是我決定死馬當成活馬醫,繼續跟書走吧。
============================================================
構造資料集
主要是下載一個子集資訊。
新建一個 deliciousrec.py 檔案:
# -*- coding:utf-8 -*-
# deliciousrec.pydelicous
from pydelicious import get_popular, get_userposts, get_urlposts
# 執行這個程式碼活的一個包含若干使用者資料的字典,其中每一項都各自指向一個等待填入具體連結的空字典。
def initializeUserDict(tag, count = 5):
user_dict = {}
# 獲取前 count 個最受歡迎的連結張貼記錄
for p1 in get_popular(tag = tag)[0:count]:
for p2 in get_urlposts(p1['href']):
user = p2['user']
user_dict[user] = {}
return user_dict
# 只有兩種評價值:
# 0:使用者沒有張貼這一連結
# 1:使用者張貼了這一連結
def fillItem(user_dict):
all_items = {}
# 查詢所有使用者都提交過的連結
for user in user_dict:
for i in range(3):
try:
posts = get_userposts(user)
break
except:
print "Failed user '+user+', retrying"
time.sleep(4)
for post in posts:
url = post['herf']
user_dict[user][url] = 1.0
all_items[url] = 1
# 用 0 填充缺失的專案
for ratings in user_dict.values():
for item in all_items:
if tiem not in ratings:
ratings[item] = 0.0
利用這個函式構造一個數據集,類似之前的影評字典:
# -*- coding:utf-8 -*-
# run deliciousrec.py
from deliciousrec import *
delusers = initializeUserDict('programming')
delusers ['tsegaran'] = {} # 如果你也是用 delicious,則將自己也加入字典中
fillItems(delusers)
這裡的第三行程式碼將使用者 tsegaran 新增到了列表中。假如你也是使用 del.icio.us,則不妨以自己的名字來替換 tsegaran。
然後對於 fillItems 的呼叫要花費幾分鐘的時間來執行,因為要向網站發起數百個請求。
當然對於網站被牆來說並無卵用。
=============================================================================
推薦近鄰與連結
為了隨機選擇一個使用者,並且找出與其品味相近的其他使用者,在 run2 裡面新增程式碼:
import random
user = delusers.keys()[random.randint(0, len(delusers) - 1)]
print user
print recommendations.topMatches(delusers, user)
也可以通過呼叫 getRecommendations 函式為該使用者獲取推薦連結。
print recommendations.getRecommendations(delusers, user)[0:10]
也可以依據連結來搜尋:
url = recommendations.getRecommendations(delusers,user)[0][1]
print recommendations.topMatches(recommendations.transformPrefs(delusers), url)
這樣我們就給網站增加了一個推薦引擎。
=============================================================================
基於物品的過濾
剛剛的方法對於上千的使用者和物品規模是沒問題的,對於像淘寶亞馬遜這樣的網站,把單個使用者和其他所有使用者比較太慢了。之前我們採用的方法叫做基於使用者的協作型過濾,此外,還有一種方法叫做基於物品的協作型過濾。在大資料情況下,這種方法能得出更好的結論。總體思路就是為每件物品預先計算好最為相近的其他物品。區別在於物品間的比較不會像使用者間的比較那麼頻繁變化。
=============================================================================
構造物品比較資料集
為了對物品比較,在 recommendations.py 裡面加入:def calculateSimilarItems(prefs, n = 10):
# 建立字典,以給出與這些物品最為相近的所有其他物品
result = {}
# 以物品為中心對偏好矩陣實施倒置處理
itemPrefs = transformPrefs(prefs)
c = 0
for item in temPrefs:
# 針對大資料集更新狀態變數
c += 1
if c % 100 == 0:print "%d / %d" % (c, len(itemPrefs))
# 尋找最為相近的物品
scores = topMatches(itemPrefs, item, n = n, similarity = sim_distance)
result[item] = scores
return result # 返回一個包含物品及其最相近物品列表的字典
先對反映評價值的字典倒置處理,從而得到一個有關物品及其使用者評價的列表。然後程式迴圈遍歷每個物品,並且把轉換了的字典傳入 topMatches 函式中,求得最為相近的物品及其相似度評價值。
在 run1.py 新增:
print '\n=========================================='
print 'itemsim'
itemsim = recommendations.calculateSimilarItems(recommendations.critics)
print itemsim
這裡結果很有意思,書上第一個是0.4,而我計算的則是0.44。仔細檢查發現書上的示例程式碼中 sim_distance 函式的返回值如果不加 sqrt 的話就是一致的。歸根結底應該是書上的程式碼有點問題。
以上是結果。
=============================================================================
獲得推薦
現在我們可以在不遍歷整個資料集的情況下,利用反映物品相似度的字典給出推薦。具體數學方法參見書上。在 recommendations.py 裡面加入:
def getRecommendedItems(prefs, itemMatch, user):
userRatings = prefs[user]
scores = {}
totalSim = {}
# 迴圈遍歷由當前使用者評分的物品
for (item, rating) in userRatings.items(): # dict.items() 此方法返回元組對的列表。
# 尋遍遍歷與當前物品相機的物品
for (similarity, item2) in itemMatch[item]:
# 如果該使用者已經對當前物品做過評價,則將其忽略
if item2 in userRatings: continue
# 評價值與相似度的加權之和
scores.setdefault(item2, 0) # setdefault 見前面註釋
scores[item2] += similarity * rating
# 全部相似度之和
totalSim.setdefault(item2, 0)
totalSim[item2] += similarity
# 將每個合計值除以加權和,求出平均值
rankings = [(score / totalSim[item], item) for item, score in scores.items()]
# 按最高值到最低值的順序,返回評分結果
rankings.sort()
rankings.reverse()
return rankings
run1.py:
print '\n=========================================='
print 'getRecommendedItems'
print recommendations.getRecommendedItems(recommendations.critics, itemsim, 'Toby')
結果:
可以看出推薦物品排行。
=============================================================================
使用 MovieLens 資料集
從 http://www.grouplens.org/node/73 下載 MovieLens 資料集。注意下載的是小的資料集,十萬資料的那個。解壓開啟檔案,我們主要關心的是 movies.csv 和 ratings.csv ,前者包含了一組有關影片ID和片面的列表,後者是實際評價情況。
每位使用者都對較多影片做出過評價。在 recommendations.py 中新建一個方法,取名 loadMovieLens,用以載入資料。
def loadMovieLens(path = 'MovieLens-latest-small/ml-latest-small'):
# 獲取影片標題
movies = {}
for line in open(path + '/movies.csv'):
(id, title, genres) = line.split('|')[0:2] # 這裡檔案中第三列是影片型別,略作修改
movies[id] = title # 把 title 和 id對應
# 載入資料
prefs = {}
for line in open(path + '/ratings.csv'):
(user, movieid, rating, ts) = line.split('\t') # 分割
prefs.setdefault(user, {})
prefs[user][movies[movieid]] = float(rating)
return prefs
很遺憾,就卡在了這一步。這裡讀不出來。也不知道是哪裡出錯了。
雖然止步於最後一步,但是基於使用者和基於物品的推薦大概方法已經過了一遍。
文末還指出,對於稀疏資料集,基於物品的過濾方法通常要優於基於使用者的過濾方法,對於密集資料集則兩者差不多。