본문 바로가기
추천시스템

추천시스템 협업필터링 Memorial Based:유사도 계산 방식 및 surprise 패키지를 활용한 평점 예측 방법

by thrcle 2025. 2. 2.

안녕하세요 오니입니다🐰

1. 추천시스템 종류

추천시스템의 종류는 크게 다음과 같이 나눠볼 수 있습니다. 이번 포스팅에서는 Collaborative Filtering(협업필터링)에서도 Memorial Based에 대해 집중적으로 다뤄보겠습니다.

출처:https://incodom.kr/%EC%B6%94%EC%B2%9C%EC%8B%9C%EC%8A%A4%ED%85%9C

 


2. 협업필터링 구현 방식 및 종류

 2-1. Memorial Based 

  •  기억기반 방식은 유사도 기반방식입니다.
  •  코사인 유사도나 피어슨 상관계수 유사도를 사용해 비슷한 사용자 혹은 아이템을 찾습니다.
  • 평점 예측시에는 가중치를 사용한 평균을 사용합니다.
  • 이해하기 쉽고 설명하기 쉬우나, 스케일하기 힘듭니다.(평점데이터의 부족)
  • 기억기반 방식은 다시 사용자 기반, 아이템 기반으로 나뉩니다.

사용자기반

  • 나와 비슷한 평점 패턴을 보이는 사람들을 찾아서 그 사람들이 높게 평가한 아이템 추천하는 방식입니다.
  • “당신과 비슷한 사용자들이 좋아하는 아이템”

아이템 기반

  • 평점의 패턴이 비슷한 아이템들을 찾아 추천하는 방식입니다.
  • “이 아이템을 좋아한 다른 사용자들이 좋아한 아이템”
  • 사용자기반 보다 행렬의 크기가 작습니다

2-2. Model Based

  •   머신러닝을 사용해 패턴을 학습하여 평점을 예측 (PCA, SVD, Matrix Factorization, 딥러닝 등) 
  •   딥러닝의 경우는 오토인코더를 사용해 차원 축소
    •  오토인코더: 딥러닝에서 데이터 차원을 축소하는 방식으로 인코딩을 통해 데이터를 압축하고 디코딩을 통해 데이터를 복구함. 인코딩을 하는 부분이 결국 차원 축소 담당
  • 행렬의 차원을 줄임으로써 평점 데이터의 부족 문제 해결
  • 동작 방식에 대한 설명이 힘듦

 


3. Memorial Based 의 유사도 측정 방식

3-1. 사용자 기반

dummy_rating = {
    "scifi1":  [4.0, 5.0, 1.0, np.nan, 1.0],
    "scifi2":  [5.0, 3.0, np.nan, 2.0, np.nan],
    "scifi3":  [3.0, 3.0, np.nan, 1.0, 2.0],
    "comedy1": [np.nan, 2.0, 4.0, 4.0, 3.0],
    "comedy2": [2.0, 2.0, 5.0, np.nan, 3.0],
    "comedy3": [1.0, np.nan, 4.0, 3.0, 4.0]
}

dummy_rating = pd.DataFrame(dummy_rating, index=["user1", "user2", "user3", "user4", "user5"])

dummy_rating

 

 

 

값이 NaN인 경우는 평가하지 않았음을 의미하는데, 0으로 채워준 뒤 유사도 측정 행렬을 생성해줍니다

dummy_rating.fillna(0, inplace=True)

corrMatrix_wo_std = pd.DataFrame(cosine_similarity(dummy_rating), index=dummy_rating.index, columns=dummy_rating.index)
corrMatrix_wo_std

 

 

평점 정보 보정 -1~1사이의 값으로 정규화한 뒤 정규화한 값에 대해 유사도 측정 행렬을 생성하면 다음과 같습니다. 

def standardize(row):
    new_row = (row - row.mean())/(row.max()-row.min())
    return new_row

dummy_rating_std = dummy_rating.apply(standardize, axis=1)
dummy_rating_std

corrMatrix = pd.DataFrame(cosine_similarity(dummy_rating_std), index=dummy_rating.index, columns=dummy_rating.index)
corrMatrix

 


3-2. 아이템 기반

아이템 기반에서는 dummy_rating을 transpose 시켜준 뒤 나머지 과정은 동일합니다.

# 아이템 기반
import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity

# 데이터셋
dummy_rating = {
    "scifi1":  [4.0, 5.0, 1.0, np.nan, 1.0],
    "scifi2":  [5.0, 3.0, np.nan, 2.0, np.nan],
    "scifi3":  [3.0, 3.0, np.nan, 1.0, 2.0],
    "comedy1": [np.nan, 2.0, 4.0, 4.0, 3.0],
    "comedy2": [2.0, 2.0, 5.0, np.nan, 3.0],
    "comedy3": [1.0, np.nan, 4.0, 3.0, 4.0]
}

