1. 程式人生 > >[推薦系統]利用使用者行為資料

[推薦系統]利用使用者行為資料

基於使用者行為分析的推薦演算法是個性化推薦系統的重要演算法,一般將這種型別的演算法稱為協同過濾演算法。協同過濾就是指使用者可以齊心協力,通過不斷地和網站互動,使自己的推薦列表能夠不斷過濾掉自己不感興趣的物品,從而越來越滿足自己的需求。

使用者行為資料簡介

使用者行為資料在網站上最簡單的存在形式就是日誌。網站在執行過程中都產生大量原始日誌raw log,並將其儲存在檔案系統中。很多網際網路業務會把多種原始日誌按照使用者行為彙總成會話日誌session log,其中每個會話表示一次使用者行為和對應的服務。會話日誌通常儲存在分散式資料倉庫中,如支援離線分析的Hadoop Hive和支援線上分析的Google Dremel。

使用者行為在個性化推薦系統中一般分兩種—顯性反饋行為(explicit feedback)和隱性反饋行為(implicit feedback):

  • 顯性反饋行為: 包括使用者明確表示對物品喜好的行為。不同網站收集顯性反饋行為的主要方式是評分和喜歡/不喜歡。
  • 隱性反饋行為: 指那些不能明確反應使用者喜好的行為。最具代表性的隱性反饋行為就是頁面瀏覽行為。使用者瀏覽一個物品的頁面並不代表使用者一定喜歡這個頁面展示的物品,比如可能因為這個頁面連結顯示在首頁,使用者更容易點選它而已。相比顯性反饋,隱性反饋雖然不明確,但資料量更大。

顯性反饋資料和隱性反饋資料的比較.

顯性反饋資料 隱性反饋行為
使用者興趣 明確 不明確
數量 較少 龐大
儲存 資料庫 分散式檔案系統
實時讀取 實時 有延遲
正負反饋 都有 只有正反饋

按照反饋的方向分,可以分為正反饋和負反饋。正反饋指使用者的行為傾向於指使用者喜歡該物品,負反饋指使用者的行為傾向於指使用者不喜歡該物品

。在顯性反饋中,很容易區分一個使用者行為是正反饋還是負反饋,而在隱性反饋行為中,就相對比較難以確定。

一般來說,不同的資料集包含不同的行為,目前比較有代表性的資料集有下面幾個:

  • 無上下文資訊的隱性反饋資料集: 每一條行為記錄僅僅包含使用者ID和物品ID。
  • 無上下文資訊的顯性反饋資料集: 每一條記錄包含使用者ID、物品ID和使用者對物品的評分;
  • 有上下文資訊的隱性反饋資料集: 每一條記錄包含使用者ID、物品ID和使用者對物品產生行為的時間戳;
  • 有上下文資訊的顯性反饋資料集: 每一條記錄包含使用者ID、物品ID和使用者對物品的評分和評分行為發生的時間戳。

使用者行為分析

在利用使用者行為資料設計推薦演算法之前,研究人員首先需要對使用者行為資料進行分析,瞭解資料中蘊含的一般規律,才能對演算法的設計起到指導作用[相當與ML中資料探索]。

使用者活躍度和物品流行度的分佈

[物品的流行度指對物品產生過行為的使用者總數]

很多網際網路資料的研究發現,網際網路上的很多資料分佈都滿足一種稱為Power Law的分佈,這個分佈在網際網路領域也稱為長尾分佈。

f ( x ) = α x k f(x) = \alpha x^k

很多研究人員發現,使用者行為資料也蘊含這種長尾分佈的規律。
物品流行度和使用者活躍度都近似於長尾分佈。

使用者活躍度和物品流行度的關係

僅僅基於使用者行為資料設計的推薦演算法一般稱為協同過濾演算法。協同過濾演算法分為基於鄰域的方法(neighborhood-based)、隱語義模型(latent factor model)、基於圖的隨機遊走演算法(random walk on graph)等。在這些方法中,最著名的、在業界得到最廣泛應用的演算法是基於鄰域的方法,而基於鄰域的方法主要包含下面兩種演算法:

  • 基於使用者的協同過濾演算法: 給使用者推薦和他興趣相似的其他使用者喜歡的物品;
  • 基於物品的協同過濾演算法: 給使用者推薦和他之前喜歡的物品相似的物品。

基於鄰域的演算法

基於鄰域的演算法分為兩類:一類是基於使用者的協同過濾演算法,另一類是基於物品的協同過濾演算法。

基於使用者的協同過濾演算法

基於使用者的協同過濾演算法是推薦系統中最古老的演算法。

1.基礎演算法

基於使用者的協同過濾演算法主要包括兩個步驟:

  • 找到和目標使用者興趣相似的使用者集合;
  • 找到這個集合中的使用者喜歡的,且目標使用者沒有聽說過的物品推薦給目標使用者。

步驟一的關鍵在於計算兩個使用者的興趣相似度。協同過濾演算法主要利用行為的相似度計算興趣的相似度。給定使用者u和使用者v,令N(u)表示使用者u曾經有過正反饋的物品集合,令N(v)為使用者v曾經有過正反饋的物品集合。可以通過Jaccard公式簡單計算u和v的興趣相似度:

Jaccard公式

餘弦相似度計算:
餘弦相似度

2.基於相似度計算的改進

原來的相似度計算公式,如餘弦相似度計算方法太過於粗糙。如果兩個使用者對冷門物品採取過同樣的行為更能說明他們興趣的相似度。John S. Breese在論文中提出如下公式,根據使用者行為計算使用者的興趣相似度:
計算公式

其中,N(i)表示物品i的流行度。公式懲罰了使用者u和v共同興趣列表中熱門物品對他們相似度的影響。

基於使用者的協同過濾演算法的缺點:首先,隨著網站的使用者數目越來越大,計算使用者興趣相似度矩陣越來越困難,其運算時間複雜度和空間複雜度的增長和使用者的增長近似於平方關係;其次,基於使用者的協同過濾很難對推薦結果做出解釋。

基於物品的系統過濾演算法

基於物品的協同過濾item-based collaborative filtering演算法是目前業界應用最多的演算法。

1.基礎演算法

基於物品的協同過濾演算法(簡稱ItemCF)給使用者與推薦那些和他們之前喜歡的物品相似的物品。ItemCF演算法並不利用物品的內容屬性計算物品之間的相似度,主要通過分析使用者的行為記錄就是那物品之間的相似度。該演算法認為,物品A和物品B具有很大的相似性是因為喜歡物品A的使用者大都也喜歡物品B。

基於物品的協同過濾演算法主要分為兩步:

  • 計算物品之間的相似度;
  • 根據物品的相似度和使用者的歷史行為給使用者生成推薦列表。

購買了該商品的使用者也經常經常購買的其他商品,從這句話的定義出發,給出定義物品相似度的計算公式:
物品相似度

其中,分母|N(i)|是喜歡物品i的使用者數,分子是同事喜歡物品i和物品j的使用者數。
但是,上述公式存在一個問題,如果物品j很熱門,很多人都喜歡,那麼Wij就會很大,接近於1.因此,該公式會造成任務物品都會和熱門的物品有很大的相似度,對致力於挖掘長尾資訊的推薦系統來說不是一個好的特性。為了避免推薦出熱門的物品,使用下面的公式:
新計算公式
這個公式懲罰了物品j的權重,因此減輕了熱門物品會和很多物品相似的可能性。

從上面的定義可以看到,在協同過濾中兩個物品產生相似度是因為它們共同被很多使用者喜歡,也就是說每個使用者都可以通過他們的歷史興趣列表給物品”貢獻“相似度。

ItemCF演算法計算物品相似度時,先建立一個使用者-物品倒排表,然後對於每個使用者,將他物品列表中的物品量量在共現矩陣C中加1.詳細程式碼:

def ItemSimilarity(train):#train是使用者-物品倒排表
    C = dict()#物品i,j共現矩陣,用字典表示;
    N = dict()#物品i的流行度--喜歡物品i的使用者數目
    for user, items in train.items():
        for i in items:
            N[i] += 1
            for j in items:
                if i != j:
                    C[i][j] = C[i].get(j, 0) + 1
    W = dict()#物品相似度矩陣 字典
    for i, related_items in C.items():
        for j, cij in related_items.items():
            W[i][j] = cij / math.sqrt(N[i]*N[j])

    return W

在得到物品之間的相似度後,ItemCF通過如下公式計算使用者u對一個物品j的興趣:
使用者u對物品i的興趣計算公式

