【推薦系統】{2} —— 基於物品的協同過濾算法

協同過濾(英語:Collaborative Filtering,簡稱CF),簡單來說是利用某興趣相投、擁有共同經驗之羣體的喜好來推薦用戶感興趣的信息,個人透過合作的機制給予信息相當程度的迴應(如評分)並記錄下來以達到過濾的目的進而幫助別人篩選信息,迴應不一定侷限於特別感興趣的,特別不感興趣信息的紀錄也相當重要。——維基百科

基於物品的協同過濾算法(item-based collaborative filtering,簡稱ItemCF)是目前業界應用最多的算法。

亞馬遜、Netflix、Hulu、YouTube的推薦算法的基礎都是基於物品的協同過濾算法。

基於用戶的協同過濾算法有一些缺點。

  • 隨着網站的用戶數目越來越大,計算用戶興趣相似度矩陣將越來越困難,其運算時間複雜度和空間複雜度的增長和用戶數的增長近似於平方關係。
  • 基於用戶的協同過濾算法很難對推薦結果作出解釋。
基於物品的協同過濾算法優勢:
  • 計算性能高,通常用戶數量遠大於物品數量。
  • 可預先計算保留,物品並不善變。

ItemCFItemCF 算法給用戶推薦那些和他們之前喜歡的物品相似的物品。

舉個例子:

用戶/物品 物品A 物品B 物品C
用戶A
用戶B
用戶C 與物品A相似,推薦

ItemCFItemCF 算法並不利用物品的內容屬性計算物品之間的相似度,它主要通過分析用戶的行爲記錄計算物品之間的 相似度。該算法認爲,物品 AA 和物品 BB 具有很大的相似度是因爲喜歡物品 AA 的用戶大都也喜歡物品 BB

基於物品的協同過濾算法可以利用用戶的歷史行爲給推薦結果提供推薦解釋,比如給用戶推薦《機器學習實戰》的解釋可以是因爲用戶之前買過《統計學習方法》。


基於物品的協同過濾算法步驟:

  1. 計算物品之間的相似度。
  2. 根據物品的相似度和用戶的歷史行爲給用戶生成推薦列表。

定義物品的相似度爲wij=N(i)N(j)N(i)w_{ij} = \frac{|N(i)\cap{N(j)|}}{{|N(i)|}}此處分母 N(i){{|N(i)|}} 是喜歡物品 ii 的用戶數,而分子 N(i)N(j){|N(i)\cap{N(j)|}} 是同時喜歡物品 ii 和物品 jj 的用戶數。因此,上述公式可以理解爲喜歡物品 ii 的用戶中有多少比例的用戶也喜歡物品 jj

但該公式存在一個問題,如果物品 jj 很熱門,很多人都喜歡,那麼 wijw_{ij} 就會很大,接近1。因此,該公式會造成任何物品都會和熱門的物品有很大的相似度,這顯然不是一個好的特性。爲了避免推薦出熱門的物品, 可以用下面的公式: wuv=N(i)N(j)N(i)N(j)w_{uv} = \frac{|N(i)\cap{N(j)|}}{\sqrt{|N(i)||{N(j)|}}}這個公式懲罰了物品 jj 的權重,因此減輕了熱門物品會和很多物品相似的可能性。


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

這裏蘊涵着一個假設,就是每個用戶的興趣都侷限在某幾個方面,因此如果兩個物品屬於一個用戶的興趣列表, 那麼這兩個物品可能就屬於有限的幾個領域,而如果兩個物品屬於很多用戶的興趣列表,那麼它們就可能屬於同一個領域,因而有很大的相似度。


UserCFUserCF 算法類似,用 ItemCFItemCF 算法計算物品相似度時也可以首先建立 >用戶->物品 倒排表(即對每個用戶建立一個包含他喜歡的物品的列表),然後對於每個用戶,將他物品列表中的物品兩兩在共現矩陣C中加 11

代碼如下:

