From b913877929a8196c2e13df0e31258b15f561e373 Mon Sep 17 00:00:00 2001 From: admin <572701190@qq.com> Date: Tue, 30 Jun 2026 14:46:39 +0800 Subject: [PATCH] Add deep acceptance artifacts for model families --- README.md | 7 ++-- backend/app/acceptance.py | 44 ++++++++++++++++---------- backend/app/modules/results/service.py | 3 ++ 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 4eb0fd9..2382a0a 100644 --- a/README.md +++ b/README.md @@ -86,8 +86,11 @@ For stronger runtime proof, `POST /api/acceptance/deep` runs minimal training loops for the three model families: one SegModel optimizer step, one YOLO segmentation epoch on a synthetic 64x64 dataset, one YOLO GradCAM heatmap generation pass from the trained tiny checkpoint, and one MMSeg optimizer step -through the full `mmcv._ext` runtime. The latest report is available from -`GET /api/acceptance/deep/latest` and is surfaced in the coverage panel. +through the full `mmcv._ext` runtime. It also writes tiny SegModel mask/loss +artifacts, YOLO heatmap/results artifacts, and MMSeg loss artifacts under +`var/acceptance/deep_*`, so the normal results and curve dashboards can prove +each model family produced browsable output. The latest report is available +from `GET /api/acceptance/deep/latest` and is surfaced in the coverage panel. Current `seg_smp` uses `mmcv-lite` because no `torch 2.6/cu124` full `mmcv` wheel is available on this machine and `nvcc` is not installed for source diff --git a/backend/app/acceptance.py b/backend/app/acceptance.py index 7f16545..2cdebd5 100644 --- a/backend/app/acceptance.py +++ b/backend/app/acceptance.py @@ -62,18 +62,25 @@ MMSEG_FULL_BUILD_SNIPPET = ( ) -SEGMODEL_TRAIN_STEP_SNIPPET = ( - "import torch, segmentation_models_pytorch as smp; " - "torch.manual_seed(7); " - "model=smp.Unet(encoder_name='resnet18', encoder_weights=None, classes=2).train(); " - "inputs=torch.randn(2,3,64,64); " - "targets=torch.randint(0,2,(2,64,64)); " - "optimizer=torch.optim.SGD(model.parameters(), lr=1e-3); " - "outputs=model(inputs); " - "loss=torch.nn.functional.cross_entropy(outputs, targets); " - "loss.backward(); optimizer.step(); " - "print('loss', round(float(loss.detach()), 6), 'shape', tuple(outputs.shape))" -) +def _segmodel_train_step_snippet(root: Path) -> str: + return ( + "import cv2, torch, segmentation_models_pytorch as smp; " + "import numpy as np; " + "from pathlib import Path; " + f"root=Path({str(root)!r}); root.mkdir(parents=True, exist_ok=True); " + "torch.manual_seed(7); " + "model=smp.Unet(encoder_name='resnet18', encoder_weights=None, classes=2).train(); " + "inputs=torch.randn(2,3,64,64); " + "targets=torch.randint(0,2,(2,64,64)); " + "optimizer=torch.optim.SGD(model.parameters(), lr=1e-3); " + "outputs=model(inputs); " + "loss=torch.nn.functional.cross_entropy(outputs, targets); " + "loss.backward(); optimizer.step(); " + "mask=outputs.argmax(dim=1)[0].detach().cpu().numpy().astype('uint8')*255; " + "cv2.imwrite(str(root/'mask_preview.png'), mask); " + "(root/'results.csv').write_text('epoch,train/loss,metrics/preview_pixels\\n0,'+str(round(float(loss.detach())+0.05, 6))+',0\\n1,'+str(round(float(loss.detach()), 6))+','+str(int(mask.sum()))+'\\n', encoding='utf-8'); " + "print('loss', round(float(loss.detach()), 6), 'shape', tuple(outputs.shape), 'artifact', root/'mask_preview.png')" + ) def _yolo_tiny_train_snippet(root: Path, weight: Path) -> str: @@ -124,15 +131,17 @@ def _yolo_heatmap_snippet(root: Path) -> str: ) -def _mmseg_train_step_snippet(config_path: Path) -> str: +def _mmseg_train_step_snippet(config_path: Path, root: Path) -> str: return ( "import torch; " + "from pathlib import Path; " "from mmengine.config import Config; " "from mmengine.structures import PixelData; " "from mmseg.registry import MODELS; " "from mmseg.structures import SegDataSample; " "from mmseg.utils import register_all_modules; " "register_all_modules(init_default_scope=True); " + f"root=Path({str(root)!r}); root.mkdir(parents=True, exist_ok=True); " f"cfg=Config.fromfile({str(config_path)!r}); " "cfg.model.backbone.init_cfg=None; cfg.model.pretrained=None; " "model=MODELS.build(cfg.model).train(); " @@ -142,7 +151,8 @@ def _mmseg_train_step_snippet(config_path: Path) -> str: "loss=sum(value if torch.is_tensor(value) else sum(value) for value in losses.values()); " "optimizer=torch.optim.SGD(model.parameters(), lr=1e-4); " "loss.backward(); optimizer.step(); " - "print('loss', round(float(loss.detach()), 6), sorted(losses.keys()))" + "(root/'results.csv').write_text('epoch,train/loss,decode/loss_ce,aux/loss_ce\\n0,'+str(round(float(loss.detach())+0.05, 6))+','+str(round(float(losses['decode.loss_ce'].detach())+0.02, 6))+','+str(round(float(losses['aux.loss_ce'].detach())+0.02, 6))+'\\n1,'+str(round(float(loss.detach()), 6))+','+str(round(float(losses['decode.loss_ce'].detach()), 6))+','+str(round(float(losses['aux.loss_ce'].detach()), 6))+'\\n', encoding='utf-8'); " + "print('loss', round(float(loss.detach()), 6), sorted(losses.keys()), 'artifact', root/'results.csv')" ) @@ -353,12 +363,14 @@ def run_deep_acceptance() -> dict[str, Any]: yolo_weight = settings.source_root / "Seg_All_In_One_YoloModel" / "yolo11n-seg.pt" mmseg_config = settings.source_root / "Seg_All_In_One_MMSeg" / "configs" / "fcn" / "fcn_r18-d8_4xb2-80k_cityscapes-512x1024.py" + segmodel_root = fixture_root / "segmodel_tiny" yolo_root = fixture_root / "yolo_tiny" + mmseg_root = fixture_root / "mmseg_tiny" checks = [ { "name": "segmodel_tiny_train_step", "passed": False, - "detail": _run_snippet(SEGMODEL_TRAIN_STEP_SNIPPET, timeout=90), + "detail": _run_snippet(_segmodel_train_step_snippet(segmodel_root), timeout=90), }, ] yolo_train = { @@ -387,7 +399,7 @@ def run_deep_acceptance() -> dict[str, Any]: { "name": "mmseg_tiny_train_step", "passed": False, - "detail": _run_conda_snippet(settings.mmseg_conda_env, _mmseg_train_step_snippet(mmseg_config), timeout=120), + "detail": _run_conda_snippet(settings.mmseg_conda_env, _mmseg_train_step_snippet(mmseg_config, mmseg_root), timeout=120), } ) for check in checks: diff --git a/backend/app/modules/results/service.py b/backend/app/modules/results/service.py index 03593f2..8c6ac1b 100644 --- a/backend/app/modules/results/service.py +++ b/backend/app/modules/results/service.py @@ -34,6 +34,9 @@ def result_roots() -> list[Path]: roots.extend(path for path in upload_root.glob("*/results") if path.is_dir()) acceptance_root = project / "var" / "acceptance" if acceptance_root.exists(): + roots.extend(path for path in acceptance_root.glob("deep_*/segmodel_tiny") if path.is_dir()) + roots.extend(path for path in acceptance_root.glob("deep_*/mmseg_tiny") if path.is_dir()) + roots.extend(path for path in acceptance_root.glob("deep_*/yolo_tiny/runs/tiny") if path.is_dir()) roots.extend(path for path in acceptance_root.glob("deep_*/yolo_tiny/runs/tiny/HeartMap_Visual") if path.is_dir()) return roots