본문 바로가기

SK네트웍스 Family AI캠프 10기/Daily 회고

35일차. Deep Learning - 추천 시스템

더보기

 

35일 차 회고.

 

 벌써 2월 마지막 날이라서 시간이 빨리 간다고 느껴졌다. 2개월동안 아직 이룬 것이 없어서 초조해지기도 하는 것 같다. 가뜩이나 이번 단위 프로젝트에서 머신러닝 모델의 결과가 별로 좋지 않아서 스트레스 받기도 하는 것 같다. 일단 주말에 최대한 높여보고 월요일에 화면 구현을 할 예정이다.

 

4. 추천 시스템 - Item Based Filtering 부터 수정

 

 

1. 추천 시스템

 

 

추천 시스템은 정보 필터링 기술의 일종으로 특정 사용자가 관심을 가질만한 정보를 추천하는 것이다.

 

 

1-1. Cold Start

 

Cold Start는 추천 시스템이 새로운 또는 일부 유저들에 대한 충분한 정보가 수집된 상태가 아니기 때문에 해당 유저들에게 적절한 제품을 추천해주지 못하는 문제를 말한다.

 

Cold Start 발생 원인

  • New Community
    • 새로운 서비스가 오픈된 경우
  • New Item
    • 새로운 제품이 출시된 경우
  • New User
    • 새로운 유저가 가입한 경우

 

Cold Start 해결 방법

  • New Community
    • Knowledge-Based Filtering
  • New Item
    • Content-Based Filtering
  • New User
    • Knowledge-Based Filtering
    • 비개인화 추천 알고리즘

 

 

1-2. 추천 시스템의 필요성

 

추천 시스템은 고객 개개인에게 맞춤형 서비스를 제공함으로써 기업에는 더 많은 수익 창출을 제공하고, 고객에게는 더 높은 만족도를 제공한다.

 

파레토 법칙

  • 전체 결과의 80%가 전체 원인의 20%에 의해 일어나는 현상

 

롱테일 법칙

  • 80%의 사소한 다수가 20%의 핵심 소수보다 더 뛰어난 성과를 창출한다는 이론

 

 

1-3. 추천 시스템 분류

 

시나리오에 따른 분류

  • 연관된 아이템 추천
  • 개인화 아이템 추천

 

피드백 종류에 따른 분류

  • 명시적 피드백(Explicit Feedback) 추천
    • ex. 별점, 좋아요 등
  • 암시적 피드백(Implicit Feedback) 추천
    • 조회수, 검색 기록, 페이지 유지 시간 등

 

 

1-4. 추천 알고리즘

 

비개인화 추천 알고리즘

  • 인기도 기반 추천
    • 조회수 기반 추천(Most Popular)
      • ex. Hacker News Ranking Algorithm 
        • $score = \frac {pageviews - 1} {(age + 2)^{gravity}}$
          • $pageviews$: 조회수
          • $age$: 현재 시각 - 기사 업로드 날짜
          • $gravity$: 중력 상수
    • 평점 기반 추천(Highly Rated)
      • ex. Stream Ranking Algorithm
        • $score = avg\;rating - (avg\;rating - 0.5) × 2^{-log_{10}(num\,of\,reviews)}$
          • $avg\;rating : \frac {긍정 리뷰 수} {전체 리뷰 수}$

 

Knowledge-Based Filtering

  • 추천하고자 하는 분야의 도메인 지식을 활용해 추천하는 방식
  • ex. 성별, 나이 등

 

Content-Based Filtering

  • 추천하려는 아이템의 메타 정보를 활용해 콘텐츠별로 특징 정보를 만들어 이를 활용해 추천하는 방식

 

Collaborative Filtering

  • Memory-Based Algorithm
    • User-Based Filtering
      • 사용자 간의 유사성을 기반으로 추천하는 방식
      • 사용자의 선호도는 시간이 지나면서 바뀔 수 있다.
    • Item-Based Filtering
      • 사용자의 이력을 기반으로 추천하는 방식
  • Model-Based Algorithm
    • 행렬 분해를 통해 잠재 요인을 추출하여 추천하는 방식

 

Hybrid Recommender System

  • Content-Based Filtering 방식과 Collaborative Filtering 방식을 결합하여 아이템마다 가중평균을 구해 랭킹을 구하는 방식 

 

 

1-5. 추천 평가 지표

 

Precision@K

  • 내가 추천한 아이템 K개 중에 실제 사용자가 관심 있는 아이템의 비율

 

Recall@K

  • 실제 사용자가 관심 있는 모든 아이템 중에서 내가 추천한 아이템 K개의 비율

 

Mean Average Precision@K

  • 성능 평가에 순서 개념을 도입한 평가 지표

 

NDCG@K

  • 추천된 아이템들의 순서를 고려한 평가 지표

 

