[SSUDA] 영화 데이터를 활용한 추천시스템 실습
단순하게 떠올릴 수 있는 영화추천시스템은 3가지가 있습니다.
1) 보편적으로 많은 사람이 좋아하는 영화를 추천합니다. 가장 단순하고 강력한 방법입니다만 개인별 추천시스템과는 거리가 있습니다.
2) 특정 항목 내 비슷한 내용이 있는 영화를 추천합니다. 영화의 장르, 감독, 설명, 배우 등을 고려합니다. 어떤 사람이 특정 영화를 좋아한다면 비슷한 성격의 영화도 좋아할 것이다라는 논리 입니다.
3) 관심사가 비슷한 사용자를 매칭시키고 매칭된 사용자를 참고해 영화를 추천합니다.
from google.colab import drive
drive.mount('/content/drive')
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import warnings
warnings.filterwarnings("ignore")
path = '/content/drive/MyDrive/movie/'
df1=pd.read_csv(path + 'tmdb_5000_credits.csv')
df2=pd.read_csv(path + 'tmdb_5000_movies.csv')
df1.columns = ['id','tittle','cast','crew']
df2= df2.merge(df1,on='id')
df2.head()
데이터를 불러옵니다.
기술이 고도화됨에 따라 개인별 맞춤 추천 시스템을 많이 주목하지만 보편적으로 최고인 영화를 과소평가해선 안됩니다.
두 가지 모두 고려해야 더 좋은 효과를 보일 수 있습니다.
최고의 영화를 판별하는 기준만 정한다면 쉽게 구할 수 있습니다.
단순히 평점을 기준으로 한다면 소규모 평가가 이루워진 영화가 고평가 될 수 있습니다.
그래서 다음과 같은 기준을 사용합니다.
v는 평가한 인원의 수, m은 차트에 기록되기 위한 최소 평가 인원수
R은 영화의 평점, C는 모든 영화 평점의 평균 입니다.
직관적으로 식을 해석해보면 평가한 인원이 많다면(V가 크다면) 영화의 평점 영향력을 크게하겠다 입니다.
C= df2['vote_average'].mean()
C
모든 영화의 평점의 평균은 10점만점에 6점정도 되네요.
m= df2['vote_count'].quantile(0.9)
m
q_movies = df2.copy().loc[df2['vote_count'] >= m]
q_movies.shape
보편적으로 최고의 영화다라고 판별하기 위해서는 최소한 영화 평가가 어느정도 있는 영화를 대상으로 판별해야합니다.
평가 개수가 상위 10%인 영화만(481개) 선별합니다.
def weighted_rating(x, m=m, C=C):
v = x['vote_count']
R = x['vote_average']
return (v/(v+m) * R) + (m/(m+v) * C)
q_movies['score'] = q_movies.apply(weighted_rating, axis=1)
앞서 설명한 가중치를 이용해 영화를 평가하는 기준 값을 apply 함수를 사용해 구합니다.
q_movies = q_movies.sort_values('score', ascending=False)
q_movies[['title', 'vote_count', 'vote_average', 'score']].head(10)
q_movies = q_movies.sort_values('vote_average', ascending=False)
q_movies[['title', 'vote_count', 'vote_average', 'score']].head(10)
윗 코드는 새로운 지표 기준 상위 10개 영화, 밑 코드는 단순한 평점 기준 상위 10개 영화입니다. 1등을 제외하고 지표가 다릅니다.
이러한 보편적으로 최고의 영화를 추천하는 방식은 특정 사용자의 흥미나 취향에 민감하지 않다는 단점이 있습니다.
df2['overview'].head(5)
유사한 overview을 가진 영화를 추천하는 모델을 만들어보겠습니다. 그러기 위해선 overview 내 언어를 숫자로 변환해야하는데요.
영어 기준으로 단어 단위로 나오는 횟수를 단순히 원핫인코딩 방식으로 기록하는 CountVectorizer 함수를 고려할 수 있습니다.
CountVectorizer 은 입력받은 모든 단어를 단어사전에 기록하고 원핫인코딩 방식으로 각 문장을 숫자로 반환해줍니다.
다만 관계대명사 등 자주 나오는 단어이나 영화 내용을 파악하는데 불필요한 단어가 많이 기록된다는 단점이 있는데요.
이를 보안한 것이 TfidfVectorizer 입니다.
from sklearn.feature_extraction.text import TfidfVectorizer
tfidf = TfidfVectorizer(stop_words='english')
df2['overview'] = df2['overview'].fillna('')
tfidf_matrix = tfidf.fit_transform(df2['overview'])
print(tfidf_matrix[1].toarray())
print(tfidf_matrix.shape)
TfidfVectorizer 의 토대가 되는 기호 정리부터 하겠습니다.
TF(Term Frequency) : 특정 단어가 하나의 데이터 안에서 등장하는 횟수.
DF(Document Frequency) : 특정 단어가 여러 데이터에 자주 등장하는지를 알려주는 지표.
IDF(Inverse Document Frequency) : DF에 역수.
TF-IDF : TF와 IDF를 곱한 값. 즉 TF가 높고, DF가 낮을수록 값이 커지는 것을 이용하는 것입니다.
다른 문장에 많이 나오지 않는 단어는 고유한 그 영화만에 특성을 가진 단어라고 볼 수 있고 TF-IDF은 이를 반영한 지표입니다.
약 4800개 영화에 21000개 정도 단어종류가 확인됬군요.
from sklearn.metrics.pairwise import linear_kernel
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
print(cosine_sim.shape)
단어집합의 유사도 검정을 할 때 코사인 유사도 값을 많이 사용합니다.
cosine_sim 은 각각의 영화별 코사인 유사도 값을 기록한 행렬값 입니다.
indices = pd.Series(df2.index, index=df2['title']).drop_duplicates()
def get_recommendations(title, cosine_sim=cosine_sim):
# 입력받은 영화 제목의 인덱스 추출
idx = indices[title]
# 입력받은 영화와 다른 모든 영화의 코사인 유사도를 행렬에서 추출함.
# enumerate로 인덱스를 붙히고 리스트로 변환함.
sim_scores = list(enumerate(cosine_sim[idx]))
# 코사인 유사도가 높은 순서대로 정렬함.
sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
# 코사인 유사도가 높은 상위 10개 영화를 추출. 이때 인덱스 값이 같이 추출됨.
sim_scores = sim_scores[1:11]
# 코사인 유사도가 높은 상위 10개 영화의 인덱스만 추출
movie_indices = [i[0] for i in sim_scores]
# 인덱스를 이용해 영화 제목 추출.
return df2['title'].iloc[movie_indices]
get_recommendations('The Dark Knight Rises')
get_recommendations('The Avengers')
영화 제목 입력받으면 그와 비슷한(코사인 유사도기반) overview를 가진 상위 10개 영화를 추출하는 함수를 제작했습니다.
!pip install scikit-surprise
surprise는 다양한 추천시스템 함수를 가진 패키지 입니다.
from surprise import Reader, Dataset, SVD
from surprise.model_selection import cross_validate
reader = Reader()
ratings = pd.read_csv(path +'ratings_small.csv')
ratings.head()
surprise 패키지 내 SVD 함수를 이용하기 위해선 데이터 프레임이 반드시 (사용자ID, 영화ID, 평점) 순으로 있어야합니다.
data = Dataset.load_from_df(ratings[['userId', 'movieId', 'rating']], reader)
svd = SVD()
cross_validate(svd, data, measures=['RMSE'], cv=5)
RMSE 값이 크진 않은 것 같습니다.
svd.predict(1, 1172, 3)
ratings[(ratings['userId'] == 1) & (ratings['movieId'] == 1172)]
예측값과 실제값의 일부 사례를 관찰해보았습니다.