Mercurial > repos > goeckslab > multimodal_learner
comparison test-data/sample_output.html @ 0:375c36923da1 draft default tip
planemo upload for repository https://github.com/goeckslab/gleam.git commit 1c6c1ad7a1b2bd3645aa0eafa2167784820b52e0
| author | goeckslab |
|---|---|
| date | Tue, 09 Dec 2025 23:49:47 +0000 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:375c36923da1 |
|---|---|
| 1 <!DOCTYPE html> | |
| 2 <html> | |
| 3 <head> | |
| 4 <meta charset="UTF-8"> | |
| 5 <title>Galaxy-Ludwig Report</title> | |
| 6 <style> | |
| 7 body { | |
| 8 font-family: Arial, sans-serif; | |
| 9 margin: 0; | |
| 10 padding: 20px; | |
| 11 background-color: #f4f4f4; | |
| 12 } | |
| 13 .container { | |
| 14 max-width: 1200px; | |
| 15 margin: auto; | |
| 16 background: white; | |
| 17 padding: 20px; | |
| 18 box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | |
| 19 overflow-x: auto; | |
| 20 } | |
| 21 h1 { | |
| 22 text-align: center; | |
| 23 color: #333; | |
| 24 } | |
| 25 h2 { | |
| 26 border-bottom: 2px solid #4CAF50; | |
| 27 color: #4CAF50; | |
| 28 padding-bottom: 5px; | |
| 29 margin-top: 28px; | |
| 30 } | |
| 31 h3 { | |
| 32 margin-top: 20px; | |
| 33 color: #333; | |
| 34 } | |
| 35 .section { | |
| 36 margin-bottom: 30px; | |
| 37 } | |
| 38 .section-title { | |
| 39 border-bottom: 2px solid #4CAF50; | |
| 40 color: #4CAF50; | |
| 41 padding-bottom: 5px; | |
| 42 margin-top: 28px; | |
| 43 } | |
| 44 .card { | |
| 45 background: #fff; | |
| 46 padding: 15px; | |
| 47 border: 1px solid #ddd; | |
| 48 border-radius: 4px; | |
| 49 margin: 10px 0; | |
| 50 } | |
| 51 | |
| 52 /* baseline table setup */ | |
| 53 table { | |
| 54 border-collapse: collapse; | |
| 55 margin: 20px 0; | |
| 56 width: 100%; | |
| 57 table-layout: fixed; | |
| 58 background: #fff; | |
| 59 } | |
| 60 table, th, td { | |
| 61 border: 1px solid #ddd; | |
| 62 } | |
| 63 th, td { | |
| 64 padding: 10px; | |
| 65 text-align: center; | |
| 66 vertical-align: middle; | |
| 67 word-break: break-word; | |
| 68 white-space: normal; | |
| 69 overflow-wrap: anywhere; | |
| 70 } | |
| 71 th { | |
| 72 background-color: #4CAF50; | |
| 73 color: white; | |
| 74 } | |
| 75 .metric-table { | |
| 76 width: 100%; | |
| 77 } | |
| 78 .kv-table { | |
| 79 width: 100%; | |
| 80 } | |
| 81 .kv-table th:first-child, | |
| 82 .kv-table td:first-child { | |
| 83 text-align: left; | |
| 84 width: 30%; | |
| 85 } | |
| 86 | |
| 87 .plot { | |
| 88 text-align: center; | |
| 89 margin: 20px 0; | |
| 90 } | |
| 91 .plot img { | |
| 92 max-width: 100%; | |
| 93 height: auto; | |
| 94 border: 1px solid #ddd; | |
| 95 } | |
| 96 | |
| 97 /* ------------------- | |
| 98 sortable columns (3-state: none ⇅, asc ↑, desc ↓) | |
| 99 ------------------- */ | |
| 100 table.performance-summary th.sortable { | |
| 101 cursor: pointer; | |
| 102 position: relative; | |
| 103 user-select: none; | |
| 104 } | |
| 105 /* default icon space */ | |
| 106 table.performance-summary th.sortable::after { | |
| 107 content: '⇅'; | |
| 108 position: absolute; | |
| 109 right: 12px; | |
| 110 top: 50%; | |
| 111 transform: translateY(-50%); | |
| 112 font-size: 0.8em; | |
| 113 color: #eaf5ea; /* light on green */ | |
| 114 text-shadow: 0 0 1px rgba(0,0,0,0.15); | |
| 115 } | |
| 116 /* three states override the default */ | |
| 117 table.performance-summary th.sortable.sorted-none::after { content: '⇅'; color: #eaf5ea; } | |
| 118 table.performance-summary th.sortable.sorted-asc::after { content: '↑'; color: #ffffff; } | |
| 119 table.performance-summary th.sortable.sorted-desc::after { content: '↓'; color: #ffffff; } | |
| 120 | |
| 121 /* show ~30 rows with a scrollbar (tweak if you want) */ | |
| 122 .scroll-rows-30 { | |
| 123 max-height: 900px; /* ~30 rows depending on row height */ | |
| 124 overflow-y: auto; /* vertical scrollbar ("sidebar") */ | |
| 125 overflow-x: auto; | |
| 126 } | |
| 127 | |
| 128 /* Tabs + Help button (used by build_tabbed_html) */ | |
| 129 .tabs { | |
| 130 display: flex; | |
| 131 align-items: center; | |
| 132 border-bottom: 2px solid #ccc; | |
| 133 margin-bottom: 1rem; | |
| 134 gap: 6px; | |
| 135 flex-wrap: wrap; | |
| 136 } | |
| 137 .tab { | |
| 138 padding: 10px 20px; | |
| 139 cursor: pointer; | |
| 140 border: 1px solid #ccc; | |
| 141 border-bottom: none; | |
| 142 background: #f9f9f9; | |
| 143 margin-right: 5px; | |
| 144 border-top-left-radius: 8px; | |
| 145 border-top-right-radius: 8px; | |
| 146 } | |
| 147 .tab.active { | |
| 148 background: white; | |
| 149 font-weight: bold; | |
| 150 } | |
| 151 .help-btn { | |
| 152 margin-left: auto; | |
| 153 padding: 6px 12px; | |
| 154 font-size: 0.9rem; | |
| 155 border: 1px solid #4CAF50; | |
| 156 border-radius: 4px; | |
| 157 background: #4CAF50; | |
| 158 color: white; | |
| 159 cursor: pointer; | |
| 160 } | |
| 161 .tab-content { | |
| 162 display: none; | |
| 163 padding: 20px; | |
| 164 border: 1px solid #ccc; | |
| 165 border-top: none; | |
| 166 background: #fff; | |
| 167 } | |
| 168 .tab-content.active { | |
| 169 display: block; | |
| 170 } | |
| 171 .plotly-center { | |
| 172 display: flex; | |
| 173 justify-content: center; | |
| 174 } | |
| 175 .plotly-center .plotly-graph-div, .plotly-center .js-plotly-plot { | |
| 176 margin: 0 auto !important; | |
| 177 } | |
| 178 .js-plotly-plot, .plotly-graph-div { | |
| 179 margin-left: auto !important; | |
| 180 margin-right: auto !important; | |
| 181 } | |
| 182 | |
| 183 /* Modal (used by get_metrics_help_modal) */ | |
| 184 .modal { | |
| 185 display: none; | |
| 186 position: fixed; | |
| 187 z-index: 9999; | |
| 188 left: 0; top: 0; | |
| 189 width: 100%; height: 100%; | |
| 190 overflow: auto; | |
| 191 background-color: rgba(0,0,0,0.4); | |
| 192 } | |
| 193 .modal-content { | |
| 194 background-color: #fefefe; | |
| 195 margin: 8% auto; | |
| 196 padding: 20px; | |
| 197 border: 1px solid #888; | |
| 198 width: 90%; | |
| 199 max-width: 900px; | |
| 200 border-radius: 8px; | |
| 201 } | |
| 202 .modal .close { | |
| 203 color: #777; | |
| 204 float: right; | |
| 205 font-size: 28px; | |
| 206 font-weight: bold; | |
| 207 line-height: 1; | |
| 208 margin-left: 8px; | |
| 209 } | |
| 210 .modal .close:hover, | |
| 211 .modal .close:focus { | |
| 212 color: black; | |
| 213 text-decoration: none; | |
| 214 cursor: pointer; | |
| 215 } | |
| 216 .metrics-guide h3 { margin-top: 20px; } | |
| 217 .metrics-guide p { margin: 6px 0; } | |
| 218 .metrics-guide ul { margin: 10px 0; padding-left: 20px; } | |
| 219 </style> | |
| 220 | |
| 221 <script> | |
| 222 // Guard to avoid double-initialization if this block is included twice | |
| 223 (function(){ | |
| 224 if (window.__perfSummarySortInit) return; | |
| 225 window.__perfSummarySortInit = true; | |
| 226 | |
| 227 function initPerfSummarySorting() { | |
| 228 // Record original order for "back to original" | |
| 229 document.querySelectorAll('table.performance-summary tbody').forEach(tbody => { | |
| 230 Array.from(tbody.rows).forEach((row, i) => { row.dataset.originalOrder = i; }); | |
| 231 }); | |
| 232 | |
| 233 const getText = td => (td?.innerText || '').trim(); | |
| 234 const cmp = (idx, asc) => (a, b) => { | |
| 235 const v1 = getText(a.children[idx]); | |
| 236 const v2 = getText(b.children[idx]); | |
| 237 const n1 = parseFloat(v1), n2 = parseFloat(v2); | |
| 238 if (!isNaN(n1) && !isNaN(n2)) return asc ? n1 - n2 : n2 - n1; // numeric | |
| 239 return asc ? v1.localeCompare(v2) : v2.localeCompare(v1); // lexical | |
| 240 }; | |
| 241 | |
| 242 document.querySelectorAll('table.performance-summary th.sortable').forEach(th => { | |
| 243 // initialize to "none" | |
| 244 th.classList.remove('sorted-asc','sorted-desc'); | |
| 245 th.classList.add('sorted-none'); | |
| 246 | |
| 247 th.addEventListener('click', () => { | |
| 248 const table = th.closest('table'); | |
| 249 const headerRow = th.parentNode; | |
| 250 const allTh = headerRow.querySelectorAll('th.sortable'); | |
| 251 const tbody = table.querySelector('tbody'); | |
| 252 | |
| 253 // Determine current state BEFORE clearing | |
| 254 const isAsc = th.classList.contains('sorted-asc'); | |
| 255 const isDesc = th.classList.contains('sorted-desc'); | |
| 256 | |
| 257 // Reset all headers in this row | |
| 258 allTh.forEach(x => x.classList.remove('sorted-asc','sorted-desc','sorted-none')); | |
| 259 | |
| 260 // Compute next state | |
| 261 let next; | |
| 262 if (!isAsc && !isDesc) { | |
| 263 next = 'asc'; | |
| 264 } else if (isAsc) { | |
| 265 next = 'desc'; | |
| 266 } else { | |
| 267 next = 'none'; | |
| 268 } | |
| 269 th.classList.add('sorted-' + next); | |
| 270 | |
| 271 // Sort rows according to the chosen state | |
| 272 const rows = Array.from(tbody.rows); | |
| 273 if (next === 'none') { | |
| 274 rows.sort((a, b) => (a.dataset.originalOrder - b.dataset.originalOrder)); | |
| 275 } else { | |
| 276 const idx = Array.from(headerRow.children).indexOf(th); | |
| 277 rows.sort(cmp(idx, next === 'asc')); | |
| 278 } | |
| 279 rows.forEach(r => tbody.appendChild(r)); | |
| 280 }); | |
| 281 }); | |
| 282 } | |
| 283 | |
| 284 // Run after DOM is ready | |
| 285 if (document.readyState === 'loading') { | |
| 286 document.addEventListener('DOMContentLoaded', initPerfSummarySorting); | |
| 287 } else { | |
| 288 initPerfSummarySorting(); | |
| 289 } | |
| 290 })(); | |
| 291 </script> | |
| 292 </head> | |
| 293 <body> | |
| 294 <div class="container"> | |
| 295 <script src="https://cdn.plot.ly/plotly-2.30.0.min.js"></script> | |
| 296 <style> | |
| 297 .plotly-center { | |
| 298 display: flex; | |
| 299 justify-content: center; | |
| 300 margin: 20px 0; | |
| 301 width: 100%; | |
| 302 } | |
| 303 .plotly-center .plotly-graph-div, .plotly-center .js-plotly-plot { | |
| 304 margin: 0 auto !important; | |
| 305 } | |
| 306 .js-plotly-plot, .plotly-graph-div { | |
| 307 margin-left: auto !important; | |
| 308 margin-right: auto !important; | |
| 309 } | |
| 310 #learning-curve-accuracy, #learning-curve-loss, #threshold-plot, | |
| 311 #confusion-matrix, #per-class-metrics, #roc-curve, #pr-curve { | |
| 312 min-height: 400px; | |
| 313 } | |
| 314 </style> | |
| 315 | |
| 316 <div class="tabs"> | |
| 317 <div class="tab active" onclick="showTab('summary')">Model Summary & Config</div> | |
| 318 <div class="tab" onclick="showTab('train')">Train/Validation Summary</div> | |
| 319 <div class="tab" onclick="showTab('test')">Test Summary</div> | |
| 320 <div class="tab" onclick="showTab('feature')">Feature Importance</div> | |
| 321 <button id="openMetricsHelp" class="help-btn">Help</button> | |
| 322 </div> | |
| 323 | |
| 324 <!-- Summary Tab --> | |
| 325 <div id="summary" class="tab-content active"> | |
| 326 <section class="section"> | |
| 327 <h2 class="section-title">Model Performance Summary</h2> | |
| 328 <div class="card"> | |
| 329 <table class="metric-table"> | |
| 330 <thead><tr><th>Metric</th><th>Train</th><th>Validation</th><th>Test</th></tr></thead> | |
| 331 <tbody> | |
| 332 <tr><td>accuracy</td><td>0.9234</td><td>0.8912</td><td>0.8856</td></tr> | |
| 333 <tr><td>f1</td><td>0.9201</td><td>0.8876</td><td>0.8823</td></tr> | |
| 334 <tr><td>precision</td><td>0.9156</td><td>0.8845</td><td>0.8798</td></tr> | |
| 335 <tr><td>recall</td><td>0.9245</td><td>0.8907</td><td>0.8849</td></tr> | |
| 336 <tr><td>roc_auc</td><td>0.9789</td><td>0.9543</td><td>0.9512</td></tr> | |
| 337 <tr><td>log_loss</td><td>0.2134</td><td>0.2876</td><td>0.3012</td></tr> | |
| 338 </tbody> | |
| 339 </table> | |
| 340 </div> | |
| 341 </section> | |
| 342 | |
| 343 <section class="section"> | |
| 344 <h2 class="section-title">Run Configuration</h2> | |
| 345 <div class="card"> | |
| 346 <table class="kv-table"> | |
| 347 <thead><tr><th>Key</th><th>Value</th></tr></thead> | |
| 348 <tbody> | |
| 349 <tr><td>Predictor type</td><td>MultiModalPredictor</td></tr> | |
| 350 <tr><td>Framework</td><td>AutoGluon Multimodal</td></tr> | |
| 351 <tr><td>Model architecture</td><td>timm_image=resnet50, hf_text=bert-base-uncased</td></tr> | |
| 352 <tr><td>Modalities & Inputs</td><td>Images + Tabular</td></tr> | |
| 353 <tr><td>Label column</td><td>target</td></tr> | |
| 354 <tr><td>Image columns</td><td>image_path</td></tr> | |
| 355 <tr><td>Tabular columns</td><td>15</td></tr> | |
| 356 <tr><td>Presets</td><td>medium_quality</td></tr> | |
| 357 <tr><td>Eval metric</td><td>accuracy</td></tr> | |
| 358 <tr><td>Decision threshold calibration</td><td>enabled</td></tr> | |
| 359 <tr><td>Decision threshold (Test only)</td><td>0.500</td></tr> | |
| 360 <tr><td>Seed</td><td>42</td></tr> | |
| 361 <tr><td>time limit(s)</td><td>3600</td></tr> | |
| 362 </tbody> | |
| 363 </table> | |
| 364 </div> | |
| 365 </section> | |
| 366 | |
| 367 <section class="section"> | |
| 368 <h2 class="section-title">Class Balance (Train Full)</h2> | |
| 369 <div class="card"> | |
| 370 <h3>Class Balance (Train Full)</h3> | |
| 371 <table class="table"> | |
| 372 <thead><tr><th>Class</th><th>Count</th><th>Percent</th></tr></thead> | |
| 373 <tbody> | |
| 374 <tr><td>0</td><td>1245</td><td>45.23%</td></tr> | |
| 375 <tr><td>1</td><td>1508</td><td>54.77%</td></tr> | |
| 376 </tbody> | |
| 377 </table> | |
| 378 </div> | |
| 379 </section> | |
| 380 </div> | |
| 381 | |
| 382 <!-- Train Tab --> | |
| 383 <div id="train" class="tab-content"> | |
| 384 <h2>Train/Validation Performance Summary</h2> | |
| 385 | |
| 386 <div class="card"> | |
| 387 <table class="metric-table"> | |
| 388 <thead><tr><th>Metric</th><th>Train</th><th>Validation</th></tr></thead> | |
| 389 <tbody> | |
| 390 <tr><td>accuracy</td><td>0.9234</td><td>0.8912</td></tr> | |
| 391 <tr><td>f1</td><td>0.9201</td><td>0.8876</td></tr> | |
| 392 <tr><td>precision</td><td>0.9156</td><td>0.8845</td></tr> | |
| 393 <tr><td>recall</td><td>0.9245</td><td>0.8907</td></tr> | |
| 394 <tr><td>roc_auc</td><td>0.9789</td><td>0.9543</td></tr> | |
| 395 <tr><td>log_loss</td><td>0.2134</td><td>0.2876</td></tr> | |
| 396 </tbody> | |
| 397 </table> | |
| 398 </div> | |
| 399 | |
| 400 <h2>Learning Curves — Label Accuracy</h2> | |
| 401 <div class="plotly-center"> | |
| 402 <div id="learning-curve-accuracy" style="width:900px;height:500px;"></div> | |
| 403 </div> | |
| 404 | |
| 405 <h2>Learning Curves — Label Loss</h2> | |
| 406 <div class="plotly-center"> | |
| 407 <div id="learning-curve-loss" style="width:900px;height:500px;"></div> | |
| 408 </div> | |
| 409 </div> | |
| 410 | |
| 411 <!-- Test Tab --> | |
| 412 <div id="test" class="tab-content"> | |
| 413 <h2>Test Performance Summary</h2> | |
| 414 <table class="performance-summary"> | |
| 415 <thead> | |
| 416 <tr> | |
| 417 <th class="sortable">Metric</th> | |
| 418 <th class="sortable">Test</th> | |
| 419 </tr> | |
| 420 </thead> | |
| 421 <tbody> | |
| 422 <tr><td>accuracy</td><td>0.8856</td></tr> | |
| 423 <tr><td>f1</td><td>0.8823</td></tr> | |
| 424 <tr><td>precision</td><td>0.8798</td></tr> | |
| 425 <tr><td>recall</td><td>0.8849</td></tr> | |
| 426 <tr><td>roc_auc</td><td>0.9512</td></tr> | |
| 427 <tr><td>log_loss</td><td>0.3012</td></tr> | |
| 428 <tr><td>specificity (TNR)</td><td>0.8765</td></tr> | |
| 429 <tr><td>sensitivity (Sensitivity/TPR)</td><td>0.8923</td></tr> | |
| 430 </tbody> | |
| 431 </table> | |
| 432 | |
| 433 <h2>Confusion Matrix</h2> | |
| 434 <div class="plotly-center"> | |
| 435 <div id="confusion-matrix" style="width:700px;height:600px;"></div> | |
| 436 </div> | |
| 437 | |
| 438 <h2>Per-Class Metrics</h2> | |
| 439 <div class="plotly-center"> | |
| 440 <div id="per-class-metrics" style="width:900px;height:500px;"></div> | |
| 441 </div> | |
| 442 | |
| 443 <h2>ROC Curve</h2> | |
| 444 <div class="plotly-center"> | |
| 445 <div id="roc-curve" style="width:800px;height:600px;"></div> | |
| 446 </div> | |
| 447 | |
| 448 <h2>Precision–Recall Curve</h2> | |
| 449 <div class="plotly-center"> | |
| 450 <div id="pr-curve" style="width:800px;height:600px;"></div> | |
| 451 </div> | |
| 452 | |
| 453 <h2>Threshold Plot</h2> | |
| 454 <div class="plotly-center"> | |
| 455 <div id="threshold-plot" style="width:900px;height:500px;"></div> | |
| 456 </div> | |
| 457 </div> | |
| 458 | |
| 459 <!-- Feature Importance Tab --> | |
| 460 <div id="feature" class="tab-content"> | |
| 461 <section class="section"> | |
| 462 <h2 class="section-title">Feature Importance</h2> | |
| 463 <div class="card"> | |
| 464 <p>Permutation importance is not supported for MultiModalPredictor in this tool. For tabular-only runs, this section shows permutation importance.</p> | |
| 465 </div> | |
| 466 </section> | |
| 467 | |
| 468 <section class="section"> | |
| 469 <h2 class="section-title">Modalities & Inputs</h2> | |
| 470 <div class="card"> | |
| 471 <h3>Modalities & Inputs</h3> | |
| 472 <p>This run used <b>MultiModalPredictor</b> (images + tabular).</p> | |
| 473 <p><b>Label column:</b> target</p> | |
| 474 <p><b>Image column:</b> image_path</p> | |
| 475 <div><b>Tabular columns:</b> 15 | |
| 476 <ul> | |
| 477 <li>feature_1</li> | |
| 478 <li>feature_2</li> | |
| 479 <li>feature_3</li> | |
| 480 <li>feature_4</li> | |
| 481 <li>feature_5</li> | |
| 482 <li>feature_6</li> | |
| 483 <li>feature_7</li> | |
| 484 <li>feature_8</li> | |
| 485 <li>feature_9</li> | |
| 486 <li>feature_10</li> | |
| 487 <li>feature_11</li> | |
| 488 <li>feature_12</li> | |
| 489 <li>feature_13</li> | |
| 490 <li>feature_14</li> | |
| 491 <li>feature_15</li> | |
| 492 </ul> | |
| 493 </div> | |
| 494 </div> | |
| 495 </section> | |
| 496 </div> | |
| 497 | |
| 498 <script> | |
| 499 function showTab(id) { | |
| 500 document.querySelectorAll('.tab-content').forEach(el => el.classList.remove('active')); | |
| 501 document.querySelectorAll('.tab').forEach(el => el.classList.remove('active')); | |
| 502 document.getElementById(id).classList.add('active'); | |
| 503 document.querySelector(`.tab[onclick*="${id}"]`).classList.add('active'); | |
| 504 } | |
| 505 | |
| 506 // Fixed random data for reproducibility | |
| 507 const epochs = Array.from({length: 31}, (_, i) => i); | |
| 508 const accuracy_train = [0.552, 0.568, 0.581, 0.595, 0.612, 0.628, 0.645, 0.662, 0.678, 0.692, 0.708, 0.722, 0.735, 0.748, 0.761, 0.773, 0.784, 0.795, 0.805, 0.814, 0.823, 0.831, 0.839, 0.846, 0.853, 0.859, 0.865, 0.870, 0.875, 0.880, 0.884]; | |
| 509 const accuracy_val = [0.752, 0.768, 0.782, 0.795, 0.807, 0.818, 0.828, 0.837, 0.845, 0.852, 0.859, 0.865, 0.871, 0.876, 0.881, 0.885, 0.889, 0.893, 0.896, 0.899, 0.902, 0.905, 0.907, 0.909, 0.911, 0.913, 0.915, 0.917, 0.918, 0.920, 0.921]; | |
| 510 const loss_train = [1.352, 1.285, 1.221, 1.158, 1.098, 1.041, 0.987, 0.936, 0.888, 0.842, 0.799, 0.758, 0.720, 0.684, 0.650, 0.618, 0.588, 0.560, 0.534, 0.510, 0.487, 0.466, 0.447, 0.429, 0.412, 0.397, 0.383, 0.370, 0.358, 0.347, 0.337]; | |
| 511 const loss_val = [0.802, 0.765, 0.730, 0.697, 0.666, 0.637, 0.610, 0.585, 0.561, 0.539, 0.518, 0.499, 0.481, 0.464, 0.448, 0.433, 0.419, 0.406, 0.394, 0.383, 0.372, 0.362, 0.353, 0.345, 0.337, 0.330, 0.323, 0.317, 0.311, 0.306, 0.301]; | |
| 512 | |
| 513 // Wait for Plotly to load before creating plots | |
| 514 function createPlots() { | |
| 515 if (typeof Plotly === 'undefined') { | |
| 516 console.log('Waiting for Plotly...'); | |
| 517 setTimeout(createPlots, 100); | |
| 518 return; | |
| 519 } | |
| 520 | |
| 521 console.log('Plotly loaded, creating plots...'); | |
| 522 | |
| 523 // Learning Curves — Label Accuracy (with both training and validation) | |
| 524 try { | |
| 525 Plotly.newPlot('learning-curve-accuracy', [ | |
| 526 { | |
| 527 x: epochs, | |
| 528 y: accuracy_train, | |
| 529 type: 'scatter', | |
| 530 mode: 'lines+markers', | |
| 531 name: 'Training', | |
| 532 line: { width: 3, color: '#1f77b4', shape: 'spline' }, | |
| 533 marker: { size: 8, color: '#1f77b4' } | |
| 534 }, | |
| 535 { | |
| 536 x: epochs, | |
| 537 y: accuracy_val, | |
| 538 type: 'scatter', | |
| 539 mode: 'lines+markers', | |
| 540 name: 'Validation', | |
| 541 line: { width: 3, color: '#ff7f0e', shape: 'spline' }, | |
| 542 marker: { size: 8, color: '#ff7f0e' } | |
| 543 } | |
| 544 ], { | |
| 545 template: 'plotly_white', | |
| 546 xaxis: { | |
| 547 title: 'Epoch', | |
| 548 gridcolor: '#e0e0e0', | |
| 549 showgrid: true, | |
| 550 zeroline: false | |
| 551 }, | |
| 552 yaxis: { | |
| 553 title: 'Accuracy', | |
| 554 gridcolor: '#e0e0e0', | |
| 555 showgrid: true, | |
| 556 zeroline: false, | |
| 557 range: [0.5, 1.0] | |
| 558 }, | |
| 559 legend: { | |
| 560 orientation: 'h', | |
| 561 yanchor: 'bottom', | |
| 562 y: 1.02, | |
| 563 xanchor: 'right', | |
| 564 x: 1.0 | |
| 565 }, | |
| 566 margin: { l: 60, r: 20, t: 40, b: 50 }, | |
| 567 plot_bgcolor: 'white', | |
| 568 paper_bgcolor: 'white' | |
| 569 }); | |
| 570 console.log('Accuracy plot created'); | |
| 571 } catch(e) { | |
| 572 console.error('Error creating accuracy plot:', e); | |
| 573 } | |
| 574 | |
| 575 // Learning Curves — Label Loss (with both training and validation) | |
| 576 try { | |
| 577 Plotly.newPlot('learning-curve-loss', [ | |
| 578 { | |
| 579 x: epochs, | |
| 580 y: loss_train, | |
| 581 type: 'scatter', | |
| 582 mode: 'lines+markers', | |
| 583 name: 'Training Loss', | |
| 584 line: { width: 3, color: '#1f77b4', shape: 'spline' }, | |
| 585 marker: { size: 8, color: '#1f77b4' } | |
| 586 }, | |
| 587 { | |
| 588 x: epochs, | |
| 589 y: loss_val, | |
| 590 type: 'scatter', | |
| 591 mode: 'lines+markers', | |
| 592 name: 'Validation Loss', | |
| 593 line: { width: 3, color: '#ff7f0e', shape: 'spline' }, | |
| 594 marker: { size: 8, color: '#ff7f0e' } | |
| 595 } | |
| 596 ], { | |
| 597 template: 'plotly_white', | |
| 598 xaxis: { | |
| 599 title: 'Epoch', | |
| 600 gridcolor: '#e0e0e0', | |
| 601 showgrid: true, | |
| 602 zeroline: false | |
| 603 }, | |
| 604 yaxis: { | |
| 605 title: 'Loss', | |
| 606 gridcolor: '#e0e0e0', | |
| 607 showgrid: true, | |
| 608 zeroline: false, | |
| 609 range: [0.2, 1.4] | |
| 610 }, | |
| 611 legend: { | |
| 612 orientation: 'h', | |
| 613 yanchor: 'bottom', | |
| 614 y: 1.02, | |
| 615 xanchor: 'right', | |
| 616 x: 1.0 | |
| 617 }, | |
| 618 margin: { l: 60, r: 20, t: 40, b: 50 }, | |
| 619 plot_bgcolor: 'white', | |
| 620 paper_bgcolor: 'white' | |
| 621 }); | |
| 622 console.log('Loss plot created'); | |
| 623 } catch(e) { | |
| 624 console.error('Error creating loss plot:', e); | |
| 625 } | |
| 626 | |
| 627 // Threshold Plot (with fixed data matching the expected visualization) | |
| 628 // Precision: starts low (~0.42), increases to peak (~0.95) around threshold 0.5, then drops sharply to 0 | |
| 629 // Recall: starts at 1, stays at 1 until ~0.1, then decreases to 0 around 0.9 | |
| 630 // F1: starts around 0.6, peaks around 0.5 at ~0.95, then drops to 0 | |
| 631 const thresholds = Array.from({length: 101}, (_, i) => i / 100); | |
| 632 | |
| 633 // Generate precision values: low at start, peak around 0.5, drop to 0 | |
| 634 const precision_vals = thresholds.map(t => { | |
| 635 if (t <= 0.5) { | |
| 636 return 0.42 + (t / 0.5) * 0.53; // 0.42 to 0.95 | |
| 637 } else if (t <= 0.9) { | |
| 638 return 0.95 - ((t - 0.5) / 0.4) * 0.95; // 0.95 to 0 | |
| 639 } else { | |
| 640 return 0; | |
| 641 } | |
| 642 }); | |
| 643 | |
| 644 // Generate recall values: starts at 1, stays at 1 until ~0.1, then decreases to 0 around 0.9 | |
| 645 const recall_vals = thresholds.map(t => { | |
| 646 if (t <= 0.1) { | |
| 647 return 1.0; | |
| 648 } else if (t <= 0.9) { | |
| 649 return 1.0 - ((t - 0.1) / 0.8) * 1.0; // 1.0 to 0 | |
| 650 } else { | |
| 651 return 0; | |
| 652 } | |
| 653 }); | |
| 654 | |
| 655 // Calculate F1 from precision and recall | |
| 656 const f1_vals = thresholds.map((t, i) => { | |
| 657 const p = precision_vals[i]; | |
| 658 const r = recall_vals[i]; | |
| 659 if (p + r === 0) return 0; | |
| 660 return 2 * (p * r) / (p + r); | |
| 661 }); | |
| 662 | |
| 663 // Queue Rate: decreases linearly from 1 to 0 | |
| 664 const queue_rate = thresholds.map(t => 1 - t); | |
| 665 | |
| 666 try { | |
| 667 Plotly.newPlot('threshold-plot', [ | |
| 668 { | |
| 669 x: thresholds, | |
| 670 y: precision_vals, | |
| 671 type: 'scatter', | |
| 672 mode: 'lines', | |
| 673 name: 'Precision', | |
| 674 line: { width: 3, color: '#1f77b4' } | |
| 675 }, | |
| 676 { | |
| 677 x: thresholds, | |
| 678 y: recall_vals, | |
| 679 type: 'scatter', | |
| 680 mode: 'lines', | |
| 681 name: 'Recall', | |
| 682 line: { width: 3, color: '#ff7f0e' } | |
| 683 }, | |
| 684 { | |
| 685 x: thresholds, | |
| 686 y: f1_vals, | |
| 687 type: 'scatter', | |
| 688 mode: 'lines', | |
| 689 name: 'F1', | |
| 690 line: { width: 3, color: '#2ca02c' } | |
| 691 }, | |
| 692 { | |
| 693 x: thresholds, | |
| 694 y: queue_rate, | |
| 695 type: 'scatter', | |
| 696 mode: 'lines', | |
| 697 name: 'Queue Rate', | |
| 698 line: { width: 2, color: '#808080', dash: 'dash' } | |
| 699 } | |
| 700 ], { | |
| 701 template: 'plotly_white', | |
| 702 xaxis: { | |
| 703 title: 'Discrimination Threshold', | |
| 704 gridcolor: '#e0e0e0', | |
| 705 showgrid: true, | |
| 706 zeroline: false | |
| 707 }, | |
| 708 yaxis: { | |
| 709 title: 'Score', | |
| 710 gridcolor: '#e0e0e0', | |
| 711 showgrid: true, | |
| 712 zeroline: false, | |
| 713 range: [0, 1] | |
| 714 }, | |
| 715 legend: { | |
| 716 orientation: 'h', | |
| 717 yanchor: 'bottom', | |
| 718 y: 1.02, | |
| 719 xanchor: 'right', | |
| 720 x: 1.0 | |
| 721 }, | |
| 722 margin: { l: 60, r: 20, t: 40, b: 50 }, | |
| 723 plot_bgcolor: 'white', | |
| 724 paper_bgcolor: 'white', | |
| 725 shapes: [{ | |
| 726 type: 'line', | |
| 727 xref: 'x', | |
| 728 yref: 'y', | |
| 729 x0: 0.51, | |
| 730 y0: 0, | |
| 731 x1: 0.51, | |
| 732 y1: 1, | |
| 733 line: { | |
| 734 color: 'black', | |
| 735 width: 2, | |
| 736 dash: 'dash' | |
| 737 } | |
| 738 }], | |
| 739 annotations: [{ | |
| 740 x: 0.51, | |
| 741 y: 0.85, | |
| 742 text: 't* = 0.51', | |
| 743 showarrow: false, | |
| 744 font: { size: 12, color: 'black' } | |
| 745 }] | |
| 746 }); | |
| 747 console.log('Threshold plot created'); | |
| 748 } catch(e) { | |
| 749 console.error('Error creating threshold plot:', e); | |
| 750 } | |
| 751 | |
| 752 // Confusion Matrix (matching imagelearner style with Blues colorscale) | |
| 753 const cm_data = [[542, 78], [65, 515]]; | |
| 754 const total = cm_data.flat().reduce((a, b) => a + b, 0); | |
| 755 const labels = ['0', '1']; | |
| 756 | |
| 757 // Build annotations array with all white text | |
| 758 const annotations = []; | |
| 759 cm_data.forEach((row, i) => { | |
| 760 row.forEach((val, j) => { | |
| 761 const pct = ((val / total) * 100).toFixed(1); | |
| 762 // All text is white | |
| 763 const textColor = 'white'; | |
| 764 // Count annotation (bold, bottom) | |
| 765 annotations.push({ | |
| 766 x: labels[j], | |
| 767 y: labels[i], | |
| 768 text: '<b>' + val + '</b>', | |
| 769 showarrow: false, | |
| 770 font: { color: textColor, size: 14 }, | |
| 771 xanchor: 'center', | |
| 772 yanchor: 'bottom', | |
| 773 yshift: 2 | |
| 774 }); | |
| 775 // Percentage annotation (top) | |
| 776 annotations.push({ | |
| 777 x: labels[j], | |
| 778 y: labels[i], | |
| 779 text: pct + '%', | |
| 780 showarrow: false, | |
| 781 font: { color: textColor, size: 13 }, | |
| 782 xanchor: 'center', | |
| 783 yanchor: 'top', | |
| 784 yshift: -2 | |
| 785 }); | |
| 786 }); | |
| 787 }); | |
| 788 | |
| 789 try { | |
| 790 Plotly.newPlot('confusion-matrix', [{ | |
| 791 z: cm_data, | |
| 792 x: labels, | |
| 793 y: labels, | |
| 794 type: 'heatmap', | |
| 795 colorscale: 'Blues', | |
| 796 showscale: true, | |
| 797 colorbar: { title: 'Count' }, | |
| 798 xgap: 2, | |
| 799 ygap: 2, | |
| 800 hovertemplate: 'True=%{y}<br>Pred=%{x}<br>Count=%{z}<extra></extra>', | |
| 801 zmin: 0 | |
| 802 }], { | |
| 803 xaxis: { title: 'Predicted label', type: 'category' }, | |
| 804 yaxis: { title: 'True label', type: 'category', autorange: 'reversed' }, | |
| 805 margin: { l: 80, r: 20, t: 40, b: 80 }, | |
| 806 template: 'plotly_white', | |
| 807 plot_bgcolor: 'white', | |
| 808 paper_bgcolor: 'white', | |
| 809 annotations: annotations | |
| 810 }); | |
| 811 console.log('Confusion matrix created'); | |
| 812 } catch(e) { | |
| 813 console.error('Error creating confusion matrix:', e); | |
| 814 } | |
| 815 | |
| 816 // Per-Class Metrics | |
| 817 const classes = ['Class 0', 'Class 1']; | |
| 818 const precision_per_class = [0.8929, 0.8685]; | |
| 819 const recall_per_class = [0.8742, 0.8879]; | |
| 820 const f1_per_class = [0.8835, 0.8781]; | |
| 821 | |
| 822 try { | |
| 823 Plotly.newPlot('per-class-metrics', [ | |
| 824 { | |
| 825 x: classes, | |
| 826 y: precision_per_class, | |
| 827 type: 'bar', | |
| 828 name: 'Precision', | |
| 829 marker: { color: '#4CAF50' } | |
| 830 }, | |
| 831 { | |
| 832 x: classes, | |
| 833 y: recall_per_class, | |
| 834 type: 'bar', | |
| 835 name: 'Recall', | |
| 836 marker: { color: '#2196F3' } | |
| 837 }, | |
| 838 { | |
| 839 x: classes, | |
| 840 y: f1_per_class, | |
| 841 type: 'bar', | |
| 842 name: 'F1', | |
| 843 marker: { color: '#FF9800' } | |
| 844 } | |
| 845 ], { | |
| 846 template: 'plotly_white', | |
| 847 xaxis: { | |
| 848 title: 'Class', | |
| 849 gridcolor: '#e0e0e0', | |
| 850 showgrid: true | |
| 851 }, | |
| 852 yaxis: { | |
| 853 title: 'Score', | |
| 854 gridcolor: '#e0e0e0', | |
| 855 showgrid: true, | |
| 856 range: [0, 1] | |
| 857 }, | |
| 858 barmode: 'group', | |
| 859 legend: { | |
| 860 orientation: 'h', | |
| 861 yanchor: 'bottom', | |
| 862 y: 1.02, | |
| 863 xanchor: 'right', | |
| 864 x: 1.0 | |
| 865 }, | |
| 866 margin: { l: 60, r: 20, t: 40, b: 50 }, | |
| 867 plot_bgcolor: 'white', | |
| 868 paper_bgcolor: 'white' | |
| 869 }); | |
| 870 console.log('Per-class metrics created'); | |
| 871 } catch(e) { | |
| 872 console.error('Error creating per-class metrics:', e); | |
| 873 } | |
| 874 | |
| 875 // ROC Curve (with fixed data) | |
| 876 const fpr = [0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.10,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.20,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.30,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.40,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.50,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.60,0.61,0.62,0.63,0.64,0.65,0.66,0.67,0.68,0.69,0.70,0.71,0.72,0.73,0.74,0.75,0.76,0.77,0.78,0.79,0.80,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.90,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99,1.00]; | |
| 877 const tpr = [0,0.12,0.23,0.33,0.42,0.50,0.57,0.63,0.69,0.74,0.78,0.82,0.85,0.88,0.90,0.92,0.94,0.95,0.96,0.97,0.98,0.985,0.99,0.992,0.994,0.996,0.997,0.998,0.999,0.9995,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0,1.0]; | |
| 878 const auc_score = 0.935; | |
| 879 | |
| 880 try { | |
| 881 Plotly.newPlot('roc-curve', [ | |
| 882 { | |
| 883 x: [0, 1], | |
| 884 y: [0, 1], | |
| 885 type: 'scatter', | |
| 886 mode: 'lines', | |
| 887 name: 'Random (AUC = 0.50)', | |
| 888 line: { dash: 'dash', color: 'gray', width: 2 } | |
| 889 }, | |
| 890 { | |
| 891 x: fpr, | |
| 892 y: tpr, | |
| 893 type: 'scatter', | |
| 894 mode: 'lines', | |
| 895 name: `ROC Curve (AUC = ${auc_score.toFixed(3)})`, | |
| 896 line: { width: 3, color: '#1f77b4' } | |
| 897 } | |
| 898 ], { | |
| 899 template: 'plotly_white', | |
| 900 xaxis: { | |
| 901 title: 'False Positive Rate', | |
| 902 gridcolor: '#e0e0e0', | |
| 903 showgrid: true, | |
| 904 zeroline: false, | |
| 905 range: [0, 1] | |
| 906 }, | |
| 907 yaxis: { | |
| 908 title: 'True Positive Rate', | |
| 909 gridcolor: '#e0e0e0', | |
| 910 showgrid: true, | |
| 911 zeroline: false, | |
| 912 range: [0, 1] | |
| 913 }, | |
| 914 legend: { | |
| 915 orientation: 'h', | |
| 916 yanchor: 'bottom', | |
| 917 y: 1.02, | |
| 918 xanchor: 'right', | |
| 919 x: 1.0 | |
| 920 }, | |
| 921 margin: { l: 60, r: 20, t: 40, b: 50 }, | |
| 922 plot_bgcolor: 'white', | |
| 923 paper_bgcolor: 'white' | |
| 924 }); | |
| 925 console.log('ROC curve created'); | |
| 926 } catch(e) { | |
| 927 console.error('Error creating ROC curve:', e); | |
| 928 } | |
| 929 | |
| 930 // Precision-Recall Curve (with fixed data) | |
| 931 const recall_pr = [0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09,0.10,0.11,0.12,0.13,0.14,0.15,0.16,0.17,0.18,0.19,0.20,0.21,0.22,0.23,0.24,0.25,0.26,0.27,0.28,0.29,0.30,0.31,0.32,0.33,0.34,0.35,0.36,0.37,0.38,0.39,0.40,0.41,0.42,0.43,0.44,0.45,0.46,0.47,0.48,0.49,0.50,0.51,0.52,0.53,0.54,0.55,0.56,0.57,0.58,0.59,0.60,0.61,0.62,0.63,0.64,0.65,0.66,0.67,0.68,0.69,0.70,0.71,0.72,0.73,0.74,0.75,0.76,0.77,0.78,0.79,0.80,0.81,0.82,0.83,0.84,0.85,0.86,0.87,0.88,0.89,0.90,0.91,0.92,0.93,0.94,0.95,0.96,0.97,0.98,0.99,1.00]; | |
| 932 const precision_pr = [0.95,0.949,0.948,0.947,0.946,0.945,0.944,0.943,0.942,0.941,0.940,0.939,0.938,0.937,0.936,0.935,0.934,0.933,0.932,0.931,0.930,0.929,0.928,0.927,0.926,0.925,0.924,0.923,0.922,0.921,0.920,0.919,0.918,0.917,0.916,0.915,0.914,0.913,0.912,0.911,0.910,0.909,0.908,0.907,0.906,0.905,0.904,0.903,0.902,0.901,0.900,0.899,0.898,0.897,0.896,0.895,0.894,0.893,0.892,0.891,0.890,0.889,0.888,0.887,0.886,0.885,0.884,0.883,0.882,0.881,0.880,0.879,0.878,0.877,0.876,0.875,0.874,0.873,0.872,0.871,0.870,0.869,0.868,0.867,0.866,0.865,0.864,0.863,0.862,0.861,0.860,0.859,0.858,0.857,0.856,0.855,0.854,0.853,0.852,0.851,0.850]; | |
| 933 const ap_score = 0.9234; | |
| 934 | |
| 935 try { | |
| 936 Plotly.newPlot('pr-curve', [ | |
| 937 { | |
| 938 x: recall_pr, | |
| 939 y: precision_pr, | |
| 940 type: 'scatter', | |
| 941 mode: 'lines', | |
| 942 name: `Precision-Recall Curve (AP = ${ap_score.toFixed(4)})`, | |
| 943 line: { width: 3, color: '#1f77b4' } | |
| 944 } | |
| 945 ], { | |
| 946 template: 'plotly_white', | |
| 947 xaxis: { | |
| 948 title: 'Recall', | |
| 949 gridcolor: '#e0e0e0', | |
| 950 showgrid: true, | |
| 951 zeroline: false, | |
| 952 range: [0, 1] | |
| 953 }, | |
| 954 yaxis: { | |
| 955 title: 'Precision', | |
| 956 gridcolor: '#e0e0e0', | |
| 957 showgrid: true, | |
| 958 zeroline: false, | |
| 959 range: [0.6, 1] | |
| 960 }, | |
| 961 legend: { | |
| 962 orientation: 'h', | |
| 963 yanchor: 'bottom', | |
| 964 y: 1.02, | |
| 965 xanchor: 'right', | |
| 966 x: 1.0 | |
| 967 }, | |
| 968 margin: { l: 60, r: 20, t: 40, b: 50 }, | |
| 969 plot_bgcolor: 'white', | |
| 970 paper_bgcolor: 'white' | |
| 971 }); | |
| 972 console.log('PR curve created'); | |
| 973 } catch(e) { | |
| 974 console.error('Error creating PR curve:', e); | |
| 975 } | |
| 976 | |
| 977 console.log('All plots created successfully!'); | |
| 978 } | |
| 979 | |
| 980 // Start creating plots when DOM is ready | |
| 981 if (document.readyState === 'loading') { | |
| 982 document.addEventListener('DOMContentLoaded', createPlots); | |
| 983 } else { | |
| 984 createPlots(); | |
| 985 } | |
| 986 </script> | |
| 987 </div> | |
| 988 </body> | |
| 989 </html> | |
| 990 |
