Em tarefas de Machine Learning usando texto, não se esqueça do CountVectorizer

Muitas das vezes quando vamos usar dados textuais para treinamento de modelos de machine learning usando o Scikit-Learn, algumas das vezes ficamos emperrados em problemas complexos com soluções simples.

TL;DR: Sempre que forem usar treinamento de modelos no Scikit, não esqueça do CountVectorizer()(ou da implementação que vai gerar a matrix de frequência).

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
# Carga do conjunto de dados
df = \
pd.read_csv("https://raw.githubusercontent.com/fclesio/metalbr/master/rebirth-remains.csv",
index_col=False)
df.columns = ['index','artist','album','lyric']
# Limpa os NaN para nao quebrar o vectorizer
df = df.dropna()
# Treino e teste basicos
X_train, X_test, y_train, y_test = \
train_test_split(df['lyric'], df['artist'], random_state=42)
# CountVectorizer para converter o texto em uma matriz de tokens
cv = CountVectorizer(strip_accents='ascii',
lowercase=True,
stop_words='english')
# Gera o dicionario, aprende com o mesmo e retorna
# uma matriz de frequencia (term-document matrix)
X_train_cv = cv.fit_transform(X_train)
# Transforma a base de teste em
X_test_cv = cv.transform(X_test)
# Treina o seu modelo e retorna as predições
naive_bayes = MultinomialNB()
naive_bayes.fit(X_train_cv, y_train)
predictions = naive_bayes.predict(X_test_cv)
# Checagem dos resultados
print('Predicoes do modelo:')
print(predictions)
print('\n')
# Novos dados
X_new_data = \
['the queen of the day the master of good pretending dreams with the grace of rain']
#########
## ERRO
#########
# Se passar os dados diretos sem usar o CountVectorizer da erro,
# pois voce vai estar passando string ou lista quando a classe
# esta esperando uma matriz de frequencia
banda = naive_bayes.predict(X_new_data)
print(f'A banda e: {banda}')
##########
## SOLUCAO
##########
# Se voce passar no CountVectorizer ele vai pegar as palavras
# verificar os termos que estao no dicionario, e gerar a matrix
# de frequencia com as palavras correspondentes
# Transforma o novo documento em uma matriz de frequencia (term-document matrix)
X_new_data_cv = cv.transform(X_new_data)
# E agora o predict consegue aceitar o documento
banda = naive_bayes.predict(X_new_data_cv)
print(f'A banda e: {banda}')
# Referencias
# [1] – https://towardsdatascience.com/brazilian-heavy-metal-an-exploratory-data-analysis-using-nlp-and-lda-f92324d0381e
# [2] – https://towardsdatascience.com/naive-bayes-document-classification-in-python-e33ff50f937e
Em tarefas de Machine Learning usando texto, não se esqueça do CountVectorizer

O modelo de #SquadGoals do Spotify falhou

Artigo de Jeremiah Lee com a tradução livre e adaptação de Flávio Clésio.

Fonte original.

Se o Spotify não usa “o modelo Spotify” você também não deveria usar.

De todos os fascínios da cultura de startup, poucos são mais desejados do que a velocidade e agilidade de uma equipe pequena. Manter esse sentimento enquanto a empresa cresce é um desafio. Em 2012, o Spotify compartilhou a sua maneira de trabalhar e sugeriu que tinha descoberto uma forma de manter essa velocidade e agilidade. 1

Fiquei empolgado em ver o modelo do Spotify em ação quando fui entrevistado para uma vaga de Product Manager [NT1], em 2017 em sua sede em Estocolmo. No entanto, a recrutadora me surpreendeu antes da primeira entrevista. Ela me avisou para não manter a expectativa de que o Spotify fosse uma utopia ágil.  

Entrei para a empresa pouco depois que ela triplicou de tamanho; indo para 3.000 pessoas em 18 meses. Aprendi que o famoso modelo de squads era apenas aspiracional e nunca foi totalmente implementado. Testemunhei o caos organizacional à medida que os líderes da empresa gradualmente transitavam para estruturas de gestão mais tradicionais.

Quando perguntei aos meus colegas por que o conteúdo não foi removido ou atualizado para refletir a realidade, nunca tive uma boa resposta. Muitas pessoas ironicamente achavam que os posts eram ótimos para recrutamento. 

Eu não trabalho mais no Spotify, então estou compartilhando minha experiência para esclarecer as coisas. O modelo de squads do Spotify falhou no próprio Spotify, e vai falhar com a sua empresa também.

Você não precisa acreditar em mim…

O co-autor do Modelo2 do Spotify e diversos agile coaches que trabalharam no Spotify têm dito a anos para que as pessoas não copiem este modelo. Infelizmente, a verdade não se espalha tão rápido ou tão amplamente quanto uma idéia em que as pessoas querem acreditar.

“Mesmo na época em que escrevemos, não estávamos fazendo isso [usando o modelo de squads]. Era parte ambição, parte aproximação. As pessoas realmente têm lutado para copiar algo que realmente não existia.”

— Joakim Sundén, Agile Coach no Spotify 2011-20174

“Me preocupa quando as pessoas olham para o que fazemos e acham que é uma estrutura que podem apenas copiar e implementar. … Estamos realmente nos esforçando para enfatizar que também temos problemas. Não é como se tudo fosse ‘brilhante, que tudo funciona bem e que todos os nossos squads são super incríveis’.”

— Anders Ivarsson, co-autor do whitepaper do Spotify3

Recaputulando sobre o que é o modelo de squads

Você pode ler e assistir o conteúdo original em menos de 30 minutos, ou pular para a próxima seção se você já estiver familiarizado com este modelo de squads. Aqui coloco um breve resumo.

O Spotify tinha equipes chamadas squads porque soava mais legal (sem brincadeira). 

Um grupo de equipes foram organizadas em um departamento chamado tribe (tribo).

Cada equipe destinava-se a ser uma mini-startup autônoma, com um Product Manager atuando como mini-CEO por cada uma das features (NT: essas features dentro do Spotify são, por exemplo, Descobertas da Semana, Alta Rotação, listas criadas algoritmicamente, etc.).

As equipes tinham designers e engenheiros de software com uma ampla gama de especializações. A intenção era que um time tivesse todas as habilidades necessárias para o desenvolvimento das features sem precisar contar com outra equipe para o sucesso desta implementação.

Os Product Managers tinham uma estrutura de gestão tradicional. Um product manager de um time reportava-se ao diretor de produto de seu departamento (o “líder da tribo”). O mesmo para designers. Os engenheiros de software, no entanto, foram gerenciados fora da estrutura da equipe.

Os “Chapter Leads” gerenciavam os engenheiros de software especializados em um tipo específico de desenvolvimento de software dentro do departamento. 

Por exemplo, todos os engenheiros de software que trabalham com APIs de backend em todos os times teriam um gerente, e todos os engenheiros trabalhassem com mobile Android dentro do mesmo departamento teriam um gerente diferente. 

A intenção era permitir que os engenheiros fossem deslocados entre times dentro de um departamento para melhor atender às necessidades do negócio sem que eles tivessem que mudar de gerente.

Por que não funcionou?

  1. A organização matricial resolveu o problema errado
  2. O Spotify fixou-se na autonomia da equipe
  3. Colaboração era uma competência assumida
  4. Ficou difícil de mudar a mitologia do modelo de squads

A organização matricial resolveu o problema errado

  • A equipe ágil “full-stack” funcionou bem, mas a organização matricial dos engenheiros de software introduziu mais problemas do que resolveu.
  • As equipes do Spotify foram bastante longevas. O benefício de não ter que mudar de gerente ao mudar para outra equipe foi limitado.
  • Os gerentes de engenharia nesse modelo tinham pouca responsabilidade além do desenvolvimento de carreira das pessoas que gerenciavam. Mesmo assim, a sua capacidade de ajudar no desenvolvimento de habilidades interpessoais de pessoas era limitada. Um gerente de engenharia não gerenciaria o suficiente as outras pessoas da equipe ou estaria envolvido o suficiente no contexto diário para avaliar e gerenciar conflitos dentro da equipe.
  • Sem um único gerente de engenharia responsável pelos engenheiros de uma equipe, o product manager não tinha um par equivalente — o mini-CTO para sua função de mini-CEO. Não havia uma única pessoa responsável pela entrega da equipe de engenharia ou que pudesse negociar a priorização do trabalho em um nível equivalente de responsabilidade.
  • Quando surgiam desentendimentos dentro da equipe de engenharia, o product manager precisava negociar com todos os engenheiros de software da equipe. 

Se os desenvolvedores não conseguissem chegar a um consenso, o product manager precisava escalar para tantos gerentes de engenharia quanto fossem necessários dentro das especializações de engenharia dentro da equipe para a resolução do conflito.

Para uma equipe com engenheiros de backendweb app e mobileproduct manager teria que tentar resolver os desentendimentos com pelo menos 3 gerentes de engenharia. Se esses gerentes de engenharia não chegassem a um consenso, a questão da equipe em questão teria que ser escalada para o diretor de engenharia do departamento.

“Os Chapter Leads são servos-líderes que ajudam você a crescer como indivíduo. Eles não trabalham com nenhuma equipe. Eles têm relatórios diretos sobre todas as equipes. Eles não têm nenhuma responsabilidade pela entrega. Eles não estão assumindo essa responsabilidade. É fácil ver o Product Owner como o gerente da equipe.”

— Joakim Sundén, Agile Coach no Spotify4

Aprenda com os erros do Spotify:
  • Um time de “produto-design-engenharia” tipicamente tem mais engenheiros do que designers ou product managers. Ter um único gerente de engenharia para os engenheiros da equipe cria um caminho responsavel de escalonamento e resolução de conflitos dentro da equipe.
  • Os product managers devem ter um par equivalente em relação ao o time de engenharia. Os product managers devem ser responsáveis pela priorização do trabalho. Os gerentes de engenharia devem ser responsáveis pela execução dos engenheiros, o que inclui poder negociar tradeoffs entre velocidade e qualidade com o product manager.

O Spotify fixou-se na autonomia da equipe

Quando a empresa é pequena, as equipes têm que fazer uma ampla gama de trabalhos para entregar, e têm que mudar as iniciativas com frequência. À medida que uma empresa cresce de startup para scale-up, funções duplicadas entre equipes passam para novas equipes dedicadas de forma a aumentar a eficiência da organização, reduzindo a duplicação. 

Com mais equipes, a necessidade de uma equipe mudar de iniciativa diminui em frequência. Ambas as mudanças permitem que as equipes pensem mais profundamente e a longo prazo sobre os problemas que estão escopo para resolver. Porém, isto não é uma garantia de uma iteração mais rápida. Toda responsabilidade que uma equipe cede para aumentar seu foco se torna uma nova dependência para com outros times (cross-team dependency).

O Spotify não definiu um processo comum para a colaboração entre equipes. Permitir que cada time tivesse uma maneira única de trabalhar significava que cada equipe precisava de uma maneira única de engajamento no que diz respeito à colaboração. A produtividade geral da organização sofreu.

O modelo do Spotify foi documentado quando o Spotify era uma empresa muito menor. Era para ser uma série de várias partes, de acordo com Anders Ivarsson. A autonomia foi a primeira parte, mas as partes de alinhamento e accountability (responsabilização) nunca foram concluídas.

Aprenda com os erros do Spotify:
  • Autonomia requer alinhamento. As prioridades da empresa devem ser definidas pela liderança. Autonomia não significa que as equipes podem fazer o que quiserem.
  • Os processos de colaboração entre os times devem ser definidos. Autonomia não significa deixar os times se auto organizarem para todos os problemas.
  • Como o sucesso é medido deve ser definido pela liderança para que as pessoas possam efetivamente negociar a priorização de tarefas que tenham dependência entre múltiplas equipes.
  • Autonomia requer accountability (responsabilização). Product Management é responsável por entregar valor. O time é responsável por entregar incrementos ‘feitos’. Equipes maduras podem justificar sua independência dependendo da sua capacidade de articular valor de negócio, risco, aprendizado e que próximo movimento ideal o time deveria tomar. 6

“Se eu fosse fazer uma coisa diferente, eu diria que não deveríamos nos concentrar tanto na autonomia.”

“Toda vez que você tem uma nova equipe, eles têm que reinventar a roda em como eles devem estar trabalhando. Talvez, apenas talvez, devêssemos ter uma “agilidade mínima viável”. Você pode começar com isso. Você é livre para optar por sair, mas as pessoas não devem ter que optar para entrar o tempo todo.

“Em que ponto você começa a inserir esse processo? Provavelmente quando é tarde demais.

— Joakim Sundén, Agile Coach no Spotify4

“Henrik Kniberg falou sobre como não somos tão bons em grandes iniciativas e ainda não somos tão bons em grandes iniciativas.

“Se você tem formas inconsistentes de trabalhar, é mais difícil para as pessoas se moverem. Se é mais difícil para as pessoas se moverem, é mais provável que você tenha maneiras inconsistentes de trabalhar. Vai reforçar até que, de repente, você não esteja mais trabalhando para a mesma empresa. Você está trabalhando para esse tipo de subculturas estranhas.

— Jason Yip. Agile Coach no Spotify desde 2015 até apresente data5

Colaboração era uma competência assumida

Embora o Spotify tenha dado às equipes controle sobre sua maneira de trabalhar, muitas pessoas não tinham uma compreensão básica das práticas ágeis.

Isso resultou em times iterando através de ajustes de processo na esperança cega de encontrar uma combinação que os ajudaria a melhorar sua entrega. 

As pessoas não tinham uma linguagem comum para discutir efetivamente os problemas do processo, a educação para resolvê-los e a experiência de avaliar o desempenho. Não foi realmente ágil. Era apenas um não-cascata.

“Agile Coaches” eram consultores internos que o Spotify fornecia para ensinar práticas ágeis e sugerir melhorias nos processos. Embora bem-intencionados, não havia Agile Coaches para ajudar todas as equipes. O engajamento de um Agile Coach com um time era raramente o bastante para cobrir a conclusão de um projeto para ajudar uma equipe a avaliar o desempenho. E mais: eles não eram responsáveis por nada.

“Controle sem competência é caos.”

— L. David Marquet, Turn the Ship Around!

Aprenda com os erros do Spotify:
  • Colaboração é uma habilidade que requer conhecimento e prática. Os gestores não devem assumir que as pessoas têm uma compreensão existente das práticas ágeis.
  • Quando uma empresa se torna grande o suficiente, as equipes precisarão de suporte dedicado para orientar o planejamento dentro da equipe e a estrutura de colaboração entre as equipes. O time de program management pode ser responsável pelo processo de planejamento. Program Managers dedicados permitem equipes de forma semelhante à forma como product managers e gerentes de engenharia dedicados fazem com suas respectivas competências.

Ficou difícil de mudar a mitologia do modelo de squads

Quando o Agile Scrum introduziu novos significados a um monte de palavras como  burn-down  e  sprintele o fez porque introduziu conceitos novos que precisavam de nomes. 

O Spotify introduziu no vocabulário missões (missions),  tribes (tribos),  squads,  guildas, e os Chapter Leads para descrever sua maneira de trabalhar. 

Isso deu a ilusão de que o Spotify havia criado algo digno de precisar aprender através da escolha de palavras incomuns. No entanto, se removermos os sinônimos desnecessários das ideias, o modelo do Spotify se revela como uma coleção de equipes multifuncionais com muita autonomia e uma estrutura de gestão ruim. 

Não caia nessa.

Se o Spotify tivesse se referido a essas ideias usando os seus nomes originais, talvez pudesse ter avaliado a falha do seu modelo de forma mais justa, ao invés simplesmente ter que enfrentar a mudança de sua identidade cultural para encontrar processos internos que funcionassem bem.

Aprenda com os erros do Spotify:
  • A maioria das empresas só pode sustentar algumas áreas de inovação. Processos internos raramente são uma área primária de inovação que diferencia uma empresa no mercado. Estudar o passado permite que as empresas escolham melhores áreas para a inovação.
  • Otimize para entender. Cada coisa nova que alguém deve aprender para ser produtivo deve sofre uma avaliação sobre o seu valor.
  • Nomenclaturas como unidades de negóciosdepartamentos, times  e  gerentes  comunicam de forma mais eficaz as funções e responsabilidades dentro de uma estrutura organizacional do que os sinônimos do Spotify, sinônimos estes que falharam com seu criador.

Ao invés de seguir este modelo, faça isso

(Brincadeira. Não existem correções rápidas.)

Você pode ter descoberto o modelo do Spotify porque estava tentando descobrir como estruturar suas equipes. Não pare por aqui. Continue pesquisando. Líderes de empresas que resistiram a testes mais longos de tempo escreveram muito mais do que o Spotify blogou

Os humanos têm tentado descobrir como trabalhar juntos desde que existe a humanidade. A era industrial e a era da informação mudaram algumas das restrições para isto, mas acadêmicos que estudam teorias de organizações encontraram verdades atemporais sobre o que os humanos precisam para serem bem-sucedidos dentro de uma coletividade.

Acontece que o Spotify em 2012 não tinha descoberto como manter a velocidade e agilidade de uma pequena equipe em uma grande organização. A empresa evoluiu além de seu modelo homônimo e olhou para fora de si para encontrar melhores respostas. E você deveria fazer o mesmo.

Algumas das minhas recomendações relacionadas aos tópicos abordados pela maneira de trabalho do modelo do Spotify:

  • Mais de 200 pessoas em seu produto — engenharia — organização de design? O Scaled Agile Framework  funcionou bem para a Fitbit quando trabalhei lá.
Notas e Citações

1:  Scaling Agile @ Spotify  whitepaper, Spotify Engineering Culture

2: Anders Ivarsson e Henrik Kniberg foram os autores do  whitepaper Scaling Agile @ Spotify.  Henrik  esclareceu seu status de criador em 2015: “as pessoas às vezes parecem fazer a suposição de que eu inventei o modelo do Spotify. Bem, eu certamente não! Sou apenas o mensageiro. … O modelo do Spotify é o resultado de muitas pessoas colaborando e experimentando ao longo do tempo, e muitos aspectos do modelo foram inventados sem o meu envolvimento. Eu certamente não gostaria de levar o crédito das pessoas envolvidas”.

3:  Episódio 112: Dentro do Spotify com Anders Ivarsson, The Agile Revolution, 2016

4:  Você pode fazer melhor do que o modelo spotify por Joakim Sundén, vídeo 2017  video,  slides

5:  Como as coisas ainda não funcionam no Spotify e como estamos tentando resolvê-lo  por Jason Yip, vídeo de 2017  video,  slides

6:  Equilibrando autonomia com responsabilidade por Edwin Dando

Recursos adicionais

Se mais de 2.200 palavras relatando a minha experiência em primeira mão e as palavras de 4 funcionários do Spotify não foram suficientes, leia como o modelo do Spotify não funcionou para essas pessoas fora do Spotify.

Ilustração de capa inspirada em Bad Blood por Taylor Swift, que sabe algo sobre metas de squads,  mas não de direitos autorais. Se você esqueceu 2015,  aqui está um exame do termo metas do squad.

Obrigado a Roland Siebelink  e Jason Harmon por revisar os rascunhos deste artigo.

Notas de Tradução

[NT1] – Os termos em inglês de algumas posições foram mantidos intencionalmente dado que o texto é orientado para pessoas que conhecem essas profissões.

[NT2] – Devido às diferenças de linguagem e de estilo, eu adaptei algumas partes para termos mais comuns em português.

[NT3] – Por uma questão de estilo, alguns parágrafos foram “quebrados” para dar uma melhor experiência de leitura; em especial em dispositivos móveis

© 2020 Jeremiah Lee. Este trabalho é licenciado sob uma licença Creative Commons Atribuição-ShareAlike 4.0 International.

O modelo de #SquadGoals do Spotify falhou

Cosine Similarity Search for new documents using Scikit-Learn

Some time ago I was working in a project of similarity search (i.e. bring similar items based on text) using Scikit-Learn and one topic that it’s not covered in the documentation is: What if I got new data? How can I calculate the similarity of this new text?

Some lovely souls of Stack Overlow gave me the tip and I’m sharing it:

# Source: https://stackoverflow.com/questions/44862712/td-idf-find-cosine-similarity-between-new-document-and-dataset/44863365#44863365
import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
# Generate DF
df = \
pd.DataFrame({'jobId' : [1, 2, 3, 4, 5],
'serviceId' : [99, 88, 77, 66, 55],
'text' : ['Ich hätte gerne ein Bild an meiner Wand.',
'Ich will ein Bild auf meinem Auto.',
'Ich brauche ein Bild auf meinem Auto.',
'Ich brauche einen Rasenmäher für meinen Garten.',
'Ich brauche einen Maler, der mein Haus streicht.'
]})
# Show DF
df
# Vectorizer to convert a collection of raw documents to a matrix of TF-IDF features
vectorizer = TfidfVectorizer()
# Learn vocabulary and idf, return term-document matrix.
tfidf = vectorizer.fit_transform(df['text'].values.astype('U'))
# Array mapping from feature integer indices to feature name
words = vectorizer.get_feature_names()
# Compute cosine similarity between samples in X and Y.
similarity_matrix = cosine_similarity(tfidf, tfidf)
# Matrix product
similarity_matrix
# Instead of using fit_transform, you need to first fit
# the new document to the TFIDF matrix corpus like this:
queryTFIDF = TfidfVectorizer().fit(words)
# We can check that using a new document text
query = 'Mähen Sie das Gras in meinem Garten, pflanzen Sie Blumen in meinem Garten.'
# Now we can 'transform' this vector into that matrix shape by using the transform function:
queryTFIDF = queryTFIDF.transform([query])
# As we transformed our query in a tfidf object
# we can calculate the cosine similarity in comparison with
# our pevious corpora
cosine_similarities = cosine_similarity(queryTFIDF, tfidf).flatten()
# Get most similar jobs based on next text
related_product_indices = cosine_similarities.argsort()[:11:1]
related_product_indices
# array([3, 2, 1, 4, 0])
Cosine Similarity Search for new documents using Scikit-Learn

RESTful API para servicing de modelos de Machine Learning com R, H2O.ai e Plumber

Em algum momento todo cientista de dados ou engenheiro de machine learning já se deparou com enquetes e blog posts com a seguinte pergunta: “Para ambientes de produção, qual é melhor R ou Python?“.

Na maioria das vezes a linguagem Python sempre leva uma vantagem neste quesito; seja por conta da sua facilidade de aprendizado, ou (especulo eu) o fato que muitos usuários não entendem a diferença entre uma linguagem de uso geral e para uma linguagem de programação com objetivo de computação científica em script.

Existem inúmeros recursos que detonam o uso da linguagem R em produção por inúmeros fatores, alguns destes muito justos como:

  • Grande parte dos usuários não têm background em desenvolvimento de software;
  • Devido ao ponto anterior, não existe na comunidade uma cultura de práticas como gestão de dependências, testes, tratamento de erros e logging (mesmo com boas ferramentas para fazer tudo isso);
  • Argumentos ocultos na linguagem, como o inacreditável stringAsFactors = TRUE que só foi corrigido agora na versão 4.0.0 (i.e. não tem retrocompatibilidade!). Em outras palavras, um bug virou feature e um update de major version é necessário para corrigir um comportamento na linguagem por um erro de design da linguagem (uma boa explicação para isso está aqui neste post);
  • A falta de familiaridade dos usuários do R com pacotes/software que poderiam garantir uma maior robustez em termos de produtos de dados/inferência em produção como packrat para checkpointing e Docker para setup de front-end.

Contudo, em termos práticos nem sempre é possível que todos os cientistas de dados, analistas e demais usuários migrem para o Python devido à inúmeros motivos (e.g. custo de migração, custo de treino de pessoal, riscos de negócios para de retirar algo de produção, etc.).

Com isso, parte dos usuários em R acabam sem ter acesso a formas de colocar os seus modelos em produção e o mais importante: realizar o servicing desses modelos (i.e. receber requisições e dar respostas com as predições em uma plataforma que vai ser, a grosso modo, como uma espécie de serviço web em que a API vai servir para comunicar duas aplicações).

A ideia desse post é ajudar estas pessoas a terem o poder de subir uma RESTful APIs em produção para fazer o servicing desses modelos, e com uma ajuda de um time de infraestrutura esses códigos podem ser colocados em um servidor ou em uma imagem Docker, e assim estar disponível para outras aplicações.

Mas para dar um pouco mais de realismo no nosso exemplo, vamos usar um exemplo de um Banco chamado de Layman Brothers [N1] que tem como objetivo entregar um serviço de Machine Learning que informa se um cliente vai entrar em uma situação de atraso de pagamento ou não. E para isso vamos usar (apenas para fins de simplicidade) o AutoML para realizar o treinamento deste modelo.

AutoML (Automatic Machine Learning)

Para quem não sabe o conceito de AutoML (Automatic Machine Learning, ou treinamento automático de Machine Learning) é o processo de automação de todo o pipeline de treinamento de modelos de machine learning através do treinamento de inúmeros modelos dentro de um limite de tempo ou condição de parada (e.g. AUC, RMSE, Recall, Precision, etc).

Isto permite que mesmo pessoas não especialistas em Data Science e Machine Learning apenas passem os dados para o AutoML, e este realiza inúmeros treinamentos com varias combinações de algoritmos dentro de um determinado limite de tempo.

A ideia aqui é simplificar o processo de treinamento de ponta a ponta, fazendo o treino de inúmeros modelos simples ou a combinação de vários algoritmos (XGBoost, Deep Learning, GLM, GBM, etc.) com varias combinações de hiperparâmetros.

Em outras palavras, ao invés de haver um ser humano testando manualmente diversas combinações, o AutoML já faz tudo isso.

Em alguns casos, os modelos do AutoML chegam até mesmo bater cientistas de dados em leaderboards do Kaggle, como podemos ver no exemplo abaixo em que a Erin LeDell com apenas 100 minutos de treinamento no AutoML conseguiu ficar em 8º lugar em um Hackathon no Kaggle:

AutoML no R com H2O.ai

No treinamento do nosso modelo de Default Prediction (ou previsão de calotes para os mais simplistas) do Layman Brothers, nós vamos usar a linguagem R e a implementação do AutoML no H2O (eu já postei alguns tutoriais e considerações sobre essa ferramenta aqui no blog anteriormente, vale a pena conferir).

No nosso caso vamos usar o AutoML to H2O devido ao fato de que além da ferramenta usar os algoritmos mais comuns, a implementação do AutoML no H2O tem também a opção de Stacked Ensembles de todos os modelos previamente treinados, e de quebra nos dá o leaderboard dos melhores modelos.

Para o treinamento do nosso modelo vamos usar os dados do nosso Layman Brothers no AutoML.

A estrutura do projeto terá 5 pastas com nomes autoexplicativos: (1) api, (2) data, (3) logs, (4) models e (5) src (onde ficará o código fonte). O caminho pode deve ser alterado (recomendável) e como não estamos usando um conjunto de dados que está no projeto mas sim em um endereço do GitHub, a pasta data é dispensável.

Antes de mais nada, vamos carregar da biblioteca de logging e usar os caminhos padrão como constantes para armazenar os nossos objetos:

