| | |
| | |
| | |
| | |
| | |
| | export class NeuralNetwork { |
| | |
| | |
| | |
| | constructor(topology) { |
| | this.topology = topology; |
| | this.weights = []; |
| | this.biases = []; |
| | this.activations = []; |
| | this._initWeights(); |
| | } |
| |
|
| | |
| | _initWeights() { |
| | for (let i = 1; i < this.topology.length; i++) { |
| | const fanIn = this.topology[i - 1]; |
| | const fanOut = this.topology[i]; |
| | const scale = Math.sqrt(2 / (fanIn + fanOut)); |
| | const layerW = new Array(fanOut); |
| | const layerB = new Array(fanOut); |
| | for (let j = 0; j < fanOut; j++) { |
| | layerW[j] = new Array(fanIn); |
| | for (let k = 0; k < fanIn; k++) { |
| | layerW[j][k] = (Math.random() * 2 - 1) * scale; |
| | } |
| | layerB[j] = (Math.random() * 2 - 1) * 0.1; |
| | } |
| | this.weights.push(layerW); |
| | this.biases.push(layerB); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | forward(inputs) { |
| | let current = inputs; |
| | this.activations = [inputs.slice()]; |
| |
|
| | for (let layer = 0; layer < this.weights.length; layer++) { |
| | const w = this.weights[layer]; |
| | const b = this.biases[layer]; |
| | const numNeurons = w.length; |
| | const next = new Array(numNeurons); |
| | const isOutput = layer === this.weights.length - 1; |
| |
|
| | for (let j = 0; j < numNeurons; j++) { |
| | let sum = b[j]; |
| | const wj = w[j]; |
| | for (let k = 0; k < current.length; k++) { |
| | sum += wj[k] * current[k]; |
| | } |
| | next[j] = Math.tanh(sum); |
| | } |
| |
|
| | current = next; |
| | this.activations.push(current.slice()); |
| | } |
| |
|
| | return current; |
| | } |
| |
|
| | |
| | get totalWeights() { |
| | let count = 0; |
| | for (let i = 0; i < this.weights.length; i++) { |
| | count += this.weights[i].length * this.weights[i][0].length; |
| | count += this.biases[i].length; |
| | } |
| | return count; |
| | } |
| |
|
| | |
| | serialize() { |
| | const flat = new Array(this.totalWeights); |
| | let idx = 0; |
| | for (let i = 0; i < this.weights.length; i++) { |
| | const w = this.weights[i]; |
| | for (let j = 0; j < w.length; j++) { |
| | for (let k = 0; k < w[j].length; k++) { |
| | flat[idx++] = w[j][k]; |
| | } |
| | } |
| | const b = this.biases[i]; |
| | for (let j = 0; j < b.length; j++) { |
| | flat[idx++] = b[j]; |
| | } |
| | } |
| | return flat; |
| | } |
| |
|
| | |
| | deserialize(flat) { |
| | let idx = 0; |
| | for (let i = 0; i < this.weights.length; i++) { |
| | const w = this.weights[i]; |
| | for (let j = 0; j < w.length; j++) { |
| | for (let k = 0; k < w[j].length; k++) { |
| | w[j][k] = flat[idx++]; |
| | } |
| | } |
| | const b = this.biases[i]; |
| | for (let j = 0; j < b.length; j++) { |
| | b[j] = flat[idx++]; |
| | } |
| | } |
| | } |
| |
|
| | |
| | clone() { |
| | const nn = new NeuralNetwork(this.topology.slice()); |
| | nn.deserialize(this.serialize()); |
| | return nn; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | mutate(rate, magnitude) { |
| | for (let i = 0; i < this.weights.length; i++) { |
| | const w = this.weights[i]; |
| | for (let j = 0; j < w.length; j++) { |
| | for (let k = 0; k < w[j].length; k++) { |
| | if (Math.random() < rate) { |
| | w[j][k] += gaussianNoise() * magnitude; |
| | } |
| | } |
| | } |
| | const b = this.biases[i]; |
| | for (let j = 0; j < b.length; j++) { |
| | if (Math.random() < rate) { |
| | b[j] += gaussianNoise() * magnitude; |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | crossover(other) { |
| | const child = new NeuralNetwork(this.topology.slice()); |
| | const w1 = this.serialize(); |
| | const w2 = other.serialize(); |
| | const childW = new Array(w1.length); |
| |
|
| | const crossPoint = Math.floor(Math.random() * w1.length); |
| | for (let i = 0; i < w1.length; i++) { |
| | childW[i] = i < crossPoint ? w1[i] : w2[i]; |
| | } |
| | child.deserialize(childW); |
| | return child; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | getMaxActivation() { |
| | let max = 0; |
| | for (const layer of this.activations) { |
| | for (const v of layer) { |
| | const abs = Math.abs(v); |
| | if (abs > max) max = abs; |
| | } |
| | } |
| | return max; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | renderMini(ctx, x, y, w, h) { |
| | const layers = this.topology.length; |
| | const layerSpacing = w / (layers - 1); |
| |
|
| | ctx.lineWidth = 0.5; |
| | for (let l = 0; l < this.weights.length; l++) { |
| | const fromCount = this.topology[l]; |
| | const toCount = this.topology[l + 1]; |
| | const fromX = x + l * layerSpacing; |
| | const toX = x + (l + 1) * layerSpacing; |
| |
|
| | for (let j = 0; j < toCount; j++) { |
| | const toY = y + (j + 0.5) * (h / toCount); |
| | for (let k = 0; k < fromCount; k++) { |
| | const fromY = y + (k + 0.5) * (h / fromCount); |
| | const weight = this.weights[l][j][k]; |
| | const alpha = Math.min(Math.abs(weight) * 0.5, 0.6); |
| | ctx.strokeStyle = |
| | weight > 0 |
| | ? `rgba(0, 240, 255, ${alpha})` |
| | : `rgba(255, 51, 102, ${alpha})`; |
| | ctx.beginPath(); |
| | ctx.moveTo(fromX, fromY); |
| | ctx.lineTo(toX, toY); |
| | ctx.stroke(); |
| | } |
| | } |
| | } |
| |
|
| | for (let l = 0; l < layers; l++) { |
| | const count = this.topology[l]; |
| | const lx = x + l * layerSpacing; |
| | const act = this.activations[l] || []; |
| |
|
| | for (let n = 0; n < count; n++) { |
| | const ny = y + (n + 0.5) * (h / count); |
| | const val = act[n] || 0; |
| | const intensity = Math.abs(val); |
| | const r = Math.max(2, 4 - layers * 0.3); |
| |
|
| | ctx.beginPath(); |
| | ctx.arc(lx, ny, r, 0, Math.PI * 2); |
| | ctx.fillStyle = |
| | val > 0 |
| | ? `rgba(0, 240, 255, ${0.3 + intensity * 0.7})` |
| | : `rgba(255, 51, 102, ${0.3 + intensity * 0.7})`; |
| | ctx.fill(); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | |
| | function gaussianNoise() { |
| | let u = 0, |
| | v = 0; |
| | while (u === 0) u = Math.random(); |
| | while (v === 0) v = Math.random(); |
| | return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export class QuantumDecisionLayer { |
| | |
| | |
| | |
| | |
| | constructor(temperature = 1.0) { |
| | this.temperature = temperature; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | sample(logits) { |
| | const t = this.temperature; |
| | const maxLogit = Math.max(...logits); |
| | const exps = logits.map((l) => Math.exp((l - maxLogit) / t)); |
| | const sum = exps.reduce((a, b) => a + b, 0); |
| | const probs = exps.map((e) => e / sum); |
| |
|
| | const r = Math.random(); |
| | let cumulative = 0; |
| | for (let i = 0; i < probs.length; i++) { |
| | cumulative += probs[i]; |
| | if (r <= cumulative) { |
| | return { action: i, probabilities: probs }; |
| | } |
| | } |
| | return { action: probs.length - 1, probabilities: probs }; |
| | } |
| | } |
| |
|