Commit 36e2cc19 authored by luwei's avatar luwei

提交修改

parent 3fd809ab
......@@ -18,6 +18,16 @@ class CategoryUpdateRequest(BaseModel):
name: str = Field(min_length=1, max_length=100)
class CleanRules(BaseModel):
enabled: bool = False
current_min: float | None = None
current_max: float | None = None
voltage_min: float | None = None
voltage_max: float | None = None
temperature_min: float | None = None
temperature_max: float | None = None
@router.get('/categories')
def get_categories():
return success_response(data=service.get_category_tree())
......@@ -62,16 +72,19 @@ class PackageCreateRequest(BaseModel):
category_id: str | int | None = Field(default=None)
remark: str | None = Field(default=None)
file_ids: list[int] = Field(default_factory=list)
clean_rules: CleanRules | None = Field(default=None)
class PreviewRequest(BaseModel):
file_ids: list[int] = Field(default_factory=list)
clean_rules: CleanRules | None = Field(default=None)
@router.post('/preview')
def preview_package(request: PreviewRequest, limit: int = Query(default=300, ge=1, le=2000)):
try:
result = service.preview_records(file_ids=request.file_ids, limit=limit)
clean_rules = request.clean_rules.model_dump() if request.clean_rules else None
result = service.preview_records(file_ids=request.file_ids, limit=limit, clean_rules=clean_rules)
return success_response(data=result)
except ValueError as error:
raise HTTPException(status_code=400, detail=str(error)) from error
......@@ -89,11 +102,13 @@ def list_packages(
def create_package(request: PackageCreateRequest):
try:
category_id = None if request.category_id in (None, '', 'all') else str(request.category_id)
clean_rules = request.clean_rules.model_dump() if request.clean_rules else None
pkg = service.create_package(
name=request.name.strip(),
category_id=category_id,
remark=request.remark,
file_ids=request.file_ids,
clean_rules=clean_rules,
)
return success_response(data=pkg, message='数据包创建成功')
except ValueError as error:
......
......@@ -17,7 +17,6 @@ class LSTMParams(BaseModel):
epochs: int = Field(default=50, ge=1, le=2000)
batch_size: int = Field(default=32, ge=1, le=512)
learning_rate: float = Field(default=0.001, gt=0, le=1)
train_ratio: float = Field(default=0.8, ge=0.5, le=0.99)
class CreateTaskRequest(BaseModel):
......
"""LSTM temperature forecasting trainer.
Uses PyTorch if available. If not installed, raises a descriptive RuntimeError
so the train service can mark the task as failed with a helpful message.
"""
from __future__ import annotations
......@@ -46,6 +44,11 @@ FEATURE_COLS = ['current', 'voltage', 'set_temperature', 'actual_temperature']
TARGET_COL = 'actual_temperature'
TARGET_IDX = FEATURE_COLS.index(TARGET_COL)
# Fixed dataset split ratios (train / val / test)
_TRAIN_RATIO = 0.70
_VAL_RATIO = 0.15
# test = 1 - _TRAIN_RATIO - _VAL_RATIO (≈ 0.15)
def _check_torch() -> None:
if not _TORCH_AVAILABLE:
......@@ -97,13 +100,14 @@ def train_lstm(
Args:
records: list of dicts with keys in FEATURE_COLS.
params: hyper-parameter dict (seq_len, hidden_size, num_layers,
epochs, batch_size, learning_rate, train_ratio).
epochs, batch_size, learning_rate).
train_ratio is ignored – fixed 70/15/15 split is used.
save_path: destination .pt file.
on_progress: callback(pct, train_loss, val_loss) called after each epoch.
cancel_event: when set, training stops with InterruptedError.
Returns:
{'train_loss': float, 'val_loss': float|None}
{'train_loss': float, 'val_loss': float|None, 'test_loss': float|None}
"""
_check_torch()
......@@ -113,7 +117,6 @@ def train_lstm(
epochs = max(1, int(params.get('epochs', 50)))
batch_size = max(1, int(params.get('batch_size', 32)))
lr = float(params.get('learning_rate', 0.001))
train_ratio = min(0.99, max(0.5, float(params.get('train_ratio', 0.8))))
# ── data preparation ────────────────────────────────────────────────────
data = _extract_features(records)
......@@ -132,17 +135,27 @@ def train_lstm(
data_norm = (data - data_min) / data_range
X, y = _make_sequences(data_norm, seq_len)
n_train = max(1, int(len(X) * train_ratio))
# Fixed 70 / 15 / 15 split
n_total = len(X)
n_train = max(1, int(n_total * _TRAIN_RATIO))
n_val = max(1, int(n_total * _VAL_RATIO))
# test gets the remainder so the three parts always sum to n_total
X_train, y_train = X[:n_train], y[:n_train]
X_val, y_val = X[n_train:], y[n_train:]
X_val, y_val = X[n_train : n_train + n_val], y[n_train : n_train + n_val]
X_test, y_test = X[n_train + n_val :], y[n_train + n_val :]
device = torch.device('cpu')
X_train_t = torch.tensor(X_train).to(device)
y_train_t = torch.tensor(y_train).to(device)
has_val = len(X_val) > 0
has_val = len(X_val) > 0
has_test = len(X_test) > 0
if has_val:
X_val_t = torch.tensor(X_val).to(device)
y_val_t = torch.tensor(y_val).to(device)
if has_test:
X_test_t = torch.tensor(X_test).to(device)
y_test_t = torch.tensor(y_test).to(device)
train_loader = DataLoader(
TensorDataset(X_train_t, y_train_t),
......@@ -186,6 +199,14 @@ def train_lstm(
pct = int((epoch + 1) / epochs * 100)
on_progress(pct, train_loss, val_loss)
# ── test-set evaluation (completely hidden during training) ──────────────
test_loss: float | None = None
if has_test:
model.eval()
with torch.no_grad():
test_pred = model(X_test_t)
test_loss = criterion(test_pred, y_test_t).item()
# ── persist ──────────────────────────────────────────────────────────────
save_path.parent.mkdir(parents=True, exist_ok=True)
torch.save(
......@@ -206,5 +227,6 @@ def train_lstm(
return {
'train_loss': round(float(train_loss), 6),
'val_loss': round(float(val_loss), 6) if val_loss is not None else None,
'val_loss': round(float(val_loss), 6) if val_loss is not None else None,
'test_loss': round(float(test_loss), 6) if test_loss is not None else None,
}
from sqlalchemy import BIGINT, TIMESTAMP, Enum, Index, Integer, String, Text, text
from sqlalchemy import BIGINT, TIMESTAMP, Enum, Index, Integer, JSON, String, Text, text
from sqlalchemy.orm import Mapped, mapped_column
from app.database import Base
......@@ -58,6 +58,7 @@ class DataPackage(Base):
category_id: Mapped[int | None] = mapped_column(BIGINT, nullable=True, comment='分类ID(data_package类型)')
remark: Mapped[str | None] = mapped_column(Text, nullable=True, comment='备注')
data_count: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text('0'), comment='数据条数')
clean_rules: Mapped[dict | None] = mapped_column(JSON, nullable=True, comment='野值清洗规则')
created_at: Mapped[str] = mapped_column(TIMESTAMP, nullable=False, server_default=text('CURRENT_TIMESTAMP'))
updated_at: Mapped[str] = mapped_column(
TIMESTAMP,
......
......@@ -25,6 +25,7 @@ class TrainTask(Base):
progress: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text('0'), comment='进度 0-100')
train_loss: Mapped[float | None] = mapped_column(FLOAT, nullable=True, comment='训练损失')
val_loss: Mapped[float | None] = mapped_column(FLOAT, nullable=True, comment='验证损失')
test_loss: Mapped[float | None] = mapped_column(FLOAT, nullable=True, comment='测试损失')
error_msg: Mapped[str | None] = mapped_column(Text, nullable=True, comment='错误信息')
is_saved: Mapped[int] = mapped_column(Integer, nullable=False, server_default=text('0'), comment='是否已保存为模型')
created_at: Mapped[str] = mapped_column(TIMESTAMP, nullable=False, server_default=text('CURRENT_TIMESTAMP'))
......@@ -51,4 +52,5 @@ class SavedModel(Base):
file_path: Mapped[str] = mapped_column(String(500), nullable=False, comment='模型文件路径')
train_loss: Mapped[float | None] = mapped_column(FLOAT, nullable=True)
val_loss: Mapped[float | None] = mapped_column(FLOAT, nullable=True)
test_loss: Mapped[float | None] = mapped_column(FLOAT, nullable=True)
created_at: Mapped[str] = mapped_column(TIMESTAMP, nullable=False, server_default=text('CURRENT_TIMESTAMP'))
......@@ -153,6 +153,7 @@ class PackageManagementService:
category_id: str | None,
remark: str | None,
file_ids: list[int],
clean_rules: dict | None = None,
) -> dict[str, Any]:
if not name:
raise ValueError('数据包名称不能为空')
......@@ -177,13 +178,22 @@ class PackageManagementService:
if len(files) != len(file_ids):
raise ValueError('部分数据文件不存在')
total_count = sum(f.data_count for f in files)
file_map = {f.id: f for f in files}
if clean_rules and clean_rules.get('enabled'):
result = self._merge_records(file_ids, file_map, limit=None, clean_rules=clean_rules)
total_count = result['count']
stored_rules: dict | None = clean_rules
else:
total_count = sum(f.data_count for f in files)
stored_rules = None
pkg = DataPackage(
name=name,
category_id=cat_db_id,
remark=remark,
data_count=total_count,
clean_rules=stored_rules,
)
session.add(pkg)
session.flush()
......@@ -214,6 +224,8 @@ class PackageManagementService:
if not pkg:
raise ValueError('数据包不存在')
stored_clean_rules = pkg.clean_rules
pf_rows = (
session.query(DataPackageFile)
.filter(DataPackageFile.package_id == db_id)
......@@ -224,9 +236,14 @@ class PackageManagementService:
files = session.query(DataFile).filter(DataFile.id.in_(file_ids)).all()
file_map = {f.id: f for f in files}
return self._merge_records(file_ids, file_map, limit)
return self._merge_records(file_ids, file_map, limit, clean_rules=stored_clean_rules)
def preview_records(self, file_ids: list[int], limit: int = 300) -> dict[str, Any]:
def preview_records(
self,
file_ids: list[int],
limit: int = 300,
clean_rules: dict | None = None,
) -> dict[str, Any]:
if not file_ids:
return {'records': [], 'count': 0}
......@@ -234,7 +251,7 @@ class PackageManagementService:
files = session.query(DataFile).filter(DataFile.id.in_(file_ids)).all()
file_map = {f.id: f for f in files}
return self._merge_records(file_ids, file_map, limit)
return self._merge_records(file_ids, file_map, limit, clean_rules=clean_rules)
# ── helpers ──────────────────────────────────────────────────────────────
......@@ -242,8 +259,10 @@ class PackageManagementService:
self,
file_ids: list[int],
file_map: dict[int, Any],
limit: int,
limit: int | None,
clean_rules: dict | None = None,
) -> dict[str, Any]:
use_filter = bool(clean_rules and clean_rules.get('enabled'))
all_records: list[dict[str, Any]] = []
total_count = 0
remaining = limit
......@@ -253,15 +272,61 @@ class PackageManagementService:
continue
file_meta = file_map[fid]
path = self._dm._resolve_local_file_path(file_meta.file_path, file_meta.stored_name)
records, count = self._dm._read_records(path, limit=remaining if remaining > 0 else 0)
if use_filter:
# Read all rows so filter can be applied correctly
records, count = self._dm._read_records(path, limit=None)
else:
read_limit = (remaining if remaining is not None and remaining > 0 else 0) if remaining is not None else None
records, count = self._dm._read_records(path, limit=read_limit)
total_count += count
all_records.extend(records)
remaining -= len(records)
if remaining <= 0:
break
if not use_filter and remaining is not None:
remaining -= len(records)
if remaining <= 0:
break
if use_filter:
all_records = self._apply_clean_rules(all_records, clean_rules)
total_count = len(all_records)
if limit is not None and limit > 0:
return {'records': all_records[:limit], 'count': total_count}
return {'records': all_records, 'count': total_count}
@staticmethod
def _apply_clean_rules(records: list[dict[str, Any]], clean_rules: dict) -> list[dict[str, Any]]:
c_min = clean_rules.get('current_min')
c_max = clean_rules.get('current_max')
v_min = clean_rules.get('voltage_min')
v_max = clean_rules.get('voltage_max')
t_min = clean_rules.get('temperature_min')
t_max = clean_rules.get('temperature_max')
result = []
for r in records:
current = r.get('current')
voltage = r.get('voltage')
temp = r.get('actual_temperature')
if current is not None:
if c_min is not None and current < c_min:
continue
if c_max is not None and current > c_max:
continue
if voltage is not None:
if v_min is not None and voltage < v_min:
continue
if v_max is not None and voltage > v_max:
continue
if temp is not None:
if t_min is not None and temp < t_min:
continue
if t_max is not None and temp > t_max:
continue
result.append(r)
return result
def _pkg_to_dict(self, pkg: DataPackage) -> dict[str, Any]:
return {
'id': pkg.id,
......
......@@ -150,6 +150,7 @@ class TrainService:
file_path=str(model_path),
train_loss=task.train_loss,
val_loss=task.val_loss,
test_loss=task.test_loss,
)
session.add(saved)
task.is_saved = 1
......@@ -242,6 +243,7 @@ class TrainService:
progress=100,
train_loss=result['train_loss'],
val_loss=result.get('val_loss'),
test_loss=result.get('test_loss'),
)
except InterruptedError:
......@@ -300,6 +302,7 @@ class TrainService:
'progress': task.progress,
'train_loss': task.train_loss,
'val_loss': task.val_loss,
'test_loss': task.test_loss,
'error_msg': task.error_msg,
'is_saved': bool(task.is_saved),
'created_at': task.created_at.strftime('%Y-%m-%d %H:%M:%S') if task.created_at else '',
......@@ -316,5 +319,6 @@ class TrainService:
'params': model.params,
'train_loss': model.train_loss,
'val_loss': model.val_loss,
'test_loss': model.test_loss,
'created_at': model.created_at.strftime('%Y-%m-%d %H:%M:%S') if model.created_at else '',
}
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -5,10 +5,10 @@
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>热实验温度控制系统</title>
<script type="module" crossorigin src="/assets/index-B6mx_Oi_.js"></script>
<script type="module" crossorigin src="/assets/index-D5S_ELZm.js"></script>
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-D1RKUtCV.js">
<link rel="modulepreload" crossorigin href="/assets/es-DCOtnflc.js">
<link rel="stylesheet" crossorigin href="/assets/index-Bdu9N6pH.css">
<link rel="stylesheet" crossorigin href="/assets/index-BrwBxauG.css">
</head>
<body>
<div id="app"></div>
......
......@@ -6,10 +6,10 @@ const route = useRoute()
const router = useRouter()
const tabs = [
{ label: '数据管理', path: '/data-management' },
{ label: '数据包管理', path: '/package-management' },
{ label: '数据文件', path: '/data-management' },
{ label: '数据包', path: '/package-management' },
{ label: '模型训练', path: '/model-training' },
{ label: '模型列表', path: '/model-list' },
{ label: '模型', path: '/model-list' },
{ label: '模型评估', path: '/model-evaluation' },
{ label: '实时监控', path: '/realtime-monitor' },
{ label: '历史数据', path: '/history-data' },
......@@ -30,7 +30,7 @@ const handleTabChange = (path) => {
<template>
<div class="app-shell">
<header class="top-header">
<div class="project-title">验温度控制系统</div>
<div class="project-title">验温度控制系统</div>
<div class="header-divider"></div>
<el-tabs class="top-tabs" :model-value="activeTab" @tab-change="handleTabChange">
<el-tab-pane
......
......@@ -81,12 +81,15 @@ const renderChart = () => {
return ''
}
const unitMap = { '电流(A)': ' A', '电压(V)': ' V', '温度(℃)': ' ℃' }
const lines = [`<div style="margin-bottom:6px;font-weight:600;">${params[0].axisValue}</div>`]
params.forEach((item) => {
const val = Number.isFinite(item.data) ? Number(item.data).toFixed(2) : '--'
const unit = unitMap[item.seriesName] ?? ''
lines.push(
`<div style="display:flex;align-items:center;gap:6px;min-width:160px;justify-content:space-between;">
<span>${item.marker}${item.seriesName}</span>
<strong>${formatValue(item.data)}</strong>
<strong>${val}${unit}</strong>
</div>`,
)
})
......@@ -103,9 +106,9 @@ const renderChart = () => {
data: ['电流(A)', '电压(V)', '温度(℃)'],
},
grid: {
top: 24,
top: 48,
left: 24,
right: 24,
right: 60,
bottom: 56,
containLabel: true,
},
......@@ -123,18 +126,26 @@ const renderChart = () => {
},
},
},
yAxis: {
type: 'value',
axisLabel: {
color: '#64748b',
},
splitLine: {
lineStyle: {
type: 'dashed',
color: 'rgba(148, 163, 184, 0.45)',
yAxis: [
{
type: 'value',
name: '温度 (℃)',
position: 'left',
nameTextStyle: { color: '#64748b', fontSize: 11 },
axisLabel: { color: '#64748b', formatter: '{value} ℃' },
splitLine: {
lineStyle: { type: 'dashed', color: 'rgba(148, 163, 184, 0.45)' },
},
},
},
{
type: 'value',
name: '电流 / 电压',
position: 'right',
nameTextStyle: { color: '#64748b', fontSize: 11 },
axisLabel: { color: '#64748b' },
splitLine: { show: false },
},
],
series: [
{
name: '电流(A)',
......@@ -142,6 +153,7 @@ const renderChart = () => {
smooth: true,
symbol: 'circle',
symbolSize: 7,
yAxisIndex: 1,
data: getSeriesData('current'),
},
{
......@@ -150,6 +162,7 @@ const renderChart = () => {
smooth: true,
symbol: 'circle',
symbolSize: 7,
yAxisIndex: 1,
data: getSeriesData('voltage'),
},
{
......@@ -158,6 +171,7 @@ const renderChart = () => {
smooth: true,
symbol: 'circle',
symbolSize: 7,
yAxisIndex: 0,
data: getSeriesData('actual_temperature'),
},
],
......
......@@ -23,7 +23,6 @@ const form = reactive({
epochs: 50,
batch_size: 32,
learning_rate: 0.001,
train_ratio: 0.8,
},
})
......@@ -152,15 +151,15 @@ const formatParams = (params) => {
`轮数 ${params.epochs}`,
`批次 ${params.batch_size}`,
`学习率 ${params.learning_rate}`,
`训练比 ${params.train_ratio}`,
].join(' / ')
}
const formatLoss = (task) => {
if (task.train_loss == null) return '-'
const train = `训练: ${Number(task.train_loss).toFixed(5)}`
const val = task.val_loss != null ? ` / 验证: ${Number(task.val_loss).toFixed(5)}` : ''
return train + val
const parts = [`训练: ${Number(task.train_loss).toFixed(5)}`]
if (task.val_loss != null) parts.push(`验证: ${Number(task.val_loss).toFixed(5)}`)
if (task.test_loss != null) parts.push(`测试: ${Number(task.test_loss).toFixed(5)}`)
return parts.join(' / ')
}
onMounted(async () => {
......@@ -279,17 +278,6 @@ onBeforeUnmount(stopPolling)
style="width: 100%"
/>
</el-form-item>
<el-form-item label="训练集比例">
<el-input-number
v-model="form.params.train_ratio"
:min="0.5"
:max="0.99"
:step="0.05"
:precision="2"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
</div>
</div>
......
......@@ -45,6 +45,30 @@ const form = reactive({
remark: '',
})
// ── clean rules ───────────────────────────────────────────────────────────────
const cleanRules = reactive({
enabled: false,
current_min: null,
current_max: null,
voltage_min: null,
voltage_max: null,
temperature_min: null,
temperature_max: null,
})
const cleanRulesPayload = computed(() => {
if (!cleanRules.enabled) return null
return {
enabled: true,
current_min: cleanRules.current_min ?? null,
current_max: cleanRules.current_max ?? null,
voltage_min: cleanRules.voltage_min ?? null,
voltage_max: cleanRules.voltage_max ?? null,
temperature_min: cleanRules.temperature_min ?? null,
temperature_max: cleanRules.temperature_max ?? null,
}
})
// ── preview ───────────────────────────────────────────────────────────────────
const previewLoading = ref(false)
const previewRecords = ref([])
......@@ -62,7 +86,10 @@ const triggerPreview = () => {
previewDebounceTimer = setTimeout(async () => {
previewLoading.value = true
try {
const result = await previewPackage({ file_ids: selectedFileIds.value }, { limit: 300 })
const result = await previewPackage(
{ file_ids: selectedFileIds.value, clean_rules: cleanRulesPayload.value },
{ limit: 300 },
)
previewRecords.value = result.records
previewTotal.value = result.count
} finally {
......@@ -72,6 +99,7 @@ const triggerPreview = () => {
}
watch(selectedFileIds, triggerPreview, { deep: true })
watch(cleanRules, triggerPreview, { deep: true })
// ── save ──────────────────────────────────────────────────────────────────────
const saving = ref(false)
......@@ -93,6 +121,7 @@ const handleGenerate = async () => {
category_id: form.categoryId || null,
remark: form.remark.trim() || null,
file_ids: selectedFileIds.value,
clean_rules: cleanRulesPayload.value,
})
ElMessage.success('数据包创建成功')
emit('saved')
......@@ -165,6 +194,67 @@ onMounted(async () => {
<el-form-item label="数据包名称" required>
<el-input v-model="form.name" maxlength="100" show-word-limit placeholder="请输入数据包名称" />
</el-form-item>
<el-form-item label="清洗规则">
<div class="clean-rules-wrap">
<el-checkbox v-model="cleanRules.enabled">野值清理</el-checkbox>
<div v-if="cleanRules.enabled" class="clean-range-grid">
<div class="clean-range-row">
<span class="clean-range-label">电流范围 (A)</span>
<el-input-number
v-model="cleanRules.current_min"
:controls="false"
:value-on-clear="null"
placeholder="最小值"
class="range-input"
/>
<span class="range-sep">~</span>
<el-input-number
v-model="cleanRules.current_max"
:controls="false"
:value-on-clear="null"
placeholder="最大值"
class="range-input"
/>
</div>
<div class="clean-range-row">
<span class="clean-range-label">电压范围 (V)</span>
<el-input-number
v-model="cleanRules.voltage_min"
:controls="false"
:value-on-clear="null"
placeholder="最小值"
class="range-input"
/>
<span class="range-sep">~</span>
<el-input-number
v-model="cleanRules.voltage_max"
:controls="false"
:value-on-clear="null"
placeholder="最大值"
class="range-input"
/>
</div>
<div class="clean-range-row">
<span class="clean-range-label">温度范围 (℃)</span>
<el-input-number
v-model="cleanRules.temperature_min"
:controls="false"
:value-on-clear="null"
placeholder="最小值"
class="range-input"
/>
<span class="range-sep">~</span>
<el-input-number
v-model="cleanRules.temperature_max"
:controls="false"
:value-on-clear="null"
placeholder="最大值"
class="range-input"
/>
</div>
</div>
</div>
</el-form-item>
<el-form-item label="备注">
<el-input
v-model="form.remark"
......@@ -323,4 +413,41 @@ onMounted(async () => {
margin-bottom: 16px;
}
}
</style>
.clean-rules-wrap {
width: 100%;
}
.clean-range-grid {
margin-top: 8px;
display: flex;
flex-direction: column;
gap: 8px;
}
.clean-range-row {
display: flex;
align-items: center;
gap: 6px;
}
.clean-range-label {
font-size: 12px;
color: #475569;
width: 86px;
flex-shrink: 0;
}
.range-input {
width: 88px;
:deep(.el-input__inner) {
text-align: center;
}
}
.range-sep {
font-size: 13px;
color: #94a3b8;
}
</style>
\ No newline at end of file
......@@ -8,7 +8,6 @@ import vueDevTools from 'vite-plugin-vue-devtools'
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
server: {
proxy: {
......
......@@ -42,6 +42,9 @@ CREATE TABLE IF NOT EXISTS data_packages (
INDEX idx_pkg_category (category_id)
) COMMENT='数据包表';
ALTER TABLE data_packages
ADD COLUMN IF NOT EXISTS clean_rules JSON NULL COMMENT '野值清洗规则';
CREATE TABLE IF NOT EXISTS data_package_files (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
package_id BIGINT NOT NULL COMMENT '数据包ID',
......@@ -50,3 +53,9 @@ CREATE TABLE IF NOT EXISTS data_package_files (
INDEX idx_dpf_package (package_id),
INDEX idx_dpf_file (file_id)
) COMMENT='数据包文件关联表';
ALTER TABLE train_tasks
ADD COLUMN IF NOT EXISTS test_loss FLOAT NULL COMMENT '测试集损失';
ALTER TABLE saved_models
ADD COLUMN IF NOT EXISTS test_loss FLOAT NULL COMMENT '测试集损失';
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment