1. 程式人生 > >集體智慧程式設計 第二章 匹配商品

集體智慧程式設計 第二章 匹配商品

我們在前面學習瞭如何為指定人員尋找品味相近的人,以及如何向其推薦商品。但是如果我們想了解哪些商品是彼此相近的,應該如何做?

匹配商品

比如我們去淘寶,點選某個商品的時候,側面總會給我們推薦一些類似商品。這是如何做到的呢?

首先我們要將之前的:{'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

很遺憾,就卡在了這一步。這裡讀不出來。也不知道是哪裡出錯了。

雖然止步於最後一步,但是基於使用者和基於物品的推薦大概方法已經過了一遍。

文末還指出,對於稀疏資料集,基於物品的過濾方法通常要優於基於使用者的過濾方法,對於密集資料集則兩者差不多。