본문 바로가기

School/추천시스템

추천시스템_9주차_02_Baselines

"MovieLens 100K Dataset"에서의 기준 모델 연습: #2 기준 모델

기준 모델은 두 가지 주요 이유로 중요합니다:

  1. 기준 모델은 모든 후속 모델과 비교할 출발점을 제공합니다.
  2. 복잡한 모델에서 누락된 데이터를 채우기 위해 스마트한 기준/평균이 필요할 수 있습니다.

여기에서는 추천 시스템을 위한 몇 가지 전형적인 기준 모델을 탐색하고, 이 데이터셋에서 가장 잘 동작하는 모델을 확인해보겠습니다.

 

# 0. Mount and load raw data

from google.colab import drive
drive.mount('/gdrive', force_remount=True)

Mounted at /gdrive

cd /gdrive/MyDrive/Lectures/2023/RecSys

[Errno 2] No such file or directory: 'cd /gdrive/MyDrive/Lectures/2023/RecSys' /content

 

 

1. 필요한 모듈과 클래스를 가져옵니다.

# <!-- collapse=True -->
%matplotlib inline
from collections import OrderedDict
from datetime import datetime
from IPython.display import Markdown
from os.path import join
from os.path import exists
from sklearn.metrics import mean_absolute_error
from sklearn.model_selection import KFold
import gc
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns

plt.style.use('seaborn-darkgrid')

위의 코드는 필요한 모듈과 클래스를 가져오는 부분입니다. 다음과 같은 모듈과 클래스가 포함되어 있습니다:

  • %matplotlib inline: 주피터 노트북에서 인라인 그래프를 표시하기 위한 매직 명령입니다.
  • from collections import OrderedDict: 정렬된 딕셔너리(OrderedDict)를 사용하기 위한 모듈입니다.
  • from datetime import datetime: 날짜 및 시간 관련 기능을 사용하기 위한 모듈입니다.
  • from IPython.display import Markdown: 주피터 노트북에서 Markdown을 표시하기 위한 모듈입니다.
  • from os.path import join, exists: 파일 경로 및 존재 여부를 확인하기 위한 모듈입니다.
  • from sklearn.metrics import mean_absolute_error: 평균 절대 오차를 계산하기 위한 함수를 가져옵니다.
  • from sklearn.model_selection import KFold: K-Fold 교차 검증을 수행하기 위한 클래스를 가져옵니다.
  • import gc: 가비지 컬렉션(gc)을 수행하기 위한 모듈입니다.
  • import matplotlib.pyplot as plt: 데이터 시각화를 위한 모듈입니다.
  • import numpy as np: 수치 연산을 위한 모듈입니다.
  • import pandas as pd: 데이터 처리를 위한 모듈입니다.
  • import seaborn as sns: 데이터 시각화를 위한 모듈입니다.

또한, plt.style.use('seaborn-darkgrid')는 그래프의 스타일을 seaborn-darkgrid로 설정하는 부분입니다. 이 스타일은 어두운 배경과 선명한 그리드 라인으로 시각화를 보다 명확하게 만들어줍니다.

 

<ipython-input-3-be7ef92a9304>:16: MatplotlibDeprecationWarning: The seaborn styles shipped by Matplotlib are deprecated since 3.6, as they no longer correspond to the styles shipped by seaborn. However, they will remain available as 'seaborn-v0_8-<style>'. Alternatively, directly use the seaborn API instead. plt.style.use('seaborn-darkgrid')

 

 

1. 데이터 불러오기 (Load the Data)

평점 데이터를 불러와서 살펴보겠습니다. 만약 따라하고 있다면 (즉, 이 노트북을 실제로 실행 중인 경우) 이전 노트북을 먼저 실행하여 데이터를 다운로드해야 합니다.

# <!-- collapse=True -->
ratings_df = pd.read_csv('raw/ml-100k/u.data', sep='\t', header=None, 
                         names=['userId', 'movieId', 'rating', 'timestamp'])
