
Contents-Based Filtering 기법
영화 구성 콘텐츠 텍스트
↓
피처 벡터화 (Count, TF-IDF)
↓
코사인 유사도
↓
유사도 및 평점에 따른 영화 추천
Contents-Based Filtering 구현 프로세스
- 콘텐츠에 대한 여러 텍스트 정보들을 피처 벡터화
- 코사인 유사도로 콘텐츠별 유사도 계산
- 콘텐츠 별로 가중 평점을 계산
- 유사도가 높은 콘텐츠 중에 평점이 좋은 콘텐츠 순으로 추천
캐글 Movie dataset : https://www.kaggle.com/tmdb/tmdb-movie-metadata
TMDB 5000 Movie Dataset
Metadata on ~5,000 movies from TMDb
www.kaggle.com
파일 읽어오기
import pandas as pd
import numpy as np
import warnings; warnings.filterwarnings('ignore')
movies =pd.read_csv('./tmdb_5000_movies.csv')
print(movies.shape)
movies.head(1)
장르, 키워드, 영화제목, 런타임, 평점, 평점 수, 등의 데이터가 나온다.
필요한 컬럼을 기준으로 추출한다.
# id, 제목, 장르, 평점, 평점남긴 수, 등을 추출해낸다.
movies_df = movies[['id','title', 'genres', 'vote_average', 'vote_count',
'popularity', 'keywords', 'overview']]
우선 장르와 키워드만 추출해보자.
# 장르컬럼 다시 확인
pd.set_option('max_colwidth', 100)
movies_df[['genres','keywords']][:1]
# 장르와 키워드를 같이 써서 코사인 유사도를 판단해도 되고, 장르만 써서 장르유사도 많으로 컨텐츠기반 필터링 진행해도 된다.
# 여러개의 딕셔너리 형태로 되어있다.
# 여기서 id, name 빼고 Action만 빼서 리스트로 만들 것이다.
# 텍스트 문서들을 어떻게 parsing을 해서, 어떻게 '장르'라는 집합체로 가지고 갈 수 있을까?
movies_df.info()
1. 텍스트 문자 1차 가공. 파이썬 딕셔너리 변환 후 리스트 형태로 변환
from ast import literal_eval # 문자를 가공해서 파이썬 객체를 만들어 준다. (문자그대로를 평가)
# 이걸 쓰면 텍스트를 자동으로 parsing을 해서 거기에 맞는 객체로 만들어 준다.
movies_df['genres'] = movies_df['genres'].apply(literal_eval)
movies_df['keywords'] = movies_df['keywords'].apply(literal_eval)
literal_eval을 쓰면 딕셔너리가 객체로 들어가게 된다.
movies_df['genres'].head(5)
# 앞에 것과 크게 다른건 없는데 달라진건 딕셔너리 하나가 객체로 들어가 있다.
# 여기서 'name' 키를 가지고있는 value만 추출해보자
movies_df['keywords'].head(5)
literal_eval() : https://emilkwak.github.io/literal-eval-str-expression
장르와 키워드를 리스트에 담아서 추출해보자.
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]
# 장르와 키워드를 추출했다!
apply(lambda ) 잘 사용하기 : https://koreadatascientist.tistory.com/115
2. 장르 콘텐츠 필터링을 이용한 영화 추천
2.1. 장르 문자열을 Count 벡터화 후에 코사인 유사도로 각 영화를 비교
장르 문자열의 Count기반 피처 벡터화
from sklearn.feature_extraction.text import CountVectorizer
# CountVectorizer를 적용하기 위해 공백문자로 word 단위가 구분되는 문자열로 변환.
movies_df['genres_literal'] = movies_df['genres'].apply(lambda x : (' ').join(x))
# 개별 원소들을 다 공백으로 결합해서, string으로 변환해준다. 반횐되는 타입이 string이다.
movies_df[['genres_literal']][:]
count_vect = CountVectorizer(min_df=0, ngram_range=(1,2))
genre_mat = count_vect.fit_transform(movies_df['genres_literal'])
print(genre_mat.shape)
# 로 : 4803개, 컬럼 : 276개
(4803, 276)
2.2. 장르에 따른 영화별 코사인 유사도 추출
from sklearn.metrics.pairwise import cosine_similarity
genre_sim = cosine_similarity(genre_mat, genre_mat)
print(genre_sim.shape)
print(genre_sim[:2])
# argument sort 하면
genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]
print(genre_sim_sorted_ind[:1])
# 0번 영화에 대해서 코사인 유사도로 가장 유사한 순으로 가지고 있는 다른 영화들의 인덱스를 나타내게 된다.
2.3. 특정 영화와 장르별 유사도가 높은 영화를 반환하는 함수 생성
#(영화 데이터프레임, 위에서 만든 영화 인덱스, 기준이 되는 영화, 기준이 되는 영화와 유사한 10개)
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)] # sorted_ind는 2차원 [아바타 인덱스 [비슷한거 n개]]
# 추출된 top_n index들 출력. top_n index는 2차원 데이터 임.
#dataframe에서 index로 사용하기 위해서 1차원 array로 변경
print(similar_indexes)
similar_indexes = similar_indexes.reshape(-1) # 1차원 변경
return df.iloc[similar_indexes] # iloc 호출
기준이 되는 영화와 비슷한 영화를 10개 정도 찾아보자.
similar_movies = find_sim_movie(movies_df, genre_sim_sorted_ind, 'The Godfather',10)
similar_movies[['title', 'vote_average']]
# 대부2가 먼저 나오긴했지만 평점이 낮은영화가 나오기도 하였다.
평점이 낮은 영화들도 많이 추천되는 것을 확인할 수 있다.
그럼 평점이 높은 영화들도 확인해보자.
movies_df[['title','vote_average','vote_count']].sort_values('vote_average', ascending=False)[:10]
2.4 평가 횟수에 대한 가중치가 부여된 평점(Weighted Rating) 계산
가중 평점(Weighted Rating) = (v/(v+m)) * R + (m/(v+m)) * C
v : 개별 영화에 평점을 투표한 횟수
m : 평점을 부여하기 위한 최소 투표 횟수
R : 개별 영화에 대한 평균 평점
C : 전체 영화에 대한 평균 평점
C = movies_df['vote_average'].mean()
m = movies_df['vote_count'].quantile(0.6) # 상위 60%의의 투표표 횟수
print('C:',round(C,3), 'm:',round(m,3))
C: 6.092 m: 370.2
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)
movies_df[['title','vote_average','weighted_vote','vote_count']].sort_values('weighted_vote',
ascending=False)[:10]
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배에 해당하는 쟝르 유사성이 높은 index 추출
similar_indexes = sorted_ind[title_index, :(top_n*2)]
similar_indexes = similar_indexes.reshape(-1)
# 기준 영화 index는 제외
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']]
결과를 보면 코사인 유사도가 조금 낮더라도, 평점 순으로 정렬했기 때문에 American Ganster가 위로 조금 올라올 수 있게 되었다.
'💡 AI > RecSys' 카테고리의 다른 글
Surprise (0) | 2021.11.10 |
---|---|
Item-Item Collaborative Filtering (Movie Dataset) (0) | 2021.10.29 |
SKT AI - 추천시스템 (0) | 2021.09.29 |
연관분석 (0) | 2021.06.16 |