Unverified Commit c785655e authored by SparkSnail's avatar SparkSnail Committed by GitHub
Browse files

Merge pull request #207 from microsoft/master

merge master
parents 9fae194a d6b61e2f
import * as React from 'react'; import * as React from 'react';
import { TableObj } from '../../static/interface'; import { TRIALS } from '../../static/datamodel';
import { formatAccuracy } from '../../static/function';
interface DefaultMetricProps { interface DefaultMetricProps {
record: TableObj; trialId: string;
} }
class DefaultMetric extends React.Component<DefaultMetricProps, {}> { class DefaultMetric extends React.Component<DefaultMetricProps, {}> {
constructor(props: DefaultMetricProps) { constructor(props: DefaultMetricProps) {
super(props); super(props);
} }
render() { render() {
const { record } = this.props; const accuracy = TRIALS.getTrial(this.props.trialId).accuracy;
let accuracy;
if (record.acc !== undefined) {
accuracy = record.acc.default;
}
let wei = 0;
if (accuracy !== undefined) {
if (accuracy.toString().indexOf('.') !== -1) {
wei = accuracy.toString().length - accuracy.toString().indexOf('.') - 1;
}
}
return ( return (
<div> <div>{accuracy !== undefined ? formatAccuracy(accuracy) : '--'}</div>
{
record.acc !== undefined && record.acc.default !== undefined
?
wei > 6
?
JSON.parse(record.acc.default).toFixed(6)
:
record.acc.default
:
'--'
}
</div>
); );
} }
} }
export default DefaultMetric; export default DefaultMetric;
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { TableObj } from '../../static/interface'; import { TRIALS } from '../../static/datamodel';
interface IntermediateValProps { interface IntermediateValProps {
record: TableObj; trialId: string;
} }
class IntermediateVal extends React.Component<IntermediateValProps, {}> { class IntermediateVal extends React.Component<IntermediateValProps, {}> {
constructor(props: IntermediateValProps) { constructor(props: IntermediateValProps) {
super(props); super(props);
} }
render() { render() {
const { record } = this.props;
const interArr = record.description.intermediate;
let lastVal;
let wei = 0;
if (interArr !== undefined) {
lastVal = interArr[interArr.length - 1];
}
let result: string = JSON.stringify(lastVal);
if (lastVal !== undefined) {
if (lastVal.toString().indexOf('.') !== -1) {
wei = lastVal.toString().length - lastVal.toString().indexOf('.') - 1;
if (wei > 6) {
result = `${lastVal.toFixed(6)}`;
}
}
// some trial haven't final result
if (record.acc !== undefined) {
if (record.acc.default !== undefined) {
result = `${result} (FINAL)`;
}
} else {
result = `${result} (LATEST)`;
}
} else {
result = '--';
}
return ( return (
<div>{result}</div> <div>{TRIALS.getTrial(this.props.trialId).formatLatestAccuracy()}</div>
); );
} }
} }
......
...@@ -2,7 +2,8 @@ import * as React from 'react'; ...@@ -2,7 +2,8 @@ import * as React from 'react';
import * as copy from 'copy-to-clipboard'; import * as copy from 'copy-to-clipboard';
import PaiTrialLog from '../public-child/PaiTrialLog'; import PaiTrialLog from '../public-child/PaiTrialLog';
import TrialLog from '../public-child/TrialLog'; import TrialLog from '../public-child/TrialLog';
import { TableObj } from '../../static/interface'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { Trial } from '../../static/model/trial';
import { Row, Tabs, Button, message, Modal } from 'antd'; import { Row, Tabs, Button, message, Modal } from 'antd';
import { MANAGER_IP } from '../../static/const'; import { MANAGER_IP } from '../../static/const';
import '../../static/style/overview.scss'; import '../../static/style/overview.scss';
...@@ -11,10 +12,7 @@ import JSONTree from 'react-json-tree'; ...@@ -11,10 +12,7 @@ import JSONTree from 'react-json-tree';
const TabPane = Tabs.TabPane; const TabPane = Tabs.TabPane;
interface OpenRowProps { interface OpenRowProps {
trainingPlatform: string; trialId: string;
record: TableObj;
logCollection: boolean;
multiphase: boolean;
} }
interface OpenRowState { interface OpenRowState {
...@@ -24,7 +22,6 @@ interface OpenRowState { ...@@ -24,7 +22,6 @@ interface OpenRowState {
class OpenRow extends React.Component<OpenRowProps, OpenRowState> { class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
public _isMounted: boolean;
constructor(props: OpenRowProps) { constructor(props: OpenRowProps) {
super(props); super(props);
this.state = { this.state = {
...@@ -33,20 +30,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -33,20 +30,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
}; };
} }
showFormatModal = (record: TableObj) => { showFormatModal = (trial: Trial) => {
// get copy parameters // get copy parameters
const params = JSON.stringify(record.description.parameters, null, 4); const params = JSON.stringify(trial.info.hyperParameters, null, 4);
// open modal with format string // open modal with format string
if (this._isMounted === true) { this.setState({ isShowFormatModal: true, formatStr: params });
this.setState(() => ({ isShowFormatModal: true, formatStr: params }));
}
} }
hideFormatModal = () => { hideFormatModal = () => {
// close modal, destroy state format string data // close modal, destroy state format string data
if (this._isMounted === true) { this.setState({ isShowFormatModal: false, formatStr: '' });
this.setState(() => ({ isShowFormatModal: false, formatStr: '' }));
}
} }
copyParams = () => { copyParams = () => {
...@@ -62,68 +55,47 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -62,68 +55,47 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
this.hideFormatModal(); this.hideFormatModal();
} }
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { trainingPlatform, record, logCollection, multiphase } = this.props;
const { isShowFormatModal, formatStr } = this.state; const { isShowFormatModal, formatStr } = this.state;
let isClick = false; const trialId = this.props.trialId;
let isHasParameters = true; const trial = TRIALS.getTrial(trialId);
if (record.description.parameters.error) { const trialLink: string = `${MANAGER_IP}/trial-jobs/${trialId}`;
isHasParameters = false; const logPathRow = trial.info.logPath || 'This trial\'s log path is not available.';
} const multiProgress = trial.info.hyperParameters === undefined ? 0 : trial.info.hyperParameters.length;
const openRowDataSource = record.description.parameters;
const trialink: string = `${MANAGER_IP}/trial-jobs/${record.id}`;
const logPathRow = record.description.logPath !== undefined
?
record.description.logPath
:
'This trial\'s log path are not available.';
return ( return (
<Row className="openRowContent hyperpar"> <Row className="openRowContent hyperpar">
<Tabs tabPosition="left" className="card"> <Tabs tabPosition="left" className="card">
<TabPane tab="Parameters" key="1"> <TabPane tab="Parameters" key="1">
{ {
multiphase EXPERIMENT.multiPhase
? ?
<Row className="link"> <Row className="link">
Trails for multiphase experiment will return a set of parameters, Trails for multiphase experiment will return a set of parameters,
we are listing the latest parameter in webportal. we are listing the latest parameter in webportal.
<br /> <br />
For the entire parameter set, please refer to the following " For the entire parameter set, please refer to the following "
<a href={trialink} target="_blank">{trialink}</a>". <a href={trialLink} target="_blank">{trialLink}</a>".
<br/> <br />
Current Phase: {record.description.multiProgress}. Current Phase: {multiProgress}.
</Row> </Row>
: :
<div /> <div />
} }
{ {
isHasParameters trial.info.hyperParameters !== undefined
? ?
<Row id="description"> <Row id="description">
<Row className="bgHyper"> <Row className="bgHyper">
{ <JSONTree
isClick hideRoot={true}
? shouldExpandNode={() => true} // default expandNode
<pre>{JSON.stringify(openRowDataSource, null, 4)}</pre> getItemString={() => (<span />)} // remove the {} items
: data={trial.description.parameters}
<JSONTree />
hideRoot={true}
shouldExpandNode={() => true} // default expandNode
getItemString={() => (<span />)} // remove the {} items
data={openRowDataSource}
/>
}
</Row> </Row>
<Row className="copy"> <Row className="copy">
<Button <Button
onClick={this.showFormatModal.bind(this, record)} onClick={this.showFormatModal.bind(this, trial)}
> >
Copy as json Copy as json
</Button> </Button>
...@@ -138,15 +110,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -138,15 +110,16 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
</TabPane> </TabPane>
<TabPane tab="Log" key="2"> <TabPane tab="Log" key="2">
{ {
trainingPlatform !== 'local' // FIXME: this should not be handled in web UI side
EXPERIMENT.trainingServicePlatform !== 'local'
? ?
<PaiTrialLog <PaiTrialLog
logStr={logPathRow} logStr={logPathRow}
id={record.id} id={trialId}
logCollection={logCollection} logCollection={EXPERIMENT.logCollectionEnabled}
/> />
: :
<TrialLog logStr={logPathRow} id={record.id} /> <TrialLog logStr={logPathRow} id={trialId} />
} }
</TabPane> </TabPane>
</Tabs> </Tabs>
...@@ -170,4 +143,4 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> { ...@@ -170,4 +143,4 @@ class OpenRow extends React.Component<OpenRowProps, OpenRowState> {
} }
} }
export default OpenRow; export default OpenRow;
\ No newline at end of file
import * as React from 'react'; import * as React from 'react';
import { Switch } from 'antd'; import { Switch } from 'antd';
import ReactEcharts from 'echarts-for-react'; import ReactEcharts from 'echarts-for-react';
import { filterByStatus } from '../../static/function'; import { EXPERIMENT, TRIALS } from '../../static/datamodel';
import { TableObj, DetailAccurPoint, TooltipForAccuracy } from '../../static/interface'; import { Trial } from '../../static/model/trial';
import { TooltipForAccuracy } from '../../static/interface';
require('echarts/lib/chart/scatter'); require('echarts/lib/chart/scatter');
require('echarts/lib/component/tooltip'); require('echarts/lib/component/tooltip');
require('echarts/lib/component/title'); require('echarts/lib/component/title');
interface DefaultPointProps { interface DefaultPointProps {
showSource: Array<TableObj>; trialIds: string[];
height: number; visible: boolean;
whichGraph: string; trialsUpdateBroadcast: number;
optimize: string;
} }
interface DefaultPointState { interface DefaultPointState {
defaultSource: object; bestCurveEnabled: boolean;
accNodata: string;
succeedTrials: number;
isViewBestCurve: boolean;
} }
class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> { class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> {
public _isDefaultMounted = false;
constructor(props: DefaultPointProps) { constructor(props: DefaultPointProps) {
super(props); super(props);
this.state = { this.state = { bestCurveEnabled: false };
defaultSource: {},
accNodata: '',
succeedTrials: 10000000,
isViewBestCurve: false
};
}
defaultMetric = (succeedSource: Array<TableObj>, isCurve: boolean) => {
const { optimize } = this.props;
const accSource: Array<DetailAccurPoint> = [];
const drawSource: Array<TableObj> = succeedSource.filter(filterByStatus);
const lengthOfSource = drawSource.length;
const tooltipDefault = lengthOfSource === 0 ? 'No data' : '';
if (this._isDefaultMounted === true) {
this.setState(() => ({
succeedTrials: lengthOfSource,
accNodata: tooltipDefault
}));
}
if (lengthOfSource === 0) {
const nullGraph = {
grid: {
left: '8%'
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
}
};
if (this._isDefaultMounted === true) {
this.setState(() => ({
defaultSource: nullGraph
}));
}
} else {
const resultList: Array<number | object>[] = [];
// lineListDefault: [[sequenceId, default metric], []]
const lineListDefault: Array<number>[] = [];
Object.keys(drawSource).map(item => {
const temp = drawSource[item];
if (temp.acc !== undefined) {
if (temp.acc.default !== undefined) {
const searchSpace = temp.description.parameters;
lineListDefault.push([temp.sequenceId, temp.acc.default]);
accSource.push({
acc: temp.acc.default,
index: temp.sequenceId,
searchSpace: searchSpace
});
}
}
});
// deal with best metric line
const bestCurve: Array<number | object>[] = []; // best curve data source
if (lineListDefault[0] !== undefined) {
bestCurve.push([lineListDefault[0][0], lineListDefault[0][1], accSource[0].searchSpace]);
}
if (optimize === 'maximize') {
for (let i = 1; i < lineListDefault.length; i++) {
const val = lineListDefault[i][1];
const latest = bestCurve[bestCurve.length - 1][1];
if (val >= latest) {
bestCurve.push([lineListDefault[i][0], val, accSource[i].searchSpace]);
} else {
bestCurve.push([lineListDefault[i][0], latest, accSource[i].searchSpace]);
}
}
} else {
for (let i = 1; i < lineListDefault.length; i++) {
const val = lineListDefault[i][1];
const latest = bestCurve[bestCurve.length - 1][1];
if (val <= latest) {
bestCurve.push([lineListDefault[i][0], val, accSource[i].searchSpace]);
} else {
bestCurve.push([lineListDefault[i][0], latest, accSource[i].searchSpace]);
}
}
}
Object.keys(accSource).map(item => {
const items = accSource[item];
let temp: Array<number | object>;
temp = [items.index, items.acc, items.searchSpace];
resultList.push(temp);
});
// isViewBestCurve: false show default metric graph
// isViewBestCurve: true show best curve
if (isCurve === true) {
if (this._isDefaultMounted === true) {
this.setState(() => ({
defaultSource: this.drawBestcurve(bestCurve, resultList)
}));
}
} else {
if (this._isDefaultMounted === true) {
this.setState(() => ({
defaultSource: this.drawDefaultMetric(resultList)
}));
}
}
}
}
drawBestcurve = (realDefault: Array<number | object>[], resultList: Array<number | object>[]) => {
return {
grid: {
left: '8%'
},
tooltip: {
trigger: 'item',
enterable: true,
position: function (point: Array<number>, data: TooltipForAccuracy) {
if (data.data[0] < realDefault.length / 2) {
return [point[0], 80];
} else {
return [point[0] - 300, 80];
}
},
formatter: function (data: TooltipForAccuracy) {
const result = '<div class="tooldetailAccuracy">' +
'<div>Trial No.: ' + data.data[0] + '</div>' +
'<div>Optimization curve: ' + data.data[1] + '</div>' +
'<div>Parameters: ' +
'<pre>' + JSON.stringify(data.data[2], null, 4) + '</pre>' +
'</div>' +
'</div>';
return result;
}
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
scale: true
},
series: [
{
type: 'line',
lineStyle: { color: '#FF6600' },
data: realDefault
},
{
symbolSize: 6,
type: 'scatter',
data: resultList
}]
};
}
drawDefaultMetric = (resultList: Array<number | object>[]) => {
return {
grid: {
left: '8%'
},
tooltip: {
trigger: 'item',
enterable: true,
position: function (point: Array<number>, data: TooltipForAccuracy) {
if (data.data[0] < resultList.length / 2) {
return [point[0], 80];
} else {
return [point[0] - 300, 80];
}
},
formatter: function (data: TooltipForAccuracy) {
const result = '<div class="tooldetailAccuracy">' +
'<div>Trial No.: ' + data.data[0] + '</div>' +
'<div>Default metric: ' + data.data[1] + '</div>' +
'<div>Parameters: ' +
'<pre>' + JSON.stringify(data.data[2], null, 4) + '</pre>' +
'</div>' +
'</div>';
return result;
}
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
scale: true
},
series: [{
symbolSize: 6,
type: 'scatter',
data: resultList
}]
};
} }
loadDefault = (checked: boolean) => { loadDefault = (checked: boolean) => {
// checked: true show best metric curve this.setState({ bestCurveEnabled: checked });
const { showSource } = this.props;
if (this._isDefaultMounted === true) {
this.defaultMetric(showSource, checked);
// ** deal with data and then update view layer
this.setState(() => ({ isViewBestCurve: checked }));
}
}
// update parent component state
componentWillReceiveProps(nextProps: DefaultPointProps) {
const { whichGraph, showSource } = nextProps;
const { isViewBestCurve } = this.state;
if (whichGraph === '1') {
this.defaultMetric(showSource, isViewBestCurve);
}
} }
shouldComponentUpdate(nextProps: DefaultPointProps, nextState: DefaultPointState) { shouldComponentUpdate(nextProps: DefaultPointProps, nextState: DefaultPointState) {
const { whichGraph } = nextProps; return nextProps.visible;
if (whichGraph === '1') {
const { succeedTrials, isViewBestCurve } = nextState;
const succTrial = this.state.succeedTrials;
const isViewBestCurveBefore = this.state.isViewBestCurve;
if (isViewBestCurveBefore !== isViewBestCurve) {
return true;
}
if (succeedTrials !== succTrial) {
return true;
}
}
// only whichGraph !== '1', default metric can't update
return false;
}
componentDidMount() {
this._isDefaultMounted = true;
}
componentWillUnmount() {
this._isDefaultMounted = false;
} }
render() { render() {
const { height } = this.props; const graph = this.generateGraph();
const { defaultSource, accNodata } = this.state; const accNodata = (graph === EmptyGraph ? 'No data' : '');
return ( return (
<div> <div>
<div className="default-metric"> <div className="default-metric">
...@@ -282,10 +45,10 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> ...@@ -282,10 +45,10 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState>
</div> </div>
</div> </div>
<ReactEcharts <ReactEcharts
option={defaultSource} option={graph}
style={{ style={{
width: '100%', width: '100%',
height: height, height: 402,
margin: '0 auto', margin: '0 auto',
}} }}
theme="my_theme" theme="my_theme"
...@@ -295,6 +58,102 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState> ...@@ -295,6 +58,102 @@ class DefaultPoint extends React.Component<DefaultPointProps, DefaultPointState>
</div> </div>
); );
} }
private generateGraph() {
const trials = TRIALS.getTrials(this.props.trialIds).filter(trial => trial.sortable);
if (trials.length === 0) {
return EmptyGraph;
}
const graph = generateGraphConfig(trials[trials.length - 1].sequenceId);
if (this.state.bestCurveEnabled) {
(graph as any).series = [ generateBestCurveSeries(trials), generateScatterSeries(trials) ];
} else {
(graph as any).series = [ generateScatterSeries(trials) ];
}
return graph;
}
}
const EmptyGraph = {
grid: {
left: '8%'
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
}
};
function generateGraphConfig(maxSequenceId: number) {
return {
grid: {
left: '8%',
},
tooltip: {
trigger: 'item',
enterable: true,
position: (point: Array<number>, data: TooltipForAccuracy) => (
[ (data.data[0] < maxSequenceId ? point[0] : (point[0] - 300)), 80 ]
),
formatter: (data: TooltipForAccuracy) => (
'<div class="tooldetailAccuracy">' +
'<div>Trial No.: ' + data.data[0] + '</div>' +
'<div>Default metric: ' + data.data[1] + '</div>' +
'<div>Parameters: <pre>' + JSON.stringify(data.data[2], null, 4) + '</pre></div>' +
'</div>'
),
},
xAxis: {
name: 'Trial',
type: 'category',
},
yAxis: {
name: 'Default metric',
type: 'value',
scale: true,
},
series: undefined,
};
}
function generateScatterSeries(trials: Trial[]) {
const data = trials.map(trial => [
trial.sequenceId,
trial.accuracy,
trial.description.parameters,
]);
return {
symbolSize: 6,
type: 'scatter',
data,
};
}
function generateBestCurveSeries(trials: Trial[]) {
let best = trials[0];
const data = [[ best.sequenceId, best.accuracy, best.info.hyperParameters ]];
for (let i = 1; i < trials.length; i++) {
const trial = trials[i];
const delta = trial.accuracy! - best.accuracy!;
const better = (EXPERIMENT.optimizeMode === 'minimize') ? (delta < 0) : (delta > 0);
if (better) {
data.push([ trial.sequenceId, trial.accuracy, trial.info.hyperParameters ]);
best = trial;
} else {
data.push([ trial.sequenceId, best.accuracy, trial.info.hyperParameters ]);
}
}
return {
type: 'line',
lineStyle: { color: '#FF6600' },
data,
};
} }
export default DefaultPoint; export default DefaultPoint;
\ No newline at end of file
...@@ -22,8 +22,6 @@ interface DurationState { ...@@ -22,8 +22,6 @@ interface DurationState {
class Duration extends React.Component<DurationProps, DurationState> { class Duration extends React.Component<DurationProps, DurationState> {
public _isMounted = false;
constructor(props: DurationProps) { constructor(props: DurationProps) {
super(props); super(props);
...@@ -142,15 +140,12 @@ class Duration extends React.Component<DurationProps, DurationState> { ...@@ -142,15 +140,12 @@ class Duration extends React.Component<DurationProps, DurationState> {
trialId: trialId, trialId: trialId,
trialTime: trialTime trialTime: trialTime
}); });
if (this._isMounted) { this.setState({
this.setState({ durationSource: this.getOption(trialRun[0])
durationSource: this.getOption(trialRun[0]) });
});
}
} }
componentDidMount() { componentDidMount() {
this._isMounted = true;
const { source } = this.props; const { source } = this.props;
this.drawDurationGraph(source); this.drawDurationGraph(source);
} }
...@@ -187,10 +182,6 @@ class Duration extends React.Component<DurationProps, DurationState> { ...@@ -187,10 +182,6 @@ class Duration extends React.Component<DurationProps, DurationState> {
return false; return false;
} }
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { durationSource } = this.state; const { durationSource } = this.state;
return ( return (
...@@ -206,4 +197,4 @@ class Duration extends React.Component<DurationProps, DurationState> { ...@@ -206,4 +197,4 @@ class Duration extends React.Component<DurationProps, DurationState> {
} }
} }
export default Duration; export default Duration;
\ No newline at end of file
...@@ -24,7 +24,6 @@ interface IntermediateProps { ...@@ -24,7 +24,6 @@ interface IntermediateProps {
class Intermediate extends React.Component<IntermediateProps, IntermediateState> { class Intermediate extends React.Component<IntermediateProps, IntermediateState> {
static intervalMediate = 1; static intervalMediate = 1;
public _isMounted = false;
public pointInput: HTMLInputElement | null; public pointInput: HTMLInputElement | null;
public minValInput: HTMLInputElement | null; public minValInput: HTMLInputElement | null;
public maxValInput: HTMLInputElement | null; public maxValInput: HTMLInputElement | null;
...@@ -45,12 +44,10 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -45,12 +44,10 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
drawIntermediate = (source: Array<TableObj>) => { drawIntermediate = (source: Array<TableObj>) => {
if (source.length > 0) { if (source.length > 0) {
if (this._isMounted) { this.setState({
this.setState(() => ({ length: source.length,
length: source.length, detailSource: source
detailSource: source });
}));
}
const trialIntermediate: Array<Intermedia> = []; const trialIntermediate: Array<Intermedia> = [];
Object.keys(source).map(item => { Object.keys(source).map(item => {
const temp = source[item]; const temp = source[item];
...@@ -118,11 +115,9 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -118,11 +115,9 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
}, },
series: trialIntermediate series: trialIntermediate
}; };
if (this._isMounted) { this.setState({
this.setState(() => ({ interSource: option
interSource: option });
}));
}
} else { } else {
const nullData = { const nullData = {
grid: { grid: {
...@@ -139,71 +134,60 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -139,71 +134,60 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
name: 'Metric' name: 'Metric'
} }
}; };
if (this._isMounted) { this.setState({ interSource: nullData });
this.setState(() => ({ interSource: nullData }));
}
} }
} }
// confirm btn function [filter data] // confirm btn function [filter data]
filterLines = () => { filterLines = () => {
if (this._isMounted) { const filterSource: Array<TableObj> = [];
const filterSource: Array<TableObj> = []; this.setState({ isLoadconfirmBtn: true }, () => {
this.setState({ isLoadconfirmBtn: true }, () => { const { source } = this.props;
const { source } = this.props; // get input value
// get input value const pointVal = this.pointInput !== null ? this.pointInput.value : '';
const pointVal = this.pointInput !== null ? this.pointInput.value : ''; const minVal = this.minValInput !== null ? this.minValInput.value : '';
const minVal = this.minValInput !== null ? this.minValInput.value : ''; const maxVal = this.maxValInput !== null ? this.maxValInput.value : '';
const maxVal = this.maxValInput !== null ? this.maxValInput.value : ''; // user not input message
// user not input message if (pointVal === '' || minVal === '') {
if (pointVal === '' || minVal === '') { alert('Please input filter message');
alert('Please input filter message'); } else {
// user not input max value
const position = JSON.parse(pointVal);
const min = JSON.parse(minVal);
if (maxVal === '') {
Object.keys(source).map(item => {
const temp = source[item];
const val = temp.description.intermediate[position - 1];
if (val >= min) {
filterSource.push(temp);
}
});
} else { } else {
// user not input max value const max = JSON.parse(maxVal);
const position = JSON.parse(pointVal); Object.keys(source).map(item => {
const min = JSON.parse(minVal); const temp = source[item];
if (maxVal === '') { const val = temp.description.intermediate[position - 1];
Object.keys(source).map(item => { if (val >= min && val <= max) {
const temp = source[item]; filterSource.push(temp);
const val = temp.description.intermediate[position - 1]; }
if (val >= min) { });
filterSource.push(temp);
}
});
} else {
const max = JSON.parse(maxVal);
Object.keys(source).map(item => {
const temp = source[item];
const val = temp.description.intermediate[position - 1];
if (val >= min && val <= max) {
filterSource.push(temp);
}
});
}
if (this._isMounted) {
this.setState({ filterSource: filterSource });
}
this.drawIntermediate(filterSource);
}
const counts = this.state.clickCounts + 1;
if (this._isMounted) {
this.setState({ isLoadconfirmBtn: false, clickCounts: counts });
} }
}); this.setState({ filterSource: filterSource });
} this.drawIntermediate(filterSource);
}
const counts = this.state.clickCounts + 1;
this.setState({ isLoadconfirmBtn: false, clickCounts: counts });
});
} }
switchTurn = (checked: boolean) => { switchTurn = (checked: boolean) => {
if (this._isMounted) { this.setState({ isFilter: checked });
this.setState({ isFilter: checked });
}
if (checked === false) { if (checked === false) {
this.drawIntermediate(this.props.source); this.drawIntermediate(this.props.source);
} }
} }
componentDidMount() { componentDidMount() {
this._isMounted = true;
const { source } = this.props; const { source } = this.props;
this.drawIntermediate(source); this.drawIntermediate(source);
} }
...@@ -272,10 +256,6 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -272,10 +256,6 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
return false; return false;
} }
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { interSource, isLoadconfirmBtn, isFilter } = this.state; const { interSource, isLoadconfirmBtn, isFilter } = this.state;
return ( return (
...@@ -292,7 +272,7 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -292,7 +272,7 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
isFilter isFilter
? ?
<span> <span>
<span className="filter-x"># Intermediate</span> <span className="filter-x"># Intermediate result</span>
<input <input
// placeholder="point" // placeholder="point"
ref={input => this.pointInput = input} ref={input => this.pointInput = input}
...@@ -327,7 +307,7 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState> ...@@ -327,7 +307,7 @@ class Intermediate extends React.Component<IntermediateProps, IntermediateState>
style={{ width: '100%', height: 418, margin: '0 auto' }} style={{ width: '100%', height: 418, margin: '0 auto' }}
notMerge={true} // update now notMerge={true} // update now
/> />
<div className="yAxis"># Intermediate</div> <div className="yAxis"># Intermediate result</div>
</Row> </Row>
</div> </div>
); );
......
...@@ -40,8 +40,6 @@ message.config({ ...@@ -40,8 +40,6 @@ message.config({
class Para extends React.Component<ParaProps, ParaState> { class Para extends React.Component<ParaProps, ParaState> {
public _isMounted = false;
private chartMulineStyle = { private chartMulineStyle = {
width: '100%', width: '100%',
height: 392, height: 392,
...@@ -121,15 +119,12 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -121,15 +119,12 @@ class Para extends React.Component<ParaProps, ParaState> {
this.swapGraph(paraData, swapAxisArr); this.swapGraph(paraData, swapAxisArr);
} }
this.getOption(paraData, lengthofTrials); this.getOption(paraData, lengthofTrials);
if (this._isMounted === true) { this.setState({ paraBack: paraData });
this.setState(() => ({ paraBack: paraData }));
}
} }
hyperParaPic = (source: Array<TableObj>, searchSpace: string) => { hyperParaPic = (source: Array<TableObj>, searchSpace: string) => {
// filter succeed trials [{}, {}, {}] // filter succeed trials [{}, {}, {}]
const origin = source.filter(filterByStatus); const dataSource = source.filter(filterByStatus);
const dataSource: Array<TableObj> = JSON.parse(JSON.stringify(origin));
const lenOfDataSource: number = dataSource.length; const lenOfDataSource: number = dataSource.length;
const accPara: Array<number> = []; const accPara: Array<number> = [];
// specific value array // specific value array
...@@ -139,15 +134,13 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -139,15 +134,13 @@ class Para extends React.Component<ParaProps, ParaState> {
// nest search space // nest search space
let isNested: boolean = false; let isNested: boolean = false;
Object.keys(searchRange).map(item => { Object.keys(searchRange).map(item => {
if (typeof searchRange[item]._value[0] === 'object') { if (searchRange[item]._value && typeof searchRange[item]._value[0] === 'object') {
isNested = true; isNested = true;
return; return;
} }
}); });
const dimName = Object.keys(searchRange); const dimName = Object.keys(searchRange);
if (this._isMounted === true) { this.setState({ dimName: dimName });
this.setState(() => ({ dimName: dimName }));
}
const parallelAxis: Array<Dimobj> = []; const parallelAxis: Array<Dimobj> = [];
// search space range and specific value [only number] // search space range and specific value [only number]
...@@ -324,23 +317,21 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -324,23 +317,21 @@ class Para extends React.Component<ParaProps, ParaState> {
color: ['#CA0000', '#FFC400', '#90EE90'] color: ['#CA0000', '#FFC400', '#90EE90']
} }
}; };
if (this._isMounted === true) { this.setState({
this.setState({ paraNodata: 'No data',
paraNodata: 'No data', option: optionOfNull,
option: optionOfNull, sutrialCount: 0,
sutrialCount: 0, succeedRenderCount: 0
succeedRenderCount: 0 });
});
}
} else { } else {
Object.keys(dataSource).map(item => { Object.keys(dataSource).map(item => {
const temp = dataSource[item]; const trial = dataSource[item];
eachTrialParams.push(temp.description.parameters); eachTrialParams.push(trial.description.parameters || '');
// may be a succeed trial hasn't final result // may be a succeed trial hasn't final result
// all detail page may be break down if havn't if // all detail page may be break down if havn't if
if (temp.acc !== undefined) { if (trial.acc !== undefined) {
if (temp.acc.default !== undefined) { if (trial.acc.default !== undefined) {
accPara.push(temp.acc.default); accPara.push(JSON.parse(trial.acc.default));
} }
} }
}); });
...@@ -361,14 +352,12 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -361,14 +352,12 @@ class Para extends React.Component<ParaProps, ParaState> {
}); });
}); });
} }
if (this._isMounted) { // if not return final result
// if not return final result const maxVal = accPara.length === 0 ? 1 : Math.max(...accPara);
const maxVal = accPara.length === 0 ? 1 : Math.max(...accPara); const minVal = accPara.length === 0 ? 1 : Math.min(...accPara);
const minVal = accPara.length === 0 ? 1 : Math.min(...accPara); this.setState({ max: maxVal, min: minVal }, () => {
this.setState({ max: maxVal, min: minVal }, () => { this.getParallelAxis(dimName, parallelAxis, accPara, eachTrialParams, lenOfDataSource);
this.getParallelAxis(dimName, parallelAxis, accPara, eachTrialParams, lenOfDataSource); });
});
}
} }
} }
...@@ -376,11 +365,9 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -376,11 +365,9 @@ class Para extends React.Component<ParaProps, ParaState> {
percentNum = (value: string) => { percentNum = (value: string) => {
let vals = parseFloat(value); let vals = parseFloat(value);
if (this._isMounted) { this.setState({ percent: vals }, () => {
this.setState({ percent: vals }, () => { this.reInit();
this.reInit(); });
});
}
} }
// deal with response data into pic data // deal with response data into pic data
...@@ -445,22 +432,17 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -445,22 +432,17 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
}; };
// please wait the data // please wait the data
if (this._isMounted) { this.setState({
this.setState(() => ({ option: optionown,
option: optionown, paraNodata: '',
paraNodata: '', succeedRenderCount: lengthofTrials,
succeedRenderCount: lengthofTrials, sutrialCount: paralleData.length
sutrialCount: paralleData.length });
}));
}
} }
// get swap parallel axis // get swap parallel axis
getSwapArr = (value: Array<string>) => { getSwapArr = (value: Array<string>) => {
this.setState({ swapAxisArr: value });
if (this._isMounted) {
this.setState(() => ({ swapAxisArr: value }));
}
} }
reInit = () => { reInit = () => {
...@@ -471,9 +453,7 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -471,9 +453,7 @@ class Para extends React.Component<ParaProps, ParaState> {
swapReInit = () => { swapReInit = () => {
const { clickCounts, succeedRenderCount } = this.state; const { clickCounts, succeedRenderCount } = this.state;
const val = clickCounts + 1; const val = clickCounts + 1;
if (this._isMounted) { this.setState({ isLoadConfirm: true, clickCounts: val, });
this.setState({ isLoadConfirm: true, clickCounts: val, });
}
const { paraBack, swapAxisArr } = this.state; const { paraBack, swapAxisArr } = this.state;
const paralDim = paraBack.parallelAxis; const paralDim = paraBack.parallelAxis;
const paraData = paraBack.data; const paraData = paraBack.data;
...@@ -523,11 +503,9 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -523,11 +503,9 @@ class Para extends React.Component<ParaProps, ParaState> {
}); });
this.getOption(paraBack, succeedRenderCount); this.getOption(paraBack, succeedRenderCount);
// please wait the data // please wait the data
if (this._isMounted) { this.setState({
this.setState(() => ({ isLoadConfirm: false
isLoadConfirm: false });
}));
}
} }
sortDimY = (a: Dimobj, b: Dimobj) => { sortDimY = (a: Dimobj, b: Dimobj) => {
...@@ -585,7 +563,6 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -585,7 +563,6 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
componentDidMount() { componentDidMount() {
this._isMounted = true;
this.reInit(); this.reInit();
} }
...@@ -623,10 +600,6 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -623,10 +600,6 @@ class Para extends React.Component<ParaProps, ParaState> {
return false; return false;
} }
componentWillUnmount() {
this._isMounted = false;
}
render() { render() {
const { option, paraNodata, dimName, isLoadConfirm } = this.state; const { option, paraNodata, dimName, isLoadConfirm } = this.state;
return ( return (
...@@ -687,4 +660,4 @@ class Para extends React.Component<ParaProps, ParaState> { ...@@ -687,4 +660,4 @@ class Para extends React.Component<ParaProps, ParaState> {
} }
} }
export default Para; export default Para;
\ No newline at end of file
...@@ -2,14 +2,15 @@ import * as React from 'react'; ...@@ -2,14 +2,15 @@ import * as React from 'react';
import axios from 'axios'; import axios from 'axios';
import ReactEcharts from 'echarts-for-react'; import ReactEcharts from 'echarts-for-react';
import { Row, Table, Button, Popconfirm, Modal, Checkbox, Select, Icon } from 'antd'; import { Row, Table, Button, Popconfirm, Modal, Checkbox, Select, Icon } from 'antd';
import { ColumnProps } from 'antd/lib/table';
const Option = Select.Option; const Option = Select.Option;
const CheckboxGroup = Checkbox.Group; const CheckboxGroup = Checkbox.Group;
import { MANAGER_IP, trialJobStatus, COLUMN_INDEX, COLUMNPro } from '../../static/const'; import { MANAGER_IP, trialJobStatus, COLUMN_INDEX, COLUMNPro } from '../../static/const';
import { convertDuration, intermediateGraphOption, killJob, filterByStatus } from '../../static/function'; import { convertDuration, formatTimestamp, intermediateGraphOption, killJob } from '../../static/function';
import { TableObj, TrialJob } from '../../static/interface'; import { TRIALS } from '../../static/datamodel';
import { TableRecord } from '../../static/interface';
import OpenRow from '../public-child/OpenRow'; import OpenRow from '../public-child/OpenRow';
import Compare from '../Modal/Compare'; import Compare from '../Modal/Compare';
import IntermediateVal from '../public-child/IntermediateVal'; // table default metric column
import '../../static/style/search.scss'; import '../../static/style/search.scss';
require('../../static/style/tableStatus.css'); require('../../static/style/tableStatus.css');
require('../../static/style/logPath.scss'); require('../../static/style/logPath.scss');
...@@ -26,14 +27,11 @@ echarts.registerTheme('my_theme', { ...@@ -26,14 +27,11 @@ echarts.registerTheme('my_theme', {
}); });
interface TableListProps { interface TableListProps {
entries: number; pageSize: number;
tableSource: Array<TableObj>; tableSource: Array<TableRecord>;
updateList: Function;
platform: string;
logCollection: boolean;
isMultiPhase: boolean;
columnList: Array<string>; // user select columnKeys columnList: Array<string>; // user select columnKeys
changeColumn: (val: Array<string>) => void; changeColumn: (val: Array<string>) => void;
trialsUpdateBroadcast: number;
} }
interface TableListState { interface TableListState {
...@@ -41,7 +39,7 @@ interface TableListState { ...@@ -41,7 +39,7 @@ interface TableListState {
modalVisible: boolean; modalVisible: boolean;
isObjFinal: boolean; isObjFinal: boolean;
isShowColumn: boolean; isShowColumn: boolean;
selectRows: Array<TableObj>; selectRows: Array<TableRecord>;
isShowCompareModal: boolean; isShowCompareModal: boolean;
selectedRowKeys: string[] | number[]; selectedRowKeys: string[] | number[];
intermediateData: Array<object>; // a trial's intermediate results (include dict) intermediateData: Array<object>; // a trial's intermediate results (include dict)
...@@ -56,10 +54,9 @@ interface ColumnIndex { ...@@ -56,10 +54,9 @@ interface ColumnIndex {
class TableList extends React.Component<TableListProps, TableListState> { class TableList extends React.Component<TableListProps, TableListState> {
public _isMounted = false;
public intervalTrialLog = 10; public intervalTrialLog = 10;
public _trialId: string; public _trialId: string;
public tables: Table<TableObj> | null; public tables: Table<TableRecord> | null;
constructor(props: TableListProps) { constructor(props: TableListProps) {
super(props); super(props);
...@@ -78,46 +75,35 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -78,46 +75,35 @@ class TableList extends React.Component<TableListProps, TableListState> {
}; };
} }
showIntermediateModal = (id: string) => { showIntermediateModal = async (id: string) => {
const res = await axios.get(`${MANAGER_IP}/metric-data/${id}`);
axios(`${MANAGER_IP}/metric-data/${id}`, { if (res.status === 200) {
method: 'GET' const intermediateArr: number[] = [];
}) // support intermediate result is dict because the last intermediate result is
.then(res => { // final result in a succeed trial, it may be a dict.
if (res.status === 200) { // get intermediate result dict keys array
const intermediateArr: number[] = []; let otherkeys: Array<string> = ['default'];
// support intermediate result is dict because the last intermediate result is if (res.data.length !== 0) {
// final result in a succeed trial, it may be a dict. otherkeys = Object.keys(JSON.parse(res.data[0].data));
// get intermediate result dict keys array }
let otherkeys: Array<string> = ['default']; // intermediateArr just store default val
if (res.data.length !== 0) { Object.keys(res.data).map(item => {
otherkeys = Object.keys(JSON.parse(res.data[0].data)); const temp = JSON.parse(res.data[item].data);
} if (typeof temp === 'object') {
// intermediateArr just store default val intermediateArr.push(temp.default);
Object.keys(res.data).map(item => { } else {
const temp = JSON.parse(res.data[item].data); intermediateArr.push(temp);
if (typeof temp === 'object') {
intermediateArr.push(temp.default);
} else {
intermediateArr.push(temp);
}
});
const intermediate = intermediateGraphOption(intermediateArr, id);
if (this._isMounted) {
this.setState(() => ({
intermediateData: res.data, // store origin intermediate data for a trial
intermediateOption: intermediate,
intermediateOtherKeys: otherkeys,
intermediateId: id
}));
}
} }
}); });
if (this._isMounted) { const intermediate = intermediateGraphOption(intermediateArr, id);
this.setState({ this.setState({
modalVisible: true intermediateData: res.data, // store origin intermediate data for a trial
intermediateOption: intermediate,
intermediateOtherKeys: otherkeys,
intermediateId: id
}); });
} }
this.setState({ modalVisible: true });
} }
// intermediate button click -> intermediate graph for each trial // intermediate button click -> intermediate graph for each trial
...@@ -147,43 +133,36 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -147,43 +133,36 @@ class TableList extends React.Component<TableListProps, TableListState> {
} }
const intermediate = intermediateGraphOption(intermediateArr, intermediateId); const intermediate = intermediateGraphOption(intermediateArr, intermediateId);
// re-render // re-render
if (this._isMounted) { this.setState({
this.setState(() => ({ intermediateOption: intermediate
intermediateOption: intermediate });
}));
}
} }
hideIntermediateModal = () => { hideIntermediateModal = () => {
if (this._isMounted) { this.setState({
this.setState({ modalVisible: false
modalVisible: false });
});
}
} }
hideShowColumnModal = () => { hideShowColumnModal = () => {
if (this._isMounted) { this.setState({
this.setState({ isShowColumn: false
isShowColumn: false });
});
}
} }
// click add column btn, just show the modal of addcolumn // click add column btn, just show the modal of addcolumn
addColumn = () => { addColumn = () => {
// show user select check button // show user select check button
if (this._isMounted) { this.setState({
this.setState({ isShowColumn: true
isShowColumn: true });
});
}
} }
// checkbox for coloumn // checkbox for coloumn
selectedColumn = (checkedValues: Array<string>) => { selectedColumn = (checkedValues: Array<string>) => {
// 7: because have seven common column, "Intermediate count" is hidden by default // 9: because have nine common column,
let count = 7; // [Intermediate count, Start Time, End Time] is hidden by default
let count = 9;
const want: Array<object> = []; const want: Array<object> = [];
const finalKeys: Array<string> = []; const finalKeys: Array<string> = [];
const wantResult: Array<string> = []; const wantResult: Array<string> = [];
...@@ -191,13 +170,13 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -191,13 +170,13 @@ class TableList extends React.Component<TableListProps, TableListState> {
switch (checkedValues[m]) { switch (checkedValues[m]) {
case 'Trial No.': case 'Trial No.':
case 'ID': case 'ID':
case 'StartTime': case 'Start Time':
case 'EndTime': case 'End Time':
case 'Duration': case 'Duration':
case 'Status': case 'Status':
case 'Operation': case 'Operation':
case 'Default': case 'Default':
case 'Intermediate count': case 'Intermediate result':
break; break;
default: default:
finalKeys.push(checkedValues[m]); finalKeys.push(checkedValues[m]);
...@@ -229,27 +208,17 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -229,27 +208,17 @@ class TableList extends React.Component<TableListProps, TableListState> {
wantResult.push(want[i].name); wantResult.push(want[i].name);
}); });
if (this._isMounted) { this.props.changeColumn(wantResult);
this.props.changeColumn(wantResult);
}
} }
openRow = (record: TableObj) => { openRow = (record: TableRecord) => {
const { platform, logCollection, isMultiPhase } = this.props;
return ( return (
<OpenRow <OpenRow trialId={record.id} />
trainingPlatform={platform}
record={record}
logCollection={logCollection}
multiphase={isMultiPhase}
/>
); );
} }
fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableObj>) => { fillSelectedRowsTostate = (selected: number[] | string[], selectedRows: Array<TableRecord>) => {
if (this._isMounted === true) { this.setState({ selectRows: selectedRows, selectedRowKeys: selected });
this.setState(() => ({ selectRows: selectedRows, selectedRowKeys: selected }));
}
} }
// open Compare-modal // open Compare-modal
compareBtn = () => { compareBtn = () => {
...@@ -258,222 +227,87 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -258,222 +227,87 @@ class TableList extends React.Component<TableListProps, TableListState> {
if (selectRows.length === 0) { if (selectRows.length === 0) {
alert('Please select datas you want to compare!'); alert('Please select datas you want to compare!');
} else { } else {
if (this._isMounted === true) { this.setState({ isShowCompareModal: true });
this.setState({ isShowCompareModal: true });
}
} }
} }
// close Compare-modal // close Compare-modal
hideCompareModal = () => { hideCompareModal = () => {
// close modal. clear select rows data, clear selected track // close modal. clear select rows data, clear selected track
if (this._isMounted) { this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
this.setState({ isShowCompareModal: false, selectedRowKeys: [], selectRows: [] });
}
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
} }
render() { render() {
const { pageSize, columnList } = this.props;
const { entries, tableSource, updateList, columnList } = this.props; const tableSource: Array<TableRecord> = JSON.parse(JSON.stringify(this.props.tableSource));
const { intermediateOption, modalVisible, isShowColumn, const { intermediateOption, modalVisible, isShowColumn,
selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys } = this.state; selectRows, isShowCompareModal, selectedRowKeys, intermediateOtherKeys } = this.state;
const rowSelection = { const rowSelection = {
selectedRowKeys: selectedRowKeys, selectedRowKeys: selectedRowKeys,
onChange: (selected: string[] | number[], selectedRows: Array<TableObj>) => { onChange: (selected: string[] | number[], selectedRows: Array<TableRecord>) => {
this.fillSelectedRowsTostate(selected, selectedRows); this.fillSelectedRowsTostate(selected, selectedRows);
} }
}; };
let showTitle = COLUMNPro; let showTitle = COLUMNPro;
let bgColor = '';
const trialJob: Array<TrialJob> = [];
const showColumn: Array<object> = []; const showColumn: Array<object> = [];
// parameter as table column
const trialMess = TRIALS.getTrial(tableSource[0].id);
const trial = trialMess.description.parameters;
const parameterColumn: Array<string> = Object.keys(trial);
const parameterStr: Array<string> = [];
parameterColumn.forEach(value => {
parameterStr.push(`${value} (search space)`);
});
showTitle = COLUMNPro.concat(parameterStr);
// only succeed trials have final keys // only succeed trials have final keys
if (tableSource.filter(filterByStatus).length >= 1) { if (tableSource.filter(record => record.status === 'SUCCEEDED').length >= 1) {
const temp = tableSource.filter(filterByStatus)[0].acc; const temp = tableSource.filter(record => record.status === 'SUCCEEDED')[0].accuracy;
if (temp !== undefined && typeof temp === 'object') { if (temp !== undefined && typeof temp === 'object') {
if (this._isMounted) { // concat default column and finalkeys
// concat default column and finalkeys const item = Object.keys(temp);
const item = Object.keys(temp); // item: ['default', 'other-keys', 'maybe loss']
// item: ['default', 'other-keys', 'maybe loss'] if (item.length > 1) {
if (item.length > 1) { const want: Array<string> = [];
const want: Array<string> = []; item.forEach(value => {
item.forEach(value => { if (value !== 'default') {
if (value !== 'default') { want.push(value);
want.push(value); }
} });
}); showTitle = COLUMNPro.concat(want);
showTitle = COLUMNPro.concat(want);
}
} }
} }
} }
trialJobStatus.map(item => { for (const item of columnList) {
trialJob.push({ const paraColumn = item.match(/ \(search space\)$/);
text: item, let cc;
value: item if (paraColumn !== null) {
}); cc = paraColumn.input;
}); }
Object.keys(columnList).map(key => {
const item = columnList[key];
switch (item) { switch (item) {
case 'Trial No.': case 'Trial No.':
showColumn.push({ showColumn.push(SequenceIdColumnConfig);
title: 'Trial No.',
dataIndex: 'sequenceId',
key: 'sequenceId',
width: 120,
className: 'tableHead',
sorter: (a: TableObj, b: TableObj) => (a.sequenceId as number) - (b.sequenceId as number)
});
break; break;
case 'ID': case 'ID':
showColumn.push({ showColumn.push(IdColumnConfig);
title: 'ID',
dataIndex: 'id',
key: 'id',
width: 60,
className: 'tableHead leftTitle',
// the sort of string
sorter: (a: TableObj, b: TableObj): number => a.id.localeCompare(b.id),
render: (text: string, record: TableObj) => {
return (
<div>{record.id}</div>
);
}
});
break; break;
case 'StartTime': case 'Start Time':
showColumn.push({ showColumn.push(StartTimeColumnConfig);
title: 'StartTime',
dataIndex: 'startTime',
key: 'startTime',
width: 160,
render: (text: string, record: TableObj) => {
const start = record.startTime !== undefined ? record.startTime : -1;
return (
<span>
{
start !== -1
?
new Date(start).toLocaleString('en-US')
:
'--'
}
</span>
);
},
});
break; break;
case 'EndTime': case 'End Time':
showColumn.push({ showColumn.push(EndTimeColumnConfig);
title: 'EndTime',
dataIndex: 'endTime',
key: 'endTime',
width: 160,
render: (text: string, record: TableObj) => {
const end = record.endTime !== undefined ? record.endTime : -1;
return (
<span>
{
end !== -1
?
new Date(end).toLocaleString('en-US')
:
'--'
}
</span>
);
},
});
break; break;
case 'Duration': case 'Duration':
showColumn.push({ showColumn.push(DurationColumnConfig);
title: 'Duration',
dataIndex: 'duration',
key: 'duration',
width: 100,
// the sort of number
sorter: (a: TableObj, b: TableObj) => (a.duration as number) - (b.duration as number),
render: (text: string, record: TableObj) => {
let duration;
if (record.duration !== undefined) {
// duration is nagative number(-1) & 0-1
if (record.duration > 0 && record.duration < 1 || record.duration < 0) {
duration = `${record.duration}s`;
} else {
duration = convertDuration(record.duration);
}
} else {
duration = 0;
}
return (
<div className="durationsty"><div>{duration}</div></div>
);
},
});
break; break;
case 'Status': case 'Status':
showColumn.push({ showColumn.push(StatusColumnConfig);
title: 'Status',
dataIndex: 'status',
key: 'status',
width: 150,
className: 'tableStatus',
render: (text: string, record: TableObj) => {
bgColor = record.status;
return (
<span className={`${bgColor} commonStyle`}>{record.status}</span>
);
},
filters: trialJob,
onFilter: (value: string, record: TableObj) => {
return record.status.indexOf(value) === 0;
},
// onFilter: (value: string, record: TableObj) => record.status.indexOf(value) === 0,
sorter: (a: TableObj, b: TableObj): number => a.status.localeCompare(b.status)
});
break; break;
case 'Intermediate count': case 'Intermediate result':
showColumn.push({ showColumn.push(IntermediateCountColumnConfig);
title: 'Intermediate count',
dataIndex: 'progress',
key: 'progress',
width: 86,
render: (text: string, record: TableObj) => {
return (
<span>{`#${record.description.intermediate.length}`}</span>
);
},
});
break; break;
case 'Default': case 'Default':
showColumn.push({ showColumn.push(AccuracyColumnConfig);
title: 'Default metric',
className: 'leftTitle',
dataIndex: 'acc',
key: 'acc',
width: 120,
sorter: (a: TableObj, b: TableObj) => {
const oneArr = a.description.intermediate;
const otherArr = b.description.intermediate;
const one = (oneArr[oneArr.length - 1] !== undefined) ? oneArr[oneArr.length - 1] : 0;
const other = (otherArr[otherArr.length - 1] !== undefined)
? otherArr[otherArr.length - 1] : 0;
return one - other;
},
render: (text: string, record: TableObj) => {
return (
<IntermediateVal record={record} />
);
}
});
break; break;
case 'Operation': case 'Operation':
showColumn.push({ showColumn.push({
...@@ -481,7 +315,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -481,7 +315,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
dataIndex: 'operation', dataIndex: 'operation',
key: 'operation', key: 'operation',
width: 120, width: 120,
render: (text: string, record: TableObj) => { render: (text: string, record: TableRecord) => {
let trialStatus = record.status; let trialStatus = record.status;
const flag: boolean = (trialStatus === 'RUNNING') ? false : true; const flag: boolean = (trialStatus === 'RUNNING') ? false : true;
return ( return (
...@@ -499,7 +333,7 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -499,7 +333,7 @@ class TableList extends React.Component<TableListProps, TableListState> {
<Popconfirm <Popconfirm
title="Are you sure to cancel this trial?" title="Are you sure to cancel this trial?"
onConfirm={killJob. onConfirm={killJob.
bind(this, record.key, record.id, record.status, updateList)} bind(this, record.key, record.id, record.status)}
> >
<Button <Button
type="default" type="default"
...@@ -515,67 +349,40 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -515,67 +349,40 @@ class TableList extends React.Component<TableListProps, TableListState> {
}, },
}); });
break; break;
case (cc):
case 'Intermediate result': // remove SEARCH_SPACE title
const realItem = item.replace(' (search space)', '');
showColumn.push({ showColumn.push({
title: 'Intermediate result', title: realItem,
dataIndex: 'intermediate', dataIndex: item,
key: 'intermediate', key: item,
width: '16%', width: '6%',
render: (text: string, record: TableObj) => { render: (text: string, record: TableRecord) => {
const eachTrial = TRIALS.getTrial(record.id);
return ( return (
<Button <span>{eachTrial.description.parameters[realItem]}</span>
type="primary"
className="tableButton"
onClick={this.showIntermediateModal.bind(this, record.id)}
>
Intermediate
</Button>
); );
}, },
}); });
break; break;
default: default:
showColumn.push({ // FIXME
title: item, alert('Unexpected column type');
dataIndex: item,
key: item,
width: 150,
render: (text: string, record: TableObj) => {
const temp = record.acc;
let decimals = 0;
let other = '';
if (temp !== undefined) {
if (temp[item].toString().indexOf('.') !== -1) {
decimals = temp[item].toString().length - temp[item].toString().indexOf('.') - 1;
if (decimals > 6) {
other = `${temp[item].toFixed(6)}`;
} else {
other = temp[item].toString();
}
}
} else {
other = '--';
}
return (
<div>{other}</div>
);
}
});
} }
}); }
return ( return (
<Row className="tableList"> <Row className="tableList">
<div id="tableList"> <div id="tableList">
<Table <Table
ref={(table: Table<TableObj> | null) => this.tables = table} ref={(table: Table<TableRecord> | null) => this.tables = table}
columns={showColumn} columns={showColumn}
rowSelection={rowSelection} rowSelection={rowSelection}
expandedRowRender={this.openRow} expandedRowRender={this.openRow}
dataSource={tableSource} dataSource={tableSource}
className="commonTableStyle" className="commonTableStyle"
pagination={{ pageSize: entries }} scroll={{x: 'max-content'}}
pagination={pageSize > 0 ? { pageSize } : false}
/> />
{/* Intermediate Result Modal */} {/* Intermediate Result Modal */}
<Modal <Modal
...@@ -640,4 +447,93 @@ class TableList extends React.Component<TableListProps, TableListState> { ...@@ -640,4 +447,93 @@ class TableList extends React.Component<TableListProps, TableListState> {
} }
} }
const SequenceIdColumnConfig: ColumnProps<TableRecord> = {
title: 'Trial No.',
dataIndex: 'sequenceId',
width: 120,
className: 'tableHead',
sorter: (a, b) => a.sequenceId - b.sequenceId
};
const IdColumnConfig: ColumnProps<TableRecord> = {
title: 'ID',
dataIndex: 'id',
width: 60,
className: 'tableHead leftTitle',
sorter: (a, b) => a.id.localeCompare(b.id),
render: (text, record) => (
<div>{record.id}</div>
)
};
const StartTimeColumnConfig: ColumnProps<TableRecord> = {
title: 'Start Time',
dataIndex: 'startTime',
width: 160,
render: (text, record) => (
<span>{formatTimestamp(record.startTime)}</span>
)
};
const EndTimeColumnConfig: ColumnProps<TableRecord> = {
title: 'End Time',
dataIndex: 'endTime',
width: 160,
render: (text, record) => (
<span>{formatTimestamp(record.endTime, '--')}</span>
)
};
const DurationColumnConfig: ColumnProps<TableRecord> = {
title: 'Duration',
dataIndex: 'duration',
width: 100,
sorter: (a, b) => a.duration - b.duration,
render: (text, record) => (
<div className="durationsty"><div>{convertDuration(record.duration)}</div></div>
)
};
const StatusColumnConfig: ColumnProps<TableRecord> = {
title: 'Status',
dataIndex: 'status',
width: 150,
className: 'tableStatus',
render: (text, record) => (
<span className={`${record.status} commonStyle`}>{record.status}</span>
),
sorter: (a, b) => a.status.localeCompare(b.status),
filters: trialJobStatus.map(status => ({ text: status, value: status })),
onFilter: (value, record) => (record.status === value)
};
const IntermediateCountColumnConfig: ColumnProps<TableRecord> = {
title: 'Intermediate result',
dataIndex: 'intermediateCount',
width: 86,
render: (text, record) => (
<span>{`#${record.intermediateCount}`}</span>
)
};
const AccuracyColumnConfig: ColumnProps<TableRecord> = {
title: 'Default metric',
className: 'leftTitle',
dataIndex: 'accuracy',
width: 120,
sorter: (a, b, sortOrder) => {
if (a.accuracy === undefined) {
return sortOrder === 'ascend' ? -1 : 1;
} else if (b.accuracy === undefined) {
return sortOrder === 'ascend' ? 1 : -1;
} else {
return a.accuracy - b.accuracy;
}
},
render: (text, record) => (
// TODO: is this needed?
<div>{record.latestAccuracy}</div>
)
};
export default TableList; export default TableList;
...@@ -19,8 +19,8 @@ time, mark, audio, video { ...@@ -19,8 +19,8 @@ time, mark, audio, video {
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;
font: inherit;
font-size: 100%; font-size: 100%;
font: inherit;
} }
/* HTML5 display-role reset for older browsers */ /* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, article, aside, details, figcaption, figure,
...@@ -48,4 +48,4 @@ q:before, q:after { ...@@ -48,4 +48,4 @@ q:before, q:after {
table { table {
border-collapse: collapse; border-collapse: collapse;
border-spacing: 0; border-spacing: 0;
} }
\ No newline at end of file
// when there are more trials than this threshold, metrics will be updated in group of this size to avoid freezing
const METRIC_GROUP_UPDATE_THRESHOLD = 100;
const METRIC_GROUP_UPDATE_SIZE = 20;
const MANAGER_IP = `/api/v1/nni`; const MANAGER_IP = `/api/v1/nni`;
const DOWNLOAD_IP = `/logs`; const DOWNLOAD_IP = `/logs`;
const trialJobStatus = [ const trialJobStatus = [
...@@ -34,11 +38,11 @@ const COLUMN_INDEX = [ ...@@ -34,11 +38,11 @@ const COLUMN_INDEX = [
index: 2 index: 2
}, },
{ {
name: 'StartTime', name: 'Start Time',
index: 3 index: 3
}, },
{ {
name: 'EndTime', name: 'End Time',
index: 4 index: 4
}, },
{ {
...@@ -50,7 +54,7 @@ const COLUMN_INDEX = [ ...@@ -50,7 +54,7 @@ const COLUMN_INDEX = [
index: 6 index: 6
}, },
{ {
name: 'Intermediate count', name: 'Intermediate result',
index: 7 index: 7
}, },
{ {
...@@ -65,9 +69,10 @@ const COLUMN_INDEX = [ ...@@ -65,9 +69,10 @@ const COLUMN_INDEX = [
// defatult selected column // defatult selected column
const COLUMN = ['Trial No.', 'ID', 'Duration', 'Status', 'Default', 'Operation']; const COLUMN = ['Trial No.', 'ID', 'Duration', 'Status', 'Default', 'Operation'];
// all choice column !dictory final // all choice column !dictory final
const COLUMNPro = ['Trial No.', 'ID', 'StartTime', 'EndTime', 'Duration', 'Status', const COLUMNPro = ['Trial No.', 'ID', 'Start Time', 'End Time', 'Duration', 'Status',
'Intermediate count', 'Default', 'Operation']; 'Intermediate result', 'Default', 'Operation'];
export { export {
MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMNPro, MANAGER_IP, DOWNLOAD_IP, trialJobStatus, COLUMNPro,
CONTROLTYPE, MONACO, COLUMN, COLUMN_INDEX, DRAWEROPTION CONTROLTYPE, MONACO, COLUMN, COLUMN_INDEX, DRAWEROPTION,
METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_SIZE,
}; };
import { Experiment } from './model/experiment';
import { TrialManager } from './model/trialmanager';
const EXPERIMENT = new Experiment();
const TRIALS = new TrialManager();
export { EXPERIMENT, TRIALS };
import axios from 'axios'; import axios from 'axios';
import { message } from 'antd'; import { message } from 'antd';
import { MANAGER_IP } from './const'; import { MANAGER_IP } from './const';
import { FinalResult, FinalType, TableObj } from './interface'; import { MetricDataRecord, FinalType, TableObj } from './interface';
const convertTime = (num: number) => { const convertTime = (num: number) => {
if (num <= 0) {
return '0';
}
if (num % 3600 === 0) { if (num % 3600 === 0) {
return num / 3600 + 'h'; return num / 3600 + 'h';
} else { } else {
...@@ -15,24 +18,28 @@ const convertTime = (num: number) => { ...@@ -15,24 +18,28 @@ const convertTime = (num: number) => {
// trial's duration, accurate to seconds for example 10min 30s // trial's duration, accurate to seconds for example 10min 30s
const convertDuration = (num: number) => { const convertDuration = (num: number) => {
if (num < 1) {
return '0s';
}
const hour = Math.floor(num / 3600); const hour = Math.floor(num / 3600);
const min = Math.floor(num / 60 % 60); const minute = Math.floor(num / 60 % 60);
const second = Math.floor(num % 60); const second = Math.floor(num % 60);
const result = hour > 0 ? `${hour} h ${min} min ${second}s` : `${min} min ${second}s`; let result = [ ];
if (hour <= 0 && min === 0 && second !== 0) { if (hour > 0) {
return `${second}s`; result.push(`${hour}h`);
} else if (hour === 0 && min !== 0 && second === 0) { }
return `${min}min`; if (minute > 0) {
} else if (hour === 0 && min !== 0 && second !== 0) { result.push(`${minute}min`);
return `${min}min ${second}s`; }
} else { if (second > 0) {
return result; result.push(`${second}s`);
} }
return result.join(' ');
}; };
// get final result value // get final result value
// draw Accuracy point graph // draw Accuracy point graph
const getFinalResult = (final: Array<FinalResult>) => { const getFinalResult = (final?: MetricDataRecord[]) => {
let acc; let acc;
let showDefault = 0; let showDefault = 0;
if (final) { if (final) {
...@@ -51,7 +58,7 @@ const getFinalResult = (final: Array<FinalResult>) => { ...@@ -51,7 +58,7 @@ const getFinalResult = (final: Array<FinalResult>) => {
}; };
// get final result value // acc obj // get final result value // acc obj
const getFinal = (final: Array<FinalResult>) => { const getFinal = (final?: MetricDataRecord[]) => {
let showDefault: FinalType; let showDefault: FinalType;
if (final) { if (final) {
showDefault = JSON.parse(final[final.length - 1].data); showDefault = JSON.parse(final[final.length - 1].data);
...@@ -101,7 +108,7 @@ const intermediateGraphOption = (intermediateArr: number[], id: string) => { ...@@ -101,7 +108,7 @@ const intermediateGraphOption = (intermediateArr: number[], id: string) => {
}; };
// kill job // kill job
const killJob = (key: number, id: string, status: string, updateList: Function) => { const killJob = (key: number, id: string, status: string, updateList?: Function) => {
axios(`${MANAGER_IP}/trial-jobs/${id}`, { axios(`${MANAGER_IP}/trial-jobs/${id}`, {
method: 'DELETE', method: 'DELETE',
headers: { headers: {
...@@ -113,7 +120,9 @@ const killJob = (key: number, id: string, status: string, updateList: Function) ...@@ -113,7 +120,9 @@ const killJob = (key: number, id: string, status: string, updateList: Function)
message.destroy(); message.destroy();
message.success('Cancel the job successfully'); message.success('Cancel the job successfully');
// render the table // render the table
updateList(); if (updateList) {
updateList(); // FIXME
}
} else { } else {
message.error('fail to cancel the job'); message.error('fail to cancel the job');
} }
...@@ -160,7 +169,22 @@ const downFile = (content: string, fileName: string) => { ...@@ -160,7 +169,22 @@ const downFile = (content: string, fileName: string) => {
} }
}; };
function formatTimestamp(timestamp?: number, placeholder?: string = 'N/A'): string {
return timestamp ? new Date(timestamp).toLocaleString('en-US') : placeholder;
}
function metricAccuracy(metric: MetricDataRecord): number {
const data = JSON.parse(metric.data);
return typeof data === 'number' ? data : NaN;
}
function formatAccuracy(accuracy: number): string {
// TODO: how to format NaN?
return accuracy.toFixed(6).replace(/0+$/, '').replace(/\.$/, '');
}
export { export {
convertTime, convertDuration, getFinalResult, getFinal, downFile, convertTime, convertDuration, getFinalResult, getFinal, downFile,
intermediateGraphOption, killJob, filterByStatus, filterDuration intermediateGraphOption, killJob, filterByStatus, filterDuration,
formatAccuracy, formatTimestamp, metricAccuracy,
}; };
// tslint:disable:no-any
// draw accuracy graph data interface // draw accuracy graph data interface
interface TableObj { interface TableObj {
key: number; key: number;
...@@ -12,6 +14,19 @@ interface TableObj { ...@@ -12,6 +14,19 @@ interface TableObj {
endTime?: number; endTime?: number;
} }
interface TableRecord {
key: string;
sequenceId: number;
startTime: number;
endTime?: number;
id: string;
duration: number;
status: string;
intermediateCount: number;
accuracy?: number;
latestAccuracy: string; // formatted string
}
interface SearchSpace { interface SearchSpace {
_value: Array<number | string>; _value: Array<number | string>;
_type: string; _type: string;
...@@ -32,26 +47,6 @@ interface Parameters { ...@@ -32,26 +47,6 @@ interface Parameters {
multiProgress?: number; multiProgress?: number;
} }
interface Experiment {
id: string;
author: string;
revision?: number;
experName: string;
logDir?: string;
runConcurren: number;
maxDuration: number;
execDuration: number;
MaxTrialNum: number;
startTime: number;
endTime?: number;
trainingServicePlatform: string;
tuner: object;
assessor?: object;
advisor?: object;
clusterMetaData?: object;
logCollection?: string;
}
// trial accuracy // trial accuracy
interface AccurPoint { interface AccurPoint {
acc: number; acc: number;
...@@ -74,21 +69,6 @@ interface TooltipForAccuracy { ...@@ -74,21 +69,6 @@ interface TooltipForAccuracy {
data: Array<number | object>; data: Array<number | object>;
} }
interface TrialNumber {
succTrial: number;
failTrial: number;
stopTrial: number;
waitTrial: number;
runTrial: number;
unknowTrial: number;
totalCurrentTrial: number;
}
interface TrialJob {
text: string;
value: string;
}
interface Dimobj { interface Dimobj {
dim: number; dim: number;
name: string; name: string;
...@@ -108,10 +88,6 @@ interface ParaObj { ...@@ -108,10 +88,6 @@ interface ParaObj {
parallelAxis: Array<Dimobj>; parallelAxis: Array<Dimobj>;
} }
interface FinalResult {
data: string;
}
interface Intermedia { interface Intermedia {
name: string; // id name: string; // id
type: string; type: string;
...@@ -119,13 +95,93 @@ interface Intermedia { ...@@ -119,13 +95,93 @@ interface Intermedia {
hyperPara: object; // each trial hyperpara value hyperPara: object; // each trial hyperpara value
} }
interface ExperimentInfo { interface MetricDataRecord {
platform: string; timestamp: number;
optimizeMode: string; trialJobId: string;
parameterId: string;
type: string;
sequence: number;
data: string;
}
interface TrialJobInfo {
id: string;
sequenceId: number;
status: string;
startTime?: number;
endTime?: number;
hyperParameters?: string[];
logPath?: string;
finalMetricData?: MetricDataRecord[];
stderrPath?: string;
}
interface ExperimentParams {
authorName: string;
experimentName: string;
description?: string;
trialConcurrency: number;
maxExecDuration: number; // seconds
maxTrialNum: number;
searchSpace: string;
trainingServicePlatform: string;
multiPhase?: boolean;
multiThread?: boolean;
versionCheck?: boolean;
logCollection?: string;
tuner?: {
className: string;
builtinTunerName?: string;
codeDir?: string;
classArgs?: any;
classFileName?: string;
checkpointDir: string;
gpuNum?: number;
includeIntermediateResults?: boolean;
};
assessor?: {
className: string;
builtinAssessorName?: string;
codeDir?: string;
classArgs?: any;
classFileName?: string;
checkpointDir: string;
gpuNum?: number;
};
advisor?: {
className: string;
builtinAdvisorName?: string;
codeDir?: string;
classArgs?: any;
classFileName?: string;
checkpointDir: string;
gpuNum?: number;
};
clusterMetaData?: {
key: string;
value: string;
}[];
}
interface ExperimentProfile {
params: ExperimentParams;
id: string;
execDuration: number;
logDir?: string;
startTime?: number;
endTime?: number;
maxSequenceId: number;
revision: number;
}
interface NNIManagerStatus {
status: string;
errors: string[];
} }
export { export {
TableObj, Parameters, Experiment, AccurPoint, TrialNumber, TrialJob, TableObj, TableRecord, Parameters, ExperimentProfile, AccurPoint,
DetailAccurPoint, TooltipForAccuracy, ParaObj, Dimobj, FinalResult, FinalType, DetailAccurPoint, TooltipForAccuracy, ParaObj, Dimobj, FinalType,
TooltipForIntermediate, SearchSpace, Intermedia, ExperimentInfo TooltipForIntermediate, SearchSpace, Intermedia, MetricDataRecord, TrialJobInfo,
NNIManagerStatus,
}; };
import axios from 'axios';
import { MANAGER_IP } from '../const';
import { ExperimentProfile, NNIManagerStatus } from '../interface';
function compareProfiles(profile1?: ExperimentProfile, profile2?: ExperimentProfile): boolean {
if (!profile1 || !profile2) {
return false;
}
const copy1 = Object.assign({}, profile1, { execDuration: undefined });
const copy2 = Object.assign({}, profile2, { execDuration: undefined });
return JSON.stringify(copy1) === JSON.stringify(copy2);
}
class Experiment {
private profileField?: ExperimentProfile = undefined;
private statusField?: NNIManagerStatus = undefined;
public async init(): Promise<void> {
while (!this.profileField || !this.statusField) {
await this.update();
}
}
public async update(): Promise<boolean> {
const profilePromise = axios.get(`${MANAGER_IP}/experiment`);
const statusPromise = axios.get(`${MANAGER_IP}/check-status`);
const [ profileResponse, statusResponse ] = await Promise.all([ profilePromise, statusPromise ]);
let updated = false;
if (statusResponse.status === 200) {
updated = JSON.stringify(this.statusField) === JSON.stringify(statusResponse.data);
this.statusField = statusResponse.data;
}
if (profileResponse.status === 200) {
updated = updated || compareProfiles(this.profileField, profileResponse.data);
this.profileField = profileResponse.data;
}
return updated;
}
get profile(): ExperimentProfile {
if (!this.profileField) {
throw Error('Experiment profile not initialized');
}
return this.profileField!;
}
get trialConcurrency(): number {
return this.profile.params.trialConcurrency;
}
get optimizeMode(): string {
const tuner = this.profile.params.tuner;
return (tuner && tuner.classArgs && tuner.classArgs.optimize_mode) ? tuner.classArgs.optimize_mode : 'unknown';
}
get trainingServicePlatform(): string {
return this.profile.params.trainingServicePlatform;
}
get searchSpace(): object {
return JSON.parse(this.profile.params.searchSpace);
}
get logCollectionEnabled(): boolean {
return !!(this.profile.params.logCollection && this.profile.params.logCollection !== 'none');
}
get multiPhase(): boolean {
return !!(this.profile.params.multiPhase);
}
get status(): string {
if (!this.statusField) {
throw Error('Experiment status not initialized');
}
return this.statusField!.status;
}
get error(): string {
if (!this.statusField) {
throw Error('Experiment status not initialized');
}
return this.statusField!.errors[0] || '';
}
}
export { Experiment };
import { MetricDataRecord, TrialJobInfo, TableObj, TableRecord, Parameters, FinalType } from '../interface';
import { getFinal, formatAccuracy, metricAccuracy } from '../function';
class Trial implements TableObj {
private metricsInitialized: boolean = false;
private infoField: TrialJobInfo | undefined;
private intermediates: (MetricDataRecord | undefined)[] = [ ];
private final: MetricDataRecord | undefined;
private finalAcc: number | undefined;
constructor(info?: TrialJobInfo, metrics?: MetricDataRecord[]) {
this.infoField = info;
if (metrics) {
this.updateMetrics(metrics);
}
}
public compareAccuracy(otherTrial: Trial): number | undefined {
if (!this.sortable || !otherTrial.sortable) {
return undefined;
}
return this.finalAcc! - otherTrial.finalAcc!;
}
get info(): TrialJobInfo {
return this.infoField!;
}
get intermediateMetrics(): MetricDataRecord[] {
const ret: MetricDataRecord[] = [ ];
for (let i = 0; i < this.intermediates.length; i++) {
if (this.intermediates[i]) {
ret.push(this.intermediates[i]!);
} else {
break;
}
}
return ret;
}
get accuracy(): number | undefined {
return this.finalAcc;
}
get sortable(): boolean {
return this.finalAcc !== undefined && !isNaN(this.finalAcc);
}
/* table obj start */
get tableRecord(): TableRecord {
const endTime = this.info.endTime || new Date().getTime();
const duration = (endTime - this.info.startTime!) / 1000;
return {
key: this.info.id,
sequenceId: this.info.sequenceId,
id: this.info.id,
startTime: this.info.startTime!,
endTime: this.info.endTime,
duration,
status: this.info.status,
intermediateCount: this.intermediates.length,
accuracy: this.finalAcc,
latestAccuracy: this.formatLatestAccuracy(),
};
}
get key(): number {
return this.info.sequenceId;
}
get sequenceId(): number {
return this.info.sequenceId;
}
get id(): string {
return this.info.id;
}
get duration(): number {
const endTime = this.info.endTime || new Date().getTime();
return (endTime - this.info.startTime!) / 1000;
}
get status(): string {
return this.info.status;
}
get acc(): FinalType | undefined {
return getFinal(this.info.finalMetricData);
}
get description(): Parameters {
let ret: Parameters = {
parameters: { },
intermediate: [ ],
multiProgress: 1
};
const tempHyper = this.info.hyperParameters;
if (tempHyper !== undefined) {
const getPara = JSON.parse(tempHyper[tempHyper.length - 1]).parameters;
ret.multiProgress = tempHyper.length;
if (typeof getPara === 'string') {
ret.parameters = JSON.parse(getPara);
} else {
ret.parameters = getPara;
}
} else {
ret.parameters = { error: 'This trial\'s parameters are not available.' };
}
if (this.info.logPath !== undefined) {
ret.logPath = this.info.logPath;
}
const mediate: number[] = [ ];
for (const items of this.intermediateMetrics) {
if (typeof JSON.parse(items.data) === 'object') {
mediate.push(JSON.parse(items.data).default);
} else {
mediate.push(JSON.parse(items.data));
}
}
ret.intermediate = mediate;
return ret;
}
get color(): string | undefined {
return undefined;
}
/* table obj end */
public initialized(): boolean {
return !!(this.infoField && this.metricsInitialized);
}
public updateMetrics(metrics: MetricDataRecord[]): boolean {
// parameter `metrics` must contain all known metrics of this trial
this.metricsInitialized = true;
const prevMetricCnt = this.intermediates.length + (this.final ? 1 : 0);
if (metrics.length <= prevMetricCnt) {
return false;
}
for (const metric of metrics) {
if (metric.type === 'PERIODICAL') {
this.intermediates[metric.sequence] = metric;
} else {
this.final = metric;
this.finalAcc = metricAccuracy(metric);
}
}
return true;
}
public updateLatestMetrics(metrics: MetricDataRecord[]): boolean {
// this method is effectively identical to `updateMetrics`, but has worse performance
this.metricsInitialized = true;
let updated = false;
for (const metric of metrics) {
if (metric.type === 'PERIODICAL') {
updated = updated || !this.intermediates[metric.sequence];
this.intermediates[metric.sequence] = metric;
} else {
updated = updated || !this.final;
this.final = metric;
this.finalAcc = metricAccuracy(metric);
}
}
return updated;
}
public updateTrialJobInfo(trialJobInfo: TrialJobInfo): boolean {
const same = (this.infoField && this.infoField.status === trialJobInfo.status);
this.infoField = trialJobInfo;
if (trialJobInfo.finalMetricData) {
this.final = trialJobInfo.finalMetricData[trialJobInfo.finalMetricData.length - 1];
this.finalAcc = metricAccuracy(this.final);
}
return !same;
}
public formatLatestAccuracy(): string { // TODO: this should be private
if (this.accuracy !== undefined) {
return `${formatAccuracy(this.accuracy)} (FINAL)`;
} else if (this.intermediates.length === 0) {
return '--';
} else {
const latest = this.intermediates[this.intermediates.length - 1]!;
return `${formatAccuracy(metricAccuracy(latest))} (LATEST)`;
}
}
}
export { Trial };
import axios from 'axios';
import { MANAGER_IP, METRIC_GROUP_UPDATE_THRESHOLD, METRIC_GROUP_UPDATE_SIZE } from '../const';
import { MetricDataRecord, TableRecord, TrialJobInfo } from '../interface';
import { Trial } from './trial';
class TrialManager {
private trials: Map<string, Trial> = new Map<string, Trial>();
private infoInitialized: boolean = false;
private metricInitialized: boolean = false;
private maxSequenceId: number = 0;
private doingBatchUpdate: boolean = false;
private batchUpdatedAfterReading: boolean = false;
public async init(): Promise<void> {
while (!this.infoInitialized || !this.metricInitialized) {
await this.update();
}
}
public async update(lastTime?: boolean): Promise<boolean> {
const [ infoUpdated, metricUpdated ] = await Promise.all([ this.updateInfo(), this.updateMetrics(lastTime) ]);
return infoUpdated || metricUpdated;
}
public getTrial(trialId: string): Trial {
return this.trials.get(trialId)!;
}
public getTrials(trialIds: string[]): Trial[] {
return trialIds.map(trialId => this.trials.get(trialId)!);
}
public table(trialIds: string[]): TableRecord[] {
return trialIds.map(trialId => this.trials.get(trialId)!.tableRecord);
}
public toArray(): Trial[] {
const trials = Array.from(this.trials.values()).filter(trial => trial.initialized());
return trials.sort((trial1, trial2) => trial1.sequenceId - trial2.sequenceId);
}
public filter(callback: (trial: Trial) => boolean): Trial[] {
const trials = Array.from(this.trials.values()).filter(trial => trial.initialized() && callback(trial));
return trials.sort((trial1, trial2) => trial1.sequenceId - trial2.sequenceId);
}
public succeededTrials(): Trial[] {
return this.filter(trial => trial.status === 'SUCCEEDED');
}
public sort(): Trial[] {
return this.filter(trial => trial.sortable).sort((trial1, trial2) => trial1.compareAccuracy(trial2)!);
}
public countStatus(): Map<string, number> {
const cnt = new Map<string, number>([
[ 'UNKNOWN', 0 ],
[ 'WAITING', 0 ],
[ 'RUNNING', 0 ],
[ 'SUCCEEDED', 0 ],
[ 'FAILED', 0 ],
[ 'USER_CANCELED', 0 ],
[ 'SYS_CANCELED', 0 ],
[ 'EARLY_STOPPED', 0 ],
]);
for (const trial of this.trials.values()) {
if (trial.initialized()) {
cnt.set(trial.info.status, cnt.get(trial.info.status)! + 1);
}
}
return cnt;
}
private async updateInfo(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/trial-jobs`);
let updated = false;
if (response.status === 200) {
for (const info of response.data as TrialJobInfo[]) {
if (this.trials.has(info.id)) {
updated = this.trials.get(info.id)!.updateTrialJobInfo(info) || updated;
} else {
this.trials.set(info.id, new Trial(info, undefined));
updated = true;
}
this.maxSequenceId = Math.max(this.maxSequenceId, info.sequenceId);
}
this.infoInitialized = true;
}
return updated;
}
private async updateMetrics(lastTime?: boolean): Promise<boolean> {
if (this.trials.size < METRIC_GROUP_UPDATE_THRESHOLD || lastTime) {
return await this.updateAllMetrics();
} else {
this.updateManyMetrics();
const ret = (await this.updateLatestMetrics()) || this.batchUpdatedAfterReading;
this.batchUpdatedAfterReading = false;
return ret;
}
}
private async updateAllMetrics(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/metric-data`);
return (response.status === 200) && this.doUpdateMetrics(response.data as MetricDataRecord[], false);
}
private async updateLatestMetrics(): Promise<boolean> {
const response = await axios.get(`${MANAGER_IP}/metric-data-latest`);
return (response.status === 200) && this.doUpdateMetrics(response.data as MetricDataRecord[], true);
}
private async updateManyMetrics(): Promise<void> {
if (this.doingBatchUpdate) {
return;
}
this.doingBatchUpdate = true;
for (let i = 0; i < this.maxSequenceId; i += METRIC_GROUP_UPDATE_SIZE) {
const response = await axios.get(`${MANAGER_IP}/metric-data-range/${i}/${i + METRIC_GROUP_UPDATE_SIZE}`);
if (response.status === 200) {
const updated = this.doUpdateMetrics(response.data as MetricDataRecord[], false);
this.batchUpdatedAfterReading = this.batchUpdatedAfterReading || updated;
}
}
this.doingBatchUpdate = false;
}
private doUpdateMetrics(allMetrics: MetricDataRecord[], latestOnly: boolean): boolean {
let updated = false;
for (const [ trialId, metrics ] of groupMetricsByTrial(allMetrics).entries()) {
if (this.trials.has(trialId)) {
const trial = this.trials.get(trialId)!;
updated = (latestOnly ? trial.updateLatestMetrics(metrics) : trial.updateMetrics(metrics)) || updated;
} else {
this.trials.set(trialId, new Trial(undefined, metrics));
updated = true;
}
}
this.metricInitialized = true;
return updated;
}
}
function groupMetricsByTrial(metrics: MetricDataRecord[]): Map<string, MetricDataRecord[]> {
const ret = new Map<string, MetricDataRecord[]>();
for (const metric of metrics) {
if (ret.has(metric.trialJobId)) {
ret.get(metric.trialJobId)!.push(metric);
} else {
ret.set(metric.trialJobId, [ metric ]);
}
}
return ret;
}
export { TrialManager };
...@@ -11,10 +11,6 @@ ...@@ -11,10 +11,6 @@
], ],
"ban": false, "ban": false,
"class-name": true, "class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true, "curly": true,
"eofline": false, "eofline": false,
"forin": true, "forin": true,
...@@ -40,19 +36,9 @@ ...@@ -40,19 +36,9 @@
"static-before-instance", "static-before-instance",
"variables-before-functions" "variables-before-functions"
], ],
"no-any": true,
"no-arg": true, "no-arg": true,
"no-bitwise": true, "no-bitwise": true,
"no-console": [ "no-console": false,
true,
"log",
"error",
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-consecutive-blank-lines": true, "no-consecutive-blank-lines": true,
"no-construct": true, "no-construct": true,
"no-debugger": true, "no-debugger": true,
...@@ -64,7 +50,6 @@ ...@@ -64,7 +50,6 @@
"no-switch-case-fall-through": true, "no-switch-case-fall-through": true,
"no-trailing-whitespace": false, "no-trailing-whitespace": false,
"no-unused-expression": true, "no-unused-expression": true,
"no-use-before-declare": true,
"one-line": [ "one-line": [
true, true,
"check-catch", "check-catch",
...@@ -123,4 +108,4 @@ ...@@ -123,4 +108,4 @@
"check-typecast" "check-typecast"
] ]
} }
} }
\ No newline at end of file
...@@ -4,5 +4,8 @@ import nni ...@@ -4,5 +4,8 @@ import nni
if __name__ == '__main__': if __name__ == '__main__':
for i in range(5): for i in range(5):
hyper_params = nni.get_next_parameter() hyper_params = nni.get_next_parameter()
print('hyper_params:[{}]'.format(hyper_params))
if hyper_params is None:
break
nni.report_final_result(0.1*i) nni.report_final_result(0.1*i)
time.sleep(3) time.sleep(3)
authorName: nni authorName: nni
experimentName: default_test experimentName: default_test
maxExecDuration: 5m maxExecDuration: 5m
maxTrialNum: 8 maxTrialNum: 2
trialConcurrency: 4 trialConcurrency: 2
searchSpacePath: ./search_space.json searchSpacePath: ./search_space.json
tuner: tuner:
......
authorName: nni authorName: nni
experimentName: default_test experimentName: default_test
maxExecDuration: 5m maxExecDuration: 5m
maxTrialNum: 8 maxTrialNum: 2
trialConcurrency: 4 trialConcurrency: 2
searchSpacePath: ./search_space.json searchSpacePath: ./search_space.json
tuner: tuner:
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment