天天看點

協同過濾算法學習筆記與相似度矩陣Python實作

顯示/隐式回報

類似視訊的評分、商品的評分這些為顯示回報;浏覽日志、購買日志這些為隐式回報。

使用者行為的統一表示方法

userId、itemId、behaviorType、context(上下文,時間地點等)、behaviorWeight、behaviorContent

常見資料集種類

  1. 無上下文的隐式回報資料集:僅記錄使用者和物品的ID
  2. 無上下文的顯示回報資料集:記錄物品和使用者的ID以及評分
  3. 有上下文的隐示回報資料集:記錄使用者和物品的ID以及物品産生行為的時間戳
  4. 有上下文的顯示回報資料集:記錄使用者和物品的ID以及評分和時間戳

評價名額

  1. 召回率:正确推薦的數目/總共喜歡的數目
  2. 準确率:正确推薦的數目/推薦出來的數目
  3. 覆寫率:推薦的種類/總類數
  4. 流行度:顧名思義
  5. AUC:正确的樣本排在錯誤樣本前面的機率

基于使用者的協同過濾算法(UserCF)

  1. 給使用者推薦和他興趣相似的其他使用者喜歡的物品,比較社會化。适用于時效性較強的領域,如新聞推薦。
  2. 步驟:(1)找到和目标使用者興趣類似的使用者集合。(2)找到這個集合中使用者喜歡的,且目标使用者還沒有聽說過的物品推薦給目标使用者。
  3. 使用者相似度計算:(1)Jaccard公式/餘弦相似度,适用于使用者較少的情況。(2)物品-使用者倒排表,一個二維矩陣,行列都是使用者ID,以物品為紐帶,如果兩個使用者之間有關聯則在矩陣中加一。C[u][v] += 1, W[u][v] = Cuv / math.sqrt(N[u] * N[v])
  4. 對推薦結果有較大影響的因素:推薦的數目。
  5. 算法的改進:(1)User-IIF,減少流行物品對相似度的影響。C[u][v] += 1 / math.log(1 + len(user)), W[u][v] = Cuv / math.sqrt(N[u] * N[v])

基于物品的協同過濾算法(ItemCF)

  1. 給使用者推薦和他之前喜歡物品類似的物品,比較個性化。适用于個性化需求較強烈的領域,如一些購物平台。
  2. 步驟:(1)計算物品之間的相似度。(2)根據物品的相似度和使用者的曆史行為給使用者生成推薦清單。
  3. 物品相似度計算:使用者-物品倒排表(對每個使用者建立一個包含他喜歡的物品清單),然後對于每個使用者将它物品清單中的物品兩兩在共現矩陣C加一。

    C[i][j] += 1, W[i][j] = Cij / math.sqrt( N[i] * N[j])

  4. 算法優化:(1)User-IUF,活躍使用者對物品相似度的貢獻應該小于不活躍使用者。

    C[i][j] += 1 / math.log(1 + len(item) * 1.0 ), W[i][j] = Cij / math.sqrt( N[i] * N[j])。

    (2)User-Norm,歸一化相似度矩陣。

UserCF與ItemCF的優缺點對比

UserCF ItemCF
性能 适用于使用者較少的場合 适用于物品數目明顯小于使用者數的場合
領域 時效性較強,個性化興趣不太明顯 長尾物品豐富
實時性 使用者有新行為不一定導緻推薦結果立即變化 使用者有新行為一定會導緻推薦結果實時變化
冷啟動 在新使用者對很少物品産生行為後不能立即進行個性化推薦,因為使用者相似度是每隔一段時間離線計算的;新物品上線一段時間後,一旦有使用者對物品産生行為就可以将它推給其他人 新使用者隻要對一個物品産生行為就可以給他推薦相關物品;沒有辦法在不離線更新物品相似度表的情況下将新物品退給使用者
推薦理由 難以提供 可以解釋

核心代碼(基于ml-100k資料集)

計算相似度矩陣

def ItemCF_Norm(self):
        # 建立物品-物品的共現矩陣
        C = dict()  # 物品-物品的共現矩陣
        N = dict()  # 物品被多少個不同使用者購買
        for user, items in self.train.items():
            for i in items.keys():
                N.setdefault(i, 0)
                N[i] += 1
                C.setdefault(i, {})
                for j in items.keys():
                    if i == j: continue
                    C[i].setdefault(j, 0)
                    C[i][j] += 1
                    #C[i][j] += 1 / math.log(1 + len(items) * 1.0)  # IUF
        # 計算相似度矩陣
        self.W = dict()
        for i, related_items in C.items():
            self.W.setdefault(i, {})
            for j, cij in related_items.items():
                self.W[i][j] = cij / (math.sqrt(N[i] * N[j]))
        # 歸一化
        for item1, dic in self.W.items():
            max = sorted(self.W[item1].items(), key=lambda x: x[1], reverse=True)[0][1]
            for item2, sim in dic.items():
                self.W[item1][item2] = self.W[item1][item2] / max
        self.SaveDict('data/itemcf_norm.data', self.W)
        return self.W
           

帶理由的推薦

def Recommend(self, user, K=10, num_recommend=10, num_recent=5):
        rank = dict()
        watched_item = self.train[user]  # 使用者user産生過行為的item和評分
        recent_item = self.getRecent(user, num_recent)  # 看過的并且喜歡(4分以上)的電影按時間排序
        for movie, list in recent_item:
            for related_movie, w in sorted(self.W[movie].items(), key=lambda x: x[1], reverse=True)[:K]:
                if related_movie in watched_item.keys():
                    continue
                rank.setdefault(related_movie, [0, movie])
                rank[related_movie][0] += list[0] * w  # list[0]是評分 list[1]是時間
        return dict(sorted(rank.items(), key=lambda x: x[1], reverse=True)[0:num_recommend])
           

召回率與準确率的計算

def Recall(self, N=30):
        hit = 0
        all = 0
        for user_id in self.train.keys():
            if user_id in self.test.keys():
                tu = self.test[user_id]
                rank = self.Recommend(user_id, num_recommend=N)
                for item_id, pui in rank.items():
                    if item_id in tu.keys():
                        hit += 1
                all += len(tu)
        return hit / (all * 1.0)

    def Precision(self, N=30):
        hit = 0
        all = 0
        for user_id in self.train.keys():
            if user_id in self.test.keys():
                tu = self.test[user_id]
                rank = self.Recommend(user_id, num_recommend=N)
                for item_id, pui in rank.items():
                    if item_id in tu.keys():
                        hit += 1
                all += N
        return hit / (all * 1.0)