TF-IDF와 유사도 그리고 상관관계


TF-IDF와 유사도 그리고 상관관계에 대해서 설명을 하기 위한 포스팅입니다.


실제 실행된 결과를 ipynb 파일로 보고 싶으시다면 다음 링크로 이동해주시면 됩니다!


In [1]:
import warnings
warnings.filterwarnings(action='ignore')

import numpy as np
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import linear_kernel, cosine_similarity

기본적인 TF-IDF

TF-IDF에 대한 이론적으로 좋은 블로그는 다음과 같다고 생각됩니다.

https://euriion.com/?p=548

In [2]:
tf = TfidfVectorizer(analyzer = 'word', ngram_range = (1, 2), min_df = 0)

list1 = ['I like apple and this monitor and this ground', 'I like this ground and this ground is 100m',
        'I am looking this ground at the monitor', 'I am looking this ground at the television',
        'pen pineapple apple pen']

tfidf_matrix = tf.fit_transform(list1)
tfidf_matrix
Out[2]:

<5x34 sparse matrix of type '<class 'numpy.float64'>'
	with 56 stored elements in Compressed Sparse Row format>

5개의 행이 34개의 단어로 이루어져 있으며 0이 아닌 값은 총 56개가 존재한다는 뜻입니다.

In [3]:
tfidf_matrix.toarray()[0], tfidf_matrix.toarray()[0].shape
Out[3]:
(array([0.        , 0.        , 0.        , 0.44642293, 0.44642293,
        0.22321146, 0.27666486, 0.        , 0.        , 0.        ,
        0.15586815, 0.        , 0.        , 0.        , 0.        ,
        0.        , 0.22321146, 0.27666486, 0.        , 0.        ,
        0.        , 0.22321146, 0.27666486, 0.        , 0.        ,
        0.        , 0.        , 0.        , 0.        , 0.        ,
        0.        , 0.31173631, 0.15586815, 0.27666486]),
 (34,))

첫 번째 행 안에 34개의 단어들이 array 안에서 값이 채워져 있는 모습입니다.

In [4]:
cosine_sim = linear_kernel(tfidf_matrix, tfidf_matrix)
cosine_sim
Out[4]:
array([[1.        , 0.46739466, 0.19012271, 0.12296691, 0.0612277 ],
       [0.46739466, 1.        , 0.19869546, 0.19439867, 0.        ],
       [0.19012271, 0.19869546, 1.        , 0.77157306, 0.        ],
       [0.12296691, 0.19439867, 0.77157306, 1.        , 0.        ],
       [0.0612277 , 0.        , 0.        , 0.        , 1.        ]])

sklearn.metrics.pairwise의 linear_kernel을 통해서 기존에 만들어 놓은 tfidf_matrix를 넣으면 코사인 유사도가 계산됩니다.

In [5]:
c2 = np.corrcoef( tfidf_matrix.toarray() )
c2
Out[5]:
array([[ 1.        ,  0.20136947, -0.23671414, -0.33755445, -0.21502735],
       [ 0.20136947,  1.        , -0.25287475, -0.25771132, -0.31606925],
       [-0.23671414, -0.25287475,  1.        ,  0.63630574, -0.33344966],
       [-0.33755445, -0.25771132,  0.63630574,  1.        , -0.33205463],
       [-0.21502735, -0.31606925, -0.33344966, -0.33205463,  1.        ]])

이 array를 corr를 계산하는 np.corrcoef를 이용해 계산하면 상관관계가 일단은 계산이 됩니다.

여기서 유사도와 상관관계에서의 가장 큰 차이점은 음수의 존재 여부입니다.

코사인 유사도의 경우, 결론적으로 빈도의 분포를 활용하기 때문에 일반적인 코사인 유사도의 값인 -1 ~ 1 사이가 아닌 0 ~ 1 사이입니다.

개별 예시와 합쳐진 예시의 차이

다음은 하나의 리스트를 이용해 계산한 결과와 각각으로 나누어서 계산한 결과를 통해서

코사인 유사도의 계산과 상관관계의 계산 결과를 살펴보려고 합니다.

합쳐진 예시

In [6]:
# 예시1
tf2 = TfidfVectorizer(analyzer = 'word', ngram_range = (1, 2), min_df = 0)

list2 = ['I like apple and this monitor and this ground', 'I like this ground and this ground is 100m',
        'I am looking this ground at the monitor', 'I am looking this ground at the television']

