데이터 분석(추천시스템) - 유사도(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>

In [15]:
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) )
In [16]:
sim_msd(ratings, 'Dave','Alex')
Out[16]:
0.2857142857142857


Dave와 Andy의 msd 유사도 구하기
In [17]:
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} } $$

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

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



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

      In [18]:
      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))
      
      In [19]:
      sim_cosine(ratings, 'Dave','Alex')
      
      Out[19]:
      0.9938837346736189
      Dave와 Cosine Similarity를 사용해서 가장 유사한 사용자는?
      In [20]:
      sim_cosine(ratings, 'Dave','David')
      
      Out[20]:
      0.8319479070496523
      In [21]:
      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$의 평균 평점

      In [22]:
      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))
      
      In [23]:
      sim_pearson(ratings, 'Dave','Alex')
      
      Out[23]:
      0.970142500145332
      In [24]:
      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]
      
      In [25]:
      top_match(ratings, 'Dave', 3)
      
      Out[25]:
      [(0.970142500145332, 'Alex'),
       (0.5406205059012895, 'David'),
       (0.39840953644479793, 'Andy')]
      top_match를 사용해서 Alex와 가장 유사한 사용자는?
      In [26]:
      top_match(ratings, 'Dave', 3, sim_function=sim_msd)
      
      Out[26]:
      [(0.2857142857142857, 'Alex'), (0.1764705882352941, 'David'), (0.15, 'Andy')]
      In [27]:
      top_match(ratings, 'Dave', 3, sim_function=sim_cosine)
      
      Out[27]:
      [(0.9938837346736189, 'Alex'),
       (0.8319479070496523, 'David'),
       (0.7795844649455863, 'Andy')]


      In [28]:
      top_match(ratings, 'Dave', 3, sim_function=sim_pearson)
      
      Out[28]:
      [(0.970142500145332, 'Alex'),
       (0.5406205059012895, 'David'),
       (0.39840953644479793, 'Andy')]