Pular para o conteúdo

Ajuste fino com privacidade diferencial usando o Opacus

Ajuste um classificador de texto ou de imagens nos rótulos do seu estudo com DP-SGD, defina epsilon e delta e leia a privacidade gasta no log da execução e no cartão do modelo.

Novo na v2.14.0

Este tutorial treina um classificador nos rótulos do próprio estudo com privacidade diferencial, de modo que o modelo divulgado revele pouco sobre qualquer item anotado. A página é para responsáveis por estudos cujos itens ou rótulos são sensíveis (por exemplo prontuários de saúde ou mensagens privadas) e que pretendem compartilhar ou publicar o modelo. Você vai adicionar um bloco dp aos parâmetros de hf-text-classification, executar o treinador no exemplo de avaliações de produtos e ler a privacidade gasta no log da execução e no cartão do modelo. O mesmo bloco funciona em hf-image-classification, e nenhum outro treinador aceita dp.

Epsilon e delta na privacidade diferencial

A privacidade diferencial é uma garantia matemática sobre uma computação em um conjunto de dados. Adicionar ou remover um registro altera a probabilidade de qualquer saída em uma quantidade limitada, então a saída revela pouco sobre se esse registro estava presente. O epsilon (ε) é o limite. Um epsilon menor torna mais difícil distinguir as saídas com e sem o registro, e um epsilon menor portanto dá privacidade mais forte. O delta (δ) é a probabilidade de que o limite do epsilon falhe, e a documentação do Potato descreve o delta como "a chance de a garantia não valer".

O DP-SGD treina uma rede neural sob essa garantia, pelo método de Abadi et al. (2016). Cada passo de treinamento corta o gradiente de cada item a uma norma máxima e adiciona ruído gaussiano à soma. O corte limita o quanto um item pode mover o modelo, e o ruído mascara esse movimento limitado. O Opacus é uma biblioteca PyTorch que implementa o DP-SGD, e o Potato entrega o laço de treinamento ao Opacus sempre que uma execução carrega dp. O contador RDP do Opacus soma o epsilon que cada passo gasta.

Instale o treinador e o Opacus

Os treinadores de transformers precisam de torch e transformers dos extras de treinamento do Potato. Nenhum extra do Potato inclui o Opacus, porque o Opacus fixa versões específicas do torch. Instale os dois na máquina em que a execução treina:

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

Uma execução com dp verifica o Opacus na máquina de treinamento antes de o treinamento começar. Em um backend ssh, SLURM ou de agente, o Opacus portanto vai no servidor com GPU ou no nó de computação que treina, e não no servidor de anotação. O treinamento remoto cobre esses backends.

Configure o treinador

O config abaixo é o exemplo de avaliações de produtos de Treine o seu primeiro modelo, com o treinamento privado definido como padrão para hf-text-classification. Copie data/reviews.json e data/gold.json de examples/ai-assisted/model-training/ do Potato para uma pasta de projeto e salve este config nela como 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 define os padrões de toda execução de hf-text-classification, então o campo Parameters da página Training abre com a entrada dp já preenchida. epsilon: 8 fica dentro do intervalo de 1 a 10 que a documentação do Potato chama de usual. delta foi deixado de fora, então o Potato define o delta como 1 / (10 × itens train). max_grad_norm: 1.0 repete o corte padrão. epochs e batch_size repetem os padrões do treinador, porque o Opacus dimensiona o ruído a partir dos dois. require_password: false e a chave de administrador simples servem apenas para um teste local.

dp aceita três chaves:

ChavePadrãoSignificado
epsilonobrigatórioO orçamento de privacidade para a execução inteira. Um epsilon menor é mais privado e adiciona mais ruído.
delta1 / (10 × itens train)A chance de a garantia não valer. O delta deve ser menor que 1 / itens train.
max_grad_norm1.0O gradiente de cada item é cortado a essa norma antes de o ruído ser adicionado.

O config passa em python -m potato.validate_cli config.yaml --strict. O validador aceita um mapeamento, null ou false para dp e recusa dp: true. O validador também verifica cada chave dentro de dp e o intervalo da chave. O Opacus e as verificações que dependem do número de itens train executam quando uma execução começa, na máquina que treina, como lista Erros no início de uma execução.

Rotule itens e inicie uma execução

hf-text-classification se recusa a ajustar com menos de 20 itens train (min_instances). Inicie o servidor e depois rotule as avaliações à mão ou com três anotadores simulados do simulador de usuários, que respondem a partir do arquivo gold:

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

