bozdaglab F4RUKYLDRM commited on
Commit
b19e8c7
·
1 Parent(s): 2e6a2a4

Fix BRCA training collapse, mobile layout and results history (#3)

Browse files

- Fix mobile dashboard layout (9a10d98c1ae2d54e01852d5dd5f5a7cda0f8e577)
- Fix BRCA training collapse with lab-grid defaults (62a8628cbfa0da789c989c19608ee2c2f6bb536b)
- Add BRCA training diagnostics and lab grid results (d1ec2eed8c6cd02479da5b4234a056cb83761ab5)
- Keep the action bar visible without scrolling (bb42e347c5bc5557015683583eba1f7f6def7354)
- Scroll long result tables inside their own box (e413269a95818f6de610533927deab1f2d19ad52)
- Align Results tables and fix overlapping table headers (c5021fff6b8ddc17cd0286c07d68bc7db95e569c)
- Align the confusion matrix with the training history chart (adaf2dbccdb6e262631b7f94b561b5d2618fd8f4)


Co-authored-by: Faruk Yildirim <F4RUKYLDRM@users.noreply.huggingface.co>

Files changed (40) hide show
  1. app.py +75 -43
  2. biolmnet/training.py +64 -19
  3. biolmnet/ui/styles.py +270 -8
  4. diagnostics/2026-10-16-brca-training-collapse/README.md +37 -0
  5. diagnostics/2026-10-16-brca-training-collapse/results/brca_weighted.txt +147 -0
  6. diagnostics/2026-10-16-brca-training-collapse/results/effective_fanin_init.txt +16 -0
  7. diagnostics/2026-10-16-brca-training-collapse/results/epochs_100.json +0 -0
  8. diagnostics/2026-10-16-brca-training-collapse/results/epochs_100.log +9 -0
  9. diagnostics/2026-10-16-brca-training-collapse/results/epochs_200.json +0 -0
  10. diagnostics/2026-10-16-brca-training-collapse/results/epochs_200.log +9 -0
  11. diagnostics/2026-10-16-brca-training-collapse/results/epochs_50.json +3105 -0
  12. diagnostics/2026-10-16-brca-training-collapse/results/epochs_50.log +9 -0
  13. diagnostics/2026-10-16-brca-training-collapse/results/paper_config_sgd.txt +4 -0
  14. diagnostics/2026-10-16-brca-training-collapse/results/seed_runs.txt +63 -0
  15. diagnostics/2026-10-16-brca-training-collapse/results/weight_decay_sweep.txt +7 -0
  16. diagnostics/2026-10-16-brca-training-collapse/scripts/diagnose.py +177 -0
  17. diagnostics/2026-10-16-brca-training-collapse/scripts/epochs.py +71 -0
  18. diagnostics/2026-10-16-brca-training-collapse/scripts/epochs_summary.py +95 -0
  19. diagnostics/2026-10-16-brca-training-collapse/scripts/seeds.py +44 -0
  20. diagnostics/2026-10-16-brca-training-collapse/scripts/sweep.py +27 -0
  21. diagnostics/2026-10-16-brca-training-collapse/upstream/BioLMNET.ipynb +947 -0
  22. diagnostics/2026-10-16-brca-training-collapse/upstream/BioLMNET_Weighted_Loss.ipynb +940 -0
  23. diagnostics/2026-10-30-lab-hyperparameter-grid/README.md +22 -0
  24. diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard0.log +55 -0
  25. diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard1.log +55 -0
  26. diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard2.log +55 -0
  27. diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard3.log +55 -0
  28. diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_singlecell_seed42.jsonl +0 -0
  29. diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_summary.txt +63 -0
  30. diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_check_singlecell.jsonl +48 -0
  31. diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_check_summary.txt +25 -0
  32. diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard0.log +13 -0
  33. diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard1.log +13 -0
  34. diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard2.log +13 -0
  35. diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard3.log +13 -0
  36. diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/build_workspaces.py +30 -0
  37. diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/grid.py +70 -0
  38. diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/grid_summary.py +31 -0
  39. diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/seed_check.py +62 -0
  40. tests/test_core.py +223 -1
app.py CHANGED
@@ -34,6 +34,7 @@ from biolmnet.data import (
34
  validate_and_align_omics,
35
  )
36
  from biolmnet.training import (
 
37
  Hyperparameters,
38
  ModelBundle,
39
  pathway_importance,
@@ -44,8 +45,11 @@ from biolmnet.ui import components as ui
44
  from biolmnet.ui.styles import CSS
45
 
46
 
 
 
 
47
  GENEPT_OPTIONS = {
48
- "Auto — bulk or single-cell based on source": "auto",
49
  "Bulk · large-3 context": "embedding_original_large_3.parquet",
50
  "Bulk · original ada-text context": "embedding_original_ada_text.parquet",
51
  "Single-cell · cell type, tissue, drug & pathway": (
@@ -101,9 +105,8 @@ def _resolve_embedding_file(option: str, source_mode: str, example_dataset: str)
101
  selected = GENEPT_OPTIONS[option]
102
  if selected != "auto":
103
  return selected
104
- if source_mode == "BioLM-NET examples" and example_dataset == "scTrioseq2":
105
- return "embedding_associations_cell_type_tissue_drug_pathway_openai_large.parquet"
106
- return "embedding_original_large_3.parquet"
107
 
108
 
109
  def _graph_preview_html(mask_density: str, gene_nodes: str, pathway_units: str, genept_dim: str) -> str:
@@ -514,6 +517,14 @@ def _loss_plot(history: list[dict[str, float]]) -> go.Figure:
514
  return figure
515
 
516
 
 
 
 
 
 
 
 
 
517
  def estimate_training_duration(
518
  workspace: PreparedWorkspace | None,
519
  epochs: int,
@@ -651,8 +662,7 @@ def train_workspace(
651
  importance = pathway_importance(bundle).head(100)
652
  architecture_audit = _architecture_audit(bundle)
653
  confusion_html = ui.blueprint_div(
654
- f'<div class="mono" style="margin-bottom:12px">Confusion matrix · validation split</div>'
655
- + ui.confusion_matrix_html(result.confusion.tolist(), bundle.label_names)
656
  )
657
 
658
  run_meta_update = {
@@ -949,6 +959,7 @@ def refresh_results(bundle: ModelBundle | None, run_meta: dict):
949
  "Build the biological architecture, train a model, or run prediction to populate this page."
950
  ),
951
  gr.update(visible=False),
 
952
  )
953
 
954
  macro_auc = run_meta.get("macro_auc")
@@ -965,7 +976,8 @@ def refresh_results(bundle: ModelBundle | None, run_meta: dict):
965
  )
966
  + "</div>"
967
  )
968
- return stats, gr.update(visible=True), "", gr.update(visible=True)
 
969
 
970
 
971
  THEME = gr.themes.Base(primary_hue="gray", neutral_hue="gray")
@@ -986,18 +998,19 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
986
  session_id_state = gr.State("")
987
  intro_step_state = gr.State(0)
988
  intro_dismissed_state = gr.BrowserState(False, storage_key="biolmnet_intro_dismissed")
 
989
 
990
  with gr.Row(elem_classes=["app-shell"]):
991
  with gr.Column(elem_classes=["rail-col"]):
992
  with gr.Column(elem_classes=["rail"]):
993
- gr.HTML(ui.brand_block_html())
994
  rail_row_htmls = []
995
  rail_row_buttons = []
996
  with gr.Column(elem_classes=["rail-nav"]):
997
  for index, label in enumerate(STAGE_LABELS):
998
  with gr.Column(elem_classes=["rail-row-wrap"]):
999
  row_html = gr.HTML(ui.rail_row_html(index + 1, label, "on" if index == 0 else "off", "Active" if index == 0 else "Locked"))
1000
- row_button = gr.Button("", elem_classes=["rail-row-click"])
1001
  rail_row_htmls.append(row_html)
1002
  rail_row_buttons.append(row_button)
1003
  with gr.Column(elem_classes=["rail-spacer", "rail-plate-wrap"]):
@@ -1011,7 +1024,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1011
  intro_open_button = gr.Button("Introduction", size="sm", variant="secondary", elem_classes=["btn-ghost", "intro-open-btn"])
1012
 
1013
  # ── Data & Priors ──────────────────────────────────────���─
1014
- with gr.Column(visible=True) as data_page:
1015
  with gr.Row(elem_classes=["pghd"]):
1016
  gr.HTML(
1017
  ui.title_block_html(
@@ -1028,7 +1041,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1028
  source_mode = gr.Radio(
1029
  ["BioLM-NET examples", "GitHub folder", "Upload files"],
1030
  value="BioLM-NET examples", show_label=False, container=False,
1031
- elem_classes=["seg-radio"],
1032
  )
1033
  with gr.Row(elem_classes=["srow-row", "first"]):
1034
  with gr.Column(scale=1, min_width=0):
@@ -1038,7 +1051,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1038
  gr.HTML('<div class="field-label">Repository dataset</div>')
1039
  example_dataset = gr.Dropdown(
1040
  ["BRCA", "COAD", "GBM", "scTrioseq2"], value="BRCA",
1041
- show_label=False, container=False,
1042
  )
1043
  with gr.Column(visible=False) as github_group:
1044
  github_folder = gr.Textbox(
@@ -1056,7 +1069,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1056
  uploaded_dna_pathway = gr.File(label="DNA → pathway mapping", file_types=[".csv"], type="filepath", elem_classes=["file-slot"])
1057
 
1058
  gr.HTML('<div class="mono sect sect-spaced">Resolved files</div>')
1059
- resolved_files_table = gr.Dataframe(
1060
  headers=["File", "Shape", "Rows matched", "Status"], interactive=False, wrap=False,
1061
  value=pd.DataFrame(),
1062
  )
@@ -1134,8 +1147,8 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1134
  graph_preview = gr.HTML(_graph_preview_html("—", "—", "—", "—"))
1135
 
1136
  with gr.Row(visible=False):
1137
- architecture_table = gr.Dataframe(interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1138
- enrichment_table = gr.Dataframe(interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1139
 
1140
  with gr.Row(elem_classes=["actbar-row"]):
1141
  data_actionbar_note = gr.HTML(f'<div class="actbar-note">{ui.esc("Choose a source, then build the biological architecture.")}</div>')
@@ -1144,12 +1157,13 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1144
  "Re-validate inputs", size="sm", variant="secondary", elem_classes=["actbar-secondary-btn"]
1145
  )
1146
  prepare_button = gr.Button(
1147
- "Build biological architecture", size="sm", variant="primary", elem_classes=["actbar-primary-btn"]
 
1148
  )
1149
  preparation_status = gr.HTML(visible=False)
1150
 
1151
  # ── Train Model ──────────────────────────────────────────
1152
- with gr.Column(visible=False) as train_page:
1153
  with gr.Row(elem_classes=["pghd"]):
1154
  gr.HTML(
1155
  ui.title_block_html(
@@ -1165,22 +1179,22 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1165
  with gr.Column(min_width=140):
1166
  gr.HTML(ui.esc("Epochs"))
1167
  with gr.Column():
1168
- epochs = gr.Slider(5, 200, value=50, step=5, show_label=False, container=False)
1169
  with gr.Row(elem_classes=["srow-row"]):
1170
  with gr.Column(min_width=140):
1171
  gr.HTML(ui.esc("Batch size"))
1172
  with gr.Column():
1173
- batch_size = gr.Dropdown([8, 16, 32, 64, 128], value=16, show_label=False, container=False)
1174
  with gr.Row(elem_classes=["srow-row"]):
1175
  with gr.Column(min_width=140):
1176
  gr.HTML(ui.esc("Learning rate"))
1177
  with gr.Column():
1178
- learning_rate = gr.Number(value=0.001, minimum=0.000001, show_label=False, container=False)
1179
  with gr.Row(elem_classes=["srow-row"]):
1180
  with gr.Column(min_width=140):
1181
  gr.HTML(ui.esc("L2 weight decay"))
1182
  with gr.Column():
1183
- weight_decay = gr.Number(value=0.01, minimum=0, show_label=False, container=False)
1184
  with gr.Row(elem_classes=["srow-row", "last"]):
1185
  with gr.Column(min_width=140):
1186
  gr.HTML(ui.esc("Dropout"))
@@ -1202,7 +1216,13 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1202
  with gr.Column(min_width=140):
1203
  gr.HTML(ui.esc("Validation fraction"))
1204
  with gr.Column():
1205
- validation_fraction = gr.Slider(0.1, 0.4, value=0.2, step=0.05, show_label=False, container=False)
 
 
 
 
 
 
1206
  with gr.Row(elem_classes=["srow-row"]):
1207
  with gr.Column(min_width=140):
1208
  gr.HTML(ui.esc("Optimizer"))
@@ -1221,11 +1241,16 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1221
  gr.HTML('<div class="mono" style="margin-bottom:9px">Training result</div>')
1222
  training_status = gr.HTML(_status("Prepare the architecture before training."))
1223
  loss_plot = gr.Plot(label="Loss by epoch", show_label=False)
1224
- gr.HTML('<div class="mono" style="margin:20px 0 9px">Paper-faithful defaults</div>')
1225
  gr.HTML(
1226
  "".join(
1227
  [
1228
  ui.kv_plain_row("First layer", "trainable W ⊙ M"),
 
 
 
 
 
1229
  ui.kv_plain_row("PDI weights", "binary"),
1230
  ui.kv_plain_row("PPI weights", "STRING norm."),
1231
  ui.kv_plain_row("Attention", "GenePT query"),
@@ -1238,11 +1263,12 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1238
  train_actionbar_note = gr.HTML('<div class="actbar-note">Set hyperparameters, then train.</div>')
1239
  with gr.Row(elem_classes=["actbar-buttons"]):
1240
  train_button = gr.Button(
1241
- "Train BioLM-NET on ZeroGPU", size="sm", variant="primary", elem_classes=["actbar-primary-btn"]
 
1242
  )
1243
 
1244
  # ── Export Artifacts ───────────────────────���─────────────
1245
- with gr.Column(visible=False) as export_page:
1246
  with gr.Row(elem_classes=["pghd"]):
1247
  gr.HTML(
1248
  ui.title_block_html(
@@ -1270,7 +1296,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1270
  gr.HTML('<div class="actbar-note">Bundle written to a temp file on export · not persisted between Space restarts</div>')
1271
 
1272
  # ── Predict ──────────────────────────────────────────────
1273
- with gr.Column(visible=False) as predict_page:
1274
  with gr.Row(elem_classes=["pghd"]):
1275
  gr.HTML(
1276
  ui.title_block_html(
@@ -1326,7 +1352,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1326
  with gr.Column(scale=6, min_width=0):
1327
  gr.HTML('<div class="mono" style="margin-bottom:9px">Predictions · last successful run</div>')
1328
  distribution_panel = gr.HTML(ui.empty_note_html("No predictions yet."))
1329
- prediction_table = gr.Dataframe(interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1330
  prediction_download = gr.File(label="Prediction CSV", interactive=False, elem_classes=["file-slot", "btn-block"])
1331
 
1332
  with gr.Row(elem_classes=["actbar-row"]):
@@ -1334,12 +1360,12 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1334
  with gr.Row(elem_classes=["actbar-buttons"]):
1335
  predict_button = gr.Button(
1336
  "Run inference", size="sm", variant="primary", interactive=False,
1337
- elem_classes=["actbar-primary-btn"],
1338
  )
1339
  prediction_status = gr.HTML(visible=False)
1340
 
1341
  # ── Results ──────────────────────────────────────────────
1342
- with gr.Column(visible=False) as results_page:
1343
  with gr.Row(elem_classes=["pghd"]):
1344
  gr.HTML(
1345
  ui.title_block_html(
@@ -1351,21 +1377,27 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1351
  results_empty_note = gr.HTML(ui.empty_note_html("Build the biological architecture, train a model, or run prediction to populate this page."))
1352
  with gr.Row(elem_classes=["results-stat-row"], visible=False) as results_stats_row:
1353
  results_stats = gr.HTML("")
1354
- with gr.Row(elem_classes=["results-grid"], equal_height=False, visible=False) as results_content:
1355
- with gr.Column(scale=6, min_width=0):
1356
- gr.HTML('<div class="mono sect">Training history</div>')
1357
- results_loss_plot = gr.Plot(show_label=False)
1358
- gr.HTML('<div class="mono sect sect-spaced">Sparse architecture audit</div>')
1359
- results_architecture_table = gr.Dataframe(interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1360
- with gr.Column(scale=6, min_width=0):
1361
- results_confusion = gr.HTML("")
1362
- gr.HTML('<div class="mono sect sect-spaced">Pathway attention · top retained</div>')
1363
- results_pathway_table = gr.Dataframe(interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
 
 
 
 
 
 
1364
  with gr.Column(visible=False) as results_tables_bottom:
1365
  gr.HTML('<div class="mono sect sect-spaced">Validation predictions</div>')
1366
- results_validation_table = gr.Dataframe(interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1367
  gr.HTML('<div class="mono sect sect-spaced">Predictions and class probabilities</div>')
1368
- results_prediction_table = gr.Dataframe(interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1369
  with gr.Row(elem_classes=["actbar-row"]):
1370
  gr.HTML(
1371
  '<div class="actbar-note">Validate cohorts, preprocessing and performance before '
@@ -1642,7 +1674,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1642
  ).then(
1643
  refresh_results,
1644
  inputs=[model_state, run_meta_state],
1645
- outputs=[results_stats, results_stats_row, results_empty_note, results_content],
1646
  show_progress="hidden",
1647
  ).then(
1648
  lambda df: gr.update(value=df), inputs=[validation_predictions_state], outputs=[results_validation_table],
@@ -1718,7 +1750,7 @@ with gr.Blocks(title="BioLM-NET Workbench", fill_width=True) as demo:
1718
  show_progress="hidden",
1719
  ).then(
1720
  refresh_results, inputs=[model_state, run_meta_state],
1721
- outputs=[results_stats, results_stats_row, results_empty_note, results_content],
1722
  show_progress="hidden",
1723
  )
1724
 
 
34
  validate_and_align_omics,
35
  )
36
  from biolmnet.training import (
37
+ DEFAULT_VALIDATION_FRACTION,
38
  Hyperparameters,
39
  ModelBundle,
40
  pathway_importance,
 
45
  from biolmnet.ui.styles import CSS
46
 
47
 
48
+ # Long result tables scroll inside a box of this height (px) instead of stretching the page.
49
+ TABLE_MAX_HEIGHT = 320
50
+
51
  GENEPT_OPTIONS = {
52
+ "Auto — lab-recommended single-cell context": "auto",
53
  "Bulk · large-3 context": "embedding_original_large_3.parquet",
54
  "Bulk · original ada-text context": "embedding_original_ada_text.parquet",
55
  "Single-cell · cell type, tissue, drug & pathway": (
 
105
  selected = GENEPT_OPTIONS[option]
106
  if selected != "auto":
107
  return selected
108
+ # Bozdag Lab recommendation (J. Rifat, 2026-10-17) for every source.
109
+ return "embedding_associations_cell_type_tissue_drug_pathway_openai_large.parquet"
 
110
 
111
 
112
  def _graph_preview_html(mask_density: str, gene_nodes: str, pathway_units: str, genept_dim: str) -> str:
 
517
  return figure
518
 
519
 
520
+ def _validation_split_note(validation_fraction: float) -> str:
521
+ validation_percent = round(float(validation_fraction) * 100)
522
+ return (
523
+ f'<div class="field-hint">{validation_percent}% of samples are held out for validation '
524
+ f"(stratified by class). The remaining {100 - validation_percent}% are used for training.</div>"
525
+ )
526
+
527
+
528
  def estimate_training_duration(
529
  workspace: PreparedWorkspace | None,
530
  epochs: int,
 
662
  importance = pathway_importance(bundle).head(100)
663
  architecture_audit = _architecture_audit(bundle)
664
  confusion_html = ui.blueprint_div(
665
+ ui.confusion_matrix_html(result.confusion.tolist(), bundle.label_names)
 
666
  )
667
 
668
  run_meta_update = {
 
959
  "Build the biological architecture, train a model, or run prediction to populate this page."
960
  ),
961
  gr.update(visible=False),
962
+ None,
963
  )
964
 
965
  macro_auc = run_meta.get("macro_auc")
 
976
  )
977
  + "</div>"
978
  )
979
+ loss_figure = _loss_plot(bundle.history) if bundle.history else None
980
+ return stats, gr.update(visible=True), "", gr.update(visible=True), loss_figure
981
 
982
 
983
  THEME = gr.themes.Base(primary_hue="gray", neutral_hue="gray")
 
998
  session_id_state = gr.State("")
999
  intro_step_state = gr.State(0)
1000
  intro_dismissed_state = gr.BrowserState(False, storage_key="biolmnet_intro_dismissed")
1001
+ nav_ids = ["nav-data", "nav-train", "nav-export", "nav-predict", "nav-results"]
1002
 
1003
  with gr.Row(elem_classes=["app-shell"]):
1004
  with gr.Column(elem_classes=["rail-col"]):
1005
  with gr.Column(elem_classes=["rail"]):
1006
+ gr.HTML(ui.brand_block_html(), elem_id="app-header")
1007
  rail_row_htmls = []
1008
  rail_row_buttons = []
1009
  with gr.Column(elem_classes=["rail-nav"]):
1010
  for index, label in enumerate(STAGE_LABELS):
1011
  with gr.Column(elem_classes=["rail-row-wrap"]):
1012
  row_html = gr.HTML(ui.rail_row_html(index + 1, label, "on" if index == 0 else "off", "Active" if index == 0 else "Locked"))
1013
+ row_button = gr.Button("", elem_id=nav_ids[index], elem_classes=["rail-row-click"])
1014
  rail_row_htmls.append(row_html)
1015
  rail_row_buttons.append(row_button)
1016
  with gr.Column(elem_classes=["rail-spacer", "rail-plate-wrap"]):
 
1024
  intro_open_button = gr.Button("Introduction", size="sm", variant="secondary", elem_classes=["btn-ghost", "intro-open-btn"])
1025
 
1026
  # ── Data & Priors ──────────────────────────────────────���─
1027
+ with gr.Column(visible=True, elem_id="page-data", elem_classes=["workspace-page", "data-workspace-page"]) as data_page:
1028
  with gr.Row(elem_classes=["pghd"]):
1029
  gr.HTML(
1030
  ui.title_block_html(
 
1041
  source_mode = gr.Radio(
1042
  ["BioLM-NET examples", "GitHub folder", "Upload files"],
1043
  value="BioLM-NET examples", show_label=False, container=False,
1044
+ elem_id="dataset-source", elem_classes=["seg-radio"],
1045
  )
1046
  with gr.Row(elem_classes=["srow-row", "first"]):
1047
  with gr.Column(scale=1, min_width=0):
 
1051
  gr.HTML('<div class="field-label">Repository dataset</div>')
1052
  example_dataset = gr.Dropdown(
1053
  ["BRCA", "COAD", "GBM", "scTrioseq2"], value="BRCA",
1054
+ show_label=False, container=False, elem_id="dataset-select",
1055
  )
1056
  with gr.Column(visible=False) as github_group:
1057
  github_folder = gr.Textbox(
 
1069
  uploaded_dna_pathway = gr.File(label="DNA → pathway mapping", file_types=[".csv"], type="filepath", elem_classes=["file-slot"])
1070
 
1071
  gr.HTML('<div class="mono sect sect-spaced">Resolved files</div>')
1072
+ resolved_files_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT,
1073
  headers=["File", "Shape", "Rows matched", "Status"], interactive=False, wrap=False,
1074
  value=pd.DataFrame(),
1075
  )
 
1147
  graph_preview = gr.HTML(_graph_preview_html("—", "—", "—", "—"))
1148
 
1149
  with gr.Row(visible=False):
1150
+ architecture_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT, interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1151
+ enrichment_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT, interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1152
 
1153
  with gr.Row(elem_classes=["actbar-row"]):
1154
  data_actionbar_note = gr.HTML(f'<div class="actbar-note">{ui.esc("Choose a source, then build the biological architecture.")}</div>')
 
1157
  "Re-validate inputs", size="sm", variant="secondary", elem_classes=["actbar-secondary-btn"]
1158
  )
1159
  prepare_button = gr.Button(
1160
+ "Build biological architecture", size="sm", variant="primary",
1161
+ elem_id="build-architecture-btn", elem_classes=["actbar-primary-btn"]
1162
  )
1163
  preparation_status = gr.HTML(visible=False)
1164
 
1165
  # ── Train Model ──────────────────────────────────────────
1166
+ with gr.Column(visible=False, elem_id="page-train", elem_classes=["workspace-page"]) as train_page:
1167
  with gr.Row(elem_classes=["pghd"]):
1168
  gr.HTML(
1169
  ui.title_block_html(
 
1179
  with gr.Column(min_width=140):
1180
  gr.HTML(ui.esc("Epochs"))
1181
  with gr.Column():
1182
+ epochs = gr.Slider(5, 200, value=Hyperparameters.epochs, step=5, show_label=False, container=False)
1183
  with gr.Row(elem_classes=["srow-row"]):
1184
  with gr.Column(min_width=140):
1185
  gr.HTML(ui.esc("Batch size"))
1186
  with gr.Column():
1187
+ batch_size = gr.Dropdown([8, 16, 32, 64, 128], value=Hyperparameters.batch_size, show_label=False, container=False)
1188
  with gr.Row(elem_classes=["srow-row"]):
1189
  with gr.Column(min_width=140):
1190
  gr.HTML(ui.esc("Learning rate"))
1191
  with gr.Column():
1192
+ learning_rate = gr.Number(value=Hyperparameters.learning_rate, minimum=0.000001, show_label=False, container=False)
1193
  with gr.Row(elem_classes=["srow-row"]):
1194
  with gr.Column(min_width=140):
1195
  gr.HTML(ui.esc("L2 weight decay"))
1196
  with gr.Column():
1197
+ weight_decay = gr.Number(value=Hyperparameters.weight_decay, minimum=0, show_label=False, container=False)
1198
  with gr.Row(elem_classes=["srow-row", "last"]):
1199
  with gr.Column(min_width=140):
1200
  gr.HTML(ui.esc("Dropout"))
 
1216
  with gr.Column(min_width=140):
1217
  gr.HTML(ui.esc("Validation fraction"))
1218
  with gr.Column():
1219
+ validation_fraction = gr.Slider(
1220
+ 0.1, 0.4, value=DEFAULT_VALIDATION_FRACTION, step=0.05, show_label=False, container=False
1221
+ )
1222
+ validation_split_note = gr.HTML(_validation_split_note(DEFAULT_VALIDATION_FRACTION))
1223
+ validation_fraction.change(
1224
+ _validation_split_note, inputs=[validation_fraction], outputs=[validation_split_note]
1225
+ )
1226
  with gr.Row(elem_classes=["srow-row"]):
1227
  with gr.Column(min_width=140):
1228
  gr.HTML(ui.esc("Optimizer"))
 
1241
  gr.HTML('<div class="mono" style="margin-bottom:9px">Training result</div>')
1242
  training_status = gr.HTML(_status("Prepare the architecture before training."))
1243
  loss_plot = gr.Plot(label="Loss by epoch", show_label=False)
1244
+ gr.HTML('<div class="mono" style="margin:20px 0 9px">Architecture defaults</div>')
1245
  gr.HTML(
1246
  "".join(
1247
  [
1248
  ui.kv_plain_row("First layer", "trainable W ⊙ M"),
1249
+ ui.kv_plain_row(
1250
+ "Activations",
1251
+ f"{Hyperparameters.biological_activation} / {Hyperparameters.projection_activation} / "
1252
+ f"{Hyperparameters.fusion_activation}",
1253
+ ),
1254
  ui.kv_plain_row("PDI weights", "binary"),
1255
  ui.kv_plain_row("PPI weights", "STRING norm."),
1256
  ui.kv_plain_row("Attention", "GenePT query"),
 
1263
  train_actionbar_note = gr.HTML('<div class="actbar-note">Set hyperparameters, then train.</div>')
1264
  with gr.Row(elem_classes=["actbar-buttons"]):
1265
  train_button = gr.Button(
1266
+ "Train BioLM-NET on ZeroGPU", size="sm", variant="primary",
1267
+ elem_id="train-btn", elem_classes=["actbar-primary-btn"]
1268
  )
1269
 
1270
  # ── Export Artifacts ───────────────────────���─────────────
1271
+ with gr.Column(visible=False, elem_id="page-export", elem_classes=["workspace-page"]) as export_page:
1272
  with gr.Row(elem_classes=["pghd"]):
1273
  gr.HTML(
1274
  ui.title_block_html(
 
1296
  gr.HTML('<div class="actbar-note">Bundle written to a temp file on export · not persisted between Space restarts</div>')
1297
 
1298
  # ── Predict ──────────────────────────────────────────────
1299
+ with gr.Column(visible=False, elem_id="page-predict", elem_classes=["workspace-page"]) as predict_page:
1300
  with gr.Row(elem_classes=["pghd"]):
1301
  gr.HTML(
1302
  ui.title_block_html(
 
1352
  with gr.Column(scale=6, min_width=0):
1353
  gr.HTML('<div class="mono" style="margin-bottom:9px">Predictions · last successful run</div>')
1354
  distribution_panel = gr.HTML(ui.empty_note_html("No predictions yet."))
1355
+ prediction_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT, interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1356
  prediction_download = gr.File(label="Prediction CSV", interactive=False, elem_classes=["file-slot", "btn-block"])
1357
 
1358
  with gr.Row(elem_classes=["actbar-row"]):
 
1360
  with gr.Row(elem_classes=["actbar-buttons"]):
1361
  predict_button = gr.Button(
1362
  "Run inference", size="sm", variant="primary", interactive=False,
1363
+ elem_id="predict-btn", elem_classes=["actbar-primary-btn"],
1364
  )
1365
  prediction_status = gr.HTML(visible=False)
1366
 
1367
  # ── Results ──────────────────────────────────────────────
1368
+ with gr.Column(visible=False, elem_id="page-results", elem_classes=["workspace-page"]) as results_page:
1369
  with gr.Row(elem_classes=["pghd"]):
1370
  gr.HTML(
1371
  ui.title_block_html(
 
1377
  results_empty_note = gr.HTML(ui.empty_note_html("Build the biological architecture, train a model, or run prediction to populate this page."))
1378
  with gr.Row(elem_classes=["results-stat-row"], visible=False) as results_stats_row:
1379
  results_stats = gr.HTML("")
1380
+ # Two rows so the side-by-side panels in each row start at the same height.
1381
+ with gr.Column(visible=False) as results_content:
1382
+ with gr.Row(elem_classes=["results-grid"], equal_height=False):
1383
+ with gr.Column(scale=6, min_width=0):
1384
+ gr.HTML('<div class="mono sect">Training history</div>')
1385
+ results_loss_plot = gr.Plot(show_label=False)
1386
+ with gr.Column(scale=6, min_width=0):
1387
+ gr.HTML('<div class="mono sect">Confusion matrix · validation split</div>')
1388
+ results_confusion = gr.HTML("")
1389
+ with gr.Row(elem_classes=["results-grid"], equal_height=False):
1390
+ with gr.Column(scale=6, min_width=0):
1391
+ gr.HTML('<div class="mono sect">Sparse architecture audit</div>')
1392
+ results_architecture_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT, interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1393
+ with gr.Column(scale=6, min_width=0):
1394
+ gr.HTML('<div class="mono sect">Pathway attention · top retained</div>')
1395
+ results_pathway_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT, interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1396
  with gr.Column(visible=False) as results_tables_bottom:
1397
  gr.HTML('<div class="mono sect sect-spaced">Validation predictions</div>')
1398
+ results_validation_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT, interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1399
  gr.HTML('<div class="mono sect sect-spaced">Predictions and class probabilities</div>')
1400
+ results_prediction_table = gr.Dataframe(max_height=TABLE_MAX_HEIGHT, interactive=False, wrap=False, value=pd.DataFrame(), headers=[""], column_count=(1, "dynamic"))
1401
  with gr.Row(elem_classes=["actbar-row"]):
1402
  gr.HTML(
1403
  '<div class="actbar-note">Validate cohorts, preprocessing and performance before '
 
1674
  ).then(
1675
  refresh_results,
1676
  inputs=[model_state, run_meta_state],
1677
+ outputs=[results_stats, results_stats_row, results_empty_note, results_content, results_loss_plot],
1678
  show_progress="hidden",
1679
  ).then(
1680
  lambda df: gr.update(value=df), inputs=[validation_predictions_state], outputs=[results_validation_table],
 
1750
  show_progress="hidden",
1751
  ).then(
1752
  refresh_results, inputs=[model_state, run_meta_state],
1753
+ outputs=[results_stats, results_stats_row, results_empty_note, results_content, results_loss_plot],
1754
  show_progress="hidden",
1755
  )
1756
 
biolmnet/training.py CHANGED
@@ -1,6 +1,7 @@
1
  from __future__ import annotations
2
 
3
  import copy
 
4
  import random
5
  from dataclasses import asdict, dataclass
6
  from typing import Callable
@@ -25,21 +26,36 @@ from .model import BioLMNet
25
 
26
  ProgressCallback = Callable[[float, str], None]
27
 
 
 
 
28
 
 
 
 
 
 
 
 
 
29
  @dataclass(frozen=True)
30
  class Hyperparameters:
31
- epochs: int = 50
32
- batch_size: int = 16
33
- learning_rate: float = 0.001
34
  weight_decay: float = 0.01
35
  dropout: float = 0.3
36
  projection_dim: int = 64
37
  fusion_dim: int = 12
38
- validation_fraction: float = 0.2
39
  optimizer: str = "Adam"
40
  class_weighting: bool = True
41
  early_stopping_patience: int = 12
42
  seed: int = 42
 
 
 
 
43
 
44
  def validate(self) -> None:
45
  if not 1 <= self.epochs <= 1000:
@@ -54,6 +70,11 @@ class Hyperparameters:
54
  raise ValueError("Validation fraction must be between 0.05 and 0.5.")
55
  if self.optimizer.lower() not in {"adam", "sgd"}:
56
  raise ValueError("Optimizer must be Adam or SGD.")
 
 
 
 
 
57
 
58
 
59
  @dataclass
@@ -86,6 +107,36 @@ def set_reproducible_seed(seed: int) -> None:
86
  torch.cuda.manual_seed_all(seed)
87
 
88
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  def _fit_scaler(values: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
90
  mean = values.mean(axis=0, dtype=np.float64).astype(np.float32)
91
  scale = values.std(axis=0, dtype=np.float64).astype(np.float32)
@@ -121,6 +172,9 @@ def _make_model(
121
  projection_dim=hyperparameters.projection_dim,
122
  fusion_dim=hyperparameters.fusion_dim,
123
  dropout=hyperparameters.dropout,
 
 
 
124
  )
125
 
126
 
@@ -153,18 +207,8 @@ def train(
153
  hyperparameters.validate()
154
  set_reproducible_seed(hyperparameters.seed)
155
  labels = workspace.labels
156
- classes, class_counts = np.unique(labels, return_counts=True)
157
- if class_counts.min() < 2:
158
- raise ValueError(
159
- "Each class needs at least two samples for a stratified train/validation split."
160
- )
161
-
162
- indices = np.arange(len(labels))
163
- train_index, validation_index = train_test_split(
164
- indices,
165
- test_size=hyperparameters.validation_fraction,
166
- random_state=hyperparameters.seed,
167
- stratify=labels,
168
  )
169
  gene_mean, gene_scale = _fit_scaler(
170
  workspace.gene_expression[train_index]
@@ -204,6 +248,7 @@ def train(
204
  optimizer = torch.optim.SGD(
205
  model.parameters(),
206
  lr=hyperparameters.learning_rate,
 
207
  weight_decay=hyperparameters.weight_decay,
208
  )
209
  else:
@@ -357,9 +402,9 @@ def train(
357
  "projection_dim": hyperparameters.projection_dim,
358
  "fusion_dim": hyperparameters.fusion_dim,
359
  "dropout": hyperparameters.dropout,
360
- "biological_activation": "relu",
361
- "projection_activation": "sigmoid",
362
- "fusion_activation": "tanh",
363
  },
364
  }
365
  bundle = ModelBundle(
 
1
  from __future__ import annotations
2
 
3
  import copy
4
+ import math
5
  import random
6
  from dataclasses import asdict, dataclass
7
  from typing import Callable
 
26
 
27
  ProgressCallback = Callable[[float, str], None]
28
 
29
+ # 75% training / 25% validation. The validation partition drives early
30
+ # stopping and best-checkpoint selection, so it is not an untouched test set.
31
+ DEFAULT_VALIDATION_FRACTION = 0.25
32
 
33
+ # Activations BioLMNet accepts.
34
+ ACTIVATIONS = ("relu", "sigmoid", "tanh")
35
+
36
+
37
+ # Defaults follow the Bozdag Lab grid (J. Rifat, 2026-10-17): the configuration that stayed
38
+ # stable across 8 seeds on BRCA (macro F1 0.749 +/- 0.018). The published activations
39
+ # (relu/sigmoid/tanh) collapse at this learning rate and L2; see
40
+ # diagnostics/2026-10-30-lab-hyperparameter-grid.
41
  @dataclass(frozen=True)
42
  class Hyperparameters:
43
+ epochs: int = 200
44
+ batch_size: int = 32
45
+ learning_rate: float = 0.01
46
  weight_decay: float = 0.01
47
  dropout: float = 0.3
48
  projection_dim: int = 64
49
  fusion_dim: int = 12
50
+ validation_fraction: float = DEFAULT_VALIDATION_FRACTION
51
  optimizer: str = "Adam"
52
  class_weighting: bool = True
53
  early_stopping_patience: int = 12
54
  seed: int = 42
55
+ biological_activation: str = "tanh"
56
+ projection_activation: str = "relu"
57
+ fusion_activation: str = "tanh"
58
+ sgd_momentum: float = 0.03
59
 
60
  def validate(self) -> None:
61
  if not 1 <= self.epochs <= 1000:
 
70
  raise ValueError("Validation fraction must be between 0.05 and 0.5.")
71
  if self.optimizer.lower() not in {"adam", "sgd"}:
72
  raise ValueError("Optimizer must be Adam or SGD.")
73
+ for name in ("biological_activation", "projection_activation", "fusion_activation"):
74
+ if getattr(self, name) not in ACTIVATIONS:
75
+ raise ValueError(f"{name.replace('_', ' ').capitalize()} must be one of {', '.join(ACTIVATIONS)}.")
76
+ if not 0 <= self.sgd_momentum < 1:
77
+ raise ValueError("SGD momentum must be in [0, 1).")
78
 
79
 
80
  @dataclass
 
107
  torch.cuda.manual_seed_all(seed)
108
 
109
 
110
+ def split_train_validation(
111
+ labels: np.ndarray, validation_fraction: float, seed: int
112
+ ) -> tuple[np.ndarray, np.ndarray]:
113
+ """Stratified sample-index split shared by both omics and the labels."""
114
+ classes, class_counts = np.unique(labels, return_counts=True)
115
+ if class_counts.min() < 2:
116
+ raise ValueError(
117
+ "Each class needs at least two samples for a stratified train/validation split."
118
+ )
119
+ sample_count = len(labels)
120
+ validation_count = math.ceil(validation_fraction * sample_count)
121
+ train_count = sample_count - validation_count
122
+ if min(validation_count, train_count) < len(classes):
123
+ raise ValueError(
124
+ f"A validation fraction of {validation_fraction:.2f} on {sample_count} samples "
125
+ f"gives {train_count} training and {validation_count} validation samples; "
126
+ f"each partition needs at least one sample per class ({len(classes)} classes) "
127
+ "for a stratified split. Adjust the validation fraction or add samples."
128
+ )
129
+
130
+ indices = np.arange(sample_count)
131
+ train_index, validation_index = train_test_split(
132
+ indices,
133
+ test_size=validation_fraction,
134
+ random_state=seed,
135
+ stratify=labels,
136
+ )
137
+ return train_index, validation_index
138
+
139
+
140
  def _fit_scaler(values: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
141
  mean = values.mean(axis=0, dtype=np.float64).astype(np.float32)
142
  scale = values.std(axis=0, dtype=np.float64).astype(np.float32)
 
172
  projection_dim=hyperparameters.projection_dim,
173
  fusion_dim=hyperparameters.fusion_dim,
174
  dropout=hyperparameters.dropout,
175
+ biological_activation=hyperparameters.biological_activation,
176
+ projection_activation=hyperparameters.projection_activation,
177
+ fusion_activation=hyperparameters.fusion_activation,
178
  )
179
 
180
 
 
207
  hyperparameters.validate()
208
  set_reproducible_seed(hyperparameters.seed)
209
  labels = workspace.labels
210
+ train_index, validation_index = split_train_validation(
211
+ labels, hyperparameters.validation_fraction, hyperparameters.seed
 
 
 
 
 
 
 
 
 
 
212
  )
213
  gene_mean, gene_scale = _fit_scaler(
214
  workspace.gene_expression[train_index]
 
248
  optimizer = torch.optim.SGD(
249
  model.parameters(),
250
  lr=hyperparameters.learning_rate,
251
+ momentum=hyperparameters.sgd_momentum,
252
  weight_decay=hyperparameters.weight_decay,
253
  )
254
  else:
 
402
  "projection_dim": hyperparameters.projection_dim,
403
  "fusion_dim": hyperparameters.fusion_dim,
404
  "dropout": hyperparameters.dropout,
405
+ "biological_activation": hyperparameters.biological_activation,
406
+ "projection_activation": hyperparameters.projection_activation,
407
+ "fusion_activation": hyperparameters.fusion_activation,
408
  },
409
  }
410
  bundle = ModelBundle(
biolmnet/ui/styles.py CHANGED
@@ -236,6 +236,12 @@ body, .gradio-container {
236
  flex: 1 1 auto !important;
237
  width: calc(100% - 236px) !important;
238
  }
 
 
 
 
 
 
239
 
240
  /* rail row (overlay: visible .rr under an invisible full-size button) */
241
  .rail-row-wrap { position: relative; }
@@ -312,7 +318,11 @@ body, .gradio-container {
312
  .sect { margin: 0 0 9px; padding-bottom: 8px; border-bottom: 1px solid var(--color-text); }
313
  .sect-spaced { margin-top: 26px !important; }
314
 
315
- .actbar-row { margin-top: auto !important; align-items: center !important; justify-content: space-between !important; flex-wrap: nowrap !important; gap: 20px !important; padding: 16px 30px !important; border-top: 1px solid var(--color-divider) !important; background: color-mix(in srgb, var(--color-text) 3%, transparent) !important; }
 
 
 
 
316
  .actbar-note { font: 500 10px/1.4 var(--font-mono); color: var(--color-muted-2); }
317
  .actbar-note.error { color: var(--color-error); }
318
  .actbar-buttons {
@@ -424,6 +434,46 @@ body, .gradio-container {
424
  .strip.error .strip-kicker { color: var(--color-error); }
425
  .strip .strip-body { margin-top: 4px; font-size: 12.5px; line-height: 1.5; }
426
  .empty-note { padding: 14px 0; color: var(--color-muted); font-size: 13px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
427
 
428
  /* ── slots (one-line gr.File) ─────────────────────────────────────────── */
429
  .file-slot .file-preview { border: 1px solid var(--color-divider) !important; border-radius: var(--radius-md); background: #fff; font-size: 12.5px !important; }
@@ -571,7 +621,9 @@ body, .gradio-container {
571
  .gradio-container table { color: var(--color-text) !important; font-size: 12.5px !important; }
572
  .gradio-container thead th {
573
  font: 500 10px/1 var(--font-mono) !important; letter-spacing: .1em; text-transform: uppercase;
574
- color: var(--color-muted-2) !important; background: transparent !important;
 
 
575
  border-bottom: 1px solid var(--color-divider) !important;
576
  }
577
  .gradio-container tbody td { border-bottom: 1px solid var(--color-row-rule) !important; }
@@ -723,13 +775,223 @@ body, .gradio-container {
723
 
724
  /* ── responsive ───────────────────────────────────────────────────────── */
725
  @media (max-width: 1000px) {
726
- .app-shell { display: block !important; }
727
- .rail-col { min-width: 100% !important; max-width: 100% !important; }
728
- .rail { position: relative; min-height: auto; border-right: 0; border-bottom: 1px solid var(--color-divider); }
729
- .stage-grid, .results-grid { grid-template-columns: 1fr !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
730
  }
731
  @media (max-width: 620px) {
732
- .pghd h2 { font-size: 26px; }
733
- .stat-grid, .metric-strip { grid-template-columns: repeat(2, minmax(0,1fr)) !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734
  }
735
  """
 
236
  flex: 1 1 auto !important;
237
  width: calc(100% - 236px) !important;
238
  }
239
+ .workspace-page,
240
+ .workspace-page > .form {
241
+ min-height: calc(100vh - 45px) !important;
242
+ display: flex !important;
243
+ flex-direction: column !important;
244
+ }
245
 
246
  /* rail row (overlay: visible .rr under an invisible full-size button) */
247
  .rail-row-wrap { position: relative; }
 
318
  .sect { margin: 0 0 9px; padding-bottom: 8px; border-bottom: 1px solid var(--color-text); }
319
  .sect-spaced { margin-top: 26px !important; }
320
 
321
+ /* Gradio's container sets overflow: hidden, which disables position: sticky inside it;
322
+ clip still clips stray content without breaking the sticky action bar below. */
323
+ .gradio-container { overflow: clip !important; }
324
+ /* Pinned to the bottom of the visible frame so the page's main action never needs a scroll to find. */
325
+ .actbar-row { position: sticky !important; bottom: 0; z-index: 20; margin-top: auto !important; align-items: center !important; justify-content: space-between !important; flex-wrap: nowrap !important; gap: 20px !important; padding: 16px 30px !important; border-top: 1px solid var(--color-divider) !important; background: color-mix(in srgb, var(--color-text) 3%, var(--color-bg)) !important; box-shadow: 0 -8px 18px -14px color-mix(in srgb, var(--color-text) 45%, transparent); }
326
  .actbar-note { font: 500 10px/1.4 var(--font-mono); color: var(--color-muted-2); }
327
  .actbar-note.error { color: var(--color-error); }
328
  .actbar-buttons {
 
434
  .strip.error .strip-kicker { color: var(--color-error); }
435
  .strip .strip-body { margin-top: 4px; font-size: 12.5px; line-height: 1.5; }
436
  .empty-note { padding: 14px 0; color: var(--color-muted); font-size: 13px; }
437
+ .field-hint { margin-top: 6px; color: var(--color-muted); font-size: 12px; line-height: 1.4; }
438
+
439
+ @media (min-width: 901px) and (max-height: 820px) {
440
+ .topbar {
441
+ padding-top: 10px;
442
+ padding-bottom: 10px;
443
+ }
444
+ .pghd {
445
+ padding-top: 18px;
446
+ }
447
+ .pghd h2 {
448
+ font-size: 31px;
449
+ }
450
+ .pghd .desc {
451
+ margin-top: 4px;
452
+ line-height: 1.35;
453
+ }
454
+ .stage-grid {
455
+ padding-top: 16px !important;
456
+ gap: 22px !important;
457
+ }
458
+ .data-workspace-page .section-heading-row {
459
+ margin-bottom: 8px !important;
460
+ }
461
+ .data-workspace-page .sect-spaced {
462
+ margin-top: 18px !important;
463
+ }
464
+ .data-workspace-page .srow-row {
465
+ min-height: 42px;
466
+ }
467
+ .data-workspace-page .dataframe,
468
+ .data-workspace-page .table-wrap {
469
+ max-height: 118px !important;
470
+ overflow: auto !important;
471
+ }
472
+ .actbar-row {
473
+ padding-top: 11px !important;
474
+ padding-bottom: 11px !important;
475
+ }
476
+ }
477
 
478
  /* ── slots (one-line gr.File) ─────────────────────────────────────────── */
479
  .file-slot .file-preview { border: 1px solid var(--color-divider) !important; border-radius: var(--radius-md); background: #fff; font-size: 12.5px !important; }
 
621
  .gradio-container table { color: var(--color-text) !important; font-size: 12.5px !important; }
622
  .gradio-container thead th {
623
  font: 500 10px/1 var(--font-mono) !important; letter-spacing: .1em; text-transform: uppercase;
624
+ color: var(--color-muted-2) !important;
625
+ /* opaque so rows scrolling under the pinned header stay hidden */
626
+ background: var(--color-bg) !important;
627
  border-bottom: 1px solid var(--color-divider) !important;
628
  }
629
  .gradio-container tbody td { border-bottom: 1px solid var(--color-row-rule) !important; }
 
775
 
776
  /* ── responsive ───────────────────────────────────────────────────────── */
777
  @media (max-width: 1000px) {
778
+ .app-shell {
779
+ display: block !important;
780
+ min-height: 100vh !important;
781
+ }
782
+ .rail-col {
783
+ min-width: 100% !important;
784
+ max-width: 100% !important;
785
+ width: 100% !important;
786
+ flex: none !important;
787
+ }
788
+ .rail {
789
+ position: sticky;
790
+ top: 0;
791
+ z-index: 40;
792
+ height: auto !important;
793
+ min-height: 0 !important;
794
+ padding: 12px 12px 10px;
795
+ border-right: 0;
796
+ border-bottom: 1px solid var(--color-divider);
797
+ background: color-mix(in srgb, var(--color-bg) 96%, #fff);
798
+ }
799
+ .rail-brand {
800
+ display: flex;
801
+ align-items: baseline;
802
+ gap: 8px;
803
+ padding: 0 4px 9px;
804
+ }
805
+ .rail-brand .name {
806
+ font-size: 22px;
807
+ }
808
+ .rail-brand .mono {
809
+ margin-top: 0 !important;
810
+ font-size: 8.5px;
811
+ }
812
+ .rail-kicker,
813
+ .rail-spacer {
814
+ display: none !important;
815
+ }
816
+ .rail-nav {
817
+ display: grid !important;
818
+ grid-auto-flow: column;
819
+ grid-auto-columns: minmax(154px, max-content);
820
+ gap: 6px;
821
+ overflow-x: auto;
822
+ overscroll-behavior-x: contain;
823
+ padding: 0 2px 2px;
824
+ scrollbar-width: none;
825
+ }
826
+ .rail-nav::-webkit-scrollbar {
827
+ display: none;
828
+ }
829
+ .rail-row-wrap {
830
+ min-width: 154px !important;
831
+ }
832
+ .rr {
833
+ min-height: 36px;
834
+ padding: 8px 9px 8px 8px;
835
+ gap: 8px;
836
+ border-left-width: 2px;
837
+ font-size: 12.5px;
838
+ white-space: nowrap;
839
+ }
840
+ .rr .rn {
841
+ width: 19px;
842
+ height: 19px;
843
+ font-size: 9.5px;
844
+ }
845
+ .rr .st {
846
+ font-size: 8px;
847
+ letter-spacing: .08em;
848
+ }
849
+ .workspace-col {
850
+ width: 100% !important;
851
+ min-width: 0 !important;
852
+ }
853
+ .topbar {
854
+ padding: 10px 116px 10px 14px;
855
+ gap: 10px;
856
+ }
857
+ .topbar > .num {
858
+ display: none;
859
+ }
860
+ .intro-open-btn {
861
+ right: 12px;
862
+ top: 5px;
863
+ }
864
+ .pghd {
865
+ padding: 20px 14px 0;
866
+ gap: 10px;
867
+ }
868
+ .pghd h2 {
869
+ font-size: 30px;
870
+ }
871
+ .pghd .desc {
872
+ max-width: none;
873
+ font-size: 13px;
874
+ }
875
+ .stage-grid,
876
+ .results-grid {
877
+ display: grid !important;
878
+ grid-template-columns: minmax(0, 1fr) !important;
879
+ padding: 20px 14px 0 !important;
880
+ gap: 22px !important;
881
+ }
882
+ .stage-grid > *,
883
+ .results-grid > * {
884
+ min-width: 0 !important;
885
+ max-width: 100% !important;
886
+ width: 100% !important;
887
+ }
888
+ .results-stat-row,
889
+ .stat-grid {
890
+ padding: 20px 14px 0 !important;
891
+ }
892
+ .metric-strip {
893
+ grid-template-columns: repeat(2, minmax(0, 1fr));
894
+ }
895
+ .field-pair {
896
+ display: grid !important;
897
+ grid-template-columns: minmax(0, 1fr) !important;
898
+ gap: 12px !important;
899
+ }
900
+ .srow-row {
901
+ display: grid !important;
902
+ grid-template-columns: minmax(118px, 34%) minmax(0, 1fr) !important;
903
+ gap: 12px !important;
904
+ padding: 8px 0 !important;
905
+ }
906
+ .srow-row > * {
907
+ min-width: 0 !important;
908
+ }
909
+ .actbar-row {
910
+ align-items: flex-start !important;
911
+ flex-wrap: wrap !important;
912
+ padding: 12px 14px !important;
913
+ gap: 12px !important;
914
+ }
915
+ .actbar-buttons {
916
+ width: 100% !important;
917
+ flex: 1 1 100% !important;
918
+ justify-content: flex-start !important;
919
+ flex-wrap: wrap !important;
920
+ }
921
+ .actbar-buttons button {
922
+ min-height: 34px !important;
923
+ padding: 0 12px !important;
924
+ }
925
+ .gradio-container .table-wrap,
926
+ .gradio-container .dataframe,
927
+ .gradio-container .wrap.svelte-1ipelgc,
928
+ .tbl {
929
+ max-width: 100%;
930
+ overflow-x: auto;
931
+ }
932
+ .intro-card {
933
+ width: min(556px, calc(100vw - 28px)) !important;
934
+ padding: 22px 20px 18px !important;
935
+ }
936
  }
937
  @media (max-width: 620px) {
938
+ .topbar {
939
+ padding-right: 112px;
940
+ }
941
+ .topbar .mono {
942
+ font-size: 8.5px;
943
+ letter-spacing: .06em;
944
+ }
945
+ .pghd h2 {
946
+ font-size: 27px;
947
+ }
948
+ .section-heading-row {
949
+ display: block !important;
950
+ }
951
+ .section-heading-row .mono {
952
+ margin-top: 6px;
953
+ text-align: left !important;
954
+ }
955
+ .seg-radio label {
956
+ padding: 7px 8px !important;
957
+ font-size: 12px !important;
958
+ }
959
+ .stat-grid,
960
+ .metric-strip {
961
+ grid-template-columns: 1fr !important;
962
+ }
963
+ .srow-row {
964
+ grid-template-columns: 1fr !important;
965
+ gap: 6px !important;
966
+ }
967
+ .srow-value-row {
968
+ justify-content: flex-start !important;
969
+ }
970
+ .rr {
971
+ font-size: 12px;
972
+ }
973
+ .rr .st {
974
+ display: none;
975
+ }
976
+ .rail-nav {
977
+ grid-auto-columns: minmax(126px, max-content);
978
+ }
979
+ .rail-row-wrap {
980
+ min-width: 126px !important;
981
+ }
982
+ .intro-card h4 {
983
+ font-size: 24px;
984
+ }
985
+ .intro-card p {
986
+ font-size: 13px;
987
+ }
988
+ .intro-card-foot {
989
+ align-items: flex-start !important;
990
+ }
991
+ .intro-card-actions {
992
+ justify-content: flex-start !important;
993
+ flex: 1 1 100% !important;
994
+ gap: 9px !important;
995
+ }
996
  }
997
  """
diagnostics/2026-10-16-brca-training-collapse/README.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # BRCA training collapse diagnostics (2026-10-16)
2
+
3
+ These diagnostic runs explain why BRCA training in the Workbench predicted one class for every sample. None of this is production code: the scripts call the project's code unchanged and only override `Hyperparameters` fields.
4
+
5
+ ## Layout
6
+
7
+ - `scripts/`
8
+ - `diagnose.py`: builds the BRCA workspace through `app.prepare_workspace`, then reports class counts, class weights, mask sparsity, per-epoch predictions and activations.
9
+ - `sweep.py`: weight-decay sweep at seed 42.
10
+ - `seeds.py`: 8 seeds at weight_decay 1e-4 and 0.01.
11
+ - `epochs.py <limit>`: 8 seeds at weight_decay 1e-4 with the given epoch limit.
12
+ - `epochs_summary.py`: summarises the three `epochs_*.json` files.
13
+ - `results/`: raw outputs from the runs on 2026-10-16.
14
+ - `brca_weighted.txt`: full `diagnose.py` output with the Workbench defaults.
15
+ - `weight_decay_sweep.txt`: weight-decay sweep.
16
+ - `paper_config_sgd.txt`: the notebook's SGD configuration.
17
+ - `effective_fanin_init.txt`: initialisation experiment, run inline with a monkeypatch.
18
+ - `seed_runs.csv` and `seed_runs.txt`: 8 seeds × 2 weight-decay values.
19
+ - `epochs_{50,100,200}.json`: per-run histories.
20
+ - `epochs_runs.csv`: per-run summary table.
21
+ - `upstream/`: copies of the bozdaglab/BioLM-NET notebooks used for the comparison.
22
+
23
+ ## Reproduce
24
+
25
+ Run from the repository root with the project venv:
26
+
27
+ ```bash
28
+ D=diagnostics/2026-10-16-brca-training-collapse/scripts
29
+ PYTHONPATH=$PWD .venv/bin/python $D/diagnose.py BRCA 1 50 # downloads data; caches workspace_BRCA.pkl (~37 MB) next to the scripts
30
+ PYTHONPATH=$PWD .venv/bin/python $D/seeds.py
31
+ for L in 50 100 200; do PYTHONPATH=$PWD .venv/bin/python $D/epochs.py $L; done
32
+ .venv/bin/python $D/epochs_summary.py
33
+ ```
34
+
35
+ `workspace_BRCA.pkl` is a regenerable cache. Don't commit it.
36
+
37
+ Note: `epochs.py` and `epochs_summary.py` write their output files next to the scripts.
diagnostics/2026-10-16-brca-training-collapse/results/brca_weighted.txt ADDED
@@ -0,0 +1,147 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ======== RAW FILES (BRCA) ========
3
+ gene shape=(875, 999) first cols=['A2ML1', 'ABAT', 'ABCA13']
4
+ dna shape=(875, 1000) first cols=['A2LD1', 'A2ML1', 'ABAT']
5
+ labels shape=(875, 1) first cols=['label']
6
+ raw label counts: {3.0: 436, 4.0: 147, 1.0: 131, 0.0: 115, 2.0: 46}
7
+ gene_pathways shape=(5614, 2) first cols=['SYMBOL', 'PathwayID']
8
+ dna_pathways shape=(5410, 2) first cols=['SYMBOL', 'PathwayID']
9
+
10
+ ======== WORKSPACE ========
11
+ warnings: []
12
+ samples: 875 classes: ['0.0', '1.0', '2.0', '3.0', '4.0']
13
+ full class counts: {'0.0': 115, '1.0': 131, '2.0': 46, '3.0': 436, '4.0': 147}
14
+ gene: shape=(875, 999) min=0 max=1 mean=0.408 zero-var cols=0
15
+ dna: shape=(875, 1000) min=0 max=1 mean=0.52 zero-var cols=0
16
+
17
+ ======== MASKS ========
18
+ gene: bio mask (999, 1008) density=0.2328% nnz=2,344 PDI=369 PPI=3559
19
+ inputs with 0 outgoing edges: 695/999 hidden genes in-degree median=1 max=32
20
+ nonzero mask values: min=0.988 median=0.998 max=1.000
21
+ pathway mask (1008, 133): hidden genes in no pathway=0/1008 genes/pathway median=35
22
+ dna: bio mask (1000, 840) density=0.1423% nnz=1,195 PDI=158 PPI=1566
23
+ inputs with 0 outgoing edges: 792/1000 hidden genes in-degree median=1 max=6
24
+ nonzero mask values: min=0.988 median=0.998 max=1.000
25
+ pathway mask (840, 142): hidden genes in no pathway=0/840 genes/pathway median=32
26
+
27
+ ======== TRAINING Hyperparameters(epochs=50, batch_size=16, learning_rate=0.001, weight_decay=0.01, dropout=0.3, projection_dim=64, fusion_dim=12, validation_fraction=0.25, optimizer='Adam', class_weighting=True, early_stopping_patience=12, seed=42) ========
28
+ training class counts: {'0.0': 86, '1.0': 98, '2.0': 35, '3.0': 327, '4.0': 110}
29
+ validation class counts: {'0.0': 29, '1.0': 33, '2.0': 11, '3.0': 109, '4.0': 37}
30
+
31
+ ======== CLASS WEIGHTS ========
32
+ weights passed to CrossEntropyLoss: [array([1.526, 1.339, 3.749, 0.401, 1.193], dtype=float32)]
33
+ weight attribute seen inside loss.forward: [1.526 1.339 3.749 0.401 1.193]
34
+ loss.forward calls: 1271
35
+
36
+ ======== HISTORY ========
37
+ epoch training_loss validation_loss validation_accuracy validation_f1_macro
38
+ 1.0 1.642350 1.601534 0.150685 0.052381
39
+ 2.0 1.629108 1.576027 0.150685 0.052381
40
+ 3.0 1.624403 1.574691 0.497717 0.132927
41
+ 4.0 1.613764 1.588255 0.168950 0.057813
42
+ 5.0 1.628252 1.574925 0.497717 0.132927
43
+ 6.0 1.620439 1.678558 0.168950 0.057813
44
+ 7.0 1.626308 1.564224 0.497717 0.132927
45
+ 8.0 1.618647 1.550071 0.497717 0.132927
46
+ 9.0 1.617843 1.545797 0.497717 0.132927
47
+ 10.0 1.614722 1.622764 0.168950 0.057813
48
+ 11.0 1.616705 1.577437 0.497717 0.132927
49
+ 12.0 1.619570 1.585625 0.168950 0.057813
50
+ 13.0 1.617500 1.541410 0.497717 0.132927
51
+ 14.0 1.619081 1.613365 0.132420 0.046774
52
+ 15.0 1.616881 1.572324 0.497717 0.132927
53
+ 16.0 1.616340 1.557646 0.497717 0.132927
54
+ 17.0 1.615816 1.569439 0.497717 0.132927
55
+ 18.0 1.613130 1.558936 0.497717 0.132927
56
+ 19.0 1.611621 1.538758 0.497717 0.132927
57
+ 20.0 1.610360 1.565398 0.497717 0.132927
58
+ 21.0 1.607601 1.592779 0.132420 0.046774
59
+ 22.0 1.612177 1.580287 0.497717 0.132927
60
+ 23.0 1.614574 1.590151 0.168950 0.057813
61
+ 24.0 1.612279 1.569710 0.497717 0.132927
62
+ 25.0 1.607981 1.565809 0.497717 0.132927
63
+ 26.0 1.611930 1.564028 0.497717 0.132927
64
+ 27.0 1.612068 1.584551 0.132420 0.046774
65
+ 28.0 1.613480 1.597781 0.132420 0.046774
66
+ 29.0 1.613598 1.575038 0.497717 0.132927
67
+ 30.0 1.617835 1.584576 0.497717 0.132927
68
+ 31.0 1.610216 1.583199 0.497717 0.132927
69
+
70
+ ======== PER-EPOCH VALIDATION PREDICTION COUNTS ========
71
+ epoch 1: counts=[0, 219, 0, 0, 0] mean max-prob=0.205
72
+ epoch 2: counts=[0, 219, 0, 0, 0] mean max-prob=0.240
73
+ epoch 3: counts=[0, 0, 0, 219, 0] mean max-prob=0.216
74
+ epoch 4: counts=[0, 0, 0, 0, 219] mean max-prob=0.215
75
+ epoch 5: counts=[0, 0, 0, 219, 0] mean max-prob=0.216
76
+ epoch 6: counts=[0, 0, 0, 0, 219] mean max-prob=0.234
77
+ epoch 7: counts=[0, 0, 0, 219, 0] mean max-prob=0.223
78
+ epoch 8: counts=[0, 0, 0, 219, 0] mean max-prob=0.236
79
+ epoch 9: counts=[0, 0, 0, 219, 0] mean max-prob=0.239
80
+ epoch 10: counts=[0, 0, 0, 0, 219] mean max-prob=0.219
81
+ epoch 11: counts=[0, 0, 0, 219, 0] mean max-prob=0.212
82
+ epoch 12: counts=[0, 0, 0, 0, 219] mean max-prob=0.208
83
+ epoch 13: counts=[0, 0, 0, 219, 0] mean max-prob=0.232
84
+ epoch 14: counts=[219, 0, 0, 0, 0] mean max-prob=0.245
85
+ epoch 15: counts=[0, 0, 0, 219, 0] mean max-prob=0.216
86
+ epoch 16: counts=[0, 0, 0, 219, 0] mean max-prob=0.226
87
+ epoch 17: counts=[0, 0, 0, 219, 0] mean max-prob=0.218
88
+ epoch 18: counts=[0, 0, 0, 219, 0] mean max-prob=0.224
89
+ epoch 19: counts=[0, 0, 0, 219, 0] mean max-prob=0.234
90
+ epoch 20: counts=[0, 0, 0, 219, 0] mean max-prob=0.221
91
+ epoch 21: counts=[219, 0, 0, 0, 0] mean max-prob=0.214
92
+ epoch 22: counts=[0, 0, 0, 219, 0] mean max-prob=0.210
93
+ epoch 23: counts=[0, 0, 0, 0, 219] mean max-prob=0.209
94
+ epoch 24: counts=[0, 0, 0, 219, 0] mean max-prob=0.218
95
+ epoch 25: counts=[0, 0, 0, 219, 0] mean max-prob=0.223
96
+ epoch 26: counts=[0, 0, 0, 219, 0] mean max-prob=0.223
97
+ epoch 27: counts=[219, 0, 0, 0, 0] mean max-prob=0.212
98
+ epoch 28: counts=[219, 0, 0, 0, 0] mean max-prob=0.215
99
+ epoch 29: counts=[0, 0, 0, 219, 0] mean max-prob=0.217
100
+ epoch 30: counts=[0, 0, 0, 219, 0] mean max-prob=0.212
101
+ epoch 31: counts=[0, 0, 0, 219, 0] mean max-prob=0.211
102
+
103
+ ======== FINAL (best checkpoint) VALIDATION ========
104
+ prediction counts: {'0.0': 0, '1.0': 0, '2.0': 0, '3.0': 219, '4.0': 0}
105
+ average predicted probability per class: {'0.0': 0.19499999284744263, '1.0': 0.210999995470047, '2.0': 0.16200000047683716, '3.0': 0.23399999737739563, '4.0': 0.1979999989271164}
106
+ training class prior: {'0.0': 0.131, '1.0': 0.149, '2.0': 0.053, '3.0': 0.498, '4.0': 0.168}
107
+ std of each class prob across samples: [0.0, 0.0, 0.0, 0.0, 0.0]
108
+ max-prob: min/median/max = [0.234, 0.234, 0.234]
109
+ margin top1-top2: median = 0.0232
110
+ first 8 validation rows (prob vectors):
111
+ [[0.195 0.211 0.162 0.234 0.198]
112
+ [0.195 0.211 0.162 0.234 0.198]
113
+ [0.195 0.211 0.162 0.234 0.198]
114
+ [0.195 0.211 0.162 0.234 0.198]
115
+ [0.195 0.211 0.162 0.234 0.198]
116
+ [0.195 0.211 0.162 0.234 0.198]
117
+ [0.195 0.211 0.162 0.234 0.198]
118
+ [0.195 0.211 0.162 0.234 0.198]]
119
+ metrics: {'validation_loss': 1.5387582778930664, 'accuracy': 0.4977168949771689, 'f1_macro': 0.1329268292682927, 'f1_weighted': 0.3307996436128745, 'precision_macro': 0.09954337899543378, 'recall_macro': 0.2, 'epochs_completed': 31, 'training_samples': 656, 'validation_samples': 219}
120
+ confusion:
121
+ [[ 0 0 0 29 0]
122
+ [ 0 0 0 33 0]
123
+ [ 0 0 0 11 0]
124
+ [ 0 0 0 109 0]
125
+ [ 0 0 0 37 0]]
126
+
127
+ ======== LOGITS / ACTIVATIONS (best checkpoint, validation rows) ========
128
+ gene: bio pre-act mean=-4.48e-19 std=1.18e-16 dead ReLU units=6.6% pathway std across samples=4.02e-17 sigmoid saturated(<.02|>.98)=0.0% proj std across samples=0
129
+ dna: bio pre-act mean=-2.92e-21 std=1.15e-18 dead ReLU units=9.4% pathway std across samples=2.91e-19 sigmoid saturated(<.02|>.98)=0.0% proj std across samples=0
130
+ fusion tanh saturated(|x|>.98)=0.0% fused std across samples=0
131
+ logits mean per class: [-0.057 0.019 -0.246 0.124 -0.042] logits std across samples per class: [0. 0. 0. 0. 0.]
132
+
133
+ ======== PARAMETER CHANGE FROM INIT ========
134
+ gene_branch.biological.weight rel change=1.0000
135
+ gene_branch.biological.bias rel change=1.0000
136
+ gene_branch.pathway_attention.query rel change=1.0000
137
+ gene_branch.projection.weight rel change=1.0000
138
+ gene_branch.projection.bias rel change=0.9941
139
+ dna_branch.biological.weight rel change=1.0000
140
+ dna_branch.biological.bias rel change=1.0000
141
+ dna_branch.pathway_attention.query rel change=1.0000
142
+ dna_branch.projection.weight rel change=1.0000
143
+ dna_branch.projection.bias rel change=0.9789
144
+ fusion.weight rel change=0.3960
145
+ fusion.bias rel change=0.2371
146
+ output.weight rel change=0.4115
147
+ output.bias rel change=0.2730
diagnostics/2026-10-16-brca-training-collapse/results/effective_fanin_init.txt ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Diagnostic only, NOT committed to biolmnet/model.py.
2
+ BioMaskedLinear.reset_parameters was monkeypatched inside the run to use LeCun-uniform
3
+ initialisation on each hidden gene's real (masked) in-degree, with zero bias:
4
+
5
+ def reset(self):
6
+ fan_in = self.mask.gt(0).sum(1).clamp(min=1).float() # real in-degree per hidden gene
7
+ bound = (3.0 / fan_in).sqrt().unsqueeze(1)
8
+ with torch.no_grad():
9
+ self.weight.copy_((torch.rand_like(self.weight) * 2 - 1) * bound)
10
+ if self.bias is not None: self.bias.zero_()
11
+ biolmnet.model.BioMaskedLinear.reset_parameters = reset
12
+
13
+ BRCA, seed 42, 75/25 split, class weighting on:
14
+
15
+ eff-fan-in init + current default (Adam wd=.01) acc=0.680 f1_macro=0.493 epochs=50 pred=[0, 34, 10, 159, 16] prob std=0.1422
16
+ eff-fan-in init + paper (SGD lr=.01 wd=.01 200ep) acc=0.511 f1_macro=0.497 epochs=148 pred=[80, 32, 9, 70, 28] prob std=0.1255
diagnostics/2026-10-16-brca-training-collapse/results/epochs_100.json ADDED
The diff for this file is too large to render. See raw diff
 
diagnostics/2026-10-16-brca-training-collapse/results/epochs_100.log ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ limit=100 seed=0 done: best=49/61 acc=0.763 f1m=0.724
2
+ limit=100 seed=1 done: best=50/62 acc=0.772 f1m=0.744
3
+ limit=100 seed=2 done: best=50/62 acc=0.790 f1m=0.761
4
+ limit=100 seed=3 done: best=71/83 acc=0.740 f1m=0.719
5
+ limit=100 seed=7 done: best=32/44 acc=0.772 f1m=0.752
6
+ limit=100 seed=42 done: best=38/50 acc=0.767 f1m=0.747
7
+ limit=100 seed=123 done: best=37/49 acc=0.763 f1m=0.740
8
+ limit=100 seed=2024 done: best=41/53 acc=0.758 f1m=0.737
9
+ limit=100 ALL DONE
diagnostics/2026-10-16-brca-training-collapse/results/epochs_200.json ADDED
The diff for this file is too large to render. See raw diff
 
diagnostics/2026-10-16-brca-training-collapse/results/epochs_200.log ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ limit=200 seed=0 done: best=49/61 acc=0.763 f1m=0.724
2
+ limit=200 seed=1 done: best=50/62 acc=0.772 f1m=0.744
3
+ limit=200 seed=2 done: best=50/62 acc=0.790 f1m=0.761
4
+ limit=200 seed=3 done: best=71/83 acc=0.740 f1m=0.719
5
+ limit=200 seed=7 done: best=32/44 acc=0.772 f1m=0.752
6
+ limit=200 seed=42 done: best=38/50 acc=0.767 f1m=0.747
7
+ limit=200 seed=123 done: best=37/49 acc=0.763 f1m=0.740
8
+ limit=200 seed=2024 done: best=41/53 acc=0.758 f1m=0.737
9
+ limit=200 ALL DONE
diagnostics/2026-10-16-brca-training-collapse/results/epochs_50.json ADDED
@@ -0,0 +1,3105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "limit": 50,
4
+ "seed": 0,
5
+ "hyperparameters": {
6
+ "epochs": 50,
7
+ "batch_size": 16,
8
+ "learning_rate": 0.001,
9
+ "weight_decay": 0.0001,
10
+ "dropout": 0.3,
11
+ "projection_dim": 64,
12
+ "fusion_dim": 12,
13
+ "validation_fraction": 0.25,
14
+ "optimizer": "Adam",
15
+ "class_weighting": true,
16
+ "early_stopping_patience": 12,
17
+ "seed": 0
18
+ },
19
+ "epochs_completed": 50,
20
+ "best_epoch": 49,
21
+ "early_stopped": false,
22
+ "hit_max": true,
23
+ "train_loss_at_best": 0.6369955467014778,
24
+ "val_loss_at_best": 0.6139159798622131,
25
+ "accuracy": 0.7625570776255708,
26
+ "f1_macro": 0.7241977003359705,
27
+ "f1_weighted": 0.7688921750314536,
28
+ "recall": {
29
+ "0.0": 0.7931034482758621,
30
+ "1.0": 0.8181818181818182,
31
+ "2.0": 0.5454545454545454,
32
+ "3.0": 0.8073394495412844,
33
+ "4.0": 0.6216216216216216
34
+ },
35
+ "pred_counts": {
36
+ "0.0": 47,
37
+ "1.0": 28,
38
+ "2.0": 8,
39
+ "3.0": 105,
40
+ "4.0": 31
41
+ },
42
+ "collapsed": false,
43
+ "history": [
44
+ {
45
+ "epoch": 1.0,
46
+ "training_loss": 1.6172636282153245,
47
+ "validation_loss": 1.5241289138793945,
48
+ "validation_accuracy": 0.4977168949771689,
49
+ "validation_f1_macro": 0.1329268292682927
50
+ },
51
+ {
52
+ "epoch": 2.0,
53
+ "training_loss": 1.5862088872165214,
54
+ "validation_loss": 1.4785966873168945,
55
+ "validation_accuracy": 0.6484018264840182,
56
+ "validation_f1_macro": 0.4379253414135677
57
+ },
58
+ {
59
+ "epoch": 3.0,
60
+ "training_loss": 1.486709772086725,
61
+ "validation_loss": 1.3735746145248413,
62
+ "validation_accuracy": 0.5844748858447488,
63
+ "validation_f1_macro": 0.4254201336812392
64
+ },
65
+ {
66
+ "epoch": 4.0,
67
+ "training_loss": 1.3238095277693214,
68
+ "validation_loss": 1.1953068971633911,
69
+ "validation_accuracy": 0.593607305936073,
70
+ "validation_f1_macro": 0.4059264209867135
71
+ },
72
+ {
73
+ "epoch": 5.0,
74
+ "training_loss": 1.1833144280968644,
75
+ "validation_loss": 1.0689646005630493,
76
+ "validation_accuracy": 0.634703196347032,
77
+ "validation_f1_macro": 0.5306606392472901
78
+ },
79
+ {
80
+ "epoch": 6.0,
81
+ "training_loss": 1.0947908875418872,
82
+ "validation_loss": 0.9768268465995789,
83
+ "validation_accuracy": 0.684931506849315,
84
+ "validation_f1_macro": 0.5910805860805861
85
+ },
86
+ {
87
+ "epoch": 7.0,
88
+ "training_loss": 1.0506021293198191,
89
+ "validation_loss": 0.9280396103858948,
90
+ "validation_accuracy": 0.680365296803653,
91
+ "validation_f1_macro": 0.5913701533244877
92
+ },
93
+ {
94
+ "epoch": 8.0,
95
+ "training_loss": 0.999169804700991,
96
+ "validation_loss": 0.903730571269989,
97
+ "validation_accuracy": 0.6986301369863014,
98
+ "validation_f1_macro": 0.624375070811654
99
+ },
100
+ {
101
+ "epoch": 9.0,
102
+ "training_loss": 0.9537633395776516,
103
+ "validation_loss": 0.8764855861663818,
104
+ "validation_accuracy": 0.6986301369863014,
105
+ "validation_f1_macro": 0.6222269848742044
106
+ },
107
+ {
108
+ "epoch": 10.0,
109
+ "training_loss": 0.9284529744124994,
110
+ "validation_loss": 0.870495080947876,
111
+ "validation_accuracy": 0.7168949771689498,
112
+ "validation_f1_macro": 0.6800145976305553
113
+ },
114
+ {
115
+ "epoch": 11.0,
116
+ "training_loss": 0.9191909265227434,
117
+ "validation_loss": 0.8306586742401123,
118
+ "validation_accuracy": 0.6940639269406392,
119
+ "validation_f1_macro": 0.6281475784972053
120
+ },
121
+ {
122
+ "epoch": 12.0,
123
+ "training_loss": 0.8589656418416558,
124
+ "validation_loss": 0.8354954123497009,
125
+ "validation_accuracy": 0.7351598173515982,
126
+ "validation_f1_macro": 0.7001971862442815
127
+ },
128
+ {
129
+ "epoch": 13.0,
130
+ "training_loss": 0.8755823068502473,
131
+ "validation_loss": 0.8147370219230652,
132
+ "validation_accuracy": 0.730593607305936,
133
+ "validation_f1_macro": 0.6888091074938482
134
+ },
135
+ {
136
+ "epoch": 14.0,
137
+ "training_loss": 0.8297777030526138,
138
+ "validation_loss": 0.7851540446281433,
139
+ "validation_accuracy": 0.7351598173515982,
140
+ "validation_f1_macro": 0.6843052757402035
141
+ },
142
+ {
143
+ "epoch": 15.0,
144
+ "training_loss": 0.7822338589807836,
145
+ "validation_loss": 0.777275562286377,
146
+ "validation_accuracy": 0.7579908675799086,
147
+ "validation_f1_macro": 0.7143041489780522
148
+ },
149
+ {
150
+ "epoch": 16.0,
151
+ "training_loss": 0.7837169664662059,
152
+ "validation_loss": 0.7484090328216553,
153
+ "validation_accuracy": 0.7077625570776256,
154
+ "validation_f1_macro": 0.62636908727515
155
+ },
156
+ {
157
+ "epoch": 17.0,
158
+ "training_loss": 0.7800104937902311,
159
+ "validation_loss": 0.7660107016563416,
160
+ "validation_accuracy": 0.7397260273972602,
161
+ "validation_f1_macro": 0.7004440484591072
162
+ },
163
+ {
164
+ "epoch": 18.0,
165
+ "training_loss": 0.7730191902416509,
166
+ "validation_loss": 0.7944260835647583,
167
+ "validation_accuracy": 0.7214611872146118,
168
+ "validation_f1_macro": 0.698860395932188
169
+ },
170
+ {
171
+ "epoch": 19.0,
172
+ "training_loss": 0.7873704033653911,
173
+ "validation_loss": 0.7639477252960205,
174
+ "validation_accuracy": 0.7397260273972602,
175
+ "validation_f1_macro": 0.7032332841288065
176
+ },
177
+ {
178
+ "epoch": 20.0,
179
+ "training_loss": 0.7558516823663944,
180
+ "validation_loss": 0.7414798736572266,
181
+ "validation_accuracy": 0.7397260273972602,
182
+ "validation_f1_macro": 0.7004440484591072
183
+ },
184
+ {
185
+ "epoch": 21.0,
186
+ "training_loss": 0.7153960161092805,
187
+ "validation_loss": 0.7372803688049316,
188
+ "validation_accuracy": 0.7397260273972602,
189
+ "validation_f1_macro": 0.7106842540796777
190
+ },
191
+ {
192
+ "epoch": 22.0,
193
+ "training_loss": 0.7558937465272298,
194
+ "validation_loss": 0.7276725172996521,
195
+ "validation_accuracy": 0.7351598173515982,
196
+ "validation_f1_macro": 0.6967894385370114
197
+ },
198
+ {
199
+ "epoch": 23.0,
200
+ "training_loss": 0.7580708134465102,
201
+ "validation_loss": 0.7531004548072815,
202
+ "validation_accuracy": 0.730593607305936,
203
+ "validation_f1_macro": 0.7047526663942533
204
+ },
205
+ {
206
+ "epoch": 24.0,
207
+ "training_loss": 0.745811897807005,
208
+ "validation_loss": 0.7062485218048096,
209
+ "validation_accuracy": 0.7534246575342466,
210
+ "validation_f1_macro": 0.7125710434661245
211
+ },
212
+ {
213
+ "epoch": 25.0,
214
+ "training_loss": 0.7463469519847776,
215
+ "validation_loss": 0.7009532451629639,
216
+ "validation_accuracy": 0.7488584474885844,
217
+ "validation_f1_macro": 0.7095670971936119
218
+ },
219
+ {
220
+ "epoch": 26.0,
221
+ "training_loss": 0.7205249856158,
222
+ "validation_loss": 0.7257617712020874,
223
+ "validation_accuracy": 0.730593607305936,
224
+ "validation_f1_macro": 0.7043211959834462
225
+ },
226
+ {
227
+ "epoch": 27.0,
228
+ "training_loss": 0.7136954007352271,
229
+ "validation_loss": 0.7064869999885559,
230
+ "validation_accuracy": 0.7397260273972602,
231
+ "validation_f1_macro": 0.6991019731795038
232
+ },
233
+ {
234
+ "epoch": 28.0,
235
+ "training_loss": 0.6904271637521139,
236
+ "validation_loss": 0.6817423701286316,
237
+ "validation_accuracy": 0.7488584474885844,
238
+ "validation_f1_macro": 0.7013489796588387
239
+ },
240
+ {
241
+ "epoch": 29.0,
242
+ "training_loss": 0.7016249146403336,
243
+ "validation_loss": 0.7140704989433289,
244
+ "validation_accuracy": 0.7442922374429224,
245
+ "validation_f1_macro": 0.7183729324463023
246
+ },
247
+ {
248
+ "epoch": 30.0,
249
+ "training_loss": 0.7061529159545898,
250
+ "validation_loss": 0.6985487341880798,
251
+ "validation_accuracy": 0.7579908675799086,
252
+ "validation_f1_macro": 0.7151389502609014
253
+ },
254
+ {
255
+ "epoch": 31.0,
256
+ "training_loss": 0.6901032234110483,
257
+ "validation_loss": 0.7164368033409119,
258
+ "validation_accuracy": 0.7534246575342466,
259
+ "validation_f1_macro": 0.7228159409058066
260
+ },
261
+ {
262
+ "epoch": 32.0,
263
+ "training_loss": 0.6837317685528499,
264
+ "validation_loss": 0.6936579346656799,
265
+ "validation_accuracy": 0.7534246575342466,
266
+ "validation_f1_macro": 0.7228092332549204
267
+ },
268
+ {
269
+ "epoch": 33.0,
270
+ "training_loss": 0.687675036308242,
271
+ "validation_loss": 0.6586704850196838,
272
+ "validation_accuracy": 0.7488584474885844,
273
+ "validation_f1_macro": 0.7046216551479709
274
+ },
275
+ {
276
+ "epoch": 34.0,
277
+ "training_loss": 0.6865368398224435,
278
+ "validation_loss": 0.6734499335289001,
279
+ "validation_accuracy": 0.7534246575342466,
280
+ "validation_f1_macro": 0.7203484733365098
281
+ },
282
+ {
283
+ "epoch": 35.0,
284
+ "training_loss": 0.6697868941033759,
285
+ "validation_loss": 0.7118483781814575,
286
+ "validation_accuracy": 0.7534246575342466,
287
+ "validation_f1_macro": 0.7191380349608197
288
+ },
289
+ {
290
+ "epoch": 36.0,
291
+ "training_loss": 0.6508736701273337,
292
+ "validation_loss": 0.6616345643997192,
293
+ "validation_accuracy": 0.7625570776255708,
294
+ "validation_f1_macro": 0.7231272175643826
295
+ },
296
+ {
297
+ "epoch": 37.0,
298
+ "training_loss": 0.6505100578069687,
299
+ "validation_loss": 0.6596866250038147,
300
+ "validation_accuracy": 0.7579908675799086,
301
+ "validation_f1_macro": 0.7151389502609014
302
+ },
303
+ {
304
+ "epoch": 38.0,
305
+ "training_loss": 0.6638081597118843,
306
+ "validation_loss": 0.6713008284568787,
307
+ "validation_accuracy": 0.7625570776255708,
308
+ "validation_f1_macro": 0.7279401737565508
309
+ },
310
+ {
311
+ "epoch": 39.0,
312
+ "training_loss": 0.6587427185802925,
313
+ "validation_loss": 0.655285656452179,
314
+ "validation_accuracy": 0.7625570776255708,
315
+ "validation_f1_macro": 0.7182102678099487
316
+ },
317
+ {
318
+ "epoch": 40.0,
319
+ "training_loss": 0.6385705103961433,
320
+ "validation_loss": 0.6573081016540527,
321
+ "validation_accuracy": 0.7625570776255708,
322
+ "validation_f1_macro": 0.7231272175643826
323
+ },
324
+ {
325
+ "epoch": 41.0,
326
+ "training_loss": 0.6348427366919633,
327
+ "validation_loss": 0.651576578617096,
328
+ "validation_accuracy": 0.7671232876712328,
329
+ "validation_f1_macro": 0.7288319857577331
330
+ },
331
+ {
332
+ "epoch": 42.0,
333
+ "training_loss": 0.6565009624492831,
334
+ "validation_loss": 0.6397656798362732,
335
+ "validation_accuracy": 0.7625570776255708,
336
+ "validation_f1_macro": 0.7188321070054815
337
+ },
338
+ {
339
+ "epoch": 43.0,
340
+ "training_loss": 0.6659962752970253,
341
+ "validation_loss": 0.6891706585884094,
342
+ "validation_accuracy": 0.771689497716895,
343
+ "validation_f1_macro": 0.7309385419512768
344
+ },
345
+ {
346
+ "epoch": 44.0,
347
+ "training_loss": 0.6738467928839893,
348
+ "validation_loss": 0.6647281646728516,
349
+ "validation_accuracy": 0.7671232876712328,
350
+ "validation_f1_macro": 0.7263824879672347
351
+ },
352
+ {
353
+ "epoch": 45.0,
354
+ "training_loss": 0.6135960897294487,
355
+ "validation_loss": 0.6542041897773743,
356
+ "validation_accuracy": 0.7488584474885844,
357
+ "validation_f1_macro": 0.7170297061311794
358
+ },
359
+ {
360
+ "epoch": 46.0,
361
+ "training_loss": 0.6420483487408336,
362
+ "validation_loss": 0.6627779006958008,
363
+ "validation_accuracy": 0.7534246575342466,
364
+ "validation_f1_macro": 0.7339515372302257
365
+ },
366
+ {
367
+ "epoch": 47.0,
368
+ "training_loss": 0.6316513110224794,
369
+ "validation_loss": 0.6684327721595764,
370
+ "validation_accuracy": 0.7579908675799086,
371
+ "validation_f1_macro": 0.7322278512955682
372
+ },
373
+ {
374
+ "epoch": 48.0,
375
+ "training_loss": 0.5976518806887836,
376
+ "validation_loss": 0.6376972794532776,
377
+ "validation_accuracy": 0.7579908675799086,
378
+ "validation_f1_macro": 0.72072080654124
379
+ },
380
+ {
381
+ "epoch": 49.0,
382
+ "training_loss": 0.6369955467014778,
383
+ "validation_loss": 0.6139159798622131,
384
+ "validation_accuracy": 0.7625570776255708,
385
+ "validation_f1_macro": 0.7241977003359705
386
+ },
387
+ {
388
+ "epoch": 50.0,
389
+ "training_loss": 0.6086341200805292,
390
+ "validation_loss": 0.6317619681358337,
391
+ "validation_accuracy": 0.7534246575342466,
392
+ "validation_f1_macro": 0.7226772044924286
393
+ }
394
+ ]
395
+ },
396
+ {
397
+ "limit": 50,
398
+ "seed": 1,
399
+ "hyperparameters": {
400
+ "epochs": 50,
401
+ "batch_size": 16,
402
+ "learning_rate": 0.001,
403
+ "weight_decay": 0.0001,
404
+ "dropout": 0.3,
405
+ "projection_dim": 64,
406
+ "fusion_dim": 12,
407
+ "validation_fraction": 0.25,
408
+ "optimizer": "Adam",
409
+ "class_weighting": true,
410
+ "early_stopping_patience": 12,
411
+ "seed": 1
412
+ },
413
+ "epochs_completed": 50,
414
+ "best_epoch": 50,
415
+ "early_stopped": false,
416
+ "hit_max": true,
417
+ "train_loss_at_best": 0.6737533146288337,
418
+ "val_loss_at_best": 0.5473684668540955,
419
+ "accuracy": 0.771689497716895,
420
+ "f1_macro": 0.7440047141753251,
421
+ "f1_weighted": 0.780656107342426,
422
+ "recall": {
423
+ "0.0": 0.8275862068965517,
424
+ "1.0": 0.8484848484848485,
425
+ "2.0": 0.7272727272727273,
426
+ "3.0": 0.7889908256880734,
427
+ "4.0": 0.6216216216216216
428
+ },
429
+ "pred_counts": {
430
+ "0.0": 50,
431
+ "1.0": 30,
432
+ "2.0": 12,
433
+ "3.0": 98,
434
+ "4.0": 29
435
+ },
436
+ "collapsed": false,
437
+ "history": [
438
+ {
439
+ "epoch": 1.0,
440
+ "training_loss": 1.6146349499865276,
441
+ "validation_loss": 1.575058102607727,
442
+ "validation_accuracy": 0.1324200913242009,
443
+ "validation_f1_macro": 0.050216450216450215
444
+ },
445
+ {
446
+ "epoch": 2.0,
447
+ "training_loss": 1.5576762571567442,
448
+ "validation_loss": 1.4663180112838745,
449
+ "validation_accuracy": 0.6712328767123288,
450
+ "validation_f1_macro": 0.42016638018716657
451
+ },
452
+ {
453
+ "epoch": 3.0,
454
+ "training_loss": 1.429895084078719,
455
+ "validation_loss": 1.3014626502990723,
456
+ "validation_accuracy": 0.5114155251141552,
457
+ "validation_f1_macro": 0.43583686743324107
458
+ },
459
+ {
460
+ "epoch": 4.0,
461
+ "training_loss": 1.3091918404509382,
462
+ "validation_loss": 1.1551529169082642,
463
+ "validation_accuracy": 0.7168949771689498,
464
+ "validation_f1_macro": 0.5488767922611446
465
+ },
466
+ {
467
+ "epoch": 5.0,
468
+ "training_loss": 1.22728247904196,
469
+ "validation_loss": 1.106502652168274,
470
+ "validation_accuracy": 0.6757990867579908,
471
+ "validation_f1_macro": 0.5671942031903903
472
+ },
473
+ {
474
+ "epoch": 6.0,
475
+ "training_loss": 1.1522581679065054,
476
+ "validation_loss": 1.0021183490753174,
477
+ "validation_accuracy": 0.7351598173515982,
478
+ "validation_f1_macro": 0.641220618738691
479
+ },
480
+ {
481
+ "epoch": 7.0,
482
+ "training_loss": 1.0948621368989713,
483
+ "validation_loss": 0.9995510578155518,
484
+ "validation_accuracy": 0.7123287671232876,
485
+ "validation_f1_macro": 0.6484724275824066
486
+ },
487
+ {
488
+ "epoch": 8.0,
489
+ "training_loss": 1.039277656776149,
490
+ "validation_loss": 0.9134731292724609,
491
+ "validation_accuracy": 0.7077625570776256,
492
+ "validation_f1_macro": 0.600579379222993
493
+ },
494
+ {
495
+ "epoch": 9.0,
496
+ "training_loss": 0.9888714748184856,
497
+ "validation_loss": 0.8895769715309143,
498
+ "validation_accuracy": 0.7123287671232876,
499
+ "validation_f1_macro": 0.6438500618850422
500
+ },
501
+ {
502
+ "epoch": 10.0,
503
+ "training_loss": 0.9783786477112189,
504
+ "validation_loss": 0.8380142450332642,
505
+ "validation_accuracy": 0.7168949771689498,
506
+ "validation_f1_macro": 0.6358597527533143
507
+ },
508
+ {
509
+ "epoch": 11.0,
510
+ "training_loss": 0.960785810540362,
511
+ "validation_loss": 0.789691686630249,
512
+ "validation_accuracy": 0.7442922374429224,
513
+ "validation_f1_macro": 0.6705080988917306
514
+ },
515
+ {
516
+ "epoch": 12.0,
517
+ "training_loss": 0.932400470826684,
518
+ "validation_loss": 0.7533499598503113,
519
+ "validation_accuracy": 0.7351598173515982,
520
+ "validation_f1_macro": 0.6678743728192538
521
+ },
522
+ {
523
+ "epoch": 13.0,
524
+ "training_loss": 0.9086572571498591,
525
+ "validation_loss": 0.7680419683456421,
526
+ "validation_accuracy": 0.7671232876712328,
527
+ "validation_f1_macro": 0.7357015533486121
528
+ },
529
+ {
530
+ "epoch": 14.0,
531
+ "training_loss": 0.8951937149210674,
532
+ "validation_loss": 0.7899214029312134,
533
+ "validation_accuracy": 0.7442922374429224,
534
+ "validation_f1_macro": 0.6859978502715481
535
+ },
536
+ {
537
+ "epoch": 15.0,
538
+ "training_loss": 0.8932757028719274,
539
+ "validation_loss": 0.7378162145614624,
540
+ "validation_accuracy": 0.771689497716895,
541
+ "validation_f1_macro": 0.7412681571302261
542
+ },
543
+ {
544
+ "epoch": 16.0,
545
+ "training_loss": 0.8621222921987859,
546
+ "validation_loss": 0.709632158279419,
547
+ "validation_accuracy": 0.7808219178082192,
548
+ "validation_f1_macro": 0.7469195430713926
549
+ },
550
+ {
551
+ "epoch": 17.0,
552
+ "training_loss": 0.8679965152973081,
553
+ "validation_loss": 0.700313925743103,
554
+ "validation_accuracy": 0.776255707762557,
555
+ "validation_f1_macro": 0.7520373878994568
556
+ },
557
+ {
558
+ "epoch": 18.0,
559
+ "training_loss": 0.856050679596459,
560
+ "validation_loss": 0.7888535857200623,
561
+ "validation_accuracy": 0.730593607305936,
562
+ "validation_f1_macro": 0.720227319958807
563
+ },
564
+ {
565
+ "epoch": 19.0,
566
+ "training_loss": 0.8359041402979595,
567
+ "validation_loss": 0.7383802533149719,
568
+ "validation_accuracy": 0.7579908675799086,
569
+ "validation_f1_macro": 0.7502831048969638
570
+ },
571
+ {
572
+ "epoch": 20.0,
573
+ "training_loss": 0.8181704050157128,
574
+ "validation_loss": 0.6877511143684387,
575
+ "validation_accuracy": 0.771689497716895,
576
+ "validation_f1_macro": 0.7477847298339626
577
+ },
578
+ {
579
+ "epoch": 21.0,
580
+ "training_loss": 0.8081453064592873,
581
+ "validation_loss": 0.6738131642341614,
582
+ "validation_accuracy": 0.771689497716895,
583
+ "validation_f1_macro": 0.7477847298339626
584
+ },
585
+ {
586
+ "epoch": 22.0,
587
+ "training_loss": 0.8146184283058818,
588
+ "validation_loss": 0.6859759092330933,
589
+ "validation_accuracy": 0.776255707762557,
590
+ "validation_f1_macro": 0.7519691047333475
591
+ },
592
+ {
593
+ "epoch": 23.0,
594
+ "training_loss": 0.7658585833340157,
595
+ "validation_loss": 0.6654026508331299,
596
+ "validation_accuracy": 0.771689497716895,
597
+ "validation_f1_macro": 0.7481259028885503
598
+ },
599
+ {
600
+ "epoch": 24.0,
601
+ "training_loss": 0.795221415961661,
602
+ "validation_loss": 0.6880972385406494,
603
+ "validation_accuracy": 0.7671232876712328,
604
+ "validation_f1_macro": 0.749113261531172
605
+ },
606
+ {
607
+ "epoch": 25.0,
608
+ "training_loss": 0.766509844762523,
609
+ "validation_loss": 0.6547732353210449,
610
+ "validation_accuracy": 0.776255707762557,
611
+ "validation_f1_macro": 0.7504815593738889
612
+ },
613
+ {
614
+ "epoch": 26.0,
615
+ "training_loss": 0.7451754779350467,
616
+ "validation_loss": 0.641086757183075,
617
+ "validation_accuracy": 0.776255707762557,
618
+ "validation_f1_macro": 0.7513249611234765
619
+ },
620
+ {
621
+ "epoch": 27.0,
622
+ "training_loss": 0.7445048566271619,
623
+ "validation_loss": 0.6966652870178223,
624
+ "validation_accuracy": 0.7579908675799086,
625
+ "validation_f1_macro": 0.7606411893521148
626
+ },
627
+ {
628
+ "epoch": 28.0,
629
+ "training_loss": 0.7651737048858549,
630
+ "validation_loss": 0.6241215467453003,
631
+ "validation_accuracy": 0.771689497716895,
632
+ "validation_f1_macro": 0.7420460368375397
633
+ },
634
+ {
635
+ "epoch": 29.0,
636
+ "training_loss": 0.7353100442304844,
637
+ "validation_loss": 0.6544505953788757,
638
+ "validation_accuracy": 0.776255707762557,
639
+ "validation_f1_macro": 0.7657700127712094
640
+ },
641
+ {
642
+ "epoch": 30.0,
643
+ "training_loss": 0.7035701282140685,
644
+ "validation_loss": 0.6174139380455017,
645
+ "validation_accuracy": 0.771689497716895,
646
+ "validation_f1_macro": 0.7498252638827072
647
+ },
648
+ {
649
+ "epoch": 31.0,
650
+ "training_loss": 0.7469911822458593,
651
+ "validation_loss": 0.6883834004402161,
652
+ "validation_accuracy": 0.7488584474885844,
653
+ "validation_f1_macro": 0.7347811350746591
654
+ },
655
+ {
656
+ "epoch": 32.0,
657
+ "training_loss": 0.7179534958630074,
658
+ "validation_loss": 0.6455731391906738,
659
+ "validation_accuracy": 0.7534246575342466,
660
+ "validation_f1_macro": 0.732234748765767
661
+ },
662
+ {
663
+ "epoch": 33.0,
664
+ "training_loss": 0.7100564270484738,
665
+ "validation_loss": 0.6091746687889099,
666
+ "validation_accuracy": 0.7671232876712328,
667
+ "validation_f1_macro": 0.7317109445711596
668
+ },
669
+ {
670
+ "epoch": 34.0,
671
+ "training_loss": 0.7199300287700281,
672
+ "validation_loss": 0.6026328206062317,
673
+ "validation_accuracy": 0.7625570776255708,
674
+ "validation_f1_macro": 0.7308664449238883
675
+ },
676
+ {
677
+ "epoch": 35.0,
678
+ "training_loss": 0.6912782046853042,
679
+ "validation_loss": 0.5964751243591309,
680
+ "validation_accuracy": 0.7671232876712328,
681
+ "validation_f1_macro": 0.7353358103716783
682
+ },
683
+ {
684
+ "epoch": 36.0,
685
+ "training_loss": 0.699575291174214,
686
+ "validation_loss": 0.6650922894477844,
687
+ "validation_accuracy": 0.7488584474885844,
688
+ "validation_f1_macro": 0.7315696649029981
689
+ },
690
+ {
691
+ "epoch": 37.0,
692
+ "training_loss": 0.663958967458911,
693
+ "validation_loss": 0.6142030954360962,
694
+ "validation_accuracy": 0.776255707762557,
695
+ "validation_f1_macro": 0.7528734567901234
696
+ },
697
+ {
698
+ "epoch": 38.0,
699
+ "training_loss": 0.7137848516789879,
700
+ "validation_loss": 0.6241359710693359,
701
+ "validation_accuracy": 0.7671232876712328,
702
+ "validation_f1_macro": 0.752736056214317
703
+ },
704
+ {
705
+ "epoch": 39.0,
706
+ "training_loss": 0.6935832740330115,
707
+ "validation_loss": 0.6146963238716125,
708
+ "validation_accuracy": 0.776255707762557,
709
+ "validation_f1_macro": 0.7642682594117683
710
+ },
711
+ {
712
+ "epoch": 40.0,
713
+ "training_loss": 0.6634499815905966,
714
+ "validation_loss": 0.647922694683075,
715
+ "validation_accuracy": 0.7442922374429224,
716
+ "validation_f1_macro": 0.7336896086369771
717
+ },
718
+ {
719
+ "epoch": 41.0,
720
+ "training_loss": 0.6981347810204436,
721
+ "validation_loss": 0.6087270379066467,
722
+ "validation_accuracy": 0.7579908675799086,
723
+ "validation_f1_macro": 0.740196849837119
724
+ },
725
+ {
726
+ "epoch": 42.0,
727
+ "training_loss": 0.6437401011949633,
728
+ "validation_loss": 0.5964922904968262,
729
+ "validation_accuracy": 0.771689497716895,
730
+ "validation_f1_macro": 0.7530886671461106
731
+ },
732
+ {
733
+ "epoch": 43.0,
734
+ "training_loss": 0.6792685098764373,
735
+ "validation_loss": 0.625365674495697,
736
+ "validation_accuracy": 0.7625570776255708,
737
+ "validation_f1_macro": 0.7529175812892382
738
+ },
739
+ {
740
+ "epoch": 44.0,
741
+ "training_loss": 0.6364530761067461,
742
+ "validation_loss": 0.5903960466384888,
743
+ "validation_accuracy": 0.7579908675799086,
744
+ "validation_f1_macro": 0.743187921582135
745
+ },
746
+ {
747
+ "epoch": 45.0,
748
+ "training_loss": 0.6544558653017369,
749
+ "validation_loss": 0.6682825684547424,
750
+ "validation_accuracy": 0.7579908675799086,
751
+ "validation_f1_macro": 0.757454120777634
752
+ },
753
+ {
754
+ "epoch": 46.0,
755
+ "training_loss": 0.6508243360170504,
756
+ "validation_loss": 0.5978586077690125,
757
+ "validation_accuracy": 0.7625570776255708,
758
+ "validation_f1_macro": 0.7392423117547007
759
+ },
760
+ {
761
+ "epoch": 47.0,
762
+ "training_loss": 0.6495987329541183,
763
+ "validation_loss": 0.6385520100593567,
764
+ "validation_accuracy": 0.7625570776255708,
765
+ "validation_f1_macro": 0.7542611570673247
766
+ },
767
+ {
768
+ "epoch": 48.0,
769
+ "training_loss": 0.6470511155884441,
770
+ "validation_loss": 0.6651841998100281,
771
+ "validation_accuracy": 0.7534246575342466,
772
+ "validation_f1_macro": 0.7437327104909329
773
+ },
774
+ {
775
+ "epoch": 49.0,
776
+ "training_loss": 0.6453332679300774,
777
+ "validation_loss": 0.5932057499885559,
778
+ "validation_accuracy": 0.771689497716895,
779
+ "validation_f1_macro": 0.7604378496705634
780
+ },
781
+ {
782
+ "epoch": 50.0,
783
+ "training_loss": 0.6737533146288337,
784
+ "validation_loss": 0.5473684668540955,
785
+ "validation_accuracy": 0.771689497716895,
786
+ "validation_f1_macro": 0.7440047141753251
787
+ }
788
+ ]
789
+ },
790
+ {
791
+ "limit": 50,
792
+ "seed": 2,
793
+ "hyperparameters": {
794
+ "epochs": 50,
795
+ "batch_size": 16,
796
+ "learning_rate": 0.001,
797
+ "weight_decay": 0.0001,
798
+ "dropout": 0.3,
799
+ "projection_dim": 64,
800
+ "fusion_dim": 12,
801
+ "validation_fraction": 0.25,
802
+ "optimizer": "Adam",
803
+ "class_weighting": true,
804
+ "early_stopping_patience": 12,
805
+ "seed": 2
806
+ },
807
+ "epochs_completed": 50,
808
+ "best_epoch": 50,
809
+ "early_stopped": false,
810
+ "hit_max": true,
811
+ "train_loss_at_best": 0.6226128528757793,
812
+ "val_loss_at_best": 0.6078133583068848,
813
+ "accuracy": 0.7899543378995434,
814
+ "f1_macro": 0.7605265274426602,
815
+ "f1_weighted": 0.8021877366164152,
816
+ "recall": {
817
+ "0.0": 0.8620689655172413,
818
+ "1.0": 0.7878787878787878,
819
+ "2.0": 0.9090909090909091,
820
+ "3.0": 0.7981651376146789,
821
+ "4.0": 0.6756756756756757
822
+ },
823
+ "pred_counts": {
824
+ "0.0": 51,
825
+ "1.0": 27,
826
+ "2.0": 17,
827
+ "3.0": 93,
828
+ "4.0": 31
829
+ },
830
+ "collapsed": false,
831
+ "history": [
832
+ {
833
+ "epoch": 1.0,
834
+ "training_loss": 1.6174468180028403,
835
+ "validation_loss": 1.546391487121582,
836
+ "validation_accuracy": 0.4977168949771689,
837
+ "validation_f1_macro": 0.1329268292682927
838
+ },
839
+ {
840
+ "epoch": 2.0,
841
+ "training_loss": 1.58593586596047,
842
+ "validation_loss": 1.4984139204025269,
843
+ "validation_accuracy": 0.634703196347032,
844
+ "validation_f1_macro": 0.32264243870355924
845
+ },
846
+ {
847
+ "epoch": 3.0,
848
+ "training_loss": 1.5026960140321313,
849
+ "validation_loss": 1.3888118267059326,
850
+ "validation_accuracy": 0.6301369863013698,
851
+ "validation_f1_macro": 0.4463465487656915
852
+ },
853
+ {
854
+ "epoch": 4.0,
855
+ "training_loss": 1.366911018767008,
856
+ "validation_loss": 1.223652720451355,
857
+ "validation_accuracy": 0.6666666666666666,
858
+ "validation_f1_macro": 0.48780167888527026
859
+ },
860
+ {
861
+ "epoch": 5.0,
862
+ "training_loss": 1.242981534178664,
863
+ "validation_loss": 1.0726312398910522,
864
+ "validation_accuracy": 0.6894977168949772,
865
+ "validation_f1_macro": 0.5486778629605984
866
+ },
867
+ {
868
+ "epoch": 6.0,
869
+ "training_loss": 1.162133698056384,
870
+ "validation_loss": 1.0369216203689575,
871
+ "validation_accuracy": 0.7077625570776256,
872
+ "validation_f1_macro": 0.5994216476773437
873
+ },
874
+ {
875
+ "epoch": 7.0,
876
+ "training_loss": 1.0751376282878038,
877
+ "validation_loss": 0.9657370448112488,
878
+ "validation_accuracy": 0.7214611872146118,
879
+ "validation_f1_macro": 0.6168606528606528
880
+ },
881
+ {
882
+ "epoch": 8.0,
883
+ "training_loss": 1.0233616610852683,
884
+ "validation_loss": 0.9052940607070923,
885
+ "validation_accuracy": 0.7214611872146118,
886
+ "validation_f1_macro": 0.6206169633347545
887
+ },
888
+ {
889
+ "epoch": 9.0,
890
+ "training_loss": 0.9568443654513941,
891
+ "validation_loss": 0.8481383919715881,
892
+ "validation_accuracy": 0.7442922374429224,
893
+ "validation_f1_macro": 0.6569653120795127
894
+ },
895
+ {
896
+ "epoch": 10.0,
897
+ "training_loss": 0.965624666795498,
898
+ "validation_loss": 0.8241379857063293,
899
+ "validation_accuracy": 0.7488584474885844,
900
+ "validation_f1_macro": 0.6993045121283534
901
+ },
902
+ {
903
+ "epoch": 11.0,
904
+ "training_loss": 0.9236650459650086,
905
+ "validation_loss": 0.7953732013702393,
906
+ "validation_accuracy": 0.7397260273972602,
907
+ "validation_f1_macro": 0.6642163006401307
908
+ },
909
+ {
910
+ "epoch": 12.0,
911
+ "training_loss": 0.9139396679110643,
912
+ "validation_loss": 0.8159263730049133,
913
+ "validation_accuracy": 0.7625570776255708,
914
+ "validation_f1_macro": 0.7289687504443403
915
+ },
916
+ {
917
+ "epoch": 13.0,
918
+ "training_loss": 0.8704477693976426,
919
+ "validation_loss": 0.7864435911178589,
920
+ "validation_accuracy": 0.7671232876712328,
921
+ "validation_f1_macro": 0.7298061488176467
922
+ },
923
+ {
924
+ "epoch": 14.0,
925
+ "training_loss": 0.8610671446090792,
926
+ "validation_loss": 0.7811137437820435,
927
+ "validation_accuracy": 0.771689497716895,
928
+ "validation_f1_macro": 0.7461521382117662
929
+ },
930
+ {
931
+ "epoch": 15.0,
932
+ "training_loss": 0.8468370241362874,
933
+ "validation_loss": 0.7946519255638123,
934
+ "validation_accuracy": 0.7579908675799086,
935
+ "validation_f1_macro": 0.7148572825024437
936
+ },
937
+ {
938
+ "epoch": 16.0,
939
+ "training_loss": 0.8112003149055853,
940
+ "validation_loss": 0.759267270565033,
941
+ "validation_accuracy": 0.771689497716895,
942
+ "validation_f1_macro": 0.7438968232086511
943
+ },
944
+ {
945
+ "epoch": 17.0,
946
+ "training_loss": 0.8073849859761029,
947
+ "validation_loss": 0.7926062941551208,
948
+ "validation_accuracy": 0.7579908675799086,
949
+ "validation_f1_macro": 0.7186664392134682
950
+ },
951
+ {
952
+ "epoch": 18.0,
953
+ "training_loss": 0.816777443740426,
954
+ "validation_loss": 0.7469311952590942,
955
+ "validation_accuracy": 0.7579908675799086,
956
+ "validation_f1_macro": 0.7219948399680252
957
+ },
958
+ {
959
+ "epoch": 19.0,
960
+ "training_loss": 0.7756974529929277,
961
+ "validation_loss": 0.7867042422294617,
962
+ "validation_accuracy": 0.7534246575342466,
963
+ "validation_f1_macro": 0.7173738004352781
964
+ },
965
+ {
966
+ "epoch": 20.0,
967
+ "training_loss": 0.7760032975092167,
968
+ "validation_loss": 0.742599606513977,
969
+ "validation_accuracy": 0.7579908675799086,
970
+ "validation_f1_macro": 0.7142244295830055
971
+ },
972
+ {
973
+ "epoch": 21.0,
974
+ "training_loss": 0.7517595632774073,
975
+ "validation_loss": 0.7031355500221252,
976
+ "validation_accuracy": 0.771689497716895,
977
+ "validation_f1_macro": 0.7442643678160918
978
+ },
979
+ {
980
+ "epoch": 22.0,
981
+ "training_loss": 0.7545412931500411,
982
+ "validation_loss": 0.6887046694755554,
983
+ "validation_accuracy": 0.7808219178082192,
984
+ "validation_f1_macro": 0.7562175932096297
985
+ },
986
+ {
987
+ "epoch": 23.0,
988
+ "training_loss": 0.7387897721151027,
989
+ "validation_loss": 0.7003820538520813,
990
+ "validation_accuracy": 0.776255707762557,
991
+ "validation_f1_macro": 0.7543517355165935
992
+ },
993
+ {
994
+ "epoch": 24.0,
995
+ "training_loss": 0.735975246603896,
996
+ "validation_loss": 0.7061943411827087,
997
+ "validation_accuracy": 0.7579908675799086,
998
+ "validation_f1_macro": 0.7328107884559498
999
+ },
1000
+ {
1001
+ "epoch": 25.0,
1002
+ "training_loss": 0.7385040648826738,
1003
+ "validation_loss": 0.6894986033439636,
1004
+ "validation_accuracy": 0.7625570776255708,
1005
+ "validation_f1_macro": 0.7331689455756886
1006
+ },
1007
+ {
1008
+ "epoch": 26.0,
1009
+ "training_loss": 0.7225778640770331,
1010
+ "validation_loss": 0.6931126713752747,
1011
+ "validation_accuracy": 0.7853881278538812,
1012
+ "validation_f1_macro": 0.7605941350168008
1013
+ },
1014
+ {
1015
+ "epoch": 27.0,
1016
+ "training_loss": 0.7251168932856583,
1017
+ "validation_loss": 0.6966921091079712,
1018
+ "validation_accuracy": 0.776255707762557,
1019
+ "validation_f1_macro": 0.7558511749080189
1020
+ },
1021
+ {
1022
+ "epoch": 28.0,
1023
+ "training_loss": 0.6874374543748251,
1024
+ "validation_loss": 0.6818851232528687,
1025
+ "validation_accuracy": 0.776255707762557,
1026
+ "validation_f1_macro": 0.7543517355165935
1027
+ },
1028
+ {
1029
+ "epoch": 29.0,
1030
+ "training_loss": 0.6856663837665464,
1031
+ "validation_loss": 0.6972566246986389,
1032
+ "validation_accuracy": 0.7625570776255708,
1033
+ "validation_f1_macro": 0.7455299037752814
1034
+ },
1035
+ {
1036
+ "epoch": 30.0,
1037
+ "training_loss": 0.7368745691165691,
1038
+ "validation_loss": 0.6822320222854614,
1039
+ "validation_accuracy": 0.771689497716895,
1040
+ "validation_f1_macro": 0.7516292198922165
1041
+ },
1042
+ {
1043
+ "epoch": 31.0,
1044
+ "training_loss": 0.6798700966486116,
1045
+ "validation_loss": 0.6768004298210144,
1046
+ "validation_accuracy": 0.7534246575342466,
1047
+ "validation_f1_macro": 0.7219549374996355
1048
+ },
1049
+ {
1050
+ "epoch": 32.0,
1051
+ "training_loss": 0.6843307061893183,
1052
+ "validation_loss": 0.6862162947654724,
1053
+ "validation_accuracy": 0.7625570776255708,
1054
+ "validation_f1_macro": 0.7359209675538737
1055
+ },
1056
+ {
1057
+ "epoch": 33.0,
1058
+ "training_loss": 0.6932616771721258,
1059
+ "validation_loss": 0.6602873206138611,
1060
+ "validation_accuracy": 0.7808219178082192,
1061
+ "validation_f1_macro": 0.745427030596522
1062
+ },
1063
+ {
1064
+ "epoch": 34.0,
1065
+ "training_loss": 0.6615989964182784,
1066
+ "validation_loss": 0.6448985934257507,
1067
+ "validation_accuracy": 0.771689497716895,
1068
+ "validation_f1_macro": 0.7500281260358693
1069
+ },
1070
+ {
1071
+ "epoch": 35.0,
1072
+ "training_loss": 0.6791574282617103,
1073
+ "validation_loss": 0.6585924029350281,
1074
+ "validation_accuracy": 0.7853881278538812,
1075
+ "validation_f1_macro": 0.7664376696199262
1076
+ },
1077
+ {
1078
+ "epoch": 36.0,
1079
+ "training_loss": 0.657524651507052,
1080
+ "validation_loss": 0.6542724967002869,
1081
+ "validation_accuracy": 0.7808219178082192,
1082
+ "validation_f1_macro": 0.7626256130648112
1083
+ },
1084
+ {
1085
+ "epoch": 37.0,
1086
+ "training_loss": 0.6647858172655106,
1087
+ "validation_loss": 0.6971855759620667,
1088
+ "validation_accuracy": 0.771689497716895,
1089
+ "validation_f1_macro": 0.753978494623656
1090
+ },
1091
+ {
1092
+ "epoch": 38.0,
1093
+ "training_loss": 0.6512645035982132,
1094
+ "validation_loss": 0.666414737701416,
1095
+ "validation_accuracy": 0.7853881278538812,
1096
+ "validation_f1_macro": 0.7705946470426236
1097
+ },
1098
+ {
1099
+ "epoch": 39.0,
1100
+ "training_loss": 0.6842886165874761,
1101
+ "validation_loss": 0.637172281742096,
1102
+ "validation_accuracy": 0.7853881278538812,
1103
+ "validation_f1_macro": 0.7539143744415602
1104
+ },
1105
+ {
1106
+ "epoch": 40.0,
1107
+ "training_loss": 0.6806678484852721,
1108
+ "validation_loss": 0.6234135627746582,
1109
+ "validation_accuracy": 0.776255707762557,
1110
+ "validation_f1_macro": 0.7615367535963815
1111
+ },
1112
+ {
1113
+ "epoch": 41.0,
1114
+ "training_loss": 0.616909401082411,
1115
+ "validation_loss": 0.6653591394424438,
1116
+ "validation_accuracy": 0.7808219178082192,
1117
+ "validation_f1_macro": 0.755696817874246
1118
+ },
1119
+ {
1120
+ "epoch": 42.0,
1121
+ "training_loss": 0.6306327792202554,
1122
+ "validation_loss": 0.6591380834579468,
1123
+ "validation_accuracy": 0.7853881278538812,
1124
+ "validation_f1_macro": 0.7742082670405406
1125
+ },
1126
+ {
1127
+ "epoch": 43.0,
1128
+ "training_loss": 0.613016491982995,
1129
+ "validation_loss": 0.6415198445320129,
1130
+ "validation_accuracy": 0.7945205479452054,
1131
+ "validation_f1_macro": 0.7785763733179202
1132
+ },
1133
+ {
1134
+ "epoch": 44.0,
1135
+ "training_loss": 0.6375173973601039,
1136
+ "validation_loss": 0.6755697727203369,
1137
+ "validation_accuracy": 0.776255707762557,
1138
+ "validation_f1_macro": 0.762229243198948
1139
+ },
1140
+ {
1141
+ "epoch": 45.0,
1142
+ "training_loss": 0.6442778532097979,
1143
+ "validation_loss": 0.6340303421020508,
1144
+ "validation_accuracy": 0.7808219178082192,
1145
+ "validation_f1_macro": 0.7672196170102448
1146
+ },
1147
+ {
1148
+ "epoch": 46.0,
1149
+ "training_loss": 0.6433215337555583,
1150
+ "validation_loss": 0.6413596272468567,
1151
+ "validation_accuracy": 0.776255707762557,
1152
+ "validation_f1_macro": 0.7579256303497852
1153
+ },
1154
+ {
1155
+ "epoch": 47.0,
1156
+ "training_loss": 0.6104130672245491,
1157
+ "validation_loss": 0.691887617111206,
1158
+ "validation_accuracy": 0.776255707762557,
1159
+ "validation_f1_macro": 0.7639407057578872
1160
+ },
1161
+ {
1162
+ "epoch": 48.0,
1163
+ "training_loss": 0.6267564820080269,
1164
+ "validation_loss": 0.6122168302536011,
1165
+ "validation_accuracy": 0.776255707762557,
1166
+ "validation_f1_macro": 0.7422206267279872
1167
+ },
1168
+ {
1169
+ "epoch": 49.0,
1170
+ "training_loss": 0.6220824849314806,
1171
+ "validation_loss": 0.6251174807548523,
1172
+ "validation_accuracy": 0.7945205479452054,
1173
+ "validation_f1_macro": 0.7765977910787982
1174
+ },
1175
+ {
1176
+ "epoch": 50.0,
1177
+ "training_loss": 0.6226128528757793,
1178
+ "validation_loss": 0.6078133583068848,
1179
+ "validation_accuracy": 0.7899543378995434,
1180
+ "validation_f1_macro": 0.7605265274426602
1181
+ }
1182
+ ]
1183
+ },
1184
+ {
1185
+ "limit": 50,
1186
+ "seed": 3,
1187
+ "hyperparameters": {
1188
+ "epochs": 50,
1189
+ "batch_size": 16,
1190
+ "learning_rate": 0.001,
1191
+ "weight_decay": 0.0001,
1192
+ "dropout": 0.3,
1193
+ "projection_dim": 64,
1194
+ "fusion_dim": 12,
1195
+ "validation_fraction": 0.25,
1196
+ "optimizer": "Adam",
1197
+ "class_weighting": true,
1198
+ "early_stopping_patience": 12,
1199
+ "seed": 3
1200
+ },
1201
+ "epochs_completed": 50,
1202
+ "best_epoch": 47,
1203
+ "early_stopped": false,
1204
+ "hit_max": true,
1205
+ "train_loss_at_best": 0.6082611465599479,
1206
+ "val_loss_at_best": 0.7235217690467834,
1207
+ "accuracy": 0.730593607305936,
1208
+ "f1_macro": 0.714038944252953,
1209
+ "f1_weighted": 0.7389451956635594,
1210
+ "recall": {
1211
+ "0.0": 0.7931034482758621,
1212
+ "1.0": 0.8181818181818182,
1213
+ "2.0": 0.5454545454545454,
1214
+ "3.0": 0.7431192660550459,
1215
+ "4.0": 0.6216216216216216
1216
+ },
1217
+ "pred_counts": {
1218
+ "0.0": 48,
1219
+ "1.0": 29,
1220
+ "2.0": 6,
1221
+ "3.0": 98,
1222
+ "4.0": 38
1223
+ },
1224
+ "collapsed": false,
1225
+ "history": [
1226
+ {
1227
+ "epoch": 1.0,
1228
+ "training_loss": 1.6220814803751504,
1229
+ "validation_loss": 1.5875742435455322,
1230
+ "validation_accuracy": 0.1689497716894977,
1231
+ "validation_f1_macro": 0.058039215686274515
1232
+ },
1233
+ {
1234
+ "epoch": 2.0,
1235
+ "training_loss": 1.5936871476289702,
1236
+ "validation_loss": 1.5093015432357788,
1237
+ "validation_accuracy": 0.5296803652968036,
1238
+ "validation_f1_macro": 0.24350741457124436
1239
+ },
1240
+ {
1241
+ "epoch": 3.0,
1242
+ "training_loss": 1.4826284995893153,
1243
+ "validation_loss": 1.3824553489685059,
1244
+ "validation_accuracy": 0.6529680365296804,
1245
+ "validation_f1_macro": 0.4183523781204423
1246
+ },
1247
+ {
1248
+ "epoch": 4.0,
1249
+ "training_loss": 1.3528232690764637,
1250
+ "validation_loss": 1.1993681192398071,
1251
+ "validation_accuracy": 0.6210045662100456,
1252
+ "validation_f1_macro": 0.37834817436637735
1253
+ },
1254
+ {
1255
+ "epoch": 5.0,
1256
+ "training_loss": 1.2376714392406185,
1257
+ "validation_loss": 1.1297868490219116,
1258
+ "validation_accuracy": 0.680365296803653,
1259
+ "validation_f1_macro": 0.5830366044722973
1260
+ },
1261
+ {
1262
+ "epoch": 6.0,
1263
+ "training_loss": 1.1285978991810868,
1264
+ "validation_loss": 1.022017002105713,
1265
+ "validation_accuracy": 0.6757990867579908,
1266
+ "validation_f1_macro": 0.5483004580309334
1267
+ },
1268
+ {
1269
+ "epoch": 7.0,
1270
+ "training_loss": 1.0585624633765802,
1271
+ "validation_loss": 0.9644467234611511,
1272
+ "validation_accuracy": 0.6940639269406392,
1273
+ "validation_f1_macro": 0.6055969275814801
1274
+ },
1275
+ {
1276
+ "epoch": 8.0,
1277
+ "training_loss": 0.9813799683640643,
1278
+ "validation_loss": 0.9254334568977356,
1279
+ "validation_accuracy": 0.684931506849315,
1280
+ "validation_f1_macro": 0.6203979879120456
1281
+ },
1282
+ {
1283
+ "epoch": 9.0,
1284
+ "training_loss": 0.9255651540872527,
1285
+ "validation_loss": 0.8911700248718262,
1286
+ "validation_accuracy": 0.6940639269406392,
1287
+ "validation_f1_macro": 0.6447277343192168
1288
+ },
1289
+ {
1290
+ "epoch": 10.0,
1291
+ "training_loss": 0.8835772624829921,
1292
+ "validation_loss": 0.8481254577636719,
1293
+ "validation_accuracy": 0.6940639269406392,
1294
+ "validation_f1_macro": 0.6407901155929054
1295
+ },
1296
+ {
1297
+ "epoch": 11.0,
1298
+ "training_loss": 0.8595382840168185,
1299
+ "validation_loss": 0.8351685404777527,
1300
+ "validation_accuracy": 0.6986301369863014,
1301
+ "validation_f1_macro": 0.6622263514454271
1302
+ },
1303
+ {
1304
+ "epoch": 12.0,
1305
+ "training_loss": 0.8846968368786138,
1306
+ "validation_loss": 0.8464142680168152,
1307
+ "validation_accuracy": 0.6940639269406392,
1308
+ "validation_f1_macro": 0.6477420680141102
1309
+ },
1310
+ {
1311
+ "epoch": 13.0,
1312
+ "training_loss": 0.8019941928910046,
1313
+ "validation_loss": 0.804776668548584,
1314
+ "validation_accuracy": 0.6940639269406392,
1315
+ "validation_f1_macro": 0.659608964024234
1316
+ },
1317
+ {
1318
+ "epoch": 14.0,
1319
+ "training_loss": 0.8261272681922447,
1320
+ "validation_loss": 0.8410500884056091,
1321
+ "validation_accuracy": 0.6986301369863014,
1322
+ "validation_f1_macro": 0.6681086611998096
1323
+ },
1324
+ {
1325
+ "epoch": 15.0,
1326
+ "training_loss": 0.7871945776590487,
1327
+ "validation_loss": 0.7961083054542542,
1328
+ "validation_accuracy": 0.6940639269406392,
1329
+ "validation_f1_macro": 0.6631208004473412
1330
+ },
1331
+ {
1332
+ "epoch": 16.0,
1333
+ "training_loss": 0.7758532391815651,
1334
+ "validation_loss": 0.7845243811607361,
1335
+ "validation_accuracy": 0.6986301369863014,
1336
+ "validation_f1_macro": 0.671621371001291
1337
+ },
1338
+ {
1339
+ "epoch": 17.0,
1340
+ "training_loss": 0.7620533690219973,
1341
+ "validation_loss": 0.7771141529083252,
1342
+ "validation_accuracy": 0.7031963470319634,
1343
+ "validation_f1_macro": 0.6743542188258713
1344
+ },
1345
+ {
1346
+ "epoch": 18.0,
1347
+ "training_loss": 0.7353853663293327,
1348
+ "validation_loss": 0.7856449484825134,
1349
+ "validation_accuracy": 0.7031963470319634,
1350
+ "validation_f1_macro": 0.6742213392095463
1351
+ },
1352
+ {
1353
+ "epoch": 19.0,
1354
+ "training_loss": 0.7261699917839795,
1355
+ "validation_loss": 0.7977001070976257,
1356
+ "validation_accuracy": 0.7123287671232876,
1357
+ "validation_f1_macro": 0.6888408000002927
1358
+ },
1359
+ {
1360
+ "epoch": 20.0,
1361
+ "training_loss": 0.7368398461400009,
1362
+ "validation_loss": 0.7927899360656738,
1363
+ "validation_accuracy": 0.7077625570776256,
1364
+ "validation_f1_macro": 0.692141508174205
1365
+ },
1366
+ {
1367
+ "epoch": 21.0,
1368
+ "training_loss": 0.7344355328780848,
1369
+ "validation_loss": 0.7827250957489014,
1370
+ "validation_accuracy": 0.7123287671232876,
1371
+ "validation_f1_macro": 0.6867708679747256
1372
+ },
1373
+ {
1374
+ "epoch": 22.0,
1375
+ "training_loss": 0.6790950581794832,
1376
+ "validation_loss": 0.7775569558143616,
1377
+ "validation_accuracy": 0.7031963470319634,
1378
+ "validation_f1_macro": 0.6765388587616696
1379
+ },
1380
+ {
1381
+ "epoch": 23.0,
1382
+ "training_loss": 0.6984621402693958,
1383
+ "validation_loss": 0.7734518051147461,
1384
+ "validation_accuracy": 0.7123287671232876,
1385
+ "validation_f1_macro": 0.6877153551218909
1386
+ },
1387
+ {
1388
+ "epoch": 24.0,
1389
+ "training_loss": 0.6878889508363677,
1390
+ "validation_loss": 0.7655962705612183,
1391
+ "validation_accuracy": 0.7168949771689498,
1392
+ "validation_f1_macro": 0.7023463479684678
1393
+ },
1394
+ {
1395
+ "epoch": 25.0,
1396
+ "training_loss": 0.6925383333752795,
1397
+ "validation_loss": 0.757779061794281,
1398
+ "validation_accuracy": 0.7077625570776256,
1399
+ "validation_f1_macro": 0.6825144295123554
1400
+ },
1401
+ {
1402
+ "epoch": 26.0,
1403
+ "training_loss": 0.6952342565466718,
1404
+ "validation_loss": 0.7567448616027832,
1405
+ "validation_accuracy": 0.7123287671232876,
1406
+ "validation_f1_macro": 0.6996234126404904
1407
+ },
1408
+ {
1409
+ "epoch": 27.0,
1410
+ "training_loss": 0.6490353898304265,
1411
+ "validation_loss": 0.7367990016937256,
1412
+ "validation_accuracy": 0.6894977168949772,
1413
+ "validation_f1_macro": 0.6604909727865581
1414
+ },
1415
+ {
1416
+ "epoch": 28.0,
1417
+ "training_loss": 0.6474557097365217,
1418
+ "validation_loss": 0.7714822888374329,
1419
+ "validation_accuracy": 0.7077625570776256,
1420
+ "validation_f1_macro": 0.6954321333750253
1421
+ },
1422
+ {
1423
+ "epoch": 29.0,
1424
+ "training_loss": 0.6501478698195481,
1425
+ "validation_loss": 0.7639806866645813,
1426
+ "validation_accuracy": 0.7123287671232876,
1427
+ "validation_f1_macro": 0.7140885958427835
1428
+ },
1429
+ {
1430
+ "epoch": 30.0,
1431
+ "training_loss": 0.6764153576478725,
1432
+ "validation_loss": 0.7494249939918518,
1433
+ "validation_accuracy": 0.7123287671232876,
1434
+ "validation_f1_macro": 0.6996234126404904
1435
+ },
1436
+ {
1437
+ "epoch": 31.0,
1438
+ "training_loss": 0.6499752293272716,
1439
+ "validation_loss": 0.7761468291282654,
1440
+ "validation_accuracy": 0.7214611872146118,
1441
+ "validation_f1_macro": 0.708692259472666
1442
+ },
1443
+ {
1444
+ "epoch": 32.0,
1445
+ "training_loss": 0.6555054213215665,
1446
+ "validation_loss": 0.7684013247489929,
1447
+ "validation_accuracy": 0.7031963470319634,
1448
+ "validation_f1_macro": 0.6801009433656993
1449
+ },
1450
+ {
1451
+ "epoch": 33.0,
1452
+ "training_loss": 0.6549598781800852,
1453
+ "validation_loss": 0.7674687504768372,
1454
+ "validation_accuracy": 0.7031963470319634,
1455
+ "validation_f1_macro": 0.6881563966843104
1456
+ },
1457
+ {
1458
+ "epoch": 34.0,
1459
+ "training_loss": 0.6319407271902736,
1460
+ "validation_loss": 0.7449424266815186,
1461
+ "validation_accuracy": 0.7031963470319634,
1462
+ "validation_f1_macro": 0.693226179799515
1463
+ },
1464
+ {
1465
+ "epoch": 35.0,
1466
+ "training_loss": 0.6287453588916034,
1467
+ "validation_loss": 0.7504529356956482,
1468
+ "validation_accuracy": 0.7031963470319634,
1469
+ "validation_f1_macro": 0.693226179799515
1470
+ },
1471
+ {
1472
+ "epoch": 36.0,
1473
+ "training_loss": 0.6294544270125831,
1474
+ "validation_loss": 0.7591035962104797,
1475
+ "validation_accuracy": 0.7123287671232876,
1476
+ "validation_f1_macro": 0.7009088674911956
1477
+ },
1478
+ {
1479
+ "epoch": 37.0,
1480
+ "training_loss": 0.6553425941525436,
1481
+ "validation_loss": 0.768791139125824,
1482
+ "validation_accuracy": 0.7123287671232876,
1483
+ "validation_f1_macro": 0.694211786459072
1484
+ },
1485
+ {
1486
+ "epoch": 38.0,
1487
+ "training_loss": 0.6051805818953165,
1488
+ "validation_loss": 0.7681089639663696,
1489
+ "validation_accuracy": 0.7214611872146118,
1490
+ "validation_f1_macro": 0.7162450669231648
1491
+ },
1492
+ {
1493
+ "epoch": 39.0,
1494
+ "training_loss": 0.6314349792352537,
1495
+ "validation_loss": 0.7359892725944519,
1496
+ "validation_accuracy": 0.7123287671232876,
1497
+ "validation_f1_macro": 0.6996234126404904
1498
+ },
1499
+ {
1500
+ "epoch": 40.0,
1501
+ "training_loss": 0.6244277274463235,
1502
+ "validation_loss": 0.7540702223777771,
1503
+ "validation_accuracy": 0.7168949771689498,
1504
+ "validation_f1_macro": 0.7048316602752086
1505
+ },
1506
+ {
1507
+ "epoch": 41.0,
1508
+ "training_loss": 0.6361175441160435,
1509
+ "validation_loss": 0.7612143754959106,
1510
+ "validation_accuracy": 0.7123287671232876,
1511
+ "validation_f1_macro": 0.7021036366124976
1512
+ },
1513
+ {
1514
+ "epoch": 42.0,
1515
+ "training_loss": 0.6066937213990746,
1516
+ "validation_loss": 0.7710081934928894,
1517
+ "validation_accuracy": 0.7168949771689498,
1518
+ "validation_f1_macro": 0.7149340559925526
1519
+ },
1520
+ {
1521
+ "epoch": 43.0,
1522
+ "training_loss": 0.6121673482220348,
1523
+ "validation_loss": 0.7481098771095276,
1524
+ "validation_accuracy": 0.726027397260274,
1525
+ "validation_f1_macro": 0.7149526054029889
1526
+ },
1527
+ {
1528
+ "epoch": 44.0,
1529
+ "training_loss": 0.6385712423702565,
1530
+ "validation_loss": 0.7499731183052063,
1531
+ "validation_accuracy": 0.7077625570776256,
1532
+ "validation_f1_macro": 0.6983818499613152
1533
+ },
1534
+ {
1535
+ "epoch": 45.0,
1536
+ "training_loss": 0.6101498901844025,
1537
+ "validation_loss": 0.734427809715271,
1538
+ "validation_accuracy": 0.7168949771689498,
1539
+ "validation_f1_macro": 0.6948720961647448
1540
+ },
1541
+ {
1542
+ "epoch": 46.0,
1543
+ "training_loss": 0.5905797506977872,
1544
+ "validation_loss": 0.7421669363975525,
1545
+ "validation_accuracy": 0.7168949771689498,
1546
+ "validation_f1_macro": 0.7123222741391517
1547
+ },
1548
+ {
1549
+ "epoch": 47.0,
1550
+ "training_loss": 0.6082611465599479,
1551
+ "validation_loss": 0.7235217690467834,
1552
+ "validation_accuracy": 0.730593607305936,
1553
+ "validation_f1_macro": 0.714038944252953
1554
+ },
1555
+ {
1556
+ "epoch": 48.0,
1557
+ "training_loss": 0.5902958159766546,
1558
+ "validation_loss": 0.779229998588562,
1559
+ "validation_accuracy": 0.726027397260274,
1560
+ "validation_f1_macro": 0.7216398602451733
1561
+ },
1562
+ {
1563
+ "epoch": 49.0,
1564
+ "training_loss": 0.5838393728907515,
1565
+ "validation_loss": 0.7277742624282837,
1566
+ "validation_accuracy": 0.730593607305936,
1567
+ "validation_f1_macro": 0.7205624757048593
1568
+ },
1569
+ {
1570
+ "epoch": 50.0,
1571
+ "training_loss": 0.5906014115345187,
1572
+ "validation_loss": 0.7636589407920837,
1573
+ "validation_accuracy": 0.7168949771689498,
1574
+ "validation_f1_macro": 0.7135170432604537
1575
+ }
1576
+ ]
1577
+ },
1578
+ {
1579
+ "limit": 50,
1580
+ "seed": 7,
1581
+ "hyperparameters": {
1582
+ "epochs": 50,
1583
+ "batch_size": 16,
1584
+ "learning_rate": 0.001,
1585
+ "weight_decay": 0.0001,
1586
+ "dropout": 0.3,
1587
+ "projection_dim": 64,
1588
+ "fusion_dim": 12,
1589
+ "validation_fraction": 0.25,
1590
+ "optimizer": "Adam",
1591
+ "class_weighting": true,
1592
+ "early_stopping_patience": 12,
1593
+ "seed": 7
1594
+ },
1595
+ "epochs_completed": 44,
1596
+ "best_epoch": 32,
1597
+ "early_stopped": true,
1598
+ "hit_max": false,
1599
+ "train_loss_at_best": 0.661002103875323,
1600
+ "val_loss_at_best": 0.6984432935714722,
1601
+ "accuracy": 0.771689497716895,
1602
+ "f1_macro": 0.7524499679210638,
1603
+ "f1_weighted": 0.7826279144428353,
1604
+ "recall": {
1605
+ "0.0": 0.8620689655172413,
1606
+ "1.0": 0.7878787878787878,
1607
+ "2.0": 0.6363636363636364,
1608
+ "3.0": 0.7614678899082569,
1609
+ "4.0": 0.7567567567567568
1610
+ },
1611
+ "pred_counts": {
1612
+ "0.0": 51,
1613
+ "1.0": 28,
1614
+ "2.0": 8,
1615
+ "3.0": 91,
1616
+ "4.0": 41
1617
+ },
1618
+ "collapsed": false,
1619
+ "history": [
1620
+ {
1621
+ "epoch": 1.0,
1622
+ "training_loss": 1.62655437574154,
1623
+ "validation_loss": 1.589923620223999,
1624
+ "validation_accuracy": 0.5296803652968036,
1625
+ "validation_f1_macro": 0.20888953657825704
1626
+ },
1627
+ {
1628
+ "epoch": 2.0,
1629
+ "training_loss": 1.5845980702376947,
1630
+ "validation_loss": 1.548748254776001,
1631
+ "validation_accuracy": 0.2785388127853881,
1632
+ "validation_f1_macro": 0.21267281105990782
1633
+ },
1634
+ {
1635
+ "epoch": 3.0,
1636
+ "training_loss": 1.468249797821045,
1637
+ "validation_loss": 1.371727466583252,
1638
+ "validation_accuracy": 0.680365296803653,
1639
+ "validation_f1_macro": 0.5202160299834718
1640
+ },
1641
+ {
1642
+ "epoch": 4.0,
1643
+ "training_loss": 1.32800172596443,
1644
+ "validation_loss": 1.2302584648132324,
1645
+ "validation_accuracy": 0.6666666666666666,
1646
+ "validation_f1_macro": 0.5269408837466517
1647
+ },
1648
+ {
1649
+ "epoch": 5.0,
1650
+ "training_loss": 1.1880390469620867,
1651
+ "validation_loss": 1.1226760149002075,
1652
+ "validation_accuracy": 0.684931506849315,
1653
+ "validation_f1_macro": 0.5486220093958027
1654
+ },
1655
+ {
1656
+ "epoch": 6.0,
1657
+ "training_loss": 1.0946302021422036,
1658
+ "validation_loss": 1.04401433467865,
1659
+ "validation_accuracy": 0.6894977168949772,
1660
+ "validation_f1_macro": 0.5714749984315202
1661
+ },
1662
+ {
1663
+ "epoch": 7.0,
1664
+ "training_loss": 1.030611061468357,
1665
+ "validation_loss": 1.023421287536621,
1666
+ "validation_accuracy": 0.6712328767123288,
1667
+ "validation_f1_macro": 0.5742135730750795
1668
+ },
1669
+ {
1670
+ "epoch": 8.0,
1671
+ "training_loss": 0.9878716337971571,
1672
+ "validation_loss": 0.9709606170654297,
1673
+ "validation_accuracy": 0.684931506849315,
1674
+ "validation_f1_macro": 0.5975834311588126
1675
+ },
1676
+ {
1677
+ "epoch": 9.0,
1678
+ "training_loss": 0.9323210745322995,
1679
+ "validation_loss": 0.9185066819190979,
1680
+ "validation_accuracy": 0.7168949771689498,
1681
+ "validation_f1_macro": 0.6833981316972961
1682
+ },
1683
+ {
1684
+ "epoch": 10.0,
1685
+ "training_loss": 0.917416339967309,
1686
+ "validation_loss": 0.8980910181999207,
1687
+ "validation_accuracy": 0.726027397260274,
1688
+ "validation_f1_macro": 0.6809404013460666
1689
+ },
1690
+ {
1691
+ "epoch": 11.0,
1692
+ "training_loss": 0.8819241145762001,
1693
+ "validation_loss": 0.8560897707939148,
1694
+ "validation_accuracy": 0.7488584474885844,
1695
+ "validation_f1_macro": 0.727852427060365
1696
+ },
1697
+ {
1698
+ "epoch": 12.0,
1699
+ "training_loss": 0.8653858725617571,
1700
+ "validation_loss": 0.8336981534957886,
1701
+ "validation_accuracy": 0.7442922374429224,
1702
+ "validation_f1_macro": 0.7134181842050694
1703
+ },
1704
+ {
1705
+ "epoch": 13.0,
1706
+ "training_loss": 0.8303249274812093,
1707
+ "validation_loss": 0.8161752820014954,
1708
+ "validation_accuracy": 0.7488584474885844,
1709
+ "validation_f1_macro": 0.7285636610973818
1710
+ },
1711
+ {
1712
+ "epoch": 14.0,
1713
+ "training_loss": 0.8463927158495275,
1714
+ "validation_loss": 0.7870453000068665,
1715
+ "validation_accuracy": 0.7488584474885844,
1716
+ "validation_f1_macro": 0.7248277326555709
1717
+ },
1718
+ {
1719
+ "epoch": 15.0,
1720
+ "training_loss": 0.8173226692327639,
1721
+ "validation_loss": 0.8049059510231018,
1722
+ "validation_accuracy": 0.7397260273972602,
1723
+ "validation_f1_macro": 0.7151836250752119
1724
+ },
1725
+ {
1726
+ "epoch": 16.0,
1727
+ "training_loss": 0.8112960501414973,
1728
+ "validation_loss": 0.8151602745056152,
1729
+ "validation_accuracy": 0.730593607305936,
1730
+ "validation_f1_macro": 0.7007828498472742
1731
+ },
1732
+ {
1733
+ "epoch": 17.0,
1734
+ "training_loss": 0.7827643555838887,
1735
+ "validation_loss": 0.8070281744003296,
1736
+ "validation_accuracy": 0.7351598173515982,
1737
+ "validation_f1_macro": 0.7055984574326514
1738
+ },
1739
+ {
1740
+ "epoch": 18.0,
1741
+ "training_loss": 0.8010357509299022,
1742
+ "validation_loss": 0.7747320532798767,
1743
+ "validation_accuracy": 0.7442922374429224,
1744
+ "validation_f1_macro": 0.7083783373985632
1745
+ },
1746
+ {
1747
+ "epoch": 19.0,
1748
+ "training_loss": 0.7957447483772184,
1749
+ "validation_loss": 0.7730648517608643,
1750
+ "validation_accuracy": 0.7397260273972602,
1751
+ "validation_f1_macro": 0.7056989471814845
1752
+ },
1753
+ {
1754
+ "epoch": 20.0,
1755
+ "training_loss": 0.7606793692926082,
1756
+ "validation_loss": 0.756865918636322,
1757
+ "validation_accuracy": 0.7442922374429224,
1758
+ "validation_f1_macro": 0.724972322759208
1759
+ },
1760
+ {
1761
+ "epoch": 21.0,
1762
+ "training_loss": 0.7359354161634678,
1763
+ "validation_loss": 0.7705332636833191,
1764
+ "validation_accuracy": 0.7442922374429224,
1765
+ "validation_f1_macro": 0.7109910765242468
1766
+ },
1767
+ {
1768
+ "epoch": 22.0,
1769
+ "training_loss": 0.7506408742288264,
1770
+ "validation_loss": 0.7497557997703552,
1771
+ "validation_accuracy": 0.7488584474885844,
1772
+ "validation_f1_macro": 0.7229705455948657
1773
+ },
1774
+ {
1775
+ "epoch": 23.0,
1776
+ "training_loss": 0.7357381807594765,
1777
+ "validation_loss": 0.7553285956382751,
1778
+ "validation_accuracy": 0.7397260273972602,
1779
+ "validation_f1_macro": 0.7082830552595245
1780
+ },
1781
+ {
1782
+ "epoch": 24.0,
1783
+ "training_loss": 0.7082513803389014,
1784
+ "validation_loss": 0.75914466381073,
1785
+ "validation_accuracy": 0.7351598173515982,
1786
+ "validation_f1_macro": 0.7139834784486362
1787
+ },
1788
+ {
1789
+ "epoch": 25.0,
1790
+ "training_loss": 0.7012432530158903,
1791
+ "validation_loss": 0.7224891185760498,
1792
+ "validation_accuracy": 0.771689497716895,
1793
+ "validation_f1_macro": 0.7516536481181431
1794
+ },
1795
+ {
1796
+ "epoch": 26.0,
1797
+ "training_loss": 0.702656633970214,
1798
+ "validation_loss": 0.7347502708435059,
1799
+ "validation_accuracy": 0.7442922374429224,
1800
+ "validation_f1_macro": 0.7160051749244645
1801
+ },
1802
+ {
1803
+ "epoch": 27.0,
1804
+ "training_loss": 0.699109113071023,
1805
+ "validation_loss": 0.7329419851303101,
1806
+ "validation_accuracy": 0.7488584474885844,
1807
+ "validation_f1_macro": 0.7380830487817869
1808
+ },
1809
+ {
1810
+ "epoch": 28.0,
1811
+ "training_loss": 0.6665385103807216,
1812
+ "validation_loss": 0.7400558590888977,
1813
+ "validation_accuracy": 0.7397260273972602,
1814
+ "validation_f1_macro": 0.7070388770928432
1815
+ },
1816
+ {
1817
+ "epoch": 29.0,
1818
+ "training_loss": 0.6689512576998734,
1819
+ "validation_loss": 0.7360281944274902,
1820
+ "validation_accuracy": 0.7488584474885844,
1821
+ "validation_f1_macro": 0.7391559722160727
1822
+ },
1823
+ {
1824
+ "epoch": 30.0,
1825
+ "training_loss": 0.6843016118538089,
1826
+ "validation_loss": 0.7508696913719177,
1827
+ "validation_accuracy": 0.7397260273972602,
1828
+ "validation_f1_macro": 0.7082830552595245
1829
+ },
1830
+ {
1831
+ "epoch": 31.0,
1832
+ "training_loss": 0.6739339893910943,
1833
+ "validation_loss": 0.7405543923377991,
1834
+ "validation_accuracy": 0.7351598173515982,
1835
+ "validation_f1_macro": 0.7156167475000182
1836
+ },
1837
+ {
1838
+ "epoch": 32.0,
1839
+ "training_loss": 0.661002103875323,
1840
+ "validation_loss": 0.6984432935714722,
1841
+ "validation_accuracy": 0.771689497716895,
1842
+ "validation_f1_macro": 0.7524499679210638
1843
+ },
1844
+ {
1845
+ "epoch": 33.0,
1846
+ "training_loss": 0.6655780258702069,
1847
+ "validation_loss": 0.7283544540405273,
1848
+ "validation_accuracy": 0.7488584474885844,
1849
+ "validation_f1_macro": 0.7389977447779943
1850
+ },
1851
+ {
1852
+ "epoch": 34.0,
1853
+ "training_loss": 0.658410701082974,
1854
+ "validation_loss": 0.7165646553039551,
1855
+ "validation_accuracy": 0.7534246575342466,
1856
+ "validation_f1_macro": 0.7410359336394337
1857
+ },
1858
+ {
1859
+ "epoch": 35.0,
1860
+ "training_loss": 0.6328801788934847,
1861
+ "validation_loss": 0.7204789519309998,
1862
+ "validation_accuracy": 0.7534246575342466,
1863
+ "validation_f1_macro": 0.7420996007457028
1864
+ },
1865
+ {
1866
+ "epoch": 36.0,
1867
+ "training_loss": 0.634215030001431,
1868
+ "validation_loss": 0.7153851389884949,
1869
+ "validation_accuracy": 0.7534246575342466,
1870
+ "validation_f1_macro": 0.7344943048421657
1871
+ },
1872
+ {
1873
+ "epoch": 37.0,
1874
+ "training_loss": 0.6196055564938522,
1875
+ "validation_loss": 0.7263249158859253,
1876
+ "validation_accuracy": 0.7488584474885844,
1877
+ "validation_f1_macro": 0.7404189709107568
1878
+ },
1879
+ {
1880
+ "epoch": 38.0,
1881
+ "training_loss": 0.6232949244539913,
1882
+ "validation_loss": 0.7163978219032288,
1883
+ "validation_accuracy": 0.7579908675799086,
1884
+ "validation_f1_macro": 0.7450726024380072
1885
+ },
1886
+ {
1887
+ "epoch": 39.0,
1888
+ "training_loss": 0.627199369232829,
1889
+ "validation_loss": 0.7059804201126099,
1890
+ "validation_accuracy": 0.7579908675799086,
1891
+ "validation_f1_macro": 0.7450726024380072
1892
+ },
1893
+ {
1894
+ "epoch": 40.0,
1895
+ "training_loss": 0.6092390396246096,
1896
+ "validation_loss": 0.703453004360199,
1897
+ "validation_accuracy": 0.7534246575342466,
1898
+ "validation_f1_macro": 0.7410359336394337
1899
+ },
1900
+ {
1901
+ "epoch": 41.0,
1902
+ "training_loss": 0.6367075861227222,
1903
+ "validation_loss": 0.7127879858016968,
1904
+ "validation_accuracy": 0.7579908675799086,
1905
+ "validation_f1_macro": 0.7450726024380072
1906
+ },
1907
+ {
1908
+ "epoch": 42.0,
1909
+ "training_loss": 0.6207717867886148,
1910
+ "validation_loss": 0.7062743902206421,
1911
+ "validation_accuracy": 0.7579908675799086,
1912
+ "validation_f1_macro": 0.7450726024380072
1913
+ },
1914
+ {
1915
+ "epoch": 43.0,
1916
+ "training_loss": 0.599103124403372,
1917
+ "validation_loss": 0.7054443955421448,
1918
+ "validation_accuracy": 0.7534246575342466,
1919
+ "validation_f1_macro": 0.7410359336394337
1920
+ },
1921
+ {
1922
+ "epoch": 44.0,
1923
+ "training_loss": 0.5925045471365858,
1924
+ "validation_loss": 0.7284175157546997,
1925
+ "validation_accuracy": 0.7534246575342466,
1926
+ "validation_f1_macro": 0.7344943048421657
1927
+ }
1928
+ ]
1929
+ },
1930
+ {
1931
+ "limit": 50,
1932
+ "seed": 42,
1933
+ "hyperparameters": {
1934
+ "epochs": 50,
1935
+ "batch_size": 16,
1936
+ "learning_rate": 0.001,
1937
+ "weight_decay": 0.0001,
1938
+ "dropout": 0.3,
1939
+ "projection_dim": 64,
1940
+ "fusion_dim": 12,
1941
+ "validation_fraction": 0.25,
1942
+ "optimizer": "Adam",
1943
+ "class_weighting": true,
1944
+ "early_stopping_patience": 12,
1945
+ "seed": 42
1946
+ },
1947
+ "epochs_completed": 50,
1948
+ "best_epoch": 38,
1949
+ "early_stopped": true,
1950
+ "hit_max": false,
1951
+ "train_loss_at_best": 0.6739114813688325,
1952
+ "val_loss_at_best": 0.6592805981636047,
1953
+ "accuracy": 0.7671232876712328,
1954
+ "f1_macro": 0.7474258785478297,
1955
+ "f1_weighted": 0.7740214080920172,
1956
+ "recall": {
1957
+ "0.0": 0.7586206896551724,
1958
+ "1.0": 0.9393939393939394,
1959
+ "2.0": 0.9090909090909091,
1960
+ "3.0": 0.7706422018348624,
1961
+ "4.0": 0.5675675675675675
1962
+ },
1963
+ "pred_counts": {
1964
+ "0.0": 46,
1965
+ "1.0": 32,
1966
+ "2.0": 16,
1967
+ "3.0": 96,
1968
+ "4.0": 29
1969
+ },
1970
+ "collapsed": false,
1971
+ "history": [
1972
+ {
1973
+ "epoch": 1.0,
1974
+ "training_loss": 1.6413315621817983,
1975
+ "validation_loss": 1.5951603651046753,
1976
+ "validation_accuracy": 0.2054794520547945,
1977
+ "validation_f1_macro": 0.09564573659061848
1978
+ },
1979
+ {
1980
+ "epoch": 2.0,
1981
+ "training_loss": 1.6051898903963042,
1982
+ "validation_loss": 1.5471464395523071,
1983
+ "validation_accuracy": 0.1506849315068493,
1984
+ "validation_f1_macro": 0.05238095238095238
1985
+ },
1986
+ {
1987
+ "epoch": 3.0,
1988
+ "training_loss": 1.541287974613469,
1989
+ "validation_loss": 1.4482790231704712,
1990
+ "validation_accuracy": 0.3744292237442922,
1991
+ "validation_f1_macro": 0.3273361566811023
1992
+ },
1993
+ {
1994
+ "epoch": 4.0,
1995
+ "training_loss": 1.4257210987370188,
1996
+ "validation_loss": 1.3203811645507812,
1997
+ "validation_accuracy": 0.6986301369863014,
1998
+ "validation_f1_macro": 0.5610404919891623
1999
+ },
2000
+ {
2001
+ "epoch": 5.0,
2002
+ "training_loss": 1.2903572379088983,
2003
+ "validation_loss": 1.1790156364440918,
2004
+ "validation_accuracy": 0.6712328767123288,
2005
+ "validation_f1_macro": 0.5303037900262373
2006
+ },
2007
+ {
2008
+ "epoch": 6.0,
2009
+ "training_loss": 1.192301835955643,
2010
+ "validation_loss": 1.1542149782180786,
2011
+ "validation_accuracy": 0.6301369863013698,
2012
+ "validation_f1_macro": 0.5829572914647542
2013
+ },
2014
+ {
2015
+ "epoch": 7.0,
2016
+ "training_loss": 1.0858885224272565,
2017
+ "validation_loss": 0.9872011542320251,
2018
+ "validation_accuracy": 0.7123287671232876,
2019
+ "validation_f1_macro": 0.6546160686293118
2020
+ },
2021
+ {
2022
+ "epoch": 8.0,
2023
+ "training_loss": 1.0201251594031728,
2024
+ "validation_loss": 0.8829815983772278,
2025
+ "validation_accuracy": 0.7351598173515982,
2026
+ "validation_f1_macro": 0.6462817282590345
2027
+ },
2028
+ {
2029
+ "epoch": 9.0,
2030
+ "training_loss": 0.9495046894724776,
2031
+ "validation_loss": 0.8631280064582825,
2032
+ "validation_accuracy": 0.7397260273972602,
2033
+ "validation_f1_macro": 0.6743014067193173
2034
+ },
2035
+ {
2036
+ "epoch": 10.0,
2037
+ "training_loss": 0.9342777976175634,
2038
+ "validation_loss": 0.9005348086357117,
2039
+ "validation_accuracy": 0.7351598173515982,
2040
+ "validation_f1_macro": 0.689469101097008
2041
+ },
2042
+ {
2043
+ "epoch": 11.0,
2044
+ "training_loss": 0.9066276433991223,
2045
+ "validation_loss": 0.8241670727729797,
2046
+ "validation_accuracy": 0.7351598173515982,
2047
+ "validation_f1_macro": 0.678359222040761
2048
+ },
2049
+ {
2050
+ "epoch": 12.0,
2051
+ "training_loss": 0.87182052833278,
2052
+ "validation_loss": 0.8306285738945007,
2053
+ "validation_accuracy": 0.7625570776255708,
2054
+ "validation_f1_macro": 0.7570054945054945
2055
+ },
2056
+ {
2057
+ "epoch": 13.0,
2058
+ "training_loss": 0.8655777348250877,
2059
+ "validation_loss": 0.7712150812149048,
2060
+ "validation_accuracy": 0.7488584474885844,
2061
+ "validation_f1_macro": 0.7153631638631638
2062
+ },
2063
+ {
2064
+ "epoch": 14.0,
2065
+ "training_loss": 0.8454430975565096,
2066
+ "validation_loss": 0.7842550277709961,
2067
+ "validation_accuracy": 0.7442922374429224,
2068
+ "validation_f1_macro": 0.7185530009472144
2069
+ },
2070
+ {
2071
+ "epoch": 15.0,
2072
+ "training_loss": 0.8387555247399865,
2073
+ "validation_loss": 0.797715425491333,
2074
+ "validation_accuracy": 0.7442922374429224,
2075
+ "validation_f1_macro": 0.7263994447222295
2076
+ },
2077
+ {
2078
+ "epoch": 16.0,
2079
+ "training_loss": 0.8214820347181181,
2080
+ "validation_loss": 0.7511270642280579,
2081
+ "validation_accuracy": 0.7442922374429224,
2082
+ "validation_f1_macro": 0.714060392728248
2083
+ },
2084
+ {
2085
+ "epoch": 17.0,
2086
+ "training_loss": 0.8219952263483187,
2087
+ "validation_loss": 0.8021854162216187,
2088
+ "validation_accuracy": 0.7488584474885844,
2089
+ "validation_f1_macro": 0.7480525030525029
2090
+ },
2091
+ {
2092
+ "epoch": 18.0,
2093
+ "training_loss": 0.7887809269311952,
2094
+ "validation_loss": 0.7537360787391663,
2095
+ "validation_accuracy": 0.7534246575342466,
2096
+ "validation_f1_macro": 0.747718705511105
2097
+ },
2098
+ {
2099
+ "epoch": 19.0,
2100
+ "training_loss": 0.7926808195870098,
2101
+ "validation_loss": 0.7402254343032837,
2102
+ "validation_accuracy": 0.7488584474885844,
2103
+ "validation_f1_macro": 0.7275013872674709
2104
+ },
2105
+ {
2106
+ "epoch": 20.0,
2107
+ "training_loss": 0.7622463841263841,
2108
+ "validation_loss": 0.7453633546829224,
2109
+ "validation_accuracy": 0.7579908675799086,
2110
+ "validation_f1_macro": 0.7481171465278779
2111
+ },
2112
+ {
2113
+ "epoch": 21.0,
2114
+ "training_loss": 0.775284816579121,
2115
+ "validation_loss": 0.7456088662147522,
2116
+ "validation_accuracy": 0.7579908675799086,
2117
+ "validation_f1_macro": 0.7482920706416814
2118
+ },
2119
+ {
2120
+ "epoch": 22.0,
2121
+ "training_loss": 0.7533575325477414,
2122
+ "validation_loss": 0.7158185243606567,
2123
+ "validation_accuracy": 0.7625570776255708,
2124
+ "validation_f1_macro": 0.7533803044226147
2125
+ },
2126
+ {
2127
+ "epoch": 23.0,
2128
+ "training_loss": 0.7445236517161857,
2129
+ "validation_loss": 0.7244470119476318,
2130
+ "validation_accuracy": 0.7579908675799086,
2131
+ "validation_f1_macro": 0.761157481482884
2132
+ },
2133
+ {
2134
+ "epoch": 24.0,
2135
+ "training_loss": 0.777114295377964,
2136
+ "validation_loss": 0.7160983085632324,
2137
+ "validation_accuracy": 0.7671232876712328,
2138
+ "validation_f1_macro": 0.7669865422393788
2139
+ },
2140
+ {
2141
+ "epoch": 25.0,
2142
+ "training_loss": 0.7019447509835406,
2143
+ "validation_loss": 0.7080726623535156,
2144
+ "validation_accuracy": 0.7534246575342466,
2145
+ "validation_f1_macro": 0.7259406795109891
2146
+ },
2147
+ {
2148
+ "epoch": 26.0,
2149
+ "training_loss": 0.7030961208227204,
2150
+ "validation_loss": 0.7158434391021729,
2151
+ "validation_accuracy": 0.7625570776255708,
2152
+ "validation_f1_macro": 0.7512252258700489
2153
+ },
2154
+ {
2155
+ "epoch": 27.0,
2156
+ "training_loss": 0.7424630958859514,
2157
+ "validation_loss": 0.7906358242034912,
2158
+ "validation_accuracy": 0.7351598173515982,
2159
+ "validation_f1_macro": 0.7316288331342096
2160
+ },
2161
+ {
2162
+ "epoch": 28.0,
2163
+ "training_loss": 0.7209375667862776,
2164
+ "validation_loss": 0.7476458549499512,
2165
+ "validation_accuracy": 0.7488584474885844,
2166
+ "validation_f1_macro": 0.7402686202686202
2167
+ },
2168
+ {
2169
+ "epoch": 29.0,
2170
+ "training_loss": 0.69572512696429,
2171
+ "validation_loss": 0.7600176334381104,
2172
+ "validation_accuracy": 0.7534246575342466,
2173
+ "validation_f1_macro": 0.7432034632034632
2174
+ },
2175
+ {
2176
+ "epoch": 30.0,
2177
+ "training_loss": 0.7271860489031163,
2178
+ "validation_loss": 0.6967012882232666,
2179
+ "validation_accuracy": 0.7625570776255708,
2180
+ "validation_f1_macro": 0.748410452041456
2181
+ },
2182
+ {
2183
+ "epoch": 31.0,
2184
+ "training_loss": 0.7023421089823653,
2185
+ "validation_loss": 0.6869670152664185,
2186
+ "validation_accuracy": 0.771689497716895,
2187
+ "validation_f1_macro": 0.7708608058608057
2188
+ },
2189
+ {
2190
+ "epoch": 32.0,
2191
+ "training_loss": 0.7001944823962886,
2192
+ "validation_loss": 0.6629666686058044,
2193
+ "validation_accuracy": 0.7534246575342466,
2194
+ "validation_f1_macro": 0.7455364218244153
2195
+ },
2196
+ {
2197
+ "epoch": 33.0,
2198
+ "training_loss": 0.6928130410066465,
2199
+ "validation_loss": 0.7037253975868225,
2200
+ "validation_accuracy": 0.771689497716895,
2201
+ "validation_f1_macro": 0.7682012832528295
2202
+ },
2203
+ {
2204
+ "epoch": 34.0,
2205
+ "training_loss": 0.6658707871669676,
2206
+ "validation_loss": 0.7267974615097046,
2207
+ "validation_accuracy": 0.7488584474885844,
2208
+ "validation_f1_macro": 0.7537267670601004
2209
+ },
2210
+ {
2211
+ "epoch": 35.0,
2212
+ "training_loss": 0.6562810256713774,
2213
+ "validation_loss": 0.7219017148017883,
2214
+ "validation_accuracy": 0.771689497716895,
2215
+ "validation_f1_macro": 0.7664590697119064
2216
+ },
2217
+ {
2218
+ "epoch": 36.0,
2219
+ "training_loss": 0.6679604137089195,
2220
+ "validation_loss": 0.7252770662307739,
2221
+ "validation_accuracy": 0.7671232876712328,
2222
+ "validation_f1_macro": 0.757229532482369
2223
+ },
2224
+ {
2225
+ "epoch": 37.0,
2226
+ "training_loss": 0.6854233596383071,
2227
+ "validation_loss": 0.7343176007270813,
2228
+ "validation_accuracy": 0.7625570776255708,
2229
+ "validation_f1_macro": 0.754186366259151
2230
+ },
2231
+ {
2232
+ "epoch": 38.0,
2233
+ "training_loss": 0.6739114813688325,
2234
+ "validation_loss": 0.6592805981636047,
2235
+ "validation_accuracy": 0.7671232876712328,
2236
+ "validation_f1_macro": 0.7474258785478297
2237
+ },
2238
+ {
2239
+ "epoch": 39.0,
2240
+ "training_loss": 0.6822220225159715,
2241
+ "validation_loss": 0.6769421696662903,
2242
+ "validation_accuracy": 0.7625570776255708,
2243
+ "validation_f1_macro": 0.7536289821289821
2244
+ },
2245
+ {
2246
+ "epoch": 40.0,
2247
+ "training_loss": 0.6873945408478016,
2248
+ "validation_loss": 0.7250295877456665,
2249
+ "validation_accuracy": 0.7671232876712328,
2250
+ "validation_f1_macro": 0.7680280026990554
2251
+ },
2252
+ {
2253
+ "epoch": 41.0,
2254
+ "training_loss": 0.6764432002858418,
2255
+ "validation_loss": 0.7391007542610168,
2256
+ "validation_accuracy": 0.7488584474885844,
2257
+ "validation_f1_macro": 0.7396409969111502
2258
+ },
2259
+ {
2260
+ "epoch": 42.0,
2261
+ "training_loss": 0.6339026725873714,
2262
+ "validation_loss": 0.6608799695968628,
2263
+ "validation_accuracy": 0.7488584474885844,
2264
+ "validation_f1_macro": 0.7407714164293111
2265
+ },
2266
+ {
2267
+ "epoch": 43.0,
2268
+ "training_loss": 0.6590220651975492,
2269
+ "validation_loss": 0.6673893332481384,
2270
+ "validation_accuracy": 0.776255707762557,
2271
+ "validation_f1_macro": 0.7676702673517622
2272
+ },
2273
+ {
2274
+ "epoch": 44.0,
2275
+ "training_loss": 0.6262539588823551,
2276
+ "validation_loss": 0.6594874262809753,
2277
+ "validation_accuracy": 0.771689497716895,
2278
+ "validation_f1_macro": 0.771904365904366
2279
+ },
2280
+ {
2281
+ "epoch": 45.0,
2282
+ "training_loss": 0.6377055292449346,
2283
+ "validation_loss": 0.729316771030426,
2284
+ "validation_accuracy": 0.7671232876712328,
2285
+ "validation_f1_macro": 0.7633931438869814
2286
+ },
2287
+ {
2288
+ "epoch": 46.0,
2289
+ "training_loss": 0.6469743251800537,
2290
+ "validation_loss": 0.6833896636962891,
2291
+ "validation_accuracy": 0.7579908675799086,
2292
+ "validation_f1_macro": 0.7558843937791307
2293
+ },
2294
+ {
2295
+ "epoch": 47.0,
2296
+ "training_loss": 0.6347538745257912,
2297
+ "validation_loss": 0.6690486669540405,
2298
+ "validation_accuracy": 0.7534246575342466,
2299
+ "validation_f1_macro": 0.7504796029796029
2300
+ },
2301
+ {
2302
+ "epoch": 48.0,
2303
+ "training_loss": 0.6262150170599542,
2304
+ "validation_loss": 0.7333135008811951,
2305
+ "validation_accuracy": 0.7625570776255708,
2306
+ "validation_f1_macro": 0.7590531975184693
2307
+ },
2308
+ {
2309
+ "epoch": 49.0,
2310
+ "training_loss": 0.6454702768383956,
2311
+ "validation_loss": 0.6962722539901733,
2312
+ "validation_accuracy": 0.7579908675799086,
2313
+ "validation_f1_macro": 0.7579154497562188
2314
+ },
2315
+ {
2316
+ "epoch": 50.0,
2317
+ "training_loss": 0.652672092362148,
2318
+ "validation_loss": 0.7580622434616089,
2319
+ "validation_accuracy": 0.7534246575342466,
2320
+ "validation_f1_macro": 0.7515952999180847
2321
+ }
2322
+ ]
2323
+ },
2324
+ {
2325
+ "limit": 50,
2326
+ "seed": 123,
2327
+ "hyperparameters": {
2328
+ "epochs": 50,
2329
+ "batch_size": 16,
2330
+ "learning_rate": 0.001,
2331
+ "weight_decay": 0.0001,
2332
+ "dropout": 0.3,
2333
+ "projection_dim": 64,
2334
+ "fusion_dim": 12,
2335
+ "validation_fraction": 0.25,
2336
+ "optimizer": "Adam",
2337
+ "class_weighting": true,
2338
+ "early_stopping_patience": 12,
2339
+ "seed": 123
2340
+ },
2341
+ "epochs_completed": 49,
2342
+ "best_epoch": 37,
2343
+ "early_stopped": true,
2344
+ "hit_max": false,
2345
+ "train_loss_at_best": 0.7013607054221921,
2346
+ "val_loss_at_best": 0.6157943606376648,
2347
+ "accuracy": 0.7625570776255708,
2348
+ "f1_macro": 0.7396509109600558,
2349
+ "f1_weighted": 0.773059563038045,
2350
+ "recall": {
2351
+ "0.0": 0.896551724137931,
2352
+ "1.0": 0.8787878787878788,
2353
+ "2.0": 0.7272727272727273,
2354
+ "3.0": 0.7431192660550459,
2355
+ "4.0": 0.6216216216216216
2356
+ },
2357
+ "pred_counts": {
2358
+ "0.0": 53,
2359
+ "1.0": 29,
2360
+ "2.0": 13,
2361
+ "3.0": 90,
2362
+ "4.0": 34
2363
+ },
2364
+ "collapsed": false,
2365
+ "history": [
2366
+ {
2367
+ "epoch": 1.0,
2368
+ "training_loss": 1.635941365870034,
2369
+ "validation_loss": 1.5491065979003906,
2370
+ "validation_accuracy": 0.4977168949771689,
2371
+ "validation_f1_macro": 0.1329268292682927
2372
+ },
2373
+ {
2374
+ "epoch": 2.0,
2375
+ "training_loss": 1.60081900619879,
2376
+ "validation_loss": 1.565390706062317,
2377
+ "validation_accuracy": 0.2054794520547945,
2378
+ "validation_f1_macro": 0.13772619984264362
2379
+ },
2380
+ {
2381
+ "epoch": 3.0,
2382
+ "training_loss": 1.558504796609646,
2383
+ "validation_loss": 1.463072657585144,
2384
+ "validation_accuracy": 0.3515981735159817,
2385
+ "validation_f1_macro": 0.34616743982970455
2386
+ },
2387
+ {
2388
+ "epoch": 4.0,
2389
+ "training_loss": 1.4567168573053872,
2390
+ "validation_loss": 1.3390640020370483,
2391
+ "validation_accuracy": 0.684931506849315,
2392
+ "validation_f1_macro": 0.5221375511647885
2393
+ },
2394
+ {
2395
+ "epoch": 5.0,
2396
+ "training_loss": 1.3068824832032366,
2397
+ "validation_loss": 1.1395671367645264,
2398
+ "validation_accuracy": 0.6438356164383562,
2399
+ "validation_f1_macro": 0.38348620210442724
2400
+ },
2401
+ {
2402
+ "epoch": 6.0,
2403
+ "training_loss": 1.2041963615068576,
2404
+ "validation_loss": 1.1338005065917969,
2405
+ "validation_accuracy": 0.639269406392694,
2406
+ "validation_f1_macro": 0.6026584562317626
2407
+ },
2408
+ {
2409
+ "epoch": 7.0,
2410
+ "training_loss": 1.1291042668063467,
2411
+ "validation_loss": 1.0177698135375977,
2412
+ "validation_accuracy": 0.6986301369863014,
2413
+ "validation_f1_macro": 0.6605897494132789
2414
+ },
2415
+ {
2416
+ "epoch": 8.0,
2417
+ "training_loss": 1.0272816027083047,
2418
+ "validation_loss": 0.9249964356422424,
2419
+ "validation_accuracy": 0.726027397260274,
2420
+ "validation_f1_macro": 0.6695071782095134
2421
+ },
2422
+ {
2423
+ "epoch": 9.0,
2424
+ "training_loss": 0.9894306848688823,
2425
+ "validation_loss": 0.8962472677230835,
2426
+ "validation_accuracy": 0.7031963470319634,
2427
+ "validation_f1_macro": 0.6633021248201514
2428
+ },
2429
+ {
2430
+ "epoch": 10.0,
2431
+ "training_loss": 0.9454721168773931,
2432
+ "validation_loss": 0.8671642541885376,
2433
+ "validation_accuracy": 0.7168949771689498,
2434
+ "validation_f1_macro": 0.6884058429093891
2435
+ },
2436
+ {
2437
+ "epoch": 11.0,
2438
+ "training_loss": 0.9224555572358574,
2439
+ "validation_loss": 0.8263513445854187,
2440
+ "validation_accuracy": 0.7214611872146118,
2441
+ "validation_f1_macro": 0.6688240031872784
2442
+ },
2443
+ {
2444
+ "epoch": 12.0,
2445
+ "training_loss": 0.902957145760699,
2446
+ "validation_loss": 0.8039170503616333,
2447
+ "validation_accuracy": 0.726027397260274,
2448
+ "validation_f1_macro": 0.6904180841904947
2449
+ },
2450
+ {
2451
+ "epoch": 13.0,
2452
+ "training_loss": 0.8497625836511937,
2453
+ "validation_loss": 0.7779726386070251,
2454
+ "validation_accuracy": 0.7442922374429224,
2455
+ "validation_f1_macro": 0.7200793198284486
2456
+ },
2457
+ {
2458
+ "epoch": 14.0,
2459
+ "training_loss": 0.835496270075077,
2460
+ "validation_loss": 0.8103938102722168,
2461
+ "validation_accuracy": 0.7168949771689498,
2462
+ "validation_f1_macro": 0.6977670831675085
2463
+ },
2464
+ {
2465
+ "epoch": 15.0,
2466
+ "training_loss": 0.8223561884426489,
2467
+ "validation_loss": 0.7685360908508301,
2468
+ "validation_accuracy": 0.7351598173515982,
2469
+ "validation_f1_macro": 0.7046996399222168
2470
+ },
2471
+ {
2472
+ "epoch": 16.0,
2473
+ "training_loss": 0.811010141925114,
2474
+ "validation_loss": 0.7409086227416992,
2475
+ "validation_accuracy": 0.7397260273972602,
2476
+ "validation_f1_macro": 0.7090524723509385
2477
+ },
2478
+ {
2479
+ "epoch": 17.0,
2480
+ "training_loss": 0.7999161881644551,
2481
+ "validation_loss": 0.7301066517829895,
2482
+ "validation_accuracy": 0.7442922374429224,
2483
+ "validation_f1_macro": 0.7120104277129728
2484
+ },
2485
+ {
2486
+ "epoch": 18.0,
2487
+ "training_loss": 0.7460179510639935,
2488
+ "validation_loss": 0.7436771988868713,
2489
+ "validation_accuracy": 0.7442922374429224,
2490
+ "validation_f1_macro": 0.7203665088299235
2491
+ },
2492
+ {
2493
+ "epoch": 19.0,
2494
+ "training_loss": 0.7962587337668349,
2495
+ "validation_loss": 0.7397180795669556,
2496
+ "validation_accuracy": 0.7442922374429224,
2497
+ "validation_f1_macro": 0.7236053752266847
2498
+ },
2499
+ {
2500
+ "epoch": 20.0,
2501
+ "training_loss": 0.7818930701511663,
2502
+ "validation_loss": 0.7297154068946838,
2503
+ "validation_accuracy": 0.7442922374429224,
2504
+ "validation_f1_macro": 0.7188234593548022
2505
+ },
2506
+ {
2507
+ "epoch": 21.0,
2508
+ "training_loss": 0.7805201520280141,
2509
+ "validation_loss": 0.7355466485023499,
2510
+ "validation_accuracy": 0.7397260273972602,
2511
+ "validation_f1_macro": 0.7253273117532787
2512
+ },
2513
+ {
2514
+ "epoch": 22.0,
2515
+ "training_loss": 0.7384932738978688,
2516
+ "validation_loss": 0.6986443996429443,
2517
+ "validation_accuracy": 0.7488584474885844,
2518
+ "validation_f1_macro": 0.7195532673450402
2519
+ },
2520
+ {
2521
+ "epoch": 23.0,
2522
+ "training_loss": 0.7141552843698641,
2523
+ "validation_loss": 0.6742230653762817,
2524
+ "validation_accuracy": 0.7671232876712328,
2525
+ "validation_f1_macro": 0.7405008288847317
2526
+ },
2527
+ {
2528
+ "epoch": 24.0,
2529
+ "training_loss": 0.7321291711272263,
2530
+ "validation_loss": 0.6987788081169128,
2531
+ "validation_accuracy": 0.7442922374429224,
2532
+ "validation_f1_macro": 0.7281183285945602
2533
+ },
2534
+ {
2535
+ "epoch": 25.0,
2536
+ "training_loss": 0.6965155884986971,
2537
+ "validation_loss": 0.7463865876197815,
2538
+ "validation_accuracy": 0.730593607305936,
2539
+ "validation_f1_macro": 0.7170696172579023
2540
+ },
2541
+ {
2542
+ "epoch": 26.0,
2543
+ "training_loss": 0.6961427418197074,
2544
+ "validation_loss": 0.6954517364501953,
2545
+ "validation_accuracy": 0.7442922374429224,
2546
+ "validation_f1_macro": 0.7281183285945602
2547
+ },
2548
+ {
2549
+ "epoch": 27.0,
2550
+ "training_loss": 0.7121596714345421,
2551
+ "validation_loss": 0.6809113621711731,
2552
+ "validation_accuracy": 0.7579908675799086,
2553
+ "validation_f1_macro": 0.7431894328050214
2554
+ },
2555
+ {
2556
+ "epoch": 28.0,
2557
+ "training_loss": 0.7088243794150468,
2558
+ "validation_loss": 0.6739152669906616,
2559
+ "validation_accuracy": 0.7397260273972602,
2560
+ "validation_f1_macro": 0.725214910428325
2561
+ },
2562
+ {
2563
+ "epoch": 29.0,
2564
+ "training_loss": 0.7163037883072365,
2565
+ "validation_loss": 0.675110399723053,
2566
+ "validation_accuracy": 0.7534246575342466,
2567
+ "validation_f1_macro": 0.7234408217962469
2568
+ },
2569
+ {
2570
+ "epoch": 30.0,
2571
+ "training_loss": 0.6662919306900443,
2572
+ "validation_loss": 0.6735612750053406,
2573
+ "validation_accuracy": 0.7442922374429224,
2574
+ "validation_f1_macro": 0.7343850277506163
2575
+ },
2576
+ {
2577
+ "epoch": 31.0,
2578
+ "training_loss": 0.7074004760602626,
2579
+ "validation_loss": 0.6266469955444336,
2580
+ "validation_accuracy": 0.7534246575342466,
2581
+ "validation_f1_macro": 0.7166526021057865
2582
+ },
2583
+ {
2584
+ "epoch": 32.0,
2585
+ "training_loss": 0.6581337015803267,
2586
+ "validation_loss": 0.7085683345794678,
2587
+ "validation_accuracy": 0.7351598173515982,
2588
+ "validation_f1_macro": 0.7259083059953252
2589
+ },
2590
+ {
2591
+ "epoch": 33.0,
2592
+ "training_loss": 0.7113121275494738,
2593
+ "validation_loss": 0.6482834219932556,
2594
+ "validation_accuracy": 0.7579908675799086,
2595
+ "validation_f1_macro": 0.7338258176370583
2596
+ },
2597
+ {
2598
+ "epoch": 34.0,
2599
+ "training_loss": 0.6737250231388139,
2600
+ "validation_loss": 0.6657728552818298,
2601
+ "validation_accuracy": 0.7488584474885844,
2602
+ "validation_f1_macro": 0.7259177036668325
2603
+ },
2604
+ {
2605
+ "epoch": 35.0,
2606
+ "training_loss": 0.6514683213175797,
2607
+ "validation_loss": 0.6538190245628357,
2608
+ "validation_accuracy": 0.7534246575342466,
2609
+ "validation_f1_macro": 0.7388930908490514
2610
+ },
2611
+ {
2612
+ "epoch": 36.0,
2613
+ "training_loss": 0.688026835278767,
2614
+ "validation_loss": 0.6805294752120972,
2615
+ "validation_accuracy": 0.7397260273972602,
2616
+ "validation_f1_macro": 0.7273117482908698
2617
+ },
2618
+ {
2619
+ "epoch": 37.0,
2620
+ "training_loss": 0.7013607054221921,
2621
+ "validation_loss": 0.6157943606376648,
2622
+ "validation_accuracy": 0.7625570776255708,
2623
+ "validation_f1_macro": 0.7396509109600558
2624
+ },
2625
+ {
2626
+ "epoch": 38.0,
2627
+ "training_loss": 0.6459702645860067,
2628
+ "validation_loss": 0.6483727693557739,
2629
+ "validation_accuracy": 0.7534246575342466,
2630
+ "validation_f1_macro": 0.7292628517453911
2631
+ },
2632
+ {
2633
+ "epoch": 39.0,
2634
+ "training_loss": 0.6444681490339884,
2635
+ "validation_loss": 0.638465166091919,
2636
+ "validation_accuracy": 0.7579908675799086,
2637
+ "validation_f1_macro": 0.7423711371070605
2638
+ },
2639
+ {
2640
+ "epoch": 40.0,
2641
+ "training_loss": 0.6560473761907438,
2642
+ "validation_loss": 0.6490275263786316,
2643
+ "validation_accuracy": 0.7488584474885844,
2644
+ "validation_f1_macro": 0.733175554993892
2645
+ },
2646
+ {
2647
+ "epoch": 41.0,
2648
+ "training_loss": 0.6408006159997568,
2649
+ "validation_loss": 0.6614896059036255,
2650
+ "validation_accuracy": 0.7579908675799086,
2651
+ "validation_f1_macro": 0.7379692009517402
2652
+ },
2653
+ {
2654
+ "epoch": 42.0,
2655
+ "training_loss": 0.6322115252657634,
2656
+ "validation_loss": 0.6705759167671204,
2657
+ "validation_accuracy": 0.7397260273972602,
2658
+ "validation_f1_macro": 0.7258992769847068
2659
+ },
2660
+ {
2661
+ "epoch": 43.0,
2662
+ "training_loss": 0.6636554475237684,
2663
+ "validation_loss": 0.6184088587760925,
2664
+ "validation_accuracy": 0.771689497716895,
2665
+ "validation_f1_macro": 0.7516816985948285
2666
+ },
2667
+ {
2668
+ "epoch": 44.0,
2669
+ "training_loss": 0.6606775471350042,
2670
+ "validation_loss": 0.6265592575073242,
2671
+ "validation_accuracy": 0.7579908675799086,
2672
+ "validation_f1_macro": 0.7323776838957562
2673
+ },
2674
+ {
2675
+ "epoch": 45.0,
2676
+ "training_loss": 0.6194041851090222,
2677
+ "validation_loss": 0.6486321091651917,
2678
+ "validation_accuracy": 0.7442922374429224,
2679
+ "validation_f1_macro": 0.734953370421454
2680
+ },
2681
+ {
2682
+ "epoch": 46.0,
2683
+ "training_loss": 0.6432370637975088,
2684
+ "validation_loss": 0.6816199421882629,
2685
+ "validation_accuracy": 0.7488584474885844,
2686
+ "validation_f1_macro": 0.7333772212502159
2687
+ },
2688
+ {
2689
+ "epoch": 47.0,
2690
+ "training_loss": 0.6377307420096746,
2691
+ "validation_loss": 0.6541978120803833,
2692
+ "validation_accuracy": 0.7442922374429224,
2693
+ "validation_f1_macro": 0.7303845381526105
2694
+ },
2695
+ {
2696
+ "epoch": 48.0,
2697
+ "training_loss": 0.6303909973400396,
2698
+ "validation_loss": 0.6276454329490662,
2699
+ "validation_accuracy": 0.7534246575342466,
2700
+ "validation_f1_macro": 0.7408801972754376
2701
+ },
2702
+ {
2703
+ "epoch": 49.0,
2704
+ "training_loss": 0.61785961215089,
2705
+ "validation_loss": 0.6480539441108704,
2706
+ "validation_accuracy": 0.7488584474885844,
2707
+ "validation_f1_macro": 0.7393257736451248
2708
+ }
2709
+ ]
2710
+ },
2711
+ {
2712
+ "limit": 50,
2713
+ "seed": 2024,
2714
+ "hyperparameters": {
2715
+ "epochs": 50,
2716
+ "batch_size": 16,
2717
+ "learning_rate": 0.001,
2718
+ "weight_decay": 0.0001,
2719
+ "dropout": 0.3,
2720
+ "projection_dim": 64,
2721
+ "fusion_dim": 12,
2722
+ "validation_fraction": 0.25,
2723
+ "optimizer": "Adam",
2724
+ "class_weighting": true,
2725
+ "early_stopping_patience": 12,
2726
+ "seed": 2024
2727
+ },
2728
+ "epochs_completed": 50,
2729
+ "best_epoch": 41,
2730
+ "early_stopped": false,
2731
+ "hit_max": true,
2732
+ "train_loss_at_best": 0.6359976275665004,
2733
+ "val_loss_at_best": 0.6597247123718262,
2734
+ "accuracy": 0.7579908675799086,
2735
+ "f1_macro": 0.7372566114501599,
2736
+ "f1_weighted": 0.7695326473497044,
2737
+ "recall": {
2738
+ "0.0": 0.7586206896551724,
2739
+ "1.0": 0.8181818181818182,
2740
+ "2.0": 0.7272727272727273,
2741
+ "3.0": 0.7798165137614679,
2742
+ "4.0": 0.6486486486486487
2743
+ },
2744
+ "pred_counts": {
2745
+ "0.0": 48,
2746
+ "1.0": 29,
2747
+ "2.0": 10,
2748
+ "3.0": 95,
2749
+ "4.0": 37
2750
+ },
2751
+ "collapsed": false,
2752
+ "history": [
2753
+ {
2754
+ "epoch": 1.0,
2755
+ "training_loss": 1.6225681711987752,
2756
+ "validation_loss": 1.511662483215332,
2757
+ "validation_accuracy": 0.4977168949771689,
2758
+ "validation_f1_macro": 0.1329268292682927
2759
+ },
2760
+ {
2761
+ "epoch": 2.0,
2762
+ "training_loss": 1.569906612721885,
2763
+ "validation_loss": 1.5234297513961792,
2764
+ "validation_accuracy": 0.4703196347031963,
2765
+ "validation_f1_macro": 0.2245614035087719
2766
+ },
2767
+ {
2768
+ "epoch": 3.0,
2769
+ "training_loss": 1.4499594322065028,
2770
+ "validation_loss": 1.3053251504898071,
2771
+ "validation_accuracy": 0.6894977168949772,
2772
+ "validation_f1_macro": 0.4256609173759133
2773
+ },
2774
+ {
2775
+ "epoch": 4.0,
2776
+ "training_loss": 1.3003037121237777,
2777
+ "validation_loss": 1.198732852935791,
2778
+ "validation_accuracy": 0.634703196347032,
2779
+ "validation_f1_macro": 0.440913835023424
2780
+ },
2781
+ {
2782
+ "epoch": 5.0,
2783
+ "training_loss": 1.2116903895285072,
2784
+ "validation_loss": 1.080140471458435,
2785
+ "validation_accuracy": 0.684931506849315,
2786
+ "validation_f1_macro": 0.5687377787336579
2787
+ },
2788
+ {
2789
+ "epoch": 6.0,
2790
+ "training_loss": 1.100791285677654,
2791
+ "validation_loss": 1.0347250699996948,
2792
+ "validation_accuracy": 0.6940639269406392,
2793
+ "validation_f1_macro": 0.6177055752611009
2794
+ },
2795
+ {
2796
+ "epoch": 7.0,
2797
+ "training_loss": 1.0073844906760425,
2798
+ "validation_loss": 0.937835693359375,
2799
+ "validation_accuracy": 0.7123287671232876,
2800
+ "validation_f1_macro": 0.6832595686116318
2801
+ },
2802
+ {
2803
+ "epoch": 8.0,
2804
+ "training_loss": 0.938835183294808,
2805
+ "validation_loss": 0.9243969917297363,
2806
+ "validation_accuracy": 0.7031963470319634,
2807
+ "validation_f1_macro": 0.6607865027822933
2808
+ },
2809
+ {
2810
+ "epoch": 9.0,
2811
+ "training_loss": 0.8966184040395225,
2812
+ "validation_loss": 0.8663153052330017,
2813
+ "validation_accuracy": 0.7168949771689498,
2814
+ "validation_f1_macro": 0.6923081349030715
2815
+ },
2816
+ {
2817
+ "epoch": 10.0,
2818
+ "training_loss": 0.8589439442971858,
2819
+ "validation_loss": 0.8406924605369568,
2820
+ "validation_accuracy": 0.7351598173515982,
2821
+ "validation_f1_macro": 0.7221737682971412
2822
+ },
2823
+ {
2824
+ "epoch": 11.0,
2825
+ "training_loss": 0.85784370870125,
2826
+ "validation_loss": 0.8226235508918762,
2827
+ "validation_accuracy": 0.7351598173515982,
2828
+ "validation_f1_macro": 0.7167905712472775
2829
+ },
2830
+ {
2831
+ "epoch": 12.0,
2832
+ "training_loss": 0.8242414804493509,
2833
+ "validation_loss": 0.8313245177268982,
2834
+ "validation_accuracy": 0.6986301369863014,
2835
+ "validation_f1_macro": 0.6600682809617517
2836
+ },
2837
+ {
2838
+ "epoch": 13.0,
2839
+ "training_loss": 0.8054670357122654,
2840
+ "validation_loss": 0.7694411873817444,
2841
+ "validation_accuracy": 0.7397260273972602,
2842
+ "validation_f1_macro": 0.7182388720082254
2843
+ },
2844
+ {
2845
+ "epoch": 14.0,
2846
+ "training_loss": 0.78004708450015,
2847
+ "validation_loss": 0.8055667281150818,
2848
+ "validation_accuracy": 0.7077625570776256,
2849
+ "validation_f1_macro": 0.6869753086419753
2850
+ },
2851
+ {
2852
+ "epoch": 15.0,
2853
+ "training_loss": 0.7828267034960956,
2854
+ "validation_loss": 0.7524726390838623,
2855
+ "validation_accuracy": 0.7397260273972602,
2856
+ "validation_f1_macro": 0.7179090965205553
2857
+ },
2858
+ {
2859
+ "epoch": 16.0,
2860
+ "training_loss": 0.7784952884767113,
2861
+ "validation_loss": 0.743364155292511,
2862
+ "validation_accuracy": 0.7442922374429224,
2863
+ "validation_f1_macro": 0.7206798541605313
2864
+ },
2865
+ {
2866
+ "epoch": 17.0,
2867
+ "training_loss": 0.7457227278046492,
2868
+ "validation_loss": 0.7856912016868591,
2869
+ "validation_accuracy": 0.7123287671232876,
2870
+ "validation_f1_macro": 0.7093765406198898
2871
+ },
2872
+ {
2873
+ "epoch": 18.0,
2874
+ "training_loss": 0.7468449764135408,
2875
+ "validation_loss": 0.7758557200431824,
2876
+ "validation_accuracy": 0.7123287671232876,
2877
+ "validation_f1_macro": 0.7032627437525654
2878
+ },
2879
+ {
2880
+ "epoch": 19.0,
2881
+ "training_loss": 0.7238862507226991,
2882
+ "validation_loss": 0.7738156914710999,
2883
+ "validation_accuracy": 0.7168949771689498,
2884
+ "validation_f1_macro": 0.707996859273741
2885
+ },
2886
+ {
2887
+ "epoch": 20.0,
2888
+ "training_loss": 0.7094513577658955,
2889
+ "validation_loss": 0.7316175699234009,
2890
+ "validation_accuracy": 0.7351598173515982,
2891
+ "validation_f1_macro": 0.7231832224960381
2892
+ },
2893
+ {
2894
+ "epoch": 21.0,
2895
+ "training_loss": 0.7254922986030579,
2896
+ "validation_loss": 0.7338428497314453,
2897
+ "validation_accuracy": 0.730593607305936,
2898
+ "validation_f1_macro": 0.7052727011184234
2899
+ },
2900
+ {
2901
+ "epoch": 22.0,
2902
+ "training_loss": 0.7048084808558952,
2903
+ "validation_loss": 0.7339456677436829,
2904
+ "validation_accuracy": 0.7214611872146118,
2905
+ "validation_f1_macro": 0.7166191562713704
2906
+ },
2907
+ {
2908
+ "epoch": 23.0,
2909
+ "training_loss": 0.7217072335685172,
2910
+ "validation_loss": 0.7096080183982849,
2911
+ "validation_accuracy": 0.7351598173515982,
2912
+ "validation_f1_macro": 0.7232759567659875
2913
+ },
2914
+ {
2915
+ "epoch": 24.0,
2916
+ "training_loss": 0.694426145495438,
2917
+ "validation_loss": 0.7198649644851685,
2918
+ "validation_accuracy": 0.730593607305936,
2919
+ "validation_f1_macro": 0.7219951080353464
2920
+ },
2921
+ {
2922
+ "epoch": 25.0,
2923
+ "training_loss": 0.685888970043601,
2924
+ "validation_loss": 0.7141242027282715,
2925
+ "validation_accuracy": 0.726027397260274,
2926
+ "validation_f1_macro": 0.709853949870283
2927
+ },
2928
+ {
2929
+ "epoch": 26.0,
2930
+ "training_loss": 0.6784876305155638,
2931
+ "validation_loss": 0.717924952507019,
2932
+ "validation_accuracy": 0.7168949771689498,
2933
+ "validation_f1_macro": 0.7044832586768071
2934
+ },
2935
+ {
2936
+ "epoch": 27.0,
2937
+ "training_loss": 0.6826280130118858,
2938
+ "validation_loss": 0.7048062682151794,
2939
+ "validation_accuracy": 0.7442922374429224,
2940
+ "validation_f1_macro": 0.7313134228007573
2941
+ },
2942
+ {
2943
+ "epoch": 28.0,
2944
+ "training_loss": 0.6632811921398815,
2945
+ "validation_loss": 0.7309033274650574,
2946
+ "validation_accuracy": 0.726027397260274,
2947
+ "validation_f1_macro": 0.7225617660101258
2948
+ },
2949
+ {
2950
+ "epoch": 29.0,
2951
+ "training_loss": 0.6795863043971178,
2952
+ "validation_loss": 0.6975738406181335,
2953
+ "validation_accuracy": 0.726027397260274,
2954
+ "validation_f1_macro": 0.7100132842068326
2955
+ },
2956
+ {
2957
+ "epoch": 30.0,
2958
+ "training_loss": 0.6437340684053374,
2959
+ "validation_loss": 0.665772020816803,
2960
+ "validation_accuracy": 0.7625570776255708,
2961
+ "validation_f1_macro": 0.7328823093731366
2962
+ },
2963
+ {
2964
+ "epoch": 31.0,
2965
+ "training_loss": 0.7039524542122353,
2966
+ "validation_loss": 0.719477117061615,
2967
+ "validation_accuracy": 0.726027397260274,
2968
+ "validation_f1_macro": 0.7113093026126989
2969
+ },
2970
+ {
2971
+ "epoch": 32.0,
2972
+ "training_loss": 0.6489482645581408,
2973
+ "validation_loss": 0.7060401439666748,
2974
+ "validation_accuracy": 0.730593607305936,
2975
+ "validation_f1_macro": 0.720639788504223
2976
+ },
2977
+ {
2978
+ "epoch": 33.0,
2979
+ "training_loss": 0.6433238772357383,
2980
+ "validation_loss": 0.7341402769088745,
2981
+ "validation_accuracy": 0.7123287671232876,
2982
+ "validation_f1_macro": 0.6899070486620005
2983
+ },
2984
+ {
2985
+ "epoch": 34.0,
2986
+ "training_loss": 0.6565981808958984,
2987
+ "validation_loss": 0.6739374399185181,
2988
+ "validation_accuracy": 0.7625570776255708,
2989
+ "validation_f1_macro": 0.7401719424176123
2990
+ },
2991
+ {
2992
+ "epoch": 35.0,
2993
+ "training_loss": 0.6548550739520933,
2994
+ "validation_loss": 0.7086837291717529,
2995
+ "validation_accuracy": 0.726027397260274,
2996
+ "validation_f1_macro": 0.7100132842068326
2997
+ },
2998
+ {
2999
+ "epoch": 36.0,
3000
+ "training_loss": 0.602200011291155,
3001
+ "validation_loss": 0.6941585540771484,
3002
+ "validation_accuracy": 0.7351598173515982,
3003
+ "validation_f1_macro": 0.7234462506071674
3004
+ },
3005
+ {
3006
+ "epoch": 37.0,
3007
+ "training_loss": 0.63544079007172,
3008
+ "validation_loss": 0.7004677653312683,
3009
+ "validation_accuracy": 0.730593607305936,
3010
+ "validation_f1_macro": 0.720639788504223
3011
+ },
3012
+ {
3013
+ "epoch": 38.0,
3014
+ "training_loss": 0.6624184563392546,
3015
+ "validation_loss": 0.7010037899017334,
3016
+ "validation_accuracy": 0.726027397260274,
3017
+ "validation_f1_macro": 0.717861527690879
3018
+ },
3019
+ {
3020
+ "epoch": 39.0,
3021
+ "training_loss": 0.5985031287844588,
3022
+ "validation_loss": 0.6666479110717773,
3023
+ "validation_accuracy": 0.7488584474885844,
3024
+ "validation_f1_macro": 0.7315324401710119
3025
+ },
3026
+ {
3027
+ "epoch": 40.0,
3028
+ "training_loss": 0.6056617698291453,
3029
+ "validation_loss": 0.6940096020698547,
3030
+ "validation_accuracy": 0.7488584474885844,
3031
+ "validation_f1_macro": 0.7360844329718171
3032
+ },
3033
+ {
3034
+ "epoch": 41.0,
3035
+ "training_loss": 0.6359976275665004,
3036
+ "validation_loss": 0.6597247123718262,
3037
+ "validation_accuracy": 0.7579908675799086,
3038
+ "validation_f1_macro": 0.7372566114501599
3039
+ },
3040
+ {
3041
+ "epoch": 42.0,
3042
+ "training_loss": 0.6047317712772183,
3043
+ "validation_loss": 0.6979882717132568,
3044
+ "validation_accuracy": 0.730593607305936,
3045
+ "validation_f1_macro": 0.7124974170733906
3046
+ },
3047
+ {
3048
+ "epoch": 43.0,
3049
+ "training_loss": 0.6072182982433133,
3050
+ "validation_loss": 0.7281728386878967,
3051
+ "validation_accuracy": 0.7214611872146118,
3052
+ "validation_f1_macro": 0.7046568261714561
3053
+ },
3054
+ {
3055
+ "epoch": 44.0,
3056
+ "training_loss": 0.5998183230801326,
3057
+ "validation_loss": 0.6768959760665894,
3058
+ "validation_accuracy": 0.730593607305936,
3059
+ "validation_f1_macro": 0.7169811762776586
3060
+ },
3061
+ {
3062
+ "epoch": 45.0,
3063
+ "training_loss": 0.6171028475935866,
3064
+ "validation_loss": 0.6866343021392822,
3065
+ "validation_accuracy": 0.730593607305936,
3066
+ "validation_f1_macro": 0.7085102123329972
3067
+ },
3068
+ {
3069
+ "epoch": 46.0,
3070
+ "training_loss": 0.5848244028847392,
3071
+ "validation_loss": 0.678470253944397,
3072
+ "validation_accuracy": 0.7351598173515982,
3073
+ "validation_f1_macro": 0.7195757575757575
3074
+ },
3075
+ {
3076
+ "epoch": 47.0,
3077
+ "training_loss": 0.6237764242218762,
3078
+ "validation_loss": 0.684725821018219,
3079
+ "validation_accuracy": 0.7442922374429224,
3080
+ "validation_f1_macro": 0.7301780448040185
3081
+ },
3082
+ {
3083
+ "epoch": 48.0,
3084
+ "training_loss": 0.5977335670372335,
3085
+ "validation_loss": 0.6758159399032593,
3086
+ "validation_accuracy": 0.730593607305936,
3087
+ "validation_f1_macro": 0.70888995215311
3088
+ },
3089
+ {
3090
+ "epoch": 49.0,
3091
+ "training_loss": 0.6079468349131142,
3092
+ "validation_loss": 0.6623165607452393,
3093
+ "validation_accuracy": 0.7488584474885844,
3094
+ "validation_f1_macro": 0.7466358704586553
3095
+ },
3096
+ {
3097
+ "epoch": 50.0,
3098
+ "training_loss": 0.6073242571295762,
3099
+ "validation_loss": 0.7163891792297363,
3100
+ "validation_accuracy": 0.7214611872146118,
3101
+ "validation_f1_macro": 0.7070384085443462
3102
+ }
3103
+ ]
3104
+ }
3105
+ ]
diagnostics/2026-10-16-brca-training-collapse/results/epochs_50.log ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ limit=50 seed=0 done: best=49/50 acc=0.763 f1m=0.724
2
+ limit=50 seed=1 done: best=50/50 acc=0.772 f1m=0.744
3
+ limit=50 seed=2 done: best=50/50 acc=0.790 f1m=0.761
4
+ limit=50 seed=3 done: best=47/50 acc=0.731 f1m=0.714
5
+ limit=50 seed=7 done: best=32/44 acc=0.772 f1m=0.752
6
+ limit=50 seed=42 done: best=38/50 acc=0.767 f1m=0.747
7
+ limit=50 seed=123 done: best=37/49 acc=0.763 f1m=0.740
8
+ limit=50 seed=2024 done: best=41/50 acc=0.758 f1m=0.737
9
+ limit=50 ALL DONE
diagnostics/2026-10-16-brca-training-collapse/results/paper_config_sgd.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ paper: SGD lr=0.01 wd=0.01 200ep acc=0.498 f1_macro=0.133 f1_w=0.331 epochs=17 pred=[0, 0, 0, 219, 0] prob std=0.0000
2
+ paper, no early stop acc=0.498 f1_macro=0.133 f1_w=0.331 epochs=200 pred=[0, 0, 0, 219, 0] prob std=0.0000
3
+
4
+ [exited with code 0]
diagnostics/2026-10-16-brca-training-collapse/results/seed_runs.txt ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ weight_decay=0.000 seed=0 accuracy=0.763 f1_macro=0.724 f1_weighted=0.769 epochs=50 classes_predicted=5 prob_std=0.265 recall_0.0=0.793 recall_1.0=0.818 recall_2.0=0.545 recall_3.0=0.807 recall_4.0=0.622
2
+ weight_decay=0.000 seed=1 accuracy=0.772 f1_macro=0.744 f1_weighted=0.781 epochs=50 classes_predicted=5 prob_std=0.266 recall_0.0=0.828 recall_1.0=0.848 recall_2.0=0.727 recall_3.0=0.789 recall_4.0=0.622
3
+ weight_decay=0.000 seed=2 accuracy=0.790 f1_macro=0.761 f1_weighted=0.802 epochs=50 classes_predicted=5 prob_std=0.278 recall_0.0=0.862 recall_1.0=0.788 recall_2.0=0.909 recall_3.0=0.798 recall_4.0=0.676
4
+ weight_decay=0.000 seed=3 accuracy=0.731 f1_macro=0.714 f1_weighted=0.739 epochs=50 classes_predicted=5 prob_std=0.271 recall_0.0=0.793 recall_1.0=0.818 recall_2.0=0.545 recall_3.0=0.743 recall_4.0=0.622
5
+ weight_decay=0.000 seed=7 accuracy=0.772 f1_macro=0.752 f1_weighted=0.783 epochs=44 classes_predicted=5 prob_std=0.259 recall_0.0=0.862 recall_1.0=0.788 recall_2.0=0.636 recall_3.0=0.761 recall_4.0=0.757
6
+ weight_decay=0.000 seed=42 accuracy=0.767 f1_macro=0.747 f1_weighted=0.774 epochs=50 classes_predicted=5 prob_std=0.270 recall_0.0=0.759 recall_1.0=0.939 recall_2.0=0.909 recall_3.0=0.771 recall_4.0=0.568
7
+ weight_decay=0.000 seed=123 accuracy=0.763 f1_macro=0.740 f1_weighted=0.773 epochs=49 classes_predicted=5 prob_std=0.267 recall_0.0=0.897 recall_1.0=0.879 recall_2.0=0.727 recall_3.0=0.743 recall_4.0=0.622
8
+ weight_decay=0.000 seed=2024 accuracy=0.758 f1_macro=0.737 f1_weighted=0.770 epochs=50 classes_predicted=5 prob_std=0.276 recall_0.0=0.759 recall_1.0=0.818 recall_2.0=0.727 recall_3.0=0.780 recall_4.0=0.649
9
+ weight_decay=0.010 seed=0 accuracy=0.498 f1_macro=0.133 f1_weighted=0.331 epochs=13 classes_predicted=1 prob_std=0.000 recall_0.0=0.000 recall_1.0=0.000 recall_2.0=0.000 recall_3.0=1.000 recall_4.0=0.000
10
+ weight_decay=0.010 seed=1 accuracy=0.498 f1_macro=0.133 f1_weighted=0.331 epochs=15 classes_predicted=1 prob_std=0.000 recall_0.0=0.000 recall_1.0=0.000 recall_2.0=0.000 recall_3.0=1.000 recall_4.0=0.000
11
+ weight_decay=0.010 seed=2 accuracy=0.626 f1_macro=0.539 f1_weighted=0.611 epochs=50 classes_predicted=5 prob_std=0.142 recall_0.0=0.103 recall_1.0=0.879 recall_2.0=0.636 recall_3.0=0.817 recall_4.0=0.243
12
+ weight_decay=0.010 seed=3 accuracy=0.498 f1_macro=0.133 f1_weighted=0.331 epochs=23 classes_predicted=1 prob_std=0.000 recall_0.0=0.000 recall_1.0=0.000 recall_2.0=0.000 recall_3.0=1.000 recall_4.0=0.000
13
+ weight_decay=0.010 seed=7 accuracy=0.498 f1_macro=0.133 f1_weighted=0.331 epochs=22 classes_predicted=1 prob_std=0.000 recall_0.0=0.000 recall_1.0=0.000 recall_2.0=0.000 recall_3.0=1.000 recall_4.0=0.000
14
+ weight_decay=0.010 seed=42 accuracy=0.498 f1_macro=0.133 f1_weighted=0.331 epochs=31 classes_predicted=1 prob_std=0.000 recall_0.0=0.000 recall_1.0=0.000 recall_2.0=0.000 recall_3.0=1.000 recall_4.0=0.000
15
+ weight_decay=0.010 seed=123 accuracy=0.498 f1_macro=0.133 f1_weighted=0.331 epochs=17 classes_predicted=1 prob_std=0.000 recall_0.0=0.000 recall_1.0=0.000 recall_2.0=0.000 recall_3.0=1.000 recall_4.0=0.000
16
+ weight_decay=0.010 seed=2024 accuracy=0.658 f1_macro=0.480 f1_weighted=0.565 epochs=50 classes_predicted=4 prob_std=0.134 recall_0.0=0.000 recall_1.0=0.848 recall_2.0=0.545 recall_3.0=0.982 recall_4.0=0.081
17
+
18
+ SUMMARY (mean ± std over seeds)
19
+ weight_decay 0.0001 0.0100
20
+ accuracy mean 0.764 0.534
21
+ std 0.017 0.067
22
+ min 0.731 0.498
23
+ max 0.790 0.658
24
+ f1_macro mean 0.740 0.227
25
+ std 0.015 0.175
26
+ min 0.714 0.133
27
+ max 0.761 0.539
28
+ f1_weighted mean 0.774 0.395
29
+ std 0.018 0.120
30
+ min 0.739 0.331
31
+ max 0.802 0.611
32
+ epochs mean 49.125 27.625
33
+ std 2.100 14.889
34
+ min 44.000 13.000
35
+ max 50.000 50.000
36
+ classes_predicted mean 5.000 1.875
37
+ std 0.000 1.642
38
+ min 5.000 1.000
39
+ max 5.000 5.000
40
+ recall_0.0 mean 0.819 0.013
41
+ std 0.051 0.037
42
+ min 0.759 0.000
43
+ max 0.897 0.103
44
+ recall_1.0 mean 0.837 0.216
45
+ std 0.051 0.400
46
+ min 0.788 0.000
47
+ max 0.939 0.879
48
+ recall_2.0 mean 0.716 0.148
49
+ std 0.141 0.275
50
+ min 0.545 0.000
51
+ max 0.909 0.636
52
+ recall_3.0 mean 0.774 0.975
53
+ std 0.024 0.064
54
+ min 0.743 0.817
55
+ max 0.807 1.000
56
+ recall_4.0 mean 0.642 0.041
57
+ std 0.055 0.087
58
+ min 0.568 0.000
59
+ max 0.757 0.243
60
+
61
+ collapsed runs (1 class predicted): [[0.01, 0.0], [0.01, 1.0], [0.01, 3.0], [0.01, 7.0], [0.01, 42.0], [0.01, 123.0]]
62
+
63
+ [exited with code 0]
diagnostics/2026-10-16-brca-training-collapse/results/weight_decay_sweep.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ app default (Adam, wd=0.01) acc=0.498 f1_macro=0.133 epochs= 31 pred counts=[0, 0, 0, 219, 0] prob std across samples=0.0000
2
+ wd=0 acc=0.749 f1_macro=0.741 epochs= 44 pred counts=[46, 29, 15, 91, 38] prob std across samples=0.2643
3
+ wd=1e-4 acc=0.767 f1_macro=0.747 epochs= 50 pred counts=[46, 32, 16, 96, 29] prob std across samples=0.2704
4
+ wd=0, no class weighting acc=0.772 f1_macro=0.692 epochs= 50 pred counts=[31, 33, 5, 112, 38] prob std across samples=0.2582
5
+ wd=0.01, lr=0.01 acc=0.498 f1_macro=0.133 epochs= 21 pred counts=[0, 0, 0, 219, 0] prob std across samples=0.0000
6
+
7
+ [exited with code 0]
diagnostics/2026-10-16-brca-training-collapse/scripts/diagnose.py ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Read-only diagnostics for a BioLM-NET training run. Does not modify project code;
2
+ it wraps library functions from the outside to observe what they do."""
3
+ from __future__ import annotations
4
+
5
+ import pickle
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+ import torch
12
+
13
+ OUT = Path(__file__).parent
14
+ DATASET = sys.argv[1] if len(sys.argv) > 1 else "BRCA"
15
+ CLASS_WEIGHTING = (sys.argv[2] if len(sys.argv) > 2 else "1") == "1"
16
+ EPOCHS = int(sys.argv[3]) if len(sys.argv) > 3 else 50
17
+
18
+ import app # noqa: E402
19
+ import biolmnet.training as training # noqa: E402
20
+ from biolmnet.data import read_csv, upstream_example_sources # noqa: E402
21
+
22
+ pd.set_option("display.width", 200)
23
+ np.set_printoptions(precision=3, suppress=True, linewidth=160)
24
+
25
+
26
+ def section(title):
27
+ print(f"\n{'=' * 8} {title} {'=' * 8}")
28
+
29
+
30
+ # ---------- raw files ----------
31
+ section(f"RAW FILES ({DATASET})")
32
+ for key, url in upstream_example_sources(DATASET).items():
33
+ frame = read_csv(url)
34
+ print(f"{key:14s} shape={frame.shape} first cols={list(frame.columns[:3])}")
35
+ if key == "labels":
36
+ print(" raw label counts:", frame.iloc[:, 0].value_counts().to_dict())
37
+
38
+ # ---------- workspace ----------
39
+ cache = OUT / f"workspace_{DATASET}.pkl"
40
+ if cache.exists():
41
+ workspace = pickle.loads(cache.read_bytes())
42
+ else:
43
+ result = app.prepare_workspace(
44
+ "BioLM-NET examples", DATASET, "", None, None, None, None, None, None, None,
45
+ True, "Auto — bulk or single-cell based on source", progress=lambda *a, **k: None,
46
+ )
47
+ workspace = result[0]
48
+ if workspace is None:
49
+ print(result[1]); sys.exit(1)
50
+ cache.write_bytes(pickle.dumps(workspace))
51
+
52
+ section("WORKSPACE")
53
+ print("warnings:", workspace.warnings)
54
+ print("samples:", len(workspace.labels), " classes:", workspace.label_names)
55
+ print("full class counts:", dict(zip(workspace.label_names, np.bincount(workspace.labels).tolist())))
56
+ for name, values in (("gene", workspace.gene_expression), ("dna", workspace.dna_methylation)):
57
+ print(f"{name}: shape={values.shape} min={values.min():.3g} max={values.max():.3g} "
58
+ f"mean={values.mean():.3g} zero-var cols={(values.std(0) < 1e-8).sum()}")
59
+
60
+ section("MASKS")
61
+ for name, branch in (("gene", workspace.gene_branch), ("dna", workspace.dna_branch)):
62
+ m = branch.biological_mask
63
+ p = branch.pathway_mask
64
+ print(f"{name}: bio mask {m.shape} density={(m > 0).mean():.4%} nnz={(m > 0).sum():,} "
65
+ f"PDI={branch.pdi_edges} PPI={branch.ppi_edges}")
66
+ print(f" inputs with 0 outgoing edges: {(m.sum(1) == 0).sum()}/{m.shape[0]} "
67
+ f"hidden genes in-degree median={np.median((m > 0).sum(0)):.0f} max={(m > 0).sum(0).max()}")
68
+ print(f" nonzero mask values: min={m[m > 0].min():.3f} median={np.median(m[m > 0]):.3f} max={m.max():.3f}")
69
+ print(f" pathway mask {p.shape}: hidden genes in no pathway={(p.sum(1) == 0).sum()}/{p.shape[0]} "
70
+ f"genes/pathway median={np.median(p.sum(0)):.0f}")
71
+
72
+ # ---------- instrumented training ----------
73
+ hp = training.Hyperparameters(epochs=EPOCHS, class_weighting=CLASS_WEIGHTING)
74
+ section(f"TRAINING {hp}")
75
+ train_index, validation_index = training.split_train_validation(workspace.labels, hp.validation_fraction, hp.seed)
76
+ names = workspace.label_names
77
+ print("training class counts: ", dict(zip(names, np.bincount(workspace.labels[train_index], minlength=len(names)).tolist())))
78
+ print("validation class counts:", dict(zip(names, np.bincount(workspace.labels[validation_index], minlength=len(names)).tolist())))
79
+
80
+ captured = {"criterion_weights": [], "evals": [], "loss_calls": 0}
81
+ orig_ce = training.nn.CrossEntropyLoss
82
+
83
+
84
+ class SpyCE(orig_ce):
85
+ def forward(self, logits, target):
86
+ captured["loss_calls"] += 1
87
+ captured["seen_weight"] = None if self.weight is None else self.weight.detach().cpu().numpy()
88
+ return super().forward(logits, target)
89
+
90
+
91
+ def make_spy(*args, **kwargs):
92
+ loss = SpyCE(*args, **kwargs)
93
+ captured["criterion_weights"].append(None if loss.weight is None else loss.weight.cpu().numpy())
94
+ return loss
95
+
96
+
97
+ training.nn.CrossEntropyLoss = make_spy
98
+ orig_eval = training._evaluate
99
+
100
+
101
+ def spy_eval(model, gene, dna, labels, device):
102
+ out = orig_eval(model, gene, dna, labels, device)
103
+ captured["evals"].append((out[1], out[2]))
104
+ return out
105
+
106
+
107
+ training._evaluate = spy_eval
108
+ orig_make = training._make_model
109
+
110
+
111
+ def spy_make(*a, **k):
112
+ model = orig_make(*a, **k)
113
+ captured["initial_state"] = {k2: v.detach().clone() for k2, v in model.state_dict().items()}
114
+ return model
115
+
116
+
117
+ training._make_model = spy_make
118
+
119
+ result = training.train(workspace, hp)
120
+ bundle = result.bundle
121
+
122
+ section("CLASS WEIGHTS")
123
+ print("weights passed to CrossEntropyLoss:", captured["criterion_weights"])
124
+ print("weight attribute seen inside loss.forward:", captured.get("seen_weight"))
125
+ print("loss.forward calls:", captured["loss_calls"])
126
+
127
+ section("HISTORY")
128
+ print(pd.DataFrame(bundle.history).to_string(index=False))
129
+
130
+ section("PER-EPOCH VALIDATION PREDICTION COUNTS")
131
+ for i, (probs, preds) in enumerate(captured["evals"][:-1]):
132
+ counts = np.bincount(preds, minlength=len(names))
133
+ print(f"epoch {i + 1:3d}: counts={counts.tolist()} mean max-prob={probs.max(1).mean():.3f}")
134
+
135
+ section("FINAL (best checkpoint) VALIDATION")
136
+ probs, preds = captured["evals"][-1]
137
+ y_val = workspace.labels[validation_index]
138
+ print("prediction counts:", dict(zip(names, np.bincount(preds, minlength=len(names)).tolist())))
139
+ print("average predicted probability per class:", dict(zip(names, probs.mean(0).round(3).tolist())))
140
+ print("training class prior:", dict(zip(names, (np.bincount(workspace.labels[train_index], minlength=len(names)) / len(train_index)).round(3).tolist())))
141
+ print("std of each class prob across samples:", probs.std(0).round(4).tolist())
142
+ print("max-prob: min/median/max =", np.percentile(probs.max(1), [0, 50, 100]).round(3).tolist())
143
+ sorted_p = np.sort(probs, 1)
144
+ print("margin top1-top2: median =", np.median(sorted_p[:, -1] - sorted_p[:, -2]).round(4))
145
+ print("first 8 validation rows (prob vectors):\n", probs[:8])
146
+ print("metrics:", {k: v for k, v in bundle.metrics.items() if k != "device"})
147
+ print("confusion:\n", result.confusion)
148
+
149
+ section("LOGITS / ACTIVATIONS (best checkpoint, validation rows)")
150
+ model = bundle.model.eval()
151
+ gene = torch.from_numpy(((workspace.gene_expression[validation_index] - bundle.gene_mean) / bundle.gene_scale).astype(np.float32))
152
+ dna = torch.from_numpy(((workspace.dna_methylation[validation_index] - bundle.dna_mean) / bundle.dna_scale).astype(np.float32))
153
+ with torch.no_grad():
154
+ stats = {}
155
+ for name, branch, x in (("gene", model.gene_branch, gene), ("dna", model.dna_branch, dna)):
156
+ pre = branch.biological(x)
157
+ hidden = torch.relu(pre)
158
+ pathways = branch.pathway_attention(hidden)
159
+ proj = torch.sigmoid(branch.projection(pathways))
160
+ dead = (hidden.max(0).values == 0).float().mean().item()
161
+ print(f"{name}: bio pre-act mean={pre.mean():.3g} std={pre.std():.3g} dead ReLU units={dead:.1%} "
162
+ f"pathway std across samples={pathways.std(0).mean():.3g} "
163
+ f"sigmoid saturated(<.02|>.98)={((proj < .02) | (proj > .98)).float().mean():.1%} "
164
+ f"proj std across samples={proj.std(0).mean():.3g}")
165
+ stats[name] = proj
166
+ fused = torch.tanh(model.fusion(torch.cat([stats["gene"], stats["dna"]], 1)))
167
+ logits = model.output(fused)
168
+ print(f"fusion tanh saturated(|x|>.98)={(fused.abs() > .98).float().mean():.1%} fused std across samples={fused.std(0).mean():.3g}")
169
+ print("logits mean per class:", logits.mean(0).numpy(), " logits std across samples per class:", logits.std(0).numpy())
170
+
171
+ section("PARAMETER CHANGE FROM INIT")
172
+ for key, value in bundle.model.state_dict().items():
173
+ init = captured["initial_state"].get(key)
174
+ if init is None or not value.is_floating_point() or key.endswith("mask") or key.endswith("embeddings"):
175
+ continue
176
+ delta = (value - init.cpu()).norm() / (init.norm() + 1e-12)
177
+ print(f"{key:45s} rel change={delta:.4f}")
diagnostics/2026-10-16-brca-training-collapse/scripts/epochs.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Diagnostic only: BRCA, weight_decay=1e-4, one epoch limit across 8 seeds.
2
+ Usage: epochs.py <epoch_limit>. Writes epochs_<limit>.json next to this file.
3
+ Project code is used unchanged; only Hyperparameters fields are overridden."""
4
+ import json
5
+ import pickle
6
+ import sys
7
+ from dataclasses import asdict, replace
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+ from sklearn.metrics import recall_score
12
+
13
+ import biolmnet.training as training
14
+
15
+ HERE = Path(__file__).parent
16
+ LIMIT = int(sys.argv[1])
17
+ SEEDS = [0, 1, 2, 3, 7, 42, 123, 2024]
18
+ TOLERANCE = 1e-5 # mirrors train(): improvement must exceed 1e-5
19
+
20
+ workspace = pickle.loads((HERE / "workspace_BRCA.pkl").read_bytes())
21
+ names = workspace.label_names
22
+
23
+
24
+ def replay(history, patience_limit):
25
+ """Re-run train()'s checkpoint/early-stop bookkeeping on the recorded history."""
26
+ best_loss, best_epoch, patience, stopped = float("inf"), None, 0, False
27
+ for row in history:
28
+ if row["validation_loss"] < best_loss - TOLERANCE:
29
+ best_loss, best_epoch, patience = row["validation_loss"], int(row["epoch"]), 0
30
+ else:
31
+ patience += 1
32
+ if patience >= patience_limit:
33
+ stopped = True
34
+ break
35
+ return best_epoch, stopped
36
+
37
+
38
+ runs = []
39
+ for seed in SEEDS:
40
+ hp = replace(
41
+ training.Hyperparameters(),
42
+ epochs=LIMIT, weight_decay=1e-4, seed=seed,
43
+ optimizer="Adam", learning_rate=0.001,
44
+ )
45
+ result = training.train(workspace, hp)
46
+ bundle = result.bundle
47
+ history = bundle.history
48
+ best_epoch, early_stopped = replay(history, hp.early_stopping_patience)
49
+ best_row = history[best_epoch - 1]
50
+ vp = result.validation_predictions
51
+ recalls = recall_score(vp["observed"], vp["predicted"], labels=names, average=None, zero_division=0)
52
+ counts = vp["predicted"].value_counts().reindex(names, fill_value=0).astype(int).tolist()
53
+ m = bundle.metrics
54
+ # Sanity: the restored checkpoint's re-evaluated loss must match the best history row.
55
+ assert abs(m["validation_loss"] - best_row["validation_loss"]) < 1e-4, (seed, m["validation_loss"], best_row)
56
+ run = {
57
+ "limit": LIMIT, "seed": seed, "hyperparameters": asdict(hp),
58
+ "epochs_completed": m["epochs_completed"], "best_epoch": best_epoch,
59
+ "early_stopped": early_stopped, "hit_max": m["epochs_completed"] == LIMIT and not early_stopped,
60
+ "train_loss_at_best": best_row["training_loss"], "val_loss_at_best": best_row["validation_loss"],
61
+ "accuracy": m["accuracy"], "f1_macro": m["f1_macro"], "f1_weighted": m["f1_weighted"],
62
+ "recall": dict(zip(names, map(float, recalls))), "pred_counts": dict(zip(names, counts)),
63
+ "collapsed": int((np.array(counts) > 0).sum()) == 1,
64
+ "history": history,
65
+ }
66
+ runs.append(run)
67
+ print(f"limit={LIMIT} seed={seed} done: best={best_epoch}/{m['epochs_completed']} "
68
+ f"acc={m['accuracy']:.3f} f1m={m['f1_macro']:.3f}", flush=True)
69
+
70
+ (HERE / f"epochs_{LIMIT}.json").write_text(json.dumps(runs, indent=1))
71
+ print(f"limit={LIMIT} ALL DONE", flush=True)
diagnostics/2026-10-16-brca-training-collapse/scripts/epochs_summary.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Summarise epochs_{50,100,200}.json produced by epochs.py."""
2
+ import json
3
+ from pathlib import Path
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+ HERE = Path(__file__).parent
9
+ LIMITS = [50, 100, 200]
10
+ data = {limit: json.loads((HERE / f"epochs_{limit}.json").read_text()) for limit in LIMITS}
11
+ names = list(data[50][0]["recall"])
12
+ pd.set_option("display.width", 250)
13
+ pd.set_option("display.max_columns", 40)
14
+
15
+ print("=" * 20, "PER RUN")
16
+ rows = []
17
+ for limit in LIMITS:
18
+ for r in data[limit]:
19
+ h = pd.DataFrame(r["history"])
20
+ best = r["best_epoch"]
21
+ after = h[h.epoch > best]
22
+ rows.append({
23
+ "limit": limit, "seed": r["seed"], "done": r["epochs_completed"], "best": best,
24
+ "early_stop": r["early_stopped"], "hit_max": r["hit_max"],
25
+ "trL@best": r["train_loss_at_best"], "vaL@best": r["val_loss_at_best"],
26
+ "acc": r["accuracy"], "f1_mac": r["f1_macro"], "f1_w": r["f1_weighted"],
27
+ **{f"rec_{n}": r["recall"][n] for n in names},
28
+ "pred_counts": "/".join(str(r["pred_counts"][n]) for n in names),
29
+ "collapsed": r["collapsed"],
30
+ # overfitting signals after the best checkpoint
31
+ "trL_drop_after_best": (r["train_loss_at_best"] - h.training_loss.iloc[-1]) if len(after) else 0.0,
32
+ "vaL_rise_after_best": (h.validation_loss.iloc[-1] - r["val_loss_at_best"]) if len(after) else 0.0,
33
+ "vaL_max_after_best": (after.validation_loss.max() - r["val_loss_at_best"]) if len(after) else 0.0,
34
+ "f1_last_epoch": h.validation_f1_macro.iloc[-1],
35
+ })
36
+ runs = pd.DataFrame(rows)
37
+ print(runs.round(3).to_string(index=False))
38
+ runs.to_csv(HERE / "epochs_runs.csv", index=False)
39
+
40
+ print("\n" + "=" * 20, "SUMMARY PER EPOCH LIMIT (8 seeds)")
41
+ summary = []
42
+ for limit, g in runs.groupby("limit"):
43
+ summary.append({
44
+ "limit": limit,
45
+ "acc": f"{g.acc.mean():.3f} ± {g.acc.std():.3f}",
46
+ "f1_macro": f"{g.f1_mac.mean():.3f} ± {g.f1_mac.std():.3f}",
47
+ "f1_weighted": f"{g.f1_w.mean():.3f} ± {g.f1_w.std():.3f}",
48
+ "f1_macro min/max": f"{g.f1_mac.min():.3f} / {g.f1_mac.max():.3f}",
49
+ "mean best epoch": f"{g.best.mean():.1f}",
50
+ "best epoch range": f"{g.best.min()}–{g.best.max()}",
51
+ "hit max": int(g.hit_max.sum()), "early stopped": int(g.early_stop.sum()),
52
+ "collapsed": int(g.collapsed.sum()),
53
+ **{f"recall {n}": f"{g[f'rec_{n}'].mean():.3f}" for n in names},
54
+ })
55
+ print(pd.DataFrame(summary).set_index("limit").T.to_string())
56
+
57
+ print("\n" + "=" * 20, "PAIRED CHANGE vs 50 EPOCHS (same seed)")
58
+ base = runs[runs.limit == 50].set_index("seed")
59
+ for limit in (100, 200):
60
+ other = runs[runs.limit == limit].set_index("seed")
61
+ for metric in ("acc", "f1_mac", "f1_w"):
62
+ diff = other[metric] - base[metric]
63
+ print(f"{limit:3d} vs 50 {metric:7s} mean Δ={diff.mean():+.4f} std={diff.std():.4f} "
64
+ f"better/equal/worse = {(diff > 1e-9).sum()}/{(diff.abs() <= 1e-9).sum()}/{(diff < -1e-9).sum()} "
65
+ f"per-seed={diff.round(3).tolist()}")
66
+ other = runs[runs.limit == 200].set_index("seed"); mid = runs[runs.limit == 100].set_index("seed")
67
+ diff = other.f1_mac - mid.f1_mac
68
+ print(f"200 vs 100 f1_mac mean Δ={diff.mean():+.4f} better/equal/worse = "
69
+ f"{(diff > 1e-9).sum()}/{(diff.abs() <= 1e-9).sum()}/{(diff < -1e-9).sum()}")
70
+
71
+ print("\n" + "=" * 20, "OVERFITTING SIGNALS (after the best checkpoint)")
72
+ for limit, g in runs.groupby("limit"):
73
+ trained_past = g[g.done > g.best]
74
+ print(f"limit {limit}: runs that trained past best={len(trained_past)} "
75
+ f"mean epochs after best={(g.done - g.best).mean():.1f} "
76
+ f"mean train-loss drop after best={trained_past.trL_drop_after_best.mean():.3f} "
77
+ f"mean val-loss rise (final-best)={trained_past.vaL_rise_after_best.mean():.3f} "
78
+ f"runs where train↓ and val↑ after best="
79
+ f"{int(((trained_past.trL_drop_after_best > 0) & (trained_past.vaL_rise_after_best > 0)).sum())}")
80
+
81
+ print("\n" + "=" * 20, "MEAN LOSS CURVES (200-epoch runs; mean over seeds still running at that epoch)")
82
+ curves = pd.concat([pd.DataFrame(r["history"]).assign(seed=r["seed"]) for r in data[200]])
83
+ checkpoints = [1, 5, 10, 20, 30, 40, 50, 60, 80, 100, 125, 150, 175, 200]
84
+ table = curves[curves.epoch.isin(checkpoints)].groupby("epoch").agg(
85
+ runs=("seed", "count"), train_loss=("training_loss", "mean"),
86
+ val_loss=("validation_loss", "mean"), val_acc=("validation_accuracy", "mean"),
87
+ val_f1_macro=("validation_f1_macro", "mean"))
88
+ print(table.round(3).to_string())
89
+
90
+ print("\n" + "=" * 20, "DETERMINISM: shared epochs identical across limits for the same seed?")
91
+ for seed in [r["seed"] for r in data[50]]:
92
+ hs = {L: pd.DataFrame(next(r for r in data[L] if r["seed"] == seed)["history"]) for L in LIMITS}
93
+ n = min(len(h) for h in hs.values())
94
+ same = all(np.allclose(hs[L].validation_loss.iloc[:n], hs[50].validation_loss.iloc[:n], atol=1e-6) for L in LIMITS)
95
+ print(f"seed {seed}: first {n} epochs identical across 50/100/200 = {same}")
diagnostics/2026-10-16-brca-training-collapse/scripts/seeds.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Seed-stability check on BRCA: weight_decay=1e-4 vs the current default 0.01.
2
+ Each seed changes the stratified split, weight init and batch order together.
3
+ Uses project code as-is; only Hyperparameters are overridden."""
4
+ import pickle
5
+ from dataclasses import replace
6
+ from pathlib import Path
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ from sklearn.metrics import recall_score
11
+
12
+ import biolmnet.training as training
13
+
14
+ workspace = pickle.loads((Path(__file__).parent / "workspace_BRCA.pkl").read_bytes())
15
+ names = workspace.label_names
16
+ seeds = [0, 1, 2, 3, 7, 42, 123, 2024]
17
+ rows = []
18
+ for weight_decay in (1e-4, 0.01):
19
+ for seed in seeds:
20
+ hp = replace(training.Hyperparameters(), weight_decay=weight_decay, seed=seed)
21
+ result = training.train(workspace, hp)
22
+ m = result.bundle.metrics
23
+ vp = result.validation_predictions
24
+ probs = vp[[f"P({n})" for n in names]].to_numpy()
25
+ recalls = recall_score(vp["observed"], vp["predicted"], labels=names, average=None, zero_division=0)
26
+ row = {
27
+ "weight_decay": weight_decay, "seed": seed,
28
+ "accuracy": m["accuracy"], "f1_macro": m["f1_macro"], "f1_weighted": m["f1_weighted"],
29
+ "epochs": m["epochs_completed"],
30
+ "classes_predicted": int(vp["predicted"].nunique()),
31
+ "prob_std": float(probs.std(0).mean()),
32
+ **{f"recall_{n}": r for n, r in zip(names, recalls)},
33
+ }
34
+ rows.append(row)
35
+ print(" ".join(f"{k}={v:.3f}" if isinstance(v, float) else f"{k}={v}" for k, v in row.items()), flush=True)
36
+
37
+ frame = pd.DataFrame(rows)
38
+ frame.to_csv(Path(__file__).parent / "seed_runs.csv", index=False)
39
+ print("\nSUMMARY (mean ± std over seeds)")
40
+ metrics = ["accuracy", "f1_macro", "f1_weighted", "epochs", "classes_predicted"] + [f"recall_{n}" for n in names]
41
+ summary = frame.groupby("weight_decay")[metrics].agg(["mean", "std", "min", "max"]).T
42
+ pd.set_option("display.width", 200)
43
+ print(summary.round(3).to_string())
44
+ print("\ncollapsed runs (1 class predicted):", frame[frame.classes_predicted == 1][["weight_decay", "seed"]].values.tolist())
diagnostics/2026-10-16-brca-training-collapse/scripts/sweep.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Train BRCA under a few optimizer settings and report whether the model collapses."""
2
+ import pickle
3
+ import sys
4
+ from dataclasses import replace
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+
9
+ import biolmnet.training as training
10
+
11
+ workspace = pickle.loads((Path(__file__).parent / "workspace_BRCA.pkl").read_bytes())
12
+ base = training.Hyperparameters(epochs=int(sys.argv[1]) if len(sys.argv) > 1 else 50)
13
+ configs = {
14
+ "app default (Adam, wd=0.01)": base,
15
+ "wd=0": replace(base, weight_decay=0.0),
16
+ "wd=1e-4": replace(base, weight_decay=1e-4),
17
+ "wd=0, no class weighting": replace(base, weight_decay=0.0, class_weighting=False),
18
+ "wd=0.01, lr=0.01": replace(base, learning_rate=0.01),
19
+ }
20
+ for name, hp in configs.items():
21
+ result = training.train(workspace, hp)
22
+ vp = result.validation_predictions
23
+ probs = vp[[c for c in vp if c.startswith("P(")]].to_numpy()
24
+ m = result.bundle.metrics
25
+ counts = vp["predicted"].value_counts().reindex(workspace.label_names, fill_value=0).tolist()
26
+ print(f"{name:30s} acc={m['accuracy']:.3f} f1_macro={m['f1_macro']:.3f} epochs={m['epochs_completed']:3d} "
27
+ f"pred counts={counts} prob std across samples={probs.std(0).mean():.4f}", flush=True)
diagnostics/2026-10-16-brca-training-collapse/upstream/BioLMNET.ipynb ADDED
@@ -0,0 +1,947 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "634f46b7-f788-499d-b014-7c28905eeff6",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import os\n",
11
+ "os.environ['PYTHONHASHSEED'] = '0'\n",
12
+ "os.environ['TF_DETERMINISTIC_OPS'] = '1'\n",
13
+ "os.environ['CUDA_VISIBLE_DEVICES'] = ''\n",
14
+ "import random\n",
15
+ "import numpy as np\n",
16
+ "import pandas as pd\n",
17
+ "import itertools\n",
18
+ "from sklearn.preprocessing import StandardScaler, LabelEncoder\n",
19
+ "from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score\n",
20
+ "from sklearn.model_selection import train_test_split, StratifiedKFold\n",
21
+ "import matplotlib.pyplot as plt\n",
22
+ "import tensorflow as tf\n",
23
+ "from pathlib import Path\n",
24
+ "from tensorflow.keras import layers, Model\n",
25
+ "from tensorflow.keras.layers import Lambda, Dense, Dropout, Input, concatenate, Flatten, BatchNormalization, Activation\n",
26
+ "from tensorflow.keras import backend as K, regularizers, initializers\n",
27
+ "from tensorflow.keras.initializers import LecunUniform, GlorotUniform\n",
28
+ "from tensorflow.keras.regularizers import l2\n",
29
+ "from joblib import Parallel, delayed\n",
30
+ "from tqdm import tqdm\n",
31
+ "from tqdm_joblib import tqdm_joblib\n",
32
+ "from huggingface_hub import hf_hub_download\n",
33
+ "tf.config.threading.set_intra_op_parallelism_threads(1)\n",
34
+ "tf.config.threading.set_inter_op_parallelism_threads(1)\n",
35
+ "tf.random.set_seed(42)\n"
36
+ ]
37
+ },
38
+ {
39
+ "cell_type": "code",
40
+ "execution_count": null,
41
+ "id": "535e884e-58a5-434e-95df-0735956321b5",
42
+ "metadata": {},
43
+ "outputs": [],
44
+ "source": [
45
+ "from huggingface_hub import hf_hub_download\n",
46
+ "\n",
47
+ "FNAME = \"embedding_associations_cell_type_tissue_drug_pathway_openai_large.parquet\" \n",
48
+ "parquet_path = hf_hub_download(\n",
49
+ " repo_id = \"honicky/genept-composable-embeddings\",\n",
50
+ " filename = FNAME,\n",
51
+ " repo_type = \"model\"\n",
52
+ ")\n",
53
+ "emb = pd.read_parquet(parquet_path)"
54
+ ]
55
+ },
56
+ {
57
+ "cell_type": "code",
58
+ "execution_count": null,
59
+ "id": "1a4d2523-d9a0-466f-bc58-8c815370e9e2",
60
+ "metadata": {},
61
+ "outputs": [],
62
+ "source": [
63
+ "df_gene = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"Gene_Expression.csv\")\n",
64
+ "df_dna = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"DNA_Methylation.csv\")\n",
65
+ " \n",
66
+ "true_label = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"label.csv\")\n",
67
+ "\n",
68
+ "scaler = StandardScaler()\n",
69
+ "le = LabelEncoder()\n",
70
+ "le.fit(true_label)\n",
71
+ "y = le.transform(true_label)\n",
72
+ "\n",
73
+ "ge_genes = list(df_gene.columns)\n",
74
+ "\n",
75
+ "df_pdi = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"PDI\"/ \"PDI.csv\",sep=',')\n",
76
+ "df_ge_pdi = df_pdi[df_pdi.Target.isin(ge_genes)]\n",
77
+ "unique_tfs = df_ge_pdi['TF'].unique()\n",
78
+ "unique_targets = df_ge_pdi['Target'].unique()\n",
79
+ "\n",
80
+ "dna_genes =list(df_dna.columns) \n",
81
+ "df_dna_pdi = df_pdi[df_pdi.Target.isin(dna_genes)]\n",
82
+ "\n",
83
+ "dna_unique_tfs = df_dna_pdi['TF'].unique()\n",
84
+ "dna_unique_targets = df_dna_pdi['Target'].unique()\n",
85
+ "\n",
86
+ "df_ppi = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"PPI\"/ \"PPI.csv\",sep=',')\n",
87
+ "df_ppi = df_ppi.drop(['Unnamed: 0'], axis =1)\n",
88
+ "df_ppi['combined_score'] = df_ppi['combined_score'] /1000\n",
89
+ "threshold = df_ppi['combined_score'].quantile(0.9)\n",
90
+ "df_ppi = df_ppi[df_ppi['combined_score'] >= threshold]\n",
91
+ "df_ge_ppi = df_ppi[df_ppi['protein1'].isin(ge_genes) | df_ppi['protein2'].isin(ge_genes)]\n",
92
+ "unique_protein1 = df_ge_ppi['protein1'].unique()\n",
93
+ "unique_protein2 = df_ge_ppi['protein2'].unique()\n",
94
+ "\n",
95
+ "df_dna_ppi = df_ppi[df_ppi['protein1'].isin(dna_genes) | df_ppi['protein2'].isin(dna_genes)]\n",
96
+ "dna_unique_protein1 = df_dna_ppi['protein1'].unique()\n",
97
+ "dna_unique_protein2 = df_dna_ppi['protein2'].unique()\n",
98
+ "\n",
99
+ "ge_pdi_ppi_genes = list(set(unique_targets) | set(unique_protein2))\n",
100
+ "dna_pdi_ppi_genes = list(set(dna_unique_targets) | set(dna_unique_protein2))"
101
+ ]
102
+ },
103
+ {
104
+ "cell_type": "code",
105
+ "execution_count": null,
106
+ "id": "e868db94-f1d2-413a-b36f-028cb0d7499d",
107
+ "metadata": {},
108
+ "outputs": [],
109
+ "source": [
110
+ "genept_gene_emb = emb.loc[emb.index.isin(ge_pdi_ppi_genes)]\n",
111
+ "ge_pdi_ppi_genept = genept_gene_emb.index"
112
+ ]
113
+ },
114
+ {
115
+ "cell_type": "code",
116
+ "execution_count": null,
117
+ "id": "dfd04c7c-6119-4f60-836e-43d2c2060299",
118
+ "metadata": {},
119
+ "outputs": [],
120
+ "source": [
121
+ "genept_dna_emb = emb.loc[emb.index.isin(dna_pdi_ppi_genes)]\n",
122
+ "dna_pdi_ppi_genept = genept_dna_emb.index"
123
+ ]
124
+ },
125
+ {
126
+ "cell_type": "code",
127
+ "execution_count": null,
128
+ "id": "537876a2-01e5-4a8f-92df-621469614756",
129
+ "metadata": {},
130
+ "outputs": [],
131
+ "source": [
132
+ "def create_ge_mask_matrix (extra_neurons):\n",
133
+ " de_gene_to_idx = {gene: i for i, gene in enumerate(ge_genes)}\n",
134
+ " target_to_idx = {target: j for j, target in enumerate(unique_targets)}\n",
135
+ " protein2_to_idx = {protein2: k for k, protein2 in enumerate(unique_protein2)}\n",
136
+ "\n",
137
+ " pdi_mask_matrix = np.zeros((len(ge_genes), len(unique_targets)), dtype=int)\n",
138
+ "\n",
139
+ " for _, row in df_ge_pdi.iterrows():\n",
140
+ " tfa = row[\"TF\"]\n",
141
+ " target = row[\"Target\"]\n",
142
+ " if tfa in de_gene_to_idx and target in target_to_idx:\n",
143
+ " tf_idx = de_gene_to_idx[tfa]\n",
144
+ " target_idx = target_to_idx[target]\n",
145
+ " pdi_mask_matrix[tf_idx, target_idx] = 1\n",
146
+ " \n",
147
+ " ppi_mask_matrix = np.zeros((len(ge_genes), len(unique_protein2)))\n",
148
+ "\n",
149
+ " for _, row in df_ge_ppi.iterrows():\n",
150
+ " protein1 = row[\"protein1\"]\n",
151
+ " protein2 = row[\"protein2\"]\n",
152
+ " combined_score = row[\"combined_score\"]\n",
153
+ "\n",
154
+ " if protein1 in de_gene_to_idx and protein2 in protein2_to_idx:\n",
155
+ " protein1_idx = de_gene_to_idx[protein1]\n",
156
+ " protein2_idx = protein2_to_idx[protein2]\n",
157
+ " ppi_mask_matrix[protein1_idx, protein2_idx] = combined_score \n",
158
+ " \n",
159
+ " pdi_mask_df= pd.DataFrame(pdi_mask_matrix)\n",
160
+ " ppi_mask_df= pd.DataFrame(ppi_mask_matrix)\n",
161
+ " pdi_mask_df.columns = unique_targets\n",
162
+ " ppi_mask_df.columns = unique_protein2\n",
163
+ " pdi_ppi_mask_df = pd.concat([pdi_mask_df, ppi_mask_df], axis=1)\n",
164
+ " pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, ~pdi_ppi_mask_df.columns.duplicated(keep='last')]\n",
165
+ " col_mask = pdi_ppi_mask_df.columns.isin(ge_pdi_ppi_genept)\n",
166
+ " sub_pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, col_mask]\n",
167
+ " \n",
168
+ " extra_columns = np.ones((len(ge_genes), extra_neurons), dtype=int)\n",
169
+ " adjusted_mask_matrix = np.hstack([sub_pdi_ppi_mask_df, extra_columns])\n",
170
+ " new_mask_matrix = tf.constant(adjusted_mask_matrix, dtype=tf.float32)\n",
171
+ " \n",
172
+ " return new_mask_matrix\n",
173
+ "\n",
174
+ "def create_dna_mask_matrix (extra_neurons):\n",
175
+ " de_gene_to_idx = {gene: i for i, gene in enumerate(dna_genes)}\n",
176
+ " target_to_idx = {target: j for j, target in enumerate(dna_unique_targets)}\n",
177
+ " protein2_to_idx = {protein2: k for k, protein2 in enumerate(dna_unique_protein2)}\n",
178
+ "\n",
179
+ "\n",
180
+ " pdi_mask_matrix = np.zeros((len(dna_genes), len(dna_unique_targets)), dtype=int)\n",
181
+ "\n",
182
+ "\n",
183
+ " for _, row in df_dna_pdi.iterrows():\n",
184
+ " tfa = row[\"TF\"]\n",
185
+ " target = row[\"Target\"]\n",
186
+ " if tfa in de_gene_to_idx and target in target_to_idx:\n",
187
+ " tf_idx = de_gene_to_idx[tfa]\n",
188
+ " target_idx = target_to_idx[target]\n",
189
+ " pdi_mask_matrix[tf_idx, target_idx] = 1\n",
190
+ " \n",
191
+ " ppi_mask_matrix = np.zeros((len(dna_genes), len(dna_unique_protein2)))\n",
192
+ "\n",
193
+ " for _, row in df_dna_ppi.iterrows():\n",
194
+ " protein1 = row[\"protein1\"]\n",
195
+ " protein2 = row[\"protein2\"]\n",
196
+ " combined_score = row[\"combined_score\"]\n",
197
+ "\n",
198
+ " if protein1 in de_gene_to_idx and protein2 in protein2_to_idx:\n",
199
+ " protein1_idx = de_gene_to_idx[protein1]\n",
200
+ " protein2_idx = protein2_to_idx[protein2]\n",
201
+ " ppi_mask_matrix[protein1_idx, protein2_idx] = combined_score \n",
202
+ " \n",
203
+ " pdi_mask_df= pd.DataFrame(pdi_mask_matrix)\n",
204
+ " ppi_mask_df= pd.DataFrame(ppi_mask_matrix)\n",
205
+ " pdi_mask_df.columns = dna_unique_targets\n",
206
+ " ppi_mask_df.columns = dna_unique_protein2\n",
207
+ " pdi_ppi_mask_df = pd.concat([pdi_mask_df, ppi_mask_df], axis=1)\n",
208
+ " pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, ~pdi_ppi_mask_df.columns.duplicated(keep='last')]\n",
209
+ " col_mask = pdi_ppi_mask_df.columns.isin(dna_pdi_ppi_genept)\n",
210
+ " sub_pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, col_mask]\n",
211
+ "\n",
212
+ " \n",
213
+ " extra_columns = np.ones((len(dna_genes), extra_neurons), dtype=int)\n",
214
+ " adjusted_mask_matrix = np.hstack([sub_pdi_ppi_mask_df, extra_columns])\n",
215
+ " new_mask_matrix = tf.constant(adjusted_mask_matrix, dtype=tf.float32)\n",
216
+ " \n",
217
+ " return new_mask_matrix"
218
+ ]
219
+ },
220
+ {
221
+ "cell_type": "code",
222
+ "execution_count": null,
223
+ "id": "e5161905-ef7c-4943-aa4d-46a2dc84f498",
224
+ "metadata": {},
225
+ "outputs": [],
226
+ "source": [
227
+ "ge_target_to_kegg = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"ge_target_to_KEGG_significant.csv\",sep=',')\n",
228
+ "dna_target_to_kegg = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"dna_target_to_KEGG_significant.csv\",sep=',')"
229
+ ]
230
+ },
231
+ {
232
+ "cell_type": "code",
233
+ "execution_count": null,
234
+ "id": "235fc5c0-0c09-47c7-aa1a-36d708372be1",
235
+ "metadata": {},
236
+ "outputs": [],
237
+ "source": [
238
+ "def create_ge_pathway_mask_matrix ():\n",
239
+ " genes = ge_pdi_ppi_genept\n",
240
+ " pathways = ge_target_to_kegg[\"PathwayID\"].unique().tolist()\n",
241
+ "\n",
242
+ " gene_to_idx = {gene: i for i, gene in enumerate(genes)}\n",
243
+ " pathway_to_idx = {pw: j for j, pw in enumerate(pathways)}\n",
244
+ "\n",
245
+ " mask_matrix = np.zeros((len(genes), len(pathways)), dtype=int)\n",
246
+ "\n",
247
+ " for _, row in ge_target_to_kegg.iterrows():\n",
248
+ " gene = row[\"SYMBOL\"]\n",
249
+ " pw = row[\"PathwayID\"]\n",
250
+ " if gene in gene_to_idx and pw in pathway_to_idx:\n",
251
+ " i = gene_to_idx[gene]\n",
252
+ " j = pathway_to_idx[pw]\n",
253
+ " mask_matrix[i, j] = 1\n",
254
+ " mask_df = pd.DataFrame(mask_matrix, index=genes, columns=pathways)\n",
255
+ " return mask_df\n",
256
+ "\n",
257
+ "def create_dna_pathway_mask_matrix ():\n",
258
+ " genes = dna_pdi_ppi_genept\n",
259
+ " pathways = dna_target_to_kegg[\"PathwayID\"].unique().tolist()\n",
260
+ "\n",
261
+ " gene_to_idx = {gene: i for i, gene in enumerate(genes)}\n",
262
+ " pathway_to_idx = {pw: j for j, pw in enumerate(pathways)}\n",
263
+ "\n",
264
+ " mask_matrix = np.zeros((len(genes), len(pathways)), dtype=int)\n",
265
+ "\n",
266
+ " for _, row in dna_target_to_kegg.iterrows():\n",
267
+ " gene = row[\"SYMBOL\"]\n",
268
+ " pw = row[\"PathwayID\"]\n",
269
+ " if gene in gene_to_idx and pw in pathway_to_idx:\n",
270
+ " i = gene_to_idx[gene]\n",
271
+ " j = pathway_to_idx[pw]\n",
272
+ " mask_matrix[i, j] = 1\n",
273
+ " mask_df = pd.DataFrame(mask_matrix, index=genes, columns=pathways)\n",
274
+ " return mask_df\n",
275
+ " \n",
276
+ "mask_gene_pathway = create_ge_pathway_mask_matrix ()\n",
277
+ "mask_dna_pathway = create_dna_pathway_mask_matrix()"
278
+ ]
279
+ },
280
+ {
281
+ "cell_type": "code",
282
+ "execution_count": null,
283
+ "id": "dbfe572a-d773-4306-94a1-69ab748f924c",
284
+ "metadata": {},
285
+ "outputs": [],
286
+ "source": [
287
+ "class MaskedLayer(layers.Layer):\n",
288
+ " def __init__(self, mask_matrix, **kwargs):\n",
289
+ " super().__init__(**kwargs)\n",
290
+ " self.mask_matrix = K.constant(mask_matrix, dtype='float32')\n",
291
+ "\n",
292
+ " def call(self, inputs):\n",
293
+ " return K.dot(inputs, self.mask_matrix)\n",
294
+ "\n",
295
+ "\n",
296
+ "class AttentionPathwayLayer(layers.Layer):\n",
297
+ " def __init__(self, genept_embs, pathway_mask, init_seed=42, **kwargs):\n",
298
+ " super().__init__(**kwargs)\n",
299
+ "\n",
300
+ " genept_np = genept_embs.values if hasattr(genept_embs, \"values\") else np.asarray(genept_embs)\n",
301
+ " n_genes, D_embed = genept_np.shape\n",
302
+ "\n",
303
+ " pw_np = pathway_mask.values if hasattr(pathway_mask, \"values\") else np.asarray(pathway_mask)\n",
304
+ " n_pathways = pw_np.shape[1]\n",
305
+ "\n",
306
+ " self.G = K.constant(genept_np, dtype=\"float32\")\n",
307
+ " self.P = K.constant(pw_np, dtype=\"float32\")\n",
308
+ "\n",
309
+ " glorot = initializers.GlorotUniform(seed=init_seed)\n",
310
+ " self.W = self.add_weight(\n",
311
+ " shape=(n_pathways, D_embed),\n",
312
+ " initializer=glorot,\n",
313
+ " trainable=True,\n",
314
+ " name=\"W_query\"\n",
315
+ " )\n",
316
+ "\n",
317
+ " def call(self, x):\n",
318
+ " G_T = K.transpose(self.G) \n",
319
+ " S = K.dot(self.W, G_T) \n",
320
+ " S_T = K.transpose(S) \n",
321
+ " S_masked = S_T * self.P \n",
322
+ " alpha = K.softmax(S_masked, axis=0) \n",
323
+ " x_sq = K.squeeze(x, axis=-1) \n",
324
+ " out_mat = K.dot(x_sq, alpha) \n",
325
+ " return K.expand_dims(out_mat, axis=-1) \n",
326
+ "\n",
327
+ " def compute_output_shape(self, input_shape):\n",
328
+ " batch_size, n_genes, _ = input_shape\n",
329
+ " n_pathways = K.int_shape(self.P)[1]\n",
330
+ " return (batch_size, n_pathways, 1)\n",
331
+ " \n",
332
+ " def get_attention(self):\n",
333
+ " G_T = K.transpose(self.G) \n",
334
+ " S = K.dot(self.W, G_T) \n",
335
+ " S_T = K.transpose(S) \n",
336
+ " S_masked = S_T * self.P \n",
337
+ " return K.softmax(S_masked, axis=0) \n",
338
+ "\n",
339
+ "\n",
340
+ "def create_model(\n",
341
+ " num_ex_neuron,\n",
342
+ " layer_one,\n",
343
+ " activation_function_one,\n",
344
+ " activation_function_two,\n",
345
+ " activation_function_three,\n",
346
+ " dropout_p,\n",
347
+ " learning_rate,\n",
348
+ " l2_reg,\n",
349
+ " optimizer_choice,\n",
350
+ " genept_gene_emb,\n",
351
+ " mask_gene_pathway,\n",
352
+ " genept_dna_emb,\n",
353
+ " mask_dna_pathway\n",
354
+ "):\n",
355
+ "\n",
356
+ " new_ge_mask_matrix = create_ge_mask_matrix(num_ex_neuron)\n",
357
+ " new_dna_mask_matrix = create_dna_mask_matrix(num_ex_neuron)\n",
358
+ " \n",
359
+ " inputA = Input(shape=(df_gene.shape[1],), name=\"gene_input\")\n",
360
+ " x = layers.Dense(\n",
361
+ " new_ge_mask_matrix.shape[0],\n",
362
+ " activation=activation_function_one,\n",
363
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
364
+ " kernel_initializer=initializers.LecunUniform(42)\n",
365
+ " )(inputA)\n",
366
+ " x = MaskedLayer(new_ge_mask_matrix)(x)\n",
367
+ " x = Dropout(dropout_p)(x)\n",
368
+ " x_feat = Lambda(lambda t: K.expand_dims(t, -1), name=\"gene_add_dim\")(x)\n",
369
+ "\n",
370
+ " gene_path_layer = AttentionPathwayLayer(\n",
371
+ " genept_embs = genept_gene_emb,\n",
372
+ " pathway_mask = mask_gene_pathway,\n",
373
+ " name = \"gene_path_attn\"\n",
374
+ " )\n",
375
+ " x_path = gene_path_layer(x_feat)\n",
376
+ " x_path = Flatten()(x_path)\n",
377
+ "\n",
378
+ " x_proj = layers.Dense(\n",
379
+ " layer_one,\n",
380
+ " activation=activation_function_two,\n",
381
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
382
+ " kernel_initializer=initializers.LecunUniform(42),\n",
383
+ " name=\"gene_proj\"\n",
384
+ " )(x_path)\n",
385
+ "\n",
386
+ " inputB = Input(shape=(df_dna.shape[1],), name=\"dna_input\")\n",
387
+ " y = layers.Dense(\n",
388
+ " new_dna_mask_matrix.shape[0],\n",
389
+ " activation=activation_function_one,\n",
390
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
391
+ " kernel_initializer=initializers.LecunUniform(42)\n",
392
+ " )(inputB)\n",
393
+ " y = MaskedLayer(new_dna_mask_matrix)(y)\n",
394
+ " y = Dropout(dropout_p)(y)\n",
395
+ " y_feat = Lambda(lambda t: K.expand_dims(t, -1), name=\"dna_add_dim\")(y)\n",
396
+ "\n",
397
+ " dna_path_layer = AttentionPathwayLayer(\n",
398
+ " genept_embs = genept_dna_emb,\n",
399
+ " pathway_mask = mask_dna_pathway,\n",
400
+ " name = \"dna_path_attn\"\n",
401
+ " )\n",
402
+ " y_path = dna_path_layer(y_feat)\n",
403
+ " y_path = Flatten()(y_path)\n",
404
+ "\n",
405
+ " y_proj = layers.Dense(\n",
406
+ " layer_one,\n",
407
+ " activation=activation_function_two,\n",
408
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
409
+ " kernel_initializer=initializers.LecunUniform(42),\n",
410
+ " name=\"dna_proj\"\n",
411
+ " )(y_path)\n",
412
+ "\n",
413
+ " merged = concatenate([x_proj, y_proj], name=\"merge_xy\")\n",
414
+ " h = layers.Dense(\n",
415
+ " 12,\n",
416
+ " activation=activation_function_three,\n",
417
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
418
+ " kernel_initializer=initializers.GlorotUniform(42),\n",
419
+ " name=\"fusion_dense\"\n",
420
+ " )(merged)\n",
421
+ " h = Dropout(dropout_p, name=\"drop_fusion\")(h)\n",
422
+ " out = layers.Dense(\n",
423
+ " 4,\n",
424
+ " activation=\"softmax\",\n",
425
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
426
+ " kernel_initializer=initializers.GlorotUniform(42),\n",
427
+ " name=\"output\"\n",
428
+ " )(h)\n",
429
+ "\n",
430
+ " model = Model(inputs=[inputA, inputB], outputs=out, name=\"PathwayModel\")\n",
431
+ "\n",
432
+ " if optimizer_choice == 'adam':\n",
433
+ " optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)\n",
434
+ " elif optimizer_choice == 'sgd':\n",
435
+ " optimizer = tf.keras.optimizers.SGD(learning_rate=learning_rate)\n",
436
+ " model.compile(\n",
437
+ " optimizer=optimizer,\n",
438
+ " loss='sparse_categorical_crossentropy',\n",
439
+ " metrics=['accuracy']\n",
440
+ " )\n",
441
+ " return model"
442
+ ]
443
+ },
444
+ {
445
+ "cell_type": "code",
446
+ "execution_count": null,
447
+ "id": "be89b57c-5f26-4b58-be38-7d4c797d4c6d",
448
+ "metadata": {},
449
+ "outputs": [],
450
+ "source": [
451
+ "def evaluate_model_cv(X_train, X_val, y_train, y_val, params):\n",
452
+ " model = create_model(\n",
453
+ " num_ex_neuron=params['num_ex_neuron'],\n",
454
+ " layer_one=params['layer_one'],\n",
455
+ " activation_function_one=params['activation_function_one'],\n",
456
+ " activation_function_two=params['activation_function_two'],\n",
457
+ " activation_function_three=params['activation_function_three'],\n",
458
+ " dropout_p=params['dropout_p'],\n",
459
+ " learning_rate=params['learning_rate'],\n",
460
+ " l2_reg=params['l2_reg'],\n",
461
+ " optimizer_choice=params['optimizer'],\n",
462
+ " genept_gene_emb = genept_gene_emb,\n",
463
+ " mask_gene_pathway = mask_gene_pathway,\n",
464
+ " genept_dna_emb = genept_dna_emb,\n",
465
+ " mask_dna_pathway = mask_dna_pathway\n",
466
+ " )\n",
467
+ " \n",
468
+ " history = model.fit(\n",
469
+ " X_train, y_train,\n",
470
+ " validation_data=(X_val, y_val),\n",
471
+ " epochs=params['epoch'],\n",
472
+ " batch_size=params['batch_size'],\n",
473
+ " shuffle = False,\n",
474
+ " verbose=0\n",
475
+ " )\n",
476
+ " \n",
477
+ " y_pred = model.predict(X_val).argmax(1)\n",
478
+ " \n",
479
+ "\n",
480
+ " f1_weighted = f1_score(y_val, y_pred, average='weighted')\n",
481
+ " f1_macro = f1_score(y_val, y_pred, average='macro')\n",
482
+ " f1_micro = f1_score(y_val, y_pred, average='micro')\n",
483
+ "\n",
484
+ " precision_weighted = precision_score(y_val, y_pred, average='weighted')\n",
485
+ " precision_macro = precision_score(y_val, y_pred, average='macro')\n",
486
+ " precision_micro = precision_score(y_val, y_pred, average='micro')\n",
487
+ " \n",
488
+ "\n",
489
+ " recall_weighted = recall_score(y_val, y_pred, average='weighted')\n",
490
+ " recall_macro = recall_score(y_val, y_pred, average='macro')\n",
491
+ " recall_micro = recall_score(y_val, y_pred, average='micro')\n",
492
+ " \n",
493
+ "\n",
494
+ " accuracy = accuracy_score(y_val, y_pred)\n",
495
+ "\n",
496
+ " return {\n",
497
+ " 'history': history.history,\n",
498
+ " 'f1_weighted': f1_weighted,\n",
499
+ " 'f1_macro': f1_macro,\n",
500
+ " 'f1_micro': f1_micro,\n",
501
+ " 'precision_weighted': precision_weighted,\n",
502
+ " 'precision_macro': precision_macro,\n",
503
+ " 'precision_micro': precision_micro,\n",
504
+ " 'recall_weighted': recall_weighted,\n",
505
+ " 'recall_macro': recall_macro,\n",
506
+ " 'recall_micro': recall_micro,\n",
507
+ " 'accuracy': accuracy\n",
508
+ " }\n",
509
+ "\n",
510
+ "def evaluate_combination(param_combination,X_gene_train,X_dna_train,y_train,n_splits=5):\n",
511
+ " skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n",
512
+ " \n",
513
+ " f1_weighted_list = []\n",
514
+ " f1_macro_list = []\n",
515
+ " f1_micro_list = []\n",
516
+ " \n",
517
+ " precision_weighted_list = []\n",
518
+ " precision_macro_list = []\n",
519
+ " precision_micro_list = []\n",
520
+ " \n",
521
+ " recall_weighted_list = []\n",
522
+ " recall_macro_list = []\n",
523
+ " recall_micro_list = []\n",
524
+ " \n",
525
+ " accuracy_list = []\n",
526
+ " all_histories = []\n",
527
+ " \n",
528
+ " for train_idx, val_idx in skf.split(X_gene_train, y_train):\n",
529
+ " Xg_tr, Xg_val = X_gene_train[train_idx], X_gene_train[val_idx]\n",
530
+ " Xd_tr, Xd_val = X_dna_train[train_idx], X_dna_train[val_idx]\n",
531
+ " y_tr, y_val = y_train[train_idx], y_train[val_idx]\n",
532
+ " \n",
533
+ " scores = evaluate_model_cv([Xg_tr, Xd_tr], [Xg_val, Xd_val], y_tr, y_val, param_combination)\n",
534
+ " \n",
535
+ " history_dict = scores.pop('history')\n",
536
+ " all_histories.append(history_dict)\n",
537
+ " \n",
538
+ " f1_weighted_list.append(scores['f1_weighted'])\n",
539
+ " f1_macro_list.append(scores['f1_macro'])\n",
540
+ " f1_micro_list.append(scores['f1_micro'])\n",
541
+ " \n",
542
+ " precision_weighted_list.append(scores['precision_weighted'])\n",
543
+ " precision_macro_list.append(scores['precision_macro'])\n",
544
+ " precision_micro_list.append(scores['precision_micro'])\n",
545
+ " \n",
546
+ " recall_weighted_list.append(scores['recall_weighted'])\n",
547
+ " recall_macro_list.append(scores['recall_macro'])\n",
548
+ " recall_micro_list.append(scores['recall_micro'])\n",
549
+ " \n",
550
+ " accuracy_list.append(scores['accuracy'])\n",
551
+ " \n",
552
+ " results = {\n",
553
+ " 'f1_weighted_mean': np.mean(f1_weighted_list),\n",
554
+ " 'f1_weighted_std': np.std(f1_weighted_list),\n",
555
+ " 'f1_macro_mean': np.mean(f1_macro_list),\n",
556
+ " 'f1_macro_std': np.std(f1_macro_list),\n",
557
+ " 'f1_micro_mean': np.mean(f1_micro_list),\n",
558
+ " 'f1_micro_std': np.std(f1_micro_list),\n",
559
+ " 'precision_weighted_mean': np.mean(precision_weighted_list),\n",
560
+ " 'precision_weighted_std': np.std(precision_weighted_list),\n",
561
+ " 'precision_macro_mean': np.mean(precision_macro_list),\n",
562
+ " 'precision_macro_std': np.std(precision_macro_list),\n",
563
+ " 'precision_micro_mean': np.mean(precision_micro_list),\n",
564
+ " 'precision_micro_std': np.std(precision_micro_list),\n",
565
+ " 'recall_weighted_mean': np.mean(recall_weighted_list),\n",
566
+ " 'recall_weighted_std': np.std(recall_weighted_list),\n",
567
+ " 'recall_macro_mean': np.mean(recall_macro_list),\n",
568
+ " 'recall_macro_std': np.std(recall_macro_list),\n",
569
+ " 'recall_micro_mean': np.mean(recall_micro_list),\n",
570
+ " 'recall_micro_std': np.std(recall_micro_list),\n",
571
+ " 'accuracy_mean': np.mean(accuracy_list),\n",
572
+ " 'accuracy_std': np.std(accuracy_list)\n",
573
+ " }\n",
574
+ " \n",
575
+ " return param_combination, results, all_histories\n",
576
+ "\n",
577
+ "\n",
578
+ "params_grid = {\n",
579
+ " 'num_ex_neuron': [0],\n",
580
+ " 'layer_one': [64],\n",
581
+ " 'activation_function_one': ['relu'],\n",
582
+ " 'activation_function_two': ['sigmoid'],\n",
583
+ " 'activation_function_three': ['tanh'],\n",
584
+ " 'dropout_p': [0.3],\n",
585
+ " 'learning_rate': [0.01],\n",
586
+ " 'l2_reg': [0.01],\n",
587
+ " 'epoch': [200],\n",
588
+ " 'batch_size':[16],\n",
589
+ " 'optimizer': ['sgd']\n",
590
+ " }\n",
591
+ "\n",
592
+ "param_combinations = list(itertools.product(\n",
593
+ " params_grid['num_ex_neuron'],\n",
594
+ " params_grid['layer_one'],\n",
595
+ " params_grid['activation_function_one'],\n",
596
+ " params_grid['activation_function_two'],\n",
597
+ " params_grid['activation_function_three'],\n",
598
+ " params_grid['dropout_p'],\n",
599
+ " params_grid['learning_rate'],\n",
600
+ " params_grid['l2_reg'],\n",
601
+ " params_grid['epoch'],\n",
602
+ " params_grid['batch_size'],\n",
603
+ " params_grid['optimizer']\n",
604
+ " \n",
605
+ "))\n",
606
+ "\n",
607
+ "\n",
608
+ "param_dicts = []\n",
609
+ "for comb in param_combinations:\n",
610
+ " param_dicts.append({\n",
611
+ " 'num_ex_neuron': comb[0],\n",
612
+ " 'layer_one': comb[1],\n",
613
+ " 'activation_function_one': comb[2],\n",
614
+ " 'activation_function_two': comb[3],\n",
615
+ " 'activation_function_three': comb[4],\n",
616
+ " 'dropout_p': comb[5],\n",
617
+ " 'learning_rate': comb[6],\n",
618
+ " 'l2_reg': comb[7],\n",
619
+ " 'epoch': comb[8],\n",
620
+ " 'batch_size': comb[9],\n",
621
+ " 'optimizer': comb[10]\n",
622
+ " })\n",
623
+ "\n",
624
+ "\n",
625
+ "X_gene_train, X_gene_test, X_dna_train, X_dna_test, y_train, y_test = train_test_split(df_gene, df_dna, y, test_size=0.2, stratify=y, random_state=42)\n",
626
+ "scaler = StandardScaler()\n",
627
+ "X_gene_train=(np.array(X_gene_train).astype(np.float32))\n",
628
+ "scaler.fit(X_gene_train)\n",
629
+ "X_gene_train=scaler.transform(X_gene_train)\n",
630
+ "\n",
631
+ "X_gene_test=(np.array(X_gene_test).astype(np.float32))\n",
632
+ "scaler.fit(X_gene_test)\n",
633
+ "X_gene_test=scaler.transform(X_gene_test)\n",
634
+ "\n",
635
+ "X_dna_train=(np.array(X_dna_train).astype(np.float32))\n",
636
+ "scaler.fit(X_dna_train)\n",
637
+ "X_dna_train=scaler.transform(X_dna_train)\n",
638
+ "\n",
639
+ "X_dna_test=(np.array(X_dna_test).astype(np.float32))\n",
640
+ "scaler.fit(X_dna_test)\n",
641
+ "X_dna_test=scaler.transform(X_dna_test)\n",
642
+ "\n",
643
+ "with tqdm_joblib(\n",
644
+ " tqdm(desc=\"Evaluating hyperparameter combinations\",\n",
645
+ " total=len(param_dicts))\n",
646
+ " ) as progress_bar:\n",
647
+ " results = Parallel(n_jobs=-1)(\n",
648
+ " delayed(evaluate_combination)(\n",
649
+ " param, X_gene_train, X_dna_train, y_train, n_splits=5\n",
650
+ " )\n",
651
+ " for param in param_dicts\n",
652
+ " )\n",
653
+ "\n",
654
+ "results_list = []\n",
655
+ "histories_dict = {}\n",
656
+ "best_param_weighted = None\n",
657
+ "best_weighted_scores = None\n",
658
+ "best_avg_f1_weighted = -np.inf\n",
659
+ "\n",
660
+ "best_param_macro = None\n",
661
+ "best_macro_scores = None\n",
662
+ "best_avg_f1_macro = -np.inf\n",
663
+ "\n",
664
+ "best_param_micro = None\n",
665
+ "best_micro_scores = None\n",
666
+ "best_avg_f1_micro = -np.inf\n",
667
+ "\n",
668
+ "for param_dict, scores_dict, histories in results:\n",
669
+ " key = str(param_dict)\n",
670
+ " histories_dict[key] = histories\n",
671
+ "\n",
672
+ " row = {}\n",
673
+ " row.update(param_dict)\n",
674
+ " row.update(scores_dict)\n",
675
+ " results_list.append(row)\n",
676
+ "\n",
677
+ " if scores_dict['f1_weighted_mean'] > best_avg_f1_weighted:\n",
678
+ " best_avg_f1_weighted = scores_dict['f1_weighted_mean']\n",
679
+ " best_param_weighted = param_dict\n",
680
+ " best_weighted_scores = scores_dict\n",
681
+ "\n",
682
+ " if scores_dict['f1_macro_mean'] > best_avg_f1_macro:\n",
683
+ " best_avg_f1_macro = scores_dict['f1_macro_mean']\n",
684
+ " best_param_macro = param_dict\n",
685
+ " best_macro_scores = scores_dict\n",
686
+ "\n",
687
+ " if scores_dict['f1_micro_mean'] > best_avg_f1_micro:\n",
688
+ " best_avg_f1_micro = scores_dict['f1_micro_mean']\n",
689
+ " best_param_micro = param_dict\n",
690
+ " best_micro_scores = scores_dict\n",
691
+ "\n",
692
+ "results_df = pd.DataFrame(results_list)\n",
693
+ "best_key = str(best_param_macro)\n",
694
+ "best_histories = histories_dict[best_key] \n",
695
+ "\n",
696
+ "\n",
697
+ "hyperparam_cols = list(param_dict.keys())\n",
698
+ "\n",
699
+ "\n",
700
+ "weighted_cols = hyperparam_cols + ['f1_weighted_mean', 'f1_weighted_std',\n",
701
+ " 'precision_weighted_mean', 'precision_weighted_std',\n",
702
+ " 'recall_weighted_mean', 'recall_weighted_std',\n",
703
+ " 'accuracy_mean', 'accuracy_std']\n",
704
+ "results_df_weighted = results_df[weighted_cols]\n",
705
+ "\n",
706
+ "macro_cols = hyperparam_cols + ['f1_macro_mean', 'f1_macro_std',\n",
707
+ " 'precision_macro_mean', 'precision_macro_std',\n",
708
+ " 'recall_macro_mean', 'recall_macro_std',\n",
709
+ " 'accuracy_mean', 'accuracy_std']\n",
710
+ "results_df_macro = results_df[macro_cols]\n",
711
+ "\n",
712
+ "micro_cols = hyperparam_cols + ['f1_micro_mean', 'f1_micro_std',\n",
713
+ " 'precision_micro_mean', 'precision_micro_std',\n",
714
+ " 'recall_micro_mean', 'recall_micro_std',\n",
715
+ " 'accuracy_mean', 'accuracy_std']\n",
716
+ "results_df_micro = results_df[micro_cols]"
717
+ ]
718
+ },
719
+ {
720
+ "cell_type": "code",
721
+ "execution_count": null,
722
+ "id": "2576c647-0fa9-44ff-b26b-b152a8f96389",
723
+ "metadata": {},
724
+ "outputs": [],
725
+ "source": [
726
+ "def create_model_test(\n",
727
+ " num_ex_neuron,\n",
728
+ " layer_one,\n",
729
+ " activation_function_one,\n",
730
+ " activation_function_two,\n",
731
+ " activation_function_three,\n",
732
+ " dropout_p,\n",
733
+ " learning_rate,\n",
734
+ " l2_reg,\n",
735
+ " optimizer_choice\n",
736
+ "):\n",
737
+ "\n",
738
+ " new_ge_mask_matrix = create_ge_mask_matrix(num_ex_neuron) \n",
739
+ " new_dna_mask_matrix = create_dna_mask_matrix(num_ex_neuron) \n",
740
+ "\n",
741
+ " inputA = Input(shape=(df_gene.shape[1],), name=\"gene_input\") \n",
742
+ " inputB = Input(shape=(df_dna.shape[1],), name=\"dna_input\") \n",
743
+ "\n",
744
+ " x1 = layers.Dense(\n",
745
+ " new_ge_mask_matrix.shape[0], \n",
746
+ " kernel_initializer=LecunUniform(seed=42),\n",
747
+ " activation=activation_function_one,\n",
748
+ " kernel_regularizer=l2(l2_reg)\n",
749
+ " )(inputA) \n",
750
+ "\n",
751
+ "\n",
752
+ " x1_masked = MaskedLayer(new_ge_mask_matrix)(x1) \n",
753
+ " x1_masked = Dropout(dropout_p)(x1_masked) \n",
754
+ "\n",
755
+ " x1_feat = layers.Lambda(lambda t: K.expand_dims(t, -1), name=\"gene_add_dim\")(x1_masked)\n",
756
+ "\n",
757
+ " gene_path_layer = AttentionPathwayLayer(\n",
758
+ " genept_embs = genept_gene_emb, \n",
759
+ " pathway_mask = mask_gene_pathway, \n",
760
+ " name = \"gene_path_attn\"\n",
761
+ " )\n",
762
+ " x1_path = gene_path_layer(x1_feat) \n",
763
+ "\n",
764
+ " x1_path = Flatten(name=\"gene_flat_path\")(x1_path) \n",
765
+ "\n",
766
+ " x1_proj = layers.Dense(\n",
767
+ " layer_one,\n",
768
+ " kernel_initializer=LecunUniform(seed=42),\n",
769
+ " activation=activation_function_two,\n",
770
+ " kernel_regularizer=l2(l2_reg),\n",
771
+ " name=\"gene_proj\"\n",
772
+ " )(x1_path) \n",
773
+ "\n",
774
+ " y1 = layers.Dense(\n",
775
+ " new_dna_mask_matrix.shape[0], \n",
776
+ " kernel_initializer=LecunUniform(seed=42),\n",
777
+ " activation=activation_function_one,\n",
778
+ " kernel_regularizer=l2(l2_reg)\n",
779
+ " )(inputB) \n",
780
+ "\n",
781
+ " y1_masked = MaskedLayer(new_dna_mask_matrix)(y1) \n",
782
+ " y1_masked = Dropout(dropout_p)(y1_masked) \n",
783
+ "\n",
784
+ " y1_feat = layers.Lambda(lambda t: K.expand_dims(t, -1), name=\"dna_add_dim\")(y1_masked)\n",
785
+ "\n",
786
+ " dna_path_layer = AttentionPathwayLayer(\n",
787
+ " genept_embs = genept_dna_emb, \n",
788
+ " pathway_mask = mask_dna_pathway, \n",
789
+ " name = \"dna_path_attn\"\n",
790
+ " )\n",
791
+ " y1_path = dna_path_layer(y1_feat) \n",
792
+ " y1_path = Flatten(name=\"dna_flat_path\")(y1_path) \n",
793
+ "\n",
794
+ " y1_proj = layers.Dense(\n",
795
+ " layer_one,\n",
796
+ " kernel_initializer=LecunUniform(seed=42),\n",
797
+ " activation=activation_function_two,\n",
798
+ " kernel_regularizer=l2(l2_reg),\n",
799
+ " name=\"dna_proj\"\n",
800
+ " )(y1_path) \n",
801
+ "\n",
802
+ " merged = concatenate([x1_proj, y1_proj], name=\"merge_xy\") \n",
803
+ "\n",
804
+ " h = layers.Dense(\n",
805
+ " 12,\n",
806
+ " activation=activation_function_three,\n",
807
+ " kernel_initializer=GlorotUniform(seed=42),\n",
808
+ " kernel_regularizer=l2(l2_reg),\n",
809
+ " name=\"fusion_dense\"\n",
810
+ " )(merged) \n",
811
+ " h = Dropout(dropout_p, name=\"drop_fusion\")(h) \n",
812
+ "\n",
813
+ " outputs = layers.Dense(\n",
814
+ " 4,\n",
815
+ " activation=\"softmax\",\n",
816
+ " kernel_initializer=GlorotUniform(seed=42),\n",
817
+ " kernel_regularizer=l2(l2_reg),\n",
818
+ " name=\"output\"\n",
819
+ " )(h) \n",
820
+ "\n",
821
+ " model = Model(inputs=[inputA, inputB], outputs=outputs, name=\"PathwayModel_Test\")\n",
822
+ "\n",
823
+ " if optimizer_choice == 'adam':\n",
824
+ " optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)\n",
825
+ " else:\n",
826
+ " optimizer = tf.keras.optimizers.SGD(learning_rate=learning_rate)\n",
827
+ "\n",
828
+ " model.compile(\n",
829
+ " optimizer=optimizer,\n",
830
+ " loss='sparse_categorical_crossentropy',\n",
831
+ " metrics=['accuracy']\n",
832
+ " )\n",
833
+ " return model"
834
+ ]
835
+ },
836
+ {
837
+ "cell_type": "code",
838
+ "execution_count": null,
839
+ "id": "fcda02d6-b3bd-4028-ba48-b0095d80245a",
840
+ "metadata": {},
841
+ "outputs": [],
842
+ "source": [
843
+ "def evaluate_test_model(best_param, avg_type, save_csv_path=None):\n",
844
+ " \n",
845
+ " per_run_results = []\n",
846
+ "\n",
847
+ " for run_i in range(1,11):\n",
848
+ " \n",
849
+ " model = create_model_test(\n",
850
+ " num_ex_neuron = best_param['num_ex_neuron'],\n",
851
+ " layer_one = best_param['layer_one'],\n",
852
+ " activation_function_one = best_param['activation_function_one'],\n",
853
+ " activation_function_two = best_param['activation_function_two'],\n",
854
+ " activation_function_three = best_param['activation_function_three'],\n",
855
+ " dropout_p = best_param['dropout_p'],\n",
856
+ " learning_rate = best_param['learning_rate'],\n",
857
+ " l2_reg = best_param['l2_reg'],\n",
858
+ " optimizer_choice = best_param['optimizer']\n",
859
+ " )\n",
860
+ " model.fit(\n",
861
+ " [X_gene_train, X_dna_train],\n",
862
+ " y_train,\n",
863
+ " epochs=best_param['epoch'],\n",
864
+ " batch_size=best_param['batch_size'],\n",
865
+ " shuffle=False,\n",
866
+ " verbose=0\n",
867
+ " )\n",
868
+ " y_pred = model.predict([X_gene_test, X_dna_test]).argmax(axis=1)\n",
869
+ "\n",
870
+ " f1_val = f1_score(y_test, y_pred, average=avg_type)\n",
871
+ " precision_v = precision_score(y_test, y_pred, average=avg_type)\n",
872
+ " recall_v = recall_score(y_test, y_pred, average=avg_type)\n",
873
+ " acc_v = accuracy_score(y_test, y_pred)\n",
874
+ "\n",
875
+ " per_run_results.append({\n",
876
+ " 'run_idx': run_i,\n",
877
+ " f'f1_{avg_type}': f1_val,\n",
878
+ " f'precision_{avg_type}': precision_v,\n",
879
+ " f'recall_{avg_type}': recall_v,\n",
880
+ " 'accuracy': acc_v\n",
881
+ " })\n",
882
+ "\n",
883
+ " df_runs = pd.DataFrame(per_run_results)\n",
884
+ " summary = {\n",
885
+ " f'f1_{avg_type}_mean': df_runs[f'f1_{avg_type}'].mean(),\n",
886
+ " f'f1_{avg_type}_std': df_runs[f'f1_{avg_type}'].std(),\n",
887
+ " f'precision_{avg_type}_mean': df_runs[f'precision_{avg_type}'].mean(),\n",
888
+ " f'precision_{avg_type}_std': df_runs[f'precision_{avg_type}'].std(),\n",
889
+ " f'recall_{avg_type}_mean': df_runs[f'recall_{avg_type}'].mean(),\n",
890
+ " f'recall_{avg_type}_std': df_runs[f'recall_{avg_type}'].std(),\n",
891
+ " 'accuracy_mean': df_runs['accuracy'].mean(),\n",
892
+ " 'accuracy_std': df_runs['accuracy'].std()\n",
893
+ " }\n",
894
+ "\n",
895
+ " df_runs[f'f1_{avg_type}_std'] = pd.NA\n",
896
+ " df_runs[f'precision_{avg_type}_std'] = pd.NA\n",
897
+ " df_runs[f'recall_{avg_type}_std'] = pd.NA\n",
898
+ " df_runs['accuracy_std'] = pd.NA\n",
899
+ "\n",
900
+ " summary_row = {\n",
901
+ " 'run_idx': 'mean',\n",
902
+ " f'f1_{avg_type}': summary[f'f1_{avg_type}_mean'],\n",
903
+ " f'f1_{avg_type}_std': summary[f'f1_{avg_type}_std'],\n",
904
+ " f'precision_{avg_type}': summary[f'precision_{avg_type}_mean'],\n",
905
+ " f'precision_{avg_type}_std': summary[f'precision_{avg_type}_std'],\n",
906
+ " f'recall_{avg_type}': summary[f'recall_{avg_type}_mean'],\n",
907
+ " f'recall_{avg_type}_std': summary[f'recall_{avg_type}_std'],\n",
908
+ " 'accuracy': summary['accuracy_mean'],\n",
909
+ " 'accuracy_std': summary['accuracy_std']\n",
910
+ " }\n",
911
+ " df_summary = pd.DataFrame([summary_row])\n",
912
+ "\n",
913
+ " df_runs = pd.concat([df_runs, df_summary], ignore_index=True)\n",
914
+ "\n",
915
+ " if save_csv_path is not None:\n",
916
+ " df_runs.to_csv(save_csv_path, index=False)\n",
917
+ "\n",
918
+ " return summary, df_runs\n",
919
+ "\n",
920
+ "summary_macro, df_macro_runs = evaluate_test_model(\n",
921
+ " best_param_macro,\n",
922
+ " avg_type=\"macro\")"
923
+ ]
924
+ }
925
+ ],
926
+ "metadata": {
927
+ "kernelspec": {
928
+ "display_name": "colo2",
929
+ "language": "python",
930
+ "name": "colo2"
931
+ },
932
+ "language_info": {
933
+ "codemirror_mode": {
934
+ "name": "ipython",
935
+ "version": 3
936
+ },
937
+ "file_extension": ".py",
938
+ "mimetype": "text/x-python",
939
+ "name": "python",
940
+ "nbconvert_exporter": "python",
941
+ "pygments_lexer": "ipython3",
942
+ "version": "3.9.13"
943
+ }
944
+ },
945
+ "nbformat": 4,
946
+ "nbformat_minor": 5
947
+ }
diagnostics/2026-10-16-brca-training-collapse/upstream/BioLMNET_Weighted_Loss.ipynb ADDED
@@ -0,0 +1,940 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "id": "634f46b7-f788-499d-b014-7c28905eeff6",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import os\n",
11
+ "os.environ['PYTHONHASHSEED'] = '0'\n",
12
+ "os.environ['TF_DETERMINISTIC_OPS'] = '1'\n",
13
+ "os.environ['CUDA_VISIBLE_DEVICES'] = ''\n",
14
+ "import random\n",
15
+ "import numpy as np\n",
16
+ "import pandas as pd\n",
17
+ "import re\n",
18
+ "import glob\n",
19
+ "from collections import defaultdict\n",
20
+ "from sklearn.preprocessing import StandardScaler, LabelEncoder\n",
21
+ "from sklearn.metrics import accuracy_score, f1_score, precision_score, recall_score\n",
22
+ "from sklearn.model_selection import train_test_split, StratifiedKFold\n",
23
+ "import matplotlib.pyplot as plt\n",
24
+ "import tensorflow as tf\n",
25
+ "from tensorflow.keras import layers, Model\n",
26
+ "from tensorflow.keras.layers import Dense, Dropout, Input, concatenate\n",
27
+ "import itertools\n",
28
+ "from joblib import Parallel, delayed\n",
29
+ "from tqdm import tqdm\n",
30
+ "from tqdm_joblib import tqdm_joblib\n",
31
+ "from sklearn.utils.class_weight import compute_class_weight\n",
32
+ "tf.config.threading.set_intra_op_parallelism_threads(1)\n",
33
+ "tf.config.threading.set_inter_op_parallelism_threads(1)\n",
34
+ "tf.random.set_seed(42)"
35
+ ]
36
+ },
37
+ {
38
+ "cell_type": "code",
39
+ "execution_count": null,
40
+ "id": "535e884e-58a5-434e-95df-0735956321b5",
41
+ "metadata": {},
42
+ "outputs": [],
43
+ "source": [
44
+ "from huggingface_hub import hf_hub_download\n",
45
+ "import pandas as pd\n",
46
+ "\n",
47
+ "FNAME = \"embedding_associations_cell_type_tissue_drug_pathway_openai_large.parquet\" \n",
48
+ "\n",
49
+ "parquet_path = hf_hub_download(\n",
50
+ " repo_id = \"honicky/genept-composable-embeddings\",\n",
51
+ " filename = FNAME,\n",
52
+ " repo_type = \"model\"\n",
53
+ ")\n",
54
+ "emb = pd.read_parquet(parquet_path)"
55
+ ]
56
+ },
57
+ {
58
+ "cell_type": "code",
59
+ "execution_count": null,
60
+ "id": "1a4d2523-d9a0-466f-bc58-8c815370e9e2",
61
+ "metadata": {},
62
+ "outputs": [],
63
+ "source": [
64
+ "df_gene = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"Gene_Expression.csv\")\n",
65
+ "df_dna = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"DNA_Methylation.csv\")\n",
66
+ " \n",
67
+ "true_label = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"label.csv\")\n",
68
+ "\n",
69
+ "scaler = StandardScaler()\n",
70
+ "le = LabelEncoder()\n",
71
+ "le.fit(true_label)\n",
72
+ "y = le.transform(true_label)\n",
73
+ "\n",
74
+ "ge_genes = list(df_gene.columns)\n",
75
+ "\n",
76
+ "df_pdi = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"PDI\"/ \"PDI.csv\",sep=',')\n",
77
+ "df_ge_pdi = df_pdi[df_pdi.Target.isin(ge_genes)]\n",
78
+ "unique_tfs = df_ge_pdi['TF'].unique()\n",
79
+ "unique_targets = df_ge_pdi['Target'].unique()\n",
80
+ "\n",
81
+ "dna_genes =list(df_dna.columns) \n",
82
+ "df_dna_pdi = df_pdi[df_pdi.Target.isin(dna_genes)]\n",
83
+ "\n",
84
+ "dna_unique_tfs = df_dna_pdi['TF'].unique()\n",
85
+ "dna_unique_targets = df_dna_pdi['Target'].unique()\n",
86
+ "\n",
87
+ "df_ppi = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"PPI\"/ \"PPI.csv\",sep=',')\n",
88
+ "df_ppi = df_ppi.drop(['Unnamed: 0'], axis =1)\n",
89
+ "df_ppi['combined_score'] = df_ppi['combined_score'] /1000\n",
90
+ "threshold = df_ppi['combined_score'].quantile(0.9)\n",
91
+ "df_ppi = df_ppi[df_ppi['combined_score'] >= threshold]\n",
92
+ "df_ge_ppi = df_ppi[df_ppi['protein1'].isin(ge_genes) | df_ppi['protein2'].isin(ge_genes)]\n",
93
+ "unique_protein1 = df_ge_ppi['protein1'].unique()\n",
94
+ "unique_protein2 = df_ge_ppi['protein2'].unique()\n",
95
+ "\n",
96
+ "df_dna_ppi = df_ppi[df_ppi['protein1'].isin(dna_genes) | df_ppi['protein2'].isin(dna_genes)]\n",
97
+ "dna_unique_protein1 = df_dna_ppi['protein1'].unique()\n",
98
+ "dna_unique_protein2 = df_dna_ppi['protein2'].unique()\n",
99
+ "\n",
100
+ "ge_pdi_ppi_genes = list(set(unique_targets) | set(unique_protein2))\n",
101
+ "dna_pdi_ppi_genes = list(set(dna_unique_targets) | set(dna_unique_protein2))"
102
+ ]
103
+ },
104
+ {
105
+ "cell_type": "code",
106
+ "execution_count": null,
107
+ "id": "e868db94-f1d2-413a-b36f-028cb0d7499d",
108
+ "metadata": {},
109
+ "outputs": [],
110
+ "source": [
111
+ "genept_gene_emb = emb.loc[emb.index.isin(ge_pdi_ppi_genes)]\n",
112
+ "ge_pdi_ppi_genept = genept_gene_emb.index"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "code",
117
+ "execution_count": null,
118
+ "id": "dfd04c7c-6119-4f60-836e-43d2c2060299",
119
+ "metadata": {},
120
+ "outputs": [],
121
+ "source": [
122
+ "genept_dna_emb = emb.loc[emb.index.isin(dna_pdi_ppi_genes)]\n",
123
+ "dna_pdi_ppi_genept = genept_dna_emb.index"
124
+ ]
125
+ },
126
+ {
127
+ "cell_type": "code",
128
+ "execution_count": null,
129
+ "id": "537876a2-01e5-4a8f-92df-621469614756",
130
+ "metadata": {},
131
+ "outputs": [],
132
+ "source": [
133
+ "def create_ge_mask_matrix (extra_neurons):\n",
134
+ " de_gene_to_idx = {gene: i for i, gene in enumerate(ge_genes)}\n",
135
+ " target_to_idx = {target: j for j, target in enumerate(unique_targets)}\n",
136
+ " protein2_to_idx = {protein2: k for k, protein2 in enumerate(unique_protein2)}\n",
137
+ "\n",
138
+ " pdi_mask_matrix = np.zeros((len(ge_genes), len(unique_targets)), dtype=int)\n",
139
+ "\n",
140
+ " for _, row in df_ge_pdi.iterrows():\n",
141
+ " tfa = row[\"TF\"]\n",
142
+ " target = row[\"Target\"]\n",
143
+ " if tfa in de_gene_to_idx and target in target_to_idx:\n",
144
+ " tf_idx = de_gene_to_idx[tfa]\n",
145
+ " target_idx = target_to_idx[target]\n",
146
+ " pdi_mask_matrix[tf_idx, target_idx] = 1\n",
147
+ " \n",
148
+ " ppi_mask_matrix = np.zeros((len(ge_genes), len(unique_protein2)))\n",
149
+ "\n",
150
+ " for _, row in df_ge_ppi.iterrows():\n",
151
+ " protein1 = row[\"protein1\"]\n",
152
+ " protein2 = row[\"protein2\"]\n",
153
+ " combined_score = row[\"combined_score\"]\n",
154
+ "\n",
155
+ " if protein1 in de_gene_to_idx and protein2 in protein2_to_idx:\n",
156
+ " protein1_idx = de_gene_to_idx[protein1]\n",
157
+ " protein2_idx = protein2_to_idx[protein2]\n",
158
+ " ppi_mask_matrix[protein1_idx, protein2_idx] = combined_score \n",
159
+ " \n",
160
+ " pdi_mask_df= pd.DataFrame(pdi_mask_matrix)\n",
161
+ " ppi_mask_df= pd.DataFrame(ppi_mask_matrix)\n",
162
+ " pdi_mask_df.columns = unique_targets\n",
163
+ " ppi_mask_df.columns = unique_protein2\n",
164
+ " pdi_ppi_mask_df = pd.concat([pdi_mask_df, ppi_mask_df], axis=1)\n",
165
+ " pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, ~pdi_ppi_mask_df.columns.duplicated(keep='last')]\n",
166
+ " col_mask = pdi_ppi_mask_df.columns.isin(ge_pdi_ppi_genept)\n",
167
+ " sub_pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, col_mask]\n",
168
+ " \n",
169
+ " extra_columns = np.ones((len(ge_genes), extra_neurons), dtype=int)\n",
170
+ " adjusted_mask_matrix = np.hstack([sub_pdi_ppi_mask_df, extra_columns])\n",
171
+ " new_mask_matrix = tf.constant(adjusted_mask_matrix, dtype=tf.float32)\n",
172
+ " \n",
173
+ " return new_mask_matrix\n",
174
+ "\n",
175
+ "def create_dna_mask_matrix (extra_neurons):\n",
176
+ " de_gene_to_idx = {gene: i for i, gene in enumerate(dna_genes)}\n",
177
+ " target_to_idx = {target: j for j, target in enumerate(dna_unique_targets)}\n",
178
+ " protein2_to_idx = {protein2: k for k, protein2 in enumerate(dna_unique_protein2)}\n",
179
+ "\n",
180
+ "\n",
181
+ " pdi_mask_matrix = np.zeros((len(dna_genes), len(dna_unique_targets)), dtype=int)\n",
182
+ "\n",
183
+ "\n",
184
+ " for _, row in df_dna_pdi.iterrows():\n",
185
+ " tfa = row[\"TF\"]\n",
186
+ " target = row[\"Target\"]\n",
187
+ " if tfa in de_gene_to_idx and target in target_to_idx:\n",
188
+ " tf_idx = de_gene_to_idx[tfa]\n",
189
+ " target_idx = target_to_idx[target]\n",
190
+ " pdi_mask_matrix[tf_idx, target_idx] = 1\n",
191
+ " \n",
192
+ " ppi_mask_matrix = np.zeros((len(dna_genes), len(dna_unique_protein2)))\n",
193
+ "\n",
194
+ " for _, row in df_dna_ppi.iterrows():\n",
195
+ " protein1 = row[\"protein1\"]\n",
196
+ " protein2 = row[\"protein2\"]\n",
197
+ " combined_score = row[\"combined_score\"]\n",
198
+ "\n",
199
+ " if protein1 in de_gene_to_idx and protein2 in protein2_to_idx:\n",
200
+ " protein1_idx = de_gene_to_idx[protein1]\n",
201
+ " protein2_idx = protein2_to_idx[protein2]\n",
202
+ " ppi_mask_matrix[protein1_idx, protein2_idx] = combined_score \n",
203
+ " \n",
204
+ " pdi_mask_df= pd.DataFrame(pdi_mask_matrix)\n",
205
+ " ppi_mask_df= pd.DataFrame(ppi_mask_matrix)\n",
206
+ " pdi_mask_df.columns = dna_unique_targets\n",
207
+ " ppi_mask_df.columns = dna_unique_protein2\n",
208
+ " pdi_ppi_mask_df = pd.concat([pdi_mask_df, ppi_mask_df], axis=1)\n",
209
+ " pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, ~pdi_ppi_mask_df.columns.duplicated(keep='last')]\n",
210
+ " col_mask = pdi_ppi_mask_df.columns.isin(dna_pdi_ppi_genept)\n",
211
+ " sub_pdi_ppi_mask_df = pdi_ppi_mask_df.loc[:, col_mask]\n",
212
+ "\n",
213
+ " \n",
214
+ " extra_columns = np.ones((len(dna_genes), extra_neurons), dtype=int)\n",
215
+ " adjusted_mask_matrix = np.hstack([sub_pdi_ppi_mask_df, extra_columns])\n",
216
+ " new_mask_matrix = tf.constant(adjusted_mask_matrix, dtype=tf.float32)\n",
217
+ " \n",
218
+ " return new_mask_matrix"
219
+ ]
220
+ },
221
+ {
222
+ "cell_type": "code",
223
+ "execution_count": null,
224
+ "id": "e5161905-ef7c-4943-aa4d-46a2dc84f498",
225
+ "metadata": {},
226
+ "outputs": [],
227
+ "source": [
228
+ "ge_target_to_kegg = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"ge_target_to_KEGG_significant.csv\",sep=',')\n",
229
+ "dna_target_to_kegg = pd.read_csv(Path(__file__).parent / \"Dataset\"/\"scTrioseq2\"/ \"dna_target_to_KEGG_significant.csv\",sep=',')"
230
+ ]
231
+ },
232
+ {
233
+ "cell_type": "code",
234
+ "execution_count": null,
235
+ "id": "235fc5c0-0c09-47c7-aa1a-36d708372be1",
236
+ "metadata": {},
237
+ "outputs": [],
238
+ "source": [
239
+ "def create_ge_pathway_mask_matrix ():\n",
240
+ " genes = ge_pdi_ppi_genept\n",
241
+ " pathways = ge_target_to_kegg[\"PathwayID\"].unique().tolist()\n",
242
+ "\n",
243
+ " gene_to_idx = {gene: i for i, gene in enumerate(genes)}\n",
244
+ " pathway_to_idx = {pw: j for j, pw in enumerate(pathways)}\n",
245
+ "\n",
246
+ " mask_matrix = np.zeros((len(genes), len(pathways)), dtype=int)\n",
247
+ "\n",
248
+ " for _, row in ge_target_to_kegg.iterrows():\n",
249
+ " gene = row[\"SYMBOL\"]\n",
250
+ " pw = row[\"PathwayID\"]\n",
251
+ " if gene in gene_to_idx and pw in pathway_to_idx:\n",
252
+ " i = gene_to_idx[gene]\n",
253
+ " j = pathway_to_idx[pw]\n",
254
+ " mask_matrix[i, j] = 1\n",
255
+ " mask_df = pd.DataFrame(mask_matrix, index=genes, columns=pathways)\n",
256
+ " return mask_df\n",
257
+ "\n",
258
+ "def create_dna_pathway_mask_matrix ():\n",
259
+ " genes = dna_pdi_ppi_genept\n",
260
+ " pathways = dna_target_to_kegg[\"PathwayID\"].unique().tolist()\n",
261
+ "\n",
262
+ " gene_to_idx = {gene: i for i, gene in enumerate(genes)}\n",
263
+ " pathway_to_idx = {pw: j for j, pw in enumerate(pathways)}\n",
264
+ "\n",
265
+ " mask_matrix = np.zeros((len(genes), len(pathways)), dtype=int)\n",
266
+ "\n",
267
+ " for _, row in dna_target_to_kegg.iterrows():\n",
268
+ " gene = row[\"SYMBOL\"]\n",
269
+ " pw = row[\"PathwayID\"]\n",
270
+ " if gene in gene_to_idx and pw in pathway_to_idx:\n",
271
+ " i = gene_to_idx[gene]\n",
272
+ " j = pathway_to_idx[pw]\n",
273
+ " mask_matrix[i, j] = 1\n",
274
+ " mask_df = pd.DataFrame(mask_matrix, index=genes, columns=pathways)\n",
275
+ " return mask_df\n",
276
+ " \n",
277
+ "mask_gene_pathway = create_ge_pathway_mask_matrix ()\n",
278
+ "mask_dna_pathway = create_dna_pathway_mask_matrix()"
279
+ ]
280
+ },
281
+ {
282
+ "cell_type": "code",
283
+ "execution_count": null,
284
+ "id": "dbfe572a-d773-4306-94a1-69ab748f924c",
285
+ "metadata": {},
286
+ "outputs": [],
287
+ "source": [
288
+ "class MaskedLayer(layers.Layer):\n",
289
+ " def __init__(self, mask_matrix, **kwargs):\n",
290
+ " super().__init__(**kwargs)\n",
291
+ " self.mask_matrix = K.constant(mask_matrix, dtype='float32')\n",
292
+ "\n",
293
+ " def call(self, inputs):\n",
294
+ " return K.dot(inputs, self.mask_matrix)\n",
295
+ "\n",
296
+ "\n",
297
+ "class AttentionPathwayLayer(layers.Layer):\n",
298
+ " def __init__(self, genept_embs, pathway_mask, init_seed=42, **kwargs):\n",
299
+ " super().__init__(**kwargs)\n",
300
+ "\n",
301
+ " genept_np = genept_embs.values if hasattr(genept_embs, \"values\") else np.asarray(genept_embs)\n",
302
+ " n_genes, D_embed = genept_np.shape\n",
303
+ "\n",
304
+ " pw_np = pathway_mask.values if hasattr(pathway_mask, \"values\") else np.asarray(pathway_mask)\n",
305
+ " n_pathways = pw_np.shape[1]\n",
306
+ "\n",
307
+ " self.G = K.constant(genept_np, dtype=\"float32\")\n",
308
+ " self.P = K.constant(pw_np, dtype=\"float32\")\n",
309
+ "\n",
310
+ " glorot = initializers.GlorotUniform(seed=init_seed)\n",
311
+ " self.W = self.add_weight(\n",
312
+ " shape=(n_pathways, D_embed),\n",
313
+ " initializer=glorot,\n",
314
+ " trainable=True,\n",
315
+ " name=\"W_query\"\n",
316
+ " )\n",
317
+ "\n",
318
+ " def call(self, x):\n",
319
+ " G_T = K.transpose(self.G) \n",
320
+ " S = K.dot(self.W, G_T) \n",
321
+ " S_T = K.transpose(S) \n",
322
+ " S_masked = S_T * self.P \n",
323
+ " alpha = K.softmax(S_masked, axis=0) \n",
324
+ " x_sq = K.squeeze(x, axis=-1) \n",
325
+ " out_mat = K.dot(x_sq, alpha) \n",
326
+ " return K.expand_dims(out_mat, axis=-1) \n",
327
+ "\n",
328
+ " def compute_output_shape(self, input_shape):\n",
329
+ " batch_size, n_genes, _ = input_shape\n",
330
+ " n_pathways = K.int_shape(self.P)[1]\n",
331
+ " return (batch_size, n_pathways, 1)\n",
332
+ " \n",
333
+ " def get_attention(self):\n",
334
+ " G_T = K.transpose(self.G) \n",
335
+ " S = K.dot(self.W, G_T) \n",
336
+ " S_T = K.transpose(S) \n",
337
+ " S_masked = S_T * self.P \n",
338
+ " return K.softmax(S_masked, axis=0) \n",
339
+ "\n",
340
+ "\n",
341
+ "def create_model(\n",
342
+ " num_ex_neuron,\n",
343
+ " layer_one,\n",
344
+ " activation_function_one,\n",
345
+ " activation_function_two,\n",
346
+ " activation_function_three,\n",
347
+ " dropout_p,\n",
348
+ " learning_rate,\n",
349
+ " l2_reg,\n",
350
+ " optimizer_choice,\n",
351
+ " genept_gene_emb,\n",
352
+ " mask_gene_pathway,\n",
353
+ " genept_dna_emb,\n",
354
+ " mask_dna_pathway\n",
355
+ "):\n",
356
+ "\n",
357
+ " new_ge_mask_matrix = create_ge_mask_matrix(num_ex_neuron)\n",
358
+ " new_dna_mask_matrix = create_dna_mask_matrix(num_ex_neuron)\n",
359
+ " \n",
360
+ " inputA = Input(shape=(df_gene.shape[1],), name=\"gene_input\")\n",
361
+ " x = layers.Dense(\n",
362
+ " new_ge_mask_matrix.shape[0],\n",
363
+ " activation=activation_function_one,\n",
364
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
365
+ " kernel_initializer=initializers.LecunUniform(42)\n",
366
+ " )(inputA)\n",
367
+ " x = MaskedLayer(new_ge_mask_matrix)(x)\n",
368
+ " x = Dropout(dropout_p)(x)\n",
369
+ " x_feat = Lambda(lambda t: K.expand_dims(t, -1), name=\"gene_add_dim\")(x)\n",
370
+ "\n",
371
+ " gene_path_layer = AttentionPathwayLayer(\n",
372
+ " genept_embs = genept_gene_emb,\n",
373
+ " pathway_mask = mask_gene_pathway,\n",
374
+ " name = \"gene_path_attn\"\n",
375
+ " )\n",
376
+ " x_path = gene_path_layer(x_feat)\n",
377
+ " x_path = Flatten()(x_path)\n",
378
+ "\n",
379
+ " x_proj = layers.Dense(\n",
380
+ " layer_one,\n",
381
+ " activation=activation_function_two,\n",
382
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
383
+ " kernel_initializer=initializers.LecunUniform(42),\n",
384
+ " name=\"gene_proj\"\n",
385
+ " )(x_path)\n",
386
+ "\n",
387
+ " inputB = Input(shape=(df_dna.shape[1],), name=\"dna_input\")\n",
388
+ " y = layers.Dense(\n",
389
+ " new_dna_mask_matrix.shape[0],\n",
390
+ " activation=activation_function_one,\n",
391
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
392
+ " kernel_initializer=initializers.LecunUniform(42)\n",
393
+ " )(inputB)\n",
394
+ " y = MaskedLayer(new_dna_mask_matrix)(y)\n",
395
+ " y = Dropout(dropout_p)(y)\n",
396
+ " y_feat = Lambda(lambda t: K.expand_dims(t, -1), name=\"dna_add_dim\")(y)\n",
397
+ "\n",
398
+ " dna_path_layer = AttentionPathwayLayer(\n",
399
+ " genept_embs = genept_dna_emb,\n",
400
+ " pathway_mask = mask_dna_pathway,\n",
401
+ " name = \"dna_path_attn\"\n",
402
+ " )\n",
403
+ " y_path = dna_path_layer(y_feat)\n",
404
+ " y_path = Flatten()(y_path)\n",
405
+ "\n",
406
+ " y_proj = layers.Dense(\n",
407
+ " layer_one,\n",
408
+ " activation=activation_function_two,\n",
409
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
410
+ " kernel_initializer=initializers.LecunUniform(42),\n",
411
+ " name=\"dna_proj\"\n",
412
+ " )(y_path)\n",
413
+ "\n",
414
+ " merged = concatenate([x_proj, y_proj], name=\"merge_xy\")\n",
415
+ " h = layers.Dense(\n",
416
+ " 12,\n",
417
+ " activation=activation_function_three,\n",
418
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
419
+ " kernel_initializer=initializers.GlorotUniform(42),\n",
420
+ " name=\"fusion_dense\"\n",
421
+ " )(merged)\n",
422
+ " h = Dropout(dropout_p, name=\"drop_fusion\")(h)\n",
423
+ " out = layers.Dense(\n",
424
+ " 4,\n",
425
+ " activation=\"softmax\",\n",
426
+ " kernel_regularizer=regularizers.l2(l2_reg),\n",
427
+ " kernel_initializer=initializers.GlorotUniform(42),\n",
428
+ " name=\"output\"\n",
429
+ " )(h)\n",
430
+ "\n",
431
+ " model = Model(inputs=[inputA, inputB], outputs=out, name=\"PathwayModel\")\n",
432
+ "\n",
433
+ " if optimizer_choice == 'adam':\n",
434
+ " optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)\n",
435
+ " elif optimizer_choice == 'sgd':\n",
436
+ " optimizer = tf.keras.optimizers.SGD(learning_rate=learning_rate)\n",
437
+ " model.compile(\n",
438
+ " optimizer=optimizer,\n",
439
+ " loss='sparse_categorical_crossentropy',\n",
440
+ " metrics=['accuracy']\n",
441
+ " )\n",
442
+ " return model"
443
+ ]
444
+ },
445
+ {
446
+ "cell_type": "code",
447
+ "execution_count": null,
448
+ "id": "be89b57c-5f26-4b58-be38-7d4c797d4c6d",
449
+ "metadata": {},
450
+ "outputs": [],
451
+ "source": [
452
+ "def evaluate_model_cv(X_train, X_val, y_train, y_val, params):\n",
453
+ " model = create_model(\n",
454
+ " params['num_ex_neuron'], params['layer_one'],\n",
455
+ " params['activation_function_one'], params['activation_function_two'], params['activation_function_three'],\n",
456
+ " params['dropout_p'], params['learning_rate'], params['l2_reg'], params['optimizer'],\n",
457
+ " genept_gene_emb, mask_gene_pathway, genept_dna_emb, mask_dna_pathway\n",
458
+ " )\n",
459
+ "\n",
460
+ " classes = np.unique(y_train)\n",
461
+ " weights = compute_class_weight('balanced', classes=classes, y=y_train)\n",
462
+ " class_weight = dict(zip(classes, weights))\n",
463
+ "\n",
464
+ " history = model.fit(\n",
465
+ " X_train, y_train,\n",
466
+ " validation_data=(X_val, y_val),\n",
467
+ " epochs=params['epoch'], batch_size=params['batch_size'],\n",
468
+ " shuffle=False, verbose=0,\n",
469
+ " class_weight=class_weight\n",
470
+ " )\n",
471
+ "\n",
472
+ " y_pred = model.predict(X_val).argmax(1)\n",
473
+ " return {\n",
474
+ " 'history': history.history,\n",
475
+ " 'f1_weighted': f1_score(y_val, y_pred, average='weighted'),\n",
476
+ " 'f1_macro': f1_score(y_val, y_pred, average='macro'),\n",
477
+ " 'f1_micro': f1_score(y_val, y_pred, average='micro'),\n",
478
+ " 'precision_weighted': precision_score(y_val, y_pred, average='weighted'),\n",
479
+ " 'precision_macro': precision_score(y_val, y_pred, average='macro'),\n",
480
+ " 'precision_micro': precision_score(y_val, y_pred, average='micro'),\n",
481
+ " 'recall_weighted': recall_score(y_val, y_pred, average='weighted'),\n",
482
+ " 'recall_macro': recall_score(y_val, y_pred, average='macro'),\n",
483
+ " 'recall_micro': recall_score(y_val, y_pred, average='micro'),\n",
484
+ " 'accuracy': accuracy_score(y_val, y_pred)\n",
485
+ " }\n",
486
+ "\n",
487
+ "def evaluate_combination(param_combination,X_gene_train,X_dna_train,y_train,n_splits=5):\n",
488
+ " skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n",
489
+ " \n",
490
+ " f1_weighted_list = []\n",
491
+ " f1_macro_list = []\n",
492
+ " f1_micro_list = []\n",
493
+ " \n",
494
+ " precision_weighted_list = []\n",
495
+ " precision_macro_list = []\n",
496
+ " precision_micro_list = []\n",
497
+ " \n",
498
+ " recall_weighted_list = []\n",
499
+ " recall_macro_list = []\n",
500
+ " recall_micro_list = []\n",
501
+ " \n",
502
+ " accuracy_list = []\n",
503
+ " all_histories = []\n",
504
+ " \n",
505
+ " for train_idx, val_idx in skf.split(X_gene_train, y_train):\n",
506
+ " Xg_tr, Xg_val = X_gene_train[train_idx], X_gene_train[val_idx]\n",
507
+ " Xd_tr, Xd_val = X_dna_train[train_idx], X_dna_train[val_idx]\n",
508
+ " y_tr, y_val = y_train[train_idx], y_train[val_idx]\n",
509
+ " \n",
510
+ " scores = evaluate_model_cv([Xg_tr, Xd_tr], [Xg_val, Xd_val], y_tr, y_val, param_combination)\n",
511
+ " \n",
512
+ " history_dict = scores.pop('history')\n",
513
+ " all_histories.append(history_dict)\n",
514
+ " \n",
515
+ " # Append each metric.\n",
516
+ " f1_weighted_list.append(scores['f1_weighted'])\n",
517
+ " f1_macro_list.append(scores['f1_macro'])\n",
518
+ " f1_micro_list.append(scores['f1_micro'])\n",
519
+ " \n",
520
+ " precision_weighted_list.append(scores['precision_weighted'])\n",
521
+ " precision_macro_list.append(scores['precision_macro'])\n",
522
+ " precision_micro_list.append(scores['precision_micro'])\n",
523
+ " \n",
524
+ " recall_weighted_list.append(scores['recall_weighted'])\n",
525
+ " recall_macro_list.append(scores['recall_macro'])\n",
526
+ " recall_micro_list.append(scores['recall_micro'])\n",
527
+ " \n",
528
+ " accuracy_list.append(scores['accuracy'])\n",
529
+ " \n",
530
+ " results = {\n",
531
+ " 'f1_weighted_mean': np.mean(f1_weighted_list),\n",
532
+ " 'f1_weighted_std': np.std(f1_weighted_list),\n",
533
+ " 'f1_macro_mean': np.mean(f1_macro_list),\n",
534
+ " 'f1_macro_std': np.std(f1_macro_list),\n",
535
+ " 'f1_micro_mean': np.mean(f1_micro_list),\n",
536
+ " 'f1_micro_std': np.std(f1_micro_list),\n",
537
+ " 'precision_weighted_mean': np.mean(precision_weighted_list),\n",
538
+ " 'precision_weighted_std': np.std(precision_weighted_list),\n",
539
+ " 'precision_macro_mean': np.mean(precision_macro_list),\n",
540
+ " 'precision_macro_std': np.std(precision_macro_list),\n",
541
+ " 'precision_micro_mean': np.mean(precision_micro_list),\n",
542
+ " 'precision_micro_std': np.std(precision_micro_list),\n",
543
+ " 'recall_weighted_mean': np.mean(recall_weighted_list),\n",
544
+ " 'recall_weighted_std': np.std(recall_weighted_list),\n",
545
+ " 'recall_macro_mean': np.mean(recall_macro_list),\n",
546
+ " 'recall_macro_std': np.std(recall_macro_list),\n",
547
+ " 'recall_micro_mean': np.mean(recall_micro_list),\n",
548
+ " 'recall_micro_std': np.std(recall_micro_list),\n",
549
+ " 'accuracy_mean': np.mean(accuracy_list),\n",
550
+ " 'accuracy_std': np.std(accuracy_list)\n",
551
+ " }\n",
552
+ " \n",
553
+ " return param_combination, results, all_histories\n",
554
+ "\n",
555
+ "params_grid = {\n",
556
+ " 'num_ex_neuron': [0],\n",
557
+ " 'layer_one': [64],\n",
558
+ " 'activation_function_one': ['relu'],\n",
559
+ " 'activation_function_two': ['sigmoid'],\n",
560
+ " 'activation_function_three': ['tanh'],\n",
561
+ " 'dropout_p': [0.3],\n",
562
+ " 'learning_rate': [0.01],\n",
563
+ " 'l2_reg': [0.01],\n",
564
+ " 'epoch': [200],\n",
565
+ " 'batch_size':[16],\n",
566
+ " 'optimizer': ['sgd']\n",
567
+ " }\n",
568
+ "\n",
569
+ "\n",
570
+ "param_combinations = list(itertools.product(\n",
571
+ " params_grid['num_ex_neuron'],\n",
572
+ " params_grid['layer_one'],\n",
573
+ " params_grid['activation_function_one'],\n",
574
+ " params_grid['activation_function_two'],\n",
575
+ " params_grid['activation_function_three'],\n",
576
+ " params_grid['dropout_p'],\n",
577
+ " params_grid['learning_rate'],\n",
578
+ " params_grid['l2_reg'],\n",
579
+ " params_grid['epoch'],\n",
580
+ " params_grid['batch_size'],\n",
581
+ " params_grid['optimizer']\n",
582
+ " \n",
583
+ "))\n",
584
+ "\n",
585
+ "\n",
586
+ "param_dicts = []\n",
587
+ "for comb in param_combinations:\n",
588
+ " param_dicts.append({\n",
589
+ " 'num_ex_neuron': comb[0],\n",
590
+ " 'layer_one': comb[1],\n",
591
+ " 'activation_function_one': comb[2],\n",
592
+ " 'activation_function_two': comb[3],\n",
593
+ " 'activation_function_three': comb[4],\n",
594
+ " 'dropout_p': comb[5],\n",
595
+ " 'learning_rate': comb[6],\n",
596
+ " 'l2_reg': comb[7],\n",
597
+ " 'epoch': comb[8],\n",
598
+ " 'batch_size': comb[9],\n",
599
+ " 'optimizer': comb[10]\n",
600
+ " })\n",
601
+ "\n",
602
+ "\n",
603
+ "X_gene_train, X_gene_test, X_dna_train, X_dna_test, y_train, y_test = train_test_split(df_gene, df_dna, y, test_size=0.2, stratify=y, random_state=42)\n",
604
+ "scaler = StandardScaler()\n",
605
+ "X_gene_train=(np.array(X_gene_train).astype(np.float32))\n",
606
+ "scaler.fit(X_gene_train)\n",
607
+ "X_gene_train=scaler.transform(X_gene_train)\n",
608
+ "\n",
609
+ "X_gene_test=(np.array(X_gene_test).astype(np.float32))\n",
610
+ "scaler.fit(X_gene_test)\n",
611
+ "X_gene_test=scaler.transform(X_gene_test)\n",
612
+ "\n",
613
+ "X_dna_train=(np.array(X_dna_train).astype(np.float32))\n",
614
+ "scaler.fit(X_dna_train)\n",
615
+ "X_dna_train=scaler.transform(X_dna_train)\n",
616
+ "\n",
617
+ "X_dna_test=(np.array(X_dna_test).astype(np.float32))\n",
618
+ "scaler.fit(X_dna_test)\n",
619
+ "X_dna_test=scaler.transform(X_dna_test)\n",
620
+ "\n",
621
+ "\n",
622
+ "with tqdm_joblib(\n",
623
+ " tqdm(desc=\"Evaluating hyperparameter combinations\",\n",
624
+ " total=len(param_dicts))\n",
625
+ " ) as progress_bar:\n",
626
+ " results = Parallel(n_jobs=-1)(\n",
627
+ " delayed(evaluate_combination)(\n",
628
+ " param, X_gene_train, X_dna_train, y_train, n_splits=5\n",
629
+ " )\n",
630
+ " for param in param_dicts\n",
631
+ " )\n",
632
+ "\n",
633
+ "results_list = []\n",
634
+ "histories_dict = {}\n",
635
+ "best_param_weighted = None\n",
636
+ "best_weighted_scores = None\n",
637
+ "best_avg_f1_weighted = -np.inf\n",
638
+ "\n",
639
+ "best_param_macro = None\n",
640
+ "best_macro_scores = None\n",
641
+ "best_avg_f1_macro = -np.inf\n",
642
+ "\n",
643
+ "best_param_micro = None\n",
644
+ "best_micro_scores = None\n",
645
+ "best_avg_f1_micro = -np.inf\n",
646
+ "\n",
647
+ "for param_dict, scores_dict, histories in results:\n",
648
+ " key = str(param_dict)\n",
649
+ " histories_dict[key] = histories\n",
650
+ "\n",
651
+ " row = {}\n",
652
+ " row.update(param_dict)\n",
653
+ " row.update(scores_dict)\n",
654
+ " results_list.append(row)\n",
655
+ "\n",
656
+ " if scores_dict['f1_weighted_mean'] > best_avg_f1_weighted:\n",
657
+ " best_avg_f1_weighted = scores_dict['f1_weighted_mean']\n",
658
+ " best_param_weighted = param_dict\n",
659
+ " best_weighted_scores = scores_dict\n",
660
+ "\n",
661
+ " if scores_dict['f1_macro_mean'] > best_avg_f1_macro:\n",
662
+ " best_avg_f1_macro = scores_dict['f1_macro_mean']\n",
663
+ " best_param_macro = param_dict\n",
664
+ " best_macro_scores = scores_dict\n",
665
+ "\n",
666
+ " if scores_dict['f1_micro_mean'] > best_avg_f1_micro:\n",
667
+ " best_avg_f1_micro = scores_dict['f1_micro_mean']\n",
668
+ " best_param_micro = param_dict\n",
669
+ " best_micro_scores = scores_dict\n",
670
+ "\n",
671
+ "results_df = pd.DataFrame(results_list)\n",
672
+ "best_key = str(best_param_macro)\n",
673
+ "best_histories = histories_dict[best_key] \n",
674
+ "\n",
675
+ "\n",
676
+ "hyperparam_cols = list(param_dict.keys())\n",
677
+ "\n",
678
+ "\n",
679
+ "weighted_cols = hyperparam_cols + ['f1_weighted_mean', 'f1_weighted_std',\n",
680
+ " 'precision_weighted_mean', 'precision_weighted_std',\n",
681
+ " 'recall_weighted_mean', 'recall_weighted_std',\n",
682
+ " 'accuracy_mean', 'accuracy_std']\n",
683
+ "results_df_weighted = results_df[weighted_cols]\n",
684
+ "\n",
685
+ "macro_cols = hyperparam_cols + ['f1_macro_mean', 'f1_macro_std',\n",
686
+ " 'precision_macro_mean', 'precision_macro_std',\n",
687
+ " 'recall_macro_mean', 'recall_macro_std',\n",
688
+ " 'accuracy_mean', 'accuracy_std']\n",
689
+ "results_df_macro = results_df[macro_cols]\n",
690
+ "\n",
691
+ "micro_cols = hyperparam_cols + ['f1_micro_mean', 'f1_micro_std',\n",
692
+ " 'precision_micro_mean', 'precision_micro_std',\n",
693
+ " 'recall_micro_mean', 'recall_micro_std',\n",
694
+ " 'accuracy_mean', 'accuracy_std']\n",
695
+ "results_df_micro = results_df[micro_cols]"
696
+ ]
697
+ },
698
+ {
699
+ "cell_type": "code",
700
+ "execution_count": null,
701
+ "id": "2576c647-0fa9-44ff-b26b-b152a8f96389",
702
+ "metadata": {},
703
+ "outputs": [],
704
+ "source": [
705
+ "def create_model_test(\n",
706
+ " num_ex_neuron,\n",
707
+ " layer_one,\n",
708
+ " activation_function_one,\n",
709
+ " activation_function_two,\n",
710
+ " activation_function_three,\n",
711
+ " dropout_p,\n",
712
+ " learning_rate,\n",
713
+ " l2_reg,\n",
714
+ " optimizer_choice\n",
715
+ "):\n",
716
+ "\n",
717
+ " new_ge_mask_matrix = create_ge_mask_matrix(num_ex_neuron) \n",
718
+ " new_dna_mask_matrix = create_dna_mask_matrix(num_ex_neuron) \n",
719
+ "\n",
720
+ " inputA = Input(shape=(df_gene.shape[1],), name=\"gene_input\") \n",
721
+ " inputB = Input(shape=(df_dna.shape[1],), name=\"dna_input\") \n",
722
+ "\n",
723
+ " x1 = layers.Dense(\n",
724
+ " new_ge_mask_matrix.shape[0], \n",
725
+ " kernel_initializer=LecunUniform(seed=42),\n",
726
+ " activation=activation_function_one,\n",
727
+ " kernel_regularizer=l2(l2_reg)\n",
728
+ " )(inputA) \n",
729
+ "\n",
730
+ "\n",
731
+ " x1_masked = MaskedLayer(new_ge_mask_matrix)(x1) \n",
732
+ " x1_masked = Dropout(dropout_p)(x1_masked) \n",
733
+ "\n",
734
+ " x1_feat = layers.Lambda(lambda t: K.expand_dims(t, -1), name=\"gene_add_dim\")(x1_masked)\n",
735
+ "\n",
736
+ " gene_path_layer = AttentionPathwayLayer(\n",
737
+ " genept_embs = genept_gene_emb, \n",
738
+ " pathway_mask = mask_gene_pathway, \n",
739
+ " name = \"gene_path_attn\"\n",
740
+ " )\n",
741
+ " x1_path = gene_path_layer(x1_feat) \n",
742
+ "\n",
743
+ " x1_path = Flatten(name=\"gene_flat_path\")(x1_path) \n",
744
+ "\n",
745
+ " x1_proj = layers.Dense(\n",
746
+ " layer_one,\n",
747
+ " kernel_initializer=LecunUniform(seed=42),\n",
748
+ " activation=activation_function_two,\n",
749
+ " kernel_regularizer=l2(l2_reg),\n",
750
+ " name=\"gene_proj\"\n",
751
+ " )(x1_path) \n",
752
+ "\n",
753
+ " y1 = layers.Dense(\n",
754
+ " new_dna_mask_matrix.shape[0], \n",
755
+ " kernel_initializer=LecunUniform(seed=42),\n",
756
+ " activation=activation_function_one,\n",
757
+ " kernel_regularizer=l2(l2_reg)\n",
758
+ " )(inputB) \n",
759
+ "\n",
760
+ " y1_masked = MaskedLayer(new_dna_mask_matrix)(y1) \n",
761
+ " y1_masked = Dropout(dropout_p)(y1_masked) \n",
762
+ "\n",
763
+ " y1_feat = layers.Lambda(lambda t: K.expand_dims(t, -1), name=\"dna_add_dim\")(y1_masked)\n",
764
+ "\n",
765
+ " dna_path_layer = AttentionPathwayLayer(\n",
766
+ " genept_embs = genept_dna_emb, \n",
767
+ " pathway_mask = mask_dna_pathway, \n",
768
+ " name = \"dna_path_attn\"\n",
769
+ " )\n",
770
+ " y1_path = dna_path_layer(y1_feat) \n",
771
+ " y1_path = Flatten(name=\"dna_flat_path\")(y1_path) \n",
772
+ "\n",
773
+ " y1_proj = layers.Dense(\n",
774
+ " layer_one,\n",
775
+ " kernel_initializer=LecunUniform(seed=42),\n",
776
+ " activation=activation_function_two,\n",
777
+ " kernel_regularizer=l2(l2_reg),\n",
778
+ " name=\"dna_proj\"\n",
779
+ " )(y1_path) \n",
780
+ "\n",
781
+ " merged = concatenate([x1_proj, y1_proj], name=\"merge_xy\") \n",
782
+ "\n",
783
+ " h = layers.Dense(\n",
784
+ " 12,\n",
785
+ " activation=activation_function_three,\n",
786
+ " kernel_initializer=GlorotUniform(seed=42),\n",
787
+ " kernel_regularizer=l2(l2_reg),\n",
788
+ " name=\"fusion_dense\"\n",
789
+ " )(merged) \n",
790
+ " h = Dropout(dropout_p, name=\"drop_fusion\")(h) \n",
791
+ "\n",
792
+ " outputs = layers.Dense(\n",
793
+ " 4,\n",
794
+ " activation=\"softmax\",\n",
795
+ " kernel_initializer=GlorotUniform(seed=42),\n",
796
+ " kernel_regularizer=l2(l2_reg),\n",
797
+ " name=\"output\"\n",
798
+ " )(h) \n",
799
+ "\n",
800
+ " model = Model(inputs=[inputA, inputB], outputs=outputs, name=\"PathwayModel_Test\")\n",
801
+ "\n",
802
+ " if optimizer_choice == 'adam':\n",
803
+ " optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)\n",
804
+ " else:\n",
805
+ " optimizer = tf.keras.optimizers.SGD(learning_rate=learning_rate)\n",
806
+ "\n",
807
+ " model.compile(\n",
808
+ " optimizer=optimizer,\n",
809
+ " loss='sparse_categorical_crossentropy',\n",
810
+ " metrics=['accuracy']\n",
811
+ " )\n",
812
+ " return model"
813
+ ]
814
+ },
815
+ {
816
+ "cell_type": "code",
817
+ "execution_count": null,
818
+ "id": "fcda02d6-b3bd-4028-ba48-b0095d80245a",
819
+ "metadata": {},
820
+ "outputs": [],
821
+ "source": [
822
+ "def _run_once(run_i,\n",
823
+ " best_param,\n",
824
+ " class_weight,\n",
825
+ " avg_type,\n",
826
+ " X_gene_train, X_dna_train, y_train,\n",
827
+ " X_gene_test, X_dna_test, y_test,\n",
828
+ " genept_gene_emb, mask_gene_pathway,\n",
829
+ " genept_dna_emb, mask_dna_pathway):\n",
830
+ " model = create_model(\n",
831
+ " best_param['num_ex_neuron'],\n",
832
+ " best_param['layer_one'],\n",
833
+ " best_param['activation_function_one'],\n",
834
+ " best_param['activation_function_two'],\n",
835
+ " best_param['activation_function_three'],\n",
836
+ " best_param['dropout_p'],\n",
837
+ " best_param['learning_rate'],\n",
838
+ " best_param['l2_reg'],\n",
839
+ " best_param['optimizer'],\n",
840
+ " genept_gene_emb, mask_gene_pathway,\n",
841
+ " genept_dna_emb, mask_dna_pathway\n",
842
+ " )\n",
843
+ "\n",
844
+ " model.fit(\n",
845
+ " [X_gene_train, X_dna_train], y_train,\n",
846
+ " epochs=best_param['epoch'],\n",
847
+ " batch_size=best_param['batch_size'],\n",
848
+ " shuffle=False,\n",
849
+ " verbose=0,\n",
850
+ " class_weight=class_weight\n",
851
+ " )\n",
852
+ " \n",
853
+ " y_pred = model.predict([X_gene_test, X_dna_test]).argmax(axis=1)\n",
854
+ " return {\n",
855
+ " 'run_idx': run_i,\n",
856
+ " f'f1_{avg_type}': f1_score(y_test, y_pred, average=avg_type),\n",
857
+ " f'precision_{avg_type}': precision_score(y_test, y_pred, average=avg_type),\n",
858
+ " f'recall_{avg_type}': recall_score(y_test, y_pred, average=avg_type),\n",
859
+ " 'accuracy': accuracy_score(y_test, y_pred)\n",
860
+ " }\n",
861
+ "\n",
862
+ "def evaluate_test_model(best_param, avg_type, n_runs=10, save_csv_path=None):\n",
863
+ " \n",
864
+ " classes = np.unique(y_train)\n",
865
+ " weights = compute_class_weight('balanced', classes=classes, y=y_train)\n",
866
+ " class_weight = dict(zip(classes, weights))\n",
867
+ "\n",
868
+ " per_run_results = Parallel(\n",
869
+ " n_jobs=n_runs,\n",
870
+ " backend='multiprocessing'\n",
871
+ " )(\n",
872
+ " delayed(_run_once)(\n",
873
+ " run_i,\n",
874
+ " best_param,\n",
875
+ " class_weight,\n",
876
+ " avg_type,\n",
877
+ " X_gene_train, X_dna_train, y_train,\n",
878
+ " X_gene_test, X_dna_test, y_test,\n",
879
+ " genept_gene_emb, mask_gene_pathway,\n",
880
+ " genept_dna_emb, mask_dna_pathway\n",
881
+ " )\n",
882
+ " for run_i in range(1, n_runs+1)\n",
883
+ " )\n",
884
+ "\n",
885
+ " df_runs = pd.DataFrame(per_run_results)\n",
886
+ " \n",
887
+ " summary = {\n",
888
+ " f'f1_{avg_type}_mean': df_runs[f'f1_{avg_type}'].mean(),\n",
889
+ " f'f1_{avg_type}_std': df_runs[f'f1_{avg_type}'].std(),\n",
890
+ " f'precision_{avg_type}_mean': df_runs[f'precision_{avg_type}'].mean(),\n",
891
+ " f'precision_{avg_type}_std': df_runs[f'precision_{avg_type}'].std(),\n",
892
+ " f'recall_{avg_type}_mean': df_runs[f'recall_{avg_type}'].mean(),\n",
893
+ " f'recall_{avg_type}_std': df_runs[f'recall_{avg_type}'].std(),\n",
894
+ " 'accuracy_mean': df_runs['accuracy'].mean(),\n",
895
+ " 'accuracy_std': df_runs['accuracy'].std()\n",
896
+ " }\n",
897
+ "\n",
898
+ " if save_csv_path:\n",
899
+ " df_runs.to_csv(save_csv_path, index=False)\n",
900
+ "\n",
901
+ " return summary, df_runs\n",
902
+ "\n",
903
+ "\n",
904
+ "summary_macro, df_macro_runs = evaluate_test_model(\n",
905
+ " best_param_macro,\n",
906
+ " avg_type=\"macro\"\n",
907
+ ")\n",
908
+ "summary_weighted, df_weighted_runs = evaluate_test_model(\n",
909
+ " best_param_weighted,\n",
910
+ " avg_type=\"weighted\"\n",
911
+ ")\n",
912
+ "summary_micro, df_micro_runs = evaluate_test_model(\n",
913
+ " best_param_micro,\n",
914
+ " avg_type=\"micro\"\n",
915
+ ")\n"
916
+ ]
917
+ }
918
+ ],
919
+ "metadata": {
920
+ "kernelspec": {
921
+ "display_name": "colo2",
922
+ "language": "python",
923
+ "name": "colo2"
924
+ },
925
+ "language_info": {
926
+ "codemirror_mode": {
927
+ "name": "ipython",
928
+ "version": 3
929
+ },
930
+ "file_extension": ".py",
931
+ "mimetype": "text/x-python",
932
+ "name": "python",
933
+ "nbconvert_exporter": "python",
934
+ "pygments_lexer": "ipython3",
935
+ "version": "3.9.18"
936
+ }
937
+ },
938
+ "nbformat": 4,
939
+ "nbformat_minor": 5
940
+ }
diagnostics/2026-10-30-lab-hyperparameter-grid/README.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Lab hyperparameter grid on BRCA (2026-10-30)
2
+
3
+ These runs test the hyperparameter grid Jubair Rifat suggested on 2026-10-17 (lr 0.01, L2 0.01, dropout 0.3, 200 epochs, SGD with momentum 0.03 or Adam, layer_one 64/128, batch 16/32, activations relu/sigmoid/tanh, single-cell GenePT file). The Workbench's own `train()` was used unchanged.
4
+
5
+ ## Result
6
+
7
+ - Of 216 configurations, 185 collapsed to a single class.
8
+ - All 108 SGD configurations failed (106 collapsed), and none with a sigmoid activation worked.
9
+ - The 5 best configurations were re-run on 8 seeds. Configuration **D** (Adam, batch 32, projection 64, tanh/relu/tanh) was the most stable, with macro F1 0.749 ± 0.018 and no collapses. It is now the Workbench default.
10
+ - `num_ex_neuron` (extra hidden neurons) isn't supported by the Workbench and was fixed at 0.
11
+ - Only BRCA has been tested.
12
+
13
+ ## Files
14
+
15
+ - `scripts/build_workspaces.py <cache_dir>`: builds the BRCA workspace with the bulk and single-cell GenePT files.
16
+ - `scripts/grid.py <workspace.pkl> <out.jsonl> <shard> <n_shards>`: runs the grid; it is resumable.
17
+ - `scripts/grid_summary.py <grid.jsonl>`: summarises a grid run.
18
+ - `scripts/seed_check.py <workspace.pkl> <out.jsonl> <shard> <n_shards>`: runs the 8-seed check of the top configurations against the 2026-10-16 baseline.
19
+ - `results/grid_singlecell_seed42.jsonl` and `results/grid_summary.txt`: grid results, seed 42.
20
+ - `results/seed_check_singlecell.jsonl` and `results/seed_check_summary.txt`: 8-seed results.
21
+
22
+ Run the scripts from the repository root with `PYTHONPATH=$PWD .venv/bin/python ...`.
diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard0.log ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [0] 0 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
2
+ [0] 4 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
3
+ [0] 8 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
4
+ [0] 12 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
5
+ [0] 16 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
6
+ [0] 20 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
7
+ [0] 24 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
8
+ [0] 28 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
9
+ [0] 32 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
10
+ [0] 36 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
11
+ [0] 40 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
12
+ [0] 44 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
13
+ [0] 48 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
14
+ [0] 52 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
15
+ [0] 56 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
16
+ [0] 60 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
17
+ [0] 64 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
18
+ [0] 68 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
19
+ [0] 72 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
20
+ [0] 76 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
21
+ [0] 80 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
22
+ [0] 84 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
23
+ [0] 88 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
24
+ [0] 92 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
25
+ [0] 96 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
26
+ [0] 100 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
27
+ [0] 104 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
28
+ [0] 108 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
29
+ [0] 112 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
30
+ [0] 116 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
31
+ [0] 120 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
32
+ [0] 124 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
33
+ [0] 128 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
34
+ [0] 132 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
35
+ [0] 136 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
36
+ [0] 140 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
37
+ [0] 144 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
38
+ [0] 148 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
39
+ [0] 152 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
40
+ [0] 156 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
41
+ [0] 160 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
42
+ [0] 164 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
43
+ [0] 168 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
44
+ [0] 172 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
45
+ [0] 176 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
46
+ [0] 180 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
47
+ [0] 184 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
48
+ [0] 188 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
49
+ [0] 192 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
50
+ [0] 196 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
51
+ [0] 200 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
52
+ [0] 204 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
53
+ [0] 208 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
54
+ [0] 212 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
55
+ SHARD 0 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard1.log ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [1] 1 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.403 classes=4
2
+ [1] 5 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
3
+ [1] 9 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.442 classes=3
4
+ [1] 13 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
5
+ [1] 17 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
6
+ [1] 21 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
7
+ [1] 25 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.690 classes=5
8
+ [1] 29 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
9
+ [1] 33 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.657 classes=5
10
+ [1] 37 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
11
+ [1] 41 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
12
+ [1] 45 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
13
+ [1] 49 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
14
+ [1] 53 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
15
+ [1] 57 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
16
+ [1] 61 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
17
+ [1] 65 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
18
+ [1] 69 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
19
+ [1] 73 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.723 classes=5
20
+ [1] 77 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.545 classes=4
21
+ [1] 81 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.741 classes=5
22
+ [1] 85 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
23
+ [1] 89 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
24
+ [1] 93 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
25
+ [1] 97 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.733 classes=5
26
+ [1] 101 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.411 classes=3
27
+ [1] 105 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.752 classes=5
28
+ [1] 109 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
29
+ [1] 113 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
30
+ [1] 117 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.789 classes=5
31
+ [1] 121 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
32
+ [1] 125 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
33
+ [1] 129 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
34
+ [1] 133 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
35
+ [1] 137 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
36
+ [1] 141 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.701 classes=5
37
+ [1] 145 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
38
+ [1] 149 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
39
+ [1] 153 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
40
+ [1] 157 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
41
+ [1] 161 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
42
+ [1] 165 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
43
+ [1] 169 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
44
+ [1] 173 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
45
+ [1] 177 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
46
+ [1] 181 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.728 classes=5
47
+ [1] 185 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
48
+ [1] 189 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.727 classes=5
49
+ [1] 193 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
50
+ [1] 197 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
51
+ [1] 201 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
52
+ [1] 205 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.712 classes=5
53
+ [1] 209 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.400 classes=3
54
+ [1] 213 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 16, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.733 classes=5
55
+ SHARD 1 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard2.log ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [2] 2 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
2
+ [2] 6 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
3
+ [2] 10 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
4
+ [2] 14 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
5
+ [2] 18 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
6
+ [2] 22 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
7
+ [2] 26 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
8
+ [2] 30 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
9
+ [2] 34 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
10
+ [2] 38 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
11
+ [2] 42 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
12
+ [2] 46 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
13
+ [2] 50 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
14
+ [2] 54 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
15
+ [2] 58 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
16
+ [2] 62 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
17
+ [2] 66 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
18
+ [2] 70 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
19
+ [2] 74 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
20
+ [2] 78 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
21
+ [2] 82 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
22
+ [2] 86 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
23
+ [2] 90 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
24
+ [2] 94 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
25
+ [2] 98 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
26
+ [2] 102 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
27
+ [2] 106 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.047 classes=1
28
+ [2] 110 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
29
+ [2] 114 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
30
+ [2] 118 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
31
+ [2] 122 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
32
+ [2] 126 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
33
+ [2] 130 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
34
+ [2] 134 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
35
+ [2] 138 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
36
+ [2] 142 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
37
+ [2] 146 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
38
+ [2] 150 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
39
+ [2] 154 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
40
+ [2] 158 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
41
+ [2] 162 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
42
+ [2] 166 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
43
+ [2] 170 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
44
+ [2] 174 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
45
+ [2] 178 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
46
+ [2] 182 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=2
47
+ [2] 186 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
48
+ [2] 190 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
49
+ [2] 194 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
50
+ [2] 198 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
51
+ [2] 202 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
52
+ [2] 206 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=2
53
+ [2] 210 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.133 classes=1
54
+ [2] 214 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'SGD', 'seed': 42} -> f1m=0.058 classes=1
55
+ SHARD 2 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_shard3.log ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [3] 3 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
2
+ [3] 7 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
3
+ [3] 11 {'projection_dim': 64, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.737 classes=5
4
+ [3] 15 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
5
+ [3] 19 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.052 classes=1
6
+ [3] 23 {'projection_dim': 64, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
7
+ [3] 27 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
8
+ [3] 31 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
9
+ [3] 35 {'projection_dim': 64, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.713 classes=5
10
+ [3] 39 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.052 classes=1
11
+ [3] 43 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
12
+ [3] 47 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
13
+ [3] 51 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
14
+ [3] 55 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
15
+ [3] 59 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.052 classes=1
16
+ [3] 63 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
17
+ [3] 67 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
18
+ [3] 71 {'projection_dim': 64, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
19
+ [3] 75 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
20
+ [3] 79 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
21
+ [3] 83 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.778 classes=5
22
+ [3] 87 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
23
+ [3] 91 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.052 classes=1
24
+ [3] 95 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
25
+ [3] 99 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.789 classes=5
26
+ [3] 103 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
27
+ [3] 107 {'projection_dim': 64, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.757 classes=5
28
+ [3] 111 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
29
+ [3] 115 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.047 classes=1
30
+ [3] 119 {'projection_dim': 128, 'act1': 'relu', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.756 classes=5
31
+ [3] 123 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
32
+ [3] 127 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
33
+ [3] 131 {'projection_dim': 128, 'act1': 'relu', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
34
+ [3] 135 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
35
+ [3] 139 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.047 classes=1
36
+ [3] 143 {'projection_dim': 128, 'act1': 'relu', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.691 classes=5
37
+ [3] 147 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
38
+ [3] 151 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
39
+ [3] 155 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.052 classes=1
40
+ [3] 159 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
41
+ [3] 163 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
42
+ [3] 167 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.058 classes=1
43
+ [3] 171 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
44
+ [3] 175 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.058 classes=1
45
+ [3] 179 {'projection_dim': 128, 'act1': 'sigmoid', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
46
+ [3] 183 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.789 classes=5
47
+ [3] 187 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.047 classes=1
48
+ [3] 191 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'relu', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.773 classes=5
49
+ [3] 195 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
50
+ [3] 199 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.133 classes=1
51
+ [3] 203 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'sigmoid', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.058 classes=1
52
+ [3] 207 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'relu', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.725 classes=5
53
+ [3] 211 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'sigmoid', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.444 classes=3
54
+ [3] 215 {'projection_dim': 128, 'act1': 'tanh', 'act2': 'tanh', 'act3': 'tanh', 'batch_size': 32, 'optimizer': 'Adam', 'seed': 42} -> f1m=0.696 classes=5
55
+ SHARD 3 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_singlecell_seed42.jsonl ADDED
The diff for this file is too large to render. See raw diff
 
diagnostics/2026-10-30-lab-hyperparameter-grid/results/grid_summary.txt ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ configs: 216 collapsed (1 class): 185 all 5 classes predicted: 23 macro F1 >= 0.70: 19
2
+
3
+ TOP 20 by macro F1
4
+ optimizer batch_size projection_dim acts acc f1_macro f1_w min_recall best epochs pred
5
+ Adam 32 64 tanh/tanh/relu 0.795 0.789 0.803 0.622 47 59 52/29/13/93/32
6
+ Adam 16 128 relu/relu/tanh 0.781 0.789 0.790 0.676 42 54 48/30/12/90/39
7
+ Adam 32 128 tanh/relu/relu 0.799 0.789 0.804 0.568 44 56 52/30/11/97/29
8
+ Adam 32 64 tanh/relu/tanh 0.776 0.778 0.785 0.649 56 68 48/31/10/93/37
9
+ Adam 32 128 tanh/relu/tanh 0.781 0.773 0.793 0.730 26 38 49/28/14/88/40
10
+ Adam 32 64 tanh/tanh/tanh 0.776 0.757 0.789 0.703 28 40 51/27/17/88/36
11
+ Adam 32 128 relu/relu/tanh 0.781 0.756 0.786 0.568 24 36 43/29/10/104/33
12
+ Adam 16 64 tanh/tanh/tanh 0.763 0.752 0.768 0.486 8 20 51/29/11/101/27
13
+ Adam 16 64 tanh/relu/tanh 0.758 0.741 0.768 0.595 8 20 50/27/13/96/33
14
+ Adam 32 64 relu/relu/tanh 0.763 0.737 0.773 0.622 24 36 48/29/9/96/37
15
+ Adam 16 64 tanh/tanh/relu 0.749 0.733 0.753 0.459 10 22 50/29/15/100/25
16
+ Adam 16 128 tanh/tanh/tanh 0.753 0.733 0.759 0.541 24 36 44/31/13/100/31
17
+ Adam 16 128 tanh/relu/relu 0.763 0.728 0.760 0.378 13 25 49/29/16/108/17
18
+ Adam 16 128 tanh/relu/tanh 0.767 0.727 0.770 0.486 24 36 52/32/10/104/21
19
+ Adam 32 128 tanh/tanh/relu 0.753 0.725 0.763 0.545 19 31 55/33/9/79/43
20
+ Adam 16 64 tanh/relu/relu 0.749 0.723 0.757 0.568 22 34 44/27/18/98/32
21
+ Adam 32 64 relu/tanh/tanh 0.744 0.713 0.754 0.621 24 36 40/26/12/101/40
22
+ Adam 16 128 tanh/tanh/relu 0.740 0.712 0.737 0.351 11 23 50/30/16/103/20
23
+ Adam 16 128 relu/tanh/tanh 0.731 0.701 0.734 0.486 38 50 38/29/11/110/31
24
+ Adam 32 128 tanh/tanh/tanh 0.726 0.696 0.725 0.351 10 22 53/29/17/100/20
25
+
26
+ BY optimizer
27
+ n f1_mean f1_max collapsed working
28
+ optimizer
29
+ Adam 108 0.270 0.789 79 19
30
+ SGD 108 0.112 0.133 106 0
31
+
32
+ BY batch_size
33
+ n f1_mean f1_max collapsed working
34
+ batch_size
35
+ 16 108 0.211 0.789 91 10
36
+ 32 108 0.171 0.789 94 9
37
+
38
+ BY projection_dim
39
+ n f1_mean f1_max collapsed working
40
+ projection_dim
41
+ 64 108 0.189 0.789 93 9
42
+ 128 108 0.192 0.789 92 10
43
+
44
+ BY act1
45
+ n f1_mean f1_max collapsed working
46
+ act1
47
+ relu 72 0.190 0.789 62 5
48
+ sigmoid 72 0.121 0.133 72 0
49
+ tanh 72 0.262 0.789 51 14
50
+
51
+ BY act2
52
+ n f1_mean f1_max collapsed working
53
+ act2
54
+ relu 72 0.216 0.789 58 10
55
+ sigmoid 72 0.118 0.133 72 0
56
+ tanh 72 0.238 0.789 55 9
57
+
58
+ BY act3
59
+ n f1_mean f1_max collapsed working
60
+ act3
61
+ relu 72 0.186 0.789 61 7
62
+ sigmoid 72 0.144 0.545 68 0
63
+ tanh 72 0.242 0.789 56 12
diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_check_singlecell.jsonl ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 3, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 3, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.684931506849315, "f1_macro": 0.6444813586786613, "f1_weighted": 0.6749256063279981, "epochs_completed": 19, "best_epoch": 7, "pred_counts": [44, 28, 6, 122, 19], "collapsed": false, "recall": [0.6896551724137931, 0.8181818181818182, 0.5454545454545454, 0.8073394495412844, 0.24324324324324326]}
2
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 1, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 1, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.726027397260274, "f1_macro": 0.7144281742354031, "f1_weighted": 0.7332409934786565, "epochs_completed": 25, "best_epoch": 13, "pred_counts": [54, 32, 9, 86, 38], "collapsed": false, "recall": [0.9310344827586207, 0.8787878787878788, 0.6363636363636364, 0.6880733944954128, 0.5675675675675675]}
3
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 2, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7397260273972602, "f1_macro": 0.7138206206911931, "f1_weighted": 0.7466811668840856, "epochs_completed": 28, "best_epoch": 16, "pred_counts": [54, 26, 20, 93, 26], "collapsed": false, "recall": [0.896551724137931, 0.7878787878787878, 1.0, 0.7522935779816514, 0.4594594594594595]}
4
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 0, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 0, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7488584474885844, "f1_macro": 0.7236842897667166, "f1_weighted": 0.7535285657158763, "epochs_completed": 40, "best_epoch": 28, "pred_counts": [48, 28, 9, 105, 29], "collapsed": false, "recall": [0.8275862068965517, 0.8181818181818182, 0.6363636363636364, 0.7889908256880734, 0.5405405405405406]}
5
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 2024, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2024, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.7541439764590951, "f1_weighted": 0.7898422552519037, "epochs_completed": 43, "best_epoch": 31, "pred_counts": [49, 29, 12, 97, 32], "collapsed": false, "recall": [0.8275862068965517, 0.8181818181818182, 0.8181818181818182, 0.8165137614678899, 0.5945945945945946]}
6
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 123, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 123, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7534246575342466, "f1_macro": 0.7359501479955786, "f1_weighted": 0.7626088247020677, "epochs_completed": 49, "best_epoch": 37, "pred_counts": [51, 29, 12, 90, 37], "collapsed": false, "recall": [0.896551724137931, 0.8787878787878788, 0.7272727272727273, 0.7339449541284404, 0.5945945945945946]}
7
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 7, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 7, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7671232876712328, "f1_macro": 0.7406338069110205, "f1_weighted": 0.7784375791314946, "epochs_completed": 45, "best_epoch": 33, "pred_counts": [50, 24, 14, 96, 35], "collapsed": false, "recall": [0.8275862068965517, 0.7272727272727273, 0.8181818181818182, 0.7889908256880734, 0.6756756756756757]}
8
+ {"config": "A b32 p64 tanh/tanh/relu", "seed": 42, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 42, "biological_activation": "tanh", "projection_activation": "tanh", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7945205479452054, "f1_macro": 0.78874693921005, "f1_weighted": 0.8026041446389447, "epochs_completed": 59, "best_epoch": 47, "pred_counts": [52, 29, 13, 93, 32], "collapsed": false, "recall": [0.9310344827586207, 0.8787878787878788, 0.9090909090909091, 0.7798165137614679, 0.6216216216216216]}
9
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 3, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 3, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7168949771689498, "f1_macro": 0.6634952144629563, "f1_weighted": 0.7175294877460144, "epochs_completed": 48, "best_epoch": 36, "pred_counts": [49, 30, 4, 108, 28], "collapsed": false, "recall": [0.7931034482758621, 0.8181818181818182, 0.36363636363636365, 0.7798165137614679, 0.4864864864864865]}
10
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 2, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.730593607305936, "f1_macro": 0.6682730017572305, "f1_weighted": 0.7194362526214491, "epochs_completed": 38, "best_epoch": 26, "pred_counts": [54, 27, 14, 110, 14], "collapsed": false, "recall": [0.896551724137931, 0.8181818181818182, 0.7272727272727273, 0.8256880733944955, 0.24324324324324326]}
11
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 0, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 0, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7488584474885844, "f1_macro": 0.7283944490396104, "f1_weighted": 0.7570745621268528, "epochs_completed": 39, "best_epoch": 27, "pred_counts": [52, 29, 9, 101, 28], "collapsed": false, "recall": [0.8275862068965517, 0.8484848484848485, 0.6363636363636364, 0.7706422018348624, 0.5675675675675675]}
12
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 1, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 1, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7625570776255708, "f1_macro": 0.7017018823470437, "f1_weighted": 0.7589129909059207, "epochs_completed": 49, "best_epoch": 37, "pred_counts": [41, 29, 15, 116, 18], "collapsed": false, "recall": [0.7241379310344828, 0.8484848484848485, 0.7272727272727273, 0.8715596330275229, 0.40540540540540543]}
13
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 2024, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2024, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7534246575342466, "f1_macro": 0.7013460186286767, "f1_weighted": 0.7603570398639725, "epochs_completed": 35, "best_epoch": 23, "pred_counts": [45, 29, 12, 99, 34], "collapsed": false, "recall": [0.7931034482758621, 0.7878787878787878, 0.6363636363636364, 0.8073394495412844, 0.5675675675675675]}
14
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 7, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 7, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.751011131155479, "f1_weighted": 0.7887772320889953, "epochs_completed": 29, "best_epoch": 17, "pred_counts": [50, 28, 8, 105, 28], "collapsed": false, "recall": [0.8275862068965517, 0.7878787878787878, 0.6363636363636364, 0.8348623853211009, 0.6216216216216216]}
15
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 123, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 123, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7534246575342466, "f1_macro": 0.7071455263854697, "f1_weighted": 0.7550850295543412, "epochs_completed": 34, "best_epoch": 22, "pred_counts": [52, 31, 12, 100, 24], "collapsed": false, "recall": [0.896551724137931, 0.9090909090909091, 0.6363636363636364, 0.7889908256880734, 0.43243243243243246]}
16
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 2, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.4977168949771689, "f1_macro": 0.1329268292682927, "f1_weighted": 0.3307996436128745, "epochs_completed": 23, "best_epoch": 11, "pred_counts": [0, 0, 0, 219, 0], "collapsed": true, "recall": [0.0, 0.0, 0.0, 1.0, 0.0]}
17
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 3, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 3, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7397260273972602, "f1_macro": 0.7148184407361271, "f1_weighted": 0.7493292901128092, "epochs_completed": 42, "best_epoch": 30, "pred_counts": [48, 29, 7, 94, 41], "collapsed": false, "recall": [0.7931034482758621, 0.8181818181818182, 0.5454545454545454, 0.7431192660550459, 0.6756756756756757]}
18
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 0, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 0, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.776255707762557, "f1_macro": 0.7578915715890517, "f1_weighted": 0.7873287183808084, "epochs_completed": 58, "best_epoch": 46, "pred_counts": [54, 29, 9, 91, 36], "collapsed": false, "recall": [0.896551724137931, 0.8484848484848485, 0.6363636363636364, 0.7522935779816514, 0.7297297297297297]}
19
+ {"config": "B b16 p128 relu/relu/tanh", "seed": 42, "hyperparameters": {"epochs": 200, "batch_size": 16, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 42, "biological_activation": "relu", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.788652381544761, "f1_weighted": 0.7895010559630641, "epochs_completed": 54, "best_epoch": 42, "pred_counts": [48, 30, 12, 90, 39], "collapsed": false, "recall": [0.8620689655172413, 0.9090909090909091, 0.9090909090909091, 0.7431192660550459, 0.6756756756756757]}
20
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 123, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 123, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7853881278538812, "f1_macro": 0.7597321666231736, "f1_weighted": 0.7932886529725107, "epochs_completed": 51, "best_epoch": 39, "pred_counts": [48, 29, 15, 97, 30], "collapsed": false, "recall": [0.8620689655172413, 0.8787878787878788, 0.8181818181818182, 0.7889908256880734, 0.6216216216216216]}
21
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 2024, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2024, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7945205479452054, "f1_macro": 0.7775926070493133, "f1_weighted": 0.8037755930922895, "epochs_completed": 46, "best_epoch": 34, "pred_counts": [45, 29, 10, 100, 35], "collapsed": false, "recall": [0.7586206896551724, 0.8181818181818182, 0.8181818181818182, 0.8348623853211009, 0.6756756756756757]}
22
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 7, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 7, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7168949771689498, "f1_macro": 0.6666606642657062, "f1_weighted": 0.7075349317809315, "epochs_completed": 65, "best_epoch": 53, "pred_counts": [56, 27, 9, 115, 12], "collapsed": false, "recall": [0.8620689655172413, 0.7575757575757576, 0.6363636363636364, 0.8256880733944955, 0.2702702702702703]}
23
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 1, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 1, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.8127853881278538, "f1_macro": 0.8004969316657136, "f1_weighted": 0.8177554227696595, "epochs_completed": 65, "best_epoch": 53, "pred_counts": [50, 31, 9, 98, 31], "collapsed": false, "recall": [0.9655172413793104, 0.9090909090909091, 0.7272727272727273, 0.8073394495412844, 0.6486486486486487]}
24
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 2, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7899543378995434, "f1_macro": 0.7647661601172038, "f1_weighted": 0.7982709673173486, "epochs_completed": 58, "best_epoch": 46, "pred_counts": [51, 29, 16, 95, 28], "collapsed": false, "recall": [0.896551724137931, 0.8484848484848485, 0.9090909090909091, 0.7981651376146789, 0.5945945945945946]}
25
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 3, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 3, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7579908675799086, "f1_macro": 0.7450983463384329, "f1_weighted": 0.7676536929640603, "epochs_completed": 59, "best_epoch": 47, "pred_counts": [44, 27, 7, 100, 41], "collapsed": false, "recall": [0.7241379310344828, 0.7878787878787878, 0.6363636363636364, 0.7798165137614679, 0.7297297297297297]}
26
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 0, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 0, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7579908675799086, "f1_macro": 0.7305752281147861, "f1_weighted": 0.7672308198023153, "epochs_completed": 40, "best_epoch": 28, "pred_counts": [46, 28, 10, 98, 37], "collapsed": false, "recall": [0.7586206896551724, 0.8181818181818182, 0.6363636363636364, 0.7706422018348624, 0.7027027027027027]}
27
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 2024, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2024, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7534246575342466, "f1_macro": 0.738332484131178, "f1_weighted": 0.7656099462973935, "epochs_completed": 42, "best_epoch": 30, "pred_counts": [52, 28, 10, 93, 36], "collapsed": false, "recall": [0.8275862068965517, 0.8181818181818182, 0.7272727272727273, 0.7614678899082569, 0.6216216216216216]}
28
+ {"config": "C b32 p128 tanh/relu/relu", "seed": 42, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 42, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "relu", "sgd_momentum": 0.0}, "accuracy": 0.7990867579908676, "f1_macro": 0.7885889251585044, "f1_weighted": 0.8040701819868671, "epochs_completed": 56, "best_epoch": 44, "pred_counts": [52, 30, 11, 97, 29], "collapsed": false, "recall": [0.9655172413793104, 0.9090909090909091, 0.8181818181818182, 0.7981651376146789, 0.5675675675675675]}
29
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 123, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 123, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.7359225592888959, "f1_weighted": 0.791296097847027, "epochs_completed": 55, "best_epoch": 43, "pred_counts": [55, 33, 10, 93, 28], "collapsed": false, "recall": [0.896551724137931, 0.9090909090909091, 0.5454545454545454, 0.7798165137614679, 0.6486486486486487]}
30
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 7, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 7, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7534246575342466, "f1_macro": 0.7357654892283638, "f1_weighted": 0.765273263266187, "epochs_completed": 44, "best_epoch": 32, "pred_counts": [50, 24, 13, 96, 36], "collapsed": false, "recall": [0.7931034482758621, 0.7272727272727273, 0.8181818181818182, 0.7706422018348624, 0.6756756756756757]}
31
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 2, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.7595667299177735, "f1_weighted": 0.7904695167211089, "epochs_completed": 32, "best_epoch": 20, "pred_counts": [51, 29, 13, 95, 31], "collapsed": false, "recall": [0.8620689655172413, 0.8484848484848485, 0.8181818181818182, 0.7889908256880734, 0.6216216216216216]}
32
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 3, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 3, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7123287671232876, "f1_macro": 0.7019184281569603, "f1_weighted": 0.7172640652092707, "epochs_completed": 47, "best_epoch": 35, "pred_counts": [48, 27, 7, 109, 28], "collapsed": false, "recall": [0.7586206896551724, 0.7878787878787878, 0.6363636363636364, 0.7706422018348624, 0.4594594594594595]}
33
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 1, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 1, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.7666271513372672, "f1_weighted": 0.7841213035584601, "epochs_completed": 46, "best_epoch": 34, "pred_counts": [53, 32, 9, 101, 24], "collapsed": false, "recall": [0.9310344827586207, 0.9090909090909091, 0.7272727272727273, 0.7981651376146789, 0.5135135135135135]}
34
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 123, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 123, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7671232876712328, "f1_macro": 0.7396749831626857, "f1_weighted": 0.7699385457307235, "epochs_completed": 34, "best_epoch": 22, "pred_counts": [52, 29, 14, 102, 22], "collapsed": false, "recall": [0.896551724137931, 0.8787878787878788, 0.8181818181818182, 0.7981651376146789, 0.4594594594594595]}
35
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 0, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 0, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7579908675799086, "f1_macro": 0.743470919668342, "f1_weighted": 0.7665191757209968, "epochs_completed": 57, "best_epoch": 45, "pred_counts": [48, 29, 8, 100, 34], "collapsed": false, "recall": [0.7931034482758621, 0.8484848484848485, 0.6363636363636364, 0.7706422018348624, 0.6486486486486487]}
36
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 2024, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2024, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7990867579908676, "f1_macro": 0.7616315451274192, "f1_weighted": 0.8058428826086493, "epochs_completed": 35, "best_epoch": 23, "pred_counts": [45, 29, 10, 106, 29], "collapsed": false, "recall": [0.7586206896551724, 0.8181818181818182, 0.7272727272727273, 0.8715596330275229, 0.6216216216216216]}
37
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 7, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 7, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7534246575342466, "f1_macro": 0.7446451031909788, "f1_weighted": 0.7653821355518723, "epochs_completed": 33, "best_epoch": 21, "pred_counts": [51, 25, 12, 93, 38], "collapsed": false, "recall": [0.8275862068965517, 0.7575757575757576, 0.8181818181818182, 0.7522935779816514, 0.6756756756756757]}
38
+ {"config": "D b32 p64 tanh/relu/tanh", "seed": 42, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 42, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.776255707762557, "f1_macro": 0.7776900614771901, "f1_weighted": 0.7854714654440004, "epochs_completed": 68, "best_epoch": 56, "pred_counts": [48, 31, 10, 93, 37], "collapsed": false, "recall": [0.8275862068965517, 0.9090909090909091, 0.8181818181818182, 0.7614678899082569, 0.6486486486486487]}
39
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 1, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 1, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7671232876712328, "f1_macro": 0.7387577790490057, "f1_weighted": 0.7727522475446735, "epochs_completed": 34, "best_epoch": 22, "pred_counts": [53, 34, 7, 92, 33], "collapsed": false, "recall": [0.9310344827586207, 0.9090909090909091, 0.5454545454545454, 0.7522935779816514, 0.6216216216216216]}
40
+ {"config": "E b32 p128 tanh/relu/tanh", "seed": 42, "hyperparameters": {"epochs": 200, "batch_size": 32, "learning_rate": 0.01, "weight_decay": 0.01, "dropout": 0.3, "projection_dim": 128, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 42, "biological_activation": "tanh", "projection_activation": "relu", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.7734406826426466, "f1_weighted": 0.7928328973182098, "epochs_completed": 38, "best_epoch": 26, "pred_counts": [49, 28, 14, 88, 40], "collapsed": false, "recall": [0.8275862068965517, 0.8484848484848485, 0.9090909090909091, 0.7522935779816514, 0.7297297297297297]}
41
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 0, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 0, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7625570776255708, "f1_macro": 0.7241977003359705, "f1_weighted": 0.7688921750314536, "epochs_completed": 61, "best_epoch": 49, "pred_counts": [47, 28, 8, 105, 31], "collapsed": false, "recall": [0.7931034482758621, 0.8181818181818182, 0.5454545454545454, 0.8073394495412844, 0.6216216216216216]}
42
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 2, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.8082191780821918, "f1_macro": 0.792199067264165, "f1_weighted": 0.8205970478829074, "epochs_completed": 81, "best_epoch": 69, "pred_counts": [53, 29, 14, 91, 32], "collapsed": false, "recall": [0.896551724137931, 0.8484848484848485, 0.9090909090909091, 0.7981651376146789, 0.7027027027027027]}
43
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 3, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 3, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7397260273972602, "f1_macro": 0.7190529426628272, "f1_weighted": 0.7474937309171131, "epochs_completed": 83, "best_epoch": 71, "pred_counts": [45, 28, 6, 102, 38], "collapsed": false, "recall": [0.7586206896551724, 0.8181818181818182, 0.5454545454545454, 0.7706422018348624, 0.6216216216216216]}
44
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 7, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 7, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.771689497716895, "f1_macro": 0.7524499679210638, "f1_weighted": 0.7826279144428353, "epochs_completed": 44, "best_epoch": 32, "pred_counts": [51, 28, 8, 91, 41], "collapsed": false, "recall": [0.8620689655172413, 0.7878787878787878, 0.6363636363636364, 0.7614678899082569, 0.7567567567567568]}
45
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 1, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 1, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7808219178082192, "f1_macro": 0.7513846597558472, "f1_weighted": 0.7897822474196114, "epochs_completed": 62, "best_epoch": 50, "pred_counts": [49, 30, 12, 98, 30], "collapsed": false, "recall": [0.8275862068965517, 0.8484848484848485, 0.7272727272727273, 0.7981651376146789, 0.6486486486486487]}
46
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 123, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 123, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7625570776255708, "f1_macro": 0.7396509109600558, "f1_weighted": 0.773059563038045, "epochs_completed": 49, "best_epoch": 37, "pred_counts": [53, 29, 13, 90, 34], "collapsed": false, "recall": [0.896551724137931, 0.8787878787878788, 0.7272727272727273, 0.7431192660550459, 0.6216216216216216]}
47
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 2024, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 2024, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7579908675799086, "f1_macro": 0.7372566114501599, "f1_weighted": 0.7695326473497044, "epochs_completed": 53, "best_epoch": 41, "pred_counts": [48, 29, 10, 95, 37], "collapsed": false, "recall": [0.7586206896551724, 0.8181818181818182, 0.7272727272727273, 0.7798165137614679, 0.6486486486486487]}
48
+ {"config": "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh", "seed": 42, "hyperparameters": {"epochs": 100, "batch_size": 16, "learning_rate": 0.001, "weight_decay": 0.0001, "dropout": 0.3, "projection_dim": 64, "fusion_dim": 12, "validation_fraction": 0.25, "optimizer": "Adam", "class_weighting": true, "early_stopping_patience": 12, "seed": 42, "biological_activation": "relu", "projection_activation": "sigmoid", "fusion_activation": "tanh", "sgd_momentum": 0.0}, "accuracy": 0.7671232876712328, "f1_macro": 0.7474258785478297, "f1_weighted": 0.7740214080920172, "epochs_completed": 50, "best_epoch": 38, "pred_counts": [46, 32, 16, 96, 29], "collapsed": false, "recall": [0.7586206896551724, 0.9393939393939394, 0.9090909090909091, 0.7706422018348624, 0.5675675675675675]}
diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_check_summary.txt ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ acc f1_macro f1_w f1m min/max collapsed min recall (mean) best epoch max epochs run recall c0 recall c1 recall c2 recall c3 recall c4
2
+ config
3
+ D b32 p64 tanh/relu/tanh 0.769 ± 0.015 0.749 ± 0.018 0.778 ± 0.013 0.731 / 0.778 0 0.609 39.5 68 0.83 0.84 0.73 0.78 0.64
4
+ BASE lr1e-3 wd1e-4 relu/sigmoid/tanh 0.769 ± 0.020 0.745 ± 0.022 0.778 ± 0.021 0.719 / 0.792 0 0.615 48.4 83 0.82 0.84 0.72 0.78 0.65
5
+ E b32 p128 tanh/relu/tanh 0.765 ± 0.026 0.745 ± 0.021 0.773 ± 0.027 0.702 / 0.773 0 0.594 26.8 57 0.83 0.84 0.74 0.78 0.60
6
+ A b32 p64 tanh/tanh/relu 0.749 ± 0.034 0.727 ± 0.041 0.755 ± 0.040 0.644 / 0.789 0 0.537 26.5 59 0.85 0.83 0.76 0.77 0.54
7
+ B b16 p128 relu/relu/tanh 0.753 ± 0.022 0.714 ± 0.042 0.756 ± 0.027 0.663 / 0.789 0 0.485 28.8 54 0.83 0.84 0.66 0.80 0.50
8
+ C b32 p128 tanh/relu/relu 0.740 ± 0.103 0.675 ± 0.223 0.724 ± 0.163 0.133 / 0.800 1 0.496 38.8 65 0.76 0.74 0.62 0.82 0.52
9
+
10
+ A b32 p64 tanh/tanh/relu vs BASE (same seed): mean Δ macro F1 -0.018, better on 2/8 seeds, per-seed [-0.001, -0.037, -0.078, -0.075, -0.012, 0.041, -0.004, 0.017]
11
+ B b16 p128 relu/relu/tanh vs BASE (same seed): mean Δ macro F1 -0.032, better on 2/8 seeds, per-seed [0.004, -0.05, -0.124, -0.056, -0.001, 0.041, -0.033, -0.036]
12
+ C b32 p128 tanh/relu/relu vs BASE (same seed): mean Δ macro F1 -0.071, better on 5/8 seeds, per-seed [0.034, 0.049, -0.659, -0.004, -0.086, 0.041, 0.02, 0.04]
13
+ D b32 p64 tanh/relu/tanh vs BASE (same seed): mean Δ macro F1 +0.004, better on 5/8 seeds, per-seed [0.006, 0.015, -0.027, 0.026, -0.017, 0.03, -0.004, 0.001]
14
+ E b32 p128 tanh/relu/tanh vs BASE (same seed): mean Δ macro F1 -0.000, better on 4/8 seeds, per-seed [0.019, -0.013, -0.033, -0.017, -0.008, 0.026, 0.0, 0.024]
15
+
16
+ config A b32 p64 tanh/tanh/relu B b16 p128 relu/relu/tanh BASE lr1e-3 wd1e-4 relu/sigmoid/tanh C b32 p128 tanh/relu/relu D b32 p64 tanh/relu/tanh E b32 p128 tanh/relu/tanh
17
+ seed
18
+ 0 0.724 0.728 0.724 0.758 0.731 0.743
19
+ 1 0.714 0.702 0.751 0.800 0.767 0.739
20
+ 2 0.714 0.668 0.792 0.133 0.765 0.760
21
+ 3 0.644 0.663 0.719 0.715 0.745 0.702
22
+ 7 0.741 0.751 0.752 0.667 0.736 0.745
23
+ 42 0.789 0.789 0.747 0.789 0.778 0.773
24
+ 123 0.736 0.707 0.740 0.760 0.736 0.740
25
+ 2024 0.754 0.701 0.737 0.778 0.738 0.762
diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard0.log ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [0] A b32 p64 tanh/tanh/relu seed=0 f1m=0.724 collapsed=False
2
+ [0] A b32 p64 tanh/tanh/relu seed=7 f1m=0.741 collapsed=False
3
+ [0] B b16 p128 relu/relu/tanh seed=0 f1m=0.728 collapsed=False
4
+ [0] B b16 p128 relu/relu/tanh seed=7 f1m=0.751 collapsed=False
5
+ [0] C b32 p128 tanh/relu/relu seed=0 f1m=0.758 collapsed=False
6
+ [0] C b32 p128 tanh/relu/relu seed=7 f1m=0.667 collapsed=False
7
+ [0] D b32 p64 tanh/relu/tanh seed=0 f1m=0.731 collapsed=False
8
+ [0] D b32 p64 tanh/relu/tanh seed=7 f1m=0.736 collapsed=False
9
+ [0] E b32 p128 tanh/relu/tanh seed=0 f1m=0.743 collapsed=False
10
+ [0] E b32 p128 tanh/relu/tanh seed=7 f1m=0.745 collapsed=False
11
+ [0] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=0 f1m=0.724 collapsed=False
12
+ [0] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=7 f1m=0.752 collapsed=False
13
+ SHARD 0 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard1.log ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [1] A b32 p64 tanh/tanh/relu seed=1 f1m=0.714 collapsed=False
2
+ [1] A b32 p64 tanh/tanh/relu seed=42 f1m=0.789 collapsed=False
3
+ [1] B b16 p128 relu/relu/tanh seed=1 f1m=0.702 collapsed=False
4
+ [1] B b16 p128 relu/relu/tanh seed=42 f1m=0.789 collapsed=False
5
+ [1] C b32 p128 tanh/relu/relu seed=1 f1m=0.800 collapsed=False
6
+ [1] C b32 p128 tanh/relu/relu seed=42 f1m=0.789 collapsed=False
7
+ [1] D b32 p64 tanh/relu/tanh seed=1 f1m=0.767 collapsed=False
8
+ [1] D b32 p64 tanh/relu/tanh seed=42 f1m=0.778 collapsed=False
9
+ [1] E b32 p128 tanh/relu/tanh seed=1 f1m=0.739 collapsed=False
10
+ [1] E b32 p128 tanh/relu/tanh seed=42 f1m=0.773 collapsed=False
11
+ [1] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=1 f1m=0.751 collapsed=False
12
+ [1] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=42 f1m=0.747 collapsed=False
13
+ SHARD 1 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard2.log ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [2] A b32 p64 tanh/tanh/relu seed=2 f1m=0.714 collapsed=False
2
+ [2] A b32 p64 tanh/tanh/relu seed=123 f1m=0.736 collapsed=False
3
+ [2] B b16 p128 relu/relu/tanh seed=2 f1m=0.668 collapsed=False
4
+ [2] B b16 p128 relu/relu/tanh seed=123 f1m=0.707 collapsed=False
5
+ [2] C b32 p128 tanh/relu/relu seed=2 f1m=0.133 collapsed=True
6
+ [2] C b32 p128 tanh/relu/relu seed=123 f1m=0.760 collapsed=False
7
+ [2] D b32 p64 tanh/relu/tanh seed=2 f1m=0.765 collapsed=False
8
+ [2] D b32 p64 tanh/relu/tanh seed=123 f1m=0.736 collapsed=False
9
+ [2] E b32 p128 tanh/relu/tanh seed=2 f1m=0.760 collapsed=False
10
+ [2] E b32 p128 tanh/relu/tanh seed=123 f1m=0.740 collapsed=False
11
+ [2] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=2 f1m=0.792 collapsed=False
12
+ [2] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=123 f1m=0.740 collapsed=False
13
+ SHARD 2 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/results/seed_shard3.log ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [3] A b32 p64 tanh/tanh/relu seed=3 f1m=0.644 collapsed=False
2
+ [3] A b32 p64 tanh/tanh/relu seed=2024 f1m=0.754 collapsed=False
3
+ [3] B b16 p128 relu/relu/tanh seed=3 f1m=0.663 collapsed=False
4
+ [3] B b16 p128 relu/relu/tanh seed=2024 f1m=0.701 collapsed=False
5
+ [3] C b32 p128 tanh/relu/relu seed=3 f1m=0.715 collapsed=False
6
+ [3] C b32 p128 tanh/relu/relu seed=2024 f1m=0.778 collapsed=False
7
+ [3] D b32 p64 tanh/relu/tanh seed=3 f1m=0.745 collapsed=False
8
+ [3] D b32 p64 tanh/relu/tanh seed=2024 f1m=0.738 collapsed=False
9
+ [3] E b32 p128 tanh/relu/tanh seed=3 f1m=0.702 collapsed=False
10
+ [3] E b32 p128 tanh/relu/tanh seed=2024 f1m=0.762 collapsed=False
11
+ [3] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=3 f1m=0.719 collapsed=False
12
+ [3] BASE lr1e-3 wd1e-4 relu/sigmoid/tanh seed=2024 f1m=0.737 collapsed=False
13
+ SHARD 3 DONE
diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/build_workspaces.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Build and cache the BRCA workspace with both GenePT files, using the app's own prepare step.
2
+ Usage: build_workspaces.py <cache_dir>"""
3
+ import pickle
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ import numpy as np
8
+
9
+ import app
10
+
11
+ CACHE = Path(sys.argv[1])
12
+ CACHE.mkdir(parents=True, exist_ok=True)
13
+ OPTIONS = {
14
+ "bulk": "Bulk · large-3 context",
15
+ "singlecell": "Single-cell · cell type, tissue, drug & pathway",
16
+ }
17
+ for key, option in OPTIONS.items():
18
+ result = app.prepare_workspace(
19
+ "BioLM-NET examples", "BRCA", "", None, None, None, None, None, None, None,
20
+ True, option, progress=lambda *a, **k: None,
21
+ )
22
+ workspace = result[0]
23
+ if workspace is None:
24
+ sys.exit(f"{key}: {result[1]}")
25
+ (CACHE / f"workspace_BRCA_{key}.pkl").write_bytes(pickle.dumps(workspace))
26
+ for name, branch in (("gene", workspace.gene_branch), ("dna", workspace.dna_branch)):
27
+ m = branch.biological_mask
28
+ print(f"{key:10s} {name}: inputs={m.shape[0]} hidden genes={m.shape[1]} edges={(m > 0).sum()} "
29
+ f"median in-degree={np.median((m > 0).sum(0)):.0f} pathways={len(branch.pathways)} "
30
+ f"GenePT dim={branch.embeddings.shape[1]}", flush=True)
diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/grid.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Jubair's hyperparameter grid (email of 2026-10-17) on BRCA, using the Workbench's train() unchanged.
2
+
3
+ Usage: grid.py <workspace.pkl> <out.jsonl> <shard> <n_shards> [seed]
4
+ Resumable: configs already in <out.jsonl> are skipped.
5
+
6
+ Grid as sent, mapped onto Workbench Hyperparameters:
7
+ layer_one -> projection_dim; activation_function_one/two/three -> biological/projection/fusion activation;
8
+ l2_reg -> weight_decay; epoch -> epochs (early stopping, patience 12, still applies);
9
+ SGD gets momentum 0.03 as instructed. num_ex_neuron is not supported by the Workbench (fixed at 0).
10
+ """
11
+ import itertools
12
+ import json
13
+ import sys
14
+ from dataclasses import asdict, replace
15
+ from pathlib import Path
16
+
17
+ import numpy as np
18
+ import pickle
19
+ import torch
20
+ from sklearn.metrics import recall_score
21
+
22
+ import biolmnet.training as training
23
+
24
+ WORKSPACE, OUT, SHARD, SHARDS = Path(sys.argv[1]), Path(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])
25
+ SEED = int(sys.argv[5]) if len(sys.argv) > 5 else 42
26
+ torch.set_num_threads(2)
27
+
28
+ ACT = ["relu", "sigmoid", "tanh"]
29
+ GRID = list(itertools.product([64, 128], ACT, ACT, ACT, [16, 32], ["SGD", "Adam"]))
30
+
31
+ workspace = pickle.loads(WORKSPACE.read_bytes())
32
+ names = workspace.label_names
33
+ done = set()
34
+ if OUT.exists():
35
+ done = {json.dumps(json.loads(line)["key"]) for line in OUT.read_text().splitlines() if line.strip()}
36
+
37
+ for index, (proj, a1, a2, a3, batch, opt) in enumerate(GRID):
38
+ if index % SHARDS != SHARD:
39
+ continue
40
+ key = {"projection_dim": proj, "act1": a1, "act2": a2, "act3": a3, "batch_size": batch, "optimizer": opt, "seed": SEED}
41
+ if json.dumps(key) in done:
42
+ continue
43
+ hp = replace(
44
+ training.Hyperparameters(),
45
+ epochs=200, batch_size=batch, learning_rate=0.01, weight_decay=0.01, dropout=0.3,
46
+ projection_dim=proj, optimizer=opt, sgd_momentum=0.03 if opt == "SGD" else 0.0,
47
+ biological_activation=a1, projection_activation=a2, fusion_activation=a3, seed=SEED,
48
+ )
49
+ try:
50
+ result = training.train(workspace, hp)
51
+ except Exception as exc: # record and keep going
52
+ row = {"key": key, "error": repr(exc)}
53
+ else:
54
+ m = result.bundle.metrics
55
+ vp = result.validation_predictions
56
+ counts = vp["predicted"].value_counts().reindex(names, fill_value=0).astype(int).tolist()
57
+ history = result.bundle.history
58
+ best_epoch = int(min(history, key=lambda h: h["validation_loss"])["epoch"])
59
+ row = {
60
+ "key": key, "hyperparameters": asdict(hp),
61
+ "accuracy": m["accuracy"], "f1_macro": m["f1_macro"], "f1_weighted": m["f1_weighted"],
62
+ "epochs_completed": m["epochs_completed"], "best_epoch": best_epoch,
63
+ "pred_counts": counts, "classes_predicted": int((np.array(counts) > 0).sum()),
64
+ "recall": recall_score(vp["observed"], vp["predicted"], labels=names, average=None, zero_division=0).tolist(),
65
+ "final_train_loss": history[-1]["training_loss"], "best_val_loss": m["validation_loss"],
66
+ }
67
+ with OUT.open("a") as handle:
68
+ handle.write(json.dumps(row) + "\n")
69
+ print(f"[{SHARD}] {index} {key} -> " + (row.get("error") or f"f1m={row['f1_macro']:.3f} classes={row['classes_predicted']}"), flush=True)
70
+ print(f"SHARD {SHARD} DONE", flush=True)
diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/grid_summary.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Summarise grid.py output. Usage: grid_summary.py <grid.jsonl>"""
2
+ import json
3
+ import sys
4
+
5
+ import pandas as pd
6
+
7
+ rows = []
8
+ for line in open(sys.argv[1]):
9
+ r = json.loads(line)
10
+ if "error" in r:
11
+ print("ERROR", r["key"], r["error"])
12
+ continue
13
+ rows.append({**r["key"], "acc": r["accuracy"], "f1_macro": r["f1_macro"], "f1_w": r["f1_weighted"],
14
+ "epochs": r["epochs_completed"], "best": r["best_epoch"], "classes": r["classes_predicted"],
15
+ "collapsed": r["classes_predicted"] == 1, "pred": "/".join(map(str, r["pred_counts"])),
16
+ "min_recall": min(r["recall"])})
17
+ df = pd.DataFrame(rows)
18
+ df["acts"] = df.act1 + "/" + df.act2 + "/" + df.act3
19
+ pd.set_option("display.width", 220)
20
+ pd.set_option("display.max_rows", 300)
21
+
22
+ print(f"configs: {len(df)} collapsed (1 class): {df.collapsed.sum()} "
23
+ f"all 5 classes predicted: {(df.classes == 5).sum()} macro F1 >= 0.70: {(df.f1_macro >= 0.70).sum()}")
24
+ print("\nTOP 20 by macro F1")
25
+ print(df.sort_values("f1_macro", ascending=False).head(20)[
26
+ ["optimizer", "batch_size", "projection_dim", "acts", "acc", "f1_macro", "f1_w", "min_recall", "best", "epochs", "pred"]
27
+ ].round(3).to_string(index=False))
28
+ for factor in ["optimizer", "batch_size", "projection_dim", "act1", "act2", "act3"]:
29
+ g = df.groupby(factor).agg(n=("f1_macro", "size"), f1_mean=("f1_macro", "mean"), f1_max=("f1_macro", "max"),
30
+ collapsed=("collapsed", "sum"), working=("f1_macro", lambda s: int((s >= 0.70).sum())))
31
+ print(f"\nBY {factor}\n" + g.round(3).to_string())
diagnostics/2026-10-30-lab-hyperparameter-grid/scripts/seed_check.py ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Re-run the best grid configurations (plus the 2026-10-16 stable baseline) across 8 seeds.
2
+ Usage: seed_check.py <workspace.pkl> <out.jsonl> <shard> <n_shards>
3
+ Resumable: (config, seed) pairs already in <out.jsonl> are skipped."""
4
+ import json
5
+ import pickle
6
+ import sys
7
+ from dataclasses import asdict, replace
8
+ from pathlib import Path
9
+
10
+ import numpy as np
11
+ import torch
12
+ from sklearn.metrics import recall_score
13
+
14
+ import biolmnet.training as training
15
+
16
+ WORKSPACE, OUT, SHARD, SHARDS = Path(sys.argv[1]), Path(sys.argv[2]), int(sys.argv[3]), int(sys.argv[4])
17
+ torch.set_num_threads(2)
18
+ SEEDS = [0, 1, 2, 3, 7, 42, 123, 2024]
19
+
20
+ GRID_BASE = dict(epochs=200, learning_rate=0.01, weight_decay=0.01, dropout=0.3, optimizer="Adam")
21
+ CONFIGS = {
22
+ "A b32 p64 tanh/tanh/relu": dict(GRID_BASE, batch_size=32, projection_dim=64,
23
+ biological_activation="tanh", projection_activation="tanh", fusion_activation="relu"),
24
+ "B b16 p128 relu/relu/tanh": dict(GRID_BASE, batch_size=16, projection_dim=128,
25
+ biological_activation="relu", projection_activation="relu", fusion_activation="tanh"),
26
+ "C b32 p128 tanh/relu/relu": dict(GRID_BASE, batch_size=32, projection_dim=128,
27
+ biological_activation="tanh", projection_activation="relu", fusion_activation="relu"),
28
+ "D b32 p64 tanh/relu/tanh": dict(GRID_BASE, batch_size=32, projection_dim=64,
29
+ biological_activation="tanh", projection_activation="relu", fusion_activation="tanh"),
30
+ "E b32 p128 tanh/relu/tanh": dict(GRID_BASE, batch_size=32, projection_dim=128,
31
+ biological_activation="tanh", projection_activation="relu", fusion_activation="tanh"),
32
+ # 2026-10-16 stable setting: Adam lr 1e-3, weight_decay 1e-4, published activations, 100-epoch limit.
33
+ "BASE lr1e-3 wd1e-4 relu/sigmoid/tanh": dict(epochs=100, weight_decay=1e-4),
34
+ }
35
+
36
+ workspace = pickle.loads(WORKSPACE.read_bytes())
37
+ names = workspace.label_names
38
+ done = set()
39
+ if OUT.exists():
40
+ done = {(r["config"], r["seed"]) for r in map(json.loads, OUT.read_text().splitlines()) if r}
41
+
42
+ jobs = [(c, s) for c in CONFIGS for s in SEEDS]
43
+ for index, (config, seed) in enumerate(jobs):
44
+ if index % SHARDS != SHARD or (config, seed) in done:
45
+ continue
46
+ hp = replace(training.Hyperparameters(), seed=seed, **CONFIGS[config])
47
+ result = training.train(workspace, hp)
48
+ m = result.bundle.metrics
49
+ vp = result.validation_predictions
50
+ counts = vp["predicted"].value_counts().reindex(names, fill_value=0).astype(int).tolist()
51
+ row = {
52
+ "config": config, "seed": seed, "hyperparameters": asdict(hp),
53
+ "accuracy": m["accuracy"], "f1_macro": m["f1_macro"], "f1_weighted": m["f1_weighted"],
54
+ "epochs_completed": m["epochs_completed"],
55
+ "best_epoch": int(min(result.bundle.history, key=lambda h: h["validation_loss"])["epoch"]),
56
+ "pred_counts": counts, "collapsed": int((np.array(counts) > 0).sum()) == 1,
57
+ "recall": recall_score(vp["observed"], vp["predicted"], labels=names, average=None, zero_division=0).tolist(),
58
+ }
59
+ with OUT.open("a") as handle:
60
+ handle.write(json.dumps(row) + "\n")
61
+ print(f"[{SHARD}] {config} seed={seed} f1m={row['f1_macro']:.3f} collapsed={row['collapsed']}", flush=True)
62
+ print(f"SHARD {SHARD} DONE", flush=True)
tests/test_core.py CHANGED
@@ -1,9 +1,11 @@
1
  from __future__ import annotations
2
 
3
  import pickle
 
4
 
5
  import numpy as np
6
  import pandas as pd
 
7
  import torch
8
 
9
  from biolmnet.artifacts import load_bundle, save_bundle
@@ -15,7 +17,13 @@ from biolmnet.data import (
15
  deterministic_gene_embeddings,
16
  )
17
  from biolmnet.model import BioMaskedLinear, BioLMNet
18
- from biolmnet.training import Hyperparameters, predict, train
 
 
 
 
 
 
19
 
20
 
21
  def test_biological_mask_uses_pdi_and_undirected_ppi() -> None:
@@ -232,3 +240,217 @@ def test_zerogpu_duration_estimator_is_bounded_and_scales() -> None:
232
  long = estimate_training_duration(common[0], 200, *common[1:])
233
  assert 30 <= short <= 300
234
  assert short <= long <= 300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from __future__ import annotations
2
 
3
  import pickle
4
+ from dataclasses import replace
5
 
6
  import numpy as np
7
  import pandas as pd
8
+ import pytest
9
  import torch
10
 
11
  from biolmnet.artifacts import load_bundle, save_bundle
 
17
  deterministic_gene_embeddings,
18
  )
19
  from biolmnet.model import BioMaskedLinear, BioLMNet
20
+ from biolmnet.training import (
21
+ DEFAULT_VALIDATION_FRACTION,
22
+ Hyperparameters,
23
+ predict,
24
+ split_train_validation,
25
+ train,
26
+ )
27
 
28
 
29
  def test_biological_mask_uses_pdi_and_undirected_ppi() -> None:
 
240
  long = estimate_training_duration(common[0], 200, *common[1:])
241
  assert 30 <= short <= 300
242
  assert short <= long <= 300
243
+
244
+
245
+ _FAST = Hyperparameters(
246
+ epochs=3,
247
+ batch_size=8,
248
+ projection_dim=4,
249
+ fusion_dim=3,
250
+ dropout=0.0,
251
+ early_stopping_patience=3,
252
+ )
253
+
254
+
255
+ def test_default_split_is_75_training_25_validation() -> None:
256
+ assert DEFAULT_VALIDATION_FRACTION == 0.25
257
+ assert Hyperparameters().validation_fraction == 0.25
258
+ labels = np.repeat(np.array([0, 1]), 50)
259
+ train_index, validation_index = split_train_validation(
260
+ labels, Hyperparameters().validation_fraction, Hyperparameters().seed
261
+ )
262
+ assert len(train_index) == 75
263
+ assert len(validation_index) == 25
264
+
265
+
266
+ def test_split_partitions_every_sample_exactly_once() -> None:
267
+ labels = np.repeat(np.array([0, 1, 2]), [40, 25, 18])
268
+ train_index, validation_index = split_train_validation(labels, 0.25, 42)
269
+ assert len(train_index) + len(validation_index) == len(labels)
270
+ assert set(train_index).isdisjoint(validation_index)
271
+ assert sorted(np.concatenate([train_index, validation_index])) == list(range(len(labels)))
272
+
273
+
274
+ def test_split_preserves_class_proportions() -> None:
275
+ labels = np.repeat(np.array([0, 1, 2]), [120, 60, 20]) # 60 / 30 / 10
276
+ train_index, validation_index = split_train_validation(labels, 0.25, 42)
277
+ expected = np.array([0.6, 0.3, 0.1])
278
+ for partition in (train_index, validation_index):
279
+ observed = np.bincount(labels[partition], minlength=3) / len(partition)
280
+ np.testing.assert_allclose(observed, expected, atol=0.02)
281
+
282
+
283
+ def test_split_is_reproducible_for_a_fixed_seed() -> None:
284
+ labels = np.repeat(np.array([0, 1]), [30, 34])
285
+ first = split_train_validation(labels, 0.25, 42)
286
+ second = split_train_validation(labels, 0.25, 42)
287
+ other_seed = split_train_validation(labels, 0.25, 7)
288
+ np.testing.assert_array_equal(first[0], second[0])
289
+ np.testing.assert_array_equal(first[1], second[1])
290
+ assert set(first[1]) != set(other_seed[1])
291
+
292
+
293
+ def test_split_rejects_partitions_too_small_to_stratify() -> None:
294
+ with pytest.raises(ValueError, match="at least two samples"):
295
+ split_train_validation(np.array([0, 0, 0, 1]), 0.25, 42)
296
+ # 4 classes x 2 samples: 25% leaves 2 validation samples for 4 classes.
297
+ with pytest.raises(ValueError, match="one sample per class"):
298
+ split_train_validation(np.repeat(np.arange(4), 2), 0.25, 42)
299
+
300
+
301
+ def test_training_uses_paired_rows_and_train_only_scaler() -> None:
302
+ workspace = _tiny_workspace()
303
+ hyperparameters = replace(_FAST, validation_fraction=0.25)
304
+ train_index, validation_index = split_train_validation(
305
+ workspace.labels, hyperparameters.validation_fraction, hyperparameters.seed
306
+ )
307
+ # Shift only the validation rows: a scaler fitted on all samples would move.
308
+ workspace.gene_expression[validation_index] += 100.0
309
+ workspace.dna_methylation[validation_index] -= 100.0
310
+
311
+ result = train(workspace, hyperparameters)
312
+ bundle = result.bundle
313
+
314
+ assert bundle.metrics["training_samples"] == 36
315
+ assert bundle.metrics["validation_samples"] == 12
316
+ np.testing.assert_array_equal(
317
+ result.validation_predictions["sample_row"].to_numpy(), validation_index
318
+ )
319
+ expected_labels = [workspace.label_names[value] for value in workspace.labels[validation_index]]
320
+ assert result.validation_predictions["observed"].tolist() == expected_labels
321
+
322
+ # Both omics scalers come from the same training rows only.
323
+ np.testing.assert_allclose(
324
+ bundle.gene_mean, workspace.gene_expression[train_index].mean(axis=0), rtol=1e-5
325
+ )
326
+ np.testing.assert_allclose(
327
+ bundle.gene_scale, workspace.gene_expression[train_index].std(axis=0), rtol=1e-5
328
+ )
329
+ np.testing.assert_allclose(
330
+ bundle.dna_mean, workspace.dna_methylation[train_index].mean(axis=0), rtol=1e-5
331
+ )
332
+ np.testing.assert_allclose(
333
+ bundle.dna_scale, workspace.dna_methylation[train_index].std(axis=0), rtol=1e-5
334
+ )
335
+
336
+ # Validation rows are transformed with that training-fitted scaler: the
337
+ # stored validation probabilities match predict(), which uses the bundle's scaler.
338
+ rescored = predict(
339
+ pd.DataFrame(workspace.gene_expression[validation_index], columns=bundle.gene_features),
340
+ pd.DataFrame(workspace.dna_methylation[validation_index], columns=bundle.dna_features),
341
+ bundle,
342
+ )
343
+ probability_columns = [f"P({label})" for label in workspace.label_names]
344
+ np.testing.assert_allclose(
345
+ result.validation_predictions[probability_columns].to_numpy(),
346
+ rescored[probability_columns].to_numpy(),
347
+ atol=1e-5,
348
+ )
349
+
350
+
351
+ def test_class_weights_use_training_labels_only(monkeypatch) -> None:
352
+ import biolmnet.training as training
353
+
354
+ captured = {}
355
+ original = training.nn.CrossEntropyLoss
356
+
357
+ def spy(*args, weight=None, **kwargs):
358
+ captured["weight"] = None if weight is None else weight.cpu().numpy()
359
+ return original(*args, weight=weight, **kwargs)
360
+
361
+ monkeypatch.setattr(training.nn, "CrossEntropyLoss", spy)
362
+ workspace = _tiny_workspace() # 24 control / 24 case: all-sample weights are [1, 1].
363
+ # Force a lopsided split so training-only weights clearly differ from all-sample ones.
364
+ train_index = np.concatenate([np.arange(0, 22), np.arange(24, 30)])
365
+ validation_index = np.setdiff1d(np.arange(48), train_index)
366
+ monkeypatch.setattr(
367
+ training, "split_train_validation", lambda *args: (train_index, validation_index)
368
+ )
369
+ train(workspace, _FAST)
370
+
371
+ counts = np.bincount(workspace.labels[train_index], minlength=2).astype(np.float32)
372
+ assert counts.tolist() == [22.0, 6.0]
373
+ np.testing.assert_allclose(captured["weight"], len(train_index) / (2 * counts), rtol=1e-6)
374
+
375
+
376
+ def test_results_page_receives_training_history_plot() -> None:
377
+ from app import refresh_results
378
+
379
+ assert refresh_results(None, {})[-1] is None
380
+
381
+ result = train(_tiny_workspace(), _FAST)
382
+ figure = refresh_results(result.bundle, {})[-1]
383
+ traces = {trace.name: trace for trace in figure.data}
384
+ assert set(traces) == {"Training", "Validation"}
385
+ history = result.bundle.history
386
+ assert list(traces["Training"].y) == [row["training_loss"] for row in history]
387
+ assert list(traces["Validation"].y) == [row["validation_loss"] for row in history]
388
+
389
+ # Artifacts saved without history still render the rest of the page.
390
+ result.bundle.history = []
391
+ assert refresh_results(result.bundle, {})[-1] is None
392
+
393
+
394
+ def test_activation_choices_reach_model_config_and_artifact(tmp_path) -> None:
395
+ hyperparameters = replace(
396
+ _FAST,
397
+ biological_activation="tanh",
398
+ projection_activation="relu",
399
+ fusion_activation="sigmoid",
400
+ )
401
+ result = train(_tiny_workspace(), hyperparameters)
402
+ branch = result.bundle.model.gene_branch
403
+ assert isinstance(branch.biological_activation, torch.nn.Tanh)
404
+ assert isinstance(branch.projection_activation, torch.nn.ReLU)
405
+ assert isinstance(result.bundle.model.fusion_activation, torch.nn.Sigmoid)
406
+ assert result.bundle.config["architecture"]["biological_activation"] == "tanh"
407
+
408
+ restored = load_bundle(save_bundle(result.bundle, tmp_path / "model.zip"))
409
+ assert isinstance(restored.model.gene_branch.biological_activation, torch.nn.Tanh)
410
+ assert isinstance(restored.model.fusion_activation, torch.nn.Sigmoid)
411
+
412
+
413
+ def test_defaults_follow_lab_grid_configuration() -> None:
414
+ defaults = Hyperparameters()
415
+ assert (defaults.optimizer, defaults.learning_rate, defaults.weight_decay) == ("Adam", 0.01, 0.01)
416
+ assert (defaults.batch_size, defaults.projection_dim, defaults.dropout, defaults.epochs) == (32, 64, 0.3, 200)
417
+ assert (defaults.biological_activation, defaults.projection_activation, defaults.fusion_activation) == (
418
+ "tanh",
419
+ "relu",
420
+ "tanh",
421
+ )
422
+ assert defaults.sgd_momentum == 0.03
423
+
424
+
425
+ def test_auto_genept_uses_lab_recommended_single_cell_file() -> None:
426
+ from app import GENEPT_OPTIONS, _resolve_embedding_file
427
+
428
+ auto = next(label for label, value in GENEPT_OPTIONS.items() if value == "auto")
429
+ for dataset in ("BRCA", "COAD", "GBM", "scTrioseq2"):
430
+ assert _resolve_embedding_file(auto, "BioLM-NET examples", dataset) == (
431
+ "embedding_associations_cell_type_tissue_drug_pathway_openai_large.parquet"
432
+ )
433
+
434
+
435
+ def test_sgd_momentum_reaches_optimizer(monkeypatch) -> None:
436
+ import biolmnet.training as training
437
+
438
+ captured = {}
439
+ original = training.torch.optim.SGD
440
+
441
+ def spy(params, **kwargs):
442
+ captured.update(kwargs)
443
+ return original(params, **kwargs)
444
+
445
+ monkeypatch.setattr(training.torch.optim, "SGD", spy)
446
+ train(_tiny_workspace(), replace(_FAST, optimizer="SGD", learning_rate=0.01, sgd_momentum=0.03))
447
+ assert captured["momentum"] == 0.03
448
+
449
+
450
+ @pytest.mark.parametrize(
451
+ "field, value",
452
+ [("biological_activation", "softmax"), ("fusion_activation", "gelu"), ("sgd_momentum", 1.0)],
453
+ )
454
+ def test_invalid_activation_or_momentum_is_rejected(field, value) -> None:
455
+ with pytest.raises(ValueError):
456
+ replace(Hyperparameters(), **{field: value}).validate()