이웃기반 협업 필터링은 메모리 기반 알고리즘으로 불리기도 하며 협업필터링의 가장 초기 알고리즘 입니다.
이웃기반 협업 필터링의 개념은 다음과 같습니다.
"유사한 사용자들은 평점을 주는 방식에서 유사한 패턴을 보이고, 유사한 아이템에는 유사한 평점을 준다."
이웃기반 협업필터링에는 크게 두가지의 방식이 있습니다.
- 사용자 기반 협업 필터링
- 아이템 기반 협업 필터링
먼저 사용자 기반 협업 필터링입니다.
사용자 기반 협업 필터링
사용자A 에게 추천을 하기 위하여 사용자A와 유사한 사용자들의 평점을 이용
다음의 표가 있다고 가정해보겠습니다.
- 행 : 사용자의 ID
- 열 : 아이템ID
- 물음표(?) : 빈값(Null)
사용자 기반 협업 필터링에서는 사용자A와 유사한 사용자들의 평점을 이용합니다.
그리고 평점을 예측하여 예측한 평점이 높다면 사용자A가 더 선호할 것이라 판단하여 평점이 높은 순으로 아이템을 추천하게 됩니다.
이때 사용자A를 저희는 3번 사용자 혹은 사용자3 이라고 가정하겠습니다.
3번 사용자와 유사한 사용자들을 찾기 위해서는 코사인 유사도나 피어슨 상관계수를 이용합니다.
- 여기서는 피어슨 상관계수를 이용하도록 하겠습니다.
피어슨 상관게수는 ~1 ~ 1사이의 값을 가지게 됩니다.
- +1 완벽한 양의 선형 상관 관계
- -1 완벽한 음의 선형 상관 관계
- 0 선형 상관이 없음
피어슨 상관계수를 이용하여 유사한 사용자들 즉 피어그룹을 구하게 됩니다.
4, 5번 사용자의 피어슨 상관계수를 확인하니 각각 -1.0, -0.817로 사용자3과 음의 선형 상관 관계를 가지고 있습니다.
이는 사용자와 유사하지 않을 가능성이 높기 때문에 피어그룹에서 제외하도록 하겠습니다.
저희의 피어그룹은 1,2번 사용자입니다.
3번 사용자와 유사한 사용자들을 구했으니 이제 평점을 이용하여 아이템을 추천을 해야합니다.
3번사용자가 아직 평가하지 않은 데이터는 물음표(?)로 되어있는 1번, 6번 아이템입니다.
단순하게 위의 식으로 가중평균을 구하여 평점을 예측하여 추천을 할 수 있습니다.
u : 3번 사용자
k : 1, 2번 사용자(피어그룹)
각각 구해보면
3번 사용자의 1번 아이템 : 6.49
3번 사용자의 6번 아이템 : 4.0
위와 같이 값이 나오게 됩니다.
1, 6번 아이템뿐만 아니라 아이템의 수가 엄청나게 많다면 예측한 평점을 정렬하여 높은 순으로 추천을 할 수 있습니다.
- 실제로는 아이템 수가 사용자 수보다 더 많고 엄청나게 많은 아이템 수가 존재합니다.
하지만 위의 방법을 사용하면 사용자의 평가 경향을 고려하지 않고 평점을 예측한 것이기 때문에 좋은 방법은 아닙니다.
좋은 방법이 아닌 이유는 사용자들마다 평가를 내리는 경향이 다 다르기 때문입니다.
예를들어 어떤 사용자는 보통 평점을 높게 주어 평점이 6~7점으로 분포되어 있을 수 있고 어떤 사용자는 보통 평점을 낮게 주어 2~3 점으로 분포가 되어 있을 수 있습니다.
이때 두 사람이 같은 아이템에 대하여 평점을 5점을 주었다면 같은 5점이라도 같은 의미로 해석할 수 없을 것입니다.
이렇듯 우리는 사용자의 평가 경향을 고려하여 평점을 예측하여 추천을 해야합니다.
사용자 평가 경향을 고려한 방법
1. 각 사용자들의 평균 평점을 구합니다.
2. 평점편차를 구합니다.
- 평점편차 : 평점 - 사용자들의 평균 평점
3. 평점편차를 이용하여 가중평균을 구하여 평가값을 구합니다.
4. 평가값에서 평균평점을 더하여 실제예측값을 구합니다.
- 평점편차를 구할 때 평균평점을 빼주었으니 다시 더해줍니다.
이 방식으로 평점을 예측한다면
각각 값은
3번 사용자의 1번 아이템 : 3.34
3번 사용자의 6번 아이템 : 0.86
으로 결과가 나오게됩니다.
첫 번째 경우와 값은 많이 달라졌지만 사용자3에 대해서 더욱 적합하게 변경된 것을 확인할 수 있습니다.
코드구현
import pandas as pd
import numpy as np
class UserBaseCF():
def __init__(self, table):
"""
R : 원본테이블
S : 원본테이블에서 평균값을 뺀 테이블
Peason : 사용자-사용자 유사도
peergroup : 피어그룹
"""
self.R = table
self.S = self._get_S()
self.Pearson = self.R.T.corr(method='pearson')
self.peergroup = self._find_peer_group()
def _get_S(self):
"""
S 테이블을 만드는 함수
R에서 사용자들의 평균 평점을 뺀 값들을 저장
"""
Mu = self.R.mean(axis=1)
return (R.T - Mu).T
def _find_peer_group(self):
"""
피어그룹을 찾는 함수
피어그룹 : 상관계수가 0보다 크고 자기자신이 아님
"""
pearson = self.Pearson
return pearson[(pearson>0) & (pearson < 1)]
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)
#유저의 평균을 구함
Mu = self.R.mean(axis=1).loc[user_id]
#유저의 피어그룹을 구함
peergroup = self.peergroup.loc[user_id].dropna().index
#계산
a = self.Pearson.loc[user_id, peergroup].dot(self.S.loc[peergroup, item_index])
b = np.sum(self.Pearson.loc[user_id, peergroup])
result = (a / b) + Mu
result.sort_values(ascending=False)
return result
정리한 내용을 코드로 구현을 해보았습니다.
입력으로 받는 유저-사용자 테이블(R), 평점예측에 사용할 테이블(S), 사용자-사용자 유사도 테이블(Pearson), peergroup은 인스턴스로 생성이 될때 미리 구해집니다.
recommend()함수를 호출할 때 인자값(user_id)에 맞게 계산이 되어 값을 출력합니다.
만약 입력받는 테이블의 크기가 크다면 메모리오류나 시간이 오래걸릴 가능성이 높습니다.
개인 깃허브에 공부한 내용을 같이 올리고 있습니다.
들어와서 피드백주시면 감사하겠습니다.
'추천시스템' 카테고리의 다른 글
[추천시스템] 이웃 기반 협업 필터링(2) - 아이템 기반 (0) | 2023.01.03 |
---|---|
[추천시스템] 카카오 Mini Reco 기출문제 회고 (2) | 2023.01.01 |
[추천시스템] 평점의 종류 (0) | 2022.11.29 |
[추천시스템] 기본 협업 필터링 모델 (0) | 2022.11.21 |
[추천시스템] 목표 (0) | 2022.11.15 |