tfidf_matrix2 = tf2.fit_transform(list2)
tfidf_matrix2
Out[6]:
<4x29 sparse matrix of type '<class 'numpy.float64'>'
	with 50 stored elements in Compressed Sparse Row format>
In [7]:
cosine_sim2 = linear_kernel(tfidf_matrix2, tfidf_matrix2)
cosine_sim2
Out[7]:
array([[1.        , 0.44199978, 0.17531557, 0.10887498],
       [0.44199978, 1.        , 0.17988015, 0.17545666],
       [0.17531557, 0.17988015, 1.        , 0.76198892],
       [0.10887498, 0.17545666, 0.76198892, 1.        ]])
In [8]:
c22 = np.corrcoef( tfidf_matrix2.toarray() )
c22
Out[8]:
array([[ 1.        ,  0.08277906, -0.38357704, -0.49188749],
       [ 0.08277906,  1.        , -0.41825066, -0.42250346],
       [-0.38357704, -0.41825066,  1.        ,  0.58031978],
       [-0.49188749, -0.42250346,  0.58031978,  1.        ]])

개별 예시

In [9]:
tf3= TfidfVectorizer(analyzer = 'word', ngram_range = (1, 2), min_df = 0)

list3 = ['I like apple and this monitor and this ground', 'I like this ground and this ground is 100m']

tfidf_matrix3 = tf3.fit_transform(list3)

cosine_sim3 = linear_kernel(tfidf_matrix3, tfidf_matrix3)
cosine_sim3
Out[9]:
array([[1.        , 0.48413539],
       [0.48413539, 1.        ]])
In [10]:
c3 = np.corrcoef( tfidf_matrix3.toarray() )
c3
Out[10]:
array([[ 1.        , -0.38957117],
       [-0.38957117,  1.        ]])
In [11]:
tf4= TfidfVectorizer(analyzer = 'word', ngram_range = (1, 2), min_df = 0)

list4 = ['I like apple and this monitor and this ground', 'I am looking this ground at the monitor']

tfidf_matrix4 = tf4.fit_transform(list4)

cosine_sim4 = linear_kernel(tfidf_matrix4, tfidf_matrix4)
cosine_sim4
Out[11]:
array([[1.        , 0.18200376],
       [0.18200376, 1.        ]])
In [12]:
c4 = np.corrcoef( tfidf_matrix4.toarray() )
c4
Out[12]:
array([[ 1.        , -0.82809385],
       [-0.82809385,  1.        ]])
In [13]:
tf5= TfidfVectorizer(analyzer = 'word', ngram_range = (1, 2), min_df = 0)

list5 = ['I like apple and this monitor and this ground', 'I am looking this ground at the television']

tfidf_matrix5 = tf5.fit_transform(list5)

cosine_sim5 = linear_kernel(tfidf_matrix5, tfidf_matrix5)
cosine_sim5
Out[13]:
array([[1.        , 0.14048494],
       [0.14048494, 1.        ]])
In [14]:
c5 = np.corrcoef( tfidf_matrix5.toarray() )
c5
Out[14]:
array([[ 1.        , -0.83667967],
       [-0.83667967,  1.        ]])

네 개의 리스트가 합쳐진 케이스와 첫 번째 항목과 나머지 항목이 각각 나뉘어진 상태에서의

코사인 유사도와 상관관계의 값의 결과입니다.

In [15]:
print(cosine_sim2[0])
print( cosine_sim3[0],cosine_sim4[0][1:], cosine_sim5[0][1:] )
[1.         0.44199978 0.17531557 0.10887498]
[1.         0.48413539] [0.18200376] [0.14048494]
In [16]:
print(c22[0])
print( c3[0], c4[0][1:], c5[0][1:] )
[ 1.          0.08277906 -0.38357704 -0.49188749]
[ 1.         -0.38957117] [-0.82809385] [-0.83667967]

일반적인 자연어에서의 경우가 아닌, 숫자와 숫자간의 경우라면

일단 상관관계에서는 이렇게 분리가 되더라도 값이 동일하여야 합니다,

하지만, 상관관계의 결과를 보면, 값이 바뀌고 있다는 것이 자연어에서의 상관관계는 상대적인 것이라는 것을 확인할 수 있습니다.

즉, 일반적인 변수와 변수간의 관계에서 상관관계를 보는 것과 다르게 자연어 처리를 할 때에는

