Browse Source

feat: 超参数寻优-实验结果

pull/174/head
cp3hnu 1 year ago
parent
commit
ed0bb92c11
18 changed files with 330 additions and 252 deletions
  1. +7
    -7
      react-ui/src/components/FormInfo/index.tsx
  2. +4
    -2
      react-ui/src/pages/AutoML/Instance/index.tsx
  3. +1
    -0
      react-ui/src/pages/AutoML/components/ExperimentResult/index.less
  4. +0
    -20
      react-ui/src/pages/Experiment/Comparison/index.less
  5. +2
    -1
      react-ui/src/pages/Experiment/components/LogGroup/index.less
  6. +1
    -6
      react-ui/src/pages/HyperParameter/Create/index.less
  7. +2
    -2
      react-ui/src/pages/HyperParameter/Create/index.tsx
  8. +1
    -1
      react-ui/src/pages/HyperParameter/Info/index.less
  9. +2
    -2
      react-ui/src/pages/HyperParameter/Info/index.tsx
  10. +1
    -1
      react-ui/src/pages/HyperParameter/Instance/index.less
  11. +55
    -29
      react-ui/src/pages/HyperParameter/Instance/index.tsx
  12. +21
    -0
      react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.less
  13. +79
    -89
      react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx
  14. +83
    -0
      react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx
  15. +3
    -38
      react-ui/src/pages/HyperParameter/components/ExperimentResult/index.less
  16. +46
    -47
      react-ui/src/pages/HyperParameter/components/ExperimentResult/index.tsx
  17. +1
    -1
      react-ui/src/pages/HyperParameter/components/HyperParameterBasic/index.tsx
  18. +21
    -6
      react-ui/src/pages/HyperParameter/types.ts

+ 7
- 7
react-ui/src/components/FormInfo/index.tsx View File

@@ -4,9 +4,9 @@ import classNames from 'classnames';
import './index.less';

