본문으로 건너뛰기

Opacus로 차등 프라이버시 파인튜닝하기

DP-SGD로 연구의 레이블에 대해 텍스트 또는 이미지 분류기를 파인튜닝하고, 엡실론과 델타를 설정하고, 소모된 프라이버시를 실행 로그와 모델 카드에서 읽습니다.

v2.14.0의 새 기능

이 튜토리얼은 연구 자체의 레이블로 차등 프라이버시를 적용해 분류기를 학습시키며, 공개된 모델이 어노테이션된 개별 항목에 대해 거의 아무것도 드러내지 않게 합니다. 이 페이지는 항목이나 레이블이 민감하고(예: 건강 기록이나 개인 메시지) 모델을 공유하거나 발표할 계획인 연구 책임자를 위한 것입니다. hf-text-classification의 매개변수에 dp 블록을 추가하고, 제품 리뷰 예제에서 트레이너를 실행하고, 소모된 프라이버시를 실행 로그와 모델 카드에서 읽습니다. 같은 블록이 hf-image-classification에서도 작동하며, 그 밖의 트레이너는 dp를 받지 않습니다.

차등 프라이버시의 엡실론과 델타

차등 프라이버시는 데이터셋에 대한 계산의 수학적 보장입니다. 레코드 하나를 더하거나 빼도 어떤 출력의 확률이든 한정된 양만큼만 바뀌므로, 출력은 그 레코드가 있었는지에 대해 거의 아무것도 드러내지 않습니다. 엡실론(ε)이 그 한계입니다. 엡실론이 작을수록 레코드가 있을 때와 없을 때의 출력을 구분하기 어려워지므로 프라이버시가 더 강합니다. 델타(δ)는 엡실론의 한계가 깨질 확률이며, Potato 문서는 델타를 "the chance the guarantee does not hold"(보장이 성립하지 않을 확률)로 설명합니다.

DP-SGD는 Abadi 외(2016)의 방법으로 이 보장 아래 신경망을 학습시킵니다. 각 학습 스텝은 모든 항목의 그래디언트를 최대 노름으로 클리핑하고 그 합에 가우스 잡음을 더합니다. 클리핑은 항목 하나가 모델을 움직일 수 있는 범위를 제한하고, 잡음은 그 제한된 움직임을 가립니다. Opacus는 DP-SGD를 구현한 PyTorch 라이브러리이며, 실행에 dp가 있으면 Potato는 학습 루프를 Opacus에 넘깁니다. Opacus의 RDP 회계기는 각 스텝이 소모하는 엡실론을 합산합니다.

트레이너와 Opacus 설치

트랜스포머 트레이너에는 Potato의 학습 extra에 포함된 torch와 transformers가 필요합니다. Opacus는 특정 torch 버전을 고정하므로 어떤 Potato extra에도 Opacus가 들어 있지 않습니다. 실행이 학습하는 머신에 둘 다 설치하십시오.

bash
pip install "potato-annotation[train-text]"   # [train-vision] for hf-image-classification
pip install opacus

dp가 있는 실행은 학습이 시작되기 전에 학습 머신에 Opacus가 있는지 확인합니다. 따라서 ssh, SLURM, 에이전트 백엔드에서는 Opacus를 어노테이션 서버가 아니라 학습하는 GPU 서버나 컴퓨트 노드에 설치합니다. 원격 학습이 이 백엔드들을 다룹니다.

트레이너 설정하기

아래 설정은 첫 모델 학습시키기의 제품 리뷰 예제이며, hf-text-classification의 기본값으로 비공개 학습을 설정했습니다. Potato의 examples/ai-assisted/model-training/에서 data/reviews.json과 data/gold.json을 프로젝트 폴더로 복사하고, 이 설정을 그곳에 config.yaml로 저장하십시오.

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.6

trainer_params는 hf-text-classification의 모든 실행에 대한 기본값을 정하므로, Training 페이지의 Parameters 필드는 dp 항목이 이미 채워진 채로 열립니다. epsilon: 8은 Potato 문서가 일반적이라고 부르는 1~10 범위 안에 있습니다. delta는 생략했으므로 Potato는 델타를 1 / (10 × train 항목 수)로 설정합니다. max_grad_norm: 1.0은 기본 클리핑을 그대로 적은 것입니다. epochs와 batch_size는 트레이너의 기본값을 그대로 적은 것이며, Opacus가 둘 모두로 잡음의 크기를 정하기 때문입니다. require_password: false와 단순한 관리자 키는 로컬 시험에만 적합합니다.

dp는 세 개의 키를 받습니다.

