利用用戶行為數據推薦

利用用戶行為數據分析的算法也稱為協同過濾算法


用戶行為日志通常存儲在分布式數據倉庫中,如支持離線分析的Hadoop Hive和支持在線分析的Google Dremel。

用戶行為分為顯性反饋行為(explicit feedback)和隱性反饋行為(implicit feedback),舉幾個例子來解釋下這兩個名詞:

用戶行為例子

用戶行為的一般規律

很多網站的普遍規律表明,用戶行為大都遵循長尾分布

長尾圖示

學術界對協同過濾算法進行了深入研究,提出了很多方法,比如基于鄰域的方法(neighborhood-based)、隱語義模型 (latent factor model)、基于圖的隨機游走算法(random walk on graph)等。在這些方法中, 最著名的、在業界得到最廣泛應用的算法是基于鄰域的方法,而基于鄰域的方法主要包含下面兩種算法:

  • ? 基于用戶的協同過濾算法 : 這種算法給用戶推薦和他興趣相似的其他用戶喜歡的物品。
  • ? 基于物品的協同過濾算法 : 這種算法給用戶推薦和他之前喜歡的物品相似的物品。

實驗設計和算法評測

  • 數據集
    這里離線實驗的數據集采用GroupLens提供的MovieLens數據集。

  • 實驗設計
    首先,將用戶行為數據集按照均勻分布隨機分成M 份(例如取M=8),挑選一份作為測試集,將剩下的M-1份作為訓練集。
    然后在訓練集上建立用戶興趣模型,并在測試集上對用戶行為進行預測,統計出相應的評測指標。
    為了保證評測指標并不是過擬合的結果,需要進行M次實驗,并且每次都使用不同的測試集。然后將M次實驗測出的評測指標的平均值作為最終的評測指標。

    def SplitData(data, M, k, seed):
        test = []
        train = []
        random.seed(seed)
        for user, item in data:
             if random.randint(0,M) == k:
                 test.append([user,item])
             else:
                 train.append([user,item])
        return train, test

這里,每次實驗選取不同的k(0≤k≤M?1)和相同的隨機數種子seed,進行M次實驗就可以得到M個不同的訓練集和測試集,然后分別進行實驗,用M次實驗的平均值作為最后的評測指標。

  • 評測指標
    可通過準確率召回率評測推薦算法的精度(基礎概念學習):
    準確率
準確率
def Precision(train, test, N):
    hit = 0
    all = 0
    for user in train.keys():
        tu = test[user]
        rank = GetRecommendation(user, N)
        for item, pui in rank: 4
            if item in tu:
                 hit += 1
        all += N
return hit / (all * 1.0)

召回率

召回率

def Recall(train, test, N):
    hit = 0
    all = 0
    for user in train.keys():
        tu = test[user]
        rank = GetRecommendation(user, N)
        for item, pui in rank:
            if item in tu:
                hit += 1
        all += len(tu)
    return hit / (all * 1.0)

覆蓋率反映了推薦算法發掘長尾的 能力,覆蓋率越高,說明推薦算法越能夠將長尾中的物品推薦給用戶。

覆蓋度
def Coverage(train, test, N):
    recommend_items = set()
    all_items = set()
    for user in train.keys():
        for item in train[user].keys():
            all_items.add(item)
        rank = GetRecommendation(user, N)
        for item, pui in rank:
            recommend_items.add(item)
    return len(recommend_items) / (len(all_items) * 1.0)

新穎度這里用推薦列表中物品的平均流行度度量推薦結果的新穎度。如果推薦出的物品都很熱門,說明推薦的新穎度較低,否則說明推薦結果比較新穎。

def Popularity(train, test, N):
         item_popularity = dict()
         for user, items in train.items():
              for item in items.keys()
                  if item not in item_popularity:
                      item_popularity[item] = 0
                  item_popularity[item] += 1
         ret = 0
         n=0
         for user in train.keys():
              rank = GetRecommendation(user, N)
              for item, pui in rank:
                  ret += math.log(1 + item_popularity[item])
                  n += 1
         ret /= n * 1.0
         return ret

重點:

基于鄰域的算法


  • 基于用戶的協同過濾算法

(1) 找到和目標用戶興趣相似的用戶集合。
(2) 找到這個集合中的用戶喜歡的,且目標用戶沒有聽說過的物品推薦給目標用戶。

用戶相似度簡單計算:
協同過濾算法主要利用行為的相似度計算興趣的相似度。給定用戶u和用戶v,令N(u)表示用戶u曾經有過正反饋的物品集合,令N(v)為用戶v曾經有過正反饋的物品集合。

用戶相似度

或者通過余弦相似度計算:

用戶相似度2

舉個例子解釋一下:UserCF計算用戶興趣相似度的例子。在該 例中,用戶A對物品{a, b, d}有過行為,用戶B對物品{a, c}有過行為,利用余弦相似度公式計算用 戶A和用戶B的興趣相似度為:

UserCF用戶相似度

