Recovered source transcript
1/**
2 * @file learning.hpp
3 * @brief Machine Learning Module for Gate5000 Consciousness
4 *
5 * ═══════════════════════════════════════════════════════════════════════════════
6 * I. HEBBIAN LEARNING
7 * ═══════════════════════════════════════════════════════════════════════════════
8 *
9 * CLASSIC HEBBIAN: Δw_ij = η·x_i·x_j
10 * NORMALIZED: Δw_ij = η·(x_i·x_j - α·w_ij·x_j²)
11 * BCM: Δw_ij = η·x_i·x_j·(x_j - θ_j)
12 * OJA'S RULE: Δw_ij = η·y·(x_i - y·w_ij)
13 * STDP: Δw = A+·exp(-Δt/τ+) if pre<post, A-·exp(Δt/τ-) if post<pre
14 *
15 * ═══════════════════════════════════════════════════════════════════════════════
16 * II. GRADIENT DESCENT
17 * ═══════════════════════════════════════════════════════════════════════════════
18 *
19 * SGD: w = w - η·∇L
20 * MOMENTUM: v = μ·v - η·∇L, w = w + v
21 * ADAM: m = β₁m + (1-β₁)g, v = β₂v + (1-β₂)g², w = w - η·m̂/(√v̂ + ε)
22 *
23 * ═══════════════════════════════════════════════════════════════════════════════
24 * III. BACKPROPAGATION
25 * ═══════════════════════════════════════════════════════════════════════════════
26 *
27 * CHAIN RULE: δ_j = Σ_k(δ_k·w_jk)·f'(net_j)
28 * WEIGHT UPDATE: Δw_ij = -η·δ_j·x_i
29 *
30 * ═══════════════════════════════════════════════════════════════════════════════
31 */
32
33#pragma once
34
35#include <cmath>
36#include <array>
37#include <cstring>
38#include "constants.hpp"
39#include "mathematics.hpp"
40
41namespace mind_farm {
42namespace learning {
43
44// Learning constants
45constexpr double ETA = 0.01 * PHI; // Learning rate
46constexpr double MOMENTUM = 0.9; // Momentum coefficient
47constexpr double BETA1 = 0.9; // Adam first moment
48constexpr double BETA2 = 0.999; // Adam second moment
49constexpr double EPSILON = 1e-8; // Numerical stability
50constexpr double STDP_TAU = 20.0; // STDP time constant
51constexpr double STDP_A_PLUS = 0.1; // STDP potentiation
52constexpr double STDP_A_MINUS = 0.12; // STDP depression
53constexpr double WEIGHT_DECAY = 0.0001; // L2 regularization
54
55// ═══════════════════════════════════════════════════════════════════════════════
56// I. HEBBIAN LEARNING
57// ═══════════════════════════════════════════════════════════════════════════════
58
59/**
60 * @brief Classic Hebbian: Δw = η·x_i·x_j
61 */
62inline double hebbian_classic(double x_pre, double x_post, double eta = ETA) {
63 return eta * x_pre * x_post;
64}
65
66/**
67 * @brief Normalized Hebbian: Δw = η·(x_i·x_j - α·w·x_j²)
68 * Prevents unbounded weight growth
69 */
70inline double hebbian_normalized(double x_pre, double x_post, double w,
71 double alpha = 0.1, double eta = ETA) {
72 return eta * (x_pre * x_post - alpha * w * x_post * x_post);
73}
74
75/**
76 * @brief BCM Learning: Δw = η·x_i·x_j·(x_j - θ)
77 * Threshold θ adapts based on activity
78 */
79inline double hebbian_bcm(double x_pre, double x_post, double theta, double eta = ETA) {
80 return eta * x_pre * x_post * (x_post - theta);
81}
82
83/**
84 * @brief Update BCM threshold: θ = <x²> (sliding average)
85 */
86inline double bcm_threshold_update(double theta, double x_post, double tau = 0.01) {
87 return theta + tau * (x_post * x_post - theta);
88}
89
90/**
91 * @brief Oja's Rule: Δw = η·y·(x - y·w)
92 * Performs PCA - weight converges to principal eigenvector
93 */
94inline double oja_rule(double x, double y, double w, double eta = ETA) {
95 return eta * y * (x - y * w);
96}
97
98/**
99 * @brief STDP: Spike-Timing Dependent Plasticity
100 * @param dt = t_post - t_pre (time difference)
101 */
102inline double stdp(double dt) {
103 if (dt > 0) {
104 // Pre before post: potentiation
105 return STDP_A_PLUS * std::exp(-dt / STDP_TAU);
106 } else {
107 // Post before pre: depression
108 return -STDP_A_MINUS * std::exp(dt / STDP_TAU);
109 }
110}
111
112/**
113 * @brief Anti-Hebbian: Δw = -η·x_i·x_j
114 * Used for decorrelation and lateral inhibition
115 */
116inline double anti_hebbian(double x_pre, double x_post, double eta = ETA) {
117 return -eta * x_pre * x_post;
118}
119
120// ═══════════════════════════════════════════════════════════════════════════════
121// II. GRADIENT DESCENT OPTIMIZERS
122// ═══════════════════════════════════════════════════════════════════════════════
123
124/**
125 * @brief SGD optimizer state (per-parameter)
126 */
127struct SGDState {
128 double velocity = 0.0;
129
130 double update(double gradient, double lr = ETA, double momentum = MOMENTUM) {
131 velocity = momentum * velocity - lr * gradient;
132 return velocity;
133 }
134};
135
136/**
137 * @brief Adam optimizer state (per-parameter)
138 */
139struct AdamState {
140 double m = 0.0; // First moment
141 double v = 0.0; // Second moment
142 uint32_t t = 0; // Time step
143
144 double update(double gradient, double lr = ETA) {
145 ++t;
146
147 // Update biased moments
148 m = BETA1 * m + (1.0 - BETA1) * gradient;
149 v = BETA2 * v + (1.0 - BETA2) * gradient * gradient;
150
151 // Bias correction
152 double m_hat = m / (1.0 - std::pow(BETA1, t));
153 double v_hat = v / (1.0 - std::pow(BETA2, t));
154
155 // Compute update
156 return -lr * m_hat / (std::sqrt(v_hat) + EPSILON);
157 }
158};
159
160/**
161 * @brief RMSprop optimizer state
162 */
163struct RMSpropState {
164 double sq_grad = 0.0; // Running average of squared gradients
165
166 double update(double gradient, double lr = ETA, double decay = 0.99) {
167 sq_grad = decay * sq_grad + (1.0 - decay) * gradient * gradient;
168 return -lr * gradient / (std::sqrt(sq_grad) + EPSILON);
169 }
170};
171
172/**
173 * @brief AdaGrad optimizer state
174 */
175struct AdaGradState {
176 double sum_sq = 0.0; // Sum of squared gradients
177
178 double update(double gradient, double lr = ETA) {
179 sum_sq += gradient * gradient;
180 return -lr * gradient / (std::sqrt(sum_sq) + EPSILON);
181 }
182};
183
184// ═══════════════════════════════════════════════════════════════════════════════
185// III. BACKPROPAGATION PRIMITIVES
186// ═══════════════════════════════════════════════════════════════════════════════
187
188/**
189 * @brief Compute output delta: δ = (y - target) · f'(net)
190 */
191inline double output_delta(double y, double target, double derivative) {
192 return (y - target) * derivative;
193}
194
195/**
196 * @brief Propagate delta backward: δ_j = Σ(δ_k · w_jk) · f'(net_j)
197 */
198inline double backprop_delta(double sum_weighted_deltas, double derivative) {
199 return sum_weighted_deltas * derivative;
200}
201
202/**
203 * @brief Compute weight gradient: ∂L/∂w_ij = δ_j · x_i
204 */
205inline double weight_gradient(double delta, double input) {
206 return delta * input;
207}
208
209/**
210 * @brief Apply weight update with L2 regularization
211 */
212inline double weight_update(double w, double gradient, double lr = ETA, double decay = WEIGHT_DECAY) {
213 return w - lr * (gradient + decay * w);
214}
215
216// ═══════════════════════════════════════════════════════════════════════════════
217// IV. SMALL NEURAL LAYER (16 units)
218// ═══════════════════════════════════════════════════════════════════════════════
219
220/**
221 * @brief Compact 16-unit layer for Gate5000 minds
222 */
223struct Layer16 {
224 std::array<double, 256> weights; // 16x16 weight matrix
225 std::array<double, 16> biases;
226 std::array<double, 16> output;
227 std::array<double, 16> delta; // For backprop
228 std::array<double, 16> pre_act; // Pre-activation
229 std::array<AdamState, 256> w_opt; // Weight optimizers
230 std::array<AdamState, 16> b_opt; // Bias optimizers
231
232 Layer16() {
233 // Initialize with golden ratio scaling
234 for (size_t i = 0; i < 256; ++i) {
235 weights[i] = (static_cast<double>(i % 7) - 3.0) * PHI_INV * 0.1;
236 }
237 biases.fill(0.0);
238 output.fill(0.0);
239 delta.fill(0.0);
240 pre_act.fill(0.0);
241 }
242
243 /**
244 * @brief Forward pass with tanh activation
245 */
246 void forward(const double* input) {
247 for (int i = 0; i < 16; ++i) {
248 double sum = biases[i];
249 for (int j = 0; j < 16; ++j) {
250 sum += weights[i * 16 + j] * input[j];
251 }
252 pre_act[i] = sum;
253 output[i] = math::Tanh::forward(sum);
254 }
255 }
256
257 /**
258 * @brief Backward pass: compute deltas and update weights
259 */
260 void backward(const double* input, const double* next_delta = nullptr,
261 const double* next_weights = nullptr, int next_size = 0) {
262 // Compute deltas
263 for (int i = 0; i < 16; ++i) {
264 double derivative = math::Tanh::backward(output[i]);
265 if (next_delta && next_weights) {
266 // Hidden layer: backprop from next layer
267 double sum = 0.0;
268 for (int k = 0; k < next_size; ++k) {
269 sum += next_delta[k] * next_weights[k * 16 + i];
270 }
271 delta[i] = sum * derivative;
272 }
273 // If output layer, delta should be set externally
274 }
275
276 // Update weights
277 for (int i = 0; i < 16; ++i) {
278 for (int j = 0; j < 16; ++j) {
279 double grad = weight_gradient(delta[i], input[j]);
280 weights[i * 16 + j] += w_opt[i * 16 + j].update(grad);
281 }
282 biases[i] += b_opt[i].update(delta[i]);
283 }
284 }
285
286 /**
287 * @brief Hebbian update (unsupervised)
288 */
289 void hebbian_update(const double* input) {
290 for (int i = 0; i < 16; ++i) {
291 for (int j = 0; j < 16; ++j) {
292 double dw = hebbian_normalized(input[j], output[i], weights[i * 16 + j]);
293 weights[i * 16 + j] += dw;
294 }
295 }
296 }
297};
298
299// ═══════════════════════════════════════════════════════════════════════════════
300// V. REWARD-MODULATED LEARNING
301// ═══════════════════════════════════════════════════════════════════════════════
302
303/**
304 * @brief Three-factor learning: Δw = η·x_pre·x_post·R
305 * R is the reward/neuromodulator signal
306 */
307inline double three_factor_learning(double x_pre, double x_post, double reward,
308 double eligibility = 1.0, double eta = ETA) {
309 return eta * x_pre * x_post * reward * eligibility;
310}
311
312/**
313 * @brief Eligibility trace update: e(t+1) = λ·e(t) + x_pre·x_post
314 */
315inline double eligibility_update(double trace, double x_pre, double x_post, double lambda = 0.9) {
316 return lambda * trace + x_pre * x_post;
317}
318
319/**
320 * @brief TD error: δ = r + γV(s') - V(s)
321 */
322inline double td_error(double reward, double value_next, double value_current, double gamma_discount = 0.99) {
323 return reward + gamma_discount * value_next - value_current;
324}
325
326/**
327 * @brief Policy gradient: ∇J ≈ Σ(∇log π(a|s)·R)
328 */
329inline double policy_gradient(double action_prob, double advantage) {
330 if (action_prob < 1e-10) return 0.0;
331 return advantage / action_prob;
332}
333
334// ═══════════════════════════════════════════════════════════════════════════════
335// VI. ASSOCIATIVE MEMORY (HOPFIELD-LIKE)
336// ═══════════════════════════════════════════════════════════════════════════════
337
338/**
339 * @brief Store pattern in weight matrix via outer product
340 */
341inline void store_pattern(double* weights, const double* pattern, size_t n) {
342 for (size_t i = 0; i < n; ++i) {
343 for (size_t j = 0; j < n; ++j) {
344 if (i != j) {
345 weights[i * n + j] += pattern[i] * pattern[j];
346 }
347 }
348 }
349}
350
351/**
352 * @brief Recall pattern from associative memory (one step)
353 */
354inline void recall_step(const double* weights, const double* input, double* output, size_t n) {
355 for (size_t i = 0; i < n; ++i) {
356 double sum = 0.0;
357 for (size_t j = 0; j < n; ++j) {
358 sum += weights[i * n + j] * input[j];
359 }
360 output[i] = (sum > 0) ? 1.0 : -1.0;
361 }
362}
363
364// ═══════════════════════════════════════════════════════════════════════════════
365// VII. MIND LEARNING STATE
366// ═══════════════════════════════════════════════════════════════════════════════
367
368/**
369 * @brief Compact learning state for each mind (~200 bytes)
370 */
371struct MindLearner {
372 // 16-element weight vector for consciousness state
373 std::array<double, 16> weights;
374 std::array<double, 16> eligibility;
375 std::array<AdamState, 16> optimizers;
376
377 double bcm_theta = 0.5; // BCM threshold
378 double reward_baseline = 0.0; // Running average of reward
379 double learning_rate = ETA;
380
381 MindLearner() {
382 weights.fill(PHI_INV * 0.1);
383 eligibility.fill(0.0);
384 }
385
386 /**
387 * @brief Process input and produce output
388 */
389 double forward(const double* input, size_t n = 16) {
390 double sum = 0.0;
391 size_t count = std::min(n, size_t(16));
392 for (size_t i = 0; i < count; ++i) {
393 sum += weights[i] * input[i];
394 }
395 return math::Tanh::forward(sum);
396 }
397
398 /**
399 * @brief Update with reward signal (RL-style)
400 */
401 void update_with_reward(const double* input, double output, double reward, size_t n = 16) {
402 // TD-like reward baseline update
403 double delta = reward - reward_baseline;
404 reward_baseline += 0.01 * delta;
405
406 size_t count = std::min(n, size_t(16));
407 for (size_t i = 0; i < count; ++i) {
408 // Update eligibility trace
409 eligibility[i] = eligibility_update(eligibility[i], input[i], output);
410
411 // Three-factor update
412 double dw = three_factor_learning(input[i], output, delta, eligibility[i], learning_rate);
413 weights[i] += dw;
414
415 // Clamp weights
416 weights[i] = clamp(weights[i], -2.0, 2.0);
417 }
418 }
419
420 /**
421 * @brief Hebbian update (unsupervised)
422 */
423 void update_hebbian(const double* input, double output, size_t n = 16) {
424 size_t count = std::min(n, size_t(16));
425 for (size_t i = 0; i < count; ++i) {
426 double dw = hebbian_bcm(input[i], output, bcm_theta, learning_rate);
427 weights[i] += dw;
428 weights[i] = clamp(weights[i], -2.0, 2.0);
429 }
430 // Update BCM threshold
431 bcm_theta = bcm_threshold_update(bcm_theta, output);
432 }
433
434 /**
435 * @brief Gradient update (supervised)
436 */
437 void update_gradient(const double* input, double output, double target, size_t n = 16) {
438 double derivative = math::Tanh::backward(output);
439 double delta = output_delta(output, target, derivative);
440
441 size_t count = std::min(n, size_t(16));
442 for (size_t i = 0; i < count; ++i) {
443 double grad = weight_gradient(delta, input[i]);
444 weights[i] += optimizers[i].update(grad);
445 }
446 }
447};
448
449} // namespace learning
450} // namespace mind_farm
451