這裡N(u)是使用者喜歡的物品集合,S(j,K)是和物品j最相似的K個物品的集合,wji是物品j和i的相似度,rui是使用者u對物品i的興趣。(對於隱反饋資料集,如果使用者u對物品i有過行為,即可令rui=1.) 該公式的含義是:和使用者歷史上感興趣的物品[N(u)裡]越相似的物品,越有可能在使用者的推薦列表中獲得比較高的排名。 [在S集合中篩選掉已經喜歡的物品]實現程式碼:

def Recommendation(train, user_id, W, K):
    rank = dict()
    ru = train[user_id]#使用者喜歡物品字典,物品:rui(使用者u對物品i的興趣,預設為1)
    for i, rui in ru.items():
        # 選擇物品i的相似度矩陣,並由大到小排序;然後選擇前K個物品
        si = sorted(W[i].items(),key=lambda a:a[1],reverse=True)
        for j, wij in si[:K]:
            if j in ru:#排除已經喜歡的物品
                continue
            rank[j] += wij * rui

    return rank

ItemCF演算法的一個優勢是可以提供推薦解釋,即利用使用者歷史上喜歡的物品為現在的推薦結果進行解釋。帶解釋的ItemCF演算法:

def Recommendation(train, user_id, W, K):
    rank = dict()
    reason = dict()
    ru = train[user_id]
    for i, rui in ru.items():
        si = sorted(W[i].items(),key=lambda a:a[1], reverse=True)
        for j, wij in si[:K]:
            if j not in ru:
                rank[j] += wij * rui
                reason[j][i] = wij * rui
    return rank, reason

對不同 K 值的測量可以看到:

  • 準確率和召回率和 K 也不成線性關係;選擇合適的K對獲得最高精度是非常重要的;
  • K 和流行度不完全正相關: 隨著K的增加,結果流行度會逐漸提高,但當K增加大到一定程度,流行度就不會再有明顯變化;
  • K 增大會降低系統的覆蓋率。

2.使用者活躍度對物品相似度的影響

在協同過濾中兩個物品蒼生相似度是因為它們共同出現在很多使用者的興趣列表中。換句話說,每個使用者的興趣列表都對物品的相似度產生貢獻。但每個使用者的貢獻不應該都相同

John S.Breese在論文中提出一個IUF(Inversr User Frequence)使用者活躍度對數的倒數的引數,他認為活躍使用者對物品相似度的貢獻應該小於不活躍的使用者,提出應該增加IUF引數來修正物品相似度的計算公式:
物品相似度修正公式

N(i)、N(j)物品i,j的流行度;u是同時購買物品i和物品j的使用者,N(u)是使用者喜歡的物品數[用來表示使用者u的活躍度]。上面公式只是對活躍使用者做了一種軟性的懲罰。
但對於很多過於活躍的使用者,為了避免相似度矩陣過於稠密,在實際運算中一般直接忽略他的興趣列表,不將其納入到相似度計算的資料集中。ItemCF-IUF實現:

def ItemSimilarity(train):
    C = dict()#分子
    N = dict()#物品i的流行度
    for u, items in train.items():
        for i in items:
            N[i] += 1
            for j in items:
                if i != j:
                    C[i][j] += 1 / math.log(1 + len(items)*1.0)
    W = dict()#相似度矩陣
    for i, related_items in C.items():
        for j, cij in related_items.items():
            W[i][j] = cij / math.sqrt(N[i] * N[j])

    return W

3.物品相似度的歸一化

在研究匯中發現如果將ItemCF的相似度矩陣按最大值歸一化,可以提高推薦的準確率。其研究表明,如果已經得到了物品相似度矩陣w,可以用如下公式得到歸一化之後的相似度矩陣w’:
歸一化相似度矩陣公式

按照行進行歸一化。歸一化的好處不僅僅在於增加推薦的準確度,還可以提高推薦的覆蓋率和多樣性

UserCF和ItemCF的綜合比較

UserCF的推薦結果著重於反應和使用者興趣相似的小群體的熱點,ItemCF的推薦結果著重於維繫使用者的歷史興趣。換句話說,UserCF的推薦更社會化,反映了使用者所在的小型群體中物品的熱門程度,而ItemCF的推薦更加個性化,反映了使用者自己的興趣傳承

