앎을 경계하기

추천시스템

집단별 추천해보기 (협업필터링 아님!)

양갱맨 2023. 1. 15. 22:55

참고도서 : python을 이용한 개인화 추천 시스템 - 임일

집단별 추천 시스템

취향이 아니라 직업, 성별 등 단순 통계적으로 구분할 수 있는 집단으로 구분하여 추천 시스템을 만들어보자.

이런 순서로 구성해야 한다.

import pandas as pd
import numpy as np

u_cols = ['user_id','age','sex','occupation','zip_code']
users = pd.read_csv('./u.user', sep='|', names=u_cols, encoding='latin-1')
i_cols = ['movie_id','title','release date',' video release date','IMDB URL','unknown','action','adventure','animation','children\'s','comedy','crime','documentary','drama','fantasy','film-noir','horror','musical','mystery','romance','sci-fi','thriller','war','western']

movies = pd.read_csv('./u.item', sep='|', names=i_cols, encoding='latin-1')
r_cols = ['user_id','movie_id','rating','timestamp']
ratings = pd.read_csv('./u.data',sep='\t',names=r_cols,encoding='latin-1')

ratings = ratings.drop('timestamp',axis=1) # 타임스탬프 컬럼 제거
movies = movies[['movie_id','title']] #영화 아이디랑 제목만 사용

앞 포스팅과 같은 데이터를 사용하기 때문에 로드하는 방식도 똑같다.

#train/test set 분리
from sklearn.model_selection import train_test_split
x = ratings.copy()
y = ratings['user_id']
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.25, stratify=y)

학습 데이터와 평가 데이터를 나누기위해 sklearn을 사용한다.

# 정확도함수
def RMSE(y_true, y_pred):
    return np.sqrt(np.mean((np.array(y_true)-np.array(y_pred))**2))

# 모델 예측 후 에러값 측정
def score(model):
    id_pairs = zip(x_test['user_id'], x_test['movie_id'])
    y_pred = np.array([model(user, movie) for (user, movie) in id_pairs])
    y_true = np.array(x_test['rating'])
    return RMSE(y_true, y_pred)

추천 모델의 추천 성능을 확인할 수 있는 함수를 작성한다.

# 학습 데이터(x_train)의 full matrix 만들기
rating_matrix = x_train.pivot(index='user_id',columns='movie_id',values='rating')

rating matrix는 다음과 같이 생겼는데, 각 셀은 user별로 전체 영화에 대해서 준 평점이다.

먼저, 앞서 작성했던 베스트셀러 방식을 확인해보자.

def best_seller(user_id, movie_id):
    try : 
        rating = train_mean[movie_id]
    except:
        rating = 3.0
    return rating

train_mean = x_train.groupby(['movie_id'])['rating'].mean() # 영화별 평균 평점
score(best_seller)

베스트셀러방식은 1.02가 나왔다.

앞 포스팅보다 RMSE 값이 커졌는데(커지면 성능이 안좋다는 것임), 그 이유는 Train, Test set을 분리해서 test set에 대해서만 평가를 했기 때문이다.

전에 봤던 데이터(Train set)라면 잘 판단할 수 있지만 처음보는 데이터(Test set)를 가지고 예측을 하자니, 잘 못하는 것이다.

그럼 특정 기준을 가지고 사용자 집단을 분리하여 추천하는 모델을 만들어보면 더 좋은 성능을 가지지 않을까?

먼저, 간단히 "성별" 로 사용자를 나눠서 예측해보는 모델을 만들었다.

# 성별기준 추천

merged_ratings = pd.merge(x_train, users)
users = users.set_index('user_id')

# 영화별 각 성별의 평균평점 구하기
g_mean = merged_ratings[['movie_id','sex','rating']].groupby(['movie_id','sex'])['rating'].mean()

def cf_gender(user_id, movie_id):
    if movie_id in rating_matrix: 
        gender = users.loc[user_id]['sex']
        if gender in g_mean[movie_id]:
            gender_rating = g_mean[movie_id][gender]
        else:
            gender_rating = 3.0
    else:
        gender_rating = 3.0

    return gender_rating

score(cf_gender)

오히려 나빠졌다.

이 말은 성별이 영화 추천 정확도 개선에 영향이 없다는 것이다.

그럼 다른 변수를 기준으로 집단을 나눠보자.

# 직업기준 추천
# 성별 추천 코드를 수정해서 직업에 따라 집단을 나눠 예측값을 구하는 함수와 정확도 계산하는 코드 작성해보기
o_mean = merged_ratings[['movie_id','occupation','rating']].groupby(['movie_id','occupation'])['rating'].mean()

def cf_job(user_id, movie_id):
    if movie_id in rating_matrix:
        job = users.loc[user_id]['occupation']
        if job in o_mean[movie_id]:
            job_rating = o_mean[movie_id][job]
        else:
            job_rating = 3.0
    else:
        job_rating = 3.0

    return job_rating

score(cf_job)

더 안 좋아졌다.

그럼 여러 변수를 기준으로 잡아 좀 더 세부적으로 그룹화하여 추천해준다면 정확도가 개선될 수 있을까?

# 성별, 직업 기준 추천

# 성별과 직업을 동시에 고려한 집단을 나눠서 예측값을 구하는 함수를 만들고, 정확도 계산 코드 작성하기
go_mean = merged_ratings[['movie_id','occupation','sex','rating']].groupby(['movie_id','occupation','sex'])['rating'].mean()

def cf_job_and_gender(user_id, movie_id):
    if movie_id in rating_matrix:
        job = users.loc[user_id]['occupation']
        gender = users.loc[user_id]['sex']
        if (job, gender) in go_mean[movie_id]: # 순서중요
            go_rating = go_mean[movie_id][job,gender]
        else:
            go_rating = 3.0
    else :
        go_rating = 3.0

    return go_rating

score(cf_job_and_gender)

웬걸;

베스트셀러, 성별만 그룹화, 직업만 그룹화한 것 보다도 더 안좋은 결과가 나왔다.

여기서 끝낼 수 없다. 책에서 더 나아가 다른 변수로 추천을 해보자.

users 데이터프레임에는 'age','sex','occupation','zip\_code' 컬럼이 있다.

단순히 생각했을때, 아무래도 영화 선호에는 성별과 나이가 영향을 미치지 않을까 싶다.

ag_mean = merged_ratings[['movie_id','age','sex','rating']].groupby(['movie_id','age','sex'])['rating'].mean()

def cf_age_and_gender(user_id,movie_id):
    if movie_id in rating_matrix:
        age = users.loc[user_id]['age']
        gender = users.loc[user_id]['sex']
        if (age, gender) in ag_mean[movie_id]:
            ag_rating = ag_mean[movie_id][age,gender]
        else:
            ag_rating = 3.0
    else:
        ag_rating = 3.0
    return ag_rating

score(cf_age_and_gender)

이것도 아니네...

이렇게 단순히 취향이 아닌 인구통계적 변수(직업, 성별, 나이 등)를 가지고 집단을 나눠서 추천을 하는 것은 성능이 좋지 못하다는 것을 알았다.