Ir al contenido

Ajuste fino con privacidad diferencial mediante Opacus

Ajusta un clasificador de texto o de imágenes con las etiquetas de tu estudio mediante DP-SGD, fija epsilon y delta y lee la privacidad gastada en el registro de la ejecución y en la ficha del modelo.

Novedad de la v2.14.0

Este tutorial entrena un clasificador con las propias etiquetas de un estudio y con privacidad diferencial, de modo que el modelo publicado revela poco sobre cualquier ítem anotado. La página está dirigida a responsables de estudios cuyos ítems o etiquetas son sensibles (por ejemplo, historiales médicos o mensajes privados) y que piensan compartir o publicar el modelo. Añadirás un bloque dp a los parámetros de hf-text-classification, ejecutarás el entrenador con el ejemplo de reseñas de productos y leerás la privacidad gastada en el registro de la ejecución y en la ficha del modelo. El mismo bloque funciona con hf-image-classification, y ningún otro entrenador admite dp.

Epsilon y delta en la privacidad diferencial

La privacidad diferencial es una garantía matemática sobre un cálculo realizado en un conjunto de datos. Añadir o quitar un registro cambia la probabilidad de cualquier salida en una cantidad acotada, de modo que la salida revela poco sobre si ese registro estaba presente. Epsilon (ε) es la cota. Un epsilon menor hace más difícil distinguir las salidas con el registro y sin él, y por tanto da una privacidad más fuerte. Delta (δ) es la probabilidad de que falle la cota de epsilon, y la documentación de Potato describe delta como «the chance the guarantee does not hold».

DP-SGD entrena una red neuronal bajo esta garantía, con el método de Abadi et al. (2016). Cada paso de entrenamiento recorta el gradiente de cada ítem a una norma máxima y añade ruido gaussiano a la suma. El recorte acota cuánto puede mover un ítem el modelo, y el ruido enmascara ese movimiento acotado. Opacus es una biblioteca de PyTorch que implementa DP-SGD, y Potato entrega el bucle de entrenamiento a Opacus siempre que una ejecución lleva dp. El contador RDP de Opacus suma el epsilon que gasta cada paso.

Instalar el entrenador y Opacus

Los entrenadores de transformers necesitan torch y transformers de los extras de entrenamiento de Potato. Ningún extra de Potato incluye Opacus, porque Opacus fija versiones concretas de torch. Instala ambos en la máquina donde se entrena la ejecución:

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

Una ejecución con dp comprueba que Opacus esté en la máquina de entrenamiento antes de empezar a entrenar. En un backend ssh, SLURM o agent, Opacus va por tanto en el servidor con GPU o en el nodo de cómputo que entrena, y no en el servidor de anotación. El entrenamiento remoto cubre esos backends.

Configurar el entrenador

La siguiente configuración es el ejemplo de reseñas de productos de Entrena tu primer modelo, con el entrenamiento privado como valor predeterminado de hf-text-classification. Copia data/reviews.json y data/gold.json de examples/ai-assisted/model-training/ de Potato en una carpeta de proyecto y guarda esta configuración allí 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 fija los valores predeterminados de cada ejecución de hf-text-classification, así que el campo Parameters de la página Training se abre con la entrada dp ya rellenada. epsilon: 8 está dentro del intervalo de 1 a 10 que la documentación de Potato llama habitual. delta se omite, así que Potato fija delta en 1 / (10 × ítems de train). max_grad_norm: 1.0 repite el recorte predeterminado. epochs y batch_size repiten los valores predeterminados del entrenador, porque Opacus dimensiona el ruido a partir de ambos. require_password: false y la clave de administración simple solo sirven para una prueba local.

dp admite tres claves:

ClaveValor predeterminadoSignificado
epsilonobligatoriaEl presupuesto de privacidad de toda la ejecución. Un epsilon menor es más privado y añade más ruido.
delta1 / (10 × ítems de train)La probabilidad de que no se cumpla la garantía. Delta debe ser menor que 1 / ítems de train.
max_grad_norm1.0El gradiente de cada ítem se recorta a esta norma antes de añadir el ruido.

La configuración pasa python -m potato.validate_cli config.yaml --strict. El validador acepta un mapeo, null o false para dp y rechaza dp: true. El validador comprueba también cada clave dentro de dp y el intervalo de la clave. Opacus y las comprobaciones que dependen del número de ítems de train se ejecutan cuando se inicia una ejecución, en la máquina que entrena, como lista Errores al inicio de una ejecución.

Etiquetar ítems e iniciar una ejecución

hf-text-classification se niega a ajustar con menos de 20 ítems de train (min_instances). Inicia el servidor y etiqueta después las reseñas a mano o con tres anotadores simulados del simulador de usuarios, que responden a partir del archivo de referencia:

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

