Machine Learning e o modelo de queijo suíço: falhas ativas e condições latentes

TL;DR: Problemas sempre vão existir. Uma postura reflexiva, sistemática, e com um plano de ação sempre foi e sempre será o caminho para resolução destes mesmos problemas.

Eu estava escrevendo um post sobre a importância dos Post Mortems em machine learning e vi que esta parte em específico estava ficando maior do que o ponto principal do outro post. Dessa forma eu resolvi quebrar esse post em um assunto específico com um pouco mais de foco e detalhes.

Aplicações de Machine Learning (ML) e Inteligência Artificial (IA) estão avançando em domínios cada vez mais críticos como medicina, aviação, setor bancário, investimentos entre outros. 

Estas aplicações estão diariamente tomando decisões de forma automatizada e em alta escala; não somente moldando a forma na qual indústrias estão operando, mas também como pessoas estão interagindo com plataformas que utilizam estas tecnologias.

Dito isso, é de fundamental importância que a cultura de engenharia em ML/AI incorpore e adapte cada vez mais conceitos como confiabilidade e robustez que são óbvios em outros campos da engenharia.

E um dos caminhos para essa adaptação é o entendimento de aspectos causais que possam elevar o risco de indisponibilidade destes sistemas.

Antes de prosseguir no texto eu recomendo a leitura do post Accountability, Core Machine Learning e Machine Learning Operations que fala um pouco de aplicações de ML em produção e da importância da engenharia na construção desses sistemas complexos.

A ideia aqui é falar sobre falhas ativas e condições latentes utilizando de forma simples o Modelo de Queijo Suíço. O objetivo é mostrar como estes dois fatores estão ligados na cadeia de eventos de indisponibilidades e/ou catastróficos em sistemas de ML.

Mas antes disso vamos entender um pouco do porque o entendimento das falhas pode ser um caminho alternativo para a melhoria da confiabilidade, e também sobre os “cases de sucesso” que vemos todos os dias na internet.

Viés de sobrevivência e aprendizado pela falha

Hoje na internet há uma miríade de informações sobre praticamente qualquer área técnica. Com todo o hype em ML e com a sua crescente adoção, estas informações materializam-se na forma de tutoriais, blog posts, fóruns de discussão, MOOCs, Twitter, entre outras fontes.

Porém, um leitor mais atento pode notar um determinado padrão em parte dessas histórias: Na maioria das vezes são cases de algo que (a) deu extremamente certo, (b) ou que gerou receita para a empresa, (c) ou como a solução o salvou X% em termos de eficiência, e/ou (d) como a nova solução de tecnologia foi uma das maiores maravilhas técnicas que já foram construídas.

Isso rende claps no Medium, posts no Hacker News, artigos em grandes portais de tecnologia, technical blog posts que que viram referências técnicas, papers e mais papers no Arxiv, palestras em conferências, etc.

Logo de antemão eu quero adiantar que eu sou um grande entusiasta da ideia de que “pessoas inteligentes aprendem com os próprios erros, e pessoas sábias aprendem com os erros dos outros”. Estes recursos, especialmente os technical blog posts e as conferências, reúnem um nível altíssimo de informações extremamente valiosas de pessoas que estão nas trincheiras técnicas.

Este bazar de ideias é extremamente saudável para a comunidade como um todo. Além do mais, este bazar está enterrando o antigo modelo de gatekeeping em que algumas consultorias de conferências surfaram por anos às custas de desinformação fazendo inúmeras empresas desperdiçarem rios de dinheiro. Ademais, este bazar de ideias está ajudando a acabar com o nefasto culto à personalidades de tecnologia em que qualquer pessoa pode ter uma voz.

Contudo, o que muitos destes posts, conference talks, papers e demais artigos não citam geralmente, são as coisas que dão/deram muito erradas durante o desenvolvimento dessas soluções; e isso essencialmente é um problema dado que estamos apenas vendo o resultado final e não o como esse resultado foi gerado e os falhas/erros cometidos no caminho.