從技術上考慮, UserCF 需要維護一個使用者相似度的矩陣,而 ItemCF 需要維護一個物品相似度矩陣。從儲存的角度說,如果使用者很多,那麼維護使用者興趣相似度矩陣需要很大的空間,同理,如果物品很多,那麼維護物品相似度矩陣代價較大。

UserCF和ItemCF優缺點對比.

UserCF ItemCF
效能 適用於使用者較少的場合,如果使用者很多,計算使用者相似度矩陣代價很大 適用於物品數明顯小魚使用者數的場合,如果物品很多(網頁),計算物品相似度矩陣代價很大
領域 時效性強,使用者個性化興趣不太明顯的領域 長尾物品豐富,使用者個性化需求強烈的領域
實時性 使用者有新行為,不一定造成推薦結果的立即變化 使用者有新行為,一定會導致推薦結果的實時變化
冷啟動 在新使用者對很少的物品產生行為的情況下,不能立即對他進行個性化推薦,因為使用者性相似度表是每隔一段時間離線計算的 新物品上線後一段時間,一旦有使用者對物品產生行為,就可以將新物品推薦給和對它產生行為的使用者興趣相似的其他使用者 新使用者只要對一個物品產生行為,就可以給他推薦和該物品相關的其他物品 但沒有辦法在不離線更新物品相似度表的情況下將新物品推薦給使用者
推薦利用 很難提供令使用者信服的推薦解釋 利用使用者的歷史行為給使用者做推薦解釋,可以令使用者比較信服

離線實驗的效能在選擇推薦演算法時病不起決定作用。首先應該滿足產品的需求,然後需要看實現代價。

隱語義模型

LFM(latent factor model)隱語義模型,該演算法最早在文字挖掘領域被提出,用於找到文字的隱含語義。相關的名詞有LSI、pLSI、LDA和Topic Model.

  1. 基礎演算法

隱語義模型的核心思想是通過隱含特徵(latent factor)聯絡使用者興趣和物品。簡單說就是對物品的興趣分類,對於使用者,首先確定他的興趣分類,然後從分類中選擇他可能喜歡的物品。基於興趣分類的方法大概解決3個問題:

  • 如何給物品分類?
  • 如何確定使用者對哪些類的物品感興趣,以及感興趣的程度?
  • 對於一個給定的類,選擇哪些屬於這個類的物品推薦給使用者,以及如何確定這些物品在一個類中的權重?

這裡的對物品分類的問題,可以用隱含語義分析技術較好地解決。它基於使用者行為統計做分類,和專家標記相比:

  • 分類來源於對使用者行為的統計,能代表各種使用者的看法;
  • 通過指定最終分類的個數,能控制分類的粒度;數目越大,分類粒度越細;
  • 能給一個物品多個分類:隱語義模型計算物品屬於每個類的權重,不是硬性地被分到某個類中;
  • 帶維度屬性,屬於多維度或同維度;
  • 可以確定物品在某個分類中的權重:通過統計使用者行為決定物品在每個類中的權重;

這些都是專家標記不能或者很難做到的。

LFM通過如下公式計算使用者u對物品i的興趣:
興趣
公式中puk和qik是模型的引數,其中puk度量了使用者u的興趣和第k個隱類的關係,而qik度量了第k個隱類和物品i之間的關係。
這兩個引數的計算方式需要一個訓練集,對於每個使用者u,訓練集裡包含了使用者u喜歡的物品和不感興趣的物品,通過學習這個資料集,就可以獲得上面的模型引數。

推薦系統的使用者行為分為顯性反饋和隱性反饋。LFM在顯性反饋資料(評分資料)上解決評分預測問題並達到了很好的精度。如果是隱性資料集,這種資料集的特點是隻有正樣本(使用者喜歡什麼物品),沒有負樣本(使用者對什麼物品不感興趣)。

在隱性反饋資料集上應用LFM解決TopN推薦的第一個關鍵問題就是如何給每個使用者生成負樣本。

對負樣本取樣時應該遵循以下原則:

  • 對每個使用者,要保證正負樣本的平衡(數目相似);
  • 對每個使用者取樣負樣本時,要選取那些很熱門,而使用者卻沒有行為的物品。

一般認為,很熱門而使用者卻沒有行為更加代表使用者對這個物品不感興趣。因為對於冷門的物品,使用者可能是壓根沒在網站中發現這個物品,所以談不上是否感興趣。

需要優化的損失函式如下:
損失函式

