Saltar al contenido principal

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íoDescripción
Archivos grandesConjuntos de datos, pesos de modelos (GB a TB)
Artefactos binariosModelos entrenados, preprocesadores
Proliferación de experimentosCientos de ramas experimentales
No determinismoEl mismo código puede producir resultados diferentes
Caos de notebooksNotebooks 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
  1. Estructure su repositorio con separación clara de código, datos y modelos
  2. Use un .gitignore integral para excluir archivos grandes y datos sensibles
  3. Considere Git LFS para archivos grandes esenciales que no puedan usar DVC
  4. Adopte ramas específicas para ML con ramas de experimentos
  5. Haga cumplir convenciones de commits para un mejor seguimiento del historial
  6. Utilice pre-commit hooks para mantener la calidad del código
  7. Gestione notebooks cuidadosamente con nbstripout o jupytext

Ejercicio Práctico​

  1. Cree un nuevo repositorio de ML con la estructura recomendada
  2. Configure .gitignore y .gitattributes
  3. Configure pre-commit hooks
  4. Cree y fusione una rama de experimento

Siguiente: Capítulo 2: Control de Versiones de Datos (DVC)