1. 程式人生 > >基於標籤的使用者推薦系統

基於標籤的使用者推薦系統

from:http://blog.csdn.net/gamer_gyt 

1:聯絡使用者興趣和物品的方式

2:標籤系統的典型代表

3:使用者如何打標籤

4:基於標籤的推薦系統

5:演算法的改進

6:標籤推薦

原始碼檢視地址:github檢視

一:聯絡使用者興趣和物品的方式

    推薦系統的目的是聯絡使用者的興趣和物品,這種聯絡方式需要依賴不同的媒介。目前流行的推薦系統基本上是通過三種方式聯絡使用者興趣和物品。

                                  

       1:利用使用者喜歡過的物品,給使用者推薦與他喜歡過的物品相似的物品,即基於item的系統過濾推薦演算法(演算法分析可參考:點選閱讀

       2:利用使用者和興趣使用者興趣相似的其他使用者,給使用者推薦哪些和他們興趣愛好相似的其他使用者喜歡的物品,即基於User的協同過濾推薦演算法(演算法分析可參考:點選閱讀

       3:通過一些特徵聯絡使用者和物品,給使用者推薦那些具有使用者喜歡的特徵的物品,這裡的特徵有不同的表現形式,比如可以表現為物品的屬性集合,也可以表現為隱語義向量,而下面我們要討論的是一種重要的特徵表現形式——標籤

二:標籤系統的典型代表

       pass掉那些國外網站,比如說豆瓣圖書(左),網易雲音樂(右)

                

    標籤系統確實能夠幫助使用者發現他們喜歡和感興趣的物品

三:使用者如何打標籤

       在網際網路中每個人的行為都是隨機的,但其實這些表面的行為隱藏著很多規律,那麼我們對使用者打的標籤進行統計呢,便引入了標籤流行度,我們定義的一個標籤被一個使用者使用在一個物品上,他的流行度就加1,可以如下程式碼實現:

#統計標籤流行度
def TagPopularity(records):
    tagfreq = dict()
    for user, item ,tag in records:
        if tag not in tagfreq:
            tagfreq[tag] = 1
        else:
            tagfreq[tag] +=1
    return tagfreq

      下面的是一個標籤流行度分佈圖(橫座標是標籤流行度K,縱座標是流行度K對應的標籤數目),其也是符合典型的長尾分佈,他的雙對數曲線幾乎是一條直線

 

                                    

 

            在使用者看到一個物品時,我們希望他打的標籤是能夠準確的描述物品內容屬性的關鍵詞,但使用者往往不是按照我們的想法操作,而是可能給物品打上各種各樣奇奇怪怪的標籤,此時便需要我們人工編輯一些特定的標籤供使用者選擇,Scott A. Golder 總結了Delicious上的標籤,將它們分為如下幾類。

 

 表明物品是什麼 比如是一隻鳥,就會有“鳥”這個詞的標籤;是豆瓣的首頁,就有一個標籤叫“豆瓣”;是喬布斯的首頁,就會有個標籤叫“喬布斯”。
 表明物品的種類 比如在Delicious的書籤中,表示一個網頁類別的標籤包括 article(文章)、blog(部落格)、 book(圖書)等。
 表明誰擁有物品 比如很多部落格的標籤中會包括部落格的作者等資訊。
 表達使用者的觀點 比如使用者認為網頁很有趣,就會打上標籤funny(有趣),認為很無聊,就會打上標籤boring(無聊)。
 使用者相關的標籤 比如 my favorite(我最喜歡的)、my comment(我的評論)等。
 使用者的任務 比如 to read(即將閱讀)、job search(找工作)

比如在豆瓣上,標籤便被分為如下幾個類別

                                                                     

 

四:基於標籤的推薦系統

       使用者用標籤來描述對物品的看法,因此標籤是聯絡使用者和物品的紐帶,也是反應使用者興趣的重要資料來源,如何利用使用者的標籤資料提高個性化推薦結果的質量是推薦系統研究的重要課題。

       豆瓣很好地利用了標籤資料,它將標籤系統融入到了整個產品線中。

       首先,在每本書的頁面上,豆瓣都提供了一個叫做“豆瓣成員常用標籤”的應用,它給出了這本書上使用者最常打的標籤。

       同時,在使用者給書做評價時,豆瓣也會讓使用者給圖書打標籤。

       最後,在最終的個性化推薦結果裡,豆瓣利用標籤將使用者的推薦結果做了聚類,顯示了對不同標籤下使用者的推薦結果,從而增加了推薦的多樣性和可解釋性。

        一個使用者標籤行為的資料集一般由一個三元組的集合表示,其中記錄(u, i, b) 表示使用者u給物品i打上了標籤b。當然,使用者的真實標籤行為資料遠遠比三元組表示的要複雜,比如使用者打標籤的時間、使用者的屬性資料、物品的屬性資料等。但是為了集中討論標籤資料,只考慮上面定義的三元組形式的資料,即使用者的每一次打標籤行為都用一個三元組(使用者、物品、標籤)表示。

       1:試驗設定

        本節將資料集隨機分成10份。這裡分割的鍵值是使用者和物品,不包括標籤。也就是說,使用者對物品的多個標籤記錄要麼都被分進訓練集,要麼都被分進測試集,不會一部分在訓練集,另一部分在測試集中。然後,我們挑選1份作為測試集,剩下的9份作為訓練集,通過學習訓練集中的使用者標籤資料預測測試集上使用者會給什麼物品打標籤。對於使用者u,令R(u)為給使用者u的長度為N的推薦列表,裡面包含我們認為使用者會打標籤的物品。令T(u)是測試集中使用者u實際上打過標籤的物品集合。然後,我們利用準確率(precision)和召回率(recall)評測個性化推薦演算法的精度。

                                                             

         將上面的實驗進行10次,每次選擇不同的測試集,然後將每次實驗的準確率和召回率的平均值作為最終的評測結果。為了全面評測個性化推薦的效能,我們同時評測了推薦結果的覆蓋率(coverage)、多樣性(diversity)和新穎度。覆蓋率的計算公式如下:

                                                                        

接下來我們用物品標籤向量的餘弦相似度度量物品之間的相似度。對於每個物品i,item_tags[i]儲存了物品i的標籤向量,其中item_tags[i][b]是對物品i打標籤b的次數,那麼物品i和j的餘弦相似度可以通過如下程式計算。

#計算餘弦相似度
def CosineSim(item_tags,i,j):
    ret = 0
    for b,wib in item_tags[i].items():     #求物品i,j的標籤交集數目
        if b in item_tags[j]:
            ret += wib * item_tags[j][b]
    ni = 0
    nj = 0
    for b, w in item_tags[i].items():      #統計 i 的標籤數目
        ni += w * w
    for b, w in item_tags[j].items():      #統計 j 的標籤數目
        nj += w * w
    if ret == 0:
        return 0
    return ret/math.sqrt(ni * nj)          #返回餘弦值

       在得到物品之間的相似度度量後,我們可以用如下公式計算一個推薦列表的多樣性:

 

                                            

      Python實現為:

#計算推薦列表多樣性
def Diversity(item_tags,recommend_items):
    ret = 0
    n = 0
    for i in recommend_items.keys():
        for j in recommend_items.keys():
            if i == j:
                continue
            ret += CosineSim(item_tags,i,j)
            n += 1
    return ret/(n * 1.0)

     推薦系統的多樣性為所有使用者推薦列表多樣性的平均值。
     至於推薦結果的新穎性,我們簡單地用推薦結果的平均熱門程度(AveragePopularity)度量。對於物品i,定義它的流行度item_pop(i)為給這個物品打過標籤的使用者數。而對推薦系統,我們定義它的平均熱門度如下:

                                                   

       2:一個簡單的演算法

       拿到了使用者標籤行為資料,相信大家都可以想到一個最簡單的個性化推薦演算法。這個演算法的
       描述如下所示。
        統計每個使用者最常用的標籤。
        對於每個標籤,統計被打過這個標籤次數最多的物品。
        對於一個使用者,首先找到他常用的標籤,然後找到具有這些標籤的最熱門物品推薦給這個使用者。
       對於上面的演算法,使用者u對物品i的興趣公式如下:

                                                                    

        這裡,B(u)是使用者u打過的標籤集合,B(i)是物品i被打過的標籤集合,nu,b是使用者u打過標籤b的次數,nb,i是物品i被打過標籤b的次數。本章用SimpleTagBased標記這個演算法。
        在Python中,我們遵循如下約定:
         用 records 儲存標籤資料的三元組,其中records[i] = [user, item, tag];
         用 user_tags 儲存nu,b,其中user_tags[u][b] = nu,b;
         用 tag_items儲存nb,i,其中tag_items[b][i] = nb,i。
        如下程式可以從records中統計出user_tags和tag_items:

<span style="font-family:Microsoft YaHei;">#從records中統計出user_tags和tag_items
def InitStat(records):
    user_tags = dict()
    tag_items = dict()
    user_items = dict()
    for user, item, tag in records.items():
        addValueToMat(user_tags, user, tag, 1)
        addValueToMat(tag_items, tag, item, 1)
        addValueToMat(user_items, user, item, 1)</span>

       統計出user_tags和tag_items之後,我們可以通過如下程式對使用者進行個性化推薦:

<span style="font-family:Microsoft YaHei;">#對使用者進行個性化推薦
def Recommend(user):
    recommend_items = dict()
    tagged_items = user_items[user]
    for tag, wut in user_tags[user].items():
        for item, wti in tag_items[tag].items():
            #if items have been tagged, do not recommend them
            if item in tagged_items:
                continue
            if item not in recommend_items:
                recommend_items[item] = wut * wti
            else:
                recommend_items[item] += wut * wti
    return recommend_items
</span>

五:演算法的改進

 

      再次回顧四中提出的簡單演算法

                                                                   

     該演算法存在許多缺點,比如說對於熱門商品的處理,資料洗漱性的處理等,這也是在推薦系統中經常會遇見的問題

      1:TF-IDF

      前面這個公式傾向於給熱門標籤對應的熱門物品很大的權重,因此會造成推薦熱門的物品給使用者,從而降低推薦結果的新穎性。另外,這個公式利用使用者的標籤向量對使用者興趣建模,其中每個標籤都是使用者使用過的標籤,而標籤的權重是使用者使用該標籤的次數。這種建模方法的缺點是給熱門標籤過大的權重,從而不能反應使用者個性化的興趣。這裡我們可以借鑑TF-IDF的思想,對這一公式進行改進:

                                                                   

         這裡,記錄了標籤b被多少個不同的使用者使用過。這個演算法記為TagBasedTFIDF。

         同理,我們也可以借鑑TF-IDF的思想對熱門物品進行懲罰,從而得到如下公式:

                                                                  

         其中, 記錄了物品i被多少個不同的使用者打過標籤。這個演算法記為TagBasedTFIDF++。

         2:資料稀疏性

          在前邊的演算法中,使用者興趣和物品的聯絡是通過B(u) B(i)交集得到的,但是對於新使用者,這個交集的結果將會非常小,為了提高推薦結果的可靠性,這裡我們要對標籤進行擴充套件,,比如若使用者曾經用過“推薦系統”這個標籤,我們可以將這個標籤的相似標籤也加入到使用者標籤集合中,比如“個性化”、“協同過濾”等標籤。

          進行標籤擴充套件的方法有很多,比如說話題模型(參考部落格),這裡遵循簡單原則介紹一種基於鄰域的方法。

          標籤擴充套件的本質是找到與他相似的標籤,也就是計算標籤之間的相似度。最簡單的相似度可以是同義詞。如果有一個同義詞詞典,就可以根據這個詞典進行標籤擴充套件。如果沒有這個詞典,我們可以從資料中統計出標籤的相似度。

          如果認為同一個物品上的不同標籤具有某種相似度,那麼當兩個標籤同時出現在很多物品的標籤集合中時,我們就可以認為這兩個標籤具有較大的相似度。對於標籤b,令N(b)為有標籤b的物品的集合,n_{b,i}為給物品i打上標籤b的使用者數,我們可以通過如下餘弦相似度公式計算標籤b和標籤b'的相似度:

                                                                 

        3:標籤清理

          不是所有標籤都能反應使用者的興趣。比如,在一個視訊網站中,使用者可能對一個視訊打了一個表示情緒的標籤,比如“不好笑”,但我們不能因此認為使用者對“不好笑”有興趣,並且給使用者推薦其他具有“不好笑”這個標籤的視訊。相反,如果使用者對視訊打過“成龍”這個標籤,我們可以據此認為使用者對成龍的電影感興趣,從而給使用者推薦成龍其他的電影。同時,標籤系統裡經常出現詞形不同、詞義相同的標籤,比如recommender system和recommendation engine就是兩個同義詞。
          標籤清理的另一個重要意義在於將標籤作為推薦解釋。如果我們要把標籤呈現給使用者,將其作為給使用者推薦某一個物品的解釋,對標籤的質量要求就很高。首先,這些標籤不能包含沒有意義的停止詞或者表示情緒的詞,其次這些推薦解釋裡不能包含很多意義相同的詞語。
          一般來說有如下標籤清理方法:
          去除詞頻很高的停止詞;
          去除因詞根不同造成的同義詞,比如 recommender system和recommendation system;
          去除因分隔符造成的同義詞,比如 collaborative_filtering和collaborative-filtering。

        為了控制標籤的質量,很多網站也採用了讓使用者進行反饋的思想,即讓使用者告訴系統某個標籤是否合適。

#!/usr/bin/env python
#-*-coding:utf-8-*-
import random  
#統計各類數量  
def addValueToMat(theMat,key,value,incr):  
    if key not in theMat: #如果key沒出先在theMat中  
        theMat[key]=dict();  
        theMat[key][value]=incr;  
    else:  
        if value not in theMat[key]:  
            theMat[key][value]=incr;  
        else:  
            theMat[key][value]+=incr;#若有值,則遞增  
  
user_tags = dict();  
tag_items = dict();  
user_items = dict();  
user_items_test = dict();#測試集資料字典  
  
#初始化,進行各種統計  
def InitStat():  
    data_file = open('delicious.dat')  
    line = data_file.readline();   
    while line:  
        if random.random()>0.1:#將90%的資料作為訓練集,剩下10%的資料作為測試集  
            terms = line.split("\t");#訓練集的資料結構是[user, item, tag]形式  
            user=terms[0];  
            item=terms[1];  
            tag=terms[2];  
            addValueToMat(user_tags,user,tag,1)  
            addValueToMat(tag_items,tag,item,1)  
            addValueToMat(user_items,user,item,1)  
            line = data_file.readline();  
        else:  
            addValueToMat(user_items_test,user,item,1)  
    data_file.close();     
    
#推薦演算法  
def Recommend(usr):  
    recommend_list = dict();  
    tagged_item = user_items[usr];#得到該使用者所有推薦過的物品  
    for tag_,wut in user_tags[usr].items():#使用者打過的標籤及次數  
        for item_,wit in tag_items[tag_].items():#物品被打過的標籤及被打過的次數  
            if item_ not in tagged_item:#已經推薦過的不再推薦  
                if item_ not in recommend_list:  
                    recommend_list[item_]=wut*wit;#根據公式  
                else:  
                    recommend_list[item_]+=wut*wit;  
    return sorted(recommend_list.iteritems(), key=lambda a:a[1],reverse=True)
 
InitStat()
recommend_list = Recommend("48411")
# print recommend_list
for recommend in recommend_list[:10]:  #興趣度最高的十個itemid
    print recommend

執行結果:
('912', 610)
('3763', 394)
('52503', 238)
('39051', 154)
('45647', 147)
('21832', 144)
('1963', 143)
('1237', 140)
('33815', 140)
('5136', 138)

 

六:標籤推薦

        當用戶瀏覽某個物品時,標籤系統非常希望使用者能夠給這個物品打上高質量的標籤,這樣才能促進標籤系統的良性迴圈。因此,很多標籤系統都設計了標籤推薦模組給使用者推薦標籤。

       1:為什麼給使用者推薦標籤

             方便使用者輸入標籤 讓使用者從鍵盤輸入標籤無疑會增加使用者打標籤的難度,這樣很多使用者不願意給物品打標籤,因此我們需要一個輔助工具來減小使用者打標籤的難度,從而提高使用者打標籤的參與度。
            提高標籤質量 同一個語義不同的使用者可能用不同的詞語來表示。這些同義詞會使標籤的詞表變得很龐大,而且會使計算相似度不太準確。而使用推薦標籤時,我們可以對詞表進行選擇,首先保證詞表不出現太多的同義詞,同時保證出現的詞都是一些比較熱門的、有代表性的詞。

      2:標籤推薦的四種簡單演算法

       a:給使用者u推薦整個系統裡最熱門的標籤(這裡將這個演算法稱為PopularTags)令tags[b]為標籤b的熱門程度,那麼這個演算法的實現如下:

<span style="font-family:Microsoft YaHei;font-size:14px;">def RecommendPopularTags(user,item, tags, N):
    return sorted(tags.items(), key=itemgetter(1), reverse=True)[0:N]    </span>

       b:給使用者u推薦物品i上最熱門的標籤(這裡將這個演算法稱為ItemPopularTags)。令item_tags[i][b]為物品i被打上標籤b的次數

<span style="font-family:Microsoft YaHei;font-size:14px;">def RecommendItemPopularTags(user,item, item_tags, N):
    return sorted(item_tags[item].items(), key=itemgetter(1), reverse=True)[0:N]</span>

       c:是給使用者u推薦他自己經常使用的標籤(這裡將這個演算法稱為UserPopularTags)。令user_tags[u][b]為使用者u使用標籤b的次數

<span style="font-family:Microsoft YaHei;font-size:14px;">def RecommendUserPopularTags(user,item, user_tags, N):
    return sorted(user_tags[user].items(), key=itemgetter(1), reverse=True)[0:N]</span>

       d:前面兩種的融合(這裡記為HybridPopularTags),該方法通過一個係數將上面的推薦結果線性加權,然後生成最終的推薦結果。

<span style="font-family:Microsoft YaHei;font-size:14px;">def RecommendHybridPopularTags(user,item, user_tags, item_tags, alpha, N):
    max_user_tag_weight = max(user_tags[user].values())
    for tag, weight in user_tags[user].items():
        ret[tag] = (1 – alpha) * weight / max_user_tag_weight
    </span>
<span style="font-family:Microsoft YaHei;font-size:14px;">    max_item_tag_weight = max(item_tags[item].values())
    for tag, weight in item_tags[item].items():
        if tag not in ret:
            ret[tag] = alpha * weight / max_item_tag_weight
        else:
            ret[tag] += alpha * weight / max_item_tag_weight
    return sorted(ret[user].items(), key=itemgetter(1), reverse=True)[0:N]</span>

注意在上面的實現中,我們在將兩個列表線性相加時都將兩個列表按最大值做了歸一化,這樣的好處是便於控制兩個列表對最終結果的影響,而不至於因為物品非常熱門而淹沒使用者對推薦結果的影響,或者因為使用者非常活躍而淹沒物品對推薦結果的影響。