데이터 분석 - 추천 시스템 (Recommendation System) - 유사도(Similarity) 계산 방식

3. 유사도(Similarity) 계산 방식

3.1. 평균제곱차이 유사도 (Mean Squared Difference Similarity)
3.2. 코사인 유사도 (Cosine Similarity)
3.3. 피어슨 유사도 (Pearson Similarity)

3.1. 평균제곱차이 유사도 (Mean Squared Difference Similarity)

Mean Squared Difference

  • User-based Collaborative Filter 경우: 사용자 $u$와 사용자 $v$간의 msd 사용자 $u$와 사용자 $v$가 평가한 상품들의 평점간의 차의 제곱 / 사용자 $u$와 사용자 $v$가 모두 평가한 상품들의 수 $$ \text{msd}(u, v) = \frac{1}{|I_{uv}|} \cdot \sum\limits_{i \in I_{uv}} (r_{ui} - r_{vi})^2 $$ - $I_{uv}$는 사용자 $u$와 사용자 $v$ 모두에 의해 평가된 상품의 집합
    - $|I_{uv}|$는 사용자 $u$와 사용자 $v$ 모두에 의해 평가된 상품의 수
  • Item-based Collaborative Filter 경우: 상품 $i$와 상품 $j$간의 msd $$ \text{msd}(i, j) = \frac{1}{|U_{ij}|} \cdot \sum\limits_{u \in U_{ij}} (r_{ui} - r_{uj})^2 $$ 위 식에서 $U_{ij}$는 상품 $i$와 상품 $j$ 모두를 평가한 사용자의 집합이고 $|U_{ij}|$는 상품 $i$와 상품 $j$ 모두를 평가한 사용자의 수

Mean Squared Difference Similarity

  • Mean Squared Difference (msd) 의 역수로 계산
  • 차이가 클 수록 Similarity 값은 작아진다!
  • Mean Squared Difference Similarity 식에서는 MSD가 0이 되는 경우를 대응하기 위해 1을 무조건 더해줌

$$ \begin{split}\text{msd_sim}(u, v) &= \frac{1}{\text{msd}(u, v) + 1}\\ \text{msd_sim}(i, j) &= \frac{1}{\text{msd}(i, j) + 1}\end{split} $$</p>

def sim_msd(data, name1, name2):
    sum = 0
    count = 0
    for movies in data[name1]:
        if movies in data[name2]: #같은 영화를 봤다면
            sum += pow(data[name1][movies]- data[name2][movies], 2)
            count += 1

    return 1 / ( 1 + (sum / count) )
sim_msd(ratings, 'Dave','Alex')
Out[16]:
0.2857142857142857
Dave와 Andy의 msd 유사도 구하기
sim_msd(ratings, 'Dave','Andy')
Out[17]:
0.15

3.2. 코사인 유사도 (Cosine Similarity)

코사인 유사도(Cosine Similarity)는 두 특성 벡터간의 유사 정도를 코사인 값으로 표현한 것임

Cosine Similarity는 −1에서 1까지의 값을 가지며, −1은 서로 완전히 반대되는 경우, 0은 서로 독립적인 경우, 1은 서로 완전히 같은 경우를 의미함

$$ x \cdot y = |x| |y| \cos\theta $$$$ \cos\theta = \dfrac{x \cdot y}{|x| |y|}$$

이를 사용하여 Consine Similarity를 적용하면

  • 사용자 $u$와 사용자 $v$간의 Cosine Similarity: 두 사용자가 모두 평가한 상품의 평점을 사용해서 계산
  • </ul> $$ \text{cosine_sim}(u, v) = \frac{ \sum\limits_{i \in I_{uv}} r_{ui} \cdot r_{vi}} {\sqrt{\sum\limits_{i \in I_{uv}} r_{ui}^2} \cdot \sqrt{\sum\limits_{i \in I_{uv}} r_{vi}^2} }$$

    • 상품 $i$와 상품 $j$간의 Cosine Similarity: 두 상품의 평점을 사용해서 계산
    • </ul> $$ \text{cosine_sim}(i, j) = \frac{ \sum\limits_{u \in U_{ij}} r_{ui} \cdot r_{uj}} {\sqrt{\sum\limits_{u \in U_{ij}} r_{ui}^2} \cdot \sqrt{\sum\limits_{u \in U_{ij}} r_{uj}^2} } $$

참고: 스칼라곱 - 두 벡터로 스칼라를 계산하는 연산

두 벡터 의 스칼라곱(두 벡터로 스칼라를 계산하는 연산)은 다음과 같다:

이를 다음과 같이 코사인 함수를 사용해서도 표현함

