flashstudio 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- flashstudio/__init__.py +5 -0
- flashstudio/app.py +64 -0
- flashstudio/cli.py +18 -0
- flashstudio/components/__init__.py +0 -0
- flashstudio/components/sidebar.py +45 -0
- flashstudio/components/styles.py +273 -0
- flashstudio/components/wizard.py +46 -0
- flashstudio/launcher.py +73 -0
- flashstudio/pages/__init__.py +0 -0
- flashstudio/pages/dashboard.py +168 -0
- flashstudio/pages/data.py +272 -0
- flashstudio/pages/export.py +212 -0
- flashstudio/pages/inference.py +1112 -0
- flashstudio/pages/model.py +370 -0
- flashstudio/pages/training.py +672 -0
- flashstudio/utils/__init__.py +0 -0
- flashstudio/utils/device.py +58 -0
- flashstudio-0.1.0.dist-info/METADATA +133 -0
- flashstudio-0.1.0.dist-info/RECORD +22 -0
- flashstudio-0.1.0.dist-info/WHEEL +5 -0
- flashstudio-0.1.0.dist-info/entry_points.txt +2 -0
- flashstudio-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""FlashStudio — Model Selection & Finetune Configuration Page."""
|
|
2
|
+
|
|
3
|
+
import streamlit as st
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
FLASHDET_MODELS = {
|
|
7
|
+
"FlashDet-Pico": {
|
|
8
|
+
"size": "p", "params": "~298K", "speed": "Ultra-fast",
|
|
9
|
+
"backbone": "LiteBackbone (0.5x)", "neck": "PicoNeck (64ch)",
|
|
10
|
+
"head": "E2EDualHead", "best_for": "Edge / Mobile / MCU",
|
|
11
|
+
},
|
|
12
|
+
"FlashDet-Nano": {
|
|
13
|
+
"size": "n", "params": "~790K", "speed": "Very fast",
|
|
14
|
+
"backbone": "FlashBackbone (stem=32)", "neck": "PicoNeck (96ch)",
|
|
15
|
+
"head": "E2EDualHead", "best_for": "Embedded / IoT",
|
|
16
|
+
},
|
|
17
|
+
"FlashDet-Small": {
|
|
18
|
+
"size": "s", "params": "~1.8M", "speed": "Fast",
|
|
19
|
+
"backbone": "FlashBackbone (stem=48)", "neck": "PicoNeck (128ch)",
|
|
20
|
+
"head": "E2EDualHead", "best_for": "General purpose",
|
|
21
|
+
},
|
|
22
|
+
"FlashDet-Medium": {
|
|
23
|
+
"size": "m", "params": "~3.6M", "speed": "Balanced",
|
|
24
|
+
"backbone": "FlashBackbone (stem=64)", "neck": "PicoNeck (192ch)",
|
|
25
|
+
"head": "E2EDualHead", "best_for": "High accuracy",
|
|
26
|
+
},
|
|
27
|
+
"FlashDet-Large": {
|
|
28
|
+
"size": "l", "params": "~5.8M", "speed": "Accurate",
|
|
29
|
+
"backbone": "FlashBackbone (stem=80)", "neck": "PicoNeck (256ch)",
|
|
30
|
+
"head": "E2EDualHead", "best_for": "High accuracy",
|
|
31
|
+
},
|
|
32
|
+
"FlashDet-X": {
|
|
33
|
+
"size": "x", "params": "~9.0M", "speed": "Max accuracy",
|
|
34
|
+
"backbone": "FlashBackbone (stem=96)", "neck": "PicoNeck (320ch)",
|
|
35
|
+
"head": "E2EDualHead", "best_for": "Max accuracy / Server",
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
OTHER_ARCHITECTURES = {
|
|
40
|
+
"YOLOv8": {"params": "Varies", "speed": "Fast", "best_for": "General YOLO"},
|
|
41
|
+
"YOLOv9": {"params": "Varies", "speed": "Fast", "best_for": "PGI-based detection"},
|
|
42
|
+
"YOLOv10": {"params": "Varies", "speed": "Fast", "best_for": "PSA-enhanced"},
|
|
43
|
+
"YOLOv11": {"params": "Varies", "speed": "Fast", "best_for": "C2PSA-based"},
|
|
44
|
+
"YOLOX": {"params": "Varies", "speed": "Fast", "best_for": "Anchor-free YOLO"},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
OPTIMIZERS = ["AdamW", "SGD", "MuSGD"]
|
|
48
|
+
LORA_VARIANTS = ["standard", "dora", "lora_plus", "adalora", "ortho", "lora_fa"]
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def render_model_page():
|
|
52
|
+
"""Render model selection and fine-tuning configuration."""
|
|
53
|
+
from flashstudio.components.styles import render_page_header
|
|
54
|
+
render_page_header("🧠", "Model Configuration",
|
|
55
|
+
"Choose your architecture and configure all training parameters.")
|
|
56
|
+
|
|
57
|
+
tab_arch, tab_hyper, tab_aug, tab_advanced = st.tabs([
|
|
58
|
+
"🏗️ Architecture", "⚙️ Hyperparameters", "🎨 Augmentation", "🔧 Advanced"
|
|
59
|
+
])
|
|
60
|
+
|
|
61
|
+
with tab_arch:
|
|
62
|
+
_render_architecture_tab()
|
|
63
|
+
|
|
64
|
+
with tab_hyper:
|
|
65
|
+
_render_hyperparameters_tab()
|
|
66
|
+
|
|
67
|
+
with tab_aug:
|
|
68
|
+
_render_augmentation_tab()
|
|
69
|
+
|
|
70
|
+
with tab_advanced:
|
|
71
|
+
_render_advanced_tab()
|
|
72
|
+
|
|
73
|
+
st.divider()
|
|
74
|
+
_render_config_summary()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _render_architecture_tab():
|
|
78
|
+
"""Model architecture selection."""
|
|
79
|
+
st.markdown("### Select Architecture")
|
|
80
|
+
|
|
81
|
+
arch_family = st.radio(
|
|
82
|
+
"Architecture Family",
|
|
83
|
+
["FlashDet (recommended)", "YOLOv8", "YOLOv9", "YOLOv10", "YOLOv11", "YOLOX"],
|
|
84
|
+
key="arch_family",
|
|
85
|
+
horizontal=True,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if arch_family == "FlashDet (recommended)":
|
|
89
|
+
_render_flashdet_config()
|
|
90
|
+
else:
|
|
91
|
+
_render_yolo_config(arch_family.replace(" (recommended)", ""))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _render_flashdet_config():
|
|
95
|
+
"""FlashDet-specific architecture configuration."""
|
|
96
|
+
col_select, col_info = st.columns([1, 2])
|
|
97
|
+
|
|
98
|
+
with col_select:
|
|
99
|
+
model_size = st.radio(
|
|
100
|
+
"Model Size",
|
|
101
|
+
list(FLASHDET_MODELS.keys()),
|
|
102
|
+
index=0,
|
|
103
|
+
key="model_arch",
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
info = FLASHDET_MODELS[model_size]
|
|
107
|
+
|
|
108
|
+
# Only Pico has backbone choice
|
|
109
|
+
if model_size == "FlashDet-Pico":
|
|
110
|
+
st.selectbox(
|
|
111
|
+
"Backbone",
|
|
112
|
+
["LiteBackbone (ShuffleNetV2-0.5x)", "PicoBackbone (RepNeXt-style)"],
|
|
113
|
+
key="pico_backbone",
|
|
114
|
+
help="Only FlashDet-Pico allows backbone selection",
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
st.info(f"**Backbone:** {info['backbone']} (fixed for {model_size})")
|
|
118
|
+
|
|
119
|
+
# Neck and Head are ALWAYS fixed — show as info, not selectable
|
|
120
|
+
st.info(f"**Neck:** {info['neck']} (fixed)")
|
|
121
|
+
st.info(f"**Head:** {info['head']} (fixed)")
|
|
122
|
+
|
|
123
|
+
with col_info:
|
|
124
|
+
st.markdown(f"### {model_size}")
|
|
125
|
+
|
|
126
|
+
metric_cols = st.columns(3)
|
|
127
|
+
with metric_cols[0]:
|
|
128
|
+
st.metric("Parameters", info["params"])
|
|
129
|
+
with metric_cols[1]:
|
|
130
|
+
st.metric("Speed", info["speed"])
|
|
131
|
+
with metric_cols[2]:
|
|
132
|
+
st.metric("Best For", info["best_for"])
|
|
133
|
+
|
|
134
|
+
with st.container(border=True):
|
|
135
|
+
st.markdown("**Architecture Details**")
|
|
136
|
+
st.caption(f"Backbone: {info['backbone']}")
|
|
137
|
+
st.caption(f"Neck: {info['neck']}")
|
|
138
|
+
st.caption(f"Head: {info['head']} (o2o + o2m dual-head, DFL-free)")
|
|
139
|
+
st.caption("Loss: CIoU + BCE + L1 with STAL assignment")
|
|
140
|
+
st.caption("Training: ProgLoss (progressive o2m→o2o balancing)")
|
|
141
|
+
|
|
142
|
+
_render_pretrain_and_finetune()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _render_yolo_config(arch_name):
|
|
146
|
+
"""YOLO-family architecture configuration."""
|
|
147
|
+
col_select, col_info = st.columns([1, 2])
|
|
148
|
+
|
|
149
|
+
with col_select:
|
|
150
|
+
st.markdown(f"#### {arch_name} Configuration")
|
|
151
|
+
|
|
152
|
+
st.slider("Width Multiplier", 0.25, 1.5, 0.5, step=0.25, key="yolo_width_mult",
|
|
153
|
+
help="Controls channel width (0.25=nano, 0.5=small, 1.0=medium)")
|
|
154
|
+
st.slider("Depth Multiplier", 0.33, 1.5, 0.33, step=0.33, key="yolo_depth_mult",
|
|
155
|
+
help="Controls network depth (0.33=small, 0.67=medium, 1.0=large)")
|
|
156
|
+
|
|
157
|
+
if arch_name == "YOLOv9":
|
|
158
|
+
st.checkbox("Enable PGI (Programmable Gradient Info)", value=True, key="yolo_use_pgi")
|
|
159
|
+
elif arch_name == "YOLOv10":
|
|
160
|
+
st.checkbox("Enable PSA (Partial Self-Attention)", value=True, key="yolo_use_psa")
|
|
161
|
+
elif arch_name == "YOLOv11":
|
|
162
|
+
st.checkbox("Enable C2PSA", value=True, key="yolo_use_c2psa")
|
|
163
|
+
|
|
164
|
+
with col_info:
|
|
165
|
+
yolo_info = OTHER_ARCHITECTURES[arch_name]
|
|
166
|
+
st.markdown(f"### {arch_name}")
|
|
167
|
+
st.caption(f"Best for: {yolo_info['best_for']}")
|
|
168
|
+
st.caption("Backbone, Neck, Head are fixed per architecture — not user-selectable.")
|
|
169
|
+
|
|
170
|
+
with st.container(border=True):
|
|
171
|
+
st.markdown("**Note**")
|
|
172
|
+
st.write(
|
|
173
|
+
f"The {arch_name} architecture uses its own fixed backbone, neck, and head. "
|
|
174
|
+
"You can control model capacity via width/depth multipliers."
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
_render_pretrain_and_finetune()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def _render_pretrain_and_finetune():
|
|
181
|
+
"""Shared pretrained weights and fine-tuning strategy UI."""
|
|
182
|
+
with st.container(border=True):
|
|
183
|
+
st.markdown("**Pretrained Weights**")
|
|
184
|
+
pretrain_opt = st.radio(
|
|
185
|
+
"Initialize from",
|
|
186
|
+
["COCO pretrained (recommended)", "ImageNet backbone only",
|
|
187
|
+
"Random (train from scratch)", "Custom weights"],
|
|
188
|
+
key="pretrain_option",
|
|
189
|
+
)
|
|
190
|
+
if pretrain_opt == "Custom weights":
|
|
191
|
+
st.text_input("Path to weights", placeholder="/content/model.pth", key="custom_weights")
|
|
192
|
+
|
|
193
|
+
with st.container(border=True):
|
|
194
|
+
st.markdown("**Fine-tuning Strategy**")
|
|
195
|
+
st.radio(
|
|
196
|
+
"Training mode",
|
|
197
|
+
[
|
|
198
|
+
"Full fine-tune (all layers trainable)",
|
|
199
|
+
"Freeze backbone (train neck + head only)",
|
|
200
|
+
"Freeze backbone + neck (train head only)",
|
|
201
|
+
"LoRA fine-tune (low-rank adapters)",
|
|
202
|
+
],
|
|
203
|
+
key="finetune_strategy",
|
|
204
|
+
)
|
|
205
|
+
if st.session_state.get("finetune_strategy", "").startswith("LoRA"):
|
|
206
|
+
st.selectbox("LoRA Variant", LORA_VARIANTS, key="lora_variant")
|
|
207
|
+
st.slider("LoRA Rank", 4, 64, 16, key="lora_rank")
|
|
208
|
+
st.slider("LoRA Alpha", 8, 128, 32, key="lora_alpha")
|
|
209
|
+
st.slider("LoRA Dropout", 0.0, 0.5, 0.0, key="lora_dropout")
|
|
210
|
+
st.multiselect("LoRA Targets", ["backbone", "fpn", "neck", "head"],
|
|
211
|
+
default=["backbone", "fpn"], key="lora_targets")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def _render_hyperparameters_tab():
|
|
215
|
+
"""Training hyperparameters — only options FlashDet actually supports."""
|
|
216
|
+
st.markdown("### Training Hyperparameters")
|
|
217
|
+
|
|
218
|
+
col1, col2 = st.columns(2)
|
|
219
|
+
|
|
220
|
+
with col1:
|
|
221
|
+
with st.container(border=True):
|
|
222
|
+
st.markdown("**Core Parameters**")
|
|
223
|
+
st.slider("Epochs", 1, 500, 100, key="epochs")
|
|
224
|
+
st.select_slider("Batch Size", [2, 4, 8, 16, 32, 64, 128], value=16, key="batch_size")
|
|
225
|
+
st.select_slider("Image Size", [320, 416, 640], value=320, key="img_size",
|
|
226
|
+
help="FlashDet default is 320; 416 and 640 also supported")
|
|
227
|
+
st.slider("Learning Rate", 1e-5, 1e-1, 1e-3, format="%.5f", key="lr")
|
|
228
|
+
st.slider("Weight Decay", 0.0, 0.1, 0.05, format="%.4f", key="weight_decay")
|
|
229
|
+
|
|
230
|
+
with st.container(border=True):
|
|
231
|
+
st.markdown("**Optimizer**")
|
|
232
|
+
st.selectbox("Optimizer", OPTIMIZERS, key="optimizer",
|
|
233
|
+
help="AdamW (default), SGD, or MuSGD (Muon+SGD hybrid)")
|
|
234
|
+
st.checkbox("Use 8-bit Optimizer (memory saving)", value=False, key="use_8bit_optimizer")
|
|
235
|
+
st.checkbox("Gradient Clipping (max_norm=10)", value=True, key="grad_clip")
|
|
236
|
+
|
|
237
|
+
with col2:
|
|
238
|
+
with st.container(border=True):
|
|
239
|
+
st.markdown("**LR Schedule**")
|
|
240
|
+
st.caption("Cosine decay with linear warmup (fixed scheduler)")
|
|
241
|
+
st.slider("Warmup Epochs", 0, 20, 3, key="warmup_epochs")
|
|
242
|
+
st.slider("Final LR Ratio (lrf)", 0.01, 0.5, 0.1, format="%.2f", key="lr_final_ratio",
|
|
243
|
+
help="Final LR = initial_LR × lrf")
|
|
244
|
+
|
|
245
|
+
with st.container(border=True):
|
|
246
|
+
st.markdown("**Training Options**")
|
|
247
|
+
st.checkbox("Mixed Precision (AMP FP16)", value=True, key="amp")
|
|
248
|
+
st.caption("EMA (decay=0.9998) is always enabled")
|
|
249
|
+
st.slider("DataLoader Workers", 0, 16, 4, key="num_workers")
|
|
250
|
+
st.number_input("Gradient Accumulation Steps", 1, 16, 1, key="grad_accum")
|
|
251
|
+
st.slider("Early Stopping Patience", 5, 100, 50, key="patience")
|
|
252
|
+
st.slider("Validation Interval", 1, 20, 5, key="val_interval")
|
|
253
|
+
|
|
254
|
+
with st.container(border=True):
|
|
255
|
+
st.markdown("**Multi-Scale Training**")
|
|
256
|
+
st.checkbox("Enable Multi-Scale", value=False, key="multiscale",
|
|
257
|
+
help="Randomly varies input size between 256-416 every 10 batches")
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def _render_augmentation_tab():
|
|
261
|
+
"""Data augmentation settings — only what FlashDet actually uses."""
|
|
262
|
+
st.markdown("### Data Augmentation")
|
|
263
|
+
st.caption("These are the augmentation options supported by FlashDet's training pipeline.")
|
|
264
|
+
|
|
265
|
+
col1, col2 = st.columns(2)
|
|
266
|
+
|
|
267
|
+
with col1:
|
|
268
|
+
with st.container(border=True):
|
|
269
|
+
st.markdown("**Multi-Image Augmentations** (CLI flags)")
|
|
270
|
+
st.checkbox("Mosaic (4-image composite)", value=True, key="aug_mosaic")
|
|
271
|
+
st.checkbox("MixUp (blend two images)", value=False, key="aug_mixup")
|
|
272
|
+
st.checkbox("CopyPaste (instance-level)", value=False, key="aug_copypaste")
|
|
273
|
+
|
|
274
|
+
with col2:
|
|
275
|
+
with st.container(border=True):
|
|
276
|
+
st.markdown("**Built-in Augmentations** (always applied)")
|
|
277
|
+
st.caption("These run automatically during training and are not separately configurable via CLI:")
|
|
278
|
+
st.markdown("""
|
|
279
|
+
- Random scale jitter (0.5–1.5×)
|
|
280
|
+
- Random horizontal flip
|
|
281
|
+
- Brightness / Contrast / Saturation / Hue jitter
|
|
282
|
+
- Letterbox resize (keep aspect ratio)
|
|
283
|
+
- ImageNet normalization
|
|
284
|
+
""")
|
|
285
|
+
st.info("💡 Individual geometric/color params are hardcoded in FlashDet's "
|
|
286
|
+
"`TrainTransform`. The YAML `augment:` section exists but is not "
|
|
287
|
+
"wired to the dataloader.")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def _render_advanced_tab():
|
|
291
|
+
"""Advanced training options — verified against FlashDet source."""
|
|
292
|
+
st.markdown("### Advanced Options")
|
|
293
|
+
|
|
294
|
+
col1, col2 = st.columns(2)
|
|
295
|
+
|
|
296
|
+
with col1:
|
|
297
|
+
with st.container(border=True):
|
|
298
|
+
st.markdown("**Loss & Assignment** (fixed in FlashDet)")
|
|
299
|
+
st.caption("These are NOT configurable — shown for reference only:")
|
|
300
|
+
st.markdown("""
|
|
301
|
+
- **Box Loss:** CIoU
|
|
302
|
+
- **Cls Loss:** BCE
|
|
303
|
+
- **Aux Loss:** L1 (on o2m head)
|
|
304
|
+
- **Assignment:** STAL (Small-Target-Aware Label)
|
|
305
|
+
- **ProgLoss:** Progressive o2m→o2o balancing
|
|
306
|
+
""")
|
|
307
|
+
|
|
308
|
+
with st.container(border=True):
|
|
309
|
+
st.markdown("**Memory & Performance**")
|
|
310
|
+
st.checkbox("Activation Checkpointing", value=False, key="activation_checkpointing",
|
|
311
|
+
help="Trade compute for memory — useful for large models")
|
|
312
|
+
st.checkbox("Activation Offloading", value=False, key="activation_offloading")
|
|
313
|
+
st.checkbox("Optimizer in Backward", value=False, key="optimizer_in_bwd",
|
|
314
|
+
help="Fuse optimizer step into backward pass")
|
|
315
|
+
st.checkbox("Compile Model (torch.compile)", value=False, key="compile_model")
|
|
316
|
+
|
|
317
|
+
with col2:
|
|
318
|
+
with st.container(border=True):
|
|
319
|
+
st.markdown("**Distributed Training**")
|
|
320
|
+
st.checkbox("Multi-GPU (DDP)", value=False, key="ddp")
|
|
321
|
+
if st.session_state.get("ddp"):
|
|
322
|
+
st.caption("Launch via torchrun for DDP; DataParallel as fallback")
|
|
323
|
+
|
|
324
|
+
with st.container(border=True):
|
|
325
|
+
st.markdown("**Checkpointing**")
|
|
326
|
+
st.text_input("Save Directory", value="/content/flashstudio_runs", key="save_dir")
|
|
327
|
+
st.checkbox("Save Best Only", value=True, key="save_best")
|
|
328
|
+
st.checkbox("Resume from Checkpoint", value=False, key="resume_training")
|
|
329
|
+
if st.session_state.get("resume_training"):
|
|
330
|
+
st.text_input("Checkpoint Path", placeholder="/content/runs/checkpoint_last.pth",
|
|
331
|
+
key="resume_path")
|
|
332
|
+
|
|
333
|
+
with st.container(border=True):
|
|
334
|
+
st.markdown("**Other Training Methods**")
|
|
335
|
+
st.caption("FlashDet also supports (via separate scripts):")
|
|
336
|
+
st.markdown("""
|
|
337
|
+
- **SSL Pretraining** (BYOL / MoCo / SimCLR)
|
|
338
|
+
- **Semi-supervised** (pseudo-labeling)
|
|
339
|
+
- **Few-shot** (N-shot fine-tuning)
|
|
340
|
+
- **Active Learning** (entropy/margin query)
|
|
341
|
+
""")
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _render_config_summary():
|
|
345
|
+
"""Show configuration summary."""
|
|
346
|
+
st.markdown("### 📋 Configuration Summary")
|
|
347
|
+
|
|
348
|
+
arch_family = st.session_state.get("arch_family", "FlashDet (recommended)")
|
|
349
|
+
model = st.session_state.get("model_arch", "FlashDet-Pico")
|
|
350
|
+
|
|
351
|
+
with st.container(border=True):
|
|
352
|
+
col1, col2, col3, col4 = st.columns(4)
|
|
353
|
+
with col1:
|
|
354
|
+
st.markdown(f"**Architecture:** {arch_family}")
|
|
355
|
+
st.markdown(f"**Model:** {model}")
|
|
356
|
+
st.markdown(f"**Strategy:** {st.session_state.get('finetune_strategy', 'Full fine-tune')[:25]}")
|
|
357
|
+
with col2:
|
|
358
|
+
st.markdown(f"**Epochs:** {st.session_state.get('epochs', 100)}")
|
|
359
|
+
st.markdown(f"**Batch Size:** {st.session_state.get('batch_size', 16)}")
|
|
360
|
+
st.markdown(f"**Image Size:** {st.session_state.get('img_size', 320)}")
|
|
361
|
+
with col3:
|
|
362
|
+
st.markdown(f"**LR:** {st.session_state.get('lr', 0.001)}")
|
|
363
|
+
st.markdown(f"**Optimizer:** {st.session_state.get('optimizer', 'AdamW')}")
|
|
364
|
+
st.markdown("**Scheduler:** Cosine + warmup")
|
|
365
|
+
with col4:
|
|
366
|
+
st.markdown(f"**AMP:** {'✅' if st.session_state.get('amp') else '❌'}")
|
|
367
|
+
st.markdown("**EMA:** ✅ (always on)")
|
|
368
|
+
st.markdown(f"**Mosaic:** {'✅' if st.session_state.get('aug_mosaic') else '❌'}")
|
|
369
|
+
|
|
370
|
+
st.success("Configuration ready! Click **Next** to start training.")
|