def ItemSimilarity(train):
	# 修改相似度矩陣
	C = dict()
	N = dict()
	for u, items in train.items():
		for i in users:
			N[i] += 1
			for j in users:
				if i == j:
					continue
				C[i][j] += 1
	# 計算相似度矩陣
	W = dict()
	for i, related_items in C.items():
		for j, cij in related_items.items():
			W[u][v] = cij / math.sqrt(N[i] * N[j])
	return W

如圖是根據上面的程序計算物品相似度的簡單例子。

最左邊是輸入的用戶行爲記錄,每一行代表一個用戶感興趣的物品集合。然後,對於每個物品集合,我們將裏面的物品兩兩加一,得到一個矩陣。最終將這些矩陣相加得到最右邊的 CC 矩陣。其中 C[i][j]C[i][j] 記錄了同時喜歡物品 ii 和物品 jj 的用戶數。最後,將 CC 矩陣歸一化可以得到物品之間的餘弦相似度矩陣 WW


在得到物品之間的相似度後,ItemCFItemCF 算法通過如下公式計算用戶 uu 對一個物品 jj 的興趣: puj=iN(u)S(j,K)wjiruip_{uj}=\displaystyle \sum_{i\in{N(u)\cap{S(j,K)}}}w_{ji}r_{ui}