Realizando um simples exercício de reflexão, é até compreensível que pouquíssimas pessoas socializem os erros cometidos e lições aprendidas; dado que nos dias de hoje, especialmente com as mídias sociais, a mensagem fica muito mais amplificada e distorcida.

Admitir erros não é algo fácil. Dependendo do grau de maturidade psicológica da pessoa que errou, junto com o erro pode vir uma montanha de sentimentos como constrangimento, inadequação, raiva, vergonha, negação etc. Isso pode levar a problemas de ordem psicológica em que um profissional de saúde mental tenha que acompanhar a pessoa que cometeu o erro. 

Do ponto de vista das empresas, a imagem que pode ficar em relação à relações públicas, é de desorganização corporativa, times de engenharia ruins, líderes técnicos que não sabem o que estão fazendo, etc. Isso pode afetar, por exemplo, ações de recrutamento.

Devido a estes pontos acima, isso implica que (1) talvez grande parte destes problemas podem estar acontecendo neste exato momento e estão sendo simplesmente suprimidos e (2) talvez exista um grande viés de sobrevivência nestes posts/talks/papers.

Não existe nada de errado com a forma na qual as empresas colocam os seus relatos, entretanto, um pouco de ceticismo e pragmatismo sempre é bom; pois, para cada caso de sucesso, sempre existirá uma infinidade de times que falharam miseramente, empresas que quebraram, pessoas que foram demitidas, etc.

Mas afinal, o que isso tudo tem a ver com a falhas que acontecem e porque entender os seus fatores contribuintes?

A resposta é: Porque primeiramente o seu time/solução tem que ser capaz de sobreviver à situações catastróficas para que o caso de sucesso exista. E ter a sobrevivência como aspecto motivador para aumentar a confiabilidade de times/sistemas, torna o entendimento dos erros em uma forma atrativa de aprendizado.

E quando existem cenários pequenas violações, supressão de erros, ausência de procedimentos, imperícia, imprudência ou negligência, as coisas dão espetacularmente muito errado, como nos exemplos abaixo:

Claro que nestas linhas mal escritas não haverá um ode à catástrofe ou disaster porn.

Porém, eu quero colocar um outro ponto de vista no sentido de que sempre existe uma lição a ser aprendida diante do que dá errado, e que empresas/times que mantém uma atitude introspectiva em relação aos problemas que acontecem ou analisam os fatores que possam a vir contribuir para um incidente, reforçam não somente uma cultura saudável de aprendizado como promovem uma cultura de engenharia mais orientada para aspectos de confiabilidade.

Partindo para o ponto prático, eu vou comentar um pouco sobre uma ferramenta (modelo mental) de gerenciamento de riscos que é o Modelo do Queijo Suíço que auxilia no entendimento de fatores causais que contribuem para a desastres em sistemas complexos.

O Modelo do Queijo Suíço

Se eu tivesse que dar um exemplo de indústria em que a confiabilidade pode ser considerada referência, com certeza seria a indústria da aviação [N2]. 

Em cada evento catastrófico que ocorre, há uma investigação minuciosa para entender o que aconteceu, e posteriormente endereçar os fatores contribuintes e fatores determinantes para um novo evento catastrófico nunca mais venha a acontecer.

Dessa forma, a aviação garante que aplicando o que foi aprendido devido ao evento catastrófico, todo o sistema fica mais confiável. Não é por acaso que mesmo com o aumento no número de voos (39 milhões de voos no último ano, 2019) o número de fatalidades vem caindo a cada ano que passa.

Uma das ferramentas mais utilizadas em investigação de acidentes aéreos para análise de riscos e aspectos causais é o Modelo de Queijo Suíço

Este modelo foi criado por James Reason através do artigo “The contribution of latent human failures to the breakdown of complex systems” em que houve a construção do seu framework (mas sem referência direta do termo). Entretanto, somente no paper “Human error: models and management o modelo aparece de forma mais direta.

