“추천시스템은 수많은 아이템 중에서
나에게 꼭 맞는 것을 골라주는 기술이다.”그리고 그 중심에는 Matrix Factorization(MF) 과 ALS(Alternating Least Squares) 모델이 있다.
추천시스템은 사용자의 취향 데이터를 학습해
“나와 비슷한 사람들이 좋아한 것을 나에게 추천”하는 AI 기술입니다.
| 구분 | 설명 | 예시 |
|---|---|---|
| 콘텐츠 기반 | 사용자가 좋아한 아이템의 속성을 분석해 유사 아이템 추천 | 내가 좋아한 영화의 장르·감독과 유사한 영화 추천 |
| 협업 필터링 | 나와 비슷한 사람들의 행동을 기반으로 추천 | 나와 비슷한 사용자가 들은 음악 추천 |
이번 실습은 협업 필터링(Collaborative Filtering) 기반으로 진행됩니다.
| 구분 | 설명 | 예시 |
|---|---|---|
| 명시적 데이터 | 사용자가 직접 선호를 표현 | 별점, 평점, 좋아요 |
| 암묵적 데이터 | 행동을 통해 간접적으로 선호 추정 | 재생 횟수, 클릭 수, 구매 기록 |
예시
이번 프로젝트에서는 암묵적 데이터(Implicit Feedback) 를 사용합니다.
핵심 아이디어
“거대한 유저-아이템 행렬을 두 개의 잠재요인 행렬로 분해하여 숨겨진 패턴을 찾자!”
MF의 목표는
유저와 아이템 벡터의 내적 결과가 실제 선호도와 최대한 비슷하도록 만드는 것.
ALS는 MF를 학습하는 효율적인 방법입니다.
이 과정에서 “암묵적 데이터의 신뢰도(confidence)”를 반영할 수 있다는 점이 핵심입니다.
암묵적 피드백(implicit) 기반의 추천시스템을
Matrix Factorization + ALS(Alternating Least Squares) 로 구현
import pandas as pd
import numpy as np
from scipy.sparse import csr_matrix
from implicit.als import AlternatingLeastSquares
# 데이터셋 로드 (TSV 형식)
data = pd.read_csv(
'lastfm-dataset-360K/usersha1-artmbid-artname-plays.tsv',
sep='\t',
usecols=['user_id', 'artist', 'plays']
)
print("데이터 로드 완료")
display(data.head())
# 문자열 통일 (대소문자 구분 제거)
data['artist'] = data['artist'].str.lower()
# 고유 사용자/아티스트 개수
num_users = data['user_id'].nunique()
num_artists = data['artist'].nunique()
print(f"유저 수: {num_users:,}, 아티스트 수: {num_artists:,}")
# user_id, artist 문자열을 숫자 인덱스로 변환
user_to_idx = {u: i for i, u in enumerate(data['user_id'].unique())}
artist_to_idx = {a: i for i, a in enumerate(data['artist'].unique())}
# 인덱싱 적용
data['user'] = data['user_id'].map(user_to_idx)
data['artist_id'] = data['artist'].map(artist_to_idx)
data = data[['user', 'artist_id', 'plays']]
display(data.head())
# Confidence 가중치: 자주 들은 노래일수록 신뢰도를 높임
alpha = 40
confidence = 1 + alpha * np.log1p(data['plays'])
csr_data = csr_matrix(
(confidence, (data['user'], data['artist_id'])),
shape=(num_users, num_artists),
dtype=np.float32
)
print(f"CSR Matrix 크기: {csr_data.shape}")
from implicit.nearest_neighbours import bm25_weight
csr_weighted = bm25_weight(csr_data).astype(np.float32)
# ALS 모델 생성
als_model = AlternatingLeastSquares(
factors=64, # 잠재요인 수
regularization=0.05, # 정규화 계수 (과적합 방지)
iterations=15, # 반복 횟수
dtype=np.float32
)
# implicit 패키지는 item-user 행렬을 입력으로 받음 → 전치 필요
csr_data_T = csr_data.T
# 학습
als_model.fit(csr_data_T)
print("ALS 모델 학습 완료!")
avorite_artist = "coldplay"
artist_id = artist_to_idx[favorite_artist]
# N=10개 비슷한 아티스트 탐색
similar_artists = als_model.similar_items(artist_id, N=10)
print(f"\n '{favorite_artist}'와 비슷한 아티스트 Top 10")
for idx, score in similar_artists:
print(f"- {list(artist_to_idx.keys())[idx]} (유사도: {score:.3f})")
출력 예시
coldplay ↔ oasis (0.974)
coldplay ↔ u2 (0.963)
coldplay ↔ travis (0.958)
coldplay ↔ keane (0.950)
def similar_artists(artist_name: str, topk: int = 10):
aid = artist_to_idx.get(artist_name.lower())
if aid is None:
return []
sims = als.similar_items(aid, N=topk)
# sims: [(artist_id, similarity_score), ...]
return [(idx_to_artist[i], float(s)) for i, s in sims]
print(similar_artists("coldplay", topk=10))
# 예시 사용자 선택
user_id = list(user_to_idx.keys())[0]
user_idx = user_to_idx[user_id]
# 개인화 추천
recommendations = als_model.recommend(
user_idx, csr_data, N=10,
filter_already_liked_items=True
)
print(f"\n {user_id}님께 추천하는 아티스트 Top 10")
for idx, score in recommendations:
print(f"- {list(artist_to_idx.keys())[idx]} (예상 선호도: {score:.3f})")
def recommend_for_user(user_name: str, topk: int = 10):
uid = user_to_idx.get(user_name)
if uid is None:
return []
recs = als.recommend(
uid, csr_data, N=topk,
filter_already_liked_items=True # 이미 소비한 아이템 제외
)
return [(idx_to_artist[i], float(s)) for i, s in recs]
# 예시: 임의 사용자
some_user = next(iter(user_to_idx.keys()))
print(some_user, recommend_for_user(some_user, 10))
target_artist = "rihanna"
artist_id = artist_to_idx[target_artist]
explanation = als_model.explain(user_idx, csr_data, itemid=artist_id)
print(f"\n 왜 '{target_artist}'을 추천했을까?")
print(explanation)
from implicit.evaluation import train_test_split, precision_at_k, mean_average_precision_at_k
# 데이터 분리
train, test = train_test_split(csr_data, train_percentage=0.8)
# 학습
als_model.fit(train.T)
# 평가
print("Precision@10 :", precision_at_k(als_model, train, test, K=10))
print("MAP@10 :", mean_average_precision_at_k(als_model, train, test, K=10))
def evaluate(factors, reg, iters):
model = AlternatingLeastSquares(factors=factors, regularization=reg, iterations=iters, dtype=np.float32)
model.fit(train.T)
return precision_at_k(model, train, test, K=10)
cands_f = [32, 64, 96]
cands_r = [0.01, 0.05, 0.1]
cands_i = [10, 15, 20]
best = (-1, None)
for f in cands_f:
for r in cands_r:
for it in cands_i:
score = evaluate(f, r, it)
if score > best[0]:
best = (score, (f, r, it))
print(f"[f={f} r={r} it={it}] precision@10={score:.4f}")
print("Best:", best)
user_freq = data.groupby('user').size()
artist_freq = data.groupby('artist_id').size()
keep_user = user_freq[user_freq >= 3].index
keep_artist = artist_freq[artist_freq >= 3].index
df = data[data['user'].isin(keep_user) & data['artist_id'].isin(keep_artist)]
“추천시스템은 데이터를 분류하는 기술이 아니라,
사람의 취향을 수학적으로 해석하는 기술이다.”
이번 실습을 통해
✅ 추천시스템의 기본 개념
✅ Matrix Factorization의 수학적 원리
✅ ALS 모델의 구현과 성능 평가
를 모두 경험했습니다.
Yifan Hu, Collaborative Filtering for Implicit Feedback Datasets
“7 Variants of Matrix Factorization for Collaborative Filtering” (TDS)
GroupLens MovieLens Dataset, Last.fm Dataset