Skip to content

Commit 060fecb

Browse files
authored
Hparams: Change some handling/generation of hparams with discrete domains. (#6489)
This addresses several issues and cleanup related to how we generate and handle hparams with discrete domains: 1. From the backend, always return the list of discrete domain values for a string hparam. Previously we were returning no discrete domain values when the number of values exceeded 10 but this was breaking assumptions in the Angular hparam logic. 2. In the polymer UI, generate a "regexp" filter for discrete string hparams with greater than 10 values. 3. Fix regexp filter display logic in the polymer UI. We were never successfully showing the regexp filter. The check `[[hparam.filter.regexp]]` was incorrect since it would evaluate to `false` when `regexp` is the empty string. Instead we must check that `hparam.filter.regex !== undefined`. 4. Simplify the logic for generating discrete domain filters in the polymer UI - we just need to do it in a single place now that we can assume that the backend will always return the list of discrete domain values (thanks to item (1) and to previous work done in go/tbpr/6393)
1 parent 2a74388 commit 060fecb

File tree

4 files changed

+31
-93
lines changed

4 files changed

+31
-93
lines changed

tensorboard/plugins/hparams/api.proto

+1-2
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,7 @@ message HParamInfo {
8888
// every instance of this hyperparameter will hold a value from this set. It
8989
// is used by the UI to allow filtering so that only session groups (see
9090
// below) whose associated hyperparameter value "passes" the filter are
91-
// displayed. If this is not populated, the domain is assumed to be the
92-
// entire domain of the type of the hyperparameter.
91+
// displayed. If this is not populated, the domain is assumed to be empty.
9392
oneof domain {
9493
// A discrete set of the values this hyperparameter can hold.
9594
google.protobuf.ListValue domain_discrete = 5;

tensorboard/plugins/hparams/backend_context.py

+3-14
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,13 @@ class Context:
4444
no better place. See http://wiki.c2.com/?MagicContainer
4545
"""
4646

47-
def __init__(self, tb_context, max_domain_discrete_len=10):
47+
def __init__(self, tb_context):
4848
"""Instantiates a context.
4949
5050
Args:
5151
tb_context: base_plugin.TBContext. The "base" context we extend.
52-
max_domain_discrete_len: int. Only used when computing the experiment
53-
from the session runs. The maximum number of disticnt values a string
54-
hyperparameter can have for us to populate its 'domain_discrete' field.
55-
Typically, only tests should specify a value for this parameter.
5652
"""
5753
self._tb_context = tb_context
58-
self._max_domain_discrete_len = max_domain_discrete_len
5954

6055
def experiment_from_metadata(
6156
self,
@@ -245,8 +240,7 @@ def _compute_hparam_infos(self, hparams_run_to_tag_to_content):
245240
Finds all the SessionStartInfo messages and collects the hparams values
246241
appearing in each one. For each hparam attempts to deduce a type that fits
247242
all its values. Finally, sets the 'domain' of the resulting HParamInfo
248-
to be discrete if the type is string and the number of distinct values is
249-
small enough.
243+
to be discrete if the type is string or boolean.
250244
251245
Returns:
252246
A list of api_pb2.HParamInfo messages.
@@ -309,12 +303,7 @@ def _compute_hparam_info_from_values(self, name, values):
309303
if result.type == api_pb2.DATA_TYPE_UNSET:
310304
return None
311305

312-
# If the result is a string, set the domain to be the distinct values if
313-
# there aren't too many of them.
314-
if (
315-
result.type == api_pb2.DATA_TYPE_STRING
316-
and len(distinct_values) <= self._max_domain_discrete_len
317-
):
306+
if result.type == api_pb2.DATA_TYPE_STRING:
318307
result.domain_discrete.extend(distinct_values)
319308

320309
if result.type == api_pb2.DATA_TYPE_BOOL:

tensorboard/plugins/hparams/backend_context_test.py

+2-56
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,9 @@ def _mock_list_hyperparameters(
152152
):
153153
return self._hyperparameters
154154

155-
def _experiment_from_metadata(self, max_domain_discrete_len=10):
155+
def _experiment_from_metadata(self):
156156
"""Calls the expected operations for generating an Experiment proto."""
157-
ctxt = backend_context.Context(
158-
self._mock_tb_context, max_domain_discrete_len
159-
)
157+
ctxt = backend_context.Context(self._mock_tb_context)
160158
request_ctx = context.RequestContext()
161159
return ctxt.experiment_from_metadata(
162160
request_ctx,
@@ -306,58 +304,6 @@ def test_experiment_without_experiment_tag_different_hparam_types(self):
306304
_canonicalize_experiment(actual_exp)
307305
self.assertProtoEquals(expected_exp, actual_exp)
308306

309-
def test_experiment_without_experiment_tag_many_distinct_values(self):
310-
self.session_1_start_info_ = """
311-
hparams:[
312-
{key: 'batch_size' value: {number_value: 100}},
313-
{key: 'lr' value: {string_value: '0.01'}}
314-
]
315-
"""
316-
self.session_2_start_info_ = """
317-
hparams:[
318-
{key: 'lr' value: {number_value: 0.02}},
319-
{key: 'model_type' value: {string_value: 'CNN'}}
320-
]
321-
"""
322-
self.session_3_start_info_ = """
323-
hparams:[
324-
{key: 'batch_size' value: {bool_value: true}},
325-
{key: 'model_type' value: {string_value: 'CNN'}}
326-
]
327-
"""
328-
expected_exp = """
329-
hparam_infos: {
330-
name: 'batch_size'
331-
type: DATA_TYPE_STRING
332-
}
333-
hparam_infos: {
334-
name: 'lr'
335-
type: DATA_TYPE_STRING
336-
}
337-
hparam_infos: {
338-
name: 'model_type'
339-
type: DATA_TYPE_STRING
340-
domain_discrete: {
341-
values: [{string_value: 'CNN'}]
342-
}
343-
}
344-
metric_infos: {
345-
name: {group: '', tag: 'accuracy'}
346-
}
347-
metric_infos: {
348-
name: {group: '', tag: 'loss'}
349-
}
350-
metric_infos: {
351-
name: {group: 'eval', tag: 'loss'}
352-
}
353-
metric_infos: {
354-
name: {group: 'train', tag: 'loss'}
355-
}
356-
"""
357-
actual_exp = self._experiment_from_metadata(max_domain_discrete_len=1)
358-
_canonicalize_experiment(actual_exp)
359-
self.assertProtoEquals(expected_exp, actual_exp)
360-
361307
def test_experiment_with_bool_types(self):
362308
self.session_1_start_info_ = """
363309
hparams:[

tensorboard/plugins/hparams/tf_hparams_query_pane/tf-hparams-query-pane.ts

+25-21
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ interface ColumnMetric {
4141
order?: string;
4242
}
4343

44+
const MAX_DOMAIN_DISCRETE_LIST_LEN = 10;
45+
4446
/**
4547
* The tf-hparams-query-pane element implements controls for querying the
4648
* server for a list of session groups. It provides filtering, and
@@ -102,7 +104,7 @@ class TfHparamsQueryPane extends LegacyElementMixin(PolymerElement) {
102104
</paper-input>
103105
</template>
104106
<!-- 3. A regexp -->
105-
<template is="dom-if" if="[[hparam.filter.regexp]]">
107+
<template is="dom-if" if="[[_hasRegexpFilter(hparam)]]">
106108
<paper-input
107109
label="Regular expression"
108110
value="{{hparam.filter.regexp}}"
@@ -581,25 +583,22 @@ class TfHparamsQueryPane extends LegacyElementMixin(PolymerElement) {
581583
filter: {} as any,
582584
};
583585
if (hparam.info.hasOwnProperty('domainDiscrete')) {
584-
hparam.filter.domainDiscrete = [];
585-
hparam.info.domainDiscrete.forEach((val) => {
586-
hparam.filter.domainDiscrete.push({
587-
value: val,
588-
checked: true,
586+
// Handle a discrete domain. Could be of any data type.
587+
if (hparam.info.domainDiscrete.length < MAX_DOMAIN_DISCRETE_LIST_LEN) {
588+
hparam.filter.domainDiscrete = [];
589+
hparam.info.domainDiscrete.forEach((val: any) => {
590+
hparam.filter.domainDiscrete.push({
591+
value: val,
592+
checked: true,
593+
});
589594
});
590-
});
591-
} else if (hparam.info.type === 'DATA_TYPE_BOOL') {
592-
hparam.filter.domainDiscrete = [
593-
{
594-
value: false,
595-
checked: true,
596-
},
597-
{
598-
value: true,
599-
checked: true,
600-
},
601-
];
595+
} else {
596+
// Don't show long lists of values. If the list surpasses a certain
597+
// threshold then the user instead specifies regex filters.
598+
hparam.filter.regexp = '';
599+
}
602600
} else if (hparam.info.type === 'DATA_TYPE_FLOAT64') {
601+
// Handle a float interval domain.
603602
hparam.filter.interval = {
604603
min: {
605604
value: '',
@@ -610,10 +609,11 @@ class TfHparamsQueryPane extends LegacyElementMixin(PolymerElement) {
610609
invalid: false,
611610
},
612611
};
613-
} else if (hparam.info.type === 'DATA_TYPE_STRING') {
614-
hparam.filter.regexp = '';
615612
} else {
616-
console.warn('unknown hparam.info.type: %s', hparam.info.type);
613+
console.warn(
614+
'cannot process domain type %s without discrete domain values',
615+
hparam.info.type
616+
);
617617
}
618618
result.push(hparam);
619619
});
@@ -690,6 +690,10 @@ class TfHparamsQueryPane extends LegacyElementMixin(PolymerElement) {
690690
metricInfos: newMetricInfos,
691691
};
692692
}
693+
// Determines if a regex filter should be rendered.
694+
_hasRegexpFilter(hparam) {
695+
return hparam.filter.regexp !== undefined;
696+
}
693697
// Sends a query to the server for the list of session groups.
694698
// Asynchronously updates the sessionGroups property with the response.
695699
_queryServer() {

0 commit comments

Comments
 (0)