diff --git a/app.js b/app.js new file mode 100644 index 0000000..ae72a6e --- /dev/null +++ b/app.js @@ -0,0 +1,1001 @@ +/* ============================================================================ + NEURALFLOW — Gerçek Zamanlı Sinir Ağı Simülatörü + Sıfır kütüphane · saf Canvas 2D · tüm matematik elle + ============================================================================ */ +"use strict"; + +/* ---------- Sabitler / durum ---------- */ +const MAX_LOSS_POINTS = 360; // saklanan loss noktası üst sınırı +const PULSE_SPEED = 0.55; // puls ilerleme hızı (birim/sn … 1 birim). +const HEAT_REFRESH_EVERY = 6; // kaç framede bir heatmap yeniden çizilir +const GRADIENT_DECAY = 0.94; // node "gradyan ısısı" sönümü + +/* Durum: kullanıcı arayüzünden gelen çizim/eğitim ayarları */ +let state = { + trainRunning: false, // RUN modu + activation: 'sigmoid', // sigmoid | relu + learningRate: 0.1, + inputCount: 2, + hiddenNeurons: 4, // her gizli katman için nöron + hiddenLayers: 3, // gizli katman sayısı (1..3) + outputCount: 1, +}; +state.layerConfig = []; +state.layerSizes = []; +state.layerLabels = []; + +/* Ağ modeli */ +let net = { + layers: [], // [{name, size}] + activations: [], // [] her katman aktivasyon dizisi + layerSum: [], // [] aktivasyondan önceki W*a+b (backprop kolaylığı) + weights: [], // [katmanIdx][hedefN][kaynakN] (katmanIdx>=1: giriş katmanı weights içinde yok) + biases: [], // [katmanIdx][hedefN] (katmanIdx>=1) + weightPulses: [], // animasyonlu ağırlık "nabzı": [{li,ri,from,to,…}] + nodeBackHeat: [], // [] her katmanın nöronuna "geri ısısı/alevi" oranı + nodeFwdHeat: [], // [] her katmanın nöronuna "ileri ısısı" (sinyal parlaması) + runningInput: [], // mevcut eğitim örneğinin girdi vektörü + runningTarget: [], // hedef çıktı (girdi katmanında etiket için kullanılabilir) +}; + +let lossHistory = []; // MSE geçmişi +let samplePointer = 0; // sürekli RUN modunda sıradaki eğitim örneğinin index'i +let animationId = null; +let lastTs = 0; +let frameCount = 0; +let epochCounter = 0; + +/* CANVAS ctx'leri */ +let neuralCv, neuralCtx; +let heatCv, heatCtx; +let lossCv, lossCtx; + +/* Geometri: nöron konumları (her çerçevede modelden türetilir) */ +let NODE_X = []; // [katman] x merkezi +let NODE_Y = []; // [katman][] nöron y konumu + +/* ---------- DOM yardımcıları ---------- */ +function $(id) { return document.getElementById(id); } + +/* ============================================================================ + AKTİVASYON & TÜREVLERİ (hep elle) + ============================================================================ */ +function sigmoid(x) { + if (x < -45) return 0; + if (x > 45) return 1; + return 1 / (1 + Math.exp(-x)); +} +function sigmoidDerivative(activated) { + // activated katmanın ÇIKIŞ değeri (zaten aktivasyon uygulanmış) + return activated * (1 - activated); +} +function relu(x) { return x > 0 ? x : 0; } +function reluDerivative(x) { + // x = activasyona GİREN net girdi (sum). ReLU türevi. + return x > 0 ? 1 : 0; +} + +/* Per-node activation + türev, katman çıktı değeri & düz net girdi üzerinden */ +function activateNode(kind, x) { + if (kind === 'relu') return relu(x); + return sigmoid(x); +} +function derivativeOf(kind, output, netInput) { + if (kind === 'relu') return reluDerivative(netInput); + return sigmoidDerivative(output); +} + +/* Küçük Gaussian benzeri rastgele ağırlık */ +function randomWeight() { + return (Math.random() + Math.random() + Math.random()) / 1.5 - 1; // [-1..1] +} +function randomBias() { + return (Math.random() - 0.5) * 0.8; +} + +/* ============================================================================ + MİMARİ YÖNETİMİ + ============================================================================ */ +function layersConfig() { + // state -> katman listesi [{name,size}] + const cfg = [{ name: 'input', size: state.inputCount }]; + for (let h = 1; h <= state.hiddenLayers; h++) { + cfg.push({ name: 'hidden' + h, size: state.hiddenNeurons }); + } + cfg.push({ name: 'output', size: state.outputCount }); + return cfg; +} + +function buildNetwork() { + const cfg = layersConfig(); + state.layerConfig = cfg; + state.layerSizes = cfg.map(function (l) { return l.size; }); + state.layerLabels = cfg.map(function (l) { return l.name; }); + + net.layers = cfg.slice(); + const L = cfg.length; + const acts = []; // katman sayısı kadar dizi + const sums = []; + const nbh = []; + const nfh = []; + for (let i = 0; i < L; i++) { + acts.push(new Array(cfg[i].size).fill(0)); + sums.push(new Array(cfg[i].size).fill(0)); + nbh.push(new Array(cfg[i].size).fill(0)); + nfh.push(new Array(cfg[i].size).fill(0)); + } + net.activations = acts; + net.layerSum = sums; + net.nodeBackHeat = nbh; + net.nodeFwdHeat = nfh; + + // weights/biases: katman indeks 1..L-1 (çıktı ya da gizli katmanı üreten) + const W = []; + const B = []; + for (let k = 1; k < L; k++) { + const prev = cfg[k - 1].size; + const cur = cfg[k].size; + const m = []; + for (let j = 0; j < cur; j++) { + const row = []; + for (let i = 0; i < prev; i++) row.push(randomWeight()); + m.push(row); + } + W.push(m); + const b = []; + for (let j = 0; j < cur; j++) b.push(randomBias()); + B.push(b); + } + net.weights = W; // length = L-1 + net.biases = B; + + net.runningInput = new Array(cfg[0].size).fill(0); + net.runningTarget = new Array(cfg[L - 1].size).fill(0); + + // puls & ısı listesini sıfırla + net.weightPulses = []; +} + +function resetNetwork() { + buildNetwork(); + lossHistory = []; + samplePointer = 0; + epochCounter = 0; + updateArchBars(); + updateHUD(); +} + +/* ============================================================================ + GERİ YAYILIM (backpropagation) — matematik elle + katman k için delta: önce çıktı katmanı + δ_out = (a - target) · act'(z) + sonra gizli katmanlar geriye doğru zincir kuralı: + δ_k = (W_{k+1}^T · δ_{k+1}) ⊙ act'(z_k) + Gradyanlar: ∇W_k = δ_k · a_{k-1}^T, ∇b_k = δ_k + ============================================================================ */ +function backprop(target) { + const L = state.layerSizes.length; + const acts = net.activations; + const sums = net.layerSum; + const delta = []; + for (let k = 0; k < L; k++) delta.push(new Float64Array(state.layerSizes[k])); + + // ---- çıktı katmanı hatası ---- + const out = acts[L - 1]; + const dOut = delta[L - 1]; + for (let j = 0; j < out.length; j++) { + const err = out[j] - target[j]; + dOut[j] = err * derivativeOf(state.activation, out[j], sums[L - 1][j]); + } + + // ---- gizli katmanlar geriye ---- + for (let k = L - 2; k >= 1; k--) { + const Wnext = net.weights[k]; // (k)->(k+1) + const dCurr = delta[k]; + const dNext = delta[k + 1]; + const szNext = state.layerSizes[k + 1]; + for (let i = 0; i < state.layerSizes[k]; i++) { + let sumErr = 0; + for (let j = 0; j < szNext; j++) sumErr += Wnext[j][i] * dNext[j]; + dCurr[i] = sumErr * derivativeOf(state.activation, acts[k][i], sums[k][i]); + } + } + + // nodeBackHeat'i gradyan ile "alevle" + for (let k = 1; k < L; k++) { + const dk = delta[k]; + const heat = net.nodeBackHeat[k]; + for (let j = 0; j < dk.length; j++) { + const mag = Math.abs(dk[j]); + if (mag > heat[j]) heat[j] = Math.min(1, heat[j] + mag * 3); + } + } + return delta; +} + +/* ============================================================================ + SGD GÜNCELLEMESİ — w -= lr·∇W , b -= lr·∇b + ============================================================================ */ +function applySGD(learningRate, delta) { + const L = state.layerSizes.length; + const acts = net.activations; + for (let k = 1; k < L; k++) { + const Wk = net.weights[k - 1]; + const Bk = net.biases[k - 1]; + const dk = delta[k]; + const prev = acts[k - 1]; + for (let j = 0; j < Wk.length; j++) { + const row = Wk[j]; + for (let i = 0; i < row.length; i++) { + row[i] -= learningRate * dk[j] * prev[i]; + } + Bk[j] -= learningRate * dk[j]; + } + } +} + +/* ============================================================================ + EĞİTİM ADIMI + ============================================================================ */ +function emitForwardSignals() { + // ileri yön sinyal puls'ları: katman k-1 -> k, mor/cyan, aktivasyon & |w| ile orantılı + const L = state.layerSizes.length; + const acts = net.activations; + const pulses = net.weightPulses; + for (let k = 1; k < L; k++) { + const prevY = NODE_Y[k - 1]; if (!prevY) continue; + const curY = NODE_Y[k]; if (!curY) continue; + const Wk = net.weights[k - 1]; + const xA = NODE_X[k - 1], xB = NODE_X[k]; + for (let j = 0; j < curY.length; j++) { + for (let i = 0; i < prevY.length; i++) { + const a = acts[k - 1][i]; + const wgt = Wk[j][i]; + const mag = a * Math.abs(wgt); + if (mag < 0.03 || pulses.length > 420) continue; + pulses.push({ + li: k - 1, ri: k, i, j, + fromX: xA, fromY: prevY[i], + toX: xB, toY: curY[j], + from: i, to: o2n(k, j), + progress: 0, dir: 'fwd', + speed: 0.5 + Math.random() * 0.4 + mag * 2, + mag: Math.min(1, 0.2 + mag * 1.8), + hueRef: wgt >= 0 ? 285 : 330, // pozitif mor, negatif pembe + }); + } + } + } +} + +/* ============================================================================ + İLERİ YAYILIM (forward pass) — matematik elle + katman katman: z_k = W_k·a_{k-1} + b_k , a_k = act(z_k) + ============================================================================ */ +function forwardPass(inputArr) { + const L = state.layerSizes.length; + const acts = net.activations; + const sums = net.layerSum; + const kind = state.activation; + // girdi katmanı aktivasyonunu koy + const inSize = state.layerSizes[0]; + for (let i = 0; i < inSize; i++) { + acts[0][i] = (i < inputArr.length) ? (inputArr[i] || 0) : 0; + } + for (let k = 1; k < L; k++) { + const curSize = state.layerSizes[k]; + const prevSize = state.layerSizes[k - 1]; + const Wk = net.weights[k - 1]; + const Bk = net.biases[k - 1]; + const prevA = acts[k - 1]; + const curA = acts[k]; + const curS = sums[k]; + for (let j = 0; j < curSize; j++) { + let z = Bk[j]; + const row = Wk[j]; + for (let i = 0; i < prevSize; i++) z += row[i] * prevA[i]; + curS[j] = z; + curA[j] = activateNode(kind, z); + } + } + return acts[L - 1]; +} + +/* Ortalama kare hata (MSE) — elle */ +function meanSquaredError(target, output) { + const n = Math.min(target.length, output.length); + let s = 0; + for (let j = 0; j < n; j++) { + const d = output[j] - target[j]; + s += d * d; + } + return n ? s / n : 0; +} + +/* ============================================================================ + EĞİTİM ÖRNEĞİ ÜRETİCİ — deterministik, tekrarlı döngü (öğrenilebilir) + Girdi sabit "desen havuzu"ndan, hedef türetilmiş yumuşak fonksiyondur. + Desen halkası tekrar ettiği için küçük ağlar bile zamanla uymayı öğrenir + → loss çizgisi gerçekten düşer. + ============================================================================ */ +function nextSample() { + const nIn = state.inputCount; + const nOut = state.outputCount; + if (!nIn || nIn < 1) { state.inputCount = 2; return nextSample(); } + + const i = samplePointer % 32; // kısa halka, tekrarlanır + const t = i / 32; + + // 1) giriş deseni: bileşik sinusler → pürüzsüz ama ayırt edici girdi + const f1 = Math.sin(t * Math.PI * 2 * 1.0); + const f2 = Math.sin(t * Math.PI * 2 * 2.0 + 0.9); + const f3 = Math.sin(t * Math.PI * 4.0 + 0.4); + const ph = i * 2.399963; // altın oran tarama + + const input = []; + for (let x = 0; x < nIn; x++) { + // her giriş biraz farklı fazlanmış dalga (0..1) + ince parazit + const raw = 0.5 * f1 + 0.28 * f2 * Math.cos(0.9 + x * ph) + 0.22 * f3; + input.push((raw * 0.5 + 0.5)); // [0,1] bandı + } + + // 2) çıktı hedefi: girişten deterministik türetilmiş sinyal (0.05..0.95) + const target = []; + for (let y = 0; y < nOut; y++) { + const yk = y + 1; + // bileşik sinüs → sigmoid aralığına sıkıştır, öğrenilebilir ilişki + const sig = + Math.sin(input[0] * Math.PI * (1 + yk * 0.17) + yk * 0.6) * 0.5 + + Math.cos((nIn > 1 ? input[nIn - 1] : input[0]) * Math.PI * (0.5 + yk * 0.23)) * 0.5; + target.push(0.5 + 0.42 * sig); // (0.08, 0.92) → sigmoid çıktısı erişebilsin + } + + return { input: input, target: target }; +} + +function trainStep() { + const sample = nextSample(); + samplePointer++; + net.runningInput = sample.input.slice(); + net.runningTarget = sample.target.slice(); + + forwardPass(sample.input); + emitForwardSignals(); + const out = net.activations[state.layerSizes.length - 1].slice(); + const loss = meanSquaredError(sample.target, out); + lossHistory.push(loss); + if (lossHistory.length > MAX_LOSS_POINTS) lossHistory.shift(); + epochCounter++; + + const delta = backprop(sample.target); + applySGD(state.learningRate, delta); + generateBackflowVisual(delta, sample.target, out); + updateHUD(); + return loss; +} + +/* "TRAIN STEP" butonu — tek adım + gradyan patlaması */ +function onEpochStep() { trainStep(); } + +/* ============================================================================ + GERİ AKIŞ GÖRSELİ — gradyan büyüklüğüne göre sağdan sola turuncu alev + ============================================================================ */ +function generateBackflowVisual(delta, target, output) { + const L = state.layerSizes.length; + const pulses = net.weightPulses; + computeLayerXCache(); + for (let k = L - 1; k >= 1; k--) { + const dk = delta[k]; + const curY = NODE_Y[k]; + const prevY = NODE_Y[k - 1]; + const xA = columnXOf(k - 1); + const xB = columnXOf(k); + for (let j = 0; j < dk.length; j++) { + const heat = Math.abs(dk[j]); + if (heat < 0.02) continue; + for (let i = 0; i < prevY.length; i++) { + const w = net.weights[k - 1][j][i] || 0; + const mag = heat * (Math.abs(w) + 0.25); + if (mag < 0.006) continue; + if (pulses.length > 320) break; + pulses.push({ + li: k - 1, ri: k, + i: i, j: j, + fromX: xA, fromY: prevY[i], + toX: xB, toY: curY[j], + from: i, to: o2n(k, j), + progress: 0, dir: 'bwd', + speed: 0.45 + Math.random() * 0.5 + mag, + mag: Math.min(1, mag), + hueRef: 20, // alev turuncusu + }); + } + } + } +} + +/* "to" hedefini node global index gibi kullanacak helper (sadece etiket) */ +function o2n(k, j) { return k * 100 + j; } + +/* ------------------ katman x konumu yardımcıları ------------------ */ +let _layerXCache = []; +function computeLayerXCache() { + const n = state.layerSizes.length; + _layerXCache = []; + for (let k = 0; k < n; k++) _layerXCache.push((k + 0.5) / n); +} +function columnXOf(k) { + if (!_layerXCache.length) computeLayerXCache(); + return _layerXCache[k]; +} + +/* ============================================================================ + SİNYAL / PULS ANİMASYONU + ============================================================================ */ +function setLinkPulse(li, i, j, dir, intensity) { + // :harici denetçiler için hedeflenmiş bir puls başlatma API'si + const k = li + 1; + if (k >= state.layerSizes.length) return; + const prevY = NODE_Y[k - 1]; + const curY = NODE_Y[k]; + if (!prevY || !curY) return; + net.weightPulses.push({ + li, ri: k, i, j, + fromX: columnXOf(k - 1), fromY: prevY[i], + toX: columnXOf(k), toY: curY[j], + from: i, to: o2n(k, j), + progress: 0, dir: dir || 'fwd', + speed: 0.5, mag: Math.min(1, intensity || 0.3), + hueRef: dir === 'bwd' ? 22 : 285, + }); +} + +function tickSignals(dt) { + // dt saniye + const pulses = net.weightPulses; + const alive = []; + for (let p = 0; p < pulses.length; p++) { + const pl = pulses[p]; + pl.progress += dt * pl.speed; + if (pl.progress >= 1) { continue; } // ölü -> atla + alive.push(pl); + } + net.weightPulses = alive; + // kurallı sıfırlayıcı büyüme kısıtı yok +} + +function growBackHeat() { + // frame bazlı nodeBackHeat sönümü + for (let k = 0; k < net.nodeBackHeat.length; k++) { + const arr = net.nodeBackHeat[k]; + for (let j = 0; j < arr.length; j++) { + if (arr[j] > 0.004) arr[j] *= GRADIENT_DECAY; else arr[j] = 0; + } + } +} + +function growFwdHeat() { + for (let k = 0; k < net.nodeFwdHeat.length; k++) { + const arr = net.nodeFwdHeat[k]; + for (let j = 0; j < arr.length; j++) { + if (arr[j] > 0.002) arr[j] *= 0.92; else arr[j] = 0; + } + } +} + +/* ============================================================================ + GEOMETRİ: nöron yerleşimi (canvas gerçek pikselinden bağımsız normalized 0..1) + ============================================================================ */ +function computeGeometry(width, height) { + const L = state.layerSizes.length; + NODE_X = []; + NODE_Y = []; + // padding + const padX = Math.min(46, width * 0.06); + const padY = Math.min(26, height * 0.07); + const drawW = Math.max(10, width - padX * 2); + const drawH = Math.max(10, height - padY * 2); + for (let k = 0; k < L; k++) { + const x = padX + drawW * (L === 1 ? 0.5 : k / (L - 1)); + NODE_X.push(x); + const size = state.layerSizes[k]; + const col = []; + if (size === 1) col.push(height / 2); + else for (let j = 0; j < size; j++) col.push(padY + drawH * (j / (size - 1))); + NODE_Y.push(col); + } + computeLayerXCache(); +} + +/* ============================================================================ + ÇİZİM: ORTA (NÖRON AKIŞI) KANVASI + ============================================================================ */ +function drawNeuralCanvas() { + const cv = neuralCv; + if (!cv) return; + const w = cv.width, h = cv.height; + const g = neuralCtx; + g.clearRect(0, 0, w, h); + + // zaman birikimi (yumuşak nabız/akış efektleri için) + const tSec = frameCount * 0.016; + + // hafif ızgara arka plan deseni — çok hafif "nefes alan" canlılık + const breathe = 0.5 + 0.5 * Math.sin(tSec * 0.7); + g.strokeStyle = 'rgba(90,120,220,' + (0.04 + breathe * 0.03) + ')'; + g.lineWidth = 1; + const step = 36; + for (let x = 0; x < w; x += step) { g.beginPath(); g.moveTo(x, 0); g.lineTo(x, h); g.stroke(); } + for (let y = 0; y < h; y += step) { g.beginPath(); g.moveTo(0, y); g.lineTo(w, y); g.stroke(); } + + // süzülen "veri tozları" — yavaşça sağa süzülen neon parçacık sürüsü + for (let d = 0; d < 14; d++) { + const drift = ((d * 0.6180339887) + tSec * 0.045) % 1; // her biri farklı fazda akar + const wave = Math.sin(tSec * 0.3 + d * 1.7) * 10; + const dy = (((d * 53.7 + wave) % h) + h) % h; + const dx = drift * w; + const pal = d % 3; + const cc = pal === 0 ? '55,224,255' : pal === 1 ? '143,123,255' : '255,93,158'; + const tw = 0.5 + 0.5 * Math.sin(tSec * 2.2 + d * 2.9); // hafif yanıp sönme + g.fillStyle = 'rgba(' + cc + ',' + (0.10 + tw * 0.12) + ')'; + g.beginPath(); + g.arc(dx, dy, 1.0 + (pal === 2 ? 0.9 : 0.3), 0, Math.PI * 2); + g.fill(); + } + + const L = state.layerSizes.length; + const acts = net.activations; + + // ---- bağlantılar ---- + for (let k = 1; k < L; k++) { + const prevY = NODE_Y[k - 1]; + const curY = NODE_Y[k]; + const Wk = net.weights[k - 1]; + const px = NODE_X[k - 1]; + const cx = NODE_X[k]; + for (let j = 0; j < curY.length; j++) { + for (let i = 0; i < prevY.length; i++) { + const wgt = Wk[j][i]; + const a = Math.abs(wgt); + const pos = wgt >= 0; + const alpha = 0.16 + a * 0.7; + g.strokeStyle = pos ? 'rgba(90,200,255,' + alpha + ')' + : 'rgba(255,90,110,' + alpha + ')'; + g.lineWidth = 0.6 + a * 3.2; + const y1 = prevY[i], y2 = curY[j]; + // hafif kavis + const mx = (px + cx) / 2; + const ctl = mx + (y2 - y1) * 0.4; + const ctlY = (y1 + y2) / 2; + g.beginPath(); + g.moveTo(px, y1); + g.quadraticCurveTo(ctl, ctlY, cx, y2); + g.stroke(); + + // güçlü bağlantıların üstüne "akan sinyal dalgası" (canlılık) + if (a > 0.35) { + const cycle = (tSec * 0.9 + k * 0.6 + ((i + j) % 5) * 0.24) % 1; + if (cycle < 0.75) { // dalga kuyruğu kesilmesin diye sadece bir aralıkta göster + const sc = cycle / 0.75; + const qx = (1 - sc) * (1 - sc) * px + 2 * (1 - sc) * sc * ctl + sc * sc * cx; + const qy = (1 - sc) * (1 - sc) * y1 + 2 * (1 - sc) * sc * ctlY + sc * sc * y2; + const fade = (1 - sc) * alpha; + const wcol = pos ? '90,235,255' : '255,150,120'; + const wg = g.createRadialGradient(qx, qy, 0, qx, qy, 6); + wg.addColorStop(0, 'rgba(' + wcol + ',' + (0.5 * fade + 0.2) + ')'); + wg.addColorStop(1, 'rgba(' + wcol + ',0)'); + g.fillStyle = wg; + g.beginPath(); g.arc(qx, qy, 6, 0, Math.PI * 2); g.fill(); + } + } + } + } + } + + // ---- puls topları (kaplamalı, renkli parlama) ---- + const pulses = net.weightPulses; + for (let p = 0; p < pulses.length; p++) { + const pl = pulses[p]; + // normalize konumları gerçek piksele çevir + let x1, y1, x2, y2; + const xCache = _layerXCache; + const li = pl.li, ri = pl.ri; + if (li < 0 || ri >= L) continue; + const px = NODE_X[li], cx = NODE_X[ri]; + // puls kaynağı/hedefi nöron Y'lerinden (gerçek piksel) + const srcY = NODE_Y[li][pl.from]; + const dstY = NODE_Y[ri][pl.to % 100]; + // basit interpolasyon (progress) — kavis ihmal + const t = pl.progress; + const X = px + (cx - px) * t; + const Y = srcY + (dstY - srcY) * t; + let col; + if (pl.dir === 'bwd') { + col = 'hsla(' + pl.hueRef + ',100%,60%,' + (0.5 + pl.mag * 0.5) + ')'; + } else { + col = 'hsla(' + pl.hueRef + ',100%,66%,' + (0.35 + pl.mag * 0.5) + ')'; + } + const rad = 2 + pl.mag * 6; + const glow = g.createRadialGradient(X, Y, 0, X, Y, rad * 5); + glow.addColorStop(0, col); + glow.addColorStop(1, 'rgba(0,0,0,0)'); + g.fillStyle = glow; + g.beginPath(); + g.arc(X, Y, rad * 5, 0, Math.PI * 2); + g.fill(); + g.fillStyle = col; + g.beginPath(); + g.arc(X, Y, rad, 0, Math.PI * 2); + g.fill(); + } + + // ---- nöronlar (aktivasyona göre parlaklık) ---- + for (let k = 0; k < L; k++) { + const x = NODE_X[k]; + const arr = NODE_Y[k]; + const fwd = net.nodeFwdHeat[k] || []; + const bck = net.nodeBackHeat[k] || []; + // katman etiketi + g.fillStyle = 'rgba(140,170,230,0.5)'; + g.font = '11px Consolas, monospace'; + g.textAlign = 'center'; + const label = state.layerLabels[k] || ''; + const isIn = k === 0, isOut = k === L - 1; + g.fillText((isIn ? 'GİRİŞ' : isOut ? 'ÇIKIŞ' : label.toUpperCase()), x, 16); + + for (let j = 0; j < arr.length; j++) { + const r = isIn || isOut ? 11 : 8; + const a = acts[k][j]; // aktivasyon 0..1 + const glow = fwd[j] || 0; + const back = bck[j] || 0; + + // dış parlama + const coreLum = 0.5 + a * 0.9; + let rgb; + if (back > 0.05) { + const t = Math.min(1, back); + rgb = 'rgb(' + Math.round(255 * (0.5 + t * 0.5)) + ',' + Math.round(120 * (1 - t)) + ',' + Math.round(80 * (1 - t)) + ')'; + } else { + const bl = Math.round(90 + coreLum * 210); + const gr = Math.round(60 + a * 200); + rgb = 'rgb(' + Math.round(50 + a * 80) + ',' + gr + ',' + bl + ')'; + } + // zaman bazlı "canlı nabız": aktif nöron hafifçe büyür / parlar + const pulseK = 1 + (a * 0.22) * (0.5 + 0.5 * Math.sin(tSec * 3.4 + k * 1.3 + j * 0.7)); + const glowR = r * 4 * (0.92 + pulseK * 0.08); + const galpha = back > 0.05 ? (0.45 + back * 0.45) : (0.32 + a * 0.5); + const radG = g.createRadialGradient(x, arr[j], 0, x, arr[j], glowR); + radG.addColorStop(0, back > 0.05 ? 'rgba(255,120,40,' + galpha + ')' : 'rgba(90,220,255,' + galpha + ')'); + radG.addColorStop(1, 'rgba(0,0,0,0)'); + g.fillStyle = radG; + g.beginPath(); g.arc(x, arr[j], glowR, 0, Math.PI * 2); g.fill(); + + // çekirdek (canlı parlama katmanı + parlayan ana gövde) + const coreR = r * (0.96 * pulseK); + const coreGlow = g.createRadialGradient(x, arr[j], 0, x, arr[j], coreR * 2.5); + coreGlow.addColorStop(0, 'rgba(255,255,255,' + (0.5 + a * 0.4) + ')'); + coreGlow.addColorStop(0.5, back > 0.05 ? 'rgba(255,180,90,' + (0.5 + a * 0.3) + ')' : 'rgba(150,240,255,' + (0.45 + a * 0.4) + ')'); + coreGlow.addColorStop(1, 'rgba(0,0,0,0)'); + g.fillStyle = coreGlow; + g.beginPath(); g.arc(x, arr[j], coreR * 2.5, 0, Math.PI * 2); g.fill(); + + const grad = g.createRadialGradient(x - 2, arr[j] - 2, 1, x, arr[j], coreR); + grad.addColorStop(0, '#ffffff'); + grad.addColorStop(0.4, back > 0.05 ? '#ffc880' : 'rgb(150,240,255)'); + grad.addColorStop(1, 'rgba(25,55,115,0.95)'); + g.fillStyle = grad; + g.beginPath(); g.arc(x, arr[j], coreR, 0, Math.PI * 2); g.fill(); + // çekirdek dış ince halka (neon kontur) + g.strokeStyle = back > 0.05 ? 'rgba(255,180,90,' + (0.5 + back * 0.5) + ')' : 'rgba(140,240,255,' + (0.4 + a * 0.5) + ')'; + g.lineWidth = 1 + a * 1.2; + g.beginPath(); g.arc(x, arr[j], coreR - 0.6, 0, Math.PI * 2); g.stroke(); + + // back ısı parlaması en dış halka + if (back > 0.05) { + g.strokeStyle = 'rgba(255,150,40,' + (0.3 + back * 0.6) + ')'; + g.lineWidth = 1.5 + back * 2; + g.beginPath(); g.arc(x, arr[j], r + 3 + back * 8, 0, Math.PI * 2); g.stroke(); + } + } + } +} + +/* ============================================================================ + ÇİZİM: SAĞ PANEL — AĞIRLIK ISILOĞ / GÖRSEL MATRİS + her bağlantı katmanı (k=1..L-1) için mini grid hücre + hücre boyutu pencere bağımlı; tüm katmanlar tek sütunda, üstte en girişçi + ============================================================================ */ +function drawHeatmap() { + const cv = heatCv; if (!cv) return; + const w = cv.width, h = cv.height; + const g = heatCtx; + g.clearRect(0, 0, w, h); + g.fillStyle = 'rgba(8,12,24,0.5)'; g.fillRect(0, 0, w, h); + + const L = state.layerSizes.length; + if (L < 2) return; + // her layer-k matrisi: rows=cur(data) x cols=prev + // bir "matris şeridi"ne yerleştir, üstten alta + let yCursor = 12; + const cellPad = 3; + const labelH = 14; + for (let k = 1; k < L; k++) { + const cur = state.layerSizes[k]; + const prevC = state.layerSizes[k - 1]; + const mat = net.weights[k - 1]; + // toplam matris alanı; max genişlik w-kaydol => cell + const padX = 4; + const availH = Math.max(20, Math.min(cur * (h - labelH) / (L - 1), h / 4.4) - cellPad * cur); + const availW = w - padX * 2; + const cw = Math.min(availW / Math.max(1, prevC), 22); + const ch = Math.min(availH / Math.max(1, cur), 22); + const cell = Math.min(cw, ch, 26); + const gridW = cell * prevC - cellPad * (prevC - 1); + const gridH = cell * cur - cellPad * (cur - 1); + let x0 = (w - gridW) / 2; + let y0 = yCursor + labelH; + + // katman etiketi: "k: cur ← prev" + g.fillStyle = 'rgba(150,180,240,0.5)'; + g.font = '9px Consolas, monospace'; + g.textAlign = 'left'; + const lab = state.layerLabels[k] + ' ' + cur + '←' + prevC; + g.fillText(lab.toUpperCase(), x0, yCursor + 10); + + for (let j = 0; j < cur; j++) { // satır = hedef nöron + for (let i = 0; i < prevC; i++) { // kolon = kaynak nöron + const val = mat[j][i]; // -1..1 + const a = Math.abs(val); + let col; + if (val >= 0) col = 'rgba(45,175,255,' + (0.15 + a * 0.85) + ')'; + else col = 'rgba(255,80,100,' + (0.15 + a * 0.85) + ')'; + g.fillStyle = col; + const px = x0 + i * (cell + cellPad); + const py = y0 + j * (cell + cellPad); + const rr = cell * 0.16; + roundRect(g, px, py, cell, cell, rr); + g.fill(); + } + } + yCursor = y0 + gridH + 14; + } +} + +function roundRect(g, x, y, w2, h2, r) { + g.beginPath(); + g.moveTo(x + r, y); + g.arcTo(x + w2, y, x + w2, y + h2, r); + g.arcTo(x + w2, y + h2, x, y + h2, r); + g.arcTo(x, y + h2, x, y, r); + g.arcTo(x, y, x + w2, y, r); + g.closePath(); +} + +/* ============================================================================ + ÇİZİM: ALT — LOSS GRAFİĞİ (neon eğri, kaydırmalı) + ============================================================================ */ +function plotLoss() { + const cv = lossCv; if (!cv) return; + const w = cv.width, h = cv.height; + const g = lossCtx; + g.clearRect(0, 0, w, h); + g.fillStyle = 'rgba(8,12,24,0.5)'; g.fillRect(0, 0, w, h); + g.strokeStyle = 'rgba(120,150,220,0.12)'; + for (let gy = 0; gy < h; gy += h / 4) { g.beginPath(); g.moveTo(0, gy); g.lineTo(w, gy); g.stroke(); } + + if (lossHistory.length < 2) return; + const maxP = Math.min(MAX_LOSS_POINTS, lossHistory.length); + // Y ölçek: dönemsel max (ilk 20 dahil) + let lo = Infinity, hi = -Infinity; + const from = Math.max(0, lossHistory.length - 260); + for (let i = from; i < lossHistory.length; i++) { const v = lossHistory[i]; if (v < lo) lo = v; if (v > hi) hi = v; } + if (!isFinite(hi) || hi - lo < 1e-6) { hi = lo + 1; } + lo = Math.max(0, lo - (hi - lo) * 0.1); + hi = hi + (hi - lo) * 0.1; + + const data = lossHistory.slice(-maxP); + // neon çizgi + alan + const stepX = w / data.length; + const py = function (v) { return h - 8 - ((v - lo) / (hi - lo || 1)) * (h - 16); }; + + // alan dolgusu + const grad = g.createLinearGradient(0, 0, 0, h); + grad.addColorStop(0, 'rgba(255,77,158,0.28)'); + grad.addColorStop(1, 'rgba(255,77,158,0)'); + g.beginPath(); + g.moveTo(0, py(data[0])); + for (let i = 0; i < data.length; i++) g.lineTo(i * stepX, py(data[i])); + g.lineTo(w, h); g.lineTo(0, h); g.closePath(); + g.fillStyle = grad; g.fill(); + + // üst parlayan çizgi + g.beginPath(); + g.moveTo(0, py(data[0])); + for (let i = 0; i < data.length; i++) g.lineTo(i * stepX, py(data[i])); + g.strokeStyle = 'rgba(255,110,190,0.15)'; + g.lineWidth = 6; g.stroke(); + g.strokeStyle = '#ff6eae'; + g.lineWidth = 1.6; + g.shadowColor = '#ff6eae'; g.shadowBlur = 8; + g.stroke(); + g.shadowBlur = 0; + + // mevcut değer noktası + const lv = data[data.length - 1]; + g.fillStyle = '#fff'; + g.beginPath(); + g.arc(w - stepX, py(lv), 2.4, 0, Math.PI * 2); + g.fill(); +} + +/* ============================================================================ + CANVAS BOYUTLANDIRMA + HUD + ARK MİMARİ ÇUBUKLAR + ============================================================================ */ +function resizeCanvases() { + if (!neuralCv || !heatCv || !lossCv) return; + const dpr = window.devicePixelRatio || 1; + for (const cv of [neuralCv, heatCv, lossCv]) { + const r = cv.getBoundingClientRect(); + cv.width = Math.max(20, Math.round(r.width * dpr)); + cv.height = Math.max(20, Math.round(r.height * dpr)); + cv.getContext('2d').setTransform(dpr, 0, 0, dpr, 0, 0); + // not: yüksek-dpi'da piksel koordinat pikselleştirme: logikal piksel kullanıyoruz + // dpr ile CSS'e oranlı çizeceğiz: transform dpr yaptığımız için coordinates logikal kalıyor. + } + computeGeometry(neuralCv.clientWidth || neuralCv.parentElement.clientWidth, neuralCv.clientHeight || neuralCv.parentElement.clientHeight); +} + +function updateHUD() { + const e = $('epochReadout'); if (e) e.textContent = epochCounter; + const lo = $('lossReadout'); + if (lo) lo.textContent = lossHistory.length ? lossHistory[lossHistory.length - 1].toFixed(4) : '—'; + const ae = $('actReadout'); if (ae) ae.textContent = state.activation; +} + +function updateArchBars() { + const holder = $('archBars'); if (!holder) return; + holder.innerHTML = ''; + const sizes = state.layerSizes; + const maxS = Math.max.apply(null, sizes); + for (let k = 0; k < sizes.length; k++) { + const b = document.createElement('div'); + b.className = 'arch-bar'; + const scale = sizes[k] / maxS; + b.style.height = (16 + scale * 26) + 'px'; + b.textContent = sizes[k]; + if (k === 0) b.style.background = 'linear-gradient(180deg,#6cf,#38e)'; + else if (k === sizes.length - 1) b.style.background = 'linear-gradient(180deg,#ff7,#ff4d6d)'; + holder.appendChild(b); + } +} + +/* ============================================================================ + ANA ANİMASYON DÖNGÜSÜ (requestAnimationFrame) + ============================================================================ */ +function requestAnimationFrameSim(ts) { + animationId = requestAnimationFrame(requestAnimationFrameSim); + const dt = lastTs ? Math.min((ts - lastTs) / 1000, 0.05) : 0.016; + lastTs = ts; + + tickSignals(dt); + growBackHeat(); + growFwdHeat(); + tickAutoTrain(); + + // çizim + drawNeuralCanvas(); + frameCount++; + if (frameCount % HEAT_REFRESH_EVERY === 0) drawHeatmap(); + plotLoss(); + updateHUD(); +} + +function startSim() { + if (animationId) return; + lastTs = 0; + animationId = requestAnimationFrame(requestAnimationFrameSim); +} +function stopSim() { if (animationId) { cancelAnimationFrame(animationId); animationId = null; } } + +/* ============================================================================ + KONTROL / UI BAĞLAMA + ============================================================================ */ +function applyActivation(choice) { + state.activation = (choice === 'relu') ? 'relu' : 'sigmoid'; + updateActivationSeg(); +} + +function updateActivationSeg() { + const seg = $('activationSeg'); if (!seg) return; + const btns = seg.querySelectorAll('.seg-btn'); + btns.forEach(function (b) { + b.classList.toggle('active', b.dataset.act === state.activation); + }); +} + +function onSliderChange() { + state.inputCount = intVal($('inputSlider'), 2); + state.hiddenNeurons = intVal($('hiddenSlider'), 4); + state.hiddenLayers = intVal($('hiddenCountSlider'), 3); + state.outputCount = intVal($('outputSlider'), 1); + state.learningRate = (intVal($('lrSlider'), 10)) / 100; + + const io = $('inputOut'); if (io) io.textContent = state.inputCount; + const ho = $('hiddenOut'); if (ho) ho.textContent = state.hiddenNeurons; + const hco = $('hiddenCountOut'); if (hco) hco.textContent = state.hiddenLayers; + const oo = $('outputOut'); if (oo) oo.textContent = state.outputCount; + const lo2 = $('lrOut'); if (lo2) lo2.textContent = state.learningRate.toFixed(2); + + buildNetwork(); + updateArchBars(); + updateHUD(); +} + +function intVal(el, d) { + if (!el) return d; + const v = parseInt(el.value, 10); + return isNaN(v) ? d : v; +} + +/* RUN/PAUSE mantığı + otomatik forward-pass denemesi her frame */ +function toggleRun() { + state.trainRunning = !state.trainRunning; + const b = $('runBtn'); if (b) b.classList.toggle('running', state.trainRunning); +} + +function tickAutoTrain() { + // startSim üzerindeki ana döngüde çağrılır — sürekli RUN modunda her frame trainStep + if (!state.trainRunning) return; + if (frameCount % 4 === 0) trainStep(); +} + +/* ============================================================================ + BAŞLATMA / DOM + ============================================================================ */ +function bindUI() { + const sliders = ['inputSlider', 'hiddenSlider', 'hiddenCountSlider', 'outputSlider', 'lrSlider']; + sliders.forEach(function (s) { + const el = $(s); if (el) el.addEventListener('input', onSliderChange); + }); + + const runBtn = $('runBtn'); + if (runBtn) runBtn.addEventListener('click', toggleRun); + const stepBtn = $('stepBtn'); + if (stepBtn) stepBtn.addEventListener('click', onEpochStep); + const resetBtn = $('resetBtn'); + if (resetBtn) resetBtn.addEventListener('click', resetNetwork); + + const seg = $('activationSeg'); + if (seg) seg.addEventListener('click', function (ev) { + const t = ev.target.closest('.seg-btn'); + if (t && t.dataset.act) applyActivation(t.dataset.act); + }); + + window.addEventListener('resize', function () { + resizeCanvases(); + drawHeatmap(); + plotLoss(); + }); +} + +function init() { + neuralCv = $('neuralCanvas'); + heatCv = $('heatCanvas'); + lossCv = $('lossCanvas'); + if (neuralCv) neuralCtx = neuralCv.getContext('2d'); + if (heatCv) heatCtx = heatCv.getContext('2d'); + if (lossCv) lossCtx = lossCv.getContext('2d'); + + onSliderChange(); // layerSizes + ilk buildNetwork + buildNetwork(); + bindUI(); + resizeCanvases(); + updateActivationSeg(); + updateHUD(); + updateArchBars(); + drawHeatmap(); + plotLoss(); + startSim(); +} + +document.addEventListener('DOMContentLoaded', init);