A justificativa do modelo por parte do autor, é feita considerando um cenário de um sistema complexo e dinâmico da seguinte forma:

Defesas, barreiras e salvaguardas ocupam uma posição-chave na abordagem do sistema. Os sistemas de alta tecnologia têm muitas camadas defensivas: algumas são projetadas (alarmes, barreiras físicas, desligamentos automáticos etc.), outras contam com pessoas (cirurgiões, anestesistas, pilotos, operadores de salas de controle, etc.) e outras dependem de procedimentos e controles administrativos. Sua função é proteger possíveis vítimas e ativos contra riscos locais. Muitas das vezes essas camadas fazem isso de maneira muito eficaz, mas sempre há fraquezas.

Em um mundo ideal, cada camada defensiva estaria intacta. Na realidade, porém, são mais como fatias de queijo suíço, com muitos buracos - embora, diferentemente do queijo, esses buracos estejam continuamente abrindo, fechando e mudando de local. A presença de orifícios em qualquer “fatia” normalmente não causa um resultado ruim. Geralmente, isso pode acontecer apenas quando os orifícios em várias camadas se alinham momentaneamente para permitir uma trajetória de oportunidade de acidente - trazendo riscos para o contato prejudicial com as vítimas.

Human error: models and management

Uma forma de visualização deste alinhamento pode ser vista no gráfico abaixo:

Fonte: Understanding models of error and how they apply in clinical practice

Ou seja, neste caso cada fatia do queijo suíço seria uma linha de defesa com camadas projetadas (ex: monitoramento, alarmes, travas de push de código em produção, etc.) e/ou as camadas procedurais que envolvem pessoas (ex: aspectos culturais, treinamento e qualificação de commiters no repositório, mecanismos de rollback, testes unitários e de integração, etc.).

Ainda dentro do que o autor colocou, cada furo em alguma das fatias do queijo acontecem por dois fatores: falhas ativas e condições latentes, em que:

  • Condições latentes são como uma espécie de situações intrinsecamente residentes dentro do sistema; que são consequências de decisões de design, engenharia, de quem escreveu as normas ou procedimentos e até mesmo dos níveis hierárquicos mais altos de uma organização. Essas condições latentes podem levar a dois tipos de efeitos adversos que são situações que provocam ao erro e a criação de vulnerabilidades. Isto é, a solução possui um design que eleva a probabilidade de eventos de alto impacto negativo que pode ser equivalente a um fator causal ou fator contribuinte.  

  • Falhas Ativas são atos inseguros ou pequenas transgressões cometidos pelas pessoas que estão em contato direto com o sistema; atos estes que podem ser deslizes, lapsos, distorções, omissões, erros e violações processuais.

Se as condições latentes estão ligadas à aspectos ligados a engenharia e produto; as falhas ativas estão muito mais relacionadas com fatores humanos. Um ótimo framework para análise de fatores humanos é o Human Factors Analysis and Classification System (HFACS).

O HFACS coloca que as falhas humanas em sistema tecnológico-sociais complexos acontecem em quatro diferentes níveis como pode ser visto na imagem abaixo:

Source: Human Factors Analysis and Classification System (HFACS)

A ideia aqui no post não é discutir esses conceitos, e sim realizar um paralelo com machine learning em que alguns destes aspectos serão tratados. Para quem quiser saber mais eu recomendo a leitura do HFACS para uma leitura aprofundada do framework.

Já que temos alguns dos conceitos bem claros do que são as falhas ativas e condições latentes, vamos realizar um exercício de reflexão usando alguns exemplos com ML.

Gerenciamento de falhas ativas e condições latentes em Machine Learning

