코딩걸음마

[추천 시스템(RS)] CBF 장르 기반 영화 추천 코드 본문

딥러닝 템플릿/추천시스템(RS) 코드

[추천 시스템(RS)] CBF 장르 기반 영화 추천 코드

코딩걸음마 2022. 7. 16. 01:29
728x90

1. 데이터 불러오기

사용할 데이터 셋 : TMDB 5000 Movie Dataset

https://www.kaggle.com/datasets/tmdb/tmdb-movie-metadata/download?datasetVersionNumber=2 

 

Kaggle: Your Home for Data Science

 

www.kaggle.com

import pandas as pd  
import numpy as np
movies_df = pd.read_csv('data/tmdb_5000_movies.csv')
print(movies_df.shape)  # 행, 열 개수 파악
movies_df.head()

 

(4803, 20)

 

 

2. 데이터 확인

결측치 및 dtype확인

movies_df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4803 entries, 0 to 4802
Data columns (total 20 columns):
 #   Column                Non-Null Count  Dtype  
---  ------                --------------  -----  
 0   budget                4803 non-null   int64  
 1   genres                4803 non-null   object 
 2   homepage              1712 non-null   object 
 3   id                    4803 non-null   int64  
 4   keywords              4803 non-null   object 
 5   original_language     4803 non-null   object 
 6   original_title        4803 non-null   object 
 7   overview              4800 non-null   object 
 8   popularity            4803 non-null   float64
 9   production_companies  4803 non-null   object 
 10  production_countries  4803 non-null   object 
 11  release_date          4802 non-null   object 
 12  revenue               4803 non-null   int64  
 13  runtime               4801 non-null   float64
 14  spoken_languages      4803 non-null   object 
 15  status                4803 non-null   object 
 16  tagline               3959 non-null   object 
 17  title                 4803 non-null   object 
 18  vote_average          4803 non-null   float64
 19  vote_count            4803 non-null   int64  
dtypes: float64(3), int64(4), object(13)
memory usage: 750.6+ KB

 

3. 장르 컬럼 추출

 

print(type(movies_df['genres'][0]))
movies_df['genres'][0]

딕셔너리 처럼 보이지만 문자열 type이다

<class 'str'>
'[{"id": 28, "name": "Action"}, {"id": 12, "name": "Adventure"}, {"id": 14, "name": "Fantasy"}, {"id": 878, "name": "Science Fiction"}]'

 

from ast import literal_eval  # 문자열 파싱 (리스트 or 딕셔너리 형태의 문자열을 리스트 or 딕셔너리로 변환)
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']][0:3]

 

 

4. 장르 컨텐츠 유사도 측정

 장르 CBF 추천 : 장르를 피처 벡터화한 후 행렬 데이터 값을 코사인 유사도(0~1)로 계산

<프로세스>
1. 장르 피처 벡터화: 문자열로 변환된 genres 칼럼을 Count 기반으로 피처 벡터화 변환
2. 코사인 유사도 계산 : genres 문자열을 피처 벡터화한 행렬로 변환한 데이터 세트를  코사인 유사도로 비교
3. 평점으로 계산 : 장르 유사도가 높은 영화 중 평점이 높은 순으로 영화 추천

!)  CountVectorizer
 1. 문서를 토큰 리스트로 변환한다.
 2. 각 문서에서 토큰의 출현 빈도를 센다.
 3. 각 문서를 BOW(Bag of Words) 인코딩 벡터로 변환한다.

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import CountVectorizer
corpus = [
    '안녕하세요~ 감사해요 다시 만나요',
    '안녕 내사람 그대여 그대 내가 지켜줄게요',
    '안녕 안녕 안녕~',
    '안녕이라고 내게 말하지마',

]

# 토큰 별 인덱스 사전을 만들어준다.
vect = CountVectorizer()
vect.fit(corpus)

vect.vocabulary_
{'안녕하세요': 11,
 '감사해요': 0,
 '다시': 6,
 '만나요': 7,
 '안녕': 9,
 '내사람': 5,
 '그대여': 2,
 '그대': 1,
 '내가': 3,
 '지켜줄게요': 12,
 '안녕이라고': 10,
 '내게': 4,
 '말하지마': 8}

 

 