Hit Rage@K

  • 전체 사용자 수 대비 적중한 사용자 수

 

 

 

2. 추천 시스템 - 인기도 기반 추천

 

 

2-1. Import Modules

 

import numpy as np
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
from google.colab import drive
drive.mount('/content/data')

 

 

2-2. Hotel Reviews

 

DATA_PATH = ''

hotel_reviews = pd.read_csv(DATA_PATH + 'Hotel_Reviews.csv')

 

 

2-3. Most Popular

 

df_reviews = hotel_reviews.copy()
df_reviews = df_reviews[['Hotel_Name', 'days_since_review']]

 

Hacker News Ranking Algorithm

def hacker_news_score(pageviews, age, gravity=1.8):
    return (pageviews - 1) / pow((age + 2), gravity)

 

Score

  • pageviews
pageviews = df_reviews['Hotel_Name'].value_counts()
df_reviews['paregiews'] = df_reviews['Hotel_Name'].map(pageviews)
  • age
df_reviews['days_since_review'] = df_reviews['days_since_review']\
    .map(lambda x: int(x.replace('days', '').replace('day', '').replace(' ', '')))
df_reviews = df_reviews.groupby(['Hotel_Name']).agg({'days_since_reviews':'min'})\
    .sort_values(by='days_since_review', ascending=False).reset_index()
df_reviews.rename(columns={'days_since_review':'age'}, inplace=True)
  • score
df_reviews['score'] = df_reviews.apply(
    lambda row: hacker_news_score(row['pageviews'], row['age']), axis=1
)

 

추천

no_ranking = 5

df_reviews.sort_values(by=['score'], ascending=False)[:no_ranking]

 

 

2-4. Highly Rated

 

df_reviews = hotel_reviews.copy()
df_reviews = df_reviews[['Hotel_Name', 'Reviewer_Score']]

 

Steam Rating Algorithm

def GetRating(positive_votes, negative_votes, standard_score=0.5):
    total_votes = positive_votes + negative_votes
    average = positive_votes / total_votes
    score = average - (average - standard_score)*2**(-np.log10(total_votes + 1))
    return score * 100

 

Score

df_score_max = df_reviews[df_reviews['Reviewer_Score'] >= 7]\
    .groupby(['Hotel_Name']).agg({'Reviewer_Score':'count'}).reset_index()

df_score_min = df_reviews[df_reviews['Reviewer_Score'] < 7]\
    .groupby(['Hotel_Name']).agg({'Reviewer_Score':'count'}).reset_index()

df_score = pd.merge(df_score_max, df_score_min, on='Hotel_Name', how='inner')

df_score['score'] = df_score.apply(lambda row: GetRating(row['score_max'], row['score_min']), axis=1)

 

추천

no_ranking = 5

df_score.sort_values(by=['score'], ascending=False)[:no_ranking]

 

 

 

3. 추천 시스템 - Contents Based Filtering

 

 

3-1. 코사인 유사도

 

 

$similarity = cos(\theta) = \frac {A × B} {|A| |B|} = \frac {\sum^n_{i=1} A_i × B_i} {\sqrt{\sum^n_{i=1}(A_i)^2} × \sqrt{\sum^n_{i=1}(B_i)^2}}$

 

 

3-2. Movie Dataset

 

import numpy as np
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 consine_similarity
from google.colab import drive
drive.mount('/content/data')
DATA_PATH = ''

movies = pd.read_csv(DATA_PATH + 'tmdb_5000_movies.csv')

 

필요한 컬럼 선택

movies = movies[['id', 'genres', 'overview', 'title', 'vote_average', 'vote_count']]

def genres_to_str(genres) -> str:
    try:
        _genres_list = json.loads(genres)
        _genres_list = sorted([i['name'] for i in _genres_list])
        _genres_str = ' '.join(_genres_list)
        
        return _genres_str
    except:
        return genres

movies['genres'].map(lambda x: genres_to_str(x))[:5]

 

결측치 확인

movies = movies.dropna()

 

genres 가공

movies['genres'] = movies['genres'].apply(literal_eval)

movies['genres'] = movies['genres'].map(lambda x: [y['name'] for y in x])

movies['genres'] = movies['genres'].apply(lambda x: (' ').join(x))

 

 

3-3. Model with Score

 

가중 평점(Weighted Rating)

  • 가중 평점 = $\frac {v} {v + m} * R + \frac {m} {v + m} * C$
    • $v$: 영화에 평가를 매긴 횟수
    • $m$: 평점을 부여하기 위한 최소 평가 수
    • $R$: 영화의 평균 평점
    • $C$: 전체 영화의 평균 평점
percentile = 0.6
m = movies['vote_count'].quantile(percentile)
C = movies['vote_average'].mean()