Para fazer a transposição destes fatores para a arena de ML de uma forma mais concreta, eu vou usar alguns exemplos do que eu já vi acontecer, do que já aconteceu comigo, e mais alguns dos pontos do excelente artigo de Sculley, David, et al. chamado “Hidden technical debt in machine learning systems.” apenas para efeitos didáticos. 

De maneira geral esses conjuntos de fatores (não-exaustivos) estariam representados da seguinte maneira:

Condições Latentes

  • Ausência de cultura de Code Review em que código de análises/relatórios, treinamento de modelos e/ou APIs vão para produção sem ao menos um segundo olhar crítico para avaliar e aprovar o que está sendo colocado em produção (e.g. código que treinamento de modelo que envolve variáveis como sexo e raça, código inteligível ou sem linter, código de treinamento de modelo difícil de ser manutenível, ou a não detecção de problemas lógicos graves como nos episódios do London Whale e da Knight Capital).

  • Cultura de arranjos técnicos improvisados (workarounds): O uso de arranjos técnicos improvisados gambiarra em algumas situações é extremamente necessário. Contudo, uma cultura de voltada a workarounds [N3] em um campo que tem complexidades intrínsecas como ML tende a incluir potenciais fragilidades em sistemas de ML e tornar o processo de identificação e correção de erros muito mais lento.

  • Ausência de monitoramento e alarmística: Em plataformas de ML alguns fatores que precisam de monitoramento específico como data drift (i.e. mudança na distribuição dos dados que servem de input para o treinamento) model drift (i.e. degradação do modelo em relação aos dados que são previstos) e adversarial monitoring que é o monitoramento para assegurar que o modelo está sendo testado para coleta de informações ou ataques adversariais.

  • Resumé-Driven Development ou RDD, é quando engenheiros ou times implementam uma ferramenta em produção apenas para ter no CV que trabalharam com a mesma, potencialmente prospectando um futuro empregador. O RDD tem como principal característica de criar uma dificuldade desnecessária para vender uma facilidade inexistente se a coisa certa tivesse sido feita. 

  • Decisões de tipo democracia com pessoas menos informadas ao invés do consenso entre especialistas e tomadores de risco: O ponto aqui é simples: Decisões chave só podem ser tomadas por (a) quem estiver envolvido diretamente na construção e na operacionalização dos sistemas, (b) quem estiver financiando e/ou tomando o risco, e (c) quem tem o nível de habilidades técnicas para saber os prós e contras de cada aspecto da decisão. A razão é que essas pessoas têm ao menos a própria pele em jogo ou sabem os pontos fracos e fortes do que está sendo tratado. O Fabio Akita já fez um argumento bem interessante nesta linha que mostra o quão ruim pode ser quando pessoas sem a pele em jogo e mal informadas estão tomando decisões. Democracia em profissões de prática não existe. Essa neo-democracia corporativa coletivista não tem rosto, e logo não tem accountability caso algo dê errado. Democracia em aspectos técnicos nos termos colocados acima é uma condição latente. Algo errado nunca será correto apenas porque uma maioria decidiu.

Falhas Ativas

  • Código não revisado indo para produção: Diferentemente da boa engenharia de software tradicional em que existe um camada de revisão de código para assegurar se tudo está dentro dos padrões de qualidade, em ML isso é um tema que ainda tem muito a amadurecer, dado que grande parte dos Data Scientists não têm um background programação e versionamento de código fonte. Outro ponto que dificulta bastante é que no fluxo de trabalho de cientistas de dados muitas das ferramentas usadas, tornam a revisão de código que impossível (e.g. Knit para o R) e Jupyter Notebook para Python.

  • Data Leakage no treinamento de modelos: Mistura entre amostras de teste/validação em treinamento são uma das falhas ativas mais comuns; em especial em situações de uso de Cross Validation. Neste post do Devin Soni ele explica outros aspectos que podem causar leakage como dados duplicados, leakage implícito com dados temporais. E um exemplo prático de como combater data leakage pode ser visto aqui no Kaggle em um excelente tutorial

  • Falta de reprodutibilidade/replicabilidade: Aqui pode ser desde a não atribuição da semente randômica antes da separação dos conjuntos de treino/teste e do treinamento do modelo, ou até soluções que, por design, não permitem um determinado grau de reprodutibilidade.

  • Glue code: Nesta categoria eu coloco os códigos que fazemos no momento da prototipação e do MVP que vai para produção da mesma forma que foram criados. Uma coisa que já vi acontecer bastante neste sentido foi ter aplicações com dependências de inúmeros pacotes e que para ter uma “integração” mínima necessitavam de muito glue code. O código ficava tão frágil que uma mudança na dependência (ex: uma simples atualização do código fonte) quebrava praticamente toda a API em produção.

