協同過濾的原理及Python實現

2022-01-06 Python中文社區

作者:李小文,先後從事過數據分析、數據挖掘工作,主要開發語言是Python,現任一家小型網際網路公司的算法工程師。

Github: https://github.com/tushushu

提到ALS相信大家應該都不會覺得陌生,它是協同過濾的一種,並被集成到Spark的Mllib庫中。本文就ALS的基本原理進行講解,並手把手、肩並肩地帶您實現這一算法。

完整實現代碼請參考:

https:
https:

1. 原理篇

我們用人話而不是大段的數學公式來講講ALS是怎麼一回事。

1.1 你聽說過推薦算法麼

假如我是豆瓣的CEO,很多豆瓣的用戶在豆瓣電影上都會對電影進行評分。那麼根據這個評分數據,我們有可能知道這些用戶除了自己評過分的電影之外還喜歡或討厭哪些電影嗎?這就是一個典型的推薦問題,解決這一類問題的算法被稱為推薦算法。

1.2 什麼是協同過濾

協同過濾的英文全稱是Collaborative Filtering,簡稱CF。注意,這不是一款遊戲!從字面上分析,協同就是尋找共同點,過濾就是篩選出優質的內容。

1.3 協同過濾的分類

一般來說,協同過濾推薦分為三種類型: 

1. 基於用戶(user-based)的協同過濾,通過計算用戶和用戶的相似度找到跟用戶A相似的用戶B, C, D…再把這些用戶喜歡的內容推薦給A;

2.基於物品(item-based)的協同過濾,通過計算物品和物品的相似度找到跟物品1相似的物品2, 3, 4…再把這些物品推薦給看過物品1的用戶們; 

3. 基於模型(model based)的協同過濾。主流的方法可以分為:矩陣分解,關聯算法,聚類算法,分類算法,回歸算法,神經網絡。

1.4 矩陣分解

矩陣分解 (decomposition, factorization)是將矩陣拆解為數個矩陣的乘積。比如豆瓣電影有m個用戶,n個電影。那麼用戶對電影的評分可以形成一個m行n列的矩陣R,我們可以找到一個m行k列的矩陣U,和一個k行n列的矩陣I,通過U * I來得到矩陣R。

1.5 ALS

如果想通過矩陣分解的方法實現基於模型的協同過濾,ALS是一個不錯的選擇,其英文全稱是Alternating Least Square,翻譯過來是交替最小二乘法。假設用戶為a,物品為b,評分矩陣為R(m, n),可分解為用戶矩陣U(k, m)和物品矩陣I(k, n),其中m, n, k代表矩陣的維度。前方小段數學公式低能預警:

1.6 求解用戶矩陣U和物品矩陣I

矩陣R是已知的,我們隨機生成用戶矩陣U, 1. 利用1.5中的式5、R和U求出I 2. 利用1.5中的式6、R和I求出U

如此交替地執行步驟1和步驟2,直到算法收斂或者迭代次數超過了最大限制,最終我們用RMSE來評價模型的好壞。

2. 實現篇

本人用全宇宙最簡單的程式語言——Python實現了ALS算法,沒有依賴任何第三方庫,便於學習和使用。簡單說明一下實現過程,更詳細的注釋請參考本人github上的代碼。

註:代碼中用到的Matrix類是我寫的一個矩陣類,可以取出矩陣的行或列,計算矩陣的乘法、轉置和逆。代碼連結:matrix.py

2.1 創建ALS類

初始化,存儲用戶ID、物品ID、用戶ID與用戶矩陣列號的對應關係、物品ID與物品矩陣列號的對應關係、用戶已經看過哪些物品、評分矩陣的Shape以及RMSE。

class ALS(object):
    def __init__(self):
        self.user_ids = None
        self.item_ids = None
        self.user_ids_dict = None
        self.item_ids_dict = None
        self.user_matrix = None
        self.item_matrix = None
        self.user_items = None
        self.shape = None
        self.rmse = None

2.2 數據預處理

