Python

[Python] 추천 시스템(1)

orin602 2025. 3. 1. 18:04

추천 시스템(Recommendation System)

  • 콘텐츠 기반 필터링(Content-Based Filtering)
    • 사용자가 선호하는 특성을 기반으로 유사한 아이템 추천
    • ex) OTT에서의 영화장르, 감독, 배우 등 유사한 영화 추천.
  • 협업 필터링(Collaborative Filtering)
    • 최근접 이웃(Nearest Neighbor) 협업 필터링과 잠재 요인(Latent Factor) 협업 필터링으로 나뉨. 
    • 사용자의 행동 데이터(평점, 구매 목록, 장바구니 목록, 클릭, 검색)를 기반으로 유사한 패턴을 찾아서 추천
    • ex) 쿠팡 등 상품 클릭 시 클릭한 다른 사용자가 본 상품 추천.
  • 하이브리드 추천 시스템(Hybrid Filtering)
    • 콘텐츠 + 협업 필터링의 정교한 추천
    • ex) 유튜브, 넷플릭스, OTT 서비스

 

추천 시스템 구현시 주요 라이브러리

라이브러리 설명
pandas 데이터 전처리(csdv, 데이터프레임)
scikit-learn 머신러닝 모델(TF-IDF, 코사인 유사도)
Surprise 협업 필터링 알고리즘(SVD, KNN 등)
LightFM 행렬 분해(Matrix Factorization) 추천
Keras, TensorFlow 딥러닝 기반 추천 시스템

 

  • 추천 시스템 구축 시 고려할 점
    • 데이터 부족 문제 >>> 하이브리드 방법 활용
    • 추천 모델 평가 >>> RMSE, Precision@K 등의 평가 지표 사용
    • 확장성과 실시간 처리 >>> Spark, AWS 등을 활용한 추천 시스템 배포 고려

사용자 - 영화 평점 데이터 생성

출력 결과

 

 

사용자 - 영화 행렬 생성

- fillna(0) : 피봇 테이블은 데이터가 없는 곳이 NaN으로 표시되므로 0으로 바꾸기 위해 사용.

  • pivot()과 pivot_table()의 차이
    • pivot() : 한 개의 값만 values로 설정 가능.
    • pivot_table() : 여러 값에 대해 그룹화 가능, aggfunc로 평균/합계 등 집계가 가능.

사용자 기반 형업 필터링

>>> 철수와 맹구의 유사도가 0.962250으로 높음! = 영화 추천 시 비슷한 영화가 추천됨.

 

특정 사용자에게 영화 추천하기

(철수가 본 영화 목록 = 범죄도시, 인터스텔라, 탑건) - 유사도가 높은 맹구가 본 영화 반환 = (범죄도시, 인터스텔라)

탑건만 남음 >>> 탑건은 철수가 본 영화여서 추천되지 않음

>>> 결과는 아무것도 추천되지 않음.

 

# 타겟과 유사한 사용자 찾기
sim_user = user_sim_df[target_user].sort_values(ascending=False)[1:] # 정렬 후 자신 제외
most_sim_user = sim_user.idxmax()
  • user_sim_df[target_user] : 타겟 사용자의 모든 사용자와의 유사도 값을 가져옴.
  • .sort_values(ascending=False)[1:] : 유사도가 높은 순서로 정렬 후 자기 자신 제외.
  • idxmax() : 유사도가 가장 높은 사용자의 이름 반환
# 타겟이 안 본 영화 찾기
target_user_movies = set(movie_matrix.loc[target_user][movie_matrix.loc[target_user] > 0].index)
  • movie_matrix.loc[target_user] : 유사도가높은 사용자가 본 영화들의 평점 벡터 가져옴.
  • [movie_matrix.loc[target_user] > 0] : 평점이 0보다 큰 영화만 필터링.
  • .index → 영화 제목만 추출하여 집합(set) 으로 저장.
sim_user_movies = set(movie_matrix.loc[most_sim_user][movie_matrix.loc[most_sim_user] > 0].index)
  • most_sim_movies : 타겟이 본 영화 목록을 가져옴.