El simulador etiqueta 120 de las 160 reseñas. Con las splits predeterminadas (70 % train, 15 % val, 15 % test) y split_seed: 0, nuestra ejecución puso 76 reseñas etiquetadas en train, 19 en val y 25 en test. Por tanto, delta toma por defecto el valor 1 / 760, aproximadamente 0,00132, y cualquier delta que fijes debe ser menor que 1 / 76, aproximadamente 0,0132.

Abre http://localhost:8000/admin/training con la clave de administración, elige hf-text-classification y el esquema sentiment, comprueba que el campo Parameters muestra la entrada dp y pulsa Train now. La misma ejecución se inicia desde la 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"]}'

La respuesta contiene el run_id. Los params propios de una ejecución sustituyen a trainer_params, así que "params": {"dp": {"epsilon": 2}} entrena una ejecución con un presupuesto menor, y "params": {"dp": null} o "params": {"dp": false} entrena una ejecución sin DP. Mantén split_seed sin cambios entre las dos ejecuciones, para que ambas entrenen y puntúen con los mismos ítems y las puntuaciones sobre ítems reservados se comparen directamente.

Líneas del registro de la ejecución con DP

La página de la ejecución en /admin/training/runs/<run_id> lista los eventos y las métricas de la ejecución. Una ejecución con DP añade tres tipos de línea a los eventos.

La primera línea nombra las tablas de embeddings que Potato congela. Los embeddings de posición y de tipo de token de un transformer de texto usan una fila compartida por todo el lote, y Opacus no puede calcular un gradiente por ítem a través de una fila compartida. Para el modelo predeterminado, distilbert/distilroberta-base, las tablas congeladas son roberta.embeddings.position_embeddings y roberta.embeddings.token_type_embeddings. Las tablas congeladas no se entrenan y no gastan privacidad, y los embeddings de palabras y todas las demás capas siguen entrenándose.

La segunda línea empieza por DP-SGD: e indica el epsilon objetivo, delta, las épocas previstas, el multiplicador de ruido que eligió Opacus, el recorte por ítem y la tasa de muestreo. Cada paso extrae un lote por muestreo de Poisson, de modo que cada ítem de train entra en un lote de forma independiente con probabilidad 1 / lotes por época. Con 76 ítems de train en lotes de 16, una época tiene 5 lotes y el registro da una tasa de muestreo de 0.2000. El multiplicador de ruido fija la desviación típica del ruido, y las preguntas frecuentes de Opacus dan de 0,1 a 2 como intervalo habitual.

Después de cada época, una línea con la forma Epoch <n>/<epochs>: loss <loss>, epsilon spent <epsilon> notifica la pérdida media de la época y el epsilon gastado hasta ese momento. El mismo valor se registra por época como la métrica dp_epsilon, y las métricas finales de la ejecución llevan dp_epsilon para toda la ejecución. Opacus dimensiona el ruido para que las épocas previstas gasten como máximo el epsilon objetivo. Una ejecución que se detiene antes por patience (dos épocas sin una puntuación val mejor, por defecto) gasta menos que el objetivo.

La entrada dp en la ficha del modelo

La ejecución escribe model_card.json en artifacts/, dentro de la carpeta de la ejecución, <task_dir>/<output_annotation_dir>/training/runs/<run_id>/. La ficha de una ejecución con DP tiene una entrada dp:

CampoValor
epsilon_targetEl epsilon que pediste
epsilon_spentLo que gastó la ejecución, según el contador RDP de Opacus
deltaEl delta que usó la ejecución, el tuyo o el predeterminado
max_grad_normEl recorte por ítem
noise_multiplierEl multiplicador de ruido que eligió Opacus
accountantrdp
coverstrain split

epsilon_spent cuenta todas las épocas que entrenó la ejecución. Las épocas posteriores al punto de control conservado cuentan para el total. Publicar en Hugging Face Hub sube model_card.json con el modelo, con las rutas locales sustituidas por nombres de archivo, de modo que la entrada dp viaja con los pesos. La ficha README.md del Hub tiene además una fila «Differential privacy» con el epsilon gastado frente al objetivo, el delta y la partición train que cubre la garantía. La fila omite el objetivo o el delta cuando la ejecución no registró el valor.

Elegir el presupuesto de privacidad y el recorte

La documentación de Potato da de 1 a 10 como intervalo habitual de epsilon y exige un delta menor que 1 / ítems de train. Las preguntas frecuentes de Opacus dan la misma regla práctica para delta y recomiendan una constante pequeña para epsilon con un delta diminuto. El delta predeterminado de 1 / (10 × ítems de train) queda por debajo del techo de 1 / ítems de train, así que la mayoría de los estudios pueden omitir delta.