對訓練數據進行處理,得到用戶ID、物品ID、用戶ID與用戶矩陣列號的對應關係、物品ID與物品矩陣列號的對應關係、評分矩陣的Shape、評分矩陣及評分矩陣的轉置。

def _process_data(self, X):
    self.user_ids = tuple((set(map(lambda x: x[0], X))))
    self.user_ids_dict = dict(map(lambda x: x[::-1],
                                    enumerate(self.user_ids)))

    self.item_ids = tuple((set(map(lambda x: x[1], X))))
    self.item_ids_dict = dict(map(lambda x: x[::-1],
                                    enumerate(self.item_ids)))

    self.shape = (len(self.user_ids), len(self.item_ids))

    ratings = defaultdict(lambda: defaultdict(int))
    ratings_T = defaultdict(lambda: defaultdict(int))
    for row in X:
        user_id, item_id, rating = row
        ratings[user_id][item_id] = rating
        ratings_T[item_id][user_id] = rating

    err_msg = "Length of user_ids %d and ratings %d not match!" % (
        len(self.user_ids), len(ratings))
    assert len(self.user_ids) == len(ratings), err_msg

    err_msg = "Length of item_ids %d and ratings_T %d not match!" % (
        len(self.item_ids), len(ratings_T))
    assert len(self.item_ids) == len(ratings_T), err_msg
    return ratings, ratings_T

2.3 用戶矩陣乘以評分矩陣

實現稠密矩陣與稀疏矩陣的矩陣乘法,得到用戶矩陣與評分矩陣的乘積。

def _users_mul_ratings(self, users, ratings_T):

    def f(users_row, item_id):
        user_ids = iter(ratings_T[item_id].keys())
        scores = iter(ratings_T[item_id].values())
        col_nos = map(lambda x: self.user_ids_dict[x], user_ids)
        _users_row = map(lambda x: users_row[x], col_nos)
        return sum(a * b for a, b in zip(_users_row, scores))

    ret = [[f(users_row, item_id) for item_id in self.item_ids]
            for users_row in users.data]
    return Matrix(ret)

2.4 物品矩陣乘以評分矩陣

實現稠密矩陣與稀疏矩陣的矩陣乘法,得到物品矩陣與評分矩陣的乘積。

def _items_mul_ratings(self, items, ratings):

    def f(items_row, user_id):
        item_ids = iter(ratings[user_id].keys())
        scores = iter(ratings[user_id].values())
        col_nos = map(lambda x: self.item_ids_dict[x], item_ids)
        _items_row = map(lambda x: items_row[x], col_nos)
        return sum(a * b for a, b in zip(_items_row, scores))

    ret = [[f(items_row, user_id) for user_id in self.user_ids]
            for items_row in items.data]
    return Matrix(ret)

2.5 生成隨機矩陣

def _gen_random_matrix(self, n_rows, n_colums):
    data = [[random() for _ in range(n_colums)] for _ in range(n_rows)]
    return Matrix(data)

2.6 計算RMSE

def _get_rmse(self, ratings):
        m, n = self.shape
        mse = 0.0
        n_elements = sum(map(len, ratings.values()))
        for i in range(m):
            for j in range(n):
                user_id = self.user_ids[i]
                item_id = self.item_ids[j]
                rating = ratings[user_id][item_id]
                if rating > 0:
                    user_row = self.user_matrix.col(i).transpose
                    item_col = self.item_matrix.col(j)
                    rating_hat = user_row.mat_mul(item_col).data[0][0]
                    square_error = (rating - rating_hat) ** 2
                    mse += square_error / n_elements
        return mse ** 0.5

2.7 訓練模型

1.數據預處理
2.變量k合法性檢查
3.生成隨機矩陣U
4.交替計算矩陣U和矩陣I,並列印RMSE信息,直到迭代次數達到max_iter
5.保存最終的RMSE

