- TMDB 5000 데이터 세트를 이용하여 사용자가 좋아하는 영화와 비슷한 콘텐츠를 가진 영화를 추천
- 영화 장르 속성을 기반으로 하여 장르 칼럼 값의 유사도를 비교한 뒤 높은 평점을 가지는 영화를 추천하는 방식으로 필터링 수행
1. 데이터 로딩 및 가공
- 데이터는 4803개의 레코드와 20개의 피처로 구성
- 주요 피처(id, title, genres, vote_avarage, vote_count, popularity, keywords, overview)만 추출해 DataFrame 생성
import pandas as pd
import numpy as np
import warnings; warnings.filterwarnings('ignore')
movies = pd.read_csv('./tmdb-5000-movie-dataset/tmdb_5000_movies.csv')
print(movies.shape)
# 주요 칼럼 추출
movies_df = movies[['id', 'title', 'genres', 'vote_average', 'vote_count', 'popularity',
'keywords', 'overview']]
movies_df.head(1)
- 'geners', 'keywords' 칼럼의 경우 리스트 안에 딕셔너리 형태의 문자열로 로딩되기 때문에 필요한 정보만을 가공해야 함
- 딕셔너리의 key인 'name'을 이용하여 개별 장르와 키워드 명칭을 리스트 객체로 추출
- 파이썬 ast 모듈의 literal_eval()를 이용하면 문자열을 list 객체로 만들 수 있음
- 장르명과 키워드만 리스트 객체로 추출하기 위해 apply lambd를 이용하여 'name' key 값에 해당하는 값을 추출
from ast import literal_eval
movies_df['genres'] = movies_df['genres'].apply(literal_eval)
movies_df['keywords'] = movies_df['keywords'].apply(literal_eval)
#키에 해당하는 값만 추출하여 리스트 형태로 변환
movies_df['genres'] = movies_df['genres'].apply(lambda x : [ y['name'] for y in x])
movies_df['keywords'] = movies_df['keywords'].apply(lambda x : [ y['name'] for y in x])
movies_df[['genres', 'keywords']][:1]
2. 장르 콘텐츠 유사도 측정
- 장르 칼럼을 문자열로 변환한 뒤 CounterVectorizer를 통해 피처 벡터화 행렬 생성
- 리스트 객체 내의 개별 값을 연속된 문자열로 변환하려면 ('구분문자'). join('리스트 객체')를 이용
- 피처 벡터 행렬은 4803개의 레코드와 276개의 개별 단어로 이루어짐
#리스트 개별 요소를 문자열로 가지는 genres_listeral칼럼 생성 후 피처벡터화
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)
[output]
(4803, 276)
- 피처 행렬 벡터에 cosine_similarity()를 적용해 코사인 유사도 측정
- 코사인 유사도 행렬을 가지는 genre_sim 객체는 결국 movies_df의 행별 장르 유사도 값을 가지고 있는 것
#코사인 유사도 측정
from sklearn.metrics.pairwise import cosine_similarity
genre_sim = cosine_similarity(genre_mat, genre_mat)
print(genre_sim.shape)
print(genre_sim[:2])
[output]
(4803, 4803)
[[1. 0.59628479 0.4472136 ... 0. 0. 0. ]
[0.59628479 1. 0.4 ... 0. 0. 0. ]]
- 가장 유사도가 높은 순으로 레코드를 추출하기 위해서 genre_sim 객체의 기준 행별로 비교 대상이 되는 열의 유사도 값이 높은 순으로 정렬된 행렬의 위치 인덱스 값 추출
- 넘파이의 argsort()[:,::-1]을 이용하면 유사도가 높은 순으로 정리된 genre_sim 객체의 비교 열 위치 인덱스 값을 얻을 수 있음
- 자기 자신인 0번 레코드를 제외하면 3494번 레코드가 가장 유사도가 높고, 2401번 레코드가 가장 유사도가 낮은 것을 확인
genre_sim_sorted_index = genre_sim.argsort()[:,::-1]
print(genre_sim_sorted_index[:1])
[output]
[[ 0 3494 813 ... 3038 3037 2401]]
2. 장르 콘텐츠 필터링을 이용한 영화 추천
- 장르 유사도에 따라 영화를 추천하는 함수 생성
- 기반데이터, 레코드별 장르 코사인 유사도 인덱스, 영화제목, 추천영화건수를 입력하면 추천 영화 정보를 가지는 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_name을 가진 DataFrame 의 index 객체를 ndarray로 반환하고
#sorted_ind 인자로 입력된 genres_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]
- '대부'와 유사한 영화 10개 중 낯선 영화와 평점이 낮은 편인 영화가 추출됨
- 개선이 필요
similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather', 10)
similar_movies[['title', 'vote_average']]
[output]
[[2731 1243 3636 1946 2640 4065 1847 4217 883 3866]]
- 영화의 평점에 따라 필터링해서 최종 추천하는 방식으로 변경
- 'vote_avarage'값은 평점을 평균한 값으로 소수의 관객이 평가한 평점에 대한 왜곡된 데이터를 가지고 있음
movies_df[['title', 'vote_average', 'vote_count']].sort_values('vote_average',
ascending=False)[:10]
- 평가 횟수에 대한 가중치가 부여된 평점 방식 사용
- m값을 높이면 평점 투표 횟수가 많은 영화에 더 많은 가중 평점 부여
각 변수의 의미
- v : 개별 영화에 평점을 투표한 횟수
- m : 평점을 부여하기 위한 최소 투표 횟수
- R : 개별 영화에 대한 평균 평점
- C : 전체 영화에 대한 평균 평점
- 기존 평점을 가중 평점으로 변경하는 함수을 통해 'vote_weighted' 칼럼 생성
percentile = 0.6
m = movies_df['vote_count'].quantile(percentile)
C = movies_df['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)
# weighted_vote 평점이 높은 순으로 상위 10개 영화 추출
movies_df[['title', 'vote_average','weighted_vote','vote_count']].sort_values(
'weighted_vote', ascending=False)[:10]
- 장르 유사성이 높은 영화를 top_n의 2배수 선정한 뒤에 weighted_vote 칼럼 값이 높은 순으로 top_n만큼 추출하는 함수 생성
def find_sim_movie(df, sorted_index, title_name, top_n=10):
title_movie = df[df['title']==title_name]
title_index = title_movie.index.values
#top_n 2배에 해당하는 장르 유사성이 높은 인덱스 추출
similar_indexes = sorted_index[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']]
'데이터 > 머신러닝' 카테고리의 다른 글
[NLP] Chapter 8 | 텍스트 분석 (네이버 영화 평점 감성 분석) (0) | 2023.02.11 |
---|---|
[NLP] Chapter 8 | 텍스트 분석 (비지도학습 기반 감성 분석) (0) | 2023.02.05 |
[NLP] Chapter 8 | 텍스트 분석 (지도학습 기반 감성 분석) (0) | 2023.02.04 |
[NLP] Chapter 8 | 텍스트 분석 (텍스트 분류) (1) | 2023.02.03 |
[NLP] Chapter 8 | 텍스트 분석 (소개 및 기반지식) (1) | 2023.02.02 |