Capítulo 1: Git para Proyectos de ML
Introducción
Git es la base del control de versiones en el desarrollo de software, pero los proyectos de ML presentan desafíos únicos que requieren prácticas y configuraciones especializadas.
Desafíos Específicos de Git en ML
La Naturaleza Única del Código de ML
A diferencia del software tradicional, los proyectos de ML involucran:
| Desafío | Descripción |
|---|---|
| Archivos grandes | Conjuntos de datos, pesos de modelos (GB a TB) |
| Artefactos binarios | Modelos entrenados, preprocesadores |
| Proliferación de experimentos | Cientos de ramas experimentales |
| No determinismo | El mismo código puede producir resultados diferentes |
| Caos de notebooks | Notebooks de Jupyter con salidas incrustadas |
Estructura del Repositorio para Proyectos de ML
Estructura Recomendada
ml-project/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ ├── training.yml
│ └── deployment.yml
├── data/
│ ├── raw/ # Datos originales (seguimiento con DVC)
│ ├── processed/ # Datos transformados (seguimiento con DVC)
│ └── features/ # Exportaciones del feature store
├── models/
│ ├── artifacts/ # Modelos entrenados (seguimiento con DVC)
│ └── configs/ # Configuraciones 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 Integral para ML
# .gitignore for ML Projects
# ===== 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
# ===== Virtual Environments =====
venv/
ENV/
env/
.venv/
.conda/
# ===== Jupyter Notebooks =====
.ipynb_checkpoints/
*/.ipynb_checkpoints/
# ===== Data Files =====
# Large data files (tracked by DVC instead)
*.csv
*.parquet
*.feather
*.h5
*.hdf5
*.pkl
*.pickle
!data/.gitkeep
data/raw/*
data/processed/*
data/features/*
# ===== Model Artifacts =====
# Trained models (tracked by DVC instead)
*.pt
*.pth
*.onnx
*.pb
*.h5
*.keras
*.joblib
*.model
models/artifacts/*
!models/artifacts/.gitkeep
# ===== MLOps Artifacts =====
mlruns/
outputs/
wandb/
.neptune/
tensorboard_logs/
# ===== IDE =====
.idea/
.vscode/
*.swp
*.swo
.DS_Store
# ===== Environment Variables =====
.env
.env.local
*.env
# ===== Secrets =====
secrets/
credentials/
*.pem
*.key
# ===== Logs =====
logs/
*.log
# ===== Cache =====
.cache/
.pytest_cache/
.mypy_cache/
.ruff_cache/
# ===== Coverage =====
htmlcov/
.coverage
.coverage.*
coverage.xml
Git LFS para Archivos Grandes
Cuándo Usar Git LFS
Git LFS (Large File Storage) es útil para archivos que:
- Son esenciales para el repositorio
- Se actualizan con poca frecuencia
- Son menores de 2GB
- No pueden ser gestionados por DVC
Configuración de Git LFS
# Instalar Git LFS
git lfs install
# Seguir tipos de archivos específicos
git lfs track "*.bin"
git lfs track "*.weights"
git lfs track "*.ckpt"
# Seguir directorios específicos
git lfs track "models/pretrained/*"
# Verificar seguimiento
cat .gitattributes
Ejemplo 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
# Ensure consistent line endings
*.py text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.json text eol=lf
*.md text eol=lf
# Notebooks as binary (prevent merge conflicts)
*.ipynb binary
Estrategias de Ramas para ML
GitFlow Adaptado para ML
Convenciones de Nombres de Ramas
# Ramas de funcionalidades (infraestructura, pipelines)
feature/data-pipeline
feature/monitoring-setup
feature/api-endpoint
# Ramas de experimentos (desarrollo de modelos)
experiment/baseline-rf
experiment/deep-learning-v1
experiment/transformer-tuning
# Ramas de hotfix
hotfix/data-drift-fix
hotfix/prediction-bug
# Ramas de release
release/v1.0.0
release/v1.1.0
Reglas de Ramas 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 # Los experimentos pueden fusionarse libremente
allow_force_push: true # Permitir rebaseo de experimentos
Convenciones de Mensajes de Commit
Mensajes de Commit Estructurados
# Formato: <type>(<scope>): <description>
# Tipos para proyectos de ML:
# - data: Cambios relacionados con datos
# - feat: Nueva funcionalidad
# - fix: Corrección de errores
# - model: Cambios en la arquitectura del modelo
# - train: Cambios relacionados con el entrenamiento
# - eval: Cambios en evaluación/métricas
# - docs: Documentación
# - refactor: Refactorización de código
# - test: Pruebas
# - ci: Cambios en CI/CD
# - config: Cambios de configuración
# Ejemplos:
git commit -m "data: agregar nuevo dataset de segmentación de clientes v2.1"
git commit -m "model: implementar mecanismo de atención en el encoder"
git commit -m "train: agregar programación de tasa de aprendizaje"
git commit -m "eval: agregar análisis de precisión-recall"
git commit -m "config: actualizar hiperparámetros de entrenamiento"
Plantilla de Mensaje de Commit
# .gitmessage
# <type>(<scope>): <subject>
# |<---- Using a Maximum Of 50 Characters ---->|
# Explique por qué se realiza este cambio
# |<---- Try To Limit Each Line to a Maximum Of 72 Characters ---->|
# Proporcione enlaces o claves a tickets, artículos u otros recursos relevantes
# Ejemplo: Experiment: exp-2024-01-15-baseline
# MLflow Run: abc123
# --- COMMIT END ---
# Type can be:
# data (data-related changes)
# feat (new feature)
# fix (bug fix)
# model (model architecture)
# train (training changes)
# eval (evaluation/metrics)
# docs (documentation)
# refactor (refactoring code)
# test (adding tests)
# ci (CI/CD changes)
# config (configuration changes)
Establézcalo como predeterminado:
git config --global commit.template ~/.gitmessage
Pre-commit Hooks para ML
Configuración de Pre-commit
# .pre-commit-config.yaml
repos:
# General Python
- 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 archivos grandes
- id: detect-private-key
- id: check-merge-conflict
# Python formatting
- 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]
# Security
- 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 # Eliminar salidas de notebooks antes del commit
# DVC
- repo: https://github.com/iterative/dvc
rev: 3.38.1
hooks:
- id: dvc-pre-commit
- id: dvc-pre-push
Instalación de Pre-commit
# Instalar pre-commit
pip install pre-commit
# Instalar hooks
pre-commit install
# Ejecutar en todos los archivos
pre-commit run --all-files
Manejo de Notebooks de Jupyter
El Problema de los Notebooks
Los notebooks de Jupyter son problemáticos para el control de versiones:
- Contienen celdas de salida (imágenes, dataframes)
- Incluyen conteos de ejecución
- Grandes diferencias debido a cambios en metadatos
Solución 1: Eliminar Salidas
# Usando nbstripout
pip install nbstripout
# Instalar como filtro de git
nbstripout --install
# Eliminar manualmente
nbstripout notebook.ipynb
Solución 2: Jupytext para Notebooks
# Instalar jupytext
pip install jupytext
# Convertir notebook a script de Python
jupytext --to py:percent notebooks/experiment.ipynb
# Sincronizar notebook y script
jupytext --sync notebooks/experiment.ipynb
# pyproject.toml - Configuración de Jupytext
[tool.jupytext]
formats = "notebooks///ipynb,scripts///py:percent"
Solución 3: ReviewNB para Revisiones de PR
Utilice ReviewNB para revisiones de PR en GitHub de notebooks:
- Diferencias visuales para notebooks
- Comentar en celdas específicas
- Rastrear cambios de manera efectiva
Integración 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 # Full history for 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
Puntos Clave
Summary
- Estructure su repositorio con separación clara de código, datos y modelos
- Use un .gitignore integral para excluir archivos grandes y datos sensibles
- Considere Git LFS para archivos grandes esenciales que no puedan usar DVC
- Adopte ramas específicas para ML con ramas de experimentos
- Haga cumplir convenciones de commits para un mejor seguimiento del historial
- Utilice pre-commit hooks para mantener la calidad del código
- Gestione notebooks cuidadosamente con nbstripout o jupytext
Ejercicio Práctico
- Cree un nuevo repositorio de ML con la estructura recomendada
- Configure .gitignore y .gitattributes
- Configure pre-commit hooks
- Cree y fusione una rama de experimento