機器學(xué)習(xí)實戰(zhàn)之集體智慧編程學(xué)習(xí)筆記(1):推薦物品

[TOC]

機器學(xué)習(xí)之集體智慧編程(1):推薦物品

前言

集體智慧編程作為機器學(xué)習(xí)的經(jīng)典入門書籍,很適合剛接觸機器入門與數(shù)據(jù)分析的小伙伴,剛好最近正在學(xué)習(xí)集體智慧編程,所以記錄一下學(xué)習(xí)過程與其中的知識點,加深自己的理解,也希望可以幫到更多的小伙伴

注意:

代碼都是Python寫的,如果有小伙伴對這個完全不了解又想學(xué)習(xí)的話,推薦先去看一下廖大的Python入門教程

在現(xiàn)代生活中,推薦系統(tǒng)與我們形影不離,無論是大型購物網(wǎng)站京東、淘寶、亞馬遜還是良心音樂播放器網(wǎng)易云音樂等等,都常會根據(jù)人們的操作記錄,給出相應(yīng)的推薦。這樣做的好處也顯而易見,針對不用用戶的喜好推薦不同的東西,更容易促進用戶的消費或者增加用戶的粘性。

后面我們會學(xué)習(xí)到如何根據(jù)用戶的操作給他們推薦更適合他們的物品,并且附有可以練習(xí)的詳細代碼

黑喂狗!

搜集數(shù)據(jù)

首先,要對人做出分析,肯定需要想辦法表達出此人的偏好。我們采用dict()字典作為數(shù)據(jù)的載體進行分析。

先新建一個包含了部分模擬數(shù)據(jù)的字典:

data = {
    '張三': {'亂世佳人': 3, '泰坦尼克號': 5, '功夫熊貓': 4.5, '小時代': 1, '爵跡': 0.4, '猩球崛起': 3.5},
    '李四': {'殺死比爾': 5, '超人': 4.5, '沉默的羔羊': 3, '功夫熊貓': 4, '小時代': 0.5, '爵跡': 5, '猩球崛起': 5},
    '王五': {'殺死比爾': 2, '超人': 1.5, '沉默的羔羊': 4, '功夫熊貓': 3.5, '讓子彈飛': 4.5, '爵跡': 3, '猩球崛起': 4},
    '趙六': {'殺死比爾': 2, '超人': 5, '繡春刀': 2, '功夫熊貓': 3, '小時代': 0.5, '爵跡': 1, '猩球崛起': 3.5},
    '陳七': {'鋼鐵俠': 1, '超人': 3.5, '沉默的羔羊': 2.5, '功夫熊貓': 4.5, '小時代': 5, '爵跡': 4, '猩球崛起': 2},
}

數(shù)據(jù)中包含了五個電影觀眾以及其對部分影片的打分情況,我們做一個簡單的列表,展示一下數(shù)據(jù)(由于篇幅限制,所以只能列舉5部看得人多的電影)

ps:我沒黑小四

殺死比爾 猩球崛起 功夫熊貓 小時代 超人
張三 3.5 4. 1
李四 5 5 4 0. 4.5
王五 2 4 3. 1.5
趙六 2 3. 3 0. 5
陳七 2 4.5 5 3.5

雖然我們現(xiàn)在有了直觀的數(shù)據(jù),但是還是無法看出到底誰跟張三的品味比較像,為了可以。所以我們現(xiàn)在要找到確認兩人相似性的方法,有很多種方法可以達到目的,我們選擇的是歐幾里得距離和皮爾遜相關(guān)度:

分析數(shù)據(jù),尋找相近用戶

歐幾里得距離評價

計算近似度時一個非常簡單的方法就是歐幾里得距離,其實大家對歐幾里得距離的內(nèi)容是非常熟悉的,但是可能對這個名字比較陌生。

如果我們把歐幾里得距離放在二維空間,就是大家耳熟能詳?shù)挠嬎銉牲c距離的公式:

所以計算n維空間中距離的公式為:
$\sqrt{(x1-x2)2+(y1-y2)2+...+(z1-z2)^2}$
我們可以把每個電影當(dāng)成一個坐標軸,把分數(shù)值當(dāng)成在此維度的值,套入公式就可以得到兩個人的區(qū)別度,這個值越大代表兩個人差別越大,但是這樣不利于我們分析人之間的相似度,所以我們把公式變形一下:
$\frac{1}{1+\sqrt{(x1-x2)2+(y1-y2)2+...+(z1-z2)^2}}$
這樣一來,我們計算出的結(jié)果就在0~1之間,并且值越大代表兩個人相似性越高

