• TensorFlow 2.9的零零碎碎(二)-构建模型


    目录

    tf.keras.models.Sequential

    一般我们见到的构建模型写法

     TensorFlow官网构建模型的写法

    Sequential类的源码


    TensorFlow 2.9的零零碎碎(二)-TensorFlow 2.9的零零碎碎(六)都是围绕使用TensorFlow 2.9在MNIST数据集上训练和评价模型来展开。

    Python环境3.8。

    代码调试都用的PyCharm。

    tf.keras.models.Sequential

    tf.keras.models.Sequential的定义在keras.engine.sequential模块下的Sequential类中。

    tf.keras.layers的定义在keras.engine.xx或者keras.layers.xx中实现。

    Sequential类的源码比较多,贴在文章的最后。

     注意以下两种写法等效

    1. model = tf.keras.models.Sequential()
    2. model = tf.keras.Sequential()

    一般我们见到的构建模型写法

    1. import tensorflow as tf
    2. model = tf.keras.models.Sequential()
    3. model.add(tf.keras.layers.Flatten(input_shape=(28, 28)))
    4. model.add(tf.keras.layers.Dense(128, activation='relu'))
    5. model.add(tf.keras.layers.Dropout(0.2))
    6. model.add(tf.keras.layers.Dense(10, activation='softmax'))

    或者

    1. import tensorflow as tf
    2. model = tf.keras.models.Sequential()
    3. model.add(tf.keras.layers.Input(shape=(28, 28)))
    4. model.add(tf.keras.layers.Flatten())
    5. model.add(tf.keras.layers.Dense(128, activation='relu'))
    6. model.add(tf.keras.layers.Dropout(0.2))
    7. model.add(tf.keras.layers.Dense(10, activation='softmax'))

    断点调试

    可以看到model对象里有一个input_shape,就是我们指定的28*28,还有一个layers,包含了添加的四层

     TensorFlow官网构建模型的写法

    1. import tensorflow as tf
    2. model = tf.keras.models.Sequential([
    3. tf.keras.layers.Flatten(input_shape=(28, 28)),
    4. tf.keras.layers.Dense(128, activation='relu'),
    5. tf.keras.layers.Dropout(0.2),
    6. tf.keras.layers.Dense(10, activation='softmax')
    7. ])

    上述代码没有调用add方法,而是在Sequential类初始化的时候直接将层的列表作为一个参数传了进去

    以下是Sequential类__init__方法的代码。可以看出__init__方法有两个参数,layers和name,默认是None。

    1. @tf.__internal__.tracking.no_automatic_dependency_tracking
    2. @traceback_utils.filter_traceback
    3. def __init__(self, layers=None, name=None):
    4. """Creates a `Sequential` model instance.
    5. Args:
    6. layers: Optional list of layers to add to the model.
    7. name: Optional name for the model.
    8. """
    9. # Skip the init in FunctionalModel since model doesn't have input/output yet
    10. super(functional.Functional, self).__init__( # pylint: disable=bad-super-call
    11. name=name, autocast=False)
    12. base_layer.keras_api_gauge.get_cell('Sequential').set(True)
    13. self.supports_masking = True
    14. self._compute_output_and_mask_jointly = True
    15. self._auto_track_sub_layers = False
    16. self._inferred_input_shape = None
    17. self._has_explicit_input_shape = False
    18. self._input_dtype = None
    19. self._layer_call_argspecs = {}
    20. self._created_nodes = set()
    21. # Flag that indicate whether the sequential network topology has been
    22. # created. It is false when there isn't any layer, or the layers don't
    23. # have an input shape.
    24. self._graph_initialized = False
    25. # Unfortunately some Sequential models using custom layers or FeatureColumn
    26. # layers have multiple inputs. This is fundamentally incompatible with
    27. # most of the Sequential API, and we have to disable a number of features
    28. # for such models.
    29. self._use_legacy_deferred_behavior = False
    30. # Add to the model any layers passed to the constructor.
    31. if layers:
    32. if not isinstance(layers, (list, tuple)):
    33. layers = [layers]
    34. for layer in layers:
    35. self.add(layer)

    在方法的末尾可以看到如下代码。处理传入的层列表的代码就在这里,可以看到,最终还是调用的add方法,和一般我们见到的构建模型写法其实殊途同归

    1. # Add to the model any layers passed to the constructor.
    2. if layers:
    3. if not isinstance(layers, (list, tuple)):
    4. layers = [layers]
    5. for layer in layers:
    6. self.add(layer)

    Sequential类的源码

    1. # Copyright 2015 The TensorFlow Authors. All Rights Reserved.
    2. #
    3. # Licensed under the Apache License, Version 2.0 (the "License");
    4. # you may not use this file except in compliance with the License.
    5. # You may obtain a copy of the License at
    6. #
    7. # http://www.apache.org/licenses/LICENSE-2.0
    8. #
    9. # Unless required by applicable law or agreed to in writing, software
    10. # distributed under the License is distributed on an "AS IS" BASIS,
    11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12. # See the License for the specific language governing permissions and
    13. # limitations under the License.
    14. # ==============================================================================
    15. # pylint: disable=protected-access
    16. """Home of the `Sequential` model."""
    17. import tensorflow.compat.v2 as tf
    18. import copy
    19. from keras import layers as layer_module
    20. from keras.engine import base_layer
    21. from keras.engine import functional
    22. from keras.engine import input_layer
    23. from keras.engine import training_utils
    24. from keras.saving.saved_model import model_serialization
    25. from keras.utils import generic_utils
    26. from keras.utils import layer_utils
    27. from keras.utils import tf_inspect
    28. from keras.utils import tf_utils
    29. from keras.utils import traceback_utils
    30. from tensorflow.python.platform import tf_logging as logging
    31. from tensorflow.python.util.tf_export import keras_export
    32. SINGLE_LAYER_OUTPUT_ERROR_MSG = ('All layers in a Sequential model should have '
    33. 'a single output tensor. For multi-output '
    34. 'layers, use the functional API.')
    35. @keras_export('keras.Sequential', 'keras.models.Sequential')
    36. class Sequential(functional.Functional):
    37. """`Sequential` groups a linear stack of layers into a `tf.keras.Model`.
    38. `Sequential` provides training and inference features on this model.
    39. Examples:
    40. ```python
    41. # Optionally, the first layer can receive an `input_shape` argument:
    42. model = tf.keras.Sequential()
    43. model.add(tf.keras.layers.Dense(8, input_shape=(16,)))
    44. # Afterwards, we do automatic shape inference:
    45. model.add(tf.keras.layers.Dense(4))
    46. # This is identical to the following:
    47. model = tf.keras.Sequential()
    48. model.add(tf.keras.Input(shape=(16,)))
    49. model.add(tf.keras.layers.Dense(8))
    50. # Note that you can also omit the `input_shape` argument.
    51. # In that case the model doesn't have any weights until the first call
    52. # to a training/evaluation method (since it isn't yet built):
    53. model = tf.keras.Sequential()
    54. model.add(tf.keras.layers.Dense(8))
    55. model.add(tf.keras.layers.Dense(4))
    56. # model.weights not created yet
    57. # Whereas if you specify the input shape, the model gets built
    58. # continuously as you are adding layers:
    59. model = tf.keras.Sequential()
    60. model.add(tf.keras.layers.Dense(8, input_shape=(16,)))
    61. model.add(tf.keras.layers.Dense(4))
    62. len(model.weights)
    63. # Returns "4"
    64. # When using the delayed-build pattern (no input shape specified), you can
    65. # choose to manually build your model by calling
    66. # `build(batch_input_shape)`:
    67. model = tf.keras.Sequential()
    68. model.add(tf.keras.layers.Dense(8))
    69. model.add(tf.keras.layers.Dense(4))
    70. model.build((None, 16))
    71. len(model.weights)
    72. # Returns "4"
    73. # Note that when using the delayed-build pattern (no input shape specified),
    74. # the model gets built the first time you call `fit`, `eval`, or `predict`,
    75. # or the first time you call the model on some input data.
    76. model = tf.keras.Sequential()
    77. model.add(tf.keras.layers.Dense(8))
    78. model.add(tf.keras.layers.Dense(1))
    79. model.compile(optimizer='sgd', loss='mse')
    80. # This builds the model for the first time:
    81. model.fit(x, y, batch_size=32, epochs=10)
    82. ```
    83. """
    84. @tf.__internal__.tracking.no_automatic_dependency_tracking
    85. @traceback_utils.filter_traceback
    86. def __init__(self, layers=None, name=None):
    87. """Creates a `Sequential` model instance.
    88. Args:
    89. layers: Optional list of layers to add to the model.
    90. name: Optional name for the model.
    91. """
    92. # Skip the init in FunctionalModel since model doesn't have input/output yet
    93. super(functional.Functional, self).__init__( # pylint: disable=bad-super-call
    94. name=name, autocast=False)
    95. base_layer.keras_api_gauge.get_cell('Sequential').set(True)
    96. self.supports_masking = True
    97. self._compute_output_and_mask_jointly = True
    98. self._auto_track_sub_layers = False
    99. self._inferred_input_shape = None
    100. self._has_explicit_input_shape = False
    101. self._input_dtype = None
    102. self._layer_call_argspecs = {}
    103. self._created_nodes = set()
    104. # Flag that indicate whether the sequential network topology has been
    105. # created. It is false when there isn't any layer, or the layers don't
    106. # have an input shape.
    107. self._graph_initialized = False
    108. # Unfortunately some Sequential models using custom layers or FeatureColumn
    109. # layers have multiple inputs. This is fundamentally incompatible with
    110. # most of the Sequential API, and we have to disable a number of features
    111. # for such models.
    112. self._use_legacy_deferred_behavior = False
    113. # Add to the model any layers passed to the constructor.
    114. if layers:
    115. if not isinstance(layers, (list, tuple)):
    116. layers = [layers]
    117. for layer in layers:
    118. self.add(layer)
    119. @property
    120. def layers(self):
    121. # Historically, `sequential.layers` only returns layers that were added
    122. # via `add`, and omits the auto-generated `InputLayer` that comes at the
    123. # bottom of the stack.
    124. # `Trackable` manages the `_layers` attributes and does filtering
    125. # over it.
    126. layers = super(Sequential, self).layers
    127. if layers and isinstance(layers[0], input_layer.InputLayer):
    128. return layers[1:]
    129. return layers[:]
    130. @tf.__internal__.tracking.no_automatic_dependency_tracking
    131. @traceback_utils.filter_traceback
    132. def add(self, layer):
    133. """Adds a layer instance on top of the layer stack.
    134. Args:
    135. layer: layer instance.
    136. Raises:
    137. TypeError: If `layer` is not a layer instance.
    138. ValueError: In case the `layer` argument does not
    139. know its input shape.
    140. ValueError: In case the `layer` argument has
    141. multiple output tensors, or is already connected
    142. somewhere else (forbidden in `Sequential` models).
    143. """
    144. # If we are passed a Keras tensor created by keras.Input(), we can extract
    145. # the input layer from its keras history and use that without any loss of
    146. # generality.
    147. if hasattr(layer, '_keras_history'):
    148. origin_layer = layer._keras_history[0]
    149. if isinstance(origin_layer, input_layer.InputLayer):
    150. layer = origin_layer
    151. if isinstance(layer, tf.Module):
    152. if not isinstance(layer, base_layer.Layer):
    153. layer = functional.ModuleWrapper(layer)
    154. else:
    155. raise TypeError('The added layer must be an instance of class Layer. '
    156. f'Received: layer={layer} of type {type(layer)}.')
    157. tf_utils.assert_no_legacy_layers([layer])
    158. if not self._is_layer_name_unique(layer):
    159. raise ValueError(
    160. 'All layers added to a Sequential model '
    161. f'should have unique names. Name "{layer.name}" is already the name '
    162. 'of a layer in this model. Update the `name` argument '
    163. 'to pass a unique name.')
    164. self.built = False
    165. set_inputs = False
    166. self._maybe_create_attribute('_self_tracked_trackables', [])
    167. if not self._self_tracked_trackables:
    168. if isinstance(layer, input_layer.InputLayer):
    169. # Case where the user passes an Input or InputLayer layer via `add`.
    170. set_inputs = True
    171. else:
    172. batch_shape, dtype = training_utils.get_input_shape_and_dtype(layer)
    173. if batch_shape:
    174. # Instantiate an input layer.
    175. x = input_layer.Input(
    176. batch_shape=batch_shape, dtype=dtype, name=layer.name + '_input')
    177. # This will build the current layer
    178. # and create the node connecting the current layer
    179. # to the input layer we just created.
    180. layer(x)
    181. set_inputs = True
    182. if set_inputs:
    183. outputs = tf.nest.flatten(layer._inbound_nodes[-1].outputs)
    184. if len(outputs) != 1:
    185. raise ValueError(SINGLE_LAYER_OUTPUT_ERROR_MSG)
    186. self.outputs = outputs
    187. self.inputs = layer_utils.get_source_inputs(self.outputs[0])
    188. self.built = True
    189. self._has_explicit_input_shape = True
    190. elif self.outputs:
    191. # If the model is being built continuously on top of an input layer:
    192. # refresh its output.
    193. output_tensor = layer(self.outputs[0])
    194. if len(tf.nest.flatten(output_tensor)) != 1:
    195. raise ValueError(SINGLE_LAYER_OUTPUT_ERROR_MSG)
    196. self.outputs = [output_tensor]
    197. self.built = True
    198. if set_inputs or self._graph_initialized:
    199. self._init_graph_network(self.inputs, self.outputs)
    200. self._graph_initialized = True
    201. else:
    202. self._self_tracked_trackables.append(layer)
    203. self._handle_deferred_layer_dependencies([layer])
    204. self._layer_call_argspecs[layer] = tf_inspect.getfullargspec(layer.call)
    205. @tf.__internal__.tracking.no_automatic_dependency_tracking
    206. @traceback_utils.filter_traceback
    207. def pop(self):
    208. """Removes the last layer in the model.
    209. Raises:
    210. TypeError: if there are no layers in the model.
    211. """
    212. if not self.layers:
    213. raise TypeError('There are no layers in the model.')
    214. layer = self._self_tracked_trackables.pop()
    215. self._layer_call_argspecs.pop(layer)
    216. if not self.layers:
    217. self.outputs = None
    218. self.inputs = None
    219. self.built = False
    220. self._inferred_input_shape = None
    221. self._has_explicit_input_shape = False
    222. self._graph_initialized = False
    223. elif self._graph_initialized:
    224. self.layers[-1]._outbound_nodes = []
    225. self.outputs = [self.layers[-1].output]
    226. self._init_graph_network(self.inputs, self.outputs)
    227. self.built = True
    228. @tf.__internal__.tracking.no_automatic_dependency_tracking
    229. def _build_graph_network_for_inferred_shape(self,
    230. input_shape,
    231. input_dtype=None):
    232. if input_shape is None or not self.layers:
    233. return
    234. if not tf.__internal__.tf2.enabled() or not tf.compat.v1.executing_eagerly_outside_functions():
    235. # This behavior is disabled in V1 or when eager execution is disabled.
    236. return
    237. if (not self._has_explicit_input_shape and
    238. not self._use_legacy_deferred_behavior):
    239. # Determine whether the input shape is novel, i.e. whether the model
    240. # should be rebuilt.
    241. input_shape = tuple(input_shape)
    242. if self._inferred_input_shape is None:
    243. new_shape = input_shape
    244. else:
    245. new_shape = relax_input_shape(self._inferred_input_shape, input_shape)
    246. if (new_shape is not None and new_shape != self._inferred_input_shape):
    247. # A novel shape has been received: we need to rebuild the model.
    248. # In case we are inside a graph function, we step out of it.
    249. with tf.init_scope():
    250. inputs = input_layer.Input(
    251. batch_shape=new_shape,
    252. dtype=input_dtype,
    253. name=self.layers[0].name + '_input')
    254. layer_input = inputs
    255. created_nodes = set()
    256. for layer in self.layers:
    257. # Clear nodes previously created via this method. This prevents
    258. # node accumulation and ensures that e.g. `layer.output` is
    259. # always connected to `model.inputs`
    260. # (this is important e.g. for the feature extraction use case).
    261. # We don't just do `layer._inbound_nodes = []` in order
    262. # not to break shared layers added to Sequential models (which is
    263. # technically illegal as per the `add()` docstring,
    264. # but wasn't previously disabled).
    265. clear_previously_created_nodes(layer, self._created_nodes)
    266. try:
    267. # Create Functional API connection by calling the current layer
    268. layer_output = layer(layer_input)
    269. except: # pylint:disable=bare-except
    270. # Functional API calls may fail for a number of reasons:
    271. # 1) The layer may be buggy. In this case it will be easier for
    272. # the user to debug if we fail on the first call on concrete data,
    273. # instead of our own call on a symbolic input.
    274. # 2) The layer is dynamic (graph-incompatible) and hasn't
    275. # overridden `compute_output_shape`. In this case, it is
    276. # impossible to build a graph network.
    277. # 3) The layer is otherwise incompatible with the Functional API
    278. # (e.g. this is the case for some probabilistic layers that rely
    279. # on hacks and that do not return tensors).
    280. # In all these cases, we should avoid creating a graph network
    281. # (or we simply can't).
    282. self._use_legacy_deferred_behavior = True
    283. return
    284. if len(tf.nest.flatten(layer_output)) != 1:
    285. raise ValueError(SINGLE_LAYER_OUTPUT_ERROR_MSG)
    286. # Keep track of nodes just created above
    287. track_nodes_created_by_last_call(layer, created_nodes)
    288. layer_input = layer_output
    289. outputs = layer_output
    290. self._created_nodes = created_nodes
    291. try:
    292. # Initialize a graph Network. This call will never fail for
    293. # a stack of valid Keras layers.
    294. # However some users have layers that are fundamentally incompatible
    295. # with the Functional API, which do not return tensors. In this
    296. # case, we fall back to the legacy deferred behavior.
    297. # TODO(fchollet): consider raising here, as we should not be
    298. # supporting such layers.
    299. self._init_graph_network(inputs, outputs)
    300. self._graph_initialized = True
    301. except: # pylint:disable=bare-except
    302. self._use_legacy_deferred_behavior = True
    303. self._inferred_input_shape = new_shape
    304. @generic_utils.default
    305. def build(self, input_shape=None):
    306. if self._graph_initialized:
    307. self._init_graph_network(self.inputs, self.outputs)
    308. else:
    309. if input_shape is None:
    310. raise ValueError('You must provide an `input_shape` argument.')
    311. self._build_graph_network_for_inferred_shape(input_shape)
    312. if not self.built:
    313. input_shape = tuple(input_shape)
    314. self._build_input_shape = input_shape
    315. super(Sequential, self).build(input_shape)
    316. self.built = True
    317. def call(self, inputs, training=None, mask=None): # pylint: disable=redefined-outer-name
    318. # If applicable, update the static input shape of the model.
    319. if not self._has_explicit_input_shape:
    320. if not tf.is_tensor(inputs) and not isinstance(
    321. inputs, tf.Tensor):
    322. # This is a Sequential with multiple inputs. This is technically an
    323. # invalid use case of Sequential, but we tolerate it for backwards
    324. # compatibility.
    325. self._use_legacy_deferred_behavior = True
    326. self._build_input_shape = tf.nest.map_structure(
    327. _get_shape_tuple, inputs)
    328. if tf.__internal__.tf2.enabled():
    329. logging.warning('Layers in a Sequential model should only have a '
    330. f'single input tensor. Received: inputs={inputs}. '
    331. 'Consider rewriting this model with the Functional '
    332. 'API.')
    333. else:
    334. self._build_graph_network_for_inferred_shape(inputs.shape, inputs.dtype)
    335. if self._graph_initialized:
    336. if not self.built:
    337. self._init_graph_network(self.inputs, self.outputs)
    338. return super(Sequential, self).call(inputs, training=training, mask=mask)
    339. outputs = inputs # handle the corner case where self.layers is empty
    340. for layer in self.layers:
    341. # During each iteration, `inputs` are the inputs to `layer`, and `outputs`
    342. # are the outputs of `layer` applied to `inputs`. At the end of each
    343. # iteration `inputs` is set to `outputs` to prepare for the next layer.
    344. kwargs = {}
    345. argspec = self._layer_call_argspecs[layer].args
    346. if 'mask' in argspec:
    347. kwargs['mask'] = mask
    348. if 'training' in argspec:
    349. kwargs['training'] = training
    350. outputs = layer(inputs, **kwargs)
    351. if len(tf.nest.flatten(outputs)) != 1:
    352. raise ValueError(SINGLE_LAYER_OUTPUT_ERROR_MSG)
    353. # `outputs` will be the inputs to the next layer.
    354. inputs = outputs
    355. mask = getattr(outputs, '_keras_mask', None)
    356. return outputs
    357. def compute_output_shape(self, input_shape):
    358. shape = input_shape
    359. for layer in self.layers:
    360. shape = layer.compute_output_shape(shape)
    361. return shape
    362. def compute_mask(self, inputs, mask):
    363. # TODO(omalleyt): b/123540974 This function is not really safe to call
    364. # by itself because it will duplicate any updates and losses in graph
    365. # mode by `call`ing the Layers again.
    366. outputs = self.call(inputs, mask=mask) # pylint: disable=unexpected-keyword-arg
    367. return getattr(outputs, '_keras_mask', None)
    368. def get_config(self):
    369. layer_configs = []
    370. for layer in super(Sequential, self).layers:
    371. # `super().layers` include the InputLayer if available (it is filtered out
    372. # of `self.layers`). Note that `self._self_tracked_trackables` is managed
    373. # by the tracking infrastructure and should not be used.
    374. layer_configs.append(generic_utils.serialize_keras_object(layer))
    375. config = {
    376. 'name': self.name,
    377. 'layers': copy.deepcopy(layer_configs)
    378. }
    379. if not self._is_graph_network and self._build_input_shape is not None:
    380. config['build_input_shape'] = self._build_input_shape
    381. return config
    382. @classmethod
    383. def from_config(cls, config, custom_objects=None):
    384. if 'name' in config:
    385. name = config['name']
    386. build_input_shape = config.get('build_input_shape')
    387. layer_configs = config['layers']
    388. else:
    389. name = None
    390. build_input_shape = None
    391. layer_configs = config
    392. model = cls(name=name)
    393. for layer_config in layer_configs:
    394. layer = layer_module.deserialize(layer_config,
    395. custom_objects=custom_objects)
    396. model.add(layer)
    397. if (not model.inputs and build_input_shape and
    398. isinstance(build_input_shape, (tuple, list))):
    399. model.build(build_input_shape)
    400. return model
    401. @property
    402. def input_spec(self):
    403. if hasattr(self, '_manual_input_spec'):
    404. return self._manual_input_spec
    405. if self._has_explicit_input_shape:
    406. return super().input_spec
    407. return None
    408. @input_spec.setter
    409. def input_spec(self, value):
    410. self._manual_input_spec = value
    411. @property
    412. def _trackable_saved_model_saver(self):
    413. return model_serialization.SequentialSavedModelSaver(self)
    414. def _is_layer_name_unique(self, layer):
    415. for ref_layer in self.layers:
    416. if layer.name == ref_layer.name and ref_layer is not layer:
    417. return False
    418. return True
    419. def _assert_weights_created(self):
    420. if self._graph_initialized:
    421. return
    422. # When the graph has not been initialized, use the Model's implementation to
    423. # to check if the weights has been created.
    424. super(functional.Functional, self)._assert_weights_created() # pylint: disable=bad-super-call
    425. def _get_shape_tuple(t):
    426. if hasattr(t, 'shape'):
    427. shape = t.shape
    428. if isinstance(shape, tuple):
    429. return shape
    430. if shape.rank is not None:
    431. return tuple(shape.as_list())
    432. return None
    433. return None
    434. def relax_input_shape(shape_1, shape_2):
    435. if shape_1 is None or shape_2 is None:
    436. return None
    437. if len(shape_1) != len(shape_2):
    438. return None
    439. return tuple(None if d1 != d2 else d1 for d1, d2 in zip(shape_1, shape_2))
    440. def clear_previously_created_nodes(layer, created_nodes):
    441. """Remove nodes from `created_nodes` from the layer's inbound_nodes."""
    442. for node in layer._inbound_nodes:
    443. prev_layers = node.inbound_layers
    444. for prev_layer in tf.nest.flatten(prev_layers):
    445. prev_layer._outbound_nodes = [
    446. n for n in prev_layer._outbound_nodes
    447. if n not in created_nodes]
    448. layer._inbound_nodes = [
    449. n for n in layer._inbound_nodes if n not in created_nodes]
    450. def track_nodes_created_by_last_call(layer, created_nodes):
    451. """Adds to `created_nodes` the nodes created by the last call to `layer`."""
    452. if not layer._inbound_nodes:
    453. return
    454. created_nodes.add(layer._inbound_nodes[-1])
    455. prev_layers = layer._inbound_nodes[-1].inbound_layers
    456. for prev_layer in tf.nest.flatten(prev_layers):
    457. if prev_layer._outbound_nodes:
    458. created_nodes.add(prev_layer._outbound_nodes[-1])

  • 相关阅读:
    Scrum敏捷开发企业实战培训
    我自己理解的JAVA反射
    高等教育学备考:教育学概述
    天选之子C++是如何发展起来的?如何学习C++呢?
    Linux环境下Arm端源码编译OpenCV+ncnn目标检测模型实例运行调试完整实践记录
    (C++17) optional的使用
    微机保护装置智能操控及无线测温等产品在某助剂厂新建项目的应用
    Error occurred while trying to proxy request项目突然起不来了
    中央空调系统运行原理以及相关设备介绍
    使用Docker部署Apache Superset并实现公网远程访问
  • 原文地址:https://blog.csdn.net/ytomc/article/details/126259427