Browse Source

feat: 完成自动机器学习复制功能

pull/146/head
cp3hnu 1 year ago
parent
commit
865af276f3
8 changed files with 200 additions and 171 deletions
  1. +22
    -5
      react-ui/src/pages/AutoML/Create/index.tsx
  2. +15
    -14
      react-ui/src/pages/AutoML/List/index.tsx
  3. +1
    -1
      react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx
  4. +18
    -13
      react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx
  5. +133
    -133
      react-ui/src/pages/AutoML/components/CreateForm/index.less
  6. +5
    -4
      react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx
  7. +4
    -1
      react-ui/src/pages/AutoML/types.ts
  8. +2
    -0
      react-ui/src/utils/sessionStorage.ts

+ 22
- 5
react-ui/src/pages/AutoML/Create/index.tsx View File

@@ -10,6 +10,7 @@ import { addAutoMLReq, getDatasetInfoReq, updateAutoMLReq } from '@/services/aut
import { parseJsonText, trimCharacter } from '@/utils'; import { parseJsonText, trimCharacter } from '@/utils';
import { safeInvoke } from '@/utils/functional'; import { safeInvoke } from '@/utils/functional';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import { useNavigate, useParams } from '@umijs/max'; import { useNavigate, useParams } from '@umijs/max';
import { App, Button, Form } from 'antd'; import { App, Button, Form } from 'antd';
import { omit } from 'lodash'; import { omit } from 'lodash';
@@ -29,13 +30,25 @@ function CreateAutoML() {
const id = safeInvoke(Number)(params.id); const id = safeInvoke(Number)(params.id);


useEffect(() => { useEffect(() => {
if (id) {
getAutoMLInfo();
// 复制和新建
const recordId = SessionStorage.getItem(SessionStorage.autoMLRecordIDKey);
if (recordId && !Number.isNaN(Number(recordId))) {
getAutoMLInfo(Number(recordId), true);
}
return () => {
SessionStorage.removeItem(SessionStorage.autoMLRecordIDKey);
};
}, []);

useEffect(() => {
// 编辑
if (id && !Number.isNaN(id)) {
getAutoMLInfo(id, false);
} }
}, [id]); }, [id]);


// 获取服务详情 // 获取服务详情
const getAutoMLInfo = async () => {
const getAutoMLInfo = async (id: number, isCopy = false) => {
const [res] = await to(getDatasetInfoReq({ id })); const [res] = await to(getDatasetInfoReq({ id }));
if (res && res.data) { if (res && res.data) {
const autoMLInfo: AutoMLData = res.data; const autoMLInfo: AutoMLData = res.data;
@@ -47,6 +60,8 @@ function CreateAutoML() {
exclude_feature_preprocessor: exclude_feature_preprocessor_str, exclude_feature_preprocessor: exclude_feature_preprocessor_str,
exclude_regressor: exclude_regressor_str, exclude_regressor: exclude_regressor_str,
metrics: metrics_str, metrics: metrics_str,
ml_name: ml_name_str,
...rest
} = autoMLInfo; } = autoMLInfo;
const include_classifier = include_classifier_str?.split(',').filter(Boolean); const include_classifier = include_classifier_str?.split(',').filter(Boolean);
const include_feature_preprocessor = include_feature_preprocessor_str const include_feature_preprocessor = include_feature_preprocessor_str
@@ -63,9 +78,10 @@ function CreateAutoML() {
name: key, name: key,
value, value,
})); }));
const ml_name = isCopy ? `${ml_name_str}-copy` : ml_name_str;