ratings_df['timestamp'] = ratings_df['timestamp'].apply(datetime.fromtimestamp)
ratings_df = ratings_df.sort_values('timestamp')
print('First 5:')
display(ratings_df.head())
print()
print('Last 5:')
display(ratings_df.tail())

 

위의 코드는 데이터를 불러오고 평점 데이터를 살펴보는 부분입니다. 아래의 과정을 수행합니다:

  • pd.read_csv('raw/ml-100k/u.data', sep='\t', header=None, names=['userId', 'movieId', 'rating', 'timestamp']): 'raw/ml-100k/u.data' 파일을 탭('\t')으로 구분하여 읽어와 데이터프레임으로 저장합니다. 컬럼명은 'userId', 'movieId', 'rating', 'timestamp'로 지정합니다.
  • ratings_df['timestamp'] = ratings_df['timestamp'].apply(datetime.fromtimestamp): 'timestamp' 컬럼을 UNIX 타임스탬프에서 날짜와 시간으로 변환합니다.
  • ratings_df = ratings_df.sort_values('timestamp'): 'timestamp'을 기준으로 데이터프레임을 정렬합니다.
  • print('First 5:'): "First 5:"라는 메시지를 출력합니다.
  • display(ratings_df.head()): 데이터프레임의 첫 5개 행을 출력합니다.
  • print(): 빈 줄을 출력합니다.
  • print('Last 5:'): "Last 5:"라는 메시지를 출력합니다.
  • display(ratings_df.tail()): 데이터프레임의 마지막 5개 행을 출력합니다.

이를 통해 'u.data' 파일에서 불러온 평점 데이터를 확인할 수 있습니다.

 

 

2. 기준 모델 테스트

위에서 설정한 모든 프레임워크를 사용하여 예상 정확도가 증가하는 순서로 몇 가지 기준 모델을 평가해보겠습니다.

2.1 단순 평균 모델

첫 번째 모델은 가능한 가장 간단한 모델입니다. 우리는 훈련 세트의 모든 평점을 평균내고, 그 평균을 모든 테스트 세트 예제의 예측값으로 사용할 것입니다.

# <!-- collapse=True -->
class SimpleAverageModel():
    """A very simple model that just uses the average of the ratings in the
    training set as the prediction for the test set.

    Attributes
    ----------
    mean : float
        Average of the training set ratings
    """

    def __init__(self):
        pass

    def fit(self, X):
        """Given a ratings dataframe X, compute the mean rating
        
        Parameters
        ----------
        X : pandas dataframe, shape = (n_ratings, >=3)
            User, item, rating dataframe. Only the 3rd column is used.
        
        Returns
        -------
        self
        """
        self.mean = X.iloc[:, 2].mean()
        return self

    def predict(self, X):
        return np.ones(len(X)) * self.mean

 

 

위의 코드는 "SimpleAverageModel"이라는 클래스를 정의하고 있습니다. 이 모델은 매우 간단한 방식으로 동작합니다. 훈련 세트의 평점을 평균내어 테스트 세트의 예측값으로 사용합니다.

클래스에는 다음과 같은 속성과 메서드가 있습니다:

  • mean: 훈련 세트의 평균 평점을 저장하는 속성입니다.
  • fit(X): 주어진 평점 데이터 프레임 X에서 평균 평점을 계산하는 메서드입니다. X는 사용자, 아이템, 평점 정보가 포함된 데이터프레임입니다. 이 메서드는 self를 반환합니다.
  • predict(X): 주어진 데이터 X의 길이만큼 평균 평점을 반복하여 예측값을 반환하는 메서드입니다. 예측값은 모두 평균 평점으로 이루어진 배열입니다.

 

 

 

2.2 ID별 평균 모델

사용자 또는 아이템(영화)의 평균을 사용하면 약간 더 나은 결과를 얻을 수 있을 것입니다. 여기에서는 userId 또는 movieId의 리스트를 X로 전달할 수 있는 기준선 모델 클래스를 설정하겠습니다. 주어진 ID에 대한 예측값은 해당 ID의 평균 평점이며, 훈련 세트에서 해당 ID를 볼 수 없는 경우 전체 평균이 사용됩니다.

