DataScience/Recommendation System

CB ( Content-Based filtering ) - 내가 재밌게 본 영화와 비슷한 영화 찾기

mkk4726 2023. 7. 2. 11:38

https://github.com/mkk4726/CB-movie

 

GitHub - mkk4726/CB-movie: Content-based filtering about movie

Content-based filtering about movie . Contribute to mkk4726/CB-movie development by creating an account on GitHub.

github.com

해당 깃헙에서 관련 코드들을 찾아볼 수 있습니다.


내용 기반 필터링 ( CB )는, 비슷한 아이템을 추천한다는 기본적인 아이디어를 가지고 추천을 합니다.

주로 텍스트 정보가 많은 제품 ( ex: 뉴스 , 책 ) 등을 분석하여 추천할 때 많이 이용되는 기술입니다.

 

이를 통해 유저가 좋게 평가한 제품과 비슷한 제품들을 추천해줄 수 있습니다.

 

이를 위한 과정을 다음과 같습니다.

1. 각 아이템 간의 유사도 계산

2. 추천 대상인 사용자가 선호하는 아이템 선정

3. 2번에서 선정한 아이템과 유사도가 높은 N개의 아이템 선정

4. 이를 사용자에게 추천

 

이를 통해 저는 제가 재밌게 본 영화와 비슷한 영화를 찾아보겠습니다.

 

 


1. 유사도 계산

정리: 유사도는 coinse similarity를 사용했고 TF-IDF를 이용해 벡터화 시켰습니다.


1.1 유사도란?

더보기

유사도란 두 개의 벡터가 얼마나 비슷한지를 의미합니다.

유사도를 계산하는데는 여러 지표가 사용될 수 있습니다. 

그 중 코사인 유사도(cosine similarity)를 사용하려 합니다. 

 

그림1. cosine distance image
index column1 column2 column3 column4
1 value1_1 value1_2 value1_3 value1_4
2 value2_1 value2_2 value2_3 value2_4

첫 번째 인덱스의 값을 item1, 두 번째 인덱스의 값을 item2라고 하면 

두 개의 row들은 하나의 벡터로 생각할 수 있고 좌표평면 상에 그릴 수 있습니다.

즉 그림1처럼 두 개의 벡터의 모습을 띄고, 이 두 개의 벡터 사이의 각을 이용해 유사도를 정의하는 것입니다.

 

$ \theta $가 0이 될 수록 두 벡터는 비슷하고, 180이 될수록 두 벡터는 서로 다르다는 것을 직관적으로 유추해볼 수 있습니다. 

$cos(\theta)$ 의 값은 -1에서 1의 값을 가지게 되고 , $\theta$가 0일 때 1, 180일 때 -1의 값을 가집니다.

이를 통해 값이 클수록 비슷하고 작을수록 다르다라고 생각할 수 있습니다.

 

이를 수식으로 표현하면 다음과 같습니다.

$cosinse \ similarity = S_c(A, B) := cos(\theta) = \frac{AB}{||A||||B||}=\frac{\sum_{i=1}^nA_iB_i}{\sqrt{\sum_{i=1}^nA_i^2}\sqrt{\sum_{i=1}^nB_i^2}}$

 


1.2 벡터를 어떻게 정의할 것인가 , TF-IDF

더보기

유사도를 어떻게 계산할지 정했으면 벡터를 어떻게 정의할 것인지 정해야합니다.

즉, column을 어떻게 정할 것인지를 정해야합니다.

 

저는 tf-idf를 이용해 영화에 대한 리뷰 또는 개요와 같은 글들을 벡터화시킬 것입니다.

 

여기에는 한 가지 가정이 존재합니다.

비슷한 단어 분포를 가진 것들은 비슷하다.

즉 '요리'라는 단어가 많이 쓰인 글은 요리와 관련된 글일 것이고, '총'이 많이 쓰인 글은 전쟁과 관련된 글일 것이라고 가정하는 것입니다.

 

이 컨셉을 통해 글을 가장 단순하게 벡터화 시킬 수 있는 방법은, row별로 단어가 사용된 횟수만큼을 column에 표시하는 것입니다.

 

index 투자 수익률 건강 회복 그리고 끝나다 좋다
1 10 15 0 8 20 8 7
2 11 0 12 14 21 5 8

예를 들면, 글에 사용된 단어의 횟수를 이용해 위와 같은 두 개의 벡터를 생성할 수 있습니다.

직관적으로 1번은 투자와 관련된 글임을, 2번은 건강과 관련된 글임을 유추해볼 수 있습니다. 

 

여기에는 한 가지 문제가 있습니다. '그리고'와 같이 공통적으로 사용되는 단어들이  존재해, 두 개의 벡터가 비슷하다고 판단하도록 만든다는 것입니다.

이를 보완하기 위해 TF-IDF라는 개념이 생겨났습니다.

 

TF-IDF(Term Frequency - Inverse Documnet Frequency)는 이름에서 그 의미를 쉽게 유추해볼 수 있습니다.

글 전체에 많이 나오는 단어에는 패널티를 부여하겠다는 것입니다.

그림2. TF-IDF 의 개념

이를 식으로 표현하면 다음과 같습니다.

$TF(t,d) = \frac{document \ d에서\ term \ t의 빈도}{document \ d에서\ 전체 term \ 의 개수}$

$IDF(t,d) = log\frac{전체 \ document의 \ 개수}{term \ t를 가지고 있는 document \ 의 \ 개수}$

$TF-IDF(t, d, D) = TF(t, d) \times IDF(t, D)$

 

이번 글에서는 이를 이용해 영화에 관련한 글들을 벡터화 시켜 사용하겠습니다.


2. 사용한 데이터

IMDb에서 review data를 직접 긁어와 사용했습니다.

2023.07.02 - [DataScience/Crawling & Scraping] - Scraping IMDb review data

 

Scraping IMDb review data

https://github.com/mkk4726/CB-movie GitHub - mkk4726/CB-movie: Content-based filtering about movie Content-based filtering about movie . Contribute to mkk4726/CB-movie development by creating an account on GitHub. github.com 이번 글의 코드를 crawlin

mkk4726.tistory.com

위 글을 참고 하시면 됩니다.

수집한 결과는 다음과 같습니다.

사용할 데이터


3. 벡터화 시키기

더보기

관련코드는 algorithms의 vectorizer.ipynb 파일에서 찾아볼 수 있습니다.


이제 이걸 벡터화 시키겠습니다.

위에서 언급한 것처럼 TF-IDF를 이용했습니다.

from sklearn.feature_extraction.text import TfidfVectorizer
from nltk import word_tokenize
import re

# 정규식 이용해서 영어만 남기자
def tokenizer(text):
  # Tokenize the text using NLTK's word_tokenizer
  words = word_tokenize(text)
  
  # Pattern to match non-English words
  pattern = re.compile(r'\b[^\W\d_]+\b', re.IGNORECASE)
  
  # Filter out non-English words
  english_words = [word for word in words if re.match(pattern, word)]
  
  return english_words

tfidf_vectorizer_kwargs = {
  'ngram_range': (1, 3), # 단어 연결해서 생성
  'stop_words': 'english', # 불용어 제거
  'lowercase': 'True', # 다 소문자로 바꿈 
  'tokenizer': tokenizer,
  # 너무 많이 나오는 단어, 코퍼스 특이적 불용어(corpus-specific stopwords) 제거
  'max_df': 0.95, # 전체 문서 중 95%이상 나온 단어를 무시 ( 많이 나오는 단어 제거 )
  'min_df': 0.01 # 전체 문서 중 1%이하로 나온 단어는 무시 ( 희소하게 나오는 단어 제거 )
}