const formData = { const formData = {
...autoMLInfo,
...rest,
include_classifier, include_classifier,
include_feature_preprocessor, include_feature_preprocessor,
include_regressor, include_regressor,
@@ -73,6 +89,7 @@ function CreateAutoML() {
exclude_feature_preprocessor, exclude_feature_preprocessor,
exclude_regressor, exclude_regressor,
metrics, metrics,
ml_name,
}; };


form.setFieldsValue(formData); form.setFieldsValue(formData);
@@ -135,7 +152,7 @@ function CreateAutoML() {
let buttonText = '新建'; let buttonText = '新建';
let title = '新增实验'; let title = '新增实验';
if (id) { if (id) {
title = '更新实验';
title = '编辑实验';
buttonText = '更新'; buttonText = '更新';
} }




+ 15
- 14
react-ui/src/pages/AutoML/List/index.tsx View File

@@ -9,6 +9,7 @@ import { useCacheState } from '@/hooks/pageCacheState';
import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML'; import { deleteAutoMLReq, getAutoMLListReq } from '@/services/autoML';
import themes from '@/styles/theme.less'; import themes from '@/styles/theme.less';
import { to } from '@/utils/promise'; import { to } from '@/utils/promise';
import SessionStorage from '@/utils/sessionStorage';
import tableCellRender, { TableCellValueType } from '@/utils/table'; import tableCellRender, { TableCellValueType } from '@/utils/table';
import { modalConfirm } from '@/utils/ui'; import { modalConfirm } from '@/utils/ui';
import { useNavigate } from '@umijs/max'; import { useNavigate } from '@umijs/max';
@@ -90,7 +91,7 @@ function AutoMLList() {
// 处理删除 // 处理删除
const handleAutoMLDelete = (record: AutoMLData) => { const handleAutoMLDelete = (record: AutoMLData) => {
modalConfirm({ modalConfirm({
title: '删除后,该服务将不可恢复',
title: '删除后,该实验将不可恢复',
content: '是否确认删除?', content: '是否确认删除?',
onOk: () => { onOk: () => {
deleteService(record); deleteService(record);
@@ -99,15 +100,21 @@ function AutoMLList() {
}; };


// 创建、编辑 // 创建、编辑
const createService = (record?: AutoMLData) => {
const createService = (record?: AutoMLData, isCopy: boolean = false) => {
setCacheState({ setCacheState({
pagination, pagination,
searchText, searchText,
}); });


if (record) { if (record) {
navigate(`/pipeline/autoML/edit/${record.id}`);
if (isCopy) {
SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, record.id, false);
navigate(`/pipeline/autoML/create`);
} else {
navigate(`/pipeline/autoML/edit/${record.id}`);
}
} else { } else {
SessionStorage.setItem(SessionStorage.autoMLRecordIDKey, '', false);
navigate(`/pipeline/autoML/create`); navigate(`/pipeline/autoML/create`);
} }
}; };
@@ -139,7 +146,7 @@ function AutoMLList() {
title: '序号', title: '序号',
dataIndex: 'index', dataIndex: 'index',
key: 'index', key: 'index',
width: '20%',
width: 80,
render: tableCellRender(false, TableCellValueType.Index, { render: tableCellRender(false, TableCellValueType.Index, {
page: pagination.current! - 1, page: pagination.current! - 1,
pageSize: pagination.pageSize!, pageSize: pagination.pageSize!,
@@ -166,7 +173,7 @@ function AutoMLList() {
title: '状态', title: '状态',
dataIndex: 'run_state', dataIndex: 'run_state',
key: 'run_state', key: 'run_state',
width: '20%',
width: 100,
render: RunStatusCell, render: RunStatusCell,
}, },
{ {
@@ -207,7 +214,7 @@ function AutoMLList() {
size="small" size="small"
key="edit" key="edit"
icon={<KFIcon type="icon-bianji" />} icon={<KFIcon type="icon-bianji" />}
onClick={() => createService(record)}
onClick={() => createService(record, false)}
> >
编辑 编辑
</Button> </Button>
@@ -216,17 +223,11 @@ function AutoMLList() {
size="small" size="small"
key="copy" key="copy"
icon={<KFIcon type="icon-fuzhi" />} icon={<KFIcon type="icon-fuzhi" />}
onClick={() => toDetail(record)}
onClick={() => createService(record, true)}
> >
复制 复制
</Button> </Button>
<Button
type="link"
size="small"
key="stop"
icon={<KFIcon type="icon-tingzhi" />}
onClick={() => toDetail(record)}
>
<Button type="link" size="small" key="stop" icon={<KFIcon type="icon-tingzhi" />}>
停止 停止
</Button> </Button>
<ConfigProvider <ConfigProvider


+ 1
- 1
react-ui/src/pages/AutoML/components/CreateForm/ExecuteConfig.tsx View File

@@ -120,7 +120,7 @@ function ExecuteConfig() {
name="task_type" name="task_type"
rules={[{ required: true, message: '请选择任务类型' }]} rules={[{ required: true, message: '请选择任务类型' }]}
> >
<Radio.Group>
<Radio.Group onChange={() => form.resetFields(['metrics'])}>
<Radio value={AutoMLTaskType.Classification}>分类</Radio> <Radio value={AutoMLTaskType.Classification}>分类</Radio>
<Radio value={AutoMLTaskType.Regression}>回归</Radio> <Radio value={AutoMLTaskType.Regression}>回归</Radio>
</Radio.Group> </Radio.Group>


+ 18
- 13
react-ui/src/pages/AutoML/components/CreateForm/TrialConfig.tsx View File

@@ -8,6 +8,14 @@ import styles from './index.less';
function TrialConfig() { function TrialConfig() {
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const task_type = Form.useWatch('task_type', form); const task_type = Form.useWatch('task_type', form);
const metrics = Form.useWatch('metrics', form) || [];
const selectedMetrics = metrics
.map((item: { name: string; value: number }) => item?.name)
.filter(Boolean);
const allMetricsOptions =
task_type === AutoMLTaskType.Classification ? classificationMetrics : regressionMetrics;
const metricsOptions = allMetricsOptions.filter((item) => !selectedMetrics.includes(item.label));

return ( return (
<> <>
<SubAreaTitle <SubAreaTitle
@@ -22,7 +30,6 @@ function TrialConfig() {
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>

<Row gutter={8}> <Row gutter={8}>
<Col span={10}> <Col span={10}>
<Form.Item label="指标权重" tooltip="用户可自定义优化指标的组合"> <Form.Item label="指标权重" tooltip="用户可自定义优化指标的组合">
@@ -30,9 +37,9 @@ function TrialConfig() {
{(fields, { add, remove }) => ( {(fields, { add, remove }) => (
<> <>
{fields.map(({ key, name, ...restField }, index) => ( {fields.map(({ key, name, ...restField }, index) => (
<Flex key={key} align="center" className={styles['advanced-config']}>
<Flex key={key} align="flex-start" className={styles['advanced-config']}>
<Form.Item <Form.Item
style={{ flex: 1, marginBottom: 0 }}
style={{ flex: 1, marginBottom: 0, minWidth: 0 }}
{...restField} {...restField}
name={[name, 'name']} name={[name, 'name']}
rules={[{ required: true, message: '请选择指标' }]} rules={[{ required: true, message: '请选择指标' }]}
@@ -41,31 +48,29 @@ function TrialConfig() {
allowClear allowClear
placeholder="请选择指标" placeholder="请选择指标"
popupMatchSelectWidth={false} popupMatchSelectWidth={false}
options={
task_type === AutoMLTaskType.Classification
? classificationMetrics
: regressionMetrics
}
options={metricsOptions}
showSearch showSearch
/> />
</Form.Item> </Form.Item>
<span style={{ margin: '0 8px' }}>:</span>
<span style={{ margin: '0 8px', lineHeight: '46px' }}>:</span>
<Form.Item <Form.Item
style={{ flex: 1, marginBottom: 0 }}
style={{ flex: 1, marginBottom: 0, minWidth: 0 }}
{...restField} {...restField}
name={[name, 'value']} name={[name, 'value']}
rules={[{ required: true, message: '请输入指标权重' }]} rules={[{ required: true, message: '请输入指标权重' }]}
> >
<InputNumber placeholder="请输入指标权重" min={0} precision={0} /> <InputNumber placeholder="请输入指标权重" min={0} precision={0} />
</Form.Item> </Form.Item>
<div style={{ width: '76px', marginLeft: '18px' }}>
<Flex
style={{ width: '76px', marginLeft: '18px', height: '46px' }}
align="center"
>
<Button <Button
style={{ style={{
marginRight: '3px', marginRight: '3px',
}} }}
shape="circle" shape="circle"
size="middle" size="middle"
// disabled={fields.length === 1}
type="text" type="text"
onClick={() => remove(name)} onClick={() => remove(name)}
icon={<MinusCircleOutlined />} icon={<MinusCircleOutlined />}
@@ -79,7 +84,7 @@ function TrialConfig() {
icon={<PlusCircleOutlined />} icon={<PlusCircleOutlined />}
></Button> ></Button>
)} )}
</div>
</Flex>
</Flex> </Flex>
))} ))}
{fields.length === 0 && ( {fields.length === 0 && (


+ 133
- 133
react-ui/src/pages/AutoML/components/CreateForm/index.less View File

@@ -6,136 +6,136 @@
} }
} }


.command {
width: 83.33%;
margin-bottom: 20px;
border: 1px solid rgba(234, 234, 234, 0.8);
border-radius: 4px;
&__header {
height: 50px;
padding-left: 8px;
color: @text-color;
font-size: @font-size;
background: #f8f8f9;
border-radius: 4px 4px 0px 0px;
&__name {
flex: none;
width: 100px;
}
&__command {
flex: 1;
margin-right: 15px;
}
&__operation {
flex: none;
width: 100px;
}
}
&__body {
padding: 8px;
border-bottom: 1px solid rgba(234, 234, 234, 0.8);
&:last-child {
border-bottom: none;
}
&__name {
flex: none;
width: 100px;
}
&__command {
flex: 1;
margin-right: 15px;
margin-bottom: 0 !important;
}
&__operation {
flex: none;
width: 100px;
}
}
&__add {
display: flex;
align-items: center;
justify-content: center;
padding: 15px 0;
}
}
.hyper-parameter {
width: 83.33%;
margin-bottom: 20px;
border: 1px solid rgba(234, 234, 234, 0.8);
border-radius: 4px;
&__header {
height: 50px;
padding-left: 8px;
color: @text-color;
font-size: @font-size;
background: #f8f8f9;
border-radius: 4px 4px 0px 0px;
&__name,
&__type,
&__space {
flex: 1;
margin-right: 15px;
}
&__operation {
flex: none;
width: 100px;
}
}
&__body {
padding: 8px;
border-bottom: 1px solid rgba(234, 234, 234, 0.8);
&:last-child {
border-bottom: none;
}
&__name,
&__type,
&__space {
flex: 1;
margin-right: 15px;
margin-bottom: 0 !important;
}
&__operation {
flex: none;
width: 100px;
}
}
&__add {
display: flex;
align-items: center;
justify-content: center;
padding: 15px 0;
}
}
.trial-metrics {
width: calc(41.67% - 6px);
margin-bottom: 20px;
padding: 0 20px;
border: 1px dashed #e0e0e0;
border-radius: 8px;
}
.upload-tip {
margin-top: 5px;
color: @text-color-secondary;
font-size: 14px;
}
.upload-button {
height: 46px;
font-size: 15px;
}
// .command {
// width: 83.33%;
// margin-bottom: 20px;
// border: 1px solid rgba(234, 234, 234, 0.8);
// border-radius: 4px;
// &__header {
// height: 50px;
// padding-left: 8px;
// color: @text-color;
// font-size: @font-size;
// background: #f8f8f9;
// border-radius: 4px 4px 0px 0px;
// &__name {
// flex: none;
// width: 100px;
// }
// &__command {
// flex: 1;
// margin-right: 15px;
// }
// &__operation {
// flex: none;
// width: 100px;
// }
// }
// &__body {
// padding: 8px;
// border-bottom: 1px solid rgba(234, 234, 234, 0.8);
// &:last-child {
// border-bottom: none;
// }
// &__name {
// flex: none;
// width: 100px;
// }
// &__command {
// flex: 1;
// margin-right: 15px;
// margin-bottom: 0 !important;
// }
// &__operation {
// flex: none;
// width: 100px;
// }
// }
// &__add {
// display: flex;
// align-items: center;
// justify-content: center;
// padding: 15px 0;
// }
// }
// .hyper-parameter {
// width: 83.33%;
// margin-bottom: 20px;
// border: 1px solid rgba(234, 234, 234, 0.8);
// border-radius: 4px;
// &__header {
// height: 50px;
// padding-left: 8px;
// color: @text-color;
// font-size: @font-size;
// background: #f8f8f9;
// border-radius: 4px 4px 0px 0px;
// &__name,
// &__type,
// &__space {
// flex: 1;
// margin-right: 15px;
// }
// &__operation {
// flex: none;
// width: 100px;
// }
// }
// &__body {
// padding: 8px;
// border-bottom: 1px solid rgba(234, 234, 234, 0.8);
// &:last-child {
// border-bottom: none;
// }
// &__name,
// &__type,
// &__space {
// flex: 1;
// margin-right: 15px;
// margin-bottom: 0 !important;
// }
// &__operation {
// flex: none;
// width: 100px;
// }
// }
// &__add {
// display: flex;
// align-items: center;
// justify-content: center;
// padding: 15px 0;
// }
// }
// .trial-metrics {
// width: calc(41.67% - 6px);
// margin-bottom: 20px;
// padding: 0 20px;
// border: 1px dashed #e0e0e0;
// border-radius: 8px;
// }
// .upload-tip {
// margin-top: 5px;
// color: @text-color-secondary;
// font-size: 14px;
// }
// .upload-button {
// height: 46px;
// font-size: 15px;
// }

+ 5
- 4
react-ui/src/pages/AutoML/components/ExecuteScheduleCell/index.tsx View File

@@ -6,18 +6,19 @@


import styles from './index.less'; import styles from './index.less';


function ExecuteScheduleCell(status?: any) {
function ExecuteScheduleCell(progress?: number) {
const width = (progress || 0) * 100 + '%';
return ( return (
<div className={styles['execute-schedule-cell']}> <div className={styles['execute-schedule-cell']}>
<div className={styles['execute-schedule-cell__progress']}> <div className={styles['execute-schedule-cell__progress']}>
<div <div
className={styles['execute-schedule-cell__progress__bar']} className={styles['execute-schedule-cell__progress__bar']}
style={{ width: '80%' }}
style={{ width: width }}
></div> ></div>
</div> </div>
<span className={styles['execute-schedule-cell__text']}>
{/* <span className={styles['execute-schedule-cell__text']}>
1/<strong>2</strong> 1/<strong>2</strong>
</span>
</span> */}
</div> </div>
); );
} }


+ 4
- 1
react-ui/src/pages/AutoML/types.ts View File

@@ -51,4 +51,7 @@ export type AutoMLData = {
exclude_feature_preprocessor?: string; exclude_feature_preprocessor?: string;
exclude_regressor?: string; exclude_regressor?: string;
dataset?: string; dataset?: string;
};
} & Omit<
FormData,
'metrics|dataset|include_classifier|include_feature_preprocessor|include_regressor|exclude_classifier|exclude_feature_preprocessor|exclude_regressor'
>;

+ 2
- 0
react-ui/src/utils/sessionStorage.ts View File

@@ -11,6 +11,8 @@ export default class SessionStorage {
static readonly editorUrlKey = 'editor-url'; static readonly editorUrlKey = 'editor-url';
// 客户端信息 // 客户端信息
static readonly clientInfoKey = 'client-info'; static readonly clientInfoKey = 'client-info';
// 自动机器学习记录ID
static readonly autoMLRecordIDKey = 'auto-ml-record-id';


static getItem(key: string, isObject: boolean = false) { static getItem(key: string, isObject: boolean = false) {
const jsonStr = sessionStorage.getItem(key); const jsonStr = sessionStorage.getItem(key);


Loading…
Cancel
Save