El recorte, max_grad_norm, equilibra dos errores. Recortar modifica todos los gradientes mayores que el umbral, y las preguntas frecuentes de Opacus dicen que el recorte perjudica la convergencia. El ruido se escala con el recorte, así que un recorte mayor añade más ruido. Las preguntas frecuentes añaden que un umbral equivocado perjudica más a los modelos lineales que a los profundos. Empieza por el valor predeterminado de 1,0 y compara las puntuaciones sobre ítems reservados antes de ajustar el recorte.

Opacus dimensiona el ruido para las epochs previstas, así que el mismo epsilon repartido en menos épocas significa menos ruido en cada paso. Las preguntas frecuentes de Opacus señalan que un lote mayor aumenta la tasa de muestreo y, con ella, el coste de privacidad. Las preguntas frecuentes sugieren una tasa de aprendizaje mayor y una parada más temprana para compensar el coste. Compara la puntuación sobre ítems reservados de una ejecución con DP con la de una ejecución sin dp sobre la misma partición. La diferencia entre las dos puntuaciones mide lo que cuesta la garantía con tus datos.

Alcance de la garantía

La garantía cubre la partición train. Cambiar un ítem de train cambia lo que podría ser el modelo publicado como máximo en el epsilon indicado. El ítem cuenta junto con la etiqueta resuelta a partir de los anotadores del ítem. Potato enuncia la garantía por ítem, y la documentación no enuncia ninguna garantía por anotador.

La partición val queda fuera de la garantía. Después de cada época, el entrenador puntúa el modelo sobre los ítems val y conserva el punto de control de la época con mejor puntuación. Esa elección lee las etiquetas de los ítems val sin ruido añadido, así que el epsilon de la ficha del modelo no promete nada sobre los ítems val. Cuando los ítems val necesitan la misma protección que los de train, fija la fracción val en 0 y pasa esa fracción a test, por ejemplo splits: {train: 0.7, val: 0, test: 0.3}. Sin ítems val, el entrenador conserva la última época, patience nunca detiene antes la ejecución y la puntuación sobre ítems reservados procede de test. Dejar sin cambios la fracción de train mantiene los mismos ítems en train, porque Potato asigna las particiones mediante un hash con semilla del identificador de cada ítem.

La lista de etiquetas del modelo queda fuera de la garantía. Potato lee la lista de etiquetas de las etiquetas que aparecen en los datos, así que una etiqueta que los anotadores usaron en un solo ítem sigue apareciendo entre las salidas del modelo.

Diferencias de entrenamiento con DP

DP-SGD necesita un gradiente por ítem, así que una ejecución con DP difiere de una ejecución ordinaria del mismo entrenador en lo siguiente:

  • El entrenamiento se ejecuta en precisión completa, diga lo que diga precision, porque la precisión mixta está desactivada con DP.
  • gradient_accumulation debe ser 1. Cada paso entrena un lote muestreado por Poisson.
  • Se rechaza un modelo base cuantizado (lora.quantize).
  • Las tablas de embeddings de posición y de tipo de token de un transformer de texto se congelan, como notifica el registro de la ejecución.
  • Un modelo con una capa que Opacus no admite, como BatchNorm, falla al empezar el entrenamiento con la capa nombrada.

Solo hf-text-classification y hf-image-classification admiten dp. Todos los demás entrenadores rechazan un parámetro que no admiten, así que dp en sklearn-text, setfit o hf-token-classification falla en el arranque bajo trainer_params y con un 409 cuando se inicia una ejecución.

Errores al inicio de una ejecución

potato validate notifica una clave que dp no admite y un valor fuera de intervalo antes de que arranque el servidor. Una ejecución vuelve a comprobar el bloque dp cuando se inicia, junto con Opacus y los límites que dependen de la partición train. Una ejecución que no supera esas comprobaciones termina en un error antes de entrenar, con el código validation_failed y todos los problemas en un solo mensaje. Los mensajes proceden de las comprobaciones de Potato:

ProblemaMensaje
Opacus no instaladodp needs opacus, which is not installed (pip install opacus).
Sin epsilondp needs epsilon, the privacy budget for the run (1 to 10 is usual).
Una clave que dp no admitedp does not take bogus; it takes epsilon, delta and max_grad_norm.
delta igual o superior a 1 / ítems de 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 superior a 1dp trains one sampled batch per step; set gradient_accumulation to 1.
lora.quantize fijadodp cannot train a quantized (4bit or 8bit) base model; drop lora.quantize.

Un mapeo vacío, dp: {}, pide DP sin presupuesto y falla con el mensaje de epsilon ausente. Cuando trainer_params fija dp, un "dp": null o "dp": false de una ejecución desactiva DP para esa ejecución. La ruta de estado, GET /admin/training/api/status, lista los mismos mensajes en errors.

Más información

Referencias