1. 程式人生 > >[機器學習]推薦系統之協同過濾演算法

[機器學習]推薦系統之協同過濾演算法

在現今的推薦技術和演算法中,最被大家廣泛認可和採用的就是基於協同過濾的推薦方法。本文將帶你深入瞭解協同過濾的祕密。下面直接進入正題.

1. 什麼是推薦演算法

推薦演算法最早在1992年就提出來了,但是火起來實際上是最近這些年的事情,因為網際網路的爆發,有了更大的資料量可以供我們使用,推薦演算法才有了很大的用武之地。

最開始,所以我們在網上找資料,都是進yahoo,然後分門別類的點進去,找到你想要的東西,這是一個人工過程,到後來,我們用google,直接搜尋自己需要的內容,這些都可以比較精準的找到你想要的東西,但是,如果我自己都不知道自己要找什麼腫麼辦?最典型的例子就是,如果我開啟豆瓣找電影,或者我去買說,我實際上不知道我想要買什麼或者看什麼,這時候推薦系統就可以派上用場了。

2. 推薦演算法的條件

現在的各種各樣的推薦演算法,但是不管怎麼樣,都繞不開幾個條件,這是推薦的基本條件

1.根據和你共同喜好的人來給你推薦 
2.根據你喜歡的物品找出和它相似的來給你推薦 
3.根據你給出的關鍵字來給你推薦,這實際上就退化成搜尋演算法了 
4.根據上面的幾種條件組合起來給你推薦

3. 推薦演算法分類

推薦演算法大致可以分為三類:

3.1 基於內容的推薦演算法

基於內容的推薦演算法,原理是使用者喜歡和自己關注過的Item在內容上類似的Item,比如你看了哈利波特I,基於內容的推薦演算法發現哈利波特II-VI,與你以前觀看的在內容上面(共有很多關鍵詞)有很大關聯性,就把後者推薦給你,這種方法可以避免Item的冷啟動問題(冷啟動:如果一個Item從沒有被關注過,其他推薦演算法則很少會去推薦,但是基於內容的推薦演算法可以分析Item之間的關係,實現推薦),弊端在於推薦的Item可能會重複,典型的就是新聞推薦,如果你看了一則關於MH370的新聞,很可能推薦的新聞和你瀏覽過的,內容一致;另外一個弊端則是對於一些多媒體的推薦(比如音樂、電影、圖片等)由於很難提內容特徵,則很難進行推薦,一種解決方式則是人工給這些Item打標籤。

3.2 協同過濾推薦演算法

協同過濾演算法,原理是使用者喜歡那些具有相似興趣的使用者喜歡過的商品,比如你的朋友喜歡電影哈利波特I,那麼就會推薦給你,這是最簡單的基於使用者的協同過濾演算法(user-based collaboratIve filtering),還有一種是基於Item的協同過濾演算法(item-based collaborative filtering),這兩種方法都是將使用者的所有資料讀入到記憶體中進行運算的,因此成為Memory-based Collaborative Filtering,另一種則是Model-based collaborative filtering,包括Aspect Model,pLSA,LDA,聚類,SVD,Matrix Factorization等,這種方法訓練過程比較長,但是訓練完成後,推薦過程比較快。

3.3基於知識的推薦演算法。

最後一種方法是基於知識的推薦演算法,也有人將這種方法歸為基於內容的推薦,這種方法比較典型的是構建領域本體,或者是建立一定的規則,進行推薦。 混合推薦演算法,則會融合以上方法,以加權或者串聯、並聯等方式盡心融合。 當然,推薦系統還包括很多方法,其實機器學習或者資料探勘裡面的方法,很多都可以應用在推薦系統中,比如說LR、GBDT、RF(這三種方法在一些電商推薦裡面經常用到),社交網路裡面的圖結構等,都可以說是推薦方法。

今天這篇文章要講的基於使用者的協同過濾演算法.

3 什麼是協同過濾

協同過濾是利用集體智慧的一個典型方法。要理解什麼是協同過濾 (Collaborative Filtering, 簡稱 CF),首先想一個簡單的問題,如果你現在想看個電影,但你不知道具體看哪部,你會怎麼做?大部分的人會問問周圍的朋友,看看最近有什麼好看的電影推薦,而我們一般更傾向於從口味比較類似的朋友那裡得到推薦。這就是協同過濾的核心思想。

換句話說,就是借鑑和你相關人群的觀點來進行推薦,很好理解。

4 協同過濾的實現

要實現協同過濾的推薦演算法,要進行以下三個步驟:

4.1)收集資料

4.2)找到相似使用者和物品

4.3進行推薦

4.1 收集資料

這裡的資料指的都是使用者的歷史行為資料,比如使用者的購買歷史,關注,收藏行為,或者發表了某些評論,給某個物品打了多少分等等,這些都可以用來作為資料供推薦演算法使用,服務於推薦演算法。需要特別指出的在於,不同的資料準確性不同,粒度也不同,在使用時需要考慮到噪音所帶來的影響。