tfidf_vectorizer = TfidfVectorizer(**tfidf_vectorizer_kwargs)

tfidf_matrix = tfidf_vectorizer.fit_transform(reviews['review'])
feature_names = tfidf_vectorizer.get_feature_names()

# Create a dataframe from the TF-IDF matrix
vectorized_df = pd.DataFrame(tfidf_matrix.toarray(), columns=feature_names, index=reviews.index)

vectorized_df.head()

 정규식을 이용해 영단어만을 남겼고, 너무 많이 나오는 단어나 너무 적게 나오는 단어는 제거했습니다.

 
 

4. 유사도 계산하기 & CB 적용

더보기

관련코드는 algorithms의 basic_CB.ipynb 파일에서 찾아볼 수 있습니다.


 먼저 벡터화 시킨 벡터는 희소행렬이기에 메모리 문제를 예방하기 위해 scipy.sparse의 csr_matrix 함수를 이용해 csr matrix로 바꿔줬습니다.

from scipy.sparse import csr_matrix
from sklearn.metrics.pairwise import cosine_similarity

# 데이터가 워낙 많고 희소행렬이기 때문에 csr로 바꿔서 유사도를 계산
csr_vec = csr_matrix(vectorized_df.values)

cosine_sim = cosine_similarity(csr_vec)
cosine_sim_df = pd.DataFrame(cosine_sim, index=vectorized_df.index, columns=vectorized_df.index)
cosine_sim_df

 결과는 다음과 같습니다.

유사도 matrix

 이제 이를 이용해 CB를 구현하면 됩니다

CB(Content-Based filtering)은 비슷한 아이템을 추천해주는 것을 뜻합니다.

유사도를 구했으니 비슷한 애들을 필터링해서 사용자에게 알려주기만 하면 끝납니다.

함수는 다음과 같습니다.

더보기
def get_CB_recomm(df1:"pd.DataFrame", df2:"pd.DataFrame", title_id:str):
  """CB 기반 추천 알고리즘

  Args:
      df1 (pd.DataFrame): title_basic_df
      df2 (pd.DataFrame): cosine_df
      title_id (str): title id
  """
  print(f"about : {m.find_title(df1, title_id)}")

  candidate_series = df2[title_id].sort_values(ascending=False)[1:10]
  
  for title_id, sim in zip(candidate_series.index, candidate_series):
    title = m.find_title(df1, title_id)
    url = f'https://www.imdb.com/title/{title_id}/?ref_=fn_al_tt_1'
    print(f"title: {title} / sim: {sim:.4f} / url: {url}")
df1은 title_id를 title로 바꿔주기 위해 필요한 것이며

df2은 앞서 구한 유사도 matrix입니다.

결과를 살펴보면,

CB 결과

제가 정말 좋아하는 D.P을 입력했을 때 추천받은 것은 위의 사진과 같습니다.

대부분의 작품들이 청춘물에 속하고, 남자 2명이 주인공인 영화들이 존재해 나름 잘 골라졌다고 판단할 수 있을 것 같습니다.

 

 


가장 아쉬운 점은 결과를 주관적으로 판단할 수 밖에 없다는 점이다.

 

MF나 FM으로 가면 평점을 예측하는 회귀 문제로 접근할 수 있고 RMSE를 통해 결과를 확인해볼 수 있겠지만,

결국 어떤 것을 추천했을 때 이를 어떻게 평가할 것인지에 대한 의문은 계속 남아있을 것 같다.

 

이에 대해 관련 블로그나 논문들을 찾아보며 정리해봐야겠다. 

 


출처 : 

그림1 : https://www.oreilly.com/library/view/statistics-for-machine/9781788295758/eb9cd609-e44a-40a2-9c3a-f16fc4f5289a.xhtml

그림2: https://betterprogramming.pub/a-friendly-guide-to-nlp-tf-idf-with-python-example-5fcb26286a33

 

 

 

 

MovieLens 100K