nan + nan = 2nan

[개인프로젝트] 영화 추천 알고리즘 본문

Statistics/Machine Learning

[개인프로젝트] 영화 추천 알고리즘

2nan 2022. 3. 17. 19:47
728x90

인턴 합격 후 시간이 남는 중간에 그냥 혼자 여러가지 사이드 프로젝트를 해보고 싶었다.

아직은 실력이 낮기 때문에, youtube에서 이것 저것 찾아 보다가 영화 추천 알고리즘을 간단히 구현해보는 게 있었는데 재밌을 것 같아서 해봤다.

해당 코드는 Youtube의 '빵형의 개발도상국' 채널에 있는 영화 추천 만들기 영상에서 참조했다.

https://www.youtube.com/watch?v=mLwMe4KUZz8

 

import pandas as pd
import numpy as np
import json

meta = pd.read_csv('movies_metadata.csv', encoding='utf8', low_memory=False)
meta.head()

 

메타 데이터셋 DataFrame

해당 데이터셋은 캐글에서 영화 관련 메타 데이터 셋을 이용했다.

https://www.kaggle.com/rounakbanik/the-movies-dataset

 

The Movies Dataset

Metadata on over 45,000 movies. 26 million ratings from over 270,000 users.

www.kaggle.com

 

# 추천 데이터에 필요한 컬럼만 추림
meta = meta[['id', 'original_title', 'original_language', 'genres']]

# id 컬럼을 알아 보기 좋게 변경
meta = meta.rename(columns={'id' : 'movieId'})

# 영어로 된 데이터가 가장 많기 때문에, 영어로 된 데이터만 분류
meta = meta[meta['original_language'] == 'en']

meta.head()

필요한 컬럼만 추출 후 확인

 

# 평점 데이터를 불러온다
ratings = pd.read_csv('ratings_small.csv', encoding='utf8')

# 필요한 컬럼만 추림
ratings = ratings[['userId', 'movieId', 'rating']]
ratings.head()

 

rating data

 

# 평균, 편차, 최대, 4분위값 등 확인
ratings.describe()

Describe()

 

# null 값 및 데이터 타입 확인
meta.info()

null 값 없고, 모두 문자형 자료

 

# 평점 관련 info. 지금 보니 non-null count가 왜 안 나왔는지 의문.
ratings.info()

유저 아이디는 정수, 나머지는 실수

 

# movieId를 object -> int 형태로 가공
meta.movieId = pd.to_numeric(meta.movieId, errors='coerce')
ratings.movieId = pd.to_numeric(meta.movieId, errors='coerce')

to_numeric은 데이터를 숫자 형식으로 바꿔주는 역할을 한다.

해당 코드에서는 object와 float 형태를 int 형태로 가공하기 위함이지 않나 싶다.

또한, 뒤의 errors 인자는 3가지 옵션이 존재한다.

1) ignore = 숫자로 변경할 수 없는 데이터라면, 원본 데이터를 그대로 반환

2) coerce = 숫자로 변경할 수 없는 데이터라면, 기존 데이터 삭제 후 Nan 값을 반환

3) raise = 숫자로 변경할 수 없는 데이터라면, 에러가 뜨면서 코드 중단

 

# genres가 json 형태로 string으로 저장
# 이를 배열 형태로, 장르만 뽑아내서 리스트에 담아주고, 
# apply 함수를 활용해서 각 행마다 해당 함수를 적용시키게끔

def parse_genres(genres_str):
    genres = json.loads(genres_str.replace('\'', '"'))

    genres_list = []
    for g in genres:
        genres_list.append(g['name'])

    return genres_list

meta['genres'] = meta['genres'].apply(parse_genres)

meta.head()

 

parse_genres 적용 후

replace로 '\''를 '"' 치환 후 append를 하는 것 같은데, 해당 문자열이 어디있는지는 원 데이터를 봐도 잘 모르겠다..

그 다음 apply 함수를 통해서 행마다 함수를 적용시킨 후의 모습이다.

 

