-
Notifications
You must be signed in to change notification settings - Fork 30
/
training.py
4475 lines (3928 loc) · 189 KB
/
training.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright 2015 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Training-related part of the TF-Keras engine."""
import copy
import itertools
import json
import warnings
import weakref
import numpy as np
import tensorflow.compat.v2 as tf
from tensorflow.python.distribute import distribute_utils
from tensorflow.python.distribute import input_ops
from tensorflow.python.eager import context
from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.util.tf_export import keras_export
from tensorflow.tools.docs import doc_controls
from tf_keras import backend
from tf_keras import callbacks as callbacks_module
from tf_keras import optimizers
from tf_keras.dtensor import dtensor_api
from tf_keras.dtensor import layout_map as layout_map_lib
from tf_keras.engine import base_layer
from tf_keras.engine import base_layer_utils
from tf_keras.engine import compile_utils
from tf_keras.engine import data_adapter
from tf_keras.engine import input_layer as input_layer_module
from tf_keras.engine import training_utils
from tf_keras.metrics import base_metric
from tf_keras.mixed_precision import loss_scale_optimizer as lso
from tf_keras.optimizers import optimizer
from tf_keras.optimizers import optimizer_v1
from tf_keras.saving import pickle_utils
from tf_keras.saving import saving_api
from tf_keras.saving import saving_lib
from tf_keras.saving import serialization_lib
from tf_keras.saving.legacy import serialization
from tf_keras.saving.legacy.saved_model import json_utils
from tf_keras.saving.legacy.saved_model import model_serialization
from tf_keras.utils import generic_utils
from tf_keras.utils import io_utils
from tf_keras.utils import layer_utils
from tf_keras.utils import steps_per_execution_tuning
from tf_keras.utils import tf_inspect
from tf_keras.utils import tf_utils
from tf_keras.utils import traceback_utils
from tf_keras.utils import version_utils
from tf_keras.utils.mode_keys import ModeKeys
try:
import h5py
except ImportError:
h5py = None
@keras_export("keras.Model", "keras.models.Model")
class Model(base_layer.Layer, version_utils.ModelVersionSelector):
"""A model grouping layers into an object with training/inference features.
Args:
inputs: The input(s) of the model: a `keras.Input` object or a
combination of `keras.Input` objects in a dict, list or tuple.
outputs: The output(s) of the model: a tensor that originated from
`keras.Input` objects or a combination of such tensors in a dict,
list or tuple. See Functional API example below.
name: String, the name of the model.
There are two ways to instantiate a `Model`:
1 - With the "Functional API", where you start from `Input`,
you chain layer calls to specify the model's forward pass,
and finally you create your model from inputs and outputs:
```python
import tensorflow as tf
inputs = tf.keras.Input(shape=(3,))
x = tf.keras.layers.Dense(4, activation=tf.nn.relu)(inputs)
outputs = tf.keras.layers.Dense(5, activation=tf.nn.softmax)(x)
model = tf.keras.Model(inputs=inputs, outputs=outputs)
```
Note: Only dicts, lists, and tuples of input tensors are supported. Nested
inputs are not supported (e.g. lists of list or dicts of dict).
A new Functional API model can also be created by using the
intermediate tensors. This enables you to quickly extract sub-components
of the model.
Example:
```python
inputs = keras.Input(shape=(None, None, 3))
processed = keras.layers.RandomCrop(width=32, height=32)(inputs)
conv = keras.layers.Conv2D(filters=2, kernel_size=3)(processed)
pooling = keras.layers.GlobalAveragePooling2D()(conv)
feature = keras.layers.Dense(10)(pooling)
full_model = keras.Model(inputs, feature)
backbone = keras.Model(processed, conv)
activations = keras.Model(conv, feature)
```
Note that the `backbone` and `activations` models are not
created with `keras.Input` objects, but with the tensors that are originated
from `keras.Input` objects. Under the hood, the layers and weights will
be shared across these models, so that user can train the `full_model`, and
use `backbone` or `activations` to do feature extraction.
The inputs and outputs of the model can be nested structures of tensors as
well, and the created models are standard Functional API models that support
all the existing APIs.
2 - By subclassing the `Model` class: in that case, you should define your
layers in `__init__()` and you should implement the model's forward pass
in `call()`.
```python
import tensorflow as tf
class MyModel(tf.keras.Model):
def __init__(self):
super().__init__()
self.dense1 = tf.keras.layers.Dense(4, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(5, activation=tf.nn.softmax)
def call(self, inputs):
x = self.dense1(inputs)
return self.dense2(x)
model = MyModel()
```
If you subclass `Model`, you can optionally have
a `training` argument (boolean) in `call()`, which you can use to specify
a different behavior in training and inference:
```python
import tensorflow as tf
class MyModel(tf.keras.Model):
def __init__(self):
super().__init__()
self.dense1 = tf.keras.layers.Dense(4, activation=tf.nn.relu)
self.dense2 = tf.keras.layers.Dense(5, activation=tf.nn.softmax)
self.dropout = tf.keras.layers.Dropout(0.5)
def call(self, inputs, training=False):
x = self.dense1(inputs)
if training:
x = self.dropout(x, training=training)
return self.dense2(x)
model = MyModel()
```
Once the model is created, you can config the model with losses and metrics
with `model.compile()`, train the model with `model.fit()`, or use the model
to do prediction with `model.predict()`.
"""
_TF_MODULE_IGNORED_PROPERTIES = frozenset(
itertools.chain(
(
"_train_counter",
"_test_counter",
"_predict_counter",
"_steps_per_execution",
"_compiled_trainable_state",
),
base_layer.Layer._TF_MODULE_IGNORED_PROPERTIES,
)
)
_SCALAR_UPRANKING_ON = False
def __new__(cls, *args, **kwargs):
# Signature detection
if is_functional_model_init_params(args, kwargs) and cls == Model:
# Functional model
from tf_keras.engine import functional
return functional.Functional(skip_init=True, *args, **kwargs)
else:
return super(Model, cls).__new__(cls, *args, **kwargs)
@tf.__internal__.tracking.no_automatic_dependency_tracking
@traceback_utils.filter_traceback
def __init__(self, *args, **kwargs):
self._is_model_for_instrumentation = True
# Special case for Subclassed Functional Model, which we couldn't detect
# when __new__ is called. We only realize it is a functional model when
# it calls super.__init__ with input and output tensor.
from tf_keras.engine import functional
if is_functional_model_init_params(args, kwargs) and not isinstance(
self, functional.Functional
):
# Filter the kwargs for multiple inheritance.
supported_kwargs = [
"inputs",
"outputs",
"name",
"trainable",
"skip_init",
]
model_kwargs = {
k: kwargs[k] for k in kwargs if k in supported_kwargs
}
other_kwargs = {
k: kwargs[k] for k in kwargs if k not in supported_kwargs
}
inject_functional_model_class(self.__class__)
functional.Functional.__init__(self, *args, **model_kwargs)
# In case there is any multiple inheritance here, we need to call
# the __init__ for any class that appears after the Functional
# class.
clz_to_init = []
found_functional_class = False
for clz in self.__class__.__bases__:
if issubclass(clz, functional.Functional):
found_functional_class = True
continue
if found_functional_class:
clz_to_init.append(clz)
if clz_to_init:
for clz in clz_to_init:
clz.__init__(self, *args, **other_kwargs)
elif other_kwargs:
# In case there are unused kwargs, we should raise an error to
# user, in case they have a typo in the param name.
raise TypeError(
"The following keyword arguments passed to `Model` aren't "
"supported: {}.".format(other_kwargs)
)
return
# The following are implemented as property functions:
# self.trainable_weights
# self.non_trainable_weights
# `inputs` / `outputs` will only appear in kwargs if either are
# misspelled.
generic_utils.validate_kwargs(
kwargs,
{
"trainable",
"dtype",
"dynamic",
"name",
"autocast",
"inputs",
"outputs",
},
)
super().__init__(**kwargs)
# By default, Model is a subclass model, which is not in graph network.
self._is_graph_network = False
self.inputs = None
self.outputs = None
self.input_names = None
self.output_names = None
# stop_training is used by callback to stop training when error happens
self.stop_training = False
self.history = None
# These objects are used in the default `Model.compile`. They are not
# guaranteed to be set after `Model.compile` is called, as users can
# override compile with custom logic.
self.compiled_loss = None
self.compiled_metrics = None
# This is True for Sequential networks and Functional networks.
self._compute_output_and_mask_jointly = False
# Don't reset compilation if already done. This may occur if calling
# `__init__` (or `_init_graph_network`) on an already-compiled model
# such as a Sequential model. Sequential models may need to rebuild
# themselves after compilation.
self._maybe_create_attribute("_is_compiled", False)
self._maybe_create_attribute("optimizer", None)
# Model must be created under scope of DistStrat it will be trained
# with.
if tf.distribute.has_strategy():
self._distribution_strategy = tf.distribute.get_strategy()
else:
self._distribution_strategy = None
self._distribute_reduction_method = None
self._cluster_coordinator = None
# Defaults to value of `tf.config.experimental_functions_run_eagerly`.
self._run_eagerly = None
# Initialize cache attrs.
self._reset_compile_cache()
# Fault-tolerance handler. Set in `ModelCheckpoint`.
self._training_state = None
self._saved_model_inputs_spec = None
self._saved_model_arg_spec = None
self._checkpoint = tf.train.Checkpoint(root=weakref.ref(self))
self._steps_per_execution = None
self._steps_per_execution_tuner = None
self._autotune_steps_per_execution = False
self._layout_map = layout_map_lib.get_current_layout_map()
self._init_batch_counters()
self._base_model_initialized = True
# `jit_compile` starts off with None as default and gets overwritten by
# the value specified in `Model.compile`, and this is effective for
# `fit`, `evaluate`, and `predict`.
self._jit_compile = None
def _create_counter_variable(self, init_value):
"""Helper function for counter variable creation.
For the DTensor use case with layout map, since the variable are not
tracked by model, they can't be visited by the layout map, and need to
be properly initialized as DVariable.
"""
# This function should be removed after we move to the strategy based
# implementation for DTensor.
if self._layout_map is None:
agg = tf.VariableAggregation.ONLY_FIRST_REPLICA
return tf.Variable(init_value, dtype="int64", aggregation=agg)
else:
layout = dtensor_api.Layout.replicated(
mesh=self._layout_map.get_default_mesh(), rank=0
)
return dtensor_api.DVariable(
init_value, dtype="int64", layout=layout
)
@tf.__internal__.tracking.no_automatic_dependency_tracking
def _init_batch_counters(self):
# Untracked Variables, used to keep track of mini-batches seen in `fit`,
# `evaluate`, and `predict`.
if not tf.inside_function():
# Creating variables inside tf.function is not allowed, hence
# these would otherwise prevent users from creating TF-Keras layers
# inside tf.function.
# These variables are not connected to outputs so they have no
# effect on graph generation anyway.
self._train_counter = self._create_counter_variable(0)
self._test_counter = self._create_counter_variable(0)
self._predict_counter = self._create_counter_variable(0)
def __setattr__(self, name, value):
if not getattr(self, "_self_setattr_tracking", True):
super().__setattr__(name, value)
return
if all(
isinstance(v, (base_layer.Layer, tf.Variable))
or base_layer_utils.has_weights(v)
for v in tf.nest.flatten(value)
):
try:
self._base_model_initialized
except AttributeError:
raise RuntimeError(
"It looks like you are subclassing `Model` and you "
"forgot to call `super().__init__()`."
" Always start with this line."
)
super().__setattr__(name, value)
def __reduce__(self):
if self.built:
return (
pickle_utils.deserialize_model_from_bytecode,
(pickle_utils.serialize_model_as_bytecode(self),),
)
else:
# SavedModel (and hence serialize_model_as_bytecode) only support
# built models, but if the model is not built,
# it may be possible to serialize as a plain Python object,
# as long as the constituent parts (layers, optimizers, losses,
# etc.) can be serialized as plain Python objects. Thus we call up
# the superclass hierarchy to get an implementation of __reduce__
# that can pickle this Model as a plain Python object.
return super().__reduce__()
def __deepcopy__(self, memo):
if self.built:
new = pickle_utils.deserialize_model_from_bytecode(
pickle_utils.serialize_model_as_bytecode(self)
)
memo[id(self)] = new
else:
# See comment in __reduce__ for explanation
deserializer, serialized, *rest = super().__reduce__()
new = deserializer(*serialized)
memo[id(self)] = new
if rest:
state = copy.deepcopy(rest[0], memo=memo)
new.__setstate__(state)
return new
def __copy__(self):
return self.__deepcopy__({})
@generic_utils.default
def build(self, input_shape):
"""Builds the model based on input shapes received.
This is to be used for subclassed models, which do not know at
instantiation time what their inputs look like.
This method only exists for users who want to call `model.build()` in a
standalone way (as a substitute for calling the model on real data to
build it). It will never be called by the framework (and thus it will
never throw unexpected errors in an unrelated workflow).
Args:
input_shape: Single tuple, `TensorShape` instance, or list/dict of
shapes, where shapes are tuples, integers, or `TensorShape`
instances.
Raises:
ValueError:
1. In case of invalid user-provided data (not of type tuple,
list, `TensorShape`, or dict).
2. If the model requires call arguments that are agnostic
to the input shapes (positional or keyword arg in call
signature).
3. If not all layers were properly built.
4. If float type inputs are not supported within the layers.
In each of these cases, the user should build their model by calling
it on real tensor data.
"""
if self._is_graph_network:
super().build(input_shape)
return
if input_shape is None:
raise ValueError(
"Input shape must be defined when calling `build()` on "
"a `Model` subclass."
)
valid_types = (tuple, list, tf.TensorShape, dict)
if not isinstance(input_shape, valid_types):
raise ValueError(
"Specified input shape is not one of the valid types. "
"Please specify a batch input shape of type tuple or "
"list of input shapes. User provided "
"input type: {}.".format(type(input_shape))
)
if input_shape and not self.inputs:
# We create placeholders for the `None`s in the shape and build the
# model in a Graph. Since tf.Variable is compatible with both eager
# execution and graph building, the variables created after building
# the model in a Graph are still valid when executing eagerly.
if tf.executing_eagerly():
graph = tf.__internal__.FuncGraph("build_graph")
else:
graph = backend.get_graph()
with graph.as_default():
if isinstance(input_shape, list) and all(
d is None or isinstance(d, int) for d in input_shape
):
input_shape = tuple(input_shape)
if isinstance(input_shape, list):
x = [
base_layer_utils.generate_placeholders_from_shape(shape)
for shape in input_shape
]
elif isinstance(input_shape, dict):
x = {
k: base_layer_utils.generate_placeholders_from_shape(
shape
)
for k, shape in input_shape.items()
}
else:
x = base_layer_utils.generate_placeholders_from_shape(
input_shape
)
kwargs = {}
call_signature = self._call_spec.full_argspec
call_args = call_signature.args
# Exclude `self`, `inputs`, and any argument with a default
# value.
if len(call_args) > 2:
if call_signature.defaults:
call_args = call_args[2 : -len(call_signature.defaults)]
else:
call_args = call_args[2:]
for arg in call_args:
if arg == "training":
# Case where `training` is a positional arg with no
# default.
kwargs["training"] = False
else:
# Has invalid call signature with unknown positional
# arguments.
raise ValueError(
"Currently, you cannot build your model if it "
"has positional or keyword arguments that are "
"not inputs to the model, but are required for "
"its `call()` method. Instead, in order to "
"instantiate and build your model, `call()` "
"your model on real tensor data with all "
"expected call arguments. The argument "
"for `call()` can be a single list/tuple that "
"contains multiple inputs."
)
elif len(call_args) < 2:
# Signature without `inputs`.
raise ValueError(
"You can only call `build()` on a model if its "
"`call()` method accepts an `inputs` argument."
)
try:
self.call(x, **kwargs)
except (tf.errors.InvalidArgumentError, TypeError) as e:
raise ValueError(
"You cannot build your model by calling `build` "
"if your layers do not support float type inputs. "
"Instead, in order to instantiate and build your "
"model, call your model on real tensor data (of "
"the correct dtype).\n\nThe actual error from "
f"`call` is: {e}."
)
super().build(input_shape)
@traceback_utils.filter_traceback
def __call__(self, *args, **kwargs):
if self._layout_map is not None and not self.built:
# Note that this method is only overridden for DTensor and layout
# injection purpose.
# Capture the inputs and create graph input as replacement for model
# to initialize its weights first.
copied_args = copy.copy(args)
copied_kwargs = copy.copy(kwargs)
(
inputs,
copied_args,
copied_kwargs,
) = self._call_spec.split_out_first_arg(copied_args, copied_kwargs)
def _convert_to_graph_inputs(x):
if isinstance(x, (tf.Tensor, np.ndarray, float, int)):
x = tf.convert_to_tensor(x)
return input_layer_module.Input(x.shape)
# TODO(scottzhu): maybe better handle mask and training flag.
inputs = tf.nest.map_structure(_convert_to_graph_inputs, inputs)
copied_args = tf.nest.map_structure(
_convert_to_graph_inputs, copied_args
)
copied_kwargs = tf.nest.map_structure(
_convert_to_graph_inputs, copied_kwargs
)
with layout_map_lib.layout_map_scope(self._layout_map):
# We ignore the result here.
super().__call__(inputs, *copied_args, **copied_kwargs)
layout_map_lib._map_subclass_model_variable(self, self._layout_map)
return super().__call__(*args, **kwargs)
@doc_controls.doc_in_current_and_subclasses
def call(self, inputs, training=None, mask=None):
"""Calls the model on new inputs and returns the outputs as tensors.
In this case `call()` just reapplies
all ops in the graph to the new inputs
(e.g. build a new computational graph from the provided inputs).
Note: This method should not be called directly. It is only meant to be
overridden when subclassing `tf.keras.Model`.
To call a model on an input, always use the `__call__()` method,
i.e. `model(inputs)`, which relies on the underlying `call()` method.
Args:
inputs: Input tensor, or dict/list/tuple of input tensors.
training: Boolean or boolean scalar tensor, indicating whether to
run the `Network` in training mode or inference mode.
mask: A mask or list of masks. A mask can be either a boolean tensor
or None (no mask). For more details, check the guide
[here](https://www.tensorflow.org/guide/keras/masking_and_padding).
Returns:
A tensor if there is a single output, or
a list of tensors if there are more than one outputs.
"""
raise NotImplementedError(
"Unimplemented `tf.keras.Model.call()`: if you "
"intend to create a `Model` with the Functional "
"API, please provide `inputs` and `outputs` "
"arguments. Otherwise, subclass `Model` with an "
"overridden `call()` method."
)
@traceback_utils.filter_traceback
def compile(
self,
optimizer="rmsprop",
loss=None,
metrics=None,
loss_weights=None,
weighted_metrics=None,
run_eagerly=None,
steps_per_execution=None,
jit_compile=None,
pss_evaluation_shards=0,
**kwargs,
):
"""Configures the model for training.
Example:
```python
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),
loss=tf.keras.losses.BinaryCrossentropy(),
metrics=[tf.keras.metrics.BinaryAccuracy(),
tf.keras.metrics.FalseNegatives()])
```
Args:
optimizer: String (name of optimizer) or optimizer instance. See
`tf.keras.optimizers`.
loss: Loss function. May be a string (name of loss function), or
a `tf.keras.losses.Loss` instance. See `tf.keras.losses`. A loss
function is any callable with the signature `loss = fn(y_true,
y_pred)`, where `y_true` are the ground truth values, and
`y_pred` are the model's predictions.
`y_true` should have shape
`(batch_size, d0, .. dN)` (except in the case of
sparse loss functions such as
sparse categorical crossentropy which expects integer arrays of
shape `(batch_size, d0, .. dN-1)`).
`y_pred` should have shape `(batch_size, d0, .. dN)`.
The loss function should return a float tensor.
If a custom `Loss` instance is
used and reduction is set to `None`, return value has shape
`(batch_size, d0, .. dN-1)` i.e. per-sample or per-timestep loss
values; otherwise, it is a scalar. If the model has multiple
outputs, you can use a different loss on each output by passing a
dictionary or a list of losses. The loss value that will be
minimized by the model will then be the sum of all individual
losses, unless `loss_weights` is specified.
metrics: List of metrics to be evaluated by the model during
training and testing. Each of this can be a string (name of a
built-in function), function or a `tf.keras.metrics.Metric`
instance. See `tf.keras.metrics`. Typically you will use
`metrics=['accuracy']`.
A function is any callable with the signature `result = fn(y_true,
y_pred)`. To specify different metrics for different outputs of a
multi-output model, you could also pass a dictionary, such as
`metrics={'output_a':'accuracy', 'output_b':['accuracy', 'mse']}`.
You can also pass a list to specify a metric or a list of metrics
for each output, such as
`metrics=[['accuracy'], ['accuracy', 'mse']]`
or `metrics=['accuracy', ['accuracy', 'mse']]`. When you pass the
strings 'accuracy' or 'acc', we convert this to one of
`tf.keras.metrics.BinaryAccuracy`,
`tf.keras.metrics.CategoricalAccuracy`,
`tf.keras.metrics.SparseCategoricalAccuracy` based on the shapes
of the targets and of the model output. We do a similar
conversion for the strings 'crossentropy' and 'ce' as well.
The metrics passed here are evaluated without sample weighting; if
you would like sample weighting to apply, you can specify your
metrics via the `weighted_metrics` argument instead.
loss_weights: Optional list or dictionary specifying scalar
coefficients (Python floats) to weight the loss contributions of
different model outputs. The loss value that will be minimized by
the model will then be the *weighted sum* of all individual
losses, weighted by the `loss_weights` coefficients. If a list,
it is expected to have a 1:1 mapping to the model's outputs. If a
dict, it is expected to map output names (strings) to scalar
coefficients.
weighted_metrics: List of metrics to be evaluated and weighted by
`sample_weight` or `class_weight` during training and testing.
run_eagerly: Bool. If `True`, this `Model`'s logic will not be
wrapped in a `tf.function`. Recommended to leave this as `None`
unless your `Model` cannot be run inside a `tf.function`.
`run_eagerly=True` is not supported when using
`tf.distribute.experimental.ParameterServerStrategy`. Defaults to
`False`.
steps_per_execution: Int or `'auto'`. The number of batches to
run during each `tf.function` call. If set to "auto", keras will
automatically tune `steps_per_execution` during runtime. Running
multiple batches inside a single `tf.function` call can greatly
improve performance on TPUs, when used with distributed strategies
such as `ParameterServerStrategy`, or with small models with a
large Python overhead. At most, one full epoch will be run each
execution. If a number larger than the size of the epoch is
passed, the execution will be truncated to the size of the epoch.
Note that if `steps_per_execution` is set to `N`,
`Callback.on_batch_begin` and `Callback.on_batch_end` methods will
only be called every `N` batches (i.e. before/after each
`tf.function` execution). Defaults to `1`.
jit_compile: If `True`, compile the model training step with XLA.
[XLA](https://www.tensorflow.org/xla) is an optimizing compiler
for machine learning.
`jit_compile` is not enabled for by default.
Note that `jit_compile=True`
may not necessarily work for all models.
For more information on supported operations please refer to the
[XLA documentation](https://www.tensorflow.org/xla).
Also refer to
[known XLA issues](https://www.tensorflow.org/xla/known_issues)
for more details.
pss_evaluation_shards: Integer or 'auto'. Used for
`tf.distribute.ParameterServerStrategy` training only. This arg
sets the number of shards to split the dataset into, to enable an
exact visitation guarantee for evaluation, meaning the model will
be applied to each dataset element exactly once, even if workers
fail. The dataset must be sharded to ensure separate workers do
not process the same data. The number of shards should be at least
the number of workers for good performance. A value of 'auto'
turns on exact evaluation and uses a heuristic for the number of
shards based on the number of workers. 0, meaning no
visitation guarantee is provided. NOTE: Custom implementations of
`Model.test_step` will be ignored when doing exact evaluation.
Defaults to `0`.
**kwargs: Arguments supported for backwards compatibility only.
"""
if jit_compile and not tf_utils.can_jit_compile(warn=True):
jit_compile = False
self._compile_config = serialization_lib.Config(
optimizer=optimizer,
loss=loss,
metrics=metrics,
loss_weights=loss_weights,
weighted_metrics=weighted_metrics,
run_eagerly=run_eagerly,
steps_per_execution=steps_per_execution,
jit_compile=jit_compile,
)
with self.distribute_strategy.scope():
if "experimental_steps_per_execution" in kwargs:
logging.warning(
"The argument `steps_per_execution` is no longer "
"experimental. Pass `steps_per_execution` instead of "
"`experimental_steps_per_execution`."
)
if not steps_per_execution:
steps_per_execution = kwargs.pop(
"experimental_steps_per_execution"
)
# When compiling from an already-serialized model, we do not want to
# reapply some processing steps (e.g. metric renaming for
# multi-output models, which have prefixes added for each
# corresponding output name).
from_serialized = kwargs.pop("from_serialized", False)
self._validate_compile(optimizer, metrics, **kwargs)
self._run_eagerly = run_eagerly
self.optimizer = self._get_optimizer(optimizer)
mesh = None
if self._layout_map is not None:
mesh = self._layout_map.get_default_mesh()
if isinstance(loss, compile_utils.LossesContainer):
self.compiled_loss = loss
else:
self.compiled_loss = compile_utils.LossesContainer(
loss,
loss_weights,
output_names=self.output_names,
mesh=mesh,
)
self.compiled_metrics = compile_utils.MetricsContainer(
metrics,
weighted_metrics,
output_names=self.output_names,
from_serialized=from_serialized,
mesh=mesh,
)
if steps_per_execution == "auto":
if self._steps_per_execution is None:
self._configure_steps_per_execution(1)
self._steps_per_execution_tuner = (
steps_per_execution_tuning.StepsPerExecutionTuner(
self.optimizer, self._steps_per_execution
)
)
self._autotune_steps_per_execution = True
else:
self._configure_steps_per_execution(steps_per_execution or 1)
self._pss_evaluation_shards = self._infer_exact_eval_shards(
pss_evaluation_shards
)
# Initializes attrs that are reset each time `compile` is called.
self._reset_compile_cache()
self._is_compiled = True
self.loss = loss or {}
if (self._run_eagerly or self.dynamic) and jit_compile:
raise ValueError(
"You cannot enable `run_eagerly` and `jit_compile` "
"at the same time."
)
else:
self._jit_compile = jit_compile
def _get_optimizer(self, optimizer):
"""Wraps `optimizer` in `LossScaleOptimizer` if necessary."""
def _get_single_optimizer(opt):
opt = optimizers.get(opt)
if self.dtype_policy.name == "mixed_float16" and not isinstance(
opt, lso.BaseLossScaleOptimizer
):
# Loss scaling is necessary with mixed_float16 for models to
# converge to the same accuracy as with float32.
opt = lso.BaseLossScaleOptimizer(opt)
return opt
return tf.nest.map_structure(_get_single_optimizer, optimizer)
@tf.__internal__.tracking.no_automatic_dependency_tracking
def _reset_compile_cache(self):
self.train_function = None
self.test_function = None
self.predict_function = None
# Used to cache the `tf.function`'ed `train_function` to be logged in
# TensorBoard, since the original `train_function` is not necessarily
# a `tf.function` (e.g., with ParameterServerStrategy, the
# `train_function` is a scheduling of the actual training function to a
# remote worker).
self.train_tf_function = None
# Used to cache `trainable` attr of `Layer`s for `fit`.
self._compiled_trainable_state = self._get_trainable_state()
@tf.__internal__.tracking.no_automatic_dependency_tracking
def _configure_steps_per_execution(self, steps_per_execution):
self._steps_per_execution = self._create_counter_variable(
steps_per_execution
)
@property
def _should_compute_mask(self):
return False
@property
def metrics(self):
"""Return metrics added using `compile()` or `add_metric()`.
Note: Metrics passed to `compile()` are available only after a
`keras.Model` has been trained/evaluated on actual data.
Examples:
>>> inputs = tf.keras.layers.Input(shape=(3,))
>>> outputs = tf.keras.layers.Dense(2)(inputs)
>>> model = tf.keras.models.Model(inputs=inputs, outputs=outputs)
>>> model.compile(optimizer="Adam", loss="mse", metrics=["mae"])
>>> [m.name for m in model.metrics]
[]
>>> x = np.random.random((2, 3))
>>> y = np.random.randint(0, 2, (2, 2))
>>> model.fit(x, y)
>>> [m.name for m in model.metrics]
['loss', 'mae']
>>> inputs = tf.keras.layers.Input(shape=(3,))
>>> d = tf.keras.layers.Dense(2, name='out')
>>> output_1 = d(inputs)
>>> output_2 = d(inputs)
>>> model = tf.keras.models.Model(
... inputs=inputs, outputs=[output_1, output_2])
>>> model.add_metric(
... tf.reduce_sum(output_2), name='mean', aggregation='mean')
>>> model.compile(optimizer="Adam", loss="mse", metrics=["mae", "acc"])
>>> model.fit(x, (y, y))
>>> [m.name for m in model.metrics]
['loss', 'out_loss', 'out_1_loss', 'out_mae', 'out_acc', 'out_1_mae',
'out_1_acc', 'mean']
"""
metrics = []
if self._is_compiled:
if self.compiled_loss is not None:
metrics += self.compiled_loss.metrics
if self.compiled_metrics is not None:
metrics += self.compiled_metrics.metrics
for l in self._flatten_layers():
metrics.extend(l._metrics)
return metrics
@property
def metrics_names(self):
"""Returns the model's display labels for all outputs.
Note: `metrics_names` are available only after a `keras.Model` has been
trained/evaluated on actual data.
Examples:
>>> inputs = tf.keras.layers.Input(shape=(3,))
>>> outputs = tf.keras.layers.Dense(2)(inputs)
>>> model = tf.keras.models.Model(inputs=inputs, outputs=outputs)
>>> model.compile(optimizer="Adam", loss="mse", metrics=["mae"])
>>> model.metrics_names
[]
>>> x = np.random.random((2, 3))
>>> y = np.random.randint(0, 2, (2, 2))
>>> model.fit(x, y)
>>> model.metrics_names
['loss', 'mae']
>>> inputs = tf.keras.layers.Input(shape=(3,))
>>> d = tf.keras.layers.Dense(2, name='out')
>>> output_1 = d(inputs)
>>> output_2 = d(inputs)
>>> model = tf.keras.models.Model(
... inputs=inputs, outputs=[output_1, output_2])
>>> model.compile(optimizer="Adam", loss="mse", metrics=["mae", "acc"])
>>> model.fit(x, (y, y))
>>> model.metrics_names
['loss', 'out_loss', 'out_1_loss', 'out_mae', 'out_acc', 'out_1_mae',
'out_1_acc']
"""
# This property includes all output names including `loss` and
# per-output losses for backward compatibility.
return [m.name for m in self.metrics]
@property
def distribute_strategy(self):
"""The `tf.distribute.Strategy` this model was created under."""
return self._distribution_strategy or tf.distribute.get_strategy()
@property
def run_eagerly(self):
"""Settable attribute indicating whether the model should run eagerly.
Running eagerly means that your model will be run step by step,
like Python code. Your model might run slower, but it should become
easier for you to debug it by stepping into individual layer calls.
By default, we will attempt to compile your model to a static graph to
deliver the best execution performance.
Returns:
Boolean, whether the model should run eagerly.
"""
if self.dynamic and self._run_eagerly == False:
# TODO(fchollet): consider using py_func to enable this.
raise ValueError(
"Your model contains layers that can only be "
"successfully run in eager execution (layers "
"constructed with `dynamic=True`). "
"You cannot set `run_eagerly=False`."
)
if self._cluster_coordinator and self._run_eagerly:
raise ValueError(
"When using `Model` with `ParameterServerStrategy`, "
"`run_eagerly` is not supported."
)
# Run eagerly logic, by priority:
# (1) Dynamic models must be run eagerly.
# (2) Explicitly setting run_eagerly causes a Model to be run eagerly.
# (3) Not explicitly setting run_eagerly defaults to TF's global
# setting.
return (
self.dynamic