추천 시스템 (TF-IDF기반)

코딩다시시작·2025년 3월 24일

LG DX SCHOOL

목록 보기
25/33

기본 세팅

import pandas as pd
import numpy as np
import urllib.request
import zipfile

# MovieLens 데이터셋 다운로드
url = 'https://files.grouplens.org/datasets/movielens/ml-latest-small.zip'
filename = 'ml-latest-small.zip'

urllib.request.urlretrieve(url, filename)
with zipfile.ZipFile(filename, 'r') as zip_ref:
    zip_ref.extractall('.')

# ratings, movies 데이터 로드
ratings = pd.read_csv('ml-latest-small/ratings.csv')
movies = pd.read_csv('ml-latest-small/movies.csv')

TF-IDF

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# 영화의 제목과 장르를 결합한 특성
movies['features'] = movies['title'] + ' ' + movies['genres']

#TF-IDF 행렬 계산
tfidf_vectorizer = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf_vectorizer.fit_transform(movies['features'])

#영화 간 코사인 유사도 계산
content_sim = cosine_similarity(tfidf_matrix, tfidf_matrix)

#영화 제목 인덱스 매핑 생성
indices = pd.Series(movies.index, index = movies['title'])

컨텐츠 기반 추천 시스템 (surprise)

1. 데이터로딩 & Reader 세팅

from surprise import Dataset, Reader, SVD
from surprise.model_selection import train_test_split, cross_validate
import pandas as pd

reader = Reader(rating_scale=(0.5, 5))
data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)
  • surpriseReader 객체는 평점 범위를 지정함(0.5~5)
  • ratings DataFrame에서 userId, movieId, rating 컬럼만 추출하여 surprise Dataset에 변환

2. SVD로 잠재요인 협업 필터링

  • 사용자아이템 간의 평점 행렬에 숨겨진 잠재요인(latent factor)를 찾아내어 추천
  • 행렬 분해를 위한 잠재요인을 추출하여 추천시스템 구축
trainset = data.build_full_trainset()
svd_model = SVD(n_factors=50, random_state=42)
svd_model.fit(trainset)
  • surprise전체 학습셋build_full_trainset()으로 변환해 사용

3. 예측테스트: 특정 사용자 (userId=1)

prediction = svd_model.predict(uid=1, iid=10)
print(f'예측 평점 (User 1 -> Movie 10) : {prediction.est:.2f}')

out:

예측 평점 (User 1 -> Movie 10) : 4.23

교차 검증 평가 (5-fold cv)

print("\n 교차 검증 결과 (RMSE,MAE)")
cross_validate(svd_model, data, measures = ['RMSE', 'MAE'], cv=5, verbose=True)
  • RMSE, MAE를 기준으로 모델 정확도 평가 -> 평점예측(회귀 사용 -> RMSE로 평가)

out:

 교차 검증 결과 (RMSE,MAE)
Evaluating RMSE, MAE of algorithm SVD on 5 split(s).

                  Fold 1  Fold 2  Fold 3  Fold 4  Fold 5  Mean    Std     
RMSE (testset)    0.8699  0.8697  0.8754  0.8675  0.8741  0.8713  0.0030  
MAE (testset)     0.6675  0.6699  0.6725  0.6670  0.6725  0.6699  0.0023  
Fit time          0.53    0.57    0.54    0.63    0.58    0.57    0.04    
Test time         0.06    0.14    0.06    0.06    0.06    0.08    0.03  

5. 사용자에게 추천할 영화 Top-n을 추출하는 함수

