grydra 1.0.0 → 2.0.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/LICENCE +34 -0
- data/README.md +1381 -80
- data/lib/grydra/activations.rb +70 -0
- data/lib/grydra/callbacks.rb +212 -0
- data/lib/grydra/documentation.rb +84 -0
- data/lib/grydra/initializers.rb +14 -0
- data/lib/grydra/layers/base.rb +10 -0
- data/lib/grydra/layers/conv.rb +106 -0
- data/lib/grydra/layers/dense.rb +17 -0
- data/lib/grydra/layers/lstm.rb +139 -0
- data/lib/grydra/losses.rb +119 -0
- data/lib/grydra/metrics.rb +75 -0
- data/lib/grydra/networks/easy_network.rb +161 -0
- data/lib/grydra/networks/main_network.rb +38 -0
- data/lib/grydra/networks/neural_network.rb +203 -0
- data/lib/grydra/networks/neuron.rb +80 -0
- data/lib/grydra/normalization.rb +77 -0
- data/lib/grydra/optimizers.rb +162 -0
- data/lib/grydra/preprocessing/data.rb +48 -0
- data/lib/grydra/preprocessing/pca.rb +132 -0
- data/lib/grydra/preprocessing/text.rb +62 -0
- data/lib/grydra/regularization.rb +19 -0
- data/lib/grydra/training/cross_validation.rb +35 -0
- data/lib/grydra/training/hyperparameter_search.rb +46 -0
- data/lib/grydra/utils/examples.rb +183 -0
- data/lib/grydra/utils/persistence.rb +94 -0
- data/lib/grydra/utils/visualization.rb +105 -0
- data/lib/grydra/version.rb +3 -0
- data/lib/grydra.rb +162 -2
- metadata +96 -17
- data/lib/gr/core.rb +0 -1926
- data/lib/gr/version.rb +0 -3
data/README.md
CHANGED
|
@@ -1,119 +1,1420 @@
|
|
|
1
|
-
# GRYDRA
|
|
1
|
+
# GRYDRA v2.0 - Neural Networks for Ruby
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
[](https://www.gnu.org/licenses/gpl-3.0.html)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* **Neural Network Architectures:** Build standard feedforward networks, multi-subnet ensembles (`MainNetwork`), and simplified interfaces (`EasyNetwork`).
|
|
8
|
-
* **Multiple Activation Functions:** Includes Tanh, Sigmoid, ReLU, Leaky ReLU, Swish, GELU, and more.
|
|
9
|
-
* **Advanced Optimizers:** Supports the Adam optimizer for efficient training.
|
|
10
|
-
* **Regularization Techniques:** Includes L1, L2 regularization, and Dropout.
|
|
11
|
-
* **Data Preprocessing:** Offers Min-Max and Z-Score normalization. Includes utilities for handling categorical (hash) and text data (vocabulary creation, vectorization, TF-IDF).
|
|
12
|
-
* **Training Features:** Supports mini-batch training, early stopping, learning rate decay, and customizable parameters.
|
|
13
|
-
* **Evaluation Metrics:** Provides MSE, MAE, Accuracy, Precision, Recall, F1-Score, Confusion Matrix, and AUC-ROC.
|
|
14
|
-
* **Cross-Validation:** Includes k-fold cross-validation for robust model evaluation.
|
|
15
|
-
* **Model Persistence:** Save and load trained models and vocabularies using Ruby's `Marshal`.
|
|
16
|
-
* **Analysis Tools:** Gradient analysis and ASCII visualization of network architecture.
|
|
17
|
-
* **Hyperparameter Search:** Basic grid search functionality.
|
|
7
|
+
A complete, modular, and powerful neural network library for Ruby.
|
|
18
8
|
|
|
19
9
|
## Installation
|
|
20
10
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
```bash
|
|
12
|
+
gem install grydra
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
require 'grydra'
|
|
19
|
+
|
|
20
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: true)
|
|
21
|
+
data_input = [[1, 2], [2, 3], [3, 4], [4, 5]]
|
|
22
|
+
data_output = [[3], [5], [7], [9]]
|
|
23
|
+
|
|
24
|
+
model.train_numerical(data_input, data_output, [[4, 1]], 0.1, 1000, :max)
|
|
25
|
+
predictions = model.predict_numerical([[5, 6]])
|
|
26
|
+
puts predictions # => [[11.0]]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Examples - From Simple to Complex
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
### Level 1: Basic Examples
|
|
26
32
|
|
|
27
|
-
|
|
33
|
+
#### Example 1.1: Simple Addition
|
|
28
34
|
|
|
29
35
|
```ruby
|
|
30
36
|
require 'grydra'
|
|
31
37
|
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
data_output = [[65], [60], [75]]
|
|
38
|
+
# Learn to add two numbers
|
|
39
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
41
|
+
data_input = [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
|
|
42
|
+
data_output = [[2], [4], [6], [8], [10]]
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
network = GRYDRA::EasyNetwork.new(print_epochs = true)
|
|
44
|
+
model.train_numerical(data_input, data_output, [[3, 1]], 0.1, 500, :max)
|
|
41
45
|
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
46
|
+
# Test
|
|
47
|
+
result = model.predict_numerical([[6, 6]])
|
|
48
|
+
puts "6 + 6 = #{result[0][0].round(0)}" # => 12
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### Example 1.2: Temperature Conversion (Celsius to Fahrenheit)
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require 'grydra'
|
|
55
|
+
|
|
56
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
57
|
+
|
|
58
|
+
# Training data: Celsius -> Fahrenheit
|
|
59
|
+
celsius = [[0], [10], [20], [30], [40], [100]]
|
|
60
|
+
fahrenheit = [[32], [50], [68], [86], [104], [212]]
|
|
61
|
+
|
|
62
|
+
model.train_numerical(celsius, fahrenheit, [[3, 1]], 0.1, 1000, :max)
|
|
63
|
+
|
|
64
|
+
# Convert 25°C to Fahrenheit
|
|
65
|
+
result = model.predict_numerical([[25]])
|
|
66
|
+
puts "25°C = #{result[0][0].round(1)}°F" # => ~77°F
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### Example 1.3: Simple Classification (Pass/Fail)
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
require 'grydra'
|
|
73
|
+
|
|
74
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
75
|
+
|
|
76
|
+
# Student scores -> Pass (1) or Fail (0)
|
|
77
|
+
scores = [[45], [55], [65], [75], [85], [35], [95], [40]]
|
|
78
|
+
results = [[0], [1], [1], [1], [1], [0], [1], [0]]
|
|
79
|
+
|
|
80
|
+
model.train_numerical(scores, results, [[3, 1]], 0.1, 1000, :max)
|
|
81
|
+
|
|
82
|
+
# Predict for score 70
|
|
83
|
+
prediction = model.predict_numerical([[70]])
|
|
84
|
+
pass_fail = prediction[0][0] > 0.5 ? "PASS" : "FAIL"
|
|
85
|
+
puts "Score 70: #{pass_fail}"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Level 2: Intermediate Examples
|
|
89
|
+
|
|
90
|
+
#### Example 2.1: House Price Prediction
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
require 'grydra'
|
|
94
|
+
|
|
95
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
96
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
97
|
+
|
|
98
|
+
# [sqft, bedrooms, age]
|
|
99
|
+
houses = [
|
|
100
|
+
[1200, 2, 5], [1500, 3, 3], [1800, 3, 8],
|
|
101
|
+
[2000, 4, 2], [2200, 4, 10], [1000, 2, 15]
|
|
102
|
+
]
|
|
103
|
+
prices = [[250000], [300000], [280000], [350000], [320000], [200000]]
|
|
104
|
+
|
|
105
|
+
model.train_numerical(houses, prices, [[6, 4, 1]], 0.05, 2000, :max,
|
|
106
|
+
lambda_l2: 0.001, patience: 100)
|
|
107
|
+
|
|
108
|
+
# Predict price for 1600 sqft, 3 bed, 4 years old
|
|
109
|
+
new_house = [[1600, 3, 4]]
|
|
110
|
+
price = model.predict_numerical(new_house)
|
|
111
|
+
puts "Predicted price: $#{price[0][0].round(0)}"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
#### Example 2.2: Customer Churn Prediction
|
|
116
|
+
|
|
117
|
+
```ruby
|
|
118
|
+
require 'grydra'
|
|
119
|
+
|
|
120
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
121
|
+
|
|
122
|
+
# [age, monthly_spend, months_active, support_tickets]
|
|
123
|
+
customers = [
|
|
124
|
+
[25, 50, 3, 5], [45, 200, 24, 0], [35, 100, 12, 2],
|
|
125
|
+
[55, 180, 36, 1], [28, 40, 2, 8], [50, 190, 30, 0]
|
|
126
|
+
]
|
|
127
|
+
churn = [[1], [0], [0], [0], [1], [0]] # 1=will churn, 0=will stay
|
|
128
|
+
|
|
129
|
+
model.train_numerical(customers, churn, [[6, 4, 1]], 0.1, 1500, :zscore,
|
|
130
|
+
dropout: true, dropout_rate: 0.3)
|
|
131
|
+
|
|
132
|
+
# Predict for new customer
|
|
133
|
+
new_customer = [[30, 75, 6, 3]]
|
|
134
|
+
probability = model.predict_numerical(new_customer, :zscore)
|
|
135
|
+
risk = probability[0][0] > 0.5 ? "HIGH RISK" : "LOW RISK"
|
|
136
|
+
puts "Churn probability: #{(probability[0][0] * 100).round(1)}% - #{risk}"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
#### Example 2.3: Sentiment Analysis
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
require 'grydra'
|
|
143
|
+
|
|
144
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
145
|
+
|
|
146
|
+
texts = [
|
|
147
|
+
"I love this product", "Amazing quality",
|
|
148
|
+
"Terrible experience", "Waste of money",
|
|
149
|
+
"Highly recommend", "Very disappointed",
|
|
150
|
+
"Excellent service", "Poor quality"
|
|
151
|
+
]
|
|
152
|
+
sentiments = [[1], [1], [0], [0], [1], [0], [1], [0]]
|
|
153
|
+
|
|
154
|
+
model.train_text(texts, sentiments, [[8, 4, 1]], 0.1, 1000, :max,
|
|
155
|
+
lambda_l1: 0.001, dropout: true, dropout_rate: 0.2)
|
|
156
|
+
|
|
157
|
+
# Analyze new reviews
|
|
158
|
+
new_reviews = ["Best purchase ever", "Complete garbage"]
|
|
159
|
+
predictions = model.predict_text(new_reviews, :max)
|
|
160
|
+
|
|
161
|
+
new_reviews.each_with_index do |review, i|
|
|
162
|
+
score = predictions[i][0]
|
|
163
|
+
sentiment = score > 0.5 ? "POSITIVE" : "NEGATIVE"
|
|
164
|
+
puts "\"#{review}\" => #{sentiment}"
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Level 3: Advanced Examples
|
|
169
|
+
|
|
170
|
+
#### Example 3.1: Multi-Output Prediction (Weather)
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
require 'grydra'
|
|
174
|
+
|
|
175
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
176
|
+
|
|
177
|
+
# [hour, latitude, longitude] -> [temperature, humidity]
|
|
178
|
+
conditions = [
|
|
179
|
+
[6, 40.7, -74.0], [12, 40.7, -74.0], [18, 40.7, -74.0],
|
|
180
|
+
[6, 34.0, -118.2], [12, 34.0, -118.2], [18, 34.0, -118.2]
|
|
181
|
+
]
|
|
182
|
+
weather = [[15, 70], [25, 50], [20, 60], [18, 40], [30, 30], [25, 35]]
|
|
183
|
+
|
|
184
|
+
model.train_numerical(conditions, weather, [[6, 4, 2]], 0.05, 2000, :max)
|
|
185
|
+
|
|
186
|
+
# Predict weather at 2 PM in New York
|
|
187
|
+
prediction = model.predict_numerical([[14, 40.7, -74.0]])
|
|
188
|
+
puts "Temperature: #{prediction[0][0].round(1)}°C"
|
|
189
|
+
puts "Humidity: #{prediction[0][1].round(1)}%"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### Example 3.2: Time Series Prediction (Stock Prices)
|
|
193
|
+
|
|
194
|
+
```ruby
|
|
195
|
+
require 'grydra'
|
|
196
|
+
|
|
197
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
198
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
199
|
+
|
|
200
|
+
# Last 3 days -> Next day price
|
|
201
|
+
sequences = [
|
|
202
|
+
[100, 102, 101], [102, 101, 103], [101, 103, 105],
|
|
203
|
+
[103, 105, 104], [105, 104, 106], [104, 106, 108]
|
|
204
|
+
]
|
|
205
|
+
next_prices = [[103], [105], [104], [106], [108], [107]]
|
|
206
|
+
|
|
207
|
+
model.train_numerical(sequences, next_prices, [[6, 4, 1]], 0.01, 2000, :max,
|
|
208
|
+
lambda_l2: 0.01, patience: 100)
|
|
209
|
+
|
|
210
|
+
# Predict next price
|
|
211
|
+
last_3_days = [[106, 108, 107]]
|
|
212
|
+
prediction = model.predict_numerical(last_3_days)
|
|
213
|
+
puts "Predicted next price: $#{prediction[0][0].round(2)}"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Example 3.3: Image Classification (Simplified)
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
require 'grydra'
|
|
220
|
+
|
|
221
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
222
|
+
|
|
223
|
+
# Simplified 4x4 "images" (16 pixels) -> 2 classes
|
|
224
|
+
images = [
|
|
225
|
+
[1,1,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0], # Pattern A
|
|
226
|
+
[0,0,1,1, 0,0,1,1, 0,0,0,0, 0,0,0,0], # Pattern B
|
|
227
|
+
[1,1,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0], # Pattern A
|
|
228
|
+
[0,0,1,1, 0,0,1,1, 0,0,0,0, 0,0,0,0] # Pattern B
|
|
229
|
+
]
|
|
230
|
+
labels = [[0], [1], [0], [1]]
|
|
231
|
+
|
|
232
|
+
model.train_numerical(images, labels, [[8, 4, 1]], 0.1, 1000, :max)
|
|
233
|
+
|
|
234
|
+
# Classify new image
|
|
235
|
+
new_image = [[1,1,0,0, 1,1,0,0, 0,0,0,0, 0,0,0,0]]
|
|
236
|
+
prediction = model.predict_numerical(new_image)
|
|
237
|
+
class_label = prediction[0][0] > 0.5 ? "Pattern B" : "Pattern A"
|
|
238
|
+
puts "Classification: #{class_label}"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Level 4: Expert Examples
|
|
242
|
+
|
|
243
|
+
#### Example 4.1: Cross-Validation
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
require 'grydra'
|
|
247
|
+
|
|
248
|
+
# Generate data
|
|
249
|
+
synthetic = GRYDRA::Preprocessing::Data.generate_synthetic_data(100, 3, 0.1, 42)
|
|
250
|
+
data_x = synthetic[:data]
|
|
251
|
+
data_y = synthetic[:labels]
|
|
252
|
+
|
|
253
|
+
# 5-fold cross-validation
|
|
254
|
+
result = GRYDRA::Training::CrossValidation.cross_validation(data_x, data_y, 5) do |train_x, train_y, test_x, test_y|
|
|
255
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
256
|
+
model.train_numerical(train_x, train_y, [[4, 3, 1]], 0.1, 500, :max, patience: 50)
|
|
257
|
+
|
|
258
|
+
predictions = model.predict_numerical(test_x)
|
|
259
|
+
GRYDRA::Metrics.mse(predictions.flatten, test_y.flatten)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
puts "Cross-Validation Results:"
|
|
263
|
+
puts " Average Error: #{result[:average].round(6)}"
|
|
264
|
+
puts " Std Deviation: #{result[:deviation].round(6)}"
|
|
265
|
+
puts " Fold Errors: #{result[:errors].map { |e| e.round(4) }}"
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
#### Example 4.2: Hyperparameter Search
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
require 'grydra'
|
|
273
|
+
|
|
274
|
+
data_x = [[1], [2], [3], [4], [5], [6], [7], [8]]
|
|
275
|
+
data_y = [[2], [4], [6], [8], [10], [12], [14], [16]]
|
|
276
|
+
|
|
277
|
+
param_grid = [
|
|
278
|
+
{ rate: 0.01, epochs: 800, lambda_l2: 0.001 },
|
|
279
|
+
{ rate: 0.05, epochs: 600, lambda_l2: 0.01 },
|
|
280
|
+
{ rate: 0.1, epochs: 500, lambda_l2: 0.001 }
|
|
281
|
+
]
|
|
51
282
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
283
|
+
best = GRYDRA::Training::HyperparameterSearch.hyperparameter_search(
|
|
284
|
+
data_x, data_y, param_grid, verbose: true
|
|
285
|
+
) do |params, x, y|
|
|
286
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
287
|
+
model.train_numerical(x, y, [[3, 1]], params[:rate], params[:epochs], :max,
|
|
288
|
+
lambda_l2: params[:lambda_l2], patience: 50)
|
|
289
|
+
|
|
290
|
+
predictions = model.predict_numerical(x)
|
|
291
|
+
GRYDRA::Metrics.mse(predictions.flatten, y.flatten)
|
|
292
|
+
end
|
|
55
293
|
|
|
56
|
-
puts "
|
|
294
|
+
puts "\nBest parameters: #{best[:parameters]}"
|
|
295
|
+
puts "Best score: #{best[:score].round(6)}"
|
|
57
296
|
```
|
|
58
297
|
|
|
59
|
-
|
|
298
|
+
#### Example 4.3: PCA for Dimensionality Reduction
|
|
60
299
|
|
|
61
300
|
```ruby
|
|
62
301
|
require 'grydra'
|
|
63
302
|
|
|
64
|
-
#
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
303
|
+
# High-dimensional data (5 features)
|
|
304
|
+
data = [
|
|
305
|
+
[2.5, 2.4, 1.2, 0.8, 3.1],
|
|
306
|
+
[0.5, 0.7, 0.3, 0.2, 0.9],
|
|
307
|
+
[2.2, 2.9, 1.5, 1.0, 2.8],
|
|
308
|
+
[1.9, 2.2, 1.0, 0.7, 2.5],
|
|
309
|
+
[3.1, 3.0, 1.8, 1.2, 3.5]
|
|
69
310
|
]
|
|
70
311
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
312
|
+
# Reduce to 2 dimensions
|
|
313
|
+
pca_result = GRYDRA::Preprocessing::PCA.pca(data, components: 2)
|
|
314
|
+
|
|
315
|
+
puts "PCA Results:"
|
|
316
|
+
puts " Explained Variance: #{pca_result[:explained_variance].map { |v| (v * 100).round(2) }}%"
|
|
317
|
+
puts " Eigenvalues: #{pca_result[:eigenvalues].map { |v| v.round(4) }}"
|
|
318
|
+
|
|
319
|
+
# Transform new data
|
|
320
|
+
new_data = [[2.0, 2.5, 1.1, 0.8, 2.7]]
|
|
321
|
+
transformed = GRYDRA::Preprocessing::PCA.transform(new_data, pca_result)
|
|
322
|
+
puts " Transformed: #{transformed[0].map { |v| v.round(3) }}"
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
#### Example 4.4: Ensemble with Multiple Subnets
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
require 'grydra'
|
|
329
|
+
|
|
330
|
+
# Create ensemble network
|
|
331
|
+
network = GRYDRA::Networks::MainNetwork.new(print_epochs: false)
|
|
332
|
+
|
|
333
|
+
# Add multiple subnets with different architectures
|
|
334
|
+
network.add_subnet([2, 4, 1], [:tanh, :sigmoid])
|
|
335
|
+
network.add_subnet([2, 3, 1], [:relu, :tanh])
|
|
336
|
+
network.add_subnet([2, 5, 1], [:sigmoid, :sigmoid])
|
|
337
|
+
|
|
338
|
+
# XOR problem
|
|
339
|
+
inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]
|
|
340
|
+
outputs = [[0], [1], [1], [0]]
|
|
341
|
+
|
|
342
|
+
# Train all subnets
|
|
343
|
+
network.train_subnets(
|
|
344
|
+
[
|
|
345
|
+
{input: inputs, output: outputs},
|
|
346
|
+
{input: inputs, output: outputs},
|
|
347
|
+
{input: inputs, output: outputs}
|
|
348
|
+
],
|
|
349
|
+
0.5, 3000, patience: 150
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
# Test with ensemble
|
|
353
|
+
puts "XOR Results (Ensemble):"
|
|
354
|
+
inputs.each do |input|
|
|
355
|
+
output = network.combine_results(input)
|
|
356
|
+
result = output[0] > 0.5 ? 1 : 0
|
|
357
|
+
puts " #{input} => #{output[0].round(3)} (#{result})"
|
|
358
|
+
end
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
#### Example 4.5: Save and Load Models
|
|
362
|
+
|
|
363
|
+
```ruby
|
|
364
|
+
require 'grydra'
|
|
365
|
+
|
|
366
|
+
# Train model
|
|
367
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
368
|
+
data_input = [[1, 2], [2, 3], [3, 4], [4, 5]]
|
|
369
|
+
data_output = [[3], [5], [7], [9]]
|
|
370
|
+
model.train_numerical(data_input, data_output, [[4, 1]], 0.1, 1000, :max)
|
|
371
|
+
|
|
372
|
+
# Save model
|
|
373
|
+
GRYDRA::Utils::Persistence.save_model(model, "my_model", "./models")
|
|
374
|
+
|
|
375
|
+
# Later... load and use
|
|
376
|
+
loaded_model = GRYDRA::Utils::Persistence.load_model("my_model", "./models")
|
|
377
|
+
prediction = loaded_model.predict_numerical([[5, 6]])
|
|
378
|
+
puts "Prediction: #{prediction[0][0].round(2)}"
|
|
379
|
+
|
|
380
|
+
# Show model summary
|
|
381
|
+
GRYDRA::Utils::Persistence.summary_model(loaded_model)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### Example 4.6: Using Different Loss Functions
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
require 'grydra'
|
|
388
|
+
|
|
389
|
+
predictions = [0.9, 0.2, 0.8, 0.1, 0.95]
|
|
390
|
+
targets = [1, 0, 1, 0, 1]
|
|
391
|
+
|
|
392
|
+
puts "Loss Function Comparison:"
|
|
393
|
+
puts " MSE: #{GRYDRA::Losses.mse(predictions, targets).round(4)}"
|
|
394
|
+
puts " MAE: #{GRYDRA::Losses.mae(predictions, targets).round(4)}"
|
|
395
|
+
puts " Huber: #{GRYDRA::Losses.huber(predictions, targets, delta: 1.0).round(4)}"
|
|
396
|
+
puts " Binary Cross-Entropy: #{GRYDRA::Losses.binary_crossentropy(predictions, targets).round(4)}"
|
|
397
|
+
puts " Log-Cosh: #{GRYDRA::Losses.log_cosh(predictions, targets).round(4)}"
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
#### Example 4.7: Network Visualization
|
|
401
|
+
|
|
402
|
+
```ruby
|
|
403
|
+
require 'grydra'
|
|
404
|
+
|
|
405
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
406
|
+
data_input = [[1, 2], [2, 3], [3, 4]]
|
|
407
|
+
data_output = [[3], [5], [7]]
|
|
408
|
+
model.train_numerical(data_input, data_output, [[4, 3, 1]], 0.1, 500, :max)
|
|
409
|
+
|
|
410
|
+
# Visualize architecture
|
|
411
|
+
GRYDRA::Utils::Visualization.plot_architecture_ascii(model.network)
|
|
412
|
+
|
|
413
|
+
# Analyze gradients
|
|
414
|
+
analysis = GRYDRA::Utils::Visualization.analyze_gradients(model.network)
|
|
415
|
+
puts "\nGradient Analysis:"
|
|
416
|
+
puts " Average: #{analysis[:average].round(6)}"
|
|
417
|
+
puts " Max: #{analysis[:maximum].round(6)}"
|
|
418
|
+
puts " Min: #{analysis[:minimum].round(6)}"
|
|
419
|
+
puts " Total Parameters: #{analysis[:total_parameters]}"
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Level 5: Expert Advanced Examples
|
|
423
|
+
|
|
424
|
+
#### Example 5.1: Custom Network with Different Optimizers
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
require 'grydra'
|
|
428
|
+
|
|
429
|
+
# Compare different optimizers on the same problem
|
|
430
|
+
data_x = Array.new(50) { |i| [i / 10.0] }
|
|
431
|
+
data_y = data_x.map { |x| [Math.sin(x[0]) * 10 + 5] }
|
|
432
|
+
|
|
433
|
+
optimizers = {
|
|
434
|
+
'Adam' => GRYDRA::Optimizers::Adam.new(alpha: 0.01),
|
|
435
|
+
'SGD with Momentum' => GRYDRA::Optimizers::SGD.new(learning_rate: 0.1, momentum: 0.9),
|
|
436
|
+
'RMSprop' => GRYDRA::Optimizers::RMSprop.new(learning_rate: 0.01),
|
|
437
|
+
'AdaGrad' => GRYDRA::Optimizers::AdaGrad.new(learning_rate: 0.1),
|
|
438
|
+
'AdamW' => GRYDRA::Optimizers::AdamW.new(alpha: 0.01, weight_decay: 0.01)
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
optimizers.each do |name, optimizer|
|
|
442
|
+
network = GRYDRA::Networks::NeuralNetwork.new([1, 8, 8, 1], activations: [:relu, :relu, :tanh])
|
|
443
|
+
network.instance_variable_set(:@optimizer, optimizer)
|
|
444
|
+
|
|
445
|
+
network.train(data_x, data_y, 0.01, 500, patience: 100)
|
|
446
|
+
|
|
447
|
+
test_x = [[2.5]]
|
|
448
|
+
prediction = network.calculate_outputs(test_x[0])
|
|
449
|
+
actual = Math.sin(2.5) * 10 + 5
|
|
450
|
+
error = (prediction[0] - actual).abs
|
|
451
|
+
|
|
452
|
+
puts "#{name}: Prediction=#{prediction[0].round(3)}, Actual=#{actual.round(3)}, Error=#{error.round(3)}"
|
|
453
|
+
end
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
#### Example 5.2: Using Callbacks for Advanced Training Control
|
|
457
|
+
|
|
458
|
+
```ruby
|
|
459
|
+
require 'grydra'
|
|
460
|
+
|
|
461
|
+
# Create network
|
|
462
|
+
network = GRYDRA::Networks::NeuralNetwork.new([2, 6, 4, 1],
|
|
463
|
+
print_epochs: true,
|
|
464
|
+
activations: [:relu, :relu, :sigmoid])
|
|
74
465
|
|
|
75
|
-
|
|
466
|
+
# XOR problem
|
|
467
|
+
inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]
|
|
468
|
+
outputs = [[0], [1], [1], [0]]
|
|
76
469
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
epochs = 10000,
|
|
84
|
-
normalization = :max
|
|
470
|
+
# Setup callbacks
|
|
471
|
+
early_stopping = GRYDRA::Callbacks::EarlyStopping.new(
|
|
472
|
+
monitor: :loss,
|
|
473
|
+
patience: 50,
|
|
474
|
+
min_delta: 0.001,
|
|
475
|
+
restore_best: true
|
|
85
476
|
)
|
|
86
477
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
478
|
+
lr_scheduler = GRYDRA::Callbacks::LearningRateScheduler.new do |epoch, current_lr|
|
|
479
|
+
# Reduce learning rate every 100 epochs
|
|
480
|
+
epoch % 100 == 0 && epoch > 0 ? current_lr * 0.9 : current_lr
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
reduce_lr = GRYDRA::Callbacks::ReduceLROnPlateau.new(
|
|
484
|
+
monitor: :loss,
|
|
485
|
+
factor: 0.5,
|
|
486
|
+
patience: 30,
|
|
487
|
+
min_lr: 1e-6
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
csv_logger = GRYDRA::Callbacks::CSVLogger.new('training_log.csv')
|
|
491
|
+
|
|
492
|
+
# Train with callbacks (manual implementation for demonstration)
|
|
493
|
+
network.train(inputs, outputs, 0.5, 1000, patience: 100)
|
|
494
|
+
|
|
495
|
+
puts "\n✅ Training completed with callbacks"
|
|
496
|
+
puts "Check 'training_log.csv' for detailed logs"
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
#### Example 5.3: Building Custom Architecture with Layers
|
|
500
|
+
|
|
501
|
+
```ruby
|
|
502
|
+
require 'grydra'
|
|
503
|
+
|
|
504
|
+
# Build a custom network manually using layers
|
|
505
|
+
class CustomNetwork
|
|
506
|
+
attr_accessor :layers
|
|
507
|
+
|
|
508
|
+
def initialize
|
|
509
|
+
@layers = []
|
|
510
|
+
# Input: 10 features
|
|
511
|
+
@layers << GRYDRA::Layers::Dense.new(16, 10, :relu)
|
|
512
|
+
@layers << GRYDRA::Layers::Dense.new(8, 16, :leaky_relu)
|
|
513
|
+
@layers << GRYDRA::Layers::Dense.new(4, 8, :swish)
|
|
514
|
+
@layers << GRYDRA::Layers::Dense.new(1, 4, :sigmoid)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def forward(input, dropout: false, dropout_rate: 0.3)
|
|
518
|
+
output = input
|
|
519
|
+
@layers.each_with_index do |layer, idx|
|
|
520
|
+
# Apply dropout to hidden layers only
|
|
521
|
+
apply_drop = dropout && idx < @layers.size - 1
|
|
522
|
+
output = layer.calculate_outputs(output, apply_drop, dropout_rate)
|
|
523
|
+
end
|
|
524
|
+
output
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
# Create and test custom network
|
|
529
|
+
custom_net = CustomNetwork.new
|
|
530
|
+
input = Array.new(10) { rand }
|
|
531
|
+
output = custom_net.forward(input, dropout: true)
|
|
532
|
+
|
|
533
|
+
puts "Custom Network Output: #{output.map { |v| v.round(4) }}"
|
|
534
|
+
puts "Network has #{custom_net.layers.size} layers"
|
|
535
|
+
custom_net.layers.each_with_index do |layer, i|
|
|
536
|
+
puts " Layer #{i + 1}: #{layer.neurons.size} neurons, activation: #{layer.activation}"
|
|
537
|
+
end
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
#### Example 5.4: Multi-Task Learning with Shared Layers
|
|
541
|
+
|
|
542
|
+
```ruby
|
|
543
|
+
require 'grydra'
|
|
544
|
+
|
|
545
|
+
# Simulate multi-task learning: predict both price and category
|
|
546
|
+
# [sqft, bedrooms, location_score] -> [price, category]
|
|
547
|
+
|
|
548
|
+
houses = [
|
|
549
|
+
[1200, 2, 0.7], [1500, 3, 0.8], [1800, 3, 0.9],
|
|
550
|
+
[2000, 4, 0.85], [2200, 4, 0.95], [1000, 2, 0.6]
|
|
551
|
+
]
|
|
552
|
+
|
|
553
|
+
# Task 1: Price (regression)
|
|
554
|
+
prices = [[250], [300], [280], [350], [320], [200]]
|
|
555
|
+
|
|
556
|
+
# Task 2: Category (classification: 0=budget, 1=luxury)
|
|
557
|
+
categories = [[0], [0], [1], [1], [1], [0]]
|
|
558
|
+
|
|
559
|
+
# Create two networks with similar architecture
|
|
560
|
+
price_model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
561
|
+
category_model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
562
|
+
|
|
563
|
+
# Train both tasks
|
|
564
|
+
puts "Training price prediction model..."
|
|
565
|
+
price_model.train_numerical(houses, prices, [[8, 6, 1]], 0.05, 1500, :zscore, lambda_l2: 0.001)
|
|
90
566
|
|
|
91
|
-
puts "
|
|
567
|
+
puts "Training category classification model..."
|
|
568
|
+
category_model.train_numerical(houses, categories, [[8, 4, 1]], 0.1, 1500, :zscore, dropout: true)
|
|
569
|
+
|
|
570
|
+
# Predict on new house
|
|
571
|
+
new_house = [[1600, 3, 0.75]]
|
|
572
|
+
predicted_price = price_model.predict_numerical(new_house, :zscore)
|
|
573
|
+
predicted_category = category_model.predict_numerical(new_house, :zscore)
|
|
574
|
+
|
|
575
|
+
puts "\n🏠 Multi-Task Prediction:"
|
|
576
|
+
puts " Predicted Price: $#{predicted_price[0][0].round(0)}k"
|
|
577
|
+
puts " Category: #{predicted_category[0][0] > 0.5 ? 'Luxury' : 'Budget'} (#{(predicted_category[0][0] * 100).round(1)}%)"
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### Example 5.5: Time Series with LSTM Layer
|
|
581
|
+
|
|
582
|
+
```ruby
|
|
583
|
+
require 'grydra'
|
|
584
|
+
|
|
585
|
+
# Create LSTM layer for sequence processing
|
|
586
|
+
lstm = GRYDRA::Layers::LSTM.new(units: 8, inputs_per_unit: 3, return_sequences: false)
|
|
587
|
+
|
|
588
|
+
# Time series data: [day1, day2, day3] -> predict day4
|
|
589
|
+
sequences = [
|
|
590
|
+
[[100], [102], [101]],
|
|
591
|
+
[[102], [101], [103]],
|
|
592
|
+
[[101], [103], [105]],
|
|
593
|
+
[[103], [105], [104]]
|
|
594
|
+
]
|
|
595
|
+
|
|
596
|
+
puts "LSTM Layer Processing:"
|
|
597
|
+
sequences.each_with_index do |seq, i|
|
|
598
|
+
lstm.reset_state # Reset for each sequence
|
|
599
|
+
output = lstm.calculate_outputs(seq)
|
|
600
|
+
puts " Sequence #{i + 1}: #{seq.flatten} => Hidden State: #{output.map { |v| v.round(3) }}"
|
|
601
|
+
end
|
|
92
602
|
```
|
|
93
603
|
|
|
94
|
-
|
|
604
|
+
#### Example 5.6: Advanced Ensemble with Weighted Voting
|
|
605
|
+
|
|
606
|
+
```ruby
|
|
607
|
+
require 'grydra'
|
|
95
608
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
* `GRYDRA::NeuralNetwork`: Represents a single neural network (used internally by `MainNetwork`).
|
|
99
|
-
* `GRYDRA::Neuron`: Represents a single neuron within a layer.
|
|
100
|
-
* `GRYDRA::DenseLayer`: A standard fully connected layer.
|
|
101
|
-
* `GRYDRA::AdamOptimizer`: An implementation of the Adam optimizer.
|
|
609
|
+
# Create ensemble with different architectures and activations
|
|
610
|
+
ensemble = GRYDRA::Networks::MainNetwork.new(print_epochs: false)
|
|
102
611
|
|
|
103
|
-
|
|
612
|
+
# Add diverse subnets
|
|
613
|
+
ensemble.add_subnet([2, 8, 4, 1], [:relu, :relu, :sigmoid])
|
|
614
|
+
ensemble.add_subnet([2, 6, 3, 1], [:tanh, :tanh, :tanh])
|
|
615
|
+
ensemble.add_subnet([2, 10, 5, 1], [:leaky_relu, :swish, :sigmoid])
|
|
616
|
+
ensemble.add_subnet([2, 4, 4, 1], [:gelu, :relu, :sigmoid])
|
|
104
617
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
* `GRYDRA.load_vocabulary(name, path)`: Loads a vocabulary.
|
|
109
|
-
* `GRYDRA.describe_method(class_name, method_name)`: Provides information and examples for specific methods.
|
|
110
|
-
* `GRYDRA.list_methods_available()`: Lists all documented public methods.
|
|
111
|
-
* `GRYDRA.generate_example(num, filename, ext, path)`: Generates example scripts demonstrating usage.
|
|
618
|
+
# XOR problem
|
|
619
|
+
inputs = [[0, 0], [0, 1], [1, 0], [1, 1]]
|
|
620
|
+
outputs = [[0], [1], [1], [0]]
|
|
112
621
|
|
|
113
|
-
|
|
622
|
+
# Train all subnets
|
|
623
|
+
data = Array.new(4) { { input: inputs, output: outputs } }
|
|
624
|
+
ensemble.train_subnets(data, 0.7, 2000, patience: 200)
|
|
625
|
+
|
|
626
|
+
# Test with different voting strategies
|
|
627
|
+
puts "\n🎯 Ensemble Predictions:"
|
|
628
|
+
inputs.each do |input|
|
|
629
|
+
# Average voting
|
|
630
|
+
avg_output = ensemble.combine_results(input)
|
|
631
|
+
|
|
632
|
+
# Weighted voting (give more weight to better performing models)
|
|
633
|
+
weights = [0.4, 0.3, 0.2, 0.1]
|
|
634
|
+
weighted_output = ensemble.combine_results_weighted(input, weights)
|
|
635
|
+
|
|
636
|
+
puts "Input: #{input}"
|
|
637
|
+
puts " Average: #{avg_output[0].round(3)} => #{avg_output[0] > 0.5 ? 1 : 0}"
|
|
638
|
+
puts " Weighted: #{weighted_output[0].round(3)} => #{weighted_output[0] > 0.5 ? 1 : 0}"
|
|
639
|
+
end
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### Example 5.7: Real-World: Credit Card Fraud Detection
|
|
643
|
+
|
|
644
|
+
```ruby
|
|
645
|
+
require 'grydra'
|
|
646
|
+
|
|
647
|
+
# Simulate credit card transaction data
|
|
648
|
+
# [amount, time_of_day, merchant_category, distance_from_home, frequency]
|
|
649
|
+
transactions = [
|
|
650
|
+
[50, 14, 1, 2, 5], # Normal
|
|
651
|
+
[30, 10, 2, 1, 8], # Normal
|
|
652
|
+
[5000, 3, 5, 500, 1], # Fraud
|
|
653
|
+
[100, 18, 3, 5, 6], # Normal
|
|
654
|
+
[2000, 2, 4, 300, 1], # Fraud
|
|
655
|
+
[75, 12, 1, 3, 7], # Normal
|
|
656
|
+
[3500, 4, 5, 450, 1], # Fraud
|
|
657
|
+
[45, 16, 2, 2, 9] # Normal
|
|
658
|
+
]
|
|
659
|
+
|
|
660
|
+
labels = [[0], [0], [1], [0], [1], [0], [1], [0]] # 0=normal, 1=fraud
|
|
661
|
+
|
|
662
|
+
# Create model with class imbalance handling
|
|
663
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: true)
|
|
664
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
665
|
+
|
|
666
|
+
# Train with heavy regularization and dropout
|
|
667
|
+
model.train_numerical(transactions, labels, [[10, 8, 4, 1]], 0.05, 2000, :zscore,
|
|
668
|
+
lambda_l1: 0.001, lambda_l2: 0.01,
|
|
669
|
+
dropout: true, dropout_rate: 0.4,
|
|
670
|
+
patience: 150)
|
|
671
|
+
|
|
672
|
+
# Test on new transactions
|
|
673
|
+
new_transactions = [
|
|
674
|
+
[60, 15, 1, 2, 6], # Should be normal
|
|
675
|
+
[4000, 3, 5, 400, 1] # Should be fraud
|
|
676
|
+
]
|
|
114
677
|
|
|
115
|
-
|
|
678
|
+
predictions = model.predict_numerical(new_transactions, :zscore)
|
|
679
|
+
|
|
680
|
+
puts "\n💳 Fraud Detection Results:"
|
|
681
|
+
new_transactions.each_with_index do |trans, i|
|
|
682
|
+
prob = predictions[i][0]
|
|
683
|
+
status = prob > 0.5 ? "🚨 FRAUD" : "✅ NORMAL"
|
|
684
|
+
puts "Transaction #{i + 1}: #{trans}"
|
|
685
|
+
puts " Fraud Probability: #{(prob * 100).round(2)}% - #{status}"
|
|
686
|
+
end
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
#### Example 5.8: A/B Testing with Statistical Validation
|
|
690
|
+
|
|
691
|
+
```ruby
|
|
692
|
+
require 'grydra'
|
|
693
|
+
|
|
694
|
+
# Simulate A/B test data: [page_views, time_on_site, clicks] -> conversion
|
|
695
|
+
variant_a = [
|
|
696
|
+
[100, 120, 5], [150, 180, 8], [80, 90, 3],
|
|
697
|
+
[120, 150, 6], [90, 100, 4]
|
|
698
|
+
]
|
|
699
|
+
conversions_a = [[0], [1], [0], [1], [0]]
|
|
700
|
+
|
|
701
|
+
variant_b = [
|
|
702
|
+
[110, 140, 7], [160, 200, 10], [85, 110, 5],
|
|
703
|
+
[130, 170, 8], [95, 120, 6]
|
|
704
|
+
]
|
|
705
|
+
conversions_b = [[1], [1], [0], [1], [1]]
|
|
706
|
+
|
|
707
|
+
# Train separate models
|
|
708
|
+
model_a = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
709
|
+
model_b = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
710
|
+
|
|
711
|
+
model_a.train_numerical(variant_a, conversions_a, [[6, 4, 1]], 0.1, 1000, :max)
|
|
712
|
+
model_b.train_numerical(variant_b, conversions_b, [[6, 4, 1]], 0.1, 1000, :max)
|
|
713
|
+
|
|
714
|
+
# Cross-validate both variants
|
|
715
|
+
puts "📊 A/B Test Results:"
|
|
716
|
+
|
|
717
|
+
result_a = GRYDRA::Training::CrossValidation.cross_validation(variant_a, conversions_a, 3) do |train_x, train_y, test_x, test_y|
|
|
718
|
+
m = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
719
|
+
m.train_numerical(train_x, train_y, [[6, 4, 1]], 0.1, 500, :max)
|
|
720
|
+
preds = m.predict_numerical(test_x)
|
|
721
|
+
GRYDRA::Metrics.mse(preds.flatten, test_y.flatten)
|
|
722
|
+
end
|
|
723
|
+
|
|
724
|
+
result_b = GRYDRA::Training::CrossValidation.cross_validation(variant_b, conversions_b, 3) do |train_x, train_y, test_x, test_y|
|
|
725
|
+
m = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
726
|
+
m.train_numerical(train_x, train_y, [[6, 4, 1]], 0.1, 500, :max)
|
|
727
|
+
preds = m.predict_numerical(test_x)
|
|
728
|
+
GRYDRA::Metrics.mse(preds.flatten, test_y.flatten)
|
|
729
|
+
end
|
|
730
|
+
|
|
731
|
+
puts "Variant A - Avg Error: #{result_a[:average].round(4)}, StdDev: #{result_a[:deviation].round(4)}"
|
|
732
|
+
puts "Variant B - Avg Error: #{result_b[:average].round(4)}, StdDev: #{result_b[:deviation].round(4)}"
|
|
733
|
+
puts "Winner: #{result_b[:average] < result_a[:average] ? 'Variant B' : 'Variant A'}"
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
#### Example 5.9: Product Recommendation System
|
|
737
|
+
|
|
738
|
+
```ruby
|
|
739
|
+
require 'grydra'
|
|
740
|
+
|
|
741
|
+
# User features: [age, purchase_history, browsing_time, category_preference]
|
|
742
|
+
# Product features: [price_range, popularity, category_match]
|
|
743
|
+
# Combined: [user_features + product_features] -> purchase_probability
|
|
744
|
+
|
|
745
|
+
training_data = [
|
|
746
|
+
[25, 5, 120, 0.8, 1, 0.7, 0.9], # Young user, low price, high match -> buy
|
|
747
|
+
[45, 20, 200, 0.6, 3, 0.9, 0.7], # Mature user, high price, popular -> buy
|
|
748
|
+
[30, 2, 50, 0.3, 1, 0.3, 0.2], # Low engagement, low match -> no buy
|
|
749
|
+
[50, 15, 180, 0.9, 2, 0.8, 0.95], # High engagement, good match -> buy
|
|
750
|
+
[22, 1, 30, 0.2, 1, 0.2, 0.1], # New user, poor match -> no buy
|
|
751
|
+
[35, 10, 150, 0.7, 2, 0.6, 0.8] # Average user, decent match -> buy
|
|
752
|
+
]
|
|
753
|
+
|
|
754
|
+
labels = [[1], [1], [0], [1], [0], [1]]
|
|
755
|
+
|
|
756
|
+
# Train recommendation model
|
|
757
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
758
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
759
|
+
|
|
760
|
+
model.train_numerical(training_data, labels, [[12, 8, 4, 1]], 0.05, 2000, :zscore,
|
|
761
|
+
lambda_l2: 0.001, dropout: true, dropout_rate: 0.2,
|
|
762
|
+
patience: 150)
|
|
763
|
+
|
|
764
|
+
# Recommend products for new user-product pairs
|
|
765
|
+
new_pairs = [
|
|
766
|
+
[28, 3, 90, 0.5, 1, 0.5, 0.6], # Young user, budget product
|
|
767
|
+
[40, 12, 160, 0.8, 3, 0.85, 0.9] # Mature user, premium product
|
|
768
|
+
]
|
|
769
|
+
|
|
770
|
+
predictions = model.predict_numerical(new_pairs, :zscore)
|
|
771
|
+
|
|
772
|
+
puts "\n🛍️ Product Recommendations:"
|
|
773
|
+
new_pairs.each_with_index do |pair, i|
|
|
774
|
+
prob = predictions[i][0]
|
|
775
|
+
recommendation = prob > 0.5 ? "✅ RECOMMEND" : "❌ DON'T RECOMMEND"
|
|
776
|
+
puts "User-Product Pair #{i + 1}:"
|
|
777
|
+
puts " Purchase Probability: #{(prob * 100).round(2)}%"
|
|
778
|
+
puts " Decision: #{recommendation}"
|
|
779
|
+
end
|
|
780
|
+
```
|
|
781
|
+
|
|
782
|
+
#### Example 5.10: Anomaly Detection in IoT Sensor Data
|
|
783
|
+
|
|
784
|
+
```ruby
|
|
785
|
+
require 'grydra'
|
|
786
|
+
|
|
787
|
+
# IoT sensor readings: [temperature, humidity, pressure, vibration, power_consumption]
|
|
788
|
+
# Normal operating conditions
|
|
789
|
+
normal_data = [
|
|
790
|
+
[22.5, 45, 1013, 0.2, 150],
|
|
791
|
+
[23.0, 47, 1012, 0.3, 155],
|
|
792
|
+
[22.8, 46, 1013, 0.25, 152],
|
|
793
|
+
[23.2, 48, 1011, 0.28, 158],
|
|
794
|
+
[22.6, 45, 1014, 0.22, 151],
|
|
795
|
+
[23.1, 47, 1012, 0.29, 156]
|
|
796
|
+
]
|
|
797
|
+
|
|
798
|
+
# Anomalous conditions
|
|
799
|
+
anomaly_data = [
|
|
800
|
+
[35.0, 80, 980, 2.5, 300], # Overheating
|
|
801
|
+
[15.0, 20, 1050, 0.1, 50], # Underpowered
|
|
802
|
+
[25.0, 50, 1010, 5.0, 200], # High vibration
|
|
803
|
+
[40.0, 90, 970, 3.0, 350] # Multiple issues
|
|
804
|
+
]
|
|
805
|
+
|
|
806
|
+
# Label data: 0 = normal, 1 = anomaly
|
|
807
|
+
all_data = normal_data + anomaly_data
|
|
808
|
+
labels = [[0]] * normal_data.size + [[1]] * anomaly_data.size
|
|
809
|
+
|
|
810
|
+
# Train anomaly detection model
|
|
811
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
812
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
813
|
+
|
|
814
|
+
model.train_numerical(all_data, labels, [[10, 8, 4, 1]], 0.05, 2000, :zscore,
|
|
815
|
+
lambda_l1: 0.001, lambda_l2: 0.01,
|
|
816
|
+
dropout: true, dropout_rate: 0.3,
|
|
817
|
+
patience: 150)
|
|
818
|
+
|
|
819
|
+
# Monitor new sensor readings
|
|
820
|
+
new_readings = [
|
|
821
|
+
[22.9, 46, 1013, 0.26, 153], # Should be normal
|
|
822
|
+
[38.0, 85, 975, 2.8, 320], # Should be anomaly
|
|
823
|
+
[23.5, 49, 1011, 0.31, 159] # Should be normal
|
|
824
|
+
]
|
|
825
|
+
|
|
826
|
+
predictions = model.predict_numerical(new_readings, :zscore)
|
|
827
|
+
|
|
828
|
+
puts "\n🔍 IoT Anomaly Detection:"
|
|
829
|
+
new_readings.each_with_index do |reading, i|
|
|
830
|
+
prob = predictions[i][0]
|
|
831
|
+
status = prob > 0.5 ? "⚠️ ANOMALY DETECTED" : "✅ NORMAL"
|
|
832
|
+
puts "Sensor Reading #{i + 1}: #{reading}"
|
|
833
|
+
puts " Anomaly Score: #{(prob * 100).round(2)}%"
|
|
834
|
+
puts " Status: #{status}"
|
|
835
|
+
end
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
#### Example 5.11: Dynamic Pricing Optimization
|
|
839
|
+
|
|
840
|
+
```ruby
|
|
841
|
+
require 'grydra'
|
|
842
|
+
|
|
843
|
+
# Features: [demand, competitor_price, inventory_level, day_of_week, season, customer_segment]
|
|
844
|
+
# Target: optimal_price_multiplier (0.8 to 1.5)
|
|
845
|
+
|
|
846
|
+
pricing_data = [
|
|
847
|
+
[100, 50, 200, 1, 1, 1], # High demand, weekday, winter, regular -> 1.2x
|
|
848
|
+
[50, 45, 500, 6, 2, 2], # Low demand, weekend, spring, premium -> 0.9x
|
|
849
|
+
[150, 55, 50, 5, 3, 1], # Very high demand, low stock, summer -> 1.4x
|
|
850
|
+
[80, 48, 300, 3, 1, 2], # Medium demand, good stock -> 1.0x
|
|
851
|
+
[30, 40, 600, 7, 4, 3], # Low demand, high stock, fall, budget -> 0.8x
|
|
852
|
+
[120, 52, 100, 2, 3, 1] # High demand, low stock, summer -> 1.3x
|
|
853
|
+
]
|
|
854
|
+
|
|
855
|
+
price_multipliers = [[1.2], [0.9], [1.4], [1.0], [0.8], [1.3]]
|
|
856
|
+
|
|
857
|
+
# Train pricing model
|
|
858
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
859
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
860
|
+
|
|
861
|
+
model.train_numerical(pricing_data, price_multipliers, [[10, 8, 4, 1]], 0.05, 2000, :zscore,
|
|
862
|
+
lambda_l2: 0.001, patience: 150)
|
|
863
|
+
|
|
864
|
+
# Optimize pricing for new scenarios
|
|
865
|
+
new_scenarios = [
|
|
866
|
+
[110, 51, 150, 4, 3, 1], # High demand, Thursday, summer
|
|
867
|
+
[40, 43, 450, 6, 2, 3], # Low demand, Saturday, spring, budget
|
|
868
|
+
[90, 49, 250, 2, 1, 2] # Medium demand, Tuesday, winter, premium
|
|
869
|
+
]
|
|
870
|
+
|
|
871
|
+
predictions = model.predict_numerical(new_scenarios, :zscore)
|
|
872
|
+
|
|
873
|
+
puts "\n💰 Dynamic Pricing Recommendations:"
|
|
874
|
+
base_price = 100
|
|
875
|
+
new_scenarios.each_with_index do |scenario, i|
|
|
876
|
+
multiplier = predictions[i][0]
|
|
877
|
+
optimal_price = base_price * multiplier
|
|
878
|
+
|
|
879
|
+
puts "Scenario #{i + 1}: Demand=#{scenario[0]}, Competitor=$#{scenario[1]}, Stock=#{scenario[2]}"
|
|
880
|
+
puts " Price Multiplier: #{multiplier.round(2)}x"
|
|
881
|
+
puts " Optimal Price: $#{optimal_price.round(2)} (base: $#{base_price})"
|
|
882
|
+
puts " Strategy: #{multiplier > 1.1 ? 'Premium Pricing' : (multiplier < 0.95 ? 'Discount Pricing' : 'Standard Pricing')}"
|
|
883
|
+
end
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
#### Example 5.12: Medical Diagnosis Assistant
|
|
887
|
+
|
|
888
|
+
```ruby
|
|
889
|
+
require 'grydra'
|
|
890
|
+
|
|
891
|
+
# Patient features: [age, blood_pressure, cholesterol, glucose, bmi, family_history, smoking]
|
|
892
|
+
# Diagnosis: 0 = healthy, 1 = at risk
|
|
893
|
+
|
|
894
|
+
patient_data = [
|
|
895
|
+
[45, 120, 180, 90, 22, 0, 0], # Healthy
|
|
896
|
+
[55, 140, 240, 110, 28, 1, 1], # At risk
|
|
897
|
+
[38, 115, 170, 85, 21, 0, 0], # Healthy
|
|
898
|
+
[62, 150, 260, 125, 31, 1, 1], # At risk
|
|
899
|
+
[50, 135, 220, 105, 27, 1, 0], # At risk
|
|
900
|
+
[42, 118, 175, 88, 23, 0, 0], # Healthy
|
|
901
|
+
[58, 145, 250, 120, 30, 1, 1], # At risk
|
|
902
|
+
[40, 122, 185, 92, 24, 0, 0] # Healthy
|
|
903
|
+
]
|
|
904
|
+
|
|
905
|
+
diagnoses = [[0], [1], [0], [1], [1], [0], [1], [0]]
|
|
906
|
+
|
|
907
|
+
# Train diagnostic model with high accuracy requirements
|
|
908
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
909
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
910
|
+
|
|
911
|
+
model.train_numerical(patient_data, diagnoses, [[12, 10, 6, 1]], 0.05, 3000, :zscore,
|
|
912
|
+
lambda_l2: 0.001, dropout: true, dropout_rate: 0.2,
|
|
913
|
+
patience: 200)
|
|
914
|
+
|
|
915
|
+
# Evaluate model performance
|
|
916
|
+
metrics = model.evaluate_model(patient_data, diagnoses, [:accuracy, :confusion_matrix], :zscore)
|
|
917
|
+
|
|
918
|
+
puts "\n🏥 Medical Diagnosis Model Performance:"
|
|
919
|
+
puts " Accuracy: #{(metrics[:accuracy] * 100).round(2)}%"
|
|
920
|
+
if metrics[:confusion_matrix]
|
|
921
|
+
cm = metrics[:confusion_matrix]
|
|
922
|
+
puts " True Positives: #{cm[:tp]}"
|
|
923
|
+
puts " True Negatives: #{cm[:tn]}"
|
|
924
|
+
puts " False Positives: #{cm[:fp]}"
|
|
925
|
+
puts " False Negatives: #{cm[:fn]}"
|
|
926
|
+
end
|
|
927
|
+
|
|
928
|
+
# Diagnose new patients
|
|
929
|
+
new_patients = [
|
|
930
|
+
[48, 125, 195, 95, 25, 0, 0], # Borderline
|
|
931
|
+
[60, 155, 270, 130, 32, 1, 1] # High risk
|
|
932
|
+
]
|
|
933
|
+
|
|
934
|
+
predictions = model.predict_numerical(new_patients, :zscore)
|
|
935
|
+
|
|
936
|
+
puts "\n👨⚕️ New Patient Diagnoses:"
|
|
937
|
+
new_patients.each_with_index do |patient, i|
|
|
938
|
+
risk_score = predictions[i][0]
|
|
939
|
+
risk_level = risk_score > 0.7 ? "HIGH RISK" : (risk_score > 0.3 ? "MODERATE RISK" : "LOW RISK")
|
|
940
|
+
|
|
941
|
+
puts "Patient #{i + 1}: Age=#{patient[0]}, BP=#{patient[1]}, Cholesterol=#{patient[2]}"
|
|
942
|
+
puts " Risk Score: #{(risk_score * 100).round(2)}%"
|
|
943
|
+
puts " Assessment: #{risk_level}"
|
|
944
|
+
puts " Recommendation: #{risk_score > 0.5 ? 'Immediate consultation recommended' : 'Regular monitoring'}"
|
|
945
|
+
end
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
## Features
|
|
949
|
+
|
|
950
|
+
### Core Components
|
|
951
|
+
- **7 Activation Functions**: tanh, relu, sigmoid, leaky_relu, swish, gelu, softmax
|
|
952
|
+
- **8 Loss Functions**: MSE, MAE, Huber, Binary/Categorical Cross-Entropy, Hinge, Log-Cosh, Quantile
|
|
953
|
+
- **5 Optimizers**: Adam, SGD (with momentum/Nesterov), RMSprop, AdaGrad, AdamW
|
|
954
|
+
- **3 Layer Types**: Dense (fully connected), Convolutional, LSTM
|
|
955
|
+
- **6 Training Callbacks**: EarlyStopping, LearningRateScheduler, ReduceLROnPlateau, ModelCheckpoint, CSVLogger, ProgressBar
|
|
956
|
+
|
|
957
|
+
### Advanced Features
|
|
958
|
+
- **Regularization**: L1 (Lasso), L2 (Ridge), Dropout
|
|
959
|
+
- **Normalization**: Z-score, Min-Max, Feature-wise
|
|
960
|
+
- **Model Validation**: K-fold cross-validation, train/test split
|
|
961
|
+
- **Hyperparameter Tuning**: Grid search with parallel evaluation
|
|
962
|
+
- **Dimensionality Reduction**: PCA with power iteration
|
|
963
|
+
- **Text Processing**: Vocabulary creation, TF-IDF vectorization
|
|
964
|
+
- **Model Persistence**: Save/load models and vocabularies
|
|
965
|
+
- **Visualization**: ASCII architecture plots, gradient analysis, training curves
|
|
966
|
+
- **Ensemble Learning**: Multiple subnets with weighted voting
|
|
967
|
+
|
|
968
|
+
### Network Architectures
|
|
969
|
+
- **EasyNetwork**: High-level API for quick prototyping
|
|
970
|
+
- **MainNetwork**: Ensemble of multiple subnets
|
|
971
|
+
- **NeuralNetwork**: Low-level customizable architecture
|
|
972
|
+
- **Custom Layers**: Build your own layer types
|
|
973
|
+
|
|
974
|
+
### Level 6: Production-Ready Examples
|
|
975
|
+
|
|
976
|
+
#### Example 6.1: Email Spam Classifier
|
|
977
|
+
|
|
978
|
+
```ruby
|
|
979
|
+
require 'grydra'
|
|
980
|
+
|
|
981
|
+
# Email features: word frequencies and metadata
|
|
982
|
+
emails = [
|
|
983
|
+
"free money click here win prize",
|
|
984
|
+
"meeting tomorrow at 3pm",
|
|
985
|
+
"congratulations you won lottery",
|
|
986
|
+
"project deadline next week",
|
|
987
|
+
"claim your prize now limited time",
|
|
988
|
+
"lunch with team on friday"
|
|
989
|
+
]
|
|
990
|
+
|
|
991
|
+
labels = [[1], [0], [1], [0], [1], [0]] # 1=spam, 0=not spam
|
|
992
|
+
|
|
993
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
994
|
+
model.train_text(emails, labels, [[10, 6, 1]], 0.1, 1500, :max,
|
|
995
|
+
lambda_l1: 0.001, dropout: true, dropout_rate: 0.3)
|
|
996
|
+
|
|
997
|
+
# Test on new emails
|
|
998
|
+
new_emails = [
|
|
999
|
+
"urgent meeting tomorrow morning",
|
|
1000
|
+
"click here for free money now"
|
|
1001
|
+
]
|
|
1002
|
+
|
|
1003
|
+
predictions = model.predict_text(new_emails, :max)
|
|
1004
|
+
new_emails.each_with_index do |email, i|
|
|
1005
|
+
spam_prob = predictions[i][0]
|
|
1006
|
+
label = spam_prob > 0.5 ? "📧 SPAM" : "✅ LEGITIMATE"
|
|
1007
|
+
puts "\"#{email}\""
|
|
1008
|
+
puts " #{label} (#{(spam_prob * 100).round(1)}% confidence)\n\n"
|
|
1009
|
+
end
|
|
1010
|
+
```
|
|
1011
|
+
|
|
1012
|
+
#### Example 6.2: Employee Attrition Prediction
|
|
1013
|
+
|
|
1014
|
+
```ruby
|
|
1015
|
+
require 'grydra'
|
|
1016
|
+
|
|
1017
|
+
# Employee data: [satisfaction, evaluation, projects, hours, tenure, accident, promotion]
|
|
1018
|
+
employees = [
|
|
1019
|
+
[0.38, 0.53, 2, 157, 3, 0, 0], # Left
|
|
1020
|
+
[0.80, 0.86, 5, 262, 6, 0, 0], # Stayed
|
|
1021
|
+
[0.11, 0.88, 7, 272, 4, 0, 0], # Left
|
|
1022
|
+
[0.72, 0.87, 5, 223, 5, 0, 0], # Stayed
|
|
1023
|
+
[0.37, 0.52, 2, 159, 3, 0, 0], # Left
|
|
1024
|
+
[0.41, 0.50, 2, 153, 3, 0, 0], # Left
|
|
1025
|
+
[0.10, 0.77, 6, 247, 4, 0, 0], # Left
|
|
1026
|
+
[0.92, 0.85, 5, 259, 5, 0, 1] # Stayed
|
|
1027
|
+
]
|
|
1028
|
+
|
|
1029
|
+
attrition = [[1], [0], [1], [0], [1], [1], [1], [0]]
|
|
1030
|
+
|
|
1031
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1032
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
1033
|
+
|
|
1034
|
+
model.train_numerical(employees, attrition, [[12, 8, 4, 1]], 0.05, 2000, :zscore,
|
|
1035
|
+
lambda_l2: 0.001, dropout: true, dropout_rate: 0.25,
|
|
1036
|
+
patience: 150)
|
|
1037
|
+
|
|
1038
|
+
# Predict for new employees
|
|
1039
|
+
new_employees = [
|
|
1040
|
+
[0.45, 0.55, 3, 180, 3, 0, 0], # Moderate risk
|
|
1041
|
+
[0.85, 0.90, 4, 240, 6, 0, 1] # Low risk
|
|
1042
|
+
]
|
|
1043
|
+
|
|
1044
|
+
predictions = model.predict_numerical(new_employees, :zscore)
|
|
1045
|
+
|
|
1046
|
+
puts "👔 Employee Attrition Predictions:"
|
|
1047
|
+
new_employees.each_with_index do |emp, i|
|
|
1048
|
+
risk = predictions[i][0]
|
|
1049
|
+
status = risk > 0.6 ? "🔴 HIGH RISK" : (risk > 0.3 ? "🟡 MODERATE" : "🟢 LOW RISK")
|
|
1050
|
+
puts "Employee #{i + 1}: Satisfaction=#{emp[0]}, Evaluation=#{emp[1]}, Projects=#{emp[2]}"
|
|
1051
|
+
puts " Attrition Risk: #{(risk * 100).round(1)}% - #{status}"
|
|
1052
|
+
puts " Action: #{risk > 0.5 ? 'Schedule retention interview' : 'Continue monitoring'}\n\n"
|
|
1053
|
+
end
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
#### Example 6.3: Real Estate Valuation with Multiple Features
|
|
1057
|
+
|
|
1058
|
+
```ruby
|
|
1059
|
+
require 'grydra'
|
|
1060
|
+
|
|
1061
|
+
# Property features: [sqft, bedrooms, bathrooms, age, lot_size, garage, pool, school_rating]
|
|
1062
|
+
properties = [
|
|
1063
|
+
[1500, 3, 2, 10, 5000, 2, 0, 8],
|
|
1064
|
+
[2200, 4, 3, 5, 7500, 2, 1, 9],
|
|
1065
|
+
[1800, 3, 2, 15, 6000, 1, 0, 7],
|
|
1066
|
+
[2800, 5, 4, 3, 10000, 3, 1, 10],
|
|
1067
|
+
[1200, 2, 1, 25, 4000, 1, 0, 6],
|
|
1068
|
+
[2000, 4, 2.5, 8, 6500, 2, 0, 8],
|
|
1069
|
+
[3200, 5, 4, 2, 12000, 3, 1, 9],
|
|
1070
|
+
[1600, 3, 2, 12, 5500, 2, 0, 7]
|
|
1071
|
+
]
|
|
1072
|
+
|
|
1073
|
+
prices = [[320], [485], [365], [650], [245], [410], [720], [340]] # in thousands
|
|
1074
|
+
|
|
1075
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1076
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
1077
|
+
|
|
1078
|
+
model.train_numerical(properties, prices, [[16, 12, 8, 1]], 0.03, 3000, :zscore,
|
|
1079
|
+
lambda_l2: 0.001, patience: 200)
|
|
1080
|
+
|
|
1081
|
+
# Evaluate with cross-validation
|
|
1082
|
+
cv_result = GRYDRA::Training::CrossValidation.cross_validation(properties, prices, 4) do |train_x, train_y, test_x, test_y|
|
|
1083
|
+
m = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1084
|
+
m.configure_adam_optimizer(alpha: 0.001)
|
|
1085
|
+
m.train_numerical(train_x, train_y, [[16, 12, 8, 1]], 0.03, 1500, :zscore, lambda_l2: 0.001, patience: 100)
|
|
1086
|
+
preds = m.predict_numerical(test_x, :zscore)
|
|
1087
|
+
GRYDRA::Metrics.mae(preds.flatten, test_y.flatten)
|
|
1088
|
+
end
|
|
1089
|
+
|
|
1090
|
+
puts "🏡 Real Estate Model Performance:"
|
|
1091
|
+
puts " Average MAE: $#{cv_result[:average].round(2)}k"
|
|
1092
|
+
puts " Std Deviation: $#{cv_result[:deviation].round(2)}k\n\n"
|
|
1093
|
+
|
|
1094
|
+
# Predict new properties
|
|
1095
|
+
new_properties = [
|
|
1096
|
+
[1900, 3, 2, 7, 6200, 2, 0, 8],
|
|
1097
|
+
[2500, 4, 3, 4, 8000, 2, 1, 9]
|
|
1098
|
+
]
|
|
1099
|
+
|
|
1100
|
+
predictions = model.predict_numerical(new_properties, :zscore)
|
|
1101
|
+
|
|
1102
|
+
puts "Property Valuations:"
|
|
1103
|
+
new_properties.each_with_index do |prop, i|
|
|
1104
|
+
price = predictions[i][0]
|
|
1105
|
+
puts "Property #{i + 1}: #{prop[0]} sqft, #{prop[1]} bed, #{prop[2]} bath"
|
|
1106
|
+
puts " Estimated Value: $#{price.round(0)}k"
|
|
1107
|
+
puts " Price per sqft: $#{(price * 1000 / prop[0]).round(2)}\n\n"
|
|
1108
|
+
end
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
#### Example 6.4: Customer Lifetime Value Prediction
|
|
1112
|
+
|
|
1113
|
+
```ruby
|
|
1114
|
+
require 'grydra'
|
|
1115
|
+
|
|
1116
|
+
# Customer features: [age, income, purchase_freq, avg_order, tenure_months, support_calls, returns]
|
|
1117
|
+
customers = [
|
|
1118
|
+
[28, 45000, 12, 85, 24, 2, 1],
|
|
1119
|
+
[45, 95000, 24, 150, 48, 1, 0],
|
|
1120
|
+
[35, 65000, 18, 110, 36, 3, 2],
|
|
1121
|
+
[52, 120000, 30, 200, 60, 0, 0],
|
|
1122
|
+
[25, 35000, 6, 50, 12, 5, 3],
|
|
1123
|
+
[40, 80000, 20, 130, 42, 2, 1],
|
|
1124
|
+
[55, 110000, 28, 180, 54, 1, 0],
|
|
1125
|
+
[30, 50000, 10, 75, 18, 4, 2]
|
|
1126
|
+
]
|
|
1127
|
+
|
|
1128
|
+
# Lifetime value in thousands
|
|
1129
|
+
ltv = [[15], [45], [28], [65], [8], [35], [55], [12]]
|
|
1130
|
+
|
|
1131
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1132
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
1133
|
+
|
|
1134
|
+
model.train_numerical(customers, ltv, [[14, 10, 6, 1]], 0.04, 2500, :zscore,
|
|
1135
|
+
lambda_l2: 0.001, dropout: true, dropout_rate: 0.2,
|
|
1136
|
+
patience: 180)
|
|
1137
|
+
|
|
1138
|
+
# Segment customers
|
|
1139
|
+
new_customers = [
|
|
1140
|
+
[32, 55000, 15, 95, 30, 2, 1], # Mid-value
|
|
1141
|
+
[48, 105000, 26, 175, 50, 1, 0], # High-value
|
|
1142
|
+
[26, 38000, 8, 60, 15, 4, 2] # Low-value
|
|
1143
|
+
]
|
|
1144
|
+
|
|
1145
|
+
predictions = model.predict_numerical(new_customers, :zscore)
|
|
1146
|
+
|
|
1147
|
+
puts "💰 Customer Lifetime Value Predictions:"
|
|
1148
|
+
new_customers.each_with_index do |cust, i|
|
|
1149
|
+
value = predictions[i][0]
|
|
1150
|
+
segment = value > 40 ? "💎 PREMIUM" : (value > 20 ? "⭐ STANDARD" : "📊 BASIC")
|
|
1151
|
+
|
|
1152
|
+
puts "Customer #{i + 1}: Age=#{cust[0]}, Income=$#{cust[1]}, Purchases/yr=#{cust[2]}"
|
|
1153
|
+
puts " Predicted LTV: $#{value.round(1)}k"
|
|
1154
|
+
puts " Segment: #{segment}"
|
|
1155
|
+
puts " Strategy: #{value > 40 ? 'VIP treatment, exclusive offers' : (value > 20 ? 'Regular engagement, loyalty program' : 'Cost-effective retention')}\n\n"
|
|
1156
|
+
end
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
#### Example 6.5: Network Traffic Anomaly Detection
|
|
1160
|
+
|
|
1161
|
+
```ruby
|
|
1162
|
+
require 'grydra'
|
|
1163
|
+
|
|
1164
|
+
# Network metrics: [packets/sec, bytes/sec, connections, failed_logins, port_scans]
|
|
1165
|
+
normal_traffic = [
|
|
1166
|
+
[1000, 500000, 50, 0, 0],
|
|
1167
|
+
[1200, 600000, 55, 1, 0],
|
|
1168
|
+
[950, 480000, 48, 0, 0],
|
|
1169
|
+
[1100, 550000, 52, 1, 0],
|
|
1170
|
+
[1050, 520000, 51, 0, 0]
|
|
1171
|
+
]
|
|
1172
|
+
|
|
1173
|
+
attack_traffic = [
|
|
1174
|
+
[5000, 2500000, 200, 50, 10], # DDoS
|
|
1175
|
+
[800, 400000, 45, 100, 0], # Brute force
|
|
1176
|
+
[1500, 750000, 80, 5, 50], # Port scan
|
|
1177
|
+
[10000, 5000000, 500, 20, 5] # Combined attack
|
|
1178
|
+
]
|
|
1179
|
+
|
|
1180
|
+
all_traffic = normal_traffic + attack_traffic
|
|
1181
|
+
labels = [[0]] * normal_traffic.size + [[1]] * attack_traffic.size
|
|
1182
|
+
|
|
1183
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1184
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
1185
|
+
|
|
1186
|
+
model.train_numerical(all_traffic, labels, [[10, 8, 4, 1]], 0.05, 2500, :zscore,
|
|
1187
|
+
lambda_l1: 0.001, lambda_l2: 0.01,
|
|
1188
|
+
dropout: true, dropout_rate: 0.35,
|
|
1189
|
+
patience: 180)
|
|
1190
|
+
|
|
1191
|
+
# Monitor live traffic
|
|
1192
|
+
live_traffic = [
|
|
1193
|
+
[1080, 540000, 53, 1, 0], # Normal
|
|
1194
|
+
[4500, 2250000, 180, 45, 8], # Suspicious
|
|
1195
|
+
[1150, 575000, 56, 0, 0] # Normal
|
|
1196
|
+
]
|
|
1197
|
+
|
|
1198
|
+
predictions = model.predict_numerical(live_traffic, :zscore)
|
|
1199
|
+
|
|
1200
|
+
puts "🔒 Network Security Monitoring:"
|
|
1201
|
+
live_traffic.each_with_index do |traffic, i|
|
|
1202
|
+
threat_level = predictions[i][0]
|
|
1203
|
+
status = threat_level > 0.7 ? "🚨 CRITICAL" : (threat_level > 0.4 ? "⚠️ WARNING" : "✅ NORMAL")
|
|
1204
|
+
|
|
1205
|
+
puts "Traffic Sample #{i + 1}: #{traffic[0]} pkt/s, #{traffic[2]} connections"
|
|
1206
|
+
puts " Threat Score: #{(threat_level * 100).round(1)}%"
|
|
1207
|
+
puts " Status: #{status}"
|
|
1208
|
+
puts " Action: #{threat_level > 0.7 ? 'Block immediately, alert SOC' : (threat_level > 0.4 ? 'Increase monitoring' : 'Continue normal operation')}\n\n"
|
|
1209
|
+
end
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
#### Example 6.6: Loan Default Prediction
|
|
1213
|
+
|
|
1214
|
+
```ruby
|
|
1215
|
+
require 'grydra'
|
|
1216
|
+
|
|
1217
|
+
# Applicant features: [age, income, credit_score, debt_ratio, employment_years, loan_amount, previous_defaults]
|
|
1218
|
+
applicants = [
|
|
1219
|
+
[35, 75000, 720, 0.3, 8, 25000, 0], # Approved
|
|
1220
|
+
[28, 45000, 650, 0.5, 3, 15000, 1], # Risky
|
|
1221
|
+
[42, 95000, 780, 0.2, 12, 35000, 0], # Approved
|
|
1222
|
+
[25, 35000, 580, 0.7, 1, 10000, 2], # Denied
|
|
1223
|
+
[50, 120000, 800, 0.15, 20, 50000, 0],# Approved
|
|
1224
|
+
[30, 50000, 620, 0.6, 4, 18000, 1], # Risky
|
|
1225
|
+
[38, 85000, 750, 0.25, 10, 30000, 0], # Approved
|
|
1226
|
+
[26, 38000, 590, 0.65, 2, 12000, 2] # Denied
|
|
1227
|
+
]
|
|
1228
|
+
|
|
1229
|
+
defaults = [[0], [1], [0], [1], [0], [1], [0], [1]]
|
|
1230
|
+
|
|
1231
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1232
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
1233
|
+
|
|
1234
|
+
model.train_numerical(applicants, defaults, [[14, 10, 6, 1]], 0.04, 2500, :zscore,
|
|
1235
|
+
lambda_l2: 0.001, dropout: true, dropout_rate: 0.25,
|
|
1236
|
+
patience: 180)
|
|
1237
|
+
|
|
1238
|
+
# Evaluate model
|
|
1239
|
+
metrics = model.evaluate_model(applicants, defaults, [:accuracy, :confusion_matrix], :zscore)
|
|
1240
|
+
|
|
1241
|
+
puts "🏦 Loan Default Model Performance:"
|
|
1242
|
+
puts " Accuracy: #{(metrics[:accuracy] * 100).round(2)}%"
|
|
1243
|
+
if metrics[:confusion_matrix]
|
|
1244
|
+
cm = metrics[:confusion_matrix]
|
|
1245
|
+
precision = GRYDRA::Metrics.precision(cm[:tp], cm[:fp])
|
|
1246
|
+
recall = GRYDRA::Metrics.recall(cm[:tp], cm[:fn])
|
|
1247
|
+
f1 = GRYDRA::Metrics.f1(precision, recall)
|
|
1248
|
+
puts " Precision: #{(precision * 100).round(2)}%"
|
|
1249
|
+
puts " Recall: #{(recall * 100).round(2)}%"
|
|
1250
|
+
puts " F1 Score: #{(f1 * 100).round(2)}%\n\n"
|
|
1251
|
+
end
|
|
1252
|
+
|
|
1253
|
+
# Evaluate new applications
|
|
1254
|
+
new_applicants = [
|
|
1255
|
+
[33, 68000, 700, 0.35, 6, 22000, 0],
|
|
1256
|
+
[27, 42000, 610, 0.55, 2, 14000, 1]
|
|
1257
|
+
]
|
|
1258
|
+
|
|
1259
|
+
predictions = model.predict_numerical(new_applicants, :zscore)
|
|
1260
|
+
|
|
1261
|
+
puts "Loan Application Decisions:"
|
|
1262
|
+
new_applicants.each_with_index do |app, i|
|
|
1263
|
+
risk = predictions[i][0]
|
|
1264
|
+
decision = risk < 0.3 ? "✅ APPROVE" : (risk < 0.6 ? "⚠️ REVIEW" : "❌ DENY")
|
|
1265
|
+
|
|
1266
|
+
puts "Applicant #{i + 1}: Age=#{app[0]}, Income=$#{app[1]}, Credit=#{app[2]}"
|
|
1267
|
+
puts " Default Risk: #{(risk * 100).round(1)}%"
|
|
1268
|
+
puts " Decision: #{decision}"
|
|
1269
|
+
puts " Interest Rate: #{risk < 0.3 ? '5.5%' : (risk < 0.6 ? '8.5%' : 'N/A')}\n\n"
|
|
1270
|
+
end
|
|
1271
|
+
```
|
|
1272
|
+
|
|
1273
|
+
#### Example 6.7: Predictive Maintenance for Manufacturing
|
|
1274
|
+
|
|
1275
|
+
```ruby
|
|
1276
|
+
require 'grydra'
|
|
1277
|
+
|
|
1278
|
+
# Machine sensor data: [temperature, vibration, pressure, rpm, power_consumption, runtime_hours, last_maintenance_days]
|
|
1279
|
+
machine_data = [
|
|
1280
|
+
[65, 0.5, 100, 1500, 45, 1000, 30], # Healthy
|
|
1281
|
+
[85, 2.5, 95, 1480, 52, 3500, 180], # Needs maintenance
|
|
1282
|
+
[70, 0.8, 98, 1495, 46, 1500, 45], # Healthy
|
|
1283
|
+
[95, 3.5, 88, 1450, 58, 4200, 240], # Critical
|
|
1284
|
+
[68, 0.6, 99, 1498, 45, 1200, 35], # Healthy
|
|
1285
|
+
[80, 2.0, 92, 1475, 50, 3000, 150], # Needs maintenance
|
|
1286
|
+
[72, 1.0, 97, 1490, 47, 1800, 60], # Healthy
|
|
1287
|
+
[90, 3.0, 90, 1460, 55, 3800, 210] # Critical
|
|
1288
|
+
]
|
|
1289
|
+
|
|
1290
|
+
maintenance_needed = [[0], [1], [0], [1], [0], [1], [0], [1]]
|
|
1291
|
+
|
|
1292
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1293
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
1294
|
+
|
|
1295
|
+
model.train_numerical(machine_data, maintenance_needed, [[14, 10, 6, 1]], 0.04, 2500, :zscore,
|
|
1296
|
+
lambda_l1: 0.001, lambda_l2: 0.01,
|
|
1297
|
+
dropout: true, dropout_rate: 0.3,
|
|
1298
|
+
patience: 180)
|
|
1299
|
+
|
|
1300
|
+
# Monitor machines
|
|
1301
|
+
current_machines = [
|
|
1302
|
+
[73, 1.2, 96, 1488, 48, 2000, 75], # Should be OK
|
|
1303
|
+
[88, 2.8, 91, 1465, 54, 3600, 195], # Should need maintenance
|
|
1304
|
+
[67, 0.7, 99, 1497, 46, 1300, 40] # Should be OK
|
|
1305
|
+
]
|
|
1306
|
+
|
|
1307
|
+
predictions = model.predict_numerical(current_machines, :zscore)
|
|
1308
|
+
|
|
1309
|
+
puts "🏭 Predictive Maintenance System:"
|
|
1310
|
+
current_machines.each_with_index do |machine, i|
|
|
1311
|
+
failure_risk = predictions[i][0]
|
|
1312
|
+
status = failure_risk > 0.7 ? "🔴 CRITICAL" : (failure_risk > 0.4 ? "🟡 WARNING" : "🟢 HEALTHY")
|
|
1313
|
+
|
|
1314
|
+
puts "Machine #{i + 1}: Temp=#{machine[0]}°C, Vibration=#{machine[1]}mm/s, Runtime=#{machine[5]}hrs"
|
|
1315
|
+
puts " Failure Risk: #{(failure_risk * 100).round(1)}%"
|
|
1316
|
+
puts " Status: #{status}"
|
|
1317
|
+
puts " Recommendation: #{failure_risk > 0.7 ? 'Schedule immediate maintenance' : (failure_risk > 0.4 ? 'Plan maintenance within 2 weeks' : 'Continue normal operation')}"
|
|
1318
|
+
puts " Estimated Time to Failure: #{failure_risk > 0.7 ? '<1 week' : (failure_risk > 0.4 ? '2-4 weeks' : '>1 month')}\n\n"
|
|
1319
|
+
end
|
|
1320
|
+
```
|
|
1321
|
+
|
|
1322
|
+
#### Example 6.8: Sales Forecasting with Seasonality
|
|
1323
|
+
|
|
1324
|
+
```ruby
|
|
1325
|
+
require 'grydra'
|
|
1326
|
+
|
|
1327
|
+
# Sales features: [month, day_of_week, is_holiday, temperature, marketing_spend, competitor_promo, inventory_level]
|
|
1328
|
+
historical_sales = [
|
|
1329
|
+
[1, 1, 0, 35, 5000, 0, 1000], # 45k sales
|
|
1330
|
+
[1, 5, 0, 32, 4500, 1, 950], # 38k sales
|
|
1331
|
+
[2, 3, 1, 40, 8000, 0, 1200], # 65k sales (Valentine's)
|
|
1332
|
+
[3, 6, 0, 55, 5500, 0, 1100], # 48k sales
|
|
1333
|
+
[4, 2, 0, 65, 6000, 1, 1050], # 42k sales
|
|
1334
|
+
[5, 7, 1, 75, 7000, 0, 1300], # 58k sales (Memorial Day)
|
|
1335
|
+
[6, 4, 0, 85, 5000, 0, 1000], # 50k sales
|
|
1336
|
+
[7, 1, 1, 90, 9000, 0, 1400] # 72k sales (July 4th)
|
|
1337
|
+
]
|
|
1338
|
+
|
|
1339
|
+
sales = [[45], [38], [65], [48], [42], [58], [50], [72]]
|
|
1340
|
+
|
|
1341
|
+
model = GRYDRA::Networks::EasyNetwork.new(print_epochs: false)
|
|
1342
|
+
model.configure_adam_optimizer(alpha: 0.001)
|
|
1343
|
+
|
|
1344
|
+
model.train_numerical(historical_sales, sales, [[14, 10, 6, 1]], 0.03, 2500, :zscore,
|
|
1345
|
+
lambda_l2: 0.001, patience: 180)
|
|
1346
|
+
|
|
1347
|
+
# Forecast future sales
|
|
1348
|
+
future_scenarios = [
|
|
1349
|
+
[8, 3, 0, 88, 5500, 0, 1100], # Regular August day
|
|
1350
|
+
[9, 1, 1, 78, 8500, 0, 1350], # Labor Day
|
|
1351
|
+
[10, 5, 0, 65, 6000, 1, 1050] # October with competitor promo
|
|
1352
|
+
]
|
|
1353
|
+
|
|
1354
|
+
predictions = model.predict_numerical(future_scenarios, :zscore)
|
|
1355
|
+
|
|
1356
|
+
puts "📈 Sales Forecasting:"
|
|
1357
|
+
future_scenarios.each_with_index do |scenario, i|
|
|
1358
|
+
forecast = predictions[i][0]
|
|
1359
|
+
month_names = %w[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec]
|
|
1360
|
+
|
|
1361
|
+
puts "Scenario #{i + 1}: #{month_names[scenario[0] - 1]}, Day #{scenario[1]}, Holiday=#{scenario[2] == 1 ? 'Yes' : 'No'}"
|
|
1362
|
+
puts " Marketing: $#{scenario[4]}, Competitor Promo: #{scenario[5] == 1 ? 'Yes' : 'No'}"
|
|
1363
|
+
puts " Forecasted Sales: $#{forecast.round(1)}k"
|
|
1364
|
+
puts " Confidence: #{forecast > 60 ? 'High season' : (forecast > 45 ? 'Normal' : 'Low season')}"
|
|
1365
|
+
puts " Inventory Recommendation: #{(forecast * 25).round(0)} units\n\n"
|
|
1366
|
+
end
|
|
1367
|
+
```
|
|
1368
|
+
|
|
1369
|
+
## Features
|
|
1370
|
+
|
|
1371
|
+
### Core Components
|
|
1372
|
+
- **7 Activation Functions**: tanh, relu, sigmoid, leaky_relu, swish, gelu, softmax
|
|
1373
|
+
- **8 Loss Functions**: MSE, MAE, Huber, Binary/Categorical Cross-Entropy, Hinge, Log-Cosh, Quantile
|
|
1374
|
+
- **5 Optimizers**: Adam, SGD (with momentum/Nesterov), RMSprop, AdaGrad, AdamW
|
|
1375
|
+
- **3 Layer Types**: Dense (fully connected), Convolutional, LSTM
|
|
1376
|
+
- **6 Training Callbacks**: EarlyStopping, LearningRateScheduler, ReduceLROnPlateau, ModelCheckpoint, CSVLogger, ProgressBar
|
|
1377
|
+
|
|
1378
|
+
### Advanced Features
|
|
1379
|
+
- **Regularization**: L1 (Lasso), L2 (Ridge), Dropout
|
|
1380
|
+
- **Normalization**: Z-score, Min-Max, Feature-wise
|
|
1381
|
+
- **Model Validation**: K-fold cross-validation, train/test split
|
|
1382
|
+
- **Hyperparameter Tuning**: Grid search with parallel evaluation
|
|
1383
|
+
- **Dimensionality Reduction**: PCA with power iteration
|
|
1384
|
+
- **Text Processing**: Vocabulary creation, TF-IDF vectorization
|
|
1385
|
+
- **Model Persistence**: Save/load models and vocabularies
|
|
1386
|
+
- **Visualization**: ASCII architecture plots, gradient analysis, training curves
|
|
1387
|
+
- **Ensemble Learning**: Multiple subnets with weighted voting
|
|
1388
|
+
|
|
1389
|
+
### Network Architectures
|
|
1390
|
+
- **EasyNetwork**: High-level API for quick prototyping
|
|
1391
|
+
- **MainNetwork**: Ensemble of multiple subnets
|
|
1392
|
+
- **NeuralNetwork**: Low-level customizable architecture
|
|
1393
|
+
- **Custom Layers**: Build your own layer types
|
|
116
1394
|
|
|
117
1395
|
## License
|
|
118
1396
|
|
|
119
|
-
|
|
1397
|
+
GPL-3.0-or-later
|
|
1398
|
+
|
|
1399
|
+
## Links
|
|
1400
|
+
|
|
1401
|
+
- GitHub: https://github.com/grcodedigitalsolutions/GRydra
|
|
1402
|
+
- RubyGems: https://rubygems.org/gems/grydra
|
|
1403
|
+
|
|
1404
|
+
---
|
|
1405
|
+
|
|
1406
|
+
## 💙 Support Gabo-Razo
|
|
1407
|
+
|
|
1408
|
+
<center>If this project has been useful, consider supporting **Gabo-Razo** via GitHub Sponsors. Your contribution helps keep development active and improve future releases.</center>
|
|
1409
|
+
|
|
1410
|
+
---
|
|
1411
|
+
|
|
1412
|
+
<div align="center">
|
|
1413
|
+
<img src="https://avatars.githubusercontent.com/u/219750358?v=4" width="90" style="border-radius: 50%; margin-bottom: 12px;"/>
|
|
1414
|
+
<p style="font-size: 22px; font-weight: 800; margin: 0;">Gabo-Razo</p>
|
|
1415
|
+
<p><strong>Developer • Ruby • Python • C++ • Java • Dart • COBOL</strong></p>
|
|
1416
|
+
<p><a href="https://github.com/Gabo-Razo?tab=followers">⭐ Follow on GitHub</a></p>
|
|
1417
|
+
<a href="https://github.com/sponsors/Gabo-Razo"><img src="https://img.shields.io/badge/Sponsor_Me-FF4081?style=for-the-badge&logo=githubsponsors&logoColor=white" height="44"/></a>
|
|
1418
|
+
</div>
|
|
1419
|
+
|
|
1420
|
+
---
|