데이터 분석 - 추천 시스템 (Recommendation System) - 유사도와 KNN을 활용한 예측 값 계산 및 추천 목록 생성 기법

4. 유사도와 KNN을 활용한 예측 값 계산 및 추천 목록 생성 기법

  • 사용자들 간의 유사도를 바탕으로 모든 항목에 대해 예측 값을 계산하고 높은 예측 값을 갖는 상위 N개의 추천 목록을 생성한다.

K Nearest Neighbors(KNN) 가중치 예측 기법

  • 유사도가 구해지면 평점을 예측하고자 하는 사용자(또는 상품)와 유사도가 큰 k 개의 사용자(또는 상품) 벡터를 사용하여 가중 평균을 구해서 가중치를 예측

4.1. KNNBasic

  • 평점들을 단순히 가중 평균한다. 다음 식에서 $N^k$는 $k$개의 가장 유사도가 큰 벡터의 집합이다.
  • </ul> </li> </ul> $$ \hat{r}_{ui} = \frac{ \sum\limits_{v \in N^k_i(u)} \text{sim}(u, v) \cdot r_{vi}} {\sum\limits_{v \in N^k_i(u)} \text{sim}(u, v)} $$

    또는 $$ \hat{r}_{ui} = \frac{ \sum\limits_{j \in N^k_u(i)} \text{sim}(i, j) \cdot r_{uj}} {\sum\limits_{j \in N^k_u(j)} \text{sim}(i, j)} $$

  1. 대상과 가장 유사도가 높은 k의 대상의 영화 평점과 유사도를 통해 추측평점((유사도 x (타인의)영화평점)을 구한다.
  2. 추측평점의 총합을 구한 후,

  3. 추측평점 총합계/유사도 합계를 통해 예상평점을 뽑아낼 수 있다.

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
    }
}
def getRecommendation (data, person, k=3, sim_function=sim_pearson):
    
    result = top_match(data, person, k)
    
    score = 0 # 평점 합을 위한 변수
    li = list() # 리턴을 위한 리스트
    score_dic = dict() # 유사도 총합을 위한 dic
    sim_dic = dict() # 평점 총합을 위한 dic

    for sim, name in result: # 튜플이므로 한번에
        print(sim, name)
        if sim < 0 : continue #유사도가 양수인 사람만
        for movie in data[name]: 
            if movie not in data[person]: #name이 평가를 내리지 않은 영화
                score += sim * data[name][movie] # 그사람의 영화평점 * 유사도
                score_dic.setdefault(movie, 0) # 기본값 설정
                score_dic[movie] += score # 합계 구함

                # 조건에 맞는 사람의 유사도의 누적합을 구한다
                sim_dic.setdefault(movie, 0) 
                sim_dic[movie] += sim

            score = 0  #영화가 바뀌었으니 초기화한다
    
    for key in score_dic: 
        score_dic[key] = score_dic[key] / sim_dic[key] # 평점 총합/ 유사도 총합
        li.append((score_dic[key],key)) # list((tuple))의 리턴을 위해서.
    li.sort() #오름차순
    li.reverse() #내림차순
    return li
getRecommendation(ratings_expand, '소이현')
0.9330597055272909 홍수환
0.8909876971472571 최홍만
0.8452277090445156 나원탁
Out[33]:
[(3.675468553454334, '꾼'),
 (3.0000000000000004, '택시운전사'),
 (1.976934805357391, '킹스맨:골든서클')]
* getRecommendation를 사용해서 최홍만 과 가장 유사한 사용자는?
* 단 유사도 함수를 sim_cosine, k는 2를 사용하시오
getRecommendation(ratings_expand, '최홍만', k=2, sim_function=sim_cosine)
0.9608329054174726 홍수환
0.9517663735117331 소이현
Out[50]:
[(3.0, '택시운전사')]

4.2. KNNWithMeans

  • 평점들을 평균값 기준으로 가중 평균한다.
  • </ul> </li> </ul> $$ \hat{r}_{ui} = \mu_u + \frac{ \sum\limits_{v \in N^k_i(u)} \text{sim}(u, v) \cdot (r_{vi} - \mu_v)} {\sum\limits_{v \in N^k_i(u)} \text{sim}(u, v)} $$

    또는 $$ \hat{r}_{ui} = \mu_i + \frac{ \sum\limits_{j \in N^k_u(i)} \text{sim}(i, j) \cdot (r_{uj} - \mu_j)} {\sum\limits_{j \in N^k_u(i)} \text{sim}(i, j)} $$

