Item協同過濾(基於Python實現)


        在眾多召回策略里面,基於Item與基於User(可參考:https://www.cnblogs.com/SysoCjs/p/11466424.html)在實現上非常相似。所以這里使用了跟基於User協同過濾的數據u.data。

u.data數據格式(user_id, item_id, rating, timestamp)

實現原理:
        區別於User,先根據User已經購買過,或者評價過的Items,基於算法,對其他Items做一個相似度計算,來獲取基於該User的Items的相似Items,這樣每個User_Item都可以得到一堆相似Items,根據相似度進行排序,每個User_Item取出topN個Items,然后將所有的User_item的各自topN個Items,共同組成一個大的Item_List,Item_list中的每個item的相似度乘上對應的User_item的權重,得到新的權重值,根據新的權重值排序,取topK個Items,作為最終的Items候選集。

具體實現:
        因為是使用Python來實現,所以還是選擇最簡單的算法:傑卡爾德算法。在User協同過濾中:通過兩個用戶共同擁有的物品集合數量,除以兩個用戶的物品的平均數量。這里,需要倒過來:通過兩個item共同用戶數量,除以兩個item各自的用戶的平均數量。所以要做的是,想方設法獲取這三個參數值:共同用戶數量、兩個item各自的用戶數量。

一、創建源數據的結構

        timestamp列數據在尋找相似用戶里面,意義不大,可以不使用;對於pyspark程序,u.data數據是沒有結構的,所以第一時間是讀取u.data,並定義數據的結構,可以將數據的結構定義為:

dict{user_id:{item_id:rating}}

 

import pandas as pd def generate_train_data(nrows=10): # 處理訓練數據 -> dict{user_id:{item_id:rating}}
    df = pd.read_csv('../row_data/u.data', sep='\t', nrows=nrows, names=['user_id', 'item_id', 'rating', 'timestamp']) # d為每個用戶的商品及對應打分的列表
    d = dict() for _, row in df.iterrows(): # 類型轉換
        user_id = str(row['user_id']) item_id = str(row['item_id']) rating = row['rating'] if user_id not in d.keys(): d[user_id] = {item_id: rating} else: d[user_id][item_id] = rating return d

 

 

 

二、統計傑卡爾德的參數

        基於Item的協同過濾,在遍歷train_data的時候,需要同時統計每個item的User數量,以及item與item之間共同的User數量,因為,相對於基於User的協同過濾,item的User數量不可以直接通過len(train_data[i])來獲取,需要另外定義一個類型為dict的N來存儲,N的數據結構為{item: userNum}。

 

N = dict()  # N{item:userNum},統計item的user數量

 

 

 

在遍歷train_data的同時,可以將N和C都收集得到:

 

N = dict()  # N{item:userNum},統計item的user數量
def item_sim(train_data): C = dict()  # C{item:{iten:simUserNum}}
    for u,items in train_data.items(): for i in items.keys(): # 對於每一個u的items,N[i]只有一個
            if N.get(i,-1)==-1: N[i] = 0 N[i] += 1
            if C.get(i,-1)==-1: C[i] = dict() for j in items.keys(): if i==j: continue
                if C[i].get(j,-1)==-1: C[i][j] = 0 C[i][j] += 1
    return C

 

 

 

        這個時候,共同用戶數量存儲在C里面,而每個item對應的用戶數量存儲在N里面,那么接下來可以借用傑卡爾德公式計算Item-Item相似度矩陣。

 

# 計算相似度矩陣,傑卡爾德算法
def item_item(C): for i, related_items in C.items(): for j, cij in related_items.items(): C[i][j] = 2 * cij/(N[i]+N[j]*1.0) return C

 

 

 

        最終結果還是保存在C里面,此時C的數據結構變成:

 

C = dict()  # C{item:{iten:sim_score}}

 

 

 

二、過濾,統計推薦候選集

        最終是要得到Item候選集,所以先定義一個字典,用於存儲item,及其對應的打分:

 

rank = dict() # rank{item:score}

 

 

 

        Item候選集仍然是指代推薦給指定User的Items,一般會根據業務需求,將該User購買過或評價過的Item進行過濾,這時,需要取出該User對應的r購買過或評價過的Item列表:

Ru = train_data[user_id]

        過濾掉User購買過或評價過的Item后,需要計算候選集中每個Item的打分,通過Item在該User的rating,乘上其相似Item的sim_score,得到最終打分score:

 

def recommendation(train_data, user_id, C, k): rank = dict() # rank{item:score}
    # 用戶user_id有很多個已經購買的item,每個item都有好多個相似item,
    # 每個item取k個相似item
    Ru = train_data[user_id] for i, rating in Ru.items():    # 相對於user-based,item-based的一個用戶對應於多個item,所以大循環,要遍歷item
        for j, sim_score in sorted(C[i].items(), key=lambda x:x[1],reverse=True)[0:k]: # 相對於user-based,每個user還要遍歷各自的item,item-based則不需要,需要它的矩陣就是item-iten
            # 過濾這個user已經打分過的item
            if j in Ru: continue
            if rank.get(j,-1)==-1: rank[j] = 0 rank[j] += sim_score*rating return rank

 

 

 

三、根據指定User,得出其推薦Item候選集

 

if __name__ == '__main__': train_data = dict() with open(train_data_path,'r') as ft: train_data = eval(ft.read()) C = dict() with open(sim_item_path, 'r') as fs: C = eval(fs.read()) user_id = '196' k = 5 rank = recommendation(user_id, C, train_data, k) print(sorted(rank.items(), key=lambda x: x[1], reverse=True)[0:k])

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM