-
Notifications
You must be signed in to change notification settings - Fork 1
/
tune.py
156 lines (118 loc) · 5.62 KB
/
tune.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""Selects the best configuration(s) from a hyperparameter grid search.
Given a directory containing multiple experiments with different configurations,
this script selects the configuration (or configurations if there is a tie) that
maximize (or minimize) the given statistic.
This script can optimize based on any statistic that is collected during training.
As these statistics are recorded as a time series over training iterations, the
'--accumulate' option allows us to specify how a sequence of statistics should be
reduced to a single scalar (default is to find the maximum value).
"""
import argparse
import json
import numpy as np
import os
import os.path
import pandas
import yaml
class Configuration:
def __init__(self, trainer, config):
self.trainer = trainer
self.config = config
self.runs = []
def parse_args():
parser = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("path", type=str, help="path to directory containing training results")
parser.add_argument("-l", "--loss", type=str, default="eval/reward_mean",
help="key of the metric to minimize (or maximize)")
parser.add_argument("-a", "--accumulate", type=str, default="max",
help="method for reducing the time series into a scalar ['mean','max','min']")
parser.add_argument("-m", "--mode", type=str, default="max",
help="whether to maximize or minimize the given key ['max','min']")
parser.add_argument("-v", "--verbose", action="store_true",
help="print results for every configuration")
return parser.parse_args()
def load_runs(path, loss, accumulate):
print(f"loading: {path}")
runs = []
if os.path.isdir(path):
for obj in os.listdir(path):
results_path = os.path.join(path, obj)
if os.path.isdir(results_path):
results_file = os.path.join(results_path, "results.csv")
if os.path.isfile(results_file):
# Load final results from CSV file (faster than tensorboard logs)
results = pandas.read_csv(results_file)
# Filter out empy data series
if len(results.index) > 0:
result = results[loss]
if "max" == accumulate:
value = np.nanmax(result)
elif "max" == accumulate:
value = np.nanmin(result)
else:
value = np.nanmean(result)
runs.append(value) # NOTE: Is there a risk that we store a reference to the full dataframe in this scalar value?
return runs
if __name__ == "__main__":
args = parse_args()
# Check that data path exists
print(f"Path: {args.path}")
assert os.path.isdir(args.path), f"Data path '{args.path}' does not exist or is not a directory"
# Load all training runs for all configurations
print("Loading runs...")
configs = dict()
for obj in os.listdir(args.path):
experiment_path = os.path.join(args.path, obj)
if os.path.isdir(experiment_path):
config_path = os.path.join(experiment_path, "config.yaml")
if os.path.isfile(config_path):
with open(config_path, 'r') as config_file:
config = yaml.load(config_file, Loader=yaml.FullLoader)
if "trainer" not in config:
config = list(config.values())[0]
trainer = config["trainer"]
trainer_config = config["config"]
runs = load_runs(experiment_path, args.loss, args.accumulate)
config_str = json.dumps({
"trainer": trainer,
"config": trainer_config
}, sort_keys=True)
# NOTE: If multiple experiment directories contain the same configuration, they will be combined
if config_str not in configs:
configs[config_str] = Configuration(trainer, trainer_config)
configs[config_str].runs.extend(runs)
# Identify best configuration(s)
if "min" == args.mode:
best_mean = np.Infinity
else:
best_mean = -np.Infinity
best_configs = []
for config in configs.values():
if len(config.runs) > 0: # NOTE: Ignores configs for which no data was available
mean = np.mean(config.runs)
if mean == best_mean:
best_configs.append(config) # NOTE: If two configurations are both optimal, we will return both
elif "min" == args.mode:
if mean < best_mean:
best_mean = mean
best_configs = [config]
else:
if mean > best_mean:
best_mean = mean
best_configs = [config]
if args.verbose:
print("\n------------")
print(f"Mean: {mean}")
print(f"Trainer: {config.trainer}")
print("Config:")
print(yaml.dump(config.config, default_flow_style=False))
# Return best config
print("Best Configs:")
for config in best_configs:
print("\n----------\n")
print(f"Trainer: {config.trainer}")
print("Config:")
print(yaml.dump(config.config, default_flow_style=False))
print(f"\n{len(best_configs)} configs (out of {len(configs)}) achieved the best observed value")
print(f"\nBest value: {best_mean}")