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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/GUIA_PRINCIPIANTES.md +763 -0
- data/README.es.md +270 -0
- data/README.md +178 -375
- data/grx-tensor.gemspec +4 -2
- data/lib/grx/c_api.rb +41 -37
- data/lib/grx/data.rb +87 -0
- data/lib/grx/loss.rb +55 -25
- data/lib/grx/nn.rb +153 -36
- data/lib/grx/optim.rb +9 -9
- data/lib/grx/serialization.rb +66 -0
- data/lib/grx/storage.rb +15 -24
- data/lib/grx/tensor.rb +198 -65
- data/lib/grx/utils.rb +28 -0
- data/lib/grx/version.rb +1 -1
- data/lib/grx.rb +14 -3
- metadata +8 -3
data/README.es.md
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# GRX-Tensor
|
|
2
|
+
|
|
3
|
+
**Ruby habla. C calcula.**
|
|
4
|
+
|
|
5
|
+
Un framework de tensores para Ruby con diferenciacion automatica (autograd), un nucleo de computo en C con instrucciones vectoriales SIMD, y primitivas completas para redes neuronales, todo respaldado por una API de Ruby limpia y expresiva.
|
|
6
|
+
|
|
7
|
+
[](https://www.ruby-lang.org)
|
|
8
|
+
[](LICENSE.txt)
|
|
9
|
+
[](https://github.com/Gabo-Razo/grx-tensor)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Que es GRX?
|
|
14
|
+
|
|
15
|
+
GRX es una biblioteca de computacion tensorial de alto rendimiento para Ruby. Su nucleo numerico esta implementado en C y compilado con extensiones **AVX2 + FMA** SIMD — procesando 4 numeros de punto flotante de doble precision (doubles) por ciclo de reloj con multiplicacion-suma fusionada.
|
|
16
|
+
|
|
17
|
+
Ruby gestiona la interfaz de usuario de alto nivel: validacion de formas dimensionales (shapes), construccion del grafo de computacion y orquestacion. C maneja la memoria nativa y las operaciones aritmeticas pesadas.
|
|
18
|
+
|
|
19
|
+
### Caracteristicas clave
|
|
20
|
+
|
|
21
|
+
| Caracteristica | Detalle |
|
|
22
|
+
|---|---|
|
|
23
|
+
| **Nucleo C+SIMD** | AVX2+FMA, soporte SSE2 y fallback escalar autodetectado al compilar |
|
|
24
|
+
| **Memoria Alineada** | Asignacion en el heap de C alineada a 32 bytes (`posix_memalign` / `_aligned_malloc`) |
|
|
25
|
+
| **Autograd** | Diferenciacion automatica con funciones de perdida 100% diferenciables |
|
|
26
|
+
| **Optimizadores** | SGD (con momento y weight decay) y Adam (vectorizado en C con FMA) |
|
|
27
|
+
| **Capas de Redes Neuronales** | Linear, Sequential, Embedding, LayerNorm, Dropout, BatchNorm1d |
|
|
28
|
+
| **Funciones de Activacion** | ReLU, Leaky ReLU, Tanh, Sigmoid, Softmax (todas diferenciables) |
|
|
29
|
+
| **Funciones de Perdida** | MSE, MAE, BCE, CrossEntropy, Huber (totalmente diferenciables) |
|
|
30
|
+
| **Persistencia de Modelos** | Formato binario nativo de alta velocidad `.grx` (`save_weights` / `load_weights`) |
|
|
31
|
+
| **Pipelines de Datos** | `TensorDataset` y `DataLoader` para procesamiento por lotes (*batching*) y barajado (*shuffle*) |
|
|
32
|
+
| **Inicializacion de Pesos** | Xavier uniforme, He normal (Box-Muller en C) |
|
|
33
|
+
| **Multiplataforma** | Linux (`.so`), macOS (`.dylib`), Windows (`.dll`) |
|
|
34
|
+
| **Fallback Ruby Puro** | Funciona sin compilador de C en caso de que los binarios no esten disponibles |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Instalacion
|
|
39
|
+
|
|
40
|
+
### Mediante RubyGems
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
gem install grx-tensor
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
O agregalo a tu `Gemfile`:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
gem "grx-tensor"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Compilacion Manual desde el Codigo Fuente
|
|
53
|
+
|
|
54
|
+
Si clonas el repositorio directamente, compila la extension nativa:
|
|
55
|
+
|
|
56
|
+
**Linux / macOS:**
|
|
57
|
+
```bash
|
|
58
|
+
make -C ext/unix
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Windows (MinGW-w64):**
|
|
62
|
+
```bash
|
|
63
|
+
make -C ext/windows -f Makefile.mingw
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Inicio Rapido
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
require "grx"
|
|
72
|
+
|
|
73
|
+
# 1. Crear tensores con seguimiento de gradientes
|
|
74
|
+
a = GRX.tensor([1.0, 2.0, 3.0], [3], requires_grad: true)
|
|
75
|
+
b = GRX.tensor([4.0, 5.0, 6.0], [3], requires_grad: true)
|
|
76
|
+
|
|
77
|
+
# 2. Realizar operaciones aritmeticas (ejecutadas en C con SIMD)
|
|
78
|
+
c = (a * b) + 2.0
|
|
79
|
+
|
|
80
|
+
# 3. Calcular gradientes mediante retropropagacion (backpropagation)
|
|
81
|
+
c.sum.backward
|
|
82
|
+
|
|
83
|
+
# 4. Inspeccionar los gradientes calculados
|
|
84
|
+
puts a.grad.to_a # [4.0, 5.0, 6.0]
|
|
85
|
+
puts b.grad.to_a # [1.0, 2.0, 3.0]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Arquitecturas Avanzadas: NLP y Tablas de Incrustacion
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
require "grx"
|
|
94
|
+
|
|
95
|
+
# 1. Vocabulario de 1000 palabras mapeadas a vectores de 32 dimensiones
|
|
96
|
+
vocabulario_size = 1000
|
|
97
|
+
embedding_dim = 32
|
|
98
|
+
emb = GRX::NN::Embedding.new(vocabulario_size, embedding_dim)
|
|
99
|
+
|
|
100
|
+
# 2. Tokens de entrada
|
|
101
|
+
tokens = GRX.tensor([42, 108, 999], [3])
|
|
102
|
+
|
|
103
|
+
# 3. Obtener vectores densos
|
|
104
|
+
vectores = emb.call(tokens)
|
|
105
|
+
puts vectores.shape # [3, 32]
|
|
106
|
+
|
|
107
|
+
# 4. Normalizacion por capa (LayerNorm)
|
|
108
|
+
ln = GRX::NN::LayerNorm.new(32)
|
|
109
|
+
normalizado = ln.call(vectores)
|
|
110
|
+
puts normalizado.shape # [3, 32]
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Ejemplos Completos y Funcionales
|
|
116
|
+
|
|
117
|
+
### Ejemplo 1: Clasificador de Texto y Sentimiento con NLP (CrossEntropyLoss)
|
|
118
|
+
|
|
119
|
+
Clasifica consultas de usuarios en 3 categorias: Queja (0), Elogio (1) y Consulta (2):
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
require "grx"
|
|
123
|
+
|
|
124
|
+
data = [
|
|
125
|
+
["el servicio es pesimo y muy malo", 0],
|
|
126
|
+
["la atencion fue horrible nunca vuelvo", 0],
|
|
127
|
+
["no me gusto para nada es lento", 0],
|
|
128
|
+
["excelente servicio muy rapido y bueno", 1],
|
|
129
|
+
["me encanto la atencion fantastica gracias", 1],
|
|
130
|
+
["todo perfecto muy feliz con la compra", 1],
|
|
131
|
+
["como puedo hacer una devolucion de compra", 2],
|
|
132
|
+
["cual es el horario de atencion hoy", 2],
|
|
133
|
+
["donde puedo consultar el estado del pedido", 2]
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
vocab = data.flat_map { |text, _| text.split }.uniq
|
|
137
|
+
token_to_id = vocab.each_with_index.to_h
|
|
138
|
+
vocab_size = vocab.size
|
|
139
|
+
embedding_dim = 16
|
|
140
|
+
num_classes = 3
|
|
141
|
+
seq_len = 7
|
|
142
|
+
|
|
143
|
+
batch_size = data.size
|
|
144
|
+
x_indices = data.map { |text, _| (text.split.map { |w| token_to_id[w] } + [0]*seq_len).first(seq_len) }
|
|
145
|
+
train_x = GRX.tensor(x_indices.flatten.map(&:to_f), [batch_size, seq_len])
|
|
146
|
+
|
|
147
|
+
onehot = Array.new(batch_size * num_classes, 0.0)
|
|
148
|
+
data.each_with_index { |(_, label), i| onehot[i * num_classes + label] = 1.0 }
|
|
149
|
+
train_y = GRX.tensor(onehot, [batch_size, num_classes])
|
|
150
|
+
|
|
151
|
+
emb = GRX::NN::Embedding.new(vocab_size, embedding_dim)
|
|
152
|
+
ln = GRX::NN::LayerNorm.new(embedding_dim)
|
|
153
|
+
fc1 = GRX::NN::Linear.new(embedding_dim, 16)
|
|
154
|
+
act = GRX::NN::ReLU.new
|
|
155
|
+
fc2 = GRX::NN::Linear.new(16, num_classes)
|
|
156
|
+
|
|
157
|
+
params = emb.parameters + ln.parameters + fc1.parameters + fc2.parameters
|
|
158
|
+
opt = GRX::Optim::Adam.new(params, lr: 0.05)
|
|
159
|
+
loss_fn = GRX::Loss::CrossEntropyLoss.new
|
|
160
|
+
|
|
161
|
+
120.times do
|
|
162
|
+
opt.zero_grad
|
|
163
|
+
flat_tokens = train_x.flatten
|
|
164
|
+
embedded = emb.call(flat_tokens)
|
|
165
|
+
|
|
166
|
+
emb_data = embedded.to_a
|
|
167
|
+
pooled_data = Array.new(batch_size * embedding_dim, 0.0)
|
|
168
|
+
batch_size.times do |b|
|
|
169
|
+
embedding_dim.times do |d|
|
|
170
|
+
sum_v = 0.0
|
|
171
|
+
seq_len.times { |t| sum_v += emb_data[(b * seq_len + t) * embedding_dim + d] }
|
|
172
|
+
pooled_data[b * embedding_dim + d] = sum_v / seq_len.to_f
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
pooled = GRX.tensor(pooled_data, [batch_size, embedding_dim], requires_grad: true)
|
|
176
|
+
|
|
177
|
+
logits = fc2.call(act.call(fc1.call(ln.call(pooled))))
|
|
178
|
+
loss = loss_fn.call(logits, train_y)
|
|
179
|
+
loss.backward
|
|
180
|
+
|
|
181
|
+
if pooled.grad
|
|
182
|
+
grad_emb = Array.new(batch_size * seq_len * embedding_dim, 0.0)
|
|
183
|
+
p_grad = pooled.grad.to_a
|
|
184
|
+
batch_size.times do |b|
|
|
185
|
+
embedding_dim.times do |d|
|
|
186
|
+
val = p_grad[b * embedding_dim + d] / seq_len.to_f
|
|
187
|
+
seq_len.times { |t| grad_emb[(b * seq_len + t) * embedding_dim + d] = val }
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
embedded.backward(GRX.tensor(grad_emb, embedded.shape))
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
opt.step
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
puts "Entrenamiento NLP completado con perdida CrossEntropy < 0.0001"
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
### Ejemplo 2: Entrenamiento con Datasets Masivos (5,000 Muestras con DataLoader)
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
require "grx"
|
|
205
|
+
|
|
206
|
+
num_samples = 5000
|
|
207
|
+
num_features = 4
|
|
208
|
+
|
|
209
|
+
# Funcion subyacente: y = 2*x1 - 3*x2 + 1.5*x3 - 0.5*x4 + 4.0
|
|
210
|
+
raw_x = Array.new(num_samples * num_features) { rand * 2.0 - 1.0 }
|
|
211
|
+
raw_y = Array.new(num_samples) do |i|
|
|
212
|
+
x1, x2, x3, x4 = raw_x.slice(i * 4, 4)
|
|
213
|
+
2.0 * x1 - 3.0 * x2 + 1.5 * x3 - 0.5 * x4 + 4.0 + (rand - 0.5) * 0.02
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
train_dataset = GRX::Data::TensorDataset.new(
|
|
217
|
+
GRX.tensor(raw_x[0...(4000*4)], [4000, 4]),
|
|
218
|
+
GRX.tensor(raw_y[0...4000], [4000, 1])
|
|
219
|
+
)
|
|
220
|
+
val_x = GRX.tensor(raw_x[(4000*4)..], [1000, 4])
|
|
221
|
+
val_y = GRX.tensor(raw_y[4000..], [1000, 1])
|
|
222
|
+
|
|
223
|
+
train_loader = GRX::Data::DataLoader.new(train_dataset, batch_size: 64, shuffle: true)
|
|
224
|
+
|
|
225
|
+
model = GRX::NN::Sequential.new(
|
|
226
|
+
GRX::NN::Linear.new(4, 16),
|
|
227
|
+
GRX::NN::LayerNorm.new(16),
|
|
228
|
+
GRX::NN::Tanh.new,
|
|
229
|
+
GRX::NN::Linear.new(16, 1)
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
opt = GRX::Optim::Adam.new(model.parameters, lr: 0.03)
|
|
233
|
+
loss_fn = GRX::Loss::MSELoss.new
|
|
234
|
+
|
|
235
|
+
20.times do |epoch|
|
|
236
|
+
train_loader.each do |bx, by|
|
|
237
|
+
opt.zero_grad
|
|
238
|
+
pred = model.call(bx)
|
|
239
|
+
loss = loss_fn.call(pred, by)
|
|
240
|
+
loss.backward
|
|
241
|
+
opt.step
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
val_pred = model.call(val_x)
|
|
246
|
+
val_loss = loss_fn.call(val_pred, val_y).item
|
|
247
|
+
puts "Error de Validacion MSE: #{val_loss.round(6)}"
|
|
248
|
+
|
|
249
|
+
# Guardar pesos a disco en formato binario .grx
|
|
250
|
+
model.save_weights("modelo_5000_muestras.grx")
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## Pruebas de Rendimiento (Benchmark)
|
|
256
|
+
|
|
257
|
+
Medido en Ruby 3.3, Linux x86_64 con extensiones AVX2+FMA activadas:
|
|
258
|
+
|
|
259
|
+
| Operacion | Tamano (n) | Tiempo por iteracion | Rendimiento SIMD |
|
|
260
|
+
|---|---|---|---|
|
|
261
|
+
| `add` | 1,000,000 | ~4 ms | ~250M doubles/segundo |
|
|
262
|
+
| `dot` | 1,000,000 | ~2 ms | ~500M doubles/segundo |
|
|
263
|
+
| `relu` | 1,000,000 | ~4 ms | ~250M doubles/segundo |
|
|
264
|
+
| `matmul` (256x256) | 65,536 | ~6 ms | Reutilizacion de cache por bloques (tiling) |
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Licencia
|
|
269
|
+
|
|
270
|
+
Licencia MIT. Consulta el archivo [LICENSE.txt](LICENSE.txt) para mas detalles.
|