def fit(self, X, k, max_iter=10):
    ratings, ratings_T = self._process_data(X)
    self.user_items = {k: set(v.keys()) for k, v in ratings.items()}
    m, n = self.shape

    error_msg = "Parameter k must be less than the rank of original matrix"
    assert k < min(m, n), error_msg

    self.user_matrix = self._gen_random_matrix(k, m)

    for i in range(max_iter):
        if i % 2:
            items = self.item_matrix
            self.user_matrix = self._items_mul_ratings(
                items.mat_mul(items.transpose).inverse.mat_mul(items),
                ratings
            )
        else:
            users = self.user_matrix
            self.item_matrix = self._users_mul_ratings(
                users.mat_mul(users.transpose).inverse.mat_mul(users),
                ratings_T
            )
        rmse = self._get_rmse(ratings)
        print("Iterations: %d, RMSE: %.6f" % (i + 1, rmse))

    self.rmse = rmse

2.8 預測一個用戶

預測一個用戶感興趣的內容,剔除用戶已看過的內容。然後按感興趣分值排序,取出前n_items個內容。

def _predict(self, user_id, n_items):
    users_col = self.user_matrix.col(self.user_ids_dict[user_id])
    users_col = users_col.transpose

    items_col = enumerate(users_col.mat_mul(self.item_matrix).data[0])
    items_scores = map(lambda x: (self.item_ids[x[0]], x[1]), items_col)
    viewed_items = self.user_items[user_id]
    items_scores = filter(lambda x: x[0] not in viewed_items, items_scores)

    return sorted(items_scores, key=lambda x: x[1], reverse=True)[:n_items]

2.9 預測多個用戶

循環調用2.8,預測多個用戶感興趣的內容。

def predict(self, user_ids, n_items=10):
    return [self._predict(user_id, n_items) for user_id in user_ids]

3 效果評估

3.1 main函數

使用電影評分數據集,訓練模型並統計RMSE。

@run_time
def main():
    print("Tesing the accuracy of ALS...")

    X = load_movie_ratings()

    model = ALS()
    model.fit(X, k=3, max_iter=5)
    print()

    print("Showing the predictions of users...")

    user_ids = range(1, 5)
    predictions = model.predict(user_ids, n_items=2)
    for user_id, prediction in zip(user_ids, predictions):
        _prediction = [format_prediction(item_id, score)
                       for item_id, score in prediction]
        print("User id:%d recommedation: %s" % (user_id, _prediction))

3.2 效果展示

設置k=3,迭代5次,並展示了前4個用戶的推薦內容,最終RMSE為0.370,運行時間46.5秒,效果還算不錯~

3.3 工具函數

本人自定義了一些工具函數,可以在github上查看

1.run_time - 測試函數運行時間

2.load_movie_ratings - 加載電影評分數據

總結

ALS的原理:雞生蛋、蛋生雞

ALS的實現:基本上就是矩陣乘法

點擊閱讀原文或長按選擇複製以下專屬連結到電腦,即可直接參與【阿里雲】雙十一活動超低價團購,一起瓜分百萬紅包:

https://m.aliyun.com/act/team1111/#/share?params=N.d2UUiz9bQC.mghg5ay8