if (!require('logger')) install.packages('logger'); library('logger')
start_time_pipeline <- Sys.time()
log_debug('Training pipeline start time – {start_time_pipeline}')
# Local directories
ROOT_DIR <- getwd()
PROJECT_DIR <-
'r-api-data-hackers'
DATA_DIR <- 'data'
MODELS_DIR <- 'models'
API_DIR <- 'api'
LOGS_DIR <- 'logs'
get_artifact_path <- function(file_name,
artifact_dir,
root_dir=ROOT_DIR,
project_dir=PROJECT_DIR){
artifact_path <-
file.path(root_dir,
project_dir,
artifact_dir,
file_name)
return (artifact_path)
}
logging_file_path <-
get_artifact_path("training_pipeline_auto_ml.log", LOGS_DIR)
log_appender(appender_file(logging_file_path))
log_layout(layout_glue_colors)
log_threshold(DEBUG)
log_info('Start logging')

Com o log criado, vamos agora instalar o H2O direto do CRAN.

install_dependencies <- function(){
package_url_logger <- 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.6/logger_0.1.tgz'
package_url_h2o <- 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.6/h2o_3.30.0.1.tgz'
package_url_cluster <- 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.6/cluster_2.1.0.tgz'
package_url_dplyr <- 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.6/dplyr_0.8.5.tgz'
package_url_tidyverse <- 'https://cran.rstudio.com/bin/macosx/el-capitan/contrib/3.6/tidyverse_1.3.0.tgz'
log_debug('logger CRAN URL: {package_url_logger}')
log_debug('h2o CRAN URL: {package_url_h2o}')
log_debug('cluster CRAN URL: {package_url_cluster}')
log_debug('dplyr CRAN URL: {package_url_dplyr}')
log_debug('tidyverse CRAN URL: {package_url_tidyverse}')
packages_urls <- c(
package_url_logger,
package_url_dplyr,
package_url_cluster,
package_url_tidyverse,
package_url_h2o
)
for(url in packages_urls)
{for(package_url in url)
log_info('Installing {} package')
{install.packages(package_url, repos=NULL, type='source')}
log_info('Package {package_url} installation finished')
}
}
log_info('Start installing dependencies')
install_dependencies()
log_info('Dependencies installed')
packageVersion_logger <- packageVersion('logger')[1]
packageVersion_h2o <- packageVersion('h2o')[1]
packageVersion_cluster <- packageVersion('cluster')[1]
packageVersion_dplyr <- packageVersion('dplyr')[1]
packageVersion_tidyverse <- packageVersion('tidyverse')[1]
log_debug('logger Version: {packageVersion_logger}')
log_debug('h2o Version: {packageVersion_h2o}')
log_debug('cluster Version: {packageVersion_cluster}')
log_debug('dplyr Version: {packageVersion_dplyr}')
log_debug('tidyverse Version: {packageVersion_tidyverse}')
log_info('Loading packages')
packages <- c(
"logger",
"h2o",
"cluster",
"dplyr",
"tidyverse")
invisible(lapply(packages, library, character.only = TRUE))
log_info('Packages loaded')
session_info_base_packages <- sessionInfo()$basePkgs
log_info('Session Info Base Packages: {session_info_base_packages}')
session_info_loaded_packages <- sessionInfo()$loadedOnly
log_debug('Session Info Loaded Packages: {session_info_loaded_packages}')

Um ponto que tem que ser levado em consideração aqui, é que eu estou realizando a instalação dos pacotes direto do CRAN devido ao fato de que, ao menos pra mim, as ferramentas de gestão de dependências do R não tem uma usabilidade boa em comparação com o Homebrew, npm e até mesmo o pip.

Dependências instaladas, vamos iniciar o nosso H2O cluster:

log_info('Initializing H2O')
host = "localhost"
host_port = 54321
cpus = 1
memory_size = "7g"
log_debug('H2O Cluster host: {host}')
log_debug('H2O Cluster host port: {host_port}')
log_debug('H2O Cluster Number CPUs: {cpus}')
log_debug('H2O Cluster Memory Size allocated: {memory_size}')
h2o.init(
ip = host,
port = host_port,
nthreads = cpus,
max_mem_size = memory_size
)
cluster_status <- h2o.clusterStatus()
log_debug('H2O Cluster Status Info: {cluster_status}')

No nosso caso vamos usar todos os CPUs das máquinas que por ventura estiverem no cluster (cpus=-1). Como eu estou rodando em uma máquina apenas, eu vou limitar o tamanho da memória em 7Gb.

Cluster iniciado, vamos agora fazer a carga dos nossos dados no H2O, separar os datasets de treinamento e teste, e determinar as variáveis [N2] que vamos usar no treinamento dos modelos:

log_debug('Load data')
layman_brothers_url =
"https://raw.githubusercontent.com/fclesio/learning-space/master/Datasets/02%20-%20Classification/default_credit_card.csv"
layman_brothers.hex = h2o.importFile(path = layman_brothers_url,
destination_frame = "layman_brothers.hex")
log_debug('Data loaded')
log_debug('Transform default variable to factor')
layman_brothers.hex$DEFAULT = as.factor(layman_brothers.hex$DEFAULT)
log_debug('Construct test and train sets using sampling')
layman_brothers.split <- h2o.splitFrame(data = layman_brothers.hex,
ratios = 0.90, seed =42)
layman_brothers.train <- layman_brothers.split[[1]]
layman_brothers.test <- layman_brothers.split[[2]]
qty_samples_train <- nrow(layman_brothers.train)
qty_samples_test <- nrow(layman_brothers.test)
log_debug('Training set with {qty_samples_train} records')
log_debug('Test set with {qty_samples_test} records')
log_debug('Set predictor and response variables')
y = "DEFAULT"
x = c(
"LIMIT_BAL"
,"SEX"
,"EDUCATION"
,"MARRIAGE"
,"AGE"
,"PAY_0"
,"PAY_2"
,"PAY_3"
,"PAY_4"
,"PAY_5"
,"PAY_6"
,"BILL_AMT1"
,"BILL_AMT2"
,"BILL_AMT3"
,"BILL_AMT4"
,"BILL_AMT5"
,"BILL_AMT6"
,"PAY_AMT1"
,"PAY_AMT2"
,"PAY_AMT3"
,"PAY_AMT4"
,"PAY_AMT5"
,"PAY_AMT6")

Agora que os nossos dados estão carregados, vamos realizar o treinamento usando o AutoML:

log_debug('Run AutoML for model training')
start_time <- Sys.time()
aml <-
h2o.automl(x=x,
y=y,
training_frame = layman_brothers.train,
validation_frame = layman_brothers.test,
max_models = 20,
nfolds = 5,
stopping_metric = c("AUC"),
project_name = "data-hackers-auto-ml",
sort_metric = c("AUC"),
verbosity = "warn",
seed = 42
)
end_time <- Sys.time()
log_debug('AutoML training ended')
time_elapsed <- end_time start_time
log_debug('Time elapsed – {time_elapsed}')

No nosso caso, vamos usar no máximo 20 modelos (max_models = 20), com o AutoML fazendo o Cross Validation com 5 partições (nfolds = 5), travando semente randômica em 42 (seed = 42) e com o AUC como métrica que vai ser a referência no treinamento para determinar qual modelo é melhor (sort_metric = c("AUC")).

Existem inúmeras outras opções que podem ser configuradas, mas vamos usar estas para fins de simplicidade.

Após o treinamento, podemos armazenar as informações do leaderboard no log; ou verificar no console:

lb <- aml@leaderboard
for (model_auto_ml in 1:nrow(lb)){
auto_ml_model_id <-
as.list(lb$model_id)[model_auto_ml][1]
auto_ml_auc <-
as.list(lb$auc)[model_auto_ml][1]
auto_ml_logloss <-
as.list(lb$logloss)[model_auto_ml][1]
auto_ml_aucpr <-
as.list(lb$aucpr)[model_auto_ml][1]
auto_ml_mean_per_class_error <-
as.list(lb$mean_per_class_error)[model_auto_ml][1]
auto_ml_rmse <-
as.list(lb$rmse)[model_auto_ml][1]
auto_ml_mse <-
as.list(lb$mse)[model_auto_ml][1]
log_info("AutoML – model_id: {auto_ml_model_id} – auc: {auto_ml_auc} – logloss: {auto_ml_logloss} – aucpr: {auto_ml_aucpr} – mean_per_class_error: {auto_ml_mean_per_class_error} – rmse: {auto_ml_rmse} – mse: {auto_ml_mse}")
}
log_info("AutoML Winning Model – model_id: {aml@leader@model_id} – algorithm: {aml@leader@algorithm} – seed: {aml@leader@parameters$seed} – metalearner_nfolds: {aml@leader@parameters$metalearner_nfolds} – training_frame: {aml@leader@parameters$training_frame} – validation_frame: {aml@leader@parameters$validation_frame}")
model_file_path <-
get_artifact_path("", MODELS_DIR)
log_info("Model destination path: {model_file_path}")
model_path <- h2o.saveModel(object=aml@leader,
path=model_file_path,
force=TRUE)
log_info("Model artifact path: {model_path}")
end_time_pipeline <- Sys.time()
log_debug('Training pipeline end time – {end_time_pipeline}')
time_elapsed_pipeline <- end_time_pipeline start_time_pipeline
log_debug('Training pipeline time elapsed – {time_elapsed_pipeline[1]} mins')
log_debug('Training pipeline finished')

Se tudo ocorreu bem aqui, no final teremos o modelo vencedor serializado na pasta models pronto para ser usado pela nossa RESTful API [N4].

Para ler as informações do log durante o treinamento do modelo, basta apenas abrir o arquivo training_pipeline_auto_ml.log no sistema operacional, ou executar o comando $ tail -F training_pipeline_auto_ml.log durante a execução.

Isso pode ajudar, por exemplo, a ter o registro de quanto cada fase esta levando para acontecer. Caso a pessoa responsável pelo script queira, podem ser aplicados tratamentos de erros no código e posterior logging desses erros para facilitar a depuração de qualquer problema que venha acontecer.

Com o nosso modelo treinado e serializado, vamos agora subir o nosso endpoint [N3].

Configuração do endpoint da RESTful API no Plumber

Para o servicing dos nossos modelos, vamos usar o Plumber que é uma ferramenta que converte código em R em uma web API [N4]. No nosso caso, vamos usar o Plumber como ferramenta para subir a nossa API e fazer o servicing do modelo [N3].

Primeiramente vamos configurar o nosso endpoint. Resumidamente, um endpoint é um caminho de URL que vai comunicar-se com uma API [N4]. Este arquivo será chamado de endpoint.r.

Este endpoint vai ser responsável por fazer a ligação do nosso arquivo em que estará a nossa função de predição (falaremos sobre ele mais tarde) e as requisições HTTP que a nossa API vai receber.

Vamos colocar aqui também um arquivo de log, neste caso chamado de automl_predictions.log em que vamos registrar todas as chamadas neste endpoint.

library(plumber)
library('logger')
ROOT_DIR <- getwd()
PROJECT_DIR <-
'r-api-data-hackers'
API_DIR <- 'api'
LOGS_DIR <- 'logs'
api_path_object <-
file.path(ROOT_DIR,
PROJECT_DIR,
API_DIR,
"api.R")
logging_file_path <-
file.path(ROOT_DIR,
PROJECT_DIR,
LOGS_DIR,
"automl_predictions.log")
log_appender(appender_file(logging_file_path))
log_layout(layout_glue_colors)
log_threshold(DEBUG)
convert_empty <- function(string) {
if (string == "") {
""
} else {
string
}
}
r <- plumb(api_path_object)
r$registerHooks(
list(
preroute = function() {
# Start timer for log info
tictoc::tic()
},
postroute = function(req, res) {
end <- tictoc::toc(quiet = TRUE)
log_info('REMOTE_ADDR: {convert_empty(req$REMOTE_ADDR)}, HTTP_USER_AGENT: "{convert_empty(req$HTTP_USER_AGENT)}", HTTP_HOST: {convert_empty(req$HTTP_HOST)}, REQUEST_METHOD: {convert_empty(req$REQUEST_METHOD)}, PATH_INFO: {convert_empty(req$PATH_INFO)}, request_status: {convert_empty(res$status)}, RESPONSE_TIME: {round(end$toc – end$tic, digits = getOption("digits", 5))}')
}
)
)
r
r$run(host="127.0.0.1", port=8000, swagger=TRUE)
view raw endpoint.r hosted with ❤ by GitHub

Os mais atentos repararam que existem 3 funções neste endpoint. A primeira é a convert_empty que vai somente colocar um traço caso alguma parte das informações da requisição estiverem vazias.

A segunda é a função r$registerHooks que é oriunda de um objeto do Plumber e vai registrar todas as informações da requisição HTTP como o IP que está chamando a API, o usuário, e o tempo de resposta da requisição.

A terceira e ultima função é função r$run que vai determinar o IP em que a API vai receber as chamadas (host="127.0.0.1") a porta (port=8000) e se a API vai ter o Swagger ativo ou não (swagger=TRUE). No nosso caso vamos usar o Swagger para fazer os testes com a nossa API e ver se o serviço está funcionando ou não.

Esta vai ser o ultimo script que será executado, e mais tarde vamos ver como ele pode ser executado sem precisarmos entrar no RStudio ou demais IDEs.

Contudo, vamos agora configurar a nossa função de predição dentro do Plumber.

Configuração da função de predição dentro no Plumber

No nosso caso, vamos criar o arquivo chamado api.R. Este arquivo vai ser usado para (a) pegar os dados da requisição, (b) fazer um leve processamento nestes dados, (c) passar os mesmos para o modelo, (d) pegar o resultado e devolver para a o endpoint.

Esse arquivo vai ser referenciado no nosso exemplo, na linha 16 do arquivo endpoint.r.

Contudo, vamos agora entender cada uma das partes do arquivo api.r.