# 타겟이 안 본 영화 추천
recommend_movie = list(sim_user_movies - target_user_movies)
  • sim_user_movies - target_user_movies : 타겟이 본 영화 중에서 유사도가 높은 사용자가 보지 않은 영화 찾기

 


콘텐츠 기반 필터링

- TMDB 5000 영화 데이터 세트

 

 

https://www.kaggle.com/tmdb/tmdb-movie-metadata/tmdb_5000_movies_csv

파일 받기
다운받은 파일을 폴더에 옮기기

 

 

 

  • 행(RangeIndex : 4803 entries) : 데이터셋에 4803개의 영화
  • 열(Data columns (total 4 columns)) : 데이터셋은 청 4개의 열.
    • movie_id : 영화 고유 식별자, int64 타입
    • title : 영화 제목, object(문자열) 타입
    • cast : 영화 출연 배우, object(문자열) 타입
    • crew : 영화 제작진 정보, object(문자열) 타입
  • 결측값(Non-Null Count) : 4803 non-null >> 모든열에 4803개의 데이터 존재
  • 메모리 사용량(memory usage)  : 150KB 정도의 메모리 사용

결측값 없음

 

  • count : 4803개의 movie_id 존재
  • mean : movie_id의 평균 값
  • std : movie_id의 표준 편차
  • min : movie_id의 최소값
  • 25% : movie_id의 하위 25% 값
  • 50% : movie_id의 중앙값
  • 75% : movie_id의의 상위 25% 값
  • max : movie_id의 최대값

 

MovieLens 데이터셋 사용 준비

https://grouplens.org/datasets/movielens/

 

 

 

 

데이터 전처리

 

MovieLens 기반 영화 추천 시스템 만들기

 

유사도 계산

 

영화 추천 함수 만들기

 

 


1. 데이터프레임 생성 및 전처리

- 라이브러리 임포트 및 데이터 로드

import pandas as pd

# 데이터 로드
movies_df = pd.read_csv('movies.csv')
ratings_df = pd.read_csv('ratings.csv')
tags_df = pd.read_csv('tags.csv')

# 데이터 확인
print(movies_df.head())
print(ratings_df.head())
print(tags_df.head())

 

- 전처리

 

- 가중 평점 계산

영화 평점 정보가 0~10점 사이인데, 1-2명의 소수 관객이 특정 영화에 만점이나 매우 높은 평점을 부여해

왜곡된 데이터를가지고 있음.

>>> 왜곡된 평점 데이터를 회피하기 위해 가중 평점 공식(Weighted Rating)을 사용

 

- 코사인 유사도 활용 영화 추천

영화 특징 벡터화

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity
import re

# 장르를 공백으로 구분해서 벡터화
count_vectorizer = CountVectorizer(stop_words='english')
count_matrix = count_vectorizer.fit_transform([' '.join(genres) for genres in movies_df['genres']])

# 코사인 유사도 계산
cosine_sim = cosine_similarity(count_matrix, count_matrix)

# 영화 제목에서 연도(괄호 안의 숫자) 제거하는 함수
def remove_year(title):
    return re.sub(r'\(\d{4}\)', '', title).strip()

# 연도 제거한 제목으로 새로운 컬럼 추가
movies_df['clean_title'] = movies_df['title'].apply(remove_year)

# 영화 제목과 유사도를 매핑
cosine_sim_df = pd.DataFrame(cosine_sim, index=movies_df['clean_title'], columns=movies_df['clean_title'])

# 특정 영화와 유사한 영화 찾기
def recommend_similar_movies(movie_title, cosine_sim_df, movies_df, top_n=5):
    # 영화 제목 정리 (연도 제거)
    movie_title_clean = remove_year(movie_title)

    # 영화 제목이 데이터에 존재하는지 확인
    if movie_title_clean not in movies_df['clean_title'].values:
        return f"'{movie_title}'에 해당하는 영화 제목을 찾을 수 없습니다."

    # 영화의 실제 제목을 찾기
    movie_index = movies_df[movies_df['clean_title'] == movie_title_clean].index[0]
    
    # 해당 영화와 유사한 영화들 찾기
    similar_movies = cosine_sim_df.iloc[movie_index].sort_values(ascending=False)[1:top_n+1]
    
    return similar_movies

