Machine Learning · Insper

MLOps

Abordagem fundamental

O que exatamente vai para produção, como uma requisição chega ao modelo com segurança e como perceber que o mundo mudou?

Esta aula começa onde as outras terminaram

  • Até aqui, toda aula terminava quando o escore de teste era impresso.
  • O que gera valor são predições entregues de forma confiável a quem age com base nelas.
  • MLOps (machine-learning operations, operações de aprendizado de máquina): os hábitos de engenharia que levam um pipeline ajustado para produção e o mantêm confiável depois que ele chega lá.
O lab acompanha o modelo de telemarketing de um banco, treinado com ligações de 2008–2009, nas ligações de 2009–2010, quando a fração de clientes que disseram sim passou de cerca de 7% para mais de 50%. Drift real, não simulado.

O modelo é a caixa pequena

Sculley et al. (2015), Hidden Technical Debt in Machine Learning Systems: o código que aprende é uma caixinha cercada de infraestrutura e a maioria das falhas acontece na infraestrutura.

212
linhas de código no lab desta aula
4 + 5
chamam fit + definem o modelo

O resto carrega, verifica, empacota, serve, monitora e compara.

O laço e o que o monitor manda de volta

Clique nos botões: o que um alerta de drift manda de volta? E um bug de dados?

Cada seta pode quebrar sem erro nenhum

etapafalha em silêncio quando...
empacotaro arquivo carrega em outra versão da biblioteca e prediz diferente
serviruma requisição malformada ainda recebe uma probabilidade com cara de confiável
treinar de novoninguém sabe dizer que dados, código e semente produziram o modelo em produção
monitoraro mundo muda e o modelo continua respondendo como se não tivesse mudado
servir vs treinaro código de serving calcula um atributo de forma diferente do código de treino

As quatro primeiras linhas são esta parte; a última abre a abordagem avançada.

O caminho desta parte

  1. Empacotar o modeloo pipeline inteiro, riscos do pickle, versões fixas
  2. Servir prediçõesbatch ou online, um schema na frente, um endpoint
  3. Reproduzir e versionarsementes, impressão digital dos dados
  4. Monitorar o drifttrês mudanças, PSI e KS à mão, rótulos atrasados
Cada etapa responde a uma pergunta: o que exatamente vai para produção? → como uma requisição chega ao modelo com segurança? → conseguiríamos reconstruir o modelo que está rodando? → como perceber que o mundo mudou?
Etapa 1 · o que exatamente vai para produção?

Empacotamento: o artefato é o pipeline inteiro

Crédito alemão (1.000 empréstimos passados): 3 colunas numéricas e 5 categóricas → scaler + one-hot → regressão logística.


joblib.dump(pipe, path)                  # o pipeline INTEIRO, um arquivo
loaded = joblib.load(path)
np.array_equal(proba, loaded.predict_proba(X_test)[:, 1])
  

AUC de teste

0,783
área sob a curva ROC (area under the curve; receiver operating characteristic)

no disco

4.721
bytes

ida e volta

True
idêntico bit a bit

Por que não distribuir só o modelo?

Preveja. Tire a LogisticRegression ajustada de dentro do pipeline e dê a ela a tabela crua. O que acontece?

ValueError: X has 8 features, but LogisticRegression is expecting 30 features as input.
  
  • O modelo viu as 30 colunas do column transformer: 3 números padronizados + 27 indicadores one-hot.
  • Servi-lo sozinho exige reconstruir essas 30 colunas à mão: mesmas médias, mesma ordem de categorias, mesmos desconhecidos. Qualquer diferença produz predições erradas sem erro nenhum.
Distribua o pipeline: o pré-processamento vira parte do artefato.

Um pickle é um programa


class Innocent:
    def __reduce__(self):                 # diz ao pickle como reconstruir este objeto...
        return (print, ('this line ran while the file was being LOADED',))

obj = pickle.loads(pickle.dumps(Innocent()))   # ...e o carregamento obedece
  

this line ran while the file was being LOADED
  
Troque print por os.system e o carregamento roda um comando de shell. Carregue arquivos de modelo apenas de fontes em que você confiaria para rodar código na sua máquina.

Fixe as versões

O scikit-learn carimba sua versão em todo estimador que salva. Forjamos um arquivo de outra versão trocando um dígito do carimbo:


InconsistentVersionWarning for: ['StandardScaler', 'OneHotEncoder',
  'ColumnTransformer', 'LogisticRegression', 'Pipeline']
  

Todo estimador reclama e ainda assim o arquivo carrega. Ele pode predizer diferente.

  • guarde as versões (Python, scikit-learn, NumPy) ao lado do modelo, num arquivo de metadados;
  • fixe-as no ambiente de serving (requirements.txt, uma imagem de contêiner);
  • ao atualizar a biblioteca, retreine e revalide.
Etapa 2 · como uma requisição chega ao modelo com segurança?

Serving: como o modelo é chamado

padrãolatênciaexemplo
batchhorasescores de churn gravados num banco de dados toda noite
online (uma API, application programming interface, interface de programação)milissegundosdecisão de crédito durante o checkout
streamingsegundosalertas de fraude num fluxo de transações
edgeno dispositivoprevisão de teclado no celular
Prefira batch sempre que a decisão puder esperar: mais barato, mais fácil de monitorar e mais fácil de rodar de novo.

O pipeline aceita lixo

Preveja. Uma requisição boa e quatro quebradas. Quantas das quebradas levantam erro?
requisiçãoresposta
requisição boaP(bad) = 0,626
idade = −3P(bad) = 0,784
valor em milharesP(bad) = 0,574
propósito desconhecido 'crypto'P(bad) = 0,519
housing ausenteValueError: columns are missing

Só uma. As outras três recebem uma probabilidade que parece tão confiável quanto a da boa.

Um schema e uma função que o verifica

Registre o que o modelo viu no treino (uma faixa por número, uma lista por categoria) e confira cada requisição antes de predizer:

requisiçãostatusmensagem
requisição boaok0,626
idade = −3rejeitadaage: −3 outside training range [19, 75]
valor em milharesrejeitadacredit_amount: 4 outside training range [250, 15945]
propósito desconhecidorejeitadapurpose: unknown category 'crypto'
housing ausenterejeitadahousing: missing
Responda com a versão do modelo, para que toda resposta registrada possa ser rastreada. Rejeite valores impossíveis; avise (prediga, mas registre) para valores que são apenas novos.

A mesma função atrás de um endpoint HTTP

HTTP: HyperText Transfer Protocol, o protocolo da web.


# app.py  (ilustrativo: rode com uvicorn, não num notebook)
from fastapi import FastAPI
from service import predict_one           # a função testada

app = FastAPI()

@app.post('/predict')
def predict(payload: dict):
    return predict_one(payload)
  
A camada web não tem nenhuma decisão. Tudo o que pode estar errado está em predict_one, uma função comum que você testa num notebook.
Etapa 3 · conseguiríamos reconstruir o modelo que está rodando?

Reprodutibilidade: cada ingrediente tem uma versão

ingredienteo que registrarferramenta típica
códigoo commit que o treinougit
dadosuma impressão digital e um jeito de recuperar as linhasDVC (Data Version Control), lakeFS, snapshots
ambienteversões das bibliotecaslock files, um contêiner
aleatoriedadecada sementerandom_state
configuraçãohiperparâmetros, atributos, limiaresum arquivo de configuração no git
resultadosmétricas, o artefatorastreador, registro de modelos

Sementes: mesmo escore, decisões diferentes

Duas random forests que diferem só na semente:

AUC

0,763 · 0,766
parecem o mesmo modelo

probabilidades

96%
diferem

decisões

18 de 250
mudam
"Por que fui recusado ontem e aprovado hoje?" merece uma resposta melhor do que "rodamos o script de novo". Fixe a semente; nunca teste sementes até o escore de teste ficar o melhor possível.

A impressão digital dos dados


training data      68ad024c9661
one value changed  7eb64a1901f0
rows shuffled      a662547f0332
same data again    68ad024c9661
  
  • Um único valor alterado entre 6.000 dá uma impressão digital completamente diferente; outra ordem das linhas também.
  • "Foi treinado com a extração deste mês?" vira uma comparação de strings.

Um hash apenas identifica os dados; reconstruir exige as próprias linhas (DVC, lakeFS, snapshots somente leitura).

Etapa 4 · como perceber que o mundo mudou?

Monitoramento: modelos se degradam em silêncio

O serviço continua respondendo 200 OK enquanto suas predições pioram. Três camadas: sistema, qualidade dos dados, drift estatístico.

mudançao que mudao que fica fixoexemplo
covariate shift\(P(x)\)\(P(y \mid x)\)o banco vende para clientes mais jovens
concept drift\(P(y \mid x)\)\(P(x)\)uma crise muda quem paga
prior shift\(P(y)\)\(P(x \mid y)\)uma epidemia: mais pacientes doentes

\(x\): as entradas de uma requisição; \(y\): o rótulo dela, o desfecho que o modelo prevê.

PSI à mão: uma faixa de cada vez

PSI (population stability index, índice de estabilidade populacional): faixas nos quartis do treino, \(e_i\) = fração esperada, \(a_i\) = fração desta semana. Preveja. Qual faixa contribui mais? A soma passa de 0,25?

O que quer dizer PSI = 0,25?

Preveja. Um atributo normal cuja média se desloca. Quanto ela precisa se mover para o PSI chegar ao nível de alarme 0,25?
média deslocada em00,1 dp0,25 dp0,5 dp1 dp
PSI (10 faixas)0,0050,0150,0640,2500,929

O nível de alarme é um deslocamento de meio desvio padrão (dp). Sem deslocamento nenhum, o PSI não é exatamente zero: duas amostras nunca têm histogramas idênticos.

Leitura usual (Siddiqi, 2006): abaixo de 0,1, nenhuma mudança relevante · de 0,1 a 0,25, merece atenção · acima de 0,25, significativa.

Simulador: desloque a distribuição de produção

Preveja. Ligue o concept drift sem mexer nas entradas. O que acontece com o PSI e com a acurácia?

A estatística KS, à mão

KS (Kolmogorov–Smirnov): \(D = \max_x |F_{\text{ref}}(x) - F_{\text{cur}}(x)|\), \(F\) = fração da amostra em \(x\) ou abaixo. Referência 1–5, atual 3–7. Preveja. Em que \(x\) a distância é maior?

As duas estatísticas em 5.000 linhas

À esquerda: frações da referência e dos dados atuais nas 10 faixas de decis da referência, com os dados atuais se acumulando nas faixas de cima, PSI 0,25; à direita: as duas distribuições acumuladas com o maior salto D = 0,21 marcado
A média andou meio desvio padrão: PSI 0,25 (à esquerda, faixa a faixa) e \(D = 0{,}21\) (à direita, o maior salto vertical). As duas são magnitudes: o quanto a distribuição se moveu.

As três mudanças, medidas

Preveja. Uma das três mudanças é invisível para todo monitor de entrada. Qual?
PSI(x)p do KSacuráciaprecisãotaxa realmédia prevista
sem mudança0,0040,6950,8160,7690,4620,465
covariate shift3,5050,0000,7280,7290,7200,780
concept drift0,0030,3400,6600,3290,1670,459
prior shift0,1520,0000,7940,4910,2000,339
Quatro painéis: sem mudança, covariate shift, concept drift e prior shift, cada um com a distribuição de x, a curva de probabilidade verdadeira e a curva do modelo

Os rótulos chegam atrasados: monitorar em duas velocidades

imediatamente

verificações de qualidade dos dados, PSI ou KS de cada entrada e a distribuição dos próprios escores do modelo: um dos detectores de mudança mais eficazes, por uma linha (Rabanser et al., 2019)

depois, com os desfechos

as métricas de verdade (AUC, precisão, calibração) por lote, contra a estimativa de validação

O churn se sabe depois de 60 dias; um calote, depois de meses. Um empréstimo recusado nunca ganha histórico de pagamento: uma pequena amostra auditada e rotulada é a única proteção contra concept drift.

Resumo

  1. Distribua o pipeline inteiro, confira a ida e volta, fixe as versões; um pickle é um programa.
  2. Ponha um schema na frente do modelo: rejeite o impossível, avise sobre o novo, responda com a versão.
  3. Versione código, dados (uma impressão digital), ambiente, sementes e configuração.
  4. Meça as mudanças com magnitudes: o PSI faixa a faixa, a estatística KS como a maior distância.
  5. Monitore entradas e escores na hora, desfechos depois; só os rótulos revelam o concept drift.
sintomao que fazer
uma requisição quebrada recebe uma probabilidade com cara de normalum schema: rejeitar ou avisar
o PSI de uma entrada passa de 0,25olhar a origem antes de retreinar
entradas estáveis, acurácia caindo quando os rótulos chegamconcept drift: métricas de desfecho, rótulos auditados
Lab fundamental · cerca de 45 min, em duplas

Produção e monitoramento de um modelo de marketing bancário, passos 1 a 8

45.211 ligações de telemarketing, de maio de 2008 a novembro de 2010, na ordem em que foram feitas. Implantado em 1º de maio de 2009, acompanhado por 19 meses.

Construir e validar (1–3), empacotar e proteger (4–6), monitorar sem rótulos (7) e depois com eles (8). Em cada passo, responda o Preveja antes de rodar.

Simulações: o vazamento, em produção (passo 2) e de onde vem o PSI de contact (passo 7).

Notebook do lab (Jupyter ou Colab, Arquivo → Fazer upload de notebook).

Ritmo: passos 1 e 2 e a primeira simulação em 12 min; passos 3 a 6 em 15 min; passo 7 e a segunda em 13 min; passo 8 em 5 min.

Por trimestre: a taxa real de contratação sobe para acima de 50% enquanto o modelo estático prevê cerca de 16% e o modelo retreinado acompanha a taxa