Metadata-Version: 2.4
Name: grnexus
Version: 0.1.9
Summary: High-performance neural network library with Keras-like API
Author: GR Code Digital Solutions
License: GNU GENERAL PUBLIC LICENSE
        Version 3, 29 June 2007
        
        Copyright (C) 2024 GR Code Digital Solutions
        
        This program is free software: you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation, either version 3 of the License, or
        (at your option) any later version.
        
        This program is distributed in the hope that it will be useful,
        but WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
        GNU General Public License for more details.
        
        You should have received a copy of the GNU General Public License
        along with this program.  If not, see <https://www.gnu.org/licenses/>.
        
        ================================================================================
        
        TERMS AND CONDITIONS
        
        0. Definitions.
        
        "This License" refers to version 3 of the GNU General Public License.
        
        "The Program" refers to any copyrightable work licensed under this License.
        
        "You" refers to the licensee, whether an individual or organization.
        
        "Modify" means to copy from or adapt all or part of the work in a fashion
        requiring copyright permission, other than the making of an exact copy.
        
        1. Source Code.
        
        The "source code" for a work means the preferred form of the work for making
        modifications to it. "Object code" means any non-source form of a work.
        
        2. Basic Permissions.
        
        All rights granted under this License are granted for the term of copyright
        on the Program, and are irrevocable provided the stated conditions are met.
        This License explicitly affirms your unlimited permission to run the unmodified
        Program.
        
        3. Protecting Users' Legal Rights From Anti-Circumvention Law.
        
        No covered work shall be deemed part of an effective technological measure
        under any applicable law fulfilling obligations under article 11 of the WIPO
        copyright treaty adopted on 20 December 1996, or similar laws prohibiting or
        restricting circumvention of such measures.
        
        4. Conveying Verbatim Copies.
        
        You may convey verbatim copies of the Program's source code as you receive it,
        in any medium, provided that you conspicuously and appropriately publish on
        each copy an appropriate copyright notice; keep intact all notices stating
        that this License and any non-permissive terms added in accord with section 7
        apply to the code; keep intact all notices of the absence of any warranty;
        and give all recipients a copy of this License along with the Program.
        
        5. Conveying Modified Source Versions.
        
        You may convey a work based on the Program, or the modifications to produce
        it from the Program, in source code form under the terms of section 4,
        provided that you also meet all of these conditions:
        
        a) The work must carry prominent notices stating that you modified it, and
           giving a relevant date.
        
        b) The work must carry prominent notices stating that it is released under
           this License and any conditions added under section 7.
        
        c) You must license the entire work, as a whole, under this License to
           anyone who comes into possession of a copy.
        
        d) If the work has interactive user interfaces, each must display Appropriate
           Legal Notices; however, if the Program has interactive interfaces that do
           not display Appropriate Legal Notices, your work need not make them do so.
        
        6. Copyleft.
        
        If you convey a covered work, you must also make the Corresponding Source
        available under the same license terms. This ensures that all derivative
        works remain free and open source.
        
        For more information, visit: https://www.gnu.org/licenses/gpl-3.0.html
        
        ================================================================================
        
        GRNexus - High-Performance Neural Network Library
        Copyright (C) 2024 GR Code Digital Solutions
        
        This program comes with ABSOLUTELY NO WARRANTY. This is free software,
        and you are welcome to redistribute it under certain conditions.
        See the GNU General Public License for more details.
        
Project-URL: Homepage, https://github.com/grcodedigitalsolutions/GRNexus
Project-URL: Repository, https://github.com/grcodedigitalsolutions/GRNexus
Project-URL: Documentation, https://github.com/grcodedigitalsolutions/GRNexus/blob/main/python/README.md
Project-URL: Bug Tracker, https://github.com/grcodedigitalsolutions/GRNexus/issues
Keywords: neural-network,deep-learning,machine-learning,ai,keras
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# GRNexus Python - Neural Network Library

![GRNexus](https://img.shields.io/badge/GRNexus-v0.1.9-blue?style=for-the-badge)
![Python](https://img.shields.io/badge/Python-3.8+-green?style=for-the-badge&logo=python)
![License](https://img.shields.io/badge/License-GPL--3.0-yellow?style=for-the-badge)
![PyPI](https://img.shields.io/pypi/v/grnexus?style=for-the-badge)

**A high-performance neural network library with Keras-like API**

---

## 📑 Table of Contents

- [Features](#-features)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [CLI Commands](#-cli-commands)
- [Import Styles](#-import-styles)
- [Complete Examples](#-complete-examples)
- [API Reference](#-api-reference)
- [Interactive Tests](#-interactive-tests)
- [Troubleshooting](#-troubleshooting)

---

## ✨ Features

- 🚀 **High Performance**: Optimized operations with native C libraries
- 🎯 **Keras-like API**: Familiar and easy to use for ML developers
- 🧩 **Modular**: Flexible architecture with decoupled layers
- 💾 **Persistence**: Save and load models in `.nexus` format
- 🎨 **50+ Activation Functions**: From ReLU to Snake, Mish, GELU, etc.
- 🔧 **Multiple Optimizers**: SGD, Momentum, Adam, RMSprop
- 📊 **Regularization**: Dropout, Batch Normalization
- 🤖 **ML Algorithms**: KNN, K-Means, Linear/Logistic Regression, Naive Bayes
- 🌍 **Cross-Platform**: Windows, macOS, Linux
- 🎮 **CLI Tool**: Built-in command-line interface for running tests

---

## 📦 Installation

```bash
pip install grnexus
```

### Verify Installation

```python
import grnexus
print(f"✅ GRNexus v{grnexus.__version__} installed!")
```

---

## 🚀 Quick Start

```python
from grnexus import NeuralNetwork
from grnexus.layers import DenseLayer, DropoutLayer
from grnexus.activations import ReLU

# Create model
model = NeuralNetwork()
model.add(DenseLayer(128, input_dim=784, activation=ReLU()))
model.add(DropoutLayer(0.2))
model.add(DenseLayer(10))

# Compile
model.compile(loss='cross_entropy', optimizer='adam', learning_rate=0.001)

# Train
history = model.train(x_train, y_train, epochs=10, batch_size=32)

# Predict
predictions = model.predict(x_test)

# Save
model.save('my_model.nexus')
```

---

## 🎮 CLI Commands

GRNexus includes a powerful CLI for running tests and examples:

### Run All Tests
```bash
grnexus test
```

### Run Tests by Category
```bash
grnexus test classification  # Run all classification tests
grnexus test regression      # Run all regression tests
grnexus test ml_algorithms   # Run all ML algorithm tests
grnexus test integration     # Run integration tests
grnexus test unit            # Run unit tests
```

### Run Specific Test
```bash
grnexus test classification/iris     # Run Iris classification
grnexus test classification/digitos  # Run digit recognition (interactive)
grnexus test regression/housing      # Run housing price prediction
```

### List Available Tests
```bash
grnexus list
```

### Get Help
```bash
grnexus --help
```

---

## 📚 Import Styles

GRNexus supports multiple import styles for flexibility:

### Option 1: Submodule Imports (Recommended)
```python
from grnexus import NeuralNetwork
from grnexus.layers import DenseLayer, DropoutLayer, SoftmaxLayer
from grnexus.activations import ReLU, Sigmoid, Tanh
from grnexus.callbacks import EarlyStopping, ModelCheckpoint
from grnexus.ml import KNeighborsClassifier, KMeans
```

### Option 2: Direct Imports
```python
from grnexus import (
    NeuralNetwork,
    DenseLayer,
    ReLU,
    EarlyStopping,
    KNeighborsClassifier
)
```

### Option 3: Module Imports
```python
import grnexus
from grnexus import layers, activations

model = grnexus.NeuralNetwork()
model.add(layers.DenseLayer(128, activation=activations.ReLU()))
```

---

### Instalación desde Código Fuente

Para desarrolladores que quieran contribuir o usar la última versión:

```bash
# Clonar el repositorio
git clone https://github.com/grcodedigitalsolutions/GRNexus.git
cd GRNexus/python

# Instalar en modo desarrollo
pip install -e .

# O instalar dependencias opcionales para tests
pip install flet numpy
```

### Requisitos

- Python 3.8 o superior
- Sin dependencias externas (100% Python puro)
- Flet (opcional, solo para tests interactivos)
- NumPy (opcional, para operaciones matriciales avanzadas)

### Verificar Instalación

```python
from grnexus import NeuralNetwork

# Crear un modelo simple
model = NeuralNetwork()
print(f"✅ GRNexus v{grnexus.__version__} instalado correctamente!")
```

---

## 🚀 Inicio Rápido

```python
from grnexus import NeuralNetwork, DenseLayer, ReLU, DropoutLayer

# 1. Crear modelo
model = NeuralNetwork()

# 2. Agregar capas
model.add(DenseLayer(128, input_dim=784, activation=ReLU()))
model.add(DropoutLayer(0.2))
model.add(DenseLayer(64, activation=ReLU()))
model.add(DenseLayer(10))  # Capa de salida

# 3. Compilar
model.compile(
    optimizer='adam',
    loss='cross_entropy',
    learning_rate=0.001
)

# 4. Entrenar
history = model.train(
    x_train,  # Datos de entrenamiento
    y_train,  # Etiquetas (one-hot encoded)
    epochs=10,
    batch_size=32
)

# 5. Predecir
predictions = model.predict(x_test)

# 6. Guardar modelo
model.save('mi_modelo.nexus')

# Cargar modelo
model_cargado = NeuralNetwork.load('mi_modelo.nexus')
```

---

## 🏗️ Arquitectura

### Estructura del Proyecto

```
GRNexus/python/
├── grnexus.py              # Clase principal GRNexus
├── lib/
│   ├── grnexus_layers.py   # Implementación de capas
│   ├── grnexus_activations.py  # Funciones de activación
│   └── grnexus_optimizers.py   # Optimizers
├── test/
│   ├── test_digitos.py     # Test de reconocimiento de dígitos
│   ├── test_sentiment.py   # Test de análisis de sentimientos
│   ├── test_iris.py        # Test de clasificación Iris
│   ├── test_housing.py     # Test de predicción de precios
│   └── test_suite.py       # Launcher de todos los tests
└── README.md               # Este archivo
```

### Flujo de Datos

```
Input → DenseLayer → Activation → Dropout → ... → Softmax → Output
   ↓                                                            ↑
   └──────────── Backpropagation (gradientes) ─────────────────┘
```

---

## 🧠 Cómo Funciona una Red Neuronal

<details>
<summary><strong>📖 Explicación Básica para Principiantes (Click para expandir)</strong></summary>

### ¿Qué es una Neurona Artificial?

Imagina una neurona como una **pequeña calculadora** que:
1. Recibe varios números de entrada
2. Los multiplica por "pesos" (importancia de cada entrada)
3. Los suma todos
4. Aplica una función de activación (decide si "activarse" o no)
5. Produce un número de salida

```
Ejemplo simple:
Entradas: [2, 3, 1]
Pesos:    [0.5, 0.3, 0.2]

Cálculo: (2 × 0.5) + (3 × 0.3) + (1 × 0.2) = 1.0 + 0.9 + 0.2 = 2.1
Activación ReLU: max(0, 2.1) = 2.1
Salida: 2.1
```

### ¿Qué son los Pesos y Bias?

- **Pesos**: Son como "perillas de volumen" que controlan qué tan importante es cada entrada
  - Al inicio son aleatorios
  - Durante el entrenamiento se ajustan automáticamente
  - Pesos grandes = entrada muy importante
  - Pesos pequeños = entrada poco importante

- **Bias**: Es como un "ajuste fino" adicional
  - Permite a la neurona activarse incluso sin entradas
  - Ayuda a que la red sea más flexible

### ¿Cómo Aprende la Red?

El aprendizaje es un proceso de **prueba y error automatizado**:

```
1. PREDICCIÓN (Forward Pass):
   Entrada → Capa 1 → Capa 2 → ... → Salida
   
2. COMPARACIÓN:
   ¿Qué predijo la red? vs ¿Cuál era la respuesta correcta?
   Error = |Predicción - Respuesta Correcta|
   
3. AJUSTE (Backward Pass / Backpropagation):
   - La red calcula: "¿Qué pesos causaron este error?"
   - Ajusta los pesos en la dirección que reduce el error
   - Usa el "learning rate" para controlar qué tan grandes son los ajustes
   
4. REPETIR:
   - Hace esto miles de veces con muchos ejemplos
   - Gradualmente los pesos mejoran
   - El error disminuye
   - ¡La red aprende!
```

### ¿Qué hace la Función de Activación?

Las funciones de activación añaden **no-linealidad** (capacidad de aprender patrones complejos):

- **Sin activación**: La red solo puede aprender líneas rectas (muy limitado)
- **Con activación**: La red puede aprender curvas, formas complejas, patrones

**Analogía**: 
- Sin activación = Solo puedes dibujar con una regla
- Con activación = Puedes dibujar cualquier forma libremente

**Funciones comunes**:
- `ReLU`: Si el número es negativo → 0, si es positivo → déjalo igual
- `Sigmoid`: Convierte cualquier número a rango 0-1
- `Softmax`: Convierte números en probabilidades que suman 1

### Ejemplo Completo: Reconocimiento de Dígitos

```
PASO 1: PREPARAR DATOS
Imagen 28×28 → Aplanar → Vector de 784 números
[pixel1, pixel2, ..., pixel784]

PASO 2: FORWARD PASS (Predicción)
784 números → Capa 1 (128 neuronas con ReLU)
           → Capa 2 (64 neuronas con ReLU)  
           → Capa 3 (10 neuronas, una por dígito)
           → Softmax (convierte a probabilidades)
           → [0.01, 0.02, 0.85, 0.03, ...] ← "85% seguro que es un 2"

PASO 3: CALCULAR ERROR
Predicción: [0.01, 0.02, 0.85, 0.03, 0.01, 0.02, 0.01, 0.03, 0.01, 0.01]
Correcto:   [0,    0,    1,    0,    0,    0,    0,    0,    0,    0   ] (es un 2)
Error: Categorical Cross-Entropy = 0.16 (bastante bajo, buena predicción)

PASO 4: BACKWARD PASS (Aprendizaje)
- Calcular gradientes: "¿Qué pesos causaron el error?"
- Actualizar pesos: peso_nuevo = peso_viejo - (learning_rate × gradiente)
- Los pesos se ajustan para que la próxima vez prediga mejor

PASO 5: REPETIR
- Entrenar con miles de imágenes
- Cada vez los pesos mejoran un poquito
- Después de muchas épocas, ¡la red reconoce dígitos con 95%+ precisión!
```

### Conceptos Clave

| Concepto | Explicación Simple | Analogía |
|----------|-------------------|----------|
| **Neurona** | Calculadora que suma entradas ponderadas | Una persona votando (cada voto tiene diferente peso) |
| **Peso** | Importancia de una conexión | Volumen de un canal de audio |
| **Bias** | Ajuste fino adicional | Temperatura base de un termostato |
| **Activación** | Función que decide si la neurona "se activa" | Interruptor que decide si pasa la señal |
| **Forward Pass** | Calcular la predicción | Resolver un problema de matemáticas |
| **Loss** | Qué tan equivocada está la predicción | Distancia entre tu respuesta y la correcta |
| **Backward Pass** | Ajustar pesos para reducir error | Aprender de tus errores |
| **Epoch** | Una pasada completa por todos los datos | Leer un libro completo una vez |
| **Batch** | Grupo pequeño de ejemplos | Estudiar un capítulo del libro |
| **Learning Rate** | Qué tan grandes son los ajustes | Tamaño de los pasos cuando caminas |

</details>

### Flujo Detallado de Entrenamiento

```mermaid
graph TD
    A[Datos de Entrada] --> B[Normalización]
    B --> C[Forward Pass]
    C --> D[Capa Dense 1]
    D --> E[Activación ReLU]
    E --> F[Dropout]
    F --> G[Capa Dense 2]
    G --> H[Activación]
    H --> I[Capa de Salida]
    I --> J[Softmax/Output]
    J --> K[Calcular Loss]
    K --> L[Backward Pass]
    L --> M[Calcular Gradientes]
    M --> N[Actualizar Pesos]
    N --> O{¿Más Epochs?}
    O -->|Sí| C
    O -->|No| P[Modelo Entrenado]
```

### Proceso de Predicción vs Entrenamiento

| Fase | Forward Pass | Backward Pass | Actualizar Pesos |
|------|--------------|---------------|------------------|
| **Entrenamiento** | ✅ Sí | ✅ Sí | ✅ Sí |
| **Predicción** | ✅ Sí | ❌ No | ❌ No |

**Durante Entrenamiento:**
1. Forward: Calcular predicción
2. Comparar con etiqueta real
3. Backward: Calcular gradientes
4. Actualizar pesos con optimizer

**Durante Predicción:**
1. Forward: Calcular predicción
2. Retornar resultado
3. ¡Listo!

---

## 📚 Guía Paso a Paso

### 1. Crear un Modelo

```python
from grnexus import GRNexus

# Especificar la dimensión de entrada
model = GRNexus(input_dim=784)
```

**Parámetros:**
- `input_dim` (int): Número de características de entrada

**¿Cómo determinar input_dim?**
- Para imágenes 28x28: `input_dim = 28 * 28 = 784`
- Para texto vectorizado: `input_dim = tamaño_vocabulario`
- Para datos tabulares: `input_dim = número_de_columnas`

---

### 2. Agregar Capas

#### 2.1 Capa Densa (Dense Layer)

```python
model.add_dense(
    units=128,              # Número de neuronas
    activation='ReLU',      # Función de activación
    use_bias=True,          # Usar bias
    weight_init='he'        # Inicialización de pesos
)
```

**Parámetros:**
- `units` (int): Número de neuronas en la capa
- `activation` (str, opcional): Nombre de la función de activación
  - Opciones: `'ReLU'`, `'Sigmoid'`, `'Tanh'`, `'Swish'`, `'GELU'`, `'Mish'`, etc.
- `use_bias` (bool): Si usar bias (default: `True`)
- `weight_init` (str): Método de inicialización
  - `'xavier'`: Para Sigmoid/Tanh
  - `'he'`: Para ReLU y variantes (recomendado)
  - `'normal'`: Distribución normal
  - `'uniform'`: Distribución uniforme

#### 2.2 Capa de Dropout

```python
model.add_dropout(rate=0.2)  # Desactivar 20% de neuronas durante entrenamiento
```

**Parámetros:**
- `rate` (float): Proporción de neuronas a desactivar (0.0 - 1.0)

**¿Cuándo usar Dropout?**
- Para prevenir overfitting
- Típicamente después de capas densas
- Valores comunes: 0.2 - 0.5

#### 2.3 Capa de Batch Normalization

```python
model.add_batch_norm(
    momentum=0.99,
    epsilon=1e-5
)
```

**Parámetros:**
- `momentum` (float): Momentum para actualizar media y varianza móviles
- `epsilon` (float): Valor pequeño para estabilidad numérica

#### 2.4 Capa de Activación

```python
model.add_activation('ReLU')
```

**¿Cuándo usar?**
- Cuando necesitas aplicar una activación sin una capa densa
- Para arquitecturas más complejas

#### 2.5 Capa Softmax

```python
model.add_softmax()  # Para clasificación multiclase
```

**Importante:**
- Solo usar en la capa de salida
- Para problemas de clasificación
- Convierte logits en probabilidades que suman 1.0

---

### 3. Compilar

```python
model.compile(
    optimizer='adam',        # Optimizer a usar
    loss='categorical_crossentropy',  # Función de pérdida
    learning_rate=0.01,      # Tasa de aprendizaje
    metrics=['accuracy']     # Métricas a monitorear
)
```

**Parámetros:**
- `optimizer` (str): Algoritmo de optimización
  - `'sgd'`: Stochastic Gradient Descent
  - `'momentum'`: SGD con Momentum
  - `'adam'`: Adaptive Moment Estimation (recomendado)
  - `'rmsprop'`: RMSprop
- `loss` (str): Función de pérdida
  - `'categorical_crossentropy'`: Para clasificación multiclase
  - `'mse'`: Mean Squared Error (para regresión)
- `learning_rate` (float): Tasa de aprendizaje
  - Valores típicos: 0.001 - 0.1
  - Valores más bajos: aprendizaje más estable pero lento
  - Valores más altos: aprendizaje más rápido pero puede divergir
- `metrics` (list, opcional): Métricas a calcular

**🎯 Guía de Learning Rate:**
| Optimizer | LR Recomendado |
|-----------|----------------|
| SGD       | 0.01 - 0.1     |
| Momentum  | 0.01 - 0.05    |
| Adam      | 0.001 - 0.01   |
| RMSprop   | 0.001 - 0.01   |

---

### 4. Entrenar

#### 4.1 Método `fit()` (Recomendado)

```python
history = model.fit(
    x_train,           # Datos de entrenamiento
    y_train,           # Etiquetas
    epochs=10,         # Número de épocas
    batch_size=32,     # Tamaño del batch
    validation_data=(x_val, y_val),  # Datos de validación (opcional)
    verbose=1          # Nivel de detalle (0, 1, 2)
)
```

**Parámetros:**
- `x_train` (list): Datos de entrada (lista de listas)
  - Ejemplo: `[[0.1, 0.2, ...], [0.3, 0.4, ...], ...]`
- `y_train` (list): Etiquetas (one-hot encoded para clasificación)
  - Ejemplo: `[[1, 0, 0], [0, 1, 0], ...]` para 3 clases
- `epochs` (int): Número de veces que pasar por todo el dataset
- `batch_size` (int): Número de muestras por actualización de pesos
- `validation_data` (tuple, opcional): (x_val, y_val) para validación
- `verbose` (int): 0=silencioso, 1=barra de progreso, 2=una línea por época

**Retorna:**
- `history` (dict): Historial de entrenamiento con pérdidas por época

#### 4.2 Método `train_on_batch()` (Para control fino)

```python
for epoch in range(epochs):
    for i in range(0, len(x_train), batch_size):
        x_batch = x_train[i:i+batch_size]
        y_batch = y_train[i:i+batch_size]
        
        loss = model.train_on_batch(x_batch, y_batch)
        print(f"Batch loss: {loss}")
```

---

### 5. Predecir

#### 5.1 Predicción Simple

```python
# Predecir una muestra
prediction = model.predict(x_sample)  # x_sample es una lista de números

# Predecir múltiples muestras
predictions = model.predict(x_test)   # x_test es lista de listas
```

#### 5.2 Obtener Clase Predicha

```python
# Para clasificación
prediction = model.predict(x_sample)
predicted_class = prediction.index(max(prediction))  # Índice de máxima probabilidad
confidence = max(prediction)  # Confianza de la predicción

print(f"Clase predicha: {predicted_class}")
print(f"Confianza: {confidence:.2%}")
```

---

### 6. Guardar/Cargar

#### 6.1 Guardar Modelo

```python
model.save('mi_modelo.nexus')
```

**Formato `.nexus`:**
- Guarda arquitectura completa del modelo
- Guarda todos los pesos y biases
- Guarda configuración del optimizer
- Compatible entre plataformas

#### 6.2 Cargar Modelo

```python
from grnexus import GRNexus

modelo_cargado = GRNexus.load('mi_modelo.nexus')

# Usar inmediatamente sin recompilar
predicciones = modelo_cargado.predict(x_test)
```

---

## 📊 Entendiendo los Datos

### Preparación de Datos para Entrenamiento

Los datos son el combustible de las redes neuronales. Aquí aprenderás cómo prepararlos correctamente.

#### 1. Formato de Entrada

Las redes neuronales solo entienden **números**. Debes convertir tus datos a listas de números.

**Ejemplos por tipo de dato:**

```python
# IMÁGENES (28x28 píxeles)
imagen_2d = [
    [0.0, 0.1, 0.2, ...],  # Fila 1
    [0.3, 0.4, 0.5, ...],  # Fila 2
    ...
]
# Aplanar a 1D
imagen_1d = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, ...]  # 784 números

# TEXTO
texto = "This movie is great"
# Convertir a bag-of-words o embeddings
vector_texto = [0.0, 0.2, 0.0, 0.5, ...]  # Cada posición = una palabra

# DATOS TABULARES
casa = {
    'habitaciones': 3,
    'baños': 2,
    'metros': 120,
    'año': 2010,
    'distancia': 5
}
# Convertir a lista
vector_casa = [3, 2, 120, 2010, 5]
```

#### 2. Normalización de Datos

**¿Por qué normalizar?**
- Las redes aprenden mejor cuando todos los valores están en rangos similares
- Evita que características con valores grandes dominen el aprendizaje
- Acelera la convergencia

**Métodos de normalización:**

```python
# MIN-MAX NORMALIZATION (escala a [0, 1])
def normalizar_min_max(valor, min_val, max_val):
    return (valor - min_val) / (max_val - min_val)

# Ejemplo:
edad = 25  # rango: 18-80
edad_norm = (25 - 18) / (80 - 18) = 0.113

# Z-SCORE NORMALIZATION (media=0, std=1)
def normalizar_z_score(valor, media, std):
    return (valor - media) / std

# PARA IMÁGENES (ya están en [0, 255])
pixel = 128
pixel_norm = pixel / 255.0  # → 0.502
```

**Ejemplo completo:**

```python
# Datos sin normalizar
datos_crudos = [
    [3, 2, 120, 2010, 5],    # habitaciones, baños, m², año, km
    [4, 3, 180, 2015, 3],
    [2, 1, 80, 1990, 10]
]

# Encontrar min y max de cada característica
mins = [2, 1, 70, 1985, 1]
maxs = [6, 5, 320, 2024, 15]

# Normalizar
datos_normalizados = []
for muestra in datos_crudos:
    muestra_norm = []
    for i, valor in enumerate(muestra):
        norm = (valor - mins[i]) / (maxs[i] - mins[i])
        muestra_norm.append(norm)
    datos_normalizados.append(muestra_norm)

# Resultado:
# [[0.25, 0.25, 0.2, 0.64, 0.29],
#  [0.5, 0.5, 0.44, 0.77, 0.14],
#  [0.0, 0.0, 0.04, 0.13, 0.64]]
```

#### 3. One-Hot Encoding

Para **clasificación**, las etiquetas deben estar en formato one-hot.

**¿Qué es One-Hot Encoding?**

Convertir una categoría en un vector donde:
- Todas las posiciones son 0
- Excepto la posición de la categoría correcta que es 1

```python
# EJEMPLO: Clasificación de dígitos (0-9)
digito = 3

# One-hot encoding:
one_hot = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]
#          0  1  2  3  4  5  6  7  8  9
#                 ↑
#            posición 3 = 1

# Función para crear one-hot
def crear_one_hot(etiqueta, num_clases):
    vector = [0.0] * num_clases
    vector[etiqueta] = 1.0
    return vector

# Ejemplos:
crear_one_hot(0, 10)  # → [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
crear_one_hot(5, 10)  # → [0, 0, 0, 0, 0, 1, 0, 0, 0, 0]
crear_one_hot(9, 10)  # → [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
```

**Para múltiples muestras:**

```python
# Etiquetas originales
etiquetas = [3, 7, 2, 9, 1]

# Convertir a one-hot
y_train = []
for etiqueta in etiquetas:
    one_hot = [0.0] * 10
    one_hot[etiqueta] = 1.0
    y_train.append(one_hot)

# Resultado:
# [[0,0,0,1,0,0,0,0,0,0],  # 3
#  [0,0,0,0,0,0,0,1,0,0],  # 7
#  [0,0,1,0,0,0,0,0,0,0],  # 2
#  [0,0,0,0,0,0,0,0,0,1],  # 9
#  [0,1,0,0,0,0,0,0,0,0]]  # 1
```

#### 4. Formato de Entrada/Salida

**Para Clasificación:**

```python
# ENTRADA: Lista de listas (cada muestra es una lista)
x_train = [
    [0.1, 0.2, 0.3, ...],  # Muestra 1
    [0.4, 0.5, 0.6, ...],  # Muestra 2
    [0.7, 0.8, 0.9, ...]   # Muestra 3
]

# SALIDA: One-hot encoded
y_train = [
    [1, 0, 0],  # Clase 0
    [0, 1, 0],  # Clase 1
    [1, 0, 0]   # Clase 0
]

# ENTRENAR
model.fit(x_train, y_train, epochs=10, batch_size=32)
```

**Para Regresión:**

```python
# ENTRADA: Igual que clasificación
x_train = [
    [3, 2, 120, 2010, 5],   # Casa 1
    [4, 3, 180, 2015, 3],   # Casa 2
    [2, 1, 80, 1990, 10]    # Casa 3
]

# SALIDA: Valores continuos (NO one-hot)
y_train = [
    [250],  # Precio casa 1: $250k
    [380],  # Precio casa 2: $380k
    [150]   # Precio casa 3: $150k
]

# ENTRENAR
model.fit(x_train, y_train, epochs=100, batch_size=8)
```

#### 5. Validación de Datos

Antes de entrenar, verifica que tus datos estén correctos:

```python
# Verificar dimensiones
print(f"Muestras de entrenamiento: {len(x_train)}")
print(f"Dimensión de entrada: {len(x_train[0])}")
print(f"Dimensión de salida: {len(y_train[0])}")

# Verificar que coincidan
assert len(x_train) == len(y_train), "¡x_train y y_train deben tener mismo tamaño!"

# Verificar que input_dim sea correcto
assert len(x_train[0]) == model.input_dim, f"¡input_dim debe ser {len(x_train[0])}!"

# Verificar rangos (después de normalizar)
import statistics
muestra = x_train[0]
print(f"Min: {min(muestra)}, Max: {max(muestra)}")
# Idealmente debe estar en [0, 1] o [-1, 1]

# Verificar one-hot (para clasificación)
for y in y_train:
    assert sum(y) == 1.0, "¡One-hot debe sumar 1!"
    assert max(y) == 1.0, "¡One-hot debe tener un 1!"
```

#### 6. División Train/Test

Siempre separa tus datos en entrenamiento y prueba:

```python
import random

# Mezclar datos
combined = list(zip(x_data, y_data))
random.shuffle(combined)
x_data, y_data = zip(*combined)

# Dividir 80% train, 20% test
split_idx = int(0.8 * len(x_data))

x_train = list(x_data[:split_idx])
y_train = list(y_data[:split_idx])

x_test = list(x_data[split_idx:])
y_test = list(y_data[split_idx:])

print(f"Train: {len(x_train)} muestras")
print(f"Test: {len(x_test)} muestras")

# Entrenar solo con train
model.fit(x_train, y_train, epochs=50)

# Evaluar con test
for x, y in zip(x_test, y_test):
    pred = model.predict(x)
    # Comparar pred con y
```

---

## 🔧 API Completa

### Clase GRNexus

#### Constructor

```python
GRNexus(input_dim: int)
```

#### Métodos de Construcción

| Método | Descripción |
|--------|-------------|
| `add_dense(units, activation=None, use_bias=True, weight_init='xavier')` | Agregar capa densa |
| `add_activation(activation)` | Agregar capa de activación |
| `add_dropout(rate)` | Agregar dropout |
| `add_batch_norm(momentum=0.99, epsilon=1e-5)` | Agregar batch normalization |
| `add_softmax()` | Agregar softmax (salida) |

#### Métodos de Configuración

| Método | Descripción |
|--------|-------------|
| `compile(optimizer, loss, learning_rate, metrics=None)` | Compilar modelo |

#### Métodos de Entrenamiento

| Método | Descripción |
|--------|-------------|
| `fit(x_train, y_train, epochs, batch_size, validation_data=None, verbose=1)` | Entrenar modelo |
| `train_on_batch(x_batch, y_batch)` | Entrenar en un batch |

#### Métodos de Predicción

| Método | Descripción |
|--------|-------------|
| `predict(x, batch_size=32)` | Hacer predicciones |
| `forward(x, training=False)` | Forward pass (bajo nivel) |

#### Métodos de Persistencia

| Método | Descripción |
|--------|-------------|
| `save(filename)` | Guardar modelo |
| `load(filename)` (estático) | Cargar modelo |

---

### Capas Disponibles

#### DenseLayer
```python
DenseLayer(units, input_dim, activation=None, use_bias=True, weight_init='xavier')
```
Capa completamente conectada.

#### DropoutLayer
```python
DropoutLayer(rate)
```
Desactiva aleatoriamente neuronas durante entrenamiento.

#### BatchNormLayer
```python
BatchNormLayer(input_dim, momentum=0.99, epsilon=1e-5)
```
Normaliza activaciones para acelerar entrenamiento.

#### ActivationLayer
```python
ActivationLayer(activation)
```
Aplica función de activación no lineal.

#### SoftmaxLayer
```python
SoftmaxLayer()
```
Convierte logits en probabilidades (suma = 1).

---

### Funciones de Activación

#### Básicas
- `Linear`: f(x) = x
- `Step`: Función escalón
- `Sigmoid`: f(x) = 1 / (1 + e^(-x))
- `Tanh`: f(x) = tanh(x)

#### ReLU y Variantes
- `ReLU`: f(x) = max(0, x)
- `LeakyReLU`: f(x) = max(αx, x)
- `PReLU`: ReLU paramétrico
- `ELU`: Exponential Linear Unit
- `SELU`: Scaled ELU
- `ReLU6`: min(max(0, x), 6)

#### Modernas
- `Swish`: f(x) = x · sigmoid(x)
- `Mish`: f(x) = x · tanh(softplus(x))
- `GELU`: Gaussian Error Linear Unit
- `LiSHT`: Linearly Scaled Hyperbolic Tangent

#### Especializadas
- `Softplus`: f(x) = log(1 + e^x)
- `Softsign`: f(x) = x / (1 + |x|)
- `HardSigmoid`: Aproximación rápida de sigmoid
- `HardSwish`: Aproximación rápida de swish
- `HardTanh`: Versión acotada de tanh

#### Exóticas
- `Snake`: Función periódica
- `ISRU`: Inverse Square Root Unit
- `FReLU`: Funnel ReLU
- **Y más de 40 adicionales...**

**Ver lista completa en:** `lib/grnexus_activations.py`

---

### Optimizers

#### SGD (Stochastic Gradient Descent)
```python
model.compile(optimizer='sgd', learning_rate=0.01)
```
El optimizer más básico. Actualiza pesos en dirección opuesta al gradiente.

#### Momentum
```python
model.compile(optimizer='momentum', learning_rate=0.01)
```
SGD con momento. Acelera convergencia y reduce oscilaciones.

#### Adam (Recomendado)
```python
model.compile(optimizer='adam', learning_rate=0.001)
```
Adaptive Moment Estimation. Combina momentum con tasas de aprendizaje adaptativas.

#### RMSprop
```python
model.compile(optimizer='rmsprop', learning_rate=0.001)
```
Root Mean Square Propagation. Bueno para RNNs.

---

### Funciones de Pérdida

#### Categorical Cross-Entropy
```python
model.compile(loss='categorical_crossentropy')
```
Para clasificación multiclase con one-hot encoding.

#### MSE (Mean Squared Error)
```python
model.compile(loss='mse')
```
Para problemas de regresión.

---

## 💡 Ejemplos Prácticos

### Ejemplo 1: Clasificación de Dígitos (MNIST-like)

```python
from grnexus import GRNexus

# ============================================
# PASO 1: CREAR EL MODELO
# ============================================
# input_dim=784 porque las imágenes son 28x28 píxeles
# 28 × 28 = 784 valores de entrada
model = GRNexus(input_dim=784)

# ============================================
# PASO 2: CONSTRUIR LA ARQUITECTURA
# ============================================
# Primera capa oculta: 784 → 128 neuronas
# - ReLU: Función de activación que elimina valores negativos
# - he: Inicialización de pesos óptima para ReLU
model.add_dense(128, activation='ReLU', weight_init='he')

# Dropout: Desactiva aleatoriamente 20% de neuronas durante entrenamiento
# - Previene overfitting (memorización de datos)
# - Solo activo durante entrenamiento, no durante predicción
model.add_dropout(0.2)

# Segunda capa oculta: 128 → 64 neuronas
model.add_dense(64, activation='ReLU')
model.add_dropout(0.2)

# Capa de salida: 64 → 10 neuronas (una por cada dígito 0-9)
model.add_dense(10)

# Softmax: Convierte las 10 salidas en probabilidades que suman 1.0
# Ejemplo: [0.01, 0.02, 0.85, 0.03, ...] → "85% seguro que es un 2"
model.add_softmax()

# ============================================
# PASO 3: COMPILAR EL MODELO
# ============================================
model.compile(
    optimizer='adam',  # Adam: optimizer adaptativo, generalmente el mejor
    loss='categorical_crossentropy',  # Para clasificación multiclase
    learning_rate=0.01  # Qué tan grandes son los ajustes de pesos
)

# ============================================
# PASO 4: PREPARAR DATOS
# ============================================
# Ejemplo de cómo preparar datos (debes tener tus propios datos)
x_train = []  # Lista de imágenes aplanadas (cada una con 784 valores)
y_train = []  # Lista de etiquetas one-hot encoded

# Ejemplo: Agregar una imagen del dígito "3"
imagen_3 = [0.0] * 784  # Imagen de 28x28 aplanada
# ... (aquí irían los valores reales de los píxeles)

etiqueta_3 = [0, 0, 0, 1, 0, 0, 0, 0, 0, 0]  # One-hot para el dígito 3

x_train.append(imagen_3)
y_train.append(etiqueta_3)

# ... agregar más ejemplos ...

# ============================================
# PASO 5: ENTRENAR
# ============================================
print("Iniciando entrenamiento...")
history = model.fit(
    x_train, y_train,
    epochs=10,      # Número de veces que ver todo el dataset
    batch_size=32,  # Procesar 32 imágenes a la vez
    verbose=1       # Mostrar progreso
)

# ============================================
# PASO 6: HACER PREDICCIONES
# ============================================
# Predecir una imagen de prueba
test_image = [...]  # Imagen de prueba 784D

# Obtener probabilidades para cada dígito
prediction = model.predict(test_image)
# Resultado: [0.01, 0.02, 0.85, 0.03, 0.01, 0.02, 0.01, 0.03, 0.01, 0.01]

# Encontrar el dígito con mayor probabilidad
digit = prediction.index(max(prediction))
confidence = max(prediction)

print(f"Dígito predicho: {digit}")
print(f"Confianza: {confidence:.1%}")
# Salida: "Dígito predicho: 2, Confianza: 85.0%"

# ============================================
# PASO 7: GUARDAR MODELO
# ============================================
model.save('mnist_model.nexus')
print("Modelo guardado exitosamente")

# ============================================
# PASO 8: CARGAR Y USAR MODELO GUARDADO
# ============================================
# En otra sesión, puedes cargar el modelo entrenado
loaded_model = GRNexus.load('mnist_model.nexus')
new_prediction = loaded_model.predict(test_image)
```

**Resultados Esperados:**
- Precisión en entrenamiento: ~90-95% después de 10 épocas
- Loss final: ~0.2-0.3
- Tiempo de entrenamiento: Depende del tamaño del dataset

---

### Ejemplo 2: Análisis de Sentimientos

```python
from grnexus import GRNexus

# ============================================
# PREPARAR DATOS DE TEXTO
# ============================================
# Construir vocabulario (palabras únicas)
reviews = [
    "This movie is amazing and wonderful",
    "Terrible film, waste of time",
    "Great acting and beautiful scenes"
]

# Crear diccionario de palabras
vocab = {}
for review in reviews:
    words = review.lower().split()
    for word in words:
        if word not in vocab:
            vocab[word] = len(vocab)

vocab_size = len(vocab)
print(f"Vocabulario: {vocab_size} palabras")

# ============================================
# CONVERTIR TEXTO A VECTORES (BAG-OF-WORDS)
# ============================================
def text_to_vector(text, vocab):
    """
    Convierte texto en vector numérico
    Cada posición representa una palabra del vocabulario
    El valor es la frecuencia de esa palabra
    """
    vector = [0.0] * len(vocab)
    words = text.lower().split()
    
    # Contar ocurrencias
    for word in words:
        if word in vocab:
            vector[vocab[word]] += 1.0
    
    # Normalizar (convertir conteos a frecuencias)
    total = sum(vector)
    if total > 0:
        vector = [v / total for v in vector]
    
    return vector

# ============================================
# CREAR MODELO
# ============================================
model = GRNexus(input_dim=vocab_size)

# Arquitectura para análisis de sentimientos
model.add_dense(64, activation='ReLU')
model.add_dropout(0.3)  # Dropout más alto para texto
model.add_dense(32, activation='Swish')  # Swish: activación moderna
model.add_dense(2)  # 2 clases: Positivo/Negativo
model.add_softmax()

model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    learning_rate=0.005  # Learning rate más bajo para mejor convergencia
)

# ============================================
# PREPARAR DATASET
# ============================================
x_train = [
    text_to_vector("This movie is amazing", vocab),
    text_to_vector("Terrible waste of time", vocab),
    # ... más ejemplos ...
]

y_train = [
    [0, 1],  # Positivo
    [1, 0],  # Negativo
    # ... más etiquetas ...
]

# ============================================
# ENTRENAR
# ============================================
model.fit(x_train, y_train, epochs=20, batch_size=16)

# ============================================
# PREDECIR SENTIMIENTO
# ============================================
new_review = "This film is absolutely fantastic"
vector = text_to_vector(new_review, vocab)
prediction = model.predict(vector)

sentiment = "Positivo" if prediction[1] > prediction[0] else "Negativo"
confidence = max(prediction)

print(f"Sentimiento: {sentiment} ({confidence:.1%} confianza)")
```

**Resultados Esperados:**
- Precisión: ~80-90% con dataset pequeño
- El modelo aprende asociaciones palabra-sentimiento
- Palabras como "amazing", "great" → Positivo
- Palabras como "terrible", "waste" → Negativo

---

### Ejemplo 3: Clasificación Multiclase (Iris)

```python
from grnexus import GRNexus

# ============================================
# DATASET IRIS
# ============================================
# 4 características: largo sépalo, ancho sépalo, largo pétalo, ancho pétalo
# 3 especies: Setosa (0), Versicolor (1), Virginica (2)

# Datos de ejemplo (valores reales del dataset Iris)
iris_data = [
    [5.1, 3.5, 1.4, 0.2, 0],  # Setosa
    [7.0, 3.2, 4.7, 1.4, 1],  # Versicolor
    [6.3, 3.3, 6.0, 2.5, 2],  # Virginica
    # ... más muestras ...
]

# ============================================
# NORMALIZAR DATOS
# ============================================
def normalize_iris(data):
    """Normaliza características al rango [0, 1]"""
    # Valores min/max del dataset Iris
    mins = [4.3, 2.0, 1.0, 0.1]
    maxs = [7.9, 4.4, 6.9, 2.5]
    
    normalized = []
    for i, val in enumerate(data[:4]):  # Solo las 4 características
        norm = (val - mins[i]) / (maxs[i] - mins[i])
        normalized.append(norm)
    
    return normalized

# ============================================
# PREPARAR DATOS
# ============================================
x_train = [normalize_iris(sample) for sample in iris_data]

y_train = []
for sample in iris_data:
    species = sample[4]  # Última columna es la especie
    one_hot = [0.0, 0.0, 0.0]
    one_hot[species] = 1.0
    y_train.append(one_hot)

# ============================================
# CREAR MODELO
# ============================================
model = GRNexus(input_dim=4)  # 4 características

# Arquitectura pequeña (dataset pequeño)
model.add_dense(16, activation='ReLU')
model.add_dense(8, activation='Swish')
model.add_dense(3)  # 3 especies
model.add_softmax()

model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',
    learning_rate=0.01
)

# ============================================
# ENTRENAR
# ============================================
model.fit(x_train, y_train, epochs=100, batch_size=16)

# ============================================
# PREDECIR ESPECIE
# ============================================
# Nueva flor con medidas: [6.0, 3.0, 4.5, 1.5]
new_flower = normalize_iris([6.0, 3.0, 4.5, 1.5, 0])
prediction = model.predict(new_flower)

species_names = ["Setosa", "Versicolor", "Virginica"]
predicted_species = prediction.index(max(prediction))

print(f"Especie predicha: {species_names[predicted_species]}")
print(f"Probabilidades:")
for i, name in enumerate(species_names):
    print(f"  {name}: {prediction[i]:.1%}")
```

**Resultados Esperados:**
- Precisión: ~95-98% (dataset Iris es muy separable)
- El modelo aprende patrones en las medidas de las flores
- Setosa es fácilmente distinguible (pétalos pequeños)
- Versicolor y Virginica son más similares

---

### Ejemplo 4: Regresión (Predicción de Precios)

```python
from grnexus import GRNexus

# ============================================
# DATOS DE CASAS
# ============================================
# [habitaciones, baños, m², año, distancia_km, precio_miles]
housing_data = [
    [3, 2, 120, 2010, 5, 250],
    [4, 3, 180, 2015, 3, 380],
    [2, 1, 80, 1990, 10, 150],
    # ... más casas ...
]

# ============================================
# NORMALIZAR ENTRADA Y SALIDA
# ============================================
def normalize_features(features):
    """Normaliza las 5 características"""
    mins = [2, 1, 70, 1985, 1]
    maxs = [6, 5, 320, 2024, 15]
    
    return [(f - mins[i]) / (maxs[i] - mins[i]) 
            for i, f in enumerate(features)]

def normalize_price(price):
    """Normaliza precio a [0, 1]"""
    return (price - 100) / (750 - 100)

def denormalize_price(norm_price):
    """Convierte precio normalizado a valor real"""
    return norm_price * (750 - 100) + 100

# ============================================
# PREPARAR DATOS
# ============================================
x_train = [normalize_features(house[:5]) for house in housing_data]
y_train = [[normalize_price(house[5])] for house in housing_data]

# ============================================
# CREAR MODELO (REGRESIÓN)
# ============================================
model = GRNexus(input_dim=5)

# Arquitectura para regresión
model.add_dense(32, activation='ReLU')
model.add_dropout(0.2)
model.add_dense(16, activation='Swish')
model.add_dense(8, activation='ReLU')
model.add_dense(1)  # UNA sola neurona de salida (precio)
# NO usar Softmax en regresión

model.compile(
    optimizer='adam',
    loss='categorical_crossentropy',  # Usaremos MSE manualmente
    learning_rate=0.001  # Learning rate bajo para regresión
)

# ============================================
# ENTRENAR
# ============================================
print("Entrenando modelo de regresión...")
for epoch in range(150):
    total_loss = 0
    for i in range(0, len(x_train), 4):
        batch_x = x_train[i:i+4]
        batch_y = y_train[i:i+4]
        loss = model.train_on_batch(batch_x, batch_y)
        total_loss += loss
    
    if (epoch + 1) % 30 == 0:
        avg_loss = total_loss / (len(x_train) / 4)
        print(f"Epoch {epoch+1}/150 - loss: {avg_loss:.4f}")

# ============================================
# PREDECIR PRECIO
# ============================================
# Casa nueva: 3 hab, 2 baños, 140m², año 2012, 6km del centro
new_house = normalize_features([3, 2, 140, 2012, 6])
prediction_norm = model.predict(new_house)

# Desnormalizar para obtener precio real
predicted_price = denormalize_price(prediction_norm[0])

print(f"\nCasa: 3 hab, 2 baños, 140m², 2012, 6km centro")
print(f"Precio predicho: ${predicted_price:.0f}k")

# ============================================
# EVALUAR MODELO
# ============================================
errors = []
for house in housing_data:
    features = normalize_features(house[:5])
    real_price = house[5]
    
    pred_norm = model.predict(features)
    pred_price = denormalize_price(pred_norm[0])
    
    error = abs(real_price - pred_price)
    errors.append(error)

avg_error = sum(errors) / len(errors)
print(f"\nError promedio: ${avg_error:.1f}k")
```

**Resultados Esperados:**
- Error promedio: $20-40k (depende del dataset)
- El modelo aprende relaciones:
  - Más habitaciones → Precio más alto
  - Más metros cuadrados → Precio más alto
  - Más cerca del centro → Precio más alto
  - Más nuevo → Precio más alto

---

## 🎮 Tests Interactivos

GRNexus incluye 5 aplicaciones interactivas con Flet para probar las capacidades de la biblioteca:

### 1. test_digitos.py - Reconocimiento de Dígitos
```bash
python3 test/test_digitos.py
```
- Dibuja dígitos con el mouse
- Entrena la red en tiempo real
- Visualiza probabilidades por clase
- Guarda/carga modelos

### 2. test_sentiment.py - Análisis de Sentimientos
```bash
python3 test/test_sentiment.py
```
- Analiza reseñas de películas
- 30 reseñas reales de IMDb
- Clasifica como positivo/negativo
- Bag-of-words + red neuronal

### 3. test_iris.py - Clasificación Iris
```bash
python3 test/test_iris.py
```
- Dataset clásico de ML
- 90 muestras de 3 especies de flores
- Sliders para ajustar características
- Visualización de probabilidades

### 4. test_housing.py - Predicción de Precios
```bash
python3 test/test_housing.py
```
- Regresión con datos reales
- 42 casas con precios
- Sliders para habitaciones, m², etc.
- Compara precio real vs predicho

### 5. test_suite.py - Launcher Maestro
```bash
python3 test/test_suite.py
```
- Ejecuta todos los tests desde una interfaz
- Descripción de cada test
- Ejecutar individual o todos juntos

---

## 🔍 Troubleshooting

### Problema: "ModuleNotFoundError: No module named 'grnexus'"

**Solución:**
```python
import sys
import os
sys.path.insert(0, os.path.abspath('path/to/GRNexus/python'))
```

### Problema: "TypeError: layer.backward() takes 2 positional arguments but 3 were given"

**Causa:** Código desactualizado de GRNexus v1.0

**Solución:** Actualizar a GRNexus v2.0. El método `backward` ya no acepta `learning_rate`.

### Problema: Pérdida (loss) no disminuye

**Posibles causas:**
1. Learning rate muy alto → Reducir a 0.001 o menos
2. Learning rate muy bajo → Aumentar a 0.01
3. Datos no normalizados → Normalizar a rango [0, 1] o [-1, 1]
4. Arquitectura inadecuada → Agregar más capas/neuronas
5. Inicialización incorrecta → Usar 'he' para ReLU, 'xavier' para Sigmoid/Tanh

### Problema: Overfitting (buena pérdida en entrenamiento, mala en validación)

**Soluciones:**
- Agregar Dropout: `model.add_dropout(0.2)`
- Reducir tamaño del modelo (menos neuronas/capas)
- Aumentar tamaño del dataset
- Agregar regularización

### Problema: Predicciones siempre retornan la misma clase

**Causas:**
- Clases desbalanceadas en el dataset
- Learning rate muy alto provocando divergencia
- Pesos no inicializados correctamente

**Soluciones:**
- Balancear clases en el dataset
- Reducir learning rate
- Verificar que `compile()` fue llamado antes de entrenar

### Problema: "IndexError: list index out of range" durante entrenamiento

**Causa:** Dimensiones incorrectas en los datos

**Solución:**
- Verificar que `x_train` tenga dimensión `input_dim`
- Verificar que `y_train` sea one-hot encoded con `num_classes` elementos
- Ejemplo: Si 3 clases, usar `[[1,0,0], [0,1,0], [0,0,1]]`

---

## 📊 Mejores Prácticas

### 1. Preparación de Datos

```python
# Normalizar datos
def normalize(data):
    min_val = min(data)
    max_val = max(data)
    return [(x - min_val) / (max_val - min_val) for x in data]

# One-hot encoding
def one_hot(label, num_classes):
    encoding = [0.0] * num_classes
    encoding[label] = 1.0
    return encoding
```

### 2. Arquitectura

- **Clasificación simple:** 1-2 capas ocultas
- **Clasificación compleja:** 3-5 capas con Dropout
- **Regresión:** 2-4 capas sin Softmax final

### 3. Hiperparámetros

| Tamaño Dataset | Batch Size | Learning Rate | Epochs |
|----------------|------------|---------------|--------|
| < 1000         | 16-32      | 0.01          | 50-100 |
| 1000-10000     | 32-64      | 0.01-0.001    | 20-50  |
| > 10000        | 64-128     | 0.001         | 10-30  |

### 4. Monitoreo

```python
history = model.fit(x_train, y_train, epochs=10, verbose=1)

# Graficar pérdida por época
import matplotlib.pyplot as plt
plt.plot(history['loss'])
plt.xlabel('Época')
plt.ylabel('Pérdida')
plt.title('Curva de Entrenamiento')
plt.show()
```

---

<div align="center">

**Hecho con ❤️ por el equipo de GRNexus**

[GitHub](https://github.com/tu-usuario/GRNexus) • [Documentación](docs/) • [Examples](test/)

</div>
