febrifahmi commited on
Commit
490d5ef
·
verified ·
1 Parent(s): 1e30b3f

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +353 -1
README.md CHANGED
@@ -4,4 +4,356 @@ datasets:
4
  - ylecun/mnist
5
  metrics:
6
  - accuracy
7
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  - ylecun/mnist
5
  metrics:
6
  - accuracy
7
+ ---
8
+ # Model Summary
9
+
10
+ ## Background motivation
11
+
12
+ The development of the latest neural network/deep learning model architectures (State-of-the-Art/SOTA) over the past two decades has seen a trend toward increasingly large-scale model development (in terms of the number of parameters, reaching billions, especially in LLM models), heavy computational burdens/requirement of capable hardware and infrastructure, large energy footprints, and large model sizes, as well as high and unsustainable memory and execution footprints. This may not be immediately apparent to end users, but it actually poses a real environmental threat.
13
+
14
+ With the current trend of climate change, it is unwise to continue the paradigm and practice of developing and using neural network products that leave a high environmental footprint (energy footprint, memory footprint, inefficient use of resources during execution/inference).
15
+
16
+ I propose a general neural network model architecture that is more sustainable (GreenAI) and has the potential to be applied to various NN tasks, namely the Network-of-Dendrites (NoD) model architecture.
17
+
18
+ This model architecture is not specific to a specific domain, but rather represents a core architectural paradigm that is fundamentally different from the model architectures currently being developed. The architecture is fundamentally a computation and routing paradigm (a lean, modular "thinking core") rather than a standalone end-to-end sensor pipeline.
19
+
20
+ The core paradigm of the NoD model architecture is minimizing the number of parameters by applying the principle of shared parameters, rather than increasing the number to billions.
21
+
22
+ NoD was developed by emulating the basic idea of ​​how the brain's neurons learn and function biologically. Brain components, modeled as dendrites, go through two main phases:
23
+
24
+ 1) the initial phase when the nervous system begins to learn and form new neural networks that become stronger over time after successfully learning from data (myelination); and
25
+ 2) the phase when the nervous system is fully formed and the training and dynamic processing can be stopped and "locked in."
26
+
27
+ In the context of the NN model architecture, this is achieved by dividing the model architecture into two phases:
28
+ a. first phase: statistically searching/matching which data can be processed by which archetype/subMLP (gating/routing);
29
+ after that, the data is fed into a shared-base of parameters before being fed to the selected archetype. This occurs in the first phase during training, where the most appropriate probability for dividing the data into each archetype is still being sought;
30
+ c. Phase two: the locked-in phase, where the transition from soft probabilistic to deterministic occurs after the optimal routing probability to K other archetypes for processing the data is obtained from Phase one.
31
+
32
+
33
+ ### Model Provenance & Lineage
34
+ * **Base Architecture:** None (Built completely from scratch).
35
+ * **Pre-trained Weights:** None (Trained with randomly initialized weights).
36
+ * **Training Dataset:** Standard MNIST Handwritten Digit Dataset.
37
+
38
+ ### Origin Statement
39
+ This model is a custom-designed architecture developed independently. It is not a fork, fine-tuned variant, or distillation of any existing pre-trained model. All weights were trained from scratch exclusively on the attached MNIST dataset.
40
+
41
+ ## Usage
42
+
43
+ ## Create NoDClassificationLayer class
44
+ ```
45
+ import tensorflow as tf
46
+ from tensorflow.keras import layers, Model, initializers, datasets
47
+ from tensorflow.keras.models import load_model
48
+ from tensorflow.keras.utils import plot_model
49
+ import numpy as np
50
+ import export_nod
51
+ import monitor
52
+
53
+ # Reuse the NetworkOfDendritesLayer implementation with Multi-Class Output adjustment
54
+ @tf.keras.utils.register_keras_serializable(package='Custom', name='NoDClassificationLayer')
55
+ class NoDClassificationLayer(layers.Layer):
56
+ def __init__(self, num_inputs, num_classes, num_archetypes, archetype_configs, embedding_dim=16, **kwargs):
57
+ super(NoDClassificationLayer, self).__init__(**kwargs)
58
+ self.num_inputs = num_inputs
59
+ self.num_classes = num_classes
60
+ self.num_archetypes = num_archetypes
61
+ self.archetype_configs = archetype_configs
62
+ self.embedding_dim = embedding_dim
63
+ self.is_locked = False
64
+ self.temperature = 1.0
65
+ self.hard_assignments = None
66
+
67
+ def build(self, input_shape):
68
+ # Discovery parameters
69
+ self.input_keys = self.add_weight(
70
+ name="input_keys", shape=(self.num_inputs, self.embedding_dim),
71
+ initializer=initializers.RandomNormal(stddev=0.1), trainable=True
72
+ )
73
+ self.archetype_prototypes = self.add_weight(
74
+ name="archetype_prototypes", shape=(self.num_archetypes, self.embedding_dim),
75
+ initializer=initializers.RandomNormal(stddev=0.1), trainable=True
76
+ )
77
+
78
+ # Bounded Bank of Archetype NPUs
79
+ self.archetype_mlps = []
80
+ for cfg in self.archetype_configs:
81
+ # Output of each mini-MLP now projects to `num_classes` so each pixel
82
+ # contributes a non-linear vote to every class score.
83
+ # input_shape(1,) is used to ensure the MLPs can process single pixel inputs and stick to 472 params/enforce True 472-params scale in Keras for this NoD arch.
84
+ mlp = tf.keras.Sequential([
85
+ layers.Dense(cfg["hidden_dim"], activation=cfg["activation"],
86
+ kernel_initializer=initializers.VarianceScaling(scale=cfg["init_scale"], mode='fan_in', distribution='normal'), input_shape=(1,)),
87
+ layers.Dense(self.num_classes, kernel_initializer=initializers.RandomNormal(stddev=0.1))
88
+ ])
89
+ self.archetype_mlps.append(mlp)
90
+ super(NoDClassificationLayer, self).build(input_shape)
91
+
92
+ def lock_routing(self):
93
+ similarity = tf.matmul(self.input_keys, self.archetype_prototypes, transpose_b=True)
94
+ self.hard_assignments = tf.argmax(similarity, axis=-1).numpy()
95
+ self.is_locked = True
96
+ self.input_keys.trainable = False
97
+ self.archetype_prototypes.trainable = False
98
+ print(f"\n[SYSTEM] Routing Locked. Archetype distribution: {np.bincount(self.hard_assignments)}")
99
+
100
+ def call(self, inputs):
101
+ batch_size = tf.shape(inputs)[0]
102
+
103
+ if not self.is_locked:
104
+ # Phase 1: Soft Dynamic Routing
105
+ similarity = tf.matmul(self.input_keys, self.archetype_prototypes, transpose_b=True)
106
+ soft_routing = tf.nn.softmax(similarity / self.temperature, axis=-1)
107
+
108
+ expanded_inputs = tf.expand_dims(inputs, axis=-1) # (Batch, 784, 1)
109
+ all_npu_outputs = []
110
+
111
+ for k in range(self.num_archetypes):
112
+ flat_in = tf.reshape(expanded_inputs, [-1, 1])
113
+ flat_out = self.archetype_mlps[k](flat_in) # (Batch*784, 10)
114
+ npu_out = tf.reshape(flat_out, [batch_size, self.num_inputs, self.num_classes])
115
+ all_npu_outputs.append(npu_out)
116
+
117
+ stacked_outputs = tf.stack(all_npu_outputs, axis=-1) # (Batch, 784, 10, Num_Arch)
118
+ routing_expanded = tf.reshape(soft_routing, [1, self.num_inputs, 1, self.num_archetypes])
119
+ dendritic_outputs = tf.reduce_sum(stacked_outputs * routing_expanded, axis=-1) # (Batch, 784, 10)
120
+ else:
121
+ # Phase 2: Fully Vectorized Structural Execution Loop
122
+ # self.hard_assignments contains the winning archetype index for each of the 784 inputs (Shape: [784])
123
+ # We map each input to its assigned archetype without breaking tensor flow.
124
+
125
+ expanded_inputs = tf.expand_dims(inputs, axis=-1) # (Batch, 784, 1)
126
+ flat_in = tf.reshape(expanded_inputs, [-1, 1]) # (Batch * 784, 1)
127
+
128
+ # Compute outputs for all archetypes across all inputs first
129
+ all_npu_outputs = []
130
+ for k in range(self.num_archetypes):
131
+ flat_out = self.archetype_mlps[k](flat_in) # (Batch * 784, 10)
132
+ npu_out = tf.reshape(flat_out, [batch_size, self.num_inputs, self.num_classes])
133
+ all_npu_outputs.append(npu_out)
134
+
135
+ stacked_outputs = tf.stack(all_npu_outputs, axis=-1) # (Batch, 784, 10, Num_Arch)
136
+
137
+ # Create a hard one-hot mask from self.hard_assignments: shape (784, Num_Arch)
138
+ hard_mask = tf.one_hot(self.hard_assignments, depth=self.num_archetypes, dtype=tf.float32)
139
+ # Reshape mask to broadcast correctly: (1, 784, 1, Num_Arch)
140
+ routing_expanded = tf.reshape(hard_mask, [1, self.num_inputs, 1, self.num_archetypes])
141
+
142
+ # Select only the winning archetype's output for each input index deterministically
143
+ dendritic_outputs = tf.reduce_sum(stacked_outputs * routing_expanded, axis=-1) # (Batch, 784, 10)
144
+
145
+ # RMS Normalization over dendritic outputs
146
+ rms = tf.math.sqrt(tf.reduce_mean(tf.math.square(dendritic_outputs), axis=1, keepdims=True) + 1e-8)
147
+ normalized_outputs = dendritic_outputs / rms
148
+
149
+ # Macro Neuron Aggregation Sum
150
+ macro_sum = tf.reduce_sum(normalized_outputs, axis=1) # (Batch, 10)
151
+ return macro_sum
152
+
153
+ def get_config(self):
154
+ """Serialization support for saving/loading the layer."""
155
+ config = super().get_config()
156
+ config.update({
157
+ "num_inputs": self.num_inputs,
158
+ "num_classes": self.num_classes,
159
+ "num_archetypes": self.num_archetypes,
160
+ "archetype_configs": self.archetype_configs,
161
+ "embedding_dim": self.embedding_dim
162
+ })
163
+ return config
164
+
165
+ @classmethod
166
+ def from_config(cls, config):
167
+ """Deserialization support for loading the layer from a config."""
168
+ # config_copy = config.copy()
169
+ return cls(**config)
170
+ ```
171
+ ## Create the load balaced layer class
172
+
173
+ ```
174
+ import tensorflow as tf
175
+ from tensorflow.keras import layers
176
+
177
+ class BalancedNoDClassificationLayer(layers.Layer):
178
+ """
179
+ 1D Network-of-Dendrites (NoD) Classification Layer with
180
+ Shared-Parameter Core and Load-Balancing Auxiliary Loss.
181
+ """
182
+ def __init__(self, num_archetypes=8, archetype_dim=16, balance_weight=0.01, **kwargs):
183
+ super(BalancedNoDClassificationLayer, self).__init__(**kwargs)
184
+ self.num_archetypes = num_archetypes
185
+ self.archetype_dim = archetype_dim
186
+ self.balance_weight = balance_weight
187
+
188
+ def build(self, input_shape):
189
+ # 1D Shared-Parameter Core: Shape (num_archetypes, archetype_dim)
190
+ self.archetype_core = self.add_weight(
191
+ shape=(self.num_archetypes, self.archetype_dim),
192
+ initializer='variance_scaling',
193
+ trainable=True,
194
+ name='nod_1d_shared_core'
195
+ )
196
+
197
+ # Router network to compute soft routing gates across archetypes
198
+ self.router_weights = self.add_weight(
199
+ shape=(input_shape[-1], self.num_archetypes),
200
+ initializer='glorot_uniform',
201
+ trainable=True,
202
+ name='nod_router_weights'
203
+ )
204
+
205
+ super(BalancedNoDClassificationLayer, self).build(input_shape)
206
+
207
+ def call(self, inputs):
208
+ batch_size = tf.shape(inputs)[0]
209
+
210
+ # 1. Compute routing probabilities via Softmax
211
+ router_logits = tf.matmul(inputs, self.router_weights)
212
+ routing_gates = tf.nn.softmax(router_logits, axis=-1) # Shape: (Batch, num_archetypes)
213
+
214
+ # 2. Load-Balancing Auxiliary Loss (Prevents routing collapse)
215
+ mean_gate_per_archetype = tf.reduce_mean(routing_gates, axis=0)
216
+ uniform_target = 1.0 / float(self.num_archetypes)
217
+ load_balance_loss = self.balance_weight * tf.reduce_sum(
218
+ tf.square(mean_gate_per_archetype - uniform_target)
219
+ )
220
+ self.add_loss(load_balance_loss)
221
+
222
+ # 3. Weighted combination of the 1D shared archetype core parameters
223
+ # routing_gates: (Batch, num_archetypes, 1) x archetype_core: (1, num_archetypes, archetype_dim)
224
+ gates_expanded = tf.expand_dims(routing_gates, axis=-1)
225
+ core_expanded = tf.expand_dims(self.archetype_core, axis=0)
226
+
227
+ modulated_archetypes = gates_expanded * core_expanded
228
+ output_features = tf.reduce_sum(modulated_archetypes, axis=1) # Shape: (Batch, archetype_dim)
229
+
230
+ return output_features
231
+
232
+ def compute_output_shape(self, input_shape):
233
+ return (input_shape[0], self.archetype_dim)
234
+
235
+ def get_config(self):
236
+ config = super(BalancedNoDClassificationLayer, self).get_config()
237
+ config.update({
238
+ "num_archetypes": self.num_archetypes,
239
+ "archetype_dim": self.archetype_dim,
240
+ "balance_weight": self.balance_weight,
241
+ })
242
+ return config
243
+
244
+ @classmethod
245
+ def from_config(cls, config):
246
+ return cls(**config)
247
+ ```
248
+
249
+ ## Load and evaluate saved model
250
+
251
+ ```
252
+ def evaluate_saved_model(model_path):
253
+ print(f"[INFO] Loading model from '{model_path}'...")
254
+
255
+ # 1. Load the model safely with compile=False
256
+ loaded_model = load_model(
257
+ model_path,
258
+ custom_objects={"NoDClassificationLayer": NoDClassificationLayer},
259
+ compile=False
260
+ )
261
+ print("[SUCCESS] Model loaded.")
262
+
263
+ # 2. Load the official MNIST test dataset
264
+ print("[INFO] Loading MNIST test dataset...")
265
+ (_, _), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
266
+
267
+ # 3. Apply the exact same preprocessing used during training (Flatten & Normalize)
268
+ x_test_processed = x_test.astype(np.float32) / 255.0
269
+ x_test_processed = x_test_processed.reshape(-1, 784) # Flatten to (10000, 784)
270
+
271
+ print(f"[INFO] Running inference on {len(x_test_processed)} test samples...")
272
+
273
+ # 4. Perform batch prediction
274
+ # (If memory is tight, you can batch this, but for 784-dim tiny models, direct prediction is fine)
275
+ predictions = loaded_model.predict(x_test_processed, batch_size=1024, verbose=1)
276
+
277
+ # 5. Extract predicted classes
278
+ predicted_labels = np.argmax(predictions, axis=1)
279
+
280
+ # 6. Calculate True Accuracy
281
+ correct_predictions = np.sum(predicted_labels == y_test)
282
+ total_samples = len(y_test)
283
+ test_accuracy = (correct_predictions / total_samples) * 100.0
284
+
285
+ print("\n" + "="*40)
286
+ print(f" FINAL EVALUATION REPORT")
287
+ print("="*40)
288
+ print(f" Total Test Samples : {total_samples}")
289
+ print(f" Correct Predictions: {correct_predictions}")
290
+ print(f" True Test Accuracy : {test_accuracy:.2f}%")
291
+ print("="*40)
292
+
293
+ return test_accuracy
294
+
295
+ # Test your custom images right away
296
+ # batch_test("mnist_test_img")
297
+
298
+ if __name__ == "__main__":
299
+ # Point to your saved model file
300
+ model_file = "mnist_nod_model.keras"
301
+ evaluate_saved_model(model_file)
302
+ ```
303
+
304
+ ## Implementation requirements
305
+
306
+ The model was trained in a standard consumer laptop with no CUDA capable GPU.
307
+
308
+ # Model Characteristics
309
+
310
+ ## Illustration
311
+ ### Data flow
312
+ ![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F370655%2F1dcf4aa21a76f8d0d2776f57de09143a%2Fnod_detailed_archetype_math.png?generation=1785884433086261&alt=media)
313
+ ### Model architecture
314
+ ![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F370655%2F7231aba2eb56120a3d6b8c726891628f%2FFigure_3.png?generation=1785884729004447&alt=media)
315
+ ### Inside the archetype
316
+ ![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F370655%2Ff1a57a83c1966824dd23d13ea2f38150%2FFigure_4.png?generation=1785884826561527&alt=media)
317
+
318
+ ## Model initialization
319
+
320
+ The model was trained from scratch on MNIST dataset.
321
+
322
+ ## Model stats
323
+ ### NoD 1 (1D base shared parameters)
324
+ Trainable Parameters: 13.080
325
+ File size: 218 Kbytes
326
+ Architecture: NoD (dendritic non-linear routing and archetype sharing)
327
+ Nr.of training epoch: 10 epoch
328
+ True accuracy: 88.18%
329
+
330
+ ![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F370655%2Fef77f0497a5382bd743aecc9c9fcd182%2FFigure_1_____%20-%20Copy.png?generation=1785826253853680&alt=media)
331
+
332
+ Trainable Parameters: 13.080
333
+ File size: 218 Kbytes
334
+ Architecture: NoD (dendritic non-linear routing and archetype sharing)
335
+ Nr.of training epoch: 20 epoch
336
+ True accuracy: 90.04%
337
+
338
+ ![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F370655%2F565d78ebd018c2637d881d98180721a5%2FFigure_1_20epoch.png?generation=1785827897342886&alt=media)
339
+
340
+ ### NoD 2 (2D base shared parameters)
341
+ Trainable Parameters: 116,714 parameters
342
+ File size: 455.91 KB (the actual model .keras file size uploaded is 1.4 MB)
343
+ Architecture: NoD (dendritic non-linear routing and archetype sharing)
344
+ Nr.of training epoch: 10 epoch
345
+ True accuracy: 98.83%
346
+
347
+ ![](https://www.googleapis.com/download/storage/v1/b/kaggle-user-content/o/inbox%2F370655%2F4a4dba898b3db2e1c163563b11299e2a%2FFigure_2_NoD_2D_parameters_stats.png?generation=1785892488927732&alt=media)
348
+
349
+ ## Other details
350
+
351
+ The model is not pruned nor quantized.
352
+
353
+ # Data Overview
354
+
355
+ The model trained and evaluated using MNIST dataset and standard method to split train/val/test data.
356
+
357
+ # Evaluation Results
358
+ True accuracy on MNIST test set: 88.18% (10 epoch)
359
+ True accuracy on MNIST test set: 90.04% (20 epoch)