Implement trace functionality for Hopfield and Hamming networks; update Gemfile.lock and app.rb for new dependencies and features
This commit is contained in:
+181
-23
@@ -3,6 +3,8 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Сети Хопфилда и Хэмминга</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.css">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js"></script>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@@ -184,6 +186,60 @@
|
||||
|
||||
#results-section { display: none; }
|
||||
|
||||
#trace-panels {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-top: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.trace-panel {
|
||||
flex: 1;
|
||||
min-width: 220px;
|
||||
background: #1e1e3a;
|
||||
border: 1px solid #3a3a5a;
|
||||
border-radius: 6px;
|
||||
padding: 0.75rem 1rem;
|
||||
font-size: 0.88rem;
|
||||
color: #b0c4e8;
|
||||
line-height: 1.8;
|
||||
max-height: 420px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.trace-panel-title {
|
||||
font-family: monospace;
|
||||
font-size: 0.75rem;
|
||||
color: #8ab4f8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
margin-bottom: 0.5rem;
|
||||
border-bottom: 1px solid #3a3a5a;
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.trace-line {
|
||||
font-family: monospace;
|
||||
font-size: 0.82rem;
|
||||
color: #888;
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.trace-header {
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
color: #8ab4f8;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.trace-formula {
|
||||
padding: 0.2rem 0;
|
||||
}
|
||||
|
||||
.katex { color: #e8d8ff; }
|
||||
|
||||
#loading {
|
||||
display: none;
|
||||
align-items: center;
|
||||
@@ -241,6 +297,9 @@
|
||||
<span id="delay-label">300 мс</span>
|
||||
</span>
|
||||
</label>
|
||||
<label class="step-toggle">
|
||||
<input type="checkbox" id="formula-mode"> Показывать формулы вычислений
|
||||
</label>
|
||||
|
||||
<div class="recall-area">
|
||||
<div class="pattern-box">
|
||||
@@ -252,6 +311,17 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="trace-panels" style="display:none">
|
||||
<div class="trace-panel">
|
||||
<div class="trace-panel-title">Хопфилд</div>
|
||||
<div id="trace-hopfield"></div>
|
||||
</div>
|
||||
<div class="trace-panel">
|
||||
<div class="trace-panel-title">Хэмминг</div>
|
||||
<div id="trace-hamming"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="results-section">
|
||||
<div class="net-results">
|
||||
<div class="net-block">
|
||||
@@ -290,15 +360,65 @@
|
||||
});
|
||||
});
|
||||
|
||||
const stepModeCheckbox = document.getElementById('step-mode');
|
||||
const speedControl = document.getElementById('speed-control');
|
||||
const stepDelaySlider = document.getElementById('step-delay');
|
||||
const delayLabel = document.getElementById('delay-label');
|
||||
let currentRunId = 0;
|
||||
|
||||
const stepModeCheckbox = document.getElementById('step-mode');
|
||||
const formulaModeCheckbox = document.getElementById('formula-mode');
|
||||
const speedControl = document.getElementById('speed-control');
|
||||
const stepDelaySlider = document.getElementById('step-delay');
|
||||
const delayLabel = document.getElementById('delay-label');
|
||||
|
||||
stepModeCheckbox.addEventListener('change', () => {
|
||||
speedControl.classList.toggle('visible', stepModeCheckbox.checked);
|
||||
});
|
||||
|
||||
function traceEl(panelId, className, content, isLatex = false) {
|
||||
const panel = document.getElementById(panelId);
|
||||
const el = document.createElement('div');
|
||||
el.className = className;
|
||||
if (isLatex) {
|
||||
el.innerHTML = katex.renderToString(content, { throwOnError: false, displayMode: false });
|
||||
} else {
|
||||
el.textContent = content;
|
||||
}
|
||||
panel.appendChild(el);
|
||||
panel.scrollTop = panel.scrollHeight;
|
||||
}
|
||||
|
||||
const hfLine = (text) => traceEl('trace-hopfield', 'trace-line', text);
|
||||
const hfFormula = (tex) => traceEl('trace-hopfield', 'trace-formula', tex, true);
|
||||
const hmLine = (text) => traceEl('trace-hamming', 'trace-line', text);
|
||||
const hmFormula = (tex) => traceEl('trace-hamming', 'trace-formula', tex, true);
|
||||
|
||||
function clearTrace() {
|
||||
document.getElementById('trace-hopfield').innerHTML = '';
|
||||
document.getElementById('trace-hamming').innerHTML = '';
|
||||
document.getElementById('trace-panels').style.display = 'none';
|
||||
}
|
||||
|
||||
function buildTrace(data) {
|
||||
document.getElementById('trace-panels').style.display = 'flex';
|
||||
const names = data.pattern_names;
|
||||
const t = data.hamming_trace;
|
||||
|
||||
hfFormula(String.raw`\operatorname{net}_i = \sum_{j} W_{ij} \cdot x_j \qquad x_i = \operatorname{sgn}(\operatorname{net}_i)`);
|
||||
data.hopfield_nets.forEach((nd, i) => {
|
||||
const mark = nd.changed === 0 ? ' ← сошлось' : '';
|
||||
hfLine(`шаг ${i + 1}: изменено ${nd.changed}/64${mark}`);
|
||||
});
|
||||
|
||||
hmFormula(String.raw`y_k = \frac{\mathbf{x} \cdot \mathbf{p}_k + n}{2}`);
|
||||
const layer = names.map((n, i) => `${n}\\!=\\!${t.initial_outputs[i]}`).join(',\\;');
|
||||
hmFormula(String.raw`\text{слой: }\;` + layer);
|
||||
hmFormula(String.raw`\varepsilon = \tfrac{0.9}{K} = \tfrac{0.9}{${t.k}} = ${t.eps}`);
|
||||
hmFormula(String.raw`y_i(t{+}1) = \max\!\left(0,\; y_i(t) - \varepsilon\!\sum_{j \neq i} y_j(t)\right)`);
|
||||
data.hamming_steps.slice(1).forEach((step, i) => {
|
||||
const vals = names.map((n, j) => `${n}\\!=\\!${step[j]}`).join(',\\;');
|
||||
hmFormula(`\\text{iter ${i + 1}: }\\;` + vals);
|
||||
});
|
||||
hmFormula(String.raw`\Rightarrow \text{победитель: } \mathbf{` + data.hamming_winner + `}`);
|
||||
}
|
||||
|
||||
stepDelaySlider.addEventListener('input', () => {
|
||||
delayLabel.textContent = stepDelaySlider.value + ' мс';
|
||||
});
|
||||
@@ -308,12 +428,17 @@
|
||||
.map(c => parseFloat(c.dataset.val));
|
||||
}
|
||||
|
||||
function hideResults() {
|
||||
document.getElementById('results-section').style.display = 'none';
|
||||
clearTrace();
|
||||
}
|
||||
|
||||
function clearGrid() {
|
||||
document.querySelectorAll('#input-grid .cell').forEach(c => {
|
||||
c.dataset.val = '-1';
|
||||
c.className = 'cell interactive off';
|
||||
});
|
||||
document.getElementById('results-section').style.display = 'none';
|
||||
hideResults();
|
||||
}
|
||||
|
||||
function randomNoise() {
|
||||
@@ -322,7 +447,7 @@
|
||||
c.dataset.val = v;
|
||||
c.className = 'cell interactive ' + (v === '1' ? 'on' : 'off');
|
||||
});
|
||||
document.getElementById('results-section').style.display = 'none';
|
||||
hideResults();
|
||||
}
|
||||
|
||||
function vectorsEqual(a, b) {
|
||||
@@ -357,17 +482,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
async function showSteps(data) {
|
||||
const delay = parseInt(stepDelaySlider.value);
|
||||
const cells = [...document.querySelectorAll('#output-grid .cell')];
|
||||
const names = data.pattern_names;
|
||||
async function showSteps(data, runId) {
|
||||
const delay = parseInt(stepDelaySlider.value);
|
||||
const cells = [...document.querySelectorAll('#output-grid .cell')];
|
||||
const names = data.pattern_names;
|
||||
const showTrace = formulaModeCheckbox.checked && data.hopfield_nets;
|
||||
|
||||
document.getElementById('results-section').style.display = 'block';
|
||||
document.getElementById('hamming-answer').textContent = '—';
|
||||
document.getElementById('hamming-label').textContent = '';
|
||||
document.getElementById('hopfield-label').textContent = '';
|
||||
|
||||
// Инициализируем бары
|
||||
if (showTrace) {
|
||||
clearTrace();
|
||||
document.getElementById('trace-panels').style.display = 'flex';
|
||||
hfFormula(String.raw`\operatorname{net}_i = \sum_{j} W_{ij} \cdot x_j \qquad x_i = \operatorname{sgn}(\operatorname{net}_i)`);
|
||||
}
|
||||
|
||||
renderBars(names, data.hamming_steps[0]);
|
||||
|
||||
const hfSteps = data.hopfield_steps;
|
||||
@@ -379,16 +510,34 @@
|
||||
renderGrid(cells, hfSteps[i]);
|
||||
document.getElementById('hopfield-step').textContent =
|
||||
i === 0 ? 'Шаг 0 — входной вектор' : `Шаг ${i} из ${hfSteps.length - 1}`;
|
||||
if (showTrace && i > 0 && data.hopfield_nets[i - 1]) {
|
||||
const nd = data.hopfield_nets[i - 1];
|
||||
const mark = nd.changed === 0 ? ' ← сошлось' : '';
|
||||
hfLine(`шаг ${i}: изменено ${nd.changed}/64${mark}`);
|
||||
}
|
||||
}
|
||||
if (i < hmSteps.length) {
|
||||
renderBars(names, hmSteps[i]);
|
||||
document.getElementById('hamming-step').textContent =
|
||||
i === 0 ? 'Шаг 0 — слой Хэмминга' : `Итерация MAXNET ${i} из ${hmSteps.length - 1}`;
|
||||
if (showTrace) {
|
||||
const t = data.hamming_trace;
|
||||
if (i === 0) {
|
||||
hmFormula(String.raw`y_k = \frac{\mathbf{x} \cdot \mathbf{p}_k + n}{2}`);
|
||||
const layer = names.map((n, j) => `${n}\\!=\\!${t.initial_outputs[j]}`).join(',\\;');
|
||||
hmFormula(String.raw`\text{слой: }\;` + layer);
|
||||
hmFormula(String.raw`\varepsilon = \tfrac{0.9}{${t.k}} = ${t.eps}`);
|
||||
hmFormula(String.raw`y_i(t{+}1) = \max\!\left(0,\; y_i(t) - \varepsilon\!\sum_{j \neq i} y_j(t)\right)`);
|
||||
} else {
|
||||
const vals = names.map((n, j) => `${n}\\!=\\!${hmSteps[i][j]}`).join(',\\;');
|
||||
hmFormula(`\\text{iter ${i}: }\\;` + vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
await sleep(delay);
|
||||
if (runId !== currentRunId) return;
|
||||
}
|
||||
|
||||
// Итог Хопфилд
|
||||
const hfFinal = hfSteps[hfSteps.length - 1];
|
||||
document.getElementById('hopfield-step').textContent =
|
||||
`Завершено за ${hfSteps.length - 1} шаг(ов)`;
|
||||
@@ -397,13 +546,13 @@
|
||||
hfLabel.textContent = hfMatch ? `Совпадает с эталоном: ${hfMatch}` : 'Не совпадает ни с одним эталоном';
|
||||
hfLabel.className = 'status-label ' + (hfMatch ? 'yes' : 'no');
|
||||
|
||||
// Итог Хэмминг
|
||||
document.getElementById('hamming-step').textContent =
|
||||
`Завершено за ${hmSteps.length - 1} итерац(ий)`;
|
||||
document.getElementById('hamming-answer').textContent = data.hamming_winner;
|
||||
const hmLabel = document.getElementById('hamming-label');
|
||||
hmLabel.textContent = `Ближайший эталон: ${data.hamming_winner}`;
|
||||
hmLabel.className = 'status-label yes';
|
||||
if (showTrace) hmFormula(String.raw`\Rightarrow \text{победитель: } \mathbf{` + data.hamming_winner + `}`);
|
||||
}
|
||||
|
||||
function showResult(data) {
|
||||
@@ -418,24 +567,32 @@
|
||||
hfLabel.textContent = hfMatch ? `Совпадает с эталоном: ${hfMatch}` : 'Не совпадает ни с одним эталоном';
|
||||
hfLabel.className = 'status-label ' + (hfMatch ? 'yes' : 'no');
|
||||
|
||||
document.getElementById('hamming-answer').textContent = data.hamming;
|
||||
document.getElementById('hamming-answer').textContent = data.hamming_winner || data.hamming;
|
||||
const hmLabel = document.getElementById('hamming-label');
|
||||
hmLabel.textContent = `Ближайший эталон: ${data.hamming}`;
|
||||
hmLabel.textContent = `Ближайший эталон: ${data.hamming_winner || data.hamming}`;
|
||||
hmLabel.className = 'status-label yes';
|
||||
|
||||
// Скрываем бары в обычном режиме
|
||||
document.getElementById('maxnet-bars').innerHTML = '';
|
||||
|
||||
document.getElementById('results-section').style.display = 'block';
|
||||
|
||||
if (data.hopfield_nets && data.hopfield_nets.length) {
|
||||
clearTrace();
|
||||
buildTrace(data);
|
||||
}
|
||||
}
|
||||
|
||||
async function recall() {
|
||||
const vector = getInputVector();
|
||||
const stepMode = stepModeCheckbox.checked;
|
||||
const endpoint = stepMode ? '/recall_steps' : '/recall';
|
||||
const runId = ++currentRunId;
|
||||
const vector = getInputVector();
|
||||
const stepMode = stepModeCheckbox.checked;
|
||||
const formulaMode = formulaModeCheckbox.checked;
|
||||
const endpoint = formulaMode ? '/recall_trace'
|
||||
: stepMode ? '/recall_steps'
|
||||
: '/recall';
|
||||
|
||||
document.getElementById('loading').style.display = 'flex';
|
||||
document.getElementById('results-section').style.display = 'none';
|
||||
clearTrace();
|
||||
|
||||
try {
|
||||
const resp = await fetch(endpoint, {
|
||||
@@ -443,11 +600,12 @@
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ vector })
|
||||
});
|
||||
if (runId !== currentRunId) return;
|
||||
const data = await resp.json();
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
|
||||
if (stepMode) {
|
||||
await showSteps(data);
|
||||
await showSteps(data, runId);
|
||||
} else {
|
||||
showResult(data);
|
||||
}
|
||||
@@ -459,6 +617,6 @@
|
||||
</script>
|
||||
|
||||
</body>
|
||||
<footer style="margin-top: 3rem; font-size: 0.8rem; color: #555; text-align: center;">
|
||||
© Визуализация работы сетей Хопфилда и Хэмминга by Антипенко Д.А. - 2026. Ruby/Sinatra
|
||||
<footer style="margin-top:3rem; font-size:0.75rem; color:#666; text-align:center;">
|
||||
© Develop by Antipenko D.A. for MIAD - 2026
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user