# <!-- collapse=True -->
class AverageByIdModel():
    """Simple model that predicts based on average ratings for a given Id
    (movieId or userId) from training data
    
    Parameters
    ----------
    id_column : string
        Name of id column (i.e. 'itemId', 'userId') to average by in
        dataframe that will be fitted to

    Attributes
    ----------
    averages_by_id : pandas Series, shape = [n_ids]
        Pandas series of rating averages by id
    overall_average : float
        Average rating over all training samples
    """
    def __init__(self, id_column):
        self.id_column = id_column

    def fit(self, X):
        """Fit training data.

        Parameters
        ----------
        X : pandas dataframe, shape = (n_ratings, >=3)
            User, item, rating dataframe. Columns beyond 3 are ignored

        Returns
        -------
        self : object
        """
        rating_column = X.columns[2]
        X = X[[self.id_column, rating_column]].copy()
        X.columns = ['id', 'rating']
        self.averages_by_id = (
            X
            .__TODO__ # TODO: prepare to calculate mean rating for each id.
            .mean()
            .rename('average_rating')
        )
        self.overall_average = X['rating'].mean()
        return self

    def predict(self, X):
        """Return rating predictions

        Parameters
        ----------
        X : pandas dataframe, shape = (n_ratings, >=3)
            Array of n_ratings movieIds or userIds

        Returns
        -------
        y_pred : numpy array, shape = (n_ratings,)
            Array of n_samples rating predictions
        """
        rating_column = X.columns[2]
        X = X[[self.id_column, rating_column]].copy()
        X.columns = ['id', 'rating']
        X = X.__TODO__ # joining the fitted value with the key 'id'
        X['average_rating'].fillna(__TODO__) # fill the missing values by overall_average
        return X['average_rating'].values

AverageByIdModel 클래스는 훈련 데이터에서 주어진 ID (예: 영화 ID 또는 사용자 ID)의 평균 평점을 기반으로 평점을 예측하는 간단한 모델입니다. 이 클래스는 두 가지 주요 메서드를 가지고 있습니다:

  • fit(self, X): 이 메서드는 훈련 데이터를 학습하는 데 사용됩니다. 입력으로 pandas 데이터프레임 X를 받으며, 사용자, 아이템 및 평점 정보가 포함되어 있어야 합니다. id_column 매개변수는 X에서 ID를 나타내는 열의 이름을 지정합니다 (예: 'itemId' 또는 'userId'). 이 메서드는 각 ID에 대한 평균 평점을 계산하고 averages_by_id 속성에 저장합니다. 또한 모든 훈련 샘플에 대한 전체 평균 평점을 계산하고 overall_average 속성에 저장합니다. 메서드는 self를 반환합니다.
  • predict(self, X): 이 메서드는 주어진 ID 집합에 대한 평점 예측을 수행하는 데 사용됩니다. 입력으로 pandas 데이터프레임 X를 받으며, 예측을 수행해야 하는 ID가 포함되어 있어야 합니다. 메서드는 X에서 ID에 해당하는 평균 평점을 averages_by_id 속성에서 검색합니다. 만약 훈련 데이터에 해당 ID가 없는 경우, 예측값으로 overall_average 값을 사용합니다. 메서드는 평점 예측값의 배열을 반환합니다.

코드에는 완성해야 할 두 가지 작업이 있는 점을 유의해주세요:

  1. fit 메서드에서는 각 ID의 평균 평점을 계산하기 위해 데이터를 준비해야 합니다. 'id' 열을 기준으로 groupby 함수를 사용하여 'rating' 열의 평균을 계산할 수 있습니다.
  2. predict 메서드에서는 주어진 ID 집합에 대한 평점 예측을 위해 데이터를 처리해야 합니다. 'id' 열을 기준으로 averages_by_id 속성에서 해당하는 평균 평점을 검색합니다. 훈련 데이터에 해당 ID가 없는 경우 overall_average 값을 예측값으로 사용합니다.

