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:
Regular → Executable
+496
-6
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user