오늘의 인기 글
최근 글
최근 댓글
Today
Total
11-09 00:03
관리 메뉴

우노

[추천시스템] 유사도(Similarity) 튜토리얼 본문

Data/Recommender System

[추천시스템] 유사도(Similarity) 튜토리얼

운호(Noah) 2020. 10. 22. 16:22

영화 평점 데이터셋

데이터셋 정보

  • ratings.csv : 평점데이터
  • ratings_small.csv : 평점데이터 (작은버전)
  • keywords.csv : 영화 키워드 데이터
  • movies_metadata.csv : 영화 정보 데이터
  • credits.csv : 영화 제작 정보
  • links.csv : imdb와 tmdb에서의 영화 id 정보
  • links_small.csv : imdb와 tmdb에서의 영화 id 정보 (작은버전)

목표

  • The Movies Data를 이용하여 비슷한 영화 찾기

순서

  • Pandas를 이용하여 데이터 불러오기
  • Pandas를 이용하여 데이터 정제하기
  • Python의 집합을 활용하여 Jaccard 유사도구하기
  • Numpy를 이용하여 Pearson 유사도구하기
  • 모든 영화에 대해서 추천 점수 계산하고, 가장 추천 점수가 높은 영화 고르기

Pandas

  • Python 용 데이터 분석 라이브러리

  • MS Office의 Excel과 같이, 행과 열로 구성된 데이터 객체를 다룸

  • Pandas 사용하기

    • Pandas, Numpy 패키지 import

        import numpy as np
        import pandas as pd

Pandas로 Metadata(영화정보데이터) 읽고 정제하기

  • metadata 읽기

      meta = pd.read_csv('/kaggle/input/movies_metadata.csv')
      meta
  • metadata에서 필요한 열(column)만 추려내기

      meta = meta[ ['id', 'original_title', 'original_language', 'genres'] ]
      meta
  • 열(column) 이름 변경하기

    • row를 바꾸기 위해선 rows

    • column을 바꾸기 위해선 columns

        meta = meta.rename(columns={'id':'movieId','original_title': 'title',
        'original_language': 'language'})
        meta
  • language가 'en'인 데이터만 솎아내기

    • loc은 location의 약자

    • loc 인자의 앞은 행이름 뒤는 열이름, iloc 인자의 앞은 행번호 뒤는 열번호를 넣는다.

        meta = meta.loc[meta['language'] == 'en',:]
        meta
  • movieId의 타입을 숫자로 변경하기

      meta.movieId = pd.to_numeric(meta.movieId)
      meta.movieId
  • genre 정제하기

    • json string을 입력 받아 장르 이름들을 python set으로 변환해 출력하는 함수 구현

        def str_to_set(x):
            genre_set = set()
            # eval : text 파일을 python 데이터 타입으로 변환해준다.
            for item in eval(x):
                genre_set.add(item['name'])
            return genre_set
    • meta.genres 열의 모든 값에 str_to_set 함수 적용

        meta.genres = meta.genres.apply(str_to_set)
        meta

Pandas로 Keywords(영화키워드데이터) 읽고 정제하기

  • keywords 데이터 읽기

      keywords = pd.read_csv('/kaggle/input/keywords.csv')
      keywords
  • keywords.keywords 열의 모든 값에 str_to_set 함수 적용

      keywords.keywords = keywords.keywords.apply(str_to_set)
      keywords
  • id 열을 movieId로 이름 변경하고 데이터 타입을 숫자로 변환하기

      keywords = keywords.rename(columns={'id': 'movieId'})
      keywords.movieId = pd.to_numeric(keywords.movieId)
      keywords

Keywords와 Metadata 합치기

  • 같은 movieId를 갖는 데이터 합치기

      meta = pd.merge(meta, keywords, on='movieId', how='inner')
      meta

Jaccard Similarity

  • 영화 찾아보고 합쳐서 출력해보기

      # title : 'The Dark Knight'인 여러행들 중 첫행
      dk = meta.loc[meta.title == 'The Dark Knight'].iloc[0]
      # title : 'The Dark Knight Rises'인 여러행들 중 첫행
      dkr = meta.loc[meta.title == 'The Dark Knight Rises'].iloc[0]
      # axis=0 하나의 열에 이어 붙이기, axis=1 하나의 행에 이어 붙이기
      # concat 후 전치행렬 적용
      pd.concat([dk, dkr], axis=1).T
  • Jaccard 유사도 함수 구현 및 실행

      def jaccard_similarity(s1, s2):
          # 분모가 0이면 계산할 수 없기 때문에 s1s2 합집합의 크기가 0인 경우 return 0
          if len(s1|s2) == 0:
           return 0
          # 아닌 경우 교집합/합집합 반환
          return len(s1&s2)/len(s1|s2)
    
      # dk의 장르와 키워드 합치기
      # dkr의 장르와 키워드 합치기
      # 두 가지를 자카드 유사도 계산
      jaccard_similarity(dk.genres|dk.keywords, dkr.genres|dkr.keywords)

Pandas로 Rating(평점데이터) 읽고 정제하기

  • Rating 데이터 읽기

      ratings = pd.read_csv('/kaggle/input/ratings_small.csv')
      ratings
  • Rating의 movieId를 숫자로 변환

      ratings.movieId = pd.to_numeric(ratings.movieId)
  • metadata에서 같은 movieId를 갖는 데이터와 합쳐 ratings 데이터에 title 달아주기

      ratings = pd.merge(ratings, meta[['movieId', 'title']], on='movieId', how='inner')
      ratings
  • pivot table 만들기 (데이터를 요약하는 통계표)

    • 행은 userID, 열은 title , 요소값은 rating

      matrix = ratings.pivot_table(index= 'userId', columns='title', values='rating')
      matrix

Pearson Correlation Coefficient

  • Pearson 유사도 함수 구현 (Centered Cosine Similarity)

      def pearson_similarity(u1, u2):
    
          # Centered 방식 : 각 벡터에서 NaN이 아닌 값들을 평균낸 값을 빼준다.
          u1_c = u1 - u1.mean()
          u2_c = u2 - u2.mean()
    
          # 여기서부턴 Cosine 유사도 구하기
          denom = np.sqrt(np.sum(u1_c ** 2) * np.sum(u2_c ** 2))
    
          if denom != 0:
              return np.sum(u1_c * u2_c)/denom
          else:
              return 0
  • The Dark Knight와 Prom Night의 Pearson 유사도 구해보기

      # dk 평점 벡터
      dk_rating = matrix['The Dark Knight']
      # pk 평점 벡터
      pk_rating = matrix['Prom Night']
    
      # dk와 pk의 pearson similarity
      pearson_similarity(dk_rating, pk_rating)

비슷한 영화 추천 기능 구현

# input_title : 찾으려고 하는 영화의 제목
# matrix : 이전에 생성한 pivot table
# n : 가장 비슷한 영화를 몇 개 출력할 것인지
# a : 유사도 반영 비율
def find_similar_movies (input_title , matrix, n, alpha):

    # input_meta : 입력된 영화의 metadata (id, title, language, genres, keywords)
    # input_set : 입력된 영화의 genres와 keyword의 합집합
    input_meta = meta.loc[meta[ 'title'] == input_title].iloc[0]
    input_set = input_meta.genres | input_meta.keywords

    # result : 모든 영화마다 유사도를 계산하여 저장
  # 입력된 영화와 동일한 영화는 유사도 계산을 하지 않고 Pass
    result = []

    # pivot table에서 영화이름을 하나씩 비교
    for this_title in matrix.columns:
        if this_title == input_title:
            continue

        # this_meta : 유사도를 계산하려는 영화의 metadata (id, title, language, genres, keywords)
        # this_set : 이 영화의 genres와 keywords의 합집합
        this_meta = meta.loc[ meta[ 'title'] == this_title].iloc[ 0]
        this_set = this_meta.genres | this_meta.keywords

        # pearson : 입력 영화와 이번 영화의 pearson 유사도 결과 (영화에 대한 평점 벡터로 계산)
        # jaccard : 입력 영화와 이번 영화의 jaccard 유사도 결과 (키워드 집합으로 계산)
        pearson = pearson_similarity(matrix[this_title], matrix[input_title])
        jaccard = jaccard_similarity(this_set, input_set)

        # score : pearson 점수와 jaccard 점수의 가중치 합
        # result에 계산 결과 추가
        score = alpha * pearson + ( 1-alpha) * jaccard
        result.append( (this_title, pearson, jaccard, score) )

    # 모든 영화에 대해 유사도 계산이 끝나면 result를 정렬
    # 상위 n개를 return
    result.sort(key= lambda r: r[3], reverse= True)

    return result[:n]
  • The Dark Knight와 비슷한 영화 추천해보기

      # 상위 10개 / pearson에 0.7 / jaccard에 0.3
      result = find_similar_movies('The Dark Knight', matrix, 10, 0.3)
    
      # 데이터프레임으로 시각화
      pd.DataFrame(result, columns = ['title', 'pearson', 'jaccard', 'score'])
Comments