秘密分散型機械学習ライブラリ　CrypTenで１＋２をするまでの道のり

## 概要
facebook AI リサーチグループより、
Pytorch ライブラリの秘密分散ラッパーとしてOSS 化されたライブラリ
`CrypTen` について数日触ってみました。
https://github.com/facebookresearch/CrypTen/issues

そのときのTipsを備忘録として残しておきます。



## CrypTen のチュートリアル
CrypTen レポジトリに公開されているチュートリアルを試してみました。

## できること
データ共有のコンテクストにて、cryptenが利用できます。
チュートリアルでは、４つのケースについてCrypTenをどう使いpytorchの学習、推論を複数サーバで実行するかのスクリプトが書かれています。

### 4つのケースについて
- 横分裂 (データのあるカラムをサーバAが保持、他のカラムをサーバBが保持しており、両者のデータを用いてお互いにデータをバラすことなく機械学習を行う。）
- 縦分裂　（データのカラムについては共通している。サーバAはデータを１００件持ち、サーバBが同じカラムのデータを２００件持っているとき、両者のデータを縦につなげ、お互いにデータをバラすことなく機械学習を行う）
- モデル保護　（サーバAが機械学習モデルを持ち、それらのパラメータに関しては知的財産として知られたくない。サーバBは自身の持つデータを、サーバAがもつモデルを使用することで利用したい。しかし、サーバBはデータを推論の際にAには知られたくない。）
- ラベル分裂（機械学習を行うとき、属性データをサーバAが保持しており、ラベルデータをサーバBが保持している。それぞれのサーバは学習の際にそれぞれのデータを知られることなく学習を完遂したい）

CrypTenは、サーバA,サーバBともに同じプログラムを用意し、実行する。
この際に、環境変数によってすべてのノードの数やIP情報などを設定しておく。（サーバA,Bの状況では world_size=2とし、それぞれのサーバのIPなどは知る必要がある。）
この状況で同じプログラムを両方のサーバで実行すると、分散などの処理はライブラリが全て賄ってくれ、
分散させた形で推論や学習を実行してくれる。これにより、両者のサーバは両者のデータを上の４つのシナリオにおいて秘匿しながら機械学習を行うことができる。

## 1 + 2 を秘密分散した形で実行する
Crypten では、pytorch のTensor オブジェクトをラップしたCryptensorを用いる。
Encrypt するときは、データの出処（つまりこのデータがサーバAのものなのか、サーバBのものなのか）を src という引数で渡す。
この例では、非常にシンプルであるが、

- サーバAが１をもち
- サーバBが２を持った状態

で、互いに自分の持っている数を隠したまま、両者の数を足し算することを目的の演算とする
（もちろんこの簡単な例では、返ってきた値から自身の値を引いてしまえば相手の数がわかるわけだが、実際の目的演算はこれより複雑なものであり、この可逆性がないものを使う。）


まず、両方のサーバにCryptenやpytorch などを用意する。

その後、両方のサーバにこのプログラムを書く。

```main.py
import torch
import crypten
from crypten import mpc
import crypten.communicator as comm


def test():
  print("somehting")

def simple_add():
  print("debug1")
  crypten.init()

  print("debug2")
  torch.manual_seed(1)

  rank = comm.get().get_rank()
  print(f'rank >>> {rank}')

  x = crypten.cryptensor(1, src=0)
  y = crypten.cryptensor(2, src=1)

  print(f'x_enc of {rank} >>> {x}')
  print(f'y_enc of {rank} >>> {y}')


  z = x + y
  print(f'z_enc of {rank} >>> {z}')

  print(f'z_plain of {rank} >>> {z.get_plain_text()}')



if __name__ == "__main__":
  simple_add()

```


## 各サーバでの環境変数設定

サーバA(IP:192.168.100.26) にて

```
 export WORLD_SIZE=2 && export RENDEZVOUS=env:// && export MASTER_ADDR=192.168.100.26 && export MASTER_PORT=29500 && export RANK=0 && export GLOO_SOCKET_IFNAME=enp4s0
```

を行い、

サーバBにて

```
export WORLD_SIZE=2 && export RENDEZVOUS=env:// && export MASTER_ADDR=192.168.100.26 && export MASTER_PORT=29500 && export RANK=1 && export GLOO_SOCKET_IFNAME=eno1

```

とする。
このとき、 world_size が登場するサーバの総数（今回はサーバAとサーバBで２）
であり、
rank はサーバのインデックスである。

gloo_socket_ifname には、

ifconfig をしたときの

サーバAでifconfig をしたときの実行結果

```bash
enp4s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.100.26  netmask 255.255.255.0  broadcast 192.168.100.255
```

で書いてある　enp4s0　を書く。


サーバBに関しても ifconfig を行ったときのものをgloo_socket_ifnameとして環境変数に設定する。


## 実行

両方のサーバで　`python main.py `
を実行する。

これで一応１＋２を秘密分散のコンテクストで行うことができる。



## おまけ　Opacus

https://ai.facebook.com/blog/introducing-opacus-a-high-speed-library-for-training-pytorch-models-with-differential-privacy/
秘密分散とは少し異なる、差分プライバシーと呼ばれるセキュリティ用語がある。
差分プライバシーは、かなりざっくり言ってしまえば機密データにノイズを加えることで、機密性を保持しつつ、行いたい演算の結果に影響が殆ど無いようにノイズを絶妙に調整することで、持っているデータを知られることなくやりたい演算を行うことである。

差分プライバシーのコンテクストにて、Facebook AI は他のライブラリを公開している。opacusとよばれる。
これもCrypTenと同様、pytorchに差分プライバシーのエッセンスをラップしたライブラリである。
opacusではpytorchのoptimizerに差分プライバシーをアタッチすることで、パラメターの更新の際にノイズを混入させてアップデートすることができる。
また、DPバジェットのようなものをリアルタイムでチェックすることで、プライバシーが守られるような工夫がある。
ユースケースとしては、仮にこの更新時のパラメター更新項をとりだし、別サーバに送ることができれば、別サーバ上でモデルをアップデートできるが、ほとんどの計算はクライアントノードで実行されている（フォーワード、バックワード）。したがって、このユースケースに意味があるかわかりにくい。