其中,後兩項是用來防止過擬合的正則化項,lambda可以通過實驗獲得。通過梯度下降演算法對損失函式進行優化求解,得到兩個引數指。

在LFM中,重要的引數有4個:

  • 隱特徵的個數F;
  • 學習速率alpha;
  • 正則化引數lambda;
  • 負樣本/正樣本比例radio。

通過實驗發現,radio對LFM的效能影響最大。

2.LFM和基於鄰域的方法的比較

LFM是一種基於機器學習的方法,具有比較好的理論基礎。和基於鄰域的方法相比,各有優缺點。

  • 理論基礎 LFM具有比較好的理論基礎,是一種學習方法,通過優化一個設定的指標建立最優的模型。基於鄰域的方法更多的是一種基於統計的方法,並沒有學習過程。
  • 離線計算的空間複雜度 基於鄰域的方法需要維護一張離線的相關表。

基於圖的模型

使用者行為很容易用二分圖表示,因此很多圖的演算法都可以應用到推薦系統中。

1.使用者行為資料的二分圖表示

在研究基於圖的模型之前,首先需要將使用者行為資料表示成圖的形式。這裡討論使用者行為資料是由一系列二元組組成的,其中每個二元組(u,i)表示使用者u對物品i產生過行為;這種資料集很容易用一個二分圖表示。

二分圖模型

2.基於圖的推薦演算法

在二分圖上給使用者進行個性化推薦。如果將個性化推薦演算法放到二分圖模型上,那麼給使用者u推薦物品的任務就可以轉換為度量使用者頂點vu和與vu沒有邊直接相連的物品節點在圖上的相關性,相關性越高的物品在推薦列表中的權重就越高。

度量圖中兩個頂點之間相關性的方法有很多,但一般來說圖中頂點的相關性主要取決於下面3個因素:

  • 兩個頂點之間的路徑數;
  • 兩個頂點之間路徑的長度;
  • 兩個頂點之間的路徑經過的頂點。

相關性高的一堆頂點一般具有如下特徵:

  • 兩個頂點之間有很多路徑相連;
  • 連線兩個頂點之間的路徑長度都比較短;
  • 連線兩個頂點之間的路徑不會經過出度比較大的頂點。

基於上面3個主要因素,設計了很多計算圖中頂點之間相關性的方法。比如隨機遊走PersonalRank演算法。

假設要給使用者u進行個性化推薦,可以從使用者u對應的節點vu開始在使用者物品二分圖上進行隨機遊走。遊走到任何一個節點時,首先按照概率alpha決定是繼續遊走,還是停止這次遊走並從vu節點開始重新遊走。如果決定繼續遊走,那麼就從當前節點指向的節點中按照均勻分佈隨機選擇一個節點作為遊走下次經過的節點。這樣,經過很多次隨機遊走後,每個物品節點被訪問到的概率後收斂到一個數。最終的推薦列表中物品的權重就是物品節點的訪問概率

表示公式如:
遊走公式

alpha遊走概率,1-alpha停留概率;

程式碼實現:

def PersonalRank(G, alpha, root, max_step):
    rank = dict()#推薦結果
    rank = {x : 0 for x in G.keys()}
    rank[root] = 1

    for k in range(max_step):
        tmp = {x : 0 for x in G.keys()}

        #取節點i和它的出邊尾節點集合ri
        for i, ri in G.items():
        #取i->j邊的尾節點j以及邊E(i,j)的權重wij, 邊的權重都為1,
            for j, wij in ri.items():
                if j not in tmp:
                    tmp[j] = 0
                tmp[j] += alpha * rank[i] / (1.0 * len(ri))
                if j == root:
                    tmp[j] += 1 - alpha
        rank = tmp

    return rank

雖然PersonalRank演算法可以通過隨機遊走進行比較好的理論解釋,但該演算法在時間複雜度上有明顯的缺點。因為在為每個使用者進行推薦時,都需要在整個使用者物品二分圖上進行迭代,知道整個圖上的每個頂點的PR值收斂。這一過程時間複雜度非常高,不僅無法線上提供實時推薦,甚至離線生成推薦結果也很耗時。

為了解決PersonalRank每次都需要在全圖迭代並造成時間複雜度高的問題,給出兩種解決方法。第一種,減少迭代次數,在收斂之前就停止。會影響最終的精度,但一般來說影響不會特別大;另一種就是從矩陣論出發,重新設計演算法。