Capítulo 15 — Segurança de IA e aprendizado de máquina: laboratório guiado
Mapa da aula
Aula 15 publicada. Mapa, exemplo, EX043.
flowchart TD
N0["15.1 Regras do laboratório"]
N1["15.2 Evasão"]
N2["15.3 Envenenamento e backdoors"]
N3["15.4 Inferência de pertencimento"]
N4["15.5 Extração"]
N5["15.5b Escrevendo o relato de um laboratório"]
N6["Síntese"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6
N6 --> EX["Exemplo resolvido"]
EX --> Q["Exercício EX043"]
Desenho lógico
flowchart LR
N0["15.1 Regras do laboratório"]
N1["15.2 Evasão"]
N2["15.3 Envenenamento e backdoors"]
N3["15.4 Inferência de pertencimento"]
N4["15.5 Extração"]
N5["15.5b Escrevendo o relato de um laboratório"]
N6["Síntese"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> N6 --> Q["EX043"]
[15.1 Regras do laboratório]
|
v
[15.2 Evasão]
|
v
[15.3 Envenenamento e backdoors]
|
v
[15.4 Inferência de pertencimento]
|
v
[15.5 Extração]
|
v
[15.5b Escrevendo o relato de um laboratório]
|
v
[Síntese]
|
v
[Exemplo resolvido] --> [Exercício EX043]
Objetivos de aprendizagem
Você vai (1) executar laboratórios contidos de evasão, envenenamento, inferência de pertencimento e extração; (2) interpretar suas saídas; (3) ligar cada um deles a um controle do mundo real; e (4) seguir as regras de segurança do laboratório.
15.1 Regras do laboratório
Os laboratórios rodam offline, com dados sintéticos e a biblioteca padrão do Python 3. São modelos de brinquedo que tornam os conceitos mensuráveis; não são evidência sobre sistemas de produção nem ferramentas para atacar sistemas reais. Jonas usa cada laboratório para entender uma classe de ataque contra o ReturnGuard e para justificar um controle, e não para estimar como o próprio ReturnGuard se sairia. O leitor deve tratar cada número de laboratório como uma propriedade do modelo de brinquedo e de seus dados sintéticos. Sementes fixas fazem cada laboratório imprimir a mesma saída em toda execução.
15.2 Evasão
"""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}")
Saída 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
A acurácia cai de 0,985 para 0,505 à medida que o orçamento de perturbação cresce. Controle: não deixe que a pontuação de um único modelo tome decisões irreversíveis; acrescente regras e revisão humana. No ReturnGuard, isso sustenta a recomendação de Jonas de que uma pontuação alta de fraude leve a uma revisão humana, e não a uma recusa automática. No ReturnGuard, os atributos que um fraudador realmente pode mudar, como o motivo e o momento da devolução, são menos numerosos e mais grosseiros do que a perturbação livre do laboratório, de modo que o laboratório mostra a direção do risco, e não seu tamanho. O tamanho é medido, em vez disso, pelos testes de evasão do capítulo 14, que mudam apenas o motivo, o momento e a divisão de casos reais de fraude.
15.3 Envenenamento e backdoors
"""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}")
Saída 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
A inversão de rótulos quase não mexe na acurácia limpa; 80 pontos de backdoor chegam a 98% de sucesso do ataque, com a acurácia limpa inalterada. Controle: procedência dos dados, direitos de rotulagem restritos, testes de gatilho. No ReturnGuard, cujos rótulos vêm da equipe, isso explica por que Jonas recomendou que apenas gerentes possam alterar os rótulos das devoluções: alguns rótulos plantados com um gatilho poderiam deixar a acurácia limpa inalterada enquanto deixam passar as devoluções correspondentes. Restringir a alteração de rótulos aos gerentes e registrar cada mudança de rótulo é o que torna difícil plantar esse gatilho sem que ninguém perceba.
15.4 Inferência de pertencimento
"""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}")
Saída 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
Quando a pontuação do modelo vem de um único vizinho mais próximo (k=1), a vantagem do ataque é 0,20; ela cai à medida que k cresce nesse cenário. Controle: limite o detalhe da saída, teste o vazamento, considere a privacidade diferencial (Abadi et al., arXiv:1607.00133). No ReturnGuard, a lição é limitar o que o serviço de pontuação revela: um rótulo é mais seguro do que uma pontuação detalhada. O cenário do laboratório é pequeno, então a vantagem decrescente é uma propriedade desse brinquedo, não uma lei geral. O ReturnGuard agora devolve à loja apenas "revisar" ou "aceitar", e não uma pontuação.
15.5 Extração
"""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}")
Saída 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
Com pontuações exatas, seis consultas recuperam o modelo exatamente; arredondar para números inteiros eleva o erro para 0,534. Controle: devolva rótulos ou pontuações grosseiras, limite a taxa, monitore os padrões de consulta (Tramèr et al., arXiv:1609.02943). Este é o laboratório por trás do conselho de Jonas de que o ReturnGuard devolva à loja apenas "revisar" ou "aceitar" e de que as chamadas de pontuação tenham limite de taxa por conta. A chave de parceiro vista mais tarde variando um atributo por vez correspondia ao padrão que este laboratório descreve. Só o arredondamento não tornou a extração impossível no laboratório, e é por isso que os limites de taxa o acompanham.
15.5b Escrevendo o relato de um laboratório
Para cada laboratório, registre: o que foi variado, o que foi medido, a tabela de resultados, uma frase sobre o que ele mostra, uma frase sobre o que ele não mostra e o controle que ele sugere. Esse hábito se transfere diretamente para avaliações reais, nas quais a mesma estrutura mantém os relatórios honestos e legíveis para colegas que não estavam no laboratório. O relato de Jonas sobre o laboratório de extração termina com o que ele não mostra: se a API real de pontuação do ReturnGuard poderia ser copiada, já que o modelo do laboratório é linear e minúsculo.
15.6 Exemplo resolvido (caso sintético da Brightwell)
Jonas roda os quatro laboratórios e escreve uma recomendação de controle para o ReturnGuard a partir de cada um: revisão humana para as recusas; apenas gerentes podem alterar os rótulos das devoluções; devolver à loja apenas "revisar/aceitar", e não uma pontuação; limitar a taxa da API de pontuação por conta da equipe. Cada recomendação vem de um laboratório: da evasão, a revisão humana; do envenenamento, os direitos de rotulagem; da inferência de pertencimento e da extração, as saídas grosseiras e os limites de taxa. Nenhuma afirma que o ReturnGuard tem a fraqueza; cada uma limita quanto uma fraqueza custaria. Cada controle se encaixa numa parte do ReturnGuard que pertence à Brightwell.
15.7 Erros e limites
Dois erros são comuns: tratar resultados de brinquedo como números de produção; e rodar código de ataque contra sistemas reais. Os relatos de Jonas evitam os dois: declaram, para cada laboratório, o que ele não mostra sobre o ReturnGuard, e todos os laboratórios rodam offline, com dados sintéticos. Um resultado de brinquedo citado como número de produção enganaria os donos da mesma forma que uma afirmação não testada, e um código de ataque apontado para um serviço real violaria a autorização da qual todo teste depende. As regras do laboratório deixam as duas coisas explícitas antes que alguém rode qualquer código.
Exercícios
- EX043 — Interpretação. Explique um número de cada laboratório. Rubrica: 3 = quatro interpretações corretas; 2 = três; 1 = uma ou duas; 0 = nenhuma.
- EX044 — Controles. Associe cada laboratório a um controle para o ReturnGuard. Rubrica: 3 = quatro adequados; 2 = três; 1 = um ou dois; 0 = nenhum.
- EX045 — Limites. Por que esses laboratórios não são evidência sobre o modelo real da Brightwell? Rubrica: 3 = dados de brinquedo, modelo diferente, modelo de ameaça diferente; 2 = dois; 1 = um; 0 = nenhum.
Síntese
Laboratórios contidos tornam mensuráveis ataques abstratos, e cada um aponta para um controle prático.
Continue estudando — capítulo 15 de 30
← Capítulo 14 (rascunho) · Índice de capítulos do curso · Capítulo 16 (rascunho) →