def weighted_rating(recored):
    v = record['vote_count']
    R = record['vote_average']
    
    return ((v / (v + m)) * R) + ((m / (m + v)) * C)

movies['weighted_rating'] = movies.apply(weighted_rating, axis=1)

 

추천

movies[['title', 'weighted_rating', 'vote_average', 'vote_count']]\
    .sort_values(['weighted_rating', 'vote_average', 'vote_count'], ascending=False)[:10]

 

 

3-4. Model with Count

 

df_movies = movies.copy()

 

CountVectorizer

  • 단어들의 출현 빈도로 여러 문서들을 벡터화한다.
count_vect = CountVectorizer(min_df=0.01, ngram_range=(1, 2))
len(count_vect.vocabulary_.keys())
# 61

genre_mat = count_vect.fit_transform(df_movies['genres'])
genre_mat.toarray()
# array([[1, 1, 0, ..., 0, 0, 0],
#        [1, 0, 0, ..., 0, 0, 0],
#        [1, 1, 0, ..., 0, 0, 0],
#        ...,
#        [0, 0, 0, ..., 0, 0, 0],
#        [0, 0, 0, ..., 0, 0, 0],
#        [0, 0, 0, ..., 0, 0, 0]])

 

유사도 측정

genre_sim = cosine_similarity(genre_mat, genre_mat)
genre_sim_sorted_ind = genre_sim.argsort()[:, ::-1]

 

추천

def find_sim_movie(df, sorted_ind, title_name, top_n=10):
    title_movie = df[df['title'] == title_name]
    title_index = title_movie.index.values
    
    similar_indexes = sorted_ind[title_index, :(top_n*1)]
    similar_indexes = similar_indexes.reshape(-1)
    similar_indexes = similar_indexes[similar_indexes != title_index]
    
    return df.iloc[similar_indexes].sort_values('weighted_rating', ascending=False)[:top_n]

similar_movies = find_sim_movie(df_movies, genre_sim_sorted_ind, 'No Country for Old Men', 10)
similar_movies[['title', 'vote_count', 'weighted_rating']]

 

 

3-5. Model with TF-IDF

 

df_movies = movies.copy()

 

TF-IDF

  • 모든 문서에 공통적으로 들어있는 단어의 경우, 문서 구별 능력이 떨어진다고 보아 가중치를 축소하는 방법
from sklearn.feature_extraction.text import TfidVectorizer

df_movies['features'] = df_movies['genres'] + df_movies['overview']

tfidf_vect_simple = TfidVectorizer(Stop_words='english')

genre_vector = genre_mat.toarray()

 

유사도 측정

similarity = cosine_similarity(genre_vector)

 

추천

def recommend(movie):
    movie_index = df_movies[df_movies['title'] == movie].index[0]
    distances = similarity[movie_index]
    movies_list = sorted(list(enumerate((distances)), reverse=True, key=lambda x: x[1]) [1:16]
    
    mov = []
    id = []
    scores = []
    
    for i in movies_list:
        mov.append(df_movies.iloc[i[0]].title)
        id.append(df_movies.iloc[i[0]].id)
        scores.append(i[1])
    
    dic = {'movie_id': id, 'title': mov, 'Similarity Score': scores, 'weighted_rating': rating}
    df_recommend = pd.DataFrame(dic)
    
    return df_recommend.sort_values('weighted_rating', ascending=False)[:top_n]

movie_name = 'Pirates of the Caribbean: On Stranger Tides'
popular_movies = recommend(movie_name)
import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams['figure.figsize'] = (10, 9)
sns.barplot(x=popular_movies['Similarity Score'], y=popular_movies['title'], palette='tab20')
plt.show()

plt.rcParams['figure.figsize'] = (10, 9)
sns.barplot(x=popular_movies['weighted_rating'], y=popular_movies['title'], palette='tab20')
plt.show()

 

 

 

4. 추천 시스템 - Item Based Filtering 

 

 

4-1. Movie Dataset

 

import pandas as pd
import numpy as np
import scipy.stats
import seaborn as sns
from google.colab import drive
drive.mount('/content/data')
DATA_PATH = ''

rating = pd.read_csv(DATA_PATH + 'rating.csv')
movies = pd.read_csv(DATA_PATH + 'movies_metadata.csv')
from ast import literal_eval

 

 

4-2. Model with Cosine Similarity

 

 

 

 

5. 추천 시스템 - Deep FM

 

 

5-1. Deep FM

 

Model Structure

  • Dense Embeddings
    • FM Component와 Deep Component가 같은 Embedding Layer를 공유한다.
      • Field vector의 크기가 다를 수 있지만, Embedding Layer로 인해 같은 크기를 가진다.
      • Pre-trained할 필요가 없고, End-to-End로 학습한다.

 

 

5-2. 코드

 

from google.colab import drive
drive.mount('/content/data/')