키기본값의미
epsilon필수실행 전체의 프라이버시 예산입니다. 엡실론이 작을수록 더 비공개적이며 잡음이 더 많이 더해집니다.
delta1 / (10 × train 항목 수)보장이 성립하지 않을 확률입니다. 델타는 1 / train 항목 수보다 작아야 합니다.
max_grad_norm1.0각 항목의 그래디언트는 잡음이 더해지기 전에 이 노름으로 클리핑됩니다.

이 설정은 python -m potato.validate_cli config.yaml --strict를 통과합니다. 검증기는 dp에 매핑, null, false를 받아들이고 dp: true는 거부합니다. 검증기는 dp 안의 각 키와 그 범위도 확인합니다. Opacus와 train 항목 수에 따른 검사는 실행 시작 시의 오류가 나열하듯이 실행이 시작될 때 학습하는 머신에서 실행됩니다.

항목에 레이블을 달고 실행 시작하기

hf-text-classification은 train 항목이 20개(min_instances) 미만이면 학습을 거부합니다. 서버를 시작한 다음, 리뷰에 직접 레이블을 달거나 사용자 시뮬레이터의 시뮬레이션 어노테이터 세 명이 정답 파일을 보고 답하게 하십시오.

bash
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(train 70%, val 15%, test 15%)와 split_seed: 0에서 우리의 실행은 레이블이 달린 리뷰 76개를 train에, 19개를 val에, 25개를 test에 넣었습니다. 따라서 델타의 기본값은 1 / 760, 약 0.00132이며, 사용자가 설정하는 델타는 1 / 76, 약 0.0132보다 작아야 합니다.

관리자 키로 http://localhost:8000/admin/training을 열고, hf-text-classification과 sentiment 스키마를 고르고, Parameters 필드에 dp 항목이 보이는지 확인한 뒤 Train now를 클릭하십시오. 같은 실행을 API에서도 시작할 수 있습니다.

bash
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가 동결하는 임베딩 테이블을 알려 줍니다. 텍스트 트랜스포머의 위치 및 토큰 유형 임베딩은 배치 전체가 공유하는 행 하나를 사용하며, Opacus는 공유된 행을 통해서는 항목별 그래디언트를 계산할 수 없습니다. 기본 모델인 distilbert/distilroberta-base에서 동결되는 테이블은 roberta.embeddings.position_embeddings와 roberta.embeddings.token_type_embeddings입니다. 동결된 테이블은 학습하지 않고 프라이버시를 소모하지 않으며, 단어 임베딩과 다른 모든 레이어는 여전히 학습합니다.

두 번째 줄은 DP-SGD:로 시작하며 목표 엡실론, 델타, 계획된 에포크, Opacus가 고른 잡음 승수, 항목별 클리핑, 샘플링 비율을 밝힙니다. 각 스텝은 푸아송 샘플링으로 배치를 뽑으므로, 모든 train 항목은 에포크당 배치 수분의 1의 확률로 독립적으로 배치에 들어갑니다. train 항목 76개를 16개씩 배치로 묶으면 에포크는 배치 5개이며 로그에는 샘플링 비율 0.2000이 나옵니다. 잡음 승수는 잡음의 표준편차를 정하며, Opacus FAQ는 일반적인 범위로 0.1~2를 제시합니다.

각 에포크 뒤에는 Epoch <n>/<epochs>: loss <loss>, epsilon spent <epsilon> 형태의 줄이 그 에포크의 평균 손실과 지금까지 소모된 엡실론을 보고합니다. 같은 값이 에포크별로 dp_epsilon 지표로 기록되며, 실행의 최종 지표는 실행 전체의 dp_epsilon을 담습니다. Opacus는 계획된 에포크가 목표 엡실론 이하만 소모하도록 잡음의 크기를 정합니다. patience(기본값은 val 점수가 나아지지 않는 에포크 두 번)에 따라 일찍 멈춘 실행은 목표보다 적게 소모합니다.

모델 카드의 dp 항목

실행은 실행 디렉터리인 <task_dir>/<output_annotation_dir>/training/runs/<run_id>/의 artifacts/에 model_card.json을 씁니다. DP 실행의 카드에는 dp 항목이 있습니다.

필드값
epsilon_target요청한 epsilon
epsilon_spentOpacus의 RDP 회계기가 계산한, 실행이 소모한 양
delta실행이 사용한 델타(직접 지정한 값 또는 기본값)
max_grad_norm항목별 클리핑
noise_multiplierOpacus가 고른 잡음 승수
accountantrdp
coverstrain split

epsilon_spent는 실행이 학습한 모든 에포크를 셉니다. 유지된 체크포인트 이후의 에포크도 합계에 들어갑니다. Hugging Face Hub에 게시하기는 로컬 경로를 파일 이름으로 바꾼 model_card.json을 모델과 함께 업로드하므로, dp 항목이 가중치와 함께 이동합니다. Hub의 README.md 모델 카드에도 목표 대비 소모된 엡실론, 델타, 보장이 적용되는 train 분할을 담은 "Differential privacy" 행이 있습니다. 실행이 목표나 델타 값을 기록하지 않았으면 행에서 해당 값을 뺍니다.

