[django에 AI 더하기] sklearn을 이용한 콘텐츠 기반 추천

2021. 12. 1. 21:41
반응형
캐글(Kaggle) 같은 경진대회를 하다 보면 제작한 AI 모델을 서비스에 적용하고 싶을 때가 있습니다.
서비스 적용을 시도하며 Kaggle Dataset으로 모델을 만들고 django를 통해 서빙하였습니다.

본 프로젝트는 django를 이용한 콘텐츠 기반의 영화 추천 서비스입니다.
하나의 예시로 삼아, 각자 개발 환경에 맞게 ML모델을 적용하시면 됩니다.

본 글에 오류가 있다면 편하게 알려주세요:) 모든 피드백과 질문을 환영합니다.

🔹 콘텐츠 기반 추천이란?

콘텐츠 기반 추천은 사용자가 과거에 좋다고 생각한 콘텐츠와 비슷한 콘텐츠를 추천하겠다는 아이디어입니다. 단순하게 말하면 아래 그림과 같이 사용자가 특정 아이템을 선호하는 경우 그 아이템과 비슷한 특성을 가진 아이템을 추천하는 방법입니다. 그림에서 User A가 Love, Rommantic 영화를 좋아합니다. 따라서 이를 기반으로 같은 장르인 Movie C를 추천받는 것입니다.

Kaggle (Getting Started with a Movie Recommendation System)

 

🔹 사이킷런(Scikit-learn) 간단 설명

사이킷런은 파이썬으로 사용 가능한 머신러닝 라이브러리입니다. 다양한 분류, 회귀, 클러스터링 등 알고리즘을 간단하게 사용할 수 있게 제공합니다. 또한, 과적합 방지, 랜덤 포레스트, k-means 등 머신러닝을 처리하면서 자주 사용되는 기법들을 제공합니다. Kaggle에서 다른 머신러닝 사용 없이 사이킷런만으로 최고 순위에 오를 수 있을 정도로 쉽고 흔하게 사용되는 머신러닝 툴입니다.

 


🔹 개발 환경

개발한 프로젝트 규모는 더 크지만, 본 글에서는 django 범위에서 모든 설명이 가능합니다. 

 

사용 버전

  • django 3.2.7
  • Python 3.9.6

패키지

🔹 코드 짜기

ML 모델은 views가 비대해지는 것을 막고자 모듈로 분리하여 사용하였습니다.

ML에 사용한 데이터와 알고리즘은 Kaggle에서 받았습니다. 

Kaggle에는 다양한 알고리즘이 있는데, 제가 진행한 프로젝트는 유저 정보가 적어 콘텐츠 기반 알고리즘을 채택하였습니다. 각자 상황에 맞는 알고리즘을 이용하여 구현하시면 됩니다.

ML 모듈은 django 런서버가 진행되며 자동으로 한번 실행됩니다. 따라서 서버를 시작할 때 시간이 오래 소요될 수 있습니다. 대신 서버가 돌아가며 학습된 모델을 사용하기 때문에 모델 학습에 걸리는 시간은 소요되지 않습니다. 만약 학습시간이 긴 모델을 사용할 경우에 미리 학습 후 pickle을 통해 불러올 수 있을 것입니다.

파일 구조

movies.py (App)

  • data/tmdb_5000_movies.csv
  • recommend_ml.py
  • views.py

 

ML 모듈

### recommend_ml.py
import pandas as pd 
import warnings; warnings.filterwarnings('ignore')
from ast import literal_eval 
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.metrics.pairwise import cosine_similarity 

# 데이터 전처리
movies = pd.read_csv('movies/data/tmdb_5000_movies.csv', encoding='utf-8') 
movies_df = movies[['id', 'title', 'genres', 'vote_average', 'vote_count', 'popularity', 'keywords', 'overview']] 
movies_df['genres'] = movies_df['genres'].apply(literal_eval) 
movies_df['genres'] = movies_df['genres'].apply(lambda x:[y['name'] for y in x]) 
movies_df['genres_literal'] = movies_df['genres'].apply(lambda x:(' ').join(x)) 

# 장르 빈도수 벡터화
count_vect = CountVectorizer(min_df=0, ngram_range=(1,2)) 
genre_mat = count_vect.fit_transform(movies_df['genres_literal'])

# 코사인 유사도 계산 및 정렬
genre_sim = cosine_similarity(genre_mat, genre_mat) 
genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]
percentile = 0.6
m = movies['vote_count'].quantile(percentile)
C = movies['vote_average'].mean()

# 가중 평점 도입 (평균 점수와 표본수 반영)
def weighted_vote_average(record): 
    v = record['vote_count'] 
    R = record['vote_average']
    return ((v/(v+m))*R) + ((m/(m+v))*C)

movies_df['weighted_vote'] = movies_df.apply(weighted_vote_average, axis=1)
movies_df[['title', 'vote_average', 'weighted_vote', 'vote_count']].sort_values('weighted_vote', ascending=False)[:10]

# 추천 함수
def find_sim_movie(id, top_n=5):
    df = movies_df
    sorted_ind = genre_sim_sorted_ind
    title_movie = df[df['id'] == id]
    title_index = title_movie.index.values
    similar_indexes = sorted_ind[title_index, :(top_n*2)] 
    similar_indexes = similar_indexes.reshape(-1)
    similar_indexes = similar_indexes[similar_indexes != title_index]
    return df.iloc[similar_indexes].sort_values('weighted_vote', ascending=False)[:top_n]

 

View

프론트에서 필요한 형태로 다듬어 응답을 보내면 됩니다. 아래는 하나의 응답 예시로만 참고해주세요.

### views.py
from rest_framework.response import Response
from .models import Movie, Genre
from .recommend_ml import find_sim_movie
import requests
import random
import numpy as np 

BASE_URL = 'https://api.themoviedb.org/3'

# 기반 영화를 받아서 추천영화를 반환 함수
def movie_recommendation(movie_id):
    # ML 모델에 넣기위한 id 추출
    base_movie = Movie.objects.get(id=movie_id)
    model_input_id = base_movie.tmdb_id

    # ML 모델을 통해 추천 결과 획득
    raw_movies = find_sim_movie(model_input_id) 
    
    # 결과 후처리
    machine_learning_recommend_movies_list = list(np.array(raw_movies[['id']]['id'].tolist()))
    recommend_movies = []
    for recommend_movie_tmdb_id in machine_learning_recommend_movies_list:
        detail_url = BASE_URL + f'/movie/{recommend_movie_tmdb_id}'
        detail_query = {
          'api_key': ENV_TMDB_KEY,
          'language': 'ko-KR',
        }
        recommendation_response = requests.get(detail_url, params=detail_query)
        recommendation_dict = recommendation_response.json()
        recommend_movies.append(recommendation_dict)
    return Response(recommend_movies)

요청에 대해 아래 응답을 보냅니다.

추천 결과 응답 예시

 

 

🔹 성능 평가

서비스 관점

추천되는 영화는 괜찮아 보입니다. 하지만 조금 뻔한 느낌이 있습니다. 예를 들면 반지의 제왕1을 기반으로 추천을 요청하면 반지의 제왕2를 추천해주는 느낌입니다. 유저 데이터가 쌓이면 모델을 수정할 필요가 있습니다.

속도 관점

로컬 환경에서 5000개의 데이터로 수행한 학습은 3~4초 정도로 수행 시간이 길지 않았습니다. 따라서 모듈 방식으로 적용하여 사용하는데 무리가 없다고 생각합니다.

url을 통해서 서버에 추천 영화를 요청할 때 200 ms 정도가 소요되었는데, 다른 영화 정보 응답과 큰 차이가 없었습니다. 학습된 모델 수행 속도는 전체 요청-응답 과정에서 영향이 거의 없다고 생각됩니다.

결론적으로 현재 데이터 규모의 크기에서 서비스로 사용하기 적합합니다.

 

🔹 후기

경진대회 용으로만 사용하던 ML 알고리즘을 웹 서비스에 적용하여 가치를 만든 부분이 좋았습니다. 항상 ML을 학습하며 서비스화하고 싶다는 생각이 많아서 그랬던 것 같습니다. 추후에 진행하는 프로젝트가 더 커지면 유저 데이터가 많이 생길 테니 DB를 기반으로 학습한 ML 모델로 추천 시스템을 교환해야 할 것 같습니다. 지금 추천도 좋긴 한데 너무 뻔한 느낌의 영화를 추천해주는감이 있습니다.

 

 

Ref.

반응형

BELATED ARTICLES

more