이전에는 이웃 기반 협업필터링 중 사용자 기반 협업 필터링에 대하여 이야기를 했습니다.
사용자 기반 협업 필터링은 아래의 링크에서 확인하실 수 있습니다.
[추천시스템] 이웃 기반 협업필터링 (1) -사용자 기반
이웃기반 협업 필터링은 메모리 기반 알고리즘으로 불리기도 하며 협업필터링의 가장 초기 알고리즘 입니다. 이웃기반 협업 필터링의 개념은 다음과 같습니다. "유사한 사용자들은 평점을 주는
my-develop-note.tistory.com
이번 포스팅에서는 아이템 기반 협업 필터링에 대하여 이야기해보겠습니다.
아이템 기반 협업 필터링
사용자 기반 협업 필터링은 특정 사용자의 피어그룹을 기준으로 피어그룹의 평점을 이용하여 사용자가 아직 평가하지 않은 아이템에 대하여 평점을 예측하여 추천하는 방식이었습니다.
- 피어그룹 : 유사 사용자 혹은 최근접 이웃
하지만 아이템 기반 협업 필터링에서는 피어그룹이 사용자가 아닌 아이템으로 구성되어 있습니다.
사용자 기반에서 "사용자-사용자 유사도"를 계산하여 피어그룹을 구성했듯이
아이템 기반에서는 "아이템 - 아이템 유사도"를 계산해야 합니다.
아이템 - 아이템 유사도를 구하는 식은 사용자 기반에서 사용된 유사도 식과는 조금 다릅니다.
사용자 기반에서는 피어슨 상관계수나 코사인 유사도로 유사도를 구했지만
아이템 기반에서는 조정된 코사인 유사도를 구하게 됩니다.
유사도를 구할 때는 사용되는 평점(sui)이 사용자 기반에서 예측값(rui)를 구할 때 사용했던 평균 중심 평점(sui)를 사용하기 때문에 조정된 코사인 유사도(AdjustedCosine)이라고 부르고 일반적으로 피어슨 상관계수보다 우수한 결과를 보입니다.
예제로 사용할 테이블은 아래와 같습니다.
사용자 기반 협업 필터링에서 사용한 예제와 동일합니다.
조정된 코사인 유사도를 구하려면 평균중심행렬(S)를 구해야 하는데 평균중심행렬(S)는 아래와 같고 "평균 - 평점"으로 구할 수 있습니다.
조정된 코사인 유사도(AdjustedCosine)의 식과 코드는 아래와 같습니다.
def AdujustedCosine(i, j):
#행렬 S에서 아이템 i, j의 열을 찾음
I_i = S.loc[:, i]
I_j = S.loc[:, j]
# 두 아이템 i, j에 모두 평가한 유저의 인덱스를 찾음
u = set((I_i + I_j).loc[(I_i + I_j).notnull()].index)
#평균 중심 행렬(S)에서 sui, suj를 찾음
#조정된 코사인 유사도 계산
sui = S.loc[u, i]
suj = S.loc[u, j]
sum_sui_2 = np.sum(np.power(sui, 2))
sum_suj_2 = np.sum(np.power(suj, 2))
sqrt_sum_sui = np.sqrt(sum_sui_2)
sqrt_sum_suj = np.sqrt(sum_suj_2)
return np.sum((sui * suj)) / (sqrt_sum_sui * sqrt_sum_suj)
아이템 1, 6과 나머지 아이템에 대한 유사도를 구하면 다음과 같습니다.
조정된 코사인 유사도까지 구했으니 평점을 예측해보겠습니다.
사용자 u가 타깃 아이템 t의 평점을 예측한다고 가정하고 예측값(rut)를 구하는 식은 다음과 같습니다.
사용자 기반에서 피어그룹을 구했듯이 아이템 기반에서도 아이템 피어그룹을 구해야합니다.
Qt(u)바로 아이템 t와 가장 유사한 상위-k 아이템 피어그룹입니다.
평점을 예측하는 코드는 다음과 같습니다.
def predict_r(u, t, k = 5):
def Q(t):
lst = []
result = []
for i in S.columns:
if i == t:
pass
else:
if AdujustedCosine(t, i) > 0:
lst.append([i, AdujustedCosine(t, i)])
lst.sort(reverse=True, key = lambda x : x[1])
for i in lst[:k]:
result.append(i[0])
return result
j_lst = Q(t)
AdujustedCosine_j = []
for j in j_lst:
AdujustedCosine_j.append(AdujustedCosine(j, t))
ruj = R.loc[u, j_lst]
return np.sum(AdujustedCosine_j * ruj) / np.sum(AdujustedCosine_j)
k를 기본 5로 지정했지만 유사한 아이템의 수가 k보다 작다면 유사한 아이템만큼만 피어그룹으로 지정됩니다.
사용자가 보지 않은 아이템에 대하여 평점을 예측한 후에 평점이 높은 순으로 아이템을 추천하면 됩니다.
전체 코드는 아래와 같이 구성하였습니다.
from itertools import combinations_with_replacement
class ItemBaseCF():
def __init__(self, table, k=5):
self.R = table
self.S = self._get_S()
self.AdjustedCosine = self._adjusted_cosine()
self.k = k
self.peergroup = self._find_peer_group()
def _get_S(self):
"""
S 테이블을 만드는 함수
"""
Mu = self.R.mean(axis=1)
return (R.T - Mu).T
def _cal_U(self):
return self.R.notnull()
def _cal_adujusted_cosine(self, i, j):
#행렬 S에서 아이템 i, j의 열을 찾음
I_i = self.S.loc[:, i]
I_j = self.S.loc[:, j]
# 두 아이템 i, j에 모두 평가한 유저의 인덱스를 찾음
u = set((I_i + I_j).loc[(I_i + I_j).notnull()].index)
#평균 중심 행렬(S)에서 sui, suj를 찾음
#조정된 코사인 유사도 계산
sui = self.S.loc[u, i]
suj = self.S.loc[u, j]
sum_sui_2 = np.sum(np.power(sui, 2))
sum_suj_2 = np.sum(np.power(suj, 2))
sqrt_sum_sui = np.sqrt(sum_sui_2)
sqrt_sum_suj = np.sqrt(sum_suj_2)
return np.sum((sui * suj)) / (sqrt_sum_sui * sqrt_sum_suj)
def _adjusted_cosine(self):
temp = pd.DataFrame()
for i, j in combinations_with_replacement(self.R.columns, 2):
temp.loc[i,j] = self._cal_adujusted_cosine(i,j)
temp.loc[j,i] = temp.loc[i, j]
return temp
def _find_peer_group(self):
"""
피어그룹을 찾는 함수
"""
adjustedcosine = self.AdjustedCosine
adjustedcosine = adjustedcosine[(adjustedcosine > 0) & (adjustedcosine < 1)]
return adjustedcosine
def _find_item(self,user_id):
"""
유저가 아직 평가하지 않은 아이템을 찾는 함수
"""
index = self.R.loc[user_id, self.R.loc[user_id].isna()].index
return index
def recommend(self, user_id):
"""
사용자가 아직 평가하지 않은 아이템을 찾고
해당 아이템에 대하여 평점을 예측한 뒤 평점이 높은 순으로 인덱스를 반환하는 함수
"""
#유저가 아직 평가하지 않은 아이템을 찾음
item_index = self._find_item(user_id)
#피어그룹
result = pd.Series()
for t in item_index:
peergroup = self.peergroup.loc[t].dropna().sort_values(ascending=False)[:5].index
a = self.AdjustedCosine.loc[peergroup, t].dot(self.R.loc[user_id, peergroup])
b = np.sum(self.AdjustedCosine.loc[peergroup, t])
result.loc[t] = (a/b)
result.sort_values(ascending=False)
return result
일반적으로 사용자 기반 협업 필터링보다 아이템 기반 협업 필터링의 성능이 더 좋다고 합니다.
감사합니다! :)
'추천시스템' 카테고리의 다른 글
[추천시스템] (잠재요인) 모델 기반 협업 필터링(2) - 경사하강법(SGD) (2) | 2023.01.11 |
---|---|
[추천시스템] (잠재요인) 모델 기반 협업 필터링(1) - 경사하강법(SGD) (0) | 2023.01.10 |
[추천시스템] 카카오 Mini Reco 기출문제 회고 (2) | 2023.01.01 |
[추천시스템] 이웃 기반 협업필터링 (1) - 사용자 기반 (0) | 2022.12.23 |
[추천시스템] 평점의 종류 (0) | 2022.11.29 |