유진정의 기록
카카오 뮤직 추천시스템 깃허브 구경하기 _ programming assignment (3) mini_reco 본문
https://github.com/kakao/recoteam/tree/master/programming_assignments/mini_reco
공개 리포지토리라서 보다가 이 문제가 흥미로워보여서 공부해봤다.
1. User-based Collaborative Filtering 개요
User-based Collaborative Filtering은 사용자 간의 유사도를 기반으로 추천을 수행하는 방식
- 유사도 계산 : 사용자들 간의 유사도를 계산
- 이웃 선정 : 가장 유사한 N명의 사용자를 선택
- 평점 예측 : 선택된 이웃들의 평점을 기반으로 새로운 아이템에 대한 평점 예측
- 추천 : 예측된 평점이 높은 순서대로 아이템 추천
핵심 수식 설명
- 기본 평점 예측 공식
$r_{u,i} = \bar{r_u} + k\sum_{u' \in U}simil(u,u')(r_{u',i} - \bar{r_{u'}})$
- $r_{u,i}$: 사용자 u의 아이템 i에 대한 예측 평점
- $\bar{r_u}$: 사용자 u의 평균 평점
- $k$: 정규화 factor
- $U$: 유사한 이웃 사용자들의 집합
- $simil(u,u')$: 사용자 u와 u' 사이의 유사도
2. Cosine Similarity (코사인 유사도)
코사인 유사도는 두 사용자의 평점 패턴이 얼마나 유사한지를 측정하는 방법
수식
$simil(x,y) = cos(\vec{x},\vec{y}) = \frac{\vec{x} \cdot \vec{y}}{||\vec{x}|| \times ||\vec{y}||} = \frac{\sum_{i \in I_{xy}}r_{x,i}r_{y,i}}{\sqrt{\sum_{i \in I_x}r_{x,i}^2}\sqrt{\sum_{i \in I_y}r_{y,i}^2}}$
구성 요소 설명
기호 | 의미 |
---|---|
$I_{xy}$ | 사용자 x와 y가 모두 평가한 아이템들의 집합 |
$r_{x,i}$ | 사용자 x가 아이템 i에 준 평점 |
$r_{y,i}$ | 사용자 y가 아이템 i에 준 평점 |
$I_x$ | 사용자 x가 평가한 모든 아이템 집합 |
$I_y$ | 사용자 y가 평가한 모든 아이템 집합 |
특징
- 결과값의 범위 : -1 ~ 1
- 1에 가까울수록 두 사용자의 평점 패턴이 유사
- 0은 관계가 없음을 의미
- -1에 가까울수록 반대되는 패턴
이제 코드야!!!!
코드 들어가기 전에 궁금할까봐... 미리 정리한...
3. 데이터 입력 및 기본 구조 설정
import sys
# 데이터 입력
num_sim_user_top_N = int(sys.stdin.readline()) # 유사도 top N 사용자
num_item_rec_top_M = int(sys.stdin.readline()) # 예측 평점 top M 아이템
num_users = int(sys.stdin.readline()) # 전체 사용자 수
num_items = int(sys.stdin.readline()) # 전체 아이템 수
num_rows = int(sys.stdin.readline()) # 입력 row 개수
데이터 저장 구조
rating_data = {} # {사용자 ID: {아이템 ID: 평점}}
items = set() # 모든 아이템을 모아놓은 set
# 데이터 입력 받기
for _ in range(num_rows):
user, item, rating = sys.stdin.readline().split(' ')
user = int(user)
item = int(item)
rating = float(rating)
if user not in rating_data:
rating_data[user] = {}
rating_data[user][item] = rating
items.add(item)
데이터 구조 설명
rating_data
구조{ user1: { item1: rating1, item2: rating2, ... }, user2: { item1: rating1, item3: rating3, ... }, ... }
- 사용자 평균 평점 계산
user_average_rating = dict() # {사용자 ID: 평균 평점} for user, item_ratings in rating_data.items(): user_average_rating[user] = sum(item_ratings.values()) / len(item_ratings)
주요 포인트
- 중첩 딕셔너리 구조를 사용하여 빠른 접근이 가능
- 아이템 집합을 별도로 관리하여 전체 아이템 목록 유지
- 사용자별 평균 평점을 미리 계산하여 저장
4. 유사도 계산 및 이웃 선정
num_reco_users = int(sys.stdin.readline()) # 추천할 사용자 수
for _ in range(num_reco_users):
query_user = int(sys.stdin.readline()) # 추천할 사용자 ID
user_sim = dict() # {사용자 ID: 유사도}
# 코사인 유사도 계산
for user in rating_data.keys() - {query_user}:
user_ratings = rating_data[user]
query_user_ratings = rating_data[query_user]
# 공통 아이템 찾기
intersection_items = rating_data[query_user].keys() & rating_data[user].keys()
# 분자 계산: 공통 아이템에 대한 평점의 곱의 합
numerator = sum(user_ratings[item] * query_user_ratings[item]
for item in intersection_items)
# 분모 계산
denominator = sum(rating ** 2 for rating in user_ratings.values()) ** 0.5
denominator *= sum(rating ** 2 for rating in query_user_ratings.values()) ** 0.5
# 유사도 저장
user_sim[user] = numerator / denominator if denominator > 0.0 else 0.0
코드 분석
- 유사도 계산 과정:
단계 코드 수식 공통 아이템 찾기 intersection_items = rating_data[query_user].keys() & rating_data[user].keys()
$I_{xy}$ 분자 계산 numerator = sum(user_ratings[item] * query_user_ratings[item])
$\sum_{i \in I_{xy}}r_{x,i}r_{y,i}$ 분모 계산 denominator = sum(rating ** 2 for rating in user_ratings.values()) ** 0.5
$\sqrt{\sum_{i \in I_x}r_{x,i}^2}$ - 이웃 선정:
# 유사도 기준으로 정렬 nearest = sorted(user_sim.items(), key=lambda x: x[1], reverse=True)
5. 평점 예측 및 추천 리스트 생성
predicted_rating = list() # [(아이템 ID, 예측 평점), ...] 형태로 저장
# 아직 평가하지 않은 모든 아이템에 대해 예측
for item in items - rating_data[query_user].keys():
sum_k = 0 # normalizing factor k 계산용
sum_u = 0 # 유사도 가중치 합계
# 유사도 상위 N명의 사용자들에 대해 계산
for user, similarity in nearest[:num_sim_user_top_N]:
# 해당 사용자가 아이템을 평가했을 경우만 계산
if item not in rating_data[user]:
continue
# 예측식의 합계 부분 계산
sum_u += similarity * (rating_data[user][item] - user_average_rating[user])
sum_k += abs(similarity)
# 최종 예측 평점 계산
r_ui = (user_average_rating[query_user] +
(1 / sum_k) * sum_u if sum_k > 0.0 else 0.0)
predicted_rating.append((item, r_ui))
수식과 코드 매핑
$r_{u,i} = \bar{r_u} + k\sum_{u' \in U}simil(u,u')(r_{u',i} - \bar{r_{u'}})$
수식 요소 | 코드 구현 | 설명 |
---|---|---|
$\bar{r_u}$ | user_average_rating[query_user] |
추천 대상 사용자의 평균 평점 |
$k$ | 1 / sum_k |
정규화 계수 |
$simil(u,u')$ | similarity |
사용자 간 유사도 |
$r_{u',i}$ | rating_data[user][item] |
이웃 사용자의 아이템 평점 |
$\bar{r_{u'}}$ | user_average_rating[user] |
이웃 사용자의 평균 평점 |
최종 추천 리스트 생성
# 예측 평점 기준으로 정렬
predicted_rating.sort(key=lambda x: x[1], reverse=True)
# Top M 아이템의 ID만 추출하여 출력
print(' '.join(str(item_rating[0])
for item_rating in predicted_rating[:num_item_rec_top_M]))
이게 끝이 아니라....
질문도 있음... 징하다 징해
https://github.com/kakao/recoteam/blob/master/paper_review/recsys/recsys2021/Next-item%20Recommendations%20in%20Short%20Sessions.md
recoteam/paper_review/recsys/recsys2021/Next-item Recommendations in Short Sessions.md at master · kakao/recoteam
카카오 추천팀 공개 리포지토리입니다. Contribute to kakao/recoteam development by creating an account on GitHub.
github.com
1. 첫 번째 질문: 기본 추천 수식의 이해
User-based Collaborative Filtering 설명의 식 (1)을 보고 추천 결과가 어떻게 생성되는지 설명해주세요.
$r_{u,i} = \bar{r_u} + k\sum_{u' \in U}simil(u,u')(r_{u',i} - \bar{r_{u'}})$
- 우변 첫 번째 항: $\bar{r_u}$
- 사용자의 평균 평점
- 해당 사용자의 기본 평점 성향을 반영
- 우변 두 번째 항: $k\sum_{u' \in U}simil(u,u')(r_{u',i} - \bar{r_{u'}})$
- 이웃들의 평점 편차에 대한 가중 평균
- 편차($r_{u',i} - \bar{r_{u'}}$): 각 이웃의 평균 대비 해당 아이템 평점 차이
- 가중치($simil(u,u')$): 이웃과의 유사도
2. 두 번째 질문: 평균 항 제거의 영향
User-based Collaborative Filtering 설명의 식 (1)이 다음과 같이 바뀐다면( ru¯, ru′¯ 항이 사라진다면) 추천 결과가 어떻게 달라질까요?
$r_{u,i} = k\sum_{u' \in U}simil(u,u')(r_{u',i})$
- 평점 스케일 무시
- 낮은 평점 성향의 사용자 불이익
- 높은 평점 성향의 사용자 과대 평가
- 예시 시나리오:
- 원본 수식: 사용자 A에게는 좋은 평점
- 변경 수식: 이웃 기준으로는 낮은 평점
사용자 A: 평균 2점 부여 이웃들: 평균 4.5점 부여 특정 아이템에 이웃들이 3점 부여
3. 세 번째 질문: 유사도 가중치 제거의 영향
User-based Collaborative Filtering 설명의 식 (1)이 다음과 같이 바꾸려고 한다면( simil(u,u′) 항이 사라진다면) 식을 어떻게 수정해야할까요?
$r_{u,i} = \bar{r_u} + k\sum_{u' \in U}(r_{u',i} - \bar{r_{u'}})$
- 유사도 가중치($simil(u,u')$) 제거
- 모든 이웃의 의견을 동등하게 취급
- 단순 산술 평균으로 변경
- 해결 방법
k = 1/|U|로 설정 |U|: 이웃 집합의 크기
각 수식의 비교 표
수식 버전 | 특징 | 장단점 |
---|---|---|
원본 (1) | 평균, 유사도 모두 반영 | 가장 개인화된 추천 가능 |
변형 (1-1) | 평균 제거 | 개인의 평점 성향 무시 |
변형 (1-2) | 유사도 제거 | 이웃 간 중요도 차이 무시 |
핵심 시사점
- 평균 평점의 중요성
- 사용자별 평점 성향 반영 필요
- 편향(bias) 보정 역할
- 유사도 가중치의 의미
- 이웃 간 영향력 차등화
- 더 유사한 사용자의 의견을 더 중요하게 반영
혹시 아까 수식을 이해하지 못했다면,
User-based Collaborative Filtering 기본 수식 이해하기
예제 상황 |
- 추천을 받을 사용자 (user A)가 아직 보지 않은 영화 X에 대한 예측 평점을 계산하려고 합니다.
1. 초기 데이터
# 각 사용자의 영화 평점 데이터
rating_data = {
'User A': {'영화1': 4, '영화2': 2, '영화3': 5}, # 평균: 3.67
'User B': {'영화1': 5, '영화2': 3, '영화X': 4}, # 평균: 4.00
'User C': {'영화2': 1, '영화3': 3, '영화X': 2}, # 평균: 2.00
'User D': {'영화1': 3, '영화3': 4, '영화X': 5} # 평균: 4.00
}
2. 단계별 계산
Step 1: 코사인 유사도 계산
User A와 다른 사용자들의 유사도:
- A-B: 0.98 (매우 유사)
- A-C: 0.45 (약간 유사)
- A-D: 0.88 (꽤 유사)
Step 2: Top-2 이웃 선정 (N=2)
- User B (유사도: 0.98)
- User D (유사도: 0.88)
Step 3: 예측 평점 계산
$r_{u,i} = \bar{r_u} + k\sum_{u' \in U}simil(u,u')(r_{u',i} - \bar{r_{u'}})$
구성요소 | 값 | 설명 |
---|---|---|
$\bar{r_u}$ | 3.67 | User A의 평균 평점 |
$k$ | 0.54 | 1/(0.98 + 0.88) |
User B 기여분 | 0.98 × (4 - 4.00) | 유사도 × (영화X 평점 - B의 평균) |
User D 기여분 | 0.88 × (5 - 4.00) | 유사도 × (영화X 평점 - D의 평균) |
Step 4: 최종 계산
3.67 + 0.54 × [0.98 × (4 - 4.00) + 0.88 × (5 - 4.00)]
= 3.67 + 0.54 × [0.98 × 0 + 0.88 × 1]
= 3.67 + 0.54 × 0.88
= 4.14
따라서 User A의 영화 X에 대한 예측 평점은 4.14가 됩니다.
해석
- User A의 기본 성향(3.67)을 시작점으로 합니다.
- 유사한 이웃들의 평가를 반영:
- User B는 자신의 평균과 같게 평가 (영향 없음)
- User D는 자신의 평균보다 1점 높게 평가 (긍정적 영향)
- 이 영향들을 정규화해서 더함
'잡념정리' 카테고리의 다른 글
Scholar Inbox 소개 (1) | 2025.01.22 |
---|---|
스트리밍 서비스 ReSys 간략한 분석 _ 유튜브 뮤직 (1) | 2025.01.19 |
스트리밍 서비스 메뚜기의 추천시스템 비교(주관적) (0) | 2025.01.19 |