HNSWアルゴリズム入門：高速な類似検索の仕組み

本記事では、近年注目されている「HNSW（Hierarchical Navigable Small World）」という類似検索アルゴリズムについて、基本的な概念から実装方法まで初心者にもわかりやすく解説します。データ検索の高速化に興味がある方、ベクトル検索の仕組みを知りたい方におすすめの内容です。

## はじめに：なぜHNSWが注目されているのか

近年、画像認識、自然言語処理、レコメンデーションシステムなど様々な分野で、大量のデータから類似したものを高速に見つけ出す「類似検索」の重要性が高まっています。例えば、Netflixの「あなたにおすすめの映画」や、ECサイトの「この商品を見た人はこんな商品も見ています」といった機能の裏側では、類似検索が活躍しています。

しかし、データ量が増えると検索速度が落ちるという問題があります。特に高次元のデータ（例：数百の特徴を持つベクトル）では、この問題が顕著です。HNSWアルゴリズムは、この「次元の呪い」と呼ばれる問題を効率的に解決する方法として2016年に登場し、その優れた性能から急速に普及しています。

## HNSWアルゴリズムとは？基本概念を理解しよう

HNSWは「Hierarchical Navigable Small World」の略で、グラフ理論と「スモールワールド」という概念を応用した検索アルゴリズムです。ここでのポイントは以下の3つです：

1. **グラフ構造を利用する**：データポイントをノード（点）として、類似したデータ同士をエッジ（線）で結びます
2. **階層構造を持つ**：複数の階層からなる構造で、上の階層ほどデータが疎になります
3. **スモールワールド特性を活用**：「知り合いの知り合い」を辿るように効率的に目的地に到達する特性を利用します

これを日常生活の例で考えてみましょう。あなたが東京から大阪の特定のレストランに行きたいとします。まず新幹線（上位階層＝大きなジャンプ）で大阪駅まで行き、次に地下鉄（中位階層）で最寄り駅へ、最後に徒歩（下位階層＝小さなジャンプ）で目的地に到達します。HNSWも同様に、まず大まかな位置に素早く移動し、徐々に精密な検索を行う仕組みになっています。

## HNSWがなぜ速いのか？仕組みを詳しく見る

HNSWの検索プロセスを具体的に見ていきましょう：

