Browse Source

feat: 完成超参数自动寻优详情

pull/171/head
cp3hnu 1 year ago
parent
commit
53731a2fc4
23 changed files with 487 additions and 509 deletions
  1. +11
    -0
      react-ui/src/enums/index.ts
  2. +1
    -1
      react-ui/src/pages/AutoML/Create/index.tsx
  3. +0
    -16
      react-ui/src/pages/AutoML/Info/index.tsx
  4. +1
    -8
      react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx
  5. +3
    -1
      react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx
  6. +1
    -0
      react-ui/src/pages/AutoML/components/ExperimentList/index.tsx
  7. +6
    -64
      react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx
  8. +3
    -3
      react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx
  9. +4
    -7
      react-ui/src/pages/HyperParameter/Create/index.tsx
  10. +14
    -28
      react-ui/src/pages/HyperParameter/Info/index.tsx
  11. +2
    -2
      react-ui/src/pages/HyperParameter/Instance/index.tsx
  12. +0
    -308
      react-ui/src/pages/HyperParameter/components/AutoMLBasic/index.tsx
  13. +0
    -20
      react-ui/src/pages/HyperParameter/components/ConfigInfo/index.less
  14. +0
    -26
      react-ui/src/pages/HyperParameter/components/ConfigInfo/index.tsx
  15. +8
    -10
      react-ui/src/pages/HyperParameter/components/CreateForm/ExecuteConfig.tsx
  16. +19
    -11
      react-ui/src/pages/HyperParameter/components/CreateForm/PopParameterRange/index.less
  17. +3
    -2
      react-ui/src/pages/HyperParameter/components/CreateForm/index.less
  18. +1
    -1
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.less
  19. +207
    -0
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  20. +7
    -0
      react-ui/src/pages/HyperParameter/components/ParameterInfo/index.less
  21. +108
    -0
      react-ui/src/pages/HyperParameter/components/ParameterInfo/index.tsx
  22. +1
    -1
      react-ui/src/pages/HyperParameter/types.ts
  23. +87
    -0
      react-ui/src/utils/format.ts

+ 11
- 0
react-ui/src/enums/index.ts View File

@@ -118,3 +118,14 @@ export const autoMLResamplingStrategyOptions = [
{ label: 'holdout', value: AutoMLResamplingStrategy.Holdout },
{ label: 'crossValid', value: AutoMLResamplingStrategy.CrossValid },
];

// 超参数自动寻优优化方向
export enum hyperParameterOptimizedMode {
Min = 'min',
Max = 'max',
}

export const hyperParameterOptimizedModeOptions = [
{ label: '越大越好', value: hyperParameterOptimizedMode.Max },
{ label: '越小越好', value: hyperParameterOptimizedMode.Min },
];

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

