Camais03's picture
Upload 130 files
53766b0 verified
raw
history blame
35.2 kB
<!DOCTYPE html>
<html>
<head>
<title>Camie-Tagger-V2 Training Monitor</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.0/chart.min.js"></script>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
margin: 0;
padding: 16px;
background-color: #f5f5f5;
color: #333;
}
.tabs {
display: flex;
margin-bottom: 16px;
background: white;
border-radius: 8px;
padding: 4px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.tab {
flex: 1;
padding: 12px 16px;
text-align: center;
cursor: pointer;
border-radius: 4px;
font-weight: 500;
transition: all 0.2s;
}
.tab:hover {
background: #f3f4f6;
}
.tab.active {
background: #3b82f6;
color: white;
}
.tab-content {
display: none;
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(500px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 16px;
}
.metric-card {
background: white;
padding: 16px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
text-align: center;
}
.metric-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.metric-value {
font-size: 24px;
font-weight: bold;
color: #333;
}
.card {
background: white;
padding: 16px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.chart-container {
position: relative;
width: 100%;
height: 350px;
}
h1 {
text-align: center;
margin-bottom: 32px;
color: #333;
}
h3 {
margin-top: 0;
color: #333;
}
/* Prediction styles */
.grid {
display: grid;
gap: 16px;
}
.flex {
display: flex;
}
.justify-between {
justify-content: space-between;
}
.items-center {
align-items: center;
}
.mb-4 {
margin-bottom: 16px;
}
.px-3 {
padding-left: 12px;
padding-right: 12px;
}
.py-1 {
padding-top: 4px;
padding-bottom: 4px;
}
.bg-blue-500 {
background-color: #3b82f6;
}
.text-white {
color: white;
}
.rounded {
border-radius: 6px;
}
.text-sm {
font-size: 14px;
}
.font-medium {
font-weight: 500;
}
.h-96 {
height: 384px;
}
.bg-gray-100 {
background-color: #f3f4f6;
}
.rounded-lg {
border-radius: 8px;
}
.max-h-full {
max-height: 100%;
}
.object-contain {
object-fit: contain;
}
.overflow-auto {
overflow: auto;
}
button {
border: none;
cursor: pointer;
transition: all 0.2s;
}
button:hover {
opacity: 0.8;
}
.filter-btn {
padding: 6px 12px;
margin: 0 4px;
border-radius: 4px;
font-size: 12px;
background: #e5e7eb;
border: none;
cursor: pointer;
}
.filter-btn.active {
background: #3b82f6;
color: white;
}
.category-section {
background: white;
padding: 16px;
border-radius: 8px;
margin-bottom: 16px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.category-title {
font-size: 16px;
font-weight: 600;
color: #1f2937;
background: #f3f4f6;
padding: 8px 12px;
border-radius: 4px;
margin-bottom: 12px;
}
.tag-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.tag-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
border-radius: 6px;
background: #f9fafb;
border: 1px solid #e5e7eb;
}
.tag-item.correct {
background-color: #ecfdf5;
border-color: #6ee7b7;
color: #065f46;
}
.tag-item.incorrect {
background-color: #fff7ed;
border-color: #fdba74;
color: #9a3412;
}
.tag-item.missing {
background-color: #fef2f2;
border-color: #fca5a5;
color: #991b1b;
}
.tag-confidence {
display: flex;
align-items: center;
gap: 8px;
}
.confidence-bar {
width: 100px;
height: 6px;
background: #e5e7eb;
border-radius: 3px;
overflow: hidden;
}
.confidence-fill {
height: 100%;
transition: width 0.3s ease;
}
.selected-tag-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
margin-bottom: 8px;
border-radius: 6px;
background: #f9fafb;
border: 1px solid #e5e7eb;
}
.selected-tag-item.ground-truth {
background-color: #ecfdf5;
border-color: #6ee7b7;
}
.selected-tag-item.non-ground-truth {
background-color: #fff7ed;
border-color: #fdba74;
}
.ground-truth .confidence-fill {
background: #059669;
}
.non-ground-truth .confidence-fill {
background: #f97316;
}
.tag-name {
font-weight: 500;
}
.tag-category {
font-size: 12px;
color: #6b7280;
}
.confidence-value {
font-size: 12px;
color: #6b7280;
}
</style>
</head>
<body>
<h1>ImageTagger Training Monitor</h1>
<div class="tabs">
<div class="tab active" onclick="showTab('overview')">Overview</div>
<div class="tab" onclick="showTab('predictions')">Predictions</div>
<div class="tab" onclick="showTab('selection')">Selection Analysis</div>
</div>
<div class="card">
<h3>Training Progress</h3>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">Current Epoch</div>
<div class="metric-value" id="current-epoch">0/0</div>
</div>
<div class="metric-card">
<div class="metric-label">Batch Progress</div>
<div class="metric-value" id="current-batch">0/0</div>
</div>
<div class="metric-card">
<div class="metric-label">Speed (it/s)</div>
<div class="metric-value" id="iter-speed">0.00</div>
</div>
<div class="metric-card">
<div class="metric-label">Time Left</div>
<div class="metric-value" id="time-remaining">--:--:--</div>
</div>
<div class="metric-card">
<div class="metric-label">Time Elapsed</div>
<div class="metric-value" id="time-elapsed">00:00:00</div>
</div>
<div class="metric-card">
<div class="metric-label">Loss</div>
<div class="metric-value" id="current-loss">0.00000</div>
</div>
<div class="metric-card">
<div class="metric-label">Micro F1</div>
<div class="metric-value" id="current-micro-f1">0.00%</div>
</div>
<div class="metric-card">
<div class="metric-label">Precision</div>
<div class="metric-value" id="current-precision">0.00%</div>
</div>
<div class="metric-card">
<div class="metric-label">Recall</div>
<div class="metric-value" id="current-recall">0.00%</div>
</div>
</div>
</div>
<div id="overview" class="tab-content">
<div class="dashboard-grid">
<div class="card">
<h3>Loss</h3>
<div class="chart-container">
<canvas id="loss-chart"></canvas>
</div>
</div>
<div class="card">
<h3>F1 Scores</h3>
<div class="chart-container">
<canvas id="f1-chart"></canvas>
</div>
</div>
</div>
</div>
<!-- Predictions Section -->
<div id="predictions" class="tab-content">
<div class="grid" style="grid-template-columns: 1fr 1fr;">
<!-- Prediction Controls -->
<div class="card">
<div class="flex justify-between items-center mb-4">
<h3>Predictions</h3>
</div>
<!-- Navigation Controls -->
<div class="flex justify-between items-center mb-4">
<button class="px-3 py-1 bg-blue-500 text-white rounded text-sm" onclick="previousPrediction()">Previous</button>
<span id="prediction-counter" class="text-sm font-medium">Sample 0 / 0</span>
<button class="px-3 py-1 bg-blue-500 text-white rounded text-sm" onclick="nextPrediction()">Next</button>
</div>
<!-- Image Display -->
<div class="h-96 flex items-center justify-center bg-gray-100 rounded-lg mb-4">
<img id="current-image" src="" alt="Current sample" class="max-h-full object-contain" />
</div>
</div>
<!-- Ground Truth and Predictions -->
<div class="card">
<div class="flex justify-between items-center mb-4">
<h3>Tag Analysis</h3>
<div class="tag-filters">
<button class="filter-btn active" onclick="filterTags('all')">All</button>
<button class="filter-btn" onclick="filterTags('correct')">Correct</button>
<button class="filter-btn" onclick="filterTags('incorrect')">Incorrect</button>
<button class="filter-btn" onclick="filterTags('missing')">Missing</button>
</div>
</div>
<div id="category-predictions" class="overflow-auto" style="max-height: 600px;">
<!-- Predictions will be inserted here -->
</div>
</div>
</div>
</div>
<!-- Selection Analysis tab content -->
<div id="selection" class="tab-content">
<div class="card mb-4">
<h3>Selection Analysis Metrics</h3>
<div class="metrics-grid">
<div class="metric-card">
<div class="metric-label">Total Ground Truth Tags</div>
<div class="metric-value" id="total-gt-tags">0</div>
</div>
<div class="metric-card">
<div class="metric-label">Selected Ground Truth Tags</div>
<div class="metric-value" id="selected-gt-tags">0</div>
</div>
<div class="metric-card">
<div class="metric-label">Ground Truth Recall</div>
<div class="metric-value" id="gt-recall">0.0000</div>
</div>
<div class="metric-card">
<div class="metric-label">Avg Prob (GT)</div>
<div class="metric-value" id="avg-prob-gt">0.0000</div>
</div>
<div class="metric-card">
<div class="metric-label">Avg Prob (Non-GT)</div>
<div class="metric-value" id="avg-prob-non-gt">0.0000</div>
</div>
<div class="metric-card">
<div class="metric-label">Unique Tags Selected</div>
<div class="metric-value" id="unique-tags-selected">0</div>
</div>
</div>
</div>
<div class="card">
<h3>Selection Analysis Graph</h3>
<div class="chart-container">
<canvas id="selection-chart"></canvas>
</div>
</div>
<div class="card">
<h3>Selected Tags Details</h3>
<div class="tag-filters mb-4">
<button class="filter-btn active" onclick="filterSelectedTags('all')">All Selected</button>
<button class="filter-btn" onclick="filterSelectedTags('ground-truth')">Ground Truth</button>
<button class="filter-btn" onclick="filterSelectedTags('non-ground-truth')">Non-Ground Truth</button>
</div>
<div id="selected-tags-list" class="overflow-auto" style="max-height: 500px;">
<!-- Selected tags will be inserted here -->
</div>
</div>
</div>
<script>
const socket = io();
const charts = {};
const CHART_WINDOW_SIZE = 200;
// Prediction history management
let predictionHistory = [];
let currentPredictionIndex = 0;
let currentFilter = 'all';
let currentSelectedFilter = 'all';
// Initialize charts
document.addEventListener('DOMContentLoaded', function() {
initializeCharts();
showTab('overview');
});
function showTab(tabName) {
// Hide all tabs
document.querySelectorAll('.tab-content').forEach(tab => {
tab.style.display = 'none';
});
// Show selected tab
const selectedTab = document.getElementById(tabName);
if (selectedTab) {
selectedTab.style.display = 'block';
}
// Update active state on tab buttons
document.querySelectorAll('.tab').forEach(tab => {
tab.classList.remove('active');
if (tab.textContent.toLowerCase().includes(tabName)) {
tab.classList.add('active');
}
});
}
function initializeCharts() {
const tooltipCallback = (context) => {
let label = context.dataset.label || '';
let value = context.parsed.y;
if (value !== null) {
if (label.includes('F1') || label.includes('Precision')) {
value = value.toFixed(2) + '%';
} else {
value = value.toFixed(6);
}
}
return `${label}: ${value}`;
};
const chartOptions = {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: false,
suggestedMin: -0.2,
suggestedMax: 1.2
}
},
plugins: {
legend: {
position: 'top',
},
tooltip: {
callbacks: {
label: tooltipCallback
}
}
}
};
// Loss chart
const lossCtx = document.getElementById('loss-chart');
if (lossCtx) {
charts.loss = new Chart(lossCtx.getContext('2d'), {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Training Loss',
data: [],
borderColor: '#ff6384',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.1,
fill: true
}, {
label: 'Validation Loss',
data: [],
borderColor: '#36a2eb',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: 0.1,
borderDash: [5, 5]
}]
},
options: {
...chartOptions,
scales: {
y: {
beginAtZero: true
}
}
}
});
}
// F1 chart
const f1Ctx = document.getElementById('f1-chart');
if (f1Ctx) {
charts.f1 = new Chart(f1Ctx.getContext('2d'), {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Train Micro F1',
data: [],
borderColor: '#36a2eb',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
tension: 0.1,
fill: true
}, {
label: 'Val Micro F1',
data: [],
borderColor: '#ff6384',
backgroundColor: 'rgba(255, 99, 132, 0.1)',
tension: 0.1,
borderDash: [5, 5]
}, {
label: 'Val Macro F1',
data: [],
borderColor: '#ffcd56',
backgroundColor: 'rgba(255, 205, 86, 0.1)',
tension: 0.1,
borderDash: [5, 5]
}]
},
options: {
...chartOptions,
scales: {
y: {
beginAtZero: true,
max: 100
}
}
}
});
}
// Selection chart
const selectionCtx = document.getElementById('selection-chart');
if (selectionCtx) {
charts.selection = new Chart(selectionCtx.getContext('2d'), {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Ground Truth Recall',
data: [],
borderColor: '#4bc0c0',
tension: 0.1
}, {
label: 'Avg Prob (GT)',
data: [],
borderColor: '#36a2eb',
tension: 0.1
}, {
label: 'Avg Prob (Non-GT)',
data: [],
borderColor: '#ff6384',
tension: 0.1
}, {
label: 'GT/Non-GT Prob Difference',
data: [],
borderColor: '#9966ff',
tension: 0.1,
borderDash: [5, 5]
}]
},
options: chartOptions
});
}
}
function formatTime(seconds) {
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
const s = Math.floor(seconds % 60);
return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`;
}
function filterTags(filter) {
currentFilter = filter;
document.querySelectorAll('.tag-filters .filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.textContent.toLowerCase().includes(filter));
});
if (currentPredictionIndex < predictionHistory.length) {
updatePredictionDisplay(predictionHistory[currentPredictionIndex]);
}
}
function filterSelectedTags(filter) {
currentSelectedFilter = filter;
document.querySelectorAll('.tag-filters .filter-btn').forEach(btn => {
btn.classList.toggle('active', btn.textContent.toLowerCase().includes(filter.replace('-', ' ')));
});
if (currentPredictionIndex < predictionHistory.length) {
updateSelectedTagsList(predictionHistory[currentPredictionIndex]);
}
}
function updatePredictionDisplay(predictionData) {
if (!predictionData) return;
// Update image
const imageElement = document.getElementById('current-image');
if (imageElement && predictionData.image) {
imageElement.src = predictionData.image.startsWith('data:') ?
predictionData.image : `data:image/jpeg;base64,${predictionData.image}`;
}
// Update counter
const counterElement = document.getElementById('prediction-counter');
if (counterElement) {
counterElement.textContent = `Sample ${currentPredictionIndex + 1} / ${predictionHistory.length}`;
}
// Update predictions
updateCategoryPredictions(predictionData);
updateSelectedTagsList(predictionData);
}
function updateCategoryPredictions(predictionData) {
const container = document.getElementById('category-predictions');
if (!container || !predictionData || !predictionData.category_predictions) return;
let html = '';
Object.entries(predictionData.category_predictions).forEach(([category, predictions]) => {
let tagsToShow = [];
if (currentFilter === 'all') {
tagsToShow = [
...predictions.correct.map(t => ({...t, type: 'correct'})),
...predictions.incorrect.map(t => ({...t, type: 'incorrect'})),
...predictions.missing.map(t => ({...t, type: 'missing'}))
];
} else {
tagsToShow = predictions[currentFilter].map(t => ({...t, type: currentFilter}));
}
if (tagsToShow.length > 0) {
html += `
<div class="category-section">
<div class="category-title">${category}</div>
<div class="tag-list">
${tagsToShow.map(tag => `
<div class="tag-item ${tag.type}">
<span class="tag-name">${tag.tag}</span>
<div class="tag-confidence">
<div class="confidence-bar">
<div class="confidence-fill"
style="width: ${tag.probability * 100}%;
background: ${tag.type === 'correct' ? '#059669' :
tag.type === 'incorrect' ? '#f97316' : '#ef4444'}">
</div>
</div>
<span class="confidence-value">${(tag.probability * 100).toFixed(1)}%</span>
</div>
</div>
`).join('')}
</div>
</div>
`;
}
});
container.innerHTML = html;
}
function updateSelectedTagsList(predictionData) {
const container = document.getElementById('selected-tags-list');
if (!container || !predictionData || !predictionData.tag_info) return;
let tagsToShow = predictionData.tag_info;
if (currentSelectedFilter === 'ground-truth') {
tagsToShow = tagsToShow.filter(tag => tag.is_ground_truth);
} else if (currentSelectedFilter === 'non-ground-truth') {
tagsToShow = tagsToShow.filter(tag => !tag.is_ground_truth);
}
// Sort by probability
tagsToShow.sort((a, b) => b.probability - a.probability);
const html = tagsToShow.map(tag => `
<div class="selected-tag-item ${tag.is_ground_truth ? 'ground-truth' : 'non-ground-truth'}">
<div class="tag-info">
<span class="tag-name">${tag.tag_name}</span>
<span class="tag-category text-sm text-gray-500">${tag.category}</span>
</div>
<div class="tag-confidence">
<div class="confidence-bar">
<div class="confidence-fill"
style="width: ${tag.probability * 100}%;
background: ${tag.is_ground_truth ? '#059669' : '#f97316'}">
</div>
</div>
<span class="confidence-value">${(tag.probability * 100).toFixed(1)}%</span>
</div>
</div>
`).join('');
container.innerHTML = html;
}
function previousPrediction() {
if (currentPredictionIndex > 0) {
currentPredictionIndex--;
updatePredictionDisplay(predictionHistory[currentPredictionIndex]);
}
}
function nextPrediction() {
if (currentPredictionIndex < predictionHistory.length - 1) {
currentPredictionIndex++;
updatePredictionDisplay(predictionHistory[currentPredictionIndex]);
}
}
socket.on('training_update', (data) => {
const update = JSON.parse(data);
const progress = update.progress;
// Update progress displays
document.getElementById('current-epoch').textContent =
`${progress.epoch}/${progress.total_epochs}`;
document.getElementById('current-batch').textContent =
`${progress.batch}/${progress.total_batches}`;
document.getElementById('iter-speed').textContent =
`${progress.iter_speed.toFixed(2)}`;
document.getElementById('time-remaining').textContent =
formatTime(progress.time_remaining);
document.getElementById('time-elapsed').textContent =
formatTime(progress.elapsed_time);
// Update current metrics
if (progress.current_metrics) {
document.getElementById('current-loss').textContent =
progress.current_metrics.loss.toFixed(6);
document.getElementById('current-micro-f1').textContent =
progress.current_metrics.micro_f1.toFixed(2) + '%';
document.getElementById('current-val-loss').textContent =
progress.current_metrics.val_loss.toFixed(6);
document.getElementById('current-precision').textContent =
progress.current_metrics.precision.toFixed(2) + '%';
document.getElementById('current-recall').textContent =
progress.current_metrics.recall.toFixed(2) + '%';
}
});
socket.on('metrics_update', (data) => {
try {
const update = JSON.parse(data);
const timestamp = new Date().toLocaleTimeString();
if (update.metrics) {
const train = update.metrics.train;
const val = update.metrics.val;
// Update loss chart
if (charts.loss && train) {
charts.loss.data.labels.push(timestamp);
charts.loss.data.datasets[0].data.push(train.loss ?? null);
// Always push something for val to keep arrays aligned
charts.loss.data.datasets[1].data.push((val && val.loss !== null) ? val.loss : null);
if (charts.loss.data.labels.length > CHART_WINDOW_SIZE) {
charts.loss.data.labels.shift();
charts.loss.data.datasets.forEach(d => d.data.shift());
}
charts.loss.update();
}
// Update F1 chart
if (charts.f1) {
charts.f1.data.labels.push(timestamp);
// Add training metrics
if (train) {
charts.f1.data.datasets[0].data.push(train.micro_f1 || 0);
}
// Add validation metrics
if (val) {
charts.f1.data.datasets[1].data.push(val.micro_f1 || null);
charts.f1.data.datasets[2].data.push(val.macro_f1 || null);
}
if (charts.f1.data.labels.length > CHART_WINDOW_SIZE) {
charts.f1.data.labels.shift();
charts.f1.data.datasets.forEach(dataset => dataset.data.shift());
}
charts.f1.update();
}
}
// Handle predictions
if (update.predictions) {
predictionHistory.push(update.predictions);
currentPredictionIndex = predictionHistory.length - 1;
updatePredictionDisplay(update.predictions);
// Handle selection analysis
if (update.predictions.tag_selection) {
const tagSelection = update.predictions.tag_selection;
const tagInfo = update.predictions.tag_info || [];
// Count ground truth matches
const groundTruthTags = tagInfo.filter(tag => tag.is_ground_truth);
const selectedGroundTruthTags = groundTruthTags.length;
// Calculate averages
const gtProbs = groundTruthTags.map(tag => tag.probability);
const nonGtProbs = tagInfo.filter(tag => !tag.is_ground_truth).map(tag => tag.probability);
const avgGtProb = gtProbs.length > 0 ? gtProbs.reduce((a, b) => a + b, 0) / gtProbs.length : 0;
const avgNonGtProb = nonGtProbs.length > 0 ? nonGtProbs.reduce((a, b) => a + b, 0) / nonGtProbs.length : 0;
const probDifference = avgGtProb - avgNonGtProb;
// Calculate recall
const recall = tagSelection.total_ground_truth > 0 ?
selectedGroundTruthTags / tagSelection.total_ground_truth : 0;
// Update metrics
document.getElementById('total-gt-tags').textContent = tagSelection.total_ground_truth || 0;
document.getElementById('selected-gt-tags').textContent = selectedGroundTruthTags;
document.getElementById('gt-recall').textContent = recall.toFixed(4);
document.getElementById('avg-prob-gt').textContent = avgGtProb.toFixed(4);
document.getElementById('avg-prob-non-gt').textContent = avgNonGtProb.toFixed(4);
document.getElementById('unique-tags-selected').textContent = tagInfo.length;
// Update selection chart
if (charts.selection) {
charts.selection.data.labels.push(timestamp);
charts.selection.data.datasets[0].data.push(recall);
charts.selection.data.datasets[1].data.push(avgGtProb);
charts.selection.data.datasets[2].data.push(avgNonGtProb);
charts.selection.data.datasets[3].data.push(probDifference);
if (charts.selection.data.labels.length > CHART_WINDOW_SIZE) {
charts.selection.data.labels.shift();
charts.selection.data.datasets.forEach(dataset => dataset.data.shift());
}
charts.selection.update('none');
}
}
}
} catch (error) {
console.error('Error in metrics update:', error);
}
});
</script>
</body>
</html>