/* ============================================================================ 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);