# Entrenamiento ML con Jupyter

Configura JupyterLab con soporte GPU para experimentos de aprendizaje automático y entrenamiento de modelos.

{% hint style="success" %}
Todos los ejemplos se pueden ejecutar en servidores GPU alquilados a través de [CLORE.AI Marketplace](https://clore.ai/marketplace).
{% endhint %}

## Requisitos del servidor

| Parámetro        | Mínimo      | Recomendado |
| ---------------- | ----------- | ----------- |
| RAM              | 16GB        | 32GB+       |
| VRAM             | 8GB         | 16GB+       |
| Red              | 200Mbps     | 500Mbps+    |
| Tiempo de inicio | 2-3 minutos | -           |

{% hint style="info" %}
El propio JupyterLab es ligero. Elige GPU y RAM según los requisitos de tu carga de trabajo de entrenamiento.
{% endhint %}

## Despliegue rápido

**Imagen Docker:**

```
pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime
```

**Puertos:**

```
22/tcp
8888/http
6006/http
```

**Entorno:**

```
JUPYTER_TOKEN=tu_token_seguro_aquí
```

**Comando:**

```bash
pip install jupyterlab tensorboard && \
jupyter lab --ip=0.0.0.0 --port=8888 --allow-root --NotebookApp.token='tu_token_seguro_aquí'
```

## Accediendo a tu servicio

Después del despliegue, encuentra tu `http_pub` URL en **Mis Pedidos**:

1. Ir a **Mis Pedidos** página
2. Haz clic en tu pedido
3. Encuentra la `http_pub` URL (por ejemplo, `abc123.clorecloud.net`)

Usa `https://TU_HTTP_PUB_URL` en lugar de `localhost` en los ejemplos abajo.

### Verificar que funciona

```bash
# Comprueba si JupyterLab es accesible
curl https://your-http-pub.clorecloud.net/

# Accede con token
# https://tu-http-pub.clorecloud.net/?token=tu_token_seguro_aquí
```

{% hint style="warning" %}
Si obtienes HTTP 502, espera 2-3 minutos: el servicio está instalando dependencias.
{% endhint %}

## Alquilar en CLORE.AI

1. Visita [CLORE.AI Marketplace](https://clore.ai/marketplace)
2. Filtrar por tipo de GPU, VRAM y precio
3. Elegir **Bajo demanda** (tarifa fija) o **Spot** (precio de puja)
4. Configura tu pedido:
   * Selecciona imagen Docker
   * Establece puertos (TCP para SSH, HTTP para interfaces web)
   * Agrega variables de entorno si es necesario
   * Introduce el comando de inicio
5. Selecciona pago: **CLORE**, **BTC**, o **USDT/USDC**
6. Crea el pedido y espera el despliegue

### Accede a tu servidor

* Encuentra los detalles de conexión en **Mis Pedidos**
* Interfaces web: Usa la URL del puerto HTTP
* SSH: `ssh -p <port> root@<proxy-address>`

## Acceder a Jupyter

1. Esperar a que se despliegue
2. Encontrar el mapeo del puerto 8888
3. Abrir: `http://<proxy>:<port>?token=tu_token_seguro_aquí`

## Imagen ML preconfigurada

Para un entorno ML completo:

**Imagen:**

```
jupyter/pytorch-notebook:cuda12-pytorch-2.1.0
```

O construye personalizado:

```dockerfile
FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime

RUN pip install --no-cache-dir \
    jupyterlab \
    numpy pandas matplotlib seaborn \
    scikit-learn \
    transformers datasets accelerate \
    tensorboard wandb \
    opencv-python pillow \
    tqdm rich

EXPOSE 8888 6006

CMD ["jupyter", "lab", "--ip=0.0.0.0", "--allow-root"]
```

## Librerías esenciales

### Instalar en Jupyter

```python
!pip install transformers datasets accelerate bitsandbytes
!pip install wandb tensorboard
!pip install scikit-learn xgboost lightgbm
!pip install opencv-python albumentations
```

### Crear requirements.txt

```

# Frameworks ML
torch>=2.1.0
torchvision
torchaudio

# PNL
transformers>=4.36.0
datasets
tokenizers
sentencepiece

# Entrenamiento
accelerate
bitsandbytes
peft
trl

# Monitoreo
wandb
tensorboard

# Datos
numpy
pandas
matplotlib
seaborn
scikit-learn

# Visión por computadora
opencv-python
pillow
albumentations
```

## Ejemplos de entrenamiento

### Clasificación de imágenes con PyTorch

```python
import torch
import torch.nn as nn
import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader

# Comprobar GPU
print(f"GPU: {torch.cuda.get_device_name(0)}")
print(f"Memoria: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB")

# Cargar datos
transform = transforms.Compose([
    transforms.Resize(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])

train_data = torchvision.datasets.CIFAR10(
    root='./data', train=True, download=True, transform=transform
)
train_loader = DataLoader(train_data, batch_size=64, shuffle=True, num_workers=4)

# Modelo
model = torchvision.models.resnet18(pretrained=True)
model.fc = nn.Linear(512, 10)
model = model.cuda()

# Entrenamiento
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
criterion = nn.CrossEntropyLoss()

for epoch in range(10):
    model.train()
    for images, labels in train_loader:
        images, labels = images.cuda(), labels.cuda()

        optimizer.zero_grad()
        outputs = model(images)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

    print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")

# Guardar modelo
torch.save(model.state_dict(), 'model.pth')
```

### Clasificación de texto con HuggingFace

```python
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from transformers import TrainingArguments, Trainer
from datasets import load_dataset
import numpy as np

# Cargar conjunto de datos
dataset = load_dataset("imdb")

# Cargar modelo
model_name = "distilbert-base-uncased"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)

# Tokenizar
def tokenize(examples):
    return tokenizer(examples["text"], padding="max_length", truncation=True)

tokenized = dataset.map(tokenize, batched=True)

# Entrenamiento
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=64,
    warmup_steps=500,
    weight_decay=0.01,
    logging_dir="./logs",
    logging_steps=100,
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=tokenized["train"],
    eval_dataset=tokenized["test"],
)

trainer.train()
trainer.save_model("./best_model")
```

### Fine-tuning de LLM con LoRA

```python
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import load_dataset
from trl import SFTTrainer
import torch

# Cargar modelo con cuantización a 4 bits
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.float16,
)

model = AutoModelForCausalLM.from_pretrained(
    "mistralai/Mistral-7B-v0.1",
    quantization_config=bnb_config,
    device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("mistralai/Mistral-7B-v0.1")
tokenizer.pad_token = tokenizer.eos_token

# Configurar LoRA
lora_config = LoraConfig(
    r=16,
    lora_alpha=32,
    target_modules=["q_proj", "v_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)

model = prepare_model_for_kbit_training(model)
model = get_peft_model(model, lora_config)

# Cargar conjunto de datos
dataset = load_dataset("timdettmers/openassistant-guanaco")

# Entrenar
trainer = SFTTrainer(
    model=model,
    train_dataset=dataset["train"],
    dataset_text_field="text",
    max_seq_length=512,
    tokenizer=tokenizer,
    args=TrainingArguments(
        output_dir="./lora_output",
        num_train_epochs=1,
        per_device_train_batch_size=4,
        gradient_accumulation_steps=4,
        learning_rate=2e-4,
        fp16=True,
        logging_steps=10,
        save_steps=100,
    ),
)

trainer.train()
trainer.save_model("./final_lora")
```

## Integración con TensorBoard

### Iniciar TensorBoard

```python
%load_ext tensorboard
%tensorboard --logdir ./logs --port 6006 --bind_all
```

O vía terminal:

```bash
tensorboard --logdir ./logs --port 6006 --bind_all &
```

### Registrar métricas de entrenamiento

```python
from torch.utils.tensorboard import SummaryWriter

writer = SummaryWriter('./logs')

for epoch in range(epochs):
    # ... bucle de entrenamiento ...
    writer.add_scalar('Loss/train', train_loss, epoch)
    writer.add_scalar('Loss/val', val_loss, epoch)
    writer.add_scalar('Accuracy/val', accuracy, epoch)

writer.close()
```

## Integración con Weights & Biases

```python
import wandb

wandb.init(project="my-project", name="experiment-1")

# Registrar métricas
wandb.log({"loss": loss, "accuracy": acc})

# Guardar modelo
wandb.save("model.pth")

# Finalizar
wandb.finish()
```

## Gestión de datos

### Descargar conjuntos de datos

```python

# Datasets de HuggingFace
from datasets import load_dataset
dataset = load_dataset("squad")

# Datasets de Kaggle
!pip install kaggle
!kaggle datasets download -d username/dataset-name

# Descarga directa
!wget https://example.com/data.zip
!unzip data.zip
```

### Montar almacenamiento en la nube

```python

# S3
!pip install boto3
import boto3
s3 = boto3.client('s3')
s3.download_file('bucket', 'key', 'local_path')

# Google Cloud
!pip install google-cloud-storage
from google.cloud import storage
client = storage.Client()
bucket = client.bucket('my-bucket')
blob = bucket.blob('data.zip')
blob.download_to_filename('data.zip')
```

## Guardar trabajo

### Guardar en almacenamiento externo

```python

# Guardar modelo en S3
import boto3
s3 = boto3.client('s3',
    aws_access_key_id='TU_KEY',
    aws_secret_access_key='TU_SECRET'
)
s3.upload_file('model.pth', 'my-bucket', 'models/model.pth')
```

### Antes de finalizar la sesión

```bash

# Descargar archivos importantes
scp -P <port> root@<host>:/workspace/model.pth ./
scp -P <port> -r root@<host>:/workspace/results/ ./results/
```

## Entrenamiento multi-GPU

```python
import torch.distributed as dist
from torch.nn.parallel import DistributedDataParallel

# Comprobar GPUs
print(f"GPUs disponibles: {torch.cuda.device_count()}")

# DataParallel (simple)
model = nn.DataParallel(model)

# DistributedDataParallel (mejor)

# Lanzar con: torchrun --nproc_per_node=4 train.py
dist.init_process_group("nccl")
model = DistributedDataParallel(model)
```

## Consejos de rendimiento

### Optimización de memoria

```python

# Gradient checkpointing
model.gradient_checkpointing_enable()

# Precisión mixta
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()

with autocast():
    output = model(input)
    loss = criterion(output, target)

scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
```

### Carga de datos

```python

# Carga de datos más rápida
loader = DataLoader(
    dataset,
    batch_size=64,
    num_workers=8,      # Usa múltiples workers
    pin_memory=True,    # Transferencia más rápida a GPU
    prefetch_factor=2   # Prefetch de lotes
)
```

## Solución de problemas

## Estimación de costos

Tarifas típicas del marketplace de CLORE.AI (a fecha de 2024):

| GPU       | Tarifa por hora | Tarifa diaria | Sesión de 4 horas |
| --------- | --------------- | ------------- | ----------------- |
| RTX 3060  | \~$0.03         | \~$0.70       | \~$0.12           |
| RTX 3090  | \~$0.06         | \~$1.50       | \~$0.25           |
| RTX 4090  | \~$0.10         | \~$2.30       | \~$0.40           |
| A100 40GB | \~$0.17         | \~$4.00       | \~$0.70           |
| A100 80GB | \~$0.25         | \~$6.00       | \~$1.00           |

*Los precios varían según el proveedor y la demanda. Consulta* [*CLORE.AI Marketplace*](https://clore.ai/marketplace) *para las tarifas actuales.*

**Ahorra dinero:**

* Usa **Spot** market para cargas de trabajo flexibles (a menudo 30-50% más barato)
* Paga con **CLORE** tokens
* Compara precios entre diferentes proveedores


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.clore.ai/guides/guides_v2-es/entrenamiento/jupyter-ml-training.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
