diff --git a/examples/automated_deep_compression/ADC.py b/examples/automated_deep_compression/ADC.py index 2d358d0a656972ee80d419b566d9a7f3a902d499..72620e68a92d2e1626e6774be69d327378325194 100755 --- a/examples/automated_deep_compression/ADC.py +++ b/examples/automated_deep_compression/ADC.py @@ -1,3 +1,18 @@ +# +# Copyright (c) 2018 Intel Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# import random import math import copy @@ -13,72 +28,23 @@ from types import SimpleNamespace from distiller import normalize_module_name from base_parameters import TaskParameters -from examples.automated_deep_compression.presets.ADC_DDPG import graph_manager -msglogger = logging.getLogger() -Observation = namedtuple('Observation', ['t', 'n', 'c', 'h', 'w', 'stride', 'k', 'MACs', 'reduced', 'rest', 'prev_a']) -ALMOST_ONE = 0.9999 +# When we import the graph_manager from the ADC_DDPG preset, we implicitly instruct +# Coach to create and use our CNNEnvironment environment. +# So Distiller calls Coach, which creates the environment, trains the agent, and ends. +from examples.automated_deep_compression.presets.ADC_DDPG import graph_manager, agent_params +# Coach imports +from schedules import ConstantSchedule, PieceWiseSchedule, ExponentialSchedule +from core_types import EnvironmentSteps -# TODO: this is also defined in test_pruning.py -def create_model_masks(model): - # Create the masks - zeros_mask_dict = {} - for name, param in model.named_parameters(): - masker = distiller.ParameterMasker(name) - zeros_mask_dict[name] = masker - return zeros_mask_dict +msglogger = logging.getLogger() +Observation = namedtuple('Observation', ['t', 'n', 'c', 'h', 'w', 'stride', 'k', 'MACs', 'reduced', 'rest', 'prev_a']) +ALMOST_ONE = 0.9999 USE_COACH = True PERFORM_THINNING = True -def coach_adc(model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn): - task_parameters = TaskParameters(framework_type="tensorflow", - experiment_path="./experiments/test") - extra_params = {'save_checkpoint_secs': None, - 'render': True} - task_parameters.__dict__.update(extra_params) - - graph_manager.env_params.additional_simulator_parameters = { - 'model': model, - 'dataset': dataset, - 'arch': arch, - 'data_loader': data_loader, - 'validate_fn': validate_fn, - 'save_checkpoint_fn': save_checkpoint_fn, - 'action_range': (0.15, 0.97) - } - graph_manager.create_graph(task_parameters) - graph_manager.improve() - - -def random_adc(model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn): - """Random ADC agent""" - action_range = (0.0, 1.0) - env = CNNEnvironment(model, dataset, arch, data_loader, - validate_fn, save_checkpoint_fn, action_range) - - best = [-1000, None] - env.action_space = RandomADCActionSpace(action_range[0], action_range[1]) - for ep in range(100): - observation = env.reset() - action_config = [] - for t in range(100): - #env.render(0, 0) - msglogger.info("[episode={}:{}] observation = {}".format(ep, t, observation)) - # take a random action - action = env.action_space.sample() - action_config.append(action) - observation, reward, done, info = env.step(action) - if reward > best[0]: - best[0] = reward - best[1] = action_config - msglogger.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") - msglogger.info("New solution found: episode={} reward={} config={}".format(ep, reward, action_config)) - if done: - msglogger.info("Episode finished after {} timesteps".format(t+1)) - break - def do_adc(model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn): np.random.seed() @@ -88,102 +54,61 @@ def do_adc(model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn): return random_adc(model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn) -class RandomADCActionSpace(object): - def __init__(self, low, high): - self.low = low - self.high = high - - def sample(self): - return random.uniform(self.low, self.high) - - -class PredictableADCActionSpace(object): - def __init__(self, low, high): - #self.actions = (0.51, 0.26, 0.23, 0.09, 0.24, 0.36, 0.90, 0.97, 0.98, 0.98, 0.98, 0.98, 0) - #self.actions = (0.51, 0.26, 0.23, 0.09, 0.24, 0.36, 0.0, 0.0, 0.50, 0.50, 0.50, 0.50, 0) - #self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.50, 0.65, 0.60, 0.00, 0.00, 0) - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.8, 0.00, 0) # Top1 90.100000 Top5 99.420000 reward -0.113175 - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.7, 0.00, 0) # Top1 90.540000 Top5 99.360000 reward -0.124923 - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.6, 0.00, 0) # Top1 90.600000 Top5 99.340000 reward -0.128869 - - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.8, 0.8, 0) # Top1 87.600000 Top5 98.980000 reward -0.198718 - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.8, 0.8, 0.65) # Top1 74.720000 Top5 97.700000 reward -0.447991 - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.8, 0.8, 0.8, 0.65) # Top1 39.540000 Top5 95.420000 reward -0.886748 - - #self.actions = [0] * 13 # Top1 90.480000 Top5 99.400000 reward -0.117374 - self.step = 0 - self.episode = 0 - self.l1 = 0 - self.l2 = 0 - self.update_action_vector() - - def update_action_vector(self): - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0, 0.8, 0.05, 0) # Top1 89.640000 Top5 99.520000 reward -0.093653 - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0, 0.8, 0.05, self.episode * 0.05) - self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0, self.l1 * 0.05, self.l2 * 0.05, 0) - - def sample(self): - action = self.actions[self.step] - self.step = (self.step + 1) % len(self.actions) - if self.step == 0: - self.l1 = (self.l1 + 1) % 20 - if self.l1 == 0: - self.l2 = (self.l2 + 1) % 20 - if self.l2 == 19: - print("Done - exiting") - exit() - self.update_action_vector() - return action - +def coach_adc(model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn): + task_parameters = TaskParameters(framework_type="tensorflow", + experiment_path="./experiments/test") + extra_params = {'save_checkpoint_secs': None, + 'render': True} + task_parameters.__dict__.update(extra_params) -def collect_conv_details(model, dataset): - if dataset == 'imagenet': - dummy_input = torch.randn(1, 3, 224, 224) - elif dataset == 'cifar10': - dummy_input = torch.randn(1, 3, 32, 32) + # Create a dictionary of parameters that Coach will handover to CNNEnvironment + # Once it creates it. + if False: + graph_manager.env_params.additional_simulator_parameters = { + 'model': model, + 'dataset': dataset, + 'arch': arch, + 'data_loader': data_loader, + 'validate_fn': validate_fn, + 'save_checkpoint_fn': save_checkpoint_fn, + 'exploration_noise': 0.5, + 'exploitation_decay': 0.996, + 'action_range': (0.10, 0.95), + 'onehot_encoding': False, + 'normalize_obs': True, + 'desired_reduction': None + } else: - raise ValueError("dataset %s is not supported" % dataset) - - g = SummaryGraph(model.cuda(), dummy_input.cuda()) - conv_layers = OrderedDict() - total_macs = 0 - total_nnz = 0 - for id, (name, m) in enumerate(model.named_modules()): - if isinstance(m, torch.nn.Conv2d): - conv = SimpleNamespace() - conv.t = len(conv_layers) - conv.k = m.kernel_size[0] - conv.stride = m.stride - - # Use the SummaryGraph to obtain some other details of the models - conv_op = g.find_op(normalize_module_name(name)) - assert conv_op is not None - - total_nnz += conv_op['attrs']['weights_vol'] - conv.macs = conv_op['attrs']['MACs'] - conv_pname = name + ".weight" - conv_p = distiller.model_find_param(model, conv_pname) - conv.macs *= distiller.density_ch(conv_p) - - total_macs += conv.macs - conv.ofm_h = g.param_shape(conv_op['outputs'][0])[2] - conv.ofm_w = g.param_shape(conv_op['outputs'][0])[3] - conv.ifm_h = g.param_shape(conv_op['inputs'][0])[2] - conv.ifm_w = g.param_shape(conv_op['inputs'][0])[3] - - conv.name = name - conv.id = id - conv_layers[len(conv_layers)] = conv - - return conv_layers, total_macs, total_nnz + graph_manager.env_params.additional_simulator_parameters = { + 'model': model, + 'dataset': dataset, + 'arch': arch, + 'data_loader': data_loader, + 'validate_fn': validate_fn, + 'save_checkpoint_fn': save_checkpoint_fn, + 'exploration_noise': 0.5, + 'exploitation_decay': 0.996, + 'action_range': (0.10, 0.95), + 'onehot_encoding': True, + 'normalize_obs': True, + 'desired_reduction': 2.0e8 # 1.5e8 + } + + steps_per_episode = 13 + agent_params.exploration.noise_percentage_schedule = PieceWiseSchedule([(ConstantSchedule(0.5), + EnvironmentSteps(100*steps_per_episode)), + (ExponentialSchedule(0.5, 0, 0.996), + EnvironmentSteps(300*steps_per_episode))]) + graph_manager.create_graph(task_parameters) + graph_manager.improve() class CNNEnvironment(gym.Env): metadata = {'render.modes': ['human']} - #STATE_EMBEDDING_LEN = len(Observation._fields) + 12 - STATE_EMBEDDING_LEN = len(Observation._fields) - def __init__(self, model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn, action_range): + def __init__(self, model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn, + exploration_noise, exploitation_decay, action_range, + onehot_encoding, normalize_obs, desired_reduction): self.pylogger = distiller.data_loggers.PythonLogger(msglogger) self.tflogger = distiller.data_loggers.TensorBoardLogger(msglogger.logdir) @@ -193,21 +118,30 @@ class CNNEnvironment(gym.Env): self.validate_fn = validate_fn self.save_checkpoint_fn = save_checkpoint_fn self.orig_model = model - + self.onehot_encoding = onehot_encoding + self.normalize_obs = normalize_obs self.max_reward = -1000 self.conv_layers, self.dense_model_macs, self.dense_model_size = collect_conv_details(model, dataset) self.reset(init_only=True) msglogger.info("Model %s has %d Convolution layers", arch, len(self.conv_layers)) msglogger.info("\tTotal MACs: %s" % distiller.pretty_int(self.dense_model_macs)) - + msglogger.info("Configuration:\n\tonehot_encoding={}\n\tnormalize_obs={}".format(self.onehot_encoding, + self.normalize_obs)) self.debug_stats = {'episode': 0} self.action_low = action_range[0] self.action_high = action_range[1] + self.exploitation_decay = exploitation_decay + self.exploration_noise = exploration_noise # Gym # spaces documentation: https://gym.openai.com/docs/ self.action_space = spaces.Box(self.action_low, self.action_high, shape=(1,)) self.action_space.default_action = self.action_low + + self.desired_reduction = desired_reduction + self.STATE_EMBEDDING_LEN = len(Observation._fields) + if self.onehot_encoding: + self.STATE_EMBEDDING_LEN += 12 self.observation_space = spaces.Box(0, float("inf"), shape=(self.STATE_EMBEDDING_LEN,)) def reset(self, init_only=False): @@ -218,18 +152,14 @@ class CNNEnvironment(gym.Env): self.current_layer_id = -1 self.prev_action = 0 self.model = copy.deepcopy(self.orig_model) - self.zeros_mask_dict = create_model_masks(self.model) + self.zeros_mask_dict = distiller.create_model_masks_dict(self.model) self._remaining_macs = self.dense_model_macs self._removed_macs = 0 if init_only: return - - #layer_macs = self.get_macs(self.current_layer()) - #return self._get_obs(layer_macs) obs, _, _, _, = self.step(0) return obs - def num_layers(self): return len(self.conv_layers) @@ -279,21 +209,18 @@ class CNNEnvironment(gym.Env): distiller.log_weights_sparsity(self.model, -1, loggers=[self.pylogger]) def get_action(self, a): - #desired_reduction = 0.5e8 - desired_reduction = 2.3e8 - #desired_reduction = 1.5e8 - #if self.current_layer_id == 0: - # reduced = 0 reduced = self._removed_macs rest = self._remaining_macs - duty = desired_reduction - (reduced + rest) + #duty = self.desired_reduction - (1.2*reduced + rest) + duty = self.desired_reduction - (reduced + rest) flops = self.get_macs(self.current_layer()) - msglogger.info("action ********** a={} duty={} desired_reduction={} reduced={} rest={} flops={}".format(a, duty, desired_reduction, reduced, rest, flops)) + msglogger.info("action ********** a={} duty={} desired_reduction={} reduced={} rest={} flops={}". + format(a, duty, self.desired_reduction, reduced, rest, flops)) if duty > 0: #duty = 0.9*desired_reduction - (reduced + rest) - duty = desired_reduction - (reduced + rest) + duty = self.desired_reduction - (reduced + rest) msglogger.info("action ********** duty/flops={}".format(duty / flops)) msglogger.info("action ********** 1 - duty/flops={}".format(1 - duty / flops)) #a = max(1-self.action_low, min(a, 1 - duty/flops)) @@ -303,38 +230,22 @@ class CNNEnvironment(gym.Env): ## using max= self.action_low for FLOP-limited? Add noise so it doesn't get stuck in one place? ## #a = max(self.action_low, min(a, 1 - duty/flops)) - a = max(0, min(a, 1 - duty/flops)) + a = max(0.05, min(a, 1 - duty/flops)) return a - # def get_action(self, a): - # desired_reduction = 1.5e8 - # #if self.current_layer_id == 0: - # # reduced = 0 - # reduced = self._removed_macs - # rest = self._remaining_macs - # - # duty = desired_reduction - reduced - rest - # flops = self.get_macs(self.current_layer()) - # msglogger.info("action ********** a={} duty={} desired_reduction={} reduced={} rest={} flops={}".format(a, duty, desired_reduction, reduced, rest, flops)) - # - # if duty > 0: - # msglogger.info("action ********** duty/flops={}".format(duty / flops)) - # msglogger.info("action ********** 1 - duty/flops={}".format(1 - duty / flops)) - # #a = max(1-self.action_low, min(a, 1 - duty/flops)) - # a = max(self.action_low, min(a, 1 - duty/flops)) - # return a - def save_checkpoint(self, is_best=False): # Save the learned-model checkpoint scheduler = distiller.CompressionScheduler(self.model) masks = {param_name: masker.mask for param_name, masker in self.zeros_mask_dict.items()} scheduler.load_state_dict(state={'masks_dict': masks}) + episode = self.debug_stats['episode'] + episode = str(episode).zfill(3) if is_best: - name = "BEST_adc_episode_{}".format(self.debug_stats['episode']) + name = "BEST_adc_episode_{}".format(episode) else: - name = "adc_episode_{}".format(self.debug_stats['episode']) - self.save_checkpoint_fn(epoch=self.debug_stats['episode'], model=self.model, scheduler=scheduler, name=name) + name = "adc_episode_{}".format(episode) + self.save_checkpoint_fn(epoch=self.debug_stats['episode'], model=self.model, scheduler=scheduler, name=name) def step(self, action): """Take a step, given an action. @@ -344,14 +255,16 @@ class CNNEnvironment(gym.Env): """ msglogger.info("env.step - current_layer_id={} action={}".format(self.current_layer_id, action)) assert action == 0 or (action >= self.action_low-0.001 and action <= self.action_high+0.001) - #action = self.get_action(action) + if self.desired_reduction is not None: + action = self.get_action(action) msglogger.info("action ********** (leave) {}".format(action)) action = 1 - action layer_macs = self.get_macs(self.current_layer()) - if action > 0 and self.current_layer_id>-1: + if action > 0 and self.current_layer_id > -1: actual_action = self.__remove_channels(self.current_layer_id, action, prune_what="filters") else: actual_action = 0 + #msglogger.info("-------****---------{}".format(actual_action)) layer_macs_after_action = self.get_macs(self.current_layer()) # Update the various counters after taking the step @@ -359,54 +272,19 @@ class CNNEnvironment(gym.Env): next_layer_macs = self.get_macs(self.current_layer()) self._removed_macs += (layer_macs - layer_macs_after_action) self._remaining_macs -= next_layer_macs - self.prev_action = actual_action + #self.prev_action = 1 - actual_action stats = ('Peformance/Validation/', - {'action': action} ) + OrderedDict([('requested_action', action), + ('actual_action', 1-actual_action)])) distiller.log_training_progress(stats, None, self.debug_stats['episode'], steps_completed=self.current_layer_id, total_steps=13, log_freq=1, loggers=[self.tflogger]) - # def step(self, action): - # """Take a step, given an action. - # - # The action represents the desired sparsity. - # This function is invoked by the Agent. - # """ - # msglogger.info("env.step - current_layer_id={} action={}".format(self.current_layer_id, action)) - # assert action == 0 or (action >= self.action_low and action <= self.action_high) - # action = 1 - action - # layer_macs = self.get_macs(self.current_layer()) - # if action > 0 and self.current_layer_id>-1: - # actual_action = self.__remove_channels(self.current_layer_id, action, prune_what="filters") - # else: - # actual_action = 0 - # layer_macs_after_action = self.get_macs(self.current_layer()) - # - # # Update the various counters after taking the step - # self.current_layer_id += 1 - # next_layer_macs = self.get_macs(self.current_layer()) - # self._removed_macs += (layer_macs - layer_macs_after_action) - # self._remaining_macs -= next_layer_macs - # self.prev_action = actual_action - # - # stats = ('Peformance/Validation/', - # {'action': action} ) - # distiller.log_training_progress(stats, None, self.debug_stats['episode'], steps_completed=self.current_layer_id, - # total_steps=13, - # log_freq=1, loggers=[self.tflogger]) - if self.episode_is_done(): observation = self.get_final_obs() reward, top1 = self.compute_reward() - # Save the learned-model checkpoint - #self.save_checkpoint() - # scheduler = distiller.CompressionScheduler(self.model) - # scheduler.load_state_dict(state={'masks_dict': self.zeros_mask_dict}) - # name = "adc_episode_{}".format(self.debug_stats['episode']) - # self.save_checkpoint_fn(epoch=self.debug_stats['episode'], model=self.model, scheduler=scheduler, name=name) self.debug_stats['episode'] += 1 - if reward > self.max_reward: self.max_reward = reward self.save_checkpoint(is_best=True) @@ -417,121 +295,66 @@ class CNNEnvironment(gym.Env): if True: reward = 0 else: - reward,_ = self.compute_reward() + reward, _ = self.compute_reward() - #self.prev_action = actual_action + self.prev_action = 1 - action + msglogger.info("###################### self.prev_action={}".format(self.prev_action)) info = {} return observation, reward, self.episode_is_done(), info - def _get_obs1(self, macs): + def _get_obs4(self, macs, current_layer, conv_module): """Produce a state embedding (i.e. an observation)""" - layer = self.current_layer() - conv_module = distiller.model_find_module(self.model, layer.name) - - obs = np.array([layer.t, conv_module.out_channels, conv_module.in_channels, - layer.ifm_h, layer.ifm_w, layer.stride[0], layer.k, - macs/self.dense_model_macs, - self.removed_macs(), self.remaining_macs(), 1-self.prev_action]) - - assert len(obs) == self.STATE_EMBEDDING_LEN - assert (macs/self.dense_model_macs + self.removed_macs() + self.remaining_macs()) <= 1 - #msglogger.info("obs={}".format(Observation._make(obs))) - msglogger.info("obs={}".format(obs)) - return obs - - def _get_obs2(self, macs): - """Produce a state embedding (i.e. an observation)""" - - layer = self.current_layer() - conv_module = distiller.model_find_module(self.model, layer.name) - - obs = np.array([layer.t, conv_module.out_channels, conv_module.in_channels, - layer.ifm_h, layer.ifm_w, layer.stride[0], layer.k, - macs/self.dense_model_macs, - self.removed_macs(), self.remaining_macs(), 1-self.prev_action]) - - id = np.zeros(13) - id[layer.t] = 1 - obs = np.array([conv_module.out_channels, conv_module.in_channels, - layer.ifm_h, layer.ifm_w, layer.stride[0], layer.k, - macs/self.dense_model_macs, - self.removed_macs(), self.remaining_macs(), 1-self.prev_action]) - - obs = np.concatenate([id, obs]) - assert len(obs) == self.STATE_EMBEDDING_LEN - assert (macs/self.dense_model_macs + self.removed_macs() + self.remaining_macs()) <= 1 - #msglogger.info("obs={}".format(Observation._make(obs))) - msglogger.info("obs={}".format(obs)) - return obs - - def _get_obs3(self, macs): - """Produce a state embedding (i.e. an observation)""" - - layer = self.current_layer() - conv_module = distiller.model_find_module(self.model, layer.name) - - obs = np.array([layer.t, conv_module.out_channels, conv_module.in_channels, - layer.ifm_h, layer.ifm_w, layer.stride[0], layer.k, - macs/self.dense_model_macs, - self.removed_macs(), self.remaining_macs(), 1-self.prev_action]) - - id = np.zeros(13) - id[layer.t] = 1 - # NORMALIZE THE FEATURES!! - obs = np.array([conv_module.out_channels/512, conv_module.in_channels/512, - layer.ifm_h/32, layer.ifm_w/32, layer.stride[0]/2, layer.k/3, - macs/self.dense_model_macs, - self.removed_macs(), self.remaining_macs(), 1-self.prev_action]) - - obs = np.concatenate([id, obs]) - assert len(obs) == self.STATE_EMBEDDING_LEN - assert (macs/self.dense_model_macs + self.removed_macs() + self.remaining_macs()) <= 1 - #msglogger.info("obs={}".format(Observation._make(obs))) - msglogger.info("obs={}".format(obs)) - return obs - - def _get_obs4(self, macs): - """Produce a state embedding (i.e. an observation)""" - - layer = self.current_layer() - conv_module = distiller.model_find_module(self.model, layer.name) + if self.normalize_obs: + obs = np.array([current_layer.t, + conv_module.out_channels / 512, + conv_module.in_channels / 512, + current_layer.ifm_h / 32, + current_layer.ifm_w / 32, + current_layer.stride[0] / 2, + current_layer.k / 3, + macs / self.dense_model_macs, + self.removed_macs(), self.remaining_macs(), self.prev_action]) + else: + obs = np.array([current_layer.t, + conv_module.out_channels, conv_module.in_channels, + current_layer.ifm_h, current_layer.ifm_w, current_layer.stride[0], current_layer.k, + macs/self.dense_model_macs, + self.removed_macs(), self.remaining_macs(), self.prev_action]) - # NORMALIZE THE FEATURES!! - obs = np.array([layer.t, conv_module.out_channels / 512, conv_module.in_channels / 512, - layer.ifm_h / 32, layer.ifm_w / 32, layer.stride[0] / 2, layer.k / 3, - macs / self.dense_model_macs, - self.removed_macs(), self.remaining_macs(), 1 - self.prev_action]) + if self.onehot_encoding: + id = np.zeros(13) + id[current_layer.t] = 1 + obs = np.concatenate([id, obs[1:]]) + msglogger.info("obs={}".format(obs)) + else: + msglogger.info("obs={}".format(Observation._make(obs))) assert len(obs) == self.STATE_EMBEDDING_LEN assert (macs / self.dense_model_macs + self.removed_macs() + self.remaining_macs()) <= 1 - msglogger.info("obs={}".format(Observation._make(obs))) return obs def _get_obs(self, macs): #return self._get_obs3(macs) - return self._get_obs4(macs) - + current_layer = self.current_layer() + conv_module = distiller.model_find_module(self.model, current_layer.name) + return self._get_obs4(macs, current_layer, conv_module) def get_final_obs(self): """Return the final stae embedding (observation) The final state is reached after we traverse all of the Convolution layers. """ - if True: - obs = np.array([-1, 0, 0, - 0, 0, 0, 0, - 0, self.removed_macs(), 0, 1 - self.prev_action]) - else: + obs = np.array([-1, 0, 0, + 0, 0, 0, 0, + 0, self.removed_macs(), 0, 1 - self.prev_action]) + + if self.onehot_encoding: id = np.zeros(13) - obs = np.array([ 0, 0, - 0, 0, 0, 0, - 0, self.removed_macs(), 0, 1 - self.prev_action]) - obs = np.concatenate([id, obs]) + obs = np.concatenate([id, obs[1:]]) assert len(obs) == self.STATE_EMBEDDING_LEN return obs - def get_macs(self, layer): """Return the number of MACs required to compute <layer>'s Convolution""" if layer is None: @@ -591,6 +414,8 @@ class CNNEnvironment(gym.Env): if PERFORM_THINNING: remove_structures(self.model, self.zeros_mask_dict, self.arch, self.dataset, optimizer=None) + conv_p = distiller.model_find_param(self.model, conv_pname) + return distiller.volume(conv_p) / layer.weights_vol actual_sparsity = calculate_sparsity(conv_p) return actual_sparsity @@ -604,8 +429,12 @@ class CNNEnvironment(gym.Env): top1, top5, vloss = self.validate_fn(model=self.model, epoch=self.debug_stats['episode']) #reward = -1 * (1 - top1/100) + if self.desired_reduction is not None: + reward = top1/100 + else: + reward = -1 * (1-top1/100) * math.log(total_macs) #reward = -1 * (1-top1/100) * math.log(total_macs/self.dense_model_macs) - reward = -1 * (1-top1/100) * math.log(total_macs) + # #reward = -1 * (1-top1/100) + math.log(total_macs/self.dense_model_macs) #reward = 4*top1/100 - math.log(total_macs) #reward = reward * total_macs/213201664 @@ -630,8 +459,114 @@ class CNNEnvironment(gym.Env): ('reward', reward), ('total_macs', int(total_macs)), ('log(total_macs)', math.log(total_macs)), - ('log(total_macs/self.dense_model_macs)', math.log(total_macs/self.dense_model_macs)), + #('log(total_macs/self.dense_model_macs)', math.log(total_macs/self.dense_model_macs)), ('total_nnz', int(total_nnz))])) distiller.log_training_progress(stats, None, self.debug_stats['episode'], steps_completed=0, total_steps=1, log_freq=1, loggers=[self.tflogger, self.pylogger]) return reward, top1 + + +def get_dummy_input(dataset): + if dataset == 'imagenet': + dummy_input = torch.randn(1, 3, 224, 224) + elif dataset == 'cifar10': + dummy_input = torch.randn(1, 3, 32, 32) + else: + raise ValueError("dataset %s is not supported" % dataset) + return dummy_input + + +def collect_conv_details(model, dataset): + dummy_input = get_dummy_input(dataset) + g = SummaryGraph(model.cuda(), dummy_input.cuda()) + conv_layers = OrderedDict() + total_macs = 0 + total_nnz = 0 + for id, (name, m) in enumerate(model.named_modules()): + if isinstance(m, torch.nn.Conv2d): + conv = SimpleNamespace() + conv.t = len(conv_layers) + conv.k = m.kernel_size[0] + conv.stride = m.stride + + # Use the SummaryGraph to obtain some other details of the models + conv_op = g.find_op(normalize_module_name(name)) + assert conv_op is not None + + conv.weights_vol = conv_op['attrs']['weights_vol'] + total_nnz += conv.weights_vol + conv.macs = conv_op['attrs']['MACs'] + conv_pname = name + ".weight" + conv_p = distiller.model_find_param(model, conv_pname) + conv.macs *= distiller.density_ch(conv_p) + total_macs += conv.macs + + conv.ofm_h = g.param_shape(conv_op['outputs'][0])[2] + conv.ofm_w = g.param_shape(conv_op['outputs'][0])[3] + conv.ifm_h = g.param_shape(conv_op['inputs'][0])[2] + conv.ifm_w = g.param_shape(conv_op['inputs'][0])[3] + + conv.name = name + conv.id = id + conv_layers[len(conv_layers)] = conv + + return conv_layers, total_macs, total_nnz + + +def random_adc(model, dataset, arch, data_loader, validate_fn, save_checkpoint_fn): + """Random ADC agent""" + action_range = (0.0, 1.0) + env = CNNEnvironment(model, dataset, arch, data_loader, + validate_fn, save_checkpoint_fn, action_range) + + best = [-1000, None] + env.action_space = RandomADCActionSpace(action_range[0], action_range[1]) + for ep in range(100): + observation = env.reset() + action_config = [] + for t in range(100): + #env.render(0, 0) + msglogger.info("[episode={}:{}] observation = {}".format(ep, t, observation)) + # take a random action + action = env.action_space.sample() + action_config.append(action) + observation, reward, done, info = env.step(action) + if reward > best[0]: + best[0] = reward + best[1] = action_config + msglogger.info("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^") + msglogger.info("New solution found: episode={} reward={} config={}".format(ep, reward, action_config)) + if done: + msglogger.info("Episode finished after {} timesteps".format(t+1)) + break + + +import os +import pandas as pd +from tabulate import tabulate +import apputils +from models import create_model + + +def summarize_experiment(experiment_dir, dataset, arch, validate_fn): + df = pd.DataFrame(columns=['File', 'NNZ', 'MACs', 'Top1']) + for file in os.listdir(experiment_dir): + if file.endswith(".pth.tar"): + cnt_macs, cnt_params, top1 = get_experiment_performance_summary(os.path.join(experiment_dir, file), dataset, arch, validate_fn) + df.loc[len(df.index)] = [file, cnt_params, cnt_macs, top1] + t = tabulate(df, headers='keys', tablefmt='psql', floatfmt=".2f") + print(t) + csv_fname = os.path.join(experiment_dir, "arch_space" + ".csv") + print("Saving results to: {}".format(csv_fname)) + df.to_csv(csv_fname, sep=',') + + +def get_experiment_performance_summary(chkpt_fname, dataset, arch, validate_fn): + model = create_model(False, dataset, arch) + model, compression_scheduler, start_epoch = apputils.load_checkpoint(model, chkpt_fname) + + dummy_input = get_dummy_input(dataset) + perf_df = distiller.model_performance_summary(model, dummy_input, 1) + total_macs = perf_df['MACs'].sum() + top1, top5, vloss = validate_fn(model=model, epoch=-1) + return total_macs, distiller.model_numel(model), top1 diff --git a/examples/automated_deep_compression/adc_controlled_envs.py b/examples/automated_deep_compression/adc_controlled_envs.py new file mode 100755 index 0000000000000000000000000000000000000000..6d74a3cd5e53e50343b1afdc1177155898c2d83a --- /dev/null +++ b/examples/automated_deep_compression/adc_controlled_envs.py @@ -0,0 +1,51 @@ +"""This file contains a couple of environments used for debugging ADC reproduction. +""" +import random + + +class RandomADCActionSpace(object): + def __init__(self, low, high): + self.low = low + self.high = high + + def sample(self): + return random.uniform(self.low, self.high) + + +class PredictableADCActionSpace(object): + def __init__(self, low, high): + #self.actions = (0.51, 0.26, 0.23, 0.09, 0.24, 0.36, 0.90, 0.97, 0.98, 0.98, 0.98, 0.98, 0) + #self.actions = (0.51, 0.26, 0.23, 0.09, 0.24, 0.36, 0.0, 0.0, 0.50, 0.50, 0.50, 0.50, 0) + #self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.50, 0.65, 0.60, 0.00, 0.00, 0) + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.8, 0.00, 0) # Top1 90.100000 Top5 99.420000 reward -0.113175 + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.7, 0.00, 0) # Top1 90.540000 Top5 99.360000 reward -0.124923 + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.6, 0.00, 0) # Top1 90.600000 Top5 99.340000 reward -0.128869 + + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.8, 0.8, 0) # Top1 87.600000 Top5 98.980000 reward -0.198718 + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.0, 0.8, 0.8, 0.65) # Top1 74.720000 Top5 97.700000 reward -0.447991 + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0.8, 0.8, 0.8, 0.65) # Top1 39.540000 Top5 95.420000 reward -0.886748 + + #self.actions = [0] * 13 # Top1 90.480000 Top5 99.400000 reward -0.117374 + self.step = 0 + self.episode = 0 + self.l1 = 0 + self.l2 = 0 + self.update_action_vector() + + def update_action_vector(self): + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0, 0.8, 0.05, 0) # Top1 89.640000 Top5 99.520000 reward -0.093653 + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0, 0.8, 0.05, self.episode * 0.05) + self.actions = (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.00, 0.0, 0, self.l1 * 0.05, self.l2 * 0.05, 0) + + def sample(self): + action = self.actions[self.step] + self.step = (self.step + 1) % len(self.actions) + if self.step == 0: + self.l1 = (self.l1 + 1) % 20 + if self.l1 == 0: + self.l2 = (self.l2 + 1) % 20 + if self.l2 == 19: + print("Done - exiting") + exit() + self.update_action_vector() + return action diff --git a/examples/automated_deep_compression/comparing_model_spaces.ipynb b/examples/automated_deep_compression/comparing_model_spaces.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..2c223f24cd1792087adbc54ded14b4064b2eac07 --- /dev/null +++ b/examples/automated_deep_compression/comparing_model_spaces.ipynb @@ -0,0 +1,515 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 60, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style>\n", + " .dataframe thead tr:only-child th {\n", + " text-align: right;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: left;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>Unnamed: 0</th>\n", + " <th>File</th>\n", + " <th>NNZ</th>\n", + " <th>MACs</th>\n", + " <th>Top1</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>0</td>\n", + " <td>BEST_adc_episode_117_checkpoint.pth.tar</td>\n", + " <td>12296696</td>\n", + " <td>263284916</td>\n", + " <td>90.54</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1</td>\n", + " <td>BEST_adc_episode_013_checkpoint.pth.tar</td>\n", + " <td>10608175</td>\n", + " <td>231102406</td>\n", + " <td>89.66</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>2</td>\n", + " <td>BEST_adc_episode_001_checkpoint.pth.tar</td>\n", + " <td>3805850</td>\n", + " <td>83514494</td>\n", + " <td>10.00</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>3</td>\n", + " <td>BEST_adc_episode_002_checkpoint.pth.tar</td>\n", + " <td>1896800</td>\n", + " <td>39151400</td>\n", + " <td>10.00</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>4</td>\n", + " <td>BEST_adc_episode_124_checkpoint.pth.tar</td>\n", + " <td>12023704</td>\n", + " <td>256894642</td>\n", + " <td>90.61</td>\n", + " </tr>\n", + " <tr>\n", + " <th>5</th>\n", + " <td>5</td>\n", + " <td>BEST_adc_episode_025_checkpoint.pth.tar</td>\n", + " <td>9965512</td>\n", + " <td>238710106</td>\n", + " <td>90.10</td>\n", + " </tr>\n", + " <tr>\n", + " <th>6</th>\n", + " <td>6</td>\n", + " <td>BEST_adc_episode_004_checkpoint.pth.tar</td>\n", + " <td>6563187</td>\n", + " <td>111504654</td>\n", + " <td>23.13</td>\n", + " </tr>\n", + " <tr>\n", + " <th>7</th>\n", + " <td>7</td>\n", + " <td>BEST_adc_episode_010_checkpoint.pth.tar</td>\n", + " <td>7941730</td>\n", + " <td>212751472</td>\n", + " <td>89.29</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8</th>\n", + " <td>8</td>\n", + " <td>BEST_adc_episode_136_checkpoint.pth.tar</td>\n", + " <td>12840751</td>\n", + " <td>273482956</td>\n", + " <td>90.68</td>\n", + " </tr>\n", + " <tr>\n", + " <th>9</th>\n", + " <td>9</td>\n", + " <td>BEST_adc_episode_005_checkpoint.pth.tar</td>\n", + " <td>8555297</td>\n", + " <td>185852876</td>\n", + " <td>81.59</td>\n", + " </tr>\n", + " <tr>\n", + " <th>10</th>\n", + " <td>10</td>\n", + " <td>BEST_adc_episode_138_checkpoint.pth.tar</td>\n", + " <td>12627569</td>\n", + " <td>273074726</td>\n", + " <td>90.69</td>\n", + " </tr>\n", + " <tr>\n", + " <th>11</th>\n", + " <td>11</td>\n", + " <td>BEST_adc_episode_139_checkpoint.pth.tar</td>\n", + " <td>12966752</td>\n", + " <td>278087582</td>\n", + " <td>90.87</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " Unnamed: 0 File NNZ MACs \\\n", + "0 0 BEST_adc_episode_117_checkpoint.pth.tar 12296696 263284916 \n", + "1 1 BEST_adc_episode_013_checkpoint.pth.tar 10608175 231102406 \n", + "2 2 BEST_adc_episode_001_checkpoint.pth.tar 3805850 83514494 \n", + "3 3 BEST_adc_episode_002_checkpoint.pth.tar 1896800 39151400 \n", + "4 4 BEST_adc_episode_124_checkpoint.pth.tar 12023704 256894642 \n", + "5 5 BEST_adc_episode_025_checkpoint.pth.tar 9965512 238710106 \n", + "6 6 BEST_adc_episode_004_checkpoint.pth.tar 6563187 111504654 \n", + "7 7 BEST_adc_episode_010_checkpoint.pth.tar 7941730 212751472 \n", + "8 8 BEST_adc_episode_136_checkpoint.pth.tar 12840751 273482956 \n", + "9 9 BEST_adc_episode_005_checkpoint.pth.tar 8555297 185852876 \n", + "10 10 BEST_adc_episode_138_checkpoint.pth.tar 12627569 273074726 \n", + "11 11 BEST_adc_episode_139_checkpoint.pth.tar 12966752 278087582 \n", + "\n", + " Top1 \n", + "0 90.54 \n", + "1 89.66 \n", + "2 10.00 \n", + "3 10.00 \n", + "4 90.61 \n", + "5 90.10 \n", + "6 23.13 \n", + "7 89.29 \n", + "8 90.68 \n", + "9 81.59 \n", + "10 90.69 \n", + "11 90.87 " + ] + }, + "execution_count": 60, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import seaborn as sns\n", + "\n", + "df = pd.read_csv(\"../classifier_compression/logs/master___2018.07.24-235532/arch_space.csv\")\n", + "df" + ] + }, + { + "cell_type": "code", + "execution_count": 70, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "<div>\n", + "<style>\n", + " .dataframe thead tr:only-child th {\n", + " text-align: right;\n", + " }\n", + "\n", + " .dataframe thead th {\n", + " text-align: left;\n", + " }\n", + "\n", + " .dataframe tbody tr th {\n", + " vertical-align: top;\n", + " }\n", + "</style>\n", + "<table border=\"1\" class=\"dataframe\">\n", + " <thead>\n", + " <tr style=\"text-align: right;\">\n", + " <th></th>\n", + " <th>Unnamed: 0</th>\n", + " <th>File</th>\n", + " <th>NNZ</th>\n", + " <th>MACs</th>\n", + " <th>Top1</th>\n", + " </tr>\n", + " </thead>\n", + " <tbody>\n", + " <tr>\n", + " <th>0</th>\n", + " <td>0</td>\n", + " <td>BEST_adc_episode_003_checkpoint.pth.tar</td>\n", + " <td>4770986</td>\n", + " <td>86797580</td>\n", + " <td>11.60</td>\n", + " </tr>\n", + " <tr>\n", + " <th>1</th>\n", + " <td>1</td>\n", + " <td>BEST_adc_episode_001_checkpoint.pth.tar</td>\n", + " <td>4804162</td>\n", + " <td>90519442</td>\n", + " <td>10.07</td>\n", + " </tr>\n", + " <tr>\n", + " <th>2</th>\n", + " <td>2</td>\n", + " <td>BEST_adc_episode_142_checkpoint.pth.tar</td>\n", + " <td>13038094</td>\n", + " <td>279552826</td>\n", + " <td>90.77</td>\n", + " </tr>\n", + " <tr>\n", + " <th>3</th>\n", + " <td>3</td>\n", + " <td>BEST_adc_episode_113_checkpoint.pth.tar</td>\n", + " <td>9877163</td>\n", + " <td>234560606</td>\n", + " <td>90.48</td>\n", + " </tr>\n", + " <tr>\n", + " <th>4</th>\n", + " <td>4</td>\n", + " <td>BEST_adc_episode_016_checkpoint.pth.tar</td>\n", + " <td>8534957</td>\n", + " <td>188821328</td>\n", + " <td>84.02</td>\n", + " </tr>\n", + " <tr>\n", + " <th>5</th>\n", + " <td>5</td>\n", + " <td>BEST_adc_episode_002_checkpoint.pth.tar</td>\n", + " <td>1643593</td>\n", + " <td>83911054</td>\n", + " <td>10.00</td>\n", + " </tr>\n", + " <tr>\n", + " <th>6</th>\n", + " <td>6</td>\n", + " <td>BEST_adc_episode_125_checkpoint.pth.tar</td>\n", + " <td>12157454</td>\n", + " <td>265993040</td>\n", + " <td>90.58</td>\n", + " </tr>\n", + " <tr>\n", + " <th>7</th>\n", + " <td>7</td>\n", + " <td>BEST_adc_episode_130_checkpoint.pth.tar</td>\n", + " <td>12513507</td>\n", + " <td>265619958</td>\n", + " <td>90.59</td>\n", + " </tr>\n", + " <tr>\n", + " <th>8</th>\n", + " <td>8</td>\n", + " <td>BEST_adc_episode_107_checkpoint.pth.tar</td>\n", + " <td>9963371</td>\n", + " <td>233031242</td>\n", + " <td>89.59</td>\n", + " </tr>\n", + " <tr>\n", + " <th>9</th>\n", + " <td>9</td>\n", + " <td>BEST_adc_episode_047_checkpoint.pth.tar</td>\n", + " <td>10195618</td>\n", + " <td>238916830</td>\n", + " <td>89.24</td>\n", + " </tr>\n", + " <tr>\n", + " <th>10</th>\n", + " <td>10</td>\n", + " <td>BEST_adc_episode_025_checkpoint.pth.tar</td>\n", + " <td>9163282</td>\n", + " <td>208964092</td>\n", + " <td>88.96</td>\n", + " </tr>\n", + " <tr>\n", + " <th>11</th>\n", + " <td>11</td>\n", + " <td>BEST_adc_episode_006_checkpoint.pth.tar</td>\n", + " <td>7850262</td>\n", + " <td>180695568</td>\n", + " <td>81.23</td>\n", + " </tr>\n", + " <tr>\n", + " <th>12</th>\n", + " <td>12</td>\n", + " <td>BEST_adc_episode_132_checkpoint.pth.tar</td>\n", + " <td>12676828</td>\n", + " <td>264931726</td>\n", + " <td>90.73</td>\n", + " </tr>\n", + " <tr>\n", + " <th>13</th>\n", + " <td>13</td>\n", + " <td>BEST_adc_episode_004_checkpoint.pth.tar</td>\n", + " <td>8719624</td>\n", + " <td>158544946</td>\n", + " <td>41.62</td>\n", + " </tr>\n", + " <tr>\n", + " <th>14</th>\n", + " <td>14</td>\n", + " <td>BEST_adc_episode_024_checkpoint.pth.tar</td>\n", + " <td>9783647</td>\n", + " <td>217139246</td>\n", + " <td>88.01</td>\n", + " </tr>\n", + " <tr>\n", + " <th>15</th>\n", + " <td>15</td>\n", + " <td>BEST_adc_episode_149_checkpoint.pth.tar</td>\n", + " <td>13085489</td>\n", + " <td>280045100</td>\n", + " <td>90.88</td>\n", + " </tr>\n", + " <tr>\n", + " <th>16</th>\n", + " <td>16</td>\n", + " <td>BEST_adc_episode_112_checkpoint.pth.tar</td>\n", + " <td>10391004</td>\n", + " <td>241402680</td>\n", + " <td>90.10</td>\n", + " </tr>\n", + " <tr>\n", + " <th>17</th>\n", + " <td>17</td>\n", + " <td>BEST_adc_episode_005_checkpoint.pth.tar</td>\n", + " <td>9241390</td>\n", + " <td>153550990</td>\n", + " <td>51.40</td>\n", + " </tr>\n", + " <tr>\n", + " <th>18</th>\n", + " <td>18</td>\n", + " <td>BEST_adc_episode_140_checkpoint.pth.tar</td>\n", + " <td>13104277</td>\n", + " <td>279860848</td>\n", + " <td>90.76</td>\n", + " </tr>\n", + " <tr>\n", + " <th>19</th>\n", + " <td>19</td>\n", + " <td>BEST_adc_episode_143_checkpoint.pth.tar</td>\n", + " <td>13071701</td>\n", + " <td>280396028</td>\n", + " <td>90.85</td>\n", + " </tr>\n", + " <tr>\n", + " <th>20</th>\n", + " <td>20</td>\n", + " <td>BEST_adc_episode_017_checkpoint.pth.tar</td>\n", + " <td>9965772</td>\n", + " <td>207411696</td>\n", + " <td>85.35</td>\n", + " </tr>\n", + " </tbody>\n", + "</table>\n", + "</div>" + ], + "text/plain": [ + " Unnamed: 0 File NNZ MACs \\\n", + "0 0 BEST_adc_episode_003_checkpoint.pth.tar 4770986 86797580 \n", + "1 1 BEST_adc_episode_001_checkpoint.pth.tar 4804162 90519442 \n", + "2 2 BEST_adc_episode_142_checkpoint.pth.tar 13038094 279552826 \n", + "3 3 BEST_adc_episode_113_checkpoint.pth.tar 9877163 234560606 \n", + "4 4 BEST_adc_episode_016_checkpoint.pth.tar 8534957 188821328 \n", + "5 5 BEST_adc_episode_002_checkpoint.pth.tar 1643593 83911054 \n", + "6 6 BEST_adc_episode_125_checkpoint.pth.tar 12157454 265993040 \n", + "7 7 BEST_adc_episode_130_checkpoint.pth.tar 12513507 265619958 \n", + "8 8 BEST_adc_episode_107_checkpoint.pth.tar 9963371 233031242 \n", + "9 9 BEST_adc_episode_047_checkpoint.pth.tar 10195618 238916830 \n", + "10 10 BEST_adc_episode_025_checkpoint.pth.tar 9163282 208964092 \n", + "11 11 BEST_adc_episode_006_checkpoint.pth.tar 7850262 180695568 \n", + "12 12 BEST_adc_episode_132_checkpoint.pth.tar 12676828 264931726 \n", + "13 13 BEST_adc_episode_004_checkpoint.pth.tar 8719624 158544946 \n", + "14 14 BEST_adc_episode_024_checkpoint.pth.tar 9783647 217139246 \n", + "15 15 BEST_adc_episode_149_checkpoint.pth.tar 13085489 280045100 \n", + "16 16 BEST_adc_episode_112_checkpoint.pth.tar 10391004 241402680 \n", + "17 17 BEST_adc_episode_005_checkpoint.pth.tar 9241390 153550990 \n", + "18 18 BEST_adc_episode_140_checkpoint.pth.tar 13104277 279860848 \n", + "19 19 BEST_adc_episode_143_checkpoint.pth.tar 13071701 280396028 \n", + "20 20 BEST_adc_episode_017_checkpoint.pth.tar 9965772 207411696 \n", + "\n", + " Top1 \n", + "0 11.60 \n", + "1 10.07 \n", + "2 90.77 \n", + "3 90.48 \n", + "4 84.02 \n", + "5 10.00 \n", + "6 90.58 \n", + "7 90.59 \n", + "8 89.59 \n", + "9 89.24 \n", + "10 88.96 \n", + "11 81.23 \n", + "12 90.73 \n", + "13 41.62 \n", + "14 88.01 \n", + "15 90.88 \n", + "16 90.10 \n", + "17 51.40 \n", + "18 90.76 \n", + "19 90.85 \n", + "20 85.35 " + ] + }, + "execution_count": 70, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df2 = pd.read_csv(\"../classifier_compression/logs/master___2018.07.24-232342/arch_space.csv\")\n", + "df2\n", + "df3 = pd.read_csv(\"../classifier_compression/logs/master___2018.07.24-225916/arch_space.csv\")\n", + "df3" + ] + }, + { + "cell_type": "code", + "execution_count": 71, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAABI8AAAJcCAYAAABwj4S5AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAIABJREFUeJzs3VmQXNed5/fvuUve3DNr34ECUCgABYICSZAUuIiSKKlJtUQp3B67PRPj6A63x+EHtx0xS/jJbTtmHuyxPRP2hB/smHF7xp6JdrdtuSU1RVFUixSphSRIQASqABRq37Mq9/XmXY4fsoiFqCIAihRF8v+JQAQq8557zl3IYP14zv8orTVCCCGEEEIIIYQQQuzF+LgHIIQQQgghhBBCCCF+e0l4JIQQQgghhBBCCCH2JeGREEIIIYQQQgghhNiXhEdCCCGEEEIIIYQQYl8SHgkhhBBCCCGEEEKIfUl4JIQQQgghhBBCCCH2JeGREEIIIcSHRCn1E6XUH33c4xBCCCGE+DBJeCSEEEKI32pKqUWlVE4plbjpsz9SSv3kLtv/qVLqH35kA/yAlFJPKKV+ppQqK6UKSqnXlFIPf9zjEkIIIYR4LwmPhBBCCPFJYAL/6cc9iP2ojrv+7yqlVBr4HvA/Ad3ACPBfAe5HM0IhhBBCiA9OwiMhhBBCfBL8Y+DvKaWye32plDqulHpxdwbPFaXUv7P7+d8B/hbwD5RSNaXUd5VSf6iU+u5NbWeVUn9+088rSqnTu39/TCn1xu7soDeUUo/ddNxPlFL/SCn1GtAADr9nTENKqV8ppf7+HkOeBNBa/xutdaC1bmqtf6i1/tVu2z/YnYn0z3b7vqyUevqmc/+hUmpGKVVVSs0rpf6j9/T9LaXUeaVURSk1p5R6ZvfzjFLqnyulNpRSa0qpf6iUMu/mAQghhBDis0vCIyGEEEJ8ErwJ/AT4e+/9Ync524vAvwb6gd8H/mel1JTW+n8B/k/gv9VaJ7XW3wReBp5UShlKqWEgApzdPddhIAn8SinVDXwf+B+BHuB/AL6vlOq5qfu/DfwdIAUs3TSmQ7v9/DOt9T/e43quAoFS6n9XSj2rlOra45hHgTmgF/gT4P/ZHRNADvgGkAb+EPgnSqkHd/t+BPiXwN8HssAXgMXddn8K+MAE8ADwNUBqNAkhhBDifUl4JIQQQohPiv8C+E+UUn3v+fwbwKLW+n/TWvta67eB/xv4G3udRGs9D1SB03SClReAdaXUceAp4Kda6xD4XWBWa/2vds/7b4DLwDdvOt2faq0v7X7v7X42Bfw18Ce74dVeY6gATwAa+F+BbaXUXyqlBm46LAf8U621p7X+M+DK7pjQWn9faz2nO14Gfgg8udvuPwD+hdb6Ra11qLVe01pf3j3314H/TGtd11rngH9CJ2wTQgghhNiX9XEPQAghhBDibmitLyqlvgf858DMTV8dBB5VSpVu+swC/tX7nO5l4It0ZuC8DJToBEdnd38GGOam2US7lujUJ3rXyh7n/lvANeAv3qd/tNYzwB9AZ9kd8H8A/xT493YPWdNa6/f0Pbx7/LN0ZiNN0vmfgXHgnd3jxoC/2qPLg4ANbCil3v3M2OcahBBCCCGuk5lHQgghhPgk+RPgP+T2AOdlrXX2pj9JrfV/vPu9vu0sN8KjJ3f//jKd8OgpboRH63QCl5sdANZu+nmvc/+XwA7wr++2npDW+jKdJWX33fTxiLop5dnte10p5dCZWfXfAQNa6yydsOjdY1eAI3t0s0KnIHfvTfcprbU+eTdjFEIIIcRnl4RHQgghhPjE0FpfA/4M+OObPv4eMKmU+ttKKXv3z8NKqRO732/xnmLWdAKiLwExrfUq8FPgGTq1jd7ePeavds/7N5VSllLq36WzJO17dximR2fJXAL4l3vtwrZb4PvvKqVGd38eozPj6Bc3HdYP/PHu9fwN4MTumCKAA2wD/u4spK/d1O6fA3+olHp6t67TiFLquNZ6g87ytv9eKZXe/e6IUuqpO1yPEEIIIT7jJDwSQgghxCfNf00nmAFAa12lE578Pp3ZQpvAf0MnYIFOmDKllCoppb6z2+YqUKMTGr1bg2geeE1rHex+lqdTT+nvAnngHwDf0Frv3GmAWus28G8BA8C/2CNAqtIpiP1LpVSdTmh0cbevd/0SOEpnFtM/Av5trXV+93r/GPi/gCLwN4G/vKnv19ktog2U6QRl786g+vfphE/Tu23/Ahi60/UIIYQQ4rNN3bqUXgghhBBCfNyUUn8A/JHW+omPeyxCCCGEEDLzSAghhBBCCCGEEELsS8IjIYQQQgghhBBCCLEvWbYmhBBCCCGEEEIIIfYlM4+EEEIIIYQQQgghxL6sj3sAd6O3t1ePj49/3MMQQgghhBBCCCGE+NQ4d+7cjta6707HfSLCo/Hxcd58882PexhCCCGEEEIIIYQQnxpKqaW7OU6WrQkhhBBCCCGEEEKIfUl4JIQQQgghhBBCCCH2JeGREEIIIYQQQgghhNiXhEdCCCGEEEIIIYQQYl8SHgkhhBBCCCGEEEKIfUl4JIQQQgghhBBCCCH2JeGREEIIIYQQQgghhNiXhEdCCCGEEEIIIYQQYl8SHgkhhBBCCCGEEEKIfUl4JIQQQgghhBBCCCH2JeGREEIIIYQQQgghhNiXhEdCCCGEEEIIIYQQYl8SHgkhhBBCCCGEEEKIfUl4JIQQQgghhBBCCCH2JeGREEIIIYQQQgghhNiXhEdCCCGEEEIIIYQQYl8SHgkhhBBCCCGEEEKIfUl4JIQQQgghhBBCCCH2JeGREEIIIYQQQgghhNiXhEdCCCGEEEIIIYQQYl8SHgkhhBBCCCGEEEKIfVkf9wCEEEIIIYQQQgghPiy+7+N5HlprALTWOI6DZd0agbTbbSqVCmEYYpommUzmtmNEh9wVIYQQQgghhBDiMyoMQ2q1GmEYYlkWiUQCpdSH3k+lUmF5eZnt7e3rgY3jOPT09DAwMMDY2Bimad7WTmt9V+NZX1/n6tWrLC8vc3lmhkK+QKNRJ1SKZCJFX18Px44dwzRNdnZ2uHz5MgsLC9TqNTQQj8fpymQZHh7mxIkTPPLII0xNTWHb9od+Lz6JJDwSQgghhBBCCCE+Bs1mk2azCXTCi2g0+hvp13Vdrly5wvz8PDs7OzRaDUKt8T0fUxlks1kmJiY4ffo0qVTq1+pre3ubN954g6WFBeo7edxqDa/ZBK0xLAsPqPltSs0mSilM06RUKhGGIbZt09/fT29vL+Pj4wwODjI6OsrExASRSASAzc1NXn31VVYXF7n81nlyK6s0ag08L8DzA0JloC2Lsu9SrJZotZt4QRufEBwDLAMM1elbGSRjcQbfHOD5l37Imfsf4JlnnuH06dMYxme76o+ER0IIIYQQQgghxG/I1tYWMzMzrK2tUSpXaPsBAI5t0ZXNMDo6ytTUFD09Pfuew3VdarUaALFYjHg8vudx9Xqda9eukcvlKBaLuK7L2toam1tbtPHxbE21Vadar1GulCmVSjQbTZQXEtUWSSvO5NGjnD17ls9//vOcPHnyemhzJ2EYcu7cOc698Qb5hUWaO3mGunsYTWdIDQxTrVaYnZtjYX6ela1tiq02hcCnHoYkzQgxNA4h10xFIhGjt7+PoQMHGD9xgq7hQe6//3583+ftc+dYfucSy1euYmMRDx1GhofJZHuIxuLkq0XOX71Ebn2BklfHVyHETYxYBDvjEEnGMCImoR+ivYBmACuNHLnZAkvbq1xbmueZp7/GM888Qzqdvsen/emh3l0D+NvszJkz+s033/y4hyGEEEIIIYQQn0qlUonNzU1c1wXAcRwGBwfJZrMf88g+ParVKq+88grX5hfZLNTJV1p4IUSjneCn1awTsRQ96RiD3QmOT07w5JNPEovFACgUCkxPT7OyskK5WCTYfVZGJEIqk2FkZISpqSkGBgao1Wr84he/YPbaHLlCjUrDpVyps7RwjUqjRJ0a0XQMO2aiIgbNoE2lWcUNfAId4NVdvEoTXfeIBRa9qS6OHTzKmQcevKuZOGEY8uMf/5hL595i+8os4/0DTB44iGPbhGHI3Nw15ucXubqwSrXRxgug1qhRd6u4YQDJNKPdI0QBv9mA0EUpj77+XiKJGMneHtrxKNVmg1izjWq2wDcJtcnA8BhOtHPP1rY3ubhwmYurV6hrF19pSNmY2RiqK4Zpmzi2jRN1iNg2bbdNs1KHVkgEAzswydoJjo0d4bmvfJ1vf/vbZDKZj/ZF+Q1TSp3TWp+503Ey80gIIYQQQgghPoPCMGRxcZHp6WkWV5YoN6v4YWcWjGWaZKMpxg+MMzU1xcGDBz9Vy3Zc18XzPAzD2HfWzodpeXmZF198kWtrBXJll8HRQ9x3/CDxZOp6PZ8wDKlXy2ytLXF+bontUoONjQ2eeuop5ubmuDozQ2Nzi9ZOHu22SUSjKKVotFrkLZPNnm5mfvUrrFgMz/NYy9fZzNfo6h0iNTBKoTKNmU1iJAP6esbYya+zVtikFTQwYxaRVJRsby/ReBTLtlGGQSNXpnhtg3Klxbn5d1grbbKwusTvfPmrPPvssyQSiT2v9+c//zkX3zxH4eo1zp44Se9uCBmGIdPTl5hbWGZ2YY3QjKEiUWyvTr9tY1vdNJSmojWtwGNo6DCmUlQKW7SbVQo7ZQYNG387z2p+m1qzSTSRYDDZSzSeZGTk4PX7uZHPcXlllourV2hYPtowiaQdzJ44YdRAA1pD2/MBMA0TJxYlEonQqNfxa220EVBTLgu5FV746UtEo1F+7/d+7zNZVPuzd8VCCCGEEEII8RlXLBZ54YUXWM2tk6vlKXt1eof6iEQdAOpNl+XNqywVN7g0O8NAVx9PP/00fX19OI6z5zm11pTLZVqtFkEQEIlESKfTex7v+z6rq6s0Gg08z8O2bRKJBKOjo3sWTf51aa1ZW1vj8uXLbG1tUS2X0X6AMg2iiQR9fX0cPXqUw4cPf+jBwPLyMt//q+d5Zz5HNNPPw08+iB25/Z4YhkEq00Uq08XooUmuXjzHq29d4eWXX+ZQdzdmqcLBvn4OnThJJpG4HuZprak2GyxubvLWK6+yVKuxqW0m7nuYM0/8Dk40xsVzP2OnlqNptRk+doKN1XkaYYvAUWjTwbcVsUQUwzaxb3pe8f4MkVSM0tUNIq6i4rU4P3eJWrOO53l861vfui18W1tb48Jbb7FzdZbHTp6iJ31jps61a9dYWl7n2tImKpbFUja6WcZo1klaDql4Gq01q7UCpXqF9fw6BwfG6eofo17eoVHNk9suoMI2yTAgYhoUCmUWWpovPHryenDUaDVY3Fpmen2OdlShQ4WZcTC7YphJB1Nr2oHXSY8MA88PaLWaJMwkyjRIpJI0jAZBvY3r+VT8BgubK7x98QIHDhzg7NmzH+o78kkg4ZEQQgghhBBCfIbkcjm+9/3vcXljnpblMXrsIPcdGMK6aVepMAzJbeWYPv8Ob719Abul+fGLL3Lq/vsZGhnhxIkTHD9+nHg8Trvd5sqVK0xPT7O1XaDtB4QaLFORiNocnZhgamqKwcFByuUyMzMzXL58mVx5h5bXJtABpjKJ2Q4DXX0cP36cqakpksnkh3K9Ozs7/OQnP2FreRl3K4dXrmB4bSKWjR8GlFDspJMsXLxIanCQxx9/nImJiQ+l70ajwY9+9BIXF7bJDh7iyPH776pdNBbn8LFT/OAv3kStzJHuSvH7X/86meTtxauVUqTjCca6uthwYiwvb9KdSOG1mtRrFUqFbdY3Fim4BQ6cuo+1tXny1W0aXg2SNvF4EtO2cZt1qrUapmkS3V0qB2DFIqSPDFC5ukE6Gic0DBZzq/z456+QTCb5xje+cctuaD/72c/YuTbP0eHRW4KjQqHA6toacyub2PEsATah18Ro1IibNql4+vr1DCe6aJS3aNYrVBoVMokMiUxvJ6DMVQgqZRKxKD3KodB0cdIWa9vrHBwcA2B+fZnFnTVc7eHrACsdRUdNjGTk3ZuGbVq0PR9tKJQy8f2QttfuhJ1KEYvHqAUBQRDgqZAmbc7NXODgyBinTp360N7PTwoJj4QQQgghhBDiM6JSqfD8889zaX0WuzvGww8/jGndOtNnY2OD5eVlmuUyul5lpCtKbmWbzXyeYHUVf/IYxbl53nz9dQKtAUWh1maz1MQNDaLxJIZh4nttWvVtFreqXHhnmlqlSDQWpaZbbFXzRFIxUtkUpuXg+j7bhRzzc6vM51Y499ZbPHD6NI8++uivtW38xYsXee2VV6jPL2JWqxwbGWbs4DjpePzGLBXXZaNQYHZ1ldzyKj8sFlm+/36++MUv/tpL9V577TXm1/LYie67Do4AwiBg5sIv6Q01oRFhKNXF9uYWmYm9dz4LAp/Z2VmKFZcHJj9HwWuzvrLItB3BNE1y9U16Dx6gXC1QrZeoNsqopI1pG0Tj7y490zRbDcxalYgTwTBuvBeRZBSnL41bcskmEignZPraZQ6OjHH48GFOnjwJdN6drdU1wlqdyalTt4xxYWGeta0iynJwA0U8HqFW3sb2PFKZ3luONQ2DvliaNbdBsVYkk+iEUPFUltxKGycI8Zptal7IgVQ3W40GW/kcgz0DuO02xUaZ7WoBPwKGYRIaYGWit7xLShmYhiIMNCqi8MMQz/NwIhFQCmUYRKNR2qFLq96m0qyRiSZZ2lhlZmaGhx9++K6f56eBhEdCCCGEEEII8QnXbreZnZ1ldnaWWq1Gu93Gsizi8TiHDh3ixIkTxONxXnnlFS7MX6JlBxw+egKv3ca0OrNMtNbMz8+zurCAt72NAwxnu+gbGkZNHuPSpWtEqyHdtk22VuON119nPdBsWEmOP3CWwycfobt/+JbApdWss7pwlRde/gHN5jYq5XP/mdOc+sJDpLK371xVzpdYm1/m7fVpam6dWq3Gl7/85Q8U4ly8eJGfvvQSlekZjvb387mpKaw9lsTFHYcjQ0McGRri2vo6b0/PML279O4rX/nKBw6vyuUyV2evsV5ocObJJ+6p7fL8ZRobq8Rdl5Of+zzri1dY31jn4PhBbMu+7fi1tXW2CxW0GaGrb4AuDfWFq2wsXiOv23gZk2RPFwuzlyjVC5jxCC3dJpnsun4O23bwPY9WO6Ber5NK3fp84oMZitsr+J5PPJPAwObc9HnGRw5w4sQJDMNgbm6O2laO8cGhW55ZuVyiWK5SqjbAThN1HNxGBeU2iEcTGOr255t1Ymw2yjSbNTzfw7Zs2m4Tx45gYqLDELcdkLIdsiqk0mywXdzB9dps10uEBgRKY0QsAtvAcG6PP0zDIAxD3t1ILAxCwjDE2H1P7IiN23JRtiIINa72WFhbYmFhQcIjIYQQQgghhBCfDLVajfPnz3P58hVy5Tq5skvT9Qm0xlSKiG1waW6VF1/8Ee22y4V3LrDRzHPg2CF+8dKrWIZFV28XI4cP0PBarMzP4edyHOrrp+89u0odPXqQt9+cZqjZ5HAqzUS6i+25ZUaHuwnDkHRX720hjxON0241SfR3k8vl6BoaoGX5KHvvMCjTkyXTk6W4XeDSL8+jL2kcx+HJJ5+8p/uSy+V47ZVXqEzP8PChwxwZGrqrdhPDw3SnUrz0qwtctSwGBwc5derUnRvu4fLly2wW6/QNjeFE774odxD4rC1dg9wGk2NHiMfjxBJpqo02ua0tRkZGbzle65DNzQ1KtRZ9I4dQKFBwbHScuXM/ZccJGR4/Sa1aptlu4Ic+gamIODGUeu/zitGoVWi1WiQTKZRxIzgzbQsrHcNtt+lK9dGolyiWSqzlNlhaWuLQoUPkcjla1Sp940duOe/OTp5qvYVh2jQ9j0wyS724gem5xBJ77+hnKIO4FaHse7TaTWzLxm+3sFAYhokOPbwgwA9CetMpStUCxVqZMAyptGqEBmhTgakwnL3raClloMOAMAwxTYsw1ATBjfAIpbAsC21D0ApwA49SrUKhULheq+uzQsIjIYQQQgghhPgE2t7e5vnnn2duPc9msUUi28foifvJdPVi2jaB77OxPM+br/yA7dwKTb9IXVfIDPfQiAWEQZt23WWnXmR+fp7yToGuqMmj9x0ns8cuWtFohEx3iuWNHdydAlEzyVce/RIX1xfJL88zk/gFp848Sb1Wxmu30VpT2tlicXGGopfn1Je/TKmYY7NQZWbmMmceemjfWT1dfd2cOvsg7/zsLZwLEQ4dOsTo6Oiex76X1pqXX36Z2vwCk/0Ddx0cvas7leLsseO8OjvLLzO/4NChQx+ovs3GxgalqsuBqbsb97t2ttbxiwWSlk0m0VmmlspkqeXXKZcrjIzcenytVqNSa6CVRSJxY7ZQNOIQMSw8t4infXS9Stt3wTbwgzZx5/ZnbBgmyjDxbq7/cxM74eA3m6BB2waRuMNqboO1tTUOHTpEqVSiXa+Tec/9qtWq1Bud8IhAQRigfB/btPecdXT9GkybahDgem1SgO95EAZYVoTQ9VFKEQYhUdNE+x71RoNQadx2m9DQKAM0oOx9irArhVKA1ihAownDALgRCpmmgWcq/NCn5bqouKLpNqlUKvT09Ow79k8bCY+EEEIIIYQQv5VarRZLS0s0m02CIMBxHAYGBujr6/u4h/ax29nZ4S//8rtML+fR0S5OP/lFEqlbZwoVchvMX/kVQQoSqT6aBQ8zahI70E1kKEU06qADTX2nzNrsMm61RLQZp5ArkTm09xbsw8N9vD67QrWoeOr0cZLpNA/EjvPjX73BbKvJzuYaAT6hDtDAxsIcpXae3qkjKMtgcOwQC1cvUqrUKJaKdHd173uNmZ4sY5PjrC3luHTp0l2HRysrK+RWVrBrNe6fOnnX9/Rmo729jG5tsrO2xqVLl3j00Ufvqb3WmkKhQK3VJpXpunODm1SKO1Cv0pu+0S4aS5D3Amq12m3H12o1Wm2fWPz2ZxZzHIKqix94+C0f13PB7GxTb+yzq51pWQShj+97t4VHZtTG1XV8t000GQPfI1/Ms7OzA3R20dOhxnzPDLRms0mz1QYjhmWb+H67EwKZ7x9JGEoBIVqHAGhC0BrTNPG1BmUShhodhEQMC7fdxt+9LA0ow+j87deom6UMA5Qi1CFBEKBUZwlbEAQf+JyfRBIeCSGEEEIIIT4yQRDQarXwPI9IJEI0Gr1j/Zrt7W0uXbrE7Ow18tUWrh+gNZimIhOzGRseZGpqiomJiY9kW/ffds1mk+eff56ZlQJWepATD5697Z4Wtje5eO5VNmorpIZ7GRsfpfnGT6lWapTKFdq1KulUCssyO0Wvu23wIhRrbeaW17Ask7Gxwdv6jjoRKl4T07VJd/fiBwFX1pbYLOXZrG6yFW6THRrAdhw812W7uUld1XDCCgtX3yGRSJNIpqk0Sqyvb7xveAQwdHCEpcvzzM3PU61WSaX2Lhh9s8uXL+NubnFiZHTPGkd36/jYAX506RJXrlzhkUceuafaR77v47bbgIFtR+6p31qlDG6TVPbGrBbTtgnCEM/3bju+1Wrh+QGRePS27xw7An5A221hRmz80EdFFAb73xfT7IQ7e4UjCtVJZXRnFzZtBjTdFo1GAwDbtlGmgRcEODe9k2EYEuoQxbuBELBbbP39BDrcnR3UOZdhGKCMTogE8G6opHfPq/SNrEgDIWACod6/k9u+2ntM10MxDZZpfqaWrIGER0IIIYQQQoiPwNbWFtPT08xem6Xte4S7NXhiTozJyUmmpqbo7r49OHjzzTf5xetvsFVxyVdd0r2DJHu6MEyDdqvFlY1lVvLzXFte5/DoRZ599lni8buvJ/NpMD09zcJGAT+S5r4HPn9bcOT7HpfefI1cY43MaD+x3gxzMxeo57eImAFJrXFCiPoeyWiESrWGXa/jpG1UVHF1NUc4G5DJpEinb53NUq1W8ANNJB7H9ducn7/KajNPrUujdEhsKMvhRx/CNEw2rsySHOklkR3E6opSLhdpVhokW2mCdpudnR1arkv0PbNbbhaJOvQO95OrF7h8+fJdFSne2trCK5cZO3zkjse+n950mqjW1EslyuUy2ezetXn2opRCAaF+n9BiH4HvQRAQubkw9m7QslesobXezT9u/zbmRDE0eG0XM3K3Yce74c4eY2v7GIaBGbH2/L6rq4tIPE65VqO/68bMKcuyME2Dtu4UpFbKBsMg9P33HUkr8AijMRzb2T2PA5ZF0HZRhkIHGq01Sin8MMC0LAw6YZOlTHzlozVob59ZQjpEq059pVBrTFPdFkgHYYjSnbtvWzamMkjE4mTeUxPs007CIyGEEEIIIcSHZmdnh1deeYXVzXUKrSKFVhnLsTEtk8ALCIsBq+VNLrxzgYNjB3nqqaeuzyb5+c9/zs/feItruTrDh47x8CNHiSdunWkyeeohttaXWbxykersCu32/8e3v/1tYrHYx3G5v3FhGDIzM8NWqcnkQ4/sufQot7ZEpVlAJWzsbIytxVkixW0SQYAVcxjpHaRWLWKjSMdihG4bKxrFNi3qtoebsVks5OlbXOGB+4/fcu5Wy8UPQpxIlAvzV1lqblN1fE5MHGO1tE0tYtJuNYnFk7RqdbygTXagn2g2Raq3m+LaJpVWEV3zqDbiNBr19w2PAHqH+tm4uEixWLzj/Wk0GtQrFcwgJPUhvBNdqSSVeoN8Pn9P4ZFlWSQSCSKWotmoEYvffc2kzlIrRXjTbJm262Jbna3jb+/LxjQUwR5BTDwSxdYGQcvF7urGNq1OIMP+S660DlEGtxTLfpffcIkYJk4iRqNYxQpC4rHY9QC3v7+faDpNrli4JTyKxxMkYlHaNR/f11iRNNq08P36vuMIwpCm74GZIuZ0rtt2YmDZtNotUqZFEHjo0CNU4AOxSBTbtolGHJptF2Wa6MAnbPnXQ6Zb++h8pgyFDgOM3ZDrZqEfQNAJkBzDoifbTW/v7cXhP+0kPBJCCCGEEEJ8KFZXV3n+Bz9gqbRKnRajhw9w4sgDxJM3ZgZVSxVWri0xu7jE9tUChUKBr3/96+TzeV4/d55ruQYnH/4CvYMje/ZhWhbDBw7TOzDM+Z//hMvLOV544QW+9a1vfeAt1T9JFhcX2dwpQSRBV+/AnsesLcylnZhBAAAgAElEQVRS9cpEe9PkV5ewC9sMZ7uxtSana3h+gGlF8AOfZrOF1hoDSDkRMlEHo1ezmFvn0uwCRw6PkU7emH1UrzcwQ0Wl3aTdKFBxPCaOHca2bSzDBB0S7i53Cv0ATYhhdX7tNAyT7tFhCqvrNCsVcpub+N77zzwBsJ0IQRjguu4dj22322jfJ3rXs2zeXzQSoeR7tNvte27b19dHMrZKubhzT+FRLJ6k5kSptxqkdusYNRs1HNskuceyvWQygWNblJq3BzG2ZRExHNyGixOLEbGjnaLPJoRBsGf4GAQBpjKwrFvvYRiEtMsNkrE00XScwkqOpGfSO9JDb28vABMTE7w92M/yhYscPzh+fdlgOp0mGY+SL+VRRNEalB0hQOMFHrZ5+/Mqug0CO0IilsLa/T4SjaEiDu3AR5s2tmXge22qnouKREjE4sScGGknSbVdx/B8AkAFmrDpY8Zv7kcT6ABldMZoGJ2d1dRNoZAOQ4LARzc8ElYEKzSYGDvE0aNH3+cJfjp9tqIyIYQQQgghxEcil8vx/A9+wGx+gfhQmqeee5rJzx2/JTgCSGXTTJ05xReeexqdsbi8Ncf3v/99XnvtNRZ36hy9/+F9g6ObRZwopz//RfJNWFhZZ21t7aO6tN8q8/Pz5CotRg5O7Pl9ubBDsZTDw6XRqmCV8gxluuhOpsnEk0S1Rb1WJ+JE8QKN22rd0l4pg6GuNKlMjIbX4sLla7d8v5OvEHUVvg7IG3UOHj5wvfaLfs86puszV25auqWUomtkEM8IaLpN6o39Z568S4edujd3M9PDNE1QBkEQ3vHYuxEEIRjGB6qtdfDgQfoycTZXFu+pXSrTBbE4pXoV6CxLKxd3SMUj9Oyx1DOVShF3bNxmA8+/NeQylEHMSWKFJioAx4yi/RDLtPH2COM0Gt9rY5nqtpo+rXwVW5kksikCz8cMwWu6jPQNXS9m3t/fz/CBA9jZDJeXblx3f38fqUQUpUMc26DZqBNLpAmdGI3W7e+AFwZst6qE0ThdqZsLjhsYpo2KOrQCn1jUxrYUa8VtjHiC3kwP/dke+uJZzNDo/DEVeCFBuYm+aTbX9XfEMNBhiGWaRN5Tn6rttlEhBE2PhBljoLufod4BJicnb39wn3ISHgkhhBBCCCF+LVprXnrpJeaLy3SN9XLq86fv+Mu2HbF58AsPY3dHubBwiZ/94g0CM8rQgcN33W8kGmX08CTbVZfp6elf9zI+EZrNJq4XkkjvvYSqViniBk2UY6FaTZKWRc/uLmzdqQwxbBr1BkoZaK0JdLhbwPjmZVKKkYFuAh2wUyjR2p1143k+lVId1fAxoiZOIko8cWNpWBAEoAyM3R20TNvGUCa++55AwzCx41E85VHIF+54za1Gk4hp3bbz114SiQROMkEz8K+P+9dRrNUwY7F7WrL2romJCQZ70rj1Evncxl236x0cgXSG7UoRz/eolAqY2iebSuxZJ8y2I/T29pKMRSgXdq5/HuqQXLlAdvgAI90HqOcKRO0oETOC4Wu8Zuv6Lmbv8tptTEMRsSNY1o2FSoHr0VgvknQSZEf6KKzksH2Drmw3w32DHDhw4Pqxjz/+OL0Th1nY2iRX7DzfaDRGf18v/b0ZvFaFttvAjiQgGqPpubS9G89Ka81arYjvREkkM6Ti6RvjCDy038aOxwhMA9OxCaMWJbeBaUfoSmeJOlH6sz30J7uJahPDBwINbkBQau720dk5jd1/T5mmgR2xseybrtn3abtt2sUGqUicGDaPnX6Ys2fPEoncWxH0TwMJj4QQQgghhBC/luXlZTYLOfxIyNTDp+66nWEYfO6xB1kprrOS26Grd+Cel54NHzxCse4zN79wfcenm4VhyMLCAj/84Q/5zne+w5//+Z/zne98hx/96EcsLy+jP0BB44+T5+0WH98nnPM9j0CHNOpVzGad7uSNX7yjdoSUEyMSmJRLlc61h2BHIijLxPVu7ORlRywcy8RvttgqlQBYWFijx45jKSiHDbr7bgQZzXaLltYY0RhOtBMoJbqyOGaU+tattYo8z8WORQjNkFq9hufdvoPYzTaW1uiOZ6/Pbnk/hmHQ29uLlUyS2x33B9VwXapui0gyuWdocye2bXPmzENMjGS5evEcbbd150Z0lq11D46i01muri6ws7lKXzbO+Pj49V3H3mt0dISudJRyIUer1QlIVne2aDsOo5NTHBybJEaUsNomm+gmaHqYGLRqN2b9hDrEbTVwbJPYTUXoQz+gPLdF3I6SGegmaPsEFZd2ucGZk6d58MEHb5kV1t/fz5lHHqF/6hivX55mfWcbgMOHjzDU10XMNrB0m3qjSizVTRhPUa6XCMOAIAxZruWpKo2KpxnqHr5+Xq1DKvkNnIhJNBYjO9iHF42w4TaId2eJ+D6teg2Ag4NjHOgaJGHGiIQWljLQrk9QaeEV6rTbHphmZxdHw8C2rVtqb4VBSKPWwC81sXxF3DP50iNPcPLYFFNTU3f1HD9tpOaREEIIIYQQ4tdy6dIl8s0iYycO3nMRWTtik+hKsbFUpu0277lvJxoj1dVD3W1SLBavF+5tt9tcvHixU1y6mGOnWcL124Q6xFAGUcvhncuXGOzpZ2pqipMnT94y0+K3VSQSwVQKf59dqpRSuM0GvlsnErdIx2/dLW2gq5fGlku+WgfTJ51wiMVi1Ot13HqDhOOgjM6spLhtoz2frWIRv+pSzVU4nhkiTFW4EjZI3rQksVirEsbiZLr7rr8D2aFBcvML1Eo5/KaLFev8ct6o1UjEIxhkcf02jUZj352rqqUKbqXJwKFeJib2Xqr3XkeOHGHp4kWurq1xoL//rtrs5dr6GnZvL4cOH/7A78apU6dYWFigUp/lV6+/wqmHn8CJ3nl3wMPH7mdrdYGZ11/l9PAAw4OH6O/fu8YVQDqd4cDoCM2Wx8bqAun+QZa2N+HQESZOPEDEidJq1gnyi9R3NsjEMhSrRXTCpGXWcWJxmvUatmUSi0WvF+b2Gi6VhRxOaJLJZohnU2xfW8Uoe5w+dh/3HZvixIkTt43noYceotls8rYyeOvyVTZ2djg+Ps7RoxO02m3emVlA+wFtO4kZT9IOPJYK6zSVInBiqGSWAwMHsHfrLoWBRzm/AX4L329wYGwE09LM57Yw0wnMUDPQnWZzbZFkuovunn6Ojh2m5bWZ2Zoj8AK0MnHrHl7bw2hEMLpjOKkYtmUSj8Wv1zry2h71UgWv1MRuaTJ2mqcf/yKPP3yWr371q5+J2mp7+e3/t6MQQgghhBDit1a9XmdxeYmqX+fBQ2Mf6Bxdgz20zQW2N9YIw/CeAygr4hAEjetFjavVKs8//zzzG0ts1XcwYjZjxw6Q7s52dn3zA0o7BZbnllld2GR5e435+Xl+53d+53r49NsqkUgQi1hUijt09w3e9r0dcQg9n6DRINE9cNvm7vGIw2hPP+2NFfLNGnmrQiKRwnEcWm2XZrtNPBrFc30cw6Ttt1meX0PFuzke6+Px41NsbW/jNDya9Qp2pJe271Fu1Al7B8h0913vy4rYZAYGqC1XqK5t0zUxiu+3cZs1upIRonaKUGu899muffnKAv2pHo4fP37XAc7Ro0f55cAAO6trLOW2OPg+oct+qs0ml9fXSZ48ycmTJ++5/buUUnzta1/DdV0uza5w7tUfcfjE/fQPHdj3PQ+DgFJ+i1azgTM6QMFt4mTvvC38oUOHqVSr5OeW+Om5nxE/forDh4/TvVtY/XOPfAH1hoGtLObXLuNYBs12E9fzaVYqRJNR4k6UVCqF32zT3K7g7lRJOnFS2TSxdJKN6QXMasDE+BG+8PDjPP3003uGKUopnnzySbLZLL9Mp9iZX+Slt87Rm0rhpJOMjPQwt7RGtZrHxaTpNvAI8cKQdCTDZN8Y0UgMrQOatQq18jZG2IagxdDQAC4eqZ4+Hjh9CleHJC2bufMXqFar1PMN1rfWiMcTZCNRBiIZ3EaTpt8mNEK0B2HQRnkhfs3H7krQ9hoEOqBVb+FVmqhmgBNajPUO8o0vP8vXvvJVHn/88dvqQH2WSHgkhBBCCCGE+MCq1Sot3yWZTWM7H6wOSDKTxIxatNtNvLZ7fdnT3Qp8D2O3wG+9Xue73/0uM+vXaFoeJ594gO7+ntvaZHu7OHjsMDsb21x5+xLNhRbe9zyee+65PbdD/21x9OhRzr8zzdXleQ5MTN0WQHT1DmDj4Jbr6OG9l+Rl4kn6onGoNrB9i621bUKlCcOAutck4bbZWN0h3KxjhJouz2HiSDdf+9wDdKdSdGWzON42XqtBuVRgu1nHT6ZJdvUQeU9dop6xEUqbmxS38hiORdv2STgW6VSKoOFBCPvN41iYuUZ9u8zE6PF7CnAikQhnH3uMH5fLvHH5CtlEkkwiceeGuzzf57XpS0SGhzl26j6Ghobuuu1eYrEYzz33HKm//mtmrs4xf+0dFq9eon/4AKlMF9HYjR3VqqUCuY1l4jZ88ZEpTHWCsNXiwtw8m4UCJ8cPkU3uvXNbq92m7Ths+00iYwNUvWZnJ7JKkVS6i1S6izNPfIVr0+dJRlJcmb9AY6tIw9/GjFuEcRsjncJfLmNog1jEIWMnsO0IjUqNynqeZGjz0NRpvvyFL91V2Hrq1CkOHDjAW2+9xezVq9TzefxqHdP3iLWb5Le2ydca1G2TtrZwIg62VszMv0PEUCg/wFIapT2cqE2mK0PN1hw9dZKBI4c4+9hjTE5OcuHCBQbHD7I6v8DS7Cw7G1vkWgU8PyQSj9JNL0ajiqs92nRmH+m2D01NPd8k0CFKg6EUMcOmL93HA1OneO6bz/HEE0/cUtPps0rCIyGEEEIIIcQH1m63CQlvKTR7rxzHwbYs6vUKvte+p/Ao8H2qpQKjAzHi8TgvvPACVzfm8GKah588i/0+W7Yrpegb7ifdneGtl3/JldVrvPjii3zzm9/8wNfyURsZGWF4oJeF3CL5rXX6hm6tAxSNJ+jpG2R12aRcKDHWd/usG9/3iSiYGhwk1tXFTqVC1XMptmvUGz7l9RL1zSqHWhHShk1vdoDnHnmEvky2s5zNiZFKpbCMkPn1RdxEmuTQKAOj47f1FU0lGT5+jNbbb7Hxq6tkxjKkJ8cYHBhk8co8ZiR227sT+AHzl2bJr2wx1X+Er37lq6TT6dvO/X5OnDjB8vIyV5pNXrpwnsdPTDHQ1XXHdtVmk9emL1GPxxk6Nsljjz12T/3uJxqN8uyzz3LkyFXefvtt1ja3KVQ22NxewvWDzjG2STIW4b4DWUaHBzhz5gzj4+NcvnyZ1157jfLiEj965wJZx6EnnSEZi6GUot5qUahUyNdrxAb6OfPt50ikUlSqNTYLVS69+QpaWSRSGWw7gg41juUwkhkm5SvqzSINv0W77OPmy/i7dcBcx8aJRUlE42SdFCO9h3j49EN8+ctf5nOf+9xdzxDMZDJ86Utf4uzZs6yurrKzs0OlUsF1XVZXV5mfn2d5bZNq3SVfrFAulaj6HrYOiJqaeDRCd7aPobERRg8d4uDRCY4dO8b9999PKpUC4MyZMzzwwAOsrKyQy+XY2Nhgc3OTWq2GUopMJkMikWB7e5u5uTm2cjm28jmKpSKtZgsdhkQjUQb6+nnggQf40pe+xIMPPkhfX98dru6zQ8IjIYQQQgghxAdm2zYGimD3F+APYmBgkIgJ5fwOpnlvv6JsrS2RsGFsZIhSqcTi+hJl3eDRJ5543+DoZk7U4YEnH+YXL77G/PIC6+vrDA8P37nhx0ApxdTUFHOrW8xfvkBXbz/We7YXHxmfZOaNn7CTK+Af9m8JZzSaarlAzDbpznbR39/Pgb4+CrUa1UaTYqnIzNY1Uq7FZCSOYdtkstnrtZP8IEBrTbPaomZrVDaNth1812VrbYlMdx8RJ4qhDILAp1YtUa5u46Qc+poJKAc05gusVnzcVoP48CDJ3Zk09WqN9flVtpbXSUcS3D90jK88/TTj4+Mf6F49/fTTBEHAvGXz1zPTjHf3cHx0dM+ZO03XZW5jg+nVFSIjwwxOTvK7v/u7H/ostMnJSY4ePcr6+jpra2vs7OzQbHZqfSUSCXp7exkdHWVw8MaSxOPHjzM6Osr58+e5euUK9UKBzVqdoFYBwIxEsEYGGchmOTo5yenTp+nq6qJcLjM9Pc3s7CzlSo16yyMImhiO4tCpA6TPnmB8fJzu7m4WFxe5cuUK8/Pz1Jr1ThHzEGKxKP19/Zw8eZIHH3yQycnJD7zTWDQaZWJi4rbaVWEYksvluHbtGvPz8xSLRVqtFlprHMehp6eH3t5eRkZG6Ovro7+/f88ljKZpMj4+flfvS7lcZmVlhVKphFKKnp4e+vr66Orquudls58VEh4JIYQQQgghPrBEIoFjRqiVNwn8ANPaexew95OMJYhoaLhNGrUK0fjdLTEKw5CVuSsMpSJMTU0xPT3Ndr3IwROHiNzjErpoPMbYxAF25ne4dOnSb214BJ0wYWZmhtrVVX71+ivc/8gXbgmQDh49SaKrn9pGkatX5jh2fALTMtFoKsU82muRTEav7yCmlKInlaI7mcSrtDicHaDb7GfAjjC9s0Mhv8OfvfoqhgKtFCWliSa6qcYMznzhUQDy+QKVZoPtlWsEoUZrjaEU0YhJX9JmYugIceskjUKVUrHEwpVFWs0mm2WT1ysevuejQuhP9nBf/1EOjo7x6KOP3hKi3CvLsnjmmWc4PzjIm6+/zsbaGosXzpOwLLpTKRzbxvMDyvUapWYLu6eb5H0nmbzvPh5//PGPbPmiUoqRkRFGRkbuuk0ymeSJJ57g0UcfZWtri+3tber1OlprYrEYfX19DAwM3DLmTCbD2bNnOXv2LNVqlUKhgOd5WJZFNpslk8lcr1d0//33A51/phqNBkEQYBgGsVjsIy8kbxgGg4ODDA4O8sQTT3ykfb0rk8nsW6Rd7E3CIyGEEEIIIcQHlk6nGRkaYbW2xcbSGqNH7r02yPriKmM9w4Qxk+m3fs6DT36VeCL1vm201sy8/UtMr8bQ2AA9PT0srSxRDRqcHr/zlu57GT18gNdm5pmbn+Ox+mMk7qFOzm+Sbdt8/etfx3X/X2aWtnnrtR9x+Pjn6BkYRimFE43ywGNf4e3vbtPMVZlVcwwO9xMGLvgumUSE4eHhW2YktVpt5udXaRUbTKWHePaph/jJhfN0Dw8xeOQI6UwGHYZYts14Nkt6bo7F+gaDPf30DvXjtlw2NzfJ5/N4nkeoQyzTIh6PMzg4SHd3N8roBBW5tU2K6zukzV4ePPogEcfBNAyiEYfJyUmmpqbo6bm9TtUHYRgGDz74IIcPH+bixYtcvXqVVqXCTr2O9gOUE8Hs6yWbiHPo8GFOnjx5T6HOb5pt24yOjjI6em/veCqVur7E6/0YhnF9JpgQN5PwSAghhBBCCPFrOXnyJP8/e/cdHHd6oHf+eTsCjZxB5EhEhiExw+FwNCMOKU5crbxa+3at2tWtfaXaO9fa5z2X93xln311Z1fZ5T+89p3l0p3OJ9try1qVNmgkSpMDh5oAajhDIhA5B4LIqfN7fxBDTWADDRCN+P1UoYju/r1vP42qmUI9eH/v2zPSp6HugU2XR5FIRCN9Q2qsrFd2epaGJmd17a2XVHfiEeUdKbnvSU4rSwvqbruuwOy46oszdenSJY2OjmrOv6j8ksK4b1f7PG9ykrIKczW3vKiRkRHV1dVtaZ6dkJqaqq997WvyXr6snqEJ9d+4qu6bXhWV1ygjO1d5hSXKqqzT3Ie/UMAfUMfYmHzpySo+kqP8ylK53G4FAkEtL69qfPyO5mcXleNIUmNSns4fO3Z382W3RycfPaNvfOMbMsYoGo3K6by7suyjjz7Sy2++qs7WmzrxpRalZaarvKJc5RXl6+YOBUMa6OhVy7FTerT5tL785S8rFArJ4/HI4/Ek7Bj0zMxMPf7443rsscc0MzOjmZkZBYNBOZ1OZWZmKjc391CfpAVshPIIAAAAwAOpqqpSfnquJsfvqPdml6qbj8Y9tv2DG0q2XpUUFuvXfu3X9PLLL8vTM6C+j66q64ZXxeXVSsvMlnE4FPCvanJkQAt3JpSXlqSa0hw988zTys/P18DAgELRsFJTNrex8uf5UnwKLS4rEAg80Dw7IS0tTV/72tfU2dmp9vZ2jd2e1u3xW5oabFckYhUILCkpJ1u+hVlVJ2fKuHwKrjrU/nGfIjYqh4y8DqeyHckq8+arKr9AxysqFAiH9c7NNiXV1X5mY+RPiiPp7m1OU1NTCt8I66O3W1Xfcky5R9bfXHh5cUk3f3FdmcanxrJanT9/Xl6vd0dPt3M4HMrNzVVubu6OvSdwEFAeAQAAAHggTqdTTz75pJYuL6u7o19WUs0GBVI0GlV7603NjtzR0ZwqPfHEE/J4PHr22WdV1tamtrY2jU/NaHr0lqYHo7KSnMYoI8Wt6oocHT1aq5MnT97btyQajd7dZ8f5YJvdGoeRtXdXRO0HHo9Hx48f17FjxzQyMqKuri4tLy8rGAzKPlSl69evyzs3r/xoVBWFhZpeWNDi6qqC4bDcTqeSPB5VFOSr5kiRvC6X+m/f1ntd3XJVV6rh1Kl7e+F8njFG58+fl7VWrg6Xuj+4qT6fR8XVZSooOXLvljhrraYnpjTaO6SFqTmVZBaqsfyonnnmGXm93p38UQF4AJRHAAAAAB5YeXm5nvryeemN19XfOaTbIxMqq63QkfLiz2yiHQoENdI/rOGeQbmCRrXZlXrm6afvHYntcDh07NgxNTc3a3R0VL29vfc28PV6vSosLNTRo0e/UDx41/bNCQaCD/Q5QsGQXA7nvis2jDEqLS1VaWnpZ55//vnndfnyZU10dqpjZFQ5SUl6qLpaZbm5cq9thLzs96t7bEzdY6Pyu9zy1B9V0+nTG25e7HQ6dfHiReXl5enjjz/W5OyUJjpH1P1hh1wel4zDoXAwJJ8rSQWpOaorK1VdXZ0effTRLZ/YBWB3UB4BAAAA2Bb19fVKTk7Wm2++qdsLdzT0UZ9uXe9QakaaXG6nwqGIFmfnleZKUVFyngrz83XhwgXl5+d/YS5jzKY2Bs7JyVGqJ0Wjo5OqPV6/pb1zotGopsZuqya15N5JZPudz+fTV7/6VbUWFamzs1OLk5N6d3RU73R2yCEja6OyDqdc2dly19So4MgRnThxQg0NDXHNb4zRyZMndezYMfX399+9fW58TOFoRLJWTodTWZlZamxsVF1d3b4r5QDcRXkEAAAAYNuUl5frG9/4hvr6+tTe3q7hsREFQ0FFg1YO49CRzGxVllWoqalJpaWl9/bTeVAlJSUqzM7XyMCEZibvKKdw/f137uf2yIS81qmigiMPdET8XuN2u3X27Fk9/PDD6unpUUdHh6anpxUJhyVJHq9XxcXFamxsVHFx8ZaKN6fTqZqaGtXU1CgSiSgQCMhaK6/Xm/Cj3gEkHv8VAwAAANhWTqdTtbW1qq2t1cLCgpaWlhQOh+XxeJSampqQo8AdDocaGho0cHtYQ90Dmy6PrLUa6h5Qri9bjY2N255vL3C5XKqvr1d9fb0kKRwOy+FwbFuB9wmn0ymfz7etcwLYXZRHAAAAAB6ItVYrKyv3Tijzer3y+Xwyxig9PV3p6Q92Alq86uvr1Xrtmjpu96i/o1eVDdVxj+3+qFPhxYDyC8tUW1ubwJR7ByuCAMSL/1sAAAAA2JLV1VV1dnaqo6NDs/OzCtu7J5S5jFM5WTlqaGhQXV3djh3FnpycrIsXLij4s4C62/sUCYdV3Xx03duwotGobn3YrjuDE6rLrdKlr1yS2+3ekbwAsF9QHgEAAADYlFAopKtXr+rWrVuaXp3VtH9eEaeVJ+nuCVqB1YAGF8Y0cHtY73/wvhrqG3T27Fk5nc4NZn5wlZWVuvjURZnXX9NA74jGB0dVXFWm4qpSeZN+tVmzf2VVI33DGu0bUrL1qD63Sl+5+BUVFxcnPCMA7DeURwAAAADitrq6qsuXL6truEfjK3eUU5SrppqTyinMvbfCx1qrO+O3NdQ1oJsT3ZpZmdfMzIyefvrpHTltq66uTikpKbpy5Yompid1p3dSAx098iR55XS7FAmFFVwNKCspQ9UpJTqSX6gvfelLB2qTbADYTsZau9sZNtTS0mJbW1t3OwYAAABwqIXDYf34xz/WzYEOLThWderJR5Savv7m1wuz8/rlWx8ox5GuY1WNeu6553ZkBZJ0t8QaHR1Ve3u7+vr7FYwEFbVROYxDXpdX1VVVampq0pEjR3YkDwDsNcaYa9balo2uY+URAAAAgLi8++67ujXco3mzokcvnpM3eeO9jNKzMnTm4jm9+/IVdQ50qeBagR555JEdSCsZY1RSUqKSkhIFg0GtrKwoHA7L7XYrOTlZHo9nR3IAwH63vWcyAgAAADiQAoGAOm91anxpSqeeeDiu4ugTySnJOnnutMaWb6u9vV3hcDiBSe/P4/EoMzNTubm5ysjIoDgCgE2gPAIAAACwoa6uLk0vzymzIEtpmembHp+Vly1fZqruLM2ot7c3AQkBAIlCeQQAAABgQ+3t7ZpenVVpbcWW5yitLdf06qza29u3LxgAIOHY8wgAAABY4/f71d3drbm5OQWDQbndbqWmpqq2tlZpaWm7HW/XBINBzc7Oyq+Q8orytzxPYWmRbr77kaampmStvXc6GwBgb6M8AgAAwKE3NTWlmzdvqru7W/OBBfkjAVkblZFDHqdb773/niorKtXU1KTS0tLdjrvjgsGgwjYit8cth2PrNy84XU453U5FohGFQiH2HQKAfYLyCAAAAIdaW1ub3nz7Tc0G5rQQXFTukTyVF1fI5XYrHA5r5va0BoeHNdVxRz39PTp14pTOnj17qFbNGGNkJFlrH3guayUjc6h+fgCw31EeAQAA4NC6ceOG3rzypkaXxlV6tEyn6x+WLzXlMyKU8WYAACAASURBVNdU1FYo4A9oqHdQ3R93KfxhROFwWE888cQupd55Xq9XTodToUBQkXBETpdzS/MEA0HZSFQul0tut3ubUwIAEoUNswEAAHAojYyM6O133tbo0riazjSrueXYF4qjT3iTvKptOqpHzp/RZOC2rt+8rps3b+5w4t3jcrlUWFCoVJdP44OjW55ntH9YGZ5UFRUVbWM6AECiUR4BAADgULp+/bqmVqdV2VSlsuryuMbkFubpxKMnNbkypevXrysajSY45d7R1NSknORMDXUPbGm8tVbD3YPKSc5SU1PT9oYDACQU5REAAAAOnbm5OQ2NDMlv/apuqNnU2KLyYrlTPZqen9bg4GCCEu49VVVVyk/PVWBhVVNjk5sePz44JuuPqCArT2VlZQlICABIFMojAAAAHDrt7e1aCC6oqKJYbs/m996pqK3UXHBB7e3tCUi3NzmdTh07dkwlqYW6cfW6Fmbm4x47OzWjjg9uqDS1UCdOnGCzbADYZyiPAAAAcOhMTU1pObSq4vLiLY0vLi/WSnhFU1NT25xsbzt58qSO1zerKDlPH7z2riaGxtY9gS0ajWq0f1i/fP19laYU6qFjJ7llDQD2IU5bAwAAwKETDAYVtVF5vN4tjfckeRSJRhUMBmWtPTQraYwxOn/+vIwxcnXeVNd7bbp1vUNltRU6Ul4kT9Ldn2dgNaCx/mEN9w7JGTKqTCvWQ8dO6vHHH9/lTwAA2ArKIwAAABw6TqdTRlI0GtnS+EgkIocxcjgch6Y4+oTT6dRTTz2lgoIC3bhxQ5MztzXVOabej7oUtXd/nk6HU+meVJUlF6iwqEDHjx9XQ0PDLicHAGwV5REAAAAOneTkZLmcbi3MLSgzJ2vT4xfnFuVyuOTz+RKQbu8zxqi5uVlNTU0aHh5We3u7JicnFQwGJUler1eFhYVqampSUVHRoSvYAOCgoTwCAADAoVNVVaXOvk4Ndg+qrLp80+MHuweU7klTVVVVAtLtH8YYlZWV3Ts97TDdwgcAhwkbZgMAAODQqa6uVlZKlpZmFjU3PbupscFAUGODo8rwpKuxsTFBCfcniiMAOJgojwAAAHCgzczMaGRkRENDQ5qYmFAgEJDL5VJ9fb2ykzJ1/RfXFQqG4prLWqvrv/hQqc4UlZeVKz09PcHpAQDYfdy2BgAAgAMnGAyqq6vr7l48U9MKRayisnI6jFK9HtXW1qimpkblQ+XqHu/RL169qjNfflTe5Ninr4XDYX149ZdanJxXRXa5HnvssR38RAAA7B7KIwAAABwot27d0ttXrmh60a+ZlaAiDo9S0tLlcDoVDAQ0OD6jsbmPdbO9U4X5uSrNKtHo7Jhe+/ErKq4oUUVtpdKzfrWiaGV5RYPdgxrqGZQ36lZ5Vpmee/Y5ZWZm7uKnBABg51AeAQAAYNctLCyos7NTk5OTCgQCcjqdSklJUU1NjSoqKuRwxLfbwvXr1/X21V9oYHpVaTn5qmusU96Rks+MX1la1FBvl/oGejTvH1d5fpbqy+o0Pjmu+aF5Xel5S+5kj9xut8LhsIKrfqW503TEk6/CvEJduHBBWVmbP6ENAID9ylhrdzvDhlpaWmxra+tuxwAAAMA2m5qa0rVr19Q/0K+l0JJWwiuK2qiMjFxOt9LcqcpJz1FDQ4NOnjwpp9MZc66uri699Mpr6pte0dETD6u0+ui67+1fWVbrldfki66quapE586dU1dXl2513ZI/4FdEUTnkkMflVnVVtZqamlRYWLjdPwIAAHaNMeaatbZlo+tYeQQAAIBd0d/fr1deeUW3V6a0Gl1RUXmx6srr5U3yKmqjmpueU39Xv6Zvz2hqfkpjY2N6+umn5fF4vjBXOBzW1atXNTCzoprjpzcsjiQpyZeih5+4qHdf+5l6h8d0dGpKjz/+uM6ePavl5WWFQiG5XC75fD653e5E/AgAANgXKI8AAACw40ZGRvTzl36u8eVxHaksUuNDX5I36bObVWfnZquqrkq3x2/r2tVrujV4S/q59Nxzz31hBVJfX5/uLCzLm5al8pr6uHN4k5JVd+yUun/5jtrb29XU1CSn08kpagAAfEp8N48DAAAA2yQUCumll1/SxMqEyurL9dDZh75QHH1a/pF8PfH0E1rSsnqGevThhx9+4Zq2tjbNLIdUXlO36Tz5xaUKOzwav31H4+Pjmx4PAMBBR3kEAACAHdXd3a25lXmlZKWo6aGmuMakpKao5fEWzQRm1dHRoWg0eu+1lZUVjU9MajUiFZaUbzqPw+FQUUWV5lZDGhgY2PR4AAAOOsojAAAA7Kj29nYtBhdUVV8tY0zc4/IK8+RLT9bMwoz6+/vvPe/3+xWKWiX5UuR0bW1XhtS0DIUiVn6/f0vjAQA4yCiPAAAAsGNmZmY0OTUp67IqKiva9PjK2kothhbV3d1977l7q5A2UUR9gTGyn54LAADcQ3kEAACAHbO8vKxQNKyM7Aw5HJv/VTQzJ0uhaFgrKyv3nvN6vXIao6DfL2vtlnIFVlflchh5vbH3XgIA4LCiPAIAAMCOiUQisorK4XBufPF9OJwOWVlFIpF7z6WmpiorI02OaFDTk1vb8HpsqE9pXqcKCwu3NB4AgIOM8ggAAAA7xuPxyGEcCvgDWxof8AfkMEYej+fec8YYNTQ0KMfn0VBv16bnnL0zpeDSgvIy01RZWbmlXAAAHGSURwAAANgxOTk58nl8mp+e1/Li0qbHjw6MyufyKT8//zPP19fXKzvFq+mJEc3emYp7vmg0qp6268pJ8ai+vl5O59ZWRAEAcJBRHgEAAGDHeL1e1dbUKtWTqv7ugU2NDQaCGhkYUZo7TY2NjZ95zefz6dixZpVkJun61Tc0Pzu94XzRaFQ3W68qOH9HxTlpam5u3lQeAAAOC8ojAAAA7KjGxkalu9M02D2oxfnFuMd1fNShZEeSysvKlZGR8YXXz5w5o+aj1SrwGX3wxkvq7bihgH/1C9dZazU1PqoP3nxZi5PDqspL07PPPiufz/dAnwsAgIPKlcjJjTF/V9J/J8lKuiHp9yQdkfR9STmSrkn6HWttMJE5AAAAsHfk5+ertrpW/i6/rr56VY9deExpGWnrjum43qGRnhEVpxbp9OnT973G4XDo0qVL8nrf1I32Tt3pa1Nf+8fKKy5TWkamHA6nggG/JkYGpeCqsn1uFRVl69lnn1VeXl4iPioAAAeC2epxphtObEyxpCuSGq21q8aYH0j6qaTnJP3IWvt9Y8y/k/SRtfbb683V0tJiW1tbE5ITAAAAOy8cDuvFF19U70iv5iMLqjhaoYraCqWkpty7JhqNamJ0Qn2dfVqYWtCRlEJdvHBRNTU1G84/MjKitrY29Q8MaG4lpGAkKmslh8Mo1etSYU6mGhoaVF9fr6SkpER+VAAA9ixjzDVrbctG1yV05dHa/MnGmJAkn6RxSU9J+utrr39P0j+RtG55BAAAgIPF5XLphRde0Guvvabu3m5Ndk+qt61HmbmZ8iZ5FY1azc/OywajSnenqyyzVBcvXFR5eXlc85eUlKikpERLS0vq7+/X6uqqIpGIvF6vcnNzVVpaKmNMgj8lAAAHQ8LKI2vtqDHmX0oakrQq6SXdvU1tzlobXrtsRFLx/cYbY74l6VuSVFZWlqiYAAAA2CUul0uXLl3Sydsn1dbWpu6ebq2urCqyHJFklOPIVl5RnpqamnT06FF5PJ5Nv0dqaqqOHTu2/eEBADhEElYeGWOyJP26pEpJc5L+VNIz8Y631n5H0neku7etJSIjAAAAdl9+fr7y8/N19uxZTU9PKxAIyOl0KiUlRbm5ubsdDwCAQy+Rt61dlNRvrZ2SJGPMjySdk5RpjHGtrT4qkTSawAwAAADYJ5KSklRcfN9F6QAAYBc5Ejj3kKRHjTE+c/eG8guS2iW9Luk31675pqS/SGAGAAAAAAAAPICElUfW2vck/VDSLyXdWHuv70j6I0l/aIzpkZQj6buJygAAAAAAAIAHk9DT1qy1/1jSP/7c032SHknk+wIAAAAAAGB7JPK2NQAAAAAAAOxzlEcAAAAAAACIifIIAAAAAAAAMVEeAQAAAAAAICbKIwAAAAAAAMREeQQAAAAAAICYKI8AAAAAAAAQE+URAAAAAAAAYqI8AgAAAAAAQEyURwAAAAAAAIiJ8ggAAAAAAAAxUR4BAAAAAAAgJsojAAAAAAAAxER5BAAAAAAAgJgojwAAAAAAABAT5REAAAAAAABiojwCAAAAAABATJRHAAAAAAAAiInyCAAAAAAAADFRHgEAAAAAACAmyiMAAAAAAADERHkEAAAAAACAmCiPAAAAAAAAEBPlEQAAAAAAAGKiPAIAAAAAAEBMlEcAAAAAAACIifIIAAAAAAAAMVEeAQAAAAAAICbKIwAAAAAAAMREeQQAAAAAAICYKI8AAAAAAAAQE+URAAAAAAAAYqI8AgAAAAAAQEyURwAAAAAAAIiJ8ggAAAAAAAAxuXY7AAAAAIAHF41GNTAwoMHBQfn9fkWjUXk8HuXk5Ki+vl4+n2+3IwIA9inKIwAAAGAf8/v9amtrU0dHh5anbssuzkqhsGSjktOlXl+Krn3wgSqrq9Xc3KzCwsLdjgwA2GcojwAAAIB9amZmRpcvX9biyJA0d0cZbpfqSouVmZoqh8PIHwxqYHxSAz3t6pkcU++tTj36+Jd04sSJ3Y4OANhHKI8AAACAfWhmZkZ/8ed/rsBgr/Kc0sMPHVNRbs4XrqspLtLyql83BwZ1Y7BH70YiCofDOn369C6kBgDsR5RHAAAAwD4TCAT0s5/9TIGhPpWnePXUQ8fldDpjXp+SnKQzDXXKzUjXGx+3q/UXDmVmZqq6unoHUwMA9itOWwMAAAD2mc7OTi2MDinXEdX5DYqjT6suOqIzdTWy48NqbW2VtTbBSQEABwHlEQAAALCPWGvV3t4uzU7rVG2NXHEWR59oLC+Vz0Y0NzmhsbGxBKUEABwklEcAAADAPjI8PKyF25NKdViV5uduerzD4VB9Wans3LTa2toSkBAAcNBQHgEAAAD7yMjIiOzinGpLimWM2dIc9WUlsgtzGhkZ2eZ0AICDiPIIAAAA2Ef8fr8UDistOXnLc/iSvHJYq1AwqHA4vI3pAAAHEeURAAAAsI9YayVr5XBsbdXRJxwOI1mraDS6TckAAAcV5REAAACwj3g8Hsnp0moguOU5wpGIwlErh8t1dz4AANZBeQQAAADsI3l5eVJKqvrGJrY8R+/YuIwv9e5cAABsgPIIAAAA2Edqamrkzc7V1PKK7swvbGmO9oEhmawcNTY2bnM6AMBBRHkEAAAA7CMul0t1dXVSRrY+7u3f9PixO9OaXgkoKSdP1dXVCUgIADhoKI8AAACAfaa5uVmu3AL1Tc/pek9f3OPmlpb06ocfyxQUq6mpSU6nM4EpAQAHBeURAAAAsM+kp6frqYsXZUoq1No3pF+0dSoYCq07ZmhySn959QMFsgtU3tikU6dO7VBaAMB+59rtAAAAAAA2r6qqSue/cklvvv6a2sZH1Pnqm6otLlJ9WYkyUnxyGKNAKKy+8Ql1DA5rPhSRKShWRUOTLl68KIeDvyMDAOJDeQQAAADsU0ePHlV6erpaW1s1OjiozvkZdb53TTYckqxkHA7JlyqTVaDUvHw1Nzfr+PHjFEcAgE2hPAIAAAD2scLCQr3wwguanZ1Ve3u7BgcH5ff7FYlE5PV6lZNz91S18vJySiMAwJZQHgEAAAAHQFZWls6dO6dz587tdhQAwAHDnx4AAAAAAAAQE+URAAAAAAAAYqI8AgAAAAAAQEyURwAAAAAAAIiJ8ggAAAAAAAAxUR4BAAAAAAAgJsojAAAAAAAAxER5BAAAAAAAgJgojwAAAAAAABAT5REAAAAAAABiojwCAAAAAABATJRHAAAAAAAAiInyCAAAAAAAADFRHgEAAAAAACAmyiMAAAAAAADERHkEAAAAAACAmCiPAAAAAAAAEBPlEQAAAAAAAGKiPAIAAAAAAEBMlEcAAAAAAACIifIIAAAAAAAAMVEeAQAAAAAAICbKIwAAAAAAAMREeQQAAAAAAICYKI8AAAAAAAAQE+URAAAAAAAAYqI8AgAAAAAAQEyURwAAAAAAAIiJ8ggAAAAAAAAxUR4BAAAAAAAgJsojAAAAAAAAxER5BAAAAAAAgJgojwAAAAAAABAT5REAAAAAAABiojwCAAAAAABATJRHAAAAAAAAiInyCAAAAAAAADFRHgEAAAAAACAmyiMAAAAAAADERHkEAAAAAACAmCiPAAAAAAAAEBPlEQAAAAAAAGKiPAIAAAAAAEBMlEcAAAAAAACIifIIAAAAAAAAMVEeAQAAAAAAICbKIwAAAAAAAMREeQQAAAAAAICYKI8AAAAAAAAQE+URAAAAAAAAYqI8AgAAAAAAQEyURwAAAAAAAIiJ8ggAAAAAAAAxJbQ8MsZkGmN+aIzpNMZ0GGPOGmOyjTEvG2O61/7NSmQGAAAAAAAAbF2iVx79saSfWWvrJZ2Q1CHpf5b0qrW2VtKra48BAAAAAACwByWsPDLGZEh6QtJ3JclaG7TWzkn6dUnfW7vse5K+lqgMAAAAAAAAeDCJXHlUKWlK0r83xnxojPl/jDEpkgqsteNr10xIKrjfYGPMt4wxrcaY1qmpqQTGBAAAAAAAQCyJLI9ckk5J+ra19iFJy/rcLWrWWivJ3m+wtfY71toWa21LXl5eAmMCAAAAAAAglkSWRyOSRqy17609/qHulkmTxpgjkrT27+0EZgAAAAAAAMADSFh5ZK2dkDRsjKlbe+qCpHZJfynpm2vPfVPSXyQqAwAAAAAAAB6MK8Hz/4GkPzHGeCT1Sfo93S2sfmCM+ZuSBiX9tQRnAAAAAAAAwBYltDyy1l6X1HKfly4k8n0BAAAAAACwPRK55xEAAAAAAAD2OcojAAAAAAAAxER5BAAAAAAAgJgojwAAAAAAABAT5REAAAAAAABiojwCAAAAAABATJRHAAAAAAAAiInyCAAAAAAAADFRHgEAAAAAACAmyiMAAAAAAADE5NroAmPMSUlfklQkaVXSTUmvWmvnE5wNAAAAAAAAuyzmyiNjzO8YY1ol/W+SsiQNSlqQdFHSG8aY7xpjSnYmJgAAAAAAAHbDeiuPsiU9aa1dvt+LxpgWSQ2SRhIRDAAAAAAAALsvZnlkrf3j9QZaa1u3Pw4AAAAAAAD2ki1tmG2M+V+2OwgAAAAAAAD2nq2etvb725oCAAAAAAAAe1LM29aMMTOxXpKUlpg4AAAAAAAA2EvW2zB7WdIZSZOfe95I6k9YIgAAAAAAAOwZ69229p8klVprI5/7Ckv6wQ7lAwAAAAAAwC5a77S1f7DOa/9TYuIAAAAAAABgL1nvtrV7jDFflfS4JCvpirX2xwlNBQAAAAAAgD1hw9PWjDH/RtLfkdQtqUfS3zbG/OtEBwMAAAAAAMDui2fl0UVJjdZaK0nGmP9X0s2EpgIAAAAAAMCesOHKI909Wa3kU4+PSOpNTBwAAAAAAADsJfGsPEqS1GGMeXft8RlJ7xtjfiRJ1trfSFQ4AAAAAAAA7K54yqN/mvAUAAAAAAAA2JM2LI+sta8aY3Iltaw91WqtvZPYWAAAAAAAANgL4jlt7euSfinpdyT9rqRWY8xfSXQwAAAAAAAA7L54blv7XyU9bK2dlCRjTIGklyT9WSKDAQAAAAAAYPfFc9qa45PiaM3tOMcBAAAAAABgn4tn5dFLxpifSPova49/S9LPExcJAAAAAAAAe0U85dHfk/RXJT2+9vh7kn6YsEQAAAAAAADYM2KWR8aYl6y1l6y1VtIP1r4AAAAAAABwiKy3d1HejqUAAAAAAADAnrTebWsZxpjfiPWitfZHCcgDAACwJdFoVEtLSwoGg3I6nUpKSlJycvJuxwIAANj31i2PJL0gydznNSuJ8ggAAOy6hYUFtbe3q7OzU6v+VUUVlZGRwzhUdKRIjY2NqqyslNPp3O2oAAAA+9J65dGgtfZv7FgSAACATfD7/XrrrbfU19en1ciq/JFVeX0eeb1ehSMRrSyvanFoQUOjg8pIzdSZM2d09OjR3Y4NAACw76xXHt1vxREAAMCuW1pa0k9+8hON3RlTwK6qtLJUtQ21ysnNvndNKBTSYN+Quju6NTo7oldfW9TS0pJOnTq1i8kBAAD2n/XKo9/daLAxxqydxgYAALAjAoGALl++rNE7I/KkuXXh4rNKSU35wnVut1s1ddWqqatWd2ePPnzvQ737/rvyer1qamraheQAAAD703qnrf0bY8wfGGPKPv2kMcZjjHnKGPM9Sd9MbDwAAIDP+uCDDzQ2NSp3qktPPfvUfYujz6utr9HD51o0H5zTlXeuaGFhYQeSAgAAHAzrlUfPSIpI+i/GmDFjTLsxpk9St6TflvSvrLX/3w5kBAAAkCQFg0HdunVLy+FlnfvyY/J6PXGPraypVGlViVZCK+ro6EhgSgAAgIMl5m1r1lq/pH8r6d8aY9ySciWtWmvndiocAADAp3V1dWk5uKy8wlxlZGVsevzRhlqN9I2os7NTp0+flsu13h38AAAAkNZfeXSPtTZkrR2nOAIAALvp1q1bWg2vqLa+Zkvjc/JylJ6drsWVBQ0ODm5zOgAAgIMprvIIAABgL1hYWFAkGlH+kYItz1FQVKBQNKzFxcVtTAYAAHBwUR4BAIB9IxQKySoqj8e95TncbresrILB4DYmAwAAOLg2LI/WTlzL2okwAAAA67m7R5FRKBTa8hzhcFhGRm731gsoAACAwySelUcFkj4wxvzAGPOMMcYkOhQAAMD9pKamymVcmp6a2fIc01PTcjqcSklJ2cZkAAAAB9eG5ZG19h9KqpX0XUn/raRuY8w/M8ZUJzgbAADAZ9TW1irJlayezp4tjV+YW9DUxB2leFJUUVGxveEAAAAOqHhPW7OSJta+wpKyJP3QGPMvEpgNAADgM+rq6pTsTtb48LiWl5Y3Pb67s1vJzmQdPXpUHo8nAQkBAAAOnnj2PPo7xphrkv6FpHckHbPW/veSTkv6eoLzAQAA3OPz+VRdVa0kZ7Leffs9RSKRuMeOj06o71a/kpzJamxsTGBKAACAgyWelUfZkn7DWvu0tfZPrbUhSbLWRiW9kNB0AAAAn3PmzBnlZeRrfmpRb796Ja5T08aGx/TOa+8o1ZWuUw+dUk5Ozg4kBQAAOBjiKY8uS7q3K6UxJt0Yc0aSrLUdiQoGAAAOpmg0+kDjU1NT9eyzzyo/LV9zE/P66Z9d1s3rbVpdWf3MddZajY9O6O1Xr+jKa1eV6khTc0OzHnnkkQd6fwAAgMPG3N3OaJ0LjPlQ0qm1fY9kjHFIarXWntqBfJKklpYW29raulNvBwAAttHi4qLa29vV09OjlZUVRaNRud1uFRYWqrGxUeXl5drKYa4LCwt6+eWXNTE1IX94VUEbVHZelrxer6LRiBbmF+VfCijZ5VOyO1mnT53W6dOnE/AJAQAA9idjzDVrbctG17nimct+qmGy1kaNMfGMAwAAh5jf79dbb72l/r4+KeSXDfqlaETGGAUlDS/MaHigX2mZWTp79qwqKys3NX96erq+/vWva2xsTG1tberr71NoNqSgliQZuY1X+bmFamhoUH19vZKTkxPyOQEAAA66eEqgPmPM35b07bXH/4OkvsRFAgAA+93S0pJefPFFzd+ekAn5VV1eqsajtcrLyZbD4dDqql/d/QNq7+rW4sSwXvr5vM49/iU1Nzdv+r2KiopUVFSklZUVzc3NKRAIyOl0KikpSXl5eVta1QQAAIBfiac8+n1J/1rSP5RkJb0q6VuJDAUAAPavYDCoy5cva35yTDmpyXr6yxeV4vN95prk5CQdb6zXsYY6fdzeqfeuf6x3rryt5ORkVVdXb+l9fT6ffJ97HwAAADy4Dcsja+1tSb+1A1kAAMABcOPGDc1Mjisz2aPnL3xZXq835rXGGJ1oapAxRu99dEPvvPOOKioq5HQ6dzAxAAAA1rNheWSMSZL0NyU1SUr65Hlr7d9IYC4AALAPRaNRdXR0SIEVnTv35LrF0acdb6xXz8CgphcX1NfXp9ra2gQnBQAAQLwccVzzHyUVSnpa0puSSiQtJjIUAADYnwYGBrS8MKeM1BQVFRZsamxjbY0UXFVbW1uC0gEAAGAr4imPaqy1/0jSsrX2e5Kel3QmsbEAAMB+NDo6KoWCqquu2vTYmspyOaIRTU5OKBQKJSAdAAAAtiKe8uiT397mjDHNkjIk5ScuEgAA2K8CgYAUjSo1ZfMbV7tcLiUleSVr784DAACAPSGe09a+Y4zJ0t3T1v5SUqqkf5TQVAAAYF9yOBySubv30VZEo1HJuTYPAAAA9oR1yyNjjEPSgrV2VtJbkja/Bh0AABwaycnJksOpmbn5TY9dWVmVPxCU8abFvdE2AAAAEm/dP+tZa6OS/v4OZQEAAPtcVVWV5E7Srd4+RSKRTY3t7OmV3B5VVFTI6XQmKCEAAAA2K5414a8YY/6eMabUGJP9yVfCkwEAgH2noKBAufn5CkSsegeG4h4XiUTU0dMreZLV1NSUwIQAAADYrHj2PPpv1v79W596zopb2AAAwH00NTXpzckJXb32S+VmZyk7K3Pd6621euPqu1oJRpRVnKvi4uIdSgoAAIB4bLjyyFpbeZ8viiMAAHBfdXV1qqo9qpDTq798+RX1Dw3LWnvfa5eWl/XSm2+rb3RC7oxsXbhwYYfTAgAAYCMbrjwyxvzu/Z631v6H7Y8DAAD2O2OMnnrqKVlr1d/TpVfeeVepSV411NYoLydbTqdTK6ur6u4f0NDYuOROkjcrT88884xycnJ2Oz4AAAA+J57b1h7+1PdJki5I+qUkyiMAAHBfTqdTX/nKV3TzyBHdvHlTC7Mz+qD9lvTJJtrGSG6PHOk5qq6u0enTp5WReEySEAAAIABJREFUkbG7oQEAAHBfG5ZH1to/+PRjY0ympO8nLBEAADgQjDE6duyYmpubNTIyou7ubq2srCgSicjr9aqwsFB1dXVKTk7e7agAAABYRzwrjz5vWVLldgcBAAAHkzFGpaWlKi0t3e0oAAAA2IJ49jz6se6eribd3WC7UdIPEhkKAAAAAAAAe0M8K4/+5ae+D0satNaOJCgPAAAAAAAA9pB4yqMhSePWWr8kGWOSjTEV1tqBhCYDAAAAAADArnPEcc2fSop+6nFk7TkAAAAAAAAccPGURy5rbfCTB2vfexIXCQAAAAAAAHtFPOXRlDHmq588MMb8uqQ7iYsEAAAAAACAvSKePY9+X9KfGGP+z7XHI5J+N3GRAAAAAAAAsFdsWB5Za3slPWqMSV17vJTwVAAAAAAAANgTNrxtzRjzz4wxmdbaJWvtkjEmyxjzf+xEOAAAAAAAAOyuePY8etZaO/fJA2vtrKTnEhcJAAAAAAAAe0U85ZHTGOP95IExJlmSd53rAQAAAAAAcEDEs2H2n0h61Rjz79ce/56k/5C4SAAAAAAAANgr4tkw+58bYz6SdHHtqf/dWvvzxMYCAAAAAADAXhDPyiNZa38m6WeSZIx53Bjzf1lr/1ZCkwEAAAAAAGDXxVUeGWMekvTbkv6apH5JP0pkKAAAAAAAAOwNMcsjY8xR3S2MflvSHUn/VZKx1p7foWwAAAAAAADYZeutPOqU9LakF6y1PZJkjPm7O5IKAAAAAAAAe4Jjndd+Q9K4pNeNMf+3MeaCJLMzsQAAAAAAALAXxCyPrLV/bq39LUn1kl6X9D9KyjfGfNsYc2mnAgIAAAAAAGD3rLfySJJkrV221v5na+2vSSqR9KGkP0p4MgAAAAAAAOy6DcujT7PWzlprv2OtvZCoQAAAAAAAANg71tswGwAA4FCLRqMKBAIKhUJyuVxKSkqSw7Gpv70BAADse5RHAAAAnzM3N6f29nbdunVL/oBfVlZGRh63R7W1tWpsbFRubu5uxwQAANgRlEcAAABrVlZW9Oabb2pgcECrkVWthlfk8rrkcrkUCUcUWgpq/uNZtbW3qehIkc6fP6/09PTdjg0AAJBQlEcAAACSFhYW9OKLL2pidlx+61dZZZlq6quVlZP1q2vmF9TT2avB3gEtDy9p7s/m9NxzzykvL28XkwMAACQWN+0DAIBDz+/366c//anGZkblzfTo+a8/p4fPtXymOJKk9Ix0nTrzkJ7/zeeVUZCuiYUJ/fSnP9XCwsIuJQcAAEg8yiMAAHDovffeexqfHpM3w6snLz2ppOSkda/3eDx6/MLjyirI0NTilK5cubJDSQEAAHYe5REAADjU/H6/urq7tBxe1tknHpXb7Y5rnMPh0NknzyqogAaHBjU/P5/gpAAAALuD8ggAABxqXV1dWgkuq6AoX2kZaZsa6/F6VFZZptXIqtrb2xOUEAAAYHdRHgEAgEOtu7tbq+EVVddVb2l8TX21VsMr6u7u3uZkAAAAewPlEQAAONSWl5cVthHl5OVsaXxWTpaiimp1dVXRaHSb0wEAAOw+yiMAAHCohcNhWVk5Xc4tz+F0OWVlFQ6HtzEZAADA3kB5BAAADjWPxyOHjIKB4JbGR6NRRUJhGZm4N9sGAADYTyiPAADAoZaZmSm3w6PxkfEtjR8fGZfL4VZmZqaMMducDgAAYPdRHgEAgEOtoaFByS6fem71bml8T2ePfK4UNTQ0bHMyAACAvYHyCAAAHGqVlZXKTM3U8tzyplcfzc3O6fb4lHwen+rq6hKUEAAAYHdRHgEAgEPN4XCoublZaZ40vff2+5qfm49r3Mryit5+5YrS3OlqqG+Q1+tNcFIAAIDdQXkEAAAOvRMnTqi28qiSbLJev/yGRoZGZa2Nef3k+G29+pNX5Qy6VF5UrkcffXQH0wIAAOwsV6LfwBjjlNQqadRa+4IxplLS9yXlSLom6XestVs73gQAAGAbOBwOXbx4UeZVo+6+Lr3/xvvypnhVfbRKBcWFcrvdCodDujN5Rz2dPVqeX1GqO1UVpZW6dOmSXK6E/0oFAACwa8x6f1Xbljcw5g8ltUhKXyuPfiDpR9ba7xtj/p2kj6y1315vjpaWFtva2prQnAAAANZa3bhxQ21tbZqem9ZqZEWhaEjWWhlj5DIu+Vw+ZaRmqqGhQQ899JCcTuduxwYAANgSY8w1a23LRtcl9M9kxpgSSc9L+qeS/tDcPb/2KUl/fe2S70n6J5LWLY8AAAB2gjFGx48f17FjxzQ8PKzOzk4tLCwoGAzK7XYrNTVVdXV1qqiokMPB3f8AAOBwSPQa638l6e9LSlt7nCNpzlobXns8Iqn4fgONMd+S9C1JKisrS3BMAACAXzHGqKysjN9BAAAAlMANs40xL0i6ba29tpXx1trvWGtbrLUteXl525wOAAAAAAAA8UjkyqNzkr5qjHlOUpKkdEl/LCnTGONaW31UImk0gRkAAAAAAADwABK28sha+w+stSXW2gpJvyXpNWvtNyS9Luk31y77pqS/SFQGAAAAAAAAPJjd2Onxj3R38+we3d0D6bu7kAEAAAAAAABxSPSG2ZIka+0bkt5Y+75P0iM78b4AAAAAAAB4MJwxCwAAAAAAgJgojwAAAAAAABAT5REAAAAAAABiojwCAAAAAABATJRHAAAAAAAAiInyCAAAAAAAADFRHgEAAAAAACAmyiMAAAAAAADERHkEAAAAAACAmCiPAAAAAAAAEBPlEQAAAAAAAGKiPAIAAAAAAEBMlEcAAAAAAACIifIIAAAAAAAAMVEeAQAAAAAAICbKIwAAAAAAAMREeQQAAAAAAICYKI8AAAAAAAAQE+URAAAAAAAAYqI8AgAAAAAAQEyURwAAAAAAAIiJ8ggAAAAAAAAxUR4BAAAAAAAgJsojAAAAAAAAxER5BAAAAAAAgJgojwAAAAAAABAT5REAAAAAAABiojwCAAAAAABATJRHAAAAAAAAiInyCAAAAAAAADFRHgEAAAAAACCm/7+9uw+y8rrvBP893Q3dNAhJgBAYBOgF0ULICEuWZEt27LzIUuQ468xk4lSSmUwlm8rWzOykNlMz2dRUZmqrtiq7qZ2a2kkmmdRMypmtlCdeJ9kolkZ25NiS7EiOACEQDUKAQLQQr0JG0EDT3Wf/6IuMUD/iRe6+vHw+VRSX5znPfX73Fqef2997znmERwAAAAA0Eh4BAAAA0Eh4BAAAAEAj4REAAAAAjYRHAAAAADQSHgEAAADQSHgEAAAAQCPhEQAAAACNhEcAAAAANBIeAQAAANBIeAQAAABAI+ERAAAAAI2ERwAAAAA0Eh4BAAAA0Eh4BAAAAEAj4REAAAAAjYRHAAAAADQSHgEAAADQSHgEAAAAQCPhEQAAAACNhEcAAAAANBIeAQAAANBIeAQAAABAI+ERAAAAAI2ERwAAAAA0Eh4BAAAA0Eh4BAAAAEAj4REAAAAAjYRHAAAAADTqancBAEy8kZGR7Ny5MwcPHszw8HC6u7uzePHizJ49u92lAQAAFznhEcBlbHh4OOvWrUt/f3+ODw4mqa09Jauffz7Xz5uXVatWZdGiRe0sEwAAuIgJjwAuU0NDQ3niiSeyZ/fupI5m1qxZuXHJkkyZMiWHDx/O1m3bsveN3Xliz558/P77s2LFinaXDAAAXISERwCXoVprnnzyyezZvTvTe6flhz/9qcyfP/9dbe67796sX78hq9esyd9+5zvp7e3NTTfd1KaKAQCAi5UFswEuQ6+//noGdr2Wnu6p+YnPPvKe4ChJurq68pGPrMp9996bjI7mu9/9bmqt4zwbAABwJRMeAVyG+vv7k1pzx4oVmTlz5vu2veOOFbnqqhl5+/D3smvXrkmqEAAAuFQIjwAuM8PDw9m5Y0c6Sklf37Kzti+l5La+vqTWbNu2bRIqBAAALiXCI4DLzLFjx1JrTW9vb6ZNm3ZOx8yZMztJMjg4OJGlAQAAlyDhEcBlpqNj7Ef76OjoOR9zqu2pYwEAAE7xWwLAZWbatGmZMmVKBgcHc+jQoXM65vXXdydJrrrqqoksDQAAuAQJjwAuMx0dHVl6661JKenv33TW9sPDw3l5y5akdKSvr28SKgQAAC4lwiOAy9Dy5cuT0pFNmzdnYGCgsV2tNU8/80yGTp7M3Ouvz5w5cyaxSgAA4FIgPAK4DM2aNSsr77wzo0me+NrX88IL63L8+PF3tdm3b1+e+NrXsnXb9nRNmZoHHnigPcUCAAAXta52FwDAxLjnnnsyMjKSlzZsyPNr1mTN2rWZN+/6TJkyJW+/fSRvvvlmUjoytbsnn3noIaOOAACAcQmPAC5TpZR8/OMfz6JFi7Jx48bs3LEju9/Y29qZ9Eyfkb6+vixfvjwzZsxob7EAAMBFS3gEcJlbuHBhFi5cmCNHjuTQoUMZGRlJd3d35s6dm87OznaXBwAAXOSERwBXiBkzZhhhBAAAnDcLZgMAAADQSHgEAAAAQCPhEQAAAACNhEcAAAAANBIeAQAAANBIeAQAAABAI+ERAAAAAI2ERwAAAAA06mp3AQBcPE6ePJlXXnklu3fvzokTJ9LZ2ZmZM2fm1ltvzZw5c9pdHgAA0AbCIwBy4sSJrF69Oi+//HIGjw9laHg0o6kpSTo7OvLi+vWZP29e7rrrrtxwww3tLhcAAJhEwiOAK9zRo0fz2GOPZc/+gzl2cjTXzb0+y2+6JdOnT8/wyEj27d2T7a9sybbXXs+ePXvzwAP3Z8WKFe0uGwAAmCTCI4Ar2NDQUB5//PHs3ncw3dNn5lOf/FSuuXbWu9rcsGhxVq66K5teWp+X1q/LM9/+drq7u7N06dI2VQ0AAEwmC2YDXMHWrVuXN/YdSPf0mfnRhx55T3B0SldXV+648yP5yEfvzZETI3nmmWcyNDQ0ydUCAADtIDwCuEKNjIxk8+bNOT48mns+dn+6u7vPesyy227P7Ouuz+DxobzyyiuTUCUAANBuwiOAK9Srr76aw0cHM/PqWblu7vXnfNytfctzfHgk/f39E1gdAABwsRAeAVyh9uzZk5MjNTfefPN5Hbdw0eLU0pmDb75p6hoAAFwBhEcAV6ihoaHUWtPTM+28juvo6MjUqd2ptQqPAADgCiA8ArhCdXV1pSQZHh4+72NHhodTUtLV5aadAABwuRMeAVyhrr766nR2lLyxe+C8jjt4YH+Gh4cybVrPOS2yDQAAXNqERwBXqKVLl6Znald273otg0ePnvNxWzZvSndXR5YtW5ZSygRWCAAAXAyERwBXqN7e3tx8002Z0tWRF9Y8f07HHDywP6/t2J7uzo4sX758gisEAAAuBsIjgCvYqlWrctW0qRnYuT3PP/e3GR0dbWx7YP++fOvJr2daV8ny5bdl5syZk1gpAADQLlY6BbiCzZo1Kw/+2I/la1/7el7dujlv7H49S5f15cabl6anpye11uzb80a2bN6U13ftTO/Uztx84+I88MAD7S4dAACYJKXW2u4azuruu++uq1evbncZAJetPXv25Jvf/GbefOt7OTE8mqHh0ZSOjtTR0XR2lHR3daRnalduX748H/vYx9LRYeAqAABc6kopa2qtd5+tnZFHAGTevHn5whe+kF27dmXjxo3ZvXt3hoeHU0pXZsyYkb6+vvT19aW3t7fdpQIAAJNMeARAkqSUkkWLFmXRokVJkpGRkXR0dLijGgAAXOGERwCMq7Ozs90lAAAAFwGLVgAAAADQSHgEAAAAQCPhEQAAAACNhEcAAAAANBIeAQAAANBowsKjUsoNpZRvllL6SykbSyn/vLV9Vinlr0spr7T+vnaiagAAAADgg5nIkUfDSX691ro8yX1J/kkpZXmS30jyjVrr0iTfaP0bAAAAgIvQhIVHtdY3aq1rW4/fTrIpyYIkP5nkj1vN/jjJ/zBRNQAAAADwwUzKmkellCVJViX5bpLra61vtHbtSXJ9wzG/UkpZXUpZvX///skoEwAAAIAzTHh4VEqZkeTPkvxarfXw6ftqrTVJHe+4Wusf1lrvrrXefd111010mQAAAACMY0LDo1LKlIwFR39Sa/3z1ua9pZT5rf3zk+ybyBoAAAAAuHATebe1kuS/JNlUa/13p+16NMk/aj3+R0n+cqJqAAAAAOCD6ZrA574/yS8k2VBKWdfa9ptJfjvJl0spv5RkZ5J/MIE1AAAAAPABTFh4VGv9dpLSsPtHJuq8AAAAAPzgTMrd1gAAAAC4NAmPAAAAAGgkPAIAAACgkfAIAAAAgEbCIwAAAAAaCY8AAAAAaCQ8AgAAAKCR8AgAAACARsIjAAAAABoJjwAAAABoJDwCAAAAoJHwCAAAAIBGwiMAAAAAGgmPAAAAAGgkPAIAAACgkfAIAAAAgEbCIwAAAAAaCY8AAAAAaCQ8AgAAAKCR8AgAAACARsIjAAAAABoJjwAAAABoJDwCAAAAoJHwCAAAAIBGwiMAAAAAGgmPAAAAAGgkPAIAAACgkfAIAAAAgEbCIwAAAAAaCY8AAAAAaCQ8AgAAAKCR8AgAAACARsIjAAAAABoJjwAAAABoJDwCAAAAoJHwCAAAAIBGXe0uALgynThxIq+++moGBwfT0dGRmTNnZvHixens7Gx3aQAAAJxGeARMqrfffjtr1qzJ1q1bMzwy/K59vdN609fXl1WrVmXKlCltqhAAAIDTCY+ASXPgwIE89thjOXb8WGqtWbhwQWbPmZNaa14fGMjBg29m7Qtrs2vXrjzyyCPp6elpd8kAAABXPOERMCmOHDmSxx9/PMeODWbBwgW5/4H7M3PmzO83uPee7NmzJ08/9XT2H9ifJ554Ip/73OfS0WFpNgAAgHbyWxkwKdavX5/BY4OZ/6H5+cxDn3l3cNQyb968fPYnPpvp03uzd++evPrqq22oFAAAgNMJj4AJNzw8nJdffjm11tz3sfvedzRRb29vVt65MrXW9Pf3T2KVAAAAjEd4BEy4gYGBnBg6kblzr8vs2bPP2n7p0qXp7OrK7t27c+zYsUmoEAAAgCbCI2DCnQqAZs2adU7tp0yZkhkzpr/rWAAAANpDeARMuM7OziRj09fO1ejo6LuOBQAAoD3cbQ2YcNdcc01KSgYGBjIyMnLWQOjQoUM5fPjtTJ0yNdOnTx+3zb59+7Jv374MDw9n6tSpWbhw4biLcAMAAPDBCI+AD+zw4cM5cOBARkZGMn369MybN+9di2LPnTs3s2fPzoED+7N9+/YsXbr0fZ9v48b+lFKybNmydHW9+8fUli1bsmHDhhw4cCA19Z3tJSWLFi3KnXfemfnz5/9gXyAAAMAVTHgEXLA33ngjL7zwQl7btSujo2NRTkdJZl51VW677basXLnynVFGK1asyFNPfSt/+51nc+2112bOnDnjPufWrVuzqX9TOkpHli9f/s72WmueeeaZ9G/qT6013T3dWbxkcbq7u3PkyJHs3LEzO3buyK5du/LJT34yfX19k/EWAAAAXPaER5Ng79692bJlS44ePZqenp7ccsstWbBgQUop7S4NLtjmzZvz1NNP58TJkaR05kMfWpgjR4/mtZ2v5rWB3dm7b38GBgby8MMPZ8qUKVm2bFl27dqVbdu35a8e/WqW3748t93W985Us3379qV/Y39eeWVrOkpH7r333nctsL169er0b+pPR0fJffd/fOyObKdNfzt+/HheXPdiXlq/IU899VR6e3uzaNGiSX9fAAAALjfCowk0Ojqav/mbv8m2bdvHJtfUJCV5+eUtWbDgQ3nwwQczderUNlcJ52/37t156umnc2xoJLevWJklS27Mc89+J4cOHshVV12V1Jp1L67Pztdey5QpU/Lwww+nlJIf/uEfTldX19jUs/Ubsv7F9enunppak6GhoZRS0tnRmfvuuy8f/vCH3znf4OBg1q1bl6TmRz/zYBYuXPiemnp6enLvffdm6tSpWbt6bZ577jnhEQAAwA+A8GgCPfvss9m6bXu6uqZk+e235/rrr8/Bgwez8aWXMvD67nzrW9/Kgw8+2O4y4bytXbs2J06OBUd3fHhl/uov/zxHjx7OtN6e3LBoUU4ODWXGjN6sX78hTzzxtdxzzz2ZPXt2Ojs78+lPfzq33357+vv7s3Xr1pwcGrsD2/Te6Vm2bFluu+229yx8vXnz5oyMjmTRksXjBkenW3nnymzq35RDh97M7t2786EPfWjC3gcAAIArgfBoghw7diz9/ZuSmjzy2c/muuuuS5IsXrw4t9xyS/7sK1/Jq6/uyFtvvZVrrrmmzdXCuXvrrbcy8PrrKR1duePDK7P1lZdz5MjbmT1ndh7+8UfeGU1356qP5O3Dh7Pl5S157rnn8sgjj7zzHHPnzs3cuXPzyU9+8p0RR1OnTm2cyrlt27bUWnPb8tvOWl9HR0eW9S3Li2vXZdu2bcIjAACAD6jj7E24EAMDAxkZHc2ChQvfCY5OmTlzZm6++ebUJDt27GhLfXCh9u/fn5HRmgULb8jUqVOzc8eOlFKzcuXKd03DvHbWrNxx58p0T+vOxo0bx32ujo6O9PT0pLu7+33XABscHExS37UG0vuZNXtWasZCXAAAAD4Y4dEEOXnyZJJkxowZ4+7vnT49STI8PDxpNcEPwsjISJJkypQpSZKTwydTkkyf/t7/61fNmJmOjo4MDQ19oHN2dIz9qBodHT2n9qfanToOAACAC+c3qwlyzTXXpCTZtWvXuL/w7nrttZRWO7iU9Pb2pqMkBw8eSJJcc821qUle27nzPW23vrIlJ4eGMmfOnA90zrF+UrJr165zaj+wayCllFx99dUf6LwAAAAIjybM/Pnzc801V2fw6NE88/TT74y8GB4eznPPPZeDBw5k2rSe3HjjjW2uFM7PggULMr23N28dejP79u1N323LU0dLXtqwPi+uW5ejR4/mrUOH8vRT38qWLS9ndGQ0DzzwwAc6Z19fX0op2bRxU2qt79v2+PHj2b5te0pKli1b9oHOCwAAgAWzJ0wpJT/0Qz+Ur371sbyyZUu2b9+ea6+9Nt/73vdycmgoHR0ln/jEJ9LZ2dnuUuG8dHZ2pq+vL8+vWZvvPPNUHnzokaxcdVdefGFN1q5ZnRfWrE6S7NmzJ3v37M1dq+5MX1/fBzrnTTfdlGeffTZvHnwzf/fdv8u99907brvh4eF848lvpI6MZsmSG99z1zYAAADOXznbt/gXg7vvvruuXr263WVckH379uX555/PwMDr72y7/vq5+ehHP5oFCxa0sTK4cCdPnsyjjz6aPXv3p3RNzdJb+9I9dWq2bduanTtezd49b6SrqzNLFt+Qv/dTP5UlS5Z84HMODAzk8ccfz8joSBYuWpgVd6x4pw+NjIxk+/bt2fDihhx681BmTJ+Rz3/+841rjgEAAJCUUtbUWu8+azvh0eQ4cuRIBgcH09PTYzQEl4Xjx4/nySefzMDA6xkeqRmpNTVjc2G7Okum9XTn05/+9A8kODpl586defLJJ3Ny+GRqrenu6U53d3cGBwczfHJ4bJ2jmVfn4Ycftp4YAADAWQiPgEmxd+/e9Pf3Z//+/RkZGUlvb2+WLl2apUuXvnNHth+kwcHBbN68OZs2bcqRI0fe2T5nzpzcfvvtueWWW9LVZUYuAADA2QiPgMtarTVHjx7N8PBwuru7M23atHaXBAAAcEk51/DI1/PAJamUYk0jAACASdDR7gIAAAAAuHgJjwAAAABoJDwCAAAAoJHwCAAAAIBGwiMAAAAAGgmPAAAAAGgkPAIAAACgkfAIAAAAgEbCIwAAAAAaCY8AAAAAaCQ8AgAAAKCR8AgAAACARsIjAAAAABoJjwAAAABoJDwCAAAAoJHwCAAAAIBGwiMAAAAAGgmPAAAAAGgkPAIAAACgkfAIAAAAgEbCIwAAAAAaCY8AAAAAaFRqre2u4axKKfuT7Gx3HVewOUkOtLsIuETpP3Dh9B+4cPoPXDj9hyvJ4lrrdWdrdEmER7RXKWV1rfXudtcBlyL9By6c/gMXTv+BC6f/wHuZtgYAAABAI+ERAAAAAI2ER5yLP2x3AXAJ03/gwuk/cOH0H7hw+g+cwZpHAAAAADQy8ggAAACARsIjAAAAABoJj0iSlFIeKqW8XErZWkr5jXH2/2IpZX8pZV3rzy+3o064GJVS/qiUsq+U8lLD/lJK+b9b/Wt9KeUjk10jXKzOof98qpTyvdOuP7812TXCxaqUckMp5ZullP5SysZSyj8fp41rEIzjHPuPaxC0dLW7ANqvlNKZ5PeS/FiSgSTPl1IerbX2n9H0T2ut/3TSC4SL3xeT/G6S/9qw/+EkS1t/7k3y+62/gbP3nyR5ptb62ckpBy4pw0l+vda6tpRyVZI1pZS/PuMznGsQjO9c+k/iGgRJjDxizD1JttZat9dah5L8tyQ/2eaa4JJRa306yZvv0+Qnk/zXOua5JNeUUuZPTnVwcTuH/gM0qLW+UWtd23r8dpJNSRac0cw1CMZxjv0HaBEekYz9kNx12r8HMv4Pzr/XGu78lVLKDZNTGlwWzrWPAeP7WCnlxVLKfy+l3N7uYuBiVEpZkmRVku+escs1CM7iffpP4hoESYRHnLu/SrKk1vrhJH+d5I/bXA8AV4a1SRbXWlcm+Q9J/r821wMXnVLKjCR/luTXaq2H210PXErO0n9cg6BFeESSvJ7k9JFEC1vb3lFrPVhrPdH6539Octck1QaXg7P2MWB8tdbDtdYjrcePJ5lSSpnT5rLgolFKmZKxX3z/pNb65+M0cQ2CBmfrP65B8H3CI5Lk+SRLSyk3llKmJvlCkkdPb3DG3PjPZWxOMHBuHk3yD1t3vLkvyfdqrW+0uyi4FJRS5pVSSuvxPRn77HKwvVXBxaHVN/5Lkk211n/X0Mw1CMZxLv3HNQi+z93WSK11uJTyT5N8LUlnkj+qtW4spfxvSVbXWh9N8j+XUj6XsbsSvJnkF9tdpWepAAAGQ0lEQVRWMFxkSilfSvKpJHNKKQNJ/k2SKUlSa/2DJI8n+fEkW5MMJvnH7akULj7n0H/+fpL/qZQynORYki/UWmubyoWLzf1JfiHJhlLKuta230yyKHENgrM4l/7jGgQtxf99AAAAAJqYtgYAAABAI+ERAAAAAI2ERwAAAAA0Eh4BAAAA0Eh4BAAAAHAJKaX8USllXynlpXNou6iU8s1SygullPWllB8/3/MJjwCAS0YpZV4p5b+VUraVUtaUUh4vpdzaplp+8wKP+0op5abW4x2llGfO2L/uzA+CpZR/X0p5vZTSccb2h0spq0sp/a0PhP/X+5z3jlLKFy+kZgDgovPFJA+dY9t/neTLtdZVSb6Q5D+e78mERwDAJaGUUpL8RZJv1VpvrrXeleR/TXJ9m0o67/ColHJ7ks5a6/bTNl9VSrmhtf+2cY7pSPL5JLuS/NBp21ck+d0kP19rXZ7k7iRbm85da92QZGEpZdH51g0AXFxqrU8nefP0baWUm0spT7S+YHumlNJ3qnmSma3HVyfZfb7nEx4BAJeKTyc5WWv9g1Mbaq0v1lqfKWN+p5TyUillQynlZ5KklPKpUspTpZS/LKVsL6X8dinl50opf9dqd3Or3RdLKX/QGsWzpZTy2db2Xyyl/O6p85VSvtp6zt9OMq01SuhPWvt+vvW860op/6mU0jnOa/i5JH95xrYvJ/mZ1uOfTfKlM/Z/KsnGJL/f2n/Kv0zyv9daN7fei5Fa6++3avnp1nvxYinl6dOO+auMfeMIAFx+/jDJP2t9wfYv8v0RRv82yc+XUgaSPJ7kn53vEwuPAIBLxYokaxr2/VSSO5OsTPKjSX6nlDK/tW9lkl9NcluSX0hya631niT/Oe/+8LQkyT1JHknyB6WUnqZCaq2/keRYrfXOWuvPtUYM/UyS+2utdyYZyVhQdKb7x3kNf9aqP0l+ImMBz+lOBUp/keSRUsqU1vb3ez9+K8lnaq0rk3zutO2rk3yi6XUBAJemUsqMJB9P8v+WUtYl+U9JTn0W+tkkX6y1Lkzy40n+nzOnwp9N1w+yWACANnkgyZdqrSNJ9pZSnkry0SSHkzxfa30jSUop25J8vXXMhoyNZjrly7XW0SSvlFK2J+nLufuRJHcleX5sdl2mJdk3Trv5Sfafse1gkkOllC8k2ZRk8NSOUsrUjH3I+19qrW+XUr6b5DNJvnqWer6T5IullC8n+fPTtu9L8qFzfVEAwCWjI8lbrS+xzvRLaa2PVGt9tvUF2ZyM/1ml8ckBAC4FGzMW0JyvE6c9Hj3t36N59xdp9YzjapLhvPvzUtNopJLkj1sjke6stS6rtf7bcdoda3iOP03ye3nvlLXPJLkmyYZSyo6MhWSnpq41vh+11l/N2OKYNyRZU0qZfVr9xxpeAwBwiaq1Hk7yainlp5OxtSJLKStbu1/L2Bddp9ZX7Ml7v8x6X8IjAOBS8TdJukspv3JqQynlw6WUTyR5JsnPlFI6SynXJflkkr87z+f/6VJKR2sdpJuSvJxkR5I7W9tvyNi0tlNOnjaF7BtJ/n4pZW6rrlmllMXjnGNTklvG2f4XSf7PJF87Y/vPJvnlWuuSWuuSJDcm+bFSSm+S30nym6fuNteq8Vdbj2+utX631vpbGftweEPr+W5NctZb+gIAF7dSypeSPJtkWSlloJTySxmbMv9LpZQXM/Yl00+2mv96kv+xtf1LSX6x1nrml2bvy7Q1AOCSUGutpZTPJ/n3pZR/leR4xsKdX0vy7SQfS/JixkYM/cta657T7jJyLl7LWOA0M8mv1lqPl1K+k+TVJP0ZC37Wntb+D5OsL6Wsba179K+TfL21hsDJJP8kyc4zzvFYxhbAfvKM1/Z2kv8jSVrT3tIKiB7K2HpNp9odLaV8O8lP1Fr/tJTya0m+1Gpb8/3pbL9TSlmasRFR32i9L8nYNL3HzuM9AQAuQrXWn23Y9dA4bfsztu7iBSvnGTYBAFx2SilfTPLVWutXJvg805J8M2MLa49M5LnGOXd3kqeSPFBrHZ7McwMAlzbT1gAAJkmt9ViSf5NkQRtOvyjJbwiOAIDzZeQRAAAAAI2MPAIAAACgkfAIAAAAgEbCIwAAAAAaCY8AAAAAaCQ8AgAAAKDR/w+tnEtFZCtpCgAAAABJRU5ErkJggg==\n", + "text/plain": [ + "<matplotlib.figure.Figure at 0x7ff847b415f8>" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "def add_results(df, cmap):\n", + " # create data\n", + " x = df['MACs'].tolist()\n", + " y = df['Top1'].tolist()\n", + " z = df['NNZ'].tolist()\n", + " z = [n/30000 for n in z]\n", + " plt.scatter(x, y, s=z, c=x, cmap=cmap, alpha=0.4, edgecolors=\"black\", linewidth=2)\n", + "\n", + "# Change color with c and alpha. I map the color to the X axis value.\n", + "plt.figure(figsize=(20,10))\n", + "add_results(df, cmap=\"Blues\")\n", + "add_results(df2, cmap=\"Reds\")\n", + "add_results(df3, cmap=\"Greens\")\n", + "\n", + "# Add titles (main and on axis)\n", + "plt.xlabel(\"Compute (MACs)\")\n", + "plt.ylabel(\"Accuracy (Top1)\")\n", + "plt.title(\"Network Space\")\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.2" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}