1 장르나 키워드 유사한 영화들 추천
import pandas as pd
movies = pd.read_csv('../data/tmdb_5000_movies.csv')
movies_df = movies[['id','title', 'genres', 'vote_average', 'vote_count',
'popularity', 'keywords', 'overview']]
pd.set_option('max_colwidth',100)
- 데이터 불러오고 => 필요한 열만 추출
pd.set_option: DataFrame의 출력 옵션 제어.
max_colwidth표시되는 텍스트 열의 최대 너비를 지정. 그 이상은 줄임표로 표시.
max_rows: 표시되는 최대 행
max_columns: 표시되는 최대 열
precision: 실수 표시할 때 소수점 이하 자릿수 지정
from ast import literal_eval
movies_df['genres'] = movies_df['genres'].apply(literal_eval)
movies_df['keywords'] = movies_df['keywords'].apply(literal_eval)
literal_eval: 문자열을 파이썬 객체로 안전하게 변환해주는 함수.
예를 들면 "[1, 2, 3, 4, 5]"가 있으면 => 리스트 [1, 2, 3, 4, 5]로 바꿔준다.
eval은 보안상 위험하지만, 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]

from sklearn.feature_extraction.text import CountVectorizer
movies_df['genres_literal'] = movies_df['genres'].apply(lambda x : (' ').join(x))
count_vect = CountVectorizer(min_df=0.0, ngram_range=(1,2))
genre_mat = count_vect.fit_transform(movies_df['genres_literal'])
- 먼저 genre에 있는 키워드들을 하나로 모두 join한다.
CountVectorizer: 텍스트 데이터를 단어의 빈도수 기반으로 벡터 형태로 변환해주는 객체. => 이렇게 벡터화해서 머신 러닝 알고리즘에 입력할 수 있게 된다.
stop_words: 불용어로 지정
tokenizer: 토큰화 수행하는 함수 지정
ngram_range: 기본값은 (1, 1), 단어의 묶음을 나타내는 n그램 범위 지정. The apple is yummy이면 (1, 1)이면 The, apple, is, yummy 로 나누고 (1, 2)면 The, apple, is, yummy, The apple, apple is, is yummy로 나눈다. 일반적으로(최소, 최대)의 형태로 지정한다. => 작은 ngram 범위는 더 많은 특성을 생성하지만, 훈련 데이터 부족할 떈 과적합 유발.
min_df: 단어의 최소 빈도 지정. 0으로 지정되면 모든 단어 고려, 2이면 적어도 2개 이상의 문서에서 나온 단어.
max_df: 단어의 최대 문서 빈도 지정. 0.5면 단어가 문서 전체 집합에서 50% 이상이면 무시.
.fit_transform(): fit과 transform을 한 번에 하는 메서드. => 위의 CountVectorizer 객체에 위에 만든 genres_literal을 넣어서 모두 벡터화한 모델을 => genre_mat으로 만든다.
from sklearn.metrics.pairwise import cosine_similarity
genre_sim = cosine_similarity(genre_mat, genre_mat)
genre_sim.argsort()
genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]
cosine_similarity(X, Y=None, dense_output): 두 개의 배열 X와 Y의 입력을 받아서, 각 배열의 벡터 간 코사인 유사도 계산. Y가 제공 안되면 X와 X의 유사도 계산. => 여기서은 genre_mat은 위에서 CountVectorizer로 장르 특성을 벡터화한 결과. 각 행은 하나의 영화, 각 열은 단어(장르)를 나타내는 희소 행렬(sparse matrix)다. 각 행렬 요소의 값은 해당 영화에 특정 단어(장르)를 나타내는 횟수다.
따라서, genre_sim은 영화 간의 장르 유사도를 나타내는 행렬이고, 이 행렬은 각 요소의 두 영화 간의 장르 유사성을 나타낸다.
genre_sim.argsort(): 코사인 유사도 행렬에 대해, 각 영화에 대해 코사인 유사도가 가장 낮은 것부터 가장 높은 것까지의 인덱스를 반환한다.
즉, genre_sim.argsort()[0][0]은, 첫 번째 영화에서, 가장 유사도가 낮은 영화의 인덱스가 나온다.
genre_sim.argsort()[:, ::-1] => 유사도가 가장 높은 순부터 정렬한다.
def find_sim_movies(df, sorted_ind, title_name, top_n = 10):
title_movie = df[df['title'] == title_name]
title_index = title_movie.index.values
similar_indexes = sorted_ind[title_index, :(top_n)]
print(similar_indexes)
similar_indexes = similar_indexes.reshape(-1)
return df.iloc[similar_indexes]
- 비슷한 영화를 출력하는 함수.
- 먼저 title_movie에 지정한 영화의 행을 꺼낸다. => title_index에 그 행의 인덱스도 꺼낸다
- similar_indexes에 sorted_ind는 기존에 genre_sim.argsort()[:, ::-1]로 해놓은 것을 넣을 예정. 거기서 원하는 영화와, top 몇인지 => top 몇으로 가까운 영화들 인덱스가 나온다.
- 2차원 배열을 => 1차원 배열로 바꿔서 => DF에서 그 행들만 추출
1-1 전체 흐름 정리
- 영화들의 장르와 키워드만 추출해서 하나의 행으로(리스트로 꺼낸 후, join)
- CountVectorizer 객체 생성 후 거기에 fit하고 transform => 텍스트 데이터를 단어의 빈도수 기반으로 벡터 형태로 변환
- cosine_similarity를 이용해, 각 벡터의 유사도 배열을 만들고 => argsort()[:, ::-1]로 가장 가까운 것들의 인덱스를 추출
- 그 인덱스를 이용해서 가장 가까운 영화 추출하는 함수 만들기
2 영화 평가한 사람 적을수록 높은 점수 몰리는 문제 해결
movies_df.sort_values('vote_average',ascending=False)
- 평점 높은 영화들 나열하면, 위에는 다 평점 매긴 사람이 1명 처럼 엄청 적다.
C = movies_df['vote_average'].mean()
m = movies_df['vote_count'].quantile(0.6)
print('C:', round(C, 3), 'm:', round(m,3))
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]
- 위는, 평균 평점과, 상위 40%가 몇 번 평점 받았는지.
시리즈.quantile: 0.6이면 하위 60%에서 40%.
- 가중평정식에서 v는 평가수, R은 평균 평점, m은 평가수 상위 40%가 몇 개인지(여기선 370개), C는 평균 평점.
그래서 평가수가 많으면 점수를 높게 주고 적으면 낮게 주고, 평점이 높으면 더 높게 주는 식으로 가중 평점.
- 그 가중치를 열로 만든다.