O simulador rotula 120 das 160 avaliações. Com o splits padrão (70% train, 15% val, 15% test) e split_seed: 0, a nossa execução pôs 76 avaliações rotuladas em train, 19 em val e 25 em test. O delta portanto assume por padrão 1 / 760, cerca de 0,00132, e qualquer delta que você definir deve ser menor que 1 / 76, cerca de 0,0132.

Abra http://localhost:8000/admin/training com a chave de administrador, escolha hf-text-classification e o esquema sentiment, confira se o campo Parameters mostra a entrada dp e clique em Train now. A mesma execução começa pela 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"]}'

A resposta traz o run_id. Os params da própria execução substituem trainer_params, então "params": {"dp": {"epsilon": 2}} treina uma execução com um orçamento menor, e "params": {"dp": null} ou "params": {"dp": false} treina uma execução sem DP. Mantenha split_seed inalterado entre as duas execuções, para que ambas treinem e pontuem nos mesmos itens e as pontuações em itens reservados sejam diretamente comparáveis.

Linhas do log da execução sob DP

A página da execução em /admin/training/runs/<run_id> lista os eventos e as métricas da execução. Uma execução com DP adiciona três tipos de linha aos eventos.

A primeira linha nomeia as tabelas de embeddings que o Potato congela. Os embeddings de posição e de tipo de token de um transformer de texto usam uma linha compartilhada por todo o lote, e o Opacus não consegue calcular um gradiente por item através de uma linha compartilhada. Para o modelo padrão, distilbert/distilroberta-base, as tabelas congeladas são roberta.embeddings.position_embeddings e roberta.embeddings.token_type_embeddings. As tabelas congeladas não treinam e não gastam privacidade, e os embeddings de palavras e todas as outras camadas continuam treinando.

A segunda linha começa com DP-SGD: e informa o epsilon alvo, o delta, as épocas planejadas, o multiplicador de ruído que o Opacus escolheu, o corte por item e a taxa de amostragem. Cada passo sorteia um lote por amostragem de Poisson, então cada item train entra em um lote de forma independente com probabilidade 1 / lotes por época. Com 76 itens train em lotes de 16, uma época tem 5 lotes e o log dá uma taxa de amostragem de 0.2000. O multiplicador de ruído define o desvio-padrão do ruído, e o FAQ do Opacus dá 0,1 a 2 como o intervalo usual.

Após cada época, uma linha da forma Epoch <n>/<epochs>: loss <loss>, epsilon spent <epsilon> informa a perda média da época e o epsilon gasto até então. O mesmo valor é registrado por época como a métrica dp_epsilon, e as métricas finais da execução carregam dp_epsilon para a execução inteira. O Opacus dimensiona o ruído para que as épocas planejadas gastem no máximo o epsilon alvo. Uma execução que para cedo por patience (duas épocas sem uma pontuação val melhor, por padrão) gasta menos que o alvo.

A entrada dp no cartão do modelo

A execução grava model_card.json em artifacts/ no diretório da execução, <task_dir>/<output_annotation_dir>/training/runs/<run_id>/. O cartão de uma execução com DP tem uma entrada dp:

CampoValor
epsilon_targetO epsilon que você pediu
epsilon_spentO que a execução gastou, pelo contador RDP do Opacus
deltaO delta que a execução usou, o seu próprio ou o padrão
max_grad_normO corte por item
noise_multiplierO multiplicador de ruído que o Opacus escolheu
accountantrdp
coverstrain split

epsilon_spent conta toda época que a execução treinou. As épocas depois do checkpoint mantido contam para o total. Publicar no Hugging Face Hub envia model_card.json com o modelo, com os caminhos locais substituídos por nomes de arquivo, então a entrada dp viaja com os pesos. O cartão de modelo README.md no Hub também tem uma linha "Differential privacy" com o epsilon gasto em relação ao alvo, o delta e a divisão train que a garantia cobre. A linha omite o alvo ou o delta quando a execução não registrou o valor.

Escolhendo o orçamento de privacidade e o corte

A documentação do Potato dá 1 a 10 como o intervalo usual para o epsilon e exige um delta menor que 1 / itens train. O FAQ do Opacus dá a mesma regra prática para o delta e recomenda uma constante pequena para o epsilon com um delta minúsculo. O delta padrão de 1 / (10 × itens train) fica abaixo do teto de 1 / itens train, então a maioria dos estudos pode deixar o delta de fora.

