1
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@
|
||||
| 后台管理端 | `server_admin/`(Vue3 + Element Plus)管理页面:订单查询、账号/授权管理(手动授权/撤销)、App 版本管理;构建产物由后端 `/admin/` 托管,登录页输入 token 后以 `X-Admin-Token` 头鉴权(`config.yml admin.token`) |
|
||||
| 版本管理 | 后台管理端上传 Android APK + 更新说明,APK 存服务器 `app.apkDir`(默认 `./workspace/`,与 `./data` 平级、挂载持久化)**固定文件名 `observer-latest.apk`,上传即覆盖,目录永远只保留最新一个文件**;**版本号从文件名识别**:文件须命名为 `observer-x.y.z.apk`(Flutter 打包产物即此命名,版本号取自 pubspec);客户端启动时 `GET /api/v1/app/update` 检查更新:服务器版本高于本地版本即弹更新提示(不可跳过)。**仅 Android 检查,iOS 不做版本下发**(iOS 走 App Store 自行更新)。版本记录可删除:删最新版本联动删除 APK 文件,删历史版本仅删记录 |
|
||||
| 数据训练(唯一入口) | 后台管理端「数据训练」一个菜单承载数据集全流程:**数据集卡片列表**(封面图/描述/图片数/已标注数/**训练状态徽标**),**卡片下方直接展示训练任务进度条与状态**(无独立训练页);详情页为**图片与标注一体视图**:分页(每页 20 条)逐行「原图 ‖ 标注图」对照展示;**图片入库(手动上传/AI 生成)自动触发 RF-DETR 全图扫描标注**,进度条展示在页顶;页顶另有「全量标注」按钮可手动重标全部图片(覆盖各图已有标注);点击原图/标注图弹窗放大,弹窗内 canvas 直接画框/点框删除/清空并保存——AI 自动标注结果直接作为标注,人工可修改/清理全部框;封面(上传自动转 jpg + UUID 命名)/**描述**/AI 生成图片(provider 抽象,默认 qwen-image/DashScope 付费 API);AI 标注端点与训练机 SSH 为**全局配置,直接读 `config.yml`**(`localAi` / `training.ssh` 节点,改配置需重启服务);图片落服务器 `app.datasetDir`/`datasets/<数据集名>/`,DB 存元数据 + 标注 JSON |
|
||||
| 模型训练 | 从数据集卡片「开始训练」触发:参数(数据集/imgsz/epochs/batch)、进度/日志/指标监控(每 epoch 粒度)、取消;训练通道 `training` 节点可配置 subprocess(与 Go 服务同机直接起 python)/ ssh(异机执行,SSH 凭据取 `config.yml` `training.ssh` 节点),并发度 1(GPU 独占);训练脚本 `server/training/train_server.py`(随项目迁移,2026-08-26)参数化,产物(best.tflite/best.pt/曲线)拉回服务器;训练收尾自动做 **tflite 产物自检**(输入/输出 shape 校验,原 `inspect_tflite.py` 逻辑内嵌脚本),自检失败任务置失败并带出原因;`dump_graph.py` 留作训练机人工深度调试 |
|
||||
| 模型训练 | 从数据集卡片「开始训练」一键触发(参数 imgsz/epochs/batch/device 默认走 `config.yml` `training` 节点,部署级配置):进度/日志/指标监控(每 epoch 粒度)、取消;训练通道 `training` 节点可配置 subprocess(与 Go 服务同机直接起 python)/ ssh(异机执行,SSH 凭据取 `config.yml` `training.ssh` 节点),并发度 1(GPU 独占);训练脚本 `server/training/train_server.py`(随项目迁移,2026-08-26)参数化,产物(best.tflite/best.pt/曲线)拉回服务器;训练收尾自动做 **tflite 产物自检**(输入/输出 shape 校验,原 `inspect_tflite.py` 逻辑内嵌脚本),自检失败任务置失败并带出原因;`dump_graph.py` 留作训练机人工深度调试 |
|
||||
| 模型版本与热更新 | **每数据集一个模型**:训练完成后一键「发布」(训练任务操作列)——tflite 落 `workspace/models/<数据集>/latest.tflite` + sha256/指标/类别名入 `model_version`(按数据集独立版本序列 m1.0.0 递增)。管理端**无模型管理界面**(版本记录仅支撑客户端下发)。**App 模型热更新**:`GET /api/v1/app/update` 扩展返回 `models` 目录数组,客户端独立检查,新模型下载校验替换,失败回退旧模型——模型迭代不再重打包 APK |
|
||||
| 模型目录与多模型推理 | `GET /api/v1/models`(登录态)返回全部数据集当前生效模型(数据集/版本/类别/大小/sha256/下载地址),下载 URL `/download/models/<数据集>/latest.tflite`;**App 模型管理页**用户自由下载/删除/启用模型,识别时**加载全部已启用模型并行推理 + 跨模型 NMS 合并**(按类别名),内置 assets 模型兜底 |
|
||||
| 标注 | **图片入库自动触发**:手动上传/AI 生成成功后,新增图自动调 `config.yml` `localAi` 节点配置的 AI 端点做 RF-DETR 全图扫描(`label_task` 记录进度,页顶进度条展示;**localAi 未配置 → 上传/生成接口直接报错;已有标注任务在跑(忙)→ 不报错**,当前任务成功完成后自动补标未标注图)→ 扫描结果(**重叠去重**:NMS 风格按置信度降序保留,重叠比 > `localAi.overlapThreshold` 默认 0.3 的框剔除——重叠比 = 交叠面积/两框较小面积,RF-DETR 同目标常输出一大一小两框,此判据能命中,同目标只留置信度最高者)**直接写 `dataset_image.labels_json`**(覆盖该图已有标注,即重标语义);点击弹窗放大后在 canvas 上画框/点框删除/清空/改类别 → 保存即整体覆写 `dataset_image.labels_json`(YOLO 归一化 JSON 数组,AI 与人工框同存,人工可修改/清理);`POST /admin/label-tasks` 详情页「全量标注」按钮入口(另有自动触发),可发起全量/指定图重标;自动/手动/混合并存,训练前自动整理(prepare_yolo 逻辑在服务端) |
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
@@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>视野管理端</title>
|
||||
<script type="module" crossorigin src="/admin/assets/index-jKHdJYxb.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/admin/assets/index-B0ehGsu8.css">
|
||||
<script type="module" crossorigin src="/admin/assets/index-CIe9te1H.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/admin/assets/index-BaD1qigE.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -207,15 +207,11 @@ type AdminTrainingListRes struct {
|
||||
List []*AdminTrainingItem `json:"list"`
|
||||
}
|
||||
|
||||
// AdminTrainingStartReq 发起训练(并发度 1:已有 running 任务时拒绝)
|
||||
// AdminTrainingStartReq 发起训练(并发度 1:已有 running 任务时拒绝;训练参数走 config.yml training 节点,name 空则自动生成)
|
||||
type AdminTrainingStartReq struct {
|
||||
g.Meta `path:"/trainings" method:"post" summary:"发起训练" tags:"管理端"`
|
||||
DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"`
|
||||
Name string `json:"name" v:"required|length:1,50" dc:"任务名称"`
|
||||
Imgsz int `json:"imgsz" v:"integer|min:64|max:2048" dc:"输入尺寸,默认 704"`
|
||||
Epochs int `json:"epochs" v:"integer|min:1|max:1000" dc:"训练轮数,默认 150"`
|
||||
Batch int `json:"batch" v:"integer|min:1|max:128" dc:"批大小,默认 16"`
|
||||
Device string `json:"device" v:"length:0,16" dc:"设备,默认 0"`
|
||||
DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"`
|
||||
Name string `json:"name" v:"length:0,50" dc:"任务名称,空自动生成"`
|
||||
}
|
||||
|
||||
type AdminTrainingStartRes struct {
|
||||
|
||||
@@ -317,21 +317,12 @@ func (s *trainingService) AdminStartTraining(ctx context.Context, req *dto.Admin
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
imgsz := req.Imgsz
|
||||
if imgsz <= 0 {
|
||||
imgsz = 704
|
||||
}
|
||||
epochs := req.Epochs
|
||||
if epochs <= 0 {
|
||||
epochs = 150
|
||||
}
|
||||
batch := req.Batch
|
||||
if batch <= 0 {
|
||||
batch = 16
|
||||
}
|
||||
device := req.Device
|
||||
if device == "" {
|
||||
device = "0"
|
||||
// 训练参数为部署级配置(config.yml training 节点,界面不传):device 随训练机硬件、
|
||||
// imgsz 须与 App 端推理对齐、epochs 随算力预期
|
||||
imgsz, epochs, batch, device := cfg.Imgsz, cfg.Epochs, cfg.Batch, cfg.Device
|
||||
name := req.Name
|
||||
if name == "" {
|
||||
name = fmt.Sprintf("%s 训练 %s", dataset.Name, gtime.Now().Format("01-02 15:04"))
|
||||
}
|
||||
now := gtime.Now()
|
||||
var taskId int64
|
||||
@@ -344,7 +335,7 @@ func (s *trainingService) AdminStartTraining(ctx context.Context, req *dto.Admin
|
||||
return gerror.NewCode(common.CodeTrainingRunning)
|
||||
}
|
||||
taskId, err = dao.Training.Insert(ctx, &entity.ModelTraining{
|
||||
Name: req.Name,
|
||||
Name: name,
|
||||
Status: consts.TrainingStatusRunning,
|
||||
DatasetId: dataset.Id,
|
||||
Imgsz: imgsz,
|
||||
|
||||
@@ -88,6 +88,10 @@ type TrainingConfig struct {
|
||||
DatasetDir string
|
||||
Python string
|
||||
TimeoutMins int
|
||||
Imgsz int
|
||||
Epochs int
|
||||
Batch int
|
||||
Device string
|
||||
}
|
||||
|
||||
// TrainingConfigOf 读取训练通道配置(未配置返回 ok=false)
|
||||
@@ -97,6 +101,10 @@ func TrainingConfigOf(ctx context.Context) (TrainingConfig, bool) {
|
||||
DatasetDir: g.Cfg().MustGet(ctx, "training.datasetDir", "datasets").String(),
|
||||
Python: g.Cfg().MustGet(ctx, "training.venvPython").String(),
|
||||
TimeoutMins: g.Cfg().MustGet(ctx, "training.timeoutMinutes", 600).Int(),
|
||||
Imgsz: g.Cfg().MustGet(ctx, "training.imgsz", 704).Int(),
|
||||
Epochs: g.Cfg().MustGet(ctx, "training.epochs", 150).Int(),
|
||||
Batch: g.Cfg().MustGet(ctx, "training.batch", 16).Int(),
|
||||
Device: g.Cfg().MustGet(ctx, "training.device", "0").String(),
|
||||
}
|
||||
return cfg, cfg.Workdir != "" && cfg.Python != ""
|
||||
}
|
||||
|
||||
+5
-1
@@ -40,7 +40,7 @@ imageGen:
|
||||
# 训练通道(并发度 1:GPU 独占,同时仅一个 running 任务):
|
||||
# mode=subprocess 训练机与服务器同机;mode=ssh 异机(训练脚本/数据集经 ssh 通道同步)
|
||||
training:
|
||||
mode: subprocess # subprocess | ssh
|
||||
mode: ssh # subprocess | ssh
|
||||
ssh:
|
||||
host: "192.168.3.210" # 训练机地址(mode=ssh 必填)
|
||||
user: "root"
|
||||
@@ -51,6 +51,10 @@ training:
|
||||
venvPython: /opt/pheasant_data/venv/bin/python
|
||||
datasetDir: datasets # 训练机数据集根目录(相对 workdir,yolo/<name> 为子目录)
|
||||
timeoutMinutes: 600 # 训练超时判死
|
||||
imgsz: 704 # 训练/导出分辨率(须与 App 端推理输入对齐)
|
||||
epochs: 150 # 训练轮数(patience 30 早停,设大可自动停)
|
||||
batch: 16 # 批大小(按训练机显存调整)
|
||||
device: "0" # GPU 编号(cpu 用 cpu)
|
||||
|
||||
# RF-DETR 预标注服务(管理端标注工作台):需从服务器可达,未配置时预标注接口返回「标注服务未配置」
|
||||
localAi:
|
||||
|
||||
@@ -350,8 +350,14 @@ training:
|
||||
datasetDir: datasets # 训练机数据集根目录(相对 workdir,数据集为子目录)
|
||||
concurrency: 1 # GPU 独占:同时仅一个 running,新任务排队
|
||||
timeoutMinutes: 600 # 超时判死
|
||||
imgsz: 704 # 训练/导出分辨率(须与 App 端推理输入对齐)
|
||||
epochs: 150 # 训练轮数(patience 30 早停,设大可自动停)
|
||||
batch: 16 # 批大小(按训练机显存调整)
|
||||
device: "0" # GPU 编号(cpu 用 cpu)
|
||||
```
|
||||
|
||||
- **训练参数默认走配置(2026-08-26)**:imgsz/epochs/batch/device 不随管理端请求传(界面一键开始),由 `training` 节点统一配置——device 取决于训练机硬件、imgsz 必须与端侧推理对齐、epochs 取决于算力预期,均为部署级参数;任务记录仍存各值(`model_training.imgsz/epochs/batch/device`)供列表展示
|
||||
|
||||
- service 内 `Runner` 接口:`Start(ctx, *TrainingJob) (pid, error)` / `FetchLogTail(ctx, job)` / `IsAlive(ctx, job) bool` / `Cancel(ctx, job)` / `FetchArtifacts(ctx, job, destDir)`;`subprocess` 与 `ssh` 两个实现,按 config `mode` 选择;**ssh 凭据直接读本节点 `training.ssh` 配置**(见「全局训练配置」节)
|
||||
- **训练脚本**(`server/training/train_server.py`,随项目迁移):支持 `--task-json <file>`(含 dataset/imgsz/epochs/batch/device/project 名),每 epoch 输出一行机器可读 JSON 到 `--log-file`(`{"epoch":1,"total":150,"metrics":{...}}`),结束写 `result.json`(最终指标)+ 自动打包 `artifact.zip`(best.pt + results.csv + 曲线);Go 侧解析日志行更新进度、轮询日志尾部截断 N KB 存 `model_training.log_tail`
|
||||
- **tflite 产物自检**(2026-08-26):`inspect_tflite.py` 的 flatbuffer 解析逻辑内嵌进 `train_server.py`(`check_tflite`),训练收尾定位 `best.tflite` 后自动校验并写 `result.json` 的 `tflite_check` 字段:`{"ok":bool,"reason":string,"inputs":[{"name","shape","type"}],"outputs":[...]}`;校验规则 = 输入恰 1 张且 4 维、元素总数 == imgsz²×3(兼容 NCHW/NHWC)、输出 ≥ 1 张且 batch 维 = 1;`ok=false`(如 shape 漂移、导出异常)时 Go 侧在拉产物前直接置训练失败并带出 reason,杜绝坏产物进入发布链路;`dump_graph.py` 保留作训练机人工深度调试
|
||||
|
||||
Reference in New Issue
Block a user