Um cenário de indisponibilidade em um sistema de ML

Vamos imaginar que a uma empresa financeira fictícia chamada “Leyman Brothers” teve uma indisponibilidade na qual a sua plataforma de trading de ações ficou indisponível por 6 horas causando perdas massivas em alguns investidores.

Após a construção de um devido Post-Mortem o time chegou à seguinte narrativa em relação aos fatores determinantes e contribuintes na indisponibilidade:

O motivo da indisponibilidade foi devido a um erro do tipo falta de memória devido a um bug na biblioteca de ML.

Este erro é conhecido pelos desenvolvedores da biblioteca e existe um ticket aberto sobre o problema desde 2017, mas que até o presente momento não teve solução (Condição Latente).

Outro aspecto verificado foi que o tempo de resposta e solução foi demasiadamente longo devido ao fato de que não haviam mecanismos de alarmística, heartbeating ou monitoramento na plataforma de ML. Dessa forma, sem as informações de diagnóstico, o problema levou mais tempo do que o necessário para ser corrigido (Condição Latente).

No momento do debugging foi verificado que o desenvolvedor responsável pela implementação do trecho de código em que aconteceu a origem do erro, tinha conhecimento das alternativas de correção, mas não o fez devido ao fato de que a correção levaria a implementação de outra biblioteca em uma linguagem de programação a qual ele não têm domínio; mesmo com esta linguagem já sendo utilizada em outras partes do stack de tecnologia (Falha Ativa).

Por fim, foi visto também que o código entrou diretamente em produção sem nenhum tipo revisão. O projeto no Github não possui nenhuma “trava” para impedir que códigos não revisados entrem em produção. (Falha Ativa devido à Condição Latente).

Transpondo o evento da narrativa para o modelo de Queijo Suíço, visualmente teríamos a seguinte imagem:

Source: Adapted from Understanding models of error and how they apply in clinical practice

No nosso Queijo Suíço cada uma das fatias seriam camadas ou linhas de defesa em que temos aspectos como a arquitetura e engenharia dos sistemas, o stack de tecnologia, os procedimentos específicos de desenvolvimento, a cultura de engenharia da empresa e por fim as pessoas como última salvaguarda.

Os furos por sua vez seriam os elementos falhos em cada uma destas camadas de defesa que podem ser falhas ativas (ex: dar commit direto na master pelo fato de hão haver Code Review) ou condições latentes (e.g. biblioteca de ML, falta de monitoramento e alarmística).

Em uma situação ideal, após um evento de indisponibilidade, todas as condições latentes e as falhas ativas seriam endereçadas e haveria um plano de ação para a solução dos problemas para que o mesmo evento nunca mais acontecesse no futuro

Apesar da narrativa de alto nível, o ponto principal é que indisponibilidades em sistemas complexos e dinâmicos nunca acontecem devido a um fator isolado, mas sim devido à conjunção e sincronização de condições latentes e falhas ativas.

CONSIDERAÇÕES FINAIS

Claro que não existe panaceia em relação ao que pode ser feito em termos de gestão de riscos: alguns riscos e problemas podem ser tolerados e muitas das vezes não existe o tempo e os recursos necessários para aplicação dos devidos ajustes.