$b_{u,i}$ 사용자 $u$가 아이템 (영화) $i$에 대한 기준선 평점
$\mu$ 모든 평점의 평균
$b_u$ 사용자 $u$와 연결된 $\mu$로부터의 편차
$b_i$ 아이템 $i$와 연결된 $\mu+b_u$로부터의 편차
$I_u$ 사용자 $u$가 평가한 모든 아이템의 집합
$\mid I_u \mid$ 사용자 $u$가 평가한 아이템의 수
$\beta_u$ 사용자에 대한 감쇠 요소 ($=\beta$)
$r_{u,i}$ 사용자 $u$가 아이템 $i$에 대해 관찰한 평점
$U_i$ 아이템 $i$를 평가한 모든 사용자의 집합
$\mid U_i \mid$ 아이템 $i$를 평가한 사용자의 수
$\beta_i$ 아이템에 대한 감쇠 요소 ($=\beta$)

 

 

 

 

 

# <!-- collapse=True -->
class DampedUserMovieBaselineModel():
    """Baseline model that of the form mu + b_u + b_i,
    where mu is the overall average, b_u is a damped user
    average rating residual, and b_i is a damped item (movie)
    average rating residual. See eqn 2.1 of
    http://files.grouplens.org/papers/FnT%20CF%20Recsys%20Survey.pdf

    Parameters
    ----------
    damping_factor : float, default=0
        Factor to bring residuals closer to 0. Must be positive.

    Attributes
    ----------
    mu : float
        Average rating over all training samples
    b_u : pandas Series, shape = [n_users]
        User residuals
    b_i : pandas Series, shape = [n_movies]
        Movie residuals
    damping_factor : float, default=0
        Factor to bring residuals closer to 0. Must be >= 0.
    """
    def __init__(self, damping_factor=0):
        self.damping_factor = damping_factor

    def fit(self, X):
        """Fit training data.

        Parameters
        ----------
        X : DataFrame, shape = [n_samples, >=3]
            User, movie, rating dataFrame. Columns beyond 3 are ignored

        Returns
        -------
        self : object
        """
        X = X.iloc[:, :3].copy()
        X.columns = ['user', 'item', 'rating']
        self.mu = np.mean(X['rating'])
        user_counts = X['user'].value_counts()
        movie_counts = X['item'].value_counts()
        b_u = (
            X[['user', 'rating']]
            .groupby('user')['rating']
            .sum()
            .subtract(user_counts * self.mu)
            .divide(user_counts + self.damping_factor) # Question: Tell me the meaning of this expresison.
            .rename('b_u')
        )
        X = X.join(b_u, on='user')
        X['item_residual'] = X['rating'] - X['b_u'] - self.mu
        b_i = (
            X[['item', 'item_residual']]
            .groupby('item')['item_residual']
            .sum()
            .divide(movie_counts + self.damping_factor)
            .rename('b_i')
        )
        self.b_u = b_u
        self.b_i = b_i
        return self

    def predict(self, X):
        """Return rating predictions

        Parameters
        ----------
        X : DataFrame, shape = (n_ratings, 2)
            User, item dataframe

        Returns
        -------
        y_pred : numpy array, shape = (n_ratings,)
            Array of n_samples rating predictions
        """
        X = X.iloc[:, :2].copy()
        X.columns = ['user', 'item']
        X = X.join(self.b_u, on='user').fillna(0)
        X = X.join(self.b_i, on='item').fillna(0)
        return (self.mu + X['b_u'] + X['b_i']).values

위 코드는 감쇠된 사용자 및 영화 기준선 모델(Damped User + Movie Baseline Model)을 구현한 클래스입니다. 이 모델은 사용자와 영화의 평균 평점을 고려하고, 감쇠 요소를 사용하여 기준선 예측을 계산합니다. 감쇠 요소는 기준선 예측을 전체 평균에 가깝게 만드는 역할을 합니다.