def get_top_n_recommendation(user_id, svd_model, ratings_df, n=10):
    #사용자가 이미 본 영화 목록 추출
    seen_movies = ratings_df[ratings_df['userId'] == user_id]['movieId'].tolist()
    
    #전체 영화 목록 중 사용자가 안 본 영화만 남기기.
    all_movies = ratings_df['movieId'].unique()
    unseen_movies = [movie for movie in all_movies if movie not in seen_movies]
    
    #안본 영화들에 대해 예측 평점 계산.
    predictions = [svd_model.predict(uid=user_id, iid=movie) for movie in unseen_movies]
    
    #예측 평점 기준으로 내림차순 정렬
    sorted_predictions = sorted(predictions, key=lambda x:x.est, reverse= True)
    
    #top-N 영화 ID 추출
    top_n_ids = [pred.iid for pred in sorted_predictions[:n]]
    return top_n_ids

6. 결과

user_Id=1에 대해 Top 10 추천영화 ID 추출

top10_ids = get_top_n_recommendation(user_id=1, svd_model=svd_model, ratings_df=ratings)
print(f'\n 추천영화 ID (user 1) : {top10_ids}')

out:

 추천영화 ID (user 1) : [318, 720, 1272, 1250, 1283, 750, 5618, 922, 858, 1148]
recommend_ids =top10_ids

#movieid title만 필터링
recommend_movies = movies[movies['movieId'].isin(recommend_ids)][['movieId', 'title']]
print('추천 영화 목록')
print(recommend_movies)
  • isin: 각 row의 movieIdrecommended_ids 안에 있는지 확인 -> (True/False)

out:

추천 영화 목록
      movieId                                              title
277       318                   Shawshank Redemption, The (1994)
585       720  Wallace & Gromit: The Best of Aardman Animatio...
602       750  Dr. Strangelove or: How I Learned to Stop Worr...
659       858                              Godfather, The (1972)
704       922      Sunset Blvd. (a.k.a. Sunset Boulevard) (1950)
868      1148        Wallace & Gromit: The Wrong Trousers (1993)
949      1250               Bridge on the River Kwai, The (1957)
971      1272                                      Patton (1970)
982      1283                                   High Noon (1952)
3984     5618  Spirited Away (Sen to Chihiro no kamikakushi) ...

하이브리드 추천 함수 구현

잠재 요인 기반 협업 필터링 + 콘텐츠 기반 = 하이브리드 추천 시스템

def hybrid_recommendation(user_id, movie_title, top_n = 10):
    # 콘텐츠 기반으로 유사한 영화 찾기
    #선택된영화 제목에 해당하는인덱스를 가져옴.
    idx = indices[movie_title]

    # 해당 영화와 다른 영화 간의 유사도 점수 가져오기.
    sim_scores = list(enumerate(content_sim[idx]))
    sim_scores = sorted(sim_scores, key=lambda x:x[1], reverse = True)[1:50]
    movie_indices = [i[0] for i in sim_scores]
    
    #협업 필터링(SVD)으로 해당 사용자에 대한 예측 평점 계산.
    movie_ids = movies.iloc[movie_indices]['movieId']
    pred=[]
    for movie_id in movie_ids:
        pred_rating = svd_model.predict(user_id, movie_id).est
        pred.append((movie_id, pred_rating))       
        
    #예측 평점 높은 순으로 Top-N 추출
    pred.sort(key = lambda x: x[1], reverse = True)
    top_movies = pred[:top_n]
    
    #최종 추천 영화 제목 반환(movieId -> 제목 mapping)
    recommended_titles = movies[movies['movieId'].isin([m[0] for m in top_movies])]['title']
    return recommended_titles.tolist() 

recommend_movies = hybrid_recommendation(user_id=1, movie_title='Toy Story (1995)', top_n=5)

print('하이브리드 추천 영화 리스트: ')
for idx, movie in enumerate(recommend_movies, 1):
    print(f'{idx}. {movie}')

out:

하이브리드 추천 영화 리스트: 
1. Watership Down (1978)
2. Toy Story 2 (1999)
3. Shrek (2001)
4. Monsters, Inc. (2001)
5. Finding Nemo (2003)
profile
gpt로 다시 배우는 개발

0개의 댓글