html • Lines: 464<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Linear Regression Visualizer | Interactive Machine Learning Lab - Palash Kanti Kundu</title>
<meta name="title" content="Linear Regression Visualizer | Interactive Machine Learning Lab">
<meta name="description"
content="A real-time, from-scratch Linear Regression engine. Build architectures, tune hyperparameters, and visualize weight traces and decision boundaries in your browser.">
<meta name="keywords"
content="Linear Regression, Machine Learning, JavaScript, AI Visualization, Deep Learning Tutorial, XOR Problem, Backpropagation Visualizer">
<meta property="og:type" content="website">
<meta property="og:url" content="https://palashkantikundu.in">
<meta property="og:title" content="Linear Regression Demystified: Interactive Trace Engine">
<meta property="og:description"
content="Explore the math behind AI. Watch synapses fire and weights adjust in real-time with this browser-based Linear Regression architect.">
<meta property="twitter:card" content="summary_large_image">
<meta property="twitter:title" content="Linear Regression Demystified: Interactive Trace Engine">
<meta property="twitter:description"
content="Real-time Linear Regression training and weight tracing in pure JavaScript. Build, train, and demystify AI architecture.">
<script src="https://cdn.jsdelivr.net/npm/plotly.js-dist@3.3.1/plotly.min.js"></script>
<style>
body {
font-family: 'Courier New', Courier, monospace;
display: flex;
flex-direction: column;
align-items: center;
background: #1a1a1a;
color: #00ff00;
margin: 0;
padding: 20px;
}
.main-container {
display: flex;
flex-direction: row;
/* Horizontal layout for the two columns */
gap: 20px;
width: 100%;
max-width: 1200px;
align-items: flex-start;
}
.charts-column {
display: flex;
flex-direction: column;
gap: 20px;
flex: 1.5;
/* Takes up more width than the internals panel */
min-width: 0;
}
.charts-stack {
display: flex;
flex-direction: column;
gap: 20px;
flex: 2;
/* Takes up more space compared to the viz panel */
}
.panel {
background: #2d2d2d;
border-radius: 8px;
border: 1px solid #444;
width: 100%;
/* Ensure panels take full width of container */
box-sizing: border-box;
overflow: hidden;
}
.panel {
background: #2d2d2d;
border-radius: 8px;
border: 1px solid #444;
width: 100%;
box-sizing: border-box;
}
#chart {
height: 450px;
/* Specific height for main chart */
}
#lossChart {
height: 450px;
/* Specific height for loss chart */
}
#chart,
#lossChart {
height: 500px;
width: 100%;
/* Force 80% width */
margin: 0 auto;
/* Center within the .charts-column */
box-sizing: border-box;
}
.layer-viz {
flex: 1;
/* Narrower than the charts column */
display: flex;
flex-direction: column;
gap: 10px;
font-size: 0.85em;
}
.matrix-box {
border: 1px solid #00ff00;
padding: 10px;
margin: 5px 0;
background: #000;
position: relative;
}
.matrix-label {
position: absolute;
top: -10px;
left: 10px;
background: #2d2d2d;
padding: 0 5px;
font-size: 0.7em;
color: #aaa;
}
.controls {
width: 100%;
max-width: 1160px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-top: 20px;
}
input,
button {
background: #333;
color: #00ff00;
border: 1px solid #00ff00;
padding: 10px;
}
button {
cursor: pointer;
font-weight: bold;
}
#trainBtn {
background: #004400;
}
#stopBtn {
background: #440000;
color: #ff5555;
border-color: #ff5555;
display: none;
}
#resetBtn {
background: #444;
color: #fff;
border-color: #888;
}
button:hover {
background: #00ff00;
color: #000;
}
.highlight {
color: #fff;
font-weight: bold;
text-shadow: 0 0 5px #00ff00;
}
label {
font-size: 0.8em;
color: #ccc;
}
.status-tag {
font-size: 0.7em;
padding: 2px 5px;
border-radius: 3px;
float: right;
background: #00ff00;
color: #000;
}
</style>
</head>
<body>
<h1>Linear Layer: Internal Trace <span id="status" class="status-tag"
style="display:none">TRAINING...</span></h1>
<p>This tool accompanies the learning guide: <a href="/#linear-regression">Linear Regression</a></p>
<div class="main-container">
<div class="charts-column">
<div id="chart" class="panel"></div>
<div id="lossChart" class="panel"></div>
</div>
<div class="layer-viz panel">
<h3>Linear Layer Internals</h3>
<div class="matrix-box">
<span class="matrix-label">Input X (with Bias Trick)</span>
<div id="matrixX"></div>
</div>
<div style="text-align: center; font-size: 1.5em;">×</div>
<div class="matrix-box">
<span class="matrix-label">Model Weights W (m and c)</span>
<div id="matrixW" class="highlight"></div>
</div>
<div style="text-align: center; font-size: 1.5em;">=</div>
<div class="matrix-box">
<span class="matrix-label">Output (Forward Pass)</span>
<div id="matrixOut"></div>
</div>
<hr style="width: 100%; border-color: #444;">
<div class="matrix-box">
<span class="matrix-label">True Weights W (m and c)</span>
<div id="trueWeights" class="highlight"></div>
</div>
<hr style="width: 100%; border-color: #444;">
<div id="traceLog">
<strong>Computation Trace:</strong><br>
1. $X \cdot W$ (Matmul) ✅<br>
2. Current Epoch: <span id="currentEpoch" class="highlight">0.00</span><br>
2. MSE Loss: <span id="valLoss" class="highlight">0.00</span><br>
3. Gradient: <span id="valGrad" class="highlight">m:0, c:0</span><br>
4. $W = W - \eta \cdot \nabla L$ ✅
</div>
</div>
</div>
<div class="controls panel">
<div style="display:flex; flex-direction:column">
<label>Data X</label>
<input type="text" id="inputX" value="1, 2, 3, 4, 5">
</div>
<div style="display:flex; flex-direction:column">
<label>Data Y</label>
<input type="text" id="inputY" value="5.6, 6.6, 9.5, 10.2, 14">
</div>
<div style="display:flex; flex-direction:column">
<label>Epochs, learning Rate & Delay(ms)</label>
<div style="display:flex; gap:5px">
<input type="number" id="epochsInput" value="100" style="width:50%">
<input type="number" id="learningRate" value="0.001" style="width:25%" step="0.001">
<input type="number" id="speedInput" value="50" style="width:25%">
</div>
</div>
<div style="display:flex; flex-direction:column; gap: 5px;">
<button id="randomDataBtn" style="background: #444; color: #00ff00;">RANDOMIZE DATA</button>
<button id="trainBtn">START TRAINING</button>
<button id="stopBtn">STOP</button>
<button id="resetBtn">RESET WEIGHTS</button>
</div>
</div>
<script>
let m, c;
let isTraining = false;
let stopFlag = false;
function getInputs() {
const x = document.getElementById('inputX').value.split(',').map(v => v.trim()).filter(v => v !== "").map(Number);
const y = document.getElementById('inputY').value.split(',').map(v => v.trim()).filter(v => v !== "").map(Number);
return { x, y };
}
// Updated internal viz to handle cases where math might break
function updateInternalViz(x, predictions, loss, m_grad, c_grad, currentEpoch) {
document.getElementById('matrixX').innerHTML = x.slice(0, 5).map(val => `| ${val.toFixed(1)}, 1.0 |`).join('<br>') + (x.length > 5 ? '<br>...' : '');
document.getElementById('matrixW').innerHTML = `| ${Number(m).toFixed(4)} | (m)<br>| ${Number(c).toFixed(4)} | (c)`;
document.getElementById('matrixOut').innerHTML = predictions.slice(0, 5).map(val => `| ${val.toFixed(2)} |`).join('<br>') + (predictions.length > 5 ? '<br>...' : '');
document.getElementById('currentEpoch').innerText = currentEpoch;
document.getElementById('valLoss').innerText = isNaN(loss) ? "EXPLODED" : loss.toFixed(6);
document.getElementById('valGrad').innerText = `m:${m_grad.toFixed(4)}, c:${c_grad.toFixed(4)}`;
}
function updateChart() {
const { x: xInput, y: yInput } = getInputs();
if (xInput.length === 0 || yInput.length === 0) return;
const minX = Math.min(...xInput);
const maxX = Math.max(...xInput);
const xRange = maxX - minX || 2;
const xPad = xRange * 0.2;
const xLine = [minX - xPad, maxX + xPad];
const yLine = xLine.map(val => (m * val) + c);
const allVisibleY = [...yInput, ...yLine].filter(v => !isNaN(v));
const minY = Math.min(...allVisibleY);
const maxY = Math.max(...allVisibleY);
const yRange = (maxY - minY) || 10;
const yPad = yRange * 0.2;
const data = [
{ x: xInput, y: yInput, mode: 'markers', name: 'Data', marker: { color: '#00ff00', size: 10 } },
{ x: xLine, y: yLine, mode: 'lines', name: 'Model', line: { color: '#ff00ff', width: 3 } }
];
const layout = {
autosize: true,
title: { text: 'Data vs Model', font: { color: '#aaa', size: 12 } },
paper_bgcolor: '#1a1a1a', plot_bgcolor: '#1a1a1a', font: { color: '#00ff00' },
xaxis: { gridcolor: '#333', range: [xLine[0], xLine[1]], zerolinecolor: '#444', title: "Y" },
yaxis: { gridcolor: '#333', range: [minY - yPad, maxY + yPad], zerolinecolor: '#444', title: "X" }
};
Plotly.react('chart', data, layout, { responsive: true });
}
function resetWeights() {
stopFlag = true;
m = (Math.random() - 0.5) * 2; // Randomize between -1 and 1
c = (Math.random() - 0.5) * 2;
const { x } = getInputs();
if (x.length > 0) {
const preds = x.map(val => (val * m) + c);
updateInternalViz(x, preds, 0, 0, 0, 0);
updateChart();
updateLossChart();
}
}
let lossHistory = []; // To store [epoch, loss] pairs
async function train() {
if (isTraining) return;
const { x: xRaw, y: yRaw } = getInputs();
// ... (Keep your existing safety checks here) ...
isTraining = true;
stopFlag = false;
lossHistory = []; // Reset history for new run
document.getElementById('trainBtn').style.display = 'none';
document.getElementById('stopBtn').style.display = 'block';
document.getElementById('status').style.display = 'inline';
const epochs = parseInt(document.getElementById('epochsInput').value);
const delay = parseInt(document.getElementById('speedInput').value);
const lr = parseFloat(document.getElementById('learningRate').value);
const n = xRaw.length;
for (let e = 0; e < epochs; e++) {
if (stopFlag) break;
let preds = xRaw.map(x => (x * m) + c);
let loss = preds.reduce((acc, p, i) => acc + Math.pow(p - yRaw[i], 2), 0) / n;
// Save loss for the curve
lossHistory.push(loss);
let m_grad = 0, c_grad = 0;
for (let i = 0; i < n; i++) {
let err = preds[i] - yRaw[i];
m_grad += (2 / n) * err * xRaw[i];
c_grad += (2 / n) * err;
}
if (isNaN(m_grad) || isNaN(c_grad)) {
alert("GRADIENT EXPLODED! LR is too high for this data scale.");
stopFlag = true;
break;
}
m -= lr * m_grad;
c -= lr * c_grad;
updateInternalViz(xRaw, preds, loss, m_grad, c_grad, e);
updateChart();
updateLossChart(); // Update the new curve
if (delay > 0) await new Promise(r => setTimeout(r, delay));
}
isTraining = false;
document.getElementById('trainBtn').style.display = 'block';
document.getElementById('stopBtn').style.display = 'none';
document.getElementById('status').style.display = 'none';
}
function updateLossChart() {
const trace = {
x: lossHistory.map((_, i) => i),
y: lossHistory,
type: 'scatter',
mode: 'lines',
name: 'MSE Loss',
line: { color: '#FF0000' }
};
const layout = {
autosize: true,
title: { text: 'Epoch vs Loss (Error)', font: { color: '#aaa', size: 12 } },
paper_bgcolor: '#1a1a1a', plot_bgcolor: '#1a1a1a',
font: { color: '#00ff00' },
xaxis: { gridcolor: '#333', title: 'Epoch' },
yaxis: { gridcolor: '#333', title: 'Loss', type: 'log' } // Log scale helps see small improvements
};
Plotly.react('lossChart', [trace], layout, { responsive: true });
}
function generateUniformDistribution(min, max, count, isInteger = false) {
const list = [];
for (let i = 0; i < count; i++) {
if (isInteger) {
// For inclusive integers between min and max
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
list.push(Math.floor(Math.random() * (maxFloored - minCeiled + 1)) + minCeiled);
} else {
// For inclusive min, exclusive max floats
list.push(Math.random() * (max - min) + min);
}
}
return list;
}
function randomizeData() {
stopFlag = true; // Stop training if it's running
const numPoints = 15;
const trueM = (Math.random() - 0.5) * 5; // Target slope
const trueC = (Math.random() - 0.5) * 10; // Target intercept
let xVals = generateUniformDistribution(1, 20, numPoints, true);
let yVals = xVals.map(x => {
const noise = (Math.random() - 0.5) * 2;
return (trueM * x) + trueC + noise;
});
// Update the input fields in the UI
document.getElementById('inputX').value = xVals.join(', ');
document.getElementById('inputY').value = yVals.join(', ');
document.getElementById('trueWeights').innerHTML = `| ${Number(trueM).toFixed(4)} | (m)<br>| ${Number(trueC).toFixed(4)} | (c)`;
// Refresh the visualizer with the new data
resetWeights();
}
// Add the click listener
document.getElementById('randomDataBtn').addEventListener('click', randomizeData);
document.getElementById('trainBtn').addEventListener('click', train);
document.getElementById('stopBtn').addEventListener('click', () => stopFlag = true);
document.getElementById('resetBtn').addEventListener('click', resetWeights);
randomizeData();
</script>
</body>
</html>