# 'Taken 3' 영화에 대한 추천 영화
recommend_similar_movies('Taken 3', cosine_sim_df, movies_df, top_n=12)

 

- 사용자-영화 평점 행렬 데이터로 최근접 이웃 협업 필터링

사용자-영화 평점 행렬 생성

# 사용자-영화 평점 행렬 생성
user_movie_matrix = ratings_df.pivot_table(index='userId', columns='movieId', values='rating')

# NaN 값을 0으로 대체
user_movie_matrix = user_movie_matrix.fillna(0)

# 사용자 간 유사도 계산
user_sim = cosine_similarity(user_movie_matrix)
user_sim_df = pd.DataFrame(user_sim, index=user_movie_matrix.index, columns=user_movie_matrix.index)

print(user_sim_df)

 

최근접 이웃 협업 필터링을 활용한 영화 추천


1. 영화 제목을 가지고 비슷한 장르의 영화 추천

영화 제목을 기반으로 장르를 벡터화하고, 코사인 유사도를 계산하여 유사한 영화들을 추천합니다.

import pandas as pd
import re
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# 1. CSV 파일 읽어오기
movies_df = pd.read_csv('movies.csv')

# 2. 영화 제목에서 연도(괄호 안의 숫자) 제거하는 함수
def remove_year(title):
    return re.sub(r'\(\d{4}\)', '', title).strip()

# 3. 영화 장르 전처리 (|로 구분된 장르 리스트로 변환)
movies_df['genres'] = movies_df['genres'].apply(lambda x: x.split('|'))

# 4. 장르를 공백으로 구분해서 벡터화
count_vectorizer = CountVectorizer(stop_words='english')
count_matrix = count_vectorizer.fit_transform([' '.join(genres) for genres in movies_df['genres']])

# 5. 코사인 유사도 계산
cosine_sim = cosine_similarity(count_matrix, count_matrix)

# 6. 영화 제목에서 연도 제거한 제목으로 새로운 컬럼 추가
movies_df['clean_title'] = movies_df['title'].apply(remove_year)

# 7. 유사도 매핑하여 DataFrame 생성
cosine_sim_df = pd.DataFrame(cosine_sim, index=movies_df['clean_title'], columns=movies_df['clean_title'])

# 8. 특정 영화와 유사한 영화 찾기 함수
def recommend_similar_movies(movie_title, cosine_sim_df, movies_df, top_n=10):
    # 영화 제목에서 연도 제거
    movie_title_clean = remove_year(movie_title)
    
    # 영화 제목이 데이터에 존재하는지 확인
    if movie_title_clean not in movies_df['clean_title'].values:
        return f"'{movie_title}'에 해당하는 영화 제목을 찾을 수 없습니다."
    
    # 영화의 실제 제목을 찾기
    movie_index = movies_df[movies_df['clean_title'] == movie_title_clean].index[0]
    
    # 해당 영화와 유사한 영화들 찾기
    similar_movies = cosine_sim_df.iloc[movie_index].sort_values(ascending=False)[1:top_n+1]
    
    # 추천된 영화의 제목과 장르 반환
    recommended_movies = []
    for movie_title in similar_movies.index:
        movie_genre = movies_df[movies_df['clean_title'] == movie_title]['genres'].values[0]
        recommended_movies.append((movie_title, movie_genre))
    
    return recommended_movies

# 9. 영화 제목을 기반으로 추천 예시
movie_title = 'Taken'  # 예시 영화 제목
recommended_movies_by_genre = recommend_similar_movies(movie_title, cosine_sim_df, movies_df, top_n=10)

# 10. 추천된 영화 출력
print(f"'{movie_title}'와 비슷한 영화들:")
for movie in recommended_movies_by_genre:
    print(f"제목: {movie[0]}, 장르: {movie[1]}")

 

 

2. 사용자가 본 영화의 평점을 가지고 비슷한 사용자의 안 본 영화 추천

