-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Hparam: Add selectors to retrieve data written by the new hparams effects #6544
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
93a76c2
5bf172f
85ad5c5
3887ef6
6a95104
e0a06b7
63783af
fd0ecab
c408707
d9c509e
71370ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,13 @@ See the License for the specific language governing permissions and | |
limitations under the License. | ||
==============================================================================*/ | ||
import {createFeatureSelector, createSelector} from '@ngrx/store'; | ||
import {DiscreteFilter, HparamAndMetricSpec, IntervalFilter} from '../types'; | ||
import { | ||
DiscreteFilter, | ||
HparamAndMetricSpec, | ||
HparamValue, | ||
IntervalFilter, | ||
RunToHparamsAndMetrics, | ||
} from '../types'; | ||
import {combineHparamAndMetricSpecs} from './hparams_selectors_utils'; | ||
import {HparamsState, HPARAMS_FEATURE_KEY} from './types'; | ||
import { | ||
|
@@ -178,3 +184,51 @@ export const getExperimentsHparamsAndMetricsSpecs = createSelector( | |
); | ||
} | ||
); | ||
|
||
export const getDashboardHparamsAndMetricsSpecs = createSelector( | ||
getHparamsState, | ||
(state: HparamsState) => { | ||
return state.dashboardSpecs; | ||
} | ||
); | ||
|
||
export const getRunsToHparamsAndMetrics = createSelector( | ||
getHparamsState, | ||
(state): RunToHparamsAndMetrics => { | ||
const runToHparamsAndMetrics: RunToHparamsAndMetrics = {}; | ||
|
||
for (const sessionGroup of state.dashboardSessionGroups) { | ||
const hparams: HparamValue[] = Object.entries(sessionGroup.hparams).map( | ||
(keyValue) => { | ||
const [hparam, value] = keyValue; | ||
return {name: hparam, value}; | ||
} | ||
); | ||
|
||
for (const session of sessionGroup.sessions) { | ||
runToHparamsAndMetrics[session.name] = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It probably doesn't matter since this is all going to get rewritten, but in case you decide to carry over this logic: I don't think this is desirable. session.name does not guarantee even being the name of a run. I don't think runs_data_source does this but I only took a quick look. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is essentially what runs_data_source does on lines 171-188 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is in runs_data_source.ts:L171-188. Those lines of code only generate runToHparamsAndMetrics keys from combination of session.name + metricValue.name.group (with some special handling for when group is empty). This statement here is generating keys from session.name alone. To illustrate this, the test at runs_data_source_test.ts:L63 does not generate a runToHparamsAndMetrics entry with key 'eid/run_name_2'.
It is not. Sometimes it is a run name. Sometimes it is a common prefix of a set of run names. Sometimes it is an experiment id. Sometimes it is empty. Even though this will be refactored soon, it would be reassuring if we could agree on this before merging the PR, especially since it's possible I'll be OOO during the refactor. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We discussed offline and it seems like we are on the same page. We will not generate data for runs that do not exist - more specifically we do not generate data based solely on session name. |
||
metrics: [], | ||
hparams, | ||
}; | ||
|
||
for (const metricValue of session.metricValues) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I recall in a recent discussion that we decided we didn't want metrics from the /session_groups response to appear in the Time Series runs table. You would instead be using the metric values from elsewhere in the data model - from the /timeSeries response perhaps? We thought that we could therefore turn off metric calculation for /session_groups requests for the dashboard. We'd save a bunch of unneccesary processing and reduce latency. With that in mind, is there any need for parsing the session metric values? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From an offline discussion:
|
||
const runId = metricValue.name.group | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. On the other hand, without metric values from the /session_groups requests, you wouldn't be able to calculate runIds in this way - you'd have to find a different approach. Perhaps get a list of runs for each experiment (I believe you get this elsewhere, too) and then determine which session groups they belong to? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From an offline discussion:
|
||
? `${session.name}/${metricValue.name.group}` | ||
: session.name; | ||
|
||
const hparamsAndMetrics = runToHparamsAndMetrics[runId] || { | ||
metrics: [], | ||
hparams, | ||
}; | ||
hparamsAndMetrics.metrics.push({ | ||
tag: metricValue.name.tag, | ||
trainingStep: metricValue.trainingStep, | ||
value: metricValue.value, | ||
}); | ||
runToHparamsAndMetrics[runId] = hparamsAndMetrics; | ||
} | ||
} | ||
} | ||
return runToHparamsAndMetrics; | ||
} | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,8 @@ See the License for the specific language governing permissions and | |
limitations under the License. | ||
==============================================================================*/ | ||
|
||
import {DeepPartial} from '../../util/types'; | ||
import {RunStatus, SessionGroup, MetricsValue, Session} from '../types'; | ||
import * as selectors from './hparams_selectors'; | ||
import { | ||
buildDiscreteFilter, | ||
|
@@ -466,6 +468,270 @@ describe('hparams/_redux/hparams_selectors_test', () => { | |
}); | ||
}); | ||
|
||
describe('#getDashboardHparamsAndMetricsSpecs', () => { | ||
it('returns dashboard specs', () => { | ||
const state = buildStateFromHparamsState( | ||
buildHparamsState({ | ||
dashboardSpecs: { | ||
hparams: [buildHparamSpec({name: 'foo'})], | ||
metrics: [buildMetricSpec({tag: 'bar'})], | ||
}, | ||
}) | ||
); | ||
|
||
expect(selectors.getDashboardHparamsAndMetricsSpecs(state)).toEqual({ | ||
hparams: [buildHparamSpec({name: 'foo'})], | ||
metrics: [buildMetricSpec({tag: 'bar'})], | ||
}); | ||
}); | ||
}); | ||
|
||
describe('#getRunsToHparamsAndMetrics', () => { | ||
let mockSessionGroups: SessionGroup[]; | ||
beforeEach(() => { | ||
mockSessionGroups = [ | ||
{ | ||
name: 'session_group_1', | ||
hparams: { | ||
hp1: 1, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use quotes for keys in 'hparams': 'hp1', 'hp2', etc... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The OSS linter doesn't like that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The definition of 'hparams' is:
It says the keys should be strings. Perhaps there is confusion because I quoted 'hparams' as well. I perhaps should have used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. Sorry, my bad. It's surprising to me but I guess makes sense when I realize that prettier is not the typescript compiler and probably doesn't know what has string keys and what does not. I could have tried this out myself which would have saved some back and forth. I looked briefly into prettier option "--quote-props=preserve", which would allow us to quote the property names. But perhaps its best to leave things alone and see how this quote issue plays out when prettier is adopted internally. |
||
hp2: true, | ||
hp3: 'foo', | ||
}, | ||
sessions: [ | ||
buildSession({ | ||
name: 'exp1/run1', | ||
metricValues: [ | ||
buildMetricsValue({name: {tag: 'foo', group: '1'}, value: 2}), | ||
rileyajones marked this conversation as resolved.
Show resolved
Hide resolved
rileyajones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
buildMetricsValue({name: {tag: 'bar', group: '2'}, value: 103}), | ||
rileyajones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
], | ||
}), | ||
buildSession({ | ||
name: 'exp1/run2', | ||
metricValues: [ | ||
buildMetricsValue({name: {tag: 'foo', group: '1'}, value: 3}), | ||
buildMetricsValue({name: {tag: 'bar', group: '2'}, value: 104}), | ||
buildMetricsValue({name: {tag: 'baz', group: '3'}, value: 201}), | ||
], | ||
}), | ||
], | ||
}, | ||
{ | ||
name: 'session_group_2', | ||
hparams: { | ||
hp1: 2, | ||
hp2: false, | ||
hp3: 'bar', | ||
}, | ||
sessions: [ | ||
buildSession({ | ||
name: 'exp1/run3', | ||
metricValues: [ | ||
buildMetricsValue({name: {tag: 'foo', group: '1'}, value: 4}), | ||
buildMetricsValue({name: {tag: 'bar', group: '2'}, value: 105}), | ||
], | ||
}), | ||
], | ||
}, | ||
{ | ||
name: 'session_group_3', | ||
hparams: { | ||
hp4: 'hyperparameter4', | ||
}, | ||
sessions: [ | ||
buildSession({ | ||
name: 'exp1/run4', | ||
metricValues: [], | ||
}), | ||
], | ||
}, | ||
{ | ||
name: 'session_group_4', | ||
hparams: { | ||
hp1: 7, | ||
hp2: false, | ||
hp3: 'foobar', | ||
}, | ||
sessions: [ | ||
buildSession({ | ||
name: 'exp2/run1', | ||
metricValues: [ | ||
buildMetricsValue({name: {tag: 'foo', group: '1'}, value: 4}), | ||
buildMetricsValue({name: {tag: 'bar', group: '2'}, value: 105}), | ||
buildMetricsValue({ | ||
name: {tag: 'baz', group: '2'}, | ||
value: 1000, | ||
}), | ||
], | ||
}), | ||
], | ||
}, | ||
]; | ||
}); | ||
|
||
function buildMetricsValue( | ||
override: DeepPartial<MetricsValue> | ||
): MetricsValue { | ||
return { | ||
trainingStep: 0, | ||
value: 1, | ||
wallTimeSecs: 123, | ||
...override, | ||
name: { | ||
tag: override.name?.tag ?? 'someTag', | ||
group: override.name?.group ?? 'someGroup', | ||
}, | ||
}; | ||
} | ||
|
||
function buildSession(override: Partial<Session>): Session { | ||
return { | ||
name: 'someExperiment/someRun', | ||
modelUri: '', | ||
monitorUrl: '', | ||
startTimeSecs: 123, | ||
endTimeSecs: 456, | ||
status: RunStatus.STATUS_UNKNOWN, | ||
...override, | ||
metricValues: [...(override.metricValues ?? [])], | ||
}; | ||
} | ||
|
||
it('contains entry for each runId', () => { | ||
rileyajones marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const state = buildStateFromHparamsState( | ||
buildHparamsState({ | ||
dashboardSessionGroups: mockSessionGroups, | ||
}) | ||
); | ||
|
||
expect(selectors.getRunsToHparamsAndMetrics(state)['exp1/run4']).toEqual({ | ||
metrics: [], | ||
hparams: [{name: 'hp4', value: 'hyperparameter4'}], | ||
}); | ||
}); | ||
|
||
it('contains entry for each runId/group', () => { | ||
const state = buildStateFromHparamsState( | ||
buildHparamsState({ | ||
dashboardSessionGroups: mockSessionGroups, | ||
}) | ||
); | ||
|
||
expect(selectors.getRunsToHparamsAndMetrics(state)).toEqual({ | ||
'exp1/run1': { | ||
metrics: [], | ||
hparams: [ | ||
{name: 'hp1', value: 1}, | ||
{name: 'hp2', value: true}, | ||
{name: 'hp3', value: 'foo'}, | ||
], | ||
}, | ||
'exp1/run1/1': { | ||
metrics: [{tag: 'foo', trainingStep: 0, value: 2}], | ||
hparams: [ | ||
{name: 'hp1', value: 1}, | ||
{name: 'hp2', value: true}, | ||
{name: 'hp3', value: 'foo'}, | ||
], | ||
}, | ||
'exp1/run1/2': { | ||
metrics: [{tag: 'bar', trainingStep: 0, value: 103}], | ||
hparams: [ | ||
{name: 'hp1', value: 1}, | ||
{name: 'hp2', value: true}, | ||
{name: 'hp3', value: 'foo'}, | ||
], | ||
}, | ||
'exp1/run2': { | ||
metrics: [], | ||
hparams: [ | ||
{name: 'hp1', value: 1}, | ||
{name: 'hp2', value: true}, | ||
{name: 'hp3', value: 'foo'}, | ||
], | ||
}, | ||
'exp1/run2/1': { | ||
metrics: [{tag: 'foo', trainingStep: 0, value: 3}], | ||
hparams: [ | ||
{name: 'hp1', value: 1}, | ||
{name: 'hp2', value: true}, | ||
{name: 'hp3', value: 'foo'}, | ||
], | ||
}, | ||
'exp1/run2/2': { | ||
metrics: [{tag: 'bar', trainingStep: 0, value: 104}], | ||
hparams: [ | ||
{name: 'hp1', value: 1}, | ||
{name: 'hp2', value: true}, | ||
{name: 'hp3', value: 'foo'}, | ||
], | ||
}, | ||
'exp1/run2/3': { | ||
metrics: [{tag: 'baz', trainingStep: 0, value: 201}], | ||
hparams: [ | ||
{name: 'hp1', value: 1}, | ||
{name: 'hp2', value: true}, | ||
{name: 'hp3', value: 'foo'}, | ||
], | ||
}, | ||
'exp1/run3': { | ||
metrics: [], | ||
hparams: [ | ||
{name: 'hp1', value: 2}, | ||
{name: 'hp2', value: false}, | ||
{name: 'hp3', value: 'bar'}, | ||
], | ||
}, | ||
'exp1/run3/1': { | ||
metrics: [{tag: 'foo', trainingStep: 0, value: 4}], | ||
hparams: [ | ||
{name: 'hp1', value: 2}, | ||
{name: 'hp2', value: false}, | ||
{name: 'hp3', value: 'bar'}, | ||
], | ||
}, | ||
'exp1/run3/2': { | ||
metrics: [{tag: 'bar', trainingStep: 0, value: 105}], | ||
hparams: [ | ||
{name: 'hp1', value: 2}, | ||
{name: 'hp2', value: false}, | ||
{name: 'hp3', value: 'bar'}, | ||
], | ||
}, | ||
'exp1/run4': { | ||
metrics: [], | ||
hparams: [{name: 'hp4', value: 'hyperparameter4'}], | ||
}, | ||
'exp2/run1': { | ||
metrics: [], | ||
hparams: [ | ||
{name: 'hp1', value: 7}, | ||
{name: 'hp2', value: false}, | ||
{name: 'hp3', value: 'foobar'}, | ||
], | ||
}, | ||
'exp2/run1/1': { | ||
metrics: [{tag: 'foo', trainingStep: 0, value: 4}], | ||
hparams: [ | ||
{name: 'hp1', value: 7}, | ||
{name: 'hp2', value: false}, | ||
{name: 'hp3', value: 'foobar'}, | ||
], | ||
}, | ||
'exp2/run1/2': { | ||
metrics: [ | ||
{tag: 'bar', trainingStep: 0, value: 105}, | ||
{tag: 'baz', trainingStep: 0, value: 1000}, | ||
], | ||
hparams: [ | ||
{name: 'hp1', value: 7}, | ||
{name: 'hp2', value: false}, | ||
{name: 'hp3', value: 'foobar'}, | ||
], | ||
}, | ||
}); | ||
}); | ||
}); | ||
|
||
it('does not use default filters when includeDefaults is false', () => { | ||
const state = buildStateFromHparamsState( | ||
buildHparamsState({ | ||
|
Uh oh!
There was an error while loading. Please reload this page.