[추Node] Excon(E09) 영화 추천 시스템 만들기

데이터 여행자·2021년 2월 14일
1

스터디

목록 보기
3/15
  • 이 포스팅은 AIFFEL대전의 LMS와 추Node 스터디의 내용을 정리한 것이다.

추천시스템이란?

추천시스템은 '아이템이 많고 유저의 취향이 다양할 때 유저가 소비할만한 아이템을 예측하는 모델'이다. 빅데이터를 사용하여 추천시스템을 만들 수 있으며, 추천 방식에는 협업 필터링(collaborative filtering) 과 콘텐츠 기반 필터링(contents-based filtering) 이 있다. 여기서는 사용자간 유사성 및 아이템 간 유사성을 기반으로 한 협업 필터링을 사용한다.

  • 콘텐츠 기반 필터링: 아이템의 고유의 정보를 바탕으로 아이템 간 유사성 파악
  • 협업 필터링을 사용하지 못하는 경우
    1) 충분한 정보를 모으지 못한 사용자나 아이템에 대한 추론을 할 수 없는 상태인 콜드 스타트(Cold Start)의 경우
    2) 계산량이 너무 많은 경우
    3) 사용자의 관심이 저조한 항목의 정보가 부족한 경우
  • 참고: 콘텐츠 추천 알고리즘의 진화
  • 추천(Recommendation) 시스템 - 알고리즘 Trend 정리

사용할 데이터

활용할 데이터셋은 Movielens 데이터이다. Movielens 데이터에 대한 설명과 사용하는 방법은 다음과 같다. (노드에서 설명 가져옴)

  • 유저가 영화에 대해 평점을 매긴 데이터가 데이터 크기별로 있다. MovieLens 1M Dataset 사용을 권장한다.

  • 별점 데이터는 대표적인 explicit 데이터(좋아요, 별점 같이 선호도 표시)이지만 implicit 데이터(플레이 횟수 등)로 간주하고 테스트해볼 수 있다. (MF 모델 사용하기 위해)

  • 별점을 시청횟수로 해석해서 생각한다.

  • 유저가 3점 미만으로 준 데이터는 선호하지 않는다고 가정하고 제외한다.

  • 참고: Explicit vs Implicit Feedback Datasets

    • 명시적 데이터: 유저가 자신의 선호도를 직접(Explicit) 표현한 데이터. 평점, 좋아요/싫어요, 영화 리뷰, 구독, 차단 데이터 등. 구하기 힘듬.
    • 암묵적 데이터: 유저가 간접적(Implicit)으로 선호, 취향을 나타내는 데이터. 검색 기록, 방문 페이지, 구매 내역, 마우스 움직임 기록. 잡음이 많고 수치는 신뢰도를 의미함.

이론

Matrix Factorization(MF) - 행렬분해 모델

추천시스템의 다양한 모델 중 하나이다. 계산이 쉽고 성능이 준수하며 Scalability가 좋아 자주 사용된다. 행렬분해 모델은 평가행렬 R(m, n)을 두 개의 Feature Matrix P(m, k)와 Q(k, n)로 분해한다. 여기서 P는 사용자 특성, Q는 영화의 특성, R은 사용자의 영화 선호도를 보여준다. 모델의 목표는 모든 유저와 아이템에 대한 k차원 벡터를 잘 만드는 것 이고, 벡터를 잘 만드는 기준은 '유저의 벡터와 아이템의 벡터를 곱했을 때, 유저의 아이템 평가 수치와 비슷한지'이다.

  • 사용모델: Collaborative Filtering for Implicit Feedback Datasets에서 제안한 모델. 사용자와 아이템 벡터를 내적했을 때 1에 가까워져야 한다.

  • 유저의 재생횟수(또는 별점)을 맞추는 것이라면 Recommender System — Matrix Factorization에 나오는 모델을 참고할 것!

  • 참고: 갈아먹는 추천 알고리즘 [3] Matrix Factorization

    • P, Q는 모델을 통해 학습시킬 대상이다.
    • R' = P * Q 가 과 유사한 결과를 내면 잘 학습한 것이다.
    • Loss function, Optimization을 통해 모델을 일반화한다.
    • Alternating Least Squares를 사용하여 단기간에 P, Q를 찾는다.
    • Alternating Least Squares: 두 개의 feature 행렬 중 하나를 고정하고 다른 쪽을 학습하는 방식을 번갈아 수행한다. (두 feature matrix를 한 번에 훈련하는 것은 잘 수렴하지 않기 때문)

CSR(Compressed Sparse Row) Matrix

한 번도 선택하지 않은 아이템도 포함되어 있기 때문에 (유저 X 아이템) 평가행렬을 구하면 너무 큰 행렬이 된다. 이 경우 CSR(Compressed Sparse Row) Matrix를 사용하여 행렬의 크기를 줄일 수 있다.

  • CSR Matrix: 희소행렬(Sparse matrix)를 행 순서대로 데이터를 압축시킨 데이터 구조(0을 제외한 데이터와 좌표 정보로 구성)
  • 참고: CSR matrix

순서

  1. 데이터 준비와 전처리
  2. 데이터 분석
  3. 모델 검증을 위한 사용자 초기 정보 세팅
  4. 모델에 활용하기 위한 전처리
  5. CSR matrix 만들기
  6. 모델 설계 및 학습
  7. 사용자가 좋아하는 영화와 비슷한 영화 추천하기
  8. 사용자가 좋아할만한 영화 추천하기

데이터 다운 받기

아래를 누르면 자동으로 데이터가 다운로드된다.
http://files.grouplens.org/datasets/movielens/ml-1m.zip

코드 살펴보기

