Pular para o conteúdo

Papers

Hidden Technical Debt in Machine Learning Systems

D. Sculley, Gary Holt, Daniel Golovin, Eugene Davydov, Todd Phillips, Dietmar Ebner, Vinay Chaudhary, Michael Young, Jean-François Crespo, Dan Dennison · NeurIPS 2015, 2015

O problema: construir rápido é barato, manter é caro

Os autores, todos do Google, partem de uma observação incômoda: desenvolver e colocar um sistema de ML em produção é relativamente rápido e barato, mas mantê-lo ao longo do tempo é difícil e caro. Para explicar essa dicotomia eles usam a metáfora de dívida técnica, introduzida por Ward Cunningham em 1992. Assim como numa dívida financeira, há boas razões estratégicas para contraí-la, nem toda dívida é ruim, mas toda dívida precisa ser paga. Pagar significa refatorar código, melhorar testes unitários, apagar código morto, reduzir dependências, apertar APIs e melhorar a documentação. O objetivo desse pagamento não é adicionar funcionalidade, e sim viabilizar melhorias futuras e reduzir erros. Adiar o pagamento faz os custos se acumularem, e a dívida escondida é perigosa justamente porque cresce em silêncio. O argumento central do artigo é que sistemas de ML têm uma capacidade especial de gerar dívida: herdam todos os problemas de manutenção do software tradicional e acrescentam problemas próprios. Essa dívida é difícil de enxergar porque vive no nível do sistema, não do código, já que os dados influenciam o comportamento e corrompem abstrações e fronteiras habituais. O artigo não propõe novos algoritmos; quer aumentar a consciência sobre trade-offs de longo prazo. O resumo lista os fatores de risco: erosão de fronteiras, entrelaçamento, loops de feedback ocultos, consumidores não declarados, dependências de dados, problemas de configuração, mudanças no mundo externo e anti-padrões de sistema.

Modelos complexos erodem fronteiras: entrelaçamento e CACE

A engenharia de software clássica depende de abstrações fortes, com encapsulamento e modularidade, para que mudanças isoladas sejam fáceis. O artigo argumenta que isso é difícil de impor em ML, porque se usa ML exatamente quando o comportamento desejado não pode ser expresso em lógica de software sem depender de dados externos. O mundo real não cabe em encapsulamentos arrumados. O primeiro efeito é o entrelaçamento: sinais são misturados dentro do modelo, o que impede isolar melhorias. Se o modelo usa as features x1 até xn e a distribuição de x1 muda, a importância e o uso das outras n-1 features podem mudar, esteja o modelo treinado em lote ou adaptando online. Adicionar ou remover uma feature tem efeito parecido. Nenhuma entrada é realmente independente. Os autores chamam isso de princípio CACE, 'Changing Anything Changes Everything', que vale também para hiperparâmetros, configurações de aprendizado, métodos de amostragem, limiares de convergência e seleção de dados. Isolar modelos e servir ensembles ajuda em alguns casos, mas ensembles funcionam bem porque os erros dos componentes são pouco correlacionados, o que cria entrelaçamento forte: melhorar um componente pode piorar o conjunto. Outra estratégia é detectar mudanças de comportamento da predição, por exemplo com visualização em alta dimensão ou métricas por fatia.

Cascatas de correção e consumidores não declarados

Duas formas de erosão de fronteira ganham nome próprio. A primeira é a cascata de correção. Existe um modelo m_a para o problema A e surge a necessidade de resolver um problema ligeiramente diferente A'. A tentação é treinar um modelo m'_a que recebe m_a como entrada e aprende uma pequena correção, o que é rápido. O custo é uma nova dependência de m_a, que torna mais caro analisar melhorias futuras. Se as correções são empilhadas, com um modelo para A'' sobre m'_a e assim por diante, o resultado pode ser um impasse de melhoria: melhorar a acurácia de um componente isolado causa prejuízo no sistema. As mitigações sugeridas são aumentar m_a para aprender as correções no mesmo modelo, adicionando features que distingam os casos, ou aceitar o custo de um modelo separado para A'. A segunda forma são os consumidores não declarados. Quando a predição de m_a fica amplamente acessível, em tempo de execução ou por arquivos e logs, outros sistemas podem passar a usá-la sem que isso seja registrado. No software clássico isso é chamado de dívida de visibilidade. Isso cria acoplamento forte e escondido, de modo que mudar m_a, mesmo para melhorar, tende a causar impactos não entendidos. Consumidores não declarados também podem gerar loops de feedback ocultos. Para evitá-los, o sistema precisa ser projetado com restrições de acesso ou SLAs rígidos, pois sob pressão de prazo engenheiros usam o sinal mais conveniente.

