You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

rl.py 24 kB

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. # codes in this file are reproduced from https://github.com/microsoft/nni with some changes.
  2. import torch
  3. import torch.nn as nn
  4. import torch.nn.functional as F
  5. from . import register_nas_algo
  6. from .base import BaseNAS
  7. from ..space import BaseSpace
  8. from ..utils import (
  9. AverageMeterGroup,
  10. replace_layer_choice,
  11. replace_input_choice,
  12. get_module_order,
  13. sort_replaced_module,
  14. )
  15. from nni.nas.pytorch.fixed import apply_fixed_architecture
  16. from tqdm import tqdm
  17. from datetime import datetime
  18. import numpy as np
  19. from ....utils import get_logger
  20. LOGGER = get_logger("random_search_NAS")
  21. def _get_mask(sampled, total):
  22. multihot = [
  23. i == sampled or (isinstance(sampled, list) and i in sampled)
  24. for i in range(total)
  25. ]
  26. return torch.tensor(multihot, dtype=torch.bool) # pylint: disable=not-callable
  27. class PathSamplingLayerChoice(nn.Module):
  28. """
  29. Mixed module, in which fprop is decided by exactly one or multiple (sampled) module.
  30. If multiple module is selected, the result will be sumed and returned.
  31. Attributes
  32. ----------
  33. sampled : int or list of int
  34. Sampled module indices.
  35. mask : tensor
  36. A multi-hot bool 1D-tensor representing the sampled mask.
  37. """
  38. def __init__(self, layer_choice):
  39. super(PathSamplingLayerChoice, self).__init__()
  40. self.op_names = []
  41. for name, module in layer_choice.named_children():
  42. self.add_module(name, module)
  43. self.op_names.append(name)
  44. assert self.op_names, "There has to be at least one op to choose from."
  45. self.sampled = None # sampled can be either a list of indices or an index
  46. def forward(self, *args, **kwargs):
  47. assert (
  48. self.sampled is not None
  49. ), "At least one path needs to be sampled before fprop."
  50. if isinstance(self.sampled, list):
  51. return sum(
  52. [getattr(self, self.op_names[i])(*args, **kwargs) for i in self.sampled]
  53. ) # pylint: disable=not-an-iterable
  54. else:
  55. return getattr(self, self.op_names[self.sampled])(
  56. *args, **kwargs
  57. ) # pylint: disable=invalid-sequence-index
  58. def __len__(self):
  59. return len(self.op_names)
  60. @property
  61. def mask(self):
  62. return _get_mask(self.sampled, len(self))
  63. class PathSamplingInputChoice(nn.Module):
  64. """
  65. Mixed input. Take a list of tensor as input, select some of them and return the sum.
  66. Attributes
  67. ----------
  68. sampled : int or list of int
  69. Sampled module indices.
  70. mask : tensor
  71. A multi-hot bool 1D-tensor representing the sampled mask.
  72. """
  73. def __init__(self, input_choice):
  74. super(PathSamplingInputChoice, self).__init__()
  75. self.n_candidates = input_choice.n_candidates
  76. self.n_chosen = input_choice.n_chosen
  77. self.sampled = None
  78. def forward(self, input_tensors):
  79. if isinstance(self.sampled, list):
  80. return sum(
  81. [input_tensors[t] for t in self.sampled]
  82. ) # pylint: disable=not-an-iterable
  83. else:
  84. return input_tensors[self.sampled]
  85. def __len__(self):
  86. return self.n_candidates
  87. @property
  88. def mask(self):
  89. return _get_mask(self.sampled, len(self))
  90. def __repr__(self):
  91. return f"PathSamplingInputChoice(n_candidates={self.n_candidates}, chosen={self.sampled})"
  92. class StackedLSTMCell(nn.Module):
  93. def __init__(self, layers, size, bias):
  94. super().__init__()
  95. self.lstm_num_layers = layers
  96. self.lstm_modules = nn.ModuleList(
  97. [nn.LSTMCell(size, size, bias=bias) for _ in range(self.lstm_num_layers)]
  98. )
  99. def forward(self, inputs, hidden):
  100. prev_h, prev_c = hidden
  101. next_h, next_c = [], []
  102. for i, m in enumerate(self.lstm_modules):
  103. curr_h, curr_c = m(inputs, (prev_h[i], prev_c[i]))
  104. next_c.append(curr_c)
  105. next_h.append(curr_h)
  106. # current implementation only supports batch size equals 1,
  107. # but the algorithm does not necessarily have this limitation
  108. inputs = curr_h[-1].view(1, -1)
  109. return next_h, next_c
  110. class ReinforceField:
  111. """
  112. A field with ``name``, with ``total`` choices. ``choose_one`` is true if one and only one is meant to be
  113. selected. Otherwise, any number of choices can be chosen.
  114. """
  115. def __init__(self, name, total, choose_one):
  116. self.name = name
  117. self.total = total
  118. self.choose_one = choose_one
  119. def __repr__(self):
  120. return f"ReinforceField(name={self.name}, total={self.total}, choose_one={self.choose_one})"
  121. class ReinforceController(nn.Module):
  122. """
  123. A controller that mutates the graph with RL.
  124. Parameters
  125. ----------
  126. fields : list of ReinforceField
  127. List of fields to choose.
  128. lstm_size : int
  129. Controller LSTM hidden units.
  130. lstm_num_layers : int
  131. Number of layers for stacked LSTM.
  132. tanh_constant : float
  133. Logits will be equal to ``tanh_constant * tanh(logits)``. Don't use ``tanh`` if this value is ``None``.
  134. skip_target : float
  135. Target probability that skipconnect will appear.
  136. temperature : float
  137. Temperature constant that divides the logits.
  138. entropy_reduction : str
  139. Can be one of ``sum`` and ``mean``. How the entropy of multi-input-choice is reduced.
  140. """
  141. def __init__(
  142. self,
  143. fields,
  144. lstm_size=64,
  145. lstm_num_layers=1,
  146. tanh_constant=1.5,
  147. skip_target=0.4,
  148. temperature=None,
  149. entropy_reduction="sum",
  150. ):
  151. super(ReinforceController, self).__init__()
  152. self.fields = fields
  153. self.lstm_size = lstm_size
  154. self.lstm_num_layers = lstm_num_layers
  155. self.tanh_constant = tanh_constant
  156. self.temperature = temperature
  157. self.skip_target = skip_target
  158. self.lstm = StackedLSTMCell(self.lstm_num_layers, self.lstm_size, False)
  159. self.attn_anchor = nn.Linear(self.lstm_size, self.lstm_size, bias=False)
  160. self.attn_query = nn.Linear(self.lstm_size, self.lstm_size, bias=False)
  161. self.v_attn = nn.Linear(self.lstm_size, 1, bias=False)
  162. self.g_emb = nn.Parameter(torch.randn(1, self.lstm_size) * 0.1)
  163. self.skip_targets = nn.Parameter(
  164. torch.tensor(
  165. [1.0 - self.skip_target, self.skip_target]
  166. ), # pylint: disable=not-callable
  167. requires_grad=False,
  168. )
  169. assert entropy_reduction in [
  170. "sum",
  171. "mean",
  172. ], "Entropy reduction must be one of sum and mean."
  173. self.entropy_reduction = torch.sum if entropy_reduction == "sum" else torch.mean
  174. self.cross_entropy_loss = nn.CrossEntropyLoss(reduction="none")
  175. self.soft = nn.ModuleDict(
  176. {
  177. field.name: nn.Linear(self.lstm_size, field.total, bias=False)
  178. for field in fields
  179. }
  180. )
  181. self.embedding = nn.ModuleDict(
  182. {field.name: nn.Embedding(field.total, self.lstm_size) for field in fields}
  183. )
  184. def resample(self):
  185. self._initialize()
  186. result = dict()
  187. for field in self.fields:
  188. result[field.name] = self._sample_single(field)
  189. return result
  190. def _initialize(self):
  191. self._inputs = self.g_emb.data
  192. self._c = [
  193. torch.zeros(
  194. (1, self.lstm_size),
  195. dtype=self._inputs.dtype,
  196. device=self._inputs.device,
  197. )
  198. for _ in range(self.lstm_num_layers)
  199. ]
  200. self._h = [
  201. torch.zeros(
  202. (1, self.lstm_size),
  203. dtype=self._inputs.dtype,
  204. device=self._inputs.device,
  205. )
  206. for _ in range(self.lstm_num_layers)
  207. ]
  208. self.sample_log_prob = 0
  209. self.sample_entropy = 0
  210. self.sample_skip_penalty = 0
  211. def _lstm_next_step(self):
  212. self._h, self._c = self.lstm(self._inputs, (self._h, self._c))
  213. def _sample_single(self, field):
  214. self._lstm_next_step()
  215. logit = self.soft[field.name](self._h[-1])
  216. if self.temperature is not None:
  217. logit /= self.temperature
  218. if self.tanh_constant is not None:
  219. logit = self.tanh_constant * torch.tanh(logit)
  220. if field.choose_one:
  221. sampled = torch.multinomial(F.softmax(logit, dim=-1), 1).view(-1)
  222. log_prob = self.cross_entropy_loss(logit, sampled)
  223. self._inputs = self.embedding[field.name](sampled)
  224. else:
  225. logit = logit.view(-1, 1)
  226. logit = torch.cat(
  227. [-logit, logit], 1
  228. ) # pylint: disable=invalid-unary-operand-type
  229. sampled = torch.multinomial(F.softmax(logit, dim=-1), 1).view(-1)
  230. skip_prob = torch.sigmoid(logit)
  231. kl = torch.sum(skip_prob * torch.log(skip_prob / self.skip_targets))
  232. self.sample_skip_penalty += kl
  233. log_prob = self.cross_entropy_loss(logit, sampled)
  234. sampled = sampled.nonzero().view(-1)
  235. if sampled.sum().item():
  236. self._inputs = (
  237. torch.sum(self.embedding[field.name](sampled.view(-1)), 0)
  238. / (1.0 + torch.sum(sampled))
  239. ).unsqueeze(0)
  240. else:
  241. self._inputs = torch.zeros(
  242. 1, self.lstm_size, device=self.embedding[field.name].weight.device
  243. )
  244. sampled = sampled.detach().numpy().tolist()
  245. self.sample_log_prob += self.entropy_reduction(log_prob)
  246. entropy = (
  247. log_prob * torch.exp(-log_prob)
  248. ).detach() # pylint: disable=invalid-unary-operand-type
  249. self.sample_entropy += self.entropy_reduction(entropy)
  250. if len(sampled) == 1:
  251. sampled = sampled[0]
  252. return sampled
  253. @register_nas_algo("rl")
  254. class RL(BaseNAS):
  255. """
  256. RL in GraphNas.
  257. Parameters
  258. ----------
  259. num_epochs : int
  260. Number of epochs planned for training.
  261. device : torch.device
  262. ``torch.device("cpu")`` or ``torch.device("cuda")``.
  263. log_frequency : int
  264. Step count per logging.
  265. grad_clip : float
  266. Gradient clipping. Set to 0 to disable. Default: 5.
  267. entropy_weight : float
  268. Weight of sample entropy loss.
  269. skip_weight : float
  270. Weight of skip penalty loss.
  271. baseline_decay : float
  272. Decay factor of baseline. New baseline will be equal to ``baseline_decay * baseline_old + reward * (1 - baseline_decay)``.
  273. ctrl_lr : float
  274. Learning rate for RL controller.
  275. ctrl_steps_aggregate : int
  276. Number of steps that will be aggregated into one mini-batch for RL controller.
  277. ctrl_steps : int
  278. Number of mini-batches for each epoch of RL controller learning.
  279. ctrl_kwargs : dict
  280. Optional kwargs that will be passed to :class:`ReinforceController`.
  281. n_warmup : int
  282. Number of epochs for training super network.
  283. model_lr : float
  284. Learning rate for super network.
  285. model_wd : float
  286. Weight decay for super network.
  287. disable_progress: boolean
  288. Control whether show the progress bar.
  289. """
  290. def __init__(
  291. self,
  292. num_epochs=5,
  293. device="cuda",
  294. log_frequency=None,
  295. grad_clip=5.0,
  296. entropy_weight=0.0001,
  297. skip_weight=0.8,
  298. baseline_decay=0.999,
  299. ctrl_lr=0.00035,
  300. ctrl_steps_aggregate=20,
  301. ctrl_kwargs=None,
  302. n_warmup=100,
  303. model_lr=5e-3,
  304. model_wd=5e-4,
  305. disable_progress=True,
  306. ):
  307. super().__init__(device)
  308. self.device = device
  309. self.num_epochs = num_epochs
  310. self.log_frequency = log_frequency
  311. self.entropy_weight = entropy_weight
  312. self.skip_weight = skip_weight
  313. self.baseline_decay = baseline_decay
  314. self.baseline = 0.0
  315. self.ctrl_steps_aggregate = ctrl_steps_aggregate
  316. self.grad_clip = grad_clip
  317. self.ctrl_kwargs = ctrl_kwargs
  318. self.ctrl_lr = ctrl_lr
  319. self.n_warmup = n_warmup
  320. self.model_lr = model_lr
  321. self.model_wd = model_wd
  322. self.disable_progress = disable_progress
  323. def search(self, space: BaseSpace, dset, estimator):
  324. self.model = space
  325. self.dataset = dset # .to(self.device)
  326. self.estimator = estimator
  327. # replace choice
  328. self.nas_modules = []
  329. k2o = get_module_order(self.model)
  330. replace_layer_choice(self.model, PathSamplingLayerChoice, self.nas_modules)
  331. replace_input_choice(self.model, PathSamplingInputChoice, self.nas_modules)
  332. self.nas_modules = sort_replaced_module(k2o, self.nas_modules)
  333. # to device
  334. self.model = self.model.to(self.device)
  335. # fields
  336. self.nas_fields = [
  337. ReinforceField(
  338. name,
  339. len(module),
  340. isinstance(module, PathSamplingLayerChoice) or module.n_chosen == 1,
  341. )
  342. for name, module in self.nas_modules
  343. ]
  344. self.controller = ReinforceController(
  345. self.nas_fields, **(self.ctrl_kwargs or {})
  346. )
  347. self.ctrl_optim = torch.optim.Adam(
  348. self.controller.parameters(), lr=self.ctrl_lr
  349. )
  350. # train
  351. with tqdm(range(self.num_epochs), disable=self.disable_progress) as bar:
  352. for i in bar:
  353. l2 = self._train_controller(i)
  354. bar.set_postfix(reward_controller=l2)
  355. selection = self.export()
  356. arch = space.parse_model(selection, self.device)
  357. # print(selection,arch)
  358. return arch
  359. def _train_controller(self, epoch):
  360. self.model.eval()
  361. self.controller.train()
  362. self.ctrl_optim.zero_grad()
  363. rewards = []
  364. with tqdm(
  365. range(self.ctrl_steps_aggregate), disable=self.disable_progress
  366. ) as bar:
  367. for ctrl_step in bar:
  368. self._resample()
  369. metric, loss = self._infer(mask="val")
  370. bar.set_postfix(acc=metric, loss=loss.item())
  371. LOGGER.info(f"{self.arch}\n{self.selection}\n{metric},{loss}")
  372. reward = metric
  373. rewards.append(reward)
  374. if self.entropy_weight:
  375. reward += (
  376. self.entropy_weight * self.controller.sample_entropy.item()
  377. )
  378. self.baseline = self.baseline * self.baseline_decay + reward * (
  379. 1 - self.baseline_decay
  380. )
  381. loss = self.controller.sample_log_prob * (reward - self.baseline)
  382. if self.skip_weight:
  383. loss += self.skip_weight * self.controller.sample_skip_penalty
  384. loss /= self.ctrl_steps_aggregate
  385. loss.backward()
  386. if (ctrl_step + 1) % self.ctrl_steps_aggregate == 0:
  387. if self.grad_clip > 0:
  388. nn.utils.clip_grad_norm_(
  389. self.controller.parameters(), self.grad_clip
  390. )
  391. self.ctrl_optim.step()
  392. self.ctrl_optim.zero_grad()
  393. if (
  394. self.log_frequency is not None
  395. and ctrl_step % self.log_frequency == 0
  396. ):
  397. LOGGER.info(
  398. "RL Epoch [%d/%d] Step [%d/%d] %s",
  399. epoch + 1,
  400. self.num_epochs,
  401. ctrl_step + 1,
  402. self.ctrl_steps_aggregate,
  403. )
  404. return sum(rewards) / len(rewards)
  405. def _resample(self):
  406. result = self.controller.resample()
  407. self.arch = self.model.parse_model(result, device=self.device)
  408. self.selection = result
  409. def export(self):
  410. self.controller.eval()
  411. with torch.no_grad():
  412. return self.controller.resample()
  413. def _infer(self, mask="train"):
  414. metric, loss = self.estimator.infer(self.arch._model, self.dataset, mask=mask)
  415. return metric[0], loss
  416. @register_nas_algo("graphnas")
  417. class GraphNasRL(BaseNAS):
  418. """
  419. RL in GraphNas.
  420. Parameters
  421. ----------
  422. device : torch.device
  423. ``torch.device("cpu")`` or ``torch.device("cuda")``.
  424. num_epochs : int
  425. Number of epochs planned for training.
  426. log_frequency : int
  427. Step count per logging.
  428. grad_clip : float
  429. Gradient clipping. Set to 0 to disable. Default: 5.
  430. entropy_weight : float
  431. Weight of sample entropy loss.
  432. skip_weight : float
  433. Weight of skip penalty loss.
  434. baseline_decay : float
  435. Decay factor of baseline. New baseline will be equal to ``baseline_decay * baseline_old + reward * (1 - baseline_decay)``.
  436. ctrl_lr : float
  437. Learning rate for RL controller.
  438. ctrl_steps_aggregate : int
  439. Number of steps that will be aggregated into one mini-batch for RL controller.
  440. ctrl_steps : int
  441. Number of mini-batches for each epoch of RL controller learning.
  442. ctrl_kwargs : dict
  443. Optional kwargs that will be passed to :class:`ReinforceController`.
  444. n_warmup : int
  445. Number of epochs for training super network.
  446. model_lr : float
  447. Learning rate for super network.
  448. model_wd : float
  449. Weight decay for super network.
  450. topk : int
  451. Number of architectures kept in training process.
  452. disable_progeress: boolean
  453. Control whether show the progress bar.
  454. """
  455. def __init__(
  456. self,
  457. device="cuda",
  458. num_epochs=10,
  459. log_frequency=None,
  460. grad_clip=5.0,
  461. entropy_weight=0.0001,
  462. skip_weight=0,
  463. baseline_decay=0.95,
  464. ctrl_lr=0.00035,
  465. ctrl_steps_aggregate=100,
  466. ctrl_kwargs=None,
  467. n_warmup=100,
  468. model_lr=5e-3,
  469. model_wd=5e-4,
  470. topk=5,
  471. disable_progress=True,
  472. ):
  473. super().__init__(device)
  474. self.device = device
  475. self.num_epochs = num_epochs
  476. self.log_frequency = log_frequency
  477. self.entropy_weight = entropy_weight
  478. self.skip_weight = skip_weight
  479. self.baseline_decay = baseline_decay
  480. self.ctrl_steps_aggregate = ctrl_steps_aggregate
  481. self.grad_clip = grad_clip
  482. self.ctrl_kwargs = ctrl_kwargs
  483. self.ctrl_lr = ctrl_lr
  484. self.n_warmup = n_warmup
  485. self.model_lr = model_lr
  486. self.model_wd = model_wd
  487. self.hist = []
  488. self.topk = topk
  489. self.disable_progress = disable_progress
  490. def search(self, space: BaseSpace, dset, estimator):
  491. self.model = space
  492. self.dataset = dset # .to(self.device)
  493. self.estimator = estimator
  494. # replace choice
  495. self.nas_modules = []
  496. k2o = get_module_order(self.model)
  497. replace_layer_choice(self.model, PathSamplingLayerChoice, self.nas_modules)
  498. replace_input_choice(self.model, PathSamplingInputChoice, self.nas_modules)
  499. self.nas_modules = sort_replaced_module(k2o, self.nas_modules)
  500. # to device
  501. self.model = self.model.to(self.device)
  502. # fields
  503. self.nas_fields = [
  504. ReinforceField(
  505. name,
  506. len(module),
  507. isinstance(module, PathSamplingLayerChoice) or module.n_chosen == 1,
  508. )
  509. for name, module in self.nas_modules
  510. ]
  511. self.controller = ReinforceController(
  512. self.nas_fields,
  513. lstm_size=100,
  514. temperature=5.0,
  515. tanh_constant=2.5,
  516. **(self.ctrl_kwargs or {}),
  517. )
  518. self.ctrl_optim = torch.optim.Adam(
  519. self.controller.parameters(), lr=self.ctrl_lr
  520. )
  521. # train
  522. with tqdm(range(self.num_epochs), disable=self.disable_progress) as bar:
  523. for i in bar:
  524. l2 = self._train_controller(i)
  525. bar.set_postfix(reward_controller=l2)
  526. # selection=self.export()
  527. selections = [x[1] for x in self.hist]
  528. candidiate_accs = [-x[0] for x in self.hist]
  529. # print('candidiate accuracies',candidiate_accs)
  530. selection = self._choose_best(selections)
  531. arch = space.parse_model(selection, self.device)
  532. # print(selection,arch)
  533. return arch
  534. def _choose_best(self, selections):
  535. # graphnas use top 5 models, can evaluate 20 times epoch and choose the best.
  536. results = []
  537. for selection in selections:
  538. accs = []
  539. for i in tqdm(range(20), disable=self.disable_progress):
  540. self.arch = self.model.parse_model(selection, device=self.device)
  541. metric, loss = self._infer(mask="val")
  542. accs.append(metric)
  543. result = np.mean(accs)
  544. LOGGER.info(
  545. "selection {} \n acc {:.4f} +- {:.4f}".format(
  546. selection, np.mean(accs), np.std(accs) / np.sqrt(20)
  547. )
  548. )
  549. results.append(result)
  550. best_selection = selections[np.argmax(results)]
  551. return best_selection
  552. def _train_controller(self, epoch):
  553. self.model.eval()
  554. self.controller.train()
  555. self.ctrl_optim.zero_grad()
  556. rewards = []
  557. baseline = None
  558. # diff: graph nas train 100 and derive 100 for every epoch(10 epochs), we just train 100(20 epochs). totol num of samples are same (2000)
  559. with tqdm(
  560. range(self.ctrl_steps_aggregate), disable=self.disable_progress
  561. ) as bar:
  562. for ctrl_step in bar:
  563. self._resample()
  564. metric, loss = self._infer(mask="val")
  565. # bar.set_postfix(acc=metric,loss=loss.item())
  566. LOGGER.debug(f"{self.arch}\n{self.selection}\n{metric},{loss}")
  567. # diff: not do reward shaping as in graphnas code
  568. reward = metric
  569. self.hist.append([-metric, self.selection])
  570. if len(self.hist) > self.topk:
  571. self.hist.sort(key=lambda x: x[0])
  572. self.hist.pop()
  573. rewards.append(reward)
  574. if self.entropy_weight:
  575. reward += (
  576. self.entropy_weight * self.controller.sample_entropy.item()
  577. )
  578. if not baseline:
  579. baseline = reward
  580. else:
  581. baseline = baseline * self.baseline_decay + reward * (
  582. 1 - self.baseline_decay
  583. )
  584. loss = self.controller.sample_log_prob * (reward - baseline)
  585. self.ctrl_optim.zero_grad()
  586. loss.backward()
  587. self.ctrl_optim.step()
  588. bar.set_postfix(acc=metric, max_acc=max(rewards))
  589. return sum(rewards) / len(rewards)
  590. def _resample(self):
  591. result = self.controller.resample()
  592. self.arch = self.model.parse_model(result, device=self.device)
  593. self.selection = result
  594. def export(self):
  595. self.controller.eval()
  596. with torch.no_grad():
  597. return self.controller.resample()
  598. def _infer(self, mask="train"):
  599. metric, loss = self.estimator.infer(self.arch._model, self.dataset, mask=mask)
  600. return metric[0], loss