1. 데이터 준비와 전처리

  • 필요 없는 데이터 제거 - 이상치 제거할 때도 사용하기도 한다. (data.drop()을 쓰기도 함)
# 평점 3점 이상만 남기기 
data = data[data['rating'] >= 3]

2. 데이터 탐색

  • 특정 컬럼을 순서대로 검색하기(예: 가장 인기있는 영화 30개 검색하기)
movie_count = df.groupby('movie_id')['user_id'].count()
movie_count.sort_values(ascending=False).head(30)
  • 필요없는 컬럼 제거(라고 쓰지만 '필요한 컬럼만 남기기'라고 읽는다.)
cols = ['col1', 'col2', 'col3']
data = data[cols]

3. 모델 검증을 위한 사용자 초기 정보 세팅

사용자 취향의 영화 5개를 입력받아 기존 데이터에 넣는다.

favorite_movies = ['title1 (year1)', 'title2 (year2)', ...'title5 (year5)']  # 선호 영화 리스트 생성 (기존 데이터의 형태대로 넣어야 함)

movielist = pd.DataFrame({'id': ['name']*5, 'title': favorite, 'count': [5]*5})   # 위에서 만든 데이터프레임의 형태로 컬럼 5개짜리 데이터프레임을 만든다. 

if not data.isin({'id':['name']}['id'].any():
    data = data.append(movielist)    # 중복되지 않으면 새로 만든 데이터프레임을 기존의 데이터프레임에 추가함

4. 모델에 활용하기 위한 전처리

  • 인덱싱: 새로 추가한 데이터는 기존 데이터와 인텍스가 다르므로 인덱싱을 해준다.
# 고유한 유저 찾기
user_unique = data['id'].unique()
# 새롭게 indexing
user_to_idx = {v:k for k,v in enumerate(user_unique)}
  • 인덱싱을 통해 데이터 컬럼 내 값을 바꾸는 코드
user_data = data['id'].map(user_to_idx.get).dropnna()  # 새롭게 인덱싱한 series 구함 

if len(user_data) == len(data):   #  모든 행이 정상적으로 인덱싱되었으면
    data['id'] = user_data        # data['id']를 위에서 만든 series로 교체
else: 
    print('indexing failed.')
  • pandas의 series: 1차원 배열과 같은 자료구조. 인덱스(설정하지 않으면 0부터 시작)와 값으로 구성됨
  • df.map(): ()안의 코드를 실행시킨 결과를 보여주는 코드
  • dictionary.get: 딕셔너리의 value 구하는 코드
  • df.dropna(): 결측치 제거

5. CSR matrix 만들기

csr_matrix((data, (row_ind, col_ind)), [shape=(M, N)])
where data, row_ind and col_ind satisfy the relationship a[row_ind[k], col_ind[k]] = data[k]

위의 코드를 사용하여 직접 만들어보자!

6. 모델 설계 및 학습

  • Matrix Factorization 모델을 implicit 패키지를 사용하여 학습한다.
from implicit.als import AlternatingLeastSquares
# implicit 라이브러리에서 권장하고 있다. 학습 내용과는 무관
os.environ['OPENBLAS_NUM_THREADS']='1'
os.environ['KMP_DUPLICATE_LIB_OK']='True'
os.environ['MKL_NUM_THREADS']='1'

als_model = AlternatingLeastSquares(factors=200, regularization=0.3, use_gpu=False, iterations=30
                                    , dtype=np.float32)
# als 모델은 input으로 (item X user) 꼴의 matrix를 받기 때문에 Transpose한다.
csr_data_t = csr_data.T

# 모델 훈련
als_model.fit(csr_data_transpose)
  • AlternatingLeastSquares 클래스의 __init__ 파라미터

    • factors(int, optional) : 유저와 아이템의 벡터의 차원
    • regularization(float, optional) : 과적합을 방지하기 위한 정규화 값
    • use_gpu(bool, optional) : GPU를 사용 여부
    • iterations(int, optional) : epochs
    • regularization과 iteration이 증가하면 학습데이터를 잘 학습하지만 과적합의 우려가 있다.
    • factors를 늘리거나 iterations를 늘려보아도 좋다.
  • 여기서 MF 모델이 벡터를 잘 만들었는지 파악할 수 있다.

  1. MF 모델이 벡터를 만드는 코드
user, movie  = user_to_idx['user'], title_to_idx['title (year)']
user_vector, movie_vector = als_model.user_factors[user], als_model.item_factors[movie]
  1. P*Q를 내적 (선호하는 영화의 결과와 다른 영화의 결과를 비교해 보자.)
np.dot(user_vector, movie_vector)
  • 다른 영화의 결과가 1에 가깝게 나와야 모델이 잘 예측한 것이다. 그러나 결과에 대한 해석은 사용자만 알 수 있다.

7. 사용자가 좋아하는 영화와 비슷한 영화 추천하기

  • implicit 패키지에 있는 als_model.similar_items()를 사용한다.
def similar_movies(movie_name: str):
    title_id = title_to_idx[movie_name]
    similar_movie = als_model.similar_items(title_id) 
    idx_to_title = {v:k for k, v in title_to_idx.items()}  # title_to_idx 뒤집기
    similar_movie = [idx_to_title[i[0]] for i in similar_movie]  # index에서 영화이름 얻는 dict 생성
    return similar_movie
  • 결과: (영화 id, 유사도) Tuple 로 반환

8. 사용자가 좋아할만한 영화 추천하기

  • 사용자가 좋아할만한 영화 추천
    als_model.recommend(user, csr_data, N=20, filter_already_liked_items=True) 를 사용한다. 7번의 코드와 유사하다.

2개의 댓글

comment-user-thumbnail
2021년 2월 18일

글 정리가 잘 되어있네요! 잘 읽고 갑니다🎉

1개의 답글