땅지원
땅지원's Personal blog
땅지원
전체 방문자
오늘
어제
  • 전체 (353)
    • Frontend (2)
      • React (2)
    • Backend (90)
      • Java (16)
      • Python (19)
      • Spring (23)
      • Database (21)
      • Troubleshooting (8)
    • DevOps (27)
      • ELK (13)
    • CS (40)
    • OS (2)
      • Linux (2)
    • Algorithm (95)
      • concept (18)
      • Algorithm Problem (77)
    • 인공지능 (25)
      • 인공지능 (12)
      • 연구노트 (13)
    • 수업정리 (35)
      • 임베디드 시스템 (10)
      • 데이터통신 (17)
      • Linux (8)
    • 한국정보통신학회 (5)
      • 학술대회 (4)
      • 논문지 (1)
    • 수상기록 (8)
      • 수상기록 (6)
      • 특허 (2)
    • 삼성 청년 SW 아카데미 (6)
    • 42seoul (12)
    • Toy project (3)
    • 땅's 낙서장 (2)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • 20.11.6 BB21플러스 온라인 학술대회
  • 20.10.30 한국정보통신학회 온라인 학술대회

인기 글

태그

  • E
  • D
  • 이것이 리눅스다 with Rocky Linux9
  • ㅗ
  • I

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
땅지원

땅지원's Personal blog

Backend

[빅데이터 추천] 협업 필터링 구현해보기(아이템 기반 협업 필터링)

2023. 3. 9. 15:47

"아이템 기반 협업 필터링" 사용 이유

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
    'Backend' 카테고리의 다른 글
    • [빅데이터 추천] 컨텐츠 기반 필터링 구현해보기
    • 빅데이터 추천 시스템(CBF, CF)에 대해
    땅지원
    땅지원
    신입 개발자의 우당탕탕 기술 블로그

    티스토리툴바