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'; import './index.less';


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


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


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

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


const NodePrefix = 'auto-hpo';

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


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

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


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




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

@@ -18,26 +18,6 @@
background-color: white; background-color: white;
border-radius: 10px; 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 { :global {
.ant-table-container { .ant-table-container {
border: none !important; border: none !important;


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

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


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


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

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


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

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


return ( return (
<div className={styles['create-hyperparameter']}>
<div className={styles['create-hyper-parameter']}>
<PageTitle title={title}></PageTitle> <PageTitle title={title}></PageTitle>
<div className={styles['create-hyperparameter__content']}>
<div className={styles['create-hyper-parameter__content']}>
<div> <div>
<Form <Form
name="create-hyperparameter" 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; position: relative;
height: 100%; height: 100%;
&__tabs { &__tabs {


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

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


return ( return (
<div className={styles['auto-ml-info']}>
<div className={styles['hyper-parameter-info']}>
<PageTitle title="实验详情"></PageTitle> <PageTitle title="实验详情"></PageTitle>
<div className={styles['auto-ml-info__content']}>
<div className={styles['hyper-parameter-info__content']}>
<HyperParameterBasic info={hyperparameterInfo} /> <HyperParameterBasic info={hyperparameterInfo} />
</div> </div>
</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%; height: 100%;


&__tabs { &__tabs {


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

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


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


const NodePrefix = 'auto-hpo';

function HyperParameterInstance() { function HyperParameterInstance() {
const [activeTab, setActiveTab] = useState<string>(TabKeys.Params); const [activeTab, setActiveTab] = useState<string>(TabKeys.Params);
const [experimentInfo, setExperimentInfo] = useState<HyperParameterData | undefined>(undefined); 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 params = useParams();
const instanceId = safeInvoke(Number)(params.id); const instanceId = safeInvoke(Number)(params.id);
const evtSourceRef = useRef<EventSource | null>(null); const evtSourceRef = useRef<EventSource | null>(null);
@@ -43,11 +49,26 @@ function HyperParameterInstance() {
const getExperimentInsInfo = async (isStatusDetermined: boolean) => { const getExperimentInsInfo = async (isStatusDetermined: boolean) => {
const [res] = await to(getRayInsReq(instanceId)); const [res] = await to(getRayInsReq(instanceId));
if (res && res.data) { 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 { param, node_status, argo_ins_name, argo_ins_ns, status } = info;
// 解析配置参数 // 解析配置参数
const paramJson = parseJsonText(param); const paramJson = parseJsonText(param);
if (paramJson) { 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); setExperimentInfo(paramJson);
} }


@@ -65,13 +86,17 @@ function HyperParameterInstance() {
const nodeStatusJson = parseJsonText(node_status); const nodeStatusJson = parseJsonText(node_status);
if (nodeStatusJson) { if (nodeStatusJson) {
Object.keys(nodeStatusJson).forEach((key) => { 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); setInstanceInfo(info);

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

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

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


// 实验结束,关闭 SSE // 实验结束,关闭 SSE
if ( if (
statusData.phase !== ExperimentStatus.Pending &&
statusData.phase !== ExperimentStatus.Running
workflowStatus.phase !== ExperimentStatus.Pending &&
workflowStatus.phase !== ExperimentStatus.Running
) { ) {
closeSSE(); closeSSE();
getExperimentInsInfo(true); getExperimentInsInfo(true);
@@ -140,9 +175,9 @@ function HyperParameterInstance() {
icon: <KFIcon type="icon-jibenxinxi" />, icon: <KFIcon type="icon-jibenxinxi" />,
children: ( children: (
<HyperParameterBasic <HyperParameterBasic
className={styles['auto-ml-instance__basic']}
className={styles['hyper-parameter-instance__basic']}
info={experimentInfo} info={experimentInfo}
runStatus={instanceInfo?.nodeStatus}
runStatus={workflowStatus}
isInstance isInstance
/> />
), ),
@@ -152,7 +187,7 @@ function HyperParameterInstance() {
label: '日志', label: '日志',
icon: <KFIcon type="icon-rizhi1" />, icon: <KFIcon type="icon-rizhi1" />,
children: ( children: (
<div className={styles['auto-ml-instance__log']}>
<div className={styles['hyper-parameter-instance__log']}>
{instanceInfo && instanceInfo.nodeStatus && ( {instanceInfo && instanceInfo.nodeStatus && (
<LogList <LogList
instanceName={instanceInfo.argo_ins_name} instanceName={instanceInfo.argo_ins_name}
@@ -174,23 +209,14 @@ function HyperParameterInstance() {
label: '实验结果', label: '实验结果',
icon: <KFIcon type="icon-shiyanjieguo1" />, icon: <KFIcon type="icon-shiyanjieguo1" />,
children: ( 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, key: TabKeys.History,
label: 'Trial 列表', label: 'Trial 列表',
icon: <KFIcon type="icon-Trialliebiao" />, 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; : basicTabItems;


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


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

@@ -10,5 +10,26 @@
&__table { &__table {
height: 100%; 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 classNames from 'classnames';
import { useEffect, useState } from 'react';
import styles from './index.less'; import styles from './index.less';


type ExperimentHistoryProps = { 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 }, 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 ( return (
<div className={styles['experiment-history']}> <div className={styles['experiment-history']}>
<div className={styles['experiment-history__content']}> <div className={styles['experiment-history__content']}>
@@ -117,11 +106,12 @@ function ExperimentHistory({ fileUrl, isClassification }: ExperimentHistoryProps
)} )}
> >
<Table <Table
dataSource={tableData}
dataSource={trialList}
columns={columns} columns={columns}
pagination={false} pagination={false}
scroll={{ y: 'calc(100% - 55px)' }}
rowKey="id"
bordered={true}
scroll={{ y: 'calc(100% - 110px)', x: '100%' }}
rowKey="trial_id"
/> />
</div> </div>
</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; background-color: white;
border-radius: 10px; 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 { &__text {
font-family: 'Roboto Mono', 'Menlo', 'Consolas', 'Monaco', monospace;
white-space: pre-wrap; 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 InfoGroup from '@/components/InfoGroup';
import { HyperParameterFileList } from '@/pages/HyperParameter/types';
import { getFileReq } from '@/services/file'; import { getFileReq } from '@/services/file';
import { to } from '@/utils/promise'; 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'; import styles from './index.less';


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


function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProps) {
function ExperimentResult({ fileList, fileUrl }: ExperimentResultProps) {
const [result, setResult] = useState<string | undefined>(''); 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(() => { useEffect(() => {
if (fileUrl) { if (fileUrl) {
@@ -37,45 +56,25 @@ function ExperimentResult({ fileUrl, imageUrl, modelPath }: ExperimentResultProp


return ( return (
<div className={styles['experiment-result']}> <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%"> <InfoGroup title="实验结果" height={420} width="100%">
<div className={styles['experiment-result__text']}>{result}</div> <div className={styles['experiment-result__text']}>{result}</div>
</InfoGroup> </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> </div>
); );
} }


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

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


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

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


// 自动机器学习实验实例 // 自动机器学习实验实例
export type AutoMLInstanceData = {
export type HyperParameterInstanceData = {
id: number; id: number;
auto_ml_id: number;
ray_id: number;
result_path: string; result_path: string;
model_path: string;
img_path: string;
run_history_path: string;
result_txt: string;
state: number; state: number;
status: string; status: string;
node_status: string; node_status: string;
@@ -60,5 +58,22 @@ export type AutoMLInstanceData = {
create_time: string; create_time: string;
update_time: string; update_time: string;
finish_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