在Python中,我們通過pow(x,n)來表示x的n次方,通過math函數(shù)中的sqrt來開根號,根據(jù)公式,我們可以得到如下代碼:

import math
def sim_distance(pData, p1, p2):
    # 獲取歐幾里得距離
    #pData代表數(shù)據(jù)源,p1、p2代表兩個人
    same_item = []
    for key in pData[p1]:
        # 只比較共同的項目
        if key in pData[p2]:
            same_item.append(key)

    if len(same_item) == 0:
        return 0

    sum_of_squares = sum(pow(pData[p1][item] - pData[p2][item], 2) for item in same_item)
    return 1 / (1 + math.sqrt(sum_of_squares))

現(xiàn)在我們可以通過此公式來計算兩個人之間的相似度了:

print(sim_distance(data,'張三','李四'))
print(sim_distance(data,'張三','王五'))
print(sim_distance(data,'張三','趙六'))
print(sim_distance(data,'張三','陳七'))

運行結(jié)果:

0.16978547670707378
0.2610833580052755
0.3715878777036432
0.15182360441527204

這說明在我們的模擬數(shù)據(jù)中,趙六和張三的相似度最高

皮爾遜相關(guān)度評價

除了歐幾里得距離,還有一種更復(fù)雜一點的方法來判斷人之間的相似度,這就是皮爾遜相關(guān)系數(shù),皮爾遜相關(guān)系數(shù)用于度量兩個變量X和Y之間的相關(guān)(線性相關(guān)),其值介于-1與1之間,其中,1 表示變量完全正相關(guān), 0 表示無關(guān),-1 表示完全負相關(guān)。

公式:
$\frac{1}{n-1}\sum_{i=1}^{n}(\frac{xi-\bar x}{sx})(\frac{yi-\bar y}{sy})$
根據(jù)公式寫出代碼:

def sim_pearson(pData, p1, p2):
    # 求皮爾遜系數(shù)
    same_item = []
    #只計算共同項
    for key in pData[p1]:
        if key in pData[p2]:
            same_item.append(key)
    # 如果沒有共同項,返回-1
    n = len(same_item)
    if n == 0:
        return -1
    # 求各自評分之和
    sum1 = sum(pData[p1][item] for item in same_item)
    sum2 = sum(pData[p2][item] for item in same_item)

    # 求各自平方和
    sum1Sq = sum(pow(pData[p1][item], 2) for item in same_item)
    sum2Sq = sum(pow(pData[p2][item], 2) for item in same_item)
    #求乘積之和
    pSum = sum(pData[p1][item] * pData[p2][item] for item in same_item)

    num = pSum - (sum1 * sum2 / n)
    den = math.sqrt(abs((sum1Sq - pow(sum1, 2) / n) * (sum2Sq - pow(sum2, 2) / n)))

    if den == 0: return 0

    return num / den

使用代碼:

print(sim_pearson(data,'張三','李四'))
print(sim_pearson(data,'張三','王五'))
print(sim_pearson(data,'張三','趙六'))
print(sim_pearson(data,'張三','陳七'))

結(jié)果:

0.3118967974932389
0.7250594180737301
0.905203655171925
-0.3323774097784896

結(jié)果表明趙六和張三最相似

為評論者打分,找到最相似的

我們不可能每次都這樣一個一個去比較,所以需要一個函數(shù)可以幫我們拿某個人和其他人進行比較并排序:

def top_matches(pData, p, n=5, similarity=sim_distance):
    # 獲取數(shù)據(jù)源中某個key的相似數(shù)據(jù)并返回
    # 返回格式為[(相似度,key),(相似度,key2)...]
    total = [(similarity(pData, other, p), other) for other in pData if other != p]
    total.sort()
    total.reverse()
    return total[:n]

使用:

print(top_matches(data,'張三'))

結(jié)果:

[(0.3715878777036432, '趙六'), (0.2610833580052755, '王五'), (0.16978547670707378, '李四'), (0.15182360441527204, '陳七')]

我們也可以指定比較時使用sim_pearson函數(shù):

print(top_matches(data,'張三',similarity=sim_pearson))

結(jié)果:

[(0.905203655171925, '趙六'), (0.7250594180737301, '王五'), (0.3118967974932389, '李四'), (-0.3323774097784896, '陳七')]

推薦物品