Entretanto, quando falamos de sistemas de missão crítica que usam ML fica claro que existem uma miríade de problemas específicos que podem acontecer além dos naturais problemas de engenharia.

O modelo do Queijo Suíço é um modelo de gerenciamento de riscos que é muito utilizado na aviação e oferece uma maneira simples de elencar condições latentes e falhas ativas em eventos que possam levar a falhas catastróficas.

O entendimento dos fatores contribuintes e determinantes em eventos de falha, pode ajudar a eliminar ou minimizar potenciais riscos e consequentemente reduzir o impacto na cadeia de consequências estes eventos.

NOTAS

[N1] - O objetivo deste post é única e exclusivamente comunicar com times de Machine Learning Engineering, Data Science, Data Product Management e demais áreas que tenham realmente a cultura de melhoria e feedback contínuo. Se você e/ou a sua empresa entende que conceitos de qualidade, robustez, confiabilidade e aprendizado são importantes, este post é dedicado especialmente a vocês.

[N2] No momento em que esse artigo estava sendo revisado apareceu essa matéria do novo avião Boeing 787 que devido ao fato de que o sistema core não consegue eliminar dados obsoletos (flush de dados) de algumas informações de sistemas críticos do avião que afetam a aeronavegabilidade, e que por isso a cada 51 dias todos os aviões deste modelo devem ser desligados. Isso mesmo, um avião Boeing precisa do mesmo tipo de reboot ao estilo “já tentou desligar a sua máquina e religar novamente?” para que um evento catastrófico não ocorra. Mas isto mostra que mesmo com uma condição latente é possível operar um sistema complexo de maneira segura.

[N3] Cultura de Gambiarras + eXtreme Go Horse (XGH) + Jenga-Oriented Architecture = Usina de indisponibilidades

[N4] - Agradecimentos especiais ao Comandante Ronald Van Der Put do canal Teaching for Free pela gentileza em me ceder alguns materiais relacionados à segurança e prevenção de acidentes.

REFERÊNCIAS

Reason, James. “The contribution of latent human failures to the breakdown of complex systems.” Philosophical Transactions of the Royal Society of London. B, Biological Sciences 327.1241 (1990): 475-484.

Reason, J. “Human error: models and management.” BMJ (Clinical research ed.) vol. 320,7237 (2000): 768-70. doi:10.1136/bmj.320.7237.768

Morgenthaler, J. David, et al. “Searching for build debt: Experiences managing technical debt at Google.” 2012 Third International Workshop on Managing Technical Debt (MTD). IEEE, 2012.

Alahdab, Mohannad, and Gül Çalıklı. “Empirical Analysis of Hidden Technical Debt Patterns in Machine Learning Software.” International Conference on Product-Focused Software Process Improvement. Springer, Cham, 2019.

Perneger, Thomas V. “The Swiss cheese model of safety incidents: are there holes in the metaphor?.” BMC health services research vol. 5 71. 9 Nov. 2005, doi:10.1186/1472-6963-5-71

“Hot cheese: a processed Swiss cheese model.” JR Coll Physicians Edinb 44 (2014): 116-21.

Breck, Eric, et al. “What’s your ML Test Score? A rubric for ML production systems.” (2016).

SEC Charges Knight Capital With Violations of Market Access Rule

Blog da Qualidade - Modelo Queijo Suíço para analisar riscos e falhas.

Machine Learning Goes Production! Engineering, Maintenance Cost, Technical Debt_,_ Applied Data Analysis Lab Seminar

Nassim Taleb - Lectures on Fat Tails, (Anti)Fragility, Precaution, and Asymmetric Exposures

Skybrary - Human Factors Analysis and Classification System (HFACS)

CEFA Aviation – Swiss Cheese Model

A List of Post-mortems

Richard Cook – How Complex Systems Fail

Airbus - Hull Losses

Number of flights performed by the global airline industry from 2004 to 2020