# DataFrame으로 변환
df = pd.DataFrame(dummy_rating)

# NaN 값을 0으로 처리하거나, 다른 값으로 처리할 수 있지만 여기선 0으로 처리
df_filled = df.fillna(0)

# 아이템 간 코사인 유사도 계산
cosine_sim = cosine_similarity(df_filled.T)  # .T로 전치하여 아이템 간 유사도를 계산

# 결과를 DataFrame 형태로 보기 좋게 표시
cosine_sim_df = pd.DataFrame(cosine_sim, index=df.columns, columns=df.columns)

# 출력
cosine_sim_df

 

 

 

계산과정

 

정규화 적용 버전

 

 


4. Surprise 라이브러리를 활용한 협업필터링 구현 

데이터 로드

from surprise import Dataset, Reader, KNNBasic
from surprise.model_selection import train_test_split
from surprise import accuracy
import pandas as pd
import numpy as np

# 1. 데이터 로드
!wget "https://grepp-reco-test.s3.ap-northeast-2.amazonaws.com/movielens/ratings.csv"
movies_df = pd.read_csv("https://grepp-reco-test.s3.ap-northeast-2.amazonaws.com/movielens/movies.csv")
# pandas로 데이터 로드
ratings_df = pd.read_csv("ratings.csv")


# 2. NaN 값 처리 (예: NaN을 0으로 채우기) NaN 값을 처리하지 않으면 모델 학습과 예측에서 오류가 발생
ratings_df['rating'] = ratings_df['rating'].fillna(0)
                                                   
# 3. 'surprise' 포맷으로 변환
reader = Reader(line_format='user item rating timestamp', sep=',', skip_lines=1)
dataset = Dataset.load_from_df(ratings_df[['userId', 'movieId', 'rating']], reader)

# 학습용, 테스트용 데이터 분리
trainset, testset = train_test_split(dataset, test_size=0.2)

 

 

 

사용자 기반 유사도 계산시 sim_options에서 user_based=True, 

아이템 기반 유사도 계산시 sim_options에서 user_based=False로 설정하면 됩니다. 

🔎 sim_options
- name: 유사도를 계산할 방법을 정의 ('cosine', 'pearson', 'msd' 등)
- user_based: 사용자 기반 추천인지 아이템 기반 추천인지를 설정 (True이면 사용자 기반, False이면 아이템 기반)
# 사용자 기반 KNN 모델 만들기
sim_options = {
    'name': 'cosine',
    'user_based': True,  # 사용자 기반 유사도 계산
}
model = KNNBasic(sim_options=sim_options)

# 모델 학습
model.fit(trainset)

# 테스트셋에서 예측
predictions = model.test(testset)

# 예측 정확도 출력
rmse = accuracy.rmse(predictions)
print(f"RMSE: {rmse}")

# 예를 들어 user1에게 'scifi1'을 추천할 경우
user_id = "user1"
item_id = "scifi1"

# 예측된 평점 출력
prediction = model.predict(user_id, item_id)
print(f"Prediction for {user_id} on {item_id}: {prediction.est}")

 

 

사용자 기반 출력 예시화면

 

 

아이템 기반 출력 예시 화면

 

 

 그런데 기억기반 방식은 평점을 예측하는게 아니라, 유사도를 측정하는 것이라고 앞선 이론에서 설명했는데, output의 결과를 보니 user1의 scifi1의 예상 평점이 나오네요. 조금 의아할 수 있습니다. 사용자 기반 CF로 평점을 예측할 수 있을까요?

 

답변은 그렇다 입니다.

1️⃣ 사용자 기반 CF는 기본적으로 유사한 사용자를 찾는 방법이지만,
2️⃣ 유사한 사용자들의 평점을 기반으로 특정 아이템에 대한 예상 평점도 계산할 수 있습니다.
3️⃣ 평점 예측 공식: 유사한 사용자들의 평점을 가중 평균하여 예측하고
4️⃣ surprise에서 KNNBasic을 사용하면 유사한 사용자를 기반으로 평점 예측이 가능합니다.

🔎  KNNBasic 수행 로직
1. 유사도 계산
2. KNN 알고리즘 적용
- 계산된 유사도 매트릭스를 바탕으로, 각 사용자 또는 아이템에 대해 k개의 최근접 이웃을 찾는 KNN 알고리즘을 사용 
- 아이템 기반 추천일 경우 k는 유사도가 높은 아이템의 개수
3. 예측값 계산
- 아이템 기반 추천이라면 아이템들의 평점의 가중 평균을 계산하여 점수 예측
- 사용자 기반 추천이라면 유사한 사용자들의 평점의 가중평균을 계산하여 점수 예측