前端--获取和展示数据完成
This commit is contained in:
parent
bf574b3dc5
commit
9fd425173c
Binary file not shown.
@ -75,6 +75,7 @@ async def get_datasets():
|
||||
"""获取可用数据集列表"""
|
||||
try:
|
||||
datasets = data_manager.get_dataset()
|
||||
print("datasets", datasets)
|
||||
return {
|
||||
"status": "success",
|
||||
"datasets": datasets
|
||||
|
||||
@ -764,7 +764,7 @@ Error Response:
|
||||
|
||||
### 3.3 获取系统中训练状态 ---- 未完成, 等开发系统后台时再实现.
|
||||
```http
|
||||
GET /api/train/status/{task_id}
|
||||
GET /model/train/status/{task_id}
|
||||
|
||||
Response:
|
||||
{
|
||||
|
||||
13
frontend/index.html
Normal file
13
frontend/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>机器学习平台</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@ -5,11 +5,15 @@ import type {
|
||||
FeatureMethod,
|
||||
ProcessRequest,
|
||||
CSVRequest,
|
||||
DatasetInfo
|
||||
DatasetInfo,
|
||||
MethodDetail
|
||||
} from '@/types/data'
|
||||
|
||||
// 获取预处理方法列表
|
||||
export const getPreprocessMethods = (): Promise<AxiosResponse<PreprocessMethod[]>> => {
|
||||
export const getPreprocessMethods = (): Promise<{
|
||||
status: string;
|
||||
methods: PreprocessMethod[];
|
||||
}> => {
|
||||
return request({
|
||||
url: '/data/preprocessing/methods',
|
||||
method: 'get'
|
||||
@ -17,7 +21,10 @@ export const getPreprocessMethods = (): Promise<AxiosResponse<PreprocessMethod[]
|
||||
}
|
||||
|
||||
// 获取预处理方法详情
|
||||
export const getPreprocessMethodDetails = (methodName: string): Promise<AxiosResponse> => {
|
||||
export const getPreprocessMethodDetails = (methodName: string): Promise<{
|
||||
status: string;
|
||||
method: MethodDetail;
|
||||
}> => {
|
||||
return request({
|
||||
url: `/data/preprocessing/method/${methodName}`,
|
||||
method: 'get'
|
||||
@ -25,7 +32,10 @@ export const getPreprocessMethodDetails = (methodName: string): Promise<AxiosRes
|
||||
}
|
||||
|
||||
// 获取特征工程方法列表
|
||||
export const getFeatureMethods = (): Promise<AxiosResponse<FeatureMethod[]>> => {
|
||||
export const getFeatureMethods = (): Promise<{
|
||||
status: string;
|
||||
methods: FeatureMethod[];
|
||||
}> => {
|
||||
return request({
|
||||
url: '/data/feature/methods',
|
||||
method: 'get'
|
||||
@ -33,7 +43,10 @@ export const getFeatureMethods = (): Promise<AxiosResponse<FeatureMethod[]>> =>
|
||||
}
|
||||
|
||||
// 获取特征工程方法详情
|
||||
export const getFeatureMethodDetails = (methodName: string): Promise<AxiosResponse> => {
|
||||
export const getFeatureMethodDetails = (methodName: string): Promise<{
|
||||
status: string;
|
||||
method: MethodDetail;
|
||||
}> => {
|
||||
return request({
|
||||
url: `/data/feature/method/${methodName}`,
|
||||
method: 'get'
|
||||
@ -41,7 +54,10 @@ export const getFeatureMethodDetails = (methodName: string): Promise<AxiosRespon
|
||||
}
|
||||
|
||||
// 处理数据集
|
||||
export const processDataset = (data: ProcessRequest): Promise<AxiosResponse> => {
|
||||
export const processDataset = (data: ProcessRequest): Promise<{
|
||||
status: string;
|
||||
data: any;
|
||||
}> => {
|
||||
return request({
|
||||
url: '/data/process',
|
||||
method: 'post',
|
||||
@ -49,8 +65,11 @@ export const processDataset = (data: ProcessRequest): Promise<AxiosResponse> =>
|
||||
})
|
||||
}
|
||||
|
||||
// 获取可用数据集列表
|
||||
export const getDatasets = (): Promise<AxiosResponse<DatasetInfo[]>> => {
|
||||
// 获取数据集列表
|
||||
export const getDatasets = (): Promise<{
|
||||
status: string;
|
||||
datasets: DatasetInfo[];
|
||||
}> => {
|
||||
return request({
|
||||
url: '/data/datasets',
|
||||
method: 'get'
|
||||
@ -58,7 +77,10 @@ export const getDatasets = (): Promise<AxiosResponse<DatasetInfo[]>> => {
|
||||
}
|
||||
|
||||
// 读取CSV文件
|
||||
export const readCSV = (data: CSVRequest): Promise<AxiosResponse> => {
|
||||
export const readCSV = (data: CSVRequest): Promise<{
|
||||
status: string;
|
||||
data: any;
|
||||
}> => {
|
||||
return request({
|
||||
url: '/data/csv',
|
||||
method: 'post',
|
||||
|
||||
@ -4,74 +4,80 @@
|
||||
title="数据预览"
|
||||
width="80%"
|
||||
>
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 训练集预览 -->
|
||||
<el-tab-pane label="训练集" name="train">
|
||||
<div v-loading="loading.train">
|
||||
<el-descriptions title="数据集信息" :column="3" border>
|
||||
<el-descriptions-item label="行数">
|
||||
{{ dataInfo.train?.info?.rows || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="列数">
|
||||
{{ dataInfo.train?.info?.columns || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="内存占用">
|
||||
{{ dataInfo.train?.info?.memory_usage || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>数据预览</h4>
|
||||
<el-table
|
||||
:data="dataInfo.train?.head || []"
|
||||
style="width: 100%"
|
||||
max-height="400"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
v-for="col in getColumns(dataInfo.train?.head)"
|
||||
:key="col"
|
||||
:prop="col"
|
||||
:label="col"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>数据统计</h4>
|
||||
<el-table
|
||||
:data="getDescribeData(dataInfo.train?.describe)"
|
||||
style="width: 100%"
|
||||
border
|
||||
>
|
||||
<el-table-column prop="metric" label="统计量" width="180" />
|
||||
<el-table-column
|
||||
v-for="col in getDescribeColumns(dataInfo.train?.describe)"
|
||||
:key="col"
|
||||
:prop="col"
|
||||
:label="col"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 验证集预览 -->
|
||||
<el-tab-pane label="验证集" name="val">
|
||||
<!-- 与训练集类似的内容 -->
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 测试集预览 -->
|
||||
<el-tab-pane label="测试集" name="test">
|
||||
<!-- 与训练集类似的内容 -->
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div v-loading="loading">
|
||||
<!-- 数据集基本信息 -->
|
||||
<el-descriptions title="数据集信息" :column="3" border>
|
||||
<el-descriptions-item label="行数">
|
||||
{{ dataInfo?.info?.rows || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="列数">
|
||||
{{ dataInfo?.info?.columns || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="内存占用">
|
||||
{{ dataInfo?.info?.memory_usage || '-' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 数据预览表格 -->
|
||||
<div class="preview-section">
|
||||
<h4>数据预览</h4>
|
||||
<el-table
|
||||
:data="dataInfo?.head || []"
|
||||
style="width: 100%"
|
||||
max-height="400"
|
||||
border
|
||||
>
|
||||
<el-table-column
|
||||
v-for="col in getColumns(dataInfo?.head)"
|
||||
:key="col"
|
||||
:prop="col"
|
||||
:label="col"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 数据统计信息 -->
|
||||
<div class="statistics-section">
|
||||
<h4>数据统计</h4>
|
||||
<el-table
|
||||
:data="getDescribeData(dataInfo?.describe)"
|
||||
style="width: 100%"
|
||||
border
|
||||
>
|
||||
<el-table-column prop="metric" label="统计量" width="180" />
|
||||
<el-table-column
|
||||
v-for="col in getDescribeColumns(dataInfo?.describe)"
|
||||
:key="col"
|
||||
:prop="col"
|
||||
:label="col"
|
||||
/>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<!-- 缺失值信息 -->
|
||||
<div class="missing-section">
|
||||
<h4>缺失值统计</h4>
|
||||
<el-table
|
||||
:data="getMissingData(dataInfo?.info?.missing_values)"
|
||||
style="width: 100%"
|
||||
border
|
||||
>
|
||||
<el-table-column prop="column" label="列名" />
|
||||
<el-table-column prop="count" label="缺失值数量" />
|
||||
<el-table-column prop="percentage" label="缺失比例">
|
||||
<template #default="{ row }">
|
||||
{{ row.percentage }}%
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import type { DatasetInfo } from '@/types/data'
|
||||
import type { DatasetInfo, DataPreview } from '@/types/data'
|
||||
import { readCSV } from '@/api/data'
|
||||
|
||||
const props = defineProps<{
|
||||
@ -84,24 +90,14 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const activeTab = ref('train')
|
||||
const loading = ref({
|
||||
train: false,
|
||||
val: false,
|
||||
test: false
|
||||
})
|
||||
|
||||
const dataInfo = ref({
|
||||
train: null,
|
||||
val: null,
|
||||
test: null
|
||||
})
|
||||
const loading = ref(false)
|
||||
const dataInfo = ref<DataPreview | null>(null)
|
||||
|
||||
// 监听对话框显示状态
|
||||
watch(() => props.visible, (val) => {
|
||||
dialogVisible.value = val
|
||||
if (val && props.dataset) {
|
||||
loadData('train')
|
||||
loadData()
|
||||
}
|
||||
})
|
||||
|
||||
@ -110,34 +106,24 @@ watch(dialogVisible, (val) => {
|
||||
emit('update:visible', val)
|
||||
})
|
||||
|
||||
// 监听标签页切换
|
||||
watch(activeTab, (val) => {
|
||||
if (!dataInfo.value[val]) {
|
||||
loadData(val)
|
||||
}
|
||||
})
|
||||
|
||||
// 加载数据
|
||||
const loadData = async (type: 'train' | 'val' | 'test') => {
|
||||
const loadData = async () => {
|
||||
if (!props.dataset) return
|
||||
|
||||
const filePath = props.dataset.output_files[type]
|
||||
if (!filePath) return
|
||||
|
||||
loading.value[type] = true
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await readCSV({
|
||||
data_path: filePath,
|
||||
data_path: props.dataset.input_file,
|
||||
head: 10,
|
||||
tail: 5,
|
||||
info: true,
|
||||
describe: true
|
||||
})
|
||||
dataInfo.value[type] = data.data
|
||||
dataInfo.value = data
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
loading.value[type] = false
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,10 +152,27 @@ const getDescribeData = (describe: any = {}) => {
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
// 转换缺失值数据为表格格式
|
||||
const getMissingData = (missingValues: Record<string, number> = {}) => {
|
||||
if (!dataInfo.value?.info?.rows) return []
|
||||
|
||||
return Object.entries(missingValues).map(([column, count]) => ({
|
||||
column,
|
||||
count,
|
||||
percentage: ((count / dataInfo.value!.info.rows) * 100).toFixed(2)
|
||||
}))
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
.preview-section,
|
||||
.statistics-section,
|
||||
.missing-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
</style>
|
||||
197
frontend/src/components/data/DataProcessDialog.vue
Normal file
197
frontend/src/components/data/DataProcessDialog.vue
Normal file
@ -0,0 +1,197 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
title="数据处理配置"
|
||||
width="60%"
|
||||
>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
label-width="120px"
|
||||
>
|
||||
<!-- 预处理方法配置 -->
|
||||
<el-form-item label="预处理方法">
|
||||
<el-select
|
||||
v-model="form.preprocessing"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="选择预处理方法"
|
||||
>
|
||||
<el-option-group
|
||||
v-for="group in preprocessMethods"
|
||||
:key="group.name"
|
||||
:label="group.description"
|
||||
>
|
||||
<el-option
|
||||
v-for="method in group.method"
|
||||
:key="method"
|
||||
:label="method"
|
||||
:value="method"
|
||||
/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 特征工程方法配置 -->
|
||||
<el-form-item label="特征工程">
|
||||
<el-select
|
||||
v-model="form.feature_methods"
|
||||
multiple
|
||||
filterable
|
||||
placeholder="选择特征工程方法"
|
||||
>
|
||||
<el-option-group
|
||||
v-for="group in featureMethods"
|
||||
:key="group.name"
|
||||
:label="group.description"
|
||||
>
|
||||
<el-option
|
||||
v-for="method in group.method"
|
||||
:key="method"
|
||||
:label="method"
|
||||
:value="method"
|
||||
/>
|
||||
</el-option-group>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 数据集划分配置 -->
|
||||
<el-form-item label="数据集划分">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="训练集">
|
||||
<el-input-number
|
||||
v-model="form.split_params.train"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="验证集">
|
||||
<el-input-number
|
||||
v-model="form.split_params.val"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="测试集">
|
||||
<el-input-number
|
||||
v-model="form.split_params.test"
|
||||
:min="0"
|
||||
:max="1"
|
||||
:step="0.1"
|
||||
:disabled="true"
|
||||
:modelValue="1 - form.split_params.train - form.split_params.val"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit" :loading="loading">
|
||||
确认
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { DatasetInfo, PreprocessMethod, FeatureMethod } from '@/types/data'
|
||||
import { getPreprocessMethods, getFeatureMethods, processDataset } from '@/api/data'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
dataset: DatasetInfo | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const loading = ref(false)
|
||||
const preprocessMethods = ref<PreprocessMethod[]>([])
|
||||
const featureMethods = ref<FeatureMethod[]>([])
|
||||
|
||||
const form = ref({
|
||||
preprocessing: [],
|
||||
feature_methods: [],
|
||||
split_params: {
|
||||
train: 0.7,
|
||||
val: 0.2,
|
||||
test: 0.1
|
||||
}
|
||||
})
|
||||
|
||||
// 监听对话框显示状态
|
||||
watch(() => props.visible, (val) => {
|
||||
dialogVisible.value = val
|
||||
if (val) {
|
||||
loadMethods()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听对话框关闭
|
||||
watch(dialogVisible, (val) => {
|
||||
emit('update:visible', val)
|
||||
})
|
||||
|
||||
// 加载处理方法
|
||||
const loadMethods = async () => {
|
||||
try {
|
||||
const [preprocess, feature] = await Promise.all([
|
||||
getPreprocessMethods(),
|
||||
getFeatureMethods()
|
||||
])
|
||||
preprocessMethods.value = preprocess.data
|
||||
featureMethods.value = feature.data
|
||||
} catch (error) {
|
||||
console.error('Failed to load methods:', error)
|
||||
ElMessage.error('加载处理方法失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 提交处理
|
||||
const handleSubmit = async () => {
|
||||
if (!props.dataset) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
await processDataset({
|
||||
input_path: props.dataset.input_file,
|
||||
output_dir: 'dataset/dataset_processed',
|
||||
preprocessing: form.value.preprocessing.map(method => ({
|
||||
method,
|
||||
params: {}
|
||||
})),
|
||||
feature_methods: form.value.feature_methods.map(method => ({
|
||||
method,
|
||||
params: {}
|
||||
})),
|
||||
split_params: form.value.split_params
|
||||
})
|
||||
|
||||
ElMessage.success('处理成功')
|
||||
dialogVisible.value = false
|
||||
emit('success')
|
||||
} catch (error) {
|
||||
console.error('Failed to process dataset:', error)
|
||||
ElMessage.error('处理失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -1,22 +1,36 @@
|
||||
<template>
|
||||
<div class="dataset-list">
|
||||
<el-table :data="datasets" style="width: 100%">
|
||||
<el-table-column prop="input_file" label="数据集" width="200">
|
||||
<!-- 上传数据集 -->
|
||||
<div class="upload-section">
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
action="/data/upload"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
:before-upload="beforeUpload"
|
||||
>
|
||||
<el-button type="primary">上传数据集</el-button>
|
||||
</el-upload>
|
||||
</div>
|
||||
|
||||
<!-- 数据集列表 -->
|
||||
<el-table :data="datasets" style="width: 100%" v-loading="loading">
|
||||
<el-table-column prop="input_file" label="数据集名称" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ getFileName(row.input_file) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="timestamp" label="处理时间" width="180">
|
||||
<el-table-column prop="timestamp" label="创建时间" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ formatTime(row.timestamp) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="数据处理">
|
||||
<el-table-column label="预处理方法">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-for="method in row.process_methods"
|
||||
v-for="method in row.process_methods"
|
||||
:key="method.method_name"
|
||||
class="mx-1"
|
||||
>
|
||||
@ -28,7 +42,7 @@
|
||||
<el-table-column label="特征工程">
|
||||
<template #default="{ row }">
|
||||
<el-tag
|
||||
v-for="method in row.feature_methods"
|
||||
v-for="method in row.feature_methods"
|
||||
:key="method.method_name"
|
||||
type="success"
|
||||
class="mx-1"
|
||||
@ -38,98 +52,56 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="数据划分">
|
||||
<el-table-column label="操作" width="250">
|
||||
<template #default="{ row }">
|
||||
<el-tooltip effect="dark" placement="top">
|
||||
<template #content>
|
||||
<div>训练集: {{ (1 - row.split_params.test_size - row.split_params.val_size) * 100 }}%</div>
|
||||
<div>验证集: {{ row.split_params.val_size * 100 }}%</div>
|
||||
<div>测试集: {{ row.split_params.test_size * 100 }}%</div>
|
||||
</template>
|
||||
<el-progress :percentage="100" :format="() => '查看详情'" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="200">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
link
|
||||
@click="handlePreview(row)"
|
||||
>
|
||||
<el-button link type="primary" @click="handlePreview(row)">
|
||||
预览
|
||||
</el-button>
|
||||
<el-button
|
||||
type="success"
|
||||
link
|
||||
@click="handleDownload(row)"
|
||||
>
|
||||
<el-button link type="success" @click="handleProcess(row)">
|
||||
处理
|
||||
</el-button>
|
||||
<el-button link type="info" @click="handleDownload(row)">
|
||||
下载
|
||||
</el-button>
|
||||
<el-button
|
||||
type="info"
|
||||
link
|
||||
@click="handleDetails(row)"
|
||||
>
|
||||
详情
|
||||
<el-button link type="danger" @click="handleDelete(row)">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 预览对话框 -->
|
||||
|
||||
<!-- 数据预览对话框 -->
|
||||
<DataPreview
|
||||
v-model:visible="previewVisible"
|
||||
:dataset="selectedDataset"
|
||||
/>
|
||||
|
||||
<!-- 详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="detailsVisible"
|
||||
title="数据处理详情"
|
||||
width="60%"
|
||||
>
|
||||
<el-timeline>
|
||||
<el-timeline-item
|
||||
v-for="step in selectedDataset?.steps"
|
||||
:key="step.step"
|
||||
:type="getStepType(step.step)"
|
||||
>
|
||||
<h4>{{ getStepTitle(step) }}</h4>
|
||||
<p>数据形状: {{ step.shape[0] }} × {{ step.shape[1] }}</p>
|
||||
<template v-if="step.method">
|
||||
<p>处理方法: {{ step.method }}</p>
|
||||
<p>参数配置:</p>
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item
|
||||
v-for="(value, key) in step.params"
|
||||
:key="key"
|
||||
:label="key"
|
||||
>
|
||||
{{ value }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</template>
|
||||
</el-timeline-item>
|
||||
</el-timeline>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 数据处理对话框 -->
|
||||
<DataProcessDialog
|
||||
v-model:visible="processVisible"
|
||||
:dataset="selectedDataset"
|
||||
@success="handleProcessSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import type { DatasetInfo } from '@/types/data'
|
||||
import DataPreview from './DataPreview.vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import dayjs from 'dayjs'
|
||||
import type { DatasetInfo } from '@/types/data'
|
||||
import DataPreview from './DataPreview.vue'
|
||||
import DataProcessDialog from './DataProcessDialog.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
datasets: DatasetInfo[]
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const loading = ref(false)
|
||||
const previewVisible = ref(false)
|
||||
const detailsVisible = ref(false)
|
||||
const processVisible = ref(false)
|
||||
const selectedDataset = ref<DatasetInfo | null>(null)
|
||||
|
||||
// 获取文件名
|
||||
@ -142,42 +114,65 @@ const formatTime = (timestamp: string) => {
|
||||
return dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss')
|
||||
}
|
||||
|
||||
// 获取步骤类型
|
||||
const getStepType = (step: string) => {
|
||||
const typeMap: Record<string, string> = {
|
||||
'load_data': 'primary',
|
||||
'cleaning': 'success',
|
||||
'feature_engineering': 'warning'
|
||||
// 上传前检查
|
||||
const beforeUpload = (file: File) => {
|
||||
const isCSV = file.type === 'text/csv'
|
||||
if (!isCSV) {
|
||||
ElMessage.error('只能上传CSV文件!')
|
||||
return false
|
||||
}
|
||||
return typeMap[step] || 'info'
|
||||
return true
|
||||
}
|
||||
|
||||
// 获取步骤标题
|
||||
const getStepTitle = (step: DatasetInfo['steps'][0]) => {
|
||||
const titleMap: Record<string, string> = {
|
||||
'load_data': '数据加载',
|
||||
'cleaning': '数据清洗',
|
||||
'feature_engineering': '特征工程'
|
||||
}
|
||||
return titleMap[step.step] || step.step
|
||||
// 上传成功
|
||||
const handleUploadSuccess = () => {
|
||||
ElMessage.success('上传成功')
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
// 处理预览
|
||||
// 上传失败
|
||||
const handleUploadError = () => {
|
||||
ElMessage.error('上传失败')
|
||||
}
|
||||
|
||||
// 预览数据集
|
||||
const handlePreview = (dataset: DatasetInfo) => {
|
||||
selectedDataset.value = dataset
|
||||
previewVisible.value = true
|
||||
}
|
||||
|
||||
// 处理下载
|
||||
const handleDownload = (dataset: DatasetInfo) => {
|
||||
// 实现文件下载逻辑
|
||||
ElMessage.success('开始下载数据集')
|
||||
// 处理数据集
|
||||
const handleProcess = (dataset: DatasetInfo) => {
|
||||
selectedDataset.value = dataset
|
||||
processVisible.value = true
|
||||
}
|
||||
|
||||
// 处理详情查看
|
||||
const handleDetails = (dataset: DatasetInfo) => {
|
||||
selectedDataset.value = dataset
|
||||
detailsVisible.value = true
|
||||
// 处理成功
|
||||
const handleProcessSuccess = () => {
|
||||
processVisible.value = false
|
||||
emit('refresh')
|
||||
ElMessage.success('数据处理成功')
|
||||
}
|
||||
|
||||
// 下载数据集
|
||||
const handleDownload = async (dataset: DatasetInfo) => {
|
||||
try {
|
||||
// 实现下载逻辑
|
||||
ElMessage.success('开始下载')
|
||||
} catch (error) {
|
||||
ElMessage.error('下载失败')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除数据集
|
||||
const handleDelete = async (dataset: DatasetInfo) => {
|
||||
try {
|
||||
// 实现删除逻辑
|
||||
ElMessage.success('删除成功')
|
||||
emit('refresh')
|
||||
} catch (error) {
|
||||
ElMessage.error('删除失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -186,7 +181,12 @@ const handleDetails = (dataset: DatasetInfo) => {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.upload-section {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.el-tag {
|
||||
margin-right: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
</style>
|
||||
@ -9,11 +9,38 @@ const router = createRouter({
|
||||
},
|
||||
{
|
||||
path: '/data',
|
||||
name: 'Data',
|
||||
component: () => import('@/views/data/DatasetView.vue'),
|
||||
meta: {
|
||||
title: '数据管理'
|
||||
}
|
||||
component: () => import('@/views/data/DataView.vue'),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'Data',
|
||||
redirect: '/data/datasets'
|
||||
},
|
||||
{
|
||||
path: 'datasets',
|
||||
name: 'Datasets',
|
||||
component: () => import('@/views/data/DatasetView.vue'),
|
||||
meta: {
|
||||
title: '数据集管理'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'preprocess',
|
||||
name: 'Preprocess',
|
||||
component: () => import('@/views/data/PreprocessView.vue'),
|
||||
meta: {
|
||||
title: '预处理方法'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'feature',
|
||||
name: 'Feature',
|
||||
component: () => import('@/views/data/FeatureView.vue'),
|
||||
meta: {
|
||||
title: '特征工程方法'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/model',
|
||||
|
||||
@ -16,7 +16,7 @@ export interface FeatureMethod {
|
||||
export interface MethodParameter {
|
||||
name: string
|
||||
type: string
|
||||
default: any
|
||||
default: string | number | boolean | null
|
||||
description: string
|
||||
}
|
||||
|
||||
@ -86,4 +86,18 @@ export interface DatasetInfo {
|
||||
validation: string
|
||||
test: string
|
||||
}
|
||||
}
|
||||
|
||||
// 数据预览信息
|
||||
export interface DataPreview {
|
||||
info: {
|
||||
rows: number
|
||||
columns: number
|
||||
column_types: Record<string, string>
|
||||
memory_usage: string
|
||||
missing_values: Record<string, number>
|
||||
}
|
||||
head: any[]
|
||||
tail: any[]
|
||||
describe: Record<string, any>
|
||||
}
|
||||
51
frontend/src/views/data/DataView.vue
Normal file
51
frontend/src/views/data/DataView.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<div class="data-view">
|
||||
<!-- 左侧导航 -->
|
||||
<div class="sidebar">
|
||||
<el-menu
|
||||
:default-active="$route.path"
|
||||
router
|
||||
>
|
||||
<el-menu-item index="/data/datasets">
|
||||
<el-icon><Folder /></el-icon>
|
||||
<span>数据集管理</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/data/preprocess">
|
||||
<el-icon><Tools /></el-icon>
|
||||
<span>预处理方法</span>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/data/feature">
|
||||
<el-icon><Operation /></el-icon>
|
||||
<span>特征工程方法</span>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<div class="content">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Folder, Tools, Operation } from '@element-plus/icons-vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.data-view {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
border-right: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@ -1,25 +1,56 @@
|
||||
<template>
|
||||
<div class="dataset-view">
|
||||
<h2>数据集管理</h2>
|
||||
<DatasetList :datasets="datasets" />
|
||||
<el-card>
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>数据集管理</span>
|
||||
<el-button type="primary" @click="refreshData">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<DatasetList
|
||||
:datasets="datasets"
|
||||
@refresh="refreshData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getDatasets } from '@/api/data'
|
||||
import DatasetList from '@/components/data/DatasetList.vue'
|
||||
import type { DatasetInfo } from '@/types/data'
|
||||
|
||||
const datasets = ref<DatasetInfo[]>([])
|
||||
const loading = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
// 加载数据集列表
|
||||
const loadDatasets = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { data } = await getDatasets()
|
||||
datasets.value = data.datasets
|
||||
const response = await getDatasets()
|
||||
if (response.status === 'success' && response.datasets) {
|
||||
datasets.value = response.datasets
|
||||
} else {
|
||||
ElMessage.error('加载数据集失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load datasets:', error)
|
||||
ElMessage.error('加载数据集失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新数据
|
||||
const refreshData = () => {
|
||||
loadDatasets()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadDatasets()
|
||||
})
|
||||
</script>
|
||||
|
||||
@ -27,4 +58,10 @@ onMounted(async () => {
|
||||
.dataset-view {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
202
frontend/src/views/data/FeatureView.vue
Normal file
202
frontend/src/views/data/FeatureView.vue
Normal file
@ -0,0 +1,202 @@
|
||||
<template>
|
||||
<div class="feature-view">
|
||||
<el-card v-loading="loading" class="method-card-container">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>特征工程方法列表</span>
|
||||
<el-button type="primary" @click="refreshMethods">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-scrollbar height="400px">
|
||||
<el-space wrap>
|
||||
<el-card
|
||||
v-for="method in methods"
|
||||
:key="method"
|
||||
class="method-card"
|
||||
@click="showMethodDetails(method)"
|
||||
>
|
||||
{{ method }}
|
||||
</el-card>
|
||||
</el-space>
|
||||
</el-scrollbar>
|
||||
</el-card>
|
||||
|
||||
<!-- 方法详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="detailsVisible"
|
||||
:title="methodDetails?.name"
|
||||
width="60%"
|
||||
>
|
||||
<div v-loading="detailsLoading">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="方法名称">
|
||||
{{ methodDetails?.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="描述">
|
||||
{{ methodDetails?.description }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="原理" v-if="methodDetails?.principle">
|
||||
{{ methodDetails?.principle }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>优点</h4>
|
||||
<el-tag
|
||||
v-for="(adv, index) in methodDetails?.advantages"
|
||||
:key="index"
|
||||
class="mx-1"
|
||||
type="success"
|
||||
>
|
||||
{{ adv }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>缺点</h4>
|
||||
<el-tag
|
||||
v-for="(dis, index) in methodDetails?.disadvantages"
|
||||
:key="index"
|
||||
class="mx-1"
|
||||
type="danger"
|
||||
>
|
||||
{{ dis }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>适用场景</h4>
|
||||
<el-tag
|
||||
v-for="(scene, index) in methodDetails?.applicable_scenarios"
|
||||
:key="index"
|
||||
class="mx-1"
|
||||
type="warning"
|
||||
>
|
||||
{{ scene }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>参数说明</h4>
|
||||
<el-table :data="methodDetails?.parameters || []" border>
|
||||
<el-table-column prop="name" label="参数名" width="180" />
|
||||
<el-table-column prop="type" label="类型" width="180" />
|
||||
<el-table-column prop="default" label="默认值" width="180">
|
||||
<template #default="{ row }">
|
||||
{{ row.default === null ? 'null' : row.default }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="description" label="说明" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getFeatureMethods, getFeatureMethodDetails } from '@/api/data'
|
||||
import type { FeatureMethod, MethodDetail } from '@/types/data'
|
||||
|
||||
const loading = ref(false)
|
||||
const methods = ref<string[]>([])
|
||||
const detailsVisible = ref(false)
|
||||
const detailsLoading = ref(false)
|
||||
const methodDetails = ref<MethodDetail | null>(null)
|
||||
|
||||
// 加载方法列表
|
||||
const loadMethods = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getFeatureMethods()
|
||||
if (response.status === 'success' && Array.isArray(response.methods)) {
|
||||
// 获取第一个分组的方法列表
|
||||
const featureGroup = response.methods.find(g => g.name === 'feature_engineering_methods')
|
||||
methods.value = featureGroup?.method || []
|
||||
} else {
|
||||
ElMessage.error('加载特征工程方法失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load feature methods:', error)
|
||||
ElMessage.error('加载特征工程方法失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 显示方法详情
|
||||
const showMethodDetails = async (methodName: string) => {
|
||||
detailsVisible.value = true
|
||||
detailsLoading.value = true
|
||||
try {
|
||||
const response = await getFeatureMethodDetails(methodName)
|
||||
if (response.status === 'success' && response.method) {
|
||||
methodDetails.value = response.method
|
||||
} else {
|
||||
ElMessage.error('加载方法详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load method details:', error)
|
||||
ElMessage.error('加载方法详情失败')
|
||||
} finally {
|
||||
detailsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新方法列表
|
||||
const refreshMethods = () => {
|
||||
loadMethods()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadMethods()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.feature-view {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.method-card-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.method-card {
|
||||
width: 200px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.method-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.el-space {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mx-1 {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
253
frontend/src/views/data/PreprocessView.vue
Normal file
253
frontend/src/views/data/PreprocessView.vue
Normal file
@ -0,0 +1,253 @@
|
||||
<template>
|
||||
<div class="preprocess-view">
|
||||
<el-card v-loading="loading" class="method-card-container">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>预处理方法列表</span>
|
||||
<el-button type="primary" @click="refreshMethods">刷新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-tabs v-model="activeTab">
|
||||
<!-- 数据缩放 -->
|
||||
<el-tab-pane label="数据缩放" name="data_scaler">
|
||||
<el-scrollbar height="400px">
|
||||
<el-space wrap>
|
||||
<el-card
|
||||
v-for="method in methodGroups.data_scaler || []"
|
||||
:key="method"
|
||||
class="method-card"
|
||||
@click="showMethodDetails(method)"
|
||||
>
|
||||
{{ method }}
|
||||
</el-card>
|
||||
</el-space>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 缺失值处理 -->
|
||||
<el-tab-pane label="缺失值处理" name="missing_value_handler">
|
||||
<el-scrollbar height="400px">
|
||||
<el-space wrap>
|
||||
<el-card
|
||||
v-for="method in methodGroups.missing_value_handler || []"
|
||||
:key="method"
|
||||
class="method-card"
|
||||
@click="showMethodDetails(method)"
|
||||
>
|
||||
{{ method }}
|
||||
</el-card>
|
||||
</el-space>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
|
||||
<!-- 异常值检测 -->
|
||||
<el-tab-pane label="异常值检测" name="outlier_detector">
|
||||
<el-scrollbar height="400px">
|
||||
<el-space wrap>
|
||||
<el-card
|
||||
v-for="method in methodGroups.outlier_detector || []"
|
||||
:key="method"
|
||||
class="method-card"
|
||||
@click="showMethodDetails(method)"
|
||||
>
|
||||
{{ method }}
|
||||
</el-card>
|
||||
</el-space>
|
||||
</el-scrollbar>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
|
||||
<!-- 方法详情对话框 -->
|
||||
<el-dialog
|
||||
v-model="detailsVisible"
|
||||
:title="methodDetails?.name"
|
||||
width="60%"
|
||||
>
|
||||
<div v-loading="detailsLoading">
|
||||
<el-descriptions :column="1" border>
|
||||
<el-descriptions-item label="方法名称">
|
||||
{{ methodDetails?.name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="描述">
|
||||
{{ methodDetails?.description }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="原理">
|
||||
{{ methodDetails?.principle }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>优点</h4>
|
||||
<el-tag
|
||||
v-for="(adv, index) in methodDetails?.advantages"
|
||||
:key="index"
|
||||
class="mx-1"
|
||||
type="success"
|
||||
>
|
||||
{{ adv }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>缺点</h4>
|
||||
<el-tag
|
||||
v-for="(dis, index) in methodDetails?.disadvantages"
|
||||
:key="index"
|
||||
class="mx-1"
|
||||
type="danger"
|
||||
>
|
||||
{{ dis }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>适用场景</h4>
|
||||
<el-tag
|
||||
v-for="(scene, index) in methodDetails?.applicable_scenarios"
|
||||
:key="index"
|
||||
class="mx-1"
|
||||
type="warning"
|
||||
>
|
||||
{{ scene }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h4>参数说明</h4>
|
||||
<el-table :data="methodDetails?.parameters || []" border>
|
||||
<el-table-column prop="name" label="参数名" />
|
||||
<el-table-column prop="type" label="类型" />
|
||||
<el-table-column prop="default" label="默认值" />
|
||||
<el-table-column prop="description" label="说明" />
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { getPreprocessMethods, getPreprocessMethodDetails } from '@/api/data'
|
||||
import type { PreprocessMethod, MethodDetail } from '@/types/data'
|
||||
|
||||
// 修改 PreprocessMethod 接口以匹配后端返回的数据结构
|
||||
interface PreprocessMethodGroup {
|
||||
name: string
|
||||
description: string
|
||||
method: string[]
|
||||
}
|
||||
|
||||
const activeTab = ref('missing_value_handler')
|
||||
const loading = ref(false)
|
||||
const methodGroups = ref<Record<string, string[]>>({
|
||||
data_scaler: [],
|
||||
missing_value_handler: [],
|
||||
outlier_detector: []
|
||||
})
|
||||
const detailsVisible = ref(false)
|
||||
const detailsLoading = ref(false)
|
||||
const methodDetails = ref<MethodDetail | null>(null)
|
||||
|
||||
// 加载方法列表
|
||||
const loadMethods = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await getPreprocessMethods()
|
||||
if (response.status === 'success' && Array.isArray(response.methods)) {
|
||||
// 将返回的数据按类型分组
|
||||
methodGroups.value = response.methods.reduce((groups: Record<string, string[]>, group: PreprocessMethodGroup) => {
|
||||
groups[group.name] = group.method
|
||||
return groups
|
||||
}, {
|
||||
data_scaler: [],
|
||||
missing_value_handler: [],
|
||||
outlier_detector: []
|
||||
})
|
||||
} else {
|
||||
ElMessage.error('加载预处理方法失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load preprocess methods:', error)
|
||||
ElMessage.error('加载预处理方法失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 显示方法详情
|
||||
const showMethodDetails = async (methodName: string) => {
|
||||
detailsVisible.value = true
|
||||
detailsLoading.value = true
|
||||
try {
|
||||
const response = await getPreprocessMethodDetails(methodName)
|
||||
if (response.status === 'success' && response.method) {
|
||||
methodDetails.value = response.method
|
||||
} else {
|
||||
ElMessage.error('加载方法详情失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load method details:', error)
|
||||
ElMessage.error('加载方法详情失败')
|
||||
} finally {
|
||||
detailsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新方法列表
|
||||
const refreshMethods = () => {
|
||||
loadMethods()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadMethods()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.preprocess-view {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.method-card-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.method-card {
|
||||
width: 200px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.method-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,.1);
|
||||
}
|
||||
|
||||
.el-space {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.mt-4 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mx-1 {
|
||||
margin: 0 0.25rem;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
0
function_old/data_process/__init__.py
Normal file
0
function_old/data_process/__init__.py
Normal file
BIN
function_old/data_process/__pycache__/__init__.cpython-39.pyc
Normal file
BIN
function_old/data_process/__pycache__/__init__.cpython-39.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
66
function_old/data_process/data_cleaner.py
Normal file
66
function_old/data_process/data_cleaner.py
Normal file
@ -0,0 +1,66 @@
|
||||
from .data_processor import DataProcessor
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from typing import Dict, List
|
||||
from sklearn.impute import SimpleImputer
|
||||
from sklearn.ensemble import IsolationForest
|
||||
from scipy import stats
|
||||
|
||||
class DataCleaner(DataProcessor):
|
||||
"""数据清洗类"""
|
||||
|
||||
def __init__(self, config: Dict = None):
|
||||
super().__init__(config)
|
||||
self.missing_value_methods = {
|
||||
'mean': SimpleImputer(strategy='mean'),
|
||||
'median': SimpleImputer(strategy='median'),
|
||||
'mode': SimpleImputer(strategy='most_frequent'),
|
||||
'constant': SimpleImputer(strategy='constant')
|
||||
}
|
||||
|
||||
def handle_missing_values(self, df: pd.DataFrame, method: str = 'mean', columns: List[str] = None) -> pd.DataFrame:
|
||||
"""处理缺失值"""
|
||||
try:
|
||||
if columns is None:
|
||||
columns = df.select_dtypes(include=[np.number]).columns
|
||||
|
||||
if method not in self.missing_value_methods:
|
||||
raise ValueError(f"Unsupported method: {method}")
|
||||
|
||||
imputer = self.missing_value_methods[method]
|
||||
df[columns] = imputer.fit_transform(df[columns])
|
||||
|
||||
self.logger.info(f"Successfully handled missing values using {method} method")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error handling missing values: {str(e)}")
|
||||
raise
|
||||
|
||||
def remove_duplicates(self, df: pd.DataFrame, subset: List[str] = None) -> pd.DataFrame:
|
||||
"""删除重复值"""
|
||||
try:
|
||||
original_shape = df.shape
|
||||
df = df.drop_duplicates(subset=subset)
|
||||
self.logger.info(f"Removed {original_shape[0] - df.shape[0]} duplicate rows")
|
||||
return df
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error removing duplicates: {str(e)}")
|
||||
raise
|
||||
|
||||
def detect_outliers(self, df: pd.DataFrame, method: str = 'zscore', threshold: float = 3) -> pd.DataFrame:
|
||||
"""检测异常值"""
|
||||
try:
|
||||
if method == 'zscore':
|
||||
z_scores = np.abs(stats.zscore(df.select_dtypes(include=[np.number])))
|
||||
outliers = (z_scores > threshold).any(axis=1)
|
||||
elif method == 'isolation_forest':
|
||||
iso_forest = IsolationForest(contamination=0.1, random_state=42)
|
||||
outliers = iso_forest.fit_predict(df.select_dtypes(include=[np.number])) == -1
|
||||
|
||||
self.logger.info(f"Detected {sum(outliers)} outliers using {method} method")
|
||||
return df[~outliers]
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error detecting outliers: {str(e)}")
|
||||
raise
|
||||
63
function_old/data_process/data_processor.py
Normal file
63
function_old/data_process/data_processor.py
Normal file
@ -0,0 +1,63 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from typing import Dict, List, Union, Optional
|
||||
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
|
||||
from sklearn.impute import SimpleImputer
|
||||
from sklearn.model_selection import train_test_split
|
||||
import logging
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
class DataProcessor:
|
||||
"""数据处理基类"""
|
||||
|
||||
def __init__(self, config: Dict = None):
|
||||
self.config = config or {}
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self._setup_logging()
|
||||
|
||||
def _setup_logging(self):
|
||||
"""设置日志"""
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
|
||||
def load_data(self, file_path: str) -> pd.DataFrame:
|
||||
"""加载数据"""
|
||||
try:
|
||||
file_type = file_path.split('.')[-1].lower()
|
||||
if file_type == 'csv':
|
||||
df = pd.read_csv(file_path, **self.config.get('csv_params', {}))
|
||||
elif file_type == 'parquet':
|
||||
df = pd.read_parquet(file_path)
|
||||
elif file_type == 'hdf5':
|
||||
df = pd.read_hdf(file_path)
|
||||
else:
|
||||
raise ValueError(f"Unsupported file type: {file_type}")
|
||||
|
||||
self.logger.info(f"Successfully loaded data from {file_path}")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error loading data: {str(e)}")
|
||||
raise
|
||||
|
||||
def save_data(self, df: pd.DataFrame, file_path: str):
|
||||
"""保存数据"""
|
||||
try:
|
||||
file_type = file_path.split('.')[-1].lower()
|
||||
if file_type == 'csv':
|
||||
df.to_csv(file_path, index=False)
|
||||
elif file_type == 'parquet':
|
||||
df.to_parquet(file_path)
|
||||
elif file_type == 'hdf5':
|
||||
df.to_hdf(file_path, key='data')
|
||||
|
||||
self.logger.info(f"Successfully saved data to {file_path}")
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error saving data: {str(e)}")
|
||||
raise
|
||||
49
function_old/data_process/data_splitter.py
Normal file
49
function_old/data_process/data_splitter.py
Normal file
@ -0,0 +1,49 @@
|
||||
from .data_processor import DataProcessor
|
||||
import pandas as pd
|
||||
from typing import Dict, Tuple
|
||||
from sklearn.model_selection import train_test_split
|
||||
|
||||
class DataSplitter(DataProcessor):
|
||||
"""数据集划分类"""
|
||||
|
||||
def __init__(self, config: Dict = None):
|
||||
super().__init__(config)
|
||||
|
||||
def train_val_test_split(
|
||||
self,
|
||||
df: pd.DataFrame,
|
||||
target: str,
|
||||
test_size: float = 0.2,
|
||||
val_size: float = 0.2,
|
||||
random_state: int = 42
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
||||
"""划分训练集、验证集和测试集"""
|
||||
try:
|
||||
# 首先划分训练集和测试集
|
||||
train_val, test = train_test_split(
|
||||
df,
|
||||
test_size=test_size,
|
||||
random_state=random_state,
|
||||
stratify=df[target] if df[target].dtype == 'object' else None
|
||||
)
|
||||
|
||||
# 再划分训练集和验证集
|
||||
train, val = train_test_split(
|
||||
train_val,
|
||||
test_size=val_size,
|
||||
random_state=random_state,
|
||||
stratify=train_val[target] if train_val[target].dtype == 'object' else None
|
||||
)
|
||||
|
||||
self.logger.info(f"""
|
||||
Data split complete:
|
||||
- Training set: {train.shape[0]} samples
|
||||
- Validation set: {val.shape[0]} samples
|
||||
- Test set: {test.shape[0]} samples
|
||||
""")
|
||||
|
||||
return train, val, test
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error splitting data: {str(e)}")
|
||||
raise
|
||||
77
function_old/data_process/feature_engineer.py
Normal file
77
function_old/data_process/feature_engineer.py
Normal file
@ -0,0 +1,77 @@
|
||||
from .data_processor import DataProcessor
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from typing import Dict, List
|
||||
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
|
||||
from sklearn.feature_selection import SelectKBest, chi2, f_classif
|
||||
|
||||
class FeatureEngineer(DataProcessor):
|
||||
"""特征工程类"""
|
||||
|
||||
def __init__(self, config: Dict = None):
|
||||
super().__init__(config)
|
||||
self.scalers = {
|
||||
'standard': StandardScaler(),
|
||||
'minmax': MinMaxScaler(),
|
||||
'robust': RobustScaler()
|
||||
}
|
||||
|
||||
def scale_features(self, df: pd.DataFrame, method: str = 'standard', columns: List[str] = None) -> pd.DataFrame:
|
||||
"""特征缩放"""
|
||||
try:
|
||||
if columns is None:
|
||||
columns = df.select_dtypes(include=[np.number]).columns
|
||||
|
||||
if method not in self.scalers:
|
||||
raise ValueError(f"Unsupported scaling method: {method}")
|
||||
|
||||
scaler = self.scalers[method]
|
||||
df[columns] = scaler.fit_transform(df[columns])
|
||||
|
||||
self.logger.info(f"Successfully scaled features using {method} method")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error scaling features: {str(e)}")
|
||||
raise
|
||||
|
||||
def select_features(self, df: pd.DataFrame, target: str, method: str = 'chi2', k: int = 10) -> pd.DataFrame:
|
||||
"""特征选择"""
|
||||
try:
|
||||
X = df.drop(columns=[target])
|
||||
y = df[target]
|
||||
|
||||
if method == 'chi2':
|
||||
# 要求输入x不能为负的
|
||||
selector = SelectKBest(chi2, k=k)
|
||||
elif method == 'f_classif':
|
||||
selector = SelectKBest(f_classif, k=k)
|
||||
else:
|
||||
raise ValueError(f"Unsupported feature selection method: {method}")
|
||||
|
||||
X_selected = selector.fit_transform(X, y)
|
||||
selected_features = X.columns[selector.get_support()].tolist()
|
||||
|
||||
self.logger.info(f"Selected {len(selected_features)} features")
|
||||
return df[selected_features + [target]]
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error selecting features: {str(e)}")
|
||||
raise
|
||||
|
||||
def create_datetime_features(self, df: pd.DataFrame, datetime_column: str) -> pd.DataFrame:
|
||||
"""创建时间特征"""
|
||||
try:
|
||||
df[datetime_column] = pd.to_datetime(df[datetime_column])
|
||||
df[f'{datetime_column}_year'] = df[datetime_column].dt.year
|
||||
df[f'{datetime_column}_month'] = df[datetime_column].dt.month
|
||||
df[f'{datetime_column}_day'] = df[datetime_column].dt.day
|
||||
df[f'{datetime_column}_weekday'] = df[datetime_column].dt.weekday
|
||||
df[f'{datetime_column}_is_weekend'] = df[datetime_column].dt.weekday.isin([5, 6])
|
||||
|
||||
self.logger.info(f"Created datetime features from {datetime_column}")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error creating datetime features: {str(e)}")
|
||||
raise
|
||||
34
function_old/data_process/test_data_processor.py
Normal file
34
function_old/data_process/test_data_processor.py
Normal file
@ -0,0 +1,34 @@
|
||||
import unittest
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from data_processor import DataProcessor
|
||||
from data_cleaner import DataCleaner
|
||||
from feature_engineer import FeatureEngineer
|
||||
from data_splitter import DataSplitter
|
||||
|
||||
class TestDataProcessor(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# 创建测试数据
|
||||
self.test_data = pd.DataFrame({
|
||||
'feature1': [1, 2, np.nan, 4, 5],
|
||||
'feature2': ['A', 'B', 'A', 'B', 'C'],
|
||||
'target': [0, 1, 0, 1, 0]
|
||||
})
|
||||
|
||||
def test_data_cleaner(self):
|
||||
cleaner = DataCleaner()
|
||||
cleaned_data = cleaner.handle_missing_values(self.test_data.copy())
|
||||
self.assertFalse(cleaned_data.isnull().any().any())
|
||||
|
||||
def test_feature_engineer(self):
|
||||
engineer = FeatureEngineer()
|
||||
scaled_data = engineer.scale_features(self.test_data.copy())
|
||||
self.assertTrue('feature1' in scaled_data.columns)
|
||||
|
||||
def test_data_splitter(self):
|
||||
splitter = DataSplitter()
|
||||
train, val, test = splitter.train_val_test_split(self.test_data.copy(), 'target')
|
||||
self.assertEqual(len(train) + len(val) + len(test), len(self.test_data))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
2
function_old/data_process/ttt.py
Normal file
2
function_old/data_process/ttt.py
Normal file
@ -0,0 +1,2 @@
|
||||
from data_processor import DataProcessor
|
||||
|
||||
93
function_old/test_data_processor.py
Normal file
93
function_old/test_data_processor.py
Normal file
@ -0,0 +1,93 @@
|
||||
import unittest
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from pathlib import Path
|
||||
from function.data_processor_date import DataProcessor
|
||||
|
||||
class TestDataProcessor(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.processor = DataProcessor()
|
||||
|
||||
# 创建测试数据
|
||||
self.test_data = pd.DataFrame({
|
||||
'feature1': [1, 2, np.nan, 4, 5],
|
||||
'feature2': [10, 20, 30, 40, 50],
|
||||
'target': [0, 1, 0, 1, 0]
|
||||
})
|
||||
|
||||
# 保存测试数据
|
||||
self.input_path = 'dataset/dataset_raw/test_data.csv'
|
||||
Path(self.input_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
self.test_data.to_csv(self.input_path, index=False)
|
||||
|
||||
# 设置输出目录
|
||||
self.output_dir = 'dataset/dataset_processed'
|
||||
|
||||
def test_process_dataset(self):
|
||||
# 定义处理方法
|
||||
cleaning_methods = [
|
||||
{
|
||||
'method_name': 'SimpleImputer',
|
||||
'params': {'strategy': 'mean'}
|
||||
}
|
||||
]
|
||||
|
||||
feature_methods = [
|
||||
{
|
||||
'method_name': 'StandardScaler',
|
||||
'params': {}
|
||||
}
|
||||
]
|
||||
|
||||
split_params = {
|
||||
'test_size': 0.2,
|
||||
'val_size': 0.2
|
||||
}
|
||||
|
||||
# 处理数据集
|
||||
result = self.processor.process_dataset(
|
||||
self.input_path,
|
||||
self.output_dir,
|
||||
cleaning_methods,
|
||||
feature_methods,
|
||||
split_params
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(result['status'], 'success')
|
||||
self.assertIn('process_record', result)
|
||||
|
||||
# 验证输出文件
|
||||
record = result['process_record']
|
||||
self.assertTrue(Path(record['output_files']['train']).exists())
|
||||
self.assertTrue(Path(record['output_files']['validation']).exists())
|
||||
self.assertTrue(Path(record['output_files']['test']).exists())
|
||||
|
||||
def test_invalid_method(self):
|
||||
# 测试无效的方法名
|
||||
cleaning_methods = [
|
||||
{
|
||||
'method_name': 'InvalidMethod',
|
||||
'params': {}
|
||||
}
|
||||
]
|
||||
|
||||
result = self.processor.process_dataset(
|
||||
self.input_path,
|
||||
self.output_dir,
|
||||
cleaning_methods,
|
||||
[],
|
||||
{'test_size': 0.2, 'val_size': 0.2}
|
||||
)
|
||||
|
||||
self.assertEqual(result['status'], 'error')
|
||||
|
||||
def tearDown(self):
|
||||
# 清理测试文件
|
||||
try:
|
||||
Path(self.input_path).unlink()
|
||||
except:
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
49
function_old/test_method_reader.py
Normal file
49
function_old/test_method_reader.py
Normal file
@ -0,0 +1,49 @@
|
||||
import unittest
|
||||
from function.method_reader_date_process import MethodReader
|
||||
|
||||
class TestMethodReader(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.reader = MethodReader()
|
||||
|
||||
def test_get_preprocessing_methods(self):
|
||||
result = self.reader.get_preprocessing_methods()
|
||||
self.assertEqual(result['status'], 'success')
|
||||
self.assertIsInstance(result['methods'], list)
|
||||
|
||||
# 检查返回的方法列表
|
||||
methods = result['methods']
|
||||
self.assertTrue(any(m['name'] == 'data_scaler' for m in methods))
|
||||
self.assertTrue(any(m['name'] == 'missing_value_handler' for m in methods))
|
||||
self.assertTrue(any(m['name'] == 'outlier_detector' for m in methods))
|
||||
|
||||
def test_get_method_details(self):
|
||||
# 测试获取StandardScaler的详细信息
|
||||
result = self.reader.get_method_details('StandardScaler')
|
||||
self.assertEqual(result['status'], 'success')
|
||||
self.assertEqual(result['method']['name'], 'StandardScaler')
|
||||
|
||||
# 检查返回的详细信息字段
|
||||
method = result['method']
|
||||
self.assertIn('description', method)
|
||||
self.assertIn('principle', method)
|
||||
self.assertIn('advantages', method)
|
||||
self.assertIn('disadvantages', method)
|
||||
self.assertIn('applicable_scenarios', method)
|
||||
self.assertIn('parameters', method)
|
||||
|
||||
# 检查参数信息
|
||||
parameters = method['parameters']
|
||||
self.assertIsInstance(parameters, list)
|
||||
if parameters:
|
||||
param = parameters[0]
|
||||
self.assertIn('name', param)
|
||||
self.assertIn('type', param)
|
||||
self.assertIn('default', param)
|
||||
self.assertIn('description', param)
|
||||
|
||||
# 测试获取不存在的方法
|
||||
result = self.reader.get_method_details('NonExistentMethod')
|
||||
self.assertEqual(result['status'], 'error')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
99
function_old/test_model_manager.py
Normal file
99
function_old/test_model_manager.py
Normal file
@ -0,0 +1,99 @@
|
||||
import pytest
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
import mlflow
|
||||
from pathlib import Path
|
||||
from function.model_manager import ModelManager
|
||||
|
||||
class TestModelManager:
|
||||
@pytest.fixture
|
||||
def model_manager(self):
|
||||
return ModelManager()
|
||||
|
||||
@pytest.fixture
|
||||
def sample_data(self):
|
||||
# 创建测试数据
|
||||
np.random.seed(42)
|
||||
n_samples = 100
|
||||
X = np.random.randn(n_samples, 4)
|
||||
y = (X[:, 0] + X[:, 1] > 0).astype(int)
|
||||
|
||||
# 保存测试数据
|
||||
data_dir = Path("dataset/dataset_processed/test_data")
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
df = pd.DataFrame(X, columns=[f'feature_{i}' for i in range(4)])
|
||||
df['label'] = y
|
||||
|
||||
data_path = data_dir / "test_data.csv"
|
||||
df.to_csv(data_path, index=False)
|
||||
|
||||
return str(data_path)
|
||||
|
||||
@pytest.fixture
|
||||
def trained_model(self, sample_data):
|
||||
# 训练一个简单的模型用于测试
|
||||
from sklearn.ensemble import RandomForestClassifier
|
||||
|
||||
# 加载数据
|
||||
data = pd.read_csv(sample_data)
|
||||
X = data.drop('label', axis=1).values
|
||||
y = data['label'].values
|
||||
|
||||
# 训练模型
|
||||
model = RandomForestClassifier(n_estimators=10, random_state=42)
|
||||
model.fit(X, y)
|
||||
|
||||
# 使用MLflow记录模型
|
||||
with mlflow.start_run() as run:
|
||||
mlflow.sklearn.log_model(model, "model")
|
||||
mlflow.log_param("algorithm", "RandomForestClassifier")
|
||||
|
||||
return run.info.run_id
|
||||
|
||||
def test_predict(self, model_manager, sample_data, trained_model):
|
||||
# 设置输出路径
|
||||
output_dir = Path("predictions/test")
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
output_path = str(output_dir / "test_predictions.csv")
|
||||
|
||||
# 执行预测
|
||||
result = model_manager.predict(
|
||||
run_id=trained_model,
|
||||
data_path=sample_data,
|
||||
output_path=output_path,
|
||||
metrics=['accuracy', 'f1']
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
assert result['status'] == 'success'
|
||||
assert 'prediction' in result
|
||||
assert Path(result['prediction']['output_file']).exists()
|
||||
assert result['prediction']['samples_count'] == 100
|
||||
assert 'accuracy' in result['prediction']['metrics']
|
||||
assert 'f1' in result['prediction']['metrics']
|
||||
|
||||
# 验证预测结果格式
|
||||
predictions = pd.read_csv(output_path)
|
||||
assert 'prediction' in predictions.columns
|
||||
assert len(predictions) == 100
|
||||
|
||||
def test_predict_invalid_run_id(self, model_manager, sample_data):
|
||||
result = model_manager.predict(
|
||||
run_id="invalid_run_id",
|
||||
data_path=sample_data,
|
||||
output_path="predictions/test/invalid.csv"
|
||||
)
|
||||
|
||||
assert result['status'] == 'error'
|
||||
assert '未找到运行ID' in result['message']
|
||||
|
||||
def test_predict_invalid_data_path(self, model_manager, trained_model):
|
||||
result = model_manager.predict(
|
||||
run_id=trained_model,
|
||||
data_path="invalid/path/data.csv",
|
||||
output_path="predictions/test/invalid.csv"
|
||||
)
|
||||
|
||||
assert result['status'] == 'error'
|
||||
assert '数据加载失败' in result['message']
|
||||
85
function_old/test_model_trainer.py
Normal file
85
function_old/test_model_trainer.py
Normal file
@ -0,0 +1,85 @@
|
||||
import unittest
|
||||
import numpy as np
|
||||
from function.model_trainer import ModelTrainer
|
||||
|
||||
class TestModelTrainer(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.trainer = ModelTrainer()
|
||||
|
||||
# 创建测试数据
|
||||
np.random.seed(42)
|
||||
self.X_train = np.random.randn(100, 5)
|
||||
self.y_train = np.random.randint(0, 2, 100)
|
||||
self.X_val = np.random.randn(30, 5)
|
||||
self.y_val = np.random.randint(0, 2, 30)
|
||||
|
||||
def test_train_model(self):
|
||||
# 准备训练数据
|
||||
train_data = {
|
||||
'features': self.X_train,
|
||||
'labels': self.y_train
|
||||
}
|
||||
|
||||
val_data = {
|
||||
'features': self.X_val,
|
||||
'labels': self.y_val
|
||||
}
|
||||
|
||||
# 模型配置
|
||||
model_config = {
|
||||
'algorithm': 'LogisticRegression',
|
||||
'task_type': 'classification',
|
||||
'params': {
|
||||
'random_state': 42
|
||||
}
|
||||
}
|
||||
|
||||
# 训练模型
|
||||
result = self.trainer.train_model(
|
||||
train_data,
|
||||
val_data,
|
||||
model_config,
|
||||
'test_experiment'
|
||||
)
|
||||
|
||||
# 验证结果
|
||||
self.assertEqual(result['status'], 'success')
|
||||
self.assertIn('run_id', result)
|
||||
self.assertIn('metrics', result)
|
||||
|
||||
# 验证指标
|
||||
metrics = result['metrics']
|
||||
self.assertIn('accuracy', metrics)
|
||||
self.assertIn('precision', metrics)
|
||||
self.assertIn('recall', metrics)
|
||||
self.assertIn('f1', metrics)
|
||||
|
||||
def test_invalid_algorithm(self):
|
||||
# 测试无效的算法名
|
||||
train_data = {
|
||||
'features': self.X_train,
|
||||
'labels': self.y_train
|
||||
}
|
||||
|
||||
val_data = {
|
||||
'features': self.X_val,
|
||||
'labels': self.y_val
|
||||
}
|
||||
|
||||
model_config = {
|
||||
'algorithm': 'InvalidAlgorithm',
|
||||
'task_type': 'classification',
|
||||
'params': {}
|
||||
}
|
||||
|
||||
result = self.trainer.train_model(
|
||||
train_data,
|
||||
val_data,
|
||||
model_config,
|
||||
'test_experiment'
|
||||
)
|
||||
|
||||
self.assertEqual(result['status'], 'error')
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
86
function_old/test_system_monitor.py
Normal file
86
function_old/test_system_monitor.py
Normal file
@ -0,0 +1,86 @@
|
||||
import pytest
|
||||
from function.system_monitor import SystemMonitor
|
||||
from typing import Dict
|
||||
|
||||
class TestSystemMonitor:
|
||||
@pytest.fixture
|
||||
def system_monitor(self):
|
||||
return SystemMonitor()
|
||||
|
||||
def test_get_system_resources(self, system_monitor):
|
||||
"""测试获取系统资源信息"""
|
||||
result = system_monitor.get_system_resources()
|
||||
|
||||
# 验证返回格式
|
||||
assert isinstance(result, dict)
|
||||
assert 'status' in result
|
||||
assert result['status'] == 'success'
|
||||
assert 'resources' in result
|
||||
assert 'timestamp' in result
|
||||
|
||||
resources = result['resources']
|
||||
|
||||
# 验证GPU信息
|
||||
assert 'gpu' in resources
|
||||
if resources['gpu']: # 如果有GPU
|
||||
gpu = resources['gpu'][0]
|
||||
assert 'id' in gpu
|
||||
assert 'name' in gpu
|
||||
assert 'memory' in gpu
|
||||
assert 'utilization' in gpu
|
||||
assert 'temperature' in gpu
|
||||
|
||||
# 验证CPU信息
|
||||
assert 'cpu' in resources
|
||||
cpu = resources['cpu']
|
||||
assert 'count' in cpu
|
||||
assert 'utilization' in cpu
|
||||
assert 'memory' in cpu
|
||||
assert 'swap' in cpu
|
||||
|
||||
# 验证内存信息
|
||||
memory = cpu['memory']
|
||||
assert 'total' in memory
|
||||
assert 'used' in memory
|
||||
assert 'free' in memory
|
||||
assert 'percent' in memory
|
||||
assert memory['total'] > 0
|
||||
assert 0 <= memory['percent'] <= 100
|
||||
|
||||
# 验证磁盘信息
|
||||
assert 'disk' in resources
|
||||
assert len(resources['disk']) > 0
|
||||
for mount_point, disk_info in resources['disk'].items():
|
||||
assert 'total' in disk_info
|
||||
assert 'used' in disk_info
|
||||
assert 'free' in disk_info
|
||||
assert 'percent' in disk_info
|
||||
assert disk_info['total'] > 0
|
||||
assert 0 <= disk_info['percent'] <= 100
|
||||
|
||||
# 验证进程信息
|
||||
assert 'processes' in resources
|
||||
processes = resources['processes']
|
||||
assert 'total' in processes
|
||||
assert 'running' in processes
|
||||
assert 'sleeping' in processes
|
||||
assert processes['total'] > 0
|
||||
assert processes['running'] >= 0
|
||||
assert processes['sleeping'] >= 0
|
||||
|
||||
def test_error_handling(self, system_monitor, monkeypatch):
|
||||
"""测试错误处理"""
|
||||
def mock_gpu_error(*args, **kwargs):
|
||||
raise Exception("GPU query failed")
|
||||
|
||||
# 模拟GPU查询错误
|
||||
monkeypatch.setattr(system_monitor, '_get_gpu_info', mock_gpu_error)
|
||||
|
||||
result = system_monitor.get_system_resources()
|
||||
assert result['status'] == 'success' # 即使GPU查询失败,其他资源信息仍应返回
|
||||
assert result['resources']['gpu'] == [] # GPU信息应为空列表
|
||||
|
||||
# 验证其他资源信息仍然可用
|
||||
assert 'cpu' in result['resources']
|
||||
assert 'disk' in result['resources']
|
||||
assert 'processes' in result['resources']
|
||||
6
function_old/处理乳腺癌数据集.py
Normal file
6
function_old/处理乳腺癌数据集.py
Normal file
@ -0,0 +1,6 @@
|
||||
import pandas as pd
|
||||
from sklearn.datasets import load_breast_cancer
|
||||
cancer = load_breast_cancer()
|
||||
df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
|
||||
df['target'] = cancer.target
|
||||
df.to_csv('./dataset/breast_cancer.csv', index=False)
|
||||
Loading…
Reference in New Issue
Block a user