Parte V — No mundo real · Cap. 15

Sistemas de ML

O modelo é a fração pequena. Os outros 95%.

◐ essencial🕒 estado da arte 2026-08revisão 2026-08-10📖 ~16 min de leitura🎯 3 exercícios⬇ md

Objetivos de aprendizagem

  • O1. Descrever os componentes de um sistema de ML além do modelo.
  • O2. Identificar as formas de dívida técnica específicas de sistemas que aprendem.
  • O3. Projetar o contrato entre treino e inferência para evitar training-serving skew.
  • O4. Decidir entre predição em lote e em tempo real a partir do requisito, não do gosto.

O problema: o modelo funciona, o sistema apodrece

O modelo entrou em produção e acertou. Seis meses depois, o mesmo time não consegue mais mexer nele.

Trocar um atributo de entrada exige reajustar o modelo inteiro, e ninguém sabe dizer o que vai acontecer. Um script sem dono junta três tabelas todo dia às 4h. Existe um arquivo de configuração com centenas de linhas que nunca passou por revisão. E um segundo modelo, de outro time, lê a saída do primeiro — o primeiro time descobriu isso por acaso.

Nada disso é bug. Nada disso aparece num teste. E, principalmente, nada disso tem uma linha no orçamento — porque cada item desses é caro sem ter nome. O erro que este capítulo previne é tratar o modelo como se fosse o sistema: ele é uma peça pequena de uma máquina grande, e os modos de falha caros moram fora dele.

De onde isto veio

O aperto. Entre 2014 e 2015, dentro do Google, sistemas de Machine Learning (ML) em produção acumulavam um custo de manutenção que nenhum vocabulário de engenharia existente nomeava. Todo mundo sentia; ninguém conseguia escrever numa planilha.

O que se fazia antes. Tratava-se o modelo como o sistema. A "cola" entre os componentes era invisível — não por ser pequena, mas porque não tinha nome.

A virada. Não foi inventar uma técnica. Foi importar um vocabulário pronto de outro campo — o de dívida técnica, da engenharia de software — e usá-lo para tornar o custo legível a quem decide orçamento. Um time de dez autores publicou o diagnóstico no NIPS 2015, e o vocabulário pegou.

A ideia reaproveitável. Um problema que não tem nome não entra no orçamento. Antes de inventar solução, verifique se o que falta é a palavra. Custo sem nome não é priorizado, não é medido e não é defendido em reunião — ele apenas cresce, e depois alguém leva a culpa por "lentidão do time".

O nome. Technical debt é de Ward Cunningham, num addendum aos anais da OOPSLA '92: "Shipping first time code is like going into debt." Entregar código na primeira versão é como contrair uma dívida — útil, desde que se pague antes que os juros comam o time.

A leitura que fecha (📖)

A figura 1 do artigo de 2015 é o par da AlexNet do capítulo 10. Duas imagens que atravessaram um campo inteiro: uma é um limite de 3 GB desenhado; a outra é uma caixa preta pequena no meio de treze retângulos. Nenhuma das duas é um resultado — as duas são argumentos visuais, e é por isso que sobreviveram. Um resultado é superado pelo resultado seguinte; um argumento visual só cai quando o argumento deixa de valer.

Procedência das afirmações desta seção:

Selo Afirmação
Tudo o que é atribuído a Sculley, Holt, Golovin, Davydov, Phillips, Ebner, Chaudhary, Young, Crespo e Dennison — figura 1, princípio CACE, dívida de configuração, pipeline jungles e os trechos citados entre aspas — de Hidden Technical Debt in Machine Learning Systems (NIPS, 2015), lido por inteiro
O aperto (2014–15, dentro do Google) e a ausência de vocabulário para o custo de manutenção
A metáfora do plumbing (encanamento) não é dos autores: no artigo ela é creditada a Lin e Ryaboy. O crédito some em quase toda repetição da metáfora — registre-o quando repetir
✓ᵐ Technical debt, de Ward Cunningham, addendum aos anais da OOPSLA '92, e a frase "Shipping first time code is like going into debt."
Feature store como termo público em "Meet Michelangelo: Uber's Machine Learning Platform", setembro de 2017, descrita como camada compartilhada que garante consistência entre offline e online — fonte é blog corporativo, não aberto em primeira mão
A formulação canônica de train/serve skew em Rules of Machine Learning, de Martin Zinkevich (Google), regras #29 a #37 — com a #32 como receita central: reutilizar o código entre treino e serviço
📖 A leitura de que a virada foi importar vocabulário, e de que a figura 1 e a AlexNet são o par de imagens-argumento do campo

Fundamentos: o que é o sistema, além do modelo

A frase que o artigo repete, e que vale decorar: "only a tiny fraction of the code in many ML systems is actually devoted to learning or prediction" — apenas uma fração minúscula do código é de fato aprendizado ou predição. A legenda da figura 1 diz o resto: "The required surrounding infrastructure is vast and complex."

O que ocupa o espaço restante, componente a componente:

Componente O que faz Onde falha
Coleta trazer o dado de onde ele nasce (ver cap. 20) fonte muda de esquema sem avisar
Validação de dados recusar o que está fora do contrato não existe, e o lixo entra calado
Extração de atributos transformar dado bruto em entrada do modelo calculado de um jeito no treino, de outro no serviço
Infraestrutura de serviço responder à chamada latência, versão errada em produção
Monitoramento perceber que algo mudou mede a máquina, não a predição
Gerenciamento de processos orquestrar quem roda quando dependências implícitas entre jobs

Os autores chamam esse entorno, citando Lin e Ryaboy, de plumbing — encanamento. É uma boa palavra: encanamento só é notado quando vaza.

Por que dependências de dados são piores que dependências de código

Esta é a afirmação mais útil do artigo, e a que mais gente ignora: dependências de dados custam mais que dependências de código.

A razão é seca. Para dependência de código existe ferramenta — o compilador acusa, o linker quebra, o teste falha. Para dependência de dados não há compilador. Você consome um sinal produzido por outro time, aquele time muda a definição do sinal, e nada quebra: o modelo simplesmente piora, devagar, e ninguém liga uma coisa à outra. E há o efeito que dá nome ao princípio mais citado do artigo:

"No inputs are ever really independent. We refer to this here as the CACE principle: Changing Anything Changes Everything."

Nenhuma entrada é realmente independente: mudar qualquer coisa muda tudo. E o princípio não se limita aos sinais de entrada — vale para hiperparâmetros, configurações de aprendizado, métodos de amostragem, limiares de convergência e seleção de dados. Remover um atributo aparentemente inútil não devolve o modelo anterior menos aquele atributo: devolve outro modelo.

Exercício15-e1escolha uma

Você vai apresentar a arquitetura do sistema de recomendação da empresa. Qual descrição é fiel ao que um sistema de ML em produção é?

As dívidas que têm nome

Nomear é o serviço que o artigo presta. Cada item abaixo é um custo que você já pagou sem saber chamar.

Glue code. O código que existe só para fazer um pacote de propósito geral caber no seu problema: adaptadores, conversões de formato, empacotamentos. É a maior parte do que se escreve em volta de uma biblioteca de ML, e não faz nada de ML.

Pipeline jungle. Caso particular de glue code, no lado dos dados. Os autores a descrevem como "a jungle of scrapes, joins, and sampling steps" — uma selva de raspagens, junções e amostragens que cresce por acréscimo, um sinal novo de cada vez. E o diagnóstico é organizacional, não técnico: "symptomatic of integration issues that may have a root cause in overly separated 'research' and 'engineering' roles." A selva é sintoma de papéis de pesquisa e engenharia separados demais. Quem for consertar a selva reescrevendo scripts, e não a divisão de trabalho, vai fazer uma selva nova.

Modelos em cascata e o correction cascade. Você tem um modelo que funciona. Precisa dele para um problema ligeiramente diferente, e o caminho barato é aprender uma correção por cima da saída dele. Depois outra correção por cima da correção. Cada camada acrescenta dependência e prende o sistema a um mínimo local: melhorar o modelo de baixo agora piora os de cima, que foram treinados para corrigir os defeitos antigos dele.

Consumidores não declarados. A sua saída está num arquivo, num tópico, numa tabela. Alguém a lê — e você não sabe quem. Isso transforma qualquer mudança sua num incidente de outra pessoa, e é a dívida mais silenciosa da lista: ela só aparece no dia em que você melhora alguma coisa.

Dívida de configuração. A que quase todo time subestima. O artigo é direto: "In a mature system which is being actively developed, the number of lines of configuration can far exceed the number of lines of the traditional code." Num sistema maduro em desenvolvimento ativo, as linhas de configuração podem superar em muito as de código. E ele traz os exemplos datados, do tipo "o atributo A foi registrado incorretamente de 14/09 a 17/09" — o tipo de detalhe que vive num arquivo de configuração e decide o resultado do treino. Dos seis princípios de boa configuração que o artigo lista, o último é o que mais dói e o mais fácil de adotar: "Configurations should undergo a full code review and be checked into a repository."

Reduzir a dívida: versionar, contratar, apagar

