코사인 유사도(cosine similarity)는 두 벡터간의 방향 유사도를 나타내며 코사인 값으로 -1 ~ 1 사이의 값이 나온다.
1에 가깝다면 두 벡터는 같은 방향을 바라보고 있다는 의미이고 0에 가까우면 두 벡터는 직교 그리고 -1에 가까우면 두 벡터는 반대 방향을 바라보고 있다는 의미가 된다.
수식으로 살펴보면 아래와 같다.
$$similarity(V_{a}, V_{b}) = \frac{ V_{a} \cdot V_{b} }{ \|V_{a}\|_{2} \times \|V_{b}\|_{2} } = V_{a(normalized)} \cdot V_{b(normalized)}$$
계산을 진행 할 때는 맨 마지막 항 처럼 정규화된 두 벡터의 내적만 계산하면 되고, 이를 numpy를 사용하여 그대로 구현하면 아래와 같이 작성을 할 수 있다.
import numpy as np | |
def cosine_similarity(a: np.array, b: np.array, eps: float = 1e-12) -> np.array: | |
a /= np.expand_dims(np.fmax(np.linalg.norm(a, axis=-1), eps), axis=-1) | |
b /= np.expand_dims(np.fmax(np.linalg.norm(b, axis=-1), eps), axis=-1) | |
return np.matmul(a, b.T) |
코드에는 eps를 사용하여 만약 L2 norm 값이 1e-12보다 작은 경우(사실 원래 목적은 0이 되는 걸 방지하려고 넣은거임)를 제거하기 위해서 추가하였다.
사실 pytorch나 scikit-learn에 있는 코사인 유사도를 사용하면 편하지만, numpy로 구현한 이유는 속도 측면에서 이득이 있기 때문에 이렇게 구현했다. 간단하게 scikit-learn과 numpy를 비교한 결과는 아래 그림1과 같다.

비교에 각 2차원 어레이를 사용하였다. 예로 가로축 값이 10이면 10x10 행렬을 계산에 사용했다는 의미가 되며 세로축은 해당 행렬을 계산하는데 걸린 시간을 의미한다.
실험을 진행한 코드는 아래와 같다.
from datetime import datetime | |
# test on 1 thread cpu | |
import mkl | |
mkl.set_num_threads(1) | |
import numpy as np | |
from sklearn.metrics.pairwise import cosine_similarity | |
def scikit_cosine_time(a:np.array, b:np.array) -> None: | |
_ = cosine_similarity(a, b) | |
def numpy_cosine_time(a:np.array, b:np.array) -> None: | |
a /= np.expand_dims(np.fmax(np.linalg.norm(a, axis=-1), 1e-12), axis=-1) | |
b /= np.expand_dims(np.fmax(np.linalg.norm(b, axis=-1), 1e-12), axis=-1) | |
_ = np.matmul(a, b.T) | |
# for save of times | |
scikit_cosine_times = [] | |
numpy_cosine_times = [] | |
array_size = list(range(1, 1001)) | |
# loop for calculate times | |
for i in array_size: | |
a = np.random.randn(i, i) | |
b = np.random.randn(i, i) | |
start = datetime.now() | |
scikit_cosine_time(a, b) | |
scikit_cosine_times.append((datetime.now() - start).total_seconds()) | |
start = datetime.now() | |
numpy_cosine_time(a, b) | |
numpy_cosine_times.append((datetime.now() - start).total_seconds()) | |
# plot processing | |
import matplotlib.pyplot as plt | |
fig, ax = plt.subplots() | |
ax.set_xlabel("array size (ex. 100 => 100x100)") | |
ax.set_ylabel("times (second)") | |
ax.plot(array_size, scikit_cosine_times, "r", label='scikit cosine') | |
ax.plot(array_size, numpy_cosine_times, "g", label='numpy cosine') | |
ax.legend() | |
plt.savefig("./result.png") |
결론은 큰 차이를 보기 힘들엇지만 시간이 중요하다면 이렇게 만들어서 사용하는 것도 괜찮을 것 같다.
'공부 또는 팁' 카테고리의 다른 글
PCIe ssd가 안잡힐 때 혹은 SATA ssd가 안잡힐 때 (0) | 2020.12.24 |
---|---|
Ubuntu GUI 켜고 끄기 (0) | 2020.12.23 |
jupyter 에서 pyspark 사용하기 (0) | 2020.10.13 |
첫 ray 사용자를 위한 팁 (0) | 2020.09.15 |
Powershell vim 설치 및 alias 설정하기 (0) | 2020.09.02 |