Opacusによる差分プライバシー付きファインチューニング
研究のラベルでテキストまたは画像の分類器をDP-SGDでファインチューニングし、epsilonとdeltaを設定して、消費したプライバシーを実行ログとモデルカードで読みます。
v2.14.0の新機能
このチュートリアルでは、研究自身のラベルで分類器を差分プライバシー付きで学習し、公開するモデルが、アノテーション済みの個々の項目についてほとんど何も明らかにしないようにします。このページは、項目やラベルが機微な情報(たとえば診療記録や私的なメッセージ)であり、モデルを共有または公開するつもりの研究の責任者向けです。hf-text-classificationのパラメーターにdpブロックを追加し、商品レビューの例でトレーナーを実行して、消費したプライバシーを実行ログとモデルカードから読みます。同じブロックはhf-image-classificationでも使え、dpを受け取るトレーナーはほかにありません。
差分プライバシーのepsilonとdelta
差分プライバシーは、データセットに対する計算についての数学的な保証です。1件のレコードを加えたり取り除いたりしても、任意の出力の確率が変わる量には上限があるため、出力から、そのレコードがあったかどうかはほとんどわかりません。epsilon(ε)がその上限です。epsilonが小さいほど、そのレコードがある場合とない場合の出力の区別が難しくなり、したがって、epsilonが小さいほど、プライバシーは強くなります。delta(δ)は、epsilonの上限が破れる確率で、Potatoのドキュメントは、deltaを「保証が成り立たない確率」と説明しています。
DP-SGDは、Abadiら(2016)の方法で、この保証のもとでニューラルネットワークを学習します。各学習ステップでは、各項目の勾配を最大ノルムにクリップし、その合計にガウスノイズを加えます。クリップは、1つの項目がモデルを動かせる大きさを制限し、ノイズは、その制限された動きを覆い隠します。Opacusは、DP-SGDを実装するPyTorchのライブラリで、実行にdpがあるときはいつも、Potatoは学習ループをOpacusに渡します。OpacusのRDPアカウンタントが、各ステップが消費するepsilonを積算します。
トレーナーとOpacusをインストールする
Transformerのトレーナーには、Potatoの学習用extraに含まれるtorchとtransformersが必要です。PotatoのextraにOpacusは含まれません。Opacusが特定のtorchのバージョンに固定されているためです。実行が学習するマシンに、両方をインストールします。
pip install "potato-annotation[train-text]" # [train-vision] for hf-image-classification
pip install opacusdpのある実行は、学習を始める前に、学習マシンにOpacusがあるかを確認します。そのため、ssh、SLURM、エージェントのバックエンドでは、Opacusは、アノテーションのサーバーではなく、学習するGPUサーバーまたは計算ノードに入れます。これらのバックエンドについては、リモート学習で説明しています。
トレーナーを設定する
次の設定は、最初のモデルを学習するの商品レビューの例に、hf-text-classificationの既定としてプライベートな学習を設定したものです。Potatoのexamples/ai-assisted/model-training/からdata/reviews.jsonとdata/gold.jsonをプロジェクトのフォルダーにコピーし、この設定をそこにconfig.yamlとして保存します。
annotation_task_name: "Review Sentiment (private training)"
data_files:
- "data/reviews.json"
item_properties:
id_key: "id"
text_key: "text"
task_dir: "."
output_annotation_dir: "annotation_output"
require_password: false
admin_api_key: "demo-admin-key"
num_annotators_per_item: 1
annotation_schemes:
- annotation_type: "radio"
name: "sentiment"
description: "Is this review positive or negative?"
labels:
- "positive"
- "negative"
sequential_key_binding: true
model_training:
enabled: true
trainer_params:
hf-text-classification:
epochs: 5
batch_size: 16
dp:
epsilon: 8
max_grad_norm: 1.0
writeback:
schemas: ["sentiment"]
include_unlabeled: true
min_confidence: 0.6trainer_paramsは、hf-text-classificationのすべての実行の既定値を設定するため、TrainingページのParameters欄は、dpのエントリがすでに入力された状態で開きます。epsilon: 8は、Potatoのドキュメントが一般的と呼ぶ1から10の範囲に収まっています。deltaは省略されているため、Potatoはdeltaを1 /(10 × trainの項目数)に設定します。max_grad_norm: 1.0は、既定のクリップを繰り返しています。epochsとbatch_sizeは、トレーナーの既定値を繰り返していますが、これは、Opacusがその両方からノイズの大きさを決めるためです。require_password: falseと単純な管理者キーは、ローカルでの試用にだけ向いています。
dpは、3つのキーを受け取ります。
| キー | 既定値 | 意味 |
|---|---|---|
epsilon | 必須 | 実行全体のプライバシー予算。epsilonが小さいほどプライバシーは強く、ノイズも多くなります。 |
delta | 1 /(10 × trainの項目数) | 保証が成り立たない確率。deltaは、1 / trainの項目数を下回っていなければなりません。 |
max_grad_norm | 1.0 | ノイズを加える前に、各項目の勾配をこのノルムにクリップします。 |
この設定は、python -m potato.validate_cli config.yaml --strictを通ります。バリデーターは、dpにマッピング、null、falseを受け付け、dp: trueは拒否します。バリデーターは、dpの中の各キーとその範囲も検査します。Opacusと、trainの項目数に依存する検査は、実行の開始時のエラーに一覧があるとおり、実行の開始時に、学習するマシンで行われます。
項目にラベルを付けて実行を開始する
hf-text-classificationは、20個未満のtrainの項目では学習を拒否します(min_instances)。サーバーを起動してから、レビューに手作業でラベルを付けるか、ユーザーシミュレーターの3人のシミュレートされたアノテーターを使います。シミュレートされたアノテーターは、ゴールドのファイルから答えます。
potato start config.yaml -p 8000
python -m potato.simulator --server http://localhost:8000 --users 3 \
--strategy gold_standard --competence good=1.0 --max-annotations 40 \
--gold-file data/gold.json --sequential --fast-mode --no-exportシミュレーターは、160件のレビューのうち120件にラベルを付けます。既定のsplits(70%がtrain、15%がval、15%がtest)とsplit_seed: 0のとき、私たちの実行では、ラベル付きのレビューのうち76件がtrain、19件がval、25件がtestに入りました。したがって、deltaの既定値は1 / 760、約0.00132で、指定するdeltaは、1 / 76、約0.0132を下回っていなければなりません。
管理者キーでhttp://localhost:8000/admin/trainingを開き、hf-text-classificationとsentimentスキームを選び、Parameters欄にdpのエントリが表示されていることを確認して、Train nowをクリックします。同じ実行は、APIからも開始できます。
curl -X POST -H "X-API-Key: demo-admin-key" -H "Content-Type: application/json" \
http://localhost:8000/admin/training/api/runs \
-d '{"trainer": "hf-text-classification", "schemas": ["sentiment"]}'レスポンスにはrun_idが含まれます。実行自身のparamsはtrainer_paramsを上書きするため、"params": {"dp": {"epsilon": 2}}は、1回の実行をより小さい予算で学習し、"params": {"dp": null}または"params": {"dp": false}は、1回の実行をDPなしで学習します。2回の実行のあいだでsplit_seedを変えなければ、どちらも同じ項目で学習とスコアリングを行うため、ホールドアウトスコアを直接比較できます。
DPのもとでの実行ログの行
/admin/training/runs/<run_id>の実行のページには、実行のイベントと指標が表示されます。DPの実行では、イベントに3種類の行が加わります。
最初の行は、Potatoが凍結する埋め込みテーブルを示します。テキストTransformerの位置埋め込みとトークンタイプ埋め込みは、バッチ全体で共有される1行を持ち、Opacusは、共有された行を通した項目ごとの勾配を計算できません。既定のモデルであるdistilbert/distilroberta-baseでは、凍結されるテーブルは、roberta.embeddings.position_embeddingsとroberta.embeddings.token_type_embeddingsです。凍結されたテーブルは学習されず、プライバシーも消費せず、単語埋め込みとそれ以外のすべての層は、引き続き学習されます。
2行目はDP-SGD:で始まり、目標のepsilon、delta、予定のエポック数、Opacusが選んだノイズ乗数、項目ごとのクリップ、サンプリング率を示します。各ステップは、ポアソンサンプリングでバッチを引くため、trainのすべての項目は、エポックあたりのバッチ数分の1の確率で、独立にバッチに入ります。76個のtrainの項目を16個ずつのバッチにすると、1エポックは5バッチで、ログにはサンプリング率0.2000が表示されます。ノイズ乗数は、ノイズの標準偏差を決め、OpacusのFAQは、一般的な範囲として0.1から2を挙げています。
各エポックの後に、Epoch <n>/<epochs>: loss <loss>, epsilon spent <epsilon>という形式の行が、そのエポックの平均損失と、そこまでに消費したepsilonを報告します。同じ値は、エポックごとのdp_epsilon指標として記録され、実行の最終的な指標には、実行全体のdp_epsilonが入ります。Opacusは、予定のエポック数で消費するepsilonが、目標のepsilon以下になるようにノイズの大きさを決めます。patience(既定では、valのスコアが2エポック連続で改善しない場合)で早期に停止した実行は、目標より少なく消費します。
モデルカードのdpのエントリ
実行は、実行ディレクトリである<task_dir>/<output_annotation_dir>/training/runs/<run_id>/のartifacts/に、model_card.jsonを書き出します。DPの実行のカードには、dpのエントリがあります。
| フィールド | 値 |
|---|---|
epsilon_target | 指定したepsilon |
epsilon_spent | OpacusのRDPアカウンタントによる、実行が消費した量 |
delta | 実行が使ったdelta。ご自身で指定したものか、既定値 |
max_grad_norm | 項目ごとのクリップ |
noise_multiplier | Opacusが選んだノイズ乗数 |
accountant | rdp |
covers | train split |
epsilon_spentは、実行が学習したすべてのエポックを数えます。保持されたチェックポイントより後のエポックも、合計に含まれます。Hugging Face Hubへの公開は、model_card.jsonをモデルとともにアップロードし、ローカルのパスはファイル名に置き換えられるため、dpのエントリは重みと一緒に渡ります。HubのREADME.mdのモデルカードにも、目標に対する消費したepsilon、delta、保証が対象とするtrain分割を示す「Differential privacy」の行があります。実行が目標またはデルタの値を記録していない場合、その行は記録のない値を省きます。
プライバシー予算とクリップの選び方
Potatoのドキュメントは、epsilonの一般的な範囲を1から10とし、deltaは1 / trainの項目数を下回ることを求めています。OpacusのFAQは、deltaについて同じ経験則を示し、epsilonには、非常に小さいdeltaとともに、小さな定数を勧めています。既定のdeltaである1 /(10 × trainの項目数)は、上限の1 / trainの項目数を下回っているため、ほとんどの研究では、deltaを省略できます。
クリップであるmax_grad_normは、2つの誤差のトレードオフです。クリップは、しきい値より大きいすべての勾配を変え、OpacusのFAQは、クリップが収束を損なうと述べています。ノイズはクリップに比例するため、クリップが大きいほど、ノイズも多くなります。FAQはさらに、しきい値が誤っていると、深いモデルよりも線形モデルのほうが悪影響を受けると述べています。既定の1.0から始めて、クリップを調整する前に、ホールドアウトスコアを比較してください。
Opacusは、予定のepochsに合わせてノイズの大きさを決めるため、同じepsilonをより少ないエポックに配分すると、各ステップのノイズは少なくなります。OpacusのFAQは、バッチが大きいとサンプリング率が上がり、サンプリング率とともにプライバシーのコストも上がると述べています。FAQは、そのコストを相殺するために、より大きな学習率と早期の停止を提案しています。DPの実行のホールドアウトスコアを、同じ分割でdpなしの実行のスコアと比較してください。2つのスコアの差が、ご自身のデータで保証が支払うコストを表します。
保証の範囲
保証が対象とするのは、train分割です。1つのtrainの項目を変えても、公開するモデルが取りうる姿は、明示されたepsilon以内でしか変わりません。その項目は、項目のアノテーターから統合したラベルとあわせて数えられます。Potatoが保証を述べるのは項目単位であり、ドキュメントは、アノテーター単位の保証を述べていません。
val分割は、保証の範囲外です。各エポックの後に、トレーナーはvalの項目でモデルをスコアリングし、最も良いスコアのエポックのチェックポイントを保持します。この選択は、ノイズを加えずにvalの項目のラベルを読むため、モデルカードのepsilonは、valの項目についての約束をしません。valの項目にもtrainの項目と同じ保護が必要な場合は、valの割合を0にして、その割合をtestに移します。たとえば、splits: {train: 0.7, val: 0, test: 0.3}です。valの項目がない場合、トレーナーは最後のエポックを保持し、patienceが実行を早期に止めることはなく、ホールドアウトスコアはtestから得られます。trainの割合を変えなければ、trainに同じ項目が残ります。Potatoは、項目のIDのシード付きハッシュで分割を割り当てるためです。
モデルのラベルの一覧は、保証の範囲外です。Potatoは、データに現れるラベルからラベルの一覧を読み取るため、アノテーターが1つの項目にだけ使ったラベルも、モデルの出力に現れます。
DPのもとでの学習の違い
DP-SGDは、項目ごとの勾配を必要とするため、DPの実行は、同じトレーナーの通常の実行と、次の点で異なります。
- 学習は、
precisionの設定にかかわらず、完全精度で行われます。DPのもとでは、混合精度がオフになるためです。 gradient_accumulationは1でなければなりません。各ステップは、ポアソンサンプリングした1つのバッチを学習します。- 量子化されたベースモデル(
lora.quantize)は拒否されます。 - テキストTransformerの位置埋め込みとトークンタイプ埋め込みのテーブルは、実行ログが報告するとおり、凍結されます。
- BatchNormなど、Opacusが対応していない層を持つモデルは、学習の開始時に、その層の名前を示して失敗します。
dpを受け取るのは、hf-text-classificationとhf-image-classificationだけです。ほかのすべてのトレーナーは、受け取らないパラメーターを拒否するため、sklearn-text、setfit、hf-token-classificationにdpを指定すると、trainer_paramsの下では起動時に失敗し、実行の開始時には409で失敗します。
実行の開始時のエラー
potato validateは、dpが受け取らないキーと、範囲外の値を、サーバーの起動前に報告します。実行は、開始時に、Opacusとtrain分割に依存する制限とあわせて、dpブロックをもう一度検査します。これらの検査に失敗した実行は、学習の前に、コードvalidation_failedで、すべての問題を1つのメッセージにまとめて終了します。メッセージは、Potatoの検査から出力されます。
| 問題 | メッセージ |
|---|---|
| Opacusがインストールされていない | dp needs opacus, which is not installed (pip install opacus). |
epsilonがない | dp needs epsilon, the privacy budget for the run (1 to 10 is usual). |
dpが受け取らないキー | dp does not take bogus; it takes epsilon, delta and max_grad_norm. |
deltaが1 / trainの項目数以上 | dp.delta is 0.05, at least 1 / 76 train items: at that delta one item's data could be released outright. Use less than 0.0131579. |
gradient_accumulationが1より大きい | dp trains one sampled batch per step; set gradient_accumulation to 1. |
lora.quantizeが設定されている | dp cannot train a quantized (4bit or 8bit) base model; drop lora.quantize. |
空のマッピングのdp: {}は、予算なしのDPを求めることになり、epsilonがないというメッセージで失敗します。trainer_paramsがdpを設定している場合、実行の"dp": nullまたは"dp": falseは、その実行についてDPをオフにします。ステータスのルートであるGET /admin/training/api/statusは、同じメッセージをerrorsの下に一覧します。
関連資料
- モデル学習の概要:Trainingページとハブへの公開について
- トレーナー:
hf-text-classificationとhf-image-classificationが学習する内容について - モデル学習の設定:分割、ホールドアウトスコア、書き戻しについて
- 最初のモデルを学習する:GPUもDPも使わない、同じプロジェクトについて
- リモート学習:GPUサーバーでDPのファインチューニングを実行する方法について
- Potatoのモデル学習のリファレンスの差分プライバシー(
dp)
参考文献
- Abadi, M., Chu, A., Goodfellow, I., McMahan, H. B., Mironov, I., Talwar, K., and Zhang, L. (2016). Deep learning with differential privacy. Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security, 308-318. https://doi.org/10.1145/2976749.2978318
- Opacus. FAQ. https://opacus.ai/docs/faq