# 두 df를 movieId를 기준으로 inner 방식을 활용해서 merge. 

data = pd.merge(ratings, meta, on='movieId', how='inner')
data.head(10)

 

meta, ratings의 교집합(inner)되는 부분에 대해 merge

 

# 피벗 테이블 생성

matrix = data.pivot_table(index='userId', columns='original_title', values='rating')
matrix.tail(20)

Pivot table 생성, user id를 index로 orginal title 열의 값은 평점 기준

 

피어슨 상관계수

통계학에서, 피어슨 상관 계수란 두 변수 X 와 Y 간의 선형 상관 관계를 계량화한 수치

 

 

# 피어슨 상관관계

GENRE_WEIGHT = 0.1

# s1 의 각 행에서 s1의 평균 값을 빼준다.
# (s1 * s2) 합 / 제곱근(s1의 제곱의 합 * s2 제곱의 합)
# 즉, 두 변수의 공분산을 각각의 표준편차의 곱으로 나눈 값
# 피어슨 상관계수 = 공분산 / 표준편차 * 표준편차

def pearsonR(s1, s2):
    s1_c = s1 - s1.mean()
    s2_c = s2 - s2.mean()
    return np.sum(s1_c * s2_c) / np.sqrt(np.sum(s1_c ** 2) * np.sum(s2_c ** 2))

 

# 추천 함수 작성
    # 비교 대상 영화 타이틀/ 피벗 테이블 / 몇 개를 추천 받을 것인가 / 비슷한 장르에 가중치를 둘 것인가?  
def recommend(input_movie, matrix, n, similar_genre=True):
    
    # 가져온 타이틀의 장르를 추출
    input_genres = meta[meta['original_title'] == input_movie]['genres'].iloc(0)[0]
    
    # 결과를 담을 리스트 생성
    result = []
    
 
    for title in matrix.columns:
        # 가져온 타이틀과 input 영화가 같다면 건너뛰기
        if title == input_movie:
            continue

        # 피어슨 상관계수 계산
        cor = pearsonR(matrix[input_movie], matrix[title])

        # 장르를 비교
        if similar_genre and len(input_genres) > 0:
            temp_genres = meta[meta['original_title'] == title]['genres'].iloc(0)[0]
            # in1d -> 배열을 비교하여 같은 요소가 있으면 True를 반환
            same_count = np.sum(np.in1d(input_genres, temp_genres))
            # 같은 장르가 많을수록 더 높은 가중치 부여
            cor += (GENRE_WEIGHT * same_count)

        # 피어슨 상관계수의 값이 nan 값이라면 건너뛰기
        if np.isnan(cor):
            continue
        else:
            # 상관계수 값이 있다면 결과 리스트에 제목 / 상관계수 점수 / 해당 title의 장르 append
            result.append((title, '{:.2f}'.format(cor), temp_genres))
    # 위 과정이 끝나면 rating이 높은 순서대로 (내림차순) sorting
    result.sort(key=lambda r: r[1], reverse=True)
    # n개 만큼 반환
    return result[:n]

 

# '다크 나이트'와 비슷한 장르의 영화를 10개를 추천, 장르는 비슷한 것일수록 좋다(가중치 부여)
recommend_result = recommend('The Dark Knight', matrix, 10, similar_genre=True)

pd.DataFrame(recommend_result, columns= ['Title', 'Correlation',' Genre'])

 

 

가벼운 추천 시스템을 경험해보았다. 머신러닝이나 딥러닝 기법을 직접 사용하지 않고 피어슨 상관계수 로직을 통해 간단히 알고리즘을 구현한 것 같다.

해당 로직이 사용자 기반 협업 필터링과 관련된 로직이라고 하는데, 다음 글은 메모리 기반 협업 필터링과 모델 기반 협업 필터링의 차이를 파악하면서 공부해야겠다. 추천 시스템, 챗봇 등 많이 쓰이고 있는 기술들의 기본적인 프로세스와 로직은 알아야 나의 자산이 되지 않을까 싶다.

Comments