프라이버시 예산과 클리핑 고르기

Potato 문서는 엡실론의 일반적인 범위로 1~10을 제시하고 델타가 1 / train 항목 수보다 작아야 한다고 요구합니다. Opacus FAQ는 델타에 같은 경험칙을 제시하며, 엡실론에는 아주 작은 델타와 함께 작은 상수를 권장합니다. 기본 델타 1 / (10 × train 항목 수)는 상한 1 / train 항목 수 아래에 있으므로 대부분의 연구는 델타를 생략해도 됩니다.

클리핑인 max_grad_norm은 두 가지 오류 사이에서 절충합니다. 클리핑은 임계값보다 큰 모든 그래디언트를 바꾸며, Opacus FAQ는 클리핑이 수렴을 해친다고 말합니다. 잡음은 클리핑에 비례해 조정되므로 클리핑이 클수록 잡음이 더 많이 더해집니다. FAQ는 잘못된 임계값이 딥 모델보다 선형 모델에 더 해롭다고 덧붙입니다. 기본값 1.0에서 시작하고, 클리핑을 조정하기 전에 홀드아웃 점수를 비교하십시오.

Opacus는 계획된 epochs에 맞춰 잡음의 크기를 정하므로, 같은 엡실론을 더 적은 에포크에 나누면 각 스텝의 잡음이 줄어듭니다. Opacus FAQ는 배치가 클수록 샘플링 비율이 높아지고 샘플링 비율과 함께 프라이버시 비용도 높아진다고 지적합니다. FAQ는 그 비용을 상쇄하기 위해 더 큰 학습률과 더 이른 중단을 제안합니다. DP 실행의 홀드아웃 점수를 같은 분할에서 dp 없이 실행한 점수와 비교하십시오. 두 점수의 차이가 사용자의 데이터에서 그 보장이 치르는 비용을 나타냅니다.

보장의 범위

보장은 train 분할에 적용됩니다. train 항목 하나를 바꾸면 공개된 모델이 될 수 있는 것이 명시된 엡실론 이하만큼만 바뀝니다. 항목은 그 항목의 어노테이터들로부터 통합된 레이블과 함께 셉니다. Potato는 보장을 항목 단위로 명시하며, 문서는 어노테이터 단위의 보장을 명시하지 않습니다.

val 분할은 보장 밖에 있습니다. 각 에포크 뒤에 트레이너는 val 항목으로 모델 점수를 매기고 점수가 가장 높은 에포크의 체크포인트를 유지합니다. 그 선택은 잡음 없이 val 항목의 레이블을 읽으므로, 모델 카드의 엡실론은 val 항목에 대해 아무것도 약속하지 않습니다. val 항목에도 train 항목과 같은 보호가 필요하면 val 비율을 0으로 하고 그 비율을 test로 옮기십시오(예: splits: {train: 0.7, val: 0, test: 0.3}). val 항목이 없으면 트레이너는 마지막 에포크를 유지하고, patience는 실행을 일찍 멈추지 않으며, 홀드아웃 점수는 test에서 나옵니다. train 비율을 바꾸지 않으면 같은 항목이 train에 남는데, Potato가 항목 ID의 시드가 있는 해시로 분할을 배정하기 때문입니다.

모델의 레이블 목록은 보장 밖에 있습니다. Potato는 데이터에 나타나는 레이블에서 레이블 목록을 읽으므로, 어노테이터가 항목 하나에만 사용한 레이블도 모델의 출력에 나타납니다.

DP에서 달라지는 학습 방식

DP-SGD는 항목마다 그래디언트가 필요하므로, DP 실행은 같은 트레이너의 일반 실행과 다음 점에서 다릅니다.

  • precision이 무엇이든 학습은 전체 정밀도로 실행됩니다. DP에서는 혼합 정밀도가 꺼지기 때문입니다.
  • gradient_accumulation은 1이어야 합니다. 각 스텝은 푸아송 샘플링한 배치 하나를 학습합니다.
  • 양자화된 베이스 모델(lora.quantize)은 거부됩니다.
  • 실행 로그가 보고하듯이 텍스트 트랜스포머의 위치 및 토큰 유형 임베딩 테이블은 동결됩니다.
  • BatchNorm처럼 Opacus가 지원하지 않는 레이어가 있는 모델은 학습 시작 시 해당 레이어를 밝히며 실패합니다.

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 아래에 나열합니다.

더 읽어보기

참고문헌