fit 메서드는 학습 데이터를 입력으로 받아 모델을 피팅합니다. 이 때, 사용자와 영화의 평균 평점과 감쇠된 사용자 및 영화의 평균 평점 잔차(b_u, b_i)를 계산합니다. 감쇠된 사용자 평점 잔차 b_u는 사용자별로 평균 평점과의 차이를 계산하고, 사용자별 평가 수와 감쇠 요소를 고려하여 나누어 계산됩니다. 마찬가지로, 감쇠된 영화 평점 잔차 b_i는 영화별로 평균 평점 잔차를 계산합니다.

predict 메서드는 입력으로 사용자와 영화 데이터를 받아 기준선 예측을 계산합니다. 사용자와 영화 데이터에 해당하는 사용자 평점 잔차와 영화 평점 잔차를 가져와 전체 평균과 더하여 기준선 예측을 반환합니다.

이 모델은 GroupLens에서 발행한 논문에서 소개된 기준선 모델을 구현한 것으로, 사용자와 영화의 특성을 고려하여 개인화된 예측을 수행할 수 있습니다.

 

 

 

2.4 교차 검증 프레임워크

평점 분포가 시간에 따라 크게 변하지 않아 시간에 독립적인 교차 검증 프레임워크를 사용하여 가장 좋은 기준선 모델을 결정할 것입니다. 아래의 get_xval_errs() 함수는 데이터프레임과 기준선 모델 객체를 전달하면 5개(또는 n_splits)의 폴드에서 각각의 평균 절대 오차(Mean Absolute Error, MAE) 값을 반환합니다.

def get_xval_errs_and_res(df, model, n_splits=5, random_state=0, rating_col='rating'):
    kf = KFold(n_splits=n_splits, random_state=random_state, shuffle=True)
    errs, stds = [], []
    residuals = np.zeros(len(df))
    for train_inds, test_inds in kf.split(df):
        train_df, test_df = df.iloc[train_inds], df.iloc[test_inds]
        pred = model.fit(train_df).predict(test_df)
        residuals[test_inds] = pred - test_df[rating_col]
        mae = mean_absolute_error(pred, test_df[rating_col])
        errs.append(mae)
    return errs, residuals

함수 get_xval_errs_and_res(df, model, n_splits=5, random_state=0, rating_col='rating')는 데이터프레임과 기준선 모델 객체를 입력받아 교차 검증을 수행하고, 각 폴드에서의 평균 절대 오차(MAE) 값을 계산하여 리스트로 반환합니다. 또한, 각 예측값에 대한 잔차(residual)를 계산하여 반환합니다.

  • df: 평가 데이터를 포함하는 데이터프레임
  • model: 기준선 모델 객체
  • n_splits: 폴드의 개수, 기본값은 5
  • random_state: 재현 가능성을 위한 난수 시드, 기본값은 0
  • rating_col: 평점을 나타내는 열의 이름, 기본값은 'rating'

함수는 다음과 같은 작업을 수행합니다:

  1. KFold를 사용하여 데이터를 교차 검증용 폴드로 분할합니다.
  2. 각 폴드에 대해 학습 데이터와 테스트 데이터를 추출합니다.
  3. 모델을 학습 데이터에 맞추고, 테스트 데이터에 대한 예측값을 계산합니다.
  4. 예측값과 테스트 데이터의 실제 평점을 비교하여 평균 절대 오차(MAE)를 계산합니다.
  5. 각 폴드에서의 MAE 값을 리스트에 저장합니다.
  6. 예측값과 실제 값의 잔차를 계산하여 배열에 저장합니다.
  7. MAE 값과 잔차 배열을 반환합니다.

이 함수를 통해 교차 검증을 수행하면서 기준선 모델의 성능을 평가할 수 있습니다.

 

