Ferramentas e organização em um projeto de Data Science

Ricardo Manhães Savii
Dafiti Group
Published in
7 min readJan 23, 2020
Header image contains work from others: Jupyter logo, GitHub logo, Python logo, Makefile logo by [Scott Doxey](https://dribbble.com/shots/2725990-Makefile-Logo), Docker logo and [Einstein’s cool image](https://www.teepublic.com/t-shirt/2925048-einstein-funny-t-shirt)

Após ter iniciado muitos projetos de data science, ter entregue alguns, e ainda estar trabalhando na integração e qualidade de software de vários, eu adquiri uma série de estratégias de organização para estruturar o software final. Em muitos projetos meus, e outros que vejo, eu encontro alguns problemas: falta de definição de ambiente e empacotamento da aplicação, falta de modularidade, falta de testes, falta de documentação, entre outros.

Acredito que não sou só eu que percebo este problema. Muitos artigos apontam os problemas causados pela falta de documentação. Por exemplo, gosto deste texto de Stacey Higginbotham na IEEE “Hey Data Scientists, show your machine learning work”. O mundo acadêmico se preocupa cada vez mais com a perda de conhecimento e o lento avanço da ciência devido à carência de replicabilidade. Por exemplo, a ACM trabalha aplicando badges conforme uma taxonomia interessante para publicações, diferenciando seu nível de replicabilidade.

Fora da academia temos de longa data a cultura de Engenharia de Software, DevOps, SRE (System Reliability Engineering), as quais já estão bem maduras em metodologias e ferramentas para construção de um sistema testado, confiável, modularizado, de manutenção simplificada, ou seja, com um nível de maturidade bom. Porém a ciência de dados sofre com a falta de padrões, na hora de entregar um Sistema Inteligente, correndo o grande risco de este ser uma caixa preta para quem o usa e, talvez, para quem o irá manter. Isto não só prejudica os profissionais envolvidos no processo, que ficarão amarrados futuramente à sistemas legados de difícil manutenção, mas também acabará prejudicando as empresas cujas equipes estarão muito ocupadas mantendo caixas pretas enquanto o débito técnico só aumenta.

Enfim, muitos e muitos problemas podem surgir. Mas acredito que um caminho para amenizar os riscos é a transparência e acordos de documentação e maturidade de software. No Dafiti Group criamos um sistema de avaliação de maturidade dos sistemas. É, basicamente, um check-list graduado de características de um sistema avaliado com notas de 0 a 5. Basicamente, um sistema pode até funcionar em seu notebook, porém se ela estiver no nível de maturidade 0 esse sistema ainda não pode ser colocado em produção, pois carece de documentação, os logs não estão formatados e bem definidos, não há uma cobertura de testes suficientes, entre outras características.

Sob esta necessidade de criar Sistemas Inteligentes com um nível de maturidade bom o suficiente para não ser uma caixa preta, eu venho estudando e aplicando algumas ferramentas que me ajudam na hora de ter um software mais compreensível.

Eu separo mentalmente os projetos em três principais etapas: EDA (Exploratory Data Analysis), PoC (Proof of Concept) e Desenvolvimento.

Exploratory Data Analysis

Esta seção, é bem pouco representada no projeto como um todo, porém pode ocupar a maior parte do tempo de desenvolvimento de um projeto. EDA é a fase em que estamos tratando de entender o problema apresentado. De forma bem resumida, definimos fontes de dados, avaliamos todos os dados que temos e sua relação com a tarefa, construímos features e definimos um pipeline de dados. Para mim, esta etapa tem dois objetivos: 1 - identificar o problema e 2 — montar o pipeline de dados para preparar a base de dados para treino, teste, validação e payload de predição do sistema final. Esta etapa, gigante, eu tento resumir em um ou dois notebooks dentro de uma pasta com mesmo nome.

Proof of Concept

A prova de conceito é tentar demonstrar que conseguimos de alguma forma fornecer a modelagem requerida para sanar o problema, com um erro aceitável. Esta tarefa, por ser muito exploratória, testa diversos modelos, diversas formas de pré-processamento e inúmeras variabilidades, eu tento também resumir em um ou dois notebooks dentro da mesma pasta mencionada anteriormente.

Até este momento meu repositório de código é algo bem pequeno, algo assim:

.
└── notebooks/
├── EDA.ipynb
├── ...
└── POC.ipynb

No fluxo de trabalho por aqui, nem todos os projetos podem/devem chegar na etapa de desenvolvimento. O projeto pode chegar ao final da PoC e concluirmos que, por exemplo, não conseguimos resolver o problema de forma aceitável. Nesse caso, o projeto seria cancelado, tentaremos extrair insights/aprendizados e formalizar uma retrospectiva do projeto para entender se podemos refatorar o projeto de alguma forma ou seguir adiante.

Desenvolvimento

No caso de obtermos sucesso na etapa de PoC entraremos na etapa de desenvolvimento e integração do modelo de aprendizado de máquina com a equipe stakeholder ou algum sistema. Neste momento é onde eu crio toda uma nova estrutura de pastas no repositório seguindo o seguinte padrão:

.
├── notebooks/
│ └── ...
├── .circleci/
│ └── config.yml
├── docker/
│ └── Dockerfile
├── Makefile
├── README.md
├── MATURITY.md
├── requirements.txt
├── <main_application>/
│ ├── config.ini
│ ├── <entrypoint_script1>
│ ├── train
│ ├── serve
│ ├── ...
│ ├── services/
│ │ ├── sagemaker_starter.py
│ │ └── terraformer.py
│ ├── utils/
│ │ ├── archives.py
│ │ ├── arg_parser.py
│ │ ├── credentials.py
│ │ ├── files.py
│ │ └── logger.py
│ └── version.py
├── ml/
│ ├── input/
│ │ ├── ...
│ │ └── query.sql
│ └── output/
├── scripts/
│ ├── build_and_push.sh
│ └── test.sh
├── secrets/
│ ├── gcp_credentials.json
│ └── secrets.yml
├── .gitignore
├── setup.cfg
└── setup.py

Antes de entrarmos nas pastas temos alguns arquivos na raiz do projeto que são muito importantes. O README.md contém uma documentação padrão - como eu tenho trabalhado seguindo uma mesma estrutura isso me economiza esforço em sempre criar um README do zero, grande parte é a mesma e eu preciso detalhar somente o algoritmo utilizado e se há alguma arquitetura específica do sistema.

O Makefile é um arquivo que, particularmente, adoro. Eu sou péssimo para memorizar comandos shell e scripts para iniciar funcionalidades. Com o Makefile, eu simplifico isso criando uma série de targets padrões para todos os meu projetos. Por exemplo, qualquer projeto meu responderá quando eu executar o comandomake data , cada projeto tem uma forma única de criar os dados, que está por trás desse comando. Outros targets que tenho padrões são make preprocess , make train , make local-serve e make local-predict . Caso queira aprender mais sobre Makefile indico esta leitura rápida para começar: https://krzysztofzuraw.com/blog/2016/makefiles-in-python-projects.html

O arquivo MATURITY.md contém o checklist de características do Dafiti Group. Isto, pessoalmente, me motiva muito quando vou cumprindo as tarefas para melhorar a maturidade do sistema. :) Os arquivos setup.py e setup.cfg seguem os padrões de packaging do python, e os uso mais como metadados do projeto: quem é o autor, tipo de aplicação, tags e labels relacionadas.

Uso, normalmente, um único arquivo requirements.txt em conjunto com um docker/Dockerfile para definir o ambiente da aplicação. Isso também me facilita no CICD (Code integration e Code Deployment), que é feito através do circleci, como pode-se ver no arquivo circleci/config.yml . Entre as etapas de deployment, normalmente está em buildar a imagem docker da aplicação na pasta <main_application>/ e subir para algum container registry, na AWS ou no Google Cloud Platform. Assim, de qualquer lugar (logado ao registry) eu posso usar o docker run <imagem> <entrypoint_script> . Esse padrão atende os principais serviços de treinamento em nuvem: AWS SageMaker, Google Cloud Machine Learning Engine ou um Kubernetes com Kubeflow.

A pasta ml existe por minha inabilidade de testar tudo em nuvem. Eu ainda sinto a necessidade de rodar localmente e como algoritmos de aprendizado de máquina possuem dependências de arquivos gerados durante alguns processos (exemplos, datasets, modelos salvos), é nessa pasta ml onde eu salvo. E normalmente eu incluo essa pasta no .gitignore para evitar problemas de arquivos muito grandes, apesar de normalmente eu lidar localmente com amostragem, servindo mais como um teste de integração.

No .gitignore eu também incluo a pasta secrets/ , onde guardo algumas chaves de acesso de serviços cloud. Conseguindo assim submeter diretamente algum job. Por último, temos a pasta scripts onde guardo scripts padrões para suporte ao CICD, por exemplo. Eu tenho algumas variações de scripts build_and_push.sh dependendo para qual cloud iremos fazer o deploy, assim o meu código do circleci não muda chamando scripts de mesma assinatura, porém para clouds distintas.

A pasta principal da aplicação contém na sua raiz os entrypoint_scripts , que para projetos deste tipo, normalmente, há train e serve , mas pode incluir outros, dependendo da necessidade. Dentro da pasta da aplicação eu tenho dois módulos: services e utils . services são classes que definem comportamentos específicos para esse projeto específico, por exemplo, uma class MLModel e uma Experiment . Já o módulo utils é composto por diversas funcionalidades rápidas e que me servem para diversos projetos. Uma intenção futura minha é tornar essa pasta utils em um pacote pip, ou algo do tipo. Pois tenho classes para lidar com Archives , que são funções de mesma assinatura para lidar com AWS S3 ou Google Cloud Storage. Por terem as mesmas assinaturas, mudar de uma cloud para outra exigem pouquíssimas mudanças nas minhas aplicações.

Algo que está em meus próximos passos é aumentar a cobertura de testes dos projetos. Até por que este texto já ficou um pouco grande.

Estou um pouco desatualizado com meus repositórios públicos. Mas minha intenção é que este padrão de projeto estará neste repositório público: https://github.com/ricoms/ml-explore-template caso alguém queira ajudar ou apontar issues, criticar ou sugerir formas, por favor, fique à vontade.

--

--

Ricardo Manhães Savii
Dafiti Group

He/Him | Machine Learning Engineer | Eternal student of Intelligent Systems • BR • Use technology as an empowering tool.