找到相似的人并不是我們的主要目的,我們的目的是看和他相似的人看過或者用過的東西里,哪些更適合推薦給他,我們當(dāng)然可以找到和這個人最相近的人,然后從他沒看過的影片里選一個評分最高的影片來推薦,但是這樣做太隨意了,有可能遇到某個比較熱衷某部影片的人,我們需要更嚴謹?shù)乃惴?

相似度 殺死比爾 加權(quán)分數(shù) 超人 加權(quán)分數(shù)
李四 0.312 5 1.56 4.5 1.404
王五 0.725 2 1.45 1.5 1.0875
趙六 0.905 2 1.81 5 4.525
陳七(X) -0.332 3.5
分數(shù)總和 4.82 7.0165
權(quán)重總合 1.942 1.942
加權(quán)平均值 2.482 3.613

解釋一下,上圖中加權(quán)分數(shù)表示其他人對電影的評分乘以他與推薦人的相似度,即為加權(quán)分數(shù),通過計算加權(quán)分數(shù)的總和以及權(quán)重的總和,然后用分數(shù)總和除以權(quán)重總和,即可得到更加科學(xué)的加權(quán)平均值,也就是預(yù)期的評分,評分越高,說明越值得看

根據(jù)上述理論,寫出加權(quán)推薦物品的函數(shù):

def getRecommendations(pData, p, n=10, similarity=sim_pearson):
    # 基于人的推薦
    # 加權(quán)平均數(shù)推薦
    totals = dict()
    simSums = dict()
    for other in pData:
        # 不跟自己比較
        if other == p: continue
        # 取得相似度
        sim = similarity(pData, p, other)
        # 忽略相似度<=0
        if sim <= 0: continue
        # 只看自己還未評價的
        for item in pData[other]:
            if item not in pData[p] or pData[p][item] == 0:
                totals.setdefault(item, 0)
                # 針對某項的加權(quán)分數(shù)
                totals[item] += sim * pData[other][item]
                # 權(quán)重之和
                simSums.setdefault(item, 0)
                simSums[item] += sim
    result = [(totals[item] / simSums[item], item) for item in totals]
    result.sort()
    result.reverse()
    return result[:n]

上面的代碼遍歷了其他人,查看與被比較人的相似度,如果為負,則略過此人,如果為正,只考慮他評價體系中被比較人還沒看的,然后通過分數(shù)x權(quán)重,計算加權(quán)分數(shù)之和和權(quán)重之和,最后對所有條目計算加權(quán)平均值,取前n個數(shù)據(jù)展示

但是這種方法也存在弊端,對一個沒有操作記錄的新用戶,我們是無法找到和他相近的人的,那應(yīng)該怎么給他推薦物品呢?

所以我們應(yīng)該獲取一個以物品為中心的數(shù)據(jù)集:

def transformData(pData):
    # 轉(zhuǎn)換人/物評分
    transData = dict()
    for item in pData:
        for k, v in pData[item].items():
            transData.setdefault(k, {})
            transData[k][item] = v
    return transData

通過這個方法,我們拿到了物品中人的評分,然后可以調(diào)用top_matches()來查看物品的相近物品

基于物品的過濾

上面提到過的過濾方法,都是以人對物品所打的分數(shù)為基礎(chǔ)進行分析的,但是對于有大量商品的網(wǎng)站,要先計算出所有人的相似度再計算所有物品的加權(quán)分數(shù),其速度可想而知,而且隨著商品數(shù)量的增長,用戶之間的相似度也會變得更低,判斷難度也會增加,所以我們需要一個可以基于物品來進行過濾的算法

上述技術(shù)可以統(tǒng)稱為基于用戶的協(xié)作型過濾,接下來我們看一下基于物品的協(xié)作型過濾 ,其思路就是先計算出物品之間的相似度,然后推薦物品是找到與他相關(guān)的物品,計算加權(quán)后的分數(shù)排行,就可以了

實現(xiàn)每個條目和其他條目比較來此物品的相似物品:

def calculateSimItems(pData, n=10):
    # 獲取所有物品最相近的n條數(shù)據(jù)評分

    simItem = dict()
    # 獲取以物品為主的數(shù)據(jù)集
    itemData = transformData(pData)
    c = 0
    # 遍歷每個物品,找到最相近的n個物品
    for item in itemData:
        c += 1
        simItem.setdefault(item, {})
        # 初步篩選出相似物品
        simItem[item] = top_matches(itemData, item, n)
        # 顯示數(shù)據(jù)進度
        if c % 100 == 0: print('%s/%s' % (c, len(itemData)))

    return simItem

