hoàn thành tính cận trên và cận dưới của tất cả các thuật toán

This commit is contained in:
Victor Phan
2026-02-24 21:36:02 +07:00
parent ae4d8cbbc9
commit 0ab6461882
203 changed files with 3089 additions and 86 deletions
Regular → Executable
+496 -6
View File
@@ -312,6 +312,113 @@
</div>
</div>
<!-- Performance Range Cards (Best & Worst) -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-top: 20px;">
<!-- Best Accuracy Checkpoint -->
<div id="bestAccuracyCard" style="background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); padding: 20px; border-radius: 12px; box-shadow: 0 8px 24px rgba(255, 215, 0, 0.3); display: none;">
<h3 style="text-align: center; margin-bottom: 12px; color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.2);">🏆 Best (Cao Nhất)</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Best Accuracy</div>
<div style="font-size: 1.8em; font-weight: bold; color: #667eea;" id="bestAccuracy">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">%</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">At Epoch/Iter</div>
<div style="font-size: 1.8em; font-weight: bold; color: #764ba2;" id="bestEpoch">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">epoch</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Train Accuracy</div>
<div style="font-size: 1.4em; font-weight: bold; color: #43e97b;" id="bestTrainAcc">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">%</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Val Accuracy</div>
<div style="font-size: 1.4em; font-weight: bold; color: #f5576c;" id="bestValAcc">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">%</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Train Loss</div>
<div style="font-size: 1.4em; font-weight: bold; color: #f093fb;" id="bestTrainLoss">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">loss</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Val Loss</div>
<div style="font-size: 1.4em; font-weight: bold; color: #4facfe;" id="bestValLoss">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">loss</div>
</div>
</div>
</div>
<!-- Worst Accuracy Checkpoint -->
<div id="worstAccuracyCard" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 20px; border-radius: 12px; box-shadow: 0 8px 24px rgba(102, 126, 234, 0.3); display: none;">
<h3 style="text-align: center; margin-bottom: 12px; color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.2);">📉 Worst (Thấp Nhất)</h3>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 12px;">
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Worst Accuracy</div>
<div style="font-size: 1.8em; font-weight: bold; color: #667eea;" id="worstAccuracy">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">% (cận dưới)</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">At Epoch/Iter</div>
<div style="font-size: 1.8em; font-weight: bold; color: #764ba2;" id="worstEpoch">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">epoch</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Train Accuracy</div>
<div style="font-size: 1.4em; font-weight: bold; color: #43e97b;" id="worstTrainAcc">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">%</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Val Accuracy</div>
<div style="font-size: 1.4em; font-weight: bold; color: #f5576c;" id="worstValAcc">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">%</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Train Loss</div>
<div style="font-size: 1.4em; font-weight: bold; color: #f093fb;" id="worstTrainLoss">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">loss</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Val Loss</div>
<div style="font-size: 1.4em; font-weight: bold; color: #4facfe;" id="worstValLoss">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">loss</div>
</div>
</div>
</div>
</div>
<!-- Performance Range & Random Baseline Analysis -->
<div id="performanceRangeCard" style="margin-top: 15px; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); padding: 20px; border-radius: 12px; box-shadow: 0 8px 24px rgba(245, 87, 108, 0.3); display: none;">
<h3 style="text-align: center; margin-bottom: 12px; color: white; text-shadow: 0 2px 4px rgba(0,0,0,0.2);">📊 Performance Range & Baseline</h3>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;">
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Accuracy Range</div>
<div style="font-size: 1.5em; font-weight: bold; color: #667eea;" id="accuracyRange">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">% (max - min)</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Avg Accuracy</div>
<div style="font-size: 1.5em; font-weight: bold; color: #43e97b;" id="avgAccuracy">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">%</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Stability</div>
<div style="font-size: 1.5em; font-weight: bold; color: #f093fb;" id="stability">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">%</div>
</div>
<div style="background: rgba(255,255,255,0.95); padding: 12px; border-radius: 8px; text-align: center;">
<div style="font-size: 0.8em; color: #666; margin-bottom: 4px;">Random Baseline</div>
<div style="font-size: 1.5em; font-weight: bold; color: #ff6b6b;" id="randomBaseline">--</div>
<div style="font-size: 0.7em; color: #888; margin-top: 2px;">% (1/n_classes)</div>
</div>
</div>
<div id="baselineComparisonText" style="margin-top: 12px; padding: 10px; background: rgba(255,255,255,0.9); border-radius: 6px; text-align: center; font-size: 0.9em; font-weight: 600;">
--
</div>
</div>
<!-- System Info -->
<div style="margin-top: 20px;">
<h3 style="color: #667eea; margin-bottom: 10px;">💻 Thông Tin Hệ Thống</h3>
@@ -338,6 +445,7 @@
<p><strong>✅ Thành công:</strong> <span id="successTrainings">0</span></p>
<p><strong>❌ Thất bại:</strong> <span id="failedTrainings">0</span></p>
<p><strong>⏱️ Thời gian TB:</strong> <span id="avgTime">-</span></p>
<p><strong>🏆 Best Accuracy Ever:</strong> <span id="bestAccuracyEver" style="color: #FFD700; font-weight: 700;">-</span></p>
</div>
</div>
@@ -450,6 +558,14 @@
<p><strong>🏷️ Label column:</strong> <span id="shapefileLabelCol">-</span></p>
<p><strong>📊 Số lớp:</strong> <span id="shapefileLabelCount">-</span></p>
<p><strong>🗺️ Bbox:</strong> <span id="shapefileBbox">-</span></p>
<p><strong>📈 Majority class baseline:</strong>
<span id="shapefileMajorityBaseline" style="font-weight:700; color:#e53935;">-</span>
<span style="color:#888; font-size:0.9em;"> % (lớp chiếm đa số)</span>
</p>
<p><strong>🎲 Weighted random baseline:</strong>
<span id="shapefileWeightedBaseline" style="font-weight:700; color:#fb8c00;">-</span>
<span style="color:#888; font-size:0.9em;"> % (Σ p²)</span>
</p>
</div>
<!-- Labels Distribution -->
@@ -617,6 +733,27 @@
};
let trainingFiles = []; // Store training shapefile data
let currentShapefileData = null; // Currently selected shapefile data
let bestAccuracyData = {
accuracy: 0,
epoch: 0,
trainAcc: 0,
valAcc: 0,
trainLoss: 0,
valLoss: 0
};
let worstAccuracyData = {
accuracy: Infinity, // sentinel - means "never updated"
epoch: 0,
trainAcc: 0,
valAcc: 0,
trainLoss: 0,
valLoss: 0
};
let accuracyHistory = [];
let randomBaseline = 0;
let numberOfClasses = 0;
// Load presets and models only after DOM is ready
document.addEventListener('DOMContentLoaded', async () => {
@@ -756,6 +893,29 @@
labelsList.innerHTML = '';
if (data.labels && data.labels.length > 0) {
// ── Compute baselines from actual class counts ──────────────
const counts = data.labels.map(l => l.count || 0);
const total = counts.reduce((a, b) => a + b, 0);
if (total > 0) {
const props = counts.map(c => c / total);
const majorityBaseline = Math.max(...props) * 100;
const weightedBaseline = props.reduce((s, p) => s + p * p, 0) * 100;
// Update global vars used by Performance Range and report
randomBaseline = majorityBaseline;
numberOfClasses = data.labels.length;
// Update shapefile info panel
document.getElementById('shapefileMajorityBaseline').textContent = majorityBaseline.toFixed(2);
document.getElementById('shapefileWeightedBaseline').textContent = weightedBaseline.toFixed(2);
// Update Performance Range card
document.getElementById('randomBaseline').textContent = majorityBaseline.toFixed(2);
console.log(`[Baseline] Counts: ${counts}`);
console.log(`[Baseline] Majority: ${majorityBaseline.toFixed(2)}% Weighted: ${weightedBaseline.toFixed(2)}%`);
}
data.labels.forEach(label => {
const labelDiv = document.createElement('div');
labelDiv.style.cssText = 'padding: 6px 10px; margin: 4px 0; background: white; border-radius: 4px; display: flex; justify-content: space-between; align-items: center;';
@@ -952,6 +1112,31 @@
const result = await response.json();
alert(result.message);
// Reset accuracy tracking for new training
bestAccuracyData = {
accuracy: 0,
epoch: 0,
trainAcc: 0,
valAcc: 0,
trainLoss: 0,
valLoss: 0
};
worstAccuracyData = {
accuracy: Infinity, // sentinel - means "never updated"
epoch: 0,
trainAcc: 0,
valAcc: 0,
trainLoss: 0,
valLoss: 0
};
accuracyHistory = [];
randomBaseline = 0;
numberOfClasses = 0;
document.getElementById('bestAccuracyCard').style.display = 'none';
document.getElementById('worstAccuracyCard').style.display = 'none';
document.getElementById('performanceRangeCard').style.display = 'none';
// Start monitoring status
if (statusInterval) clearInterval(statusInterval);
statusInterval = setInterval(checkStatus, 2000);
@@ -994,10 +1179,136 @@
statusBox.className = 'status-box';
if (status.is_training) {
statusBox.classList.add('training');
// Track accuracy metrics during training
const currentAcc = status.result?.test_accuracy || status.result?.val_accuracy || 0;
const currentTrainAcc = status.result?.train_accuracy || 0;
const currentValAcc = status.result?.test_accuracy || status.result?.val_accuracy || 0;
const currentTrainLoss = status.result?.train_loss || 0;
const currentValLoss = status.result?.val_loss || status.result?.test_loss || 0;
// Track accuracy history
if (currentAcc > 0) {
accuracyHistory.push(currentAcc * 100);
}
// Extract epoch/iteration info from progress text
const epochMatch = status.progress?.match(/epoch[\s:]*(\d+)/i) || status.progress?.match(/(\d+)\s*\//);
const currentEpoch = epochMatch ? parseInt(epochMatch[1]) : 0;
// Extract number of classes and calculate random baseline
if (status.result?.n_classes && numberOfClasses === 0) {
numberOfClasses = status.result.n_classes;
randomBaseline = (100 / numberOfClasses);
}
// Track best accuracy (cao nhất - cận trên)
if (currentAcc > bestAccuracyData.accuracy && currentAcc > 0) {
bestAccuracyData = {
accuracy: currentAcc,
epoch: currentEpoch,
trainAcc: currentTrainAcc,
valAcc: currentValAcc,
trainLoss: currentTrainLoss,
valLoss: currentValLoss
};
// Update best accuracy display
document.getElementById('bestAccuracyCard').style.display = 'block';
document.getElementById('bestAccuracy').textContent = (bestAccuracyData.accuracy * 100).toFixed(2);
document.getElementById('bestEpoch').textContent = bestAccuracyData.epoch || '--';
document.getElementById('bestTrainAcc').textContent = bestAccuracyData.trainAcc ? (bestAccuracyData.trainAcc * 100).toFixed(2) : '--';
document.getElementById('bestValAcc').textContent = bestAccuracyData.valAcc ? (bestAccuracyData.valAcc * 100).toFixed(2) : '--';
document.getElementById('bestTrainLoss').textContent = bestAccuracyData.trainLoss ? bestAccuracyData.trainLoss.toFixed(4) : '--';
document.getElementById('bestValLoss').textContent = bestAccuracyData.valLoss ? bestAccuracyData.valLoss.toFixed(4) : '--';
console.log('🏆 New Best Accuracy:', (bestAccuracyData.accuracy * 100).toFixed(2) + '%');
}
// Track worst accuracy (thấp nhất - cận dưới)
if (currentAcc < worstAccuracyData.accuracy && currentAcc > 0) {
worstAccuracyData = {
accuracy: currentAcc,
epoch: currentEpoch,
trainAcc: currentTrainAcc,
valAcc: currentValAcc,
trainLoss: currentTrainLoss,
valLoss: currentValLoss
};
// Update worst accuracy display
document.getElementById('worstAccuracyCard').style.display = 'block';
document.getElementById('worstAccuracy').textContent = (worstAccuracyData.accuracy * 100).toFixed(2);
document.getElementById('worstEpoch').textContent = worstAccuracyData.epoch || '--';
document.getElementById('worstTrainAcc').textContent = worstAccuracyData.trainAcc ? (worstAccuracyData.trainAcc * 100).toFixed(2) : '--';
document.getElementById('worstValAcc').textContent = worstAccuracyData.valAcc ? (worstAccuracyData.valAcc * 100).toFixed(2) : '--';
document.getElementById('worstTrainLoss').textContent = worstAccuracyData.trainLoss ? worstAccuracyData.trainLoss.toFixed(4) : '--';
document.getElementById('worstValLoss').textContent = worstAccuracyData.valLoss ? worstAccuracyData.valLoss.toFixed(4) : '--';
console.log('📉 New Worst Accuracy:', (worstAccuracyData.accuracy * 100).toFixed(2) + '% (cận dưới)');
}
// Update Performance Range Summary
if (bestAccuracyData.accuracy > 0 && worstAccuracyData.accuracy < 100 && accuracyHistory.length > 0) {
document.getElementById('performanceRangeCard').style.display = 'block';
const range = (bestAccuracyData.accuracy - worstAccuracyData.accuracy) * 100;
const avgAcc = accuracyHistory.reduce((a, b) => a + b, 0) / accuracyHistory.length;
const stability = avgAcc > 0 ? ((1 - range / avgAcc) * 100) : 0;
document.getElementById('accuracyRange').textContent = range.toFixed(2);
document.getElementById('avgAccuracy').textContent = avgAcc.toFixed(2);
document.getElementById('stability').textContent = Math.max(0, stability).toFixed(1);
// Display random baseline
if (randomBaseline > 0) {
document.getElementById('randomBaseline').textContent = randomBaseline.toFixed(2);
// Compare worst accuracy with random baseline
const worstAccPct = worstAccuracyData.accuracy * 100;
const comparisonText = document.getElementById('baselineComparisonText');
if (worstAccPct > randomBaseline) {
comparisonText.innerHTML = `✅ Worst Accuracy (${worstAccPct.toFixed(2)}%) > Random Baseline (${randomBaseline.toFixed(2)}%)<br>Model vượt ngẫu nhiên ngay cả ở epoch tệ nhất!`;
comparisonText.style.color = '#28a745';
} else {
comparisonText.innerHTML = `⚠️ Worst Accuracy (${worstAccPct.toFixed(2)}%) ≤ Random Baseline (${randomBaseline.toFixed(2)}%)<br>Cần cải thiện hyperparameters - một số epoch tệ hơn đoán ngẫu nhiên!`;
comparisonText.style.color = '#dc3545';
}
}
}
} else if (status.error) {
statusBox.classList.add('error');
} else if (status.result) {
statusBox.classList.add('success');
// Final update with completed results
const finalAcc = status.result.test_accuracy || status.result.val_accuracy || 0;
if (finalAcc > 0) {
const finalTrainAcc = status.result.train_accuracy || 0;
const finalValAcc = status.result.test_accuracy || status.result.val_accuracy || 0;
const finalTrainLoss = status.result.train_loss || 0;
const finalValLoss = status.result.val_loss || status.result.test_loss || 0;
if (finalAcc >= bestAccuracyData.accuracy) {
bestAccuracyData = {
accuracy: finalAcc,
epoch: 'Final',
trainAcc: finalTrainAcc,
valAcc: finalValAcc,
trainLoss: finalTrainLoss,
valLoss: finalValLoss
};
document.getElementById('bestAccuracyCard').style.display = 'block';
document.getElementById('bestAccuracy').textContent = (bestAccuracyData.accuracy * 100).toFixed(2);
document.getElementById('bestEpoch').textContent = 'Final';
document.getElementById('bestTrainAcc').textContent = bestAccuracyData.trainAcc ? (bestAccuracyData.trainAcc * 100).toFixed(2) : '--';
document.getElementById('bestValAcc').textContent = bestAccuracyData.valAcc ? (bestAccuracyData.valAcc * 100).toFixed(2) : '--';
document.getElementById('bestTrainLoss').textContent = bestAccuracyData.trainLoss ? bestAccuracyData.trainLoss.toFixed(4) : '--';
document.getElementById('bestValLoss').textContent = bestAccuracyData.valLoss ? bestAccuracyData.valLoss.toFixed(4) : '--';
}
}
}
// Show/hide buttons
@@ -1014,15 +1325,157 @@
// Add to history when training completes
if (status.result || status.error) {
// FALLBACK: For one-shot models (XGBoost/RF) that don't have epochs,
// status.result is null during training so JS tracking never fires.
// Populate best/worst at completion time.
if (status.result && (bestAccuracyData.accuracy === 0 || worstAccuracyData.accuracy === Infinity)) {
// PRIORITY 1: Use per-tree best/worst from backend (XGBoost eval_set)
if (status.result.best_checkpoint) {
const bc = status.result.best_checkpoint;
bestAccuracyData = {
accuracy: bc.accuracy ?? bc.valAcc ?? 0,
epoch: bc.epoch ?? 'Final',
trainAcc: bc.trainAcc ?? 0,
valAcc: bc.valAcc ?? bc.accuracy ?? 0,
trainLoss: bc.trainLoss ?? 0,
valLoss: bc.valLoss ?? 0
};
}
if (status.result.worst_checkpoint) {
const wc = status.result.worst_checkpoint;
worstAccuracyData = {
accuracy: wc.accuracy ?? wc.valAcc ?? 0,
epoch: wc.epoch ?? 'Final',
trainAcc: wc.trainAcc ?? 0,
valAcc: wc.valAcc ?? wc.accuracy ?? 0,
trainLoss: wc.trainLoss ?? 0,
valLoss: wc.valLoss ?? 0
};
}
// PRIORITY 2: No per-tree data — fall back to final accuracy
if (!status.result.best_checkpoint) {
const finalAcc = status.result.test_accuracy || status.result.val_accuracy || 0;
const finalTrainAcc = status.result.train_accuracy || 0;
const finalTrainLoss = status.result.train_loss || 0;
const finalValLoss = status.result.val_loss || 0;
if (finalAcc > 0) {
bestAccuracyData = { accuracy: finalAcc, epoch: 'Final', trainAcc: finalTrainAcc, valAcc: finalAcc, trainLoss: finalTrainLoss, valLoss: finalValLoss };
worstAccuracyData = { ...bestAccuracyData };
}
}
// Update Best Accuracy display card
if (bestAccuracyData.accuracy > 0) {
document.getElementById('bestAccuracyCard').style.display = 'block';
document.getElementById('bestAccuracy').textContent = (bestAccuracyData.accuracy * 100).toFixed(2);
document.getElementById('bestEpoch').textContent = `Cây #${bestAccuracyData.epoch}`;
document.getElementById('bestTrainAcc').textContent = (bestAccuracyData.trainAcc * 100).toFixed(2);
document.getElementById('bestValAcc').textContent = (bestAccuracyData.valAcc * 100).toFixed(2);
document.getElementById('bestTrainLoss').textContent = bestAccuracyData.trainLoss.toFixed(4);
document.getElementById('bestValLoss').textContent = bestAccuracyData.valLoss.toFixed(4);
}
// Update Worst Accuracy display card
if (worstAccuracyData.accuracy > 0 && worstAccuracyData.accuracy < Infinity) {
document.getElementById('worstAccuracyCard').style.display = 'block';
document.getElementById('worstAccuracy').textContent = (worstAccuracyData.accuracy * 100).toFixed(2);
document.getElementById('worstEpoch').textContent = `Cây #${worstAccuracyData.epoch}`;
document.getElementById('worstTrainAcc').textContent = (worstAccuracyData.trainAcc * 100).toFixed(2);
document.getElementById('worstValAcc').textContent = (worstAccuracyData.valAcc * 100).toFixed(2);
document.getElementById('worstTrainLoss').textContent = worstAccuracyData.trainLoss.toFixed(4);
document.getElementById('worstValLoss').textContent = worstAccuracyData.valLoss.toFixed(4);
}
}
// FALLBACK: populate randomBaseline from result if not captured
// (only triggers if shapefile was never loaded / counts unavailable)
if (randomBaseline === 0 && status.result) {
if (status.result.majority_class_baseline) {
randomBaseline = status.result.majority_class_baseline * 100;
document.getElementById('randomBaseline').textContent = randomBaseline.toFixed(2);
} else {
const nClasses = status.result.n_classes
|| status.result.classes?.length
|| status.result.class_names?.length
|| 0;
if (nClasses > 0) {
numberOfClasses = nClasses;
randomBaseline = 100 / nClasses;
document.getElementById('randomBaseline').textContent = randomBaseline.toFixed(2);
}
}
}
const record = {
timestamp: new Date().toLocaleString('vi-VN'),
success: !!status.result,
accuracy: status.result?.test_accuracy,
error: status.error,
duration: status.start_time && status.end_time ?
(new Date(status.end_time) - new Date(status.start_time)) / 1000 : null
(new Date(status.end_time) - new Date(status.start_time)) / 1000 : null,
// Include best accuracy checkpoint data
bestCheckpoint: bestAccuracyData.accuracy > 0 ? {
accuracy: bestAccuracyData.accuracy,
epoch: bestAccuracyData.epoch,
trainAcc: bestAccuracyData.trainAcc,
valAcc: bestAccuracyData.valAcc,
trainLoss: bestAccuracyData.trainLoss,
valLoss: bestAccuracyData.valLoss
} : null
};
addTrainingRecord(record);
// Regenerate report with best_checkpoint and worst_checkpoint
if (status.result && bestAccuracyData.accuracy > 0) {
try {
const reportData = {
training_result: status.result,
best_checkpoint: {
accuracy: bestAccuracyData.accuracy,
epoch: bestAccuracyData.epoch,
trainAcc: bestAccuracyData.trainAcc,
valAcc: bestAccuracyData.valAcc,
trainLoss: bestAccuracyData.trainLoss,
valLoss: bestAccuracyData.valLoss
}
};
// Only include worst_checkpoint if it has a valid value (not sentinel Infinity)
if (worstAccuracyData.accuracy < Infinity && worstAccuracyData.accuracy > 0) {
reportData.worst_checkpoint = {
accuracy: worstAccuracyData.accuracy,
epoch: worstAccuracyData.epoch,
trainAcc: worstAccuracyData.trainAcc,
valAcc: worstAccuracyData.valAcc,
trainLoss: worstAccuracyData.trainLoss,
valLoss: worstAccuracyData.valLoss
};
}
if (randomBaseline > 0) {
reportData.random_baseline = randomBaseline;
}
const regenerateResponse = await fetch(`${API_BASE}/training/report/regenerate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(reportData)
});
const regenerateData = await regenerateResponse.json();
if (regenerateData.success) {
console.log('Report regenerated with checkpoints:', regenerateData.report_path);
console.log('📊 Báo cáo đã được cập nhật với Best/Worst Checkpoint và Random Baseline');
// Reload reports list after regeneration
setTimeout(loadReports, 1000);
}
} catch (err) {
console.error('Failed to regenerate report:', err);
}
}
}
}
@@ -1235,14 +1688,34 @@
const statusColor = record.success ? '#4caf50' : '#f44336';
const duration = record.duration ? ` (${Math.round(record.duration / 60)}m)` : '';
// Generate best checkpoint info if available
let bestCheckpointHtml = '';
if (record.bestCheckpoint && record.bestCheckpoint.accuracy > 0) {
const best = record.bestCheckpoint;
bestCheckpointHtml = `
<div style="margin-top: 6px; padding: 6px; background: linear-gradient(135deg, #FFD70020, #FFA50020); border-radius: 4px; border-left: 2px solid #FFA500;">
<div style="font-size: 11px; font-weight: 600; color: #FF8C00; margin-bottom: 3px;">🏆 Best Checkpoint</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; font-size: 10px;">
<div><span style="color: #888;">Best Acc:</span> <strong style="color: #667eea;">${(best.accuracy * 100).toFixed(2)}%</strong></div>
<div><span style="color: #888;">Epoch:</span> <strong style="color: #764ba2;">${best.epoch}</strong></div>
<div><span style="color: #888;">Train:</span> <strong style="color: #43e97b;">${best.trainAcc ? (best.trainAcc * 100).toFixed(1) + '%' : '--'}</strong></div>
<div><span style="color: #888;">Val:</span> <strong style="color: #f5576c;">${best.valAcc ? (best.valAcc * 100).toFixed(1) + '%' : '--'}</strong></div>
<div><span style="color: #888;">Train Loss:</span> <strong>${best.trainLoss ? best.trainLoss.toFixed(4) : '--'}</strong></div>
<div><span style="color: #888;">Val Loss:</span> <strong>${best.valLoss ? best.valLoss.toFixed(4) : '--'}</strong></div>
</div>
</div>
`;
}
return `
<div style="padding: 8px; margin-bottom: 8px; background: white; border-radius: 4px; border-left: 3px solid ${statusColor};">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600;">${statusIcon} ${record.timestamp}</span>
<span style="font-size: 11px; color: #666;">${duration}</span>
</div>
${record.accuracy ? `<div style="font-size: 12px; color: #666; margin-top: 4px;">Accuracy: ${(record.accuracy * 100).toFixed(1)}%</div>` : ''}
${record.accuracy ? `<div style="font-size: 12px; color: #666; margin-top: 4px;">Final Accuracy: ${(record.accuracy * 100).toFixed(1)}%</div>` : ''}
${record.error ? `<div style="font-size: 11px; color: #f44336; margin-top: 4px;">${record.error}</div>` : ''}
${bestCheckpointHtml}
</div>
`;
}).join('');
@@ -1267,6 +1740,23 @@
} else {
document.getElementById('avgTime').textContent = '-';
}
// Calculate best accuracy ever from all training history
let bestAccuracyEver = 0;
let bestEpochEver = '';
trainingHistory.forEach(record => {
if (record.bestCheckpoint && record.bestCheckpoint.accuracy > bestAccuracyEver) {
bestAccuracyEver = record.bestCheckpoint.accuracy;
bestEpochEver = record.bestCheckpoint.epoch;
}
});
if (bestAccuracyEver > 0) {
document.getElementById('bestAccuracyEver').innerHTML =
`${(bestAccuracyEver * 100).toFixed(2)}% <span style="font-size: 0.85em; color: #888;">(Epoch ${bestEpochEver})</span>`;
} else {
document.getElementById('bestAccuracyEver').textContent = '-';
}
}
// Initialize Leaflet Map
@@ -1548,15 +2038,15 @@
document.getElementById('maxLat').value = meta.max_lat;
// Update map rectangle
if (rectangle) {
drawnItems.removeLayer(rectangle);
if (currentRectangle) {
drawnItems.removeLayer(currentRectangle);
}
const bounds = L.latLngBounds(
[meta.min_lat, meta.min_lon],
[meta.max_lat, meta.max_lon]
);
rectangle = L.rectangle(bounds, {color: '#3388ff', weight: 3, fillOpacity: 0.2});
drawnItems.addLayer(rectangle);
currentRectangle = L.rectangle(bounds, {color: '#3388ff', weight: 3, fillOpacity: 0.2});
drawnItems.addLayer(currentRectangle);
map.fitBounds(bounds);
}