type FormInfoProps = {
/** 自定义类名 */
/** */
value?: any;
/** 如果 `value` 是对象,取对象的哪个属性作为值 */
/** 如果 `value` 是对象,取对象的哪个属性作为值 */
valuePropName?: string;
/** 是否是多行文本 */
textArea?: boolean;
@@ -32,11 +32,11 @@ function FormInfo({
style,
textArea = false,
}: FormInfoProps) {
let data = value;
let showValue = value;
if (value && typeof value === 'object' && valuePropName) {
data = value[valuePropName];
showValue = value[valuePropName];
} else if (select === true && options) {
data = formatEnum(options)(value);
showValue = formatEnum(options)(value);
}

return (
@@ -50,8 +50,8 @@ function FormInfo({
)}
style={style}
>
<Typography.Paragraph ellipsis={textArea ? false : { tooltip: data }}>
{data}
<Typography.Paragraph ellipsis={textArea ? false : { tooltip: showValue }}>
{showValue}
</Typography.Paragraph>
</div>
);


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

@@ -22,6 +22,8 @@ enum TabKeys {
History = 'history',
}

const NodePrefix = 'auto-hpo';

function AutoMLInstance() {
const [activeTab, setActiveTab] = useState<string>(TabKeys.Params);
const [autoMLInfo, setAutoMLInfo] = useState<AutoMLData | undefined>(undefined);
@@ -66,7 +68,7 @@ function AutoMLInstance() {
const nodeStatusJson = parseJsonText(node_status);
if (nodeStatusJson) {
Object.keys(nodeStatusJson).forEach((key) => {
if (key.startsWith('auto-ml')) {
if (key.startsWith(NodePrefix)) {
const value = nodeStatusJson[key];
info.nodeStatus = value;
}
@@ -100,7 +102,7 @@ function AutoMLInstance() {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
const statusData = Object.values(nodes).find((node: any) =>
node.displayName.startsWith('auto-ml'),
node.displayName.startsWith(NodePrefix),
) as NodeStatus;
if (statusData) {
setInstanceInfo((prev) => ({


+ 1
- 0
react-ui/src/pages/AutoML/components/ExperimentResult/index.less View File

@@ -25,6 +25,7 @@
}

&__text {
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
white-space: pre-wrap;
}



+ 0
- 20
react-ui/src/pages/Experiment/Comparison/index.less View File

@@ -18,26 +18,6 @@
background-color: white;
border-radius: 10px;

&__footer {
display: flex;
align-items: center;
padding-top: 20px;
color: @text-color-secondary;
font-size: 12px;
background-color: white;

div {
flex: 1;
height: 1px;
background-color: @border-color;
}

p {
flex: none;
margin: 0 8px;
}
}

:global {
.ant-table-container {
border: none !important;


+ 2
- 1
react-ui/src/pages/Experiment/components/LogGroup/index.less View File

@@ -20,7 +20,8 @@
padding: 15px;
color: white;
font-size: 14px;
white-space: pre-line;
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
white-space: pre-wrap;
text-align: left;
word-break: break-all;
background: #19253b;


+ 1
- 6
react-ui/src/pages/HyperParameter/Create/index.less View File

@@ -1,4 +1,4 @@
.create-hyperparameter {
.create-hyper-parameter {
height: 100%;

&__content {
@@ -11,11 +11,6 @@
background-color: white;
border-radius: 10px;

&__type {
color: @text-color;
font-size: @font-size-input-lg;
}

:global {
.ant-input-number {
width: 100%;


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

@@ -118,9 +118,9 @@ function CreateHyperParameter() {
}

return (
<div className={styles['create-hyperparameter']}>
<div className={styles['create-hyper-parameter']}>
<PageTitle title={title}></PageTitle>
<div className={styles['create-hyperparameter__content']}>
<div className={styles['create-hyper-parameter__content']}>
<div>
<Form
name="create-hyperparameter"


+ 1
- 1
react-ui/src/pages/HyperParameter/Info/index.less View File

@@ -1,4 +1,4 @@
.auto-ml-info {
.hyper-parameter-info {
position: relative;
height: 100%;
&__tabs {


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

@@ -35,9 +35,9 @@ function HyperparameterInfo() {
};

return (
<div className={styles['auto-ml-info']}>
<div className={styles['hyper-parameter-info']}>
<PageTitle title="实验详情"></PageTitle>
<div className={styles['auto-ml-info__content']}>
<div className={styles['hyper-parameter-info__content']}>
<HyperParameterBasic info={hyperparameterInfo} />
</div>
</div>


+ 1
- 1
react-ui/src/pages/HyperParameter/Instance/index.less View File

@@ -1,4 +1,4 @@
.auto-ml-instance {
.hyper-parameter-instance {
height: 100%;

&__tabs {


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

@@ -1,5 +1,5 @@
import KFIcon from '@/components/KFIcon';
import { AutoMLTaskType, ExperimentStatus } from '@/enums';
import { ExperimentStatus } from '@/enums';
import LogList from '@/pages/Experiment/components/LogList';
import { getRayInsReq } from '@/services/hyperParameter';
import { NodeStatus } from '@/types';
@@ -12,7 +12,7 @@ import { useEffect, useRef, useState } from 'react';
import ExperimentHistory from '../components/ExperimentHistory';
import ExperimentResult from '../components/ExperimentResult';
import HyperParameterBasic from '../components/HyperParameterBasic';
import { AutoMLInstanceData, HyperParameterData } from '../types';
import { HyperParameterData, HyperParameterInstanceData } from '../types';
import styles from './index.less';

enum TabKeys {
@@ -22,10 +22,16 @@ enum TabKeys {
History = 'history',
}

const NodePrefix = 'auto-hpo';

function HyperParameterInstance() {
const [activeTab, setActiveTab] = useState<string>(TabKeys.Params);
const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined);
const [instanceInfo, setInstanceInfo] = useState<AutoMLInstanceData | undefined>(undefined);
const [instanceInfo, setInstanceInfo] = useState<HyperParameterInstanceData | undefined>(
undefined,
);
// 超参数寻优运行有3个节点,运行状态取工作流状态,而不是 auto-hpo 节点状态
const [workflowStatus, setWorkflowStatus] = useState<NodeStatus | undefined>(undefined);
const params = useParams();
const instanceId = safeInvoke(Number)(params.id);
const evtSourceRef = useRef<EventSource | null>(null);
@@ -43,11 +49,26 @@ function HyperParameterInstance() {
const getExperimentInsInfo = async (isStatusDetermined: boolean) => {
const [res] = await to(getRayInsReq(instanceId));
if (res && res.data) {
const info = res.data as AutoMLInstanceData;
const info = res.data as HyperParameterInstanceData;
const { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数
const paramJson = parseJsonText(param);
if (paramJson) {
// 实例详情返回的参数是字符串,需要转换
if (typeof paramJson.parameters === 'string') {
paramJson.parameters = parseJsonText(paramJson.parameters);
}
if (!Array.isArray(paramJson.parameters)) {
paramJson.parameters = [];
}

// 实例详情返回的运行参数是字符串,需要转换
if (typeof paramJson.points_to_evaluate === 'string') {
paramJson.points_to_evaluate = parseJsonText(paramJson.points_to_evaluate);
}
if (!Array.isArray(paramJson.points_to_evaluate)) {
paramJson.points_to_evaluate = [];
}
setExperimentInfo(paramJson);
}

@@ -65,13 +86,17 @@ function HyperParameterInstance() {
const nodeStatusJson = parseJsonText(node_status);
if (nodeStatusJson) {
Object.keys(nodeStatusJson).forEach((key) => {
if (key.startsWith('auto-ml')) {
const value = nodeStatusJson[key];
info.nodeStatus = value;
if (key.startsWith(NodePrefix)) {
const nodeStatus = nodeStatusJson[key];
info.nodeStatus = nodeStatus;
} else if (key.startsWith('workflow')) {
const workflowStatus = nodeStatusJson[key];
setWorkflowStatus(workflowStatus);
}
});
}
setInstanceInfo(info);

// 运行中或者等待中,开启 SSE
if (status === ExperimentStatus.Pending || status === ExperimentStatus.Running) {
setupSSE(argo_ins_name, argo_ins_ns);
@@ -98,19 +123,29 @@ function HyperParameterInstance() {
if (dataJson) {
const nodes = dataJson?.result?.object?.status?.nodes;
if (nodes) {
const statusData = Object.values(nodes).find((node: any) =>
node.displayName.startsWith('auto-ml'),
const nodeStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith(NodePrefix),
) as NodeStatus;
if (statusData) {
const workflowStatus = Object.values(nodes).find((node: any) =>
node.displayName.startsWith('workflow'),
) as NodeStatus;

// 节点状态
if (nodeStatus) {
setInstanceInfo((prev) => ({
...prev!,
nodeStatus: statusData,
nodeStatus: nodeStatus,
}));
}

// 设置工作流状态
if (workflowStatus) {
setWorkflowStatus(workflowStatus);

// 实验结束,关闭 SSE
if (
statusData.phase !== ExperimentStatus.Pending &&
statusData.phase !== ExperimentStatus.Running
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
) {
closeSSE();
getExperimentInsInfo(true);
@@ -140,9 +175,9 @@ function HyperParameterInstance() {
icon: <KFIcon type="icon-jibenxinxi" />,
children: (
<HyperParameterBasic
className={styles['auto-ml-instance__basic']}
className={styles['hyper-parameter-instance__basic']}
info={experimentInfo}
runStatus={instanceInfo?.nodeStatus}
runStatus={workflowStatus}
isInstance
/>
),
@@ -152,7 +187,7 @@ function HyperParameterInstance() {
label: '日志',
icon: <KFIcon type="icon-rizhi1" />,
children: (
<div className={styles['auto-ml-instance__log']}>
<div className={styles['hyper-parameter-instance__log']}>
{instanceInfo && instanceInfo.nodeStatus && (
<LogList
instanceName={instanceInfo.argo_ins_name}
@@ -174,23 +209,14 @@ function HyperParameterInstance() {
label: '实验结果',
icon: <KFIcon type="icon-shiyanjieguo1" />,
children: (
<ExperimentResult
fileUrl={instanceInfo?.result_path}
imageUrl={instanceInfo?.img_path}
modelPath={instanceInfo?.model_path}
/>
<ExperimentResult fileUrl={instanceInfo?.result_txt} fileList={instanceInfo?.file_list} />
),
},
{
key: TabKeys.History,
label: 'Trial 列表',
icon: <KFIcon type="icon-Trialliebiao" />,
children: (
<ExperimentHistory
fileUrl={instanceInfo?.run_history_path}
isClassification={experimentInfo?.task_type === AutoMLTaskType.Classification}
/>
),
children: <ExperimentHistory trialList={instanceInfo?.trial_list} />,
},
];

@@ -200,9 +226,9 @@ function HyperParameterInstance() {
: basicTabItems;

return (
<div className={styles['auto-ml-instance']}>
<div className={styles['hyper-parameter-instance']}>
<Tabs
className={styles['auto-ml-instance__tabs']}
className={styles['hyper-parameter-instance__tabs']}
items={tabItems}
activeKey={activeTab}
onChange={setActiveTab}


+ 21
- 0
react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.less View File

@@ -10,5 +10,26 @@
&__table {
height: 100%;
}

:global {
.ant-table-container {
border: none !important;
}
.ant-table-thead {
.ant-table-cell {
background-color: rgb(247, 247, 247);
border-color: @border-color !important;
}
}
.ant-table-tbody {
.ant-table-cell {
border-right: none !important;
border-left: none !important;
}
}
.ant-table-tbody-virtual::after {
border-bottom: none !important;
}
}
}
}

+ 79
- 89
react-ui/src/pages/HyperParameter/components/ExperimentHistory/index.tsx View File

@@ -1,101 +1,44 @@
import { getFileReq } from '@/services/file';
import { to } from '@/utils/promise';
import tableCellRender from '@/utils/table';
import { Table, type TableProps } from 'antd';
import { HyperParameterTrialList } from '@/pages/HyperParameter/types';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { Table, Tooltip, type TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import styles from './index.less';

type ExperimentHistoryProps = {
fileUrl?: string;
isClassification: boolean;
trialList?: HyperParameterTrialList[];
};

type TableData = {
id?: string;
accuracy?: number;
duration?: number;
train_loss?: number;
status?: string;
feature?: string;
althorithm?: string;
};

function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps) {
const [tableData, setTableData] = useState<TableData[]>([]);
useEffect(() => {
if (fileUrl) {
getHistoryFile();
}
}, [fileUrl]);

// 获取实验运行历史记录
const getHistoryFile = async () => {
const [res] = await to(getFileReq(fileUrl));
if (res) {
const data: any[] = res.data;
const list: TableData[] = data.map((item) => {
return {
id: item[0]?.[0],
accuracy: item[1]?.[5]?.accuracy,
duration: item[1]?.[5]?.duration,
train_loss: item[1]?.[5]?.train_loss,
status: item[1]?.[2]?.['__enum__']?.split('.')?.[1],
};
});
list.forEach((item) => {
if (!item.id) return;
const config = (res as any).configs?.[item.id];
item.feature = config?.['feature_preprocessor:__choice__'];
item.althorithm = isClassification
? config?.['classifier:__choice__']
: config?.['regressor:__choice__'];
});
setTableData(list);
}
};
function ExperimentHistory({ trialList = [] }: ExperimentHistoryProps) {
const first: HyperParameterTrialList | undefined = trialList[0];
const config: Record<string, any> = first?.config ?? {};
const metricAnalysis: Record<string, any> = first?.metric_analysis ?? {};
const paramsNames = Object.keys(config);
const metricNames = Object.keys(metricAnalysis);

const columns: TableProps<TableData>['columns'] = [
{
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 80,
render: tableCellRender(false),
},
{
title: '准确率',
dataIndex: 'accuracy',
key: 'accuracy',
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
const columns: TableProps<HyperParameterTrialList>['columns'] = [
{
title: '耗时',
dataIndex: 'duration',
key: 'duration',
render: tableCellRender(true),
ellipsis: { showTitle: false },
title: '序号',
dataIndex: 'index',
key: 'index',
width: 100,
align: 'center',
render: tableCellRender(false, TableCellValueType.Index),
},
{
title: '训练损失',
dataIndex: 'train_loss',
key: 'train_loss',
render: tableCellRender(true),
ellipsis: { showTitle: false },
},
{
title: '特征处理',
dataIndex: 'feature',
key: 'feature',
render: tableCellRender(true),
ellipsis: { showTitle: false },
title: '运行次数',
dataIndex: 'training_iteration',
key: 'training_iteration',
width: 120,
render: tableCellRender(false),
},
{
title: '算法',
dataIndex: 'althorithm',
key: 'althorithm',
render: tableCellRender(true),
title: '平均时长(秒)',
dataIndex: 'time_avg',
key: 'time_avg',
width: 150,
render: tableCellRender(false, TableCellValueType.Custom, {
format: (value = 0) => Number(value).toFixed(2),
}),
ellipsis: { showTitle: false },
},
{
@@ -107,6 +50,52 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps
},
];

if (paramsNames.length) {
columns.push({
title: '运行参数',
dataIndex: 'config',
key: 'config',
align: 'center',
children: paramsNames.map((name) => ({
title: (
<Tooltip title={name}>
<span>{name}</span>
</Tooltip>
),
dataIndex: ['config', name],
key: name,
width: 120,
align: 'center',
render: tableCellRender(true),
ellipsis: { showTitle: false },
showSorterTooltip: false,
})),
});
}

if (metricNames.length) {
columns.push({
title: `指标分析(${first.metric ?? ''})`,
dataIndex: 'metrics',
key: 'metrics',
align: 'center',
children: metricNames.map((name) => ({
title: (
<Tooltip title={name}>
<span>{name}</span>
</Tooltip>
),
dataIndex: ['metric_analysis', name],
key: name,
width: 120,
align: 'center',
render: tableCellRender(true),
ellipsis: { showTitle: false },
showSorterTooltip: false,
})),
});
}

return (
<div className={styles['experiment-history']}>
<div className={styles['experiment-history__content']}>
@@ -117,11 +106,12 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps
)}
>
<Table
dataSource={tableData}
dataSource={trialList}
columns={columns}
pagination={false}
scroll={{ y: 'calc(100% - 55px)' }}
rowKey="id"
bordered={true}
scroll={{ y: 'calc(100% - 110px)', x: '100%' }}
rowKey="trial_id"
/>
</div>
</div>


+ 83
- 0
react-ui/src/pages/HyperParameter/components/ExperimentLog/index.tsx View File

@@ -0,0 +1,83 @@
import KFIcon from '@/components/KFIcon';
import { ExperimentStatus } from '@/enums';
import LogList from '@/pages/Experiment/components/LogList';
import { HyperParameterInstanceData } from '@/pages/HyperParameter/types';
import { Tabs } from 'antd';
import { useEffect } from 'react';
import styles from './index.less';

type ExperimentLogProps = {
instanceInfo: HyperParameterInstanceData;
};

function ExperimentLog({ instanceInfo }: ExperimentLogProps) {
const tabItems = [
{
key: 'git-clone-1',
label: '框架代码日志',
icon: <KFIcon type="icon-jibenxinxi" />,
children: (
<div className={styles['auto-ml-instance__log']}>
{instanceInfo && instanceInfo.nodeStatus && (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
pipelineNodeId={instanceInfo.nodeStatus.displayName}
workflowId={instanceInfo.nodeStatus.id}
instanceNodeStartTime={instanceInfo.nodeStatus.startedAt}
instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus}
></LogList>
)}
</div>
),
},
{
key: 'git-clone-2',
label: '训练代码日志',
icon: <KFIcon type="icon-rizhi1" />,
children: (
<div className={styles['auto-ml-instance__log']}>
{instanceInfo && instanceInfo.nodeStatus && (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
pipelineNodeId={instanceInfo.nodeStatus.displayName}
workflowId={instanceInfo.nodeStatus.id}
instanceNodeStartTime={instanceInfo.nodeStatus.startedAt}
instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus}
></LogList>
)}
</div>
),
},
{
key: 'auto-hpo',
label: '超参寻优日志',
icon: <KFIcon type="icon-rizhi1" />,
children: (
<div className={styles['auto-ml-instance__log']}>
{instanceInfo && instanceInfo.nodeStatus && (
<LogList
instanceName={instanceInfo.argo_ins_name}
instanceNamespace={instanceInfo.argo_ins_ns}
pipelineNodeId={instanceInfo.nodeStatus.displayName}
workflowId={instanceInfo.nodeStatus.id}
instanceNodeStartTime={instanceInfo.nodeStatus.startedAt}
instanceNodeStatus={instanceInfo.nodeStatus.phase as ExperimentStatus}
></LogList>
)}
</div>
),
},
];

useEffect(() => {}, []);

return (
<div className={styles['experiment-log']}>
<Tabs className={styles['auto-ml-instance__tabs']} items={tabItems} />
</div>
);
}

export default ExperimentLog;

+ 3
- 38
react-ui/src/pages/HyperParameter/components/ExperimentResult/index.less View File

@@ -6,47 +6,12 @@
background-color: white;
border-radius: 10px;

&__download {
padding-top: 16px;
padding-bottom: 16px;

padding-left: @content-padding;
color: @text-color;
font-size: 13px;
background-color: #f8f8f9;
border-radius: 4px;

&__btn {
display: block;
height: 36px;
margin-top: 15px;
font-size: 14px;
}
&__table {
height: 400px;
}

&__text {
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
white-space: pre-wrap;
}

&__images {
display: flex;
align-items: flex-start;
width: 100%;
overflow-x: auto;

:global {
.ant-image {
margin-right: 20px;

&:last-child {
margin-right: 0;
}
}
}

&__item {
height: 248px;
border: 1px solid rgba(96, 107, 122, 0.3);
}
}
}

+ 46
- 47
react-ui/src/pages/HyperParameter/components/ExperimentResult/index.tsx View File

@@ -1,25 +1,44 @@
import InfoGroup from '@/components/InfoGroup';
import { HyperParameterFileList } from '@/pages/HyperParameter/types';
import { getFileReq } from '@/services/file';
import { to } from '@/utils/promise';
import { Button, Image } from 'antd';
import { useEffect, useMemo, useState } from 'react';
import tableCellRender, { TableCellValueType } from '@/utils/table';
import { Table, type TableProps } from 'antd';
import classNames from 'classnames';
import { useEffect, useState } from 'react';
import styles from './index.less';

type ExperimentResultProps = {
fileList?: HyperParameterFileList[];
fileUrl?: string;
imageUrl?: string;
modelPath?: string;
};

function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProps) {
function ExperimentResult({ fileList, fileUrl }: ExperimentResultProps) {
const [result, setResult] = useState<string | undefined>('');

const images = useMemo(() => {
if (imageUrl) {
return imageUrl.split(',').map((item) => item.trim());
}
return [];
}, [imageUrl]);
const columns: TableProps<HyperParameterFileList>['columns'] = [
{
title: '序号',
dataIndex: 'index',
key: 'index',
width: 120,
align: 'center',
render: tableCellRender(false, TableCellValueType.Index),
},
{
title: '文件名称',
dataIndex: 'name',
key: 'name',
render: tableCellRender(false),
},
{
title: '文件大小',
dataIndex: 'size',
key: 'size',
width: 200,
render: tableCellRender(false),
},
];

useEffect(() => {
if (fileUrl) {
@@ -37,45 +56,25 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp

return (
<div className={styles['experiment-result']}>
<InfoGroup title="文件列表" style={{ margin: '16px 0' }}>
<div
className={classNames(
'vertical-scroll-table-no-page',
styles['experiment-result__table'],
)}
>
<Table
dataSource={fileList}
columns={columns}
pagination={false}
scroll={{ y: 'calc(100% - 55px)', x: '100%' }}
rowKey="name"
/>
</div>
</InfoGroup>
<InfoGroup title="实验结果" height={420} width="100%">
<div className={styles['experiment-result__text']}>{result}</div>
</InfoGroup>
<InfoGroup title="可视化结果" style={{ margin: '16px 0' }}>
<div className={styles['experiment-result__images']}>
<Image.PreviewGroup
preview={{
onChange: (current, prev) =>
console.log(`current index: ${current}, prev index: ${prev}`),
}}
>
{images.map((item) => (
<Image
key={item}
className={styles['experiment-result__images__item']}
src={item}
height={248}
draggable={false}
alt=""
/>
))}
</Image.PreviewGroup>
</div>
</InfoGroup>
{modelPath && (
<div className={styles['experiment-result__download']}>
<span style={{ marginRight: '12px', color: '#606b7a' }}>文件名</span>
<span>save_model.joblib</span>
<Button
type="primary"
className={styles['experiment-result__download__btn']}
onClick={() => {
window.location.href = modelPath;
}}
>
模型下载
</Button>
</div>
)}
</div>
);
}


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

@@ -90,7 +90,7 @@ function HyperParameterBasic({
return [
{
label: '代码',
value: info.code,
value: info.code_config,
format: formatCodeConfig,
},
{


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

@@ -42,13 +42,11 @@ export type HyperParameterData = {
} & FormData;

// 自动机器学习实验实例
export type AutoMLInstanceData = {
export type HyperParameterInstanceData = {
id: number;
auto_ml_id: number;
ray_id: number;
result_path: string;
model_path: string;
img_path: string;
run_history_path: string;
result_txt: string;
state: number;
status: string;
node_status: string;
@@ -60,5 +58,22 @@ export type AutoMLInstanceData = {
create_time: string;
update_time: string;
finish_time: string;
nodeStatus?: NodeStatus;
nodeStatus?: NodeStatus; // json之后的节点状态
trial_list?: HyperParameterTrialList[];
file_list?: HyperParameterFileList[];
};

export type HyperParameterTrialList = {
trial_id?: string;
training_iteration?: number;
time?: number;
status?: string;
config?: Record<string, any>;
metric_analysis?: Record<string, any>;
metric: string;
};

export type HyperParameterFileList = {
name?: string;
size?: string;
};

Loading…
Cancel
Save