| @@ -10,219 +10,9 @@ from .base import BaseNAS | |||||
| from ..space import BaseSpace | from ..space import BaseSpace | ||||
| from ..utils import AverageMeterGroup, replace_layer_choice, replace_input_choice, get_module_order, sort_replaced_module | from ..utils import AverageMeterGroup, replace_layer_choice, replace_input_choice, get_module_order, sort_replaced_module | ||||
| from nni.nas.pytorch.fixed import apply_fixed_architecture | from nni.nas.pytorch.fixed import apply_fixed_architecture | ||||
| from tqdm import tqdm | |||||
| _logger = logging.getLogger(__name__) | _logger = logging.getLogger(__name__) | ||||
| def _get_mask(sampled, total): | |||||
| multihot = [i == sampled or (isinstance(sampled, list) and i in sampled) for i in range(total)] | |||||
| return torch.tensor(multihot, dtype=torch.bool) # pylint: disable=not-callable | |||||
| class PathSamplingLayerChoice(nn.Module): | |||||
| """ | |||||
| Mixed module, in which fprop is decided by exactly one or multiple (sampled) module. | |||||
| If multiple module is selected, the result will be sumed and returned. | |||||
| Attributes | |||||
| ---------- | |||||
| sampled : int or list of int | |||||
| Sampled module indices. | |||||
| mask : tensor | |||||
| A multi-hot bool 1D-tensor representing the sampled mask. | |||||
| """ | |||||
| def __init__(self, layer_choice): | |||||
| super(PathSamplingLayerChoice, self).__init__() | |||||
| self.op_names = [] | |||||
| for name, module in layer_choice.named_children(): | |||||
| self.add_module(name, module) | |||||
| self.op_names.append(name) | |||||
| assert self.op_names, 'There has to be at least one op to choose from.' | |||||
| self.sampled = None # sampled can be either a list of indices or an index | |||||
| def forward(self, *args, **kwargs): | |||||
| assert self.sampled is not None, 'At least one path needs to be sampled before fprop.' | |||||
| if isinstance(self.sampled, list): | |||||
| return sum([getattr(self, self.op_names[i])(*args, **kwargs) for i in self.sampled]) # pylint: disable=not-an-iterable | |||||
| else: | |||||
| return getattr(self, self.op_names[self.sampled])(*args, **kwargs) # pylint: disable=invalid-sequence-index | |||||
| def __len__(self): | |||||
| return len(self.op_names) | |||||
| @property | |||||
| def mask(self): | |||||
| return _get_mask(self.sampled, len(self)) | |||||
| class PathSamplingInputChoice(nn.Module): | |||||
| """ | |||||
| Mixed input. Take a list of tensor as input, select some of them and return the sum. | |||||
| Attributes | |||||
| ---------- | |||||
| sampled : int or list of int | |||||
| Sampled module indices. | |||||
| mask : tensor | |||||
| A multi-hot bool 1D-tensor representing the sampled mask. | |||||
| """ | |||||
| def __init__(self, input_choice): | |||||
| super(PathSamplingInputChoice, self).__init__() | |||||
| self.n_candidates = input_choice.n_candidates | |||||
| self.n_chosen = input_choice.n_chosen | |||||
| self.sampled = None | |||||
| def forward(self, input_tensors): | |||||
| if isinstance(self.sampled, list): | |||||
| return sum([input_tensors[t] for t in self.sampled]) # pylint: disable=not-an-iterable | |||||
| else: | |||||
| return input_tensors[self.sampled] | |||||
| def __len__(self): | |||||
| return self.n_candidates | |||||
| @property | |||||
| def mask(self): | |||||
| return _get_mask(self.sampled, len(self)) | |||||
| class StackedLSTMCell(nn.Module): | |||||
| def __init__(self, layers, size, bias): | |||||
| super().__init__() | |||||
| self.lstm_num_layers = layers | |||||
| self.lstm_modules = nn.ModuleList([nn.LSTMCell(size, size, bias=bias) | |||||
| for _ in range(self.lstm_num_layers)]) | |||||
| def forward(self, inputs, hidden): | |||||
| prev_h, prev_c = hidden | |||||
| next_h, next_c = [], [] | |||||
| for i, m in enumerate(self.lstm_modules): | |||||
| curr_h, curr_c = m(inputs, (prev_h[i], prev_c[i])) | |||||
| next_c.append(curr_c) | |||||
| next_h.append(curr_h) | |||||
| # current implementation only supports batch size equals 1, | |||||
| # but the algorithm does not necessarily have this limitation | |||||
| inputs = curr_h[-1].view(1, -1) | |||||
| return next_h, next_c | |||||
| class ReinforceField: | |||||
| """ | |||||
| A field with ``name``, with ``total`` choices. ``choose_one`` is true if one and only one is meant to be | |||||
| selected. Otherwise, any number of choices can be chosen. | |||||
| """ | |||||
| def __init__(self, name, total, choose_one): | |||||
| self.name = name | |||||
| self.total = total | |||||
| self.choose_one = choose_one | |||||
| def __repr__(self): | |||||
| return f'ReinforceField(name={self.name}, total={self.total}, choose_one={self.choose_one})' | |||||
| class ReinforceController(nn.Module): | |||||
| """ | |||||
| A controller that mutates the graph with RL. | |||||
| Parameters | |||||
| ---------- | |||||
| fields : list of ReinforceField | |||||
| List of fields to choose. | |||||
| lstm_size : int | |||||
| Controller LSTM hidden units. | |||||
| lstm_num_layers : int | |||||
| Number of layers for stacked LSTM. | |||||
| tanh_constant : float | |||||
| Logits will be equal to ``tanh_constant * tanh(logits)``. Don't use ``tanh`` if this value is ``None``. | |||||
| skip_target : float | |||||
| Target probability that skipconnect will appear. | |||||
| temperature : float | |||||
| Temperature constant that divides the logits. | |||||
| entropy_reduction : str | |||||
| Can be one of ``sum`` and ``mean``. How the entropy of multi-input-choice is reduced. | |||||
| """ | |||||
| def __init__(self, fields, lstm_size=64, lstm_num_layers=1, tanh_constant=1.5, | |||||
| skip_target=0.4, temperature=None, entropy_reduction='sum'): | |||||
| super(ReinforceController, self).__init__() | |||||
| self.fields = fields | |||||
| self.lstm_size = lstm_size | |||||
| self.lstm_num_layers = lstm_num_layers | |||||
| self.tanh_constant = tanh_constant | |||||
| self.temperature = temperature | |||||
| self.skip_target = skip_target | |||||
| self.lstm = StackedLSTMCell(self.lstm_num_layers, self.lstm_size, False) | |||||
| self.attn_anchor = nn.Linear(self.lstm_size, self.lstm_size, bias=False) | |||||
| self.attn_query = nn.Linear(self.lstm_size, self.lstm_size, bias=False) | |||||
| self.v_attn = nn.Linear(self.lstm_size, 1, bias=False) | |||||
| self.g_emb = nn.Parameter(torch.randn(1, self.lstm_size) * 0.1) | |||||
| self.skip_targets = nn.Parameter(torch.tensor([1.0 - self.skip_target, self.skip_target]), # pylint: disable=not-callable | |||||
| requires_grad=False) | |||||
| assert entropy_reduction in ['sum', 'mean'], 'Entropy reduction must be one of sum and mean.' | |||||
| self.entropy_reduction = torch.sum if entropy_reduction == 'sum' else torch.mean | |||||
| self.cross_entropy_loss = nn.CrossEntropyLoss(reduction='none') | |||||
| self.soft = nn.ModuleDict({ | |||||
| field.name: nn.Linear(self.lstm_size, field.total, bias=False) for field in fields | |||||
| }) | |||||
| self.embedding = nn.ModuleDict({ | |||||
| field.name: nn.Embedding(field.total, self.lstm_size) for field in fields | |||||
| }) | |||||
| def resample(self): | |||||
| self._initialize() | |||||
| result = dict() | |||||
| for field in self.fields: | |||||
| result[field.name] = self._sample_single(field) | |||||
| return result | |||||
| def _initialize(self): | |||||
| self._inputs = self.g_emb.data | |||||
| self._c = [torch.zeros((1, self.lstm_size), | |||||
| dtype=self._inputs.dtype, | |||||
| device=self._inputs.device) for _ in range(self.lstm_num_layers)] | |||||
| self._h = [torch.zeros((1, self.lstm_size), | |||||
| dtype=self._inputs.dtype, | |||||
| device=self._inputs.device) for _ in range(self.lstm_num_layers)] | |||||
| self.sample_log_prob = 0 | |||||
| self.sample_entropy = 0 | |||||
| self.sample_skip_penalty = 0 | |||||
| def _lstm_next_step(self): | |||||
| self._h, self._c = self.lstm(self._inputs, (self._h, self._c)) | |||||
| def _sample_single(self, field): | |||||
| self._lstm_next_step() | |||||
| logit = self.soft[field.name](self._h[-1]) | |||||
| if self.temperature is not None: | |||||
| logit /= self.temperature | |||||
| if self.tanh_constant is not None: | |||||
| logit = self.tanh_constant * torch.tanh(logit) | |||||
| if field.choose_one: | |||||
| sampled = torch.multinomial(F.softmax(logit, dim=-1), 1).view(-1) | |||||
| log_prob = self.cross_entropy_loss(logit, sampled) | |||||
| self._inputs = self.embedding[field.name](sampled) | |||||
| else: | |||||
| logit = logit.view(-1, 1) | |||||
| logit = torch.cat([-logit, logit], 1) # pylint: disable=invalid-unary-operand-type | |||||
| sampled = torch.multinomial(F.softmax(logit, dim=-1), 1).view(-1) | |||||
| skip_prob = torch.sigmoid(logit) | |||||
| kl = torch.sum(skip_prob * torch.log(skip_prob / self.skip_targets)) | |||||
| self.sample_skip_penalty += kl | |||||
| log_prob = self.cross_entropy_loss(logit, sampled) | |||||
| sampled = sampled.nonzero().view(-1) | |||||
| if sampled.sum().item(): | |||||
| self._inputs = (torch.sum(self.embedding[field.name](sampled.view(-1)), 0) / (1. + torch.sum(sampled))).unsqueeze(0) | |||||
| else: | |||||
| self._inputs = torch.zeros(1, self.lstm_size, device=self.embedding[field.name].weight.device) | |||||
| sampled = sampled.detach().numpy().tolist() | |||||
| self.sample_log_prob += self.entropy_reduction(log_prob) | |||||
| entropy = (log_prob * torch.exp(-log_prob)).detach() # pylint: disable=invalid-unary-operand-type | |||||
| self.sample_entropy += self.entropy_reduction(entropy) | |||||
| if len(sampled) == 1: | |||||
| sampled = sampled[0] | |||||
| return sampled | |||||
| from .rl import PathSamplingLayerChoice,PathSamplingInputChoice,ReinforceField,ReinforceController | |||||
| class Enas(BaseNAS): | class Enas(BaseNAS): | ||||
| """ | """ | ||||
| @@ -272,7 +62,7 @@ class Enas(BaseNAS): | |||||
| def __init__(self, device='cuda', workers=4,log_frequency=None, | def __init__(self, device='cuda', workers=4,log_frequency=None, | ||||
| grad_clip=5., entropy_weight=0.0001, skip_weight=0.8, baseline_decay=0.999, | grad_clip=5., entropy_weight=0.0001, skip_weight=0.8, baseline_decay=0.999, | ||||
| ctrl_lr=0.00035, ctrl_steps_aggregate=20, ctrl_kwargs=None,*args,**kwargs): | |||||
| ctrl_lr=0.00035, ctrl_steps_aggregate=20, ctrl_kwargs=None,n_warmup=100,model_lr=5e-3,model_wd=5e-4,*args,**kwargs): | |||||
| super().__init__(device) | super().__init__(device) | ||||
| self.device=device | self.device=device | ||||
| self.num_epochs = kwargs.get("num_epochs", 5) | self.num_epochs = kwargs.get("num_epochs", 5) | ||||
| @@ -287,14 +77,13 @@ class Enas(BaseNAS): | |||||
| self.workers = workers | self.workers = workers | ||||
| self.ctrl_kwargs=ctrl_kwargs | self.ctrl_kwargs=ctrl_kwargs | ||||
| self.ctrl_lr=ctrl_lr | self.ctrl_lr=ctrl_lr | ||||
| self.n_warmup=n_warmup | |||||
| self.model_lr = model_lr | |||||
| self.model_wd = model_wd | |||||
| def search(self, space: BaseSpace, dset, estimator): | def search(self, space: BaseSpace, dset, estimator): | ||||
| self.model = space | self.model = space | ||||
| self.dataset = dset#.to(self.device) | self.dataset = dset#.to(self.device) | ||||
| self.estimator = estimator | |||||
| self.model_optim = torch.optim.SGD( | |||||
| self.model.parameters(), lr=0.01, weight_decay=3e-4 | |||||
| ) | |||||
| self.estimator = estimator | |||||
| # replace choice | # replace choice | ||||
| self.nas_modules = [] | self.nas_modules = [] | ||||
| @@ -305,20 +94,44 @@ class Enas(BaseNAS): | |||||
| # to device | # to device | ||||
| self.model = self.model.to(self.device) | self.model = self.model.to(self.device) | ||||
| self.model_optim = torch.optim.Adam( | |||||
| self.model.parameters(), lr=self.model_lr, weight_decay=self.model_wd | |||||
| ) | |||||
| # fields | # fields | ||||
| self.nas_fields = [ReinforceField(name, len(module), | self.nas_fields = [ReinforceField(name, len(module), | ||||
| isinstance(module, PathSamplingLayerChoice) or module.n_chosen == 1) | isinstance(module, PathSamplingLayerChoice) or module.n_chosen == 1) | ||||
| for name, module in self.nas_modules] | for name, module in self.nas_modules] | ||||
| self.controller = ReinforceController(self.nas_fields, **(self.ctrl_kwargs or {})) | self.controller = ReinforceController(self.nas_fields, **(self.ctrl_kwargs or {})) | ||||
| self.ctrl_optim = torch.optim.Adam(self.controller.parameters(), lr=self.ctrl_lr) | self.ctrl_optim = torch.optim.Adam(self.controller.parameters(), lr=self.ctrl_lr) | ||||
| # warm up supernet | |||||
| with tqdm(range(self.n_warmup)) as bar: | |||||
| for i in bar: | |||||
| acc,l1=self._train_model(i) | |||||
| with torch.no_grad(): | |||||
| val_acc,val_loss=self._infer('val') | |||||
| bar.set_postfix(loss=l1,acc=acc,val_acc=val_acc,val_loss=val_loss) | |||||
| # train | # train | ||||
| for i in range(self.num_epochs): | |||||
| self._train_model(i) | |||||
| self._train_controller(i) | |||||
| with tqdm(range(self.num_epochs)) as bar: | |||||
| for i in bar: | |||||
| try: | |||||
| l1=self._train_model(i) | |||||
| l2=self._train_controller(i) | |||||
| except Exception as e: | |||||
| print(e) | |||||
| nm=self.nas_modules | |||||
| for i in range(len(nm)): | |||||
| print(nm[i][1].sampled) | |||||
| import pdb | |||||
| pdb.set_trace() | |||||
| bar.set_postfix(loss_model=l1,reward_controller=l2) | |||||
| selection=self.export() | selection=self.export() | ||||
| print(selection) | |||||
| return space.export(selection,self.device) | return space.export(selection,self.device) | ||||
| def _train_model(self, epoch): | def _train_model(self, epoch): | ||||
| self.model.train() | self.model.train() | ||||
| self.controller.eval() | self.controller.eval() | ||||
| @@ -330,15 +143,19 @@ class Enas(BaseNAS): | |||||
| nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip) | nn.utils.clip_grad_norm_(self.model.parameters(), self.grad_clip) | ||||
| self.model_optim.step() | self.model_optim.step() | ||||
| return metric,loss.item() | |||||
| def _train_controller(self, epoch): | def _train_controller(self, epoch): | ||||
| self.model.eval() | self.model.eval() | ||||
| self.controller.train() | self.controller.train() | ||||
| self.ctrl_optim.zero_grad() | self.ctrl_optim.zero_grad() | ||||
| rewards=[] | |||||
| for ctrl_step in range(self.ctrl_steps_aggregate): | for ctrl_step in range(self.ctrl_steps_aggregate): | ||||
| self._resample() | self._resample() | ||||
| with torch.no_grad(): | with torch.no_grad(): | ||||
| metric,loss=self._infer() | |||||
| reward =-metric # todo : now metric is loss | |||||
| metric,loss=self._infer(mask='val') | |||||
| reward =metric | |||||
| rewards.append(reward) | |||||
| if self.entropy_weight: | if self.entropy_weight: | ||||
| reward += self.entropy_weight * self.controller.sample_entropy.item() | reward += self.entropy_weight * self.controller.sample_entropy.item() | ||||
| self.baseline = self.baseline * self.baseline_decay + reward * (1 - self.baseline_decay) | self.baseline = self.baseline * self.baseline_decay + reward * (1 - self.baseline_decay) | ||||
| @@ -357,6 +174,7 @@ class Enas(BaseNAS): | |||||
| if self.log_frequency is not None and ctrl_step % self.log_frequency == 0: | if self.log_frequency is not None and ctrl_step % self.log_frequency == 0: | ||||
| _logger.info('RL Epoch [%d/%d] Step [%d/%d] %s', epoch + 1, self.num_epochs, | _logger.info('RL Epoch [%d/%d] Step [%d/%d] %s', epoch + 1, self.num_epochs, | ||||
| ctrl_step + 1, self.ctrl_steps_aggregate) | ctrl_step + 1, self.ctrl_steps_aggregate) | ||||
| return sum(rewards)/len(rewards) | |||||
| def _resample(self): | def _resample(self): | ||||
| result = self.controller.resample() | result = self.controller.resample() | ||||
| @@ -368,6 +186,6 @@ class Enas(BaseNAS): | |||||
| with torch.no_grad(): | with torch.no_grad(): | ||||
| return self.controller.resample() | return self.controller.resample() | ||||
| def _infer(self): | |||||
| metric, loss = self.estimator.infer(self.model, self.dataset) | |||||
| def _infer(self,mask='train'): | |||||
| metric, loss = self.estimator.infer(self.model, self.dataset,mask=mask) | |||||
| return metric, loss | return metric, loss | ||||
| @@ -0,0 +1,383 @@ | |||||
| # codes in this file are reproduced from https://github.com/microsoft/nni with some changes. | |||||
| import copy | |||||
| import logging | |||||
| import torch | |||||
| import torch.nn as nn | |||||
| import torch.nn.functional as F | |||||
| from .base import BaseNAS | |||||
| from ..space import BaseSpace | |||||
| from ..utils import AverageMeterGroup, replace_layer_choice, replace_input_choice, get_module_order, sort_replaced_module | |||||
| from nni.nas.pytorch.fixed import apply_fixed_architecture | |||||
| from tqdm import tqdm | |||||
| _logger = logging.getLogger(__name__) | |||||
| def _get_mask(sampled, total): | |||||
| multihot = [i == sampled or (isinstance(sampled, list) and i in sampled) for i in range(total)] | |||||
| return torch.tensor(multihot, dtype=torch.bool) # pylint: disable=not-callable | |||||
| class PathSamplingLayerChoice(nn.Module): | |||||
| """ | |||||
| Mixed module, in which fprop is decided by exactly one or multiple (sampled) module. | |||||
| If multiple module is selected, the result will be sumed and returned. | |||||
| Attributes | |||||
| ---------- | |||||
| sampled : int or list of int | |||||
| Sampled module indices. | |||||
| mask : tensor | |||||
| A multi-hot bool 1D-tensor representing the sampled mask. | |||||
| """ | |||||
| def __init__(self, layer_choice): | |||||
| super(PathSamplingLayerChoice, self).__init__() | |||||
| self.op_names = [] | |||||
| for name, module in layer_choice.named_children(): | |||||
| self.add_module(name, module) | |||||
| self.op_names.append(name) | |||||
| assert self.op_names, 'There has to be at least one op to choose from.' | |||||
| self.sampled = None # sampled can be either a list of indices or an index | |||||
| def forward(self, *args, **kwargs): | |||||
| assert self.sampled is not None, 'At least one path needs to be sampled before fprop.' | |||||
| if isinstance(self.sampled, list): | |||||
| return sum([getattr(self, self.op_names[i])(*args, **kwargs) for i in self.sampled]) # pylint: disable=not-an-iterable | |||||
| else: | |||||
| return getattr(self, self.op_names[self.sampled])(*args, **kwargs) # pylint: disable=invalid-sequence-index | |||||
| def __len__(self): | |||||
| return len(self.op_names) | |||||
| @property | |||||
| def mask(self): | |||||
| return _get_mask(self.sampled, len(self)) | |||||
| class PathSamplingInputChoice(nn.Module): | |||||
| """ | |||||
| Mixed input. Take a list of tensor as input, select some of them and return the sum. | |||||
| Attributes | |||||
| ---------- | |||||
| sampled : int or list of int | |||||
| Sampled module indices. | |||||
| mask : tensor | |||||
| A multi-hot bool 1D-tensor representing the sampled mask. | |||||
| """ | |||||
| def __init__(self, input_choice): | |||||
| super(PathSamplingInputChoice, self).__init__() | |||||
| self.n_candidates = input_choice.n_candidates | |||||
| self.n_chosen = input_choice.n_chosen | |||||
| self.sampled = None | |||||
| def forward(self, input_tensors): | |||||
| if isinstance(self.sampled, list): | |||||
| return sum([input_tensors[t] for t in self.sampled]) # pylint: disable=not-an-iterable | |||||
| else: | |||||
| return input_tensors[self.sampled] | |||||
| def __len__(self): | |||||
| return self.n_candidates | |||||
| @property | |||||
| def mask(self): | |||||
| return _get_mask(self.sampled, len(self)) | |||||
| def __repr__(self): | |||||
| return f'PathSamplingInputChoice(n_candidates={self.n_candidates}, chosen={self.sampled})' | |||||
| class StackedLSTMCell(nn.Module): | |||||
| def __init__(self, layers, size, bias): | |||||
| super().__init__() | |||||
| self.lstm_num_layers = layers | |||||
| self.lstm_modules = nn.ModuleList([nn.LSTMCell(size, size, bias=bias) | |||||
| for _ in range(self.lstm_num_layers)]) | |||||
| def forward(self, inputs, hidden): | |||||
| prev_h, prev_c = hidden | |||||
| next_h, next_c = [], [] | |||||
| for i, m in enumerate(self.lstm_modules): | |||||
| curr_h, curr_c = m(inputs, (prev_h[i], prev_c[i])) | |||||
| next_c.append(curr_c) | |||||
| next_h.append(curr_h) | |||||
| # current implementation only supports batch size equals 1, | |||||
| # but the algorithm does not necessarily have this limitation | |||||
| inputs = curr_h[-1].view(1, -1) | |||||
| return next_h, next_c | |||||
| class ReinforceField: | |||||
| """ | |||||
| A field with ``name``, with ``total`` choices. ``choose_one`` is true if one and only one is meant to be | |||||
| selected. Otherwise, any number of choices can be chosen. | |||||
| """ | |||||
| def __init__(self, name, total, choose_one): | |||||
| self.name = name | |||||
| self.total = total | |||||
| self.choose_one = choose_one | |||||
| def __repr__(self): | |||||
| return f'ReinforceField(name={self.name}, total={self.total}, choose_one={self.choose_one})' | |||||
| class ReinforceController(nn.Module): | |||||
| """ | |||||
| A controller that mutates the graph with RL. | |||||
| Parameters | |||||
| ---------- | |||||
| fields : list of ReinforceField | |||||
| List of fields to choose. | |||||
| lstm_size : int | |||||
| Controller LSTM hidden units. | |||||
| lstm_num_layers : int | |||||
| Number of layers for stacked LSTM. | |||||
| tanh_constant : float | |||||
| Logits will be equal to ``tanh_constant * tanh(logits)``. Don't use ``tanh`` if this value is ``None``. | |||||
| skip_target : float | |||||
| Target probability that skipconnect will appear. | |||||
| temperature : float | |||||
| Temperature constant that divides the logits. | |||||
| entropy_reduction : str | |||||
| Can be one of ``sum`` and ``mean``. How the entropy of multi-input-choice is reduced. | |||||
| """ | |||||
| def __init__(self, fields, lstm_size=64, lstm_num_layers=1, tanh_constant=1.5, | |||||
| skip_target=0.4, temperature=None, entropy_reduction='sum'): | |||||
| super(ReinforceController, self).__init__() | |||||
| self.fields = fields | |||||
| self.lstm_size = lstm_size | |||||
| self.lstm_num_layers = lstm_num_layers | |||||
| self.tanh_constant = tanh_constant | |||||
| self.temperature = temperature | |||||
| self.skip_target = skip_target | |||||
| self.lstm = StackedLSTMCell(self.lstm_num_layers, self.lstm_size, False) | |||||
| self.attn_anchor = nn.Linear(self.lstm_size, self.lstm_size, bias=False) | |||||
| self.attn_query = nn.Linear(self.lstm_size, self.lstm_size, bias=False) | |||||
| self.v_attn = nn.Linear(self.lstm_size, 1, bias=False) | |||||
| self.g_emb = nn.Parameter(torch.randn(1, self.lstm_size) * 0.1) | |||||
| self.skip_targets = nn.Parameter(torch.tensor([1.0 - self.skip_target, self.skip_target]), # pylint: disable=not-callable | |||||
| requires_grad=False) | |||||
| assert entropy_reduction in ['sum', 'mean'], 'Entropy reduction must be one of sum and mean.' | |||||
| self.entropy_reduction = torch.sum if entropy_reduction == 'sum' else torch.mean | |||||
| self.cross_entropy_loss = nn.CrossEntropyLoss(reduction='none') | |||||
| self.soft = nn.ModuleDict({ | |||||
| field.name: nn.Linear(self.lstm_size, field.total, bias=False) for field in fields | |||||
| }) | |||||
| self.embedding = nn.ModuleDict({ | |||||
| field.name: nn.Embedding(field.total, self.lstm_size) for field in fields | |||||
| }) | |||||
| def resample(self): | |||||
| self._initialize() | |||||
| result = dict() | |||||
| for field in self.fields: | |||||
| result[field.name] = self._sample_single(field) | |||||
| return result | |||||
| def _initialize(self): | |||||
| self._inputs = self.g_emb.data | |||||
| self._c = [torch.zeros((1, self.lstm_size), | |||||
| dtype=self._inputs.dtype, | |||||
| device=self._inputs.device) for _ in range(self.lstm_num_layers)] | |||||
| self._h = [torch.zeros((1, self.lstm_size), | |||||
| dtype=self._inputs.dtype, | |||||
| device=self._inputs.device) for _ in range(self.lstm_num_layers)] | |||||
| self.sample_log_prob = 0 | |||||
| self.sample_entropy = 0 | |||||
| self.sample_skip_penalty = 0 | |||||
| def _lstm_next_step(self): | |||||
| self._h, self._c = self.lstm(self._inputs, (self._h, self._c)) | |||||
| def _sample_single(self, field): | |||||
| self._lstm_next_step() | |||||
| logit = self.soft[field.name](self._h[-1]) | |||||
| if self.temperature is not None: | |||||
| logit /= self.temperature | |||||
| if self.tanh_constant is not None: | |||||
| logit = self.tanh_constant * torch.tanh(logit) | |||||
| if field.choose_one: | |||||
| sampled = torch.multinomial(F.softmax(logit, dim=-1), 1).view(-1) | |||||
| log_prob = self.cross_entropy_loss(logit, sampled) | |||||
| self._inputs = self.embedding[field.name](sampled) | |||||
| else: | |||||
| logit = logit.view(-1, 1) | |||||
| logit = torch.cat([-logit, logit], 1) # pylint: disable=invalid-unary-operand-type | |||||
| sampled = torch.multinomial(F.softmax(logit, dim=-1), 1).view(-1) | |||||
| skip_prob = torch.sigmoid(logit) | |||||
| kl = torch.sum(skip_prob * torch.log(skip_prob / self.skip_targets)) | |||||
| self.sample_skip_penalty += kl | |||||
| log_prob = self.cross_entropy_loss(logit, sampled) | |||||
| sampled = sampled.nonzero().view(-1) | |||||
| if sampled.sum().item(): | |||||
| self._inputs = (torch.sum(self.embedding[field.name](sampled.view(-1)), 0) / (1. + torch.sum(sampled))).unsqueeze(0) | |||||
| else: | |||||
| self._inputs = torch.zeros(1, self.lstm_size, device=self.embedding[field.name].weight.device) | |||||
| sampled = sampled.detach().numpy().tolist() | |||||
| self.sample_log_prob += self.entropy_reduction(log_prob) | |||||
| entropy = (log_prob * torch.exp(-log_prob)).detach() # pylint: disable=invalid-unary-operand-type | |||||
| self.sample_entropy += self.entropy_reduction(entropy) | |||||
| if len(sampled) == 1: | |||||
| sampled = sampled[0] | |||||
| return sampled | |||||
| class RL(BaseNAS): | |||||
| """ | |||||
| ENAS trainer. | |||||
| Parameters | |||||
| ---------- | |||||
| model : nn.Module | |||||
| PyTorch model to be trained. | |||||
| loss : callable | |||||
| Receives logits and ground truth label, return a loss tensor. | |||||
| metrics : callable | |||||
| Receives logits and ground truth label, return a dict of metrics. | |||||
| reward_function : callable | |||||
| Receives logits and ground truth label, return a tensor, which will be feeded to RL controller as reward. | |||||
| optimizer : Optimizer | |||||
| The optimizer used for optimizing the model. | |||||
| num_epochs : int | |||||
| Number of epochs planned for training. | |||||
| dataset : Dataset | |||||
| Dataset for training. Will be split for training weights and architecture weights. | |||||
| batch_size : int | |||||
| Batch size. | |||||
| workers : int | |||||
| Workers for data loading. | |||||
| device : torch.device | |||||
| ``torch.device("cpu")`` or ``torch.device("cuda")``. | |||||
| log_frequency : int | |||||
| Step count per logging. | |||||
| grad_clip : float | |||||
| Gradient clipping. Set to 0 to disable. Default: 5. | |||||
| entropy_weight : float | |||||
| Weight of sample entropy loss. | |||||
| skip_weight : float | |||||
| Weight of skip penalty loss. | |||||
| baseline_decay : float | |||||
| Decay factor of baseline. New baseline will be equal to ``baseline_decay * baseline_old + reward * (1 - baseline_decay)``. | |||||
| ctrl_lr : float | |||||
| Learning rate for RL controller. | |||||
| ctrl_steps_aggregate : int | |||||
| Number of steps that will be aggregated into one mini-batch for RL controller. | |||||
| ctrl_steps : int | |||||
| Number of mini-batches for each epoch of RL controller learning. | |||||
| ctrl_kwargs : dict | |||||
| Optional kwargs that will be passed to :class:`ReinforceController`. | |||||
| """ | |||||
| def __init__(self, device='cuda', workers=4,log_frequency=None, | |||||
| grad_clip=5., entropy_weight=0.0001, skip_weight=0.8, baseline_decay=0.999, | |||||
| ctrl_lr=0.00035, ctrl_steps_aggregate=20, ctrl_kwargs=None,n_warmup=100,model_lr=5e-3,model_wd=5e-4,*args,**kwargs): | |||||
| super().__init__(device) | |||||
| self.device=device | |||||
| self.num_epochs = kwargs.get("num_epochs", 5) | |||||
| self.workers = workers | |||||
| self.log_frequency = log_frequency | |||||
| self.entropy_weight = entropy_weight | |||||
| self.skip_weight = skip_weight | |||||
| self.baseline_decay = baseline_decay | |||||
| self.baseline = 0. | |||||
| self.ctrl_steps_aggregate = ctrl_steps_aggregate | |||||
| self.grad_clip = grad_clip | |||||
| self.workers = workers | |||||
| self.ctrl_kwargs=ctrl_kwargs | |||||
| self.ctrl_lr=ctrl_lr | |||||
| self.n_warmup=n_warmup | |||||
| self.model_lr = model_lr | |||||
| self.model_wd = model_wd | |||||
| self.log=open('log.txt','w') | |||||
| def search(self, space: BaseSpace, dset, estimator): | |||||
| self.model = space | |||||
| self.dataset = dset#.to(self.device) | |||||
| self.estimator = estimator | |||||
| # replace choice | |||||
| self.nas_modules = [] | |||||
| k2o = get_module_order(self.model) | |||||
| replace_layer_choice(self.model, PathSamplingLayerChoice, self.nas_modules) | |||||
| replace_input_choice(self.model, PathSamplingInputChoice, self.nas_modules) | |||||
| self.nas_modules = sort_replaced_module(k2o, self.nas_modules) | |||||
| # to device | |||||
| self.model = self.model.to(self.device) | |||||
| # fields | |||||
| self.nas_fields = [ReinforceField(name, len(module), | |||||
| isinstance(module, PathSamplingLayerChoice) or module.n_chosen == 1) | |||||
| for name, module in self.nas_modules] | |||||
| self.controller = ReinforceController(self.nas_fields, **(self.ctrl_kwargs or {})) | |||||
| self.ctrl_optim = torch.optim.Adam(self.controller.parameters(), lr=self.ctrl_lr) | |||||
| # train | |||||
| with tqdm(range(self.num_epochs)) as bar: | |||||
| for i in bar: | |||||
| l2=self._train_controller(i) | |||||
| # try: | |||||
| # l2=self._train_controller(i) | |||||
| # except Exception as e: | |||||
| # print(e) | |||||
| # nm=self.nas_modules | |||||
| # for i in range(len(nm)): | |||||
| # print(nm[i][1].sampled) | |||||
| # # import pdb | |||||
| # # pdb.set_trace() | |||||
| bar.set_postfix(reward_controller=l2) | |||||
| selection=self.export() | |||||
| arch=space.export(selection,self.device) | |||||
| print(selection,arch) | |||||
| return arch | |||||
| def _train_controller(self, epoch): | |||||
| self.model.eval() | |||||
| self.controller.train() | |||||
| self.ctrl_optim.zero_grad() | |||||
| rewards=[] | |||||
| with tqdm(range(self.ctrl_steps_aggregate)) as bar: | |||||
| for ctrl_step in bar: | |||||
| self._resample() | |||||
| metric,loss=self._infer(mask='val') | |||||
| bar.set_postfix(acc=metric,loss=loss.item()) | |||||
| self.log.write(f'{self.arch}\n{self.selection}\n{metric},{loss}\n') | |||||
| self.log.flush() | |||||
| reward =metric | |||||
| rewards.append(reward) | |||||
| if self.entropy_weight: | |||||
| reward += self.entropy_weight * self.controller.sample_entropy.item() | |||||
| self.baseline = self.baseline * self.baseline_decay + reward * (1 - self.baseline_decay) | |||||
| loss = self.controller.sample_log_prob * (reward - self.baseline) | |||||
| if self.skip_weight: | |||||
| loss += self.skip_weight * self.controller.sample_skip_penalty | |||||
| loss /= self.ctrl_steps_aggregate | |||||
| loss.backward() | |||||
| if (ctrl_step + 1) % self.ctrl_steps_aggregate == 0: | |||||
| if self.grad_clip > 0: | |||||
| nn.utils.clip_grad_norm_(self.controller.parameters(), self.grad_clip) | |||||
| self.ctrl_optim.step() | |||||
| self.ctrl_optim.zero_grad() | |||||
| if self.log_frequency is not None and ctrl_step % self.log_frequency == 0: | |||||
| _logger.info('RL Epoch [%d/%d] Step [%d/%d] %s', epoch + 1, self.num_epochs, | |||||
| ctrl_step + 1, self.ctrl_steps_aggregate) | |||||
| return sum(rewards)/len(rewards) | |||||
| def _resample(self): | |||||
| result = self.controller.resample() | |||||
| self.arch=self.model.export(result,device=self.device) | |||||
| self.selection=result | |||||
| def export(self): | |||||
| self.controller.eval() | |||||
| with torch.no_grad(): | |||||
| return self.controller.resample() | |||||
| def _infer(self,mask='train'): | |||||
| metric, loss = self.estimator.infer(self.arch, self.dataset,mask=mask) | |||||
| return metric, loss | |||||
| @@ -3,7 +3,7 @@ import torch.nn.functional as F | |||||
| from ..space import BaseSpace | from ..space import BaseSpace | ||||
| from .base import BaseEstimator | from .base import BaseEstimator | ||||
| import torch | |||||
| class OneShotEstimator(BaseEstimator): | class OneShotEstimator(BaseEstimator): | ||||
| """ | """ | ||||
| @@ -18,4 +18,28 @@ class OneShotEstimator(BaseEstimator): | |||||
| pred = model(dset)[getattr(dset, f"{mask}_mask")] | pred = model(dset)[getattr(dset, f"{mask}_mask")] | ||||
| y = dset.y[getattr(dset, f'{mask}_mask')] | y = dset.y[getattr(dset, f'{mask}_mask')] | ||||
| loss = F.nll_loss(pred, y) | loss = F.nll_loss(pred, y) | ||||
| return loss, loss | |||||
| acc=sum(pred.max(1)[1]==y).item()/y.size(0) | |||||
| return acc, loss | |||||
| from autogl.module.train import NodeClassificationFullTrainer | |||||
| class TrainEstimator(BaseEstimator): | |||||
| def __init__(self): | |||||
| self.estimator=OneShotEstimator() | |||||
| def infer(self,model: BaseSpace, dataset, mask="train"): | |||||
| # self.trainer.model=model | |||||
| # self.trainer.device=model.device | |||||
| self.trainer=NodeClassificationFullTrainer( | |||||
| model=model, | |||||
| optimizer=torch.optim.Adam, | |||||
| lr=0.01, | |||||
| max_epoch=200, | |||||
| early_stopping_round=200, | |||||
| weight_decay=5e-4, | |||||
| device="auto", | |||||
| init=False, | |||||
| feval=['acc'], | |||||
| loss="nll_loss", | |||||
| lr_scheduler_type=None) | |||||
| self.trainer.train(dataset) | |||||
| with torch.no_grad(): | |||||
| return self.estimator.infer(model,dataset,mask) | |||||
| @@ -43,6 +43,9 @@ class LambdaModule(nn.Module): | |||||
| def forward(self, x): | def forward(self, x): | ||||
| return self.lambd(x) | return self.lambd(x) | ||||
| def __repr__(self): | |||||
| return '{}({})'.format(self.__class__.__name__,self.lambd) | |||||
| class StrModule(nn.Module): | class StrModule(nn.Module): | ||||
| def __init__(self, lambd): | def __init__(self, lambd): | ||||
| super().__init__() | super().__init__() | ||||
| @@ -50,6 +53,9 @@ class StrModule(nn.Module): | |||||
| def forward(self, *args,**kwargs): | def forward(self, *args,**kwargs): | ||||
| return self.str | return self.str | ||||
| def __repr__(self): | |||||
| return '{}({})'.format(self.__class__.__name__,self.str) | |||||
| def act_map(act): | def act_map(act): | ||||
| if act == "linear": | if act == "linear": | ||||
| return lambda x: x | return lambda x: x | ||||
| @@ -108,7 +114,11 @@ def gnn_map(gnn_name, in_dim, out_dim, concat=False, bias=True) -> nn.Module: | |||||
| elif gnn_name == "linear": | elif gnn_name == "linear": | ||||
| return LinearConv(in_dim, out_dim, bias=bias) | return LinearConv(in_dim, out_dim, bias=bias) | ||||
| elif gnn_name == "zero": | elif gnn_name == "zero": | ||||
| return ZeroConv(in_dim, out_dim, bias=bias) | |||||
| # return ZeroConv(in_dim, out_dim, bias=bias) | |||||
| return Identity() | |||||
| class Identity(nn.Module): | |||||
| def forward(self, x, edge_index, edge_weight=None): | |||||
| return x | |||||
| class LinearConv(nn.Module): | class LinearConv(nn.Module): | ||||
| def __init__(self, | def __init__(self, | ||||
| in_channels, | in_channels, | ||||
| @@ -128,6 +138,15 @@ class LinearConv(nn.Module): | |||||
| self.out_channels) | self.out_channels) | ||||
| from torch.autograd import Function | |||||
| class ZeroConvFunc(Function): | |||||
| @staticmethod | |||||
| def forward(ctx,x): | |||||
| return x | |||||
| @staticmethod | |||||
| def backward(ctx, grad_output): | |||||
| return 0 | |||||
| class ZeroConv(nn.Module): | class ZeroConv(nn.Module): | ||||
| def __init__(self, | def __init__(self, | ||||
| in_channels, | in_channels, | ||||
| @@ -138,9 +157,8 @@ class ZeroConv(nn.Module): | |||||
| self.out_channels = out_channels | self.out_channels = out_channels | ||||
| self.out_dim = out_channels | self.out_dim = out_channels | ||||
| def forward(self, x, edge_index, edge_weight=None): | def forward(self, x, edge_index, edge_weight=None): | ||||
| return torch.zeros([x.size(0), self.out_dim]).to(x.device) | |||||
| return ZeroConvFunc.apply(torch.zeros([x.size(0), self.out_dim]).to(x.device)) | |||||
| def __repr__(self): | def __repr__(self): | ||||
| return '{}({}, {})'.format(self.__class__.__name__, self.in_channels, | return '{}({}, {})'.format(self.__class__.__name__, self.in_channels, | ||||
| @@ -193,6 +211,8 @@ class GraphNasNodeClassificationSpace(BaseSpace): | |||||
| setattr(self,f"act",self.setLayerChoice(2*layer,[act_map_nn(a)for a in act_list],key=f"act")) | setattr(self,f"act",self.setLayerChoice(2*layer,[act_map_nn(a)for a in act_list],key=f"act")) | ||||
| setattr(self,f"concat",self.setLayerChoice(2*layer+1,map_nn(["add", "product", "concat"]) ,key=f"concat")) | setattr(self,f"concat",self.setLayerChoice(2*layer+1,map_nn(["add", "product", "concat"]) ,key=f"concat")) | ||||
| self._initialized = True | self._initialized = True | ||||
| self.classifier1 = nn.Linear(self.hidden_dim*self.layer_number, self.output_dim) | |||||
| self.classifier2 = nn.Linear(self.hidden_dim, self.output_dim) | |||||
| def forward(self, data): | def forward(self, data): | ||||
| x, edges = data.x, data.edge_index # x [2708,1433] ,[2, 10556] | x, edges = data.x, data.edge_index # x [2708,1433] ,[2, 10556] | ||||
| @@ -202,10 +222,11 @@ class GraphNasNodeClassificationSpace(BaseSpace): | |||||
| node_in = getattr(self, f"in_{layer}")(prev_nodes_out) | node_in = getattr(self, f"in_{layer}")(prev_nodes_out) | ||||
| node_out= getattr(self, f"op_{layer}")(node_in,edges) | node_out= getattr(self, f"op_{layer}")(node_in,edges) | ||||
| prev_nodes_out.append(node_out) | prev_nodes_out.append(node_out) | ||||
| if self.search_act_con: | |||||
| if not self.search_act_con: | |||||
| x = torch.cat(prev_nodes_out[2:],dim=1) | x = torch.cat(prev_nodes_out[2:],dim=1) | ||||
| x = F.leaky_relu(x) | x = F.leaky_relu(x) | ||||
| x = F.dropout(x, p=self.dropout, training = self.training) | x = F.dropout(x, p=self.dropout, training = self.training) | ||||
| x = self.classifier1(x) | |||||
| else: | else: | ||||
| act=getattr(self, f"act") | act=getattr(self, f"act") | ||||
| con=getattr(self, f"concat")() | con=getattr(self, f"concat")() | ||||
| @@ -222,6 +243,10 @@ class GraphNasNodeClassificationSpace(BaseSpace): | |||||
| x=tmp | x=tmp | ||||
| x = act(x) | x = act(x) | ||||
| x = F.dropout(x, p=self.dropout, training = self.training) | x = F.dropout(x, p=self.dropout, training = self.training) | ||||
| if con=='concat': | |||||
| x=self.classifier1(x) | |||||
| else: | |||||
| x=self.classifier2(x) | |||||
| return F.log_softmax(x, dim=1) | return F.log_softmax(x, dim=1) | ||||
| def export(self, selection, device) -> BaseModel: | def export(self, selection, device) -> BaseModel: | ||||
| @@ -28,9 +28,9 @@ if __name__ == '__main__': | |||||
| feval=['acc'], | feval=['acc'], | ||||
| loss="nll_loss", | loss="nll_loss", | ||||
| lr_scheduler_type=None,), | lr_scheduler_type=None,), | ||||
| nas_algorithms=[Enas(num_epochs=10)], | |||||
| nas_algorithms=[Enas(num_epochs=400,n_warmup=250)], | |||||
| #nas_algorithms=[Darts(num_epochs=200)], | #nas_algorithms=[Darts(num_epochs=200)], | ||||
| nas_spaces=[GraphNasNodeClassificationSpace(hidden_dim=16, ops=[GCNConv, GCNConv],search_act_con=True)], | |||||
| nas_spaces=[GraphNasNodeClassificationSpace(hidden_dim=32,search_act_con=False,layer_number=2)], | |||||
| nas_estimators=[OneShotEstimator()] | nas_estimators=[OneShotEstimator()] | ||||
| ) | ) | ||||
| solver.fit(dataset) | solver.fit(dataset) | ||||
| @@ -0,0 +1,42 @@ | |||||
| import sys | |||||
| sys.path.append('../') | |||||
| from torch_geometric.nn import GCNConv | |||||
| import torch | |||||
| from autogl.datasets import build_dataset_from_name | |||||
| from autogl.solver import AutoNodeClassifier | |||||
| from autogl.module.train import NodeClassificationFullTrainer | |||||
| from autogl.module.nas import Darts, OneShotEstimator | |||||
| from autogl.module.nas.space.graph_nas import GraphNasNodeClassificationSpace | |||||
| from autogl.module.train import Acc | |||||
| from autogl.module.nas.algorithm.enas import Enas | |||||
| from autogl.module.nas.algorithm.rl import RL | |||||
| from autogl.module.nas.estimator.one_shot import TrainEstimator | |||||
| import logging | |||||
| if __name__ == '__main__': | |||||
| logging.getLogger().setLevel(logging.WARNING) | |||||
| dataset = build_dataset_from_name('cora') | |||||
| solver = AutoNodeClassifier( | |||||
| feature_module='PYGNormalizeFeatures', | |||||
| graph_models=[], | |||||
| hpo_module=None, | |||||
| ensemble_module=None, | |||||
| default_trainer=NodeClassificationFullTrainer( | |||||
| optimizer=torch.optim.Adam, | |||||
| lr=0.01, | |||||
| max_epoch=200, | |||||
| early_stopping_round=200, | |||||
| weight_decay=5e-4, | |||||
| device="auto", | |||||
| init=False, | |||||
| feval=['acc'], | |||||
| loss="nll_loss", | |||||
| lr_scheduler_type=None,), | |||||
| nas_algorithms=[RL(num_epochs=400)], | |||||
| #nas_algorithms=[Darts(num_epochs=200)], | |||||
| nas_spaces=[GraphNasNodeClassificationSpace(hidden_dim=16,search_act_con=True,layer_number=2)], | |||||
| nas_estimators=[TrainEstimator()] | |||||
| ) | |||||
| solver.fit(dataset) | |||||
| solver.get_leaderboard().show() | |||||
| out = solver.predict_proba() | |||||
| print('acc on cora', Acc.evaluate(out, dataset[0].y[dataset[0].test_mask].detach().numpy())) | |||||
| @@ -0,0 +1,73 @@ | |||||
| import sys | |||||
| from nni.nas.pytorch.mutables import Mutable | |||||
| sys.path.append('../') | |||||
| from torch_geometric.nn import GCNConv | |||||
| import torch | |||||
| from autogl.datasets import build_dataset_from_name | |||||
| from autogl.solver import AutoNodeClassifier | |||||
| from autogl.module.train import NodeClassificationFullTrainer | |||||
| from autogl.module.nas import Darts, OneShotEstimator | |||||
| from autogl.module.nas.space.graph_nas import * | |||||
| from autogl.module.train import Acc | |||||
| from autogl.module.nas.algorithm.enas import Enas | |||||
| from autogl.module.nas.algorithm.rl import * | |||||
| from autogl.module.nas.estimator.one_shot import TrainEstimator | |||||
| import logging | |||||
| import numpy as np | |||||
| from tqdm import tqdm | |||||
| if __name__ == '__main__': | |||||
| logging.getLogger().setLevel(logging.WARNING) | |||||
| dataset = build_dataset_from_name('cora') | |||||
| space=GraphNasNodeClassificationSpace(hidden_dim=16,search_act_con=True,layer_number=2) | |||||
| space.instantiate(input_dim=dataset[0].x.shape[1], | |||||
| output_dim=dataset.num_classes,) | |||||
| estim=TrainEstimator() | |||||
| # solver.fit(dataset) | |||||
| # solver.get_leaderboard().show() | |||||
| # out = solver.predict_proba() | |||||
| # print('acc on cora', Acc.evaluate(out, dataset[0].y[dataset[0].test_mask].detach().numpy())) | |||||
| class Tmp: | |||||
| def __init__(self,space): | |||||
| self.model = space | |||||
| self.nas_modules = [] | |||||
| k2o = get_module_order(self.model) | |||||
| replace_layer_choice(self.model, PathSamplingLayerChoice, self.nas_modules) | |||||
| replace_input_choice(self.model, PathSamplingInputChoice, self.nas_modules) | |||||
| self.nas_modules = sort_replaced_module(k2o, self.nas_modules) | |||||
| t=Tmp(space) | |||||
| print(t.nas_modules) | |||||
| nm=t.nas_modules | |||||
| selection_range={} | |||||
| for k,v in nm: | |||||
| selection_range[k]=len(v) | |||||
| ks=list(selection_range.keys()) | |||||
| selections=[] | |||||
| def dfs(selection,d): | |||||
| if d>=len(ks): | |||||
| selections.append(selection.copy()) | |||||
| return | |||||
| k=ks[d] | |||||
| r=selection_range[k] | |||||
| for i in range(r): | |||||
| selection[k]=i | |||||
| dfs(selection,d+1) | |||||
| dfs({},0) | |||||
| print(f'#selections {len(selections)}') | |||||
| device=torch.device('cuda:0') | |||||
| accs=[] | |||||
| from datetime import datetime | |||||
| timestamp=datetime.now().strftime('%m%d-%H-%M-%S') | |||||
| log=open(f'acclog{timestamp}.txt','w') | |||||
| with tqdm(selections) as bar: | |||||
| for selection in bar: | |||||
| arch=space.export(selection,device) | |||||
| m,l=estim.infer(arch,dataset,'test') | |||||
| bar.set_postfix(m=m,l=l.item()) | |||||
| log.write(f'{arch}\n{selection}\n{m},{l}\n') | |||||
| log.flush() | |||||
| accs.append(m) | |||||
| np.save(f'space_acc{timestamp}',np.array(accs)) | |||||
| print(f'max acc {np.max(accs)}') | |||||