< 목차 >
- 용어정의
- 데이터로딩 및 가공
- 장르 유사도 측정
- 콘텐츠 기반 필터링을 이용한 추천시스템 개발
- 추천결과 분석 및 왜곡데이터 이해
- 추천시스템 개선
1. 용어정의
- 피드백 후 작성예정입니다.
- TMDB 5000 Movies 데이터셋은 데이터셋 자료실 에 있습니다
- 전체코드는 https://github.com/pjh5672/Recommendation_System 에 올려두었습니다.
2. 데이터 로딩 및 가공
TMDB 5000 Movies 데이터셋은 4,803개 영화에 대한 영화제목, 개요, 장르, 인기도, 평점, 투표 수, 예산, 출시일, 키워드 등 20개의 메타정보(피처) 테이블로 구성된다. 이 중에서 주요 칼럼인 아이디, 영화제목, 장르, 평균 평점, 평균 투표 수, 인기도, 키워드, 개요설명 등을 뽑아내면 아래와 같다.
import pandas as pd
import numpy as np
import warnings; warnings.filterwarnings('ignore')
movies = pd.read_csv('./tmdb-movie-metadata/tmdb_5000_movies.csv')
movies_df = movies[['id', 'title', 'genres', 'vote_average', 'vote_count', 'popularity', 'keywords', 'overview']]
movies_df.head(1)
이때 장르(genres) 컬럼을 살펴보면, 리스트 내부에 여러 개의 딕셔러니가 있는 문자열로 장르에 대한 문자열만 리스트 객체로 추출할 필요가 있다. 따라서, literal_eval() 함수를 이용해서 문자열을 list [dict1, dict2] 의 객체로 변환하고 apply lambda 식을 이용해서 각 딕셔너리의 'name' 키에 해당하는 값(장르명)만 찾아 리스트 객체로 변환한다.
from ast import literal_eval
movies_df['genres'] = movies_df['genres'].apply(literal_eval)
movies_df['genres'] = movies_df['genres'].apply(lambda x:[y['name'] for y in x])
movies_df[['genres']][:1]
3. 장르 유사도 측정
리스트 객체에 담겨진 장르의 빈도수를 각 영화별로 Count 기반으로 벡터화하기 해 CountVectorizer 를 사용하고 영화ㅡ장르 매트릭스를 생성한다. 이때, 매트릭스의 형태는 (4803, 276) 크기이며 4803은 영화수, 276은 장르의 종류를 나타낸다.
from sklearn.feature_extraction.text import CountVectorizer
# CountVectorizer를 적용하기 위해 공백문자로 word 단위가 구분되는 문자열로 변환
movies_df['genres_literal'] = movies_df['genres'].apply(lambda x:(' ').join(x))
count_vect = CountVectorizer(min_df=0, ngram_range=(1,2))
genre_mat = count_vect.fit_transform(movies_df['genres_literal'])
print(genre_mat.shape)
피처 벡터화된 행렬에 cosine_similarity( )를 적용해서 반환된 코사인 유사도 행렬의 크기 및 앞 2개 데이터만 추출해보면 다음과 같다. 그리고 행의 유사도 값이 높은 순으로 정렬된 행렬의 위치인덱스 값을 추출한다.
from sklearn.metrics.pairwise import cosine_similarity
genre_sim = cosine_similarity(genre_mat, genre_mat)
print(genre_sim.shape)
print(genre_sim[:1])
genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]
print(genre_sim_sorted_ind[:1])
4. 콘텐츠 필터링을 이용한 추천시스템 개발
아래는 장르 유사도에 따라 영화 추천하는 함수이며, 가공한 데이터프레임인 DataFrame 과 레코드별 장르 코사인 유사도 인덱스를 가르키는 genre_sim_sorted_ind, 특정한 영화제목, 추천 영화 건수를 입력하는 추천 영화 정보에 대한 DataFrame 을 반환한다.
def find_sim_movie(df, sorted_ind, title_name, top_n=10):
# 인자로 입력된 movies_df DataFrame에서 'title' 칼럼이 입력된 title_name 값인 DataFrame 추출
title_movie = df[df['title'] == title_name]
# title_named을 가진 DataFrame의 index 객체를 ndarray로 반환하고
# sorted_ind 인자로 입력된 genre_sim_sorted_ind 객체에서 유사도 순으로 top_n개의 index 추출
title_index = title_movie.index.values
similar_indexes = sorted_ind[title_index, :(top_n)]
# 추출된 top_n index 출력. top_n index는 2차원 데이터임.
# DataFrame 에서 index로 사용하기 위해서 1차원 array로 변경
print(similar_indexes)
similar_indexes = similar_indexes.reshape(-1)
return df.iloc[similar_indexes]
similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather', 10)
similar_movies[['title', 'vote_average']]
5. 추천결과 분석 및 왜곡데이터 이해
그러나 위의 결과물을 보면 Mi America 등을 보면 평점이 0 이고, 대부(The Godfather)과 비슷한 장르가 아닌 Kids나 Light Sleeper 등 추천하기 어려운 영화들도 포함되어 있다. 따라서, 영화의 평점에 따라 필터링해서 최종적으로 추천하는 방식으로 변경하기 위해 vote_average 별로 오름차순 정렬하면 다음과 같이 왜곡된 데이터가 보인다.
movies_df[['title', 'vote_average', 'vote_count']].sort_values('vote_average', ascending=False)[:10]
'쇼생크 탈출(The Shawshank Redemption)' 이나 '대부(The Godfather)' 같은 명작보다 높은 순위에 'Still Upper Lips', 'Me You and Five Bucks' 와 같이 유명하지 않은 영화가 관객 한두 명에 의해 더 높은 평균 평점을 받은 것을 확인할 수 있다. 때문에, 평점에 가중치를 부여하는 방식을 사용하고, 공식은 다음을 따른다.
v 는 movies_df의 'vote_count' 값이며, R 은 'vote_average' 값에 해당하고, C의 경우 전체 영화 평균 평점이므로, movies_df['vote_average'].mean() 으로 구할 수 있다. 또한, m 은 투표 횟수에 따른 가중치를 직접 조절하는 역할을 하는데, m 값을 높이면 평점 투표 횟수가 많은 영화에 더 많은 가중 평점을 부여한다.
여기서 m은 전체 투표 횟수에서 상위 60%에 해당하는 횟수를 기준으로 정한다.
percentile = 0.6
m = movies['vote_count'].quantile(percentile)
C = movies['vote_average'].mean()
def weighted_vote_average(record):
v = record['vote_count']
R = record['vote_average']
return ((v/(v+m))*R) + ((m/(m+v))*C)
movies_df['weighted_vote'] = movies_df.apply(weighted_vote_average, axis=1)
movies_df[['title', 'vote_average', 'weighted_vote', 'vote_count']].sort_values('weighted_vote', ascending=False)[:10]
계산된 Weighted_vote 기준으로 평점 높은 순으로 상위 10개 영화를 추출하면 아래 결과와 같이 나온다.
6. 추천시스템 개선
계산된 weighted_vote 기준으로 상위 후보군을 추출하고, 후보군 가운데서 장르 유사성이 높은 영화들을 콘텐츠 기반 필터링 방식으로 추천하면 아래와 같은 결과가 나온다.
def find_sim_movie(df, sorted_ind, title_name, top_n=10):
title_movie = df[df['title'] == title_name]
title_index = title_movie.index.values
# top_n 의 2배에 해당하는 장르 유사성이 높은 인덱스 추출
similar_indexes = sorted_ind[title_index, :(top_n*2)]
similar_indexes = similar_indexes.reshape(-1)
# 기준 영화 인덱스는 제외
similar_indexes = similar_indexes[similar_indexes != title_index]
# top_n 의 2배에 해당하는 후보군에서 weighted_vote가 높은 순으로 top_n 만큼 추출
return df.iloc[similar_indexes].sort_values('weighted_vote', ascending=False)[:top_n]
similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather', 10)
similar_movies[['title', 'vote_average', 'weighted_vote']]
결과를 보면 '대부(The Godfather)'과 가장 유사한 내용의 The Godfather: Part II 부터해서 느와르 장르의 명작 영화들이 추천되는 것을 알 수 있다.
'인공지능 > 추천시스템' 카테고리의 다른 글
5. Surprise 라이브러리를 이용한 추천시스템 개발 (3) | 2021.04.10 |
---|---|
4. 행렬 분해를 이용한 잠재요인 협업 필터링 (4) | 2021.04.07 |
3. 아이템 기반 최근집 이웃 협업 필터링 (10) | 2021.04.06 |
1. 추천시스템 개요 (2) | 2021.04.06 |