# <!-- collapse=True -->
errs_1, res_1 = get_xval_errs_and_res(ratings_df, SimpleAverageModel())
errs_2, res_2 = get_xval_errs_and_res(ratings_df, AverageByIdModel('movieId'))
errs_3, res_3 = get_xval_errs_and_res(ratings_df, AverageByIdModel('userId'))
errs_4, res_4 = get_xval_errs_and_res(ratings_df, DampedUserMovieBaselineModel(0))
errs_5, res_5 = get_xval_errs_and_res(ratings_df, DampedUserMovieBaselineModel(10))
errs_6, res_6 = get_xval_errs_and_res(ratings_df, DampedUserMovieBaselineModel(25))
errs_7, res_7 = get_xval_errs_and_res(ratings_df, DampedUserMovieBaselineModel(50))
df_errs = pd.DataFrame(
    OrderedDict(
        (
            ('Average', errs_1),
            ('Item Average', errs_2),
            ('User Average', errs_3),
            ('Combined 0', errs_4),
            ('Combined 10', errs_5),
            ('Combined 25', errs_6),
            ('Combined 50', errs_7),
        )
    )
)
display(df_errs)
df_errs = (
    pd.melt(df_errs, value_vars=df_errs.columns)
    .rename({'variable': 'Baseline Model', 'value': 'MAE'}, axis=1)
)
df_res = pd.DataFrame(
    OrderedDict(
        (
            ('Average', res_1),
            ('Item Average', res_2),
            ('User Average', res_3),
            ('Combined 0', res_4),
            ('Combined 10', res_5),
            ('Combined 25', res_6),
            ('Combined 50', res_7),
        )
    )
)
display(df_res.tail())
df_res = (
    pd.melt(df_res, value_vars=df_res.columns)
    .rename({'variable': 'Baseline Model', 'value': 'Residual'}, axis=1)
)

위의 코드는 다양한 기준선 모델을 사용하여 교차 검증을 수행하고, 각 모델의 MAE 값과 잔차를 데이터프레임에 저장하는 과정입니다.

  1. SimpleAverageModel()을 사용하여 교차 검증을 수행하고, MAE 값과 잔차를 errs_1과 res_1에 저장합니다.
  2. AverageByIdModel('movieId')를 사용하여 교차 검증을 수행하고, MAE 값과 잔차를 errs_2와 res_2에 저장합니다. 이 모델은 영화 ID를 기준으로 평균 평점을 계산합니다.
  3. AverageByIdModel('userId')를 사용하여 교차 검증을 수행하고, MAE 값과 잔차를 errs_3와 res_3에 저장합니다. 이 모델은 사용자 ID를 기준으로 평균 평점을 계산합니다.
  4. DampedUserMovieBaselineModel(0)을 사용하여 교차 검증을 수행하고, MAE 값과 잔차를 errs_4와 res_4에 저장합니다. 이 모델은 사용자와 영화의 평균 평점을 고려하며, damping factor를 0으로 설정합니다.
  5. DampedUserMovieBaselineModel(10)을 사용하여 교차 검증을 수행하고, MAE 값과 잔차를 errs_5와 res_5에 저장합니다. 이 모델은 damping factor를 10으로 설정합니다.
  6. DampedUserMovieBaselineModel(25)을 사용하여 교차 검증을 수행하고, MAE 값과 잔차를 errs_6과 res_6에 저장합니다. 이 모델은 damping factor를 25로 설정합니다.
  7. DampedUserMovieBaselineModel(50)을 사용하여 교차 검증을 수행하고, MAE 값과 잔차를 errs_7와 res_7에 저장합니다. 이 모델은 damping factor를 50으로 설정합니다.

그런 다음, MAE 값을 담은 데이터프레임 df_errs와 잔차 값을 담은 데이터프레임 df_res를 생성합니다. 이 두 데이터프레임은 각 모델별로 MAE 값과 잔차를 저장하고, 표 형식으로 나타냅니다. df_errs는 'Baseline Model' 열에 모델 이름, 'MAE' 열에 MAE 값이 들어있으며, df_res는 'Baseline Model' 열에 모델 이름, 'Residual' 열에 잔차 값이 들어있습니다.

마지막으로, df_errs와 df_res를 표시합니다.

이 코드는 각 모델의 성능과 잔차를 비교하기 위해 교차 검증을 수행하고, 결과를 시각적으로 표현하기 위해 데이터프레임을 사용합니다.

 

 

