File size: 7,321 Bytes
99a05f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
import itertools
import operator
import os
import shutil
import time
from functools import reduce
from typing import List, Union

import configargparse
import yaml
from flatten_dict import flatten, unflatten
from loguru import logger
from yacs.config import CfgNode as CN

from utils.cluster import execute_task_on_cluster
from utils.default_hparams import hparams


def parse_args():
    def add_common_cmdline_args(parser):
        # for cluster runs
        parser.add_argument('--cfg', required=True, type=str, help='cfg file path')
        parser.add_argument('--opts', default=[], nargs='*', help='additional options to update config')
        parser.add_argument('--cfg_id', type=int, default=0, help='cfg id to run when multiple experiments are spawned')
        parser.add_argument('--cluster', default=False, action='store_true', help='creates submission files for cluster')
        parser.add_argument('--bid', type=int, default=10, help='amount of bid for cluster')
        parser.add_argument('--memory', type=int, default=64000, help='memory amount for cluster')
        parser.add_argument('--gpu_min_mem', type=int, default=12000, help='minimum amount of GPU memory')
        parser.add_argument('--gpu_arch', default=['tesla', 'quadro', 'rtx'],
                            nargs='*', help='additional options to update config')
        parser.add_argument('--num_cpus', type=int, default=8, help='num cpus for cluster')
        return parser

    # For Blender main parser
    arg_formatter = configargparse.ArgumentDefaultsHelpFormatter
    cfg_parser = configargparse.YAMLConfigFileParser
    description = 'PyTorch implementation of DECO'

    parser = configargparse.ArgumentParser(formatter_class=arg_formatter,
                                      config_file_parser_class=cfg_parser,
                                      description=description,
                                      prog='deco')

    parser = add_common_cmdline_args(parser)

    args = parser.parse_args()
    print(args, end='\n\n')

    return args

def get_hparams_defaults():
    """Get a yacs hparamsNode object with default values for my_project."""
    # Return a clone so that the defaults will not be altered
    # This is for the "local variable" use pattern
    return hparams.clone()

def update_hparams(hparams_file):
    hparams = get_hparams_defaults()
    hparams.merge_from_file(hparams_file)
    return hparams.clone()

def update_hparams_from_dict(cfg_dict):
    hparams = get_hparams_defaults()
    cfg = hparams.load_cfg(str(cfg_dict))
    hparams.merge_from_other_cfg(cfg)
    return hparams.clone()

def get_grid_search_configs(config, excluded_keys=[]):
    """
    :param config: dictionary with the configurations
    :return: The different configurations
    """

    def bool_to_string(x: Union[List[bool], bool]) -> Union[List[str], str]:
        """
        boolean to string conversion
        :param x: list or bool to be converted
        :return: string converted thinghat
        """
        if isinstance(x, bool):
            return [str(x)]
        for i, j in enumerate(x):
            x[i] = str(j)
        return x

    # exclude from grid search

    flattened_config_dict = flatten(config, reducer='path')
    hyper_params = []

    for k,v in flattened_config_dict.items():
        if isinstance(v,list):
            if k in excluded_keys:
                flattened_config_dict[k] = ['+'.join(v)]
            elif len(v) > 1:
                hyper_params += [k]

        if isinstance(v, list) and isinstance(v[0], bool) :
            flattened_config_dict[k] = bool_to_string(v)

        if not isinstance(v,list):
            if isinstance(v, bool):
                flattened_config_dict[k] = bool_to_string(v)
            else:
                flattened_config_dict[k] = [v]

    keys, values = zip(*flattened_config_dict.items())
    experiments = [dict(zip(keys, v)) for v in itertools.product(*values)]

    for exp_id, exp in enumerate(experiments):
        for param in excluded_keys:
            exp[param] = exp[param].strip().split('+')
        for param_name, param_value in exp.items():
            # print(param_name,type(param_value))
            if isinstance(param_value, list) and (param_value[0] in ['True', 'False']):
                exp[param_name] = [True if x == 'True' else False for x in param_value]
            if param_value in ['True', 'False']:
                if param_value == 'True':
                    exp[param_name] = True
                else:
                    exp[param_name] = False


        experiments[exp_id] = unflatten(exp, splitter='path')

    return experiments, hyper_params

def get_from_dict(dict, keys):
    return reduce(operator.getitem, keys, dict)

def save_dict_to_yaml(obj, filename, mode='w'):
    with open(filename, mode) as f:
        yaml.dump(obj, f, default_flow_style=False)

def run_grid_search_experiments(
        args,
        script='train.py',
        change_wt_name=True
):
    cfg = yaml.safe_load(open(args.cfg))
    # parse config file to split into a list of configs with tuning hyperparameters separated
    # Also return the names of tuned hyperparameters hyperparameters
    different_configs, hyperparams = get_grid_search_configs(
        cfg,
        excluded_keys=['TRAINING/DATASETS', 'TRAINING/DATASET_MIX_PDF', 'VALIDATION/DATASETS'],
    )
    logger.info(f'Grid search hparams: \n {hyperparams}')

    # The config file may be missing some default values, so we need to add them
    different_configs = [update_hparams_from_dict(c) for c in different_configs]
    logger.info(f'======> Number of experiment configurations is {len(different_configs)}')

    config_to_run = CN(different_configs[args.cfg_id])

    if args.cluster:
        execute_task_on_cluster(
            script=script,
            exp_name=config_to_run.EXP_NAME,
            output_dir=config_to_run.OUTPUT_DIR,
            condor_dir=config_to_run.CONDOR_DIR,
            cfg_file=args.cfg,
            num_exp=len(different_configs),
            bid_amount=args.bid,
            num_workers=config_to_run.DATASET.NUM_WORKERS,
            memory=args.memory,
            exp_opts=args.opts,
            gpu_min_mem=args.gpu_min_mem,
            gpu_arch=args.gpu_arch,
        )
        exit()

    # ==== create logdir using hyperparam settings
    logtime = time.strftime('%d-%m-%Y_%H-%M-%S')
    logdir = f'{logtime}_{config_to_run.EXP_NAME}'
    wt_file = config_to_run.EXP_NAME + '_'
    for hp in hyperparams:
        v = get_from_dict(different_configs[args.cfg_id], hp.split('/'))
        logdir += f'_{hp.replace("/", ".").replace("_", "").lower()}-{v}'
        wt_file += f'{hp.replace("/", ".").replace("_", "").lower()}-{v}_'
    logdir = os.path.join(config_to_run.OUTPUT_DIR, logdir)
    os.makedirs(logdir, exist_ok=True)
    config_to_run.LOGDIR = logdir

    wt_file += 'best.pth'
    wt_path = os.path.join(os.path.dirname(config_to_run.TRAINING.BEST_MODEL_PATH), wt_file)
    if change_wt_name: config_to_run.TRAINING.BEST_MODEL_PATH = wt_path

    shutil.copy(src=args.cfg, dst=os.path.join(logdir, 'config.yaml'))

    # save config
    save_dict_to_yaml(
        unflatten(flatten(config_to_run)),
        os.path.join(config_to_run.LOGDIR, 'config_to_run.yaml')
    )

    return config_to_run