相關焦點

  • 【過濾】魚缸使用活性炭過濾的原理
    活性炭的吸附能力非常好,但是活性炭的吸附能力與水溫、水質都有一定的關係,那麼魚缸中使用活性炭過濾的原理是怎樣的呢?小編就來為你詳細解說一下。  活性炭的吸附能力非常好,但是活性炭的吸附能力與水溫、水質都有一定的關係,那麼魚缸中使用活性炭過濾的原理是怎樣的呢?小編就來為你詳細解說一下。
  • python教程:3個非常有用的內置函數
    這三個內置函數還是非常有用的,在工作中用的還不少,順手,下面一一進行介紹 1、filter 語法:filter(function,iterable) 解釋:把迭代器通過function函數進行過濾出想要的數據
  • 紅隊作業 | Python實現免殺遠控
    ,說的詳細點就是反彈shell,首先要解決三個問題:1.與服務端建立socket連接2.在服務端命令執行3.服務端把回顯返回給客戶端這裡我們用python實現,聲明一下,涉及到的內容是網絡編程,以下函數實現多半都是我百度的,所以有很多用法以及邏輯的實現都是欠考慮的
  • 原理+ 代碼|手把手教你用Python實現智能推薦算法
    本文就將詳細介紹如何用Python實現智能推薦算法,主要將分為兩個部分:詳細原理介紹Python代碼實戰02常見的推薦系統與算法常見的推薦系統分類有:基於應用領域: 電子商務/社交好友推薦等基於設計思想: 基於協同過濾的推薦等基於使用數據: 基於用戶標籤的推薦等「 京騰 」 合作構建用戶畫像標籤圖常見的推薦算法有:
  • 一文講解圖像插值算法原理!附Python實現
    ,並以圖像縮放例,對原理進行了C++及Python實現。本文目標了解插值算法與常見幾何變換之間的關係理解插值算法的原理掌握OpenCV框架下插值算法API的使用插值算法原理介紹近鄰插值算法1.原理簡介將目標圖像中的點,對應到原圖像中後,找到最相鄰的整數坐標點的像素值,作為該點的像素值輸出。
  • 《小灰教你零基礎學python》-Python入門語言
    電腦(包括手機)由硬體和程序構成:很多硬體 + 很多程序 = 電腦具體硬體和程序如何集成這個咱們不用太了解,這個是計算機設計原理裡面的東西了,咱只需要了解,電腦就是硬體(攝像頭、程式語言有很多,咱們就學簡單強大的python即可。
  • 出國必備,用python實現美元和人民幣的實時匯率兌換
    各個國家的流通貨幣是不同的,而當我們要出國時,就需要先算好貨幣之間的兌換,而羽憶教程下面為你介紹用python實現美元和人民幣之間的實時匯率兌換。python美元和人民幣匯率兌換python美元和人民幣匯率兌換匯率兌換是一個十分簡單的python程序,只需要知道其兌換的比例就可以輕鬆得出結果
  • python圖像處理-濾鏡處理
    今天我們就嘗試用python的PIL庫對圖片做一些濾鏡處理,希望可以帶給你一些想法。打開原始圖片這裡我用的是一張貓的圖片,先打開原圖查看。進行模糊濾鏡處理PIL中的ImageFilter模塊中已經有很多集成好的濾鏡方法,這裡我們直接調用,原理下一篇會詳細講解並自己嘗試者去實現同樣的效果。
  • python利用opencv實現證件照換底
    opencv今天就給大家介紹一下python利用opencv庫進行藍底換紅底或者白底照片的操作。它是一個跨平臺的計算機視覺庫,可以運行在不同作業系統上,它由一些列c函數和少量c++函數組成,並提供python,matlab等語言的接口,實現了圖像處理和計算機視覺方面的很多通用算法。我們這裡用的opencv-python 就是opencv的python API接口。
  • 2019 必知的 10 大頂級 python 庫
    在 TensorFlow 創建的所有庫都是用 C 和 C++編寫的,但是,它有一個複雜的前端,是用 python 實現的。你的 python 代碼將被編譯,然後在使用 C 和 C++構建的 TensorFlow 分布式執行引擎上執行。實際上,TensorFlow 的應用是無限的,這就是它美妙的地方。
  • 零基礎學習python,這幾本書少不了
    目前,社會上已經掀起了一波學習python的熱潮,但是很多人由於並沒有python基礎,不知道從哪裡開始學習,小編今天整理了零基礎學習python的一些書籍,希望對大家有所幫助。1.《"笨辦法"學Python》推薦理由:本書是一本python入門書籍,比較適合沒有計算機、編程基礎,但是對python感興趣的小白學習使用。這本書是以習題的方式一步一步引導讀者了解、學習python,從簡單的列印一直講到完整項目的實踐,讓初學者從基礎的python知識入手,最終體驗到軟體開發的基本過程。
  • 5個助你效率提升的python小技巧
    使用交互模式使用python -i xxxx.py可以直接進入python的交互模式,可以很方便的調用xxxx.py中定義的方法和函數,特別適合調試沒有main()方法的文件,強力推薦。使用pdb進行調試很多從c++/java轉到python的同學可能對python沒有斷點功能相當失望。其實python自帶的pdb庫就可以解決這個問題。看這個例子。
  • Python實現手繪效果的cute圖表
    下面就給大家演示下如何用 cutecharts 實現手繪效果的折線圖、條形圖、餅圖。通過 pip 可以直接安裝:python -m pip install cutecharts切記!!!是在CMD中輸入命令,不是python中。
  • python代碼實現OpenCV 輪廓近似原理
    假如,我們要 開發一個自動導航的機器人,在機器人導航的過程中,必然會搜集大量的路徑數據,進而指導機器人的前進,但是很多時候,路徑上的拐點很多,這就加大了計算的負擔,如何能儘可能地保留原始路徑數據,又可以降低大量的計算,這就需要路徑的一些估算,這就要使用到本次的路徑輪廓近似原理。原始路徑
  • python系列15:跳出循環:break與continue
    前言在實際使用的過程中會有其他的問題,比如如果只想列印str_condiion = 'I like python'中的字母,而不列印空格呢(可以考慮if判斷等)?如果列印過程中碰到字母o即結束循環?那應該怎麼做呢?前面學習了for與while循環,也大致了解了兩者的差異,也清楚了在不同場景下各自的優先選擇誰。
  • 想要養龜,龜缸過濾系統的搭建很重要,怎麼實現高效過濾?
    搭建養龜良好的過濾系統,你該這麼做!一、物理過濾物理過濾,是我們常見的一種過濾方式。物理過濾就是濾材處理雜質,通過某些能夠讓水通過,雜質留下的濾材,讓水和雜質分離,進而達到淨化水質的作用。通常一套物理過濾裝置都是由水泵、過濾盒、水管三個基本部分組成,其工作原理如下圖。通過水泵吸取龜缸內的水,然後進入到過濾盒,通過淨化再由水管送回龜缸。但是這種方式也有自身的缺點,那就是過濾盒內濾材的更換和清洗要到位。
  • 過濾原理深度分析,你明白了嗎?
    現在各大水族論壇講生化過濾的帖子都在講,氨有毒,會毒死魚,要靠硝化細菌轉化,所以生化過濾很重要。這種說法也從魚友傳播到龜友,大家也覺得既然對魚有毒,那對龜也有毒,所以有些龜友主要精力都在弄生化過濾,物理過濾只用一層薄棉,我以前也犯這個錯誤。可是氨為什麼有毒?很少有人講。
  • 50行Python代碼實現經典遊戲,不僅是划水神器,更是學習利器!
    那麼,今天要介紹的這款Python項目就可以輕鬆實現你成為遊戲開發者的想法,實現前面提到的這些經典遊戲只需要50-100代碼即可完成。但是,free-python-games這個項目讓我眼前一亮,終於找到了一個合適的Python學習項目。當年,它對於成年人同樣適用、有價值。我之所以介紹這款項目,不單單是它實現了讓人回到童年的經典遊戲。更重要的原因是它能夠作為一個Python學習、鍛鍊探索和理解能力的一款好工具。
  • 泳池過濾沙(砂)缸的工用原理介紹?
    水循環水經過沙床時,水中的髒物及有機物流砂床截留,起到精細過濾的效果。 工作原理:        泳池過濾砂缸利用石英砂滲透原理,將池水中的髒物及微小生物進行截留,過濾精度一般為40um。石英沙作為過濾介質,被裝填 入過濾器內,當過濾器開始時,池水通過水泵將水注入泳池過濾砂缸中,通過壓力池水由上往下進行滲透,再從過濾器底部的部水器返回到中間管路中,從而起來過濾效果。
  • NMF的算法原理
    如果要在計算機中實現NMF,則可以根據下圖所示的步驟進行。 nmf更詳盡的原理可以參考維基百科:Non-negative matrix factorization。