for name in ratings_expand:
    sum = 0
    count = 0
    for movies in ratings_expand[name]:
        sum += ratings_expand[name][movies]
        count += 1
    ratings_expand[name]['avg'] = sum / count
ratings_expand
Out[35]:
{'나원탁': {'avg': 3.7,
  '꾼': 3.0,
  '남한산성': 4.0,
  '범죄도시': 5.0,
  '아이 캔 스피크': 3.5,
  '택시운전사': 3.0},
 '마동석': {'avg': 2.8333333333333335,
  '꾼': 3.0,
  '남한산성': 1.5,
  '범죄도시': 3.5,
  '아이 캔 스피크': 2.5,
  '킹스맨:골든서클': 3.0,
  '택시운전사': 3.5},
 '설경구': {'avg': 3.5, '꾼': 4.0, '남한산성': 3.0, '범죄도시': 4.5, '택시운전사': 2.5},
 '소이현': {'avg': 3.1666666666666665, '남한산성': 4.5, '범죄도시': 4.0, '아이 캔 스피크': 1.0},
 '윤계상': {'avg': 2.75,
  '꾼': 3.0,
  '남한산성': 2.5,
  '범죄도시': 3.0,
  '아이 캔 스피크': 3.5,
  '킹스맨:골든서클': 1.5,
  '택시운전사': 3.0},
 '이정재': {'avg': 3.5,
  '꾼': 5.0,
  '남한산성': 4.5,
  '범죄도시': 1.5,
  '아이 캔 스피크': 4.5,
  '킹스맨:골든서클': 0.5,
  '택시운전사': 5.0},
 '최홍만': {'avg': 3.5,
  '꾼': 4.5,
  '남한산성': 4.5,
  '범죄도시': 3.0,
  '아이 캔 스피크': 2.5,
  '킹스맨:골든서클': 3.0},
 '홍수환': {'avg': 2.75,
  '꾼': 3.5,
  '남한산성': 4.0,
  '범죄도시': 3.0,
  '아이 캔 스피크': 2.0,
  '킹스맨:골든서클': 1.0,
  '택시운전사': 3.0}}
def getRecommendation (data, person, k=3, sim_function=sim_pearson):
    
    result = top_match(data, person, k)
    
    score = 0 # 평점 합을 위한 변수
    li = list() # 리턴을 위한 리스트
    score_dic = dict() # 유사도 총합을 위한 dic
    sim_dic = dict() # 평점 총합을 위한 dic

    for sim, name in result: # 튜플이므로 한번에
        print(sim, name)
        if sim < 0 : continue #유사도가 양수인 사람만
        for movie in data[name]: 
            if movie not in data[person]: #name이 평가를 내리지 않은 영화
                score += sim * (data[name][movie] - data[name]['avg']) # 그사람의 영화평점 * 유사도
                score_dic.setdefault(movie, 0) # 기본값 설정
                score_dic[movie] += score # 합계 구함

                # 조건에 맞는 사람의 유사도의 누적합을 구한다
                sim_dic.setdefault(movie, 0) 
                sim_dic[movie] += sim

            score = 0  #영화가 바뀌었으니 초기화한다
    
    for key in score_dic: 
        score_dic[key] = data[person]['avg'] + (score_dic[key] / sim_dic[key]) # 평점 총합/ 유사도 총합
        li.append((score_dic[key],key)) # list((tuple))의 리턴을 위해서.
    li.sort() #오름차순
    li.reverse() #내림차순
    return li
getRecommendation(ratings_expand, '소이현')
0.9661614876137535 홍수환
0.9438405255065262 최홍만
0.9437757473484876 설경구
Out[42]:
[(3.91667234143915, '꾼'),
 (2.7989920841776077, '택시운전사'),
 (2.0343626946178484, '킹스맨:골든서클')]
* getRecommendation를 사용해서 최홍만 과 가장 유사한 사용자는?
* 단 유사도 함수를 sim_cosine, k는 2를 사용하시오
getRecommendation(ratings_expand, '최홍만', k=2, sim_function=sim_cosine)
0.9652129842456227 홍수환
0.9438405255065262 소이현
Out[43]:
[(3.75, '택시운전사')]

여기까지가 추천시스템에서 사용되는 가장 일반적인 방법임