Dependências de dados custam mais que dependências de código

Dependências de código podem ser achadas por análise estática de compiladores e linkers, mas os autores afirmam que dependências de dados têm capacidade parecida de gerar dívida e são mais difíceis de detectar. Sem ferramentas equivalentes, é fácil construir longas cadeias de dependência de dados difíceis de desatar. Há dois tipos centrais. Dependências instáveis: consumir sinais produzidos por outros sistemas é conveniente, mas alguns sinais mudam de comportamento ao longo do tempo, seja porque vêm de outro modelo que se atualiza, seja porque a equipe dona do sinal é outra. Uma atualização silenciosa que corrige um sinal antes mal calibrado pode prejudicar o modelo consumidor, que se ajustou à calibração errada. A mitigação é criar uma cópia versionada do sinal, ao custo de defasagem e de manter várias versões. Dependências subutilizadas: sinais que dão pouco ganho incremental mas tornam o sistema vulnerável a mudanças. O texto lista como entram: features legadas, features em pacote (adicionadas todas juntas por pressão de prazo), epsilon-features (ganho minúsculo de acurácia com grande complexidade) e features correlacionadas, em que o modelo pode creditar a não causal e ficar frágil quando as correlações mudam. Elas podem ser detectadas por avaliações leave-one-feature-out regulares. Para o conjunto, os autores propõem análise estática de dependências de dados, como um sistema de gerenciamento de features com anotações e verificações automáticas.

Loops de feedback e anti-padrões de sistema

Sistemas de ML em produção costumam influenciar o próprio comportamento quando se atualizam, o que gera dívida de análise: é difícil prever o comportamento de um modelo antes do lançamento. Em loops diretos, o modelo influencia a seleção de seus dados de treino futuros. Bandits seriam a solução teórica, mas escalam mal para os espaços de ação de problemas reais; mitiga-se com alguma aleatorização ou isolando partes dos dados da influência do modelo. Os loops ocultos são mais difíceis: dois sistemas se influenciam indiretamente pelo mundo, como um que escolhe produtos e outro que escolhe avaliações exibidas na mesma página, em que mudar um altera os cliques no outro. Isso pode ocorrer até entre sistemas totalmente disjuntos, como modelos de previsão de bolsa de duas empresas de investimento. Em seguida, o artigo observa que só uma fração minúscula do código de muitos sistemas de ML faz aprendizado ou predição (a Figura 1 mostra o código de ML como uma pequena caixa cercada por infraestrutura vasta). Os anti-padrões incluem glue code (muito código para encaixar pacotes genéricos, num sistema maduro talvez no máximo 5% de ML e ao menos 95% de cola), pipeline jungles (selvas de scrapes, joins e amostragens na preparação de dados) e caminhos de código experimentais mortos, com o exemplo da Knight Capital, que perdeu 465 milhões de dólares em 45 minutos, aparentemente por comportamento inesperado de código experimental obsoleto. O texto também cita dívida de abstração e smells como tipos de dados simples demais, múltiplas linguagens e protótipos.

Configuração, mundo externo e como medir a dívida

Os autores apontam a configuração como outra fonte surpreendente de dívida. Em sistemas maduros e ativos, as linhas de configuração podem superar as de código, e cada linha pode conter erros. Eles dão exemplos como uma feature logada incorretamente em certo período ou código que muda conforme o formato de log. Os princípios de um bom sistema de configuração incluem: expressá-la como pequena mudança sobre a anterior, dificultar erros manuais, permitir ver a diferença entre dois modelos, afirmar e verificar fatos automaticamente, detectar configurações redundantes e submetê-la a revisão de código e repositório. Quanto ao mundo externo, ele raramente é estável. Limiares de decisão fixados manualmente podem se tornar inválidos quando o modelo se atualiza; uma alternativa é aprender limiares com dados de validação. Testes unitários e ponta a ponta não bastam, então são necessários monitoramento em tempo real e resposta automatizada. O artigo sugere monitorar o viés de predição (a distribuição dos rótulos previstos deveria se parecer com a dos observados), limites de ação e produtores a montante. Outras áreas incluem dívida de teste de dados, de reprodutibilidade, de gerenciamento de processos e cultural. Na conclusão, os autores dizem que a dívida não tem métrica estrita e que continuar rápido não prova boas práticas. Eles propõem perguntas, como a facilidade de testar uma nova abordagem em escala, o fecho transitivo das dependências de dados e se melhorar um modelo degrada outros, e defendem uma cultura que premie remover features e reduzir complexidade.

Veja as ideias em ação

Pratique na escola de System Design os desenhos que usam este paper.