코사인 유사도(Cosine similarity) 이해 및 Java로 구현하기

    유클리드 거리(Euclidean Distance)와 함께 어떠한 값들이 얼마나 유사한지 가장 많이 사용하는 알고리즘 중 하나로 코사인 유사도가 있다. 그리고 유클리드 거리를 변형하여 실제 도시들의 모양을 감안해서 구하는 맨하탄 거리(Manhattan Distance)와 같은 알고리즘도 꽤 많이 활용하고 있다.


    서울시와 수원시의 거리가 얼마나 가까운가? 예를 들어 도시들간의 거리가 얼마나 가까운지를 구하기 위해서는 유클리드 거리와 같은 알고리즘을 많이 활용할 수 있다. 서울시의 위도와 경도값과 수원시의 위도와 경도 값을 기반으로 둘간의 선을 그은 후, 거리를 구한다면 도시간에 거리를 구할 수 있게 된다.


    그러나 한번 이런 문제가 있다고 가정을 해보자. 포유류간 키와 몸무게를 기반으로 얼마나 유사한지를 측정하는 것이다.


    너무 뚱뚱하지 않거나 마르지 않는 경우 보편적으로 키가 170cm일 경우 몸무게는 50~100kg 정도 나갈 것이다.

    그리고, 키가 180cm일 경우 몸무게는 60~110kg 정도 나가는 모양새를 보일 것이다. 이렇게 동물들은 키에 대응하는 몸무게가 있는데 단순히 데이터간의 거리로 구할 경우 작은 사람과 큰 사람을 꽤나 먼 사람으로 인식하여 다른 존재로 인식할지 모르겠다.


    인간간의 키와 몸무게를 보여주는 그래프


    예를 들면, A라는 사람은 150cm에 몸무게가 40kg나오며, B라는 사람은 키가 230cm에 몸무게가 130kg 나오는 사람이 있다고 가정해보자. A와 B의 데이터의 거리는 꽤 멀지만 둘다 인간이고 해당 키에 대해 합당한 몸무게를 가지고 있다. 그러나 거리로 단순히 구하게 되면 같은 인간이지만 둘은 꽤 먼 거리를 가질지 모르겠다.


    이번에는 인간의 데이터에 다른 동물들을 대입해보자,  예를 들어 키가 150cm 정도이며 몸무게가 150kg 정도 나가는 C라는 동물이 있다고 가정을 해보자. 그래프 상으로 봤을 때 인간의 데이터와 동떨어진 모양을 가졌지만, A라는 사람과 거리를 비교할 경우 B라는 사람보다 C라는 동물과 더 유사하다 계산할 수 있다.


    인간과 다른 동물간의 키와 몸무게를 보여주는 그래프

    이렇게 단순히 좌표간의 거리로 계산을 하게 되면 이런 문제가 발생할 수 있다. 


    이는 텍스트 분석을 할때도 나타날 수 있는 문제이다. 예를 들어, "분석"이라는 단어가 A라는 문서에 100번 나왔고, B라는 문서에 50번 나왔다고 가정을 해보자. 그러면 A라는 문서가 분석에 좀 더 중요하다 인식할 수 있는데 A라는 문서의 길이가 B의 문서의 길이보다 2배 길 경우 둘의 문서는 비슷한 확률로 해당 단어가 등장을 한 것이다.



    이를 단순히 거리로 계산하면 알 수가 없기 때문에 이를 알 수 있는 방법이 각도를 구하는 것이다.



    이렇게 A라는 점과 B라는 점을 서로 직선으로 그을 경우 유클리드 거리이며, 둘간의 각도를 구하면 코사인 유사도를 구하는 공식이 되며, 이렇게 구할경우 같은 직선 영역에 위치한 값들은 같은 값이라 볼 수 있다.


    코사인 유사도 공식



    코사인 유사도 소스 for Java


    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
    30
    31
    /**
         * Calculates the cosine similarity for two given vectors.
         *
         * @param leftVector left vector
         * @param rightVector right vector
         * @return cosine similarity between the two vectors
         */
        public Double cosineSimilarity(final Map<CharSequence, Integer> leftVector, final Map<CharSequence, Integer> rightVector) {
            if (leftVector == null || rightVector == null) {
                throw new IllegalArgumentException("Vectors must not be null");
            }
     
            final Set<CharSequence> intersection = getIntersection(leftVector, rightVector);
     
            final double dotProduct = dot(leftVector, rightVector, intersection);
            double d1 = 0.0d;
            for (final Integer value : leftVector.values()) {
                d1 += Math.pow(value, 2);
            }
            double d2 = 0.0d;
            for (final Integer value : rightVector.values()) {
                d2 += Math.pow(value, 2);
            }
            double cosineSimilarity;
            if (d1 <= 0.|| d2 <= 0.0) {
                cosineSimilarity = 0.0;
            } else {
                cosineSimilarity = (double) (dotProduct / (double) (Math.sqrt(d1) * Math.sqrt(d2)));
            }
            return cosineSimilarity;
        }
    cs

    소스참조 https://commons.apache.org/sandbox/commons-text/jacoco/org.apache.commons.text.similarity/CosineSimilarity.java.html



    연관자료


    댓글

    Designed by JB FACTORY