annoy: Approximate Nearest Neighbors
annoy는 스포티파이에서 개발한 라이브러리로, 벡터들 간의 거리를 계산하여 빠르게 유사한 벡터들을 찾아주는 라이브러리이다. Approximate Nearest Neighbors Oh Yeah의 줄임말로, 직역하자면 근사한 이웃 찾기 아싸? 만세(?) 정도가 될 것 같다.
이름에서부터 느끼다 시피 annoy는 최근접 이웃 알고리즘을 사용한다. annoy를 추천시스템에 적용하기 위해 최근접 이웃을 찾아야하는 경우를 생각해보자.
1. 사용자의 로그가 없을 때: 일반적인 프로필 내용(성별, 나이, 지역 등)을 이용해 사용자와 유사한 특성을 가진 다른 유저들 찾기 → 다른 유저들이 선호했던 아이템들을 사용자에게 추천
2. 사용자의 로그가 쌓인 후
- Collaborative Filtering: 사용 기록(history)을 벡터화하여 다른 유저들의 사용 기록과 비교 → 사용 기록 벡터들끼리 거리를 구하고 유사한 사용 기록 찾기 → 유사한 사용 기록에 기록된 아이템을 추천
- Content based Filtering: 사용자가 관심있어 한 아이템들의 특징을 벡터화하여 다른 아이템과 비교 → 아이템 벡터들끼리 거리를 구하고 유사한 아이템 찾기 → 사용자가 클릭하지 않았지만 유사한 아이템을 추천
추천시스템은 수집한 벡터들끼리 거리를 계산한 후에 특정 벡터와 거리가 가까운, 즉 유사한 벡터를 찾아서 제공한다. 그런데 벡터들이 많아지고 차원도 커지면 계산량이 기하급수적으로 늘어나게 된다. 이 계산량을 줄이기 위해 spotify에서 개발한 알고리즘이 바로 annoy이다. annoy는 트리 기반 알고리즘으로 빠르게 유사한 벡터를 검색할 수 있다.
annoy 작동방식
먼저 전체 벡터에서 랜덤하게 두 벡터를 선택하고, 두 지점 사이의 거리를 정확히 동일하게 나누는 hyperplane(초평면)을 찾는다. 초평면을 기준으로 하나의 공간은 두 공간으로 나누어진다. 이때 특정 벡터를 주면 그 벡터는 두 공간 중 하나의 공간에 속하게 될 것이다. 그 후 해당 공간에 속한 다른 벡터들과의 거리를 구하고, 유사한(=가까운) 벡터를 N개 찾는 방식이다.
그런데 그 공간에 속하는 벡터가 너무 적어서, 원하는 N개만큼 아이템이 없을 수도 있다. 또는 실제로는 유사한데, 랜덤하게 공간을 분리하다보니 우연하게 다른 space에 속하게 되는 가능성도 있다. annoy는 tree를 여러개 만들어 이 문제를 해결한다. 각각의 트리가 랜덤하게 공간을 분리한 후, 모든 트리에서 서치를 한다. 모든 트리에서 추천할 후보들이 추려지면, 그 중 중복된 아이템은 제거하고, 가장 유사한 N개만 반환하는 방식이다.
annoy는 C++으로 구현되어 계산이 매우 빠른데, 이때문에 C++이 설치되어 있어야 이후에 파이썬에서 annoy를 설치할 수 있다.
annoy 사용방법
파이썬 및 라이브러리 version
파이썬: 3.7.4
annoy: 1.17.1
import pandas as pd
import numpy as np
from sklearn.datasets import load_boston
import annoy
데이터 준비
data = load_boston()
df = pd.DataFrame(data.data, columns = data.feature_names)
display(df.shape, df.head())
사용한 데이터는 sklearn에서 기본으로 제공하는 보스턴 부동산 데이터이다.
- CRIM: 타운별 1인당 범죄율
- ZN: 25,000 평방 피트 이상의 부지에 대해 구획된 주거용 토지의 비율.
- INDUS: 타운별 비소매업 에이커의 비율.
- CHAS: Charles River 여부
- NOX: 일산화질소 농도
- RM: 주거당 평균 객실 수
- AGE: 1940년 이전에 제작된 자가용 유닛의 비율
- DIS: 5개의 보스턴 고용 센터까지의 DIS 가중 거리
- RAD: 방사형 고속도로 접근성 지수
- TAX: $10,000당 전체 가치 재산세율
- PTRATIO: 타운별 학생-교사 비율
- B: 1000(Bk - 0.63)^2, Bk는 타운별 흑인의 비율
- LSTAT: 저소득층 비율
annoy 모델 초기화
annoy_index = annoy.AnnoyIndex(f=df.shape[1], metric='angular')
df_arr = df.to_numpy()
for idx in range(df_arr.shape[0]):
vector = df_arr[idx, :]
annoy_index.add_item(idx, vector)
annoy의 AnnoyIndex 를 이용해 모델을 구축할 수 있다. 모델 구축에 앞서 초기화를 위해 먼저 f, 피처의 개수와 metric, 거리계산 방법을 지정해주었다.
거리 계산 방법으로는 5가지 방식(augular, euclidean, manhattan, hamming, dot) 중 하나를 사용할 수 있다.
본 예제에서는 angular distance를 사용하였다. angular라고 하길래 cosine 거리의 다른 이름인가? 싶었는데 검색해보니 차이가 있는 것 같다. 두 벡터 사이의 각도가 작을 때 cosine similarity가 큰 차이를 보이지 않아 이를 보완한 것이 angular distance라고 한다. 자세한 내용은 아래 사이트를 통해 확인해보시길..
https://math.stackexchange.com/questions/2874940/cosine-similarity-vs-angular-distance
Cosine similarity vs angular distance
While checking Google's Universal sentence encoder paper, I found that they mention that using a similarity based on angular distance performs better than raw cosine similarity. More specifically...
math.stackexchange.com
annoy 초기화 후에는 add_item 함수를 이용해 인덱스마다 벡터를 할당한다. 거리계산하려는 벡터마다 라벨을 달아주는 건데, 유사한 벡터를 찾은 output을 라벨로 제공하기 때문에 필요한 과정이다.
annoy 모델 구축
annoy_index.build(n_trees=5)
annoy_index.save('test.ann')
built 함수를 통해 annoy 모델을 만들 수 있다. n_tree는 모델링 시 만드는 트리 수를 지정하는 것으로, 많은 트리를 만들수록 정확도가 올라간다고 한다. 한번 build한 후에는 인덱스와 아이템을 추가할 수 없으니 주의해야 한다.
만든 annoy 모델을 저장하고 싶다면 save 함수를 사용한다.
유사 벡터 검색
본 예제에서는 get_nns_by_vector 함수를 이용하여 컬럼별 중앙값 벡터와 유사한 벡터들을 찾아보겠다
med_value = df.median()
med_value
get_nns_list = annoy_index.get_nns_by_vector(vector=med_value, n=5, include_distances=True)
get_nns_list
중앙값 벡터와 유사한 벡터들을 5개 찾고, 계산한 결과도 함께 제공하게 하였다. 위 함수는 결과로 유사한 벡터의 라벨을 제공한다. 때문에 기존 벡터 목록으로 돌아와 라벨을 이용해 유사한 벡터가 그래서 무엇이었는지를 확인할 수 있다.
df.iloc[get_nns_list[0]]
사용해보니 annoy는 정말 빠르긴 하지만, 데이터가 적은 경우에는 numpy나 scipy를 이용해 직접 계산하나, annoy를 사용하나 시간은 비슷하게 걸리는 듯 했다. 다만 알고리즘 구조 상 공간 내에서 유사한 벡터를 찾는다는 점에서 데이터가 아주 많은 경우에 매우 유용하게 쓰일 것 같다.
참고
https://github.com/spotify/annoy
GitHub - spotify/annoy: Approximate Nearest Neighbors in C++/Python optimized for memory usage and loading/saving to disk
Approximate Nearest Neighbors in C++/Python optimized for memory usage and loading/saving to disk - GitHub - spotify/annoy: Approximate Nearest Neighbors in C++/Python optimized for memory usage an...
github.com
https://scikit-learn.org/stable/datasets/toy_dataset.html
7.1. Toy datasets
scikit-learn comes with a few small standard datasets that do not require to download any file from some external website. They can be loaded using the following functions: These datasets are usefu...
scikit-learn.org
Nearest neighbors and vector models – part 2 – algorithms and data structures
This is a blog post rewritten from a presentation at NYC Machine Learning on Sep 17. It covers a library called Annoy that I have built that helps you do nearest neighbor queries in high dimensional spaces.
erikbern.com
'繩鋸木斷水滴石穿 > AI | 머신러닝' 카테고리의 다른 글
[모니터링] 1) model drift의 개념과 원인(data drift, label drift, concept drift) (0) | 2023.04.16 |
---|---|
[metric] 군집분석 평가 지표 1: 실루엣 계수(Silhouette Coefficient) (0) | 2023.03.24 |
RFE와 REFCV: 유의미한 변수를 선택하는 방법 (0) | 2022.07.17 |
[AutoML] pycaret 패키지를 이용한 분류모델 학습 (2) create_model, tune_model, predict_model, finalize_model (0) | 2022.04.24 |
[AutoML] pycaret 패키지를 이용한 분류모델 학습 (1) setup, add_metrics, compare_models (0) | 2022.03.27 |