Pular para o conteúdo principal

Capítulo 1: Git para Projetos de ML

Introdução​

O Git é a base do controle de versão no desenvolvimento de software, mas projetos de ML apresentam desafios únicos que exigem práticas e configurações especializadas.

Desafios Específicos de Git em ML​

A Natureza Única do Código de ML​

Diferente do software tradicional, projetos de ML envolvem:

DesafioDescrição
Arquivos grandesConjuntos de dados, pesos de modelos (GB a TB)
Artefatos bináriosModelos treinados, pré-processadores
Proliferação de experimentosCentenas de branches experimentais
Não-determinismoO mesmo código pode produzir resultados diferentes
Caos de notebooksNotebooks Jupyter com outputs embutidos

Estrutura de Repositório para Projetos de ML​

Estrutura Recomendada​

ml-project/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── training.yml
│ └── deployment.yml
├── data/
│ ├── raw/ # Dados originais (controlados pelo DVC)
│ ├── processed/ # Dados transformados (controlados pelo DVC)
│ └── features/ # Exportações do feature store
├── models/
│ ├── artifacts/ # Modelos treinados (controlados pelo DVC)
│ └── configs/ # Configurações de modelos
├── notebooks/
│ ├── exploration/ # Notebooks de EDA
│ └── experiments/ # Notebooks de experimentos
├── src/
│ ├── __init__.py
│ ├── data/
│ │ ├── __init__.py
│ │ ├── ingestion.py
│ │ └── preprocessing.py
│ ├── features/
│ │ ├── __init__.py
│ │ └── engineering.py
│ ├── models/
│ │ ├── __init__.py
│ │ ├── train.py
│ │ └── predict.py
│ └── utils/
│ ├── __init__.py
│ └── helpers.py
├── tests/
│ ├── unit/
│ ├── integration/
│ └── data/
├── configs/
│ ├── training_config.yaml
│ └── serving_config.yaml
├── .dvc/
├── .gitignore
├── .pre-commit-config.yaml
├── dvc.yaml
├── dvc.lock
├── pyproject.toml
├── requirements.txt
└── README.md

.gitignore Abrangente para ML​

# .gitignore para Projetos de ML

# ===== Python =====
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# ===== Ambientes Virtuais =====
venv/
ENV/
env/
.venv/
.conda/

# ===== Jupyter Notebooks =====
.ipynb_checkpoints/
*/.ipynb_checkpoints/

