grx-tensor 0.1.0 → 0.2.0

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.
@@ -0,0 +1,763 @@
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 (Con loss.backward directo)](#11-el-ciclo-sagrado-del-entrenamiento-con-lossbackward-directo)
32
+ 12. [Proyectos Guiados Paso a Paso (Completos y Ejecutables)](#12-proyectos-guiados-paso-a-paso-completos-y-ejecutables)
33
+ - [Proyecto 1: El Predictor de Formulas (Regresion Lineal)](#proyecto-1-el-predictor-de-formulas-regresion-lineal)
34
+ - [Proyecto 2: Ajuste de Curvas No Lineales y Guardado en .grx](#proyecto-2-ajuste-de-curvas-no-lineales-y-guardado-en-grx)
35
+ - [Proyecto 3: Clasificador Binario Inteligente con BCELoss](#proyecto-3-clasificador-binario-inteligente-con-bceloss)
36
+ - [Proyecto 4: Entrenamiento con Datasets Masivos (5,000 Filas con DataLoader)](#proyecto-4-entrenamiento-con-datasets-masivos-5000-filas-con-dataloader)
37
+ - [Proyecto 5: Chatbot Financiero Inteligente con Base de Conocimiento y Filtro de Incertidumbre](#proyecto-5-chatbot-financiero-inteligente-con-base-de-conocimiento-y-filtro-de-incertidumbre)
38
+ 13. [Buenas Practicas y Errores Comunes](#13-buenas-practicas-y-errores-comunes)
39
+ 14. [Glosario de Terminos Clave](#14-glosario-de-terminos-clave)
40
+
41
+ ---
42
+
43
+ ## 1. Bienvenida y Filosofia del Framework
44
+
45
+ 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.
46
+
47
+ Por otro lado, C ofrece velocidad extrema, pero programar grafos de autograd y redes neuronales en C directamente es propenso a errores de memoria.
48
+
49
+ **GRX-Tensor une lo mejor de ambos mundos:**
50
+ - **Ruby habla:** Escribes codigo fluido, declarativo y limpio para definir arquitecturas, procesar datos y crear aplicaciones.
51
+ - **C calcula:** Cada operacion matematica, producto matricial, retropropagacion y actualizacion de pesos se ejecuta en un buffer nativo de C con aceleracion vectorial **SIMD (AVX2 + FMA)**, procesando 4 numeros de doble precision por ciclo de CPU.
52
+
53
+ ---
54
+
55
+ ## 2. Fundamentos: Que es un Shape, Numel, Strides y Rango?
56
+
57
+ ### De Escalar a Tensor Multidimensional
58
+
59
+ Matematicamente, un tensor es una estructura de datos que generaliza escalares, vectores y matrices a cualquier numero de dimensiones:
60
+
61
+ ```
62
+ Rango 0 (Escalar):
63
+ Un solo valor numerico.
64
+ Shape: [1]
65
+ Ejemplo: 42.0
66
+
67
+ Rango 1 (Vector):
68
+ Una secuencia unidimensional de longitud N.
69
+ Shape: [3]
70
+ Visual: [ 1.0, 2.0, 3.0 ]
71
+
72
+ Rango 2 (Matriz):
73
+ Una tabla bidimensional de Filas x Columnas.
74
+ Shape: [2, 3] (2 filas, 3 columnas)
75
+ Visual:
76
+ ┌ ┐
77
+ │ 1.0 2.0 3.0 │ <- Fila 0
78
+ │ 4.0 5.0 6.0 │ <- Fila 1
79
+ └ ┘
80
+
81
+ Rango 3 (Tensor 3D):
82
+ Un bloque tridimensional (Profundidad x Filas x Columnas).
83
+ Shape: [2, 2, 3] (2 matrices de 2x3)
84
+
85
+ Rango 4 (Tensor 4D):
86
+ Comun en procesamiento de imagenes y batches: [Batch, Canales, Alto, Ancho].
87
+ Shape: [64, 3, 224, 224] (64 imagenes RGB de 224x224 pixeles)
88
+ ```
89
+
90
+ ---
91
+
92
+ ### Conceptos Clave
93
+
94
+ #### 1. Shape (Forma dimensional)
95
+ Es el arreglo de enteros que describe el tamano de cada dimension del tensor.
96
+ - Ejemplo: `[5000, 4]` representa una tabla de 5,000 filas y 4 columnas de caracteristicas.
97
+
98
+ #### 2. Numel (Numero total de elementos)
99
+ Es la cantidad total de valores escalares que contiene el tensor:
100
+
101
+ $$\text{numel} = \prod_{i=0}^{d-1} \text{shape}[i]$$
102
+
103
+ En Ruby:
104
+ ```ruby
105
+ t = GRX.tensor([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
106
+ puts t.numel # => 6 (porque 2 * 3 = 6)
107
+ ```
108
+
109
+ #### 3. Strides (Saltos de memoria)
110
+ Indica cuantos elementos de la memoria plana contigua se deben saltar para avanzar una posicion en cada eje:
111
+ - Para `shape = [2, 3]`, los strides contiguos son `[3, 1]`:
112
+ - Para avanzar 1 columna a la derecha: saltas **1** posicion en memoria.
113
+ - Para avanzar 1 fila hacia abajo: saltas **3** posiciones en memoria.
114
+
115
+ ---
116
+
117
+ ### Como se guardan los datos en memoria real (C vs Ruby)
118
+
119
+ En la memoria fisica (RAM), los datos existen en una sola linea recta de bytes contiguos:
120
+
121
+ ```
122
+ Buffer plano en memoria C:
123
+ Posicion C: [0] [1] [2] [3] [4] [5]
124
+ Valor: 1.0 2.0 3.0 4.0 5.0 6.0
125
+
126
+ Interpretacion logica 2D (Shape [2, 3]):
127
+ Fila 0: 1.0 2.0 3.0
128
+ Fila 1: 4.0 5.0 6.0
129
+ ```
130
+
131
+ ### La formula matematica del indice plano
132
+
133
+ $$\text{Indice Plano} = \text{offset} + (\text{fila} \times \text{stride}_0) + (\text{columna} \times \text{stride}_1)$$
134
+
135
+ Para acceder a `(fila: 1, columna: 1)` con `strides = [3, 1]`:
136
+ $$\text{Indice Plano} = 0 + (1 \times 3) + (1 \times 1) = 4 \implies \text{Valor } 5.0$$
137
+
138
+ ---
139
+
140
+ ## 3. Margen de Error y Precision Numerica en GRX
141
+
142
+ ### Doble precision IEEE 754 (Float 64-bit)
143
+
144
+ A diferencia de librerias que usan 32 bits (`float32`), GRX utiliza **doble precision de 64 bits (`double`)** en todo su nucleo de C:
145
+
146
+ | Formato | Bits Totales | Bits de Mantisa | Digitos Decimales Significativos | Margen de Error por Redondeo |
147
+ |---|---|---|---|---|
148
+ | `float32` | 32 bits | 24 bits | ~7 digitos | $\approx 1 \times 10^{-7}$ |
149
+ | `double` (GRX) | 64 bits | 53 bits | **~15 a 17 digitos** | $\mathbf{\approx 2 \times 10^{-16}}$ |
150
+
151
+ ### FMA (Fused Multiply-Add)
152
+ En el producto punto (`dot`), multiplicacion de matrices (`matmul`) y optimizador `Adam`, se calcula $a \times b + c$:
153
+ - **Sin FMA:** Se multiplica $a \times b$, se redondea el resultado, se suma $c$ y se vuelve a redondear.
154
+ - **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.
155
+
156
+ ---
157
+
158
+ ## 4. Procesamiento de Lenguaje Natural (NLP): Tokenizacion y Chatbots
159
+
160
+ 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:
161
+
162
+ ### Que es la Tokenizacion y como se hace?
163
+
164
+ La tokenizacion es el proceso de convertir un texto en una secuencia ordenada de numeros enteros (*tokens*):
165
+
166
+ ```
167
+ Frase del usuario:
168
+ "cual es mi saldo disponible hoy"
169
+
170
+
171
+ 1. Limpieza y separacion en palabras (split):
172
+ ["cual", "es", "mi", "saldo", "disponible", "hoy"]
173
+
174
+
175
+ 2. Diccionario de Vocabulario (mapeo de palabra a ID entero):
176
+ { "cual" => 1, "es" => 2, "mi" => 3, "saldo" => 4, "disponible" => 5, "hoy" => 6 }
177
+
178
+
179
+ 3. Arreglo de IDs de entrada:
180
+ [1, 2, 3, 4, 5, 6]
181
+ ```
182
+
183
+ En Ruby:
184
+ ```ruby
185
+ vocab = ["cual", "es", "mi", "saldo", "disponible", "hoy"]
186
+ token_to_id = vocab.each_with_index.to_h
187
+
188
+ mensaje = "mi saldo disponible"
189
+ ids = mensaje.split.map { |w| token_to_id[w] || 0 }
190
+ # => [3, 4, 5]
191
+ ```
192
+
193
+ ---
194
+
195
+ ### De Tokens a Vectores Semanticos (`Embedding`)
196
+
197
+ 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):
198
+
199
+ ```
200
+ Token 4 ("saldo") ───> [ 0.42, -0.18, 0.95, ..., -0.04 ] (16 floats)
201
+ Token 5 ("dinero") ──> [ 0.40, -0.15, 0.91, ..., -0.02 ] (Muy similar!)
202
+ ```
203
+
204
+ Durante el entrenamiento, palabras con significados similares terminan con vectores muy cercanos en el espacio vectorial.
205
+
206
+ ---
207
+
208
+ ### Como crear un modelo que hable contigo y use su memoria?
209
+
210
+ Para crear un agente conversacional que entienda lo que le pides y recupere informacion de su base de conocimiento:
211
+
212
+ 1. **Base de Conocimiento (Memoria de Hechos):** Se define un diccionario con las respuestas correctas para cada intencion de negocio (saldo, tasas, prestamos, horarios).
213
+ 2. **Clasificador de Intenciones Neuronal:** La red procesa la frase mediante `Embedding -> LayerNorm -> Linear -> ReLU -> Linear -> Softmax` y calcula probabilidades para cada intencion.
214
+ 3. **Recuperacion de Memoria:** Al identificar la intencion con mayor probabilidad, el bot extrae la respuesta precisa de su memoria.
215
+
216
+ ---
217
+
218
+ ### Control de Confianza: Que pasa cuando el bot no entiende?
219
+
220
+ Si un usuario escribe algo fuera de dominio (ej. *"como hago una pizza"*):
221
+ - **Verificacion de Cobertura de Vocabulario:** Si la mayoria de las palabras son desconocidas para el modelo ($< 50\%$ de cobertura), se rechaza inmediatamente.
222
+ - **Filtro de Probabilidad Softmax (*Confidence Threshold*):** Si la certeza calculada por la red es menor al $70\%$, el bot responde de forma segura:
223
+ > *"Disculpa, no comprendo tu mensaje con suficiente certeza. Solo puedo responder sobre saldo, prestamos o tasas de interes."*
224
+
225
+ ---
226
+
227
+ ## 5. Como Usar Modelos Entrenados en Produccion (Sin Reentrenar)
228
+
229
+ 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?*
230
+
231
+ ### El Flujo de Vida: Entrenamiento vs Inferencia
232
+
233
+ ```
234
+ ┌─────────────────────────────────────────────────────────────┐
235
+ │ FASE 1: ENTRENAMIENTO (Se hace 1 sola vez) │
236
+ │ 1. Lees tu dataset historico (5,000+ filas). │
237
+ │ 2. Entrenas la red con DataLoader y Adam. │
238
+ │ 3. Guardas los pesos: modelo.save_weights("finanzas.grx") │
239
+ │ 4. Guardas el vocabulario / normalizacion en un JSON. │
240
+ └──────────────────────────────┬──────────────────────────────┘
241
+
242
+ Genera el archivo "finanzas.grx"
243
+
244
+
245
+ ┌─────────────────────────────────────────────────────────────┐
246
+ │ FASE 2: PRODUCCION / INFERENCIA (En tu API) │
247
+ │ 1. Al arrancar el servidor web (Rails/Sinatra): │
248
+ │ - Instancias la arquitectura del modelo. │
249
+ │ - Cargas los pesos: modelo.load_weights("finanzas.grx") │
250
+ │ - Pones la red en modo inferencia: modelo.eval! │
251
+ │ 2. Al recibir una peticion HTTP POST: │
252
+ │ - Conviertes el JSON de entrada a un Tensor. │
253
+ │ - Ejecutas pred = modelo.call(tensor_entrada). │
254
+ │ - Retornas la respuesta en < 0.1 milisegundos! │
255
+ └─────────────────────────────────────────────────────────────┘
256
+ ```
257
+
258
+ ### Ejemplo de API Web con Sinatra / Rails en Finanzas
259
+
260
+ ```ruby
261
+ # app_api.rb — Microservicio de evaluacion de credito en tiempo real
262
+ require "grx"
263
+ require "json"
264
+
265
+ # 1. Cargar el modelo en memoria al iniciar la aplicacion (Toma < 1 ms)
266
+ MODELO_RIESGO = GRX::NN::Sequential.new(
267
+ GRX::NN::Linear.new(4, 16),
268
+ GRX::NN::LayerNorm.new(16),
269
+ GRX::NN::Tanh.new,
270
+ GRX::NN::Linear.new(16, 1),
271
+ GRX::NN::Sigmoid.new
272
+ )
273
+ MODELO_RIESGO.load_weights("modelo_riesgo_crediticio.grx")
274
+ MODELO_RIESGO.eval!
275
+
276
+ # 2. Endpoint de inferencia para evaluar solicitudes de credito
277
+ def evaluar_solicitud(ingresos_mensuales, deuda_actual, score_buro, monto_solicitado)
278
+ # Normalizar datos (utilizando las medias y desviaciones del entrenamiento)
279
+ x1 = (ingresos_mensuales - 25000.0) / 10000.0
280
+ x2 = (deuda_actual - 5000.0) / 4000.0
281
+ x3 = (score_buro - 650.0) / 100.0
282
+ x4 = (monto_solicitado - 50000.0) / 30000.0
283
+
284
+ entrada = GRX.tensor([x1, x2, x3, x4], [1, 4])
285
+ probabilidad_aprobacion = MODELO_RIESGO.call(entrada).to_a[0]
286
+
287
+ decision = probabilidad_aprobacion >= 0.70 ? "APROBADO" : "RECHAZADO"
288
+
289
+ {
290
+ decision: decision,
291
+ confianza: (probabilidad_aprobacion * 100).round(2),
292
+ tasa_sugerida: decision == "APROBADO" ? "12.5% anual" : "N/A"
293
+ }
294
+ end
295
+
296
+ # Prueba en produccion:
297
+ # res = evaluar_solicitud(45000.0, 2000.0, 720.0, 80000.0)
298
+ # puts res.inspect
299
+ ```
300
+
301
+ ---
302
+
303
+ ## 6. Primeros Pasos: Creando Tensores
304
+
305
+ ```ruby
306
+ require "grx"
307
+
308
+ # 1. Vector de 3 elementos
309
+ v = GRX.tensor([1.0, 2.0, 3.0], [3])
310
+ puts "Shape: #{v.shape}" # => [3]
311
+ puts "Numel: #{v.numel}" # => 3
312
+
313
+ # 2. Matriz de 2 filas x 3 columnas
314
+ m = GRX.tensor([1.0, 2.0, 3.0, 4.0, 5.0, 6.0], [2, 3])
315
+ puts "Matriz: #{m.shape}" # => [2, 3]
316
+
317
+ # 3. Factorias
318
+ ceros = GRX.zeros([2, 2]) # Matriz 2x2 de 0.0
319
+ unos = GRX.ones([3, 1]) # Matriz 3x1 de 1.0
320
+
321
+ # 4. Obtener un valor escalar
322
+ scalar = GRX.tensor([99.0], [1])
323
+ puts scalar.item # => 99.0 (Float)
324
+ ```
325
+
326
+ ---
327
+
328
+ ## 7. Aritmetica y Operaciones Basicas
329
+
330
+ ```ruby
331
+ require "grx"
332
+
333
+ a = GRX.tensor([10.0, 20.0, 30.0], [3])
334
+ b = GRX.tensor([2.0, 5.0, 10.0], [3])
335
+
336
+ # Operaciones tensor-tensor (C + SIMD)
337
+ puts (a + b).to_a # => [12.0, 25.0, 40.0]
338
+ puts (a * b).to_a # => [20.0, 100.0, 300.0]
339
+
340
+ # Operaciones con escalares
341
+ puts (a + 5.0).to_a # => [15.0, 25.0, 35.0]
342
+ puts (2.0 * a).to_a # => [20.0, 40.0, 60.0]
343
+
344
+ # Reducciones diferenciables
345
+ x = GRX.tensor([1.0, 4.0, 9.0, 16.0], [4])
346
+ puts x.sum.item # => 30.0
347
+ puts x.mean.item # => 7.5
348
+ ```
349
+
350
+ ---
351
+
352
+ ## 8. Algebra Lineal Intuitiva: Multiplicacion de Matrices
353
+
354
+ ```ruby
355
+ require "grx"
356
+
357
+ # [2, 3] x [3, 2] -> [2, 2]
358
+ a = GRX.tensor([1.0, 2.0, 3.0,
359
+ 4.0, 5.0, 6.0], [2, 3])
360
+
361
+ b = GRX.tensor([7.0, 8.0,
362
+ 9.0, 10.0,
363
+ 11.0, 12.0], [3, 2])
364
+
365
+ c = a.matmul(b)
366
+ puts c.shape # => [2, 2]
367
+ puts c.to_a # => [58.0, 64.0, 139.0, 154.0]
368
+ ```
369
+
370
+ ---
371
+
372
+ ## 9. El Superpoder de GRX: Que es Autograd?
373
+
374
+ ```ruby
375
+ require "grx"
376
+
377
+ x = GRX.tensor([2.0, 3.0], [2], requires_grad: true)
378
+ y = GRX.tensor([4.0, 5.0], [2], requires_grad: true)
379
+
380
+ # Computamos z = (x + y) * y
381
+ z = ((x + y) * y).sum
382
+ z.backward
383
+
384
+ # Derivadas exactas calculadas automaticamente
385
+ puts x.grad.to_a # => [4.0, 5.0]
386
+ puts y.grad.to_a # => [10.0, 13.0]
387
+ ```
388
+
389
+ ---
390
+
391
+ ## 10. Construyendo Redes Neuronales Bloque a Bloque
392
+
393
+ ```ruby
394
+ require "grx"
395
+
396
+ modelo = GRX::NN::Sequential.new(
397
+ GRX::NN::Linear.new(4, 16),
398
+ GRX::NN::LayerNorm.new(16),
399
+ GRX::NN::ReLU.new,
400
+ GRX::NN::Linear.new(16, 1)
401
+ )
402
+
403
+ puts modelo
404
+ ```
405
+
406
+ ---
407
+
408
+ ## 11. El Ciclo Sagrado del Entrenamiento (Con `loss.backward` directo)
409
+
410
+ ```ruby
411
+ opt.zero_grad # 1. Limpiar gradientes anteriores
412
+ pred = modelo.call(train_x) # 2. Forward pass
413
+ loss = loss_fn.call(pred, train_y) # 3. Calcular la perdida
414
+ loss.backward # 4. Backward pass automatico
415
+ opt.step # 5. Ajustar pesos
416
+ ```
417
+
418
+ ---
419
+
420
+ ## 12. Proyectos Guiados Paso a Paso (Completos y Ejecutables)
421
+
422
+ ---
423
+
424
+ ### Proyecto 1: El Predictor de Formulas (Regresion Lineal)
425
+
426
+ Aprende la relacion $y = 3x + 2$ usando `loss.backward` nativo.
427
+
428
+ ```ruby
429
+ require "grx"
430
+
431
+ train_x = GRX.tensor([1.0, 2.0, 3.0, 4.0, 5.0], [5, 1])
432
+ train_y = GRX.tensor([5.0, 8.0, 11.0, 14.0, 17.0], [5, 1])
433
+
434
+ modelo = GRX::NN::Sequential.new(
435
+ GRX::NN::Linear.new(1, 1)
436
+ )
437
+
438
+ opt = GRX::Optim::Adam.new(modelo.parameters, lr: 0.1)
439
+ loss_fn = GRX::Loss::MSELoss.new
440
+
441
+ 150.times do
442
+ opt.zero_grad
443
+ pred = modelo.call(train_x)
444
+ loss = loss_fn.call(pred, train_y)
445
+ loss.backward
446
+ opt.step
447
+ end
448
+
449
+ test_x = GRX.tensor([6.0], [1, 1])
450
+ puts "Prediccion para x=6 (esperado=20): #{modelo.call(test_x).to_a[0].round(3)}"
451
+ ```
452
+
453
+ ---
454
+
455
+ ### Proyecto 2: Ajuste de Curvas No Lineales y Guardado en `.grx`
456
+
457
+ ```ruby
458
+ require "grx"
459
+
460
+ train_x = GRX.tensor((1..10).map(&:to_f), [10, 1])
461
+ train_y = GRX.tensor((1..10).map { |x| 2.0 * x + 1.0 }, [10, 1])
462
+
463
+ mlp = GRX::NN::Sequential.new(
464
+ GRX::NN::Linear.new(1, 16),
465
+ GRX::NN::LayerNorm.new(16),
466
+ GRX::NN::Tanh.new,
467
+ GRX::NN::Linear.new(16, 1)
468
+ )
469
+
470
+ opt = GRX::Optim::Adam.new(mlp.parameters, lr: 0.05)
471
+ loss_fn = GRX::Loss::MSELoss.new
472
+
473
+ 300.times do
474
+ opt.zero_grad
475
+ pred = mlp.call(train_x)
476
+ loss = loss_fn.call(pred, train_y)
477
+ loss.backward
478
+ GRX::Utils.clip_grad_norm!(mlp.parameters, 1.0)
479
+ opt.step
480
+ end
481
+
482
+ # Guardar pesos a disco
483
+ mlp.save_weights("/tmp/mlp_entrenado.grx")
484
+
485
+ # Cargar en una nueva instancia limpia para inferencia
486
+ modelo_nuevo = GRX::NN::Sequential.new(
487
+ GRX::NN::Linear.new(1, 16),
488
+ GRX::NN::LayerNorm.new(16),
489
+ GRX::NN::Tanh.new,
490
+ GRX::NN::Linear.new(16, 1)
491
+ )
492
+ modelo_nuevo.load_weights("/tmp/mlp_entrenado.grx")
493
+
494
+ puts "Inferencia x=11: #{modelo_nuevo.call(GRX.tensor([11.0], [1, 1])).to_a[0].round(3)} (Esperado: 23.0)"
495
+ ```
496
+
497
+ ---
498
+
499
+ ### Proyecto 3: Clasificador Binario Inteligente con BCELoss
500
+
501
+ ```ruby
502
+ require "grx"
503
+
504
+ train_x = GRX.tensor([0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0], [4, 2])
505
+ train_y = GRX.tensor([0.0, 1.0, 1.0, 1.0], [4, 1])
506
+
507
+ clasificador = GRX::NN::Sequential.new(
508
+ GRX::NN::Linear.new(2, 4),
509
+ GRX::NN::Tanh.new,
510
+ GRX::NN::Linear.new(4, 1),
511
+ GRX::NN::Sigmoid.new
512
+ )
513
+
514
+ opt = GRX::Optim::Adam.new(clasificador.parameters, lr: 0.08)
515
+ loss_fn = GRX::Loss::BCELoss.new
516
+
517
+ 400.times do
518
+ opt.zero_grad
519
+ pred = clasificador.call(train_x)
520
+ loss = loss_fn.call(pred, train_y)
521
+ loss.backward
522
+ opt.step
523
+ end
524
+
525
+ puts "Resultados OR: #{clasificador.call(train_x).to_a.map { |v| v.round(4) }}"
526
+ ```
527
+
528
+ ---
529
+
530
+ ### Proyecto 4: Entrenamiento con Datasets Masivos (5,000 Filas con DataLoader)
531
+
532
+ ```ruby
533
+ require "grx"
534
+
535
+ num_samples = 5000
536
+ num_features = 4
537
+
538
+ # y = 2*x1 - 3*x2 + 1.5*x3 - 0.5*x4 + 4.0
539
+ raw_x = Array.new(num_samples * num_features) { rand * 2.0 - 1.0 }
540
+ raw_y = Array.new(num_samples) do |i|
541
+ x1, x2, x3, x4 = raw_x.slice(i * 4, 4)
542
+ 2.0 * x1 - 3.0 * x2 + 1.5 * x3 - 0.5 * x4 + 4.0 + (rand - 0.5) * 0.02
543
+ end
544
+
545
+ train_dataset = GRX::Data::TensorDataset.new(
546
+ GRX.tensor(raw_x[0...(4000*4)], [4000, 4]),
547
+ GRX.tensor(raw_y[0...4000], [4000, 1])
548
+ )
549
+ val_x = GRX.tensor(raw_x[(4000*4)..], [1000, 4])
550
+ val_y = GRX.tensor(raw_y[4000..], [1000, 1])
551
+
552
+ train_loader = GRX::Data::DataLoader.new(train_dataset, batch_size: 64, shuffle: true)
553
+
554
+ model = GRX::NN::Sequential.new(
555
+ GRX::NN::Linear.new(4, 16),
556
+ GRX::NN::LayerNorm.new(16),
557
+ GRX::NN::Tanh.new,
558
+ GRX::NN::Linear.new(16, 1)
559
+ )
560
+
561
+ opt = GRX::Optim::Adam.new(model.parameters, lr: 0.03)
562
+ loss_fn = GRX::Loss::MSELoss.new
563
+
564
+ 20.times do |epoch|
565
+ train_loader.each do |bx, by|
566
+ opt.zero_grad
567
+ pred = model.call(bx)
568
+ loss = loss_fn.call(pred, by)
569
+ loss.backward
570
+ opt.step
571
+ end
572
+ end
573
+
574
+ val_loss = loss_fn.call(model.call(val_x), val_y).item
575
+ puts "Error de Validacion MSE (5,000 muestras): #{val_loss.round(6)}"
576
+ ```
577
+
578
+ ---
579
+
580
+ ### Proyecto 5: Chatbot Financiero Inteligente con Base de Conocimiento y Filtro de Incertidumbre
581
+
582
+ 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:
583
+
584
+ ```ruby
585
+ require "grx"
586
+
587
+ # 1. Base de conocimiento (Memoria del asistente)
588
+ KNOWLEDGE_BASE = {
589
+ "saludo" => [
590
+ "Hola, soy tu asistente financiero. En que puedo ayudarte hoy?",
591
+ "Que tal! Estoy listo para responder tus dudas financieras y de cuenta."
592
+ ],
593
+ "consultar_saldo" => [
594
+ "Tu saldo disponible actual es de $14,850.50 MXN en tu cuenta principal.",
595
+ "Actualmente cuentas con $14,850.50 MXN listos para operar."
596
+ ],
597
+ "tasa_interes" => [
598
+ "Nuestra tasa de rendimiento anual fija actual es del 11.5% anual.",
599
+ "La tasa de interes en cuentas de inversion es de 11.5% anual con pago mensual."
600
+ ],
601
+ "prestamo" => [
602
+ "Puedes solicitar un prestamo de hasta $100,000 MXN directamente desde la app.",
603
+ "Para prestamos personales, ofrecemos plazos de 6 a 36 meses con aprobacion inmediata."
604
+ ],
605
+ "despedida" => [
606
+ "Hasta luego! Que tengas un excelente dia.",
607
+ "Nos vemos, vuelve pronto si necesitas mas informacion."
608
+ ]
609
+ }
610
+
611
+ # 2. Frases de entrenamiento
612
+ training_phrases = [
613
+ ["hola buenos dias", "saludo"],
614
+ ["hola que tal", "saludo"],
615
+ ["buenas tardes", "saludo"],
616
+ ["cuanto dinero tengo en mi cuenta", "consultar_saldo"],
617
+ ["cual es mi saldo disponible hoy", "consultar_saldo"],
618
+ ["cuanto saldo me queda", "consultar_saldo"],
619
+ ["que tasa de interes tienen", "tasa_interes"],
620
+ ["cuanto rendimiento me da la inversion", "tasa_interes"],
621
+ ["como puedo pedir un prestamo", "prestamo"],
622
+ ["necesito un credito personal", "prestamo"],
623
+ ["quiero solicitar un prestamo", "prestamo"],
624
+ ["adios muchas gracias por todo", "despedida"],
625
+ ["hasta luego nos vemos pronto", "despedida"]
626
+ ]
627
+
628
+ # 3. Tokenizacion y vocabulario
629
+ intents = KNOWLEDGE_BASE.keys
630
+ intent_to_id = intents.each_with_index.to_h
631
+ id_to_intent = intent_to_id.invert
632
+ num_classes = intents.size
633
+
634
+ vocab = training_phrases.flat_map { |text, _| text.split }.uniq
635
+ token_to_id = vocab.each_with_index.to_h
636
+ vocab_size = vocab.size
637
+ embedding_dim = 16
638
+ seq_len = 6
639
+
640
+ batch_size = training_phrases.size
641
+ x_ids = training_phrases.map do |text, _|
642
+ tokens = text.split.map { |w| token_to_id[w] }
643
+ (tokens + [0] * seq_len).first(seq_len)
644
+ end
645
+ train_x = GRX.tensor(x_ids.flatten.map(&:to_f), [batch_size, seq_len])
646
+
647
+ onehot = Array.new(batch_size * num_classes, 0.0)
648
+ training_phrases.each_with_index { |(_, label), i| onehot[i * num_classes + intent_to_id[label]] = 1.0 }
649
+ train_y = GRX.tensor(onehot, [batch_size, num_classes])
650
+
651
+ # 4. Arquitectura de la Red
652
+ emb = GRX::NN::Embedding.new(vocab_size, embedding_dim)
653
+ ln = GRX::NN::LayerNorm.new(embedding_dim)
654
+ fc1 = GRX::NN::Linear.new(embedding_dim, 16)
655
+ act = GRX::NN::ReLU.new
656
+ fc2 = GRX::NN::Linear.new(16, num_classes)
657
+
658
+ params = emb.parameters + ln.parameters + fc1.parameters + fc2.parameters
659
+ opt = GRX::Optim::Adam.new(params, lr: 0.05)
660
+ loss_fn = GRX::Loss::CrossEntropyLoss.new
661
+
662
+ # 5. Entrenamiento
663
+ 150.times do
664
+ opt.zero_grad
665
+ flat = train_x.flatten
666
+ e = emb.call(flat)
667
+ e_data = e.to_a
668
+ pooled_data = Array.new(batch_size * embedding_dim, 0.0)
669
+ batch_size.times do |b|
670
+ embedding_dim.times do |d|
671
+ s = 0.0
672
+ seq_len.times { |t| s += e_data[(b * seq_len + t) * embedding_dim + d] }
673
+ pooled_data[b * embedding_dim + d] = s / seq_len.to_f
674
+ end
675
+ end
676
+ pooled = GRX.tensor(pooled_data, [batch_size, embedding_dim], requires_grad: true)
677
+ logits = fc2.call(act.call(fc1.call(ln.call(pooled))))
678
+ loss = loss_fn.call(logits, train_y)
679
+ loss.backward
680
+
681
+ if pooled.grad
682
+ grad_emb = Array.new(batch_size * seq_len * embedding_dim, 0.0)
683
+ p_grad = pooled.grad.to_a
684
+ batch_size.times do |b|
685
+ embedding_dim.times do |d|
686
+ v = p_grad[b * embedding_dim + d] / seq_len.to_f
687
+ seq_len.times { |t| grad_emb[(b * seq_len + t) * embedding_dim + d] = v }
688
+ end
689
+ end
690
+ e.backward(GRX.tensor(grad_emb, e.shape))
691
+ end
692
+ opt.step
693
+ end
694
+
695
+ # 6. Motor de conversacion con control de confianza
696
+ def chatear(mensaje, emb, ln, fc1, act, fc2, token_to_id, id_to_intent, seq_len, embedding_dim)
697
+ words = mensaje.downcase.gsub(/[^a-z0-9\s]/, "").split
698
+ known_count = words.count { |w| token_to_id.key?(w) }
699
+ coverage = words.empty? ? 0.0 : known_count.to_f / words.size
700
+
701
+ # Si la mayoria de las palabras son desconocidas, rechazar amablemente
702
+ if coverage < 0.40
703
+ return "Bot: Disculpa, no comprendo tu mensaje. Solo puedo responder sobre saldo, prestamos o tasas."
704
+ end
705
+
706
+ tokens = words.map { |w| token_to_id[w] || 0 }
707
+ padded = (tokens + [0] * seq_len).first(seq_len)
708
+ t_in = GRX.tensor(padded.map(&:to_f), [seq_len])
709
+ e = emb.call(t_in)
710
+ e_data = e.to_a
711
+
712
+ m_data = Array.new(embedding_dim) do |d|
713
+ seq_len.times.sum { |t| e_data[t * embedding_dim + d] } / seq_len.to_f
714
+ end
715
+ p_t = GRX.tensor(m_data, [1, embedding_dim])
716
+ logits = fc2.call(act.call(fc1.call(ln.call(p_t))))
717
+ probs = logits.softmax.to_a
718
+
719
+ best_prob, best_idx = probs.each_with_index.max_by { |p, idx| p }
720
+
721
+ if best_prob < 0.70
722
+ "Bot: No estoy seguro de haberte entendido (#{(best_prob * 100).round(1)}% certeza). Podrias reformular?"
723
+ else
724
+ intent = id_to_intent[best_idx]
725
+ respuesta = KNOWLEDGE_BASE[intent].sample
726
+ "Bot: #{respuesta} (Certeza: #{(best_prob * 100).round(1)}%)"
727
+ end
728
+ end
729
+
730
+ # 7. Prueba interactiva
731
+ puts "\n--- Simulacion de Chat ---"
732
+ [
733
+ "Hola muy buenos dias",
734
+ "Oye cuanto dinero tengo en mi cuenta",
735
+ "Que tasa de interes tienen actualmente",
736
+ "Quiero pedir un prestamo personal",
737
+ "Como puedo cocinar una hamburguesa", # Fuera de tema
738
+ "Adios muchas gracias"
739
+ ].each do |msg|
740
+ puts "\nUsuario: #{msg}"
741
+ puts chatear(msg, emb, ln, fc1, act, fc2, token_to_id, id_to_intent, seq_len, embedding_dim)
742
+ end
743
+ ```
744
+
745
+ ---
746
+
747
+ ## 13. Buenas Practicas y Errores Comunes
748
+
749
+ 1. **Llamar `opt.zero_grad` en cada iteracion:** Evita que los gradientes se acumulen indefinidamente.
750
+ 2. **Dimensiones compatibles:** Las capas lineales esperan siempre tensores 2D `[batch_size, num_features]`.
751
+ 3. **Manejar `train!` y `eval!`:** Modos necesarios para capas como `Dropout` y `BatchNorm1d` durante inferencia.
752
+
753
+ ---
754
+
755
+ ## 14. Glosario de Terminos Clave
756
+
757
+ - **Shape:** Dimensiones del tensor (ej. `[10, 4]` -> 10 filas, 4 columnas).
758
+ - **Numel:** Total de elementos del tensor.
759
+ - **Strides:** Saltos de memoria plana para avanzar en cada eje.
760
+ - **Tokenizacion:** Division y conversion de texto a identificadores enteros.
761
+ - **Embedding:** Capa que transforma tokens en vectores densos continuos.
762
+ - **CrossEntropyLoss:** Perdida multiclase para clasificar texto e intenciones.
763
+ - **FMA:** Fused Multiply-Add en CPU que calcula $a \times b + c$ en 1 solo ciclo con minima desviacion.