본문 바로가기

추천 시스템/Collaborative Filtering

KNN CF 구현

KNN을 2가지 방법으로 구현하겠다. 참고한 출처는 맨밑에 있다.

1. sklearn패키지 이용

2. 코드 구현

 

 

1. sklearn패키지 사용

 

Import & Data

 

df는 rating, movie 데이터로 만든 데이터 프레임이다. rating을 하나도 못받은 영화의 경우,  df_rating movie id가 존재하지 않기 때문에 df 데이터프레임을 만들었다.

1
2
3
4
5
6
7
8
9
10
import pandas as pd
import numpy as np
import glob
import matplotlib.pyplot as plt
from matplotlib.backends.backend_pdf import PdfPages
import time
 
df_ratings = pd.read_csv( ("../../../../Papers/MovieLens 100K/data/ratings.csv"))
df_movies = pd.read_csv(("../../../../Papers/MovieLens 100K/data/movies.csv"))
df= pd.merge(df_ratings.drop('timestamp', axis=1), df_movies.drop('genres', axis=1), how='outer', on='movieId') [['movieId','userId','rating']].sort_values(by=['movieId']).fillna(0)
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

 

 

Data Filtering

 

explicit feedbacks은 매우 희박하고, 평점이 있는 영화들은 대부분 인기많은 영화들이다. 따라서 long tail과 같은 분포를 따른다.

또한, sparse rating을 준 user나 item들은 매우 민감도가 높아 nosiy한 패턴을 보일 수 있다.

 

rating 수 - movie 분포

따라서, noisy pattern을 없애기 위해 대용량 데이터셋으로 이뤄져 있는 인기 많은 영화로만 필터링 한다.
필터링 후, rating 데이터에는 13500편의 영화가 남게 되므로 추천 모델로 충분하다.

 

 

Modeling

 

그렇다면 KNN의 rating은 어떻게 매길것인가?

- 적절한 format으로 바꿔야 한다.

우리는 데이터를 MxN형식으로 matrix를 만들고(M: movies 수, N: User 수) 결측치(rating)에는 0을 채운다.
또, sparse data가 많기 때문에 scipy sparse matrix를 사용해 memory 용량을 효율적으로 사용하겠다.

 

1
2
3
4
5
6
7
8
9
from scipy.sparse import csr_matrix
# pivot ratings into movie features
df_movie_features = df_ratings.pivot(
    index='movieId',
    columns='userId',
    values='rating'
).fillna(0)
# convert dataframe of movie features to scipy sparse matrix
mat_movie_features = csr_matrix(df_movie_features.values)
 

 

1
2
 
from sklearn.neighbors import NearestNeighbors
model_knn = NearestNeighbors(metric='cosine', algorithm='brute', n_neighbors=20, n_jobs=-1)
 
 

 

 

2. KNN 구현

 

필요 Data 만들기

장르/ 인기 유사도로 KNN을 구현했다. 다른 feature로도 유사도를 만들 수 있다.

 

인기 유사도를 측정하기 위해 movieProperties, movieNormalizedNumRatings를 만들었다.

-movieProperties: rating의 수, rating평균으로 이뤄져 있다.

-movieNormalizedNumRatings: rating의 수를 minmax sclar하여 정규화했다.

1
2
3
4
movieProperties = df.groupby('movieId').agg({'rating': [np.size, np.mean]})
 
movieNumRatings = pd.DataFrame(movieProperties['rating']['size'])
movieNormalizedNumRatings = movieNumRatings.apply(lambda x: (x - np.min(x)) / (np.max(x) - np.min(x)))
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

 

그다음 movieDict을 만들었다.

movieDict는 [영화이름, 장르 리스트, 정규화된 rating 수, 평균 rating 점수]로 이루어져있다.

1
2
3
4
5
6
movieDict = {}
for index, row in df_movies.iterrows():
    movieID = int(row['movieId'])
    name = row['title']
    genres =list(row[2:])
    movieDict[movieID] = (name, np.array(list(genres)), movieNormalizedNumRatings.loc[movieID].get('size'), movieProperties.loc[movieID].rating.get('mean'))
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

 

 

유사도 측정

1
2
3
4
5
6
7
8
9
10
11
12
13
from scipy import spatial
 
# 장르, 인기도의 consine 유사도 적용
def ComputeDistance(a, b):
    genresA = a[1]
    genresB = b[1]
    genreDistance = spatial.distance.cosine(genresA, genresB)
    popularityA = a[2]
    popularityB = b[2]
    popularityDistance = abs(popularityA - popularityB)
    return genreDistance + popularityDistance
 
ComputeDistance(movieDict[1], movieDict[4])
 

위의 코드를 실행하면 유사도 1.4가 나온다. 즉, 각각의 영화에 대해서 유사도를 뽑아낼 수 있다.

 

코드 실행

- getNeighbors: movie ID가 들어오면, 자기자신을 제외한 모든 영화들과의 ditance를 잰다. 그 후 K개만큼 neighbors들을 reture한다.

- recommend: 원하는 movie ID와  K를 입력하면 K개 만큼의 추천 영화를 뽑고, 평균(예상) rating을 구한다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import operator
 
# neighbors 출력
def getNeighbors(movieID, K):
    distances = []
    for movie in movieDict:
        # 같은 movie가 아닐때만 movie distance를 구함
        if (movie != movieID):
            dist = ComputeDistance(movieDict[movieID], movieDict[movie])
            distances.append((movie, dist))
    # movie distance를 sort시켜주어 가장 가까운 영화들을 추천
    distances.sort(key=operator.itemgetter(1))
    neighbors = []
    for x in range(K):
        neighbors.append(distances[x][0])
    return neighbors
 
 
# 최종 추천
def recommend(movieID,K):
    avgRating = 0
    print(movieDict[movieID], '\n')
    neighbors = getNeighbors(movieID, K) # Toy Story (1995)
    for neighbor in neighbors:
        # neigbor의 평균 rating을 더해줌
        avgRating += movieDict[neighbor][3]
        print (movieDict[neighbor][0+ " " + str(movieDict[neighbor][3]))
    avgRating /= K
    print("평균 Rating: ",avgRating)
 
recommend(1,10)
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

movieID에 1(toy story)를 넣고, k=10으로 잡았을 때 아래와 같은 결과가 나온다.

 

 

 

출처

https://towardsdatascience.com/prototyping-a-recommender-system-step-by-step-part-1-knn-item-based-collaborative-filtering-637969614ea

https://hendra-herviawan.github.io/Movie-Recommendation-based-on-KNN-K-Nearest-Neighbors.html