對兩兩用戶都利用余弦相似度計算相似度。這種方法的時間復雜度是O(|U|*|U|),這在 用戶數很大時非常耗時。事實上,很多用戶相互之間并沒有對同樣的物品產生過行為,即很多時 候 N (u) 相交? N (v)等于 ? 0 。

可以首先建立物品到用戶的倒排表,對于每個物品都保存對該物品產生過行為的用戶列表。令稀疏矩陣C[u][v]= N (u) 交? N (v) 。那么,假設用戶u和用戶v同時屬于倒排表中K個物品對 應的用戶列表,就有C[u][v]=K。從而,可以掃描倒排表中每個物品對應的用戶列表,將用戶列 表中的兩兩用戶對應的C[u][v]加1,最終就可以得到所有用戶之間不為0的C[u][v]。

def UserSimilarity(train):
        # build inverse table for item_users
        item_users = dict()
        for u, items in train.items():
             for i in items.keys():
                 if i not in item_users:
                     item_users[i] = set()
                 item_users[i].add(u)
        #calculate co-rated items between users
        C = dict()
        N = dict()
        for i, users in item_users.items():
             for u in users:
                 N[u] += 1
                 for v in users:
                     if u == v:
                         continue
                     C[u][v] += 1
        #calculate finial similarity matrix W
        W = dict()
        for u, related_users in C.items():
             for v, cuv in related_users.items():
                 W[u][v] = cuv / math.sqrt(N[u] * N[v])
return W
用戶-物品倒排表

得到用戶之間的興趣相似度后,UserCF算法會給用戶推薦和他興趣最相似的K個用戶喜歡的 物品。如下的公式度量了UserCF算法中用戶u對物品i的感興趣程度:

S(u, K)包含和用戶u興趣最接近的K個用戶,N(i)是對物品i有過行為的用戶集合,wuv 是用戶u和用戶v的興趣相似度,rvi代表用戶v對物品i的興趣,因為使用的是單一行為的隱反饋數 據,所以所有的rvi=1。

選取K=3,用戶A對物品c、e沒有過行為, 因此可以把這兩個物品推薦給用戶A。根據UserCF算法,用戶A對物品c、e的興趣是:
p(A,c) =? wAB+ ? wAD =? 0.7416
p(A,e) =? wAC +? wAD =? 0.7416

用戶相似度計算改進:

以圖書為例,如果兩個用戶都曾經買過《新華字典》,這絲毫不能說明他們興趣相似, 因為絕大多數中國人小時候都買過《新華字典》。但如果兩個用戶都買過《數據挖掘導論》,那可 以認為他們的興趣比較相似,因為只有研究數據挖掘的人才會買這本書。換句話說,兩個用戶對冷門物品采取過同樣的行為更能說明他們興趣的相似度

該算法減小了用戶u和用戶v共同興趣列表中熱門物品對他們相似度的影響。
比較上面的算法,只是在計算C[u][v]加1時做了改變:

//簡單版
C[u][v] += 1
//優化版
C[u][v] += 1 / math.log(1 + len(users))

  • 基于物品的協同過濾算法

基礎算法
ItemCF算法并不利用物品的內容屬性計算物品之間的相似度,它主要通過分析用戶的行為記錄計算物品之間的相似度。該算法認為,物品A和物品B具有很大的相似度是因為喜歡物品A的用戶大都也喜歡物品B。

1、基于物品的協同過濾算法主要分為兩步。
(1) 計算物品之間的相似度。
(2) 根據物品的相似度和用戶的歷史行為給用戶生成推薦列表。


分母|N(i)|是喜歡物品i的用戶數,而分子 N(i)?N(j) 是同時喜歡物品i和物品j的用戶 數。因此,上述公式可以理解為喜歡物品i的用戶中有多少比例的用戶也喜歡物品j。雖然看起來很有道理,但是卻存在一個問題。如果物品j很熱門,很多人都喜歡,那么Wij就會很大,接近1。

這個公式懲罰了物品j的權重,因此減輕了熱門物品會和很多物品相似的可能性。

和UserCF算法類似,用ItemCF算法計算物品相似度時也可以首先建立用戶—物品倒排表(即 對每個用戶建立一個包含他喜歡的物品的列表),然后對于每個用戶,將他物品列表中的物品兩 兩在共現矩陣C中加1。

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


這里N(u)是用戶喜歡的物品的集合,S(j,K)是和物品j最相似的K個物品的集合,wji是物品j和i 的相似度,rui是用戶u對物品i的興趣。(對于隱反饋數據集,如果用戶u對物品i有過行為,即可令 5 rui=1。)

2、用戶活躍度對物品相似度的影響:
活躍用戶對物品相似度的貢獻應該小于不活躍的用戶\


上面的公式只是對活躍用戶做了一種軟性的懲罰 。

3、物品相似度的歸一化
如果已經得到了物品相似度矩陣w,那么可以用如下公式得到歸一化之后的相似度 矩陣w':


UserCF和ItemCF的綜合比較

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

UserCF和ItemCF優缺點的對比

隱語義模型

LFM(latent factor model)隱語義模型通過如下公式計算用戶u對物品i的興趣:


