Capítulo 25 — Laboratorio I
Mapa de la lección
Lección 25 publicada. Mapa, ejemplo, EX073.
flowchart TD
N0["Resultados de aprendizaje"]
N1["25.1 Reglas del laboratorio"]
N2["25.2 Experimentos"]
N3["25.3 Interpretación"]
N4["25.4 Informe de laboratorio"]
N5["25.4b Cambio de parámetros"]
N6["25.4c Seguridad del código del laboratorio"]
N7["Síntesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7
N7 --> EX["Ejemplo resuelto"]
EX --> Q["Ejercicio EX073"]
Dibujo lógico
flowchart LR
N0["Resultados de aprendizaje"]
N1["25.1 Reglas del laboratorio"]
N2["25.2 Experimentos"]
N3["25.3 Interpretación"]
N4["25.4 Informe de laboratorio"]
N5["25.4b Cambio de parámetros"]
N6["25.4c Seguridad del código del laboratorio"]
N7["Síntesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> N7 --> Q["EX073"]
[Resultados de aprendizaje]
|
v
[25.1 Reglas del laboratorio]
|
v
[25.2 Experimentos]
|
v
[25.3 Interpretación]
|
v
[25.4 Informe de laboratorio]
|
v
[25.4b Cambio de parámetros]
|
v
[25.4c Seguridad del código del laboratorio]
|
v
[Síntesis]
|
v
[Ejemplo resuelto] --> [Ejercicio EX073]
Resultados de aprendizaje
Usted podrá (1) montar un laboratorio de seguridad de la IA seguro y aislado; (2) ejecutar experimentos de evasión, envenenamiento, privacidad e inyección sobre modelos de juguete; (3) registrar los resultados de forma reproducible; y (4) redactar un informe de laboratorio breve.
25.1 Reglas del laboratorio
Use entornos aislados, datos sintéticos y cargas útiles inofensivas (por ejemplo, direcciones en el dominio reservado .invalid). No apunte nunca las herramientas del laboratorio a servicios reales. Registre lo que ejecutó, con versiones y semillas. El dominio reservado importa porque una carga útil que envía datos a una dirección real podría filtrar información incluso en una prueba. Registrar las versiones y las semillas permite a Rina reproducir más tarde exactamente la misma tabla, y permite a su supervisor comprobar que las cifras de su informe proceden del código que entregó y no de una edición anterior.
25.2 Experimentos
Ejecute los cuatro laboratorios y registre sus salidas:
"""Lab E: evasion of a toy linear classifier (synthetic data, stdlib only).
Trains logistic regression on two synthetic clusters, then applies a
fast-gradient-sign (FGSM-style) perturbation of size eps to every test point
and reports clean vs adversarial accuracy. Teaching model only."""
import math, random
rng = random.Random(7)
def point(label):
cx = 1.0 if label else -1.0
return [rng.gauss(cx, 0.7), rng.gauss(cx, 0.7)], label
train = [point(i % 2) for i in range(400)]
test = [point(i % 2) for i in range(200)]
w, b = [0.0, 0.0], 0.0
sig = lambda z: 1 / (1 + math.exp(-z))
for epoch in range(200):
gw, gb = [0.0, 0.0], 0.0
for x, y in train:
p = sig(w[0]*x[0] + w[1]*x[1] + b)
gw[0] += (p - y)*x[0]; gw[1] += (p - y)*x[1]; gb += p - y
w = [w[i] - 0.1*gw[i]/len(train) for i in range(2)]; b -= 0.1*gb/len(train)
def acc(data, eps):
ok = 0
for x, y in data:
# gradient of the loss w.r.t. x is (p - y) * w; FGSM steps along its sign
p = sig(w[0]*x[0] + w[1]*x[1] + b)
xa = [x[i] + eps*math.copysign(1, (p - y)*w[i]) for i in range(2)]
ok += (sig(w[0]*xa[0] + w[1]*xa[1] + b) >= 0.5) == bool(y)
return ok / len(data)
print(f"weights w=({w[0]:.3f}, {w[1]:.3f}) b={b:.3f}")
for eps in (0.0, 0.25, 0.5, 0.75, 1.0):
print(f"eps={eps:.2f} accuracy={acc(test, eps):.3f}")
Salida esperada:
weights w=(1.795, 1.756) b=-0.080
eps=0.00 accuracy=0.985
eps=0.25 accuracy=0.950
eps=0.50 accuracy=0.840
eps=0.75 accuracy=0.660
eps=1.00 accuracy=0.505
"""Lab P: label-flip poisoning and a backdoor trigger on a toy nearest-centroid
classifier (synthetic data, stdlib only). Teaching model only."""
import random
rng = random.Random(11)
def point(label, trigger=False):
cx = 1.0 if label else -1.0
x = [rng.gauss(cx, 0.8), rng.gauss(cx, 0.8), 0.0]
if trigger: x[2] = 5.0 # third feature is the 'trigger'
return x, label
clean = [point(i % 2) for i in range(400)]
test = [point(i % 2) for i in range(400)]
def fit(data):
c = {}
for lab in (0, 1):
pts = [x for x, y in data if y == lab]
c[lab] = [sum(p[i] for p in pts)/len(pts) for i in range(3)]
return c
def pred(c, x):
d = {k: sum((x[i]-v[i])**2 for i in range(3)) for k, v in c.items()}
return min(d, key=d.get)
def accuracy(c, data): return sum(pred(c, x) == y for x, y in data)/len(data)
print("label-flip poisoning (flip a fraction of class-1 labels to 0):")
for frac in (0.0, 0.1, 0.2, 0.3, 0.4):
data = [(x, y) for x, y in clean]; ones = [i for i, (x, y) in enumerate(data) if y == 1]
for i in ones[:int(frac*len(ones))]: data[i] = (data[i][0], 0)
print(f" flipped={frac:.1f} clean_test_accuracy={accuracy(fit(data), test):.3f}")
print("backdoor: add n triggered class-1 points labelled 0")
trig_test = [point(1, trigger=True) for _ in range(200)]
for n in (0, 10, 40, 80):
data = clean + [(point(1, trigger=True)[0], 0) for _ in range(n)]
c = fit(data)
asr = sum(pred(c, x) == 0 for x, _ in trig_test)/len(trig_test)
print(f" poison_points={n:3d} clean_accuracy={accuracy(c, test):.3f} attack_success={asr:.3f}")
Salida esperada:
label-flip poisoning (flip a fraction of class-1 labels to 0):
flipped=0.0 clean_test_accuracy=0.965
flipped=0.1 clean_test_accuracy=0.965
flipped=0.2 clean_test_accuracy=0.953
flipped=0.3 clean_test_accuracy=0.950
flipped=0.4 clean_test_accuracy=0.945
backdoor: add n triggered class-1 points labelled 0
poison_points= 0 clean_accuracy=0.965 attack_success=0.075
poison_points= 10 clean_accuracy=0.963 attack_success=0.145
poison_points= 40 clean_accuracy=0.963 attack_success=0.685
poison_points= 80 clean_accuracy=0.968 attack_success=0.980
"""Lab M: membership inference against an overfit 1-nearest-neighbour 'model'
that returns a confidence score. Synthetic records only; teaching model."""
import random, math
rng = random.Random(5)
def rec():
y = rng.random() < 0.5
return [rng.gauss(0.6 if y else -0.6, 1.0) for _ in range(4)], int(y)
members = [rec() for _ in range(150)]; nonmembers = [rec() for _ in range(150)]
def confidence(x, y, k):
# k-NN vote share for the true label, computed over the training set
d = sorted((sum((a-b)**2 for a, b in zip(x, m)), my) for m, my in members)
return sum(1 for _, my in d[:k] if my == y)/k
def auc(pos, neg):
s = sum((p > n) + 0.5*(p == n) for p in pos for n in neg)
return s/(len(pos)*len(neg))
for k in (1, 5, 25):
pos = [confidence(x, y, k) for x, y in members]
neg = [confidence(x, y, k) for x, y in nonmembers]
best = max(sum(p >= t for p in pos)/len(pos) - sum(n >= t for n in neg)/len(neg)
for t in sorted(set(pos + neg)))
print(f"k={k:2d} attack_auc={auc(pos, neg):.3f} best_advantage={best:.3f}")
Salida esperada:
k= 1 attack_auc=0.600 best_advantage=0.200
k= 5 attack_auc=0.521 best_advantage=0.073
k=25 attack_auc=0.520 best_advantage=0.060
"""Lab X: model extraction of a toy linear scoring API by querying it.
The 'API' returns a real-valued score; d+1 well-chosen queries recover the
weights exactly, and rounding the output limits fidelity. Synthetic model."""
import random
rng = random.Random(9)
d = 5
secret_w = [round(rng.uniform(-2, 2), 3) for _ in range(d)]; secret_b = 0.42
def api(x, decimals=None):
s = sum(a*b for a, b in zip(secret_w, x)) + secret_b
return round(s, decimals) if decimals is not None else s
for dec in (None, 2, 0):
b = api([0]*d, dec)
w = [api([1 if j == i else 0 for j in range(d)], dec) - b for i in range(d)]
err = max(abs(a-c) for a, c in zip(w, secret_w))
test = [[rng.uniform(-1, 1) for _ in range(d)] for _ in range(500)]
agree = sum((sum(a*c for a, c in zip(w, x))+b > 0) == (api(x) > 0) for x in test)/500
print(f"output_rounding={str(dec):4s} queries={d+1} max_weight_error={err:.3f} label_agreement={agree:.3f}")
Salida esperada:
output_rounding=None queries=6 max_weight_error=0.000 label_agreement=1.000
output_rounding=2 queries=6 max_weight_error=0.004 label_agreement=0.998
output_rounding=0 queries=6 max_weight_error=0.534 label_agreement=0.882
Rina ejecutó los cuatro en su portátil con las semillas indicadas y comparó cada línea con la salida esperada antes de escribir nada. Todas coincidieron. Cuando la ejecución de evasión de un compañero difirió en el tercer decimal, la causa era una versión distinta de Python, que anotaron en las notas de configuración. Comprobar primero contra la salida esperada detecta esas diferencias antes de que lleguen a un informe o a un corrector.
25.3 Interpretación
Evasión: la exactitud cae a medida que crece el presupuesto. Envenenamiento: la inversión de etiquetas apenas importa para este modelo, pero una puerta trasera alcanza un 98 % de éxito del ataque con la exactitud limpia sin cambios. Pertenencia: un modelo que memoriza filtra; uno más suave filtra poco. Extracción: una API lineal se copia exactamente con seis consultas; redondear las salidas reduce la fidelidad, pero no la elimina. Estos resultados dejan lecciones distintas. La evasión y la extracción dependen de lo que el atacante puede consultar; el envenenamiento depende de quién controla los datos de entrenamiento; la pertenencia depende de cuánto memoriza el modelo. Cada uno apunta a un control distinto en LCH: límites de consultas y salidas gruesas para NoShow, procedencia de las etiquetas para DocSort.
25.4 Informe de laboratorio
Estructura: objetivo, configuración (versiones, semillas), método, resultados (tablas), interpretación, limitaciones (modelos de juguete, datos sintéticos) y lo que probaría a continuación en un sistema real con autorización. El informe de Rina indicaba en sus limitaciones que el laboratorio de envenenamiento usa un modelo lineal minúsculo, así que la cifra de 80 puntos no predice cuántas cartas harían falta para implantar una puerta trasera en DocSort. Lo que sí se traslada es el patrón: el éxito del ataque puede ser alto mientras la exactitud limpia no cambia, así que las pruebas limpias por sí solas no pueden detectar una puerta trasera.
25.4b Cambio de parámetros
Un laboratorio se convierte en experimento cuando usted cambia un parámetro y predice el efecto antes de ejecutarlo. Pruebe a cambiar la lista de presupuestos de evasión, el número de puntos de la puerta trasera o los valores de k del laboratorio de pertenencia; escriba su predicción, ejecute y compare. Registre los parámetros y la salida de cada ejecución. Este hábito —predecir, ejecutar, comparar, registrar— es el núcleo del trabajo experimental de los capítulos posteriores y del proyecto final. Rina predijo que 20 puntos de puerta trasera darían un éxito del ataque entre los resultados de 10 y de 40 puntos; después lo ejecutó y anotó el valor junto a su predicción.
25.4c Seguridad del código del laboratorio
El código del laboratorio que implementa ataques debe quedarse en el laboratorio. No lo adapte para atacar servicios reales ni comparta versiones modificadas que apunten a puntos de conexión reales. Los modelos de juguete de aquí son deliberadamente sencillos para que los conceptos sean visibles; sus ataques no son herramientas eficaces contra sistemas reales, y eso es intencionado. El repositorio de Rina marca la carpeta del laboratorio como material docente y no guarda en ella ninguna configuración de puntos de conexión. Las direcciones de las cargas útiles usan el dominio reservado, así que ni siquiera un script copiado puede enviar datos a ningún lugar real.
25.5 Ejemplo resuelto (caso sintético de LCH)
El informe de laboratorio de Rina conectó cada resultado de juguete con un sistema de LCH: la evasión con las cartas elaboradas de DocSort, el envenenamiento con las etiquetas de los contratistas, la pertenencia con las salidas de confianza de NoShow y la extracción con una API para asociados prevista. Sus «próximos pasos» proponían pruebas autorizadas sobre las copias de preproducción de LCH. El responsable de seguridad de LCH aprobó las pruebas en preproducción con un alcance escrito que indicaba qué sistemas, qué fechas y qué cargas útiles podían usarse. El resultado de extracción fue el más convincente, porque la API para asociados prevista devolvía puntuaciones exactas; el equipo acordó redondear las salidas antes del lanzamiento, aceptando la pérdida de fidelidad mostrada en el laboratorio.
25.6 Errores y límites
- Presentar resultados de juguete como hechos sobre sistemas reales.
- Versiones y semillas ausentes.
- Herramientas del laboratorio apuntadas a puntos de conexión reales.
Un modelo de juguete con cinco características nos informa sobre mecanismos, no sobre la robustez real de DocSort, así que los resultados deben etiquetarse como resultados docentes. Sin versiones ni semillas, un resultado no puede reproducirse ni comprobarse. Apuntar el código del laboratorio a servicios reales, aunque sea por accidente, puede perjudicar a pacientes e incumple las reglas bajo las que se permite el laboratorio; la configuración del laboratorio de Rina solo contiene direcciones locales.
Ejercicios
- EX073 — Extracción. ¿Cuántas consultas recuperan exactamente el modelo lineal y por qué? Rúbrica: 3 = 6 (d + 1): una para el sesgo, una por peso; 2 = el número; 1 = vago; 0 = incorrecto.
- EX074 — Interpretación. ¿Por qué la inversión de etiquetas apenas cambia el modelo de juguete? Rúbrica: 3 = los centroides promedian muchos puntos; las inversiones minoritarias los desplazan poco; 2 = parcial; 1 = vago; 0 = incorrecto.
- EX075 — Informe. Enumere las siete partes del informe de laboratorio. Rúbrica: 3 = las siete; 2 = cinco; 1 = tres; 0 = nada.
Síntesis
El Laboratorio I aporta una intuición práctica de las cuatro familias de ataques y el hábito de redactar informes de laboratorio reproducibles y honestos.
Siga estudiando — capítulo 25 de 40
← Capítulo 24 (borrador) · Índice de capítulos del curso · Capítulo 26 (borrador) →