코사인 유사도를 보든간에 상관관계를 보든간에 상대적이라는 것을 확인할 수 있습니다.

별개 케이스 용

다음은 리스트를 하나 더 추가하여, TF-IDF를 활용할 경우, 값이 어떻게 변화되는지 확인해보고자 합니다.

In [17]:
tf6= TfidfVectorizer(analyzer = 'word', ngram_range = (1, 2), min_df = 0)

list_case2 = ['In this case, we have no choice', 'Life is choice between birth and death',
             'Sometimes i watching youtube for 10 times', 'google\'s youtube has grown significantly in 10 years']

tf_matrix = tf6.fit_transform(list_case2)
tf_matrix
Out[17]:
<4x48 sparse matrix of type '<class 'numpy.float64'>'
	with 52 stored elements in Compressed Sparse Row format>
In [18]:
cos_sim = linear_kernel(tf_matrix, tf_matrix)
cos_sim
Out[18]:
array([[1.        , 0.05000364, 0.        , 0.04770921],
       [0.05000364, 1.        , 0.        , 0.        ],
       [0.        , 0.        , 1.        , 0.10431864],
       [0.04770921, 0.        , 0.10431864, 1.        ]])
In [19]:
coef2 = np.corrcoef( tf_matrix.toarray() )
coef2
Out[19]:
array([[ 1.        , -0.30056629, -0.32935693, -0.33965525],
       [-0.30056629,  1.        , -0.33001768, -0.40765924],
       [-0.32935693, -0.33001768,  1.        , -0.22094461],
       [-0.33965525, -0.40765924, -0.22094461,  1.        ]])

리스트 추가 후 결과 보기

In [20]:
tf7= TfidfVectorizer(analyzer = 'word', ngram_range = (1, 2), min_df = 0)

list_case3 = list2 + list_case2

tf_matrix3 = tf7.fit_transform(list_case3, list_case3)
tf_matrix3
Out[20]:
<8x74 sparse matrix of type '<class 'numpy.float64'>'
	with 102 stored elements in Compressed Sparse Row format>
In [21]:
cos_sim3 = linear_kernel(tf_matrix3, tf_matrix3)

coef3 = np.corrcoef(tf_matrix3.toarray())
Out[21]:
array([ 1.        ,  0.37740849,  0.04803899, -0.03319176])
cosine_sim2[0], cos_sim[0]
Out[31]:
(array([1.        , 0.44199978, 0.17531557, 0.10887498]),
 array([1.        , 0.05000364, 0.        , 0.04770921]))
In [28]:
cos_sim3[0][0:4], cos_sim3[4][4:]
Out[28]:
(array([1.        , 0.4732777 , 0.20111892, 0.13268325]),
 array([1.        , 0.05940594, 0.        , 0.05462482]))

코사인 유사도의 경우, 별개의 경우와 합쳐진 경우에서 해당되는 경우를 가지고 와서 비교를 해본 결과

값의 차이가 존재는 하지만, 이는 애초에 유사도 자체가 상대적인 것이기 때문에 상관이 없으며,

첫 번째와 나머지를 비교했을 때의 결과도 얼마나 유사한지를 잘 보여주고 있다고 생각됩니다.

In [32]:
c22[0], coef2[0]
Out[32]:
(array([ 1.        ,  0.08277906, -0.38357704, -0.49188749]),
 array([ 1.        , -0.30056629, -0.32935693, -0.33965525]))
In [27]:
coef3[0][0:4], coef3[4][4:]
Out[27]:
(array([ 1.        ,  0.37740849,  0.04803899, -0.03319176]),
 array([ 1.        , -0.1379455 , -0.19043601, -0.16249011]))

반면에 상관관계의 경우, TF-IDF라는 상대적인 값을 통해서 상관관계를 구했기 때문에,

일단 일반적으로 생각되는 것처럼 값이 고정되지 않는 것을 볼 수 있었습니다.

첫 번째와 나머지를 비교한 결과에서는 추가로 생성된 리스트에서

원래는 -0.3293, -0.3396의 차이를 가지던 것이 -0.1904, -0.1624로 값의 차이가 바뀐 것을 볼 수 있습니다.

이렇게 작은 추가만으로도 값이 크게 바뀌는데다가 순위까지 바뀌는 것을 볼 수 있었기 때문에

TF-IDF를 사용할 때는 상관관계가 아닌 유사도를 사용하는 것이 순위를 매기기에는 좋다고 생각됩니다.