# CountVectorizer로 학습시켰더니 4803개 영화에 대한 276개 장르의 '장르 매트릭스'가 생성되었다.

count_vect = CountVectorizer(min_df=0, ngram_range=(1, 2))  # min_df: 단어장에 들어갈 최소빈도, ngram_range: 1 <= n <= 2
genre_mat = count_vect.fit_transform(movies_df['genres_literal'])

print(genre_mat.shape)
print(genre_mat)
# 영화 별 장르 인덱스 사전을 만들어준다
# CountVectorizer로 학습시켰더니 4803개 영화에 대한 22개 장르의 '장르 매트릭스'가 생성되었다.
count_vect2 = CountVectorizer(min_df=1, ngram_range=(1, 1))  # min_df: 단어장에 들어갈 최소빈도, ngram_range: 1 <= n <= 2
genre_mat2 = count_vect2.fit_transform(movies_df['genres_literal'])
print(genre_mat2.shape)
print(genre_mat2)
(4803, 22)
  (0, 0)	1
  (0, 1)	1
  (0, 8)	1
  (0, 17)	1
  (0, 9)	1
.
.
.
  (4799, 3)	1
  (4799, 16)	1
  (4800, 6)	1
  (4800, 3)	1
  (4800, 16)	1
  (4800, 19)	1
  (4800, 13)	1
  (4802, 5)	1

 

 

 5) 코사인 유사도(cosine_similarity)이용해서 영화별 유사도 계산

# 코사인 유사도에 의해 4803개 영화 각각 유사한 영화들이 계산됨
from sklearn.metrics.pairwise import cosine_similarity
genre_sim = cosine_similarity(genre_mat, genre_mat)

print(genre_sim.shape)
print(genre_sim[:10])
print(genre_sim[0][:5])
[1.         0.59628479 0.4472136  0.12598816 0.75592895]

 

 

 

# 자료를 정렬하는 것이 아니라 순서만 알고 싶다면 argsort
# 유사도가 높은 영화를 앞에서부터 순서대로 보여줌

# 0번째 영화의 경우 유사도 순서 : 0번, 3494번, 813번, ..., 2401 순서
genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1] # ::-1 : 역순으로 정렬
print(genre_sim_sorted_ind[:5])
genre_sim_sorted_ind
array([[   0, 3494,  813, ..., 3038, 3037, 2401],
       [ 262,    1,  129, ..., 3069, 3067, 2401],
       [   2, 1740, 1542, ..., 3000, 2999, 2401],
       ...,
       [4800, 3809, 1895, ..., 2229, 2230,    0],
       [4802, 1594, 1596, ..., 3204, 3205,    0],
       [4802, 4710, 4521, ..., 3140, 3141,    0]])

 

 

6) 장르 코사인 유사도에 의해 영화를 추천하는 함수

# 인자로 입력된 movies_df DataFrame에서 'title' 컬럼이 입력된 title_name 값인 DataFrame추출
title_movie = movies_df[movies_df['title'] == 'Avatar']
title_movie
# 추출된 top_n index들 출력. top_n index는 2차원 데이터 임.
# dataframe에서 index로 사용하기 위해서 2차원 array를 1차원 array로 변경
similar_indexes = similar_indexes.reshape(-1)
print(similar_indexes)
[   0 3494  813  870   46   14 1296 1652  419  420]

 

movies_df.iloc[similar_indexes]

+) 유사한 장르의 영화를 찾는 함수

def find_sim_movie_ver1(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)    
    # 2차원 데이터를 1차원으로 변환
    similar_indexes = similar_indexes.reshape(-1)
    
    return df.iloc[similar_indexes]

+) 상위 10개 영화 출력

similar_movies = find_sim_movie_ver1(movies_df, genre_sim_sorted_ind, 'The Godfather', 10)

similar_movies[['title', 'vote_average', 'genres', 'vote_count']]
# 문제 ; 평점 기반으로 추천하고자 하는데, vote_count가 낮은 영화는 제외하고 싶음

 

728x90
Comments