지난 장에서 가장 기본적인 예제를 통해서 사용자 간의 유사도를 구해보는 작업을 하였다. 이번 장에서는 사용자들 간의 유사도를 바탕으로 모든 항목에 대해서 예측값을 계산하고, 높은 예측 값을 갖는 상위 N개의 추천 목록을 생성하는 작업을 해보도록한다.
KNN 가중치 예측 기법
예제 풀이 방법
1. 대상과 가장 유사도가 높은 k의 대상의 영화 평점과 유사도를 통해 추측평점(유사도 x (타인의)영화평점)을 구한다.
2. 추측평점의 총합을 구한다.
3. (추측평점 총합계)/(유사도 합계) 계산을 통해 예상평점을 뽑아낼 수 있다.
협업 필터링의 구성
1. 메모리 기반
1.1 사용자 기반 <<<<< 현재 하는 작업
1.2 아이템 기반
2. 모델 기반
2.1 MF
다음은 우리가 사용할 딕셔너리 형태의 데이터입니다.
#rating; 평가
ratings_expand={
'마동석': {
'택시운전사': 3.5,
'남한산성': 1.5,
'킹스맨:골든서클': 3.0,
'범죄도시': 3.5,
'아이 캔 스피크': 2.5,
'꾼': 3.0,
},
'이정재': {
'택시운전사': 5.0,
'남한산성': 4.5,
'킹스맨:골든서클': 0.5,
'범죄도시': 1.5,
'아이 캔 스피크': 4.5,
'꾼': 5.0,
},
'윤계상': {
'택시운전사': 3.0,
'남한산성': 2.5,
'킹스맨:골든서클': 1.5,
'범죄도시': 3.0,
'꾼': 3.0,
'아이 캔 스피크': 3.5,
},
'설경구': {
'택시운전사': 2.5,
'남한산성': 3.0,
'범죄도시': 4.5,
'꾼': 4.0,
},
'최홍만': {
'남한산성': 4.5,
'킹스맨:골든서클': 3.0,
'꾼': 4.5,
'범죄도시': 3.0,
'아이 캔 스피크': 2.5,
},
'홍수환': {
'택시운전사': 3.0,
'남한산성': 4.0,
'킹스맨:골든서클': 1.0,
'범죄도시': 3.0,
'꾼': 3.5,
'아이 캔 스피크': 2.0,
},
'나원탁': {
'택시운전사': 3.0,
'남한산성': 4.0,
'꾼': 3.0,
'범죄도시': 5.0,
'아이 캔 스피크': 3.5,
},
'소이현': {
'남한산성': 4.5,
'아이 캔 스피크': 1.0,
'범죄도시': 4.0
}
}
ratings = pd.DataFrame(ratings_expand).T
print(ratings)
-> 사용자별로 입력하지 않은 평점들이 존재한다는 것을 알 수 있다. 이제 사용자 소이현에게 '택시운전사', '킹스맨:골든서클', '꾼' 에 대한 예측 평점을 KNN 방법을 통해서 예측해보도록 한다. 유사도는 코사인 유사도로 구하며, K 값은 2로 하여 작업해보도록 하겠습니다.
우선 소이현과 다른 사용자 간의 코사인 유사도를 모두 구해보겠습니다. 우선 소이현이 평가한 영화들만을 대상으로 해야 합니다. 즉 '남한산성', '범죄도시', '아이 캔 스피크' 에 대해서 다른 사용자들이 남긴 평점을 토대로 유사도를 구하게 됩니다. 이 때 설경구 사용자는 '아이 캔 스피크'에 대해서 평가를 하지 않았기 때문에 이 영화를 제외한 두 개의 영화에 대한 평점으로 둘 간의 유사도를 구하게 됩니다.
이름과 데이터 프레임을 주었을 때, 그 사람과 코사인 유사도를 내림차순으로 나타내주는 함수를 만듭니다.
🐱👓🐱👓
import math
import numpy as np
def cosim(name, dataframe):
# movies 안에는 name 사용자가 평가한 영화 이름만을 담습니다.
movies = []
for i in dataframe.ix[name,:].index:
if math.isnan(dataframe.ix[name,i]) == False:
movies.append(i)
# U_df는 name 사용자가 평가한 것만 추출합니다.
U_df = pd.DataFrame(dataframe.ix[name,movies] ).T
# other_df는 name 사용자를 제외한 데이터프레임입니다.
other_df=dataframe.ix[:,movies].drop(name, axis=0)
#U_list는 name 사용자가 평가한 영화(str)를 리스트에 담습니다.
U_list= list(U_df.index)
#sum_dict에 name과 다른 사용자 간의 유사도를 평가한 결과를 담게 됩니다.
sim_dict={}
# user와 name 사용자 둘 다 평점을 매긴 영화에 대한 벡터로 코사인 유사도를 구합니다.
for user in other_df.index:
sm= [m for m in U_df.columns if math.isnan(other_df.ix[user,m])==False]
main_n = np.linalg.norm(U_df.ix[name,sm])
user_n = np.linalg.norm(other_df.ix[user,sm])
prod = np.dot(U_df.ix[name,sm], other_df.ix[user,sm])
sim_dict[user]=prod/(main_n*user_n)
return sim_dict
cosim('소이현', ratings)
-> 위의 함수를 통해서 입력하는 사용자와 모든 다른 사용자 간의 코사인 유사도를 구할 수 있게 되었습니다.
K를 2로 둔다고 했으니 유사도가 큰 2개의 사용자를 불러오도록 하였습니다.
a=cosim('소이현',ratings)
import operator
sorted(a.items() ,key=operator.itemgetter(1), reverse=True)[:2]
'소이현' 사용자는 [ '홍수환', '설경구' ] 사용자와 가장 유사하다는 것을 확인하였고, 이제 이 두 사람의 유사도를 통해서 '소이현'이 평가하지 않은 영화에 대한 예측평점을 해보도록 하겠습니다. 우리는 '소이현'이 평가하지 않은 영화들 중에서 ['홍수환', '설경구']가 평가한 영화인 '택시운전사'와 '꾼' 에 대해서 '소이현'의 예측평점을 구해보도록 하겠습니다.
이 식을 다시 가져와서 분모와 분자별로 구해보겠습니다.
우선 영화 '택시운전사' 에 대한 '소이현'의 예측 평점을 구해보겠습니다.
분모는 '소이현'과 K명의 유사도 큰 사용자 즉, ['홍수환', '설경구']와의 유사도를 다 더한 것입니다.
(0.97+0.96)
#1.93
분자는 (유사도('소이현&홍수환') x 평점('홍수환'_택시운전사)) 와 (유사도('소이현&설경구') x 평점('설경구'_택시운전사)) 의 합입니다.
((0.97*3)+(0.96*2.5))
#5.31
이제 분자를 분모로 나누면 홍수환과 설경구와의 유사도를 가중치로 하여 그들이 매긴 평점에 가중치를 주어 가중 평균 값을 구하고 이 가중 평균 값으로 '소이현'의 평점을 예측해볼 수 있습니다.
((0.97*3)+(0.96*2.5))/(0.97+0.96)
#2.75
'소이현' 사용자가 영화 '택시운전사'에 평점을 매긴다면 2.75를 매길 것이다. 라고 할 수 있습니다.
이제 '소이현'이 평가하지 않은 영화들 중에서 예측 평점이 가장 높은 것을 추천해 주는 마무리 작업을 합니다.
'설경구', '홍수환' 사용자(유사도가 큰 k)는 영화 '택시운전사'와 '꾼'은 '소이현'이 평가하지 않은 영화에 평가했기 때문에 이들의 평점으로 예측 평점을 구하지만 영화'꾼'은 '설경구'의 평점이 없으므로 설경구 다음으로 유사도가 높은 '최홍만' 사용자의 '꾼' 평점을 대신 사용합니다.
import math
import numpy as np
def cosim(name, dataframe):
# movies 안에는 name 사용자가 평가한 영화 이름만을 담습니다.
movies = []
for i in dataframe.ix[name,:].index:
if math.isnan(dataframe.ix[name,i]) == False:
movies.append(i)
# U_df는 name 사용자가 평가한 것만 추출합니다.
U_df = pd.DataFrame(dataframe.ix[name,movies] ).T
# other_df는 name 사용자를 제외한 데이터프레임입니다.
other_df=dataframe.ix[:,movies].drop(name, axis=0)
#U_list는 name 사용자가 평가한 영화(str)를 리스트에 담습니다.
U_list= list(U_df.index)
#sum_dict에 name과 다른 사용자 간의 유사도를 평가한 결과를 담게 됩니다.
sim_dict={}
# user와 name 사용자 둘 다 평점을 매긴 영화에 대한 벡터로 코사인 유사도를 구합니다.
for user in other_df.index:
sm= [m for m in U_df.columns if math.isnan(other_df.ix[user,m])==False]
main_n = np.linalg.norm(U_df.ix[name,sm])
user_n = np.linalg.norm(other_df.ix[user,sm])
prod = np.dot(U_df.ix[name,sm], other_df.ix[user,sm])
sim_dict[user]=prod/(main_n*user_n)
###########################################################################
# 추천 영화 출력 #
import operator
sim_mat=sorted(sim_dict.items() ,key=operator.itemgetter(1), reverse=True)
#1. name 사용자가 평가하지 않은 영화를 추출합니다.
recommend_list = list(set(dataframe.columns) - set(U_df.columns))
others_k = [i[0] for i in sim_mat]
recommender=dict()
for movie in recommend_list:
rating=[]
sim=[]
for person in others_k:
if math.isnan(dataframe.ix[person,movie])==False:
rating.append(dataframe.ix[person,movie])
sim.append(sim_dict[person])
pred=((sim[0]*rating[0])+(sim[1]*rating[1]))/(sum(sim[:2]))
recommender[movie]= pred
return sorted(recommender.items(), key=operator.itemgetter(1), reverse=True)
cosim('소이현', ratings)
'소이현'에게는 평점이 가장 높을 것으로 예상되는 영화 '꾼'을 추천해주게 됩니다.
다른 사용자들의 추천 결과를 보도록 하겠습니다.
cosim('설경구', ratings)
이상 협업필터링의 메모리 기반에서 사용자 기반으로 특정 사용자가 평가하지 않은 영화들에 예상평점을 매겨 추천을 하였습니다.
사용자 기반과 대칭? 대비? 되는 아이템 기반은 그 해석만 다를뿐 행렬을 전치 해주면 됩니다. 예컨데, '킹스맨:골든서클'을 가장 적은 인원이 평가해서 이 영화를 누구에게 추천할까?로 해석하면 결과는 아래와 같습니다.
cosim('킹스맨:골든서클', ratings.T)
'설경구' 사용자에게 해당 영화를 추천해주게 됩니다. ? .. ?
아이템 기반 CF에 대한 정확한 설명이 아니다. ㅠ 무비렌즈를 사용해 구현한 다른 페이지를 참조하자.
https://data-science-hi.tistory.com/73
간단한 예제를 통해서 특정 사용자에 대한 평점을 예측해보았지만 주의할 것이 있습니다.
사용자와 다른 사용자와의 유사도를 구할 때, 둘 다 평점을 매긴 벡터를 통해 유사도를 측정해야 한다는 점입니다. 이 외에는 이 것을 좀 더 편하게 구현할 수 있도록 만드는 프로그래밍 작업이 되겠습니다.
reference
'추천시스템' 카테고리의 다른 글
MovieLens 데이터를 활용한 Collaborative Filtering 구현 (0) | 2020.01.15 |
---|---|
콘텐츠 기반 필터링 추천 예제 (0) | 2020.01.14 |
연구주제를 위한 논문 리뷰 (0) | 2020.01.10 |
사용자간 유사도 계산 (0) | 2019.11.23 |
추천시스템 개념 (0) | 2019.11.23 |
댓글