综合训练(多物种合并模型)全链路:合并打包/独立版本序列/App 覆盖互斥 + 管理端发布入口
- 后端:/admin/trainings/combined 发起(≥2 数据集、类别重映射、防重名、负样本单份); model_training/model_version 加 kind+dataset_ids(迁移 v14),综合任务 dataset_id=0、 文件基名 combined(_n)、版本序列独立;训练列表补 published 标记 - 管理端:数据训练页工具栏发起综合训练;横幅常驻进行中任务 + 每档最近一条已结束任务, 成功未发布给「发布模型」入口(可关闭收起) - App:目录解析 kind/datasetIds、激活覆盖互斥、自动更新退场改目标档待办横幅手动一键下载
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { EditPen, Loading, Picture, Plus, VideoPlay } from '@element-plus/icons-vue'
|
||||
import { EditPen, Loading, Picture, Plus, Share, VideoPlay } from '@element-plus/icons-vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import request from '../api/request'
|
||||
import FalseTargets from './FalseTargets.vue'
|
||||
@@ -267,6 +267,101 @@ function resetCoverState() {
|
||||
coverDeleted.value = false
|
||||
}
|
||||
|
||||
// ---------- 综合训练(2026-09-09):多数据集合并训练一个全类模型 ----------
|
||||
const combinedDlg = ref(false)
|
||||
const combinedIds = ref([]) // 勾选的数据集 id(≥2)
|
||||
const combinedVariants = ref(['s', 'n']) // 档位(s=高精度 n=高性能)
|
||||
const combinedSubmitting = ref(false)
|
||||
const combinedAll = ref([]) // 最近拉取的综合任务(/trainings 倒序,最多 50 条)
|
||||
const combinedDismissed = ref([]) // 已结束任务本会话关闭的 id(发布/失败回看后收起)
|
||||
// 横幅行 = 进行中任务(queued/running)+ 每档位最近一条已结束任务(成功未发布=发布入口,
|
||||
// 发布过/失败仅状态回看);只留最近一条避免横幅被历史任务堆满
|
||||
const combinedRows = computed(() => {
|
||||
const all = combinedAll.value.filter((t) => !combinedDismissed.value.includes(t.id))
|
||||
const active = all.filter((t) => t.status === 'running' || t.status === 'queued')
|
||||
const latestDone = {}
|
||||
for (const t of all) {
|
||||
if (t.status !== 'success' && t.status !== 'failed') continue
|
||||
if (!latestDone[t.variant] || t.id > latestDone[t.variant].id) latestDone[t.variant] = t
|
||||
}
|
||||
return [...active, ...Object.values(latestDone)]
|
||||
})
|
||||
|
||||
const variantLabels = { s: '高精度', n: '高性能' }
|
||||
|
||||
function openCombined() {
|
||||
if (list.value.length < 2) {
|
||||
ElMessage.warning('综合训练至少需要 2 个数据集')
|
||||
return
|
||||
}
|
||||
combinedIds.value = []
|
||||
combinedVariants.value = ['s', 'n']
|
||||
combinedDlg.value = true
|
||||
}
|
||||
|
||||
function combinedSubmit() {
|
||||
if (combinedIds.value.length < 2) {
|
||||
ElMessage.warning('请至少勾选 2 个数据集')
|
||||
return
|
||||
}
|
||||
if (!combinedVariants.value.length) {
|
||||
ElMessage.warning('请选择训练档位')
|
||||
return
|
||||
}
|
||||
combinedSubmitting.value = true
|
||||
request
|
||||
.post('/trainings/combined', {
|
||||
datasetIds: combinedIds.value,
|
||||
variants: combinedVariants.value,
|
||||
})
|
||||
.then(() => {
|
||||
ElMessage.success('综合训练已发起,进入队列')
|
||||
combinedDlg.value = false
|
||||
loadCombined()
|
||||
})
|
||||
.finally(() => {
|
||||
combinedSubmitting.value = false
|
||||
})
|
||||
}
|
||||
|
||||
function loadCombined() {
|
||||
request
|
||||
.get('/trainings', { params: { page: 1, size: 50 } })
|
||||
.then((data) => {
|
||||
combinedAll.value = (data.list || []).filter((t) => t.kind === 'combined')
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
function cancelCombined(t) {
|
||||
ElMessageBox.confirm(`终止${t.status === 'queued' ? '排队中的' : '运行中的'}综合训练(${variantLabels[t.variant]})?`, '取消综合训练', { type: 'warning' })
|
||||
.then(() => request.post('/trainings/cancel', { id: t.id }).then(() => {
|
||||
ElMessage.success('已取消')
|
||||
loadCombined()
|
||||
}))
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 发布综合模型版本:任务成功即直写 trainings/combined(_n).tflite,发布只落版本记录
|
||||
function publishCombined(t) {
|
||||
ElMessageBox.confirm(
|
||||
`确定将综合模型 ${variantLabels[t.variant]}档(训练 #${t.id}「${t.name || ''}」)结果发布为模型版本?版本号综合序列独立自增,App 按识别档位热更新下载。`,
|
||||
'发布模型版本',
|
||||
{ confirmButtonText: '发布', cancelButtonText: '取消', type: 'warning' },
|
||||
)
|
||||
.then(() => request.post('/trainings/publish', { id: t.id }))
|
||||
.then((data) => {
|
||||
ElMessage.success(`已发布版本 ${data.version}`)
|
||||
loadCombined()
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
|
||||
// 关闭横幅中已结束任务的回看行(本会话内不再展示;进行中任务不可关)
|
||||
function dismissCombined(t) {
|
||||
combinedDismissed.value.push(t.id)
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
Object.assign(dlgForm, {
|
||||
mode: 'create',
|
||||
@@ -583,8 +678,10 @@ let trainPoll = null
|
||||
onMounted(() => {
|
||||
load()
|
||||
loadFtPendingCount()
|
||||
loadCombined()
|
||||
trainPoll = setInterval(() => {
|
||||
if (list.value.some((r) => trList(r).some((t) => t.status === 'running'))) load()
|
||||
if (combinedRows.value.some((t) => t.status === 'running' || t.status === 'queued')) loadCombined()
|
||||
}, 10000)
|
||||
})
|
||||
|
||||
@@ -620,10 +717,58 @@ onBeforeUnmount(() => {
|
||||
<el-button @click="search">搜索</el-button>
|
||||
</div>
|
||||
<div class="toolbar-right">
|
||||
<el-button :icon="Share" @click="openCombined">综合训练</el-button>
|
||||
<el-button type="primary" :icon="Plus" @click="openCreate">新建数据集</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 综合训练横幅:进行中任务进度 + 每档位最近一条已结束任务(综合任务 dataset_id=0
|
||||
不归属任何数据集卡片,成功未发布在此给发布入口;失败/已发布可「关闭」收起) -->
|
||||
<div v-if="combinedRows.length" class="combined-strip">
|
||||
<div v-for="t in combinedRows" :key="t.id" class="combined-line">
|
||||
<el-tag type="warning" size="small">综合训练</el-tag>
|
||||
<span class="combined-name">{{ variantLabels[t.variant] }}档</span>
|
||||
<template v-if="t.status === 'running'">
|
||||
<el-progress
|
||||
class="combined-bar"
|
||||
:percentage="trainPercent(t)"
|
||||
:stroke-width="6"
|
||||
:show-text="false"
|
||||
/>
|
||||
<span class="combined-text">{{ t.currentEpoch || 0 }}/{{ t.totalEpochs || '-' }} 轮</span>
|
||||
<span v-if="t.etaMinutes" class="combined-text">· 剩余约 {{ etaText(t.etaMinutes) }}</span>
|
||||
</template>
|
||||
<el-tag v-else-if="t.status === 'queued'" size="small" type="info">排队中</el-tag>
|
||||
<template v-else-if="t.status === 'success'">
|
||||
<el-tag type="success" size="small">已完成</el-tag>
|
||||
<el-button
|
||||
v-if="!t.published"
|
||||
size="small"
|
||||
type="success"
|
||||
class="ds-pub"
|
||||
@click="publishCombined(t)"
|
||||
>
|
||||
发布模型
|
||||
</el-button>
|
||||
<span v-else class="combined-text">已发布</span>
|
||||
</template>
|
||||
<el-tooltip v-else-if="t.status === 'failed' && t.error" :content="t.error" placement="top">
|
||||
<el-tag type="danger" size="small">失败</el-tag>
|
||||
</el-tooltip>
|
||||
<el-tag v-else type="danger" size="small">失败</el-tag>
|
||||
<el-button
|
||||
v-if="t.status === 'running' || t.status === 'queued'"
|
||||
text
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="cancelCombined(t)"
|
||||
>
|
||||
{{ t.status === 'queued' ? '取消排队' : '终止' }}
|
||||
</el-button>
|
||||
<el-button v-else text size="small" @click="dismissCombined(t)">关闭</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-loading="loading" class="ds-grid">
|
||||
<div v-for="row in list" :key="row.id" class="ds-card" @click="router.push(`/datasets/${row.id}`)">
|
||||
<div class="ds-cover">
|
||||
@@ -768,6 +913,43 @@ onBeforeUnmount(() => {
|
||||
<FalseTargets v-if="pageTab === 'falseTargets'" ref="ftRef" />
|
||||
|
||||
<!-- 负样本批量生成:内置场景池 + RF-DETR 空检自动剔除,进度在负样本 tab 内展示 -->
|
||||
<!-- 综合训练发起:多选数据集(≥2)+ 档位,合并训练一个全类模型 -->
|
||||
<el-dialog v-model="combinedDlg" title="综合训练" width="min(480px, 94vw)">
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
title="多个数据集合并训练出一个全类模型,App 下载一个模型即可识别多种动物;任一物种补数据后需重新发起综合训练"
|
||||
style="margin-bottom: 14px"
|
||||
/>
|
||||
<el-form label-width="70px">
|
||||
<el-form-item label="数据集">
|
||||
<el-checkbox-group v-model="combinedIds">
|
||||
<el-checkbox v-for="d in list" :key="d.id" :value="d.id">
|
||||
{{ d.name }}({{ d.imageCount }} 张)
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="档位">
|
||||
<el-checkbox-group v-model="combinedVariants">
|
||||
<el-checkbox value="s">高精度(s@1280)</el-checkbox>
|
||||
<el-checkbox value="n">高性能(n@704)</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="combinedDlg = false">取消</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="combinedSubmitting"
|
||||
:disabled="combinedIds.length < 2"
|
||||
@click="combinedSubmit"
|
||||
>
|
||||
发起训练({{ combinedIds.length }} 个数据集)
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog v-model="negGenVisible" title="AI 生成负样本" width="min(480px, 94vw)">
|
||||
<el-form label-width="90px">
|
||||
<el-form-item label="生成张数">
|
||||
|
||||
Reference in New Issue
Block a user