사용자 간 유사도를 계산하여 비슷한 사용자가 본 영화를 추천합니다.

import pandas as pd
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity

# 1. 데이터 불러오기
ratings_df = pd.read_csv('ratings.csv')
movies_df = pd.read_csv('movies.csv')

# 2. 전체 영화 평균 평점 계산
C = ratings_df['rating'].mean()

# 3. 각 영화에 대한 평점 횟수와 평균 평점 계산
movie_rating_count = ratings_df.groupby('movieId').size()
movie_avg_rating = ratings_df.groupby('movieId')['rating'].mean()

# 4. 평점 가중치 계산 함수
def weighted_rating(x, C=C, m=10):
    v = x['vote_count']
    R = x['avg_rating']
    return (v / (v + m) * R) + (m / (v + m) * C)

# 5. 영화 데이터프레임에 가중 평점 추가
movies_df['vote_count'] = movie_rating_count
movies_df['avg_rating'] = movie_avg_rating
movies_df['weighted_rating'] = movies_df.apply(weighted_rating, axis=1)

# 6. 추천 함수 정의
def recommend_movies(user_ratings, movies_df, top_n=10):
    # 사용자가 본 영화 리스트와 평점
    user_watched_movies = user_ratings[user_ratings > 0].index.tolist()
    
    # 사용자가 이미 본 영화들을 제외한 나머지 영화들에 대해 가중 평점 기반으로 추천
    recommended_movies = movies_df[~movies_df['movieId'].isin(user_watched_movies)]
    
    # 가중 평점 기준으로 상위 N개의 영화 추천
    recommended_movies = recommended_movies.sort_values('weighted_rating', ascending=False).head(top_n)
    
    # 추천된 영화 제목과 가중 평점 반환
    return recommended_movies[['title', 'weighted_rating']]

# 7. 사용자 영화 평점 예시 (userId, movieId, rating)
# 예시로 사용자 1이 본 영화의 평점 정보를 제공한다고 가정
user_ratings = ratings_df[ratings_df['userId'] == 1].set_index('movieId')['rating']

# 8. 영화 추천
recommended_movies = recommend_movies(user_ratings, movies_df, top_n=10)

# 9. 추천된 영화 출력
print("추천 영화들:")
print(recommended_movies)

 

- 최종 개인화된 영화 추천

콘텐츠 기반 추천협업 필터링 방법을 통합하여, 최종적으로 개인화된 영화 추천 시스템을 구현

import pandas as pd
import numpy as np
import re
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# 1. 데이터 불러오기
ratings_df = pd.read_csv('ratings.csv')
movies_df = pd.read_csv('movies.csv')

# 2. 영화 제목에서 연도(괄호 안의 숫자) 제거하는 함수
def remove_year(title):
    return re.sub(r'\(\d{4}\)', '', title).strip()

# 3. 영화 장르 전처리 (|로 구분된 장르 리스트로 변환)
movies_df['genres'] = movies_df['genres'].apply(lambda x: x.split('|'))

# 4. 장르를 공백으로 구분해서 벡터화
count_vectorizer = CountVectorizer(stop_words='english')
count_matrix = count_vectorizer.fit_transform([' '.join(genres) for genres in movies_df['genres']])

# 5. 코사인 유사도 계산 (콘텐츠 기반)
cosine_sim = cosine_similarity(count_matrix, count_matrix)

# 6. 영화 제목에서 연도 제거한 제목으로 새로운 컬럼 추가
movies_df['clean_title'] = movies_df['title'].apply(remove_year)

# 7. 유사도 매핑하여 DataFrame 생성
cosine_sim_df = pd.DataFrame(cosine_sim, index=movies_df['clean_title'], columns=movies_df['clean_title'])

# 8. 전체 영화 평균 평점 계산
C = ratings_df['rating'].mean()

# 9. 각 영화에 대한 평점 횟수와 평균 평점 계산
movie_rating_count = ratings_df.groupby('movieId').size()
movie_avg_rating = ratings_df.groupby('movieId')['rating'].mean()

# 10. 평점 가중치 계산 함수 (협업 필터링)
def weighted_rating(x, C=C, m=10):
    v = x['vote_count']
    R = x['avg_rating']
    return (v / (v + m) * R) + (m / (v + m) * C)

