用 Opacus 做差分隱私微調
用 DP-SGD 在研究的標籤上微調文本或影像分類器,設定 epsilon 和 delta,並在執行日誌和模型卡中檢視已消耗的隱私。
v2.14.0 新增
本教程用差分隱私在研究自己的標籤上訓練分類器,使釋出的模型很少洩露任何一個已標註條目的資訊。本頁面向條目或標籤敏感(例如健康記錄或私人訊息)、並打算共享或發表模型的研究負責人。你將在 hf-text-classification 的參數中新增 dp 塊,在商品評論示例上執行該訓練器,並從執行日誌和模型卡中讀取已消耗的隱私。同樣的塊適用於 hf-image-classification,其他訓練器都不接受 dp。
差分隱私中的 epsilon 和 delta
差分隱私是關於資料集上某項計算的數學保證。增加或刪除一條記錄,任何輸出的機率變化都有界,所以輸出很少洩露該記錄是否存在。Epsilon(ε)就是這個界。epsilon 越小,有該記錄和沒有該記錄時的輸出越難區分,因此 epsilon 越小,隱私越強。Delta(δ)是 epsilon 界不成立的機率,Potato 的文件把 delta 描述為“保證不成立的可能性”。
DP-SGD 按 Abadi 等人(2016)的方法,在這一保證之下訓練神經網路。每個訓練步驟把每個條目的梯度裁剪到最大範數,並給求和結果加上高斯噪聲。裁剪限制單個條目能讓模型移動多遠,噪聲則掩蓋這個有界的移動。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 opacus帶 dp 的執行在訓練開始之前會檢查訓練機器上是否有 Opacus。因此在 ssh、SLURM 或 agent 後端上,Opacus 要裝在負責訓練的 GPU 伺服器或計算節點上,而不是標註伺服器上。這些後端見遠端訓練。
配置訓練器
下面的配置是訓練你的第一個模型中的商品評論示例,並把私有訓練設為 hf-text-classification 的預設值。請把 data/reviews.json 和 data/gold.json 從 Potato 的 examples/ai-assisted/model-training/ 複製到一個項目資料夾中,並把這份配置作為 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 的每次執行設定預設值,所以訓練頁面的 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 接受三個鍵:
| 鍵 | 預設值 | 含義 |
|---|---|---|
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)上擬合。請啟動伺服器,然後手工標註評論,或者用使用者模擬器中的三個模擬標註者,它們根據 gold 檔案作答:
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}} 以更小的預算訓練一次執行,"params": {"dp": null} 或 "params": {"dp": false} 則訓練一次不帶 DP 的執行。兩次執行之間請保持 split_seed 不變,這樣兩者在同樣的條目上訓練和評分,留出得分可以直接比較。
DP 下的執行日誌行
/admin/training/runs/<run_id> 的執行頁面列出該次執行的事件和指標。DP 執行會在事件中增加三類行。
第一行指出 Potato 凍結的嵌入表。文本 transformer 的位置嵌入和 token 類型嵌入對整批資料共用一行,而 Opacus 無法通過共用的行計算每個條目的梯度。對預設模型 distilbert/distilroberta-base,被凍結的表是 roberta.embeddings.position_embeddings 和 roberta.embeddings.token_type_embeddings。凍結的表不訓練,也不消耗隱私,詞嵌入和其他每一層仍然訓練。
第二行以 DP-SGD: 開頭,給出目標 epsilon、delta、計劃的 epoch 數、Opacus 選定的噪聲乘數、每個條目的裁剪值和取樣率。每一步通過 Poisson 取樣抽取一批,所以每個 train 條目以 1 / 每個 epoch 的批數 的機率獨立加入一批。76 個 train 條目按每批 16 個,一個 epoch 有 5 批,日誌給出的取樣率是 0.2000。噪聲乘數設定噪聲的標準差,Opacus 的 FAQ 給出的常用範圍是 0.1 到 2。
每個 epoch 之後,形如 Epoch <n>/<epochs>: loss <loss>, epsilon spent <epsilon> 的一行報告該 epoch 的平均損失和目前已消耗的 epsilon。同一個值按 epoch 記錄為 dp_epsilon 指標,執行的最終指標帶有整次執行的 dp_epsilon。Opacus 確定噪聲的大小,使計劃的 epoch 至多消耗目標 epsilon。因 patience(預設為連續兩個 epoch 沒有更好的 val 得分)而提前停止的執行,消耗的隱私少於目標。
模型卡中的 dp 條目
執行把 model_card.json 寫入執行目錄 <task_dir>/<output_annotation_dir>/training/runs/<run_id>/ 下的 artifacts/。DP 執行的卡片帶有 dp 條目:
| 欄位 | 值 |
|---|---|
epsilon_target | 你請求的 epsilon |
epsilon_spent | 該次執行消耗的量,來自 Opacus 的 RDP 記賬器 |
delta | 該次執行使用的 delta,可以是你自己設的,也可以是預設值 |
max_grad_norm | 每個條目的裁剪值 |
noise_multiplier | Opacus 選定的噪聲乘數 |
accountant | rdp |
covers | train split |
epsilon_spent 計入執行訓練過的每個 epoch。保留的檢查點之後的 epoch 也計入總量。釋出到 Hugging Face Hub 會把 model_card.json 連同模型一起上傳,其中的本地路徑被替換為檔名,所以 dp 條目隨權重一起流轉。Hub 上的 README.md 模型卡還有一行“Differential privacy”,給出已消耗的 epsilon 與目標的對比、delta,以及該保證所覆蓋的 train 劃分。執行沒有記錄目標或 delta 的值時,這一行會省略它。
選擇隱私預算和裁剪值
Potato 的文件給出 epsilon 的常用範圍是 1 到 10,並要求 delta 小於 1 / train 條目數。Opacus 的 FAQ 對 delta 給出同樣的經驗規則,並建議 epsilon 取一個小的常數、delta 取極小值。預設的 delta 1 / (10 × train 條目數) 低於 1 / train 條目數 這個上限,所以大多數研究可以省略 delta。
裁剪值 max_grad_norm 要在兩種誤差之間權衡。裁剪會改變每個大於閾值的梯度,Opacus 的 FAQ 說裁剪會損害收斂。噪聲按裁剪值縮放,所以裁剪值越大,加入的噪聲越多。FAQ 還補充說,閾值設錯對線性模型的傷害比對深度模型更大。請從預設的 1.0 開始,先比較留出得分,再調整裁剪值。
Opacus 按計劃的 epochs 確定噪聲的大小,所以同樣的 epsilon 分攤到更少的 epoch 上,每一步的噪聲就更小。Opacus 的 FAQ 指出,批越大,取樣率越高,隱私代價也隨取樣率升高。FAQ 建議用更大的學習率和更早停止來抵消這一代價。請把 DP 執行的留出得分與同一劃分上不帶 dp 的執行的得分比較。兩個得分之間的差距衡量這一保證在你的資料上的代價。
保證的範圍
該保證覆蓋 train 劃分。改變一個 train 條目,釋出的模型可能發生的變化至多是所述的 epsilon。該條目與由其標註者得出的已解析標籤一起計算。Potato 按條目陳述該保證,文件沒有陳述按標註者的保證。
val 劃分在保證之外。每個 epoch 之後,訓練器在 val 條目上給模型評分,並保留得分最好的 epoch 的檢查點。這一選擇讀取了 val 條目的標籤,沒有加噪聲,所以模型卡上的 epsilon 對 val 條目不作任何承諾。當 val 條目需要與 train 條目同樣的保護時,請把 val 比例設為 0,並把這部分比例移給 test,例如 splits: {train: 0.7, val: 0, test: 0.3}。沒有 val 條目時,訓練器保留最後一個 epoch,patience 永遠不會提前停止執行,留出得分來自 test。保持 train 比例不變就能讓 train 中的條目不變,因為 Potato 按每個條目 id 的帶種子雜湊來分配劃分。
模型的標籤列表在保證之外。Potato 從資料中出現的標籤讀取標籤列表,所以標註者只在一個條目上用過的標籤,仍然會出現在模型的輸出中。
DP 下的訓練差異
DP-SGD 需要每個條目的梯度,所以 DP 執行與同一訓練器的普通執行有以下區別:
- 無論
precision怎麼設定,訓練都以全精度執行,因為 DP 下混合精度是關閉的。 gradient_accumulation必須為 1。每一步訓練一個 Poisson 取樣的批。- 量化的基礎模型(
lora.quantize)會被拒絕。 - 文本 transformer 的位置嵌入表和 token 類型嵌入表被凍結,執行日誌會報告這一點。
- 帶有 Opacus 不支援的層(例如 BatchNorm)的模型,會在訓練開始時失敗,並指出這一層。
只有 hf-text-classification 和 hf-image-classification 接受 dp。其他每個訓練器都會拒絕自己不接受的參數,所以在 sklearn-text、setfit 或 hf-token-classification 上使用 dp,在 trainer_params 下會在啟動時失敗,在一次執行開始時則以 409 失敗。
執行開始時的錯誤
potato validate 在伺服器啟動之前報告 dp 不接受的鍵和超出範圍的值。一次執行在開始時會再次檢查 dp 塊,同時檢查 Opacus 以及取決於 train 劃分的限制。沒有通過這些檢查的執行會在訓練之前以錯誤結束,錯誤碼為 validation_failed,所有問題合在一條資訊中。這些資訊來自 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 下列出同樣的資訊。
延伸閱讀
- 模型訓練概覽,介紹訓練頁面和 Hub 釋出
- 訓練器,介紹
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