import math
def sim_cosine(data, name1, name2):
    sum_name1 = 0
    sum_name2 = 0
    sum_name1_name2 = 0
    count = 0
    for movies in data[name1]:
        if movies in data[name2]: #같은 영화를 봤다면
            sum_name1 += pow(data[name1][movies], 2)
            sum_name2 += pow(data[name2][movies], 2)
            sum_name1_name2 += data[name1][movies]*data[name2][movies]
    
    return sum_name1_name2 / (math.sqrt(sum_name1)*math.sqrt(sum_name2))
sim_cosine(ratings, 'Dave','Alex')
Out[19]:
0.9938837346736189
Dave와 Cosine Similarity를 사용해서 가장 유사한 사용자는?
sim_cosine(ratings, 'Dave','David')
Out[20]:
0.8319479070496523
sim_cosine(ratings, 'Dave','Andy')
Out[21]:
0.7795844649455863

3.3. 피어슨 유사도 (Pearson Similarity)

피어슨 유사도는 두 벡터의 상관계수(Pearson correlation coefficient)를 의미

피어슨 유사도는 유사도가 가장 높을 경우 값이 1, 가장 낮을 경우 -1의 값을 가짐

특정인물의 점수기준이 극단적으로 너무 낮거나 높을 경우 유사도에 영향을 크게 주기 때문에, 이를 막기 위해 상관계수를 사용하는 방법

  • 사용자 $u$와 사용자 $v$간의 Pearson Similarity
$$ \text{pearson_sim}(u, v) = \frac{ \sum\limits_{i \in I_{uv}} (r_{ui} - \mu_u) \cdot (r_{vi} - \mu_{v})} {\sqrt{\sum\limits_{i \in I_{uv}} (r_{ui} - \mu_u)^2} \cdot \sqrt{\sum\limits_{i \in I_{uv}} (r_{vi} - \mu_{v})^2} } $$

$\mu_u$는 사용자 $u$의 평균 평점

  • 상품 $i$와 상품 $j$간의 Pearson Similarity
$$ \text{pearson_sim}(i, j) = \frac{ \sum\limits_{u \in U_{ij}} (r_{ui} - \mu_i) \cdot (r_{uj} - \mu_{j})} {\sqrt{\sum\limits_{u \in U_{ij}} (r_{ui} - \mu_i)^2} \cdot \sqrt{\sum\limits_{u \in U_{ij}} (r_{uj} - \mu_{j})^2} } $$

$\mu_i$는 상품 $i$의 평균 평점

def sim_pearson(data, name1, name2):
    avg_name1 = 0
    avg_name2 = 0
    count = 0
    for movies in data[name1]:
        if movies in data[name2]: #같은 영화를 봤다면
            avg_name1 = data[name1][movies]
            avg_name2 = data[name2][movies]
            count += 1
    
    avg_name1 = avg_name1 / count
    avg_name2 = avg_name2 / count
    
    sum_name1 = 0
    sum_name2 = 0
    sum_name1_name2 = 0
    count = 0
    for movies in data[name1]:
        if movies in data[name2]: #같은 영화를 봤다면
            sum_name1 += pow(data[name1][movies] - avg_name1, 2)
            sum_name2 += pow(data[name2][movies] - avg_name2, 2)
            sum_name1_name2 += (data[name1][movies] - avg_name1) * (data[name2][movies] - avg_name2)
    
    return sum_name1_name2 / (math.sqrt(sum_name1)*math.sqrt(sum_name2))
sim_pearson(ratings, 'Dave','Alex')
Out[23]:
0.970142500145332
def top_match(data, name, index=3, sim_function=sim_pearson):
    li=[]
    for i in data: #딕셔너리를 돌고
        if name!=i: #자기 자신이 아닐때만
            li.append((sim_function(data,name,i),i)) #sim_function()을 통해 상관계수를 구하고 li[]에 추가
    li.sort() #오름차순
    li.reverse() #내림차순
    return li[:index]
top_match(ratings, 'Dave', 3)
Out[25]:
[(0.970142500145332, 'Alex'),
 (0.5406205059012895, 'David'),
 (0.39840953644479793, 'Andy')]
top_match를 사용해서 Alex와 가장 유사한 사용자는?
top_match(ratings, 'Dave', 3, sim_function=sim_msd)
Out[26]:
[(0.2857142857142857, 'Alex'), (0.1764705882352941, 'David'), (0.15, 'Andy')]
top_match(ratings, 'Dave', 3, sim_function=sim_cosine)
Out[27]:
[(0.9938837346736189, 'Alex'),
 (0.8319479070496523, 'David'),
 (0.7795844649455863, 'Andy')]
top_match(ratings, 'Dave', 3, sim_function=sim_pearson)
Out[28]:
[(0.970142500145332, 'Alex'),
 (0.5406205059012895, 'David'),
 (0.39840953644479793, 'Andy')]