| # Copyright 2020 The Kubeflow 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. | |
| # ============================================================================== | |
| """ | |
| An example of multi-worker training with Keras model using Strategy API. | |
| https://github.com/kubeflow/training-operator/blob/master/examples/tensorflow/distribution_strategy/keras-API/multi_worker_strategy-with-keras.py | |
| """ | |
| import tensorflow as tf | |
| import tensorflow_datasets as tfds | |
| from packaging.version import Version | |
| from tensorflow.keras import layers, models | |
| import submarine | |
| def make_datasets_unbatched(): | |
| BUFFER_SIZE = 10000 | |
| # Scaling MNIST data from (0, 255] to (0., 1.] | |
| def scale(image, label): | |
| image = tf.cast(image, tf.float32) | |
| image /= 255 | |
| return image, label | |
| # If we use tensorflow_datasets > 3.1.0, we need to disable GCS | |
| # https://github.com/tensorflow/datasets/issues/2761#issuecomment-1187413141 | |
| if Version(tfds.__version__) > Version("3.1.0"): | |
| tfds.core.utils.gcs_utils._is_gcs_disabled = True | |
| datasets, _ = tfds.load(name="mnist", with_info=True, as_supervised=True) | |
| return datasets["train"].map(scale).cache().shuffle(BUFFER_SIZE) | |
| def build_and_compile_cnn_model(): | |
| model = models.Sequential() | |
| model.add(layers.Conv2D(32, (3, 3), activation="relu", input_shape=(28, 28, 1))) | |
| model.add(layers.MaxPooling2D((2, 2))) | |
| model.add(layers.Conv2D(64, (3, 3), activation="relu")) | |
| model.add(layers.MaxPooling2D((2, 2))) | |
| model.add(layers.Conv2D(64, (3, 3), activation="relu")) | |
| model.add(layers.Flatten()) | |
| model.add(layers.Dense(64, activation="relu")) | |
| model.add(layers.Dense(10, activation="softmax")) | |
| model.summary() | |
| model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"]) | |
| return model | |
| def main(): | |
| strategy = tf.distribute.experimental.MultiWorkerMirroredStrategy( | |
| communication=tf.distribute.experimental.CollectiveCommunication.AUTO | |
| ) | |
| BATCH_SIZE_PER_REPLICA = 4 | |
| BATCH_SIZE = BATCH_SIZE_PER_REPLICA * strategy.num_replicas_in_sync | |
| with strategy.scope(): | |
| ds_train = make_datasets_unbatched().batch(BATCH_SIZE).repeat() | |
| options = tf.data.Options() | |
| options.experimental_distribute.auto_shard_policy = tf.data.experimental.AutoShardPolicy.DATA | |
| ds_train = ds_train.with_options(options) | |
| # Model building/compiling need to be within `strategy.scope()`. | |
| multi_worker_model = build_and_compile_cnn_model() | |
| class MyCallback(tf.keras.callbacks.Callback): | |
| def on_epoch_end(self, epoch, logs=None): | |
| # monitor the loss and accuracy | |
| print(logs) | |
| submarine.log_metric("loss", logs["loss"], epoch) | |
| submarine.log_metric("accuracy", logs["accuracy"], epoch) | |
| multi_worker_model.fit(ds_train, epochs=10, steps_per_epoch=70, callbacks=[MyCallback()]) | |
| # save model | |
| submarine.save_model(multi_worker_model, "tensorflow") | |
| if __name__ == "__main__": | |
| main() |