前端--获取和展示数据完成

This commit is contained in:
haotian 2025-02-25 16:05:06 +08:00
parent bf574b3dc5
commit 9fd425173c
36 changed files with 1739 additions and 210 deletions

View File

@ -75,6 +75,7 @@ async def get_datasets():
"""获取可用数据集列表"""
try:
datasets = data_manager.get_dataset()
print("datasets", datasets)
return {
"status": "success",
"datasets": datasets

View File

@ -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
View 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>

View File

@ -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',

View File

@ -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>

View 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>

View File

@ -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>

View File

@ -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',

View File

@ -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>
}

View 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>

View File

@ -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>

View 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>

View 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>

View File

View 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

View 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

View 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

View 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

View 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()

View File

@ -0,0 +1,2 @@
from data_processor import DataProcessor

View 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()

View 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()

View 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']

View 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()

View 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']

View 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)