Ssul's Blog
[추천시스템#4]CB(콘텐츠 기반 필터링) 본문
처음 추천시스템을 공부했을땐,
CF에서 IBCF/UBCF가 헤깔렸고,
IBCF와 CB가 헤깔렸다. 이번 글에서 명확히 정리해보자.
우선 앞에서 본
IBCF는 user와 item(영화)간의 rating을 기반으로, item(영화)간의 유사도를 구해서,
영화(item)간의 유사도를 기반으로 입력된 user-item에 매칭되는 평점을 예상하는 것이다.
UBCF는 user와 item(영화)간의 rating을 기반으로, user간의 유사도를 구해서,
user간의 유사도를 기반으로 입력된 user-item에 매칭되는 평점을 예상하는 것이다.
그렇다면 CB는.... 코드를 보며 자세히 설명하겠지만,
간단하게, 콘텐츠(영화)가 가지고 있는 특성을 기반으로, 콘텐츠(영화)간의 유사도를 구해서,
콘텐츠간 유사도를 기반으로 입력된 user-item에 매칭되는 평점을 예상하는 것이다.
여기서 콘텐츠가 가지고 있는 특성은
- 줄거리 데이터
- 영화 출연진, 감독 등
다양한 것을 활용할수 있다.
1. 영화들의 줄거리 데이터를 기반으로 콘텐츠간 유사도 측정해서 추천하는 CB
import pandas as pd
# Meta data 읽기
movies = pd.read_csv('./movies.csv', encoding='latin-1', low_memory=False)
movies = movies[['id', 'title', 'overview']]
- 영화의 제목/줄거리 데이터를 가져옴
movies = movies.drop_duplicates()
# 데이터프레임에서 모든 결측값이 있는 행을 제거
movies = movies.dropna()
# movies 데이터프레임의 'overview' 열에 있는 모든 결측값을 빈 문자열로 대체
movies['overview'] = movies['overview'].fillna('')
- 데이터 중복/결측값 제거
TF-IDF 사용해서 콘텐츠간 유사도를 구하기
*TF-IDF는?
- TF: 해당 문서(줄거리)에서 자주 등장하는 단어인지 표현 > 높으면 해당 문서에서 자주 등장, 낮으면 덜 등장
- DF: 모든 영화의 줄거리에서 해당 단어가 나타난 문서수 > 높으면, 해당 단어는 모든 문서에서 자주 쓰는 단어 > IDF는 DF역수, IDF가 높다는 것은 해당 단어는 모든 줄거리에서 희소하게 등장하는 단어
- TF*IDF값이 크다 = 해당문서에서 자주 등장 & 모든 문서에서 희소하게 등장하는 단어 = 굉장히 중요한 단어(비중있게 다뤄지는, 가중치 높음)
# TfIdfVectorizer 가져오기
from sklearn.feature_extraction.text import TfidfVectorizer
# 불용어를 english로 지정하고 tf-idf 계산
# stop_words='english' 옵션은 영어의 불용어(예: the, and, is 등)를 제외
tfidf = TfidfVectorizer(stop_words='english')
tfidf_matrix = tfidf.fit_transform(movies['overview'])
- tfidf_matrix는 44,300개 영화가 행, 74686개의 모든 줄거리에서 등장한 단어가 열, 그리고 값이 TF*IDF값
- 근데 이게 spars한 값이기 때문에 아래와 같이 표현됨
1번째 영화에 등장한 단어(28783번)의 TF*IDF값 = 0.133...
44299번째 영화에 등장한 단어(27061번)의 TF*IDF값 = 0.073..
그래서 각 영화의 줄거리에 등장하는 모든 단어의 TF*IDF값을 계산
# Cosine 유사도 계산, overview기반으로 영화간 유사도 계산
from sklearn.metrics.pairwise import cosine_similarity
# 44300*44300
cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix)
cosine_sim = pd.DataFrame(cosine_sim, index=movies.index, columns=movies.index)
cosine_sim.shape
# index-title을 뒤집는다
indices = pd.Series(movies.index, index=movies['title'])
- 각 영화별 줄거리에 등장하는 단어의 TF*IDF값을 기반으로 영화들간의 유사도 구하기
(위에서 IBCF가 user-movie의 rating을 기반으로 했다면, CB는 영화의 줄거릴 단어들의 TF*IDF값으로 유사도 구함)
# 영화제목을 받아서 추천 영화를 돌려주는 함수
def content_recommender(title, n_of_recomm):
# title에서 영화 index 받아오기
idx = indices[title]
# 주어진 영화(idx) 다른 영화의 similarity를 가져온다 > 1*44300
sim_scores = cosine_sim[idx]
# similarity 기준으로 정렬하고 n_of_recomm만큼 가져오기 (자기자신은 빼기)
sim_scores = sim_scores.sort_values(ascending=False)[1:n_of_recomm+1]
# 영화 title 반환
return movies.loc[sim_scores.index]['title']
# 해당 영화와 유사한 영화 추천받기
print(content_recommender('The Lion King', 10))
- 영화제목을 입력받아서,
- 유사한 영화 상위 10개를 받아서 출력해주기
2. 영화의 감독, 출연진 등의 데이터를 기반으로, 콘텐츠(영화)간 유사도 측정 후 추천하는 CB
import pandas as pd
import numpy as np
# Meta data 읽기
movies = pd.read_csv('./movies_metadata.csv', encoding='latin-1', low_memory=False)
movies = movies[['id', 'title']]
movies = movies.dropna()
# 영화 크래딧 정보
credits = pd.read_csv('./credits.csv', encoding='latin-1', low_memory=False)
# 영화의 키워드
keywords = pd.read_csv('./keywords.csv', encoding='latin-1', low_memory=False)
- 데이터 가져오기
# 아이디가 string을 int로
def clean_ids(x):
try:
return int(x)
except:
return np.nan
# Clean the ids of df
# id값을 int형으로
movies['id'] = movies['id'].apply(clean_ids)
movies['id'].notnull()
# Filter all rows that have a null ID
# id가 결측값이 아닌 무비들만 가져오기
movies = movies[movies['id'].notnull()]
# Convert IDs into integer
# id값 int형으로 변환
movies['id'] = movies['id'].astype('int')
keywords['id'] = keywords['id'].astype('int')
credits['id'] = credits['id'].astype('int')
# Merge keywords and credits into your main metadata dataframe
movies = movies.merge(credits, on='id')
movies = movies.merge(keywords, on='id')
# movies: (46439, 5)
movies.head()
- 데이터 preprocessing
from ast import literal_eval
features = ['cast', 'crew', 'keywords']
for feature in features:
movies[feature] = movies[feature].apply(literal_eval)
# Print the first cast member of the first movie
movies.iloc[0]
movies.iloc[0]['crew']
movies.iloc[0]['crew'][0]
movies.iloc[0]['crew'][1]
- movies로 데이터 통합
- 한개의 crew안에 여러개의 참여자(dict)가 있는 구조
def get_director(x):
for crew_member in x:
if crew_member['job'] == 'Director':
return crew_member['name']
return np.nan
# Define the new director feature
movies['director'] = movies['crew'].apply(get_director)
- crew의 dict중에 job이 'Director'(감독)인 정보를 찾아서, 그 이름을 리턴
- 영화별 감독정보 추가
# Return the list top 3 elements
def generate_list(x):
# x가 list형인지 체크
if isinstance(x, list):
# names 리스트 만들고,
names = [item['name'] for item in x]
# Check if more than 3 elements exist. If yes, return only first three
# If not, return entire list
if len(names) > 3:
# 3개짜리 리스트로 만들기
names = names[:3]
return names
# Return empty list in case of missing/malformed data
return []
# Apply the generate_list function to cast and keywords
# cast, keywords 3개씩
movies['cast'] = movies['cast'].apply(generate_list)
movies['keywords'] = movies['keywords'].apply(generate_list)
- cast, keywords의 객체 중, 앞 3개의 이름만 뽑아서 cast, keywords 데이터 생성
# Removes spaces and converts to lowercase
def sanitize(x):
if isinstance(x, list):
# Strip spaces and convert to lowercase
return [str.lower(i.replace(" ","")) for i in x]
else:
# Check if an item exists. If not, return empty string
if isinstance (x, str):
return str.lower(x.replace(" ",""))
else:
return ''
# Apply the generate_list function to cast, keywords, and director
for feature in ['cast', 'director', 'keywords']:
movies[feature] = movies[feature].apply(sanitize)
- 띄어쓰기 삭제, 소문자 전환
def create_soup(x):
return ' '.join(x['keywords']) + ' ' + ' '.join(x['cast']) + ' ' + x['director']
# Create the new soup feature
movies['soup'] = movies.apply(create_soup, axis=1)
- soup로 그동안 정제한 metadata 기록 > CB에 활용예정
# Import CountVectorizer from the scikit-learn library
from sklearn.feature_extraction.text import CountVectorizer
# Define a new CountVectorizer object and create vectors for the soup
count = CountVectorizer(stop_words='english')
# 영화*해당 단어가 있는지 카운트 벡터
count_matrix = count.fit_transform(movies['soup'])
# Cosine 유사도 계산
from sklearn.metrics.pairwise import cosine_similarity
cosine_sim = cosine_similarity(count_matrix, count_matrix)
cosine_sim = pd.DataFrame(cosine_sim, index=movies.index, columns=movies.index)
- soup를 기반으로 영화관 유사도 매트릭스 구성
- CountVectorizer는 soup에 등장하는 모든 단어를 열로, 행은 한개의 영화, 데이터는 그 영화의 soup에 있는 단어는 1로 체크(TfidfVectorizer와 유사하지만, 더 간단한 구조)
# index-title을 뒤집는다
indices = pd.Series(movies.index, index=movies['title'])
# 영화제목을 받아서 추천 영화를 돌려주는 함수
def content_recommender(title, n_of_recomm):
# title에서 영화 index 받아오기
idx = indices[title]
# 주어진 영화와 다른 영화의 similarity를 가져온다
sim_scores = cosine_sim[idx]
# similarity 기준으로 정렬하고 n_of_recomm만큼 가져오기 (자기자신은 빼기)
sim_scores = sim_scores.sort_values(ascending=False)[1:n_of_recomm+1]
# 영화 title 반환
return movies.loc[sim_scores.index]['title']
# 추천받기
print(content_recommender('The Lion King', 10))
- 영화제목을 받아서, 해당 영화와 유사한 영화 상위 10개 돌려주기
3. CB정리
- CB는 콘텐츠가 가지고 있는 속성(줄거리, 감독, 출연진 등)을 일정한 구조의 데이터로 구성
- 해당 데이터를 기반으로 TfidfVectorizer, CountVectorizer 등을 사용한 통일된 특성 구성
- 해당 특성을 기반으로 유사도 매트릭스 구성
- 파악한 콘텐츠(영화)간 유사도를 기반으로, 비슷한 상위 콘텐츠 추천하는 구조
이제 메모리기반의 CF(IBCF, UBCF)와 CB를 봤으니,
모델기반의 CF를 알아보자. MF부터..
'AI & ML' 카테고리의 다른 글
[추천시스템#6] FM(Factorization Machines) (2) | 2024.01.03 |
---|---|
[추천시스템#5] MF(Matrix Factorization) (1) | 2023.12.29 |
[추천시스템#3]CF-UBCF(유저기반 협업필터링) (0) | 2023.12.21 |
[추천시스템#2]CF-IBCF(아이템기반 협업필터링) (1) | 2023.12.21 |
[추천시스템#1] 추천관련 개요 (0) | 2023.12.21 |