| @@ -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 }, | |||
| ]; | |||
| @@ -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> | |||
| @@ -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(); | |||
| @@ -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 ? '越大越好' : '越小越好'; | |||
| @@ -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> | |||
| ); | |||
| @@ -400,6 +400,7 @@ function ExperimentList({ type }: ExperimentListProps) { | |||
| expandable={{ | |||
| expandedRowRender: (record) => ( | |||
| <ExperimentInstance | |||
| type={type} | |||
| experimentInsList={experimentInsList} | |||
| experimentInsTotal={experimentInsTotal} | |||
| onClickInstance={(item) => gotoInstanceInfo(record, item)} | |||
| @@ -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, | |||
| }, | |||
| { | |||
| @@ -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; | |||
| } | |||
| @@ -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> | |||
| @@ -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; | |||
| @@ -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} | |||
| @@ -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; | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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> | |||
| @@ -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; | |||
| } | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -1,4 +1,4 @@ | |||
| .auto-ml-basic { | |||
| .hyper-parameter-basic { | |||
| height: 100%; | |||
| padding: 20px @content-padding; | |||
| overflow-y: auto; | |||
| @@ -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; | |||
| @@ -0,0 +1,7 @@ | |||
| .parameter-info { | |||
| &__title { | |||
| margin: 20px 0; | |||
| color: @text-color-secondary; | |||
| font-size: @font-size; | |||
| } | |||
| } | |||
| @@ -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; | |||
| @@ -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; // 调度算法 | |||
| @@ -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; | |||
| }; | |||