根據(jù)用戶的情況進行推薦:

def getRecommendedItems(pData, simItems, user, n=10):
    # 基于物品的推薦
    # 獲取用戶推薦列表,pData是以用戶為基礎(chǔ)的數(shù)據(jù)源,simItems是所有物品的相似性表,user是查詢用戶,n是查詢數(shù)據(jù)量

    scores = dict()
    total_sim = dict()
    # 獲取用戶打分情況
    userRating = pData[user]
    # 遍歷獲取用戶打分過的物品
    for item in userRating:
        # 獲取每個打分物品的相似物品,遍歷之
        for sim_score, sim_item in simItems[item]:
            # 如果物品已經(jīng)打過分,忽略
            if sim_item in userRating: continue
            # 獲取打分物品的分數(shù),求得相似物品的加權(quán)分數(shù)和
            scores.setdefault(sim_item, 0)
            scores[sim_item] += userRating[item] * sim_score
            #權(quán)重之和
            total_sim.setdefault(sim_item, 0)
            total_sim[sim_item] += sim_score
    # 取得數(shù)據(jù)集
    result = [(scores[item] / total_sim[item], item) for item in scores]
    result.sort()
    result.reverse()
    return result[:n]

為了驗證我們的推薦算法,可以從網(wǎng)上找到真實的電影評分數(shù)據(jù)來檢測,此處提供了一些真實的數(shù)據(jù),[點我下載][http://pan.baidu.com/s/1cDD9cq] ,下載下來解壓之后可以看到u.item和u.data兩個文件,將其放在項目目錄下,使用工具打開之后可以看到,u.item中數(shù)據(jù)格式:

電影編號     電影名
1|Toy Story (1995)|01-Jan-1995||......

前兩項分別代表了電影編號和電影名

u.data數(shù)據(jù)格式:

評分人 電影編號 分數(shù) 時間
196 242 3   881250949
186 302 3   891717742
22  377 1   878887116

每一行依次是評分人,電影編號,分數(shù),時間

有了以上數(shù)據(jù),我們就可以測試自己的系統(tǒng)了!

載入數(shù)據(jù):

def loadMovies():
    movies = dict()
    data = dict()
    with open('u.item', 'r') as f:
        for line in f:
            id, name = line.split('|')[0:2]
            movies.setdefault(id, '')
            movies[id] = name

    with open('u.data', 'r') as f:
        for line in f:
            user, movieid, rating, ts = line.split('\t')
            data.setdefault(user, {})
            if movieid in movies:
                data[user][movies[movieid]] = float(rating)
    return data

使用數(shù)據(jù):

data=loadMovies()
itemSim = calculateSimItems(data, 20)
result = getRecommendedItems(pData=data, simItems=itemSim, user='87')
print(str(result))

運行結(jié)果:

100/1656
200/1656
...
[(5.0, "What's Eating Gilbert Grape (1993)"), (5.0, 'Vertigo (1958)'), (5.0, 'Usual Suspects, The (1995)'), (5.0, 'Toy Story (1995)'), (5.0, 'Titanic (1997)'), (5.0, 'Time to Kill, A (1996)'), (5.0, 'Temptress Moon (Feng Yue) (1996)'), (5.0, 'Substitute, The (1996)'), (5.0, 'Spice World (1997)'), (5.0, 'Rent-a-Kid (1995)')]

這樣我們就拿到了最適合推薦給87號的電影名單

基于用戶還是基于物品?

在針對大數(shù)據(jù)進行推薦時,明顯基于物品的推薦比基于用戶運算量小,速度快,但是有額外的維護數(shù)據(jù)的開銷.同時,由數(shù)據(jù)'稀疏程度'不同帶來的差異也會影響我們的選擇,在電影的例子中,幾乎每個評論者對每部電影都有評價,所以數(shù)據(jù)是'密集的',無論我們使用哪種方法,都有大量數(shù)據(jù)支持.但是如果我們要找書簽/郵票等小眾物品的評價,就很難基于人來分析了,因為數(shù)據(jù)集非常稀疏.

對于稀疏數(shù)據(jù),基于物品的過濾通常更好,對于密集數(shù)據(jù),則相差不大.而且,對于數(shù)據(jù)更替較快且數(shù)據(jù)較小的數(shù)據(jù)集,基于用戶的過濾通常更好.而且有時候,告訴用戶有哪些人和他很像也有一定得價值

剛開始記錄,如果寫的不好請見諒0.0

有問題請留言

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容