Contents-Based Filtering 기법

영화 구성 콘텐츠 텍스트

피처 벡터화 (Count, TF-IDF)

코사인 유사도

유사도 및 평점에 따른 영화 추천

 


Contents-Based Filtering 구현 프로세스

  1. 콘텐츠에 대한 여러 텍스트 정보들을 피처 벡터화
  2. 코사인 유사도로 콘텐츠별 유사도 계산
  3. 콘텐츠 별로 가중 평점을 계산 
  4. 유사도가 높은 콘텐츠 중에 평점이 좋은 콘텐츠 순으로 추천

캐글 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
복사했습니다!