카카오 AI추천 : 카카오의 콘텐츠 기반 필터링 (Content-based Filtering in Kakao) – tech.kakao.com
카카오 AI추천 : 카카오의 콘텐츠 기반 필터링 (Content-based Filtering in Kakao)
카카오 서비스 사용자들의 아이템(콘텐츠 또는 상품) 소비 패턴을 살펴보면, 기존에 소비한 아이템과 유사한 아이템을 소비하는 경우를 쉽게 찾아볼 수 있습니다. 예를 들면, 브런치의 특정 작
tech.kakao.com
Content Based Filtering
사용자가 특정 아이템을 선호하는 경우 그 아이템과 비슷한 콘텐츠를 가진 다른 아이템을 추천해주는 방식
사례 : 최근 본 상품과 비슷한 상품 추천, 읽은 자품과 비슷한 작품 추천 등등
아이템 특성화
아이템 특성을 벡터 형태로 어떻게 표현할지가 중요한 포인트
예 : One-Hot Encoding, Embedding
- Text 데이터 임베딩 (책 제목)
- TF-IDF
- 다른 문서에는 많지 않고, 해당 문서에서 자주 등장하는 단어에 높은 값을 부여 문서를 대표하는 특정 단어를 알아낼 수 있음
- tf(d,t) : 특정 문서 d에서의 특정 단어 t의 등장 횟수
- df(t) : 특정 단어 t가 등장하는 문서의 수
- idf(d,t) : log(n/(1+df(t)))
- 다른 문서에는 많지 않고, 해당 문서에서 자주 등장하는 단어에 높은 값을 부여 문서를 대표하는 특정 단어를 알아낼 수 있음
- Word2Vec
- 단어의 의미를 다차원 공간에 벡터화하는 방법을 사용
- 단어 간 의미적 유사성을 벡터화 하는 작업을 통해 임베딩 수행
- TF-IDF
유사도 측정
Similarity : 특성화된 아이템이 서로 얼마나 비슷한지
콘텐츠 기반 추천의 장점
- 다른 유저들의 데이터가 필요X -> cold start, sparsity 문제가 없다
- 독특한 취향을 가진 유저에게도 추천 가능
- 새로운 아이템이나 유명하지 않은 아이템도 추천 가능
콘텐츠 기반 추천의 한계점
- 아이템에 적합한 피처를 뽑기 어려움
- 유사한 아이템만 반복적으로 추천되어 다양성이 떨어짐
- 다른 유저의 데이터나 사용자의 평가를 반영하기 어려움
실습
캐글 데이터 사용
https://www.kaggle.com/datasets/ruchi798/bookcrossing-dataset
Book-Crossing: User review ratings
A collection of book ratings
www.kaggle.com
데이터
import pandas as pd
import numpy as np
from typing import List, Set,Optional
books = pd.read_csv(path+'books.csv') # (232348,6)
# Index(['isbn', 'book_title', 'book_author', 'publisher', 'language', 'category'], dtype='object')
users = pd.read_csv(path+'users.csv') # (79516,3)
# Index(['user_id', 'location', 'age'], dtype='object')
ratings = pd.read_csv(path+'ratings.csv') # (56290,3)
# Index(['user_id', 'isbn', 'rating'], dtype='object')
# books, users, ratings 합치기
df = ratings.merge(books, on='isbn')
df = df.merge(users, on='user_id', how='inner') # (56290, 10)
총 916개의 책이 존재, 책 리스트 따로 만들기
df['book_title'].nunique() # 916
book_title_list = df['book_title'].unique()
자카드 유사도(교집합/합집합)를 이용한 Content Based Filtering
# 예시
a = set('I like banana'.split()) # {'I', 'banana', 'like'}
b = set('I like apple'.split()) # {'I', 'apple', 'like'}
c = a.intersection(b) # {'I', 'like'}
jaccard = len(c) / (len(a) + len(b) - len(c)) # 0.5
책
def content_based_filtering_jaccard(book_title_list: list, title: str, topn: Optional[int]=None) -> pd.DataFrame:
topn=11 if topn is None else topn+1
# 단어로 나누기
target_split_set = set(title.split())
# 책 제목 df
sim_df = pd.DataFrame(book_title_list, columns=['book_title'])
# 전체 책 제목들과 자카드 유사도 구하기
sim_list = []
for idx, book in enumerate(book_title_list):
title_split_set = set(book.split())
title_intersection = target_split_set.intersection(title_split_set)
jac_sim = float(len(title_intersection)) / (len(target_split_set) + len(title_split_set) - len(title_intersection))
sim_list.append(jac_sim)
sim_df['jaccard_similarity'] = sim_list
# 자카드 유사도가 높은 순으로 topn개
return sim_df.sort_values('jaccard_similarity', ascending=False).reset_index(drop=True)[1:topn]
# 해리 포터와 아즈카반의 죄수와 유사한 책 5권 -> 해리포터 관련 책
content_based_filtering_jaccard(book_title_list,'Harry Potter and the Chamber of Secrets (Book 2)', 5)
TF-IDF 를 이용한 텍스트 데이터 Vectorization
from sklearn.feature_extraction.text import TfidfVectorizer
# 예시
title_example = ["The Lion, the Witch, and the Wardrobe",
"King Solomon's Mines"]
tfidf = TfidfVectorizer()
print(tfidf.fit_transform(title_example).toarray()) # 각 TF-idf 를 계산
print(tfidf.vocabulary_) # 각 단어의 인덱스가 어떻게 부여되었는지 보여준다
유클리드 유사도(거리)를 이용한 Content Based Filtering
tfidf = TfidfVectorizer()
wordmatrix = tfidf.fit_transform(book_title_list).toarray() #책 제목 리스트를 TF-IDF를 활용해 벡터라이징 합니다.
from sklearn.metrics.pairwise import euclidean_distances # 유클리드 유사도를 바로 계산
# wordmatrix는 책 제목을 vectorization 한 array 입니다.
def content_based_filtering_euclidean(book_title_list: list, wordmatrix: np.array, title: str, topn: Optional[int]=None) -> pd.DataFrame:
topn=11 if topn is None else topn+1
# 유클리드 유사도
sim_matrix = pd.DataFrame(euclidean_distances(wordmatrix), index=book_title_list, columns=book_title_list)
target_similarity_df = sim_matrix[title].reset_index().copy()
target_similarity_df.columns=['title', 'euclidean_similarity']
# 유클리드 유사도 상위 topn개 반환
return target_similarity_df.sort_values('euclidean_similarity', ascending=True).reset_index(drop=True)[1:topn]
# 해리 포터와 아즈카반의 죄수와 유사한 책 5권 -> 해리포터 관련 책
content_based_filtering_euclidean(book_title_list, wordmatrix, 'Harry Potter and the Chamber of Secrets (Book 2)',5)
코사인 유사도를 이용한 Content Based Filtering
from sklearn.metrics.pairwise import cosine_similarity
#코사인 유사도 계산를 content_based_filtering 결과
def content_based_filtering_cosin(book_title_list: list, wordmatrix: np.array, title: str, topn: Optional[int]=None) -> pd.DataFrame:
topn=11 if topn is None else topn+1
# 코사인 유사도 계산
sim_matrix = pd.DataFrame(cosine_similarity(wordmatrix), index=book_title_list, columns=book_title_list)
target_similarity_df = sim_matrix[title].reset_index().copy()
target_similarity_df.columns=['title', 'cosine_similarity']
# 코사인 유사도 상위 topn개 반환
return target_similarity_df.sort_values('cosine_similarity', ascending=False).reset_index(drop=True)[1:topn]
# 해리 포터와 아즈카반의 죄수와 유사한 책 5권 -> 해리포터 관련 책
content_based_filtering_cosin(book_title_list, wordmatrix, 'Harry Potter and the Chamber of Secrets (Book 2)', 5)
피어슨 상관 계수를 이용한 Content Based Filtering
def content_based_filtering_pearson(book_title_list: list, wordmatrix: np.array, title: str, topn: Optional[int]=None) -> pd.DataFrame:
topn=11 if topn is None else topn+1
# corr메서드는 각 열 간의 상관 계수를 반환하는 메서드
# 각 행으로 바꾸고 싶어서 transpose
sim_matrix = pd.DataFrame(wordmatrix, index=book_title_list).T.corr(method='pearson')
target_similarity_df = sim_matrix[title].reset_index().copy()
target_similarity_df.columns=['title', 'pearson_similarity']
# 피어슨 상관계수 상위 topn개 반환
return target_similarity_df.sort_values('pearson_similarity', ascending=False).reset_index(drop=True)[1:topn]
# 해리 포터와 아즈카반의 죄수와 유사한 책 5권 -> 해리포터 관련 책
content_based_filtering_pearson(book_title_list, wordmatrix, 'Harry Potter and the Chamber of Secrets (Book 2)', 5)
Content Based Filtering - Train / Test Split - T
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
# 훈련,테스트 데이터 나누기
train_df, test_df = train_test_split(df,test_size=0.1,random_state=42)
# 책 제목 리스트를 TF-IDF를 활용해 벡터라이징
book_title_train_list = train_df['book_title'].unique()
tfidf = TfidfVectorizer()
wordmatrix = tfidf.fit_transform(book_title_train_list).toarray()
# 각 책 벡터간 유사도 계산
sim_matrix = pd.DataFrame(cosine_similarity(wordmatrix), index=book_title_train_list, columns=book_title_train_list)
F-IDF, Cosine Similarity
def content_based_filtering(train_df: pd.DataFrame,
test_df: pd.DataFrame) -> list:
# 각 유저가 책마다 매긴 점수 테이블
user_item_matrix = train_df.pivot_table(index=['user_id'], columns=['book_title'], values='rating').fillna(0)
# 테스트 데이터 예측 결과
pred_rating_list = []
# test_id유저가 test_title책에 매길 평점 예측
for test_id, test_title in zip(test_df['user_id'], test_df['book_title']):
similarity_list = []
rating_list = []
# test_id유저가 매긴 책
for read_book in train_df[train_df['user_id']==test_id]['book_title']:
# test_title책과 다른 책과의 유사도
similarity_list.append(sim_matrix[read_book][test_title])
# test_id가 매긴 다른 책들의 평점
rating_list.append(user_item_matrix[read_book][test_id])
similarity_list = np.array(similarity_list)
rating_list = np.array(rating_list)
# 평점 예측
pred = (similarity_list * rating_list).sum() / (similarity_list.sum() + 1e-10) #분모가 0이되는 것을 방지하기 위해 작은 수를 더합니다.
pred_rating_list.append(pred)
return pred_rating_list
pred_rating = content_based_filtering(train_df, test_df)
RMSE
표준편차와 동일하다. 특정 수치에 대한 예측의 정확도를 표현할 때, Accuracy로 판단하기에는 정확도를 올바르게 표기할 수 없어, RMSE 수치로 정확도 판단을 하곤 한다. 일반적으로 해당 수치가 낮을수록 정확도가 높다고 판단한다.
from sklearn.metrics import mean_squared_error
mean_squared_error(test_df['rating'],pred_rating)**0.5 # 3.31792070726792
'TIL - 외 > 추천시스템' 카테고리의 다른 글
[추천시스템] Model Based Collaborative Filtering - Unsupervised (0) | 2023.04.11 |
---|---|
[추천시스템] Memory Based Collaborative Filtering (0) | 2023.04.11 |
[추천시스템] 추천 시스템 개론 (2) | 2023.04.11 |
댓글