"아이템 기반 협업 필터링" 사용 이유
1. Sparse Data 문제 해결
=> 사용자 간 유사성을 계산하기 위해서는 많은 사용자들이 공통적으로 평가한 아이템이 있어야함
만약 대부분의 사용자들이 특정 아이템을 평가하지 않은 경우, 이 아이템에 대한 추천 불가
2. 계산 효율성
=> 사용자 간 유사성을 계산해야하기 때문에 사용자가 많아질수록 계산 비용이 높아지는데
아이템 기반 협업 필터링은 아이템 수가 많아져도 계산 비용이 비교적 낮음
3. 일반성
=> 사용자 기반 CF은 특정 사용자에게만 맞춤 추천을 제공할 가능성이 높음
근데 아이템 기반 CF는 아이템 간 유사성을 계산하여 추천을 제공하기 때문에 다양한 아이템 추천 가능
사용자 기반 협업 필터링은 나와 성향이 비슷한 사람들이 사용한 아이템을 추천해주는 방식
A : 1,2,3
B : 1 2
결과적으로 B에게 3을 추천
아이템 기반 협업 필터링
1 item : 5,4,3,3,' ',5(각 유저가 내린 평점)
2 item : 4,4,3,3,5,' '(각 유저가 내린 평점)
1,2 item은 서로 비슷하다고 판단하여 user4에게 1 item, user5에게 2 item 추천
'''
rating.csv == review table(user_id, theme_id, user_rating)
userId movieId rating
1 31 2.5
1 1029 3
1 1061 3
==>
user_id theme_id user_rating
1 31 2.5
1 1029 3
1 1061 3
movies.csv == theme table(theme_id, title) // 장르테마는 필요없음, 장르 기준으로 CF 돌리는게 아니니까
movieId title genres
1 Toy Story (1995) Adventure|Animation|Children|Comedy|Fantasy
2 Jumanji (1995) Adventure|Children|Fantasy
=>
theme_id, title
1 마션
2 헨젤과 그레텔
'''
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import pymysql
conn = pymysql.connect(host='localhost', user='root', password='1234', port=3307,
db='bbkk', charset='utf8')
# STEP 3: Connection 으로부터 Cursor 생성
cur = conn.cursor()
rating_data = pd.read_sql_query (''' SELECT * FROM review ''', conn)
theme_data = pd.read_sql_query (''' SELECT * FROM theme ''', conn)
#rating_data = pd.read_csv('./ratings.csv')
#theme_data = pd.read_csv('./movies.csv')
#print(rating_data.head(2))
#print(movie_data.head(2))
# 불필요한 컬럼은 drop, axis=1은 열 방향으로 동작
# inplace=True 명령어를 실행 한 후 메소드가 적용된 데이터 프레임으로 반환
#rating_data.drop('timestamp', axis = 1, inplace=True)
#theme_data.drop('genres', axis = 1, inplace=True)
'''
userId movieId rating title genres
0 1 31 2.5 Dangerous Minds (1995) Drama
1 7 31 3.0 Dangerous Minds (1995) Drama
'''
user_movie_rating = pd.merge(rating_data, theme_data, on='theme_id')
#print(user_movie_rating.head(2))
'''
아이템 기반 협업 필터링을 수행하려면 pivot table을 만들어야함
pivot_table
data(영화 평점 rating), index(영화 title), columns(userId)
'''
# 아이템 기반 협업 필터링
theme_user_rating = user_movie_rating.pivot_table('user_rating', index='title', columns='user_id')
#print(movie_user_rating.head(5))
# 사용자 기반 협업 필터링
user_movie_rating = user_movie_rating.pivot_table('user_rating', index='user_id', columns='title')
#print(user_movie_rating.head(5))
# NaN의 값을 가진 데이터에 fillna(0, inplace=True)을 통해 null 값을 채워준다
theme_user_rating.fillna(0, inplace=True)
user_movie_rating.fillna(0, inplace=True)
#print(theme_user_rating)
'''
아이템 기반 협업 필터링 추천 시스템은 유사한 아이템끼리 추천을 해준다
여기선 유사한 아이템이 평점이 비슷한 아이템이 된다
따라서, 현재 평점이 data로 들어가 있는 상태이니까, 이 상태에서 코사인 유사도(cosine similarity)를 구하면 됨
'''
from sklearn.metrics.pairwise import cosine_similarity
item_based_collabor = cosine_similarity(theme_user_rating)
#print(theme_user_rating)
#print(item_based_collabor)
item_based_collabor = pd.DataFrame(data = item_based_collabor, index=user_movie_rating.columns, columns=user_movie_rating.columns)
#print(theme_user_rating.columns)
print(item_based_collabor)
'''
자신과 일치하는 값은 유사도 1이 나오게 되니까
어떤 영화를 시청했을 때(사용자가 어떤 영화를 보았을 때) 그 영화가 마음에 들면 그것과 비슷한 영화를 추천할 수 있겠지?
'''
def get_item_based_collabor(title):
return item_based_collabor[title].sort_values(ascending=False)[:]
# 이건 단순히 테마간 유사도만으로 추천을 진행
#print(get_item_based_collabor('1번 테마'))
# 개인의 평점이 반영된 추천 시스템을 구현해야됨
# ratings_arr.dot(item_sim_arr)는 평점 * 테마 유사도
# ratings_arr는 사용자 u의 아이템 i와 가장 유사도가 높은 Top_N개 아이템에 대한 실제 평점 벡터
# item_sim_arr는 아이템 i와 가장 유사도가 높은 Top_N개 아이템의 유사도 벡터
import numpy as np
def predict_rating(ratings_arr, item_sim_arr):
ratings_pred = ratings_arr.dot(item_sim_arr) / np.array([np.abs(item_sim_arr).sum(axis=1)])
return ratings_pred
# 개인화된 예측 평점 구하기
# 평점 value와 유사도 value만 뽑아서 대입
ratings_pred = predict_rating(user_movie_rating.values, item_based_collabor.values)
ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index=user_movie_rating.index,
columns = user_movie_rating.columns)
# 개인별로 계산된 예측 평점
print(ratings_pred_matrix)
# 우리가 예측한 평점과 실제 평점간의 차이를 MSE로 계산
from sklearn.metrics import mean_squared_error
def get_mse(pred, actual):
# 평점이 있는 실제 영화만 추출
pred = pred[actual.nonzero()].flatten()
actual = actual[actual.nonzero()].flatten()
return mean_squared_error(pred, actual)
print('===============================================')
print(ratings_pred)
print('아이템 기반 모든 최근접 이웃(KNN) MSE: ', get_mse(ratings_pred, user_movie_rating.values))
# 3개의 col까지만. 3개의 맥주에 대해서 유사도가 큰 5개 선택
top_n_items = [np.argsort(item_based_collabor.values[:,3])[:-5:-1]]
print(top_n_items)
# 따라서 가장 비슷한 유사도를 가지는 테마만 유사도 벡터로 사용
# 특정 테마와 비슷한 유사도를 가지는 테마 Top_N에 대해서만 적용 -> 시간오래걸림
def predict_rating_topsim(ratings_arr, item_sim_arr, n):
# 사용자-아이템 평점 행렬 크기만큼 0으로 채운 예측 행렬 초기화
pred = np.zeros(ratings_arr.shape)
# 사용자-아이템 평점 행렬의 테마 개수만큼 루프
for col in range(ratings_arr.shape[1]):
# 유사도 행렬에서 유사도가 큰 순으로 n개의 데이터 행렬의 인덱스 반환
top_n_items = [np.argsort(item_sim_arr[:, col])[:-n-1:-1]]
# 개인화된 예측 평점 계산 : 각 col 테마별(1개), x명의 사용자들의 예측 평점
for row in range(ratings_arr.shape[0]):
pred[row, col] = item_sim_arr[col,:][top_n_items].dot(ratings_arr[row, :][top_n_items].T)
pred[row, col] /= np.sum(item_sim_arr[col,:][top_n_items])
return pred
ratings_pred = predict_rating_topsim(user_movie_rating.values, item_based_collabor.values, n=2)
print('===============================================')
print(ratings_pred)
# N을 적절하게 해서 오차를 줄여야됨
print('아이템 기반 최근접 TOP-N 이웃 MSE: ', get_mse(ratings_pred, user_movie_rating.values))
# 계산된 예측 평점 데이터는 DataFrame으로 재생성
ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index=user_movie_rating.index,
columns=user_movie_rating.columns)
print(ratings_pred_matrix)
username=1
# 특정 유저 대상 으로 테마추천
user_rating_id = user_movie_rating.loc[username, :]
# 유저가 먹었던 테마를 출력
print(user_rating_id[user_rating_id > 0].sort_values(ascending=False)[:10])
# 사용자가 안 먹어본 테마를 추천하자.
def get_not_tried_theme(ratings_matrix, userId):
# userId로 입력받은 사용자의 모든 테마 정보를 추출해 Series로 반환
# 반환된 user_rating은 영화명(title)을 인덱스로 가지는 Series 객체
user_rating = ratings_matrix.loc[userId, :]
# user_rating이 0보다 크면 기존에 관란함 테마
# 대상 인덱스를 추출해 list 객체로 만듦
tried = user_rating[user_rating>0].index.tolist()
# 모든 테마명을 list 객체로 만듦
theme_list = ratings_matrix.columns.tolist()
# list comprehension으로 tried에 해당하는 영화는 beer_list에서 제외
not_tried = [theme for theme in theme_list if theme not in tried]
return not_tried
# 예측 평점 DataFrame에서 사용자 id 인덱스와 not_tried로 들어온 테마명 추출 후
# 가장 예측 평점이 높은 순으로 정렬
def recomm_theme_by_userid(pred_df, userId, not_tried, top_n):
recomm_theme = pred_df.loc[userId, not_tried].sort_values(ascending=False)[:top_n]
return recomm_theme
# 유저가 먹지 않은 맥주 테마 추출
not_tried = get_not_tried_theme(user_movie_rating, username)
print(not_tried)
# top_n과 비슷한 맥주만 추천에 사용
ratings_pred = predict_rating_topsim(user_movie_rating.values, item_based_collabor.values, n=5)
# 계산된 예측 평점 데이터는 DataFrame으로 재생성
ratings_pred_matrix = pd.DataFrame(data=ratings_pred, index=user_movie_rating.index,
columns=user_movie_rating.columns)
# 유저가 먹지 않은 맥주 이름 추출
not_tried = get_not_tried_theme(user_movie_rating, username)
# 아이템 기반의 최근접 이웃 CF로 맥주 추천
recomm_theme = recomm_theme_by_userid(ratings_pred_matrix, username, not_tried, top_n=3)
recomm_theme = pd.DataFrame(data=recomm_theme.values, index=recomm_theme.index,
columns=['예측평점'])
print(recomm_theme)
'Backend' 카테고리의 다른 글
[빅데이터 추천] 컨텐츠 기반 필터링 구현해보기 (0) | 2023.03.09 |
---|---|
빅데이터 추천 시스템(CBF, CF)에 대해 (0) | 2023.02.20 |