O corte, max_grad_norm, troca dois erros. O corte altera todo gradiente maior que o limiar, e o FAQ do Opacus diz que o corte prejudica a convergência. O ruído é escalado pelo corte, então um corte maior adiciona mais ruído. O FAQ acrescenta que um limiar errado prejudica mais os modelos lineares que os profundos. Comece do padrão de 1,0 e compare as pontuações em itens reservados antes de ajustar o corte.

O Opacus dimensiona o ruído para as epochs planejadas, então o mesmo epsilon distribuído por menos épocas significa menos ruído em cada passo. O FAQ do Opacus observa que um lote maior eleva a taxa de amostragem e, com ela, o custo de privacidade. O FAQ sugere uma taxa de aprendizado maior e uma parada mais cedo para compensar o custo. Compare a pontuação em itens reservados de uma execução com DP com a pontuação de uma execução sem dp na mesma divisão. A diferença entre as duas pontuações mede o que a garantia custa nos seus dados.

Escopo da garantia

A garantia cobre a divisão train. Alterar um item train altera o que o modelo divulgado poderia ser em no máximo o epsilon declarado. O item conta junto com o rótulo resolvido a partir dos anotadores do item. O Potato declara a garantia por item, e a documentação não declara nenhuma garantia por anotador.

A divisão val está fora da garantia. Após cada época, o treinador pontua o modelo nos itens val e mantém o checkpoint da época de melhor pontuação. Essa escolha lê os rótulos dos itens val sem ruído adicionado, então o epsilon no cartão do modelo não faz nenhuma promessa sobre os itens val. Quando os itens val precisam da mesma proteção que os itens train, defina a fração val como 0 e mova a fração para test, por exemplo splits: {train: 0.7, val: 0, test: 0.3}. Sem itens val, o treinador mantém a última época, patience nunca interrompe a execução mais cedo e a pontuação em itens reservados vem de test. Manter a fração train inalterada mantém os mesmos itens em train, porque o Potato atribui as divisões por um hash com semente do id de cada item.

A lista de rótulos do modelo está fora da garantia. O Potato lê a lista de rótulos a partir dos rótulos que aparecem nos dados, então um rótulo que os anotadores usaram em um único item ainda aparece entre as saídas do modelo.

Diferenças de treinamento sob DP

O DP-SGD precisa de um gradiente para cada item, então uma execução com DP difere de uma execução comum do mesmo treinador nestes pontos:

  • O treinamento executa em precisão total, qualquer que seja o valor de precision, porque a precisão mista fica desligada sob DP.
  • gradient_accumulation deve ser 1. Cada passo treina um lote amostrado por Poisson.
  • Um modelo base quantizado (lora.quantize) é recusado.
  • As tabelas de embeddings de posição e de tipo de token de um transformer de texto ficam congeladas, como o log da execução informa.
  • Um modelo com uma camada que o Opacus não suporta, como BatchNorm, falha no início do treinamento com a camada nomeada.

Apenas hf-text-classification e hf-image-classification aceitam dp. Todo outro treinador recusa um parâmetro que não aceita, então dp em sklearn-text, setfit ou hf-token-classification falha na inicialização em trainer_params e com um 409 quando uma execução começa.

Erros no início de uma execução

potato validate informa uma chave que dp não aceita e um valor fora do intervalo antes de o servidor iniciar. Uma execução verifica o bloco dp de novo quando começa, junto com o Opacus e os limites que dependem da divisão train. Uma execução que falha nessas verificações termina em erro antes do treinamento, com o código validation_failed e todos os problemas em uma mensagem. As mensagens vêm das verificações do Potato:

ProblemaMensagem
Opacus não instaladodp needs opacus, which is not installed (pip install opacus).
Sem epsilondp needs epsilon, the privacy budget for the run (1 to 10 is usual).
Uma chave que dp não aceitadp does not take bogus; it takes epsilon, delta and max_grad_norm.
delta igual ou acima de 1 / itens traindp.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 acima de 1dp trains one sampled batch per step; set gradient_accumulation to 1.
lora.quantize definidodp cannot train a quantized (4bit or 8bit) base model; drop lora.quantize.

Um mapeamento vazio, dp: {}, pede DP sem orçamento e falha com a mensagem de epsilon ausente. Quando trainer_params define dp, um "dp": null ou "dp": false da execução desativa o DP para essa execução. A rota de status, GET /admin/training/api/status, lista as mesmas mensagens em errors.

Leitura complementar

Referências