grx-tensor 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +26 -0
- data/GUIA_PRINCIPIANTES.md +1046 -0
- data/README.es.md +1199 -0
- data/README.md +1026 -283
- data/ext/grx/extconf.rb +4 -18
- data/ext/grx/grx_core.c +411 -331
- data/ext/grx/grx_core.h +23 -13
- data/ext/unix/Makefile +3 -27
- data/ext/windows/Makefile.mingw +3 -23
- data/grx-tensor.gemspec +41 -35
- data/lib/grx/c_api.rb +75 -39
- data/lib/grx/data.rb +87 -0
- data/lib/grx/loss.rb +55 -25
- data/lib/grx/nn.rb +160 -41
- data/lib/grx/optim.rb +21 -16
- data/lib/grx/serialization.rb +66 -0
- data/lib/grx/storage.rb +19 -27
- data/lib/grx/tensor.rb +234 -65
- data/lib/grx/utils.rb +43 -0
- data/lib/grx/version.rb +1 -1
- data/lib/grx.rb +22 -6
- metadata +37 -28
|
@@ -0,0 +1,1046 @@
|
|
|
1
|
+
# Guia de GRX-Tensor para Principiantes (Desde Cero a Produccion)
|
|
2
|
+
|
|
3
|
+
**Aprende computacion tensorial, autograd, redes neuronales, chatbots con memoria, NLP, datasets masivos y despliegue en sistemas reales paso a paso.**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tabla de Contenido
|
|
8
|
+
|
|
9
|
+
1. [Bienvenida y Filosofia del Framework](#1-bienvenida-y-filosofia-del-framework)
|
|
10
|
+
2. [Fundamentos: Que es un Shape, Numel, Strides y Rango?](#2-fundamentos-que-es-un-shape-numel-strides-y-rango)
|
|
11
|
+
- [De Escalar a Tensor Multidimensional](#de-escalar-a-tensor-multidimensional)
|
|
12
|
+
- [Como se guardan los datos en memoria real (C vs Ruby)](#como-se-guardan-los-datos-en-memoria-real-c-vs-ruby)
|
|
13
|
+
- [La formula matematica del indice plano](#la-formula-matematica-del-indice-plano)
|
|
14
|
+
3. [Margen de Error y Precision Numerica en GRX](#3-margen-de-error-y-precision-numerica-en-grx)
|
|
15
|
+
- [Doble precision IEEE 754 (Float 64-bit)](#doble-precision-ieee-754-float-64-bit)
|
|
16
|
+
- [Que es FMA (Fused Multiply-Add) y por que reduce el error?](#que-es-fma-fused-multiply-add-y-por-que-reduce-el-error)
|
|
17
|
+
- [Tolerancias (Epsilon) y medicion real del error](#tolerancias-epsilon-y-medicion-real-del-error)
|
|
18
|
+
4. [Procesamiento de Lenguaje Natural (NLP): Tokenizacion y Chatbots](#4-procesamiento-de-lenguaje-natural-nlp-tokenizacion-y-chatbots)
|
|
19
|
+
- [Que es la Tokenizacion y como se hace?](#que-es-la-tokenizacion-y-como-se-hace)
|
|
20
|
+
- [De Tokens a Vectores Semanticos (Embedding)](#de-tokens-a-vectores-semanticos-embedding)
|
|
21
|
+
- [Como crear un modelo que hable contigo y use su memoria?](#como-crear-un-modelo-que-hable-contigo-y-use-su-memoria)
|
|
22
|
+
- [Control de Confianza: Que pasa cuando el bot no entiende?](#control-de-confianza-que-pasa-cuando-el-bot-no-entiende)
|
|
23
|
+
5. [Como Usar Modelos Entrenados en Produccion (Sin Reentrenar)](#5-como-usar-modelos-entrenados-en-produccion-sin-reentrenar)
|
|
24
|
+
- [El Flujo de Vida: Entrenamiento vs Inferencia](#el-flujo-de-vida-entrenamiento-vs-inferencia)
|
|
25
|
+
- [Ejemplo de API Web con Sinatra / Rails en Finanzas](#ejemplo-de-api-web-con-sinatra--rails-en-finanzas)
|
|
26
|
+
6. [Primeros Pasos: Creando Tensores](#6-primeros-pasos-creando-tensores)
|
|
27
|
+
7. [Aritmetica y Operaciones Basicas](#7-aritmetica-y-operaciones-basicas)
|
|
28
|
+
8. [Algebra Lineal Intuitiva: Multiplicacion de Matrices](#8-algebra-lineal-intuitiva-multiplicacion-de-matrices)
|
|
29
|
+
9. [El Superpoder de GRX: Que es Autograd?](#9-el-superpoder-de-grx-que-es-autograd)
|
|
30
|
+
10. [Construyendo Redes Neuronales Bloque a Bloque](#10-construyendo-redes-neuronales-bloque-a-bloque)
|
|
31
|
+
11. [El Ciclo Sagrado del Entrenamiento (Explicacion Paso a Paso)](#11-el-ciclo-sagrado-del-entrenamiento-explicacion-paso-a-paso)
|
|
32
|
+
12. [El Diccionario Sagrado de Parametros e Hiperparametros](#12-el-diccionario-sagrado-de-parametros-e-hiperparametros)
|
|
33
|
+
13. [Proyectos Guiados Paso a Paso (Completos y Ejecutables)](#13-proyectos-guiados-paso-a-paso-completos-y-ejecutables)
|
|
34
|
+
- [Proyecto 1: El Conversor de Temperatura (Celsius a Fahrenheit)](#proyecto-1-el-conversor-de-temperatura-celsius-a-fahrenheit)
|
|
35
|
+
- [Proyecto 2: Compuertas Logicas (AND Lineal vs XOR No Lineal)](#proyecto-2-compuertas-logicas-and-lineal-vs-xor-no-lineal)
|
|
36
|
+
- [Proyecto 3: El Predictor de Formulas (Regresion Lineal)](#proyecto-3-el-predictor-de-formulas-regresion-lineal)
|
|
37
|
+
- [Proyecto 4: Ajuste de Curvas No Lineales y Guardado en .grx](#proyecto-4-ajuste-de-curvas-no-lineales-y-guardado-en-grx)
|
|
38
|
+
- [Proyecto 5: Clasificador Binario Inteligente con BCELoss](#proyecto-5-clasificador-binario-inteligente-con-bceloss)
|
|
39
|
+
- [Proyecto 6: Entrenamiento con Datasets Masivos (5,000 Filas con DataLoader)](#proyecto-6-entrenamiento-con-datasets-masivos-5000-filas-con-dataloader)
|
|
40
|
+
- [Proyecto 7: Chatbot Financiero Inteligente con Base de Conocimiento y Filtro de Incertidumbre](#proyecto-7-chatbot-financiero-inteligente-con-base-de-conocimiento-y-filtro-de-incertidumbre)
|
|
41
|
+
14. [Buenas Practicas y Errores Comunes](#14-buenas-practicas-y-errores-comunes)
|
|
42
|
+
15. [Glosario de Terminos Clave](#15-glosario-de-terminos-clave)
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## 1. Bienvenida y Filosofia del Framework
|
|
47
|
+
|
|
48
|
+
Ruby es un lenguaje celebrado por su elegancia y legibilidad. Sin embargo, en el procesamiento de matrices masivas e inteligencia artificial, la sobrecarga del recolector de basura (GC) puede restar velocidad si todo se hace en Ruby puro.
|
|
49
|
+
|
|
50
|
+
Por otro lado, C ofrece velocidad extrema, pero programar grafos de autograd y redes neuronales en C directamente es propenso a errores de memoria.
|
|
51
|
+
|
|
52
|
+
**GRX-Tensor une lo mejor de ambos mundos:**
|
|
53
|
+
- **Ruby habla:** Escribes codigo fluido, declarativo y limpio para definir arquitecturas, procesar datos y crear aplicaciones.
|
|
54
|
+
- **C calcula:** Cada operacion matematica, producto matricial, retropropagacion y actualizacion de pesos se ejecuta en un buffer nativo de C con aceleracion vectorial **SIMD multi-target (AVX2 + FMA, SSE y C escalar)**.
|
|
55
|
+
|
|
56
|
+
### Instalacion y Compatibilidad de Hardware Universal
|
|
57
|
+
1. **Instalacion 100% automatica:** Al instalar con `gem install grx-tensor`, RubyGems compila y enlaza automaticamente la extension nativa de C en segundo plano sin requerir comandos de compilacion manuales.
|
|
58
|
+
2. **Despacho Dinamico por CPU:** El motor en C detecta en tiempo real las caracteristicas de tu procesador:
|
|
59
|
+
- Si tu procesador soporta **AVX2 + FMA**, activa la maxima aceleracion vectorial (4 doubles/ciclo).
|
|
60
|
+
- Si tu procesador soporta **SSE**, activa las instrucciones vectoriales SSE.
|
|
61
|
+
- Si tu maquina es ARM, una maquina virtual o una CPU clasica, conmuta a **C escalar**, evitando cualquier error de "instruccion ilegal".
|
|
62
|
+
3. **Estado del Soporte en Windows:** En Windows, la aceleracion nativa funciona compilando automaticamente mediante **RubyInstaller con DevKit (MSYS2 / MinGW-w64)**. Si no hay compilador presente, el framework corre en modo fallback de Ruby puro de forma segura. El empaquetado de binarios pre-compilados (.dll) sin necesidad de DevKit se encuentra en desarrollo activo.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 2. Fundamentos: Que es un Shape, Numel, Strides y Rango?
|
|
67
|
+
|
|
68
|
+
### De Escalar a Tensor Multidimensional
|
|
69
|
+
|
|
70
|
+
Matematicamente, un tensor es una estructura de datos que generaliza escalares, vectores y matrices a cualquier numero de dimensiones:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Rango 0 (Escalar):
|
|
74
|
+
Un solo valor numerico.
|
|
75
|
+
Shape: [1]
|
|
76
|
+
Ejemplo: 42.0
|
|
77
|
+
|
|
78
|
+
Rango 1 (Vector):
|
|
79
|
+
Una secuencia unidimensional de longitud N.
|
|
80
|
+
Shape: [3]
|
|
81
|
+
Visual: [ 1.0, 2.0, 3.0 ]
|
|
82
|
+
|
|
83
|
+
Rango 2 (Matriz):
|
|
84
|
+
Una tabla bidimensional de Filas x Columnas.
|
|
85
|
+
Shape: [2, 3] (2 filas, 3 columnas)
|
|
86
|
+
Visual:
|
|
87
|
+
┌ ┐
|
|
88
|
+
│ 1.0 2.0 3.0 │ <- Fila 0
|
|
89
|
+
│ 4.0 5.0 6.0 │ <- Fila 1
|
|
90
|
+
└ ┘
|
|
91
|
+
|
|
92
|
+
Rango 3 (Tensor 3D):
|
|
93
|
+
Un bloque tridimensional (Profundidad x Filas x Columnas).
|
|
94
|
+
Shape: [2, 2, 3] (2 matrices de 2x3)
|
|
95
|
+
|
|
96
|
+
Rango 4 (Tensor 4D):
|
|
97
|
+
Comun en procesamiento de imagenes y batches: [Batch, Canales, Alto, Ancho].
|
|
98
|
+
Shape: [64, 3, 224, 224] (64 imagenes RGB de 224x224 pixeles)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### Conceptos Clave
|
|
104
|
+
|
|
105
|
+
#### 1. Shape (Forma dimensional)
|
|
106
|
+
Es el arreglo de enteros que describe el tamano de cada dimension del tensor.
|
|
107
|
+
- Ejemplo: `[5000, 4]` representa una tabla de 5,000 filas y 4 columnas de caracteristicas.
|
|
108
|
+
|
|
109
|
+
#### 2. Numel (Numero total de elementos)
|
|
110
|
+
Es la cantidad total de valores escalares que contiene el tensor:
|
|
111
|
+
|
|
112
|
+
$$\text{numel} = \prod_{i=0}^{d-1} \text{shape}[i]$$
|
|
113
|
+
|
|
114
|
+
En Ruby:
|
|
115
|
+
```ruby
|
|
116
|
+
t = GRX.tensor([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
|
|
117
|
+
puts t.numel # => 6 (porque 2 * 3 = 6)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### 3. Strides (Saltos de memoria)
|
|
121
|
+
Indica cuantos elementos de la memoria plana contigua se deben saltar para avanzar una posicion en cada eje:
|
|
122
|
+
- Para `shape = [2, 3]`, los strides contiguos son `[3, 1]`:
|
|
123
|
+
- Para avanzar 1 columna a la derecha: saltas **1** posicion en memoria.
|
|
124
|
+
- Para avanzar 1 fila hacia abajo: saltas **3** posiciones en memoria.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
### Como se guardan los datos en memoria real (C vs Ruby)
|
|
129
|
+
|
|
130
|
+
En la memoria fisica (RAM), los datos existen en una sola linea recta de bytes contiguos:
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
Buffer plano en memoria C:
|
|
134
|
+
Posicion C: [0] [1] [2] [3] [4] [5]
|
|
135
|
+
Valor: 1.0 2.0 3.0 4.0 5.0 6.0
|
|
136
|
+
|
|
137
|
+
Interpretacion logica 2D (Shape [2, 3]):
|
|
138
|
+
Fila 0: 1.0 2.0 3.0
|
|
139
|
+
Fila 1: 4.0 5.0 6.0
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### La formula matematica del indice plano
|
|
143
|
+
|
|
144
|
+
$$\text{Indice Plano} = \text{offset} + (\text{fila} \times \text{stride}_0) + (\text{columna} \times \text{stride}_1)$$
|
|
145
|
+
|
|
146
|
+
Para acceder a `(fila: 1, columna: 1)` con `strides = [3, 1]`:
|
|
147
|
+
$$\text{Indice Plano} = 0 + (1 \times 3) + (1 \times 1) = 4 \implies \text{Valor } 5.0$$
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 3. Margen de Error y Precision Numerica en GRX
|
|
152
|
+
|
|
153
|
+
### Doble precision IEEE 754 (Float 64-bit)
|
|
154
|
+
|
|
155
|
+
A diferencia de librerias que usan 32 bits (`float32`), GRX utiliza **doble precision de 64 bits (`double`)** en todo su nucleo de C:
|
|
156
|
+
|
|
157
|
+
| Formato | Bits Totales | Bits de Mantisa | Digitos Decimales Significativos | Margen de Error por Redondeo |
|
|
158
|
+
|---|---|---|---|---|
|
|
159
|
+
| `float32` | 32 bits | 24 bits | ~7 digitos | $\approx 1 \times 10^{-7}$ |
|
|
160
|
+
| `double` (GRX) | 64 bits | 53 bits | **~15 a 17 digitos** | $\mathbf{\approx 2 \times 10^{-16}}$ |
|
|
161
|
+
|
|
162
|
+
### FMA (Fused Multiply-Add)
|
|
163
|
+
En el producto punto (`dot`), multiplicacion de matrices (`matmul`) y optimizador `Adam`, se calcula $a \times b + c$:
|
|
164
|
+
- **Sin FMA:** Se multiplica $a \times b$, se redondea el resultado, se suma $c$ y se vuelve a redondear.
|
|
165
|
+
- **Con FMA en GRX:** La CPU calcula $a \times b + c$ con precision infinita intermedia y hace **un solo redondeo final**, reduciendo a la mitad el margen de error acumulado.
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 4. Procesamiento de Lenguaje Natural (NLP): Tokenizacion y Chatbots
|
|
170
|
+
|
|
171
|
+
Una computadora no puede multiplicar palabras directamente como `"prestamo"` o `"saldo"`. Para que una red neuronal pueda procesar texto, comprender intenciones y responder con lenguaje natural, se sigue un pipeline matematico estricto:
|
|
172
|
+
|
|
173
|
+
### Que es la Tokenizacion y como se hace?
|
|
174
|
+
|
|
175
|
+
La tokenizacion es el proceso de convertir un texto en una secuencia ordenada de numeros enteros (*tokens*):
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
Frase del usuario:
|
|
179
|
+
"cual es mi saldo disponible hoy"
|
|
180
|
+
│
|
|
181
|
+
▼
|
|
182
|
+
1. Limpieza y separacion en palabras (split):
|
|
183
|
+
["cual", "es", "mi", "saldo", "disponible", "hoy"]
|
|
184
|
+
│
|
|
185
|
+
▼
|
|
186
|
+
2. Diccionario de Vocabulario (mapeo de palabra a ID entero):
|
|
187
|
+
{ "cual" => 1, "es" => 2, "mi" => 3, "saldo" => 4, "disponible" => 5, "hoy" => 6 }
|
|
188
|
+
│
|
|
189
|
+
▼
|
|
190
|
+
3. Arreglo de IDs de entrada:
|
|
191
|
+
[1, 2, 3, 4, 5, 6]
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
En Ruby:
|
|
195
|
+
```ruby
|
|
196
|
+
vocab = ["cual", "es", "mi", "saldo", "disponible", "hoy"]
|
|
197
|
+
token_to_id = vocab.each_with_index.to_h
|
|
198
|
+
|
|
199
|
+
mensaje = "mi saldo disponible"
|
|
200
|
+
ids = mensaje.split.map { |w| token_to_id[w] || 0 }
|
|
201
|
+
# => [3, 4, 5]
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
### De Tokens a Vectores Semanticos (`Embedding`)
|
|
207
|
+
|
|
208
|
+
Un simple numero entero como `4` no contiene significado de lo que es un `"saldo"`. Por ello, la capa `GRX::NN::Embedding` traduce cada ID a un vector denso de punto flotante en un espacio continuo de $D$ dimensiones (ej. 16 dimensiones):
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
Token 4 ("saldo") ───> [ 0.42, -0.18, 0.95, ..., -0.04 ] (16 floats)
|
|
212
|
+
Token 5 ("dinero") ──> [ 0.40, -0.15, 0.91, ..., -0.02 ] (Muy similar!)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Durante el entrenamiento, palabras con significados similares terminan con vectores muy cercanos en el espacio vectorial.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
### Como crear un modelo que hable contigo y use su memoria?
|
|
220
|
+
|
|
221
|
+
Para crear un agente conversacional que entienda lo que le pides y recupere informacion de su base de conocimiento:
|
|
222
|
+
|
|
223
|
+
1. **Base de Conocimiento (Memoria de Hechos):** Se define un diccionario con las respuestas correctas para cada intencion de negocio (saldo, tasas, prestamos, horarios).
|
|
224
|
+
2. **Clasificador de Intenciones Neuronal:** La red procesa la frase mediante `Embedding -> LayerNorm -> Linear -> ReLU -> Linear -> Softmax` y calcula probabilidades para cada intencion.
|
|
225
|
+
3. **Recuperacion de Memoria:** Al identificar la intencion con mayor probabilidad, el bot extrae la respuesta precisa de su memoria.
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
### Control de Confianza: Que pasa cuando el bot no entiende?
|
|
230
|
+
|
|
231
|
+
Si un usuario escribe algo fuera de dominio (ej. *"como hago una pizza"*):
|
|
232
|
+
- **Verificacion de Cobertura de Vocabulario:** Si la mayoria de las palabras son desconocidas para el modelo ($< 50\%$ de cobertura), se rechaza inmediatamente.
|
|
233
|
+
- **Filtro de Probabilidad Softmax (*Confidence Threshold*):** Si la certeza calculada por la red es menor al $70\%$, el bot responde de forma segura:
|
|
234
|
+
> *"Disculpa, no comprendo tu mensaje con suficiente certeza. Solo puedo responder sobre saldo, prestamos o tasas de interes."*
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## 5. Como Usar Modelos Entrenados en Produccion (Sin Reentrenar)
|
|
239
|
+
|
|
240
|
+
Una duda muy comun es: *Si entreno una red para finanzas o clasificacion, como la uso en un sistema real (API web, Rails, Sinatra o app de escritorio) sin tener que reentrenarla cada vez?*
|
|
241
|
+
|
|
242
|
+
### El Flujo de Vida: Entrenamiento vs Inferencia
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
246
|
+
│ FASE 1: ENTRENAMIENTO (Se hace 1 sola vez) │
|
|
247
|
+
│ 1. Lees tu dataset historico (5,000+ filas). │
|
|
248
|
+
│ 2. Entrenas la red con DataLoader y Adam. │
|
|
249
|
+
│ 3. Guardas los pesos: modelo.save_weights("finanzas.grx") │
|
|
250
|
+
│ 4. Guardas el vocabulario / normalizacion en un JSON. │
|
|
251
|
+
└──────────────────────────────┬──────────────────────────────┘
|
|
252
|
+
│
|
|
253
|
+
Genera el archivo "finanzas.grx"
|
|
254
|
+
│
|
|
255
|
+
▼
|
|
256
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
257
|
+
│ FASE 2: PRODUCCION / INFERENCIA (En tu API) │
|
|
258
|
+
│ 1. Al arrancar el servidor web (Rails/Sinatra): │
|
|
259
|
+
│ - Instancias la arquitectura del modelo. │
|
|
260
|
+
│ - Cargas los pesos: modelo.load_weights("finanzas.grx") │
|
|
261
|
+
│ - Pones la red en modo inferencia: modelo.eval! │
|
|
262
|
+
│ 2. Al recibir una peticion HTTP POST: │
|
|
263
|
+
│ - Conviertes el JSON de entrada a un Tensor. │
|
|
264
|
+
│ - Ejecutas pred = modelo.call(tensor_entrada). │
|
|
265
|
+
│ - Retornas la respuesta en < 0.1 milisegundos! │
|
|
266
|
+
└─────────────────────────────────────────────────────────────┘
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Ejemplo de API Web con Sinatra / Rails en Finanzas
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
# app_api.rb — Microservicio de evaluacion de credito en tiempo real
|
|
273
|
+
require "grx"
|
|
274
|
+
require "json"
|
|
275
|
+
|
|
276
|
+
# 1. Cargar el modelo en memoria al iniciar la aplicacion (Toma < 1 ms)
|
|
277
|
+
MODELO_RIESGO = GRX::NN::Sequential.new(
|
|
278
|
+
GRX::NN::Linear.new(4, 16),
|
|
279
|
+
GRX::NN::LayerNorm.new(16),
|
|
280
|
+
GRX::NN::Tanh.new,
|
|
281
|
+
GRX::NN::Linear.new(16, 1),
|
|
282
|
+
GRX::NN::Sigmoid.new
|
|
283
|
+
)
|
|
284
|
+
MODELO_RIESGO.load_weights("modelo_riesgo_crediticio.grx")
|
|
285
|
+
MODELO_RIESGO.eval!
|
|
286
|
+
|
|
287
|
+
# 2. Endpoint de inferencia para evaluar solicitudes de credito
|
|
288
|
+
def evaluar_solicitud(ingresos_mensuales, deuda_actual, score_buro, monto_solicitado)
|
|
289
|
+
# Normalizar datos (utilizando las medias y desviaciones del entrenamiento)
|
|
290
|
+
x1 = (ingresos_mensuales - 25000.0) / 10000.0
|
|
291
|
+
x2 = (deuda_actual - 5000.0) / 4000.0
|
|
292
|
+
x3 = (score_buro - 650.0) / 100.0
|
|
293
|
+
x4 = (monto_solicitado - 50000.0) / 30000.0
|
|
294
|
+
|
|
295
|
+
entrada = GRX.tensor([x1, x2, x3, x4], [1, 4])
|
|
296
|
+
probabilidad_aprobacion = MODELO_RIESGO.call(entrada).to_a[0]
|
|
297
|
+
|
|
298
|
+
decision = probabilidad_aprobacion >= 0.70 ? "APROBADO" : "RECHAZADO"
|
|
299
|
+
|
|
300
|
+
{
|
|
301
|
+
decision: decision,
|
|
302
|
+
confianza: (probabilidad_aprobacion * 100).round(2),
|
|
303
|
+
tasa_sugerida: decision == "APROBADO" ? "12.5% anual" : "N/A"
|
|
304
|
+
}
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# Prueba en produccion:
|
|
308
|
+
# res = evaluar_solicitud(45000.0, 2000.0, 720.0, 80000.0)
|
|
309
|
+
# puts res.inspect
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## 6. Primeros Pasos: Creando Tensores
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
require "grx"
|
|
318
|
+
|
|
319
|
+
# 1. Vector de 3 elementos
|
|
320
|
+
v = GRX.tensor([1.0, 2.0, 3.0], [3])
|
|
321
|
+
puts "Shape: #{v.shape}" # => [3]
|
|
322
|
+
puts "Numel: #{v.numel}" # => 3
|
|
323
|
+
|
|
324
|
+
# 2. Matriz de 2 filas x 3 columnas
|
|
325
|
+
m = GRX.tensor([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
|
|
326
|
+
puts "Matriz: #{m.shape}" # => [2, 3]
|
|
327
|
+
|
|
328
|
+
# 3. Factorias
|
|
329
|
+
ceros = GRX.zeros([2, 2]) # Matriz 2x2 de 0.0
|
|
330
|
+
unos = GRX.ones([3, 1]) # Matriz 3x1 de 1.0
|
|
331
|
+
|
|
332
|
+
# 4. Obtener un valor escalar
|
|
333
|
+
scalar = GRX.tensor([99.0], [1])
|
|
334
|
+
puts scalar.item # => 99.0 (Float)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## 7. Aritmetica y Operaciones Basicas
|
|
340
|
+
|
|
341
|
+
```ruby
|
|
342
|
+
require "grx"
|
|
343
|
+
|
|
344
|
+
a = GRX.tensor([10.0, 20.0, 30.0], [3])
|
|
345
|
+
b = GRX.tensor([2.0, 5.0, 10.0], [3])
|
|
346
|
+
|
|
347
|
+
# Operaciones tensor-tensor (C + SIMD)
|
|
348
|
+
puts (a + b).to_a # => [12.0, 25.0, 40.0]
|
|
349
|
+
puts (a * b).to_a # => [20.0, 100.0, 300.0]
|
|
350
|
+
|
|
351
|
+
# Operaciones con escalares
|
|
352
|
+
puts (a + 5.0).to_a # => [15.0, 25.0, 35.0]
|
|
353
|
+
puts (2.0 * a).to_a # => [20.0, 40.0, 60.0]
|
|
354
|
+
|
|
355
|
+
# Reducciones diferenciables
|
|
356
|
+
x = GRX.tensor([1.0, 4.0, 9.0, 16.0], [4])
|
|
357
|
+
puts x.sum.item # => 30.0
|
|
358
|
+
puts x.mean.item # => 7.5
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## 8. Algebra Lineal Intuitiva: Multiplicacion de Matrices
|
|
364
|
+
|
|
365
|
+
```ruby
|
|
366
|
+
require "grx"
|
|
367
|
+
|
|
368
|
+
# [2, 3] x [3, 2] -> [2, 2]
|
|
369
|
+
a = GRX.tensor([1.0, 2.0, 3.0,
|
|
370
|
+
4.0, 5.0, 6.0], [2, 3])
|
|
371
|
+
|
|
372
|
+
b = GRX.tensor([7.0, 8.0,
|
|
373
|
+
9.0, 10.0,
|
|
374
|
+
11.0, 12.0], [3, 2])
|
|
375
|
+
|
|
376
|
+
c = a.matmul(b)
|
|
377
|
+
puts c.shape # => [2, 2]
|
|
378
|
+
puts c.to_a # => [58.0, 64.0, 139.0, 154.0]
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
## 9. El Superpoder de GRX: Que es Autograd?
|
|
384
|
+
|
|
385
|
+
```ruby
|
|
386
|
+
require "grx"
|
|
387
|
+
|
|
388
|
+
x = GRX.tensor([2.0, 3.0], [2], requires_grad: true)
|
|
389
|
+
y = GRX.tensor([4.0, 5.0], [2], requires_grad: true)
|
|
390
|
+
|
|
391
|
+
# Computamos z = (x + y) * y
|
|
392
|
+
z = ((x + y) * y).sum
|
|
393
|
+
z.backward
|
|
394
|
+
|
|
395
|
+
# Derivadas exactas calculadas automaticamente
|
|
396
|
+
puts x.grad.to_a # => [4.0, 5.0]
|
|
397
|
+
puts y.grad.to_a # => [10.0, 13.0]
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
## 10. Construyendo Redes Neuronales Bloque a Bloque
|
|
403
|
+
|
|
404
|
+
```ruby
|
|
405
|
+
require "grx"
|
|
406
|
+
|
|
407
|
+
modelo = GRX::NN::Sequential.new(
|
|
408
|
+
GRX::NN::Linear.new(4, 16),
|
|
409
|
+
GRX::NN::LayerNorm.new(16),
|
|
410
|
+
GRX::NN::ReLU.new,
|
|
411
|
+
GRX::NN::Linear.new(16, 1)
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
puts modelo
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
---
|
|
418
|
+
|
|
419
|
+
## 11. El Ciclo Sagrado del Entrenamiento (Explicacion Paso a Paso)
|
|
420
|
+
|
|
421
|
+
El entrenamiento de cualquier modelo en GRX sigue un ciclo de 5 pasos universales:
|
|
422
|
+
|
|
423
|
+
```ruby
|
|
424
|
+
# 1. Limpiar gradientes anteriores
|
|
425
|
+
# En cada paso, el optimizador calcula la direccion del cambio.
|
|
426
|
+
# Si no limpias los gradientes, se acumularan como una bola de nieve.
|
|
427
|
+
opt.zero_grad
|
|
428
|
+
|
|
429
|
+
# 2. Pase hacia adelante (Forward Pass)
|
|
430
|
+
# Los datos entran a la red y se procesan a traves de capas de matrices en C con SIMD.
|
|
431
|
+
pred = modelo.call(train_x)
|
|
432
|
+
|
|
433
|
+
# 3. Calculo de la Perdida (Loss / Error)
|
|
434
|
+
# La funcion de perdida mide numericamente que tan lejos estamos de la realidad.
|
|
435
|
+
loss = loss_fn.call(pred, train_y)
|
|
436
|
+
|
|
437
|
+
# 4. Pase hacia atras (Backward Pass / Autograd)
|
|
438
|
+
# GRX recorre el grafo DAG hacia atras calculando la derivada exacta de cada peso.
|
|
439
|
+
loss.backward
|
|
440
|
+
|
|
441
|
+
# 5. Paso de Optimizacion (Optimizer Step)
|
|
442
|
+
# El optimizador (Adam o SGD) actualiza los pesos en C para reducir el error.
|
|
443
|
+
opt.step
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
---
|
|
447
|
+
|
|
448
|
+
## 12. El Diccionario Sagrado de Parametros e Hiperparametros
|
|
449
|
+
|
|
450
|
+
Cuando creas optimizadores, capas o funciones de perdida, veras expresiones como `lr: 0.001`, `lr: 1e-3`, `momentum: 0.9`, `weight_decay: 1e-4`, `eps: 1e-8` o `betas: [0.9, 0.999]`. Que significan, que valores admiten y como se deben configurar?
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
### 1. Que significa la Notacion Cientifica en Machine Learning (`1e-1` a `1e-8`)?
|
|
455
|
+
|
|
456
|
+
En Ruby y en Inteligencia Artificial, los numeros muy pequenos se escriben en **notacion cientifica exponencial** (`1e-X` que equivale a $1.0 \times 10^{-X}$). Esto evita errores tipograficos al contar ceros decimales:
|
|
457
|
+
|
|
458
|
+
| Notacion Ruby | Valor Decimal Exacto | Fraccion | Nombre Comun | Uso Tipico en GRTensor |
|
|
459
|
+
|---|---|---|---|---|
|
|
460
|
+
| `1e-1` | `0.1` | $1/10$ | Una decima | Momentum de actualizacion en `BatchNorm1d`, learning rate rapido. |
|
|
461
|
+
| `1e-2` | `0.01` | $1/100$ | Una centesima | Learning rate estandar para `SGD`, pendiente `alpha` en `LeakyReLU`. |
|
|
462
|
+
| `1e-3` | `0.001` | $1/1,000$ | Una milesima | Learning rate estandar de oro para `Adam` en redes profundas. |
|
|
463
|
+
| `1e-4` | `0.0001` | $1/10,000$ | Una diezmilesima | Penalizacion de regularizacion L2 (`weight_decay`) para evitar sobreajuste. |
|
|
464
|
+
| `1e-5` | `0.00001` | $1/100,000$ | Una cienmilesima | Epsilon (`eps`) de varianza en `LayerNorm` y `BatchNorm1d`. |
|
|
465
|
+
| `1e-7` | `0.0000001` | $1/10,000,000$ | Una diezmillonesima | Epsilon de corte en `BCELoss` para evitar $\log(0) \to -\infty$. |
|
|
466
|
+
| `1e-8` | `0.00000001` | $1/100,000,000$ | Una cienmillonesima | Epsilon de estabilidad numerica en el denominador de `Adam`. |
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
### 2. Parametros de Optimizadores (`GRX::Optim`)
|
|
471
|
+
|
|
472
|
+
#### `lr` (Learning Rate / Tasa de Aprendizaje)
|
|
473
|
+
* **Que es:** La velocidad y tamano del paso que da la red hacia el error minimo en cada iteracion.
|
|
474
|
+
* **Analogia:** Caminar hacia el fondo de un valle con los ojos vendados.
|
|
475
|
+
* Si `lr` es muy grande (`lr: 10.0`), saltaras de una colina a otra sin tocar el fondo y la perdida explotara (`Infinity` o `NaN`).
|
|
476
|
+
* Si `lr` es muy pequeno (`lr: 1e-6`), tardaras semanas en dar 3 pasos.
|
|
477
|
+
* **Valores recomendados:**
|
|
478
|
+
* `0.001` o `1e-3` (Valor por defecto de oro para `Adam` en casi todas las redes neuronales).
|
|
479
|
+
* `0.01` a `0.1` (`1e-2` a `1e-1`) (Para `SGD` con momentum o regresiones lineales rapidas).
|
|
480
|
+
* `0.5` a `0.8` (Para modelos de 1 sola neurona como el conversor de temperatura).
|
|
481
|
+
|
|
482
|
+
#### `momentum` (Momento / Inercia en SGD)
|
|
483
|
+
* **Que es:** Acumula la velocidad y direccion de los gradientes anteriores para acelerar el descenso.
|
|
484
|
+
* **Analogia:** Una bola pesada de boliche rodando colina abajo. En lugar de detenerse o rebotar caoticamente por cada bache diminuto en los datos, la bola mantiene su inercia hacia adelante.
|
|
485
|
+
* **Valores:** `0.0` (sin momento) a `0.99`. Valor recomendado estandar: `0.9`.
|
|
486
|
+
|
|
487
|
+
#### `weight_decay` (Decaimiento de Pesos / Regularizacion L2)
|
|
488
|
+
* **Que es:** Una penalizacion matematica que encoge ligeramente los pesos hacia cero en cada paso ($w \leftarrow w - \lambda \cdot w$).
|
|
489
|
+
* **Analogia:** La Navaja de Ockham. Evita que la red memorice las respuestas exactas (*Overfitting* o Sobreajuste) forzandola a preferir explicaciones simples y pesos pequenos y equilibrados.
|
|
490
|
+
* **Valores recomendados:** `0.0` (por defecto, sin penalizacion), `1e-4` ($0.0001$) o `1e-5` para datasets del mundo real.
|
|
491
|
+
|
|
492
|
+
#### `betas` / `beta1`, `beta2` (En `GRX::Optim::Adam`)
|
|
493
|
+
* **Que es:** Las tasas de decaimiento exponencial para la estimacion de momentos de 1er y 2do orden.
|
|
494
|
+
* $\beta_1$ (defecto: `0.9`): Memoria del 90% de la direccion del gradiente anterior (momento direccional).
|
|
495
|
+
* $\beta_2$ (defecto: `0.999`): Memoria del 99.9% de la varianza del gradiente al cuadrado (escala automaticamente pasos grandes para gradientes raros y pasos pequenos para gradientes frecuentes).
|
|
496
|
+
* **Valores recomendados:** `[0.9, 0.999]` (el estandar probado de Kingma & Ba).
|
|
497
|
+
|
|
498
|
+
#### `eps` / `epsilon` (Estabilidad Numerica)
|
|
499
|
+
* **Que es:** Un numero microscopico ($1e-8$ en Adam, $1e-5$ en LayerNorm/BatchNorm) sumado al denominador.
|
|
500
|
+
* **Por que existe:** Si la varianza de un parametro es 0, dividir entre 0 causaria un fallo critico de hardware (`NaN`). El epsilon actua como cinturon de seguridad matematico.
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
### 3. Parametros de Capas Neuronales (`GRX::NN`)
|
|
505
|
+
|
|
506
|
+
| Capa / Modulo | Parametro | Tipo | Por Defecto | Rango Valido | Explicacion Didactica |
|
|
507
|
+
|---|---|---|---|---|---|
|
|
508
|
+
| `GRX::NN::Linear` | `in_features` | Integer | (Requerido) | $\ge 1$ | Cantidad de numeros de entrada que recibe la capa. |
|
|
509
|
+
| `GRX::NN::Linear` | `out_features` | Integer | (Requerido) | $\ge 1$ | Cantidad de neuronas o caracteristicas que produce de salida. |
|
|
510
|
+
| `GRX::NN::Linear` | `bias` | Boolean | `true` | `true` / `false` | Si es `true`, agrega el termino de sesgo $b$ ($y = Wx + b$). |
|
|
511
|
+
| `GRX::NN::Embedding`| `num_embeddings` | Integer | (Requerido) | $\ge 1$ | Tamano del vocabulario o numero total de entidades unicas. |
|
|
512
|
+
| `GRX::NN::Embedding`| `embedding_dim` | Integer | (Requerido) | $\ge 1$ | Dimension del vector denso continuo para representar cada palabra. |
|
|
513
|
+
| `GRX::NN::Dropout` | `p` | Float | `0.5` | `0.0` a `0.99` | Probabilidad de desactivar aleatoriamente una neurona durante el entrenamiento (0.2 = 20%, 0.5 = 50%) para evitar que la red se vuelva dependiente de una sola neurona. |
|
|
514
|
+
| `GRX::NN::LeakyReLU`| `alpha` | Float | `0.01` | `0.001` a `0.3` | Pendiente para numeros negativos. Evita que las neuronas "mueran" permitiendo pasar un 1% de gradiente cuando $x < 0$. |
|
|
515
|
+
| `GRX::NN::LayerNorm`| `normalized_shape`| Integer/Array | (Requerido) | Dimensiones | Dimension sobre la que se calcula la media y varianza unitaria. |
|
|
516
|
+
| `GRX::NN::LayerNorm`| `eps` / `epsilon` | Float | `1e-5` | `1e-8` a `1e-4` | Termino sumado a la varianza para evitar division entre 0. |
|
|
517
|
+
| `GRX::NN::BatchNorm1d`| `num_features` | Integer | (Requerido) | $\ge 1$ | Cantidad de canales a normalizar a lo largo del lote (*batch*). |
|
|
518
|
+
| `GRX::NN::BatchNorm1d`| `eps` / `epsilon` | Float | `1e-5` | `1e-8` a `1e-4` | Termino de estabilidad sumado a la varianza por lote. |
|
|
519
|
+
| `GRX::NN::BatchNorm1d`| `momentum` | Float | `0.1` | `0.01` a `0.5` | Factor de actualizacion de medias y varianzas moviles para inferencia. |
|
|
520
|
+
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
### 4. Parametros de Funciones de Perdida (`GRX::Loss`)
|
|
524
|
+
|
|
525
|
+
| Funcion de Perdida | Parametro | Opciones / Tipo | Por Defecto | Explicacion |
|
|
526
|
+
|---|---|---|---|---|
|
|
527
|
+
| `MSELoss` / `MAELoss` | `reduction` | Symbol | `:mean` | `:mean` promedia el error entre todas las muestras del lote. `:sum` suma todos los errores directos. |
|
|
528
|
+
| `BCELoss` | `reduction` | Symbol | `:mean` | `:mean` o `:sum`. |
|
|
529
|
+
| `BCELoss` | `eps` | Float | `1e-7` | Limite de seguridad para evitar $\log(0) \to -\infty$ en probabilidades extremas (0.0 o 1.0). |
|
|
530
|
+
| `CrossEntropyLoss` | `reduction` | Symbol | `:mean` | `:mean` o `:sum`. Aplica Softmax con Log-Sum-Exp. |
|
|
531
|
+
| `HuberLoss` | `delta` | Float | `1.0` | Umbral de transicion: si el error es menor a $\delta$ se calcula como cuadratico (MSE); si es mayor, se calcula lineal (MAE) para no volverse loco con datos atipicos (*outliers*). |
|
|
532
|
+
| `HuberLoss` | `reduction` | Symbol | `:mean` | `:mean` o `:sum`. |
|
|
533
|
+
|
|
534
|
+
---
|
|
535
|
+
|
|
536
|
+
### 5. Parametros de Creacion y Manipulacion de Tensores (`GRX::Tensor`)
|
|
537
|
+
|
|
538
|
+
| Metodo / Factory | Parametro | Tipo | Por Defecto | Descripcion |
|
|
539
|
+
|---|---|---|---|---|
|
|
540
|
+
| `GRX.tensor` | `data` | Array / Storage | (Requerido) | Datos numericos en arreglo plano (`[1.0, 2.0]`) o anidado (`[[1, 2], [3, 4]]`). |
|
|
541
|
+
| `GRX.tensor` | `shape` | Array[Integer] | `nil` (Auto) | Dimensiones del tensor (ej. `[2, 3]` para 2 filas y 3 columnas). |
|
|
542
|
+
| `GRX.tensor` | `requires_grad`| Boolean | `false` | Activa el rastreo en el grafo DAG para calcular gradientes con `backward`. |
|
|
543
|
+
| `GRX.zeros` / `GRX.ones` | `shape` | Array[Integer] | (Requerido) | Dimensiones del tensor a inicializar con ceros o unos. |
|
|
544
|
+
| `GRX.rand` | `shape` | Array[Integer] | (Requerido) | Genera tensor con distribucion uniforme $U[0, 1)$. |
|
|
545
|
+
| `GRX.randn` | `shape` | Array[Integer] | (Requerido) | Genera tensor con distribucion normal estandar $N(0, 1)$ mediante Box-Muller. |
|
|
546
|
+
| `Tensor.xavier_uniform`| `shape` | Array[Integer] | (Requerido) | Inicializador Xavier/Glorot ($U[-\sqrt{6/(f_{in}+f_{out})}, \sqrt{6/(f_{in}+f_{out})}]$). |
|
|
547
|
+
| `Tensor.he_normal` | `shape` | Array[Integer] | (Requerido) | Inicializador He/Kaiming ($N(0, \sqrt{2/f_{in}})$), optimo para capas con ReLU. |
|
|
548
|
+
| `Tensor.zeros_like` | `other` | Tensor | (Requerido) | Crea un tensor de ceros con la misma forma que `other`. |
|
|
549
|
+
| `Tensor.ones_like` | `other` | Tensor | (Requerido) | Crea un tensor de unos con la misma forma que `other`. |
|
|
550
|
+
| `tensor.clip` | `lo`, `hi` | Numeric | (Requeridos) | Limites inferior y superior. Fija valores fuera del intervalo $[lo, hi]$. |
|
|
551
|
+
| `tensor.pow` | `exponent` | Numeric | (Requerido) | Exponente al que se eleva cada elemento ($x^e$). Totalmente diferenciable. |
|
|
552
|
+
| `tensor.reshape` | `new_shape` | Array[Integer] | (Requerido) | Nueva forma manteniendo el mismo `numel` total. Vista zero-copy. |
|
|
553
|
+
| `tensor.transpose` | (sin args) | - | - | Intercambia filas y columnas en tensores 2D. Vista zero-copy por strides. |
|
|
554
|
+
| `tensor.flatten` | (sin args) | - | - | Aplana el tensor a 1 dimension `[numel]`. Vista zero-copy. |
|
|
555
|
+
| `tensor.contiguous`| (sin args) | - | - | Re-empaca la memoria no contigua (tras un transpose) en un buffer contiguo nuevo. |
|
|
556
|
+
| `tensor.get` | `*indices` | Integers | (Requerido) | Coordenadas del elemento a leer (ej. `t.get(0, 2)`). |
|
|
557
|
+
| `tensor.set` | `*indices, val`| Integers, Float | (Requeridos) | Modifica directamente el valor en la posicion especificada. |
|
|
558
|
+
| `tensor.item` | (sin args) | - | - | Extrae el valor numerico Float de un tensor escalar de 1 solo elemento. |
|
|
559
|
+
| `tensor.argmax` | (sin args) | - | - | Retorna el indice del elemento con el valor maximo. |
|
|
560
|
+
| `tensor.argmin` | (sin args) | - | - | Retorna el indice del elemento con el valor minimo. |
|
|
561
|
+
| `tensor.backward` | `gradient` | Tensor | `nil` | Ejecuta la retropropagacion inversa a lo largo del grafo computacional DAG. |
|
|
562
|
+
|
|
563
|
+
---
|
|
564
|
+
|
|
565
|
+
### 6. Parametros de Carga de Datos, Persistencia y Utilidades (`GRX::Data`, `GRX::Serialization`, `GRX::Utils`)
|
|
566
|
+
|
|
567
|
+
* `TensorDataset.new(*tensors)`:
|
|
568
|
+
* `*tensors` (Tensors requeridos): Tensores paralelos (ej. caracteristicas $X$ y etiquetas $Y$) que comparten la dimension 0 de lote.
|
|
569
|
+
* `DataLoader.new(dataset, batch_size: 32, shuffle: true)`:
|
|
570
|
+
* `dataset` (`GRX::Data::Dataset`): Coleccion indexable de datos.
|
|
571
|
+
* `batch_size` (Integer, por defecto: `32`): Cantidad de muestras agrupadas que se procesan simultaneamente en C por iteracion antes de actualizar pesos.
|
|
572
|
+
* `shuffle` (Boolean, por defecto: `true`): Si es `true`, desordena aleatoriamente los indices en cada epoca para que la red no aprenda el orden de los datos.
|
|
573
|
+
* `GRX::Serialization.save(model, path)` / `model.save_weights(path)`:
|
|
574
|
+
* `model` (`GRX::NN::Module`): Instancia del modelo a persistir.
|
|
575
|
+
* `path` (String): Ruta del archivo destino `.grx`. Vuelca directamente los doubles IEEE 754 de 64 bits de la memoria C sin sobrecosto de JSON/YAML.
|
|
576
|
+
* `GRX::Serialization.load(model, path)` / `model.load_weights(path)`:
|
|
577
|
+
* `model` (`GRX::NN::Module`): Modelo con la misma arquitectura en memoria.
|
|
578
|
+
* `path` (String): Ruta del archivo `.grx` binario a cargar.
|
|
579
|
+
* `model.train!` y `model.eval!`:
|
|
580
|
+
* `train!`: Pone el modelo en modo de entrenamiento (activa `Dropout` y calcula medias dinamicas en `BatchNorm1d`).
|
|
581
|
+
* `eval!`: Pone el modelo en modo de evaluacion/inferencia (desactiva `Dropout` y usa medias moviles fijas en `BatchNorm1d`).
|
|
582
|
+
* `GRX::Utils.clip_grad_norm!(params, max_norm: 1.0)`:
|
|
583
|
+
* `params` (Array[Tensor]): Coleccion de parametros con gradientes acumulados.
|
|
584
|
+
* `max_norm` (Float, por defecto: `1.0`): Norma L2 maxima permitida. Si la norma combinada supera `max_norm`, los gradientes se reescalan proporcionalmente para evitar explosiones.
|
|
585
|
+
* `GRX::Utils.one_hot(indices, num_classes: nil, requires_grad: false)`:
|
|
586
|
+
* `indices` (Array[Integer] o Tensor): Vector con identificadores enteros de clase (ej. `[0, 2, 1]`).
|
|
587
|
+
* `num_classes` (Integer, opcional): Numero total de columnas de clase (si no se indica, se autocalcula como `max + 1`).
|
|
588
|
+
* `requires_grad` (Boolean, por defecto: `false`): Si la matriz resultante requiere autograd.
|
|
589
|
+
* `GRX.simd_mode`:
|
|
590
|
+
* Retorna el nivel de aceleracion de hardware activo en la maquina: `:avx2` (4 doubles/ciclo con FMA), `:sse` (2 doubles/ciclo), `:scalar` (C portable) o `:ruby` (fallback).
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
### 7. Jerarquia de Excepciones de GRTensor
|
|
595
|
+
|
|
596
|
+
| Excepcion | Hereda de | Causa Principal |
|
|
597
|
+
|---|---|---|
|
|
598
|
+
| `GRX::Error` | `StandardError` | Clase base para todas las excepciones del framework. |
|
|
599
|
+
| `GRX::ShapeError` | `GRX::Error` | Dimensiones incompatibles en operaciones algebraicas (ej. sumar matrices de distinto tamano o multiplicar dimensiones internas dispares). |
|
|
600
|
+
| `GRX::DimensionError` | `GRX::Error` | Rango de dimensiones invalido (ej. llamar `transpose` o `matmul` sobre tensores de 1 sola dimension). |
|
|
601
|
+
| `GRX::StorageError` | `GRX::Error` | Fallo de memoria nativa C (`malloc` OOM) o error de lectura en archivos binarios `.grx` corruptos. |
|
|
602
|
+
|
|
603
|
+
---
|
|
604
|
+
|
|
605
|
+
## 13. Proyectos Guiados Paso a Paso (Completos y Ejecutables)
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
### Proyecto 1: El Conversor de Temperatura (Celsius a Fahrenheit)
|
|
610
|
+
|
|
611
|
+
Este es el "Hello World" absoluto de la Inteligencia Artificial. La formula fisica real es:
|
|
612
|
+
$$F = 1.8 \times C + 32$$
|
|
613
|
+
|
|
614
|
+
La red neuronal tiene **1 sola neurona** (`Linear(1, 1)`: $y = w \cdot x + b$). La red no conoce la formula, pero tras 1,500 iteraciones con Adam, descubrira por si sola que el peso $w \approx 1.8$ y el sesgo $b \approx 32.0$.
|
|
615
|
+
|
|
616
|
+
```ruby
|
|
617
|
+
require "grx"
|
|
618
|
+
|
|
619
|
+
# 1. Datos de entrenamiento (8 pares de temperaturas reales)
|
|
620
|
+
celsius_datos = [18.0, 25.0, 14.0, 21.0, 9.0, 16.0, 4.0, 32.0]
|
|
621
|
+
fahrenheit_datos = [64.4, 77.0, 57.2, 69.8, 48.2, 60.8, 39.2, 89.6]
|
|
622
|
+
|
|
623
|
+
# Convertir a tensores 2D [8 filas, 1 columna]
|
|
624
|
+
x = GRX.tensor(celsius_datos, [8, 1])
|
|
625
|
+
y = GRX.tensor(fahrenheit_datos, [8, 1])
|
|
626
|
+
|
|
627
|
+
# 2. Definir la arquitectura (1 neurona)
|
|
628
|
+
termometro = GRX::NN::Sequential.new(
|
|
629
|
+
GRX::NN::Linear.new(1, 1)
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
# 3. Optimizador y funcion de error cuadratico medio (MSE)
|
|
633
|
+
opt = GRX::Optim::Adam.new(termometro.parameters, lr: 0.8)
|
|
634
|
+
loss_fn = GRX::Loss::MSELoss.new
|
|
635
|
+
|
|
636
|
+
puts "Entrenando la neurona para aprender la escala Fahrenheit..."
|
|
637
|
+
|
|
638
|
+
# 4. Bucle de entrenamiento
|
|
639
|
+
1500.times do
|
|
640
|
+
opt.zero_grad
|
|
641
|
+
pred = termometro.call(x)
|
|
642
|
+
loss = loss_fn.call(pred, y)
|
|
643
|
+
loss.backward
|
|
644
|
+
opt.step
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
puts "Entrenamiento finalizado!"
|
|
648
|
+
|
|
649
|
+
# 5. Pruebas con temperaturas nunca vistas
|
|
650
|
+
test_celsius = GRX.tensor([[100.0], [0.0], [37.0]], [3, 1])
|
|
651
|
+
predicciones = termometro.call(test_celsius).to_a
|
|
652
|
+
|
|
653
|
+
puts "\n--- Resultados del Termometro Neuronal ---"
|
|
654
|
+
puts "100.0 C -> #{predicciones[0].round(2)} F (Esperado: 212.00 F)"
|
|
655
|
+
puts " 0.0 C -> #{predicciones[1].round(2)} F (Esperado: 32.00 F)"
|
|
656
|
+
puts " 37.0 C -> #{predicciones[2].round(2)} F (Esperado: 98.60 F)"
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
### Proyecto 2: Compuertas Logicas (AND Lineal vs XOR No Lineal)
|
|
662
|
+
|
|
663
|
+
Una compuerta **AND** se puede resolver con 1 sola neurona lineal porque los datos son linealmente separables.
|
|
664
|
+
Sin embargo, la compuerta **XOR** (Or Exclusivo) es el clasico problema no lineal: una sola linea recta no puede separar los ceros de los unos. Por eso agregamos una **capa oculta con activacion ReLU**:
|
|
665
|
+
|
|
666
|
+
```ruby
|
|
667
|
+
require "grx"
|
|
668
|
+
|
|
669
|
+
# Tabla de verdad XOR:
|
|
670
|
+
# (0, 0) -> 0
|
|
671
|
+
# (0, 1) -> 1
|
|
672
|
+
# (1, 0) -> 1
|
|
673
|
+
# (1, 1) -> 0
|
|
674
|
+
entradas_xor = GRX.tensor([[0.0, 0.0], [0.0, 1.0], [1.0, 0.0], [1.0, 1.0]], [4, 2])
|
|
675
|
+
salidas_xor = GRX.tensor([[0.0], [1.0], [1.0], [0.0]], [4, 1])
|
|
676
|
+
|
|
677
|
+
# Red neuronal multicapa: 2 entradas -> 4 ocultas (ReLU) -> 1 salida (Sigmoide)
|
|
678
|
+
red_xor = GRX::NN::Sequential.new(
|
|
679
|
+
GRX::NN::Linear.new(2, 4),
|
|
680
|
+
GRX::NN::ReLU.new,
|
|
681
|
+
GRX::NN::Linear.new(4, 1),
|
|
682
|
+
GRX::NN::Sigmoid.new
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
opt = GRX::Optim::Adam.new(red_xor.parameters, lr: 0.05)
|
|
686
|
+
loss_fn = GRX::Loss::BCELoss.new
|
|
687
|
+
|
|
688
|
+
# Entrenamiento
|
|
689
|
+
400.times do
|
|
690
|
+
opt.zero_grad
|
|
691
|
+
pred = red_xor.call(entradas_xor)
|
|
692
|
+
loss = loss_fn.call(pred, salidas_xor)
|
|
693
|
+
loss.backward
|
|
694
|
+
opt.step
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
puts "\n--- Predicciones de la Compuerta XOR ---"
|
|
698
|
+
entradas_xor.to_a.each_slice(2).each_with_index do |(in1, in2), i|
|
|
699
|
+
muestra = GRX.tensor([[in1, in2]], [1, 2])
|
|
700
|
+
resultado = red_xor.call(muestra).item
|
|
701
|
+
puts "XOR(#{in1.to_i}, #{in2.to_i}) = #{resultado.round(3)} -> #{resultado >= 0.5 ? 1 : 0}"
|
|
702
|
+
end
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
### Proyecto 3: El Predictor de Formulas (Regresion Lineal)
|
|
708
|
+
|
|
709
|
+
Aprende la relacion $y = 3x + 2$ usando `loss.backward` nativo.
|
|
710
|
+
|
|
711
|
+
```ruby
|
|
712
|
+
require "grx"
|
|
713
|
+
|
|
714
|
+
train_x = GRX.tensor([1.0, 2.0, 3.0, 4.0, 5.0], [5, 1])
|
|
715
|
+
train_y = GRX.tensor([5.0, 8.0, 11.0, 14.0, 17.0], [5, 1])
|
|
716
|
+
|
|
717
|
+
modelo = GRX::NN::Sequential.new(
|
|
718
|
+
GRX::NN::Linear.new(1, 1)
|
|
719
|
+
)
|
|
720
|
+
|
|
721
|
+
opt = GRX::Optim::Adam.new(modelo.parameters, lr: 0.1)
|
|
722
|
+
loss_fn = GRX::Loss::MSELoss.new
|
|
723
|
+
|
|
724
|
+
150.times do
|
|
725
|
+
opt.zero_grad
|
|
726
|
+
pred = modelo.call(train_x)
|
|
727
|
+
loss = loss_fn.call(pred, train_y)
|
|
728
|
+
loss.backward
|
|
729
|
+
opt.step
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
test_x = GRX.tensor([6.0], [1, 1])
|
|
733
|
+
puts "Prediccion para x=6 (esperado=20): #{modelo.call(test_x).to_a[0].round(3)}"
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
### Proyecto 4: Ajuste de Curvas No Lineales y Guardado en `.grx`
|
|
739
|
+
|
|
740
|
+
```ruby
|
|
741
|
+
require "grx"
|
|
742
|
+
|
|
743
|
+
train_x = GRX.tensor((1..10).map(&:to_f), [10, 1])
|
|
744
|
+
train_y = GRX.tensor((1..10).map { |x| 2.0 * x + 1.0 }, [10, 1])
|
|
745
|
+
|
|
746
|
+
mlp = GRX::NN::Sequential.new(
|
|
747
|
+
GRX::NN::Linear.new(1, 16),
|
|
748
|
+
GRX::NN::LayerNorm.new(16),
|
|
749
|
+
GRX::NN::Tanh.new,
|
|
750
|
+
GRX::NN::Linear.new(16, 1)
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
opt = GRX::Optim::Adam.new(mlp.parameters, lr: 0.05)
|
|
754
|
+
loss_fn = GRX::Loss::MSELoss.new
|
|
755
|
+
|
|
756
|
+
300.times do
|
|
757
|
+
opt.zero_grad
|
|
758
|
+
pred = mlp.call(train_x)
|
|
759
|
+
loss = loss_fn.call(pred, train_y)
|
|
760
|
+
loss.backward
|
|
761
|
+
GRX::Utils.clip_grad_norm!(mlp.parameters, 1.0)
|
|
762
|
+
opt.step
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
# Guardar pesos a disco
|
|
766
|
+
mlp.save_weights("/tmp/mlp_entrenado.grx")
|
|
767
|
+
|
|
768
|
+
# Cargar en una nueva instancia limpia para inferencia
|
|
769
|
+
modelo_nuevo = GRX::NN::Sequential.new(
|
|
770
|
+
GRX::NN::Linear.new(1, 16),
|
|
771
|
+
GRX::NN::LayerNorm.new(16),
|
|
772
|
+
GRX::NN::Tanh.new,
|
|
773
|
+
GRX::NN::Linear.new(16, 1)
|
|
774
|
+
)
|
|
775
|
+
modelo_nuevo.load_weights("/tmp/mlp_entrenado.grx")
|
|
776
|
+
|
|
777
|
+
puts "Inferencia x=11: #{modelo_nuevo.call(GRX.tensor([11.0], [1, 1])).to_a[0].round(3)} (Esperado: 23.0)"
|
|
778
|
+
```
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
### Proyecto 5: Clasificador Binario Inteligente con BCELoss
|
|
783
|
+
|
|
784
|
+
```ruby
|
|
785
|
+
require "grx"
|
|
786
|
+
|
|
787
|
+
train_x = GRX.tensor([0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0], [4, 2])
|
|
788
|
+
train_y = GRX.tensor([0.0, 1.0, 1.0, 1.0], [4, 1])
|
|
789
|
+
|
|
790
|
+
clasificador = GRX::NN::Sequential.new(
|
|
791
|
+
GRX::NN::Linear.new(2, 4),
|
|
792
|
+
GRX::NN::Tanh.new,
|
|
793
|
+
GRX::NN::Linear.new(4, 1),
|
|
794
|
+
GRX::NN::Sigmoid.new
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
opt = GRX::Optim::Adam.new(clasificador.parameters, lr: 0.08)
|
|
798
|
+
loss_fn = GRX::Loss::BCELoss.new
|
|
799
|
+
|
|
800
|
+
400.times do
|
|
801
|
+
opt.zero_grad
|
|
802
|
+
pred = clasificador.call(train_x)
|
|
803
|
+
loss = loss_fn.call(pred, train_y)
|
|
804
|
+
loss.backward
|
|
805
|
+
opt.step
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
puts "Resultados OR: #{clasificador.call(train_x).to_a.map { |v| v.round(4) }}"
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
---
|
|
812
|
+
|
|
813
|
+
### Proyecto 6: Entrenamiento con Datasets Masivos (5,000 Filas con DataLoader)
|
|
814
|
+
|
|
815
|
+
```ruby
|
|
816
|
+
require "grx"
|
|
817
|
+
|
|
818
|
+
num_samples = 5000
|
|
819
|
+
num_features = 4
|
|
820
|
+
|
|
821
|
+
# y = 2*x1 - 3*x2 + 1.5*x3 - 0.5*x4 + 4.0
|
|
822
|
+
raw_x = Array.new(num_samples * num_features) { rand * 2.0 - 1.0 }
|
|
823
|
+
raw_y = Array.new(num_samples) do |i|
|
|
824
|
+
x1, x2, x3, x4 = raw_x.slice(i * 4, 4)
|
|
825
|
+
2.0 * x1 - 3.0 * x2 + 1.5 * x3 - 0.5 * x4 + 4.0 + (rand - 0.5) * 0.02
|
|
826
|
+
end
|
|
827
|
+
|
|
828
|
+
train_dataset = GRX::Data::TensorDataset.new(
|
|
829
|
+
GRX.tensor(raw_x[0...(4000*4)], [4000, 4]),
|
|
830
|
+
GRX.tensor(raw_y[0...4000], [4000, 1])
|
|
831
|
+
)
|
|
832
|
+
val_x = GRX.tensor(raw_x[(4000*4)..], [1000, 4])
|
|
833
|
+
val_y = GRX.tensor(raw_y[4000..], [1000, 1])
|
|
834
|
+
|
|
835
|
+
train_loader = GRX::Data::DataLoader.new(train_dataset, batch_size: 64, shuffle: true)
|
|
836
|
+
|
|
837
|
+
model = GRX::NN::Sequential.new(
|
|
838
|
+
GRX::NN::Linear.new(4, 16),
|
|
839
|
+
GRX::NN::LayerNorm.new(16),
|
|
840
|
+
GRX::NN::Tanh.new,
|
|
841
|
+
GRX::NN::Linear.new(16, 1)
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
opt = GRX::Optim::Adam.new(model.parameters, lr: 0.03)
|
|
845
|
+
loss_fn = GRX::Loss::MSELoss.new
|
|
846
|
+
|
|
847
|
+
20.times do |epoch|
|
|
848
|
+
train_loader.each do |bx, by|
|
|
849
|
+
opt.zero_grad
|
|
850
|
+
pred = model.call(bx)
|
|
851
|
+
loss = loss_fn.call(pred, by)
|
|
852
|
+
loss.backward
|
|
853
|
+
opt.step
|
|
854
|
+
end
|
|
855
|
+
end
|
|
856
|
+
|
|
857
|
+
val_loss = loss_fn.call(model.call(val_x), val_y).item
|
|
858
|
+
puts "Error de Validacion MSE (5,000 muestras): #{val_loss.round(6)}"
|
|
859
|
+
```
|
|
860
|
+
|
|
861
|
+
---
|
|
862
|
+
|
|
863
|
+
### Proyecto 7: Chatbot Financiero Inteligente con Base de Conocimiento y Filtro de Incertidumbre
|
|
864
|
+
|
|
865
|
+
Este proyecto crea un agente de atencion al cliente que comprende intenciones mediante redes neuronales y recupera respuestas exactas de su memoria. Si una consulta es incomprensible o fuera de tema, lo reconoce honestamente:
|
|
866
|
+
|
|
867
|
+
```ruby
|
|
868
|
+
require "grx"
|
|
869
|
+
|
|
870
|
+
# 1. Base de conocimiento (Memoria del asistente)
|
|
871
|
+
KNOWLEDGE_BASE = {
|
|
872
|
+
"saludo" => [
|
|
873
|
+
"Hola, soy tu asistente financiero. En que puedo ayudarte hoy?",
|
|
874
|
+
"Que tal! Estoy listo para responder tus dudas financieras y de cuenta."
|
|
875
|
+
],
|
|
876
|
+
"consultar_saldo" => [
|
|
877
|
+
"Tu saldo disponible actual es de $14,850.50 MXN en tu cuenta principal.",
|
|
878
|
+
"Actualmente cuentas con $14,850.50 MXN listos para operar."
|
|
879
|
+
],
|
|
880
|
+
"tasa_interes" => [
|
|
881
|
+
"Nuestra tasa de rendimiento anual fija actual es del 11.5% anual.",
|
|
882
|
+
"La tasa de interes en cuentas de inversion es de 11.5% anual con pago mensual."
|
|
883
|
+
],
|
|
884
|
+
"prestamo" => [
|
|
885
|
+
"Puedes solicitar un prestamo de hasta $100,000 MXN directamente desde la app.",
|
|
886
|
+
"Para prestamos personales, ofrecemos plazos de 6 a 36 meses con aprobacion inmediata."
|
|
887
|
+
],
|
|
888
|
+
"despedida" => [
|
|
889
|
+
"Hasta luego! Que tengas un excelente dia.",
|
|
890
|
+
"Nos vemos, vuelve pronto si necesitas mas informacion."
|
|
891
|
+
]
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
# 2. Frases de entrenamiento
|
|
895
|
+
training_phrases = [
|
|
896
|
+
["hola buenos dias", "saludo"],
|
|
897
|
+
["hola que tal", "saludo"],
|
|
898
|
+
["buenas tardes", "saludo"],
|
|
899
|
+
["cuanto dinero tengo en mi cuenta", "consultar_saldo"],
|
|
900
|
+
["cual es mi saldo disponible hoy", "consultar_saldo"],
|
|
901
|
+
["cuanto saldo me queda", "consultar_saldo"],
|
|
902
|
+
["que tasa de interes tienen", "tasa_interes"],
|
|
903
|
+
["cuanto rendimiento me da la inversion", "tasa_interes"],
|
|
904
|
+
["como puedo pedir un prestamo", "prestamo"],
|
|
905
|
+
["necesito un credito personal", "prestamo"],
|
|
906
|
+
["quiero solicitar un prestamo", "prestamo"],
|
|
907
|
+
["adios muchas gracias por todo", "despedida"],
|
|
908
|
+
["hasta luego nos vemos pronto", "despedida"]
|
|
909
|
+
]
|
|
910
|
+
|
|
911
|
+
# 3. Tokenizacion y vocabulario
|
|
912
|
+
intents = KNOWLEDGE_BASE.keys
|
|
913
|
+
intent_to_id = intents.each_with_index.to_h
|
|
914
|
+
id_to_intent = intent_to_id.invert
|
|
915
|
+
num_classes = intents.size
|
|
916
|
+
|
|
917
|
+
vocab = training_phrases.flat_map { |text, _| text.split }.uniq
|
|
918
|
+
token_to_id = vocab.each_with_index.to_h
|
|
919
|
+
vocab_size = vocab.size
|
|
920
|
+
embedding_dim = 16
|
|
921
|
+
seq_len = 6
|
|
922
|
+
|
|
923
|
+
batch_size = training_phrases.size
|
|
924
|
+
x_ids = training_phrases.map do |text, _|
|
|
925
|
+
tokens = text.split.map { |w| token_to_id[w] }
|
|
926
|
+
(tokens + [0] * seq_len).first(seq_len)
|
|
927
|
+
end
|
|
928
|
+
train_x = GRX.tensor(x_ids.flatten.map(&:to_f), [batch_size, seq_len])
|
|
929
|
+
|
|
930
|
+
onehot = Array.new(batch_size * num_classes, 0.0)
|
|
931
|
+
training_phrases.each_with_index { |(_, label), i| onehot[i * num_classes + intent_to_id[label]] = 1.0 }
|
|
932
|
+
train_y = GRX.tensor(onehot, [batch_size, num_classes])
|
|
933
|
+
|
|
934
|
+
# 4. Arquitectura de la Red
|
|
935
|
+
emb = GRX::NN::Embedding.new(vocab_size, embedding_dim)
|
|
936
|
+
ln = GRX::NN::LayerNorm.new(embedding_dim)
|
|
937
|
+
fc1 = GRX::NN::Linear.new(embedding_dim, 16)
|
|
938
|
+
act = GRX::NN::ReLU.new
|
|
939
|
+
fc2 = GRX::NN::Linear.new(16, num_classes)
|
|
940
|
+
|
|
941
|
+
params = emb.parameters + ln.parameters + fc1.parameters + fc2.parameters
|
|
942
|
+
opt = GRX::Optim::Adam.new(params, lr: 0.05)
|
|
943
|
+
loss_fn = GRX::Loss::CrossEntropyLoss.new
|
|
944
|
+
|
|
945
|
+
# 5. Entrenamiento
|
|
946
|
+
150.times do
|
|
947
|
+
opt.zero_grad
|
|
948
|
+
flat = train_x.flatten
|
|
949
|
+
e = emb.call(flat)
|
|
950
|
+
e_data = e.to_a
|
|
951
|
+
pooled_data = Array.new(batch_size * embedding_dim, 0.0)
|
|
952
|
+
batch_size.times do |b|
|
|
953
|
+
embedding_dim.times do |d|
|
|
954
|
+
s = 0.0
|
|
955
|
+
seq_len.times { |t| s += e_data[(b * seq_len + t) * embedding_dim + d] }
|
|
956
|
+
pooled_data[b * embedding_dim + d] = s / seq_len.to_f
|
|
957
|
+
end
|
|
958
|
+
end
|
|
959
|
+
pooled = GRX.tensor(pooled_data, [batch_size, embedding_dim], requires_grad: true)
|
|
960
|
+
logits = fc2.call(act.call(fc1.call(ln.call(pooled))))
|
|
961
|
+
loss = loss_fn.call(logits, train_y)
|
|
962
|
+
loss.backward
|
|
963
|
+
|
|
964
|
+
if pooled.grad
|
|
965
|
+
grad_emb = Array.new(batch_size * seq_len * embedding_dim, 0.0)
|
|
966
|
+
p_grad = pooled.grad.to_a
|
|
967
|
+
batch_size.times do |b|
|
|
968
|
+
embedding_dim.times do |d|
|
|
969
|
+
v = p_grad[b * embedding_dim + d] / seq_len.to_f
|
|
970
|
+
seq_len.times { |t| grad_emb[(b * seq_len + t) * embedding_dim + d] = v }
|
|
971
|
+
end
|
|
972
|
+
end
|
|
973
|
+
e.backward(GRX.tensor(grad_emb, e.shape))
|
|
974
|
+
end
|
|
975
|
+
opt.step
|
|
976
|
+
end
|
|
977
|
+
|
|
978
|
+
# 6. Motor de conversacion con control de confianza
|
|
979
|
+
def chatear(mensaje, emb, ln, fc1, act, fc2, token_to_id, id_to_intent, seq_len, embedding_dim)
|
|
980
|
+
words = mensaje.downcase.gsub(/[^a-z0-9\s]/, "").split
|
|
981
|
+
known_count = words.count { |w| token_to_id.key?(w) }
|
|
982
|
+
coverage = words.empty? ? 0.0 : known_count.to_f / words.size
|
|
983
|
+
|
|
984
|
+
# Si la mayoria de las palabras son desconocidas, rechazar amablemente
|
|
985
|
+
if coverage < 0.40
|
|
986
|
+
return "Bot: Disculpa, no comprendo tu mensaje. Solo puedo responder sobre saldo, prestamos o tasas."
|
|
987
|
+
end
|
|
988
|
+
|
|
989
|
+
tokens = words.map { |w| token_to_id[w] || 0 }
|
|
990
|
+
padded = (tokens + [0] * seq_len).first(seq_len)
|
|
991
|
+
t_in = GRX.tensor(padded.map(&:to_f), [seq_len])
|
|
992
|
+
e = emb.call(t_in)
|
|
993
|
+
e_data = e.to_a
|
|
994
|
+
|
|
995
|
+
m_data = Array.new(embedding_dim) do |d|
|
|
996
|
+
seq_len.times.sum { |t| e_data[t * embedding_dim + d] } / seq_len.to_f
|
|
997
|
+
end
|
|
998
|
+
p_t = GRX.tensor(m_data, [1, embedding_dim])
|
|
999
|
+
logits = fc2.call(act.call(fc1.call(ln.call(p_t))))
|
|
1000
|
+
probs = logits.softmax.to_a
|
|
1001
|
+
|
|
1002
|
+
best_prob, best_idx = probs.each_with_index.max_by { |p, idx| p }
|
|
1003
|
+
|
|
1004
|
+
if best_prob < 0.70
|
|
1005
|
+
"Bot: No estoy seguro de haberte entendido (#{(best_prob * 100).round(1)}% certeza). Podrias reformular?"
|
|
1006
|
+
else
|
|
1007
|
+
intent = id_to_intent[best_idx]
|
|
1008
|
+
respuesta = KNOWLEDGE_BASE[intent].sample
|
|
1009
|
+
"Bot: #{respuesta} (Certeza: #{(best_prob * 100).round(1)}%)"
|
|
1010
|
+
end
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
# 7. Prueba interactiva
|
|
1014
|
+
puts "\n--- Simulacion de Chat ---"
|
|
1015
|
+
[
|
|
1016
|
+
"Hola muy buenos dias",
|
|
1017
|
+
"Oye cuanto dinero tengo en mi cuenta",
|
|
1018
|
+
"Que tasa de interes tienen actualmente",
|
|
1019
|
+
"Quiero pedir un prestamo personal",
|
|
1020
|
+
"Como puedo cocinar una hamburguesa", # Fuera de tema
|
|
1021
|
+
"Adios muchas gracias"
|
|
1022
|
+
].each do |msg|
|
|
1023
|
+
puts "\nUsuario: #{msg}"
|
|
1024
|
+
puts chatear(msg, emb, ln, fc1, act, fc2, token_to_id, id_to_intent, seq_len, embedding_dim)
|
|
1025
|
+
end
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
---
|
|
1029
|
+
|
|
1030
|
+
## 14. Buenas Practicas y Errores Comunes
|
|
1031
|
+
|
|
1032
|
+
1. **Llamar `opt.zero_grad` en cada iteracion:** Evita que los gradientes se acumulen indefinidamente.
|
|
1033
|
+
2. **Dimensiones compatibles:** Las capas lineales esperan siempre tensores 2D `[batch_size, num_features]`.
|
|
1034
|
+
3. **Manejar `train!` y `eval!`:** Modos necesarios para capas como `Dropout` y `BatchNorm1d` durante inferencia.
|
|
1035
|
+
|
|
1036
|
+
---
|
|
1037
|
+
|
|
1038
|
+
## 15. Glosario de Terminos Clave
|
|
1039
|
+
|
|
1040
|
+
- **Shape:** Dimensiones del tensor (ej. `[10, 4]` -> 10 filas, 4 columnas).
|
|
1041
|
+
- **Numel:** Total de elementos del tensor.
|
|
1042
|
+
- **Strides:** Saltos de memoria plana para avanzar en cada eje.
|
|
1043
|
+
- **Tokenizacion:** Division y conversion de texto a identificadores enteros.
|
|
1044
|
+
- **Embedding:** Capa que transforma tokens en vectores densos continuos.
|
|
1045
|
+
- **CrossEntropyLoss:** Perdida multiclase para clasificar texto e intenciones.
|
|
1046
|
+
- **FMA:** Fused Multiply-Add en CPU que calcula $a \times b + c$ en 1 solo ciclo con minima desviacion.
|