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.

mutator.py 10 kB

2 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. # Copyright (c) Microsoft Corporation.
  2. # Licensed under the MIT license.
  3. import json
  4. import logging
  5. import os
  6. import sys
  7. sys.path.append('..'+ '/' + '..')
  8. import torch
  9. from pytorch.mutables import LayerChoice, InputChoice, MutableScope
  10. from pytorch.mutator import Mutator
  11. import numpy as np
  12. import random
  13. logger = logging.getLogger(__name__)
  14. NNI_GEN_SEARCH_SPACE = "NNI_GEN_SEARCH_SPACE"
  15. NNI_PLATFORM = "GPU"
  16. LAYER_CHOICE = "layer_choice"
  17. INPUT_CHOICE = "input_choice"
  18. def get_and_apply_next_architecture(model):
  19. """
  20. Wrapper of :class:`~nni.nas.pytorch.classic_nas.mutator.ClassicMutator` to make it more meaningful,
  21. similar to ``get_next_parameter`` for HPO.
  22. It will generate search space based on ``model``.
  23. If env ``NNI_GEN_SEARCH_SPACE`` exists, this is in dry run mode for
  24. generating search space for the experiment.
  25. If not, there are still two mode, one is nni experiment mode where users
  26. use ``nnictl`` to start an experiment. The other is standalone mode
  27. where users directly run the trial command, this mode chooses the first
  28. one(s) for each LayerChoice and InputChoice.
  29. Parameters
  30. ----------
  31. model : nn.Module
  32. User's model with search space (e.g., LayerChoice, InputChoice) embedded in it.
  33. """
  34. ClassicMutator(model)
  35. class ClassicMutator(Mutator):
  36. """
  37. This mutator is to apply the architecture chosen from tuner.
  38. It implements the forward function of LayerChoice and InputChoice,
  39. to only activate the chosen ones.
  40. Parameters
  41. ----------
  42. model : nn.Module
  43. User's model with search space (e.g., LayerChoice, InputChoice) embedded in it.
  44. """
  45. def __init__(self, model,trial_id,selected_path,search_space_path,load_selected_space=False):
  46. super(ClassicMutator, self).__init__(model)
  47. self._chosen_arch = {}
  48. self._search_space = self._generate_search_space()
  49. self.trial_id = trial_id
  50. #if NNI_GEN_SEARCH_SPACE in os.environ:
  51. # dry run for only generating search space
  52. self._dump_search_space(search_space_path)
  53. #sys.exit(0)
  54. if load_selected_space:
  55. logger.warning("load selected space.")
  56. self._chosen_arch = self.load_selected_space
  57. else:
  58. # get chosen arch from tuner
  59. self._chosen_arch = self.random_generate_chosen()
  60. self._generate_selected_space(selected_path)
  61. self.reset()
  62. def _sample_layer_choice(self, mutable, idx, value, search_space_item):
  63. """
  64. Convert layer choice to tensor representation.
  65. Parameters
  66. ----------
  67. mutable : Mutable
  68. idx : int
  69. Number `idx` of list will be selected.
  70. value : str
  71. The verbose representation of the selected value.
  72. search_space_item : list
  73. The list for corresponding search space.
  74. """
  75. # doesn't support multihot for layer choice yet
  76. onehot_list = [False] * len(mutable)
  77. assert 0 <= idx < len(mutable) and search_space_item[idx] == value, \
  78. "Index '{}' in search space '{}' is not '{}'".format(idx, search_space_item, value)
  79. onehot_list[idx] = True
  80. return torch.tensor(onehot_list, dtype=torch.bool) # pylint: disable=not-callable
  81. def _sample_input_choice(self, mutable, idx, value, search_space_item):
  82. """
  83. Convert input choice to tensor representation.
  84. Parameters
  85. ----------
  86. mutable : Mutable
  87. idx : int
  88. Number `idx` of list will be selected.
  89. value : str
  90. The verbose representation of the selected value.
  91. search_space_item : list
  92. The list for corresponding search space.
  93. """
  94. candidate_repr = search_space_item["candidates"]
  95. multihot_list = [False] * mutable.n_candidates
  96. for i, v in zip(idx, value):
  97. assert 0 <= i < mutable.n_candidates and candidate_repr[i] == v, \
  98. "Index '{}' in search space '{}' is not '{}'".format(i, candidate_repr, v)
  99. assert not multihot_list[i], "'{}' is selected twice in '{}', which is not allowed.".format(i, idx)
  100. multihot_list[i] = True
  101. return torch.tensor(multihot_list, dtype=torch.bool) # pylint: disable=not-callable
  102. def sample_search(self):
  103. """
  104. See :meth:`sample_final`.
  105. """
  106. return self.sample_final()
  107. def sample_final(self):
  108. """
  109. Convert the chosen arch and apply it on model.
  110. """
  111. assert set(self._chosen_arch.keys()) == set(self._search_space.keys()), \
  112. "Unmatched keys, expected keys '{}' from search space, found '{}'.".format(self._search_space.keys(),
  113. self._chosen_arch.keys())
  114. result = dict()
  115. for mutable in self.mutables:
  116. if isinstance(mutable, (LayerChoice, InputChoice)):
  117. assert mutable.key in self._chosen_arch, \
  118. "Expected '{}' in chosen arch, but not found.".format(mutable.key)
  119. data = self._chosen_arch[mutable.key]
  120. assert isinstance(data, dict) and "_value" in data and "_idx" in data, \
  121. "'{}' is not a valid choice.".format(data)
  122. if isinstance(mutable, LayerChoice):
  123. result[mutable.key] = self._sample_layer_choice(mutable, data["_idx"], data["_value"],
  124. self._search_space[mutable.key]["_value"])
  125. elif isinstance(mutable, InputChoice):
  126. result[mutable.key] = self._sample_input_choice(mutable, data["_idx"], data["_value"],
  127. self._search_space[mutable.key]["_value"])
  128. elif isinstance(mutable, MutableScope):
  129. logger.info("Mutable scope '%s' is skipped during parsing choices.", mutable.key)
  130. else:
  131. raise TypeError("Unsupported mutable type: '%s'." % type(mutable))
  132. return result
  133. def _standalone_generate_chosen(self):
  134. """
  135. Generate the chosen architecture for standalone mode,
  136. i.e., choose the first one(s) for LayerChoice and InputChoice.
  137. ::
  138. { key_name: {"_value": "conv1",
  139. "_idx": 0} }
  140. { key_name: {"_value": ["in1"],
  141. "_idx": [0]} }
  142. Returns
  143. -------
  144. dict
  145. the chosen architecture
  146. """
  147. chosen_arch = {}
  148. for key, val in self._search_space.items():
  149. if val["_type"] == LAYER_CHOICE:
  150. choices = val["_value"]
  151. chosen_arch[key] = {"_value": choices[0], "_idx": 0}
  152. elif val["_type"] == INPUT_CHOICE:
  153. choices = val["_value"]["candidates"]
  154. n_chosen = val["_value"]["n_chosen"]
  155. if n_chosen is None:
  156. n_chosen = len(choices)
  157. chosen_arch[key] = {"_value": choices[:n_chosen], "_idx": list(range(n_chosen))}
  158. else:
  159. raise ValueError("Unknown key '%s' and value '%s'." % (key, val))
  160. return chosen_arch
  161. def random_generate_chosen(self):
  162. """
  163. Generate the chosen architecture for standalone mode,
  164. i.e., choose the first one(s) for LayerChoice and InputChoice.
  165. ::
  166. { key_name: {"_value": "conv1",
  167. "_idx": 0} }
  168. { key_name: {"_value": ["in1"],
  169. "_idx": [0]} }
  170. Returns
  171. -------
  172. dict
  173. the chosen architecture
  174. """
  175. chosen_arch = {}
  176. np.random.seed(self.trial_id)
  177. random.seed(self.trial_id)
  178. for key, val in self._search_space.items():
  179. if val["_type"] == LAYER_CHOICE:
  180. choices = val["_value"]
  181. chosen_idx = np.random.randint(len(choices))
  182. chosen_arch[key] = {"_value": choices[chosen_idx], "_idx": chosen_idx}
  183. elif val["_type"] == INPUT_CHOICE:
  184. choices = val["_value"]["candidates"]
  185. n_chosen = val["_value"]["n_chosen"]
  186. if n_chosen is None:
  187. n_chosen = len(choices)
  188. chosen_idx = random.sample(list(range(n_chosen)),n_chosen)
  189. chosen_arch[key] = {"_value": [choices[idx] for idx in chosen_idx], "_idx": chosen_idx}
  190. else:
  191. raise ValueError("Unknown key '%s' and value '%s'." % (key, val))
  192. return chosen_arch
  193. def _generate_search_space(self):
  194. """
  195. Generate search space from mutables.
  196. Here is the search space format:
  197. ::
  198. { key_name: {"_type": "layer_choice",
  199. "_value": ["conv1", "conv2"]} }
  200. { key_name: {"_type": "input_choice",
  201. "_value": {"candidates": ["in1", "in2"],
  202. "n_chosen": 1}} }
  203. Returns
  204. -------
  205. dict
  206. the generated search space
  207. """
  208. search_space = {}
  209. for mutable in self.mutables:
  210. # for now we only generate flattened search space
  211. if isinstance(mutable, LayerChoice):
  212. key = mutable.key
  213. val = mutable.names
  214. search_space[key] = {"_type": LAYER_CHOICE, "_value": val}
  215. elif isinstance(mutable, InputChoice):
  216. key = mutable.key
  217. search_space[key] = {"_type": INPUT_CHOICE,
  218. "_value": {"candidates": mutable.choose_from,
  219. "n_chosen": mutable.n_chosen}}
  220. elif isinstance(mutable, MutableScope):
  221. logger.info("Mutable scope '%s' is skipped during generating search space.", mutable.key)
  222. else:
  223. raise TypeError("Unsupported mutable type: '%s'." % type(mutable))
  224. return search_space
  225. def _dump_search_space(self, file_path):
  226. with open(file_path, "w") as ss_file:
  227. json.dump(self._search_space, ss_file, sort_keys=True, indent=2)
  228. def _generate_selected_space(self,file_path):
  229. with open(file_path, "w") as ss_file:
  230. json.dump(self._chosen_arch, ss_file, sort_keys=True, indent=2)

一站式算法开发平台、高性能分布式深度学习框架、先进算法模型库、视觉模型炼知平台、数据可视化分析平台等一系列平台及工具,在模型高效分布式训练、数据处理和可视分析、模型炼知和轻量化等技术上形成独特优势,目前已在产学研等各领域近千家单位及个人提供AI应用赋能