4.2找到相似使用者和物品

這一步也很簡單,其實就是計算使用者間以及物品間的相似度。以下是幾種計算相似度的方法: 
這裡寫圖片描述

4.3 進行推薦

在知道了如何計算相似度後,就可以進行推薦了。

在協同過濾中,有兩種主流方法:

1)基於使用者的協同過濾

2)基於物品的協同過濾

具體怎麼來闡述他們的原理呢,看個圖大家就明白了

基於使用者的 CF 的基本思想相當簡單,基於使用者對物品的偏好找到相鄰鄰居使用者,然後將鄰居使用者喜歡的推薦給當前使用者。計算上,就是將一個使用者對所有物品的偏好作為一個向量來計算使用者之間的相似度,找到 K 鄰居後,根據鄰居的相似度權重以及他們對物品的偏好,預測當前使用者沒有偏好的未涉及物品,計算得到一個排序的物品列表作為推薦。 下圖給出了一個例子,對於使用者 A,根據使用者的歷史偏好,這裡只計算得到一個鄰居 - 使用者 C,然後將使用者 C 喜歡的物品 D 推薦給使用者 A。 
這裡寫圖片描述 
基於物品的 CF 的原理和基於使用者的 CF 類似,只是在計算鄰居時採用物品本身,而不是從使用者的角度,即基於使用者對物品的偏好找到相似的物品,然後根據使用者的歷史偏好,推薦相似的物品給他。從計算的角度看,就是將所有使用者對某個物品的偏好作為一個向量來計算物品之間的相似度,得到物品的相似物品後,根據使用者歷史的偏好預測當前使用者還沒有表示偏好的物品,計算得到一個排序的物品列表作為推薦。下圖給出了一個例子,對於物品 A,根據所有使用者的歷史偏好,喜歡物品 A 的使用者都喜歡物品 C,得出物品 A 和物品 C 比較相似,而使用者 C 喜歡物品 A,那麼可以推斷出使用者 C 可能也喜歡物品 C。 
這裡寫圖片描述

演算法存在的問題

這個演算法實現起來也比較簡單,但是在實際應用中有時候也會有問題的。

比如一些非常流行的商品可能很多人都喜歡,這種商品推薦給你就沒什麼意義了,所以計算的時候需要對這種商品加一個權重或者把這種商品完全去掉也行。

再有,對於一些通用的東西,比如買書的時候的工具書,如現代漢語詞典,新華字典神馬的,通用性太強了,推薦也沒什麼必要了。

適用場景

  在非社交網路的網站中,內容內在的聯絡是很重要的推薦原則,它比基於相似使用者的推薦原則更加有效。比如在購書網站上,當你看一本書的時候,推薦引擎會給你推薦相關的書籍,這個推薦的重要性遠遠超過了網站首頁對該使用者的綜合推薦。可以看到,在這種情況下,Item CF 的推薦成為了引導使用者瀏覽的重要手段。同時 Item CF 便於為推薦做出解釋,在一個非社交網路的網站中,給某個使用者推薦一本書,同時給出的解釋是某某和你有相似興趣的人也看了這本書,這很難讓使用者信服,因為使用者可能根本不認識那個人;但如果解釋說是因為這本書和你以前看的某本書相似,使用者可能就覺得合理而採納了此推薦。

具體實現

# -*- coding=utf-8 -*-

import math
import sys
from texttable import Texttable


#
#   使用 |A&B|/sqrt(|A || B |)計算餘弦距離
#
#
#
def calcCosDistSpe(user1,user2):
    avg_x=0.0
    avg_y=0.0
    for key in user1:
        avg_x+=key[1]
    avg_x=avg_x/len(user1)

    for key in user2:
        avg_y+=key[1]
    avg_y=avg_y/len(user2)

    u1_u2=0.0
    for key1 in user1:
        for key2 in user2:
            if key1[1] > avg_x and key2[1]>avg_y and key1[0]==key2[0]:
                u1_u2+=1
    u1u2=len(user1)*len(user2)*1.0
    sx_sy=u1_u2/math.sqrt(u1u2)
    return sx_sy


#
#   計算餘弦距離
#
#
def calcCosDist(user1,user2):
    sum_x=0.0
    sum_y=0.0
    sum_xy=0.0
    for key1 in user1:
        for key2 in user2:
            if key1[0]==key2[0] :
                sum_xy+=key1[1]*key2[1]
                sum_y+=key2[1]*key2[1]
                sum_x+=key1[1]*key1[1]

    if sum_xy == 0.0 :
        return 0
    sx_sy=math.sqrt(sum_x*sum_y) 
    return sum_xy/sx_sy


#
#
#   相似餘弦距離
#
#
#
def calcSimlaryCosDist(user1,user2):
    sum_x=0.0
    sum_y=0.0
    sum_xy=0.0
    avg_x=0.0
    avg_y=0.0
    for key in user1:
        avg_x+=key[1]
    avg_x=avg_x/len(user1)

    for key in user2:
        avg_y+=key[1]
    avg_y=avg_y/len(user2)

    for key1 in user1:
        for key2 in user2:
            if key1[0]==key2[0] :
                sum_xy+=(key1[1]-avg_x)*(key2[1]-avg_y)
                sum_y+=(key2[1]-avg_y)*(key2[1]-avg_y)
        sum_x+=(key1[1]-avg_x)*(key1[1]-avg_x)

    if sum_xy == 0.0 :
        return 0
    sx_sy=math.sqrt(sum_x*sum_y) 
    return sum_xy/sx_sy


#
#   讀取檔案
#
#
def readFile(file_name):
    contents_lines=[]
    f=open(file_name,"r")
    contents_lines=f.readlines()
    f.close()
    return contents_lines



#
#   解壓rating資訊,格式:使用者id\t硬碟id\t使用者rating\t時間
#   輸入:資料集合
#   輸出:已經解壓的排名資訊
#
def getRatingInformation(ratings):
    rates=[]
    for line in ratings:
        rate=line.split("\t")
        rates.append([int(rate[0]),int(rate[1]),int(rate[2])])
    return rates


#
#   生成使用者評分的資料結構
#   
#   輸入:所以資料 [[2,1,5],[2,4,2]...]
#   輸出:1.使用者打分字典 2.電影字典
#   使用字典,key是使用者id,value是使用者對電影的評價,
#   rate_dic[2]=[(1,5),(4,2)].... 表示使用者2對電影1的評分是5,對電影4的評分是2
#
def createUserRankDic(rates):
    user_rate_dic={}
    item_to_user={}
    for i in rates:
        user_rank=(i[1],i[2])
        if i[0] in user_rate_dic:
            user_rate_dic[i[0]].append(user_rank)
        else:
            user_rate_dic[i[0]]=[user_rank]

        if i[1] in item_to_user:
            item_to_user[i[1]].append(i[0])
        else:
            item_to_user[i[1]]=[i[0]]

    return user_rate_dic,item_to_user


#
#   計算與指定使用者最相近的鄰居
#   輸入:指定使用者ID,所以使用者資料,所以物品資料
#   輸出:與指定使用者最相鄰的鄰居列表
#
def calcNearestNeighbor(userid,users_dic,item_dic):
    neighbors=[]
    #neighbors.append(userid)
    for item in users_dic[userid]:
        for neighbor in item_dic[item[0]]:
            if neighbor != userid and neighbor not in neighbors: 
                neighbors.append(neighbor)

    neighbors_dist=[]
    for neighbor in neighbors:
        dist=calcSimlaryCosDist(users_dic[userid],users_dic[neighbor])  #calcSimlaryCosDist  calcCosDist calcCosDistSpe
        neighbors_dist.append([dist,neighbor])
    neighbors_dist.sort(reverse=True)
    #print neighbors_dist
    return  neighbors_dist


#
#   使用UserFC進行推薦
#   輸入:檔名,使用者ID,鄰居數量
#   輸出:推薦的電影ID,輸入使用者的電影列表,電影對應使用者的反序表,鄰居列表
#
def recommendByUserFC(file_name,userid,k=5):

    #讀取檔案資料
    test_contents=readFile(file_name)

    #檔案資料格式化成二維陣列 List[[使用者id,電影id,電影評分]...] 
    test_rates=getRatingInformation(test_contents)

    #格式化成字典資料 
    #    1.使用者字典:dic[使用者id]=[(電影id,電影評分)...]
    #    2.電影字典:dic[電影id]=[使用者id1,使用者id2...]
    test_dic,test_item_to_user=createUserRankDic(test_rates)

    #尋找鄰居
    neighbors=calcNearestNeighbor(userid,test_dic,test_item_to_user)[:k]

    recommend_dic={}
    for neighbor in neighbors:
        neighbor_user_id=neighbor[1]
        movies=test_dic[neighbor_user_id]
        for movie in movies:
            #print movie
            if movie[0] not in recommend_dic:
                recommend_dic[movie[0]]=neighbor[0]
            else:
                recommend_dic[movie[0]]+=neighbor[0]
    #print len(recommend_dic)

    #建立推薦列表
    recommend_list=[]
    for key in recommend_dic:
        #print key
        recommend_list.append([recommend_dic[key],key])


    recommend_list.sort(reverse=True)
    #print recommend_list
    user_movies = [ i[0] for i in test_dic[userid]]

    return [i[1] for i in recommend_list],user_movies,test_item_to_user,neighbors



#
#
#   獲取電影的列表
#
#
#
def getMoviesList(file_name):
    #print sys.getdefaultencoding()
    movies_contents=readFile(file_name)
    movies_info={}
    for movie