Files
ray/python/ray/tune/tests/test_var.py
T
krfricke 06af62ba91 [tune] refactor tune search space (#10444)
* Added basic functionality and tests

* Feature parity with old tune search space config

* Convert Optuna search spaces

* Introduced quantized values

* Updated Optuna resolving

* Added HyperOpt search space conversion

* Convert search spaces to AxSearch

* Convert search spaces to BayesOpt

* Added basic functionality and tests

* Feature parity with old tune search space config

* Convert Optuna search spaces

* Introduced quantized values

* Updated Optuna resolving

* Added HyperOpt search space conversion

* Convert search spaces to AxSearch

* Convert search spaces to BayesOpt

* Re-factored samplers into domain classes

* Re-added base classes

* Re-factored into list comprehensions

* Added `from_config` classmethod for config conversion

* Applied suggestions from code review

* Removed truncated normal distribution

* Set search properties in tune.run

* Added test for tune.run search properties

* Move sampler initializers to base classes

* Add tune API sampling test, fixed includes, fixed resampling bug

* Add to API docs

* Fix docs

* Update metric and mode only when set. Set default metric and mode to experiment analysis object.

* Fix experiment analysis tests

* Raise error when delimiter is used in the config keys

* Added randint/qrandint to API docs, added additional check in tune.run

* Fix tests

* Fix linting error

* Applied suggestions from code review. Re-aded tune.function for the time being

* Fix sampling tests

* Fix experiment analysis tests

* Fix tests and linting error

* Removed unnecessary default_config attribute from OptunaSearch

* Revert to set AxSearch default metric

* fix-min-max

* fix

* nits

* Added function check, enhanced loguniform error message

* fix-print

* fix

* fix

* Raise if unresolved values are in config and search space is already set

Co-authored-by: Richard Liaw <rliaw@berkeley.edu>
2020-09-03 09:06:13 -07:00

309 lines
9.5 KiB
Python

import os
import numpy as np
import random
import unittest
import ray
from ray.rllib import _register_all
from ray import tune
from ray.tune.result import DEFAULT_RESULTS_DIR
from ray.tune.suggest import grid_search, BasicVariantGenerator
from ray.tune.suggest.variant_generator import (RecursiveDependencyError,
resolve_nested_dict)
class VariantGeneratorTest(unittest.TestCase):
def setUp(self):
ray.init()
def tearDown(self):
ray.shutdown()
_register_all() # re-register the evicted objects
def generate_trials(self, spec, name):
suggester = BasicVariantGenerator()
suggester.add_configurations({name: spec})
return suggester.next_trials()
def testParseToTrials(self):
trials = self.generate_trials({
"run": "PPO",
"num_samples": 2,
"max_failures": 5,
"config": {
"env": "Pong-v0",
"foo": "bar"
},
}, "tune-pong")
trials = list(trials)
self.assertEqual(len(trials), 2)
self.assertTrue("PPO_Pong-v0" in str(trials[0]))
self.assertEqual(trials[0].config, {"foo": "bar", "env": "Pong-v0"})
self.assertEqual(trials[0].trainable_name, "PPO")
self.assertEqual(trials[0].experiment_tag, "0")
self.assertEqual(trials[0].max_failures, 5)
self.assertEqual(trials[0].evaluated_params, {})
self.assertEqual(trials[0].local_dir,
os.path.join(DEFAULT_RESULTS_DIR, "tune-pong"))
self.assertEqual(trials[1].experiment_tag, "1")
def testEval(self):
trials = self.generate_trials({
"run": "PPO",
"config": {
"foo": {
"eval": "2 + 2"
},
},
}, "eval")
trials = list(trials)
self.assertEqual(len(trials), 1)
self.assertEqual(trials[0].config, {"foo": 4})
self.assertEqual(trials[0].evaluated_params, {"foo": 4})
self.assertEqual(trials[0].experiment_tag, "0_foo=4")
def testGridSearch(self):
trials = self.generate_trials({
"run": "PPO",
"config": {
"bar": {
"grid_search": [True, False]
},
"foo": {
"grid_search": [1, 2, 3]
},
"baz": "asd",
},
}, "grid_search")
trials = list(trials)
self.assertEqual(len(trials), 6)
self.assertEqual(trials[0].config, {
"bar": True,
"foo": 1,
"baz": "asd",
})
self.assertEqual(trials[0].evaluated_params, {
"bar": True,
"foo": 1,
})
self.assertEqual(trials[0].experiment_tag, "0_bar=True,foo=1")
self.assertEqual(trials[1].config, {
"bar": False,
"foo": 1,
"baz": "asd",
})
self.assertEqual(trials[1].evaluated_params, {
"bar": False,
"foo": 1,
})
self.assertEqual(trials[1].experiment_tag, "1_bar=False,foo=1")
self.assertEqual(trials[2].config, {
"bar": True,
"foo": 2,
"baz": "asd",
})
self.assertEqual(trials[2].evaluated_params, {
"bar": True,
"foo": 2,
})
self.assertEqual(trials[3].config, {
"bar": False,
"foo": 2,
"baz": "asd",
})
self.assertEqual(trials[3].evaluated_params, {
"bar": False,
"foo": 2,
})
self.assertEqual(trials[4].config, {
"bar": True,
"foo": 3,
"baz": "asd",
})
self.assertEqual(trials[4].evaluated_params, {
"bar": True,
"foo": 3,
})
self.assertEqual(trials[5].config, {
"bar": False,
"foo": 3,
"baz": "asd",
})
self.assertEqual(trials[5].evaluated_params, {
"bar": False,
"foo": 3,
})
def testGridSearchAndEval(self):
trials = self.generate_trials({
"run": "PPO",
"config": {
"qux": tune.sample_from(lambda spec: 2 + 2),
"bar": grid_search([True, False]),
"foo": grid_search([1, 2, 3]),
"baz": "asd",
},
}, "grid_eval")
trials = list(trials)
self.assertEqual(len(trials), 6)
self.assertEqual(trials[0].config, {
"bar": True,
"foo": 1,
"qux": 4,
"baz": "asd",
})
self.assertEqual(trials[0].evaluated_params, {
"bar": True,
"foo": 1,
"qux": 4,
})
self.assertEqual(trials[0].experiment_tag, "0_bar=True,foo=1,qux=4")
def testConditionResolution(self):
trials = self.generate_trials({
"run": "PPO",
"config": {
"x": 1,
"y": tune.sample_from(lambda spec: spec.config.x + 1),
"z": tune.sample_from(lambda spec: spec.config.y + 1),
},
}, "condition_resolution")
trials = list(trials)
self.assertEqual(len(trials), 1)
self.assertEqual(trials[0].config, {"x": 1, "y": 2, "z": 3})
self.assertEqual(trials[0].evaluated_params, {"y": 2, "z": 3})
self.assertEqual(trials[0].experiment_tag, "0_y=2,z=3")
def testDependentLambda(self):
trials = self.generate_trials({
"run": "PPO",
"config": {
"x": grid_search([1, 2]),
"y": tune.sample_from(lambda spec: spec.config.x * 100),
},
}, "dependent_lambda")
trials = list(trials)
self.assertEqual(len(trials), 2)
self.assertEqual(trials[0].config, {"x": 1, "y": 100})
self.assertEqual(trials[1].config, {"x": 2, "y": 200})
def testDependentGridSearch(self):
trials = self.generate_trials({
"run": "PPO",
"config": {
"x": grid_search([
tune.sample_from(lambda spec: spec.config.y * 100),
tune.sample_from(lambda spec: spec.config.y * 200)
]),
"y": tune.sample_from(lambda spec: 1),
},
}, "dependent_grid_search")
trials = list(trials)
self.assertEqual(len(trials), 2)
self.assertEqual(trials[0].config, {"x": 100, "y": 1})
self.assertEqual(trials[1].config, {"x": 200, "y": 1})
def testDependentGridSearchCallable(self):
class Normal:
def __call__(self, _config):
return random.normalvariate(mu=0, sigma=1)
class Single:
def __call__(self, _config):
return 20
trials = self.generate_trials({
"run": "PPO",
"config": {
"x": grid_search(
[tune.sample_from(Normal()),
tune.sample_from(Normal())]),
"y": tune.sample_from(Single()),
},
}, "dependent_grid_search")
trials = list(trials)
self.assertEqual(len(trials), 2)
self.assertEqual(trials[0].config["y"], 20)
self.assertEqual(trials[1].config["y"], 20)
def testNestedValues(self):
trials = self.generate_trials({
"run": "PPO",
"config": {
"x": {
"y": {
"z": tune.sample_from(lambda spec: 1)
}
},
"y": tune.sample_from(lambda spec: 12),
"z": tune.sample_from(lambda spec: spec.config.x.y.z * 100),
},
}, "nested_values")
trials = list(trials)
self.assertEqual(len(trials), 1)
self.assertEqual(trials[0].config, {
"x": {
"y": {
"z": 1
}
},
"y": 12,
"z": 100
})
self.assertEqual(trials[0].evaluated_params, {
"x/y/z": 1,
"y": 12,
"z": 100
})
def testLogUniform(self):
sampler = tune.loguniform(1e-10, 1e-1)
results = sampler.sample(None, 1000)
assert abs(np.log(min(results)) / np.log(10) - -10) < 0.1
assert abs(np.log(max(results)) / np.log(10) - -1) < 0.1
sampler_e = tune.loguniform(np.e**-4, np.e, base=np.e)
results_e = sampler_e.sample(None, 1000)
assert abs(np.log(min(results_e)) - -4) < 0.1
assert abs(np.log(max(results_e)) - 1) < 0.1
def test_resolve_dict(self):
config = {
"a": {
"b": 1,
"c": 2,
},
"b": {
"a": 3
}
}
resolved = resolve_nested_dict(config)
for k, v in [(("a", "b"), 1), (("a", "c"), 2), (("b", "a"), 3)]:
self.assertEqual(resolved.get(k), v)
def testRecursiveDep(self):
try:
list(
self.generate_trials({
"run": "PPO",
"config": {
"foo": tune.sample_from(lambda spec: spec.config.foo),
},
}, "recursive_dep"))
except RecursiveDependencyError as e:
assert "`foo` recursively depends on" in str(e), e
else:
raise
if __name__ == "__main__":
import pytest
import sys
sys.exit(pytest.main(["-v", __file__]))