# ===== Arquivos de Dados =====
# Arquivos de dados grandes (controlados pelo DVC)
*.csv
*.parquet
*.feather
*.h5
*.hdf5
*.pkl
*.pickle
!data/.gitkeep
data/raw/*
data/processed/*
data/features/*

# ===== Artefatos de Modelos =====
# Modelos treinados (controlados pelo DVC)
*.pt
*.pth
*.onnx
*.pb
*.h5
*.keras
*.joblib
*.model
models/artifacts/*
!models/artifacts/.gitkeep

# ===== Artefatos de MLOps =====
mlruns/
outputs/
wandb/
.neptune/
tensorboard_logs/

# ===== IDE =====
.idea/
.vscode/
*.swp
*.swo
.DS_Store

# ===== Variáveis de Ambiente =====
.env
.env.local
*.env

# ===== Segredos =====
secrets/
credentials/
*.pem
*.key

# ===== Logs =====
logs/
*.log

# ===== Cache =====
.cache/
.pytest_cache/
.mypy_cache/
.ruff_cache/

# ===== Cobertura =====
htmlcov/
.coverage
.coverage.*
coverage.xml

Git LFS para Arquivos Grandes​

Quando Usar Git LFS​

O Git LFS (Large File Storage) é útil para arquivos que:

  • São essenciais para o repositório
  • São atualizados com pouca frequência
  • Têm menos de 2GB
  • Não podem ser controlados pelo DVC

Configurando o Git LFS​

# Instalar o Git LFS
git lfs install

# Rastrear tipos de arquivos específicos
git lfs track "*.bin"
git lfs track "*.weights"
git lfs track "*.ckpt"

# Rastrear diretórios específicos
git lfs track "models/pretrained/*"

# Verificar rastreamento
cat .gitattributes

Exemplo de .gitattributes​

# Git LFS tracking
*.bin filter=lfs diff=lfs merge=lfs -text
*.weights filter=lfs diff=lfs merge=lfs -text
*.ckpt filter=lfs diff=lfs merge=lfs -text
models/pretrained/* filter=lfs diff=lfs merge=lfs -text

# Garantir finais de linha consistentes
*.py text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.json text eol=lf
*.md text eol=lf

# Notebooks como binário (evitar conflitos de merge)
*.ipynb binary

Estratégias de Branch para ML​

GitFlow Adaptado para ML​

Convenções de Nomes de Branch​

# Branches de feature (infraestrutura, pipelines)
feature/data-pipeline
feature/monitoring-setup
feature/api-endpoint

# Branches de experimento (desenvolvimento de modelo)
experiment/baseline-rf
experiment/deep-learning-v1
experiment/transformer-tuning

# Branches de hotfix
hotfix/data-drift-fix
hotfix/prediction-bug

# Branches de release
release/v1.0.0
release/v1.1.0

Regras de Branch Específicas para ML​

# .github/branch-protection.yml
branches:
main:
protection:
required_reviews: 2
required_status_checks:
- unit-tests
- integration-tests
- model-validation
enforce_admins: true

develop:
protection:
required_reviews: 1
required_status_checks:
- unit-tests
- linting

experiment/*:
protection:
required_reviews: 0 # Experimentos podem ser mesclados livremente
allow_force_push: true # Permitir rebase em experimentos

Convenções de Mensagens de Commit​

Mensagens de Commit Estruturadas​

# Formato: <type>(<scope>): <description>

# Tipos para projetos de ML:
# - data: Mudanças relacionadas a dados
# - feat: Nova funcionalidade
# - fix: Correção de bug
# - model: Mudanças na arquitetura do modelo
# - train: Mudanças relacionadas ao treinamento
# - eval: Mudanças de avaliação/métricas
# - docs: Documentação
# - refactor: Refatoração de código
# - test: Testes
# - ci: Mudanças em CI/CD
# - config: Mudanças de configuração

# Exemplos:
git commit -m "data: adicionar novo dataset de segmentação de clientes v2.1"
git commit -m "model: implementar mecanismo de atenção no encoder"
git commit -m "train: adicionar agendamento de learning rate"
git commit -m "eval: adicionar análise de precisão-recall"
git commit -m "config: atualizar hiperparâmetros de treinamento"

Template de Mensagem de Commit​

# .gitmessage
# <type>(<scope>): <subject>
# |<---- Usar no Máximo 50 Caracteres ---->|

# Explique por que esta mudança está sendo feita
# |<---- Tente Limitar Cada Linha a no Máximo 72 Caracteres ---->|

# Forneça links ou chaves para tickets, artigos ou outros recursos relevantes
# Exemplo: Experiment: exp-2024-01-15-baseline
# MLflow Run: abc123

# --- COMMIT END ---
# Type pode ser:
# data (mudanças relacionadas a dados)
# feat (nova funcionalidade)
# fix (correção de bug)
# model (arquitetura do modelo)
# train (mudanças de treinamento)
# eval (avaliação/métricas)
# docs (documentação)
# refactor (refatoração de código)
# test (adição de testes)
# ci (mudanças em CI/CD)
# config (mudanças de configuração)

Defina como padrão:

git config --global commit.template ~/.gitmessage

Hooks de Pre-commit para ML​

Configuração do Pre-commit​

# .pre-commit-config.yaml
repos:
# Python geral
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-json
- id: check-added-large-files
args: ['--maxkb=1000'] # Prevenir arquivos grandes
- id: detect-private-key
- id: check-merge-conflict

# Formatação Python
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: ruff
args: [--fix]
- id: ruff-format

# Type checking
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.8.0
hooks:
- id: mypy
additional_dependencies: [types-all]

# Segurança
- repo: https://github.com/PyCQA/bandit
rev: 1.7.6
hooks:
- id: bandit
args: ['-c', 'pyproject.toml']

# Jupyter notebooks
- repo: https://github.com/kynan/nbstripout
rev: 0.6.1
hooks:
- id: nbstripout # Remove outputs dos notebooks antes do commit

# DVC
- repo: https://github.com/iterative/dvc
rev: 3.38.1
hooks:
- id: dvc-pre-commit
- id: dvc-pre-push

Instalando o Pre-commit​

# Instalar pre-commit
pip install pre-commit

# Instalar hooks
pre-commit install

# Rodar em todos os arquivos
pre-commit run --all-files

Lidando com Jupyter Notebooks​

O Problema dos Notebooks​

Notebooks Jupyter são problemáticos para controle de versão:

  • Contêm células de output (imagens, dataframes)
  • Incluem contadores de execução
  • Diffs grandes devido a mudanças de metadata

Solução 1: Remover Outputs​

# Usando nbstripout
pip install nbstripout

# Instalar como filtro do git
nbstripout --install

# Remover manualmente
nbstripout notebook.ipynb

Solução 2: Jupytext para Notebooks​

# Instalar jupytext
pip install jupytext

# Converter notebook para script Python
jupytext --to py:percent notebooks/experiment.ipynb

# Sincronizar notebook e script
jupytext --sync notebooks/experiment.ipynb
# pyproject.toml - Configuração do Jupytext
[tool.jupytext]
formats = "notebooks///ipynb,scripts///py:percent"

Solução 3: ReviewNB para Revisão de PR​

Use o ReviewNB para revisões de PR de notebooks no GitHub:

  • Diffs visuais para notebooks
  • Comentários em células específicas
  • Acompanhe mudanças de forma eficaz

Integração CI/CD​

GitHub Actions para ML​

# .github/workflows/ml-ci.yml
name: ML CI Pipeline

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
lint-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Histórico completo para DVC

- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
cache: 'pip'

- name: Install dependencies
run: |
pip install -r requirements.txt
pip install -r requirements-dev.txt

- name: Run linting
run: ruff check .

- name: Run type checking
run: mypy src/

- name: Run unit tests
run: pytest tests/unit/ -v --cov=src/

- name: Upload coverage
uses: codecov/codecov-action@v3

data-validation:
runs-on: ubuntu-latest
needs: lint-and-test
steps:
- uses: actions/checkout@v4

- name: Setup DVC
uses: iterative/setup-dvc@v1

- name: Pull data
run: dvc pull data/processed/

- name: Validate data
run: python src/data/validate.py

Principais Aprendizados​

Summary
  1. Estruture seu repositório com separação clara de código, dados e modelos
  2. Use um .gitignore abrangente para excluir arquivos grandes e dados sensíveis
  3. Considere Git LFS para arquivos grandes essenciais que não podem usar DVC
  4. Adote branching específico para ML com branches de experimento
  5. Enforce convenções de commit para melhor rastreabilidade do histórico
  6. Use hooks de pre-commit para manter a qualidade do código
  7. Gerencie notebooks com cuidado usando nbstripout ou jupytext

Exercício Prático​

  1. Crie um novo repositório de ML com a estrutura recomendada
  2. Configure o .gitignore e o .gitattributes
  3. Configure hooks de pre-commit
  4. Crie e faça merge de um branch de experimento

Próximo: Capítulo 2: Controle de Versão de Dados (DVC)