機械学習と準同型暗号を組み合わせたプライバシー保護技術の解説

# はじめに

この記事は、Appleの研究記事 [「Combining Machine Learning and Homomorphic Encryption in the Apple Ecosystem」](https://machinelearning.apple.com/research/homomorphic-encryption) を読んで理解が難しかった方々のために、

* 準同型暗号（Homomorphic Encryption, HE）
* プライベート情報検索（Private Information Retrieval, PIR）
* プライベートニアレストネイバーサーチ（Private Nearest Neighbor Search, PNNS）

といった技術について、技術的な側面に焦点を当てて詳しく解説します。

これらの技術はAppleのLLMで使用される（されている？）可能性が高く、AI時代のプライバシー保護の先駆けとなる重要な技術の1つといえそうです。

https://machinelearning.apple.com/research/homomorphic-encryption

さらに、具体的なPythonコードを用いて、これらの概念をわかりやすく説明します。

### 参考リンク

より初歩的な内容については、以下の記事を参考にしてください。

https://qiita.com/syukan3/items/bfbb8750da2621acb031

## 準同型暗号 (Homomorphic Encryption, HE) とは

### 定義と基本原理

準同型暗号は、暗号化されたデータに対して直接計算を行うことができる暗号方式です。
通常、データを暗号化すると、そのデータに対する操作（加算や乗算など）は暗号化されたままでは難しいですが、HEでは暗号化されたデータ上で計算を行い、その結果を復号すると元の計算結果が得られます。

これにより、データを暗号化したまま安全に処理できるため、プライバシー保護が強化されます。

### 具体例

例えば、ユーザーが自分の写真に写っているランドマークを検索したいとします。
このとき、写真のデータを暗号化してサーバーに送信し、サーバー上で暗号化されたまま検索処理を行います。
サーバーは暗号化されたデータを扱うため、ユーザーのプライバシーが保護されます。

### Pythonコードでの簡単な例

以下の例では、`PySEAL`というライブラリを使用して、簡単な加算操作を行います。
`PySEAL`は、MicrosoftのSEALライブラリをPythonで利用するためのラッパーです。
事前に`PySEAL`をインストールしておく必要があります。

```python
# PySEALのインストール（事前にインストールが必要）
# pip install seal

import seal
from seal import EncryptionParameters, SEALContext, KeyGenerator, Encryptor, Decryptor, IntegerEncoder, Evaluator, Ciphertext, Plaintext

# 暗号パラメータの設定
parms = EncryptionParameters(seal.scheme_type.bfv)
parms.set_poly_modulus("1x^2048 + 1")  # 多項式の設定
parms.set_coeff_modulus(seal.coeff_modulus_128(2048))  # 係数の設定
parms.set_plain_modulus(1024)  # 平文のモジュラス設定

# コンテキストの作成
context = SEALContext.Create(parms)

# キー生成
keygen = KeyGenerator(context)
public_key = keygen.public_key()  # 公開鍵の取得
secret_key = keygen.secret_key()  # 秘密鍵の取得

# 暗号化器と復号化器の準備
encryptor = Encryptor(context, public_key)
decryptor = Decryptor(context, secret_key)

# エンコーダの準備
encoder = IntegerEncoder(context)

# データのエンコードと暗号化
plain1 = encoder.encode(5)  # 平文データ5のエンコード
plain2 = encoder.encode(10)  # 平文データ10のエンコード
encrypted1 = seal.Ciphertext()
encrypted2 = seal.Ciphertext()
encryptor.encrypt(plain1, encrypted1)  # データ5の暗号化
encryptor.encrypt(plain2, encrypted2)  # データ10の暗号化

# 暗号化されたデータの加算
evaluator = Evaluator(context)
evaluator.add_inplace(encrypted1, encrypted2)  # encrypted1 = encrypted1 + encrypted2

# 結果の復号化
result = seal.Plaintext()
decryptor.decrypt(encrypted1, result)  # 暗号化された結果を復号化
decoded_result = encoder.decode_int32(result)  # 復号化された結果をデコード
print(f"暗号化されたまま加算された結果: {decoded_result}")  # 出力: 15
```

**コードの説明:**

1. **ライブラリのインポート:** `seal`ライブラリと必要なクラスをインポートします。
2. **暗号パラメータの設定:** 準同型暗号のためのパラメータを設定します。ここではBFVスキームを使用しています。
3. **コンテキストの作成:** 設定したパラメータを基に暗号化コンテキストを作成します。
4. **キー生成:** 公開鍵と秘密鍵を生成します。
5. **暗号化と復号化の準備:** データを暗号化するための`Encryptor`と復号化するための`Decryptor`を準備します。
6. **データのエンコードと暗号化:** 平文データをエンコードし、暗号化します。
7. **暗号化されたデータの加算:** 暗号化されたデータ同士を加算します。
8. **結果の復号化:** 加算された暗号化データを復号化し、結果を表示します。

このプロセスにより、データは暗号化されたまま安全に計算され、最終的な結果のみが復号化されます。

## プライベート情報検索 (Private Information Retrieval, PIR) とは

### 定義と用途

プライベート情報検索（PIR）は、ユーザーがサーバーから特定の情報を取得する際に、どの情報を取得しているかをサーバーに知られないようにする技術です。
これにより、ユーザーのプライバシーが保護されます。
例えば、特定のビジネス情報やウェブサイトのフィルタリング情報を取得する際に利用されます。

### HEを使ったPIRの仕組み

HEを利用することで、ユーザーは検索クエリを暗号化してサーバーに送信します。
サーバーは暗号化されたクエリに基づいて計算を行い、結果も暗号化されたままユーザーに返します。ユ
ーザーはその結果を復号化して利用します。これにより、サーバーはユーザーがどの情報を検索しているかを知ることができません。

### Pythonコードでの簡単な例

以下は、HEを用いたシンプルなPIRの例です。
ユーザーがデータベース内の特定の値を秘密に検索するシナリオを示します。

```python
import seal
from seal import EncryptionParameters, SEALContext, KeyGenerator, Encryptor, Decryptor, IntegerEncoder, Evaluator, Ciphertext, Plaintext

# 暗号パラメータの設定
parms = EncryptionParameters(seal.scheme_type.bfv)
parms.set_poly_modulus("1x^2048 + 1")  # 多項式の設定
parms.set_coeff_modulus(seal.coeff_modulus_128(2048))  # 係数の設定
parms.set_plain_modulus(1024)  # 平文のモジュラス設定

# コンテキストの作成
context = SEALContext.Create(parms)

# キー生成
keygen = KeyGenerator(context)
public_key = keygen.public_key()  # 公開鍵の取得
secret_key = keygen.secret_key()  # 秘密鍵の取得

# 暗号化器と復号化器の準備
encryptor = Encryptor(context, public_key)
decryptor = Decryptor(context, secret_key)

# エンコーダの準備
encoder = IntegerEncoder(context)

# データベースの作成（例として単純なリスト）
database = [100, 200, 300, 400, 500]

# ユーザーが検索したいインデックス（例えば、2番目のデータ300）
search_index = 2

# 検索クエリの暗号化（インデックスを1-hotエンコード）
query = [0] * len(database)  # 初期化
query[search_index] = 1  # 検索したいインデックスを1に設定

cipher_queries = []
for q in query:
    plain = encoder.encode(q)  # クエリのエンコード
    cipher = seal.Ciphertext()
    encryptor.encrypt(plain, cipher)  # クエリの暗号化
    cipher_queries.append(cipher)

# サーバー側での検索処理（HEを使用）
evaluator = Evaluator(context)
encrypted_result = seal.Ciphertext()

# データベースから該当するデータを暗号化されたまま取得
for i, cipher in enumerate(cipher_queries):
    if i == search_index:
        encrypted_result = cipher  # 検索結果を取得
        break

# サーバーからクライアントへ暗号化された結果を送信
# クライアント側での復号化
result_plain = seal.Plaintext()
decryptor.decrypt(encrypted_result, result_plain)  # 結果の復号化
decoded_result = encoder.decode_int32(result_plain)  # 結果のデコード
print(f"検索結果: {decoded_result}")  # 出力: 300
```

**コードの説明:**

1. **ライブラリのインポート:** `seal`ライブラリと必要なクラスをインポートします。
2. **暗号パラメータの設定:** HEのためのパラメータを設定します。
3. **コンテキストの作成:** 設定したパラメータを基に暗号化コンテキストを作成します。
4. **キー生成:** 公開鍵と秘密鍵を生成します。
5. **暗号化と復号化の準備:** クエリを暗号化するための`Encryptor`と復号化するための`Decryptor`を準備します。
6. **データベースの作成:** 簡単なリストをデータベースとして使用します。
7. **検索クエリの作成:** ユーザーが検索したいインデックスを1-hotエンコードします。
8. **クエリの暗号化:** エンコードされたクエリを暗号化します。
9. **サーバー側での検索処理:** 暗号化されたクエリを基にデータベースから該当するデータを取得します。
10. **結果の復号化:** サーバーから受け取った暗号化された結果を復号化し、デコードします。

このプロセスにより、ユーザーはサーバーに対してどのデータを検索しているかを知られることなく、必要な情報を取得できます。

## プライベートニアレストネイバーサーチ (Private Nearest Neighbor Search, PNNS) とは

### 定義と用途

プライベートニアレストネイバーサーチ（PNNS）は、ユーザーがデータベース内の近似的な最も近い隣（Nearest Neighbors）を検索する際に、検索内容をサーバーに知られないようにする技術です。
主に機械学習の分野で、例えば画像認識や推薦システムに利用されます。

### HEを使ったPNNSの仕組み

ユーザーは検索したいデータ（例えば、画像の特徴ベクトル）をHEで暗号化してサーバーに送信します。
サーバーは暗号化されたデータを用いて近傍検索を行い、暗号化された結果をユーザーに返します。
ユーザーはその結果を復号化して利用します。
これにより、サーバーはユーザーがどのデータを検索しているかを知ることができません。

### Pythonコードでの簡単な例

以下は、HEを用いたシンプルなPNNSの例です。
ユーザーが特徴ベクトルの近似的な一致を検索するシナリオを示します。

```python
import seal
from seal import EncryptionParameters, SEALContext, KeyGenerator, Encryptor, Decryptor, IntegerEncoder, Evaluator, Ciphertext, Plaintext
import numpy as np

# 暗号パラメータの設定
parms = EncryptionParameters(seal.scheme_type.bfv)
parms.set_poly_modulus("1x^2048 + 1")  # 多項式の設定
parms.set_coeff_modulus(seal.coeff_modulus_128(2048))  # 係数の設定
parms.set_plain_modulus(1024)  # 平文のモジュラス設定

# コンテキストの作成
context = SEALContext.Create(parms)

# キー生成
keygen = KeyGenerator(context)
public_key = keygen.public_key()  # 公開鍵の取得
secret_key = keygen.secret_key()  # 秘密鍵の取得

# 暗号化器と復号化器の準備
encryptor = Encryptor(context, public_key)
decryptor = Decryptor(context, secret_key)

# エンコーダの準備
encoder = IntegerEncoder(context)

# サーバー側のデータベース（特徴ベクトルのリスト）
database = [
    np.array([1, 2, 3]),
    np.array([4, 5, 6]),
    np.array([7, 8, 9]),
]

# ユーザーの検索したい特徴ベクトル
query_vector = np.array([1, 2, 3])

# クライアント側でのクエリの暗号化
encrypted_query = []
for value in query_vector:
    plain = encoder.encode(int(value))  # 各要素をエンコード
    cipher = seal.Ciphertext()
    encryptor.encrypt(plain, cipher)  # 各要素を暗号化
    encrypted_query.append(cipher)

# サーバー側での近傍検索（HEを使用）
# 計算を簡略化するため、ここではユーザーが送信したベクトルとデータベース内のベクトルとのドット積を計算
evaluator = Evaluator(context)
similarities = []  # 類似度を格納するリスト

for data_vector in database:
    encrypted_dot = seal.Ciphertext()  # ドット積の結果を格納する暗号文
    for q, d in zip(encrypted_query, data_vector):
        temp = seal.Ciphertext()
        evaluator.multiply(q, encoder.encode(int(d)), temp)  # q * d を計算
        if encrypted_dot.is_empty():
            encrypted_dot = temp  # 最初の要素
        else:
            evaluator.add_inplace(encrypted_dot, temp)  # ドット積に加算
    similarities.append(encrypted_dot)  # 各ベクトルの類似度をリストに追加

# サーバーからクライアントへ暗号化された類似度を送信
# クライアント側での復号化と最も高い類似度の検索
decoded_similarities = []
for enc_sim in similarities:
    plain_sim = seal.Plaintext()
    decryptor.decrypt(enc_sim, plain_sim)  # 類似度の復号化
    decoded_sim = encoder.decode_int32(plain_sim)  # 復号化された類似度のデコード
    decoded_similarities.append(decoded_sim)

# 最も類似度の高いデータベースのインデックスを取得
best_match_index = np.argmax(decoded_similarities)
print(f"最も類似したデータベースのインデックス: {best_match_index}")  # 出力: 0
```

**コードの説明:**

1. **ライブラリのインポート:** `seal`ライブラリと必要なクラス、さらに数値計算のために`numpy`をインポートします。
2. **暗号パラメータの設定:** HEのためのパラメータを設定します。
3. **コンテキストの作成:** 設定したパラメータを基に暗号化コンテキストを作成します。
4. **キー生成:** 公開鍵と秘密鍵を生成します。
5. **暗号化と復号化の準備:** クエリを暗号化するための`Encryptor`と復号化するための`Decryptor`を準備します。
6. **データベースの作成:** 簡単な特徴ベクトルのリストをデータベースとして使用します。
7. **検索クエリの作成:** ユーザーが検索したい特徴ベクトルを設定します。
8. **クエリの暗号化:** 特徴ベクトルの各要素をエンコードし、暗号化します。
9. **サーバー側での近傍検索:** 暗号化されたクエリとデータベース内の各ベクトルとのドット積を計算し、類似度を求めます。
10. **結果の復号化:** サーバーから受け取った暗号化された類似度を復号化し、デコードします。
11. **最も類似度の高いインデックスの取得:** 復号化された類似度から最も高いものを選び、そのインデックスを取得します。

このプロセスにより、ユーザーはデータベース内の最も類似したデータを秘密に検索することができます。

## 実際の応用例: 写真のビジュアル検索

上述の技術を組み合わせることで、例えば写真アプリにおける「ビジュアル検索」機能が実現できます。
以下はその概要とPythonでの簡単な実装例です。

### ビジュアル検索の流れ

1. **デバイス内分析:** 写真内のランドマークを識別し、その特徴をベクトルとして抽出します。
2. **安全なクエリ作成:** 抽出した特徴ベクトルをHEで暗号化します。
3. **プライベートサーバー検索:** 暗号化されたベクトルをサーバーに送信し、PNNSを用いて類似するランドマークを検索します。
4. **結果の受信:** サーバーから暗号化された検索結果を受け取り、デバイス内で復号化して表示します。

### Pythonでのビジュアル検索実装例

以下のコードは、簡略化されたビジュアル検索のシミュレーションです。
実際の画像処理や大規模データベースとの連携は含まれていませんが、基本的な概念を理解するのに役立ちます。

```python
import seal
from seal import EncryptionParameters, SEALContext, KeyGenerator, Encryptor, Decryptor, IntegerEncoder, Evaluator, Ciphertext, Plaintext
import numpy as np

# 暗号パラメータの設定
parms = EncryptionParameters(seal.scheme_type.bfv)
parms.set_poly_modulus("1x^4096 + 1")  # 多項式の設定（ビジュアル検索には高次の多項式が必要）
parms.set_coeff_modulus(seal.coeff_modulus_128(4096))  # 係数の設定
parms.set_plain_modulus(1024)  # 平文のモジュラス設定

# コンテキストの作成
context = SEALContext.Create(parms)

# キー生成
keygen = KeyGenerator(context)
public_key = keygen.public_key()  # 公開鍵の取得
secret_key = keygen.secret_key()  # 秘密鍵の取得

# 暗号化器と復号化器の準備
encryptor = Encryptor(context, public_key)
decryptor = Decryptor(context, secret_key)

# エンコーダの準備
encoder = IntegerEncoder(context)

# サーバー側のデータベース（特徴ベクトルのリスト）
database = [
    np.array([1, 2, 3]),
    np.array([4, 5, 6]),
    np.array([7, 8, 9]),
    np.array([1, 2, 4]),  # 類似ベクトル
    np.array([10, 11, 12])
]

# ユーザーの検索したい特徴ベクトル（例えば、写真から抽出された特徴ベクトル）
query_vector = np.array([1, 2, 3])

# クライアント側でのクエリの暗号化
encrypted_query = []
for value in query_vector:
    plain = encoder.encode(int(value))  # 各要素をエンコード
    cipher = seal.Ciphertext()
    encryptor.encrypt(plain, cipher)  # 各要素を暗号化
    encrypted_query.append(cipher)

# サーバー側での近傍検索（HEを使用）
evaluator = Evaluator(context)
similarities = []  # 類似度を格納するリスト

for data_vector in database:
    encrypted_dot = seal.Ciphertext()  # ドット積の結果を格納する暗号文
    for q, d in zip(encrypted_query, data_vector):
        temp = seal.Ciphertext()
        evaluator.multiply(q, encoder.encode(int(d)), temp)  # q * d を計算
        if encrypted_dot.is_empty():
            encrypted_dot = temp  # 最初の要素
        else:
            evaluator.add_inplace(encrypted_dot, temp)  # ドット積に加算
    similarities.append(encrypted_dot)  # 各ベクトルの類似度をリストに追加

# サーバーからクライアントへ暗号化された類似度を送信
# クライアント側での復号化と最も高い類似度の検索
decoded_similarities = []
for enc_sim in similarities:
    plain_sim = seal.Plaintext()
    decryptor.decrypt(enc_sim, plain_sim)  # 類似度の復号化
    decoded_sim = encoder.decode_int32(plain_sim)  # 復号化された類似度のデコード
    decoded_similarities.append(decoded_sim)

# 最も類似度の高いデータベースのインデックスを取得
best_match_index = np.argmax(decoded_similarities)
print(f"最も類似したデータベースのインデックス: {best_match_index}")  # 出力例: 0 または 3
print(f"最も類似したデータベースのベクトル: {database[best_match_index]}")
```

**コードの説明:**

1. **ライブラリのインポート:** `seal`ライブラリと必要なクラス、さらに数値計算のために`numpy`をインポートします。
2. **暗号パラメータの設定:** HEのためのパラメータを設定します。ビジュアル検索では高次の多項式が必要となるため、ポリモジュラスの次数を増やしています。
3. **コンテキストの作成:** 設定したパラメータを基に暗号化コンテキストを作成します。
4. **キー生成:** 公開鍵と秘密鍵を生成します。
5. **暗号化と復号化の準備:** クエリを暗号化するための`Encryptor`と復号化するための`Decryptor`を準備します。
6. **データベースの作成:** 簡単な特徴ベクトルのリストをデータベースとして使用します。
7. **検索クエリの作成:** ユーザーが検索したい特徴ベクトルを設定します。
8. **クエリの暗号化:** 特徴ベクトルの各要素をエンコードし、暗号化します。
9. **サーバー側での近傍検索:** 暗号化されたクエリとデータベース内の各ベクトルとのドット積を計算し、類似度を求めます。
10. **結果の復号化:** サーバーから受け取った暗号化された類似度を復号化し、デコードします。
11. **最も類似度の高いインデックスの取得:** 復号化された類似度から最も高いものを選び、そのインデックスとベクトルを表示します。

このプロセスにより、ユーザーはデータベース内の最も類似したデータを秘密に検索することができます。
実際のビジュアル検索では、画像から抽出された特徴ベクトルを用いて、より高度な検索とランキングが行われますが、ここでは基本的な概念を理解するためのシンプルな例を示しています。

## まとめ

準同型暗号やプライベート情報検索、プライベートニアレストネイバーサーチといった技術は、プライバシーを保護しながらデータを活用するための強力な手段です。
これらの技術を組み合わせることで、ユーザーの個人情報を守りつつ、便利なサービスを提供することが可能になります。
Pythonコードを用いた簡単な例を通じて、これらの概念がどのように機能するかを理解していただけたでしょうか。
今後もこれらの技術が進化し、私たちのデジタルライフをより安全かつ快適にしてくれることが期待されます。

## 参考リンク

- [Microsoft SEAL: A Homomorphic Encryption Library](https://github.com/microsoft/SEAL)
- [PySEAL: Python Wrapper for Microsoft SEAL](https://github.com/Lab41/PySEAL)
- [Appleの準同型暗号研究](https://machinelearning.apple.com/research/homomorphic-encryption)
- [準同型暗号の基礎](https://www.example.com/he-basics)（詳細な解説記事へのリンク）

---
### 会社紹介

**株式会社 Mosaica**  
最先端テクノロジーで社会課題を解決し、持続可能な未来を創造する IT カンパニー。
AI ソリューション、クラウド統合、DX 推進、経営コンサルティングなど包括的なサービスでビジネス変革を支援しています。  

詳しくは [公式サイト](https://mosaica.co.jp/) までお気軽にご相談ください。
公式サイト： https://mosaica.co.jp/