Três movimentos, em ordem de retorno por esforço.

1. Versione o dado e a configuração como você versiona código. Configuração revisada e no repositório é a recomendação literal do artigo. Dado versionado é o que permite responder à pergunta que sempre aparece depois de um incidente: com qual dado exatamente este modelo foi treinado? Sem versão, essa pergunta não tem resposta — só opinião.

2. Escreva contratos de entrada. Um contrato declara, para cada atributo, o tipo, a faixa aceitável, a taxa de nulos tolerada e quem é o dono. O que o contrato compra é o compilador que não existe: com ele, uma mudança silenciosa na fonte vira falha ruidosa na sua fronteira, e não uma degradação lenta seis semanas depois. É o mesmo assunto de qualidade e vazamento do capítulo 02, agora escrito como código executável.

3. Apague código morto. Atributo que não é mais usado, ramo experimental que nunca saiu, modelo que ninguém consulta. Cada um deles é superfície para o CACE agir e para um consumidor não declarado aparecer. Apagar é a única forma de pagamento de dívida que não cria dívida nova.

O contrato entre treino e serviço

O train/serve skew é a fronteira mais cara do sistema: o mesmo atributo calculado de dois jeitos — um no treino, em lote, com a tabela inteira disponível; outro no serviço, sob latência, com o que chegou na requisição.

A formulação canônica está nas Rules of Machine Learning, de Martin Zinkevich, regras #29 a #37 (⏳). A regra #32 é a receita inteira, e é curta: reutilize o código entre treino e serviço. Não "escreva os dois com cuidado", não "documente a fórmula" — reutilize o mesmo código, de modo que a divergência se torne impossível em vez de improvável.

A feature store é a versão de plataforma dessa ideia: uma camada compartilhada que serve o mesmo atributo ao treino e à inferência. O termo aparece publicamente em setembro de 2017, na descrição do Michelangelo, plataforma de ML do Uber (⏳). O custo é que ela é mais uma peça de encanamento para manter — e o CACE também vale para ela.

Exercício15-e2escolha todas que valem

Sua equipe calcula media_de_compras_90d num job Spark noturno para treinar, e reimplementa o mesmo atributo em Java no serviço de inferência. Quais medidas atacam de fato o train/serve skew? Marque todas que valem.

Exercício15-e3responda com suas palavras

Um sistema de previsão de demanda funciona assim: um job diário raspa três bancos internos e uma planilha do time comercial, junta tudo e amostra os últimos 18 meses. Sobre a saída do modelo de demanda, um segundo modelo aplica uma correção sazonal, e um terceiro corrige a correção para as lojas do Nordeste. O time de precificação lê a tabela final — o time de demanda descobriu isso no mês passado. O comportamento do sistema é regido por um arquivo params.yaml de 600 linhas que fica na máquina do analista sênior.

Liste as dívidas ocultas deste sistema, usando os nomes do capítulo, e diga por onde você começaria a pagar — justificando a ordem.

Síntese — o que levar

  • O código de aprendizado é uma fração pequena do sistema. O resto é encanamento — e encanamento só é notado quando vaza.
  • Um problema que não tem nome não entra no orçamento. Às vezes o que falta não é solução: é a palavra.
  • Dependências de dados são piores que dependências de código, porque não existe compilador que as acuse. Quem substitui o compilador é o contrato de entrada.
  • CACE: mudar qualquer coisa muda tudo — inclusive hiperparâmetro, amostragem e limiar de convergência. Não existe mudança local num sistema que aprende.
  • Glue code, pipeline jungle, cascata de correções, consumidores não declarados e configuração: cinco custos que você já paga — agora com nome. E configuração é código: revisão e repositório, sempre.
  • Contra train/serve skew, reutilize o código entre treino e serviço. Documentar a fórmula duas vezes não é contrato.
  • Apagar código morto é a única forma de pagar dívida sem criar dívida nova.
  • A operação contínua desse sistema — o que monitorar, quando retreinar, como implantar — é o capítulo 16, a sequência direta deste. A decisão de não lançar está no capítulo 25.

Verificação

  1. Desenhe, para um sistema que você conhece, os componentes além do modelo. Qual deles não existe hoje — e o que essa ausência já custou?
  2. Um colega propõe remover um atributo "que não faz diferença" e diz que o resto do modelo fica igual. Por que o CACE contradiz isso, e o que você pediria antes de aprovar?
  3. Escolha um atributo do seu sistema e descreva como ele é calculado no treino e no serviço. Se as duas descrições não citarem o mesmo código, qual é o seu plano — e por que "conferir com cuidado" não é um plano?