這裏 N(u)N(u) 是用戶喜歡的物品的集合,S(j,K)S(j,K) 是和物品 jj 最相似的 KK 個物品的集合,wjiw_{ji} 是物品 jjii 的相似度,ruir_{ui} 是用戶 uu 對物品 ii 的興趣。(對於隱反饋數據集,如果用戶 uu 對物品 ii 有過行爲,即可令 rui=1r_{ui}=1

該公式的含義是,和用戶歷史上感興趣的物品越相似的物品,越有可能在用戶的推薦列表中獲得比較高的排名。

該公式的實現代碼如下:

def Recommendation(train, user_id, W, K):
	rank = dict()
	ru = train[user_id]
	for i, pi in ru.items():
		for j, wj in sorted(W[i].items(), key=itengetter(1), reverse=True)[0:K]:
			if j in ru:
				continue
			rank[j] += pi * wj
	return rank

此處是一個基於物品推薦的簡單例子。該例子中,用戶喜歡《C++ Primer中文版》和《編程之美》兩本書。然後 ItemCFItemCF 會爲這兩本書分別找出和它們最相似的 55 本書,然後根據公式計算用戶對每本書的感興趣程度。

ItemCFItemCF 給用戶推薦《算法導論》,是因爲這本書和《C++ Primer中文版》相似,相似度爲 0.40.4,而且這本書也和《編程之美》相似,相似度是 0.50.5。用戶對《C++ Primer中文版》的興趣度是 1.31.3,對《編程之美》的興趣度是 0.90.9,則用戶對《算法導論》的興趣度就是 1.3×0.4+0.9×0.5=0.971.3 × 0.4 + 0.9 × 0.5 = 0.97。 以此類推……


從上述例子可以看到,ItemCFItemCF 的一個優勢就是可以提供推薦解釋,即利用用戶歷史上喜歡的物品爲現在的推薦結果進行解釋。

如下代碼實現了帶解釋的 ItemCFItemCF 算法:

def Recommendation(train, user_id, W, K):
	rank = dict()
	ru = train[user_id]
	for i, pi in ru.items():
		for j, wj in sorted(W[i].items(), key=itengetter(1), reverse=True)[0:K]:
			if j in ru:
				continue
			rank[j].weight += pi * wj
			rank[j].reason[i] = pi * wj
	return rank

用戶活躍度對物品相似度的影響

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

但不是每個用戶的貢獻都相同

假設有一個開書店的用戶,買了噹噹網上80%的書自己賣。則他的購物車裏包含噹噹網80%的書。假設噹噹網有100萬本書,則他買了80萬本。這意味着由於存在該用戶,有80萬本書兩兩之間產生了相似度,那麼內存裏即將誕生一個80萬乘80萬的稠密矩陣。

另外,該用戶雖然活躍,但買這些書並非出於自身興趣,且這些書覆蓋了當當網圖書的很多領域,所以這個用戶對於他所買書的兩兩相似度的貢獻遠遠小於一個只買了十幾本自己喜歡的書的文學青年。


爲了解決上述問題,John S. Breese 提出了一個稱爲IUF(Inverse User Frequence),即用戶活躍度對數的 倒數的參數,他認爲活躍用戶對物品相似度的貢獻應該小於不活躍的用戶,他提出應該增加IUF參數來修正物品相似度的計算公式: wij=uN(i)N(j)1log1+N(u)N(i)N(j)w_{ij}=\frac{\displaystyle \sum_{u\in{N(i)\cap{N(j)}}}\frac{1}{log1+|N(u)|}}{\sqrt{|N(i)||N(j)|}}

將該算法記爲 ItemCFIUFItemCF-IUF

上述公式對活躍用戶做了一種軟性的懲罰,但對於很多過於活躍的用戶,爲了避免相似度矩陣過於稠密,在實際計算中一般直接忽略其興趣列表,不將其納入到相似度計算的數據集中。

代碼如下:

def ItemSimilarity(train):
	# 統計相似度矩陣
	C = dict()
	N = dict()
	for u, items in train.items():
		for i in users:
			N[i] += 1
			for j in users:
				if i == j:
					continue
				C[i][j] += 1 / math.1og(1 + len(items) * 1.0)
	# 計算相似度矩陣
	W = dict()
	for i, related_items in C.items():
		for j, cij in related_items.items():
			W[u][v] = cij / math.sqrt(N[i] * N[j])
	return W

物品相似度的歸一化

如果將 ItemCFItemCF 的相似度矩陣按最大值歸一化,可以提高推薦的準確率。如果已經得到了物品相似度矩陣 ww,可以用如下公式得到歸一化之後的相似度矩陣 ww'wij=wijmaxjwijw'_{ij}=\frac{w_{ij}}{max_{j}\,w_{ij}}

歸一化的優點:
  • 增加推薦的準確度。
  • 提高推薦的多樣性。
  • 提高推薦的覆蓋率。
多樣性:

一般來說,物品總是屬於很多不同的類,每一類中的物品聯繫比較緊密。

舉一個例子,假設在某電影網站中,有 AA 類片和 BB 類片。那麼,ItemCFItemCF 算出來的相似度一般是 AA 類片和 AA 類片的相似度或者 BB 類片和 BB 類片的相似度大於 AA 類片和 BB 類片的相似度。但是 AA 類片之間的相似度和 BB 類片之間的相似度卻不一定相同。

假設物品分爲兩類——AABBAA 類物品之間的相似度爲 0.50.5BB 類物品之間的相似度爲 0.60.6,而 AA 類物品和 BB 類物品之間的相似度是 0.20.2

在這種情況下, 如果一個用戶喜歡了 55AA 類物品和 55BB 類物品,用 ItemCFItemCF 給他進行推薦,推薦的就都是 BB 類物品, 因爲 BB 類物品之間的相似度大。但如果歸一化之後,AA 類物品之間的相似度變成了 11BB 類物品之間的相似度也是 11,這種情況下,用戶如果喜歡 55AA 類物品和 55BB 類物品,則他的推薦列表中 AA 類物品和 BB 類物品的數目也是大致相等的。

從該例子可以看出,相似度的歸一化可以提高推薦的多樣性

覆蓋率:

對於兩個不同的類,一般來說,熱門的類其類內物品相似度一般比較大。如果不進行歸一化,就會推薦比較熱門的類裏面的物品,而這些物品也是比較熱門的。因此,推薦的覆蓋率就比較低。相反,如果進行相似度的歸一化,則可以提高推薦系統的覆蓋率。


參考資料:《推薦系統實踐》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章