grydra 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e449d883a411813be922b8336a3e8044d33c36cbecc7590472cf98ba83d7bd24
4
+ data.tar.gz: d95118b578b70a82f539153a203be2afb11be345fc5bf317d010ea476b43225c
5
+ SHA512:
6
+ metadata.gz: 83d5f6bad1790e03bd60cf59f061c4e1c3b23eef6dbd4e7a04d475b1262c9543a1cbea0188f6d84fde6de04e927142ef8f5d1f220784e12d7416ad002c06caab
7
+ data.tar.gz: 64b5c314cb50f51cc0c01ac6763c2a643442be772b96acbeed26f0c5cb3b54248452c31f4549e06008aa070fc53a9ac07217834b6fd93a6dd3e3b8aa9b1f27ec
data/lib/gr/core.rb ADDED
@@ -0,0 +1,698 @@
1
+ module GR
2
+ require 'set'
3
+
4
+ ### FUNCIONES DE ACTIVACIÓN ###
5
+ def self.tanh(x)
6
+ Math.tanh(x)
7
+ end
8
+
9
+ def self.derivada_tanh(x)
10
+ 1 - tanh(x)**2
11
+ end
12
+
13
+ def self.relu(x)
14
+ x > 0 ? x : 0
15
+ end
16
+
17
+ def self.derivada_relu(x)
18
+ x > 0 ? 1 : 0
19
+ end
20
+
21
+ def self.sigmoid(x)
22
+ 1.0 / (1.0 + Math.exp(-x))
23
+ end
24
+
25
+ def self.derivada_sigmoid(x)
26
+ s = sigmoid(x)
27
+ s * (1 - s)
28
+ end
29
+
30
+ def self.softmax(vector)
31
+ max = vector.max
32
+ exps = vector.map { |v| Math.exp(v - max) }
33
+ sum = exps.sum
34
+ exps.map { |v| v / sum }
35
+ end
36
+
37
+ ### INICIALIZACIÓN XAVIER ###
38
+ def self.xavier_init(num_inputs)
39
+ limit = Math.sqrt(6.0 / num_inputs)
40
+ rand * 2 * limit - limit
41
+ end
42
+
43
+ ### NORMALIZACIÓN Z-SCORE ###
44
+ def self.normalizar_zscore(datos)
45
+ n = datos.size
46
+ medias = datos.first.size.times.map { |i| datos.map { |fila| fila[i] }.sum.to_f / n }
47
+ desviaciones = datos.first.size.times.map do |i|
48
+ m = medias[i]
49
+ Math.sqrt(datos.map { |fila| (fila[i] - m)**2 }.sum.to_f / n)
50
+ end
51
+ normalizados = datos.map do |fila|
52
+ fila.each_with_index.map { |valor, i| desviaciones[i] != 0 ? (valor - medias[i]) / desviaciones[i] : 0 }
53
+ end
54
+ return normalizados, medias, desviaciones
55
+ end
56
+
57
+ def self.desnormalizar_zscore(normalizados, medias, desviaciones)
58
+ normalizados.map do |fila|
59
+ fila.each_with_index.map { |valor, i| valor * desviaciones[i] + medias[i] }
60
+ end
61
+ end
62
+
63
+ ### MÉTRICAS DE EVALUACIÓN ###
64
+ def self.mse(predicciones, reales)
65
+ n = predicciones.size
66
+ sum = predicciones.zip(reales).map { |p, r| (p - r)**2 }.sum
67
+ sum / n.to_f
68
+ end
69
+
70
+ def self.mae(predicciones, reales)
71
+ n = predicciones.size
72
+ sum = predicciones.zip(reales).map { |p, r| (p - r).abs }.sum
73
+ sum / n.to_f
74
+ end
75
+
76
+ def self.precision(tp, fp)
77
+ tp.to_f / (tp + fp)
78
+ end
79
+
80
+ def self.recall(tp, fn)
81
+ tp.to_f / (tp + fn)
82
+ end
83
+
84
+ def self.f1(precision, recall)
85
+ 2 * (precision * recall) / (precision + recall)
86
+ end
87
+
88
+ ### CLASE NEURONA ###
89
+ class Neurona
90
+ attr_accessor :pesos, :sesgo, :salida, :delta
91
+
92
+ def initialize(entradas, activacion=:tanh)
93
+ raise ArgumentError, "El número de entradas debe ser un entero positivo" unless entradas.is_a?(Integer) && entradas > 0
94
+ @pesos = Array.new(entradas) { GR.xavier_init(entradas) }
95
+ @sesgo = GR.xavier_init(entradas)
96
+ @salida = 0
97
+ @delta = 0
98
+ @activacion = activacion
99
+ @suma = 0
100
+ end
101
+
102
+ def calcular_salida(entradas)
103
+ raise ArgumentError, "Las entradas deben ser un array de números" unless entradas.is_a?(Array) && entradas.all? { |e| e.is_a?(Numeric) }
104
+ if @pesos.size != entradas.size
105
+ raise "Error: entradas (#{entradas.size}) no coinciden con pesos (#{@pesos.size})"
106
+ end
107
+ @suma = @pesos.zip(entradas).map { |peso, entrada| peso * entrada }.sum + @sesgo
108
+ @salida = case @activacion
109
+ when :tanh then GR.tanh(@suma)
110
+ when :relu then GR.relu(@suma)
111
+ when :sigmoid then GR.sigmoid(@suma)
112
+ else @suma
113
+ end
114
+ @salida
115
+ end
116
+
117
+ def derivada_activacion
118
+ case @activacion
119
+ when :tanh then GR.derivada_tanh(@salida)
120
+ when :relu then GR.derivada_relu(@salida)
121
+ when :sigmoid then GR.derivada_sigmoid(@suma)
122
+ else 1
123
+ end
124
+ end
125
+ end
126
+
127
+ ### CLASE CAPA BASE ###
128
+ class Capa
129
+ def calcular_salidas(entradas)
130
+ raise NotImplementedError, "Implementar en subclase"
131
+ end
132
+ end
133
+
134
+ ### CLASE CAPA DENSE ###
135
+ class CapaDensa < Capa
136
+ attr_accessor :neuronas, :activacion
137
+
138
+ def initialize(numero_neuronas, entradas_por_neurona, activacion=:tanh)
139
+ @activacion = activacion
140
+ @neuronas = Array.new(numero_neuronas) { Neurona.new(entradas_por_neurona, activacion) }
141
+ end
142
+
143
+ def calcular_salidas(entradas)
144
+ @neuronas.map { |neurona| neurona.calcular_salida(entradas) }
145
+ end
146
+ end
147
+
148
+ ### CLASE RED NEURONAL ###
149
+ class RedNeuronal
150
+ attr_accessor :capas
151
+
152
+ def initialize(estructura, imprimir_epocas = false, activaciones = nil)
153
+ @imprimir_epocas = imprimir_epocas
154
+ @capas = []
155
+ activaciones ||= Array.new(estructura.size - 1, :tanh)
156
+ estructura.each_cons(2).with_index do |(entradas, salidas), i|
157
+ @capas << CapaDensa.new(salidas, entradas, activaciones[i])
158
+ end
159
+ end
160
+
161
+ def calcular_salidas(entradas)
162
+ raise ArgumentError, "Entradas deben ser array de números" unless entradas.is_a?(Array) && entradas.all? { |e| e.is_a?(Numeric) }
163
+ @capas.inject(entradas) { |salidas, capa| capa.calcular_salidas(salidas) }
164
+ end
165
+
166
+ # Entrenamiento con mini-batch, early stopping, decay learning rate
167
+ def entrenar(datos_entrada, datos_salida, tasa_aprendizaje, epocas, umbral_error = nil, batch_size: 1, paciencia: 5, decay: 0.95)
168
+ mejor_error = Float::INFINITY
169
+ contador_paciencia = 0
170
+
171
+ epocas.times do |epoca|
172
+ error_total = 0
173
+
174
+ # Shuffle datos
175
+ indices = (0...datos_entrada.size).to_a.shuffle
176
+ datos_entrada = indices.map { |i| datos_entrada[i] }
177
+ datos_salida = indices.map { |i| datos_salida[i] }
178
+
179
+ datos_entrada.each_slice(batch_size).with_index do |batch_entradas, batch_idx|
180
+ batch_salidas_reales = datos_salida[batch_idx*batch_size, batch_size]
181
+
182
+ batch_entradas.zip(batch_salidas_reales).each do |entrada, salida_real|
183
+ salidas = calcular_salidas(entrada)
184
+ errores = salidas.zip(salida_real).map { |salida, real| real - salida }
185
+ error_total += errores.map { |e| e**2 }.sum / errores.size
186
+
187
+ # Capa salida
188
+ @capas.last.neuronas.each_with_index do |neurona, i|
189
+ neurona.delta = errores[i] * neurona.derivada_activacion
190
+ end
191
+
192
+ # Backpropagation capas ocultas
193
+ (@capas.size - 2).downto(0) do |i|
194
+ @capas[i].neuronas.each_with_index do |neurona, j|
195
+ sum_deltas = @capas[i + 1].neuronas.sum { |n| n.pesos[j] * n.delta }
196
+ neurona.delta = sum_deltas * neurona.derivada_activacion
197
+ end
198
+ end
199
+
200
+ # Actualización pesos y sesgo
201
+ @capas.each_with_index do |capa, idx|
202
+ entradas_capa = idx.zero? ? entrada : @capas[idx - 1].neuronas.map(&:salida)
203
+ capa.neuronas.each do |neurona|
204
+ neurona.pesos.each_with_index do |peso, i|
205
+ neurona.pesos[i] += tasa_aprendizaje * neurona.delta * entradas_capa[i]
206
+ end
207
+ neurona.sesgo += tasa_aprendizaje * neurona.delta
208
+ end
209
+ end
210
+ end
211
+ end
212
+
213
+ if umbral_error && error_total < umbral_error
214
+ puts "Error umbral alcanzado en época #{epoca+1}: #{error_total}"
215
+ break
216
+ end
217
+
218
+ if error_total < mejor_error
219
+ mejor_error = error_total
220
+ contador_paciencia = 0
221
+ else
222
+ contador_paciencia += 1
223
+ if contador_paciencia >= paciencia
224
+ puts "Early stopping en época #{epoca+1}, error no mejora desde hace #{paciencia} épocas."
225
+ break
226
+ end
227
+ end
228
+
229
+ tasa_aprendizaje *= decay
230
+
231
+ puts "Época #{epoca+1}, Error total: #{error_total.round(6)}, tasa_aprendizaje: #{tasa_aprendizaje.round(6)}" if @imprimir_epocas
232
+ end
233
+ end
234
+
235
+ def info_red
236
+ puts "Red neuronal con #{@capas.size} capas:"
237
+ @capas.each_with_index do |capa, i|
238
+ puts " Capa #{i+1}: #{capa.neuronas.size} neuronas, activación: #{capa.activacion}"
239
+ capa.neuronas.each_with_index do |neurona, j|
240
+ puts " Neurona #{j+1}: Pesos=#{neurona.pesos.map { |p| p.round(3) }}, Sesgo=#{neurona.sesgo.round(3)}"
241
+ end
242
+ end
243
+ end
244
+
245
+ # Exportar a DOT para Graphviz
246
+ def exportar_graphviz(nombre_archivo = "red_neuronal.dot")
247
+ File.open(nombre_archivo, "w") do |f|
248
+ f.puts "digraph RedNeuronal {"
249
+ @capas.each_with_index do |capa, i|
250
+ capa.neuronas.each_with_index do |neurona, j|
251
+ nodo = "C#{i}_N#{j}"
252
+ f.puts " #{nodo} [label=\"N#{j+1}\"];"
253
+ if i < @capas.size - 1
254
+ @capas[i+1].neuronas.each_with_index do |sig_neurona, k|
255
+ peso = sig_neurona.pesos[j].round(3)
256
+ f.puts " #{nodo} -> C#{i+1}_N#{k} [label=\"#{peso}\"];"
257
+ end
258
+ end
259
+ end
260
+ end
261
+ f.puts "}"
262
+ end
263
+ puts "Red exportada a #{nombre_archivo} (Graphviz DOT)"
264
+ end
265
+ end
266
+
267
+ ### RED PRINCIPAL ###
268
+ class RedPrincipal
269
+ attr_accessor :subredes
270
+
271
+ def initialize(imprimir_epocas = false)
272
+ @subredes = []
273
+ @imprimir_epocas = imprimir_epocas
274
+ end
275
+
276
+ def agregar_subred(estructura, activaciones=nil)
277
+ @subredes << RedNeuronal.new(estructura, @imprimir_epocas, activaciones)
278
+ end
279
+
280
+ def entrenar_subredes(datos, tasa_aprendizaje, epocas, **opts)
281
+ datos.each_with_index do |datos_subred, index|
282
+ puts "Entrenando Subred #{index + 1}..."
283
+ @subredes[index].entrenar(datos_subred[:entrada], datos_subred[:salida], tasa_aprendizaje, epocas, **opts)
284
+ end
285
+ end
286
+
287
+ def combinar_resultados(entrada_principal)
288
+ salidas_subredes = @subredes.map { |subred| subred.calcular_salidas(entrada_principal) }
289
+ salidas_subredes.transpose.map { |salidas| salidas.sum / salidas.size }
290
+ end
291
+ end
292
+
293
+ ### GUARDAR Y CARGAR MODELO Y VOCABULARIO ###
294
+ def self.guardar_modelo(modelo, nombre, path = File.dirname(__FILE__), vocabulario=nil)
295
+ File.open("#{nombre}.red", "wb") { |f| Marshal.dump(modelo, f) }
296
+ puts "\e[33mModelo guardado en '#{path}'\e[0m"
297
+ if vocabulario
298
+ guardar_vocabulario(vocabulario, nombre, path)
299
+ end
300
+ end
301
+
302
+ def self.cargar_modelo(nombre, path = File.dirname(__FILE__))
303
+ modelo = nil
304
+ File.open("#{nombre}.red", "rb") { |f| modelo = Marshal.load(f) }
305
+ modelo
306
+ end
307
+
308
+ def self.guardar_vocabulario(vocabulario, nombre, path = File.dirname(__FILE__))
309
+ File.open("#{nombre}_vocab.bin", "wb") { |f| Marshal.dump(vocabulario, f) }
310
+ puts "\e[33mVocabulario guardado en '#{path}'\e[0m"
311
+ end
312
+
313
+ def self.cargar_vocabulario(nombre, path = File.dirname(__FILE__))
314
+ vocabulario = nil
315
+ File.open("#{nombre}_vocab.bin", "rb") { |f| vocabulario = Marshal.load(f) }
316
+ vocabulario
317
+ end
318
+
319
+ ### PREPROCESAMIENTO ACTUALIZADO ###
320
+ def self.normalizar_varios(datos, maximos, metodo=:max)
321
+ case metodo
322
+ when :max
323
+ datos.map do |fila|
324
+ fila.each_with_index.map { |valor, idx| valor.to_f / maximos[idx] }
325
+ end
326
+ when :zscore
327
+ medias = maximos[:medias]
328
+ desviaciones = maximos[:desviaciones]
329
+ datos.map do |fila|
330
+ fila.each_with_index.map do |valor, idx|
331
+ desviaciones[idx] != 0 ? (valor.to_f - medias[idx]) / desviaciones[idx] : 0
332
+ end
333
+ end
334
+ else
335
+ raise "Método de normalización desconocido"
336
+ end
337
+ end
338
+
339
+ def self.calcular_maximos(datos, metodo=:max)
340
+ if metodo == :max
341
+ maximos = {}
342
+ datos.first.size.times do |i|
343
+ maximos[i] = datos.map { |fila| fila[i] }.max.to_f
344
+ end
345
+ maximos
346
+ elsif metodo == :zscore
347
+ medias = []
348
+ desviaciones = []
349
+ n = datos.size
350
+
351
+ medias = datos.first.size.times.map do |i|
352
+ datos.map { |fila| fila[i] }.sum.to_f / n
353
+ end
354
+
355
+ desviaciones = datos.first.size.times.map do |i|
356
+ m = medias[i]
357
+ Math.sqrt(datos.map { |fila| (fila[i] - m)**2 }.sum.to_f / n)
358
+ end
359
+ { medias: medias, desviaciones: desviaciones }
360
+ else
361
+ raise "Método desconocido para calcular máximos"
362
+ end
363
+ end
364
+
365
+ ### FUNCIONES DE TEXTO ###
366
+ def self.crear_vocabulario(textos)
367
+ textos.map(&:split).flatten.map(&:downcase).uniq
368
+ end
369
+
370
+ def self.vectorizar_texto(texto, vocabulario)
371
+ vector = Array.new(vocabulario.size, 0)
372
+ palabras = texto.downcase.split
373
+ palabras.each do |palabra|
374
+ indice = vocabulario.index(palabra)
375
+ vector[indice] = 1 if indice
376
+ end
377
+ vector
378
+ end
379
+
380
+ def self.normalizar_con_vocabulario(datos, vocabulario)
381
+ max_valor = vocabulario.size
382
+ datos.map { |vector| vector.map { |v| v.to_f / max_valor } }
383
+ end
384
+
385
+ ### CLASE FacilRed (sin cambios, solo agregando opción normalización zscore y activaciones) ###
386
+ class FacilRed
387
+ attr_accessor :red, :vocabulario, :max_valores, :max_valores_salida
388
+
389
+ def initialize(imprimir_epocas = false)
390
+ @red = GR::RedPrincipal.new(imprimir_epocas)
391
+ @vocabulario = nil
392
+ @max_valores = {}
393
+ @max_valores_salida = {}
394
+ end
395
+
396
+ # --------- Para datos tipo hash ---------
397
+ def entrenar_hashes(datos_hash, claves_entrada, clave_etiqueta, estructuras, tasa, epocas, normalizacion=:max)
398
+ @red.subredes.clear # limpiar subredes previas
399
+
400
+ entradas = datos_hash.map do |item|
401
+ claves_entrada.map do |clave|
402
+ valor = item[clave]
403
+ valor == true ? 1.0 : valor == false ? 0.0 : valor.to_f
404
+ end
405
+ end
406
+
407
+ @max_valores = GR.calcular_maximos(entradas, normalizacion)
408
+
409
+ datos_normalizados = GR.normalizar_varios(entradas, @max_valores, normalizacion)
410
+
411
+ etiquetas = datos_hash.map { |item| [item[clave_etiqueta].to_f] }
412
+
413
+ @max_valores_salida = GR.calcular_maximos(etiquetas, normalizacion)
414
+ etiquetas_no = GR.normalizar_varios(etiquetas, @max_valores_salida, normalizacion)
415
+
416
+ estructuras.each do |estructura|
417
+ @red.agregar_subred([claves_entrada.size, *estructura])
418
+ end
419
+
420
+ datos_para_subredes = estructuras.map do |_|
421
+ { entrada: datos_normalizados, salida: etiquetas_no }
422
+ end
423
+
424
+ @red.entrenar_subredes(datos_para_subredes, tasa, epocas)
425
+ end
426
+
427
+ def predecir_hashes(nuevos_hashes, claves_entrada, normalizacion=:max)
428
+ entradas = nuevos_hashes.map do |item|
429
+ claves_entrada.map do |clave|
430
+ valor = item[clave]
431
+ valor == true ? 1.0 : valor == false ? 0.0 : valor.to_f
432
+ end
433
+ end
434
+
435
+ datos_normalizados = GR.normalizar_varios(entradas, @max_valores, normalizacion)
436
+
437
+ datos_normalizados.map do |entrada|
438
+ pred_norm = @red.combinar_resultados(entrada)
439
+ if normalizacion == :zscore && @max_valores_salida.is_a?(Hash)
440
+ pred_norm.map.with_index do |val, idx|
441
+ val * @max_valores_salida[:desviaciones][idx] + @max_valores_salida[:medias][idx]
442
+ end
443
+ else
444
+ pred_norm.map.with_index { |val, idx| val * @max_valores_salida[idx] }
445
+ end
446
+ end
447
+ end
448
+
449
+
450
+ # --------- Para datos numéricos ---------
451
+ def entrenar_numericos(datos_entrada, datos_salida, estructuras, tasa, epocas, normalizacion=:max)
452
+ @red.subredes.clear # limpiar subredes previas
453
+
454
+ @max_valores = GR.calcular_maximos(datos_entrada, normalizacion)
455
+ @max_valores_salida = GR.calcular_maximos(datos_salida, normalizacion)
456
+
457
+ datos_entrada_no = GR.normalizar_varios(datos_entrada, @max_valores, normalizacion)
458
+ datos_salida_no = GR.normalizar_varios(datos_salida, @max_valores_salida, normalizacion)
459
+
460
+ estructuras.each do |estructura|
461
+ @red.agregar_subred([datos_entrada.first.size, *estructura])
462
+ end
463
+
464
+ datos_para_subredes = estructuras.map do |_|
465
+ { entrada: datos_entrada_no, salida: datos_salida_no }
466
+ end
467
+
468
+ @red.entrenar_subredes(datos_para_subredes, tasa, epocas)
469
+ end
470
+
471
+ def predecir_numericos(nuevos_datos, normalizacion=:max)
472
+ datos_normalizados = GR.normalizar_varios(nuevos_datos, @max_valores, normalizacion)
473
+ datos_normalizados.map do |entrada|
474
+ pred_norm = @red.combinar_resultados(entrada)
475
+ if normalizacion == :zscore && @max_valores_salida.is_a?(Hash)
476
+ pred_norm.map.with_index do |val, idx|
477
+ val * @max_valores_salida[:desviaciones][idx] + @max_valores_salida[:medias][idx]
478
+ end
479
+ else
480
+ pred_norm.map.with_index { |val, idx| val * @max_valores_salida[idx] }
481
+ end
482
+ end
483
+ end
484
+
485
+ # --------- Para texto ---------
486
+ def entrenar_texto(textos, etiquetas, estructuras, tasa, epocas, normalizacion=:max)
487
+ @red.subredes.clear # limpiar subredes previas
488
+
489
+ @vocabulario = GR.crear_vocabulario(textos)
490
+ entradas = textos.map { |texto| GR.vectorizar_texto(texto, @vocabulario) }
491
+ @max_valores = { 0 => @vocabulario.size } # Solo tamaño vocabulario para texto
492
+
493
+ datos_normalizados = GR.normalizar_varios(entradas, @max_valores, normalizacion)
494
+
495
+ @max_valores_salida = GR.calcular_maximos(etiquetas, normalizacion)
496
+ etiquetas_no = GR.normalizar_varios(etiquetas, @max_valores_salida, normalizacion)
497
+
498
+ estructuras.each do |estructura|
499
+ @red.agregar_subred([@vocabulario.size, *estructura])
500
+ end
501
+
502
+ datos_para_subredes = estructuras.map do |_|
503
+ { entrada: datos_normalizados, salida: etiquetas_no }
504
+ end
505
+
506
+ @red.entrenar_subredes(datos_para_subredes, tasa, epocas)
507
+ end
508
+
509
+ def predecir_texto(nuevos_textos, normalizacion=:max)
510
+ entradas = nuevos_textos.map { |texto| GR.vectorizar_texto(texto, @vocabulario) }
511
+ datos_normalizados = GR.normalizar_varios(entradas, @max_valores, normalizacion)
512
+
513
+ datos_normalizados.map do |entrada|
514
+ pred_norm = @red.combinar_resultados(entrada)
515
+ if normalizacion == :zscore && @max_valores_salida.is_a?(Hash)
516
+ pred_norm.map.with_index do |val, idx|
517
+ val * @max_valores_salida[:desviaciones][idx] + @max_valores_salida[:medias][idx]
518
+ end
519
+ else
520
+ pred_norm.map.with_index { |val, idx| val * @max_valores_salida[idx] }
521
+ end
522
+ end
523
+ end
524
+ end
525
+ DESCRIPCIONES_METODOS = {
526
+ # RedPrincipal
527
+ 'RedPrincipal.agregar_subred' => {
528
+ descripcion: "Agrega una subred a la red principal con la estructura dada. La estructura define el número de neuronas por capa (incluyendo entradas).",
529
+ ejemplo: <<~EX
530
+ red = GR::RedPrincipal.new
531
+ red.agregar_subred([2, 4, 1]) # 2 entradas, 4 neuronas capa oculta, 1 salida
532
+ EX
533
+ }, #34,36
534
+ 'RedPrincipal.entrenar_subredes' => {
535
+ descripcion: "Entrena todas las subredes usando los datos de entrada y salida, con tasa de aprendizaje, épocas y opciones como paciencia para early stopping.",
536
+ ejemplo: <<~EX
537
+ datos = [
538
+ {entrada: [[0.1, 0.2]], salida: [[0.3]]},
539
+ {entrada: [[0.5, 0.6]], salida: [[0.7]]}
540
+ ]
541
+ red = GR::RedPrincipal.new(true) <-- (true) es para que impriam el umbral de error
542
+ red.agregar_subred([2, 3, 1])
543
+ red.agregar_subred([2, 2, 1])
544
+ red.entrenar_subredes(datos, 0.01, 1000, paciencia: 5)
545
+ EX
546
+ },
547
+ 'RedPrincipal.combinar_resultados' => {
548
+ descripcion: 'Promedia las salidas de todas las subredes para una entrada dada, generando la predicción final.',
549
+ ejemplo: <<~EX
550
+ resultado = red.combinar_resultados([0.2, 0.8])
551
+ EX
552
+ },
553
+
554
+ # FacilRed (interfaz más sencilla)
555
+ 'FacilRed.entrenar_numericos' => {
556
+ descripcion: 'Entrena la red con datos numéricos (arrays de números) para entrada y salida. Normaliza, crea subredes y entrena.',
557
+ ejemplo: <<~EX
558
+ datos_entrada = [[170, 25], [160, 30], [180, 22]]
559
+ datos_salida = [[65], [60], [75]]
560
+ estructuras = [[4, 1], [3, 1]]
561
+ red = GR::FacilRed.new(true)
562
+ red.entrenar_numericos(datos_entrada, datos_salida, estructuras, 0.05, 15000, :max)
563
+ EX
564
+ },
565
+ 'FacilRed.predecir_numericos' => {
566
+ descripcion: 'Predice valores con nuevos datos numéricos normalizados igual que el entrenamiento.',
567
+ ejemplo: <<~EX
568
+ nuevos_datos = [[172, 26]]
569
+ predicciones = red.predecir_numericos(nuevos_datos, :max)
570
+ EX
571
+ },
572
+ 'FacilRed.entrenar_hashes' => {
573
+ descripcion: 'Entrena la red con datos de entrada en formato hash, especificando las claves a usar y la clave para la etiqueta.',
574
+ ejemplo: <<~EX
575
+ datos_hash = [
576
+ {altura: 170, edad: 25, peso: 65},
577
+ {altura: 160, edad: 30, peso: 60}
578
+ ]
579
+ red = GR::FacilRed.new(true) <-- (true) es para que impriam el umbral de error
580
+ red.entrenar_hashes(datos_hash, [:altura, :edad], :peso, [[4, 1]], 0.05, 15000, :max)
581
+ EX
582
+ },
583
+ 'FacilRed.predecir_hashes' => {
584
+ descripcion: 'Predice usando datos hash con las claves especificadas para entrada.',
585
+ ejemplo: <<~EX
586
+ nuevos_hashes = [{altura: 172, edad: 26}]
587
+ predicciones = red.predecir_hashes(nuevos_hashes, [:altura, :edad], :max)
588
+ EX
589
+ },
590
+ 'FacilRed.entrenar_texto' => {
591
+ descripcion: 'Entrena la red con textos y etiquetas numéricas, creando un vocabulario para vectorizar textos.',
592
+ ejemplo: <<~EX
593
+ textos = ["hola mundo", "buen día"]
594
+ etiquetas = [[1], [0]]
595
+ estructuras = [[5, 1]]
596
+ red = GR::FacilRed.new(true)
597
+ red.entrenar_texto(textos, etiquetas, estructuras, 0.01, 5000)
598
+ EX
599
+ },
600
+ 'FacilRed.predecir_texto' => {
601
+ descripcion: 'Predice con nuevos textos, vectorizando según el vocabulario aprendido.',
602
+ ejemplo: <<~EX
603
+ nuevos_textos = ["hola"]
604
+ predicciones = red.predecir_texto(nuevos_textos)
605
+ EX
606
+ },
607
+
608
+ # Guardar y cargar modelos y vocabularios
609
+ 'GR.guardar_modelo' => {
610
+ descripcion: 'Guarda el modelo entrenado en un archivo binario para poder cargarlo después. Opcionalmente guarda también el vocabulario.',
611
+ ejemplo: <<~EX
612
+ GR.guardar_modelo(modelo, "mi_modelo", "./modelos", vocabulario) El path por defecto es la carpeta donde se este ejecunatdo
613
+ EX
614
+ },
615
+ 'GR.cargar_modelo' => {
616
+ descripcion: 'Carga un modelo guardado desde un archivo binario para usarlo sin entrenar de nuevo.',
617
+ ejemplo: <<~EX
618
+ modelo = GR.cargar_modelo("mi_modelo", "./modelos") El path por defecto es la carpeta donde se este ejecunatdo
619
+ EX
620
+ },
621
+ 'GR.guardar_vocabulario' => {
622
+ descripcion: 'Guarda el vocabulario en un archivo binario para su posterior carga.',
623
+ ejemplo: <<~EX
624
+ GR.guardar_vocabulario(vocabulario, "mi_modelo", "./modelos") El path por defecto es la carpeta donde se este ejecunatdo
625
+ EX
626
+ },
627
+ 'GR.cargar_vocabulario' => {
628
+ descripcion: 'Carga el vocabulario desde un archivo binario guardado.',
629
+ ejemplo: <<~EX
630
+ vocabulario = GR.cargar_vocabulario("mi_modelo", "./modelos") El path por defecto es la carpeta donde se este ejecunatdo
631
+ EX
632
+ },
633
+
634
+ # Normalización y preprocesamiento
635
+ 'GR.normalizar_varios' => {
636
+ descripcion: 'Normaliza un conjunto de datos según el método especificado (:max o :zscore).',
637
+ ejemplo: <<~EX
638
+ maximos = GR.calcular_maximos(datos, :max)
639
+ datos_norm = GR.normalizar_varios(datos, maximos, :max)
640
+ EX
641
+ },
642
+ 'GR.calcular_maximos' => {
643
+ descripcion: 'Calcula valores máximos o medias y desviaciones según el método para normalizar datos.',
644
+ ejemplo: <<~EX
645
+ maximos = GR.calcular_maximos(datos, :max)
646
+ estadisticas = GR.calcular_maximos(datos, :zscore)
647
+ EX
648
+ },
649
+
650
+ # Funciones de texto
651
+ 'GR.crear_vocabulario' => {
652
+ descripcion: 'Crea un vocabulario único a partir de una lista de textos, separando palabras.',
653
+ ejemplo: <<~EX
654
+ textos = ["hola mundo", "buen día"]
655
+ vocabulario = GR.crear_vocabulario(textos)
656
+ EX
657
+ },
658
+ 'GR.vectorizar_texto' => {
659
+ descripcion: 'Convierte un texto en un vector binario basado en la presencia de palabras en el vocabulario.',
660
+ ejemplo: <<~EX
661
+ vector = GR.vectorizar_texto("hola mundo", vocabulario)
662
+ EX
663
+ },
664
+ 'GR.normalizar_con_vocabulario' => {
665
+ descripcion: 'Normaliza vectores generados con el vocabulario dividiendo entre el tamaño del vocabulario.',
666
+ ejemplo: <<~EX
667
+ vectores_norm = GR.normalizar_con_vocabulario(vectores, vocabulario)
668
+ EX
669
+ }
670
+ }
671
+
672
+ # Función para mostrar descripción y ejemplo de método dado clase y método (strings)
673
+ def self.describe_metodo(clase, metodo)
674
+ clave = "#{clase}.#{metodo}"
675
+ info = DESCRIPCIONES_METODOS[clave]
676
+ if info
677
+ puts "\e[1;3;5;41;37mDescripción de #{clave}:"
678
+ puts info[:descripcion]
679
+ puts "\nEjemplo de uso:"
680
+ puts "#{info[:ejemplo]}\e[0m"
681
+ else
682
+ puts "\e[31;1mNo se encontró descripción para el método '#{clave}'"
683
+ puts "\e[31mAsegúrate de usar el nombre exacto de clase y método (como strings)"
684
+ puts "\e[36mPuedes llamar el metodo para verificar: listar_metodos_disponibles\e[0m"
685
+ end
686
+ end
687
+
688
+ # Función que lista todos los métodos públicos documentados en DESCRIPCIONES_METODOS
689
+ def self.listar_metodos_disponibles
690
+ puts "\e[1;3;5;41;37mMétodos públicos documentados:"
691
+ grouped = DESCRIPCIONES_METODOS.keys.group_by { |k| k.split('.').first }
692
+ grouped.each do |clase, metodos|
693
+ puts " #{clase}:"
694
+ metodos.each { |m| puts " - #{m.split('.').last}" }
695
+ end
696
+ print "\e[0m"
697
+ end
698
+ end
data/lib/gr/version.rb ADDED
@@ -0,0 +1,3 @@
1
+ module Gr
2
+ VERSION = "0.1.2"
3
+ end
data/lib/grydra.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "gr/version"
2
+ require "gr/core"
metadata ADDED
@@ -0,0 +1,44 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: grydra
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Razo
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: Implementación de redes neuronales con clases para neuronas y poder crear
13
+ redes neuronales, con diferentes metodos de activacion como Tanh, Relu y Sigmoid
14
+ email:
15
+ - garabatoangelopolis@email.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/gr/core.rb
21
+ - lib/gr/version.rb
22
+ - lib/grydra.rb
23
+ homepage: https://rubygems.org/gems/gr
24
+ licenses:
25
+ - MIT
26
+ metadata: {}
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 2.7.0
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubygems_version: 3.6.9
42
+ specification_version: 4
43
+ summary: Librería para redes neuronales en Ruby
44
+ test_files: []