@@ -190,7 +190,7 @@ function CreateAutoML() {
<TrialConfig />
<DatasetConfig />

<Form.Item wrapperCol={{ offset: 0, span: 16 }}>
<Form.Item wrapperCol={{ offset: 0, span: 16 }} style={{ marginTop: '40px' }}>
<Button type="primary" htmlType="submit">
{buttonText}
</Button>


+ 0
- 16
react-ui/src/pages/AutoML/Info/index.tsx View File

@@ -3,9 +3,7 @@
* @Date: 2024-04-16 13:58:08
* @Description: 自主机器学习详情
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { CommonTabKeys } from '@/enums';
import { getAutoMLInfoReq } from '@/services/autoML';
import { safeInvoke } from '@/utils/functional';
import { to } from '@/utils/promise';
@@ -16,24 +14,10 @@ import { AutoMLData } from '../types';
import styles from './index.less';

function AutoMLInfo() {
const [activeTab, setActiveTab] = useState<string>(CommonTabKeys.Public);
const params = useParams();
const autoMLId = safeInvoke(Number)(params.id);
const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined);

const tabItems = [
{
key: CommonTabKeys.Public,
label: '基本信息',
icon: <KFIcon type="icon-jibenxinxi" />,
},
{
key: CommonTabKeys.Private,
label: 'Trial列表',
icon: <KFIcon type="icon-Trialliebiao" />,
},
];

useEffect(() => {
if (autoMLId) {
getAutoMLInfo();


+ 1
- 8
react-ui/src/pages/AutoML/components/AutoMLBasic/index.tsx View File

@@ -4,6 +4,7 @@ import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { elapsedTime } from '@/utils/date';
import { formatDataset } from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useMemo } from 'react';
@@ -15,14 +16,6 @@ import ConfigInfo, {
} from '../ConfigInfo';
import styles from './index.less';

// 格式化数据集
const formatDataset = (dataset: { name: string; version: string }) => {
if (!dataset || !dataset.name || !dataset.version) {
return '--';
}
return `${dataset.name}:${dataset.version}`;
};

// 格式化优化方向
const formatOptimizeMode = (value: boolean) => {
return value ? '越大越好' : '越小越好';


+ 3
- 1
react-ui/src/pages/AutoML/components/ConfigInfo/index.tsx View File

@@ -11,13 +11,15 @@ type ConfigInfoProps = {
labelWidth: number;
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
};

function ConfigInfo({ title, data, labelWidth, className, style }: ConfigInfoProps) {
function ConfigInfo({ title, data, labelWidth, className, style, children }: ConfigInfoProps) {
return (
<InfoGroup title={title} className={classNames(styles['config-info'], className)} style={style}>
<div className={styles['config-info__content']}>
<BasicInfo datas={data} labelWidth={labelWidth} />
{children}
</div>
</InfoGroup>
);


+ 1
- 0
react-ui/src/pages/AutoML/components/ExperimentList/index.tsx View File

@@ -400,6 +400,7 @@ function ExperimentList({ type }: ExperimentListProps) {
expandable={{
expandedRowRender: (record) => (
<ExperimentInstance
type={type}
experimentInsList={experimentInsList}
experimentInsTotal={experimentInsTotal}
onClickInstance={(item) => gotoInstanceInfo(record, item)}


+ 6
- 64
react-ui/src/pages/Dataset/components/ResourceIntro/index.tsx View File

@@ -1,17 +1,8 @@
import BasicTableInfo, { BasicInfoData } from '@/components/BasicTableInfo';
import SubAreaTitle from '@/components/SubAreaTitle';
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
import {
DataSource,
DatasetData,
ModelData,
ProjectDependency,
ResourceType,
TrainTask,
resourceConfig,
} from '@/pages/Dataset/config';
import { DatasetData, ModelData, ResourceType, resourceConfig } from '@/pages/Dataset/config';
import ModelMetrics from '@/pages/Model/components/ModelMetrics';
import { getGitUrl } from '@/utils';
import { formatCodeConfig, formatDatasets, formatSource, formatTrainTask } from '@/utils/format';
import classNames from 'classnames';
import styles from './index.less';

@@ -24,55 +15,6 @@ type ResourceIntroProps = {
version?: string;
};

export const formatDataset = (datasets?: DatasetData[]) => {
if (!datasets || datasets.length === 0) {
return undefined;
}
return datasets.map((item) => ({
value: item.name,
url: `${origin}/dataset/dataset/info/${item.id}?tab=${ResourceInfoTabKeys.Version}&version=${item.version}&name=${item.name}&owner=${item.owner}&identifier=${item.identifier}`,
}));
};

export const getProjectUrl = (project?: ProjectDependency) => {
if (!project || !project.url || !project.branch) {
return undefined;
}
const { url, branch } = project;
return getGitUrl(url, branch);
};

export const formatProject = (project?: ProjectDependency) => {
if (!project) {
return undefined;
}
return {
value: project.name,
url: getProjectUrl(project),
};
};

export const formatTrainTask = (task?: TrainTask) => {
if (!task) {
return undefined;
}
return {
value: task.name,
url: `${origin}/pipeline/experiment/instance/${task.workflow_id}/${task.ins_id}`,
};
};

export const formatSource = (source?: string) => {
if (source === DataSource.Create) {
return '用户上传';
} else if (source === DataSource.HandExport) {
return '手动导入';
} else if (source === DataSource.AtuoExport) {
return '实验自动导入';
}
return source;
};

const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
{
label: '数据集名称',
@@ -109,7 +51,7 @@ const getDatasetDatas = (data: DatasetData): BasicInfoData[] => [
{
label: '处理代码',
value: data.processing_code,
format: formatProject,
format: formatCodeConfig,
ellipsis: true,
},
{
@@ -153,19 +95,19 @@ const getModelDatas = (data: ModelData): BasicInfoData[] => [
{
label: '训练代码',
value: data.project_depency,
format: formatProject,
format: formatCodeConfig,
ellipsis: true,
},
{
label: '训练数据集',
value: data.train_datasets,
format: formatDataset,
format: formatDatasets,
ellipsis: true,
},
{
label: '测试数据集',
value: data.test_datasets,
format: formatDataset,
format: formatDatasets,
ellipsis: true,
},
{


+ 3
- 3
react-ui/src/pages/Dataset/components/VersionCompareModal/index.tsx View File

@@ -8,11 +8,11 @@ import {
resourceConfig,
} from '@/pages/Dataset/config';
import { isEmpty } from '@/utils';
import { formatSource } from '@/utils/format';
import { to } from '@/utils/promise';
import { Typography, type ModalProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useMemo, useState } from 'react';
import { formatSource } from '../ResourceIntro';
import styles from './index.less';

type CompareData = {
@@ -47,10 +47,10 @@ const formatProject = (project?: ProjectDependency) => {
if (!project) {
return undefined;
}
return project.name;
return `${project.name}:${project.branch}`;
};

export const formatTrainTask = (task?: TrainTask) => {
const formatTrainTask = (task?: TrainTask) => {
if (!task) {
return undefined;
}


+ 4
- 7
react-ui/src/pages/HyperParameter/Create/index.tsx View File

@@ -41,10 +41,9 @@ function CreateHyperparameter() {
const name = isCopy ? `${name_str}-copy` : name_str;
if (parameters && Array.isArray(parameters)) {
parameters.forEach((item) => {
item.range = item.bounds || item.values || item.value;
delete item.bounds;
delete item.values;
delete item.value;
const paramName = getReqParamName(item.type);
item.range = item[paramName];
item[paramName] = undefined;
});
}

@@ -63,8 +62,6 @@ function CreateHyperparameter() {
const createExperiment = async (formData: FormData) => {
// 按后台接口要求,修改参数表单数据结构,将 "value" 参数改为 "bounds"/"values"/"value"
const parameters = formData['parameters'];
// const points_to_evaluate = formData['points_to_evaluate'];
// const runParameters = formData['parameters'];
parameters.forEach((item) => {
const paramName = getReqParamName(item.type);
item[paramName] = item.range;
@@ -142,7 +139,7 @@ function CreateHyperparameter() {
<BasicConfig />
<ExecuteConfig />

<Form.Item wrapperCol={{ offset: 0, span: 16 }}>
<Form.Item wrapperCol={{ offset: 0, span: 16 }} style={{ marginTop: '40px' }}>
<Button type="primary" htmlType="submit">
{buttonText}
</Button>


+ 14
- 28
react-ui/src/pages/HyperParameter/Info/index.tsx View File

@@ -3,48 +3,34 @@
* @Date: 2024-04-16 13:58:08
* @Description: 自主机器学习详情
*/
import KFIcon from '@/components/KFIcon';
import PageTitle from '@/components/PageTitle';
import { CommonTabKeys } from '@/enums';
import { getAutoMLInfoReq } from '@/services/autoML';
import { getRayInfoReq } from '@/services/hyperParameter';
import { safeInvoke } from '@/utils/functional';
import { to } from '@/utils/promise';
import { useParams } from '@umijs/max';
import { useEffect, useState } from 'react';
import AutoMLBasic from '../components/AutoMLBasic';
import HyperParameterBasic from '../components/HyperParameterBasic';
import { HyperparameterData } from '../types';
import styles from './index.less';

function AutoMLInfo() {
const [activeTab, setActiveTab] = useState<string>(CommonTabKeys.Public);
function HyperparameterInfo() {
const params = useParams();
const autoMLId = safeInvoke(Number)(params.id);
const [autoMLInfo, setAutoMLInfo] = useState<HyperparameterData | undefined>(undefined);

const tabItems = [
{
key: CommonTabKeys.Public,
label: '基本信息',
icon: <KFIcon type="icon-jibenxinxi" />,
},
{
key: CommonTabKeys.Private,
label: 'Trial列表',
icon: <KFIcon type="icon-Trialliebiao" />,
},
];
const hyperparameterId = safeInvoke(Number)(params.id);
const [hyperparameterInfo, setHyperparameterInfo] = useState<HyperparameterData | undefined>(
undefined,
);

useEffect(() => {
if (autoMLId) {
getAutoMLInfo();
if (hyperparameterId) {
getHyperparameterInfo();
}
}, []);

// 获取自动机器学习详情
const getAutoMLInfo = async () => {
const [res] = await to(getAutoMLInfoReq({ id: autoMLId }));
const getHyperparameterInfo = async () => {
const [res] = await to(getRayInfoReq({ id: hyperparameterId }));
if (res && res.data) {
setAutoMLInfo(res.data);
setHyperparameterInfo(res.data);
}
};

@@ -52,10 +38,10 @@ function AutoMLInfo() {
<div className={styles['auto-ml-info']}>
<PageTitle title="实验详情"></PageTitle>
<div className={styles['auto-ml-info__content']}>
<AutoMLBasic info={autoMLInfo} />
<HyperParameterBasic info={hyperparameterInfo} />
</div>
</div>
);
}

export default AutoMLInfo;
export default HyperparameterInfo;

+ 2
- 2
react-ui/src/pages/HyperParameter/Instance/index.tsx View File

@@ -9,9 +9,9 @@ import { to } from '@/utils/promise';
import { useParams } from '@umijs/max';
import { Tabs } from 'antd';
import { useEffect, useRef, useState } from 'react';
import AutoMLBasic from '../components/AutoMLBasic';
import ExperimentHistory from '../components/ExperimentHistory';
import ExperimentResult from '../components/ExperimentResult';
import HyperParameterBasic from '../components/HyperParameterBasic';
import { AutoMLInstanceData, HyperparameterData } from '../types';
import styles from './index.less';

@@ -140,7 +140,7 @@ function AutoMLInstance() {
label: '基本信息',
icon: <KFIcon type="icon-jibenxinxi" />,
children: (
<AutoMLBasic
<HyperParameterBasic
className={styles['auto-ml-instance__basic']}
info={autoMLInfo}
runStatus={instanceInfo?.nodeStatus}


+ 0
- 308
react-ui/src/pages/HyperParameter/components/AutoMLBasic/index.tsx View File

@@ -1,308 +0,0 @@
import { AutoMLTaskType, autoMLEnsembleClassOptions, autoMLTaskTypeOptions } from '@/enums';
import { AutoMLData } from '@/pages/AutoML/types';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { type NodeStatus } from '@/types';
import { parseJsonText } from '@/utils';
import { elapsedTime } from '@/utils/date';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useMemo } from 'react';
import ConfigInfo, {
formatBoolean,
formatDate,
formatEnum,
type BasicInfoData,
} from '../ConfigInfo';
import styles from './index.less';

// 格式化数据集
const formatDataset = (dataset: { name: string; version: string }) => {
if (!dataset || !dataset.name || !dataset.version) {
return '--';
}
return `${dataset.name}:${dataset.version}`;
};

// 格式化优化方向
const formatOptimizeMode = (value: boolean) => {
return value ? '越大越好' : '越小越好';
};

const formatMetricsWeight = (value: string) => {
if (!value) {
return '--';
}
const json = parseJsonText(value);
if (!json) {
return '--';
}
return Object.entries(json)
.map(([key, value]) => `${key}:${value}`)
.join('\n');
};

type AutoMLBasicProps = {
info?: AutoMLData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
};

function AutoMLBasic({ info, className, runStatus, isInstance = false }: AutoMLBasicProps) {
const basicDatas: BasicInfoData[] = useMemo(() => {
if (!info) {
return [];
}

return [
{
label: '实验名称',
value: info.ml_name,
ellipsis: true,
},
{
label: '实验描述',
value: info.ml_description,
ellipsis: true,
},
{
label: '创建人',
value: info.create_by,
ellipsis: true,
},
{
label: '创建时间',
value: info.create_time,
ellipsis: true,
format: formatDate,
},
{
label: '更新时间',
value: info.update_time,
ellipsis: true,
format: formatDate,
},
];
}, [info]);

const configDatas: BasicInfoData[] = useMemo(() => {
if (!info) {
return [];
}
return [
{
label: '任务类型',
value: info.task_type,
ellipsis: true,
format: formatEnum(autoMLTaskTypeOptions),
},
{
label: '特征预处理算法',
value: info.include_feature_preprocessor,
ellipsis: true,
},
{
label: '排除的特征预处理算法',
value: info.exclude_feature_preprocessor,
ellipsis: true,
},
{
label: info.task_type === AutoMLTaskType.Regression ? '回归算法' : '分类算法',
value:
info.task_type === AutoMLTaskType.Regression
? info.include_regressor
: info.include_classifier,
ellipsis: true,
},
{
label: info.task_type === AutoMLTaskType.Regression ? '排除的回归算法' : '排除的分类算法',
value:
info.task_type === AutoMLTaskType.Regression
? info.exclude_regressor
: info.exclude_classifier,
ellipsis: true,
},
{
label: '集成方式',
value: info.ensemble_class,
ellipsis: true,
format: formatEnum(autoMLEnsembleClassOptions),
},
{
label: '集成模型数量',
value: info.ensemble_size,
ellipsis: true,
},
{
label: '集成最佳模型数量',
value: info.ensemble_nbest,
ellipsis: true,
},
{
label: '最大数量',
value: info.max_models_on_disc,
ellipsis: true,
},
{
label: '内存限制(MB)',
value: info.memory_limit,
ellipsis: true,
},
{
label: '单次时间限制(秒)',
value: info.per_run_time_limit,
ellipsis: true,
},
{
label: '搜索时间限制(秒)',
value: info.time_left_for_this_task,
ellipsis: true,
},
{
label: '重采样策略',
value: info.resampling_strategy,
ellipsis: true,
},
{
label: '交叉验证折数',
value: info.folds,
ellipsis: true,
},
{
label: '是否打乱',
value: info.shuffle,
ellipsis: true,
format: formatBoolean,
},
{
label: '训练集比率',
value: info.train_size,
ellipsis: true,
},
{
label: '测试集比率',
value: info.test_size,
ellipsis: true,
},
{
label: '计算指标',
value: info.scoring_functions,
ellipsis: true,
},
{
label: '随机种子',
value: info.seed,
ellipsis: true,
},

{
label: '数据集',
value: info.dataset,
ellipsis: true,
format: formatDataset,
},
{
label: '预测目标列',
value: info.target_columns,
ellipsis: true,
},
];
}, [info]);

const metricsData = useMemo(() => {
if (!info) {
return [];
}
return [
{
label: '指标名称',
value: info.metric_name,
ellipsis: true,
},
{
label: '优化方向',
value: info.greater_is_better,
ellipsis: true,
format: formatOptimizeMode,
},
{
label: '指标权重',
value: info.metrics,
ellipsis: true,
format: formatMetricsWeight,
},
];
}, [info]);

const instanceDatas = useMemo(() => {
if (!runStatus) {
return [];
}

return [
{
label: '启动时间',
value: formatDate(runStatus.startedAt),
ellipsis: true,
},
{
label: '执行时长',
value: elapsedTime(runStatus.startedAt, runStatus.finishedAt),
ellipsis: true,
},
{
label: '状态',
value: (
<Flex align="center">
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[runStatus.phase]?.icon}
draggable={false}
alt=""
/>
<div
style={{
color: experimentStatusInfo[runStatus?.phase]?.color,
fontSize: '15px',
lineHeight: 1.6,
}}
>
{experimentStatusInfo[runStatus?.phase]?.label}
</div>
</Flex>
),
ellipsis: true,
},
];
}, [runStatus]);

return (
<div className={classNames(styles['auto-ml-basic'], className)}>
{isInstance && runStatus && (
<ConfigInfo
title="运行信息"
data={instanceDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
/>
)}
{!isInstance && (
<ConfigInfo
title="基本信息"
data={basicDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
/>
)}
<ConfigInfo
title="配置信息"
data={configDatas}
labelWidth={150}
style={{ marginBottom: '20px' }}
/>
<ConfigInfo title="优化指标" data={metricsData} labelWidth={70} />
</div>
);
}

export default AutoMLBasic;

+ 0
- 20
react-ui/src/pages/HyperParameter/components/ConfigInfo/index.less View File

@@ -1,20 +0,0 @@
.config-info {
:global {
.kf-basic-info {
width: 100%;

&__item {
width: calc((100% - 80px) / 3);
&__label {
font-size: @font-size;
text-align: left;
text-align-last: left;
}
&__value {
min-width: 0;
font-size: @font-size;
}
}
}
}
}

+ 0
- 26
react-ui/src/pages/HyperParameter/components/ConfigInfo/index.tsx View File

@@ -1,26 +0,0 @@
import BasicInfo, { type BasicInfoData } from '@/components/BasicInfo';
import InfoGroup from '@/components/InfoGroup';
import classNames from 'classnames';
import styles from './index.less';
export * from '@/components/BasicInfo/format';
export type { BasicInfoData };

type ConfigInfoProps = {
title: string;
data: BasicInfoData[];
labelWidth: number;
className?: string;
style?: React.CSSProperties;
};

function ConfigInfo({ title, data, labelWidth, className, style }: ConfigInfoProps) {
return (
<InfoGroup title={title} className={classNames(styles['config-info'], className)} style={style}>
<div className={styles['config-info__content']}>
<BasicInfo datas={data} labelWidth={labelWidth} />
</div>
</InfoGroup>
);
}

export default ConfigInfo;

+ 8
- 10
react-ui/src/pages/HyperParameter/components/CreateForm/ExecuteConfig.tsx View File

@@ -5,6 +5,7 @@ import ResourceSelect, {
requiredValidator,
} from '@/components/ResourceSelect';
import SubAreaTitle from '@/components/SubAreaTitle';
import { hyperParameterOptimizedModeOptions } from '@/enums';
import { modalConfirm } from '@/utils/ui';
import { MinusCircleOutlined, PlusCircleOutlined } from '@ant-design/icons';
import { Button, Col, Flex, Form, Input, InputNumber, Radio, Row, Select } from 'antd';
@@ -456,10 +457,7 @@ function ExecuteConfig() {
name="mode"
rules={[{ required: true, message: '请选择优化方向' }]}
>
<Radio.Group>
<Radio value={'max'}>越大越好</Radio>
<Radio value={'min'}>越小越好</Radio>
</Radio.Group>
<Radio.Group options={hyperParameterOptimizedModeOptions}></Radio.Group>
</Form.Item>
</Col>
</Row>
@@ -467,16 +465,16 @@ function ExecuteConfig() {
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="CPU 数"
label="CPU 数"
name="cpu"
rules={[
{
required: true,
message: '请输入 CPU 数',
message: '请输入 CPU 数',
},
]}
>
<InputNumber placeholder="请输入 CPU 数" min={0} precision={0} />
<InputNumber placeholder="请输入 CPU 数" min={0} precision={0} />
</Form.Item>
</Col>
</Row>
@@ -484,16 +482,16 @@ function ExecuteConfig() {
<Row gutter={8}>
<Col span={10}>
<Form.Item
label="GPU 数"
label="GPU 数"
name="gpu"
rules={[
{
required: true,
message: '请输入 GPU 数',
message: '请输入 GPU 数',
},
]}
>
<InputNumber placeholder="请输入 GPU 数" min={0} precision={0} />
<InputNumber placeholder="请输入 GPU 数" min={0} precision={0} />
</Form.Item>
</Col>
</Row>


+ 19
- 11
react-ui/src/pages/HyperParameter/components/CreateForm/PopParameterRange/index.less View File

@@ -1,5 +1,8 @@
.parameter-range {
:global {
.ant-popover-inner {
padding: 20px 20px 12px;
}
.ant-popconfirm-description {
padding-top: 20px;
}
@@ -22,15 +25,6 @@
border-radius: 8px;
cursor: pointer;

&:hover {
border-color: #4086ff;
}

&--disabled {
background-color: rgba(0, 0, 0, 0.04);
cursor: not-allowed;
}

&__text {
flex: 1;
margin-right: 10px;
@@ -40,8 +34,22 @@
flex: none;
}

&--disabled &__icon {
color: @text-color-tertiary;
&:hover {
border-color: #4086ff;
}

&:hover &__icon {
color: #4086ff;
}

&&--disabled {
background-color: rgba(0, 0, 0, 0.04);
border-color: rgba(0, 0, 0, 0.04) !important;
cursor: not-allowed;
}

&&--disabled &__icon {
color: #aaaaaa !important;
}
}
}

+ 3
- 2
react-ui/src/pages/HyperParameter/components/CreateForm/index.less View File

@@ -92,12 +92,13 @@
.run-parameter {
width: calc(41.66% + 126px);
margin-bottom: 20px;
border-radius: 8px;
&__body {
flex: 1;
margin-right: 10px;
padding: 20px 20px 0;
border: 1px dashed @border-color-base;
border: 1px dashed #dddddd;
border-radius: 8px;
}
&__operation {
display: flex;


react-ui/src/pages/HyperParameter/components/AutoMLBasic/index.less → react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.less View File

@@ -1,4 +1,4 @@
.auto-ml-basic {
.hyper-parameter-basic {
height: 100%;
padding: 20px @content-padding;
overflow-y: auto;

+ 207
- 0
react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx View File

@@ -0,0 +1,207 @@
import { hyperParameterOptimizedMode } from '@/enums';
import ConfigInfo, { formatDate, type BasicInfoData } from '@/pages/AutoML/components/ConfigInfo';
import { experimentStatusInfo } from '@/pages/Experiment/status';
import { HyperparameterData } from '@/pages/HyperParameter/types';
import { type NodeStatus } from '@/types';
import { elapsedTime } from '@/utils/date';
import { formatDataset, formatSelectCodeConfig } from '@/utils/format';
import { Flex } from 'antd';
import classNames from 'classnames';
import { useMemo } from 'react';
import ParameterInfo from '../ParameterInfo';
import styles from './index.less';

// 格式化优化方向
const formatOptimizeMode = (value: string) => {
return value === hyperParameterOptimizedMode.Max ? '越大越好' : '越小越好';
};

type HyperParameterBasicProps = {
info?: HyperparameterData;
className?: string;
isInstance?: boolean;
runStatus?: NodeStatus;
};

function HyperParameterBasic({
info,
className,
runStatus,
isInstance = false,
}: HyperParameterBasicProps) {
const basicDatas: BasicInfoData[] = useMemo(() => {
if (!info) {
return [];
}

return [
{
label: '实验名称',
value: info.name,
ellipsis: true,
},
{
label: '实验描述',
value: info.description,
ellipsis: true,
},
{
label: '创建人',
value: info.create_by,
ellipsis: true,
},
{
label: '创建时间',
value: info.create_time,
ellipsis: true,
format: formatDate,
},
{
label: '更新时间',
value: info.update_time,
ellipsis: true,
format: formatDate,
},
];
}, [info]);

const configDatas: BasicInfoData[] = useMemo(() => {
if (!info) {
return [];
}
return [
{
label: '代码',
value: info.code,
ellipsis: true,
format: formatSelectCodeConfig,
},
{
label: '主函数代码文件',
value: info.main_py,
ellipsis: true,
},
{
label: '数据集',
value: info.dataset,
ellipsis: true,
format: formatDataset,
},

{
label: '数据集挂载路径',
value: info.dataset_path,
ellipsis: true,
},
{
label: '总实验次数',
value: info.num_samples,
ellipsis: true,
},
{
label: '搜索算法',
value: info.search_alg,
ellipsis: true,
},
{
label: '调度算法',
value: info.scheduler,
ellipsis: true,
},
{
label: '优化方向',
value: info.mode,
ellipsis: true,
format: formatOptimizeMode,
},
{
label: '指标',
value: info.metric,
ellipsis: true,
},
{
label: 'CPU 数量',
value: info.cpu,
ellipsis: true,
},
{
label: 'GPU 数量',
value: info.gpu,
ellipsis: true,
},
];
}, [info]);

const instanceDatas = useMemo(() => {
if (!runStatus) {
return [];
}

return [
{
label: '启动时间',
value: formatDate(runStatus.startedAt),
ellipsis: true,
},
{
label: '执行时长',
value: elapsedTime(runStatus.startedAt, runStatus.finishedAt),
ellipsis: true,
},
{
label: '状态',
value: (
<Flex align="center">
<img
style={{ width: '17px', marginRight: '7px' }}
src={experimentStatusInfo[runStatus.phase]?.icon}
draggable={false}
alt=""
/>
<div
style={{
color: experimentStatusInfo[runStatus?.phase]?.color,
fontSize: '15px',
lineHeight: 1.6,
}}
>
{experimentStatusInfo[runStatus?.phase]?.label}
</div>
</Flex>
),
ellipsis: true,
},
];
}, [runStatus]);

return (
<div className={classNames(styles['hyper-parameter-basic'], className)}>
{isInstance && runStatus && (
<ConfigInfo
title="运行信息"
data={instanceDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
/>
)}
{!isInstance && (
<ConfigInfo
title="基本信息"
data={basicDatas}
labelWidth={70}
style={{ marginBottom: '20px' }}
/>
)}
<ConfigInfo
title="配置信息"
data={configDatas}
labelWidth={150}
style={{ marginBottom: '20px' }}
>
{info && <ParameterInfo info={info} />}
</ConfigInfo>
</div>
);
}

export default HyperParameterBasic;

+ 7
- 0
react-ui/src/pages/HyperParameter/components/ParameterInfo/index.less View File

@@ -0,0 +1,7 @@
.parameter-info {
&__title {
margin: 20px 0;
color: @text-color-secondary;
font-size: @font-size;
}
}

+ 108
- 0
react-ui/src/pages/HyperParameter/components/ParameterInfo/index.tsx View File

@@ -0,0 +1,108 @@
import {
getReqParamName,
type FormParameter,
} from '@/pages/HyperParameter/components/CreateForm/utils';
import { HyperparameterData } from '@/pages/HyperParameter/types';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { Table, Tooltip, type TableProps } from 'antd';
import { useMemo } from 'react';
import styles from './index.less';

type ParameterInfoProps = {
info: HyperparameterData;
};

function ParameterInfo({ info }: ParameterInfoProps) {
const parameters = useMemo(() => {
if (!info.parameters) {
return [];
}
return info.parameters.map((item) => {
const paramName = getReqParamName(item.type);
const range = item[paramName];
return {
...item,
range,
};
});
}, [info]);

const runParameters = useMemo(() => {
if (!info.points_to_evaluate) {
return [];
}
return info.points_to_evaluate.map((item, index) => ({
...item,
id: index,
}));
}, [info]);

const columns: TableProps<FormParameter>['columns'] = [
{
title: '参数名称',
dataIndex: 'name',
key: 'type',
width: '40%',
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
title: '参数类型',
dataIndex: 'type',
key: 'type',
width: '20%',
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
title: '取值范围',
dataIndex: 'range',
key: 'range',
width: '40%',
render: tableCellRender(true, TableCellValueType.Custom, {
format: (value) => {
return JSON.stringify(value);
},
}),
ellipsis: { showTitle: false },
},
];

const runColumns: TableProps<Record<string, any>>['columns'] =
runParameters.length > 0
? Object.keys(runParameters[0])
.filter((key) => key !== 'id')
.map((key) => {
return {
title: (
<Tooltip title={key}>
<span>{key}</span>
</Tooltip>
),
dataIndex: key,
key: key,
width: 150,
render: tableCellRender(true),
ellipsis: { showTitle: false },
};
})
: [];

return (
<div className={styles['parameter-info']}>
<div className={styles['parameter-info__title']}>参数</div>
<Table dataSource={parameters} columns={columns} rowKey="name" bordered pagination={false} />
<div className={styles['parameter-info__title']}>手动运行参数</div>
<Table
dataSource={runParameters}
columns={runColumns}
rowKey="id"
bordered
pagination={false}
scroll={{ x: '100%' }}
/>
</div>
);
}

export default ParameterInfo;

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

@@ -16,7 +16,7 @@ export type FormData = {
dataset: ParameterInputObject; // 数据集
dataset_path: string; // 数据集路径
main_py: string; // 主函数代码文件
metrics: string; // 指标
metric: string; // 指标
mode: string; // 优化方向
search_alg?: string; // 搜索算法
scheduler?: string; // 调度算法


+ 87
- 0
react-ui/src/utils/format.ts View File

@@ -0,0 +1,87 @@
import { ResourceInfoTabKeys } from '@/pages/Dataset/components/ResourceInfo';
import { DataSource, DatasetData, ProjectDependency, TrainTask } from '@/pages/Dataset/config';
import { getGitUrl } from '@/utils';

// 格式化数据集数组
export const formatDatasets = (datasets?: DatasetData[]) => {
if (!datasets || datasets.length === 0) {
return undefined;
}
return datasets.map((item) => ({
value: item.name,
url: `${origin}/dataset/dataset/info/${item.id}?tab=${ResourceInfoTabKeys.Introduction}&version=${item.version}&name=${item.name}&owner=${item.owner}&identifier=${item.identifier}`,
}));
};

// 格式化数据集
export const formatDataset = (dataset?: DatasetData) => {
if (!dataset) {
return undefined;
}
return {
value: dataset.name,
url: `${origin}/dataset/dataset/info/${dataset.id}?tab=${ResourceInfoTabKeys.Introduction}&version=${dataset.version}&name=${dataset.name}&owner=${dataset.owner}&identifier=${dataset.identifier}`,
};
};

// 获取代码配置的仓库的 url
export const getRepoUrl = (project?: ProjectDependency) => {
if (!project) {
return undefined;
}
const { url, branch } = project;
return getGitUrl(url, branch);
};

// 格式化代码配置
export const formatCodeConfig = (project?: ProjectDependency) => {
if (!project) {
return undefined;
}
return {
value: project.name,
url: getRepoUrl(project),
};
};

// 格式化选中的代码配置
export const formatSelectCodeConfig = (value?: {
code_path: string;
branch: string;
showValue: string;
}) => {
if (!value) {
return undefined;
}
const { showValue, code_path, branch } = value;
return {
value: showValue,
url: getRepoUrl({
url: code_path,
branch,
} as ProjectDependency),
};
};

// 格式化训练任务(实验实例)
export const formatTrainTask = (task?: TrainTask) => {
if (!task) {
return undefined;
}
return {
value: task.name,
url: `${origin}/pipeline/experiment/instance/${task.workflow_id}/${task.ins_id}`,
};
};

// 格式化数据来源
export const formatSource = (source?: string) => {
if (source === DataSource.Create) {
return '用户上传';
} else if (source === DataSource.HandExport) {
return '手动导入';
} else if (source === DataSource.AtuoExport) {
return '实验自动导入';
}
return source;
};

Loading…
Cancel
Save