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')
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'])
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)
surprise의 Reader 객체는 평점 범위를 지정함(0.5~5)ratings DataFrame에서 userId, movieId, rating 컬럼만 추출하여 surprise Dataset에 변환사용자와 아이템 간의 평점 행렬에 숨겨진 잠재요인(latent factor)를 찾아내어 추천trainset = data.build_full_trainset()
svd_model = SVD(n_factors=50, random_state=42)
svd_model.fit(trainset)
surprise는 전체 학습셋을 build_full_trainset()으로 변환해 사용prediction = svd_model.predict(uid=1, iid=10)
print(f'예측 평점 (User 1 -> Movie 10) : {prediction.est:.2f}')
out:
예측 평점 (User 1 -> Movie 10) : 4.23
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
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
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의 movieId가 recommended_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)