# 11. 영화 데이터프레임에 가중 평점 추가 (협업 필터링)
movies_df['vote_count'] = movie_rating_count
movies_df['avg_rating'] = movie_avg_rating
movies_df['weighted_rating'] = movies_df.apply(weighted_rating, axis=1)

# 12. 장르 기반 추천 함수 (콘텐츠 기반)
def recommend_similar_movies(movie_title, cosine_sim_df, movies_df, top_n=10):
    movie_title_clean = remove_year(movie_title)
    
    if movie_title_clean not in movies_df['clean_title'].values:
        return f"'{movie_title}'에 해당하는 영화 제목을 찾을 수 없습니다."
    
    movie_index = movies_df[movies_df['clean_title'] == movie_title_clean].index[0]
    similar_movies = cosine_sim_df.iloc[movie_index].sort_values(ascending=False)[1:top_n+1]
    
    recommended_movies = []
    for movie_title in similar_movies.index:
        movie_genre = movies_df[movies_df['clean_title'] == movie_title]['genres'].values[0]
        recommended_movies.append((movie_title, movie_genre))
    
    return recommended_movies

# 13. 사용자 평점 기반 추천 함수 (협업 필터링)
def recommend_movies(user_ratings, movies_df, top_n=10):
    user_watched_movies = user_ratings[user_ratings > 0].index.tolist()
    recommended_movies = movies_df[~movies_df['movieId'].isin(user_watched_movies)]
    recommended_movies = recommended_movies.sort_values('weighted_rating', ascending=False).head(top_n)
    return recommended_movies[['title', 'weighted_rating']]

# 14. 최종 개인화된 영화 추천 함수
def personalized_movie_recommendation(user_id, movie_title, ratings_df, movies_df, cosine_sim_df, top_n=10):
    # 사용자 평점 데이터 가져오기
    user_ratings = ratings_df[ratings_df['userId'] == user_id].set_index('movieId')['rating']
    
    # 협업 필터링 기반 추천
    collaborative_recommendations = recommend_movies(user_ratings, movies_df, top_n)
    
    # 콘텐츠 기반 추천
    genre_based_recommendations = recommend_similar_movies(movie_title, cosine_sim_df, movies_df, top_n)
    
    # 추천 결과 결합
    combined_recommendations = []
    
    # 협업 필터링 결과 추가
    for movie in collaborative_recommendations.itertuples():
        combined_recommendations.append((movie.title, movie.weighted_rating, 'Collaborative'))
    
    # 콘텐츠 기반 결과 추가
    for movie in genre_based_recommendations:
        # 콘텐츠 기반 추천의 평점은 협업 필터링 평점 평균으로 설정 (혹은 다른 로직으로 조정 가능)
        avg_rating = np.mean([rating for title, rating in collaborative_recommendations.values if title == movie[0]])
        combined_recommendations.append((movie[0], avg_rating, 'Content-based'))
    
    # 추천된 영화들을 가중 평균 기준으로 정렬
    combined_recommendations.sort(key=lambda x: x[1], reverse=True)
    
    return combined_recommendations[:top_n]

# 15. 예시로 사용자 1과 영화 'Taken'에 대해 추천
user_id = 1
movie_title = 'Taken'
recommended_movies = personalized_movie_recommendation(user_id, movie_title, ratings_df, movies_df, cosine_sim_df, top_n=10)

# 16. 최종 추천 결과 출력
print(f"사용자 ID: {user_id}, 입력된 영화: {movie_title}")
print("개인화된 추천 영화들:")
for movie in recommended_movies:
    print(f"제목: {movie[0]}, 가중 평점: {movie[1]}")


 

'Python' 카테고리의 다른 글

[Python] 람다 표현식  (0) 2025.03.02
[Python] 추천 시스템(2)  (0) 2025.03.01
[Python] 함수 사용하기  (0) 2025.02.22
[Python] 2차원 리스트  (0) 2025.02.22
[Python] 리스트와 튜플 (3)  (0) 2025.02.22