# <!-- collapse=True -->
fig, (ax0, ax1) = plt.subplots(2, 1, figsize=(12,8))
sns.swarmplot(data=df_errs, x='Baseline Model', y='MAE', ax=ax0)
sns.violinplot(data=df_res, x='Baseline Model', y='Residual', ax=ax1)
ax0.xaxis.set_visible(False)
plt.tight_layout()
plt.show()

위의 코드는 교차 검증 결과를 시각화하기 위한 그래프를 생성하는 과정입니다.

  1. fig, (ax0, ax1) = plt.subplots(2, 1, figsize=(12,8))를 사용하여 크기가 12x8인 2x1 서브플롯 그림을 생성합니다. 이 그림은 2개의 축 ax0와 ax1을 가지게 됩니다.
  2. sns.swarmplot(data=df_errs, x='Baseline Model', y='MAE', ax=ax0)를 사용하여 첫 번째 축 ax0에 대한 스웜 플롯을 생성합니다. 이 플롯은 'Baseline Model'을 x축으로, 'MAE'를 y축으로 사용하며, df_errs 데이터프레임을 데이터로 사용합니다. 스웜 플롯은 각 모델의 MAE 값을 점으로 나타내어 모델들 간의 성능을 비교할 수 있도록 합니다.
  3. sns.violinplot(data=df_res, x='Baseline Model', y='Residual', ax=ax1)를 사용하여 두 번째 축 ax1에 대한 바이올린 플롯을 생성합니다. 이 플롯은 'Baseline Model'을 x축으로, 'Residual'을 y축으로 사용하며, df_res 데이터프레임을 데이터로 사용합니다. 바이올린 플롯은 각 모델의 잔차 분포를 나타내어 모델들 간의 잔차 차이를 비교할 수 있도록 합니다.
  4. ax0.xaxis.set_visible(False)를 사용하여 첫 번째 축의 x축 레이블을 숨깁니다.
  5. plt.tight_layout()를 사용하여 그림의 요소들이 겹치지 않도록 레이아웃을 조정합니다.
  6. plt.show()를 사용하여 그림을 출력합니다.

이 코드는 각 모델의 성능과 잔차를 시각적으로 비교하기 위해 스웜 플롯과 바이올린 플롯을 사용합니다. 스웜 플롯은 MAE 값의 분포를 점으로 표현하여 모델들 간의 성능 차이를 확인할 수 있습니다. 바이올린 플롯은 각 모델의 잔차 분포를 바이올린 형태로 나타내어 모델들 간의 잔차 차이를 시각적으로 비교할 수 있습니다.

위의 MAE 그래프는 damping factor가 0 또는 10인 조합 모델이 가장 우수한 성능을 보이며, 그 뒤를 아이템 평균 모델, 그리고 사용자 평균 모델이 따릅니다. 사용자와 아이템 모두에 의한 평균에서 기대값의 편차를 고려하는 것이 가장 좋은 성능을 낼 수 있는 이유는 각 기준선 예측에 대해 더 많은 데이터가 고려되기 때문입니다. 동일한 이유로 아이템 평균이 사용자 평균보다 우수한 성능을 보이는 것은 이 데이터셋에서 아이템의 수가 사용자의 수보다 많기 때문에, 아이템을 기준으로 평균을 구하면 기준선 예측 당 더 많은 데이터가 고려된다는 점입니다. MAE 그래프 아래의 잔차 플롯은 더 많은 데이터를 고려할수록 잔차의 밀도가 0에 가까워진다는 것을 보여줍니다.

협업 필터링 모델로 넘어가기 전에, 기준선으로 사용할 모델을 선택해야 합니다. damping factor가 0 또는 10인 조합 모델이 동일한 성능을 보였지만, regularization 효과가 더 강한 damping factor 10을 선택할 것입니다. 이는 과적합을 방지하는 데 더 효과적이기 때문입니다.

다음 포스트/노트북에서 이 기준선을 바탕으로 협업 필터링 모델을 살펴보겠습니다!