model.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. #
  2. # Copyright 2019 The FATE Authors. All Rights Reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. #
  16. import copy
  17. import json
  18. from federatedml.util import LOGGER
  19. from federatedml.util import consts
  20. from federatedml.param.hetero_nn_param import HeteroNNParam
  21. from federatedml.nn.hetero.strategy.selector import SelectorFactory
  22. from federatedml.nn.hetero.nn_component.bottom_model import BottomModel
  23. from federatedml.nn.hetero.nn_component.top_model import TopModel
  24. from federatedml.nn.backend.utils.common import global_seed
  25. from federatedml.protobuf.generated.hetero_nn_model_meta_pb2 import HeteroNNModelMeta
  26. from federatedml.protobuf.generated.hetero_nn_model_meta_pb2 import OptimizerParam
  27. from federatedml.protobuf.generated.hetero_nn_model_param_pb2 import HeteroNNModelParam
  28. from federatedml.nn.hetero.interactive.he_interactive_layer import HEInteractiveLayerGuest, HEInteractiveLayerHost
  29. class HeteroNNModel(object):
  30. def __init__(self):
  31. self.partition = 1
  32. self.batch_size = None
  33. self.bottom_nn_define = None
  34. self.top_nn_define = None
  35. self.interactive_layer_define = None
  36. self.optimizer = None
  37. self.config_type = None
  38. self.transfer_variable = None
  39. self._predict_round = 0
  40. def load_model(self):
  41. pass
  42. def predict(self, data):
  43. pass
  44. def export_model(self):
  45. pass
  46. def get_hetero_nn_model_meta(self):
  47. pass
  48. def get_hetero_nn_model_param(self):
  49. pass
  50. def set_hetero_nn_model_meta(self, model_meta):
  51. pass
  52. def set_hetero_nn_model_param(self, model_param):
  53. pass
  54. def set_partition(self, partition):
  55. pass
  56. def inc_predict_round(self):
  57. self._predict_round += 1
  58. class HeteroNNGuestModel(HeteroNNModel):
  59. def __init__(self, hetero_nn_param, component_properties, flowid):
  60. super(HeteroNNGuestModel, self).__init__()
  61. self.role = consts.GUEST
  62. self.bottom_model: BottomModel = None
  63. self.top_model: TopModel = None
  64. self.interactive_model: HEInteractiveLayerGuest = None
  65. self.loss = None
  66. self.hetero_nn_param = None
  67. self.is_empty = False
  68. self.coae_param = None
  69. self.seed = 100
  70. self.set_nn_meta(hetero_nn_param)
  71. self.component_properties = component_properties
  72. self.flowid = flowid
  73. self.label_num = 1
  74. self.selector = SelectorFactory.get_selector(
  75. hetero_nn_param.selector_param.method,
  76. hetero_nn_param.selector_param.selective_size,
  77. beta=hetero_nn_param.selector_param.beta,
  78. random_rate=hetero_nn_param.selector_param.random_state,
  79. min_prob=hetero_nn_param.selector_param.min_prob)
  80. def set_nn_meta(self, hetero_nn_param: HeteroNNParam):
  81. self.bottom_nn_define = hetero_nn_param.bottom_nn_define
  82. self.top_nn_define = hetero_nn_param.top_nn_define
  83. self.interactive_layer_define = hetero_nn_param.interactive_layer_define
  84. self.config_type = hetero_nn_param.config_type
  85. self.optimizer = hetero_nn_param.optimizer
  86. self.loss = hetero_nn_param.loss
  87. self.hetero_nn_param = hetero_nn_param
  88. self.batch_size = hetero_nn_param.batch_size
  89. self.seed = hetero_nn_param.seed
  90. coae_param = hetero_nn_param.coae_param
  91. if coae_param.enable:
  92. self.coae_param = coae_param
  93. def set_empty(self):
  94. self.is_empty = True
  95. def set_label_num(self, label_num):
  96. self.label_num = label_num
  97. if self.top_model is not None: # warmstart case
  98. self.top_model.label_num = label_num
  99. def train(self, x, y, epoch, batch_idx):
  100. if self.batch_size == -1:
  101. self.batch_size = x.shape[0]
  102. global_seed(self.seed)
  103. if self.top_model is None:
  104. self._build_top_model()
  105. LOGGER.debug('top model is {}'.format(self.top_model))
  106. if not self.is_empty:
  107. if self.bottom_model is None:
  108. self._build_bottom_model()
  109. LOGGER.debug('bottom model is {}'.format(self.bottom_model))
  110. self.bottom_model.train_mode(True)
  111. guest_bottom_output = self.bottom_model.forward(x)
  112. else:
  113. guest_bottom_output = None
  114. if self.interactive_model is None:
  115. self._build_interactive_model()
  116. interactive_output = self.interactive_model.forward(
  117. x=guest_bottom_output, epoch=epoch, batch=batch_idx, train=True)
  118. self.top_model.train_mode(True)
  119. selective_ids, gradients, loss = self.top_model.train_and_get_backward_gradient(
  120. interactive_output, y)
  121. interactive_layer_backward = self.interactive_model.backward(
  122. error=gradients, epoch=epoch, batch=batch_idx, selective_ids=selective_ids)
  123. if not self.is_empty:
  124. self.bottom_model.backward(
  125. x, interactive_layer_backward, selective_ids)
  126. return loss
  127. def predict(self, x, batch=0):
  128. if not self.is_empty:
  129. self.bottom_model.train_mode(False)
  130. guest_bottom_output = self.bottom_model.predict(x)
  131. else:
  132. guest_bottom_output = None
  133. interactive_output = self.interactive_model.forward(
  134. guest_bottom_output, epoch=self._predict_round, batch=batch, train=False)
  135. self.top_model.train_mode(False)
  136. preds = self.top_model.predict(interactive_output)
  137. # prediction procedure has its prediction iteration count, we do this
  138. # to avoid reusing communication suffixes
  139. self.inc_predict_round()
  140. return preds
  141. def get_hetero_nn_model_param(self):
  142. model_param = HeteroNNModelParam()
  143. model_param.is_empty = self.is_empty
  144. if not self.is_empty:
  145. model_param.bottom_saved_model_bytes = self.bottom_model.export_model()
  146. model_param.top_saved_model_bytes = self.top_model.export_model()
  147. model_param.interactive_layer_param.CopyFrom(
  148. self.interactive_model.export_model())
  149. coae_bytes = self.top_model.export_coae()
  150. if coae_bytes is not None:
  151. model_param.coae_bytes = coae_bytes
  152. return model_param
  153. def set_hetero_nn_model_param(self, model_param):
  154. self.is_empty = model_param.is_empty
  155. if not self.is_empty:
  156. self._restore_bottom_model(model_param.bottom_saved_model_bytes)
  157. self._restore_interactive_model(model_param.interactive_layer_param)
  158. self._restore_top_model(model_param.top_saved_model_bytes)
  159. self.top_model.restore_coae(model_param.coae_bytes)
  160. def get_hetero_nn_model_meta(self):
  161. model_meta = HeteroNNModelMeta()
  162. model_meta.config_type = self.config_type
  163. model_meta.bottom_nn_define.append(json.dumps(self.bottom_nn_define))
  164. model_meta.top_nn_define.append(json.dumps(self.top_nn_define))
  165. model_meta.interactive_layer_define = json.dumps(
  166. self.interactive_layer_define)
  167. model_meta.interactive_layer_lr = self.hetero_nn_param.interactive_layer_lr
  168. optimizer_param = OptimizerParam()
  169. model_meta.loss = json.dumps(self.loss)
  170. optimizer_param.optimizer = self.optimizer['optimizer']
  171. tmp_dict = copy.deepcopy(self.optimizer)
  172. tmp_dict.pop('optimizer')
  173. optimizer_param.kwargs = json.dumps(tmp_dict)
  174. model_meta.optimizer_param.CopyFrom(optimizer_param)
  175. return model_meta
  176. def set_hetero_nn_model_meta(self, model_meta):
  177. self.config_type = model_meta.config_type
  178. self.bottom_nn_define = json.loads(model_meta.bottom_nn_define[0])
  179. self.top_nn_define = json.loads(model_meta.top_nn_define[0])
  180. self.interactive_layer_define = json.loads(
  181. model_meta.interactive_layer_define)
  182. self.loss = json.loads(model_meta.loss)
  183. if self.optimizer is None:
  184. from types import SimpleNamespace
  185. self.optimizer = SimpleNamespace(optimizer=None, kwargs={})
  186. self.optimizer.optimizer = model_meta.optimizer_param.optimizer
  187. self.optimizer.kwargs = json.loads(
  188. model_meta.optimizer_param.kwargs)
  189. tmp_opt = {'optimizer': self.optimizer.optimizer}
  190. tmp_opt.update(self.optimizer.kwargs)
  191. self.optimizer = tmp_opt
  192. def set_transfer_variable(self, transfer_variable):
  193. self.transfer_variable = transfer_variable
  194. def set_partition(self, partition=1):
  195. self.partition = partition
  196. if self.interactive_model is not None:
  197. self.interactive_model.set_partition(self.partition)
  198. def _init_bottom_select_strategy(self):
  199. if self.selector:
  200. self.bottom_model.set_backward_select_strategy()
  201. self.bottom_model.set_batch(self.batch_size)
  202. def _build_bottom_model(self):
  203. self.bottom_model = BottomModel(
  204. optimizer=self.optimizer,
  205. layer_config=self.bottom_nn_define)
  206. self._init_bottom_select_strategy()
  207. def _restore_bottom_model(self, model_bytes):
  208. self._build_bottom_model()
  209. self.bottom_model.restore_model(model_bytes)
  210. self._init_bottom_select_strategy()
  211. def _init_top_select_strategy(self):
  212. if self.selector:
  213. self.top_model.set_backward_selector_strategy(
  214. selector=self.selector)
  215. self.top_model.set_batch(self.batch_size)
  216. def _build_top_model(self):
  217. if self.top_nn_define is None:
  218. raise ValueError(
  219. 'top nn model define is None, you must define your top model in guest side')
  220. self.top_model = TopModel(
  221. optimizer=self.optimizer,
  222. layer_config=self.top_nn_define,
  223. loss=self.loss,
  224. coae_config=self.coae_param,
  225. label_num=self.label_num
  226. )
  227. self._init_top_select_strategy()
  228. def _restore_top_model(self, model_bytes):
  229. self._build_top_model()
  230. self.top_model.restore_model(model_bytes)
  231. self._init_top_select_strategy()
  232. def _init_inter_layer(self):
  233. self.interactive_model.set_partition(self.partition)
  234. self.interactive_model.set_batch(self.batch_size)
  235. self.interactive_model.set_flow_id('{}_interactive_layer'.format(self.flowid))
  236. if self.selector:
  237. self.interactive_model.set_backward_select_strategy()
  238. def _build_interactive_model(self):
  239. self.interactive_model = HEInteractiveLayerGuest(
  240. params=self.hetero_nn_param,
  241. layer_config=self.interactive_layer_define,
  242. host_num=len(
  243. self.component_properties.host_party_idlist))
  244. self._init_inter_layer()
  245. def _restore_interactive_model(self, interactive_model_param):
  246. self._build_interactive_model()
  247. self.interactive_model.restore_model(interactive_model_param)
  248. self._init_inter_layer()
  249. class HeteroNNHostModel(HeteroNNModel):
  250. def __init__(self, hetero_nn_param, flowid):
  251. super(HeteroNNHostModel, self).__init__()
  252. self.role = consts.HOST
  253. self.bottom_model: BottomModel = None
  254. self.interactive_model = None
  255. self.hetero_nn_param = None
  256. self.seed = 100
  257. self.set_nn_meta(hetero_nn_param)
  258. self.selector = SelectorFactory.get_selector(
  259. hetero_nn_param.selector_param.method,
  260. hetero_nn_param.selector_param.selective_size,
  261. beta=hetero_nn_param.selector_param.beta,
  262. random_rate=hetero_nn_param.selector_param.random_state,
  263. min_prob=hetero_nn_param.selector_param.min_prob)
  264. self.flowid = flowid
  265. def set_nn_meta(self, hetero_nn_param):
  266. self.bottom_nn_define = hetero_nn_param.bottom_nn_define
  267. self.config_type = hetero_nn_param.config_type
  268. self.optimizer = hetero_nn_param.optimizer
  269. self.hetero_nn_param = hetero_nn_param
  270. self.batch_size = hetero_nn_param.batch_size
  271. self.seed = hetero_nn_param.seed
  272. def _build_bottom_model(self):
  273. if self.bottom_nn_define is None:
  274. raise ValueError(
  275. 'bottom nn model define is None, you must define your bottom model in host')
  276. self.bottom_model = BottomModel(
  277. optimizer=self.optimizer,
  278. layer_config=self.bottom_nn_define)
  279. def _restore_bottom_model(self, model_bytes):
  280. self._build_bottom_model()
  281. self.bottom_model.restore_model(model_bytes)
  282. def _build_interactive_model(self):
  283. self.interactive_model = HEInteractiveLayerHost(self.hetero_nn_param)
  284. self.interactive_model.set_partition(self.partition)
  285. self.interactive_model.set_flow_id('{}_interactive_layer'.format(self.flowid))
  286. def _restore_interactive_model(self, interactive_layer_param):
  287. self._build_interactive_model()
  288. self.interactive_model.restore_model(interactive_layer_param)
  289. self.interactive_model.set_partition(self.partition)
  290. self.interactive_model.set_flow_id('{}_interactive_layer'.format(self.flowid))
  291. def set_transfer_variable(self, transfer_variable):
  292. self.transfer_variable = transfer_variable
  293. def set_partition(self, partition=1):
  294. self.partition = partition
  295. if self.interactive_model is not None:
  296. self.interactive_model.set_partition(self.partition)
  297. LOGGER.debug(
  298. "set_partition, partition num is {}".format(
  299. self.partition))
  300. def get_hetero_nn_model_meta(self):
  301. model_meta = HeteroNNModelMeta()
  302. model_meta.config_type = self.config_type
  303. model_meta.bottom_nn_define.append(json.dumps(self.bottom_nn_define))
  304. model_meta.interactive_layer_lr = self.hetero_nn_param.interactive_layer_lr
  305. optimizer_param = OptimizerParam()
  306. optimizer_param.optimizer = self.optimizer['optimizer']
  307. tmp_opt = copy.deepcopy(self.optimizer)
  308. tmp_opt.pop('optimizer')
  309. optimizer_param.kwargs = json.dumps(tmp_opt)
  310. model_meta.optimizer_param.CopyFrom(optimizer_param)
  311. return model_meta
  312. def set_hetero_nn_model_meta(self, model_meta):
  313. self.config_type = model_meta.config_type
  314. self.bottom_nn_define = json.loads(model_meta.bottom_nn_define[0])
  315. if self.optimizer is None:
  316. from types import SimpleNamespace
  317. self.optimizer = SimpleNamespace(optimizer=None, kwargs={})
  318. self.optimizer.optimizer = model_meta.optimizer_param.optimizer
  319. self.optimizer.kwargs = json.loads(
  320. model_meta.optimizer_param.kwargs)
  321. tmp_opt = {'optimizer': self.optimizer.optimizer}
  322. tmp_opt.update(self.optimizer.kwargs)
  323. self.optimizer = tmp_opt
  324. def set_hetero_nn_model_param(self, model_param):
  325. self._restore_bottom_model(model_param.bottom_saved_model_bytes)
  326. self._restore_interactive_model(model_param.interactive_layer_param)
  327. def get_hetero_nn_model_param(self):
  328. model_param = HeteroNNModelParam()
  329. model_param.bottom_saved_model_bytes = self.bottom_model.export_model()
  330. model_param.interactive_layer_param.CopyFrom(
  331. self.interactive_model.export_model())
  332. return model_param
  333. def train(self, x, epoch, batch_idx):
  334. if self.bottom_model is None:
  335. global_seed(self.seed)
  336. self._build_bottom_model()
  337. if self.batch_size == -1:
  338. self.batch_size = x.shape[0]
  339. self._build_interactive_model()
  340. if self.selector:
  341. self.bottom_model.set_backward_select_strategy()
  342. self.bottom_model.set_batch(self.batch_size)
  343. self.interactive_model.set_backward_select_strategy()
  344. self.bottom_model.train_mode(True)
  345. host_bottom_output = self.bottom_model.forward(x)
  346. self.interactive_model.forward(
  347. host_bottom_output, epoch, batch_idx, train=True)
  348. host_gradient, selective_ids = self.interactive_model.backward(
  349. epoch, batch_idx)
  350. self.bottom_model.backward(x, host_gradient, selective_ids)
  351. def predict(self, x, batch=0):
  352. self.bottom_model.train_mode(False)
  353. guest_bottom_output = self.bottom_model.predict(x)
  354. self.interactive_model.forward(
  355. guest_bottom_output,
  356. epoch=self._predict_round,
  357. batch=batch,
  358. train=False)
  359. # prediction procedure has its prediction iteration count, we do this
  360. # to avoid reusing communication suffixes
  361. self.inc_predict_round()