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
+543
-15
@@ -26,6 +26,8 @@ def generate_confusion_matrix_image(conf_matrix, class_names):
|
||||
return None
|
||||
|
||||
try:
|
||||
# Normalize class names to strings
|
||||
class_names = [str(c) for c in class_names]
|
||||
fig, ax = plt.subplots(figsize=(10, 8))
|
||||
conf_matrix = np.array(conf_matrix)
|
||||
|
||||
@@ -70,18 +72,22 @@ def generate_class_distribution_chart(class_names, classification_report):
|
||||
return None
|
||||
|
||||
try:
|
||||
# Normalize keys to strings to handle integer/string mismatch
|
||||
cls_report_str = {str(k): v for k, v in classification_report.items()}
|
||||
class_names_str = [str(c) for c in class_names]
|
||||
|
||||
# Extract support (number of samples) for each class
|
||||
supports = []
|
||||
for cls in class_names:
|
||||
if cls in classification_report:
|
||||
supports.append(classification_report[cls].get('support', 0))
|
||||
for cls in class_names_str:
|
||||
if cls in cls_report_str:
|
||||
supports.append(int(cls_report_str[cls].get('support', 0)))
|
||||
else:
|
||||
supports.append(0)
|
||||
|
||||
fig, ax = plt.subplots(figsize=(10, 6))
|
||||
colors = plt.cm.Set3(np.linspace(0, 1, len(class_names)))
|
||||
|
||||
bars = ax.bar(class_names, supports, color=colors)
|
||||
bars = ax.bar(class_names_str, supports, color=colors)
|
||||
ax.set_xlabel('Loại đất')
|
||||
ax.set_ylabel('Số mẫu')
|
||||
ax.set_title('Phân bố số mẫu theo loại đất')
|
||||
@@ -89,8 +95,9 @@ def generate_class_distribution_chart(class_names, classification_report):
|
||||
|
||||
# Add value labels on bars
|
||||
for bar, val in zip(bars, supports):
|
||||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
|
||||
str(int(val)), ha='center', va='bottom', fontsize=9)
|
||||
if val > 0:
|
||||
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5,
|
||||
str(val), ha='center', va='bottom', fontsize=9)
|
||||
|
||||
fig.tight_layout()
|
||||
|
||||
@@ -113,21 +120,25 @@ def generate_metrics_chart(class_names, classification_report):
|
||||
return None
|
||||
|
||||
try:
|
||||
# Normalize keys to strings to handle integer/string mismatch
|
||||
cls_report_str = {str(k): v for k, v in classification_report.items()}
|
||||
class_names_str = [str(c) for c in class_names]
|
||||
|
||||
precisions = []
|
||||
recalls = []
|
||||
f1_scores = []
|
||||
|
||||
for cls in class_names:
|
||||
if cls in classification_report:
|
||||
precisions.append(classification_report[cls].get('precision', 0))
|
||||
recalls.append(classification_report[cls].get('recall', 0))
|
||||
f1_scores.append(classification_report[cls].get('f1-score', 0))
|
||||
for cls in class_names_str:
|
||||
if cls in cls_report_str:
|
||||
precisions.append(cls_report_str[cls].get('precision', 0))
|
||||
recalls.append(cls_report_str[cls].get('recall', 0))
|
||||
f1_scores.append(cls_report_str[cls].get('f1-score', 0))
|
||||
else:
|
||||
precisions.append(0)
|
||||
recalls.append(0)
|
||||
f1_scores.append(0)
|
||||
|
||||
x = np.arange(len(class_names))
|
||||
x = np.arange(len(class_names_str))
|
||||
width = 0.25
|
||||
|
||||
fig, ax = plt.subplots(figsize=(12, 6))
|
||||
@@ -140,7 +151,7 @@ def generate_metrics_chart(class_names, classification_report):
|
||||
ax.set_ylabel('Score')
|
||||
ax.set_title('Precision / Recall / F1-Score theo loại đất')
|
||||
ax.set_xticks(x)
|
||||
ax.set_xticklabels(class_names, rotation=45, ha='right')
|
||||
ax.set_xticklabels(class_names_str, rotation=45, ha='right')
|
||||
ax.legend()
|
||||
ax.set_ylim(0, 1.1)
|
||||
|
||||
@@ -182,8 +193,10 @@ def generate_training_report(training_result, config=None):
|
||||
train_samples = training_result.get('training_samples', 0)
|
||||
test_samples = training_result.get('testing_samples', 0)
|
||||
test_size = training_result.get('test_size', 0.2)
|
||||
classes = training_result.get('classes', [])
|
||||
classes = [str(c) for c in training_result.get('classes', [])] # normalize to strings
|
||||
cls_report = training_result.get('classification_report', {})
|
||||
# Also normalize cls_report keys to strings (handles integer keys from JSON)
|
||||
cls_report = {str(k): v for k, v in cls_report.items()}
|
||||
conf_matrix = training_result.get('confusion_matrix', [])
|
||||
model_type = training_result.get('model_type', 'unknown')
|
||||
model_path = training_result.get('model_path', '')
|
||||
@@ -191,6 +204,68 @@ def generate_training_report(training_result, config=None):
|
||||
time_range = training_result.get('time_range', '')
|
||||
resolution = training_result.get('resolution', 20)
|
||||
|
||||
# NEW: Extract PSNR metrics (for cloud removal)
|
||||
val_psnr = training_result.get('val_psnr', None)
|
||||
baseline_psnr = training_result.get('baseline_psnr', None)
|
||||
improvement = None
|
||||
if val_psnr is not None and baseline_psnr is not None:
|
||||
improvement = val_psnr - baseline_psnr
|
||||
|
||||
# NEW: Extract Best Checkpoint data
|
||||
best_checkpoint = training_result.get('best_checkpoint', training_result.get('bestCheckpoint', None))
|
||||
|
||||
# NEW: Extract Worst Checkpoint data (for performance range analysis)
|
||||
worst_checkpoint = training_result.get('worst_checkpoint', training_result.get('worstCheckpoint', None))
|
||||
|
||||
# Calculate baseline comparison message for cloud removal Performance Range
|
||||
baseline_comparison_msg = ""
|
||||
if worst_checkpoint and 'modelPSNR' in worst_checkpoint and 'baselinePSNR' in worst_checkpoint:
|
||||
worst_psnr = worst_checkpoint.get('modelPSNR', 0)
|
||||
baseline = worst_checkpoint.get('baselinePSNR', 0)
|
||||
margin = worst_psnr - baseline
|
||||
if margin > 5:
|
||||
baseline_comparison_msg = f'• <strong style="color: #2ecc71;">✅ Tốt:</strong> PSNR thấp nhất ({worst_psnr:.2f}dB) vượt baseline +{margin:.2f}dB - Model học tốt ngay cả trong worst case'
|
||||
elif margin > 2:
|
||||
baseline_comparison_msg = f'• <strong style="color: #f39c12;">⚠️ Khá:</strong> PSNR thấp nhất ({worst_psnr:.2f}dB) vượt baseline +{margin:.2f}dB - Cần cải thiện stability'
|
||||
elif margin > 0:
|
||||
baseline_comparison_msg = f'• <strong style="color: #e67e22;">⚠️ Yếu:</strong> PSNR thấp nhất ({worst_psnr:.2f}dB) chỉ vượt baseline +{margin:.2f}dB - Model không ổn định'
|
||||
else:
|
||||
baseline_comparison_msg = f'• <strong style="color: #e74c3c;">❌ Kém:</strong> PSNR thấp nhất ({worst_psnr:.2f}dB) không vượt baseline ({baseline:.2f}dB) - Model thất bại'
|
||||
|
||||
|
||||
# NEW: Extract Random Baseline (for land classification)
|
||||
random_baseline = training_result.get('random_baseline', None)
|
||||
# If backend provided exact baselines, prefer majority_class_baseline over uniform 1/n_classes
|
||||
majority_class_baseline = training_result.get('majority_class_baseline', None)
|
||||
weighted_random_baseline = training_result.get('weighted_random_baseline', None)
|
||||
# random_baseline from frontend is already in % (e.g. 28.5);
|
||||
# majority_class_baseline from backend is a fraction (e.g. 0.285)
|
||||
if random_baseline is None and majority_class_baseline is not None:
|
||||
random_baseline = majority_class_baseline * 100
|
||||
|
||||
# NEW: Extract detailed hyperparameters
|
||||
hyperparams = training_result.get('hyperparameters', {})
|
||||
if not hyperparams and config:
|
||||
hyperparams = config
|
||||
|
||||
# Extract common hyperparameters
|
||||
n_estimators = training_result.get('n_estimators', hyperparams.get('n_estimators', 'N/A'))
|
||||
max_depth = training_result.get('max_depth', hyperparams.get('max_depth', 'N/A'))
|
||||
learning_rate = training_result.get('learning_rate', hyperparams.get('learning_rate', 'N/A'))
|
||||
batch_size = training_result.get('batch_size', hyperparams.get('batch_size', 'N/A'))
|
||||
num_epochs = training_result.get('num_epochs', hyperparams.get('num_epochs', 'N/A'))
|
||||
use_gpu = training_result.get('use_gpu', hyperparams.get('use_gpu', False))
|
||||
use_s1 = training_result.get('use_s1', hyperparams.get('use_s1', False))
|
||||
|
||||
# Feature information
|
||||
features = training_result.get('features', [])
|
||||
feature_mode = training_result.get('feature_mode', 'simple')
|
||||
n_features = training_result.get('n_features', len(features) if features else 'N/A')
|
||||
|
||||
# Data source info
|
||||
data_source = training_result.get('data_source', 'Unknown')
|
||||
collections = training_result.get('collections', [])
|
||||
|
||||
# Generate charts
|
||||
conf_matrix_img = generate_confusion_matrix_image(conf_matrix, classes) if conf_matrix else None
|
||||
class_dist_img = generate_class_distribution_chart(classes, cls_report) if cls_report else None
|
||||
@@ -201,6 +276,11 @@ def generate_training_report(training_result, config=None):
|
||||
for cls in classes:
|
||||
if cls in cls_report:
|
||||
metrics = cls_report[cls]
|
||||
elif str(cls) in cls_report:
|
||||
metrics = cls_report[str(cls)]
|
||||
else:
|
||||
metrics = None
|
||||
if metrics:
|
||||
cls_report_rows += f"""
|
||||
<tr>
|
||||
<td><strong>{cls}</strong></td>
|
||||
@@ -239,6 +319,33 @@ def generate_training_report(training_result, config=None):
|
||||
conf_matrix_table += "</tr>"
|
||||
conf_matrix_table += "</table>"
|
||||
|
||||
# Pre-compute accuracy Performance Range stats (avoid division-by-zero in f-strings)
|
||||
_bc = best_checkpoint or {}
|
||||
_wc = worst_checkpoint or {}
|
||||
if best_checkpoint and worst_checkpoint and 'accuracy' in _bc and 'accuracy' in _wc:
|
||||
_best_acc = _bc.get('accuracy', 0)
|
||||
_worst_acc = _wc.get('accuracy', 0)
|
||||
acc_range_pct = (_best_acc - _worst_acc) * 100
|
||||
acc_avg_pct = ((_best_acc + _worst_acc) / 2) * 100
|
||||
acc_avg_raw = (_best_acc + _worst_acc) / 2
|
||||
if acc_avg_raw > 0:
|
||||
acc_stability = max(0, (1 - (_best_acc - _worst_acc) / acc_avg_raw) * 100)
|
||||
else:
|
||||
acc_stability = 100.0 # best == worst == 0, consider stable
|
||||
acc_stable_label = 'ổn định ✅' if acc_stability >= 80 else 'cần cải thiện ⚠️'
|
||||
rnd_bl = random_baseline or 0
|
||||
rnd_bl_display = f'{rnd_bl:.2f}' if rnd_bl else 'N/A'
|
||||
worst_beat_baseline = rnd_bl > 0 and _worst_acc * 100 > rnd_bl
|
||||
best_beat_margin = (_best_acc * 100 - rnd_bl) if rnd_bl else None
|
||||
n_classes_display = len(classes) if classes else training_result.get('n_classes', '?')
|
||||
else:
|
||||
acc_range_pct = acc_avg_pct = acc_stability = 0.0
|
||||
acc_stable_label = ''
|
||||
rnd_bl = rnd_bl_display = 0
|
||||
worst_beat_baseline = False
|
||||
best_beat_margin = None
|
||||
n_classes_display = '?'
|
||||
|
||||
# HTML Template
|
||||
html = f"""
|
||||
<!DOCTYPE html>
|
||||
@@ -428,9 +535,338 @@ def generate_training_report(training_result, config=None):
|
||||
<div class="value">{test_size*100:.0f}%</div>
|
||||
<div class="label">Test Size</div>
|
||||
</div>
|
||||
{f'''<div class="stat-card success">
|
||||
<div class="value">{val_psnr:.2f} dB</div>
|
||||
<div class="label">📈 Model PSNR</div>
|
||||
</div>''' if val_psnr is not None else ''}
|
||||
{f'''<div class="stat-card">
|
||||
<div class="value">{baseline_psnr:.2f} dB</div>
|
||||
<div class="label">📉 Baseline PSNR</div>
|
||||
</div>''' if baseline_psnr is not None else ''}
|
||||
{f'''<div class="stat-card {'success' if improvement > 0 else 'warning'}">
|
||||
<div class="value">{'+' if improvement > 0 else ''}{improvement:.2f} dB</div>
|
||||
<div class="label">⚡ Improvement</div>
|
||||
</div>''' if improvement is not None else ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Best Checkpoint (if available) -->
|
||||
{f'''<div class="section">
|
||||
<h2>🏆 Best Accuracy Checkpoint</h2>
|
||||
<div style="background: linear-gradient(135deg, #ffd70020 0%, #ffa50020 100%); padding: 25px; border-radius: 10px; border: 2px solid #ffa500;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 3em; font-weight: bold; color: #ff8c00;">
|
||||
{best_checkpoint.get('accuracy', 0) * 100:.2f}%
|
||||
</div>
|
||||
<div style="color: #666; font-size: 1.1em; margin-top: 5px;">
|
||||
Đạt tại epoch {best_checkpoint.get('epoch', 'N/A')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px;">
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Train Accuracy</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #28a745; margin-top: 5px;">
|
||||
{(best_checkpoint.get('trainAcc') or 0) * 100:.2f}%
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Val Accuracy</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #28a745; margin-top: 5px;">
|
||||
{(best_checkpoint.get('valAcc') or 0) * 100:.2f}%
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Train Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{f"{best_checkpoint.get('trainLoss'):.4f}" if best_checkpoint.get('trainLoss') is not None else 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Val Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{f"{best_checkpoint.get('valLoss'):.4f}" if best_checkpoint.get('valLoss') is not None else 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.7); border-radius: 5px; font-size: 0.95em;">
|
||||
<strong>📊 Giải thích:</strong><br>
|
||||
Đây là điểm checkpoint tốt nhất trong toàn bộ quá trình training, được ghi nhận khi validation accuracy đạt giá trị cao nhất.
|
||||
Các metrics này thể hiện hiệu suất thực sự của model tại thời điểm tối ưu.
|
||||
</div>
|
||||
</div>
|
||||
</div>''' if best_checkpoint else ''}
|
||||
|
||||
<!-- Worst Accuracy Checkpoint for Land Classification (if available) -->
|
||||
{f'''<div class="section">
|
||||
<h2>📉 Worst Accuracy Checkpoint (Cận Dưới)</h2>
|
||||
<div style="background: linear-gradient(135deg, #667eea20 0%, #764ba220 100%); padding: 25px; border-radius: 10px; border: 2px solid #667eea;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 3em; font-weight: bold; color: #667eea;">
|
||||
{worst_checkpoint.get('accuracy', 0) * 100:.2f}%
|
||||
</div>
|
||||
<div style="color: #666; font-size: 1.1em; margin-top: 5px;">
|
||||
Accuracy thấp nhất tại epoch {worst_checkpoint.get('epoch', 'N/A')} (cận dưới training)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px;">
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Train Accuracy</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #28a745; margin-top: 5px;">
|
||||
{(worst_checkpoint.get('trainAcc') or 0) * 100:.2f}%
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Val Accuracy</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #28a745; margin-top: 5px;">
|
||||
{(worst_checkpoint.get('valAcc') or 0) * 100:.2f}%
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Train Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{f"{worst_checkpoint.get('trainLoss'):.4f}" if worst_checkpoint.get('trainLoss') is not None else 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Val Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{f"{worst_checkpoint.get('valLoss'):.4f}" if worst_checkpoint.get('valLoss') is not None else 'N/A'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.7); border-radius: 5px; font-size: 0.95em;">
|
||||
<strong>📊 Giải thích:</strong><br>
|
||||
Đây là epoch có accuracy thấp nhất trong quá trình training, thể hiện điểm cận dưới của khả năng model.
|
||||
{f"Ngay cả ở epoch tệ nhất, accuracy vẫn {worst_checkpoint.get('accuracy', 0) * 100:.2f}% {'> random baseline' if random_baseline and worst_checkpoint.get('accuracy', 0) * 100 > random_baseline else '≤ random baseline'}." if random_baseline else ''}
|
||||
</div>
|
||||
</div>
|
||||
</div>''' if worst_checkpoint and 'accuracy' in worst_checkpoint else ''}
|
||||
|
||||
<!-- Performance Range Analysis for Land Classification (if both best and worst available) -->
|
||||
{f'''<div class="section">
|
||||
<h2>📊 Performance Range & Random Baseline Analysis</h2>
|
||||
<div style="background: linear-gradient(135deg, #f093fb20 0%, #f5576c20 100%); padding: 25px; border-radius: 10px; border: 2px solid #f093fb;">
|
||||
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px;">
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;">Accuracy Range</div>
|
||||
<div style="font-size: 2.5em; font-weight: bold; color: #667eea;">
|
||||
{acc_range_pct:.2f}
|
||||
</div>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 5px;">% (max - min)</div>
|
||||
<div style="color: #666; font-size: 0.85em; margin-top: 10px;">
|
||||
Max: {_bc.get('accuracy', 0) * 100:.2f}%<br>
|
||||
Min: {_wc.get('accuracy', 0) * 100:.2f}%
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;">Average Accuracy</div>
|
||||
<div style="font-size: 2.5em; font-weight: bold; color: #43e97b;">
|
||||
{acc_avg_pct:.2f}
|
||||
</div>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 5px;">%</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;">Stability Score</div>
|
||||
<div style="font-size: 2.5em; font-weight: bold; color: #f093fb;">
|
||||
{acc_stability:.1f}
|
||||
</div>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 5px;">%</div>
|
||||
<div style="color: #666; font-size: 0.85em; margin-top: 10px;">{acc_stable_label}</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;">Random Baseline</div>
|
||||
<div style="font-size: 2.5em; font-weight: bold; color: #ff6b6b;">
|
||||
{rnd_bl_display}
|
||||
</div>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 5px;">% (lớp chiếm đa số)</div>
|
||||
<div style="color: #666; font-size: 0.85em; margin-top: 10px;">{n_classes_display} classes{f'<br><span style="font-size:0.9em;color:#aaa;">Weighted: {weighted_random_baseline*100:.2f}%</span>' if weighted_random_baseline else ''}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.7); border-radius: 5px; font-size: 0.95em;">
|
||||
<strong>💡 Đánh giá:</strong><br>
|
||||
• <strong>Range</strong>: {acc_range_pct:.2f}% - Model {"ổn định" if acc_range_pct < 5 else "có dao động"} trong quá trình training<br>
|
||||
• <strong>Worst Accuracy vs Random Baseline</strong>: {_wc.get('accuracy', 0) * 100:.2f}% {">" if worst_beat_baseline else "≤"} {rnd_bl_display}% → {"✅ Model luôn tốt hơn đoán ngẫu nhiên ngay cả ở điểm tệ nhất!" if worst_beat_baseline else ("⚠️ Điểm tệ nhất chưa vượt mức ngẫu nhiên - cần cải thiện hyperparameters!" if rnd_bl else "ℹ️ Không có dữ liệu random baseline")}<br>
|
||||
• <strong>Stability</strong>: {acc_stability:.1f}% - Training {acc_stable_label}<br>
|
||||
• <strong>Best Accuracy</strong>: {_bc.get('accuracy', 0) * 100:.2f}% {">" if best_beat_margin is not None and best_beat_margin > 0 else "≤"} {rnd_bl_display}% → {"✅ Vượt random baseline " + f"{best_beat_margin:.2f}%" if best_beat_margin is not None and best_beat_margin > 0 else "⚠️ Không vượt random baseline"}
|
||||
</div>
|
||||
</div>
|
||||
</div>''' if best_checkpoint and worst_checkpoint and 'accuracy' in _bc and 'accuracy' in _wc else ''}
|
||||
|
||||
<!-- Best PSNR Checkpoint for Cloud Removal (if available) -->
|
||||
{f'''<div class="section">
|
||||
<h2>🏆 Best PSNR Checkpoint</h2>
|
||||
<div style="background: linear-gradient(135deg, #ffd70020 0%, #ffa50020 100%); padding: 25px; border-radius: 10px; border: 2px solid #ffa500;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 3em; font-weight: bold; color: #ff8c00;">
|
||||
{best_checkpoint.get('modelPSNR', 0):.2f} dB
|
||||
</div>
|
||||
<div style="color: #666; font-size: 1.1em; margin-top: 5px;">
|
||||
Model PSNR đạt tại epoch {best_checkpoint.get('epoch', 'N/A')}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px;">
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Train Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{best_checkpoint.get('trainLoss', 0):.6f}
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Val Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{best_checkpoint.get('valLoss', 0):.6f}
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Baseline PSNR</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #6c757d; margin-top: 5px;">
|
||||
{best_checkpoint.get('baselinePSNR', 0):.2f} dB
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Improvement</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: {'#28a745' if best_checkpoint.get('improvement', 0) > 0 else '#ffc107'}; margin-top: 5px;">
|
||||
{'+' if best_checkpoint.get('improvement', 0) > 0 else ''}{best_checkpoint.get('improvement', 0):.2f} dB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.7); border-radius: 5px; font-size: 0.95em;">
|
||||
<strong>📊 Giải thích:</strong><br>
|
||||
Đây là điểm checkpoint tốt nhất trong toàn bộ quá trình training cloud removal, được ghi nhận khi Model PSNR đạt giá trị cao nhất.
|
||||
Improvement thể hiện mức cải thiện so với baseline (không xử lý gì).
|
||||
</div>
|
||||
</div>
|
||||
</div>''' if best_checkpoint and 'modelPSNR' in best_checkpoint else ''}
|
||||
|
||||
<!-- Worst PSNR Checkpoint for Cloud Removal (if available) -->
|
||||
{f'''<div class="section">
|
||||
<h2>📉 Worst PSNR Checkpoint (Cận Dưới)</h2>
|
||||
<div style="background: linear-gradient(135deg, #667eea20 0%, #764ba220 100%); padding: 25px; border-radius: 10px; border: 2px solid #667eea;">
|
||||
<div style="text-align: center; margin-bottom: 20px;">
|
||||
<div style="font-size: 3em; font-weight: bold; color: #667eea;">
|
||||
{worst_checkpoint.get('modelPSNR', 0):.2f} dB
|
||||
</div>
|
||||
<div style="color: #666; font-size: 1.1em; margin-top: 5px;">
|
||||
PSNR thấp nhất tại epoch {worst_checkpoint.get('epoch', 'N/A')} (cận dưới training)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin-top: 20px;">
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Train Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{worst_checkpoint.get('trainLoss', 0):.6f}
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Val Loss</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #dc3545; margin-top: 5px;">
|
||||
{worst_checkpoint.get('valLoss', 0):.6f}
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Baseline PSNR</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: #6c757d; margin-top: 5px;">
|
||||
{worst_checkpoint.get('baselinePSNR', 0):.2f} dB
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 15px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em;">Gap from Baseline</div>
|
||||
<div style="font-size: 1.5em; font-weight: bold; color: {'#28a745' if worst_checkpoint.get('improvement', 0) > 0 else '#dc3545'}; margin-top: 5px;">
|
||||
{'+' if worst_checkpoint.get('improvement', 0) > 0 else ''}{worst_checkpoint.get('improvement', 0):.2f} dB
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.7); border-radius: 5px; font-size: 0.95em;">
|
||||
<strong>📊 Giải thích:</strong><br>
|
||||
Đây là epoch có PSNR thấp nhất trong quá trình training, thể hiện điểm cận dưới của khả năng model.
|
||||
Nếu worst PSNR vẫn > baseline, có nghĩa là ngay cả ở epoch tệ nhất, model vẫn tốt hơn không làm gì!
|
||||
</div>
|
||||
</div>
|
||||
</div>''' if worst_checkpoint and 'modelPSNR' in worst_checkpoint else ''}
|
||||
|
||||
<!-- Performance Range Analysis (if both best and worst available) -->
|
||||
{f'''<div class="section">
|
||||
<h2>📊 Performance Range Analysis</h2>
|
||||
<div style="background: linear-gradient(135deg, #f093fb20 0%, #f5576c20 100%); padding: 25px; border-radius: 10px; border: 2px solid #f093fb;">
|
||||
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;">
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;">PSNR Range</div>
|
||||
<div style="font-size: 2.5em; font-weight: bold; color: #667eea;">
|
||||
{(best_checkpoint.get('modelPSNR', 0) - worst_checkpoint.get('modelPSNR', 0)):.2f}
|
||||
</div>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 5px;">dB (max - min)</div>
|
||||
<div style="color: #666; font-size: 0.85em; margin-top: 10px;">
|
||||
Max: {best_checkpoint.get('modelPSNR', 0):.2f} dB<br>
|
||||
Min: {worst_checkpoint.get('modelPSNR', 0):.2f} dB
|
||||
</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;">Average PSNR</div>
|
||||
<div style="font-size: 2.5em; font-weight: bold; color: #43e97b;">
|
||||
{((best_checkpoint.get('modelPSNR', 0) + worst_checkpoint.get('modelPSNR', 0)) / 2):.2f}
|
||||
</div>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 5px;">dB (estimated)</div>
|
||||
</div>
|
||||
<div style="background: white; padding: 20px; border-radius: 8px; text-align: center;">
|
||||
<div style="color: #666; font-size: 0.9em; margin-bottom: 10px;">Baseline PSNR</div>
|
||||
<div style="font-size: 2.5em; font-weight: bold; color: #ff6b6b;">
|
||||
{worst_checkpoint.get('baselinePSNR', 0):.2f}
|
||||
</div>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 5px;">dB (cận dưới)</div>
|
||||
<div style="color: #666; font-size: 0.85em; margin-top: 10px;">
|
||||
Cloudy vs Clean
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: rgba(255,255,255,0.7); border-radius: 5px; font-size: 0.95em;">
|
||||
<strong>💡 Baseline Comparison:</strong><br>
|
||||
{baseline_comparison_msg}<br>
|
||||
• <strong>Range {'nhỏ' if (best_checkpoint.get('modelPSNR', 0) - worst_checkpoint.get('modelPSNR', 0)) < 2 else 'lớn'}</strong> ({(best_checkpoint.get('modelPSNR', 0) - worst_checkpoint.get('modelPSNR', 0)):.2f} dB): Training {'ổn định' if (best_checkpoint.get('modelPSNR', 0) - worst_checkpoint.get('modelPSNR', 0)) < 2 else 'dao động'}<br>
|
||||
• <strong>Stability {'>=' if max(0, (1 - (best_checkpoint.get('modelPSNR', 0) - worst_checkpoint.get('modelPSNR', 0)) / ((best_checkpoint.get('modelPSNR', 0) + worst_checkpoint.get('modelPSNR', 0)) / 2)) * 100) >= 80 else '<'} 80%</strong>: Training {'rất ổn định ✅' if max(0, (1 - (best_checkpoint.get('modelPSNR', 0) - worst_checkpoint.get('modelPSNR', 0)) / ((best_checkpoint.get('modelPSNR', 0) + worst_checkpoint.get('modelPSNR', 0)) / 2)) * 100) >= 80 else 'cần cải thiện hyperparameters ⚠️'}
|
||||
</div>
|
||||
</div>
|
||||
</div>''' if best_checkpoint and worst_checkpoint and 'modelPSNR' in best_checkpoint and 'modelPSNR' in worst_checkpoint else ''}
|
||||
|
||||
<!-- PSNR Analysis (if available) -->
|
||||
{f'''<div class="section">
|
||||
<h2>🎯 PSNR Baseline Analysis</h2>
|
||||
<div class="info-box" style="background: {'#d4edda' if improvement > 0 else '#fff3cd'}; border-left-color: {'#28a745' if improvement > 0 else '#ffc107'};">
|
||||
<div class="info-row">
|
||||
<span class="info-label">📈 Model PSNR:</span>
|
||||
<span class="info-value" style="font-weight: bold; font-size: 1.1em;">{val_psnr:.2f} dB</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">📉 Baseline PSNR (cận dưới):</span>
|
||||
<span class="info-value">{baseline_psnr:.2f} dB</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">⚡ Improvement:</span>
|
||||
<span class="info-value" style="color: {'green' if improvement > 0 else 'orange'}; font-weight: bold;">{'+' if improvement > 0 else ''}{improvement:.2f} dB ({'+' if improvement > 0 else ''}{(improvement/baseline_psnr*100):.1f}%)</span>
|
||||
</div>
|
||||
<div class="info-row" style="margin-top: 15px; padding-top: 15px; border-top: 1px solid #ddd;">
|
||||
<span class="info-label">📊 Đánh giá:</span>
|
||||
<span class="info-value" style="font-weight: bold;">{'✅ Model TỐT HƠN baseline - Kết quả đáng tin cậy!' if improvement > 0 else '⚠️ Model chưa vượt qua baseline - Cần điều chỉnh!'}</span>
|
||||
</div>
|
||||
<div style="margin-top: 15px; padding: 10px; background: rgba(0,0,0,0.05); border-radius: 5px; font-size: 0.9em;">
|
||||
<strong>Giải thích:</strong><br>
|
||||
- <strong>Baseline PSNR</strong>: PSNR giữa ảnh cloudy và ảnh clean (không làm gì)<br>
|
||||
- <strong>Model PSNR</strong>: PSNR giữa output model và ảnh clean<br>
|
||||
- <strong>Improvement > 0</strong>: Model đang khử mây hiệu quả!<br>
|
||||
- <strong>Improvement < 0</strong>: Model làm tồi hơn không làm gì!
|
||||
</div>
|
||||
</div>
|
||||
</div>''' if val_psnr is not None and baseline_psnr is not None else ''}
|
||||
|
||||
<!-- Configuration Info -->
|
||||
<div class="section">
|
||||
<h2>⚙️ Cấu Hình Training</h2>
|
||||
@@ -453,7 +889,99 @@ def generate_training_report(training_result, config=None):
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">💾 Model Path:</span>
|
||||
<span class="info-value">{model_path}</span>
|
||||
<span class="info-value" style="font-family: monospace; font-size: 0.9em;">{model_path}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">📊 Data Source:</span>
|
||||
<span class="info-value">{data_source}</span>
|
||||
</div>
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">🛰️ Collections:</span>
|
||||
<span class="info-value">{', '.join(collections)}</span>
|
||||
</div>''' if collections else ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hyperparameters Detail -->
|
||||
<div class="section">
|
||||
<h2>🎯 Hyperparameters - Chi Tiết Tái Hiện</h2>
|
||||
<div class="info-box" style="background: #fff3cd; border-left-color: #ffc107;">
|
||||
<div style="margin-bottom: 15px; padding: 10px; background: rgba(0,0,0,0.05); border-radius: 5px;">
|
||||
<strong>🔄 Để tái hiện kết quả training này, sử dụng các tham số dưới đây:</strong>
|
||||
</div>
|
||||
|
||||
<h3 style="color: #ffc107; margin-top: 20px; margin-bottom: 10px;">📊 Model Hyperparameters</h3>
|
||||
<div class="info-row">
|
||||
<span class="info-label">Model Type:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{model_type}</span>
|
||||
</div>
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">n_estimators:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{n_estimators}</span>
|
||||
</div>''' if n_estimators != 'N/A' else ''}
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">max_depth:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{max_depth}</span>
|
||||
</div>''' if max_depth != 'N/A' else ''}
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">learning_rate:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{learning_rate}</span>
|
||||
</div>''' if learning_rate != 'N/A' else ''}
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">batch_size:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{batch_size}</span>
|
||||
</div>''' if batch_size != 'N/A' else ''}
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">num_epochs:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{num_epochs}</span>
|
||||
</div>''' if num_epochs != 'N/A' else ''}
|
||||
<div class="info-row">
|
||||
<span class="info-label">use_gpu:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{use_gpu}</span>
|
||||
</div>
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">use_s1 (Sentinel-1):</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{use_s1}</span>
|
||||
</div>''' if model_type in ['cloud_removal', 'unet'] else ''}
|
||||
|
||||
<h3 style="color: #ffc107; margin-top: 20px; margin-bottom: 10px;">📦 Data Processing</h3>
|
||||
<div class="info-row">
|
||||
<span class="info-label">test_size:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{test_size}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">feature_mode:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{feature_mode}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">n_features:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{n_features}</span>
|
||||
</div>
|
||||
{f'''<div class="info-row">
|
||||
<span class="info-label">features:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px; font-size: 0.85em;">{', '.join(features)}</span>
|
||||
</div>''' if features else ''}
|
||||
|
||||
<h3 style="color: #ffc107; margin-top: 20px; margin-bottom: 10px;">🌍 Geo & Time Parameters</h3>
|
||||
<div class="info-row">
|
||||
<span class="info-label">bbox:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px; font-size: 0.85em;">{bbox}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">time_range:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{time_range}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">resolution:</span>
|
||||
<span class="info-value" style="font-family: monospace; background: #f8f9fa; padding: 2px 8px; border-radius: 3px;">{resolution}m</span>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 20px; padding: 15px; background: #e3f2fd; border-radius: 5px; border-left: 4px solid #2196f3;">
|
||||
<strong>📝 Lưu ý:</strong><br>
|
||||
• Lưu toàn bộ các tham số trên để reproduce kết quả<br>
|
||||
• Sử dụng cùng dataset và time range để đảm bảo tính nhất quán<br>
|
||||
• Random seed: 42 (mặc định)<br>
|
||||
• Generated: {datetime.now().strftime("%d/%m/%Y %H:%M:%S")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user