[핸즈온 추천시스템] Content Based Filtering (CBF)란?

Content Based Filtering이 무엇인지 알아보고, python을 활용해 추천시스템을 만들어 보겠습니다.

chrisjune
9 min readMay 31, 2023

컨텐츠 기반 추천은, 사용자가 특정 아이템을 선호하는 경우 그 아이템과 비슷한 콘텐츠를 가진 다른 아이템을 추천하는 방식입니다.

Image: towarddatascience

예를 들어 여성 유저에게 여성 베스트 상품을 보여주거나, 최근에 본 상품의 카테고리가 전자기기일 경우 이와 유사한 전자기기 상품을 추천하는 것이 이에 포함됩니다.

메타데이터(고객의 성별, 나이, 위치, 상품의 생산일, 색상, 가격, 카테고리 등등의 정보)를 활용하면 새로운 상품이 추가되어 학습시킬 데이터가 없는 Cold start 문제를 해결할 수 있습니다.

많은 경우 단독으로 쓰이기 보단 CF(Collaborative Filtering)방식과 앙상블하여 하이브리드로 사용합니다.

고객으로부터 입력을 받아 초기 고객데이터를 받아 추천을 하는 지식기반추천(Knowledge based recommendation)과 유사하며 큰 틀에서는 같은 추천 방식으로 봅니다.

본 포스팅에서는 Movie data를 활용하여 영화의 장르와 설명키워드로 추천모델을 만들고, 영화명으로 영화리스트를 추천받아보겠습니다.

사전작업

# 라이브러리설치
!pip install kaggle pandas numpy matplotlib scikit-learn

# 데이터셋 다운로드 및 압축해제
# 방법1. 직접 캐글데이터셋을 받아 압축을 푸셔도 됩니다!
# https://www.kaggle.com/rounakbanik/the-movies-dataset

# 방법2.
# https://www.geeksforgeeks.org/how-to-download-kaggle-datasets-into-jupyter-notebook/
# 링크로 이동하여 1번, 2번 스탭 가이드대로 현재위치에 kaggle.json 파일을 다운로드

# kaggle 폴더 생성
!mkdir ~/.kaggle
!cp ./kaggle.json ~/.kaggle
!chmod 600 ~/.kaggle/kaggle.json

# 데이터셋 다운로드
!kaggle datasets download -d rounakbanik/the-movies-dataset

# 데이터셋 압축해제
!mkdir dataset
!mv ./the-movies-dataset.zip ./dataset
!unzip -d dataset dataset/the-movies-dataset.zip

데이터 전처리 — 1

# 라이브러리 임포트
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from ast import literal_eval
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

# 데이터셋 로드
movie_data = pd.read_csv('dataset/movies_metadata.csv')
movie_data = movie_data[movie_data['original_language'] == 'en']
movie_data = movie_data[['id','title','original_language', 'genres']]
movie_data.shape

# 데이터 확인
movie_data.head()

# 영화 설명 키워드 로드하여 movie_data에 머지
keyword_data = pd.read_csv('dataset/keywords.csv')
movie_data['id'] = movie_data.id.astype(np.int64)
movie_data = pd.merge(movie_data, keyword_data, on='id')
movie_data.shape

# 데이터 확인
movie_data.head()

데이터 전처리 — 2

genre와 keyword는 List[Dict]로 보이지만 string 타입이기 때문에 object로 변환하고, 이를 공백으로 구분한 이름 리스트로 변환하도록 하겠습니다
ex) [{id: 1, name: Romance}, {id: 2, name: Comedy}] ⇒ Romance Comedy

# string 타입을 object로 변환
movie_data['genres'] = movie_data['genres'].apply(literal_eval)
movie_data['keywords'] = movie_data['keywords'].apply(literal_eval)

# List[Dict] -> 문자열로 변경
movie_data['genres'] = movie_data['genres'].apply(lambda x: [d['name'] for d in x]).apply(lambda x: " ".join(x))
movie_data['keywords'] = movie_data['keywords'].apply(lambda x: [d['name'] for d in x]).apply(lambda x: " ".join(x))

# 장르와 키워드가 빈 결측데이터 제거
movie_data = movie_data.drop(movie_data[(movie_data['genres']=='') | (movie_data['keywords'] =='')].index)
movie_data['genres_keyword'] = movie_data['genres'] + " " + movie_data['keywords']

데이터 학습

TF-IDF를 활용하여 영화의 장르와 키워드 텍스트를 feature matrix로 임베딩하고, 영화간 Cosine 유사도를 구합니다.

TF-IDF에 관한 내용은 Link 글에서 좀더 자세히 확인 가능합니다

TfidfVectorizer에서는 기본적으로 도큐먼트의 토크나이징과 Stop word를 활용하여 의미가 적은 단어를 feature에서 제거합니다. (Sklearn stop word list)

# sklearn에서 제공하는 tfidf 클래스 로드
tfidf_vector = TfidfVectorizer()

# tfidf로 임베딩
tfidf_matrix = tfidf_vector.fit_transform(movie_data['genres_keyword']).toarray()

# 장르와 키워드를 합친 텍스트이외 장르, 키워드만 활용가능
# tfidf_matrix = tfidf_vector.fit_transform(movie_data['genres']).toarray()
# tfidf_matrix = tfidf_vector.fit_transform(movie_data['keyword']).toarray()

# 임베딩된 도큐먼트(단어)들을 리스트로 할당
# ex) tfidf_matrix_feature = ["comedy", "drama", "game", "best", "old"...]
tfidf_matrix_feature = tfidf_vector.get_feature_names_out()

# 임베딩을 데이터프레임으로 변경
tfidf_matrix = pd.DataFrame(tfidf_matrix, columns=tfidf_matrix_feature, index=movie_data.title)

유사도계산 및 추천

벡터간의 유사한지 판단하기 위하여 유사도를 이용합니다. 유클리디언거리, 자카드, 피어슨, 코사인 유사도등등 여러가지가 있고, 그 중 가장 많이 사용되는 cosine 유사도를 사용하겠습니다.

# cosine 유사도 계산
cossim = cosine_similarity(tfidf_matrix)
cossim_df = pd.DataFrame(cossim, index=movie_data.title, columns=movie_data.title)

# 유사도 높은 영화 반환
def top_k(target_title, matrix, items, k=10):
rec_idx = matrix.loc[:,target_title].values.reshape(1,-1).argsort()[:,::-1].flatten()[1:k+1]
rec_title = items.iloc[rec_idx, :].title.values
rec_genre = items.iloc[rec_idx, :].genres.values
return pd.DataFrame({'title': rec_title, 'genre': rec_genre})

# 죽은 시인의 사회 영화와 관련된 영화 추천
top_k('Dead Poets Society', cossim_df, movie_data)

본 포스팅에서는 영화의 메타데이터, TF-IDF 와 cosine 유사도를 활용하여 간단한 추천 모델을 만들어 보았습니다.

참고링크

조금이라도 도움이 되셨으면 좋겠습니다. 감사합니다.

--

--