Aos moldes do que foi feito anteriormente, vamos iniciar o nosso arquivo buscando o caminho em que o nosso modelo esta salvo para posteriormente fazer a carga do mesmo em memória (linha 27 – “StackedEnsemble_AllModels_AutoML_20200428_181354") e posteriormente vamos iniciar o nosso logging (linha 27 "api_predictions.log").

No neste exemplo, o modelo serializado é o “StackedEnsemble_AllModels_AutoML_20200428_181354" que foi o melhor do leaderboard do AutoML.

Na linha 34, fazemos a carga do modelo em memória e a contar deste ponto o mesmo está pronto para receber dados para realizar as suas predições.

library('logger')
library(h2o)
library('data.table')
h2o.init()
ROOT_DIR <- getwd()
PROJECT_DIR <-
'r-api-data-hackers'
MODELS_DIR <- 'models'
API_DIR <- 'api'
LOGS_DIR <- 'logs'
model_path_object <-
file.path(ROOT_DIR,
PROJECT_DIR,
MODELS_DIR,
"StackedEnsemble_AllModels_AutoML_20200428_181354")
logging_file_path <-
file.path(ROOT_DIR,
PROJECT_DIR,
LOGS_DIR,
"api_predictions.log")
log_appender(appender_file(logging_file_path))
log_layout(layout_glue_colors)
log_threshold(DEBUG)
log_info('Load saved model')
saved_model <-
h2o.loadModel(model_path_object)
log_info('Model loaded')

Logging e modelo carregado, agora entra a parte em que vamos configurar as variáveis que serão recebidas pelo modelo. No nosso caso, temos o seguinte código:

#* Return the prediction from Laymans Brothers Bank Model
#* @param LIMIT_BAL
#* @param SEX
#* @param EDUCATION
#* @param MARRIAGE
#* @param AGE
#* @param PAY_0
#* @param PAY_2
#* @param PAY_3
#* @param PAY_4
#* @param PAY_5
#* @param PAY_6
#* @param BILL_AMT1
#* @param BILL_AMT2
#* @param BILL_AMT3
#* @param BILL_AMT4
#* @param BILL_AMT5
#* @param BILL_AMT6
#* @param PAY_AMT1
#* @param PAY_AMT2
#* @param PAY_AMT3
#* @param PAY_AMT4
#* @param PAY_AMT5
#* @param PAY_AMT6
#* @post /prediction

Os caracteres #* significam que estamos informado os parâmetros que serão passados para a função.

Abaixo temos o comando #* @post /prediction que, a grosso modo, vai ser o nome da pagina que vai receber o método POST. [N4]

Agora que temos as variáveis que o modelo vai receber devidamente declaradas para o Plumber (ou seja, a nossa API é capaz de receber os dados através das requisições), vamos criar a função que vai receber os dados e vai realizar a predição:

function(LIMIT_BAL, SEX, EDUCATION, MARRIAGE,
AGE, PAY_0, PAY_2, PAY_3, PAY_4, PAY_5,
PAY_6, BILL_AMT1, BILL_AMT2, BILL_AMT3,
BILL_AMT4, BILL_AMT5, BILL_AMT6, PAY_AMT1,
PAY_AMT2, PAY_AMT3, PAY_AMT4, PAY_AMT5, PAY_AMT6) {
LIMIT_BAL <- as.numeric(LIMIT_BAL)
SEX <- as.numeric(SEX)
EDUCATION <- as.numeric(EDUCATION)
MARRIAGE <- as.numeric(MARRIAGE)
AGE <- as.numeric(AGE)
PAY_0 <- as.numeric(PAY_0)
PAY_2 <- as.numeric(PAY_2)
PAY_3 <- as.numeric(PAY_3)
PAY_4 <- as.numeric(PAY_4)
PAY_5 <- as.numeric(PAY_5)
PAY_6 <- as.numeric(PAY_6)
BILL_AMT1 <- as.numeric(BILL_AMT1)
BILL_AMT2 <- as.numeric(BILL_AMT2)
BILL_AMT3 <- as.numeric(BILL_AMT3)
BILL_AMT4 <- as.numeric(BILL_AMT4)
BILL_AMT5 <- as.numeric(BILL_AMT5)
BILL_AMT6 <- as.numeric(BILL_AMT6)
PAY_AMT1 <- as.numeric(PAY_AMT1)
PAY_AMT2 <- as.numeric(PAY_AMT2)
PAY_AMT3 <- as.numeric(PAY_AMT3)
PAY_AMT4 <- as.numeric(PAY_AMT4)
PAY_AMT5 <- as.numeric(PAY_AMT5)
PAY_AMT6 <- as.numeric(PAY_AMT6)
log_debug('Generate data.table with converted variables')
predict_objects <- data.frame(
LIMIT_BAL = c(LIMIT_BAL),
SEX = c(SEX),
EDUCATION = c(EDUCATION),
MARRIAGE = c(MARRIAGE),
AGE = c(AGE),
PAY_0 = c(PAY_0),
PAY_2 = c(PAY_2),
PAY_3 = c(PAY_3),
PAY_4 = c(PAY_4),
PAY_5 = c(PAY_5),
PAY_6 = c(PAY_6),
BILL_AMT1 = c(BILL_AMT1),
BILL_AMT2 = c(BILL_AMT2),
BILL_AMT3 = c(BILL_AMT3),
BILL_AMT4 = c(BILL_AMT4),
BILL_AMT5 = c(BILL_AMT5),
BILL_AMT6 = c(BILL_AMT6),
PAY_AMT1 = c(PAY_AMT1),
PAY_AMT2 = c(PAY_AMT2),
PAY_AMT3 = c(PAY_AMT3),
PAY_AMT4 = c(PAY_AMT4),
PAY_AMT5 = c(PAY_AMT5),
PAY_AMT6 = c(PAY_AMT6),
stringsAsFactors = FALSE
)
log_debug('Convert to H20.ai Object…')
predict_objects <-
as.h2o(predict_objects)
log_debug('Make prediction…')
prediction <-
h2o.predict(object = saved_model,
newdata = predict_objects)
prediction <- as.data.table(prediction)
log_debug('Default: {prediction}')
return(prediction)
}

Essa é uma função simples em R que vai receber como argumentos, as variáveis que foram declaradas anteriormente para o Plumber.

Entre as linhas 8-30 eu fiz a conversão de todas as variáveis para numérico por um motivo simples: No momento em que eu passo a função direto (sem as conversões) o Plumber não faz a verificação de tipagem das variáveis antes de passar para o modelo.

Por causa desse problema eu perdi algumas horas tentando ver se havia alguma forma de fazer isso direto no Plumber, e até tem; mas no meu caso eu preferi deixar dentro da função e ter o controle da conversão lá. Na minha cabeça, eu posso deixar o tratamento de erro dentro da própria função e ao menos tentar algumas conversões, se for o caso. Mas aí vai da escolha de cada um.

Entre a linhas 34 e 59 eu construo o data.table, para posteriormente nas linhas 62 e 63 converter como objeto do H2O.ai.

Essa conversão torna-se necessária, pois os modelos do H2O.ai, até a presente versão, só aceitam objetos de dados dentro do seu próprio formato.

Finalmente entre as linhas 62 e 70 realizamos a predição propriamente dita, e retornamos a nossa predição na função.

Em seguida tem uma segunda função que pega o corpo da requisição (body) e mostra os valores no console (esses valores podem ser gravados no log também).

function(req) {
raw_body = req$postBody
print(raw_body)
}

E assim temos os nossos arquivos endpoint.r e api.r criados em que, em termos simples os arquivos tem os seguintes objetivos:

  • api.R: Eu tenho o modelo carregado em memória, eu recebo os dados, eu faço o tratamento desses inputs, jogo no modelo e devolvo uma predição. E de quebra, eu sou responsável por falar quais parâmetros o modelo vai receber.
  • endpoint.r: Eu subo a API, recebo as informações de quem está fazendo a requisição como IP e usuário, e faço a referência ao api.R que vai fazer a parte difícil da predição.

No seu caso, se voce já tiver o seu modelo, basta apenas trabalhar nos arquivos api.R e endpoint.r, e adaptar os inputs com os seus dados e colocar o seu modelo de machine learning em memória.

Agora que temos os nossos arquivos, vamos subir a nossa API.

Inicializando a RESTful API

Com os nossos arquivos da API e do nosso endpoint devidamente configurados, para inicializar a nossa API podemos executar o arquivo endpoint.R dentro do R Studio.

Entretanto, como estamos falando em um ambiente de produção, fazer isso manualmente não é prático, principalmente me um ambiente em que mudanças estão sendo feitas de forma constante.

Dessa forma, podemos inicializar essa API executando o seguinte comando na linha de comando (terminal para os usuários de Linux/MacOS):

$ R < /<<YOUR-PATH>>/r-api-data-hackers/api/endpoint.R --no-save

Na execução desse comando, teremos no terminal a seguinte imagem:

Com esse comando precisamos apenas dos arquivos nos diretórios para inicializar a nossa RESTful API que ser inicializada no endereço http://127.0.0.1:8000.

Contudo, acessando essa URL no browser não vai aparecer nada, e para isso vamos usar o Swagger para realizar os testes. Para isso, vamos acessar no nosso browser o endereço: http://127.0.0.1:8000/__swagger__/

No browser teremos uma tela semelhante a esta:

Para realizar a predição via a interface do Swagger, vamos clicar no ícone verde escrito POST. Veremos uma tela semelhante a esta:

Em seguida, vamos clicar no botão escrito “Try it out” e preencha as informações dos campos que declaramos como parâmetros lá no arquivo endpoint.r:

No final, após todas as informações estarem preenchidas, clique no botão azul que contem a palavra execute:

Clicando neste botão, podemos ver o resultado da nossa predição no response body:

O corpo dessa resposta de requisição que mandamos para a URL tem as seguintes informações:

[
  {
    "predict": "1",
    "p0": 0.5791,
    "p1": 0.4209
  }
]

Ou seja, dentro desses valores passados na requisição, o modelo do banco Layman Brothers previu que o cliente vai entrar na situação de default (ou dar o calote). Se quisermos trabalhar com as probabilidades, o modelo da essas informações na resposta, sendo que o cliente tem a probabilidade de 58% de dar o calote, contra 42% de probabilidade de não dar o calote.

Mas para os leitores que não morreram ate aqui, alguns deles podem perguntar: “Poxa Flavio, mas os clientes não vão entrar na nossa pagina swagger e fazer a requisição. Como uma aplicação em produção vai fazer o uso desse modelo?

Lembram que eu falei que essa RESTful API seria, a grosso modo, uma espécie de serviço web? Então o ponto aqui é que a aplicação principal, i.e. Plataforma do nosso banco Layman Brothers que vai receber as informações de credito, vai passar essas informações para a nossa RESTful API que está fazendo o servicing dos modelos via requisições HTTP e a nossa API vai devolver os valores da mesma maneira que vimos no corpo da mensagem anterior.

Trazendo para termos mais concretos: No momento em que a sua RESTful API está rodando, o seu modelo está pronto para ser requisitado pela aplicação principal.

Essa chamada HTTP pode ser feita copiando o comando curl que está sendo informado pelo Swagger, como podemos ver na imagem abaixo:

Neste caso, para simular a chamada que a aplicação principal do Layman Brothers tem que fazer, vamos copiar o seguinte comando curl:

curl -X POST "http://127.0.0.1:8000/prediction?PAY_AMT6=1000&PAY_AMT5=2000&PAY_AMT4=300&PAY_AMT3=200&PAY_AMT2=450&PAY_AMT1=10000&BILL_AMT6=300&BILL_AMT5=23000&BILL_AMT4=24000&BILL_AMT3=1000&BILL_AMT2=1000&BILL_AMT1=1000&PAY_6=200&PAY_5=200&PAY_4=200&PAY_3=200&PAY_2=200&PAY_0=2000&AGE=35&MARRIAGE=1&EDUCATION=1&SEX=1&LIMIT_BAL=1000000" -H "accept: application/json"

Após copiarmos esse comando, vamos colar no terminal, e executar apertando a tecla enter. Teremos o seguinte resultado:

Ou seja, recebemos o mesmo resultado que executamos no Swagger. Sucesso.

Para ler os nossos logs posteriormente, basta executarmos o comando tail -F api_predictions.log dentro da pasta logs como abaixo, temos o seguinte resultado:

Aqui temos todas as informações que colocamos para serem registradas no arquivo de logs. Dessa forma, caso esse processo seja automatizado, pode ser feito uma depuração ou auditoria dos resultados, casa seja necessário.

Existem duas versões desse código no GitHub. Essa versão light está no repositório r-api-data-hackers e a versão mais completa, está no repositório r-h2o-prediction-rest-api.

CONSIDERAÇÕES FINAIS

O objetivo aqui neste foi mostrar de um passo a passo como cientistas de dados, estatísticos, e demais interessados podem subir uma RESTful API inteiramente usando código R.

O projeto em si, dentro da perspectiva de codificação em produção tem muitas limitações como tratamento de erro, segurança, logging, tratamento das requisições e das respostas no log, e subir tudo isso em um ambiente mais isolado, como por exemplo no Docker.

Entretanto, acho que depois desse tutorial muitos problemas relativos à parte prática de colocar modelos de machine learning em produção no R podem, no mínimo ser endereçados e com isso dar mais poder aos cientistas de dados que desenvolvem em R e demais interessados.

NOTAS

  • [N1] – Nome sem nenhuma ligação com a realidade.
  • [N2] – As variáveis SEX (gênero), MARRIAGE (se o/a cliente é casado(a) ou não) e AGE (idade) estão apenas para fins de demonstração como qualquer outra variável. No mundo real, idealmente essas variáveis seriam totalmente eliminadas para não trazer vieses discriminatórios nos modelos e demais problemas éticos.
  • [N3] – Existem inúmeras opções para subir a API em produção através de hosting que nada mais são do que serviços pagos que garantem parte da infraestrutura e cuidam de algumas questões de segurança e autenticação como a Digital Ocean, o RStudio Connect, e existem alguns recursos para fazer o hosting do Plumber em imagens Docker. No nosso caso, vamos assumir que essa API vai ser colocada em produção em uma maquina em rede na qual um analista de infraestrutura ou um cientista de dados possa fazer o deployment dessa API.
  • [N4] – Embora o objetivo deste post seja “fazer funcionar primeiro, para depois entender” é de extrema importância o entendimento dos aspectos ligados as nomenclaturas o que faz cada parte da arquitetura REST. Existem ótimos recursos para isso como aqui, aqui, aqui, e aqui

REFERÊNCIAS

RESTful API para servicing de modelos de Machine Learning com R, H2O.ai e Plumber

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:

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:

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

  • 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.
  • 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:

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

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

Funções com Multiprocessing para processamento de textos

Quem acompanhou o post A small journey in the valley of Natural Language Processing and Text Pre-Processing for German language acompanhou um pouco dos desafios de modelar um classificador de textos em alemão.

No entanto uma coisa que me salvou na parte de pre-processing foi que eu praticamente usei o multiprocessing para paralelizar o pré-processamento na coluna de texto e isso me salvou um tempo incrível (relembrando: eu tinha 1+ milhão de registros de texto, com 250 palavras média por registro (com um desvio padrão de 700, tudo isso usando biblioteca interna).

import time
import numpy as np
import pandas as pd
import nlp_pre_processing
# An internal NLP lib to process text
nlp = nlp_pre_processing_library.NLPPreprocessor()
# Multiprocessing library that uses pool
# to distribute the task for all processors
from multiprocessing import Pool
print(f'Start processing…: {(time.time() start_time)}')
# Tracking the time
start_time = time.time()
# Number of partitions that
# the Pandas Dataframe will be
# splited to parallel processing
num_partitions = 20
# Number of cores that will be used
# more it's better
num_cores = 16
print(f'Partition Number: {num_partitions} – Number of Cores: {num_cores}…')
def main_process_pipeline(df, func):
"""
Function that will split the dataframe
and process all those parts in a n number
of processors
Args:
df (Pandas dataframe): Dataframe that will be splited
func (function): Python function that will be executed in parallel
Returns:
df: Dataframe with all parts concatenated after the function be applied
"""
df_split = np.array_split(df, num_partitions)
pool = Pool(num_cores)
df = pd.concat(pool.map(func, df_split))
pool.close()
pool.join()
return df
def pre_process_wrapper(df):
""" Will take the Dataframe and apply a function using lambda"""
df['text'] = df['text'].apply(lambda text: nlp.pre_processing_pipeline(text))
return df
# Unite the Dataframe and the Wrapper
processed_df = main_process_pipeline(df, pre_process_wrapper)
print(f'Processing finished in seconds: {(time.time() start_time)}')

É isso: Simples e tranquilo.

Funções com Multiprocessing para processamento de textos

Accountability, Core Machine Learning e Machine Learning Operations

Para quem acompanha o debate de tecnologia através da academia, indústria, conferências, e na mídia já percebeu que a Inteligência Artificial (AI) e suas subáreas são os assuntos mais quentes no momento.

Algumas empresas já que têm o núcleo do seu negócio em sistemas/plataformas digitais (e alguns não digitais) entenderam que o uso de Machine Learning (ML) tem um grande potencial tanto para casos de otimização na forma com a qual a empresa funciona, quanto para casos de geração direta de receita.

Isso pode ser visto em inúmeros negócios que vão desde o setor bancário, passando por sistemas de recomendação voltados para o entretenimento, e chegando até mesmo em algumas aplicações médicas.

Este post vai tentar colocar de maneira bem breve sobre como ML está moldando muitos negócios, uma breve reflexão em relação à marcha do accountability [1], e finalmente algumas breves considerações no que se refere a times de Core Machine Learning, e da abordagem de Machine Learning Operations (MLOps).

De que forma Machine Learning está moldando algumas indústrias e qual o grau de responsabilidade dos times de engenharia?

Com a adoção de machine learning por parte da indústria, isto deu início a um movimento natural que tanto machine learning quanto a indústria estão moldando um ao outro.

Se em um primeiro momento a indústria beneficia-se de plataformas de Machine Learning para obter predições, classificações, inferências e tomada de decisão em escala com custo marginal perto de zero; Machine Learning beneficia-se da indústria com o acesso a recursos de pesquisa e desenvolvimento inimagináveis na academia, acesso a recursos que teriam um custo inviável para condução de estudos, e um aumento da maturidade de seus métodos em termos de engenharia.

Entretanto, o que estamos falando aqui em última instância é da escala em que decisões são tomadas na indústria, e como a P&D em Machine Learning avançam em altíssima velocidade.

Dito isso, podemos afirmar que nos dias de hoje estes sistemas não estão mais na inofensiva arena das ideias e provas de conceito; mas sim estão como elementos ativos em processos de interação entre pessoas e negócios de forma massiva em alta escala.

E devido à esta escala, uma série de novas questões que se não eram tão preocupantes ou eram veladas no passado, hoje passam ter uma maior importância, como por exemplo:

  • Se no passado um gerente de branco recusasse um empréstimo para algum cliente por conta da cor da pele, gênero, ou deficiência; pouco ou quase nada ocorria com este gerente e/ou banco. Nos dias de hoje, algoritmos de Machine Learning sem a devida análise e monitoramento de seus outputs podem automatizar e amplificar este tipo de viés, colocando assim o banco em um passivo jurídico e de relações públicas sem precedentes. Atualmente existem pessoas trabalhando em disciplinas como equidade e ética para minimizar estes problemas.

Como podemos ver nestes exemplos, aspectos atuais como vieses humanos estruturais, falta de diversidade, promoção estrutural de injustiça, abuso de autoridade podem ser minimizados com ML usando ferramentas como equidade (Fairness), transparência, accountability, e explicabilidade.

E dado os pontos colocados acima, é desnecessário dizer a importância e a responsabilidade de cada dos profissionais de ML para assegurar que uma decisão automatizada não inclua e/ou amplifique estes vieses sistemáticos.

Uma das maiores verdades em tecnologia, é que os sistemas computacionais grande parte das vezes trabalham na amplificação de comportamentos e competências. Um sistema de ML que não leva em consideração vieses estruturais estão fadados a não somente a dar continuidade, mas como também a amplificar estes mesmos vieses em alta escala.

E dada a enorme autoridade da engenharia tem em relação à implementação destes sistemas, automaticamente o accountability virá com a mesma intensidade do grau de impacto destas soluções.

O Accountability virá de maneira voluntária e/ou coercitiva

Dado todos os cenários em que plataformas de ML têm um impacto direto na indústria, e de todos os potenciais riscos e impactos na sociedade, existe uma marcha regulatória vindo de inúmeras frentes que vão colocar uma responsabilização muito maior nas empresas e nos engenheiros de ML.

Esta responsabilização será essencialmente em relação a aspectos sensíveis que preocupam a sociedade como um todo: ética, equidade, diversidade, privacidade, segurança, direito de explicação de decisões algorítmicas (para quem está sob a GDPR), além claro de aspectos específicos de ML (e.g. reprodutibilidade, avaliação dos modelos, etc.).

Desta forma, isto mais do que nunca coloca uma pressão muito grande em todos nós engenheiros, cientistas de dados, gerentes de produto, CTOs, CEOs e demais stakeholders de não somente fazer o nosso trabalho, mas também atentar a todos estes aspectos.

Se este cenário soa distante ou fora da realidade, eu convido os mais céticos a responderem de forma honesta as seguintes perguntas em relação ao seu atual empregador:

Eu poderia colocar inúmeros outros casos que já estão acontecendo nos dias de hoje, mas acredito que consegui fazer o meu ponto. Para quem quiser saber mais, eu recomendo o livro da Cathy O ‘Neil chamado Weapons of Math Destruction que mostra alguns destes cenários ou a palestra baseada no livro, chamada ”A era da fé cega nos “Big Data” tem que acabar.

Além disso, se esta responsabilização não vier pela via de mercado, isso obrigatoriamente virá pela via coercitiva da regulamentação estatal; esta última que neste momento está sendo desenvolvida por inúmeros governos em todo o mundo para imputar responsabilização, tanto nas empresas quanto nos indivíduos.

Isso pode ser visto nos inúmeros observatórios e Think Tanks como a The AI4EU Observatory, em algumas recomendações da OECD em relação à Inteligência Artificial, e nos recentes guidelines divulgados pelas estratégias nacionais de IA em países como a Estônia, Finlândia, Alemanha, China, Estados Unidos, França e a própria Comissão da União Europeia que já disse que claramente que vai regular massivamente IA da perspectiva de riscos e transparência.  

Isto quer dizer em última instância que erros em um sistema que interage diretamente com seres humanos é algo que vai resultar em uma cadeia de consequências totalmente distinta da que temos atualmente.

Dado esse cenário extremamente complexo, podemos deduzir que se a era do “analista-com-um-script-na-própria-máquina” não acabou ela está em vias de acontecer de forma muito mais rápida do que podemos imaginar; seja pelo profissionalismo e conscientização, ou pela coerção, ameaça e/ou perdas fiduciárias.

E não se engane com quem fala que você é somente “uma pessoa que deve cumprir ordens” e que nada vai acontecer. No momento em que a sua empresa tiver algum tipo de problema cível/criminal/relações públicas você será corresponsável. E já existe precedente de engenheiro que está pegando cadeia por conta de más práticas dentro do seu ofício. E aqui não vai ser somente uma questão de “se”, mas sim “quando” isso vai chegar em engenharia de software em ML.

A mensagem que eu quero deixar aqui não é de desespero ou mesmo induzir situações de enfrentamento corporativo. O que eu quero deixar como mensagem final é apenas que devemos ter uma consciência situacional desta marcha do accountability/responsabilização e do porquê isso será inevitável.

Em outras palavras: Raciocínio crítico é parte intrínseca do trabalho, você é responsável pelo o que faz, e o valor disso já está embutido no seu salário.

Core Machine Learning

A primeira vez que eu tive contato com a abordagem de Core Machine Learning foi em meados de 2015 no Strata Data Conference e continuando em 2016 através de algumas palestras do Hussein Mehanna. Contudo, somente em 2017 no Facebook @Scale após o contato com as pessoas da indústria eu consegui entender um pouco mais do que era esta abordagem.

Não que exista uma definição formal, mas basicamente um time de Core Machine Learning seria o responsável pelo desenvolvimento de plataformas de Machine Learning dentro do Core Business das organizações; seja embutindo algoritmos nas plataformas existentes ou entregando serviços de inferência/predição via APIs.

Parte da missão desse time seria lidar diretamente todas as iniciativas de aplicações de machine learning dentro da atividade principal da empresa. Isto vai desde pesquisa aplicada, adoção de práticas de software engineering em ML, até construção da parte de infraestrutura destas aplicações.

Pensando na nova economia que chegou para ficar, a meu ver, estamos no meio de uma transição de paradigmas de desenvolvimento de produtos.

Em um lado temos um paradigma que tem o foco na construção de aplicações estáticas que tem uma preocupação com os fluxos de negócios. Em um outro lado temos um paradigma herda as mesmas características, mas que usam os dados para alavancar estas aplicações.

Óbvio que existe muito hype e muito solucionismo usando ML, mas eu estou falando aqui de empresas que conseguem aplicar ML de forma oportunística e com pragmatismo para a construção destas aplicações.

Em outras palavras: o algoritmo na plataforma passa a virou o produto em si.

Vejamos alguns exemplos de plataformas em que o algoritmo é o produto:

  • Spotify: O Discovery Weekly é um bom exemplo de uma feature de Machine Learning que acabou virando um produto dentro da plataforma;

Estes são alguns dos exemplos públicos mais famosos de alguns cases de machine learning no core business dos negócios tanto no Brasil quanto em outros lugares.

Uma forma muito interessante de entender como alguns algoritmos auxiliaram na alavancagem de produtos, pode ser vista no paper Applied Machine Learning at Facebook: A Datacenter Infrastructure Perspective do Facebook:

Claro que sabemos que internamente a coisa não é tão simples assim, mas podemos ter uma ideia de como aspectos vitais para o produto Facebook depende essencialmente da implementação de Core Machine Learning.

Pode parecer a mesma coisa em um primeiro momento, mas a principal diferença entre as atribuições de um time de Data Science para um time de Core Machine Learning, é que enquanto o primeiro geralmente tem um foco na parte de análise e modelagem; o segundo coloca tudo isto de maneira que seja escalável e automatizada dentro do núcleo principal do negócio.

Porém, dado que falamos que Core Machine Learning idealmente seria um time/abordagem que potencialmente alavanca o core business através da aplicação de ML, eu vou falar um pouco da forma como a coisa toda é operacionalizada. 

MLOps – Machine Learning Operations

Na Engenharia de Software existe um grau de maturidade altíssimo na forma em que as aplicações são construídas e as suas ferramentas que vão desde excelentes IDEs, passando por frameworks que lidam bem com inversão de controle e injeção de dependência, metodologias de desenvolvimento maduras e battle-tested, ferramentas de deployment que simplificam muito o processo de CI/CD, e ferramentas de observabilidade que facilitam o monitoramento das aplicações.

Em contraste, em machine learning existe um abismo em termos de maturidade em relação à adoção dessas práticas como também em grande parte do tempo engenheiros de machine learning trabalham com artefatos de dados como modelos e datasets.

Alguns destes artefatos (não exaustivos) são:

  • Análises de Data Science;
  • Pipelines de extração de dados e geração de features via Data Engineering;
  • Versionamento de dados que geram os modelos;
  • Tracking de treinamento de modelos;
  • Tracking dos hiperparâmetros usados nos experimentos;
  • Versionamento de modelos de Machine Learning;
  • Serialização e promoção dos modelos para produção;
  • Manutenção de privacidade dos dados e do modelo;
  • Treinamento dos modelos considerando contramedidas de segurança (e.g. ataques adversariais);
  • Monitoramento da performance dos modelos dada a natureza de degradação intrínseca de performance desses artefatos (data/model drift).

Uma das consequências de tantas distinções em termos de processos entre essas áreas, é que a operacionalização desses recursos também deve ser feita de forma totalmente distinta.

Em outras palavras, talvez DevOps pode não ser o bastante nestes casos.

Uma figura que resume bem este ponto é da talk do Luke Marsden chamada “MLOps Lifecycle Description” em que ele coloca a diferença entre essas duas áreas da seguinte forma:

MLOps Lifecycle Description

A ideia por trás é que enquanto tradicionalmente engenharia de software lida com funcionalidades e tem no código a materialização de fluxos; na abordagem de Machine Learning Operations (MLOps) além de existirem as mesmas preocupações, há uma adição de muitas partes móveis como dados, modelos e métricas e da operacionalização de todos estes aspectos [2].

Isto é, a operacionalização desde fluxo de desenvolvimento e deployment requer uma nova maneira de entrega dessas soluções de forma end-to-end.

Para isto, uma proposta de como seria uma aplicação end-to-end considerando esses aspectos operacionais de ML é apresentada no artigo “Continuous Delivery for Machine Learning: Automating the end-to-end lifecycle of Machine Learning applications dentro da perspectiva de entrega contínua:

Continuous Delivery for Machine Learning (CD4ML) is a software engineering approach in which a cross-functional team produces machine learning applications based on code, data, and models in small and safe increments that can be reproduced and reliably released at any time, in short adaptation cycles.

Continuous Delivery for Machine Learning: Automating the end-to-end lifecycle of Machine Learning applications

No mesmo artigo, ainda tem uma figura de como seria um fluxo end-to-end de uma plataforma de machine learning:

Continuous Delivery for Machine Learning: Automating the end-to-end lifecycle of Machine Learning applications

E com essas novas camadas de complexidades somadas com a pouca educação em engenharia de software por parte de grande parte dos cientistas de dados, fica claro que o espectro de potenciais problemas no que diz respeito à entrega de aplicações de machine learning fica muito maior.

Entretanto, até o momento discutimos aspectos bem de alto nível como questões de impacto dos sistemas de ML, aspectos ligados à responsabilização, Core Machine Learning e as suas responsabilidades e a abordagem de MLOps.

Porém, eu quero aprofundar um pouco mais o nível e entrar em alguns pontos mais específicos em que MLOps têm uma atuação mais; isto é, jogar um pouco de luz na trilha escura em que SysOps, DevOps, Software Engineering, e Data Science geralmente não entrariam.

Fonte: Christian Collins – shades of mirkwood

Complexidade em sistemas de Machine Learning

No clássico paper Hidden Technical Debt in Machine Learning Systems existe uma imagem que cristaliza bem o que é realmente um sistema de machine learning em relação à complexidade e esforço para cada componente deste sistema:

Hidden Technical Debt in Machine Learning Systems

Ainda que sem uma menção direta sobre MLOps, no artigo tem algumas considerações em relação aos problemas específicos de sistemas de machine learning e que acarretaria débitos técnicos e demais problemas que potencialmente deixariam essas aplicações mais frágeis em termos de escalabilidade e manutenção.

Eu resolvi pegar alguns dos sete pontos do artigo e colocar alguns exemplos práticos. A ideia é mostrar uma abordagem de MLOps em alguns cenários (hipotéticos ou não) como podemos ver abaixo:

A corrosão de limites devido à modelos complexos
Dependências de dados custam mais do que dependências de código
Ciclos de Feedback
  • O time de produtos pediu para fazer uma estratégia de experimentação com Multi-Armed Bandits com n modelos. Como os dados das estratégias perdedoras estão sendo isolados (i.e. dado que as estratégias afetam os dados presentes e o futuro treino no futuro)? Existe alguma assinatura de log que identifique estes registros?[3]
  • Um sistema recomendador retorna uma lista de itens ordenada pela probabilidade em termos de relevância para o usuário. Entretanto, o nDCG do modelo está muito baixo. Quanto tempo demoraria para você saber que o motivo é devido ao fato de que o Front-End ao invés de respeitar a ranqueamento recebido do sistema recomendador, o mesmo está refazendo a ordenação por ordem alfabética? Como seria um teste ou ciclo de feedback entre o sistema de recomendação e o Front-End neste caso?[3]
Anti-Patterns em Sistemas de Machine Learning
Débito de Configuração
  • Cada uma dos seus microserviços de ML têm configurações de logging locais e enviar esses logs para um ELK envolveria refazer os scripts e o deployment em todos esses serviços.
Lidando com mudanças no mundo externo
  • O sistema de ML que faz detecção/classificação de anomalias e que é usado para alarmística e monitoramento de receita está recebendo um aumento no volume de requisições. Porém, passado algum tempo o sistema começa a disparar inúmeros alertas de queda de receita; alertas estes que acionam os desenvolvedores para solução desse problema. Contudo, você descobre a razão dos alertas: o time de marketing fez uma campanha não recorrente que aumentou de maneira não-orgânica a receita, e o classificador “aprendeu” que aqueles níveis de receita seriam o “novo normal”. [3]
  • O seu sistema de recomendação está oferecendo os mesmos itens fora de catálogo por 15 dias seguidos, trazendo não só uma experiência de usuário horrível, mas também afetando negativamente a receita. O motivo? Não existe o monitoramento dos dados (filebeat) e nem das métricas da aplicação (metricbeat). [3]
Outras áreas de débitos técnicos relacionadas com Machine Learning, como:

Os pontos acima foram alguns exemplos de como sistemas de machine learning carregam complexidades intrínsecas que envolvem conjuntos de habilidades muito específicas e que devem ser levadas em consideração no tocante à sua operacionalização.

CONSIDERAÇÕES FINAIS

Estamos na marcha de ter cada vez mais sistemas de Machine Learning envolvidos de forma direta ou marginal no core business das empresas.

Com o aumento do impacto destes sistemas nas vidas das pessoas, na sociedade e nos negócios, é questão de tempo para termos protocolos de accountability caso alguma coisa saia do controle; em especial em aspectos ligados a equidade, transparência, e explicabilidade destes sistemas e algoritmos.

Dentro disso fica cada vez mais claro que a era do “analista-com-um-script-na-própria-máquina” está com os dias contados quando falamos de plataformas que tem interatividade com pessoas.

Ao passo que sistemas de Machine learning ainda não tem o mesmo nível de maturidade de engenharia de software em relação ao seu desenvolvimento, deployment e operacionalização, como também conta com muitos aspectos específicos; talvez exista uma avenida para o crescimento do que é conhecido hoje como MLOps, ou Machine Learning Operations.

A abordagem de MLOps não veem apenas para lidar com aspectos ligados a parte de infraestrutura ou desenvolvimento software, mas esses times vêm para atender uma demanda, ainda latente, da eliminação ou mitigação dos problemas e débitos intrínsecos da atividade de desenvolvimento Machine Learning.

NOTAS

[1] – Os pares de termos como “Data Scientist /Cientistas de Dados”, “Sistemas/Plataformas”, “Product Manager/Gerente de Produto”, “Accountability/Responsabilização”, “Fairness/Equidade” serão usados alternadamente ao longo deste texto.

[2] – Para quem tiver interessado o Luke Marsden escreveu uma espécie de MLOps Manifesto onde tem algumas dessas ideias.

[3] – Eventos em que eu fui testemunha ou que aconteceram comigo diretamente.

LINKS E REFERÊNCIAS

Accountability, Core Machine Learning e Machine Learning Operations

Como escolher entre o RMSE e o RMSLE?

Existem inúmeros artigos descrevendo o RMSE e o RMSLE, mas aqui eu vou tentar ser o mais direto possível em relação ao que eu faço entre essas duas métricas.

Quando eu quero apenas ter a medida do erro em termos de viés e variância do modelo, sem considerar nenhum aspecto em relação às diferenças de magnitudes entre o que foi previsto (y_hat) e o que era o esperado na base de validação (y), eu uso o RMSE.

Exemplo: Uma previsão errada de {y=1, y_hat=2} vai entrar na média quadrática da mesma forma que {y=1000000, y_hat=1000500}; isso significa que a magnitude da segunda previsão não importa e que eu aceito que ela vai influenciar na média quadrática (no caso uma magnitude de 500x maior do que o primeiro erro).

Quando eu quero a mensuração do viés e da variância mas eu não quero penalizar erros que ocorram em magnitudes distintas, aí eu uso o RMSLE. Isto é, os erros são isolados dentro da mesma ordem de magnitude entre y_hat e y.

Usando o exemplo anterior, no caso de erro de {y=1000000, y_hat=1000500} o termo logaritmo do RMSLE vai realizar o ajuste entre y_hat e y e calcular a diferença dentro da mesma magnitude antes do cálculo da média quadrática. Isso significa que, mesmo com uma ordem de magnitude muito maior do que nos erros anteriores, o logaritmo fará a suavização desses erros desses “grandes números” retirando a magnitude na media quadrática.

Como de costume, o código está abaixo:

import pandas as pd
import math
import numpy as np
# Create dataframe
df_preds = pd.DataFrame(columns=['y', 'y_hat'])
# Fill it
df_preds.loc[len(df_preds)] = [1, 1]
df_preds.loc[len(df_preds)] = [2, 3]
df_preds.loc[len(df_preds)] = [50, 55]
df_preds.loc[len(df_preds)] = [500, 502]
df_preds.loc[len(df_preds)] = [1000000, 1000005]
# Check
df_preds
# y y_hat
# 0 1 1
# 1 2 3
# 2 50 55
# 3 500 502
# 4 1000000 1000005
# Create functions
def rmse(predictions, targets):
'''Source: https://stackoverflow.com/questions/17197492/is-there-a-library-function-for-root-mean-square-error-rmse-in-python''&#39;
return np.sqrt(((predictions targets) ** 2).mean())
def rmsle(predict, target):
'''Source: https://towardsdatascience.com/metrics-and-python-850b60710e0c''&#39;
total = 0
for k in range(len(predict)):
LPred= np.log1p(predict[k]+1)
LTarg = np.log1p(target[k] + 1)
if not (math.isnan(LPred)) and not (math.isnan(LTarg)):
total = total + ((LPredLTarg) **2)
total = total / len(predict)
return np.sqrt(total)
# Check data before executiion
df_preds
# y y_hat
# 0 1 1
# 1 2 3
# 2 50 55
# 3 500 502
# 4 1000000 1000005
# Get stats
print ('RMSE: ' + str(rmse(df_preds['y_hat'].values, df_preds['y'].values)))
print ('RMSLE: ' + str(rmsle(df_preds['y_hat'].values, df_preds['y'].values)))
# RMSE: 3.3166247903554
# RMSLE: 0.1079235658917167
# Increase the error in the biggest number in terms of magnitude (from 5 to 500)
# Create dataframe
df_preds = pd.DataFrame(columns=['y', 'y_hat'])
# Fill it
df_preds.loc[len(df_preds)] = [1, 1]
df_preds.loc[len(df_preds)] = [2, 3]
df_preds.loc[len(df_preds)] = [50, 55]
df_preds.loc[len(df_preds)] = [500, 502]
df_preds.loc[len(df_preds)] = [1000000, 1000500]
# Check
df_preds
# y y_hat
# 0 1 1
# 1 2 3
# 2 50 55
# 3 500 502
# 4 1000000 1000500
# The RMSE exploded, but the RMSLE stayed the same due to not penalize the error in bigger magnitude predictions
print ('RMSE: ' + str(rmse(df_preds['y_hat'].values, df_preds['y'].values)))
print ('RMSLE: ' + str(rmsle(df_preds['y_hat'].values, df_preds['y'].values)))
# RMSE: 223.6202137553759
# RMSLE: 0.10792379739703087
Como escolher entre o RMSE e o RMSLE?

Classification Report do Scikit-Learn em Dataframe

Não sei se só eu tinha esse problema, mas eu encontrei essa solução no Stack Overflow.

def get_classification_report(y_test, y_pred):
'''Source: https://stackoverflow.com/questions/39662398/scikit-learn-output-metrics-classification-report-into-csv-tab-delimited-format''&#39;
from sklearn import metrics
report = metrics.classification_report(y_test, y_pred, output_dict=True)
df_classification_report = pd.DataFrame(report).transpose()
df_classification_report = df_classification_report.sort_values(by=['f1-score'], ascending=False)
return df_classification_report
Resultado final.
Classification Report do Scikit-Learn em Dataframe

A Topic Modeling using LDA on the White Paper on Artificial Intelligence: a European approach to excellence and trust

This month the European Union Commission released a White Paper from called “White Paper on Artificial Intelligence: a European approach to excellence and trust”. The idea of this white paper was to establish the guidelines for the EU regarding Artificial Intelligence (AI).

For those who don’t know the report recognizes that the EU is now behind the USA and China when we discuss AI systems and user data, and the document tries to give some perspectives in the usage of AI in industrial data, where the EU has a great advantage in comparison with those regions.

I won’t discuss the political aspects of the document, but there are some good summaries like this one from Covington Digital Health and this one also from them.

I’ve read the report and at least for me was very disappointing, especially due to the fact that state members from the EU contain far better initiatives.

The reason that I found the report disappointing it’s because the EU Commission instead to look the best of some local initiatives like Finland (that made a very deep report that talks about AI in business, self-regulation and capturing values like moral, ethics and policies as north star); or Estonia (that contains an ambitious plan to implement most of the initiatives in public sector to increase their efficiency and give a proper return to the taxpayers) the Commission just put trigger/effect words regarding only risks and regulation as we’ll see below.

On top of that, considering the importance of AI in nowadays and the problems and endeavors that EU will face in a foreseeable future (e.g. aging population and the economic and social impacts, a challenging economic environment with central banks delaying financial crisis via Quantitative Easingan open tariff warbetween two of biggest players, and so on) the report takes in consideration too much focus in privacy and risks with almost no mention in innovation, usage in public sector or even in potentialities of AI. 

The report speaks for itself, but my point here it’s just to bring some charts and some analysis over the text contained in the report. 

First of all, let’s check as usual the most common words in the report:

As expected, the word {data} is the top 1 (sorry, but there’s no AI without data). Along the patch we have some interesting sequence {eu, systems, risks} that I consider that is the drivers of all report (I’ve read the full report).

Using a simple vanilla Word Cloud, we have:

Again, as we can see the words with more frequency in the report were {eu, ai, data, system, risk}. 

But as I told before I really dislike Word Clouds and other kinds of word frequencies, so I decided to use a TF-IDF through all document to check the real importance of each word inside of the document. For a matter of simplicity, I took only the top 30 terms. 

As expected we have the same words in top {ai, data, systems, risks} but we have some notable mentions like: a) the world {regulatory} in front of {human}; b) the word {commission} in front of {citizens} and {innovation} and c) the word {digital} out of top 10.

But spare words can be misleading in some instance, so let’s take a look in the most frequent word bigrams contained in the report:

In the top 3, we have {ai, systems}, {ai, applications} and {use, ai} that are the drivers of the report. Not a surprising result. After those word n-grams we have some interesting combinations like {fundamental,  rights}, {regulatory,  framework} and {personal, data}. 

Let’s take a look in the trigram compositions:

If the bigrams takes a history about regulatory framework over AI systems, the 3 grams gives a clearer history about the major points of the report that are: i) {highrisk, ai, applications}, ii) {remote, biometric, identification} and } iii) {regulatory, biometric, ai}.

Using a simple LDA analysis in 7 arbitrarily chosen topics, we had the following topics and those main with their Intertopic Distance Map:

Topics found via LDA:

Topic #1:

{europe, ensure, assessment, law, economic, member, conformity}

Topic #2:

{systems, requirements, safety, product, existing, information, ensuring}

Topic #3:

{ai, data, use, applications, legal, national, highrisk}

Topic #4:

{risks, products, services, public, certain, authorities, enforcement}

Topic #5:

{eu, ai, commission, european, rules, framework, including}

Topic #6:

{rights, protection, system, relevant, particular, citizens, eg}

Topic #7:

{legislation, ai, liability, digital, need, set, paper}

Conclusion

Today was a short one because I have as the main principle to not talk about politics due to personal reasons, but I believe that with those graphs anyone can at least guess the tonic of the words in the report.

As usual we have all code and data in the repo.

PS: Those are my personal views and this post doesn’t represent anyone else than me. I do not endorse any kind of political affiliation, candidate, or even any kind of political association inside of traditional frameworks. 

A Topic Modeling using LDA on the White Paper on Artificial Intelligence: a European approach to excellence and trust

A small journey in the valley of Natural Language Processing and Text Pre-Processing for German language

Originally posted in MyHammer blog.

TL;DR: If you find yourself in the same situation what I was (i.e. millions of records with labeling problems, no fluency in the language, 200+ classes to predict and all of this in a very specific business segment) invest the maximum amount of time in text pre-processing, generation of word embeddings, and using some language rules/heuristics to refine your corpora.

Warning: Very long post with tons of references. Will take at least 40 min of reading.

A small journey in the German language for Pre-Processing in NLP

This is a summary of a talk that I was to give in Data Council in Berlin last year, but I rather gave a broader one called Low Hanging Fruit Projects in Machine Learning. This post will expand some points of the bullet points that I prepared for this presentation. If you saw my talk about LHF Projects, some of the content will be kind of familiar to you.

Disclaimer Project Report: This is only a project report with additional personal views and experiences, i.e. this is not a post in Towards Data Science, Keynote in O’Reilly Strata conference, best practices talk, Top-10-rule-list-that-you-must-do, Cautionary Tale, cognitive linguistics, applied linguistics, computational linguistics or any other kind of science at all.

Introduction

With all this hype about language models like BERT, GPT-2, RoBERTa, and others, there is no doubt that NLP is one of the hottest topics nowadays.

NLP is in the center of countless discussions today like for instance, the “dangerous” GPT-2. OpenAI said that it would be dangerous to society and did not report the weights. And that a few months later some good developers managed to replicate all the code and afterwards everyone saw that was a good model that sometimes generates very brittle results.

Debates aside, one positive point today is that there are countless resources that brings the state of the art mixed with everyday applications, for example, like this NLP e-mail list provided by Sebastian Ruder.

However, what I am going to put in the following lines are some small aspects of NLP for the German language regarding the pre-processing part for a text project.

I will put some basic aspects of our journey and some more project-level considerations that deal with natural language processing, where I will try to compile our journey and some other features that I saw during that time.

Los geht’s?

German Language: Respecting the unknown unknowns

A hard lesson that I got during this time was: Language is extremely hard! No Deep Neural Network architecture will rescue you out, no AutoML will solve your problem, no big pre-trained model will be useful unless you do a proper text pre-processing.

If I could choose a single piece of advice of this very long note, probably those following mottos would be the ones for that:

Original Saying:

“ Give me six hours to chop down a tree and I will spend the first four sharpening the ax.” (Abraham Lincoln)

Machine Learning Saying:

“ Give me six hours to deliver a Machine Learning Model and I will spend the first four doing Feature Engineering.”

German NLP Saying:

“ Give me six hours to deliver a German NLP Model and I will spend the first five hours and thirty minutes doing text pre-processing.”:

As a few know, I wasn’t born or raised in any German-speaking country; and this already places a very big initial barrier on some aspects of language such as its nuances and even the understanding of trivial issues such as grammatical structure.

And here, I already put the first tip: If you are not a native speaker of the language, I suggest an understanding of at least an A2 equivalent certificate so that you have an understanding of the basic grammatical structure of the language before dealing directly with that language.

German, unlike my mother (Brazilian Portuguese), has a very different sentence structure in which Portuguese has the SVO (Subject-Verb-Object) structure, and in the German language, this rule is not so common, like for instance, the verbs can be at the end of a sentence.

It may seem small, but for example, for a Portuguese speaker, a negative answer or even a verb action is indicated in the first words of a sentence, not in the end. It forces us to an extra mental load to read the sentence until the end and then have the right context what’s going in the sentence.

In German, this rule is not necessarily true with the disadvantage that as a literate person in Brazilian Portuguese, I have to literally read the sentence completely, do the translation work in order to understand the sentence.

One factor that helped me a lot in that matter was, that since I was dealing with simple service request texts on an internet platform, this somewhat eased things because the textual structure is quite similar when someone wants certain types of services.

In other words, my corpora would be very restricted and would need a very large degree of specialization but in a single domain with a singular corpus.

If it were a type of text that required a very high degree of specialization as a constitutional, legal, or scientific text, in this case, I would have to go a little beyond A2 just as an initial prerequisite.

Obviously, this is not a mandatory requirement, but I see that understanding the language represents 20% + performance of your model that you have only by understanding what makes sense or not in a sentence or even in the form of preprocessing.

Here is the simple tip: Respect the complexities of language, and if the language is one that you are not native respect more and try to understand its structures first before the first line of code. I will explore the language a bit more in a few topics later in this post.

First, let’s take a look at the MyHammer case.

Context: Classification as a triage for a better match between tradesman and consumers

For those who don’t know, MyHammer is a marketplace that unites between Craftsman and consumer that needs some home services with the best quality.

Our main objective is defined by Craftsman receiving relevant jobs to work on and consumers placing jobs that need to be done and receiving good offers for high-quality services.  

To reach that goal, we need to deliver the most suitable job for each craftsman considering their skills, availability, relevance, and potential interestingness in terms of economics.

Our Text Classification project enters in that equation for re-label some jobs that are in different categories inside our platform and help those matches happen. 

In summary, our data hold the following characteristics: 

  • 200+ classes
  • Overlap of keywords between several classes
  • Past data mislabel, and as we created new classes, we didn’t correct the past (this phenomena I call as “Class Drift
  • Tons of abbreviations
  • Hierarchical Data (Taxonomy)  in terms of Business but not related at all in terms of language semantics
  • A lot of classes with 1000+ words per record
  • Dominance of imbalanced data (Top 10 categories have 26% of all data, Bottom 100 has less than 10%)
  • Miscellaneous classes that englobe several categories, and with that arising the entropy interclass

With this scenario, we made the first hard decision about the project that was to invest at least 95% of our time in understanding the language across each class and building a strong pre-processing pipeline. 

In other words: If we understand well our language inside our corpora, we can leverage that even using plain vanilla models to our advantage.

With that in mind, we jump to understand better our language instead of starting to use algorithms and hoping for some very complex algorithm to work. 

Language is Hard

Language is not hard. Language is very hardI don’t want to enter much in details around some hype about it  and the brittleness of the State of the art. 

But personally speaking I strongly believe that we’re far away even to be near to solve that kind of problem that involves language in terms of conversation or even for machines to generate texts sufficiently good enough to pass in a simple essay. 

Language contains tons of aspects and complexities that makes everything hard. In this very good post of Monkeylearn are described some of those complexities like:

polysemy: words that have several meanings

synonymy: different words that have similar meanings

ambiguity: statement or resolution is not explicitly defined, making several interpretations plausible.

phonology: systematic organization of sounds in spoken languages and signs in sign languages

morphology: study of the internal structure of words and forms a core part of linguistic study today.

syntax: Set of rules, principles, and processes that govern the sentence structure in a given language

semantics:  study of meaning in language that is concerned with the relationship between signifiers—like words, phrases, signs, and symbols—and what they stand for in reality, their denotation.

To understand in depth, one of these aspects in depth would demand at least a master’s degree in full time, at least.

The point that I would like to make here is that knowing those aspects and understand that language is hard.

From the beginning, our strategy was to start some statistical approach first to prune out non-relevant words in our corpora. After the heavy-lift work has been done, we would jump to the language/symbolic approach to fine-tune the corpora before going to train models to get a more safe side in terms of NLP modeling. 

Symbolic or statistical, what’s the best approach?

There’s a huge discussion about Symbolic versus Statistical approaches for language occurring nowadays.

Some proponents about the Statistical as a main paradigm are  Yann LeCun and Yoshua Bengio, and on the other side of the debate, it’s Gary Marcus. There are some resources available and some debates about that like this one between LeCun and Marcus  and this thread about it.

For practitioners that are daily in the trenches I would suggest pragmatism and use all tools and methods that solve your problem in an efficient and scalable way.

Here at MyHammer, I adopted a statistical approach for heavy lift work and some language ruling for tuning. Here the quotes from Lexalytics that I like about it: 

[…]The good point about statistical methods is that you can do a lot with a little. So if you want to build a NLP application, you may want to start with this family of methods[…]

[…]Statistical approaches have their limitations. When the era of HMM-based PoS taggers started, performances were around 95%. Well, it seems a very good result, an error rate of 5% seems acceptable. Maybe, but if you consider sentences of 20 words on average, 5% means that each sentence will have a word mislabeled […]

Source: Machine Learning Micromodels: More Data is Not Always Better

Yoav Goldberg in the SpaCy IRL Conference gave a great talk called “The missing elements in NLP”  where I think he excelled in say that as we move from a more linguistics expertise to a more Deep Learning approach to model NLP, we’re going in a path to have less debuggability and a more black-box approach. We can see better this in the following slide:

NLP Tomorrow

After that, I took a very hard decision to stay in some tool for training the Text Classification model that can provide me a certain minimum level of debuggability and transparency. That’s why in the beginning I choose Facebook FastText.

I know that FastText deals with neural networks internally, but as FastText relies a lot on the WordNGrams if I needed to debug some result or convergence problem in the classes I could use simple data analysis to explain why we’re getting some rogue results.

The strategy here was: Let’s do a very extreme pre-processing approach in our corpora to get the leanest corpora as we can, and after this optimization, we can play with different models and see what’s going on.

To exemplify that this figure from Kavita Ganesan explains our point:

Level of Text preprocessing. Source: All you need to know about text pre-processing for NLP and Machine Learning.  https://www.freecodecamp.org/news/all-you-need-to-know-about-text-preprocessing-for-nlp-and-machine-learning-bc1c5765ff67/

Some specifics in Pre-Processing for the German language

Umlauts (ä, ö and ü)  and Encoding

Long German Nouns

As we know that these long nouns can appear according to the situation inside of class our strategy was to analyze the WordNGrams and TF-IDF scores and see the relevance of these words. If word is relevant we did use some rules to break down those words and keep it, if not, remove of the vocabulary.

Part-of-Speech Tagging

  • We used  Part-of-Speech tagging to remove some words of our corpora. Our strategy consisted in a) always keep the verbs, b) placeholder usage to abstract data entities inside our text (ex: In our domain we use placeholders like sqm (square meters) and this placeholder gives some information gain in all particular classes that contains this word; c) as pronouns and conjunctions in our case most of the time do not contains any meaningful info we did cut it out.

For whom is interested in some examples, this table from NLTK is a good start:

German examples. Source: NLTK Universal Part-of-Speech Tagset

Stopwords: Analyze first, cut after…

One of the biggest endeavors in the project was to find a very nice library with consolidated German corpora in a word where the majority of the implementations and SOTA algorithms it’s crafted to English and first citizen language.

(Short Note: this blog post was written in July/2019. During this time we had a great evolution of NLP libraries in German. However, as we consolidate all ideas contained here in our own NLP library we decided not be dependent of those libraries anymore.)

As our task was only a multi-label text classification and we had a good amount of data, we decided to perform an extreme cut out of stopwords because as we’re not going to do any posterior application that heavily relied upon sequence like LSTM or seq2seq, we wouldn’t need to”save words” for our classifier. This gave us more room to use a very unorthodox approach.  

In the work of Silva e Ribeiro called “ The importance of stop word removal on recall values in text categorization“,  the authors showed a positive relation between stopword removal and recall, and we follow that methodology in our work. 

If I could give a specific advice in that matter I would suggest using only out-of-the-box stopwords lists from those packages only if you don’t have time at all to perform analysis in your corpora. Otherwise, always perform the analysis and create your personalized list.

To make my point clear about this matter, I’ll use the example from Chris Diehl.

Chris Diehl in his post called  “Social Signaling and Language Use” provided a linguistic analysis in an e-mail from a company called Enron. This company was involved in a gigantic case of finance/corporate fraud and the whole story it’s described in the documentary “Enron: The Smartest Guys of the Room”.

The analysis consisted of discovering if there’s an existence of a manager-subordinate social relationship.

The original e-mail is presented below:

Doing a skim read, we can see that there’s a clear social relationship that characterizes a subordination. However, if we give this same text to a regular stopwords package, this will be the outcome:

As Chris Diehl pointed out, the terms that matter in this message are function words, not content words. In other words, the removal of these words could mischaracterize the whole message and meaning. For more, I suggest the reading of the entire article written by Chris. 

A great post about the differences in stopwords across several open source packages was made by Gosia Adamczyk in the post called Common pitfalls with the preprocessing of German text for NLP where she showed some differences between those packages.

Source: Common pitfalls with the preprocessing of German text for NLP, Gosia Adamczyk 

The key takeaway that we got here was: Trust in the stopwords from packages but check and if it’s necessary to mix all of them and use some information about your domain to enhance it.

Stopwords as Hyperparameters

In our project, as we’re started to go deeper into our corpora, we discovered very quickly that the normal list of stopwords not only was not suitable for us, but we needed to consolidate the maximum of them to remove from our corpora.

This was necessary because as we’re dealing with such amount of text, we wished to reduce the maximum amount of training time, and having lean corpora was mandatory to deal with that.

The main problem that I see in the current stopwords lists is that it is built on the top of tons of texts that it’s suitable for general purposes (e.g. German Senate corpora, Wikipedia Dump, etc.) but when we need to go in specific domains like Craftsmanship, the coverage of those stopwords lists wasn’t enough and not knowing that caused us a huge source of inefficiency.  I talk about this later on in how the German city names almost broke our classifier.

We followed a strategy to analyze the results and if we noticed some improvement and in the model performance or in gains in processing time, we add more stopwords in the list. Roughly speaking it was kind of “stopwords list as hyperparameters

This example from Lousy Linguist translates my point:

Source: https://twitter.com/lousylinguist/status/1068285983483822085

A single example that occurred to us in how a lack of personalized stopwords list almost broke our classifier.

In some point of time our models started to give very strange results when we received in the text the name Hamburg or München. Basically everytime that a model received those words, the model always gave a single service as a prediction. In other words, it was a clear case of overfitting.

Long story make short: We discovered that when the customers placed a service request for our Craftsman with those two cities in the text, our classifier always returned the same class, no matter if there’s another subset of words contained in the request (i.e. it makes totally sense since when someone needs to move most of the time the cites are placed).

A single example to illustrate that:

  • I would like to move my piano from Hamburg to Köln (Moving service)
  • I would like to paint my apartment. I’m located in downtown Hamburg. (Painting service)

The problem was that for the second case we always ended with the Moving Service classification.

The solution here was to debug the model analyzing the WordNGrams composition for this service, including the cities as stopwords and after that everything worked well. 

There’s no exhaustive list, but if I can to give some hints, I would classify the stopwords lists like this:

  • German states and citiesbayern, baden-württemberg, nordrhein-westfalen, hessen, sachsen, niedersachsen, rheinland-pfalz, thüringen
  • W-Fragewer, was, wann, wo, warum, wie, wozu
  • Pronounsdas, dein, deine, der, dich, die, diese, diesem, diesen, dieser, dieses, dir, du, er, es, euch, eur, eure, ich, ihm, ihn, ihnen, ihr, ihre, mein, meine, meinem, meinen, meiner, meines, mich, mir, sie, uns, unser, unsere, unserem, unseren, unserer, unseres, wir
  • Numbersnull, eins, zwei, drei, vier, fünf, sechs, sieben, acht, neun, zehn, elf, zwölf
  • Ordinalserste, zweite, dritte, vierte, fünfte, sechste, siebte, achte, neunte, zehnte, elfte, zwölfte, dreizehnte
  • Greetingsciao, hallo, bis, später, guten, tag, tschüss, wiederhören, wiedersehen, wochenende, hallo
  • Clarification wordsdass, dafür, daher, dabei, ab, zb, usw, schon, sowie, sowieso, seit, bereits, hierfür, oft, mehr, na

Some lessons along the way…

We had some lessons along the way for our case. The point here is not to define any truths but only exemplify that during the process of data analysis and perform experimentations with our dataset we found something totally different from what we hear about NLP and text pre-processing.

I divide those sections between what did work and what didn’t work.

What didn’t work
  • Lemmatization: Here I think it was the most surprising takeaway. Using Lemmatization as a common  “best” practice, we had 3% decrease in the accuracy in Top@5. The reason behind that was because some categories have some specific subset of words that makes them distinguishable and the Lemmatization was causing an involuntary category binning“. For example, We have some services that despite to have some similarity words in our corpora like  Wunschlackierung (Desired painting), Lackaufbereitung (paint preparation), Unfallreparaturen (accident repairs) and Kratzer im Lack (scratches in the paint); the Lemmatization caused a great loss in the distinguishable aspects of the corpora of a class and as consequence, we faced a harm in the algorithm performance. To solve this problem, we removed the Lemmatization of our pre-processing pipeline.
  • Lemmatization was too slow for our data: Another point that was a deal-breaker is that Lemmatization, even using a very good API as Spicy, took ages for our data in the beginning. Our dataset contains millions of records with text fields that contain dozens or hundreds of words for each line. Even using a 128 CPUs machine took a long time, and as we got this decrease in the model performance, we abandoned that approach. [Note: In the beginning, we used a previous version of spaCy that didn’t contain several improvements in comparison with the current version] 
  • Hyperparametrization of FastText: on the start of this project, we relied a lot in FastText, and we didn’t regret of that. But a huge limiting factor is that FastText has only a few number of meaningful parameters to use as hyperparameters, and here I’m specifically talking about the parameters  Window Size and Dimension that in our data  didn’t show any sign to be meaningful and/or useful in some strategy of grid-search or tuning. 
  • Spark for data pipeline and model building and training: Spark is a cool tool for Data Processing and I really like it, but for NLP all integrations that uses native Scala didn’t provide a minimum in terms of libraries, flexibility and easiness to use for us to rely upon for text pre-processing in German language. I personally think that text processing using linguistic features is one of the weakest points in Spark libraries. We’re still using it, but only for data aggregation and to dump from one place to another.
What worked
  • Own library of Pre-Processing and  Personalized list of Stopwords: This can sound a bit as over-engineering, but this was the only way for us to get the maximum of flexibility for our needs. We just unite all the best of all tools for German NLP like stopwords list, stemming, PoS and so on and craft something that can save us tons of time.
  • Hierarchical Models: This one deserves a special blog post, but for us using the natural ontology of the classes helped a lot in terms of accuracy in Top@5 because using a quasi segmented architecture for our models we naturally prune out the clashes between classes non related but that contains some words in common.
  • Using EDA + TF-IDF score to remove low frequent words: We learned that, for our case, removing low TF-IDF score words helped a lot in terms of experimentation without sacrificing performance. In the beginning, I just create a full list of TF-IDF and as I’ve included more words, I just monitor the performance to see if there’s some loss. My heuristic was: Get the minimum amount of words as possible with a tolerance of model performance in no more than 1%. This can look quite hard metric but at least in the beginning I was more concerned to have very lean corpora instead to have a good performance and a model more complex and brittle.
  • The oldie but Goldie Regex: This worked way better than Lambda and map() in Python for word replacement. The takeaway here is to use lambda and map only if it’s really necessary. 
  • Word Embeddings: We didn’t use embeddings before to force us to stress all possibilities with our corpora and hyperparameters. However, the loss in performance that I had with the removal of low IDF score words and using a personalized list of stopwords, I gained back just plugging embeddings in the model using FastText.

The key takeaway that we took was: Use the best practices as a start point but always check some alternatives because our data can have some specificities that can generate better results.

Final Remarks

If I have to do a wrap-up of all of these things, I would highlight as the main ones:

  1. Recognize your own language and jargon: For us understand that we’re dealing, i.e. web-based texts in the context of Craftsmanship and their language helped us to perform a better pre-processing that enhanced our models;
  2. Make your own corpus/vocabulary/embeddings: Out of the box stopwords lists and common text pre-processing helped a lot but in the end of the day we needed to create our own stopwords list and we used some low TF-IDF score stopwords list to prune out unnecessary words and reduce our corpora and consequently reducing the training time
  3. Do not automate misleading data: In our case one of the capital mistakes in the beginning was to try to automate the data pipeline and cleaning without considering some specifications of our case like jargon and abbreviations. It means: In Text Classification data pre-processing is gold, models are silver. 

Libraries for German NLP

  • German_stopwords: Last commit 3yo, but contains a good set of loosen words and abbreviations  
  • DEMorphy: morphological analyzer for German language (several types of Tagset for dictionary filtering)
  • textblob-de by markuskiller: PoS
  • GermaLemma: First one that uses PoS before Lemma (Spacy will do that out of the box in next version)* tmtoolkit: Wrapper with PoS, Lemmatization and Stemming for German. Good for EDA with Latent Dirichlet Allocation.
  • NLTK: There’s a small trick to use PoS with German
  • StanfordNLP: (for future test)
  • SpaCy: NLP with batteries included (syntactic dependency parsing, named entity recognition, PoS)
  • Python Stopwords: Generic compilation of several Stopwords 

REFERENCES

Useful Links
Papers
Books
A small journey in the valley of Natural Language Processing and Text Pre-Processing for German language

Por que acredito que no Brasil estamos entrando em uma bolha de Data Science?

TL;DR: Faça uma avaliação racional e pragmática baseada em fatos e informações de mercado antes de escolher uma mudança de carreira ou até mesmo alternativas de investimento em qualificações em Data Science & Machine Learning. 

Eu sei que nos dias de hoje o que eu estou falando pode parecer ser contraintuitivo, ao mesmo tempo que temos empresas anunciando que vão contratar 100 cientistas de dados em um único ano (mesmo sem nenhuma justificativa sólida do porque e principalmente do retorno esperado com essas contratações), ou quando o volume de buscas em “ciência de dados” aumenta em mais de 50% em menos de 3 anos, ou em uma busca simples podemos encontrar mais de 1.500 vagas disponíveis no LinkedIn.

Claro que a minha opinião não é a mais popular atualmente, mas o que eu vejo hoje são os mesmos padrões das bolhas passadas em tecnologia no Brasil.

Quem não se lembra da bolha de Certificações Microsoft? (alguém lembra do famigerado programa Maestro de SQL Server?)

Ou da bolha de certificações Oracle?

Do cursos da dobradinha campeã PHP e MySQL que prometiam os maiores salários do mercado?

Ou das promessas do eldorado de salários e empregabilidade como Web Designer com os cursos de Corel Draw, Dreamweaver e Flash?

E os cursos de frameworks ITIL, COBIT, TOGAF ou BABOK que nos prometiam cargos maravilhosos apenas gerenciando processos de negócios? (Sendo que muita faculdade e cursos livres igualmente picaretas ao invés de ensinarem o básico de código, jogaram uma geração inteira de universitários em frameworks de “gestão” sendo que parte deles não conseguem gerenciar nem mesmo a própria vida financeira).

Tudo o que eu vou colocar aqui diz respeito ao lado da oferta de cientistas de dados, e não da demanda propriamente dita (i.e. isso merece um post especial, mas na prática pouquíssimas empresas sabem o que estão contratando e tem uma galera contratando somente para sinalização). 

Desta forma este post vai ser muito mais voltado para gestão de carreira do que para um retrato de mercado. 

Longe de ser algum tipo de coach ou algo do semelhante, o meu objetivo vai ser convidar o leitor para uma reflexão sobre alguns aspectos que eu julgo como importantes de acordo com algumas observações que eu tenho feito no mercado como um todo de forma empírica.

Alguns aspectos que sinalizam que talvez estamos em uma bolha de Data Science e Machine Learning na minha visão são:

1) O Retorno do Investimento (ROI) em uma formação em DS em relação aos salários não compensa: Eu vou tirar desta análise os MOOCs e cursos sensacionais como o da Fast.ai do Jeremy Howard por um motivo muito simples: Nós no Brasil adoramos um diploma e o nosso sistema educacional foi moldado de uma forma que não promove o autodidatismo, mas promove um modelo baseado em tutoria em que o professor não é um facilitador mas é o responsável e guardião do  conhecimento em si. O que eu quero dizer é que esta análise do ROI vale exclusivamente para cursos de pós-graduação e cursos de extensão. Eu fiz uma pequena pesquisa em alguns cursos e existem algumas formações que custam mais de R$ 30.000. Nada contra o valor em si, porém, vamos falar que o retorno esperado deste investimento seja de 4 anos. Isso dá R$ 625/mês por 4 anos. Em outras palavras: desde o dia em que o curso termina, o nosso recém-formado já precisa de um aumento de pouco mais de R$ 600 só para ficar no empate em relação a sua formação. Ah, e lógico fora o custo de oportunidade do tempo (e.g. tempo de aula, deslocamento, alimentação, etc) e o custo de oportunidade do dinheiro (e.g. usar esse dinheiro em algum investimento, fundo de ações, etc). Tendo em vista uma recessão brutal que tivemos nos últimos anos (e com a renda praticamente estagnada) eu consideraria muito bem uma decisão de investimento (ou endividamento) desde porte sem uma perspectiva de retorno de no mínimo de 2 anos.

2) O mercado está cada vez mais competitivo e a barreira de entrada está quase nula e ao mesmo tempo que isso é bom, isso pode ser um problema. Se eu tivesse que descrever o mercado a frase que mais se assemelha de como eu vejo é Bellum omnium contra omnes, ou guerra de todos contra todos. Se você é Cientista da Computação, você vai competir com Econometristas que sabem mais de modelagem do que você; se você é Estatístico vai competir com Cientistas da Computação que codam mais que você; se você é Econometrista vai competir com Estatísticos que dominam um ferramental matemático/estatístico melhor do que o seu, e todos eles vão competir com pessoas com mestrado e doutorado. O ponto aqui é que a competição vai ficar cada vez mais brutal e isto no longo prazo não é escalável em termos de carreira dado que são disciplinas que demandam tempo para o aprendizado.

3) Assim que o mercado começar a ter a desilusão com contratações erradas e as frustrações corporativas com os cientistas de dados começarem, os processos seletivos vão ficar mais acirrados e não haverá bons espaços para todos. Uma coisa que eu aprendi como contratante em um período da minha carreira foi: A cada decepção devido a uma contratação errada duas medidas brutais entravam em cena: a) os requerimentos, testes e as exigências aumentam muito mais e b)  salário aumentava em proporção dado que a exigência seria a maior caso o candidato fosse aprovado. O que eu quero dizer aqui é que as posições vão começar a sofrer uma escalada insana de habilidades para as melhores posições e o mercado vai ser dividido em “posições boas” e “posições ruins”. Em outras palavras: entrar como Data Scientist ganhando abaixo do mercado só fazendo consulta em SQL e mexendo em macro no Excel é fácil, o difícil é ir para a tigela dos altos salários e usar ferramental moderno para resolução de problemas difíceis.

4) A enxurrada de gente sem a mínima ideia do que é Ciência ou Dados entrando na área: Pensa bem, neste exato momento milhares de pessoas estão entrando no campo sem nenhum tipo de ideia do mercado por conta de hype, influencers, mídia e outras fontes que prometem o eldorado dos altos salários ou descrevem Data Science como a profissão mais sexy do século 21. Isto não é escalável e uma hora grande parte dessas pessoas que estão se aventurando vão ter uma desilusão muito grande dado os motivos no item 3) ou mesmo quando as empresas darem conta que o maluco do Excel que está a milhares de anos da empresa manja mais do negócio e dos números do que toda a galera que fica fazendo script copiado do Towards Data Science no Macbook Pro de retina display com sticker de conferência gringa.

5) Vocês acham mesmo que repentinamente todas as empresas do sistema solar nunca ouviram falar de análise de dados básica ou estatística básica? Que somente agora elas acordaram de um torpor dos últimos 25 anos e elas decidiram que precisam de unicórnios mandados dos céus para salvar as empresas da falência através de insights gerados dos dados? Com ou sem cientistas de dados todos os dias no sistema solar negócios são fechados, vendas são feitas, pessoas compram coisas, e o dinheiro circula de uma mão para a outra.

Mas vamos supor que você me veja como um vendido ou uma pessoa com interesses ocultos por conta do que eu disse. Dessa forma, não acredite em mim, mas sim nas pessoas e instituições atores abaixo; eles sim sabem o que é melhor para a sua carreira:

a) Universidades e alguns professores de cursos de extensão e pós-graduação: Se a sua principal fonte de receita dependesse de um maior volume de alunos possível ou se a sua instituição servisse como o proxy predileto para contratação, você anunciaria que estamos em uma bolha educacional (onde já temos o incrível advento dos diplomas inúteis no Brasil) ou surfaria na onda e ofereceria cursos caça níquel que estão no mínimo 3 anos atrás do mercado? Uma coisa que eu vejo muito são instituições que estiveram dormindo por mais de 5 anos em relação a ciência de dados ou dados em geral que repentinamente abrem um curso de Data Science e Machine Learning com um corpo docente que nunca foi do mercado e com grades que não correspondem com a realidade do que está sendo feito nas empresas e nem cobrem aspectos básicos que todo cientista deveria saber. Novamente não precisam acreditar em mim: Vá nos cursos de extensão e pós-graduação e veja quais deles estão ensinando fundamentos de estatística básica, inferência causal, cálculo, álgebra, etc.

b) Mídia: Não se engane, por trás de todo anuncio de empresas contratando inúmeros cientistas de dados existe um submundo de matérias pagas para gerar algo chamado Brand Awareness. Inúmeras empresas usam a mídia para veicular matérias e “notícias” que favoreçam essas empresas (e.g. a revista faz uma matéria positiva sobre a empresa  A e meses depois uma empresa subsidiada da empresa A paga um anúncio de 300 mil reais para a mesma revista) para dar a percepção de que aquela empresa é legal e que faz coisas incríveis, quando na verdade está apenas vinculando a sua marca positivamente enquanto as vagas reais mesmo estão fechadas (isto quando estas vagas existem).

c) Papo de conferência: Eu como um bom rato de conferência tenho que dar o braço a torcer em que eu caí muito nisso no passado. Eu ia na conferência e deslumbrado com a tecnologia eu já fazia um plano de estudos para a nova ferramenta para Data Science ou Machine Learning. Você volta energizado da conferência para a sua empresa com a expectativa de implantar aqueles cases maravilhosos, mas na realidade você termina fechando ticket de tarefa sem sentido no JIRA.

d) Influencers, tecnologistas e afins: Estes adoram quando você fica mudando de tecnologias assim como muda de roupa por um motivo simples: isto dá mais views no youtube, mais comentários sobre o que está “trendando”, mais artigos em seus blogs no Medium, e mais do que isso: eles ganham credibilidade somente mostrando o que fazer e não fazendo algo corporativamente ou academicamente e pior – tudo isso sem nenhum tipo de risco envolvido caso o que eles recomendem de errado. Ciência e Engenharia são coisas que andam lentamente mas com passos firmes. Uma troca de tecnologia ou mesmo a adoção de novas ferramentas (ao menos em empresas sérias) é um processo que pode levar anos ou deve ter um motivo muito razoável para acontecer. Eu trabalhei em uma empresa que o software de fila só foi trocado depois de muitos problemas de instabilidade, e mesmo com um novo sistema o nosso “failback” ainda ficava nos velhos e sempre confiáveis arquivos texto. O ponto que eu quero deixar aqui é: ao ver influencers, tecnologistas e afins falando sobre novas ferramentas e frameworks que você precisa aprender, desconfie e entenda que as empresas não vão sair de tecnologias estabelecidas em que elas tem corpo de conhecimento e know-how para embarcar na sua aventura como Cientista de Dados “antenado” com o mercado. 

e) Amigos e colegas que já estão no mercado: Uma pequena parte desta galera nunca vai admitir que estão em uma bolha por um motivo simples: esta galera acredita realmente que são excelentes e merecedores de suas posições e salários nababescos. Entretanto, parte destes colegas esquecem dos fatores causais não identificados que os levaram a ter a posição que eles têm hoje. Por exemplo, pode ser tempo de casa, tempo na posição de DS antes do hype do mercado, track record na empresa não relacionado com DS, ou mesmo falta de alguém que tenha um conhecimento técnico mas que também conheça o negócio. E lógico não vamos esquecer que nós seres humanos somos ótimos em subestimar o papel do acaso e da sorte nas nossas vidas. No final do dia somos seres humanos e amamos uma narrativa romantizada da realidade. Agora cair na narrativa é questão de escolha.

Por fim eu quero colocar algumas mentiras que as pessoas contam sobre Data Science e Machine Learning:

As formações não estão caras: Parem pra pensar: uma formação não é somente o valor pago, mas sim o custo de oportunidade e mais o tempo que vai ser investido como eu coloquei anteriormente. Ciência de Dados hoje é uma área muito dinâmica que está mudando muito rápido. Ferramentas em menos de 1 ano ganham direcionamentos totalmente diferentes. Será que vale a pena investir 18 ou 36 meses pagando mais de R$ 1.500 em uma formação e ao final praticamente todos os frameworks ensinados já estão sendo descontinuados ou em outras versões? Tudo isso para que? Conseguir um emprego em uma vaga júnior ou no máximo ter R$ 350 de aumento sendo que você gastou R$ 40 mil em uma formação? Não parece um ROI ideal pra mim.

Há um déficit de cientistas de dados no mercado: Isto é parcialmente verdade. No caso, inúmeras empresas precisam modernizar a maneira com a qual elas fazem análise, dado que algumas ainda estão no paradigma descritivo ou diagnóstico e querem ir para a parte preditiva e prescritiva. Contudo, a não ser que você trabalhe em empresas que realmente estão usando os dados em seus produtos como por exemplo Nubank, Quinto Andar, Pipefy, Movile e etc, grande parte das empresas ainda precisam sair do excel e do combo “média-desvio-padrão-correlação-gráfico-de-pizza”. E não existe absolutamente nada de errado com isso. O ponto e o que o mercado pensa que um cientista de dados tem habilidades de Data Engineer, Data Scientist, Software Engineer, DBA, analista de requisitos tudo no mesmo papel. Então se você pensa que vai chegar na sua posição de Cientista de Dados fazendo análises  em dashboards como no Minority Report enquanto toma um delicioso vinho italiano enquanto escuta Toccata em Ré menor de Bach enquanto tem os seus insights, eu tenho uma péssima notícia: Isto não vai acontecer. No melhor dos casos você vai ficar brigando com DBA para ter acesso no banco de dados, vai encontrar muita defensividade de analistas que não tem um emprego tão sexy quanto o seu, e sem o conhecimento de negócio você vai ser sempre colocado de escanteio pela galera do Excel (esse relato antológico mostra bem isso).

Eu preciso ser Data Scientist para dar certo na vida: Existem muitas coisas bacanas em engenharia fora de Data Science que são tão importantes quanto como DevOps, Site Reliability Engineering, Front/Backend Engineering, Data Engineering, Automation, Incident Response, Mobile Development, Security, Infraestrutura, etc. E acredite são posições que pagam muito bem, exigem conhecimentos que são difíceis de serem adquiridos e sempre terá demanda e impactam diretamente no negócio.

Considerações Finais

Lendo esse relato que beira o pessimismo extremo alguns podem falar “mas poxa, então eu não devo ir para a área de Data Science?” a minha resposta e “vá, mas entenda os principais problemas da área e saiba que este não é o único caminho.Tenha em mente que frustrações de expectativas nos aspectos profissionais e financeiros podem ser uma realidade e poucas pessoas estão falando sobre isso”. Eu penso que sempre haverá espaço para bons profissionais, independente do que fazem e empresas boas sempre contratam pessoas boas mesmo se não tiver o budget, a vaga, ou mesmo a posição propriamente dita. Espero que esse pequeno relato tenha jogado um pouco de racionalidade e luz sobre a carreira em Data Science e sirva ao menos para uma reflexão.

Notas

[1] Este post foi descaradamente inspirado no relato de 2010 do Bolha Imobiliária de Brasília que viu o principal problema no seio da economia brasileira em relação à bolha de crédito.

[2] Este escriba recusa-se (ao menos aqui no blogue) a adotar o novo padrão de escrita da internet sentenças com no máximo 7 palavras muito simples. Como eu acredito que os leitores deste blogue são pessoas de capacidade cognitiva avançada, as sentenças vão ficar complexas. Reduzir a complexidade para uma linguagem que promove o emburrecimento dos leitores nunca foi e nunca será o foco aqui.

Por que acredito que no Brasil estamos entrando em uma bolha de Data Science?

Frutas no corredor e viés de disponibilidade em Sistemas de Recomendação

Como alguns dos meus 3 leitores sabem, eu venho fazendo alguns cursos totalmente fora da área de Data Science/Machine Learning como Investigação de Acidentes Aéreos, Medicina Baseada em Evidências, Econometria e Inferência Causal.

Eu vou escrever no futuro sobre o porque eu estou tomando esses cursos (motivo: porque penso estamos em uma bolha de DS/ML); mas o ponto que eu quero colocar aqui é que um dos princípios fundamentais de Medicina Baseada em Evidências está muito relacionado ao entendimento do nível de incerteza e como determinar um tratamento (e principalmente não tratar ninguém se o resultado for frágil).

No momento em que pensamos sistemas de recomendação, sempre imaginamos os cases de sucesso como Netflix, Spotify, Amazon entre outros, em que os sistemas de recomendação alavancaram essas empresas ao sucesso.

Porém, alguns aspectos causais e/ou ausência de entendimento dos fatores aleatórios geralmente não são muito discutidos de forma mais profunda. E esta ausência de discussão também é refletida em papers de conferências importantes como WWW, ACM RecSys e ICML, só para dar alguns exemplos em que tem muita coisa sobre resultados mas fala-se pouco de aspectos de incerteza que possam ter influenciado estes mesmos resultados.

Como consequência podemos ser influenciados a adotar uma postura voltada para os resultados desconsiderando o fato de que estes resultados podem ser fruto apenas do acaso e não de uma competência intrínseca do nosso trabalho.

Neste post eu vou falar especificamente de um fator de incerteza em Sistemas de Recomendação que é o viés de disponibilidade. Este é um problema real que pode acontecer em sistemas produção, em especial em protocolos de avaliação de modelos/sistemas. No final eu vou discutir uma alternativa que minimiza esse problema.

No entanto vamos entrar em uma situação hipotética para exemplificar esse ponto de uma maneira mais acessível.  

Ideias e frutas no corredor…

Imagine a seguinte cena dentro de um escritório: Existe dentro do escritório um corredor no qual as pessoas usam para locomover-se entre dois pontos. A titulo de simplicidade vamos dizer que este corredor é uma passagem comum como qualquer outra.

Um certo dia o time de Recursos Humanos observa um trabalho acadêmico que atesta uma correlação entre melhoria do ambiente de trabalho com o consumo de frutas.

Ato contínuo, após a leitura deste artigo o time do RH pensa em realizar a seguinte intervenção: “Porque não distribuir frutas aqui no escritório para aumentar a satisfação no trabalho?

Em seguida o time do RH pensa em uma forma de implementar esta intervenção: “Por que não deixar algumas frutas disponíveis em um lugar em que todas as pessoas podem ter acesso? Desta forma todos podem passar e pegar a fruta que quiserem”.

Sendo assim o time do RH coloca uma bandeja de frutas diversas no corredor deixando as frutas acessíveis para todas as pessoas de forma livre.

A ideia é a seguinte: A frutas ficarão disponíveis por 3 semanas no corredor. Após este período uma pesquisa de satisfação será realizada para mensurar o nível de bem-estar no trabalho e o efeito dessa nova política de RH dentro da empresa.  

Mensuração e descoberta de ótimos resultados

Três semanas depois o time do RH realiza a pesquisa de satisfação e o resultado foi um aumento de 70% na satisfação dos empregados, afinal de contas, quem não gosta de fruta de graça no trabalho?

Com este ótimo resultado o time do RH implementa como política permanente as frutas disponíveis para todos os empregados.

É neste ponto é onde vemos cases como “Nossa empresa implantou frutas grátis como política e aumentamos satisfação em 70%”, cases aparecem em conferências de RH, inúmeros cases de sucesso e Fanfics no Linkedin, e em alguns casos a pessoa que é responsável pelo projeto vira Chief People Officer com uma porção de promoções de pessoas da equipe na esteira desse sucesso.

Entretanto, vamos olhar em relação ao que não foi dito.  

O acaso sendo medido como competência

Após seis meses o time do RH verifica um nível de estagnação nos indicadores de satisfação mesmo com a implementação da política das frutas no corredor.

Tendo isto em vista o time do RH e o time de People Analytics inicia uma investigação e resolve realizar alguns experimentos, que aqui vamos chamar de “A/B/C Fruit Corridor Experiment”

Neste caso serão 3 cestas de frutas que serão colocados em dias da semana alternados seguindo as seguintes configurações:

·  Cesta 1: Somente bananas

·  Cesta 2: Somente maças

·  Cesta 3: Frutas sortidas

Após 1 mês de experimentos foram obtidos os seguintes resultados:

·  Cesta 1: Somente bananas – Melhoria de + 1%

·  Cesta 2: Somente maçãs – Melhoria de + 2%

·  Cesta 3: Frutas sortidas – Melhoria de + 1%

Realizando uma comparação simples, temos o seguinte quadro:

Policy (intervenção)Resultado (Satisfação)
Implementação da política das frutas+70%
Otimização e experimentos+1%

Podemos ver neste exemplo que mesmo com mais esforço em termos de análise, experimentação, implementação e otimização o ganho foi praticamente marginal.

O que pode ter acontecido neste caso é que as pessoas podem não ter pego as frutas devido ao arranjo de recomendação provido pelo o RH no teste A/B/C mas sim elas consumiram apenas devido ao fato de estarem disponíveis para o consumo.

Em outras palavras: As pessoas oportunisticamente pegaram as frutas apenas devido ao fato de que elas estavam disponíveis.

Como assim disponibilidade?

Nenhum sistema de recomendação em produção corre o risco de não ter algum tipo de viés de disponibilidade. A ação humana ainda carrega um certo grau de não determinismo, o que significa que não importa quão boa seja a recomendação, algumas pessoas irão interagir com o sistema só pelo fato do mesmo estar disponível.

Em todos os sistemas de recomendação em que há uma utilização de forma passiva (i.e. não mandando ativamente recomendações como push notification, email, etc) pode haver um determinado potencial de pessoas utilizando de forma oportunístico.

Exemplos práticos de aspectos oportunisticos não relacionados com a recomendação em si:

  • Quantas vezes estávamos sem sede, mas paramos em um bebedouro para apenas tomar um pouco de água para nos manter hidratados?
  • Quantas vezes não tínhamos muita coisa para jogar no lixo, mas acabamos dispensando na lixeira mais próxima?
  • Quantas vezes ao passar em um lugar turístico não pegamos uma comida de rua apenas pelo fato de estarmos no lugar?

Estes são casos de utilização oportunística devido a um potencial viés de disponibilidade.  

Uma simples alternativa

Para medir inicialmente o viés da disponibilidade eu gosto de usar sempre um baseline randômico no começo de todo o projeto. Desta forma eu (a) me certifico do papel da aleatoriedade na recomendação (ou de outros elementos causais não identificados e/ou variáveis de confusão) e (b) com essa informação disponível eu consigo mensurar melhor o real impacto de outras variáveis durante a experimentação. Abaixo um exemplo:

Policy (implementação)Resultado
Recomendações aleatórias+10%
Policy #1 (A)+14% (Ajustado +4%)
Policy #2 (B)+12% (Ajustado +2%)

Ou seja, logo de começo eu tenho 10% de viés de disponibilidade não importa a recomendação que seja colocada em tela em um novo sistema. Eu vou tirar esses 10% de performance do meu resultado pois eles serão atingidos só pelo fato de estarem disponíveis.

Desta maneira, apenas com um baseline aleatório eu ja consigo ter parâmetros de comparação para saber o quanto do resultado é devido a disponibilidade.  

Considerações Finais

Ter um viés de disponibilidade em uma plataforma de recomendação não é problema nenhum e as vezes é até esperado em novos sistemas. O grande problema é quando confundem-se os efeitos de disponibilidade e toda a carga de incerteza que este viés pode carregar com efeitos da implementação dos algoritmos de forma propriamente dita. A consideração final que eu deixo aqui é que antes de qualquer implementação de sistemas de recomendação ter o entendimento a priori dos fatores de incerteza e contrafactuais que podem influenciar o resultado.

Frutas no corredor e viés de disponibilidade em Sistemas de Recomendação

A sua empresa é Engineering-First ou Product-First?

Uma das maiores vantagens que eu tive ao longo da minha carreira foi ter trabalhado em diferentes tipos de empresas atuando com Consultoria ou mesmo em empresas do tipo Holding. Acho que esses tipos de empresa te dão um tipo de exposição que é muito difícil de se encontrar em empresas convencionais (e.g. um negócio com uma única vertical).

Contudo, por mais que estes negócios tivessem dinâmicas diferentes, depois de um determinado tempo atuando em cada uma delas, uma distinção bem clara na qual essas empresas utilizavam a engenharia/tecnologia começava aparecer.

Estas distinções que eu via não era somente dentro do campo estritamente técnico, mas sim dentro do aspecto cultural da organização.

E aqui quando eu falo cultura, eu remeto a definição da Wikipedia que usa a citação direta do Edward B. Tylor no qual ele coloca que a cultura é “todo aquele complexo que inclui o conhecimento, as crenças, a arte, a moral, a lei, os costumes e todos os outros hábitos e capacidades adquiridos pelo homem como membro de uma sociedade“.

Transpondo essa definição para o contexto corporativo, a cultura de uma empresa é determinante na forma na qual as pessoas são contratadas, na avaliação dos comportamentos esperados, estrutura de incentivos e principalmente na forma em que as decisões são tomadas e os rumos estratégicos são decididos.

Estas abordagens que em um primeiro momento podem parecer coisas com baixa relevância, tem um peso muito grande seja para as organizações e principalmente para as pessoas.

Ao final deste post eu pretendo deixar clara a ideia de empresas com abordagens Engineering-First e Product-First, falar um pouco das suas vantagens e desvantagens e do porquê isso é relevante em termos de gestão de carreira [1], [2].

A ideia aqui não é falar que abordagem A é melhor que B; mas sim fazer um paralelo e mostrar um pouco das distinções entre essas abordagens e as vantagens e potenciais desafios.

Engineering-First e Product-First: Duas abordagens

Disclaimer: Diferente do que pode parecer essas abordagens não são excludentes. Isto é, a adoção de uma ou de outra abordagem não significa explicitamente que a organização só faz A ou B. Na verdade as melhores organizações são aquelas que conseguem alternar de forma quase que adaptativa estas duas abordagens dependendo da estratégia e o que deve ser alcançado.

Escrevo isto devido ao fato de que uma coisa comum que eu venho notando em algumas discussões com alguns colegas é que a falta de conhecimento do tipo de empresa que está se entrando juntamente com o fato de falta de autoconhecimento acaba levando a uma espiral de frustrações ao longo do tempo em que sempre os dois lados estão insatisfeitos e com isso levando ao inevitável boreout, pedidos de demissão, stress corporativo e similares.

Em um caso a parte de produto fica frustrada por conta de constantes atrasos e reclamações, seja a parte de engenharia devido a falta de uma correta conceptualização do projeto ou escopos. Isto é, uma situação de eterno atrito.

Desta forma entender os tipos de empresas é o primeiro passo para sair desta espiral de frustrações e atritos. Mas primeiramente vamos a algumas características relativas a cada tipo de abordagem.

Paradigmas e Características

Empresas Engineering-First

Empresas Engineering-First são empresas que têm na sua engenharia e capacidade de inovação tecnológicas as suas principais vantagens comparativas. Geralmente por causa desses fatores algumas destas empresas são muito dominantes chegando às vezes a construírem monopólios quase que invencíveis (e.g. Siemens, Airbus, Google, Facebook, Amazon) devido de que estas empresas usam novos métodos (ou métodos clássicos) de engenharia e tecnologia não para alavancar o seu Core Business mas como forma de sobrevivência.

Geralmente são empresas que trabalham com plataformas/infraestrutura como principais serviços, ou empresas que têm a engenharia no centro do processo de inovação e conceptualização dos produtos. Ou seja, o negócio é voltado para a incorporação de tecnologias de ponta nos produtos ou formas mais eficientes de se fazer algo via otimização.

Em linhas gerais, aqui a abordagem é a engenharia vai fazer e o produto vai colocar em uma embalagem mais atraente e deixar o sabor mais palatável. Algumas das vezes estas empresas Engineering-First trabalham oferecem plataformas/infraestrutura do tipo missão crítica e produtos de alta complexidade tecnológica que não tem uma interface direta com o cliente final comum.

Essas empresas têm a engenharia/tecnologia em seu core business e uma estagnação ou defasagem tecnológica pode ser a diferença entre a glória e a bancarrota.

Alguns exemplos de empresas Engineering-First poderiam ser a Honda e Mercedes-AMG na parte de motores de Fórmula 1, Huawei na parte de plataformas de telecomunicações, Amazon Web Services no que se refere à parte de infraestrutura e a Airbus na parte aeroespacial.

Essas empresas têm uma cultura de engenharia extrema em que a otimização é levada ao extremo e pequenos ganhos fazem toda a diferença no resultado final, o que de certa maneira coloca uma pressão muito grande no grau de especialização necessário para fazer isso acontecer.  

Empresas Product-First

Já as empresas Product-First caracterizam-se por serem organizações em que a tecnologia entra mais como elemento de alavancagem, otimização e escalabilidade para facilitar transações de negócios que não demandam tecnologia de uma maneira direta.

Grande parte das vezes estas empresas são extremamente pragmáticas em relação à tecnologia devido ao fato de que para estas empresas elas têm um problema de ordem maior para ser resolvido e a engenharia seria apenas uma ferramenta/meio para resolver esse problema.

Essas empresas geralmente trabalham centrados no consumidor final desde a conceptualização até a experiência do usuário final, e diferente da abordagem Engineering-First aqui há a inclusão de fatores humanos no resultado final.

Neste caso a engenharia entra nas empresas Product-First apenas para materializar o conceito definido na visão de produto de forma para ou alavancar ou escalar os seus processos de negócios.

Ou seja, a tecnologia pode ser usada para alavancar ou otimizar um processo, mas no final isso tem que fazer parte de um produto específico que irá satisfazer um usuário final, e este é o real Core Business da empresa.  

Alguns exemplos de empresas Product-First poderiam ser Apple no que se refere a parte de computadores pessoais, Microsoft em sua linha de vídeo-games, Netflix na parte de distribuição de conteúdo e Spotify como um marketplace entre artistas e ouvintes de música/podcast.  

A grosso modo, empresas Engineering-First são baseadas na ultra-otimização, escalabilidade, redundância, e do foco nos seus componentes de engenharia/tecnologia como core business; enquanto as empresas Product-First geralmente trabalham em como colocar a tecnologia para resolver algum tipo de necessidade humana através de produtos que as pessoas gostem e usem, ou seja, neste caso essas empresas usam a engenharia/tecnologia como suporte de suas operações e não como foco principal.

Já que temos uma definição do que são as empresas, eu vou tentar colocar alguns pontos do que eu vi ao longo do tempo em relação às vantagens e desvantagens de se trabalhar em cada tipo de empresa de forma não é exaustiva.

Engineering-First: Vantagens e Desvantagens

·  Vantagens

  • Geralmente este tipo de empresa têm uma abertura bem grande para Research/PoCs para incorporação de novas tecnologias seja como novas formas de solucionar problemas latentes ou para ganhar performance e escalabilidade (e.g. Bell Labs).
  • Fine-Tuning/Otimização em algumas empresas são usadas como estratégias que se bem definida pode virar uma vantagem competitiva.  Aqui não tem segredo: cada pequena otimização pode ter ganhos de escala muito grandes (e.g. aumento de throughput de uma plataforma, eficiência de um algoritmo novo de compactação que reduz necessidade de espaço, redes que têm a redução de latência, etc.)
  • Tende a atrair engenheiros com um grau de especialização maior em poucos aspectos, mas com um grau de profundidade muito maior (e.g. aqui seria o equivalente ao engenheiro responsável pela liga de titânio que faz parte da construção das blades de um motor de Airbus A320).

·  Desvantagens

  • Parte do trabalho sempre será invisível, e pior: Este mesmo trabalho tende a mover com um grau de incerteza muito maior e com velocidade que pode ser muito menor em termos de ritmo de entrega;
  • Trabalhar em ambientes voltados para ganhos em otimização pode ser frustrante para quem quer trabalhar com inovações voltadas ao usuário final ou quem está saturado por conta de trabalhar no mesmo assunto por meses com pouco ou nenhum resultado (e.g. gastar 30.000% em pesquisa para ganhar os 3% finais).
  • Muita otimização pode levar a estagnação tecnológica dado que enquanto buscam-se os parâmetros corretos para algo, algum concorrente pode estar trabalhando em algo completamente novo (e.g. Airbus passando a Boeing com um paradigma totalmente diferente de engenharia)
  • Pode ser um pesadelo para Product-Engineers dado que como o trabalho em algumas vezes pode ser para fazer algo rodar melhor, o impacto no cliente final pode não ser tão perceptível
  • Pode trazer algo interessante tecnicamente e em termos de execução, mas pode ser pouquíssimo atrativo ao cliente final (e.g. Concorde, Windows Vista)
  • Over-engineering aqui pode ser um problema muito grande e quando não há comunicação com os times de produtos, e geralmente leva a desperdícios aumenta o custo de manutenção em um momento no futuro.

Product-First: Vantagens e Desvantagens

·  Vantagens

  • Tem um enorme atrativo para Product-Engineers. Para quem gosta de ver o resultado do seu trabalho no final do dia impactando o usuário final e ter a real visibilidade e impacto do que está sendo feito uma empresa Product-First é o melhor lugar para se estar. Eu já estive em algumas e posso afirmar que ver algo funcionando e as pessoas usando a sua aplicação é algo que vale a pena.
  • Uma abordagem Product-First consegue capturar o timing do mercado para lançar novas features e /ou defender parte do Market Share, as vezes até ao mesmo tempo. Em alguns cenários de engenharia isso nem sempre é possível, dado que uma empresa Product-First deve ser capaz de adaptar-se em uma velocidade maior às tendências do mercado ou acompanhar os competidores se for o caso de uma competição mais acirrada para ganho ou manutenção de vantagem competitiva (e.g. usar Swift ao invés de Objective-C em projetos de iOS, para soluções de dados serem acessíveis em tempo real, ao invés de usar um SGBD tradicional usar Redis, etc)
  • Aqui a competição sobe o jogo de todos. Esse é um assunto que ao meu ver é pouco discutido que é como uma competição dinâmica pode elevar o jogo e todos, e para uma empresa Product-First competição (e elevação do seu nível de excelência operacional) é quase uma questão de quem sobrevive no mercado e de quem vai à falência.

·  Desvantagens

  • Em muitas das vezes a otimização de engenharia fica em segundo plano devido a pipelines de produtos muito cheios ou falta de visibilidade do que deve ser feito. Em uma analogia simples, seria como um chef pedir uma reforma na cozinha por causa de condições de trabalho, mas ao mesmo tempo o dono do restaurante está satisfeito que o seu comércio está gerando lucro apesar dos problemas.
  • Se você gosta de ir a fundo em um tópico técnico (e.g. resolução de cenários técnicos world class) talvez estar em uma empresa Product-First não seja o ideal, pois na busca para a solução ideal talvez a sua empresa já esteja pensando em outro produto, outra implementação e muitas das vezes as soluções tecnicamente melhores perdem prioridade devido ao fato de que coisas novas precisam ser feitas;
  • Uma continuação do ponto anterior é que se as coisas não têm uma prioridade do ponto de vista de engenharia na concepção inicial, o Débito Técnico vai ser a lei. Isto significa que todo projeto já começa com um débito técnico a ser pago e ao longo do tempo os juros começarão a ser um problema real em caso de potenciais melhorias e manutenções. Isto é um dos pontos cegos mais comuns em projetos, dado que a resolução de um débito técnico muitas das vezes não aparece no backlog de produto, contudo a sua execução pode eliminar problemas latentes no produto que venham a causar um mau maior (e.g. indisponibilidades, vulnerabilidades de segurança).
  • E já que as empresas Product-First lida com usuários finais em grande parte das vezes, quando as coisas dão errado elas vão acontecer da forma mais trágica possível e a pressão tanto de entrega quanto de ajustar algo errada é quase que permanente quando se trabalha em uma empresa Product-First.

Mas isso realmente importa?

Bem dentro do que eu vivi no ambiente corporativo, importa e muito. Grande parte dos erros que eu cometi na minha carreira estão intimamente relacionados com o fato de uma falta de identificação da minha parte de que tipo de empresa eu estava entrando. Este fato me levou grande parte das vezes em uma estrada de frustrações, boredom/boreout com fricções extremas dentro do ambiente corporativo (todas em âmbito respeitoso).

Duas situações para exemplificar o meu ponto.

A primeira foi quando eu estava em uma empresa Engineering-First com a mentalidade de Product-Engineer. Na época eu era analista de BI onde eu era responsável pela parte de tuning do banco de dados e do DWH; mas o que eu realmente queria fazer era participar do processo de precificação dos ativos da empresa e fazer um trabalho menos voltado a rotinas operacionais de otimização e monitoramento; mas sim realizar modelagem matemática para prever os preços dos derivativos que eu colocava dentro do meu banco de dados.  

A segunda foi quando eu cheguei em uma empresa Product-First com uma mentalidade de como se eu estivesse em uma empresa Engineering-First. Ao mesmo tempo em que eu fazia algumas análises mais complexas do negócio, consultava benckmarks técnicos e até mesmo implementava algoritmos de alguns papers da nossa plataforma, a empresa queria simplesmente deixar uma solução muito ruim (em termos de débito técnico) e mover para o próximo projeto (e isso com eles totalmente satisfeitos com o resultado da péssima implementação em termos técnicos).

E por que eu estou colocando isto? Pelo simples motivo de que estar na empresa errada no momento errado da sua carreira pode ser algo que não vai apenas desperdiçar o seu potencial de carreira: isso pode literalmente escalar para doenças mentais com consequências quase que imprevisíveis.

Óbvio que em muitas das vezes não tem como escolhermos o nosso emprego, mas se eu tivesse que deixar uma recomendação eu diria saiba o tipo de engenheiro que você é no momento e procure uma empresa que tenha uma abordagem que tenha o máximo de intersecção com os seus interesses.

Considerações Finais

Ambos os paradigmas têm as suas vantagens e desvantagens e essas características não são totalmente excludentes.

Contudo, é muito difícil encontrar organizações que operam bem usando as duas abordagens em paralelo. Dessa forma se eu tivesse que deixar uma dica essa seria algo como no aforismo grego conhece a ti mesmo como engenheiro. Isso por si só vai levar a melhores decisões de carreira ao longo do tempo e com sorte evitar todo tipo de frustrações.

Dedicado especialmente ao Daniel “Oz” Santos que passou comigo grande parte dos apuros descritos nestas duas abordagens (i.e. vivemos as desvantagens das duas abordagens por mais de 3 anos) e para o Rodolfo Zahn pelas conversas que deram origem a este post.

Links úteis

How to cultivate an engineering first culture — from a coders perspective

Product engineers

The Over-Engineering Problem (and How to Avoid It)

Notas

[1] – Existe um terceiro tipo de abordagem que eu venho notando que são as empresas Research/Experimentation-First, mas isso é tema para um outro momento.

[2] – Este post não tem como ponto principal falar de projetos em si ou de negócios específicos, mas sim discutir essas duas abordagens que a meu ver definem bem o mercado para engenheiros, cientistas de dados, e demais profissionais de dados. Este argumento pode ser estendido para outras áreas, mas aqui eu vou falar especificamente para o público da parte de dados e IT no geral.

[3] – E como vocês já sabem eu fui alfabetizado no pior sistema educacional do mundo e a minha mente fala muito mais rápido que os meus dedos e a vontade de revisar isso é tão alta quanto uma folha de papel deitada, então relevem todos os erros cometidos.

A sua empresa é Engineering-First ou Product-First?

RecSys 2019 – Recommendation in Multi-Stakeholder Environments (RMSE) and 7th International Workshop on News Recommendation and Analytics (INRA 2019) in RecSys 2019

Once that you’re in a conference, the first thing that you do is certainly go to the main talks and see the presentations of big companies, look for the big cases, hang out with authors of great papers stating the SOTA and so on. 

This is the safest path and probably most of those cases will have press releases discussed in media or subject in blogposts and you can have access to this before almost everyone. 

However, one thing that I think is very underestimated in conferences are the workshops.

My favorite definition of what a workshop is comes from Oxford Dictionary that is “a meeting of people to discuss and/or perform practical work in a subject or activity”.

For me, workshops are the best blend between the conference format – that contains the peer-review and trusteeship from the chair – in a smaller format that you can go for a specific but subjacent subject and have direct contact with the authors. In those places, there’s some information that is not available for the greater public.

I’ll talk today about two workshops that I attended in RecSys 2019 that is Recommendation in Multi-Stakeholder Environments (RMSE) and 7th International Workshop on News Recommendation and Analytics (INRA 2019).

But first I’ll explain why I attended those two workshops in RecSys. 

First of all, I decided to attend RMSE because here at MyHammer, we’re dealing with several challenges regarding the recommendations in a marketplace. We need to take into consideration not only a single platform user but several different users that not only interact with each other, but we have all the dynamics of a job marketplace. We have complex competition dynamics and seeing the proceedings. I saw that I could learn tons of ways to apply this knowledge in the company.

For the INRA as this specific topic doesn’t have much in common with job recommendation, I decided to attend because some papers are talking about some very relevant aspects that have a big intersection with our use case like giving recommendations for non-logged users, contextual multi-armed bandits, content-representation and strategies to use word embeddings.   

For the matter of clarity, I’ll include the official descriptions of those workshops:

7th International Workshop on News Recommendation and Analytics (INRA 2019)

This workshop primarily addresses news recommender systems and analytics. The news ecosystem engulfs a variety of actors including publishers, journalists, and readers. The news may originate in large media companies or digital social networks. INRA aims to connect researchers, media companies, and practitioners to exchange ideas about creating and maintaining a reliable and sustainable environment for digital news production and consumption.

Topics of interests for this workshop include but are not limited to:

  • News Recommendation
  • News Analytics
  • Ethical Aspects of News Recommendation

For the RMSE: Recommendation in Multi-Stakeholder Environments we have the following description:

One of the most essential aspects of any recommender system is personalization — how well the recommendations delivered suit the user’s interests. However, in many real world applications, there are other stakeholders whose needs and interests should be taken into account. In multisided e-commerce platforms, such as auction sites, there are parties on both sides of the recommendation transaction whose perspectives should be considered. There are also contexts in which the recommender system itself also has certain objectives that should be incorporated into the recommendation generation. Problems like long-tail promotion, fairness-aware recommendation, and profit maximization are all examples of objectives that may be important in different applications. In such multistakeholder environments, the recommender system will need to balance the (possibly conflicting) interests of different parties.

This workshop will encourage submissions that address the challenges of producing recommendations in multistakeholder settings, including but not limited to the following topics:

  • The requirements of different multistakeholder applications such as:
    • Recommendation in multisided platforms
    • Fairness-aware recommendation
    • Multi-objective optimization in Recommendation
    • Value-aware recommendation in commercial settings
    • Reciprocal recommendation
  • Algorithms for multistakeholder recommendation including multi-objective optimization, re-ranking and others
  • Evaluation of multistakeholder recommendation systems
  • User experience considerations in multistakeholder recommendation including ethics, transparency, and interfaces for different stakeholders.

RecSys is one of the best conferences for Recommendation Systems because it’s an excellent blend between industry and academics, where in one side in academia we have a fast paced rhythm of research in scenarios more complex than ever before and for industry most of the companies are moving forward those new methods in battle-tested environments where we have not only sterile benchmarks, but recommender systems applied in real live data. 

Below I’ll highlight some interesting papers and some quick notes about them. I strongly suggest the read on the full papers because this is the SOTA in terms of research and industrial applications in recommender systems.

7th International Workshop on News Recommendation and Analytics (INRA 2019)

  • On the Importance of News Content Representation in Hybrid Neural Session-based Recommender Systems, Gabriel De Souza P. Moreira, Dietmar Jannach and Adilson Marques Da Cunha. Abstract: News recommender systems are designed to surface relevant information for online readers by personalizing their user experiences. A particular problem in that context is that online readers are often anonymous, which means that this personalization can only be based on the last few recorded interactions with the user, a setting named session-based recommendation. Another particularity of the news domain is that constantly fresh articles are published, which should be immediately considered for recommendation. To deal with this item cold-start problem, it is important to consider the actual content of items when recommending. Hybrid approaches are therefore often considered as the method of choice in such settings. In this work, we analyze the importance of considering content information in a hybrid neural news recommender system. We contrast content-aware and content-agnostic techniques and also explore the effects of using different content encodings. Experiments on two public datasets confirm the importance of adopting a hybrid approach. Furthermore, we show that the choice of the content encoding can have an impact on the resulting performance.
  • Defining a Meaningful Baseline for News Recommender Systems, Benjamin Kille and Andreas Lommatzsch.Abstract: The analysis of images in the context of recommender systems is a challenging research topic. NewsREEL Multimedia enables researchers to study new algorithms with a large dataset. The dataset comprises news items and the number of impressions as a proxy for interestingness. Each news article comes with textual and image features. This paper presents data characteristics and baseline prediction models. We discuss the performance of these predictors and explain the detected patterns.
  • Trend-responsive user segmentation enabling traceable publishing insights. A case study of a real-world large-scale news recommendation system, Joanna Misztal-Radecka, Dominik Rusiecki, Michał Żmuda and Artur Bujak. Abstract: The traditional offline approaches are no longer sufficient for building modern recommender systems in domains such as online news services, mainly due to the high dynamics of environment changes and necessity to operate on a large scale with high data sparsity. The ability to balance exploration with exploitation makes the multi-armed bandits an efficient alternative to the conventional methods, and a robust user segmentation plays a crucial role in providing the context for such online recommendation algorithms. In this work, we present an unsupervised and trend-responsive method for segmenting users according to their semantic interests, which has been integrated with a real-world system for large-scale news recommendations. The results of an online A/B test show significant improvements compared to a global-optimization algorithm on several services with different characteristics. Based on the experimental results as well as the exploration of segments descriptions and trend dynamics, we propose extensions to this approach that address particular real-world challenges for different use-cases. Moreover, we describe a method of generating traceable publishing insights facilitating the creation of content that serves the diversity of all users needs.

RMSE: Recommendation in Multi-Stakeholder Environments

  • Multi-stakeholder Recommendation and its Connection to Multi-sided Fairness (Himan Abdollahpouri and Robin Burke). Abstract: There is growing research interest in recommendation as a multistakeholder problem, one where the interests of multiple parties should be taken into account. This category subsumes some existing well-established areas of recommendation research including reciprocal and group recommendation, but a detailed taxonomy of different classes of multi-stakeholder recommender systems is still lacking. Fairness-aware recommendation has also grown as a research area, but its close connection with multi-stakeholder recommendation is not always recognized. In this paper, we define the most commonly observed classes of multi-stakeholder recommender systems and discuss how different fairness concerns may come into play in such systems.
  • Simple Objectives Work Better (Joaquin Delgado, Samuel Lind, Carl Radecke and Satish Konijeti). Abstract: Groupon is a dynamic two-sided marketplace where millions of deals organized in three different lines of businesses or verticals: Local, Goods and Getaways, using various taxonomies, are matched with customers’ demand across 15 countries around the world. Customers discover deals by directly entering the search query or browsing on the mobile or desktop devices. Relevance is Groupon’s homegrown search and recommendation engine, tasked to find the best deals for its users while ensuring the business objectives are also met at the same time. Hence the objective function is designed to calibrate the score to meet the needs of multiple stakeholders. Currently, the function is comprised of multiple weighted factors that are combined to satisfy the needs of the respective stakeholders in the multi-objective scorer, a key component of Groupon’s ranking pipeline. The purpose of this paper is to describe various techniques explored by Groupon’s Relevance team to improve various parts of Search and Ranking algorithms specifically related to the multi-objective scorer. It is for research only, and it does not reflect the views, plans, policy or practices of Groupon. The main contributions of this paper are in the areas of factorization of the different abstract objectives and the simplification of the objective function to capture the essence of short, mid and long term benefits while preserv
  • Recommender Systems Fairness Evaluation via Generalized Cross Entropy (Yashar Deldjoo, Vito Walter Anelli, Hamed Zamani, Alejandro Bellogin Kouki and Tommaso Di Noia). Abstract: Fairness in recommender systems has been considered with respect to sensitive attributes of users (e.g., gender, race) or items (e.g., revenue in a multistakeholder setting). Regardless, the concept has been commonly interpreted as some form of equality — i.e., the degree to which the system is meeting the information needs of all its users in an equal sense. In this paper, we argue that fairness in recommender systems does not necessarily imply equality, but instead it should consider a distribution of resources based on merits and needs.
    We present a probabilistic framework based on generalized cross entropy to evaluate fairness of recommender systems under this perspective, where we show that the proposed framework is flexible and explanatory by allowing to incorporate domain knowledge (through an ideal fair distribution) that can help to understand which item or user aspects a recommendation algorithm is over- or under-representing. Results on two real-world datasets show the merits of the proposed evaluation framework both in terms of user and item fairness.
  • The Unfairness of Popularity Bias in Recommendation (Himan Abdollahpouri, Masoud Mansoury, Robin Burke and Bamshad Mobasher). Abstract: Recommender systems are known to suffer from the popularity bias problem: popular (i.e. frequently rated) items get a lot of exposure while less popular ones are under-represented in the recommendations. Research in this area has been mainly focusing on finding ways to tackle this issue by increasing the number of recommended long-tail items or otherwise the overall catalog coverage. In this paper, however, we look at this problem from the users’ perspective: we want to see how popularity bias causes the recommendations to deviate from what the user expects to get from the recommender system. We define three different groups of users according to their interest in popular items (Niche, Diverse and Blockbuster-focused) and show the impact of popularity bias on the users in each group. Our experimental results on a movie dataset show that in many recommendation algorithms the recommendations the users get are extremely concentrated on popular items even if a user is interested in long-tail and non-popular items showing an extreme bias disparity.
  • Bias Disparity in Recommendation Systems (Virginia Tsintzou, Evaggelia Pitoura and Panayiotis Tsaparas).Abstract: Recommender systems have been applied successfully in a number of different domains, such as, entertainment, commerce, and employment. Their success lies in their ability to exploit the collective behavior of users in order to deliver highly targeted, personalized recommendations. Given that recommenders learn from user preferences, they incorporate different biases that users exhibit in the input data. More importantly, there are cases where recommenders may amplify such biases, leading to the phenomenon of bias disparity. In this short paper, we present a preliminary experimental study on synthetic data, where we investigate different conditions under which a recommender exhibits bias disparity, and the long-term effect of recommendations on data bias. We also consider a simple re-ranking algorithm for reducing bias disparity, and present some observations for data disparity on real data.
  • Joint Optimization of Profit and Relevance for Recommendation Systems in E-commerce (Raphael Louca, Moumita Bhattacharya, Diane Hu and Liangjie Hong). Abstract: Traditionally, recommender systems for e-commerce platforms are designed to optimize for relevance (e.g., purchase or click probability). Although such recommendations typically align with users’ interests, they may not necessarily generate the highest profit for the platform. In this paper, we propose a novel revenue model which jointly optimizes both for probability of purchase and profit. The model is tested on a recommendation module at Etsy.com, a two-sided marketplace for buyers and sellers. Notably, optimizing for profit, in addition to purchase probability, benefits not only the platform but also the sellers. We show that the proposed model outperforms several baselines by increasing offline metrics associated with both relevance and profit.
  • A Multistakeholder Recommender Systems Algorithm for Allocating Sponsored Recommendations (Edward Malthouse, Khadija Ali Vakeel, Yasaman Kamyab Hessary, Robin Burke and Morana Fuduric). Abstract:Retailing and social media platforms recommend two types of items to their users: sponsored items that generate ad revenue and non-sponsored ones that do not. The platform selects sponsored items to maximize ad revenue, often through some form of programmatic auction, and non-sponsored items to maximize user utility with a recommender system (RS). We develop a multiobjective binary integer programming model to allocate sponsored recommendations considering a dual objective of maximizing ad revenue and user utility. We propose an algorithm to solve it in a computationally efficient way. Our method can be applied as a form of post processing to an existing RS, making it widely applicable. We apply the model to data from an online grocery retailer and show that user utility for the recommended items can be improved while reducing ad revenue by a small amount. This multiobjective approach, which unifies programmatic advertising and RS, opens a new frontier for advertising and RS research and we therefore provide an extended discussion of future research topics.

Final Remarks

I think that for every practitioner or researcher engineer involved in Recommendation Systems, RecSys is a great conference to attend. There’s a great overlap with academia and industry where the first one pushes forward in terms of new methods, algorithms, and a reflexive attitude for important themes like bias and fairness; and the second one applies those methods using engineering and presents some results on battle tested applications using contextual bandits, click prediction and the combination between domain heuristics with optimization method in machine learning. 

RecSys 2019 – Recommendation in Multi-Stakeholder Environments (RMSE) and 7th International Workshop on News Recommendation and Analytics (INRA 2019) in RecSys 2019

Security in Machine Learning Engineering: A white-box attack and simple countermeasures

Some weeks ago during a security training for developers provided by Marcus from Hackmanit (by the way, it’s a very good course that goes in some topics since web development until vulnerabilities of NoSQL and some defensive coding) we discussed about some white box attacks in web applications (e.g.attacks where the offender has internal access in the object) I got a bit curious to check if there’s some similar vulnerabilities in ML models. 

After running a simple script based in [1],[2],[3] using Scikit-Learn, I noticed there’s some latent vulnerabilities not only in terms of objects but also in regarding to have a proper security mindset when we’re developing ML models. 

But first let’s check a simple example.

A white-box attack in a Scikit-Learn random forest object

I have a dataset called Layman Brothers that consists in a database of loans that I did grab from internet (if someone knows the authors let me know to give the credit) that contains records regarding consumers of a bank that according some variables indicates whether the consumer defaulted or not. This is a plain vanilla case of classification and for a matter of simplicity I used a Random Forest to generate a classification model. 

The main points in this post it’s check what kind of information the Scikit-Learn object (model) reveals in a white-box attack and raises some simple countermeasures to reduce the attack surface in those models.

After ran the classifier, I serialized the Random Forest model using Pickle. The model has the following performance against the test set:

# Accuracy: 0.81
# status
# 0 8071
# 1 929

Keep attention in those numbers because we’re going to talk about them later on in this post. 

In a quick search in internet the majority of applications that uses Scikit-Learn for production environments deals with a pickled (or serialized model in Joblib) object that it’s hosted in a machine or S3 bucket and an API REST take care to do the servicing of this model. The API receives some parameters in the request and bring back some response (the prediction result). 

In our case, the response value based on the independent variables (loan features) will be defaulted {1}or not {0}. Quite straightforward.  

Having access in the Scikit-Learn object I noticed that the object discloses valuable pieces of information that in the hands of a white-box attacker could be potentially very damaging for a company. 

Loading the pickled object, we can check all classes contained in a model:

So, we have a model with 2 possible outcomes, {0} and {1}. From the perspective of an attacker we can infer that this model has some binary decision containing a yes {1}or no {0}decision. 

I need to confess that I expected to have only a read accessin this object (because the Scikit-Learn documentation gives it for grant), but I got surprised when I discovered that I can write in the objecti.e. overriding the training object completely. I made that using the following snippet:

# Load model from Pickle
model_rf_reload_pkl = pickle.load(open(‘model_rf.pkl’, ‘rb’))
# Displays prediction classes
model_rf_reload_pkl.classes_
# >>> array([0, 1])

One can noticed that with this command I changed all the possible classes of the model only using a single numpy array and hereafter this model will contain only the outcome {1}.

Just for a matter of exemplification I ran the same function against the test dataset to get the results and I got the following surprise:

# Actual against test set
# Accuracy: 0.2238888888888889
# status
# 1 9000
# Previous against test set
# Accuracy: 0.8153333333333334
# status
# 0 8071
# 1 929
view raw comparison.py hosted with ❤ by GitHub

In this simple example we moved more than 8k records to the wrong class. It’s unnecessary to say how damaging this could be in production in some critical domain like that. 

If we do a simple mental exercise, where this object could be a credit score application, or some classifier for in a medical domain, or some pre-order of some market stocks; we can see that it brings a very cold reality that we’re not close to be safe doing the traditional ML using the popular tools. 

In the exact moment that we Machine Learning Engineers or Data Scientists just run some scripts without even think in terms of vulnerabilities and security, we’re exposing our employers, business and exposing ourselves in such liability risk that can cause a high and unnecessary damage because of the lack of a better security thinking in ML models/objects. 

After that, I opened an issue/question in Scikit-Learn project to check the main reason why this type of modification it’s possible. Maybe I was missing something that was thought by the developers during the implementation phase. My issue in the project can be seeing below:

And I got the following response:

Until the day when this post was published there’s no answer for my last question about this potential vulnerability in a parameter that should not be changed after model training.

This is a simple white-box attack that can interfere directly in the model object itself. Now let’s pretend that we’re not an attacker in the object, but we want to explore other attack surfaces and check which valuable information those models can give for us. 

Models revealing more than expected

Using the same object, I’ll explore the same attributes that is given in the docs to check if we’re able to fetch more information from the model and how this information can be potentially useful.

First, I’ll try to see the number of estimators:

print(fNumber of Estimators: {len(model_rf_reload_pkl.estimators_)}’)
# >>> Number of Estimators: 10
view raw estimators.py hosted with ❤ by GitHub

This Random Forest training should be not so complex, because we have only 10 estimators (i.e. 10 different trees) and grab all the complexities of this Random Forest won’t be so hard to a mildly motivated attacker. 

I’ll grab a single estimator to perform a quick assessment in the tree (estimator) complexity:

model_rf_reload_pkl.estimators_[5]
# >>> DecisionTreeClassifier(class_weight=None, criterion='gini',
# max_depth=5,max_features='auto',
# max_leaf_nodes=5, min_impurity_decrease=0.0,
# min_impurity_split=None, min_samples_leaf=100,
# min_samples_split=2, min_weight_fraction_leaf=0.0,
# presort=False, random_state=1201263687,
# splitter='best')

Then this tree it’s not using a class_weight to perform training adjustments if there’s some unbalance in the dataset. As an attacker with this piece of information, I know that if I want to perform attacks in this model, I need to be aware to alternate the classes during my requests. 

It means that if I get a single positive result, I can explore in alternate ways without being detected as the requests are following by a non-weighted distribution.

Moving forward we can see that this tree has only 5 levels of depth (max_depth) with a maximum 5 leaf nodes (max_leaf_nodes) with a minimum of 100 records per leaf (min_samples_leaf).

It means that even with such depth I can see that this model can concentrate a huge amount of cases in some leaf nodes (i.e. low depth with limited number of leaf nodes). As an attacker maybe I don’t could not have access in the number of transactions that Layman Brothers used in the training, but I know that the tree it’s simple and it’s not so deep. 

In other words, it means that my search space in terms of parameters won’t be so hard because with a small number of combinations I can easily get a single path from the root until the leaf node and explore it.

As an attacker I would like to know how many features one estimator contains. The point here is if I get the features and their importance, I can prune my search space and concentrate attack efforts only in the meaningful features. To know how many features one estimator contains, I need to run the follow snippet:

# Extract single tree
estimator = model_rf_reload_pkl.estimators_[5]
print(fNumber of Features: {estimator.n_features_}’)
# >> Number of Features: 9

As we can see, we have only 9 parameters that were used in this model. Then, my job as an attacker could not be better. This is a model of dreams for an attacker. 

But 9 parameters can be big enough in terms of a search space. To prune out some non-fruitful attacks of my search space, I’ll try to check which variables are relevant to the model. With this information I can reduce my search space and go directly in the relevant part of the attack surface. For that let’s run the following snippet:

features_list = [str(x + 0) for x in range(estimator.n_features_)]
features_list
# >>> ['0', '1', '2', '3', '4', '5', '6', '7', '8']
importances = estimator.feature_importances_
indices = np.argsort(importances)
plt.figure(1)
plt.title(‘Feature Importances’)
plt.barh(range(len(indices)), importances[indices], color=b’, align=center’)
plt.yticks(range(len(indices)), indices)
plt.xlabel(‘Relative Importance’)

Let me explain: I did grab the number of the features and put an id for each one and after that I checked the relative importance using np.argsort()to assess the importance of those variables. 

As we can see I need only to concentrate my attack in the features in the position [4][3]and [5]. This will reduce my work in tons because I can discard other 6 variables and the only thing that I need to do it’s just tweak 3 knobs in the model. 

But trying something without a proper warmup could be costly for me as an attacker and I can lose the momentum to perform this attack (e.g. the ML team can update the model, someone can identify the threat and remove the model and rollback an old artifact, etc).

To solve my problem, I’ll check the values of one of trees and use it as a warmup before to do the attack. To check those values, I’ll run the following code that will generate the complete tree:

from sklearn.tree import export_graphviz
# Export as dot file
export_graphviz(estimator, out_file=tree.dot’,
feature_names = features_list,
rounded = True, proportion = False,
precision = 2, filled = True)
# Convert to png using system command (requires Graphviz)
from subprocess import call
call([‘dot’, ‘Tpng’, ‘tree.dot’, ‘o’, ‘tree.png’, ‘Gdpi=600’])
# Display in jupyter notebook
from IPython.display import Image
Image(filename =tree.png’)
view raw plot-tree.py hosted with ❤ by GitHub

Looking the tree graph, it’s clearer that to have our Loan in Layman Brothers always with false {0}in the defaultvariable we need to tweak the values in {Feature 3<=1.5 && Feature 5<=-0.5 && Feature 4<=1.5}.

Doing a small recap in terms of what we discovered: (i) we have the complete model structure, (ii) we know which features it’s important or not, (iii) we know the complexity of the tree, (iv) which features to tweak and (v) the complete path how to get our Loan in Layman Brothers bank always approved and game the system. 

With this information until the moment that the ML Team changes the model, as an attacker I can explore the model using all information contained in a single object. 

As discussed before this is a simple white-box approach that takes in consideration the access in the object. 

The point that I want to make here it’s how a single object can disclose a lot for a potential attacker and ML Engineers must be aware about it.

Some practical model security countermeasures

There are some practical countermeasures that can take place to reduce the surface attack or at least make it harder for an attacker. This list it’s not exhaustive but the idea here is give some practical advice for ML Engineers of what can be done in terms of security and how to incorporate that in ML Production Pipelines. Some of those countermeasures can be:

  • Consistency Checks on the object/model: If using a model/object it’s unavoidable, one thing that can be done is load this object and using some routine check (i) the value of some attributes (e.g. number of classes in the model, specific values of the features, tree structure, etc), (ii) get the model accuracy against some holdout dataset (e.g. using a holdout dataset that has 85% of accuracyand raise an error in any different value), (iii) object size and last modification.  

  • Use “false features: False features will be some information that can be solicited in the request but in reality, won’t be used in the model. The objective here it’s to increase the complexity for an attacker in terms of search space (e.g.a model can use only 9 features, but the API will request 25 features (14 false features)). 

  • Model requests monitoring: Some tactics can be since monitoring IP requests in API, cross-check requests based in some patterns in values, time intervals between requests.

  • Incorporate consistency checks in CI/CD/CT/CC: CI/CD it’s a common term in ML Engineering but I would like to barely scratch two concepts that is Continuous Training (CT) and Continuous Consistency (CC). In Continuous Training the model will have some constant routine of training during some period of time in the way that using the same data and model building parameters the model will always produce the same results.  In Continuous Consistency it’s an additional checking layer on top of CI/CD to assess the consistency of all parameters and all data contained in ML objects/models. In CC if any attribute value got different from the values provided by the CT, the pipeline will break, and someone should need to check which attribute it’s inconsistent and investigate the root cause of the difference.

  • Avoid expose pickled models in any filesystem (e.g. S3) where someone can have access:  As we saw before if someone got access in the ML model/objects, it’s quite easy to perform some white-box attacks, i.e.no object access will reduce the exposure/attack surface.

  • If possible, encapsulates the coefficients in the application code and make it private: The heart of those vulnerabilities it’s in the ML object access. Remove those objects and incorporates only model coefficients and/or rules in the code (using private classes) can be a good way out to disclose less information about the model.

  • Incorporate the concept of Continuous Training in ML Pipeline: The trick here it’s to change the model frequently to confuse potential attackers (e.g.different positions model features between the API and ML object, check the reproducibility of results (e.g.accuracy, recall, F1 Score, etc) in the pipeline).

  • Use heuristics and rules to prune edge cases: Attackers likes to start test their search space using some edge (absurd) cases and see if the model gives some exploitable results and fine tuning on top of that. Some heuristics and/or rules in the API side can catch those cases and throw a cryptic error to the attacker make their job quite harder.

  • Talk its silver, silence its gold: One of the things that I learned in security it’s less you talk about what you’re doing in production, less you give free information to attackers. This can be harsh but its a basic countermeasure regarding social engineering. I saw in several conferences people giving details about the training set in images like image sizing in the training, augmentation strategies, pre-checks in API side, and even disclosing the lack of strategies to deal with adversarial examples. This information itself can be very useful to attackers in order to give a clear perspective of model limitations. If you want to talk about your solution talk more about reasons (why) and less in terms of implementation (how). Telling in Social Media that I keep all my money underneath my pillow, my door has only a single lock and I’ll arrive from work only after 8PM do not make my house safer. Remember: Less information = less exposure. 

Conclusion

I’ll try to post some countermeasures in a future post, but I hope that as least this post can shed some light in the ML Security. 

There’s a saying in aviation culture (a good example of industry security-oriented) that means “the price of safety it’s the eternal vigilance” and I hope that hereafter more ML Engineers and Data Scientists can be more vigilant about ML and security. 

As usual, all codes and notebooks are in Github.

Security in Machine Learning Engineering: A white-box attack and simple countermeasures

Instituto Mises Brasil – Uma análise editorial usando Natural Language Processing

Este post será o primeiro de uma pequena série onde eu fiz algumas analises sobre a mudança editorial do Instituto Mises Brasil usando um pouco de Natural Language Processing (NLP) e Latent Dirichlet Allocation (LDA). Este texto inicial é apenas a descrição do repositório em que os dados e os scripts estão armazenados. Tudo é livre e pode ser copiado e contestado sem nenhum tipo de restrição.

O endereço do repositório é: https://github.com/fclesio/mises-brasil-nlp/

Sabe quando algo muda na redação de algum jornal, revista ou algum meio de comunicação, mas você não sabe o que é? Pois bem, eu fiquei com a mesma dúvida e resolvi usar algumas ferramentas para validar se houve uma mudança ou não.

Background

Tem algum tempo que eu venho acompanhando a política brasileira da perspectiva de ensaístas de vertentes ligadas ao libertarianismo, anarcocapitalismo, secessão, autopropriedade e assuntos correlatos; e um fato que me chamou bastante a atenção foi a mudança editorial que está acontecendo de forma lenta em um dos principais Think Tanks liberais do Brasil que é o Instituto Mises Brasil (IMB).

Para quem não sabe, em meados de 2015 houve uma ruptura no núcleo do IMB em que de um lado ficou o Presidente do IMB (Hélio Beltrão) e do outro ficaram o Chiocca Brothers (Fernando, Cristiano, Roberto) que na sequência criaram o Instituto Rothbard. O motivo dessa ruptura foi devido a divergências relativas a artigos ligados a secessão.

E devido a esse processo de ruptura que eu penso que houve essa transição do IMB para uma linha editorial mais leve no que diz respeito a assuntos ligados à liberdade o que contrário as ideias do próprio Ludwig von Mises.

Qual o motivo desse repositório?

O principal motivo é fazer uma análise de dados simples usando Natural Processing Language (NLP) em todos textos do Mises Brasil para validar uma hipótese que é:

  • Hipótese [0]: Houve uma mudança editorial no Instituto Mises Brasil em que assuntos ligados ao austrolibertarianismo, liberdade, ética, e secessão e outras questões relacionadas deram espaço para temas efêmeros como financismo, burocracia e principalmente política.

Caso a resposta da H0 seja positiva, eu vou tentar chegar nas repostas das seguintes perguntas que são:

  • Pergunta [1]: Caso H0 for verdadeira, assuntos ligados ao austrolibertarianismo como praxeologia, fim do estatismo, ética argumentativa, e secessão estão sendo deixados de lado em termos editoriais?
  • Pergunta [2]: O Instituto Mises Brasil está se tornando em termos editoriais mais liberal-mainstream do que libertário?
  • Pergunta [3]: Houve uma mudança em relação ao grupo de assuntos que são tratados ao longo do tempo como também a mudança do espectro de assuntos dos articulistas presentes?

Preparação

Tudo isso foi gerado em um MacMini com Python 3.6, mas também pode ser executado em computadores com Linux com a pré-instalação das seguintes bibliotecas:

$ pip install numpy==1.17.2
$ pip install pandas==0.25.1
$ pip install requests==2.22.0
$ pip install spacy==2.2.1
$ pip install beautifulsoup4==4.8.1
$ pip install bs4==0.0.1
$ python -m spacy download pt_core_news_sm

Sinceramente: Usem o R para geração dos seus próprios gráficos. Eu adoro o Seaborn e o Matplotlib para geração de gráficos, mas nesse sentido o R é muito mais flexível e precisa de bem menos “hacking” para fazer as coisas ficarem legais.

Extração de dados

A base que está no repositório foi gerada em 16.10.2019 para fins de congelar a análise e deixar a mesma com um grau maior de replicabilidade.

A extração busca todos os textos, independente de ser artigo do blog ou post da página principal. Isso ocorre devido ao fato de que não há uma divisão das URLs que faça essa distinção e algumas vezes temos artigos do blog que viram posts na página principal.

Outro ponto é que deve ser mencionado é que o Leandro Roque é o principal tradutor/ ensaísta do site e alguns posts de tradução são assinados por ele (o que é correto). Isso leva a dois efeitos que são 1) ele e muito profícuo com o fluxo de artigos no site e definitivamente isso distorce as estatísticas individuais dele como ensaísta e 2) por causa das traduções ele tem um espectro de assuntos bem mais diversos do que os outros autores, e isso tem que ser considerado quando analisarmos os assuntos os quais ele mais escreve. Pessoalmente eu desconsideraria ele de todas as análises dado esses dois pontos colocados. Mas aí vai de cada um.

Para quem quiser gerar uma base de dados nova com os dados até a presente data basta executar o comando abaixo no terminal:

$ python3 data-extraction.py

Ao final da execução vão aparecer as seguintes informações:

Fetching Time: 00:16:52
Articles fetched: 2855

Avisos gerais

Essa análise é apenas para fins educacionais. É obvio que uma análise editorial que envolva questões linguísticas/semânticas é algo muito complexo até mesmo para nós seres humanos, e colocar que uma máquina consiga fazer isso é algo que não tem muito sentido dado a natureza da complexidade da linguagem e as suas nuances.

Esse repositório como também a análise não tem a menor pretensão de ser algo “cientifico”. Isso significa que não haverá elementos de linguística cognitiva, linguística computacional, análise do discurso ou ciências similares. Esse repositório traz muitas visões pessoais e observações que porventura usa alguns dados e alguns scripts.

Distribuição e usos

Todos os dados, scripts, gráficos podem ser usados livremente sem nenhum tipo de restrição. Quem puder ajudar faz um hyperlink para o meu site/blog ou pode citar academicamente que vai ajudar bastante.

Eu não sou dono dos direitos dos textos do Instituto Mises Brasil e aqui tem apenas uma compilação dos dados extraídos do site, dados estes públicos e que podem ser extraídos por qualquer pessoa.

Garantias, erros e afins

Não existe garantia nestas análises, gráficos, dados e scripts e o uso está por conta de quem usar. Vão ter muitos erros (principalmente gramaticais, sintáticos e semânticos) e na medida que forem acontecendo podem abrir um Pull Request ou me mandar um e-mail que eu vou ajustando. Porém, como eu vou escrevendo na velocidade dos meus pensamentos nem sempre o sistema responsável pela correção sintática vai funcionar bem.

Instituto Mises Brasil – Uma análise editorial usando Natural Language Processing

O Campeonato Brasileiro está ficando mais injusto? (UPDATE FINAL)

Introdução

Como o Campeonato Brasileiro terminou oficialmente neste domingo com o Flamengo campeão e com todas as rodadas encerradas, vamos novamente realizar a mesma pergunta que inicei no meu post que indaga: “O Campeonato Brasileiro está ficando mais injusto?

Mais uma vez fui na Wikipedia, e atualizei os dados já incluindo o ano de 2019.

A desigualdade estrutural no Campeonato Brasileiro é uma tendência?

Na primeira análise feita aqui no blog eu cheguei a conclusão de que Sim. Com o uso do Coeficiente de Gini como métrica para mensurar se há uma desigualdade estrutural mostrou que existem sim elementos latentes dessa desigualdade.

Dado a campanha excepcional do Flamengo que não só bateu o recorde de números de pontos em uma única edição, como também se considerarmos apenas os jogos fora de casa o Flamengo ainda sim não seria rebaixado (37 jogos – 11v 4e 4d) (Fonte: Tiago Vinhoza).

Mas como estamos falando aqui da variância da distribuição dos pontos dentro desta edição Brasileirão um fato colocado pelo @Impedimento foi que esta edição teve a Menor pontuação efetivamente conquistada por um time que se salvou: 39 (Ceará) sendo que eram necessários apenas 37 para se salvar. Ou como disse o Tiago Vinhoza, uma estratégia só de empates em todos os jogos já seria suficiente para se salvar.

Sem mais demora, vamos rodar os mesmos scripts agora com os dados atualizados.

Ranking de desigualdade entre todas as edições do Brasileirão usando o Coeficiente de Gini

Diferentemente da nossa última análise em que a edição de 2018 (Palmeiras campeão) era a mais desigual até então, essa edição com o Flamengo Campeão teve um aumento de 17% ((1 – (0.1449/0.1746)) x 100) na desigualdade em relação ao número de pontos em 2018, o que mostra que a tendência dessa desigualdade veio pra ficar.

Assim como fizemos anteriormente, vamos olhar com calma essa edição de 2019 para verificar alguns fatos interessantes:

Aos moldes da nossa última análise, vamos ver alguns pontos da tabela final:

  • O Flamengo finalmente furou a impressionante barreira dos 90 pontos em campeonato de 20 times; o que é para efeitos de comparação é o mesmo número de pontos que o Real Madrid e/ou Barcelona fazem em temporadas avassaladoras no Campeonato Espanhol o que indica que houve uma disparidade muito grande dentro de campo;
  • Como já colocado acima, uma estratégia de apenas empates (38 pontos) já seria o suficiente para sair do rebaixamento;
  • O Avaí foi o saco de pancadas desse brasileirão em que cedeu 72 pontos (24 * 3) ao longo da tabela para inúmeros times, e perdeu 22 pontos em empates (ou tirou de outros times). Alem disso tivemos além do Avaí mais 4 times com mais de 20 derrotas (Botafogo, CSA, e Chapecoense) o que possivelmente pode ter contribuído para essa desigualdade de pontos.
  • Tivemos na verdade 4 Campeonatos: Campeonato 1 que eu chamaria de “Passeio Flamenguista”; Campeonato 2 que seria “Briga pelo Vice estrelando Santos e Palmeiras”; e Campeonato 3 “Vagas na Libertadores e Sulamericana”; e o último campeonato (4) que seria “Quem vai ser rebaixado com o CSA, Chape e Avaí?”; e
  • Entre o 7o Colocado (Internacional) até o penúltimo colocado (Chapecoense) a maior diferença de pontos foram de 4 pontos.

Em linhas gerais o que podemos ver é que tivemos alguns blocos de times com um determinado número de pontos, mas o grosso de todos os pontos foram para os times de cima, em especial o Campeão e os dois vice campeões.

Vamos agora olhar a evolução dessa desigualdade ao longo do tempo, e comparar com as edições anteriores.

Na minha última análise eu tinha feito a seguinte consideração:

Algo surpreendente é que os vales costumam acontecer nos anos ímpares e os picos nos anos pares. Isso talvez seja explicado por algum efeito externo, tal como as Olimpíadas e Copa do Mundo que ocorrem em anos pares. E uma hipótese bem fraca, mas ainda sim é uma hipótese.

Ou seja: O Brasileirão desse ano não somente mostrou que essa era uma hipótese que não faz mais sentido, como mostra agora que é um outlier dentro de todas as edições de pontos corridos, em termos de desigualdade dos times.

Para suavizar um pouco esse efeito, vamos considerar aos moldes do post anterior apenas uma média móvel considerando um recuo de 3 anos.

Olhando com mais calma, podemos até mesmo pensar na hipótese de que Não foi o ano de 2019 que foi um outlier, mas os anos de 2017 e 2009 que são os verdadeiros outliers em relação à desigualdade.

Aos moldes do post anterior, vamos remover o campeão e o pior time de todas as temporadas e recalcular novamente.

Mesmo removendo o Avaí (pior time) e o Campeão (Flamengo) ainda sim o campeonato de 2019 continua o mais desigual de todos os tempos.

Vamos gerar o gráfico apenas para verificar se a tendência do aumento da desigualdade permanece ou não.

Olhando o Coeficiente de Gini removendo o campeão e o pior time, podemos ver que ainda temos a tendência de aumento da desigualdade dentro da liga.

Considerações finais

Se tivermos que responder a nossa pergunta principal que foi “O brasileirão está ficando mais injusto ao longo do tempo?” a resposta seria:

“Sim. Existe uma esigualdade estrutural no Campeonato Brasileiro com uma tendência de alta, sendo que a edição de 2019 foi a mais desigual de todas.”.

Aqui eu vou tomar a liberdade de realizar algumas considerações em relação ao que eu penso que pode estar acontecendo no Campeonato:

Por inspiração de uma interação que eu tive no Twitter com Tiago Vinhoza – @tiagotvv eu vou coletar os dados das ligas européias desde a década de 90 e analisar se essa desigualdade acontece nas outras ligas do mundo e comparar com a liga Brasileira.

Como sempre os dados e o código estão no GitHub.

Inequality in the Premier League – Çınar Baymul

An Analysis Of Parity Levels In Soccer – Harvard Sports

Which Sports League has the Most Parity? – Harvard Sports

Major League Soccer and the Effect of Egalitarianism – Harvard Sports

The Gini Coefficient as a Measure of League Competitiveness and Title Uncertainty – Australia Sports Betting

Mourão, P. R., & Teixeira, J. S. (2015). Gini playing soccer. Applied Economics, 47(49), 5229-5246

How “fair” are European soccer leagues? Gini index applied to points distribution of 5 soccer leagues between 2000 and 2015 – r/soccer

Footballomics: Estimating League Disparity Performance with a Point-Rank Gini Index – Christoforos Nikolaou

O Campeonato Brasileiro está ficando mais injusto? (UPDATE FINAL)

Algumas dicas úteis de como fazer uma revisão sistemática

Esse é um artigo bem antigo que escrevi em 2013, mas face aos recentes eventos na minha carreira acadêmica estou postando publicamente para ajudar quem se propõe a fazer tal tarefa.

Existem inúmeros manuais de como se fazer uma boa Revisão Sistemática, então aqui eu vou colocar um apanhado de idéias que eu copiei rigorosamente dos autores das referências e colocar o que eu fiz para manter a minha sanidade durante o processo.

Um dos principais fatos dos dias de hoje é que vivemos na era da informação em que o volume de dados e informações geradas aumentam quase que exponencialmente a cada ano

Este fato tem um impacto gigantesco quando falamos de pesquisa acadêmica, especificamente para os pesquisadores que desejam entender o que está sendo escrito mesmo no meio desta miríade de informações que está sendo gerada a cada dia que passa. 

Esse post é dedicado especialmente para:

  1. pessoas que estão em momento de definir o seu projeto de pesquisa para um doutorado ou mestrado
  2. pessoas que estão escrevendo um artigo científico, mas que gostariam de saber o que está sendo discutido na literatura
  3. pessoas que estão fazendo pesquisa corporativa para determinar rumos de ação práticos em algum departamento de R&D

Uma das técnicas acadêmicas mais subestimadas na minha opinião para resolver isso no que se refere à atividade de pesquisa é a Revisão Sistemática.

Pessoalmente eu não consigo imaginar pesquisas cientificas sérias começando sem o uso desta ferramenta, e ao final desse artigo esta questão vai ficar mais clara. 

Mas o que é uma revisão sistemática? Aqui eu pego emprestado a citação de Cook, D. J., Mulrow, C. D., & Haynes, R. B. (1997):

Systematic reviews are scientific investigations in themselves, with pre-planned methods and an as  sembly of original studies as their “subjects.” They synthesize the results of multiple primary investigations by using strategies that limit bias and random error (9, 10). These strategies include a comprehensive search of all potentially relevant articles and the use of explicit, reproducible criteria in the selection of articles for review. Primary research designs and study characteristics are appraised, data are synthesized, and results are interpreted.

Cook, D. J., Mulrow, C. D., & Haynes, R. B. (1997)

A percepção que eu tenho é que inúmeros trabalhos iniciam-se cheios de expectativas e promessas de ineditismo, mas grande parte das vezes são trabalhos que só reinventaram a roda sobre outros trabalhos de outras pessoas que não levaram o crédito, e que caso houvesse uma revisão sistemática mais apurada esses trabalhos ou receberiam menos recursos ou nem existiriam e os recursos poderiam ser alocados em outros espaços com maior potencial de relevância/retorno.

Mas se eu tivesse que sumarizar em alguns pontos básicos da importância da revisão sistemática, eu consideraria os seguintes:

  1. A Revisão Sistemática ajuda a entender o passado de um tópico dentro de um campo da ciência e a sua evolução ao longo do tempo;
  2. Apresenta o atual estado da arte para os pesquisadores do presente;
  3. É uma importante ferramenta para descobrir os gaps e limitações (e.g.metodológicas) na atual literatura;
  4. Faz a sua pesquisa conversar com a literatura corrente desde o dia da publicação, 
  5. Faz o trabalho de mostrar formas de monitoramento da literatura quando trás fontes relevantes; e último, mas não menos importante;
  6. Evita que os pesquisadores reinventem a roda alocando recursos para o desenvolvimento de trabalhos com um grau maior de ineditismo

Aqui eu concordo com a afirmação de Webster e Watson(2002) de que a Revisão Sistemática une os conceitos ao longo do tempo, mas ocasionalmente prepara para o futuro, em que a revisão ela entende a teoria e a prática e a relação ontológica do campo de estudos.

Como afirma Webster, Watson(2002) revisões sistemáticas ou de literatura não podem ser uma compilação de citações como uma lista telefônica, mas sim um exercício ativo de análise dos estudos que estão sendo analisados. 

Systematic reviews can help practitioners keep abreast of the medical literature by summarizing large bodies of evidence and helping to explain differences among studies on the same question. A systematic review involves the application of scientific strategies, in ways that limit bias, to the assembly, critical appraisal, and synthesis of all relevant studies that address a specific clinical question. 

Cook, D. J., Mulrow, C. D., & Haynes, R. B. (1997)

Um dos pontos que eu quero ressaltar em algum ponto do futuro, é como a pesquisa de forma sistematizada deveria ser o objetivo de qualquer empresa para incorporar dados em processos decisórios e na arquitetura de novas soluções corporativas. Mas o meu argumento é o mesmo do Cook, D. J., Mulrow, C. D., & Haynes, R. B. (1997) que eu coloco a citação abaixo:

Review articles are one type of integrative publication; practice guidelines, economic evaluations, and clinical decision analyses are others. These other types of integrative articles often incorporate the results of systematic reviews. For example, practice guidelines are systematically developed statements intended to assist practitioners and patients with decisions about appropriate health care for specific clinical circumstances (11). Evidence-based practice guidelines are based on systematic reviews of the literature, appropriately adapted to local circumstances and values. Economic evaluations compare both the costs and the consequences of different courses of action; the knowledge of consequences that are considered in these evaluations is often generated by systematic reviews of primary studies. Decision analyses quantify both the likelihood and the valuation of the expected outcomes associated with competing alternatives. 

Cook, D. J., Mulrow, C. D., & Haynes, R. B. (1997)

Um ponto a fato das revisões sistemáticas é que muitos jornais por questões de limitação de espaço geralmente limitam o número de páginas dos trabalhos em que infelizmente a primeira área a ser sacrificada é a revisão de literatura pelo motivo de que ela não tem um foco tão grande quanto a metodologia, os resultados ou as conclusões. 

Aqui eu vou reunir algumas dicas de 3 artigos, que considero que são boas referências no que se refere à revisão sistemática. São algumas anotações desses artigos, junto com alguns comentários do que eu faço quando tenho que realizar uma revisão sistemática seja para ver como está o estado da arte de um tópico de pesquisa. 

A base fundamental desse artigo está nas ideias de Cook, Mulrow, & Haynes (1997);  Webster e Watson (2002) e Brereton e autores (2007). Esta será apenas uma lista não exaustiva de tópicos, e a leitura dos originais são imprescindíveis. 

Se eu tivesse que escolher um framework de adoção de revisão sistemática, seria este de Brereton e autores (2007):

/var/folders/8d/tpf0m9tx1b51b7lw05rfxnn40000gp/T/com.microsoft.Word/WebArchiveCopyPasteTempFiles/p41606

Autores e tópicos prospectivos

Posicionar sobre o progresso e o aprendizado e embarcar em novos projetos para o desenvolvimento de novos modelos teóricos Webster e Watson (2002) em que essas revisões podem ser em um a) tópico maduro com um vasto corpo de conhecimento ou b) sobre um tópico emergente com uma velocidade de desenvolvimento maior. 

A revisão sobre os tópicos dá direções sobre conceitos e a sua evolução e direções, e a revisão em autores ajuda o trabalho a comunicar com os grandes laboratórios ou com pesquisadores que vão auxiliar no debate sobre o campo científico. 

Escrevendo um artigo de revisão sistemática

O que está sendo buscado? Quais são as keywords? Qual é a contribuição esperada?

Um dos pontos mais importantes é realizar o disclosuredas limitações da revisão como:

  • Escopo da busca (e.g.keywordsusadas, base de artigos)
  • Limite temporal dos artigos
  • Sumário da pesquisa passada, destaque nos gaps, propostas de como encurtar esse gap e implicações da teoria na prática 

Esse disclosuresinaliza que o seu trabalho está tirando uma foto da literatura no momento, e que ela pode não ser perfeita por questões de vícios metodológicos ou mesmo por fatores exógenos a sua pesquisa, como por exemplo, se uma base de dados mudar o indexador de artigos, e a query com os mesmos parâmetros trouxerem outros resultados.

Identificação da literatura relevante 

Aqui a revisão sistemática foca no conceito não importando onde esses conceitos estão. 

Isso implica dizer que o foco não está somente:

  • Nos melhores journals
  • Em alguns autores mais produtivos
  • Em algumas áreas do conhecimento
  • Em questões de amplitude geográfica do país

Escolha de bases de dados

Aqui a revisão toma mais ares de arte do que de ciência de fato, e aqui vem uma visão muito pessoal: Eu particularmente gosto de lidar com mais de 5 bases de pesquisa. Este número eu encontrei através de algumas experimentações, mas foi o número que me dá uma certa amplitude em relação aos artigos que estão indexados nos melhores journalse ajuda a pegar alguns bons artigos e principalmente teses de doutorado que por ventura estão escondidas na página 8 de alguma keywordobscura. 

Outro ponto da base de dados é entender a seletividade da mesma, e seletividade aqui eu chamo de o quanto a ferramenta de busca consegue me trazer um número suficientemente relevantes de artigos com o menor índice de sinal e ruído. 

E como eu não poderia deixar de falar, é sempre tentador ir apenas onde estamos mais familiarizados como o Google Scholare no Microsoft Research; mas a dica aqui é procurar bases de dados de outras áreas do conhecimento. 

Estrutura da revisão

Deve focar principalmente nos conceitos e não nos autores.

Uma coisa que ajuda e muito é as categorizações dos artigos de forma qualitativa, em que aspectos de gaps, tipo de metodologia, natureza do trabalho pode ser compiladas posteriormente.

Desenvolvimento Teórico

A ponto aqui é baseado no passado, no atual estado das coisas e as limitações e gaps presentes, como usar isso para o futuro?

Aqui eu recomendo uma expansão de ideias modesta, algo que não seja uma pesquisa de 10 anos para o futuro e que tenha plausibilidade.

Razão para os proponentes

  • Explicações teóricas (O porquê?): Essa será a cola que vai grudar toda a prática de uma maneira sistematizada, reprodutível, observável, transferível e replicável;
  • Achados empíricos do passado: O suporte do que foi observado ao longo do tempo, e a qualidade das evidências apresentadas e como essas observações foram realizadas; e
  • Prática e experiência: Mecanismos de validação da teoria e refinamentos posteriores do que está sendo teorizado e feito. 

Conclusão

Eu acho que neste ponto eu consegui mostrar o meu ponto em relação a importância da revisão sistemática como ferramenta para entendimento do passado e do presente, como também como método que ajuda a planejar o futuro em termos de pesquisa. 

Eu pessoalmente recomendo sempre que houver algum tipo de adoção de prática o uso dessa ferramenta antes de qualquer projeto acadêmico.

Referências 

Cook, D. J., Mulrow, C. D., & Haynes, R. B. (1997). Systematic reviews: synthesis of best evidence for clinical decisions. Annals of internal medicine126(5), 376-380. – Link: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.733.1479&rep=rep1&type=pdf

Brereton, P., Kitchenham, B. A., Budgen, D., Turner, M., & Khalil, M. (2007). Lessons from applying the systematic literature review process within the software engineering domain. Journal of systems and software80(4), 571-583. – 

Link: https://www.sciencedirect.com/science/article/pii/S016412120600197X

Webster, J., & Watson, R. T. (2002). Analyzing the past to prepare for the future: Writing a literature review. MIS quarterly, xiii-xxiii. Link: https://www.researchgate.net/profile/Harald_Kindermann/post/How_to_write_the_academic_review_article_in_the_field_of_management/attachment/5abe1af54cde260d15d5d477/AS%3A609838266593280%401522408181169/download/2002_Webster_Writing+a+Literature+Review.pdf

Algumas dicas úteis de como fazer uma revisão sistemática

O Campeonato Brasileiro está ficando mais injusto?

Uma análise exploratória usando o Coeficiente de Gini

Todos os dados e a análise completa pode ser encontrada no repositório brasileirao-gini. Lá tem todas as instruções para executar passo a passo a análise.

Introdução

Uma das coisas boas da era digital é que com o uso do WhatsApp podemos ter as nossas mesas redondas virtuais com os nossos amigos não importa o quanto estamos longe estamos deles. E dentro de um dessas resenhas virtuais estavamos discutindo o ótimo trabalho do treinador do Flamengo na atual temporada (i.e. aspectos relativos de como ele mudou positivamente o time).

Porém, apareceu um tópico relevante durante o debate que foi a hipótese que na medida em que entra mais dinheiro no campeonato e nos times maiores o Brasileirão vai ficando cada vez mais injusto com um conjunto de times ganhando muitos jogos e os times pequenos ficarem relegados a meros coadjuvantes dentro da liga.

Em outras palavras, isso significa que o campeonato sempre é disputado pelos mesmos clubes com poderio econômico e por causa dessa disparidade a competitividade poderia estar diminuindo ao longo do tempo.

Isso levantou a questão dentro do nosso grupo que foi: O brasileirão está ficando mais injusto ao longo do tempo?

E é essa pergunta que eu vou tentar responder no final desse post.

Como checar se há uma desigualdade estrutural no Campeonato Brasileiro?

E para responder inicialmente essa pregunta eu vou usar o Coeficiente de Gini que é uma métrica para medir a dispersão estatística que inicialmente foi criada para medir a distribuição de renda e riqueza entre países e é amplamente usada em economia como um importante indicador de monitoramento dessas questões.

O Coeficiente de Gini é usado como medida relativa para benchmark e monitoramento de desigualdade e pobreza através de diversos países e serve como base de analise e desenvolvimento de politicas publicas como você pode conferir nos trabalhos de SobottkaMoreira e autores e pelo Instituto de Pesquisa Econômica Aplicada o IPEA no trabalho de Barros e autores.

Como este post não é para falar em profundidade sobre este indicador eu sugiro a leitura do trabalho original de Conrrado Gini chamado Variabilità e Mutabilità ou este trabalho sobre como esse indicador foi usado de parâmetro para alguns algumas analises de bem estar social no Brasil.

Isto é, eu vou medir de forma simples a variância da distribuição dos pontos dentro de cada uma das edições do Campeonato Brasileiro (que eu vou chamar daqui em diante de Brasileirão) para verificar se há uma desigualdade latente estrutural dentro dos campeonatos e ao longo dos anos.

Algumas considerações prévias sobre algumas limitações sobre a forma de medir essa desigualdade

De acordo com o que coloquei anteriormente podemos fazer de maneira bem simples a mensuração de fatores importantes como desigualdade econômica através da renda e/ou riqueza usando o Coeficiente de Gini, e aqui eu vou usar uma alegoria simples de como eu vou aplicar isso nos dados do Brasileirão.

Se esse índice serve de alguma maneira medir a desigualdade entre países considerando a sua riqueza ou mesmo a sua renda, trazendo para o mundo do futebol podemos usar como representação dos dados os pontos ganhos de um time dentro de uma temporada como se fosse a renda e aplicar o Coeficiente de Gini regular não somente dentro de um ano especifico da liga mas também monitorar essa desigualdade ao longo do tempo.

Para isso eu vou usar a base de dados de todos os resultados do Brasileirão desde 2003 até 2018 extraídos da Wikipédia. E aqui eu tenho que fazer duas considerações que são:

São algumas limitações importantes que devem ser consideradas, dado que o número de pontos em disputa foi modificado e um ajuste é necessário.

Cabe ressaltar que o Coeficiente de Gini e da análise em si possuem inúmeras limitações que devem ser entendidas como:

  • Não levar o “Fator Tradição” em consideração a um efeito de produtividade dos clubes ao longo do tempo, i.e., um clube grande e antigo carrega uma estrutura financeira/econômica/institucional maior do que os clubes mais novos, e isso pode ser visto na distribuição dos campeões na era dos pontos corridos (Em economia isso seria similar ao efeito de transição de produtividade de uma base instalada produtiva ao longo dos anos);
  • Diferenças econômicas das regiões em que os clubes têm as suas bases;
  • O Coeficiente de Gini olha somente o resultado final sem levar em considerações fatores conjunturais que podem influenciar estes mesmos resultados como gestão, e momento econômico do time, e outros eventos como Olimpíada e Copa do Mundo;
  • O conceito da natureza da geração dos pontos (o que em economia seria a renda) podem ter dinâmicas muito diferentes dado que o índice não captura se os pontos foram gerados através de 3 empates (1 ponto multiplicado por 3) ou por uma vitória e duas derrotas (3 pontos);
  • Analisar a geração de pontos em si ao longo do tempo pode ser bem complicado e não representaria uma comparação plausível. Por exemplo: O Flamengo campeão brasileiro de 2009 seria na melhor das possibilidades um mero quinto colocado em 2014;
  • O índice em si por tratar somente do resultado final, não mostra a transitividade desses pontos ao longo do campeonato. Explico: Se um time nas rodadas finais do campeonato não tem mais nenhum tipo de chance de ir para uma boa competição(Copa Libertadores), de ser rebaixado para divisões inferiores, ou mesmo já conquistou o titulo pode acontecer desses times entrarem com menos disposição para ganhar os jogos. Incentivos financeiros entre os times também podem ocorrerEste artigo da Investopedia fala um pouco sobre esse efeito;

Algumas outras limitações do Coeficiente de Gini podem ser encontradas no trabalho de Tsai, no Working Paper de Osberg, no site do HSRC ou para quem quiser uma critica mais vocal tem esse ensaio do Craig Wright.

Para calcular o Coeficiente de Gini eu usei o código da Olivia Guest apenas para fins de simplicidade, mas qualquer software pode ser usado uma vez que os dados estão disponíveis no repositório.

Como o código têm muito boilerplate code de coisas que eu já fiz no passado e o meu foco é a analise em si, eu não vou comentar o código inteiro.

Vamos dar uma olhada inicial apenas para ver se os dados foram carregados corretamente.

# Imports, 538 theme in the charts and load data
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%pylab inline
plt.style.use('fivethirtyeight')
df_brasileirao = pd.read_csv('dataset-2003-2018.csv', delimiter=';')
print(f'Number of Records: {df_brasileirao.shape[0]} – Number of Columns: {df_brasileirao.shape[1]}')
# Loading Check
df_brasileirao.head(5)
view raw imports.py hosted with ❤ by GitHub

Aparentemente tudo OK com os dados, então eu vou realizar o calculo do Coeficiente de Gini usando todas as edições do Brasileirão.

Ranking de desigualdade entre todas as edições do Brasileirão usando o Coeficiente de Gini

Para quem quiser calcular, basta usar essas duas funções:

# Gini function as PyGini package
def gini(arr, eps=1e-8):
'''
Reference: PyGini (I owe you a beer @o_guest)
https://github.com/mckib2/pygini/blob/master/pygini/gini.py
Calculate the Gini coefficient of a numpy array.
Notes
—–
Based on bottom eq on [2]_.
References
———-
.. [2]_ http://www.statsdirect.com/help/
default.htm#nonparametric_methods/gini.htm
'''
# All values are treated equally, arrays must be 1d and > 0:
arr = np.abs(arr).flatten() + eps
# Values must be sorted:
arr = np.sort(arr)
# Index per array element:
index = np.arange(1, arr.shape[0]+1)
# Number of array elements:
N = arr.shape[0]
# Gini coefficient:
return(np.sum((2*index N 1)*arr))/(N*np.sum(arr))
def get_gini_df(df):
"""Generate DF with Gini Index
Parameters
———-
df : Pandas Dataframe
Dataframe with Brasileirão data
Returns
——-
gini_df : Pandas Dataframe
Returns a Pandas Dataframe with the year, team and gini index
"""
gini_per_year = []
for year in df['year'].unique():
championship_index = gini(np.array(df[df['year'] == year]['points']))
champion = (df[(df['year'] == year) & (df['position'] == 1)]['team'])
gini_per_year.append((year, champion.values[0], round(championship_index, 4)))
gini_df = pd.DataFrame(gini_per_year)
gini_df.columns = ['year', 'team', 'gini']
# Indexing the date field for graph it smoothly
gini_df.set_index('year', inplace=True)
return gini_df
gini_df = get_gini_df(df_brasileirao)
gini_df.sort_values(by=['gini'], ascending=True)

Usando os dados que foram carregados e o código acima, eu realizei o cálculo do Coeficiente de Gini e obtive a seguinte tabela:

Coeficiente de Gini Calculado com os respectivos campeões de cada edição

Considerando o Coeficiente de Gini como principal métrica de ordenação, podemos ver que a edição do Brasileirão de 2017, 2005 (ambas com o Corinthians campeão) e de 2009 (Flamengo campeão) foram as que tiveram mais igualdade dentro da era dos pontos corridos.

Por outro lado, as edições de 2018 (Palmeiras campeão), 2014 (Cruzeiro campeão) e 2012 (Fluminense campeão) foram as mais desiguais no que se refere a distribuição final dos pontos. Um fato curioso é que se pegarmos as 5 temporadas mais desiguais, veremos o Palmeiras e o Fluminense com 2 títulos cada (respectivamente 2018, 2016 e 2012, 2010).

Essas informações apontam que Corinthians e Flamengo tendem a ganhar as temporadas com a menor distribuição de pontos finais, e quando Palmeiras e Fluminense ganham são geralmente temporadas mais desiguais da perspectiva de distribuição dos pontos no final. Aliaá isso seria uma boa hipótese inicial para ser testada com mais dados.

Vamos olhar os dois extremos que são as temporadas de 2017 (mais igual) e 2018 (mais desigual).

Brasileirão 2017
Brasileirão 2018

Olhando a distribuição dos pontos aqui, podemos ver que a diferença de pontos entre os campeões foi de 8 pontos (80-72) e considerando a distancia do campeão com o quinto colocado nos dois campeonatos, se em 2017 temos uma distância de 15 pontos (72-57) em 2018 temos 17 pontos (80-63), ou seja muito parecida.

Entretanto, sabendo que o campeão sempre têm um pouco de margem considerando apenas esses extremos, se considerarmos apenas a distância entre o vice-campeão e o quinto colocado em 2017 temos apenas 6 pontos (63-57) enquanto em 2018 essa distância vai para 9 pontos (72-63).

Realizando o mesmo exercício entre o campeão e o pior time do campeonato em 2017 temos uma diferença de 36 pontos (72-36) enquanto em 2018 chegamos a expressiva marca de 57 pontos de diferença (80-23). Isso mostra que mesmo entre os piores times ao longo desses dois campeonatos temos a expressiva diferença de 13 pontos (36 (Atlético Goianiense/2017 – 23 (Paraná). Vamos ficar atentos a essas informações, pois eu vou voltar aqui posteriormente.

Agora eu vou gerar o gráfico de como ficou esse Coeficiente de Gini ao longo do tempo.

def get_graph_ts(df, column, title, label):
"""Generate graph of a Time Series
Parameters
———-
df : Pandas Dataframe
Dataframe with Brasileirão data
column : string
Column with the metric to be ploted
title : string
Graph title to be displayed
label : string
Name of the series that will be placed
as legend
Returns
——-
"""
plt.figure(figsize=(20,10))
plt.title(title)
plt.xlabel('Years')
plt.ylabel('Gini Index')
plt.plot(df[column], label=label)
plt.xticks(gini_df.index)
get_graph_ts(gini_df,
'gini',
'Gini Index in Brasileirão',
'Gini Index',
)
view raw graph-gini.py hosted with ❤ by GitHub
Coeficiente de Gini no Brasileirão ao longo do tempo

Em uma primeira análise podemos perceber algumas curiosidades:

  • Ao que parece temos sim uma tendência não tão clara de crescimento da desigualdade, mas com vales e picos bem distintos e considerando que o formato de 20 times têm apenas 12 temporadas completas isso tem que ser levado com cautela;
  • Algo surpreendente é que os vales costumam acontecer nos anos ímpares e os picos nos anos pares. Isso talvez seja explicado por algum efeito externo, tal como as Olimpíadas e Copa do Mundo que ocorrem em anos pares. E uma hipótese bem fraca, mas ainda sim é uma hipótese. (Nota do Autor: Se algueém tiver uma explicação razoável pode me mandar um comentário que eu coloco aqui e dou o crédito);
  • Ao que parece depois de 2011 houve uma subida mais consistente e manutenção desse aumento da desigualdade com o Coeficiente de Gini voltando para baixo de 0.115 apenas em 2017, ou seja, uma janela de 6 anos acima do patamar de 0.115;
  • E falando em 2017, essa temporada ao que parece foi uma total quebra em relação a essa desigualdade, dado que considerando o segundo, terceiro e quarto colocados terminarem com a diferença de apenas 1 ponto3 times ficaram com 43 pontos, com dois deles sendo rebaixados para a segunda divisão.

Entretanto, uma coisa que eu considerei foi que talvez tenha algum efeito que eu chamo de “Última temporada na Série A” ou “Já estou rebaixado mesmo, não tem mais o que fazer” em relação a essa (des)igualdade, dado que sempre saem/entram 4 times por ano (i.e. 20% dos times são trocados todos os anos). Para remover esse potencial efeito, eu vou considerar uma média móvel considerando os 3 últimos campeonatos. Ou seja, sempre haverá a combinação de a) dois anos desiguais e um ano igual e b) dois anos iguais com um ano desigual.

# Some graphs with rolling average
date_range = [2003, 2004, 2005, 2006, 2007,
2008, 2009, 2010, 2011, 2012,
2013, 2014, 2015, 2016, 2017,
2018]
def get_graph_ts_rolling_average(ts, title, window, date_range=date_range):
"""Generate graph of a Time Series with a simple rolling average
Parameters
———-
ts : Pandas Dataframe column
Dataframe column with a metric to be ploted
title : Pandas Dataframe
Graph title to be displayed
window : int
Rolling back window to be considered in the average
date_range : Array
Array to be used in the ploting. Matplotlib has a
very bad way to deal with that, so I need to use this
workaround to place all years properly
Returns
——-
"""
plt.figure(figsize=(20,10))
plt.plot(date_range, ts.rolling(window=window, center=False).mean(), label='gini');
plt.title(f'{title}{window}')
plt.xlabel('Years')
plt.ylabel('Gini Index')
plt.xticks(gini_df.index)
plt.legend()
get_graph_ts_rolling_average(gini_df['gini'],
'Rolling Average in Gini Index in Brasileirão – Rolling Average window=',
3,
)
view raw rolling-gini.py hosted with ❤ by GitHub
Coeficiente de Gini quando consideramos uma janela de 3 edições para computar a média

Realizando essa suavização a grosso modo, podemos ver o efeito desse aumento da desigualdade mais claro, quase que de forma linear de 2009 até 2016 sendo quebrado apenas pela famigerada temporada de 2017, e com a temporada de 2018 sofrendo um efeito claro com esse ajuste.

Anteriormente eu falei sobre essa disparidade dos pontos entre os primeiros colocados, entre os últimos colocados e os campeões em relação aos piores times da liga. Podemos perceber que sempre nestes casos extremos temos o campeão vencendo com uma pequena folga, e com os times subsequentes com algum tipo de disputa em ordens diferentes de magnitude.

Para validar esse ponto de que essa desigualdade está aumentando de uma maneira um pouco mais robusta, eu vou remover os nossos outliers desses campeonatos, ou resumindo: Eu vou tirar o campeão e o pior time da temporada da análise.

(Nota do Autor: Eu sei que existem inúmeras abordagens de como se fazer isso da maneira correta, com recursos quase que infinitos na internet de técnicas (1234 e inclusive com testes estatísticos muito robustos de como se fazer isso da maneira correta, caso seja necessário. Por questões de simplicidade e para reforçar o meu ponto, eu estou removendo dado que dentro dessas situações extremas como eu tenho como resultados a) o campeão com uma leve vantagem no final e b) o pior time da temporada sendo muito pior mesmo, a ideia é ver a igualdade dos outros times no campeonato. Lembrem-se que eu quero ver de maneira geral (des)igualdade da liga e tirar o efeito dos supercampeões e dos vergonhosos sacos de pancada da temporada).)

Dito isto, vou remover esses outliers e calcular novamente o Coeficiente de Gini.

def get_brasileirao_no_outliers(df):
"""Generate a DF removing the champion and the worst team of the championship
Parameters
———-
df : Pandas Dataframe
Dataframe with Brasileirão data
Returns
——-
df_concat : Pandas Dataframe
Returns a Pandas Dataframe without the outliers
"""
df_concat = pd.DataFrame()
for year in df['year'].unique():
pos_min = df[df['year'] == year]['position'].min()
pos_max = df[df['year'] == year]['position'].max()
df_filtered = df[(df['year'] == year) \
& (~df['position'].isin([pos_min, pos_max]))]
df_concat = df_concat.append(df_filtered)
return df_concat
def get_gini(df):
"""Generate a DF with the year and the following Gini Index calculated
Parameters
———-
df : Pandas Dataframe
Dataframe with Brasileirão data
Returns
——-
gini_df : Pandas Dataframe
Returns a Pandas Dataframe with the year, and gini index
"""
gini_per_year = []
for year in df['year'].unique():
championship_index = gini(np.array(df[df['year'] == year]['points']))
gini_per_year.append((year, round(championship_index, 4)))
gini_df = pd.DataFrame(gini_per_year)
gini_df.columns = ['year', 'gini']
# Indexing the date field for graph it smoothly
gini_df.set_index('year', inplace=True)
return gini_df
# Outlier removal
df_brasileirao_no_outliers = get_brasileirao_no_outliers(df_brasileirao)
df_brasileirao_no_outliers_gini = get_gini(df_brasileirao_no_outliers)
df_brasileirao_no_outliers_gini.sort_values(by=['gini'], ascending=True)
Gini Index com o campeão e o último colocado do campeonato removidos

Agora temos algumas mudanças significativas no nosso painel que são:

  • Se antes tínhamos as temporadas de 2017, 2005 e 2009 como as mais iguais, agora tiramos a temporada de 2009 vencida pelo Flamengo, e colocamos a de 2007 vencida pelo São Paulo;
  • Pelo lado das temporadas mais desiguais, no caso tínhamos a ordem de desigualdade pelas temporadas de 2018, 2014, e 2012, agora temos a nova ordem em relação as edições de 2014 (Cruzeiro), 2012 (Fluminense) e 2018 (Palmeiras)

Vamos checar como ficou a tabela das edições mais extremas sem os campeões em 2007 e 2014.

Brasileirão 2007
Brasileirão 2014

Olhando as duas tabelas finais em que removemos o campeão e o pior time, logo de cara podemos reparar que enquanto na temporada de 2014 temos dois vales de diferenças de 7 pontos (4° e 5° e 7° e 8°), no campeonato de 2007 a maior diferença foi de 4 pontos (15° e 16°) e fazendo um pequeno exercício de imaginação podemos dizer que o Paraná que teve 41 pontos em 2007 não teria perigo nenhum de ir para a segunda divisão, se tivesse o mesmo número de pontos no campeonato de 2014. Como eu coloquei anteriormente essa análise de transpor um time ao longo do tempo não é muito valida, porém, mencionei isso apenas para ressaltar a importância da distribuição dos pontos ao invés do número absoluto de pontos no resultado final.

Vamos gerar o gráfico apenas para verificar se a tendência do aumento da desigualdade permanece ou não.

Gini Index ao longo do tempo, removendo o campeão e o pior time da temporada
Gini Index ao longo do tempo, removendo o campeão e o pior time da temporada, considerando a média das 3 últimas edições

Olhando o Coeficiente de Gini removendo o campeão e o pior time, podemos ver que ainda temos a tendência de aumento da desigualdade dentro da liga, seja usando a métrica de forma regular ou aplicando uma média móvel com uma janela de 3 temporadas ao longo do tempo.

Fatos

Ao longo dos dados apresentados nessa análise eu cheguei aos seguintes fatos:

  • Temos uma tendência crescente na desigualdade em relação ao número de pontos entre os times dentro do campeonato brasileiro;
  • Esse crescimento começa de forma mais substancial em 2010;
  • As temporadas mais recentes (2018 e 2017) são respectivamente as temporadas com a maior e a menor desigualdade;
  • Essa tendência do aumento da desigualdade ocorre mesmo removendo o campeão e o pior time da temporada, dado que o campeão nos casos extremos possui uma pequena vantagem em relação ao segundo colocado, e o pior time termina com uma pontuação virtualmente impossível de reverter em uma ocasião de última rodada;
  • Nas temporadas que há uma desigualdade maior existe vales de pontos entre blocos de times, no caso que vimos esses vales foram de 7 pontos (2 vitorias + 1 empate);
  • Nas temporadas mais iguais esses vales de blocos times provavelmente são menores ou em alguns casos inexistentes, no caso observado havia somente um bloco de 4 pontos (1 vitória + 1 empate) e excluindo este fato não havia nenhuma distância maior do que 1 vitória por toda a tabela entre posições imediatamente superiores.

Conclusão e considerações para o futuro

Se tivermos que responder a nossa pergunta principal que foi “O brasileirão está ficando mais injusto ao longo do tempo?” a resposta correta seria:

“Sim. Com o uso do Coeficiente de Gini como métrica para mensurar se há uma desigualdade estrutural mostrou que existem sim elementos latentes dessa desigualdade”.

O leitor mais atento pode repaar que uma coisa que eu tomei muito cuidado aqui foi para não realizar afirmações relativas à competitividade, afirmações relativas às condições financeiras dos clubes, incremento de premiações ou ausência de incentivos para os piores colocados, etc.; dado que estes aspectos são difíceis de mensurar e há pouquíssimos dados disponíveis de maneira confiável para a análise, mas empiricamente talvez podemos realizar algumas afirmações nessas direções.

Em relação a análises futuras existem muitas hipóteses que podem ser testadas como um potencial problema fundamental de competitividade devido ao fato de muitos clubes que não representam uma elite (i.e. muitos times fracos na liga principal) e inclusive existem pautas relativas ao aumento do número dos clubes rebaixados, hipóteses que elencam fatores importantes como a disparidade financeira como um dos potenciais fatores de existir poucos supertimes, há uma hipótese que ganha tração também que é a respeito das cotas financeiras de direitos de exibição de jogos na televisão que compõe grande parte da receita desses clubes que a contar do ano que vem vai punir pesadamente os times que forem rebaixados.

São muitas hipóteses em discussão e muitos aspectos que poderiam ser a razão para o aumento dessa desigualdade. Há um grande número de aspectos que podem ser estudados e no final desse post tem alguns links para referências em outras ligas.

Todos os dados e a análise completa pode ser encontrada no repositório brasileirao-gini. Lá tem todas as instruções para executar passo a passo a análise.

Referências e links úteis

Inequality in the Premier League – Çınar Baymul

An Analysis Of Parity Levels In Soccer – Harvard Sports

Which Sports League has the Most Parity? – Harvard Sports

Major League Soccer and the Effect of Egalitarianism – Harvard Sports

The Gini Coefficient as a Measure of League Competitiveness and Title Uncertainty – Australia Sports Betting

Mourão, P. R., & Teixeira, J. S. (2015). Gini playing soccer. Applied Economics, 47(49), 5229-5246

How “fair” are European soccer leagues? Gini index applied to points distribution of 5 soccer leagues between 2000 and 2015 – r/soccer

Footballomics: Estimating League Disparity Performance with a Point-Rank Gini Index – Christoforos Nikolaou

O Campeonato Brasileiro está ficando mais injusto?