![HNSWの階層構造](https://www.pinecone.io/_next/image/?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fvr8gru94%2Fproduction%2F42d4a3ffc43e5dc2758ba8e5d2ef29d4c4d78254-1920x1040.png&w=3840&q=75)
*出典: [Pinecone - Hierarchical Navigable Small Worlds (HNSW)](https://www.pinecone.io/learn/series/faiss/hnsw/)*

1. **多層グラフの構築**：
   - 最下層（L0）にはすべてのデータポイントがあります
   - 上の層ほどノード数が少なく、長距離のリンクが増えます
   - あるノードが上の層に存在する確率は、層が上がるごとに指数関数的に減少します

2. **検索プロセス**：
   - 最上位層からスタートし、クエリ（検索したいデータ）に最も近いノードを見つけます
   - そのノードを起点に下の層に降りていきます
   - 各層で「グリーディ探索」と呼ばれる方法で最も近いノードを探します
   - 最下層まで到達したら、最終的な最近傍を返します

![HNSWの検索プロセス](https://www.pinecone.io/_next/image/?url=https%3A%2F%2Fcdn.sanity.io%2Fimages%2Fvr8gru94%2Fproduction%2Fe63ca5c638bc3cd61cc1cd2ab33b101d82170426-1920x1080.png&w=3840&q=75)
*出典: [Pinecone - Hierarchical Navigable Small Worlds (HNSW)](https://www.pinecone.io/learn/series/faiss/hnsw/)*

重要なのは、上位層で「大まかな方向性」を決め、下位層で「精密な探索」をするという階層的なアプローチです。これにより検索時間を大幅に削減できます。

## 従来手法との比較：何が違うのか？

HNSWの良さを理解するために、主な類似検索アルゴリズムと比較してみましょう：

| 手法 | 特徴 | 長所 | 短所 |
|------|------|------|------|
| 全探索 | すべてのデータを比較 | 正確な結果が得られる | データ量が多いと非常に遅い |
| k-d木 | 空間を再帰的に分割 | 低次元では効率的 | 高次元では効率が下がる |
| LSH | ハッシュ関数でデータをバケットに分類 | 実装が比較的簡単 | 精度と速度のバランスが難しい |
| IVF | データをクラスタリングして分類 | バランスの取れた性能と簡単な実装 | クラスタ数の設定が難しい |
| HNSW | 階層型スモールワールドグラフ | 高次元でも高速で精度も高い | インデックス構築に時間がかかる |

例えば、100万件の128次元ベクトルデータを検索する場合、全探索では数秒かかるところ、HNSWでは数ミリ秒で結果が得られます。しかも、その精度は95%以上を保つことができるのです。

### IVF（Inverted File Index）とHNSWの比較

IVFは特にFAISS（Facebook AI Similarity Search）ライブラリで広く使われている手法で、HNSWと並んで人気のある近似最近傍探索アルゴリズムです。両者の比較を詳しく見てみましょう。

#### IVFの基本的な仕組み

IVFは以下のステップで動作します：

1. **クラスタリング**：すべてのデータをk-means等のアルゴリズムで複数のクラスタ（バケット）に分類します
2. **転置インデックス作成**：各クラスタにどのデータが含まれるかを記録します
3. **検索時**：
   - クエリに最も近いクラスタを特定します
   - そのクラスタ内（または複数の近いクラスタ内）のデータのみを探索します

IVFの仕組みはスーパーマーケットに例えることができます。店内を食品、日用品、衣料品など「セクション（クラスタ）」に分けることで、買い物客（検索クエリ）は目的の商品がありそうなセクションに直行し、店内全体を探し回る必要がなくなります。

#### IVFの技術的詳細

IVFでは検索プロセスが「粗い検索（Coarse Search）」と「細かい検索（Fine Search）」の2段階に分かれています：

1. **粗い検索**：クエリベクトルに最も近いクラスタ中心（セントロイド）を特定します
2. **細かい検索**：特定したクラスタ内のベクトルのみを対象に、クエリとの距離を計算して最も近いものを見つけます

この2段階アプローチにより、全てのデータポイントとの距離計算が必要な全探索に比べて、計算量を大幅に削減できます。

IVFの重要なパラメータは以下の2つです：

- **nlist**：クラスタの数を指定します。多すぎると各クラスタに含まれるデータが少なくなり過ぎ、少なすぎると各クラスタに多くのデータが含まれて検索効率が落ちます。
- **nprobe**：検索時に探索するクラスタの数です。大きくすると精度が上がりますが、検索速度は低下します。

#### HNSWとIVFの比較

| 比較項目 | IVF | HNSW |
|---------|-----|------|
| **仕組み** | クラスタリングベース | グラフベース |
| **構築速度** | 比較的速い | 比較的遅い |
| **メモリ消費** | 中程度 | 高い（特に高いM値の場合） |
| **検索速度** | 速い（nprobe設定に依存） | 非常に速い |
| **精度** | 良い（適切なnprobe設定で） | 非常に良い |
| **スケーラビリティ** | 非常に良い | 良い |
| **実装の複雑さ** | 比較的シンプル | やや複雑 |
| **重要パラメータ** | nlist（クラスタ数）<br>nprobe（検索時に探索するクラスタ数） | M（接続数）<br>ef_construction（構築時の探索範囲）<br>ef（検索時の探索範囲） |
| **メモリ/ディスク** | ディスクベースの実装が容易 | 主にメモリベース |
| **大規模データ** | 数十億規模のデータにも対応可能 | 数億規模まで効率的 |
| **検索アルゴリズム** | クラスタ内の線形探索 | グリーディ探索 |

#### 使い分けのポイント

**IVFが適している場合：**
- 非常に大規模なデータセット（10億件以上）を扱う場合
- メモリリソースが限られている場合
- インデックス構築時間を短縮したい場合
- 精度よりも検索速度とスケーラビリティを重視する場合
- ディスクベースの実装が必要な場合

**HNSWが適している場合：**
- 高い検索精度が必要な場合
- 検索速度を最大限に高めたい場合
- データサイズが中規模（百万〜数千万件程度）の場合
- 十分なメモリリソースがある場合
- リアルタイム性が重要な場合

IVFとHNSWは、どちらも優れた近似最近傍探索アルゴリズムで、用途に応じて最適な選択が変わります。多くの実用システムでは、両方を試して比較したり、ハイブリッドアプローチ（例：IVF+HNSWやIVF+PQ）を採用したりすることも一般的です。

## HNSWの実装例

実際にPythonでHNSWを実装してみましょう。ここでは一般的なライブラリである`hnswlib`を使います：

```python
import numpy as np
import hnswlib
import time

# サンプルデータを作成（1万件の128次元ベクトル）
dim = 128
num_elements = 10000
data = np.random.rand(num_elements, dim).astype('float32')

# クエリデータ（検索したいベクトル）
query_data = np.random.rand(1, dim).astype('float32')

# HNSWインデックスの作成
index = hnswlib.Index(space='l2', dim=dim)  # l2は「ユークリッド距離」を意味します
index.init_index(max_elements=num_elements, ef_construction=200, M=16)
index.add_items(data)

# 検索パラメータの設定
index.set_ef(50)  # ef: 探索の精度と速度のバランスを決めるパラメータ

# 検索実行（k=5で最も近い5つを検索）
start_time = time.time()
labels, distances = index.knn_query(query_data, k=5)
end_time = time.time()

print(f"検索時間: {(end_time - start_time) * 1000:.2f}ミリ秒")
print(f"最も近い5つのインデックス: {labels[0]}")
print(f"それらの距離: {distances[0]}")
```

ここで重要なパラメータは：

- **M**: グラフの平均次数（各ノードからの接続数）を制御します。大きいほど精度が上がりますが、メモリ使用量も増えます。
- **ef_construction**: インデックス構築時の探索範囲を決めます。大きいほどインデックスの品質が向上しますが、構築時間も増えます。
- **ef**: 検索時の探索範囲を決めます。大きいほど精度が向上しますが、検索時間も増えます。

これらのパラメータは、用途に応じて調整する必要があります。

## HNSWの応用例：実世界ではどう使われているか

HNSWはすでに多くの実用システムに採用されています：

1. **画像検索**: 類似画像の検索や画像検索エンジンで使用されています。
2. **レコメンデーションシステム**: コンテンツベースのレコメンデーションシステムで、類似アイテムを推薦するのに活用されています。
3. **テキスト分析**: 文書の類似性検索や質問応答システムに利用されています。
4. **異常検知**: 通常のパターンから外れたデータを効率的に検出するのに役立っています。

これらの応用例では、HNSWが提供する高速性と精度のバランスが重要な役割を果たしています。特に大量のデータを扱うシステムでは、効率的な類似検索が不可欠です。

## 実践的なHNSWチューニングのコツ

実際にHNSWを使う際のチューニングポイントをいくつか紹介します：

### インデックス構築時のポイント

- データ量が多い場合は、`M`を小さめ（8-16程度）に設定し、メモリ消費を抑えることを検討しましょう
- 高い精度が必要な場合は、`ef_construction`を大きく（200-500程度）設定しましょう
- インデックス構築は検索に比べて時間がかかりますが、一度構築すれば何度でも使えるので、時間をかけても良い場合が多いです

```python
# 高精度なインデックスを構築したい場合
index.init_index(max_elements=num_elements, ef_construction=400, M=32)

# メモリ効率を重視したい場合
index.init_index(max_elements=num_elements, ef_construction=200, M=12)
```

### 検索時のポイント

- リアルタイム性が重要な場合は、`ef`を小さめ（20-50程度）に設定しましょう
- バッチ処理など時間に余裕がある場合は、`ef`を大きく（100以上）設定して精度を高めましょう

```python
# 高速検索モード
index.set_ef(30)

# 高精度検索モード
index.set_ef(150)
```

### 実運用での注意点

- インデックスの保存と読み込みをサポートしているので、事前に構築しておくことでアプリケーション起動時間を短縮できます

```python
# インデックスの保存
index.save_index("my_index.bin")

# インデックスの読み込み
new_index = hnswlib.Index(space='l2', dim=dim)
new_index.load_index("my_index.bin", max_elements=num_elements)
```

## 最新の応用例と今後の展望

HNSWアルゴリズムは、その高速性と精度のバランスから、さまざまな分野で活用されています。公開されている情報に基づいた主な応用分野と展望は以下の通りです：

- **大規模データベース**: HNSWはFaissやMilvusといったベクトルデータベースに組み込まれ、効率的な類似検索を支えています。
- **情報検索システム**: 文書や画像の類似性検索において、検索速度と精度を向上させています。
- **AI分野**: 機械学習パイプラインの一部として、特徴ベクトルの効率的な検索に活用されています。

今後も、データ量の増加とともにHNSWのような効率的なアルゴリズムの重要性は高まるでしょう。特に、ベクトル検索の需要が高まる中、より高速で正確な検索技術への研究開発が進んでいくと考えられます。

## まとめ

HNSWアルゴリズムは、高次元データの類似検索において優れた性能を発揮する強力なツールです。その主な利点をまとめると：

- 高次元データでも高速に検索できる
- 高い検索精度を維持できる
- スケーラビリティに優れている

一方、IVFもまた優れた近似最近傍探索アルゴリズムで、特に大規模データセットでの扱いやすさとスケーラビリティに強みがあります。これらのアルゴリズムは、使用ケースや要件によって使い分けるのが良いでしょう。


## 参考文献

2. [Faiss: A library for efficient similarity search](https://github.com/facebookresearch/faiss)
3. [hnswlib: Fast Approximate Nearest Neighbor Search](https://github.com/nmslib/hnswlib)
4. [Milvus: An open-source vector database for scalable similarity search](https://milvus.io/)
5. [Vespaの公式ドキュメント - Approximate Nearest Neighbor Search using HNSW](https://docs.vespa.ai/en/approximate-nn-hnsw.html)
6. [Inverted File Indexing (IVF) in FAISS: A Comprehensive Guide](https://medium.com/@Jawabreh0/inverted-file-indexing-ivf-in-faiss-a-comprehensive-guide-c183fe979d20)
7. [Hierarchical Navigable Small Worlds (HNSW) - Pinecone](https://www.pinecone.io/learn/series/faiss/hnsw/)