這個公式中 Pu,k 和 qi,k 是模型的參數,其中 Pu,k 度量了用戶u的興趣和第k個隱類的關系,而 qi,k 度量了第k個隱類和物品i之間的關系。這兩 個參數是從數據集中計算出來的。

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

  • 對每個用戶,要保證正負樣本的平衡(數目相似)。
  • 對每個用戶采樣負樣本時,要選取那些很熱門,而用戶卻沒有行為的物品。
def RandomSelectNegativeSample(self, items):
    ret = dict()
    for i in items.keys():
        ret[i] = 1
    n=0
    for i in range(0, len(items) * 3):
        item = items_pool[random.randint(0, len(items_pool) - 1)]
        if item in ret:
             continue
        ret[item] = 0
        n+=1
        if n > len(items):
             break
    return ret

LFM和基于鄰域的方法的比較
LFM是一種基于機器學習的方法,具有比較好的理論基礎。這個方法和基于鄰域的方法(比 如UserCF、ItemCF)相比,各有優缺點。

  • 理論基礎 LFM具有比較好的理論基礎,它是一種學習方法,通過優化一個設定的指標 建立最優的模型。基于鄰域的方法更多的是一種基于統計的方法,并沒有學習過程。

  • 離線計算的空間復雜度 基于鄰域的方法需要維護一張離線的相關表。在離線計算相關表的過程中,如果用戶/物品數很多,將會占據很大的內存。假設有M個用戶和N個物品, 在計算相關表的過程中,我們可能會獲得一張比較稠密的臨時相關表(盡管最終我們對 每個物品只保留K個最相關的物品,但在中間計算過程中稠密的相關表是不可避免的), 那么假設是用戶相關表,則需要O(MM)的空間,而對于物品相關表,則需要O(NN)的空 間。而LFM在建模過程中,如果是F個隱類,那么它需要的存儲空間是O(F*(M+N)),這在 M和N很大時可以很好地節省離線計算的內存。在Netflix Prize中,因為用戶數很龐大
    (40多萬),很少有人使用UserCF算法(據說需要30 GB左右的內存),而LFM由于大量節 省了訓練過程中的內存(只需要4 GB),從而成為Netflix Prize中最流行的算法。

  • 離線計算的時間復雜度 假設有M個用戶、N個物品、K條用戶對物品的行為記錄。那么, UserCF計算用戶相關表的時間復雜度是O(N * (K/N)^2),而ItemCF計算物品相關表的時間 復雜度是O(M(K/M)^2)。而對于LFM,如果用F個隱類,迭代S次,那么它的計算復雜度 是O(K * F * S)。那么,如果K/N > FS,則代表UserCF的時間復雜度低于LFM,如果 K/M>F*S,則說明ItemCF的時間復雜度低于LFM。在一般情況下,LFM的時間復雜度要 稍微高于UserCF和ItemCF,這主要是因為該算法需要多次迭代。但總體上,這兩種算法 在時間復雜度上沒有質的差別。

  • 在線實時推薦UserCF和ItemCF在線服務算法需要將相關表緩存在內存中,然后可以在 線進行實時的預測。以ItemCF算法為例,一旦用戶喜歡了新的物品,就可以通過查詢內 存中的相關表將和該物品相似的其他物品推薦給用戶。因此,一旦用戶有了新的行為, 而且該行為被實時地記錄到后臺的數據庫系統中,他的推薦列表就會發生變化。而從LFM 的預測公式可以看到,LFM在給用戶生成推薦列表時,需要計算用戶對所有物品的興趣 權重,然后排名,返回權重最大的N個物品。那么,在物品數很多時,這一過程的時間 復雜度非常高,可達O(MNF)。因此,LFM不太適合用于物品數非常龐大的系統,如 果要用,我們也需要一個比較快的算法給用戶先計算一個比較小的候選列表,然后再用 LFM重新排名。另一方面,LFM在生成一個用戶推薦列表時速度太慢,因此不能在線實 時計算,而需要離線將所有用戶的推薦結果事先計算好存儲在數據庫中。因此,LFM不 能進行在線實時推薦,也就是說,當用戶有了新的行為后,他的推薦列表不會發生變化。

  • 推薦解釋 ItemCF算法支持很好的推薦解釋,它可以利用用戶的歷史行為解釋推薦結果。 但LFM無法提供這樣的解釋,它計算出的隱類雖然在語義上確實代表了一類興趣和物品, 卻很難用自然語言描述并生成解釋展現給用戶。

基于圖的模型

下圖是一個簡單的用戶物品二分圖模型,其中圓形節點代表用戶,方形節點代表物品,圓形節點和方形節點之間的邊代表用戶對物品的行為。比如圖中用戶節點A和物品節點a、b、d相連,說明用戶A對物品a、b、d產生過行為。

用戶物品二分圖模型

基于圖的推薦算法:

  • 兩個頂點之間有很多路徑相連;
  • ? 連接兩個頂點之間的路徑長度都比較短;
  • ? 連接兩個頂點之間的路徑不會經過出度比較大的頂點。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內容