<jupyter_start><jupyter_text>Chapter 19: Training and Deploying TensorFlow Models at Scale—大规模地训练和部署TensorFlow 一旦你有了一个可以做出惊人预测的漂亮模型，你会怎么做呢?嗯，你需要把它投入生产!  这可能非常简单，你只需要在一批数据上运行模型，然后每晚编写运行该模型的脚本。然而，这件事情通常比这更加复杂。你的使用设备的各个部分可能需要使用该模型的实时数据,在这种情况下,你可能会想在一个web服务上封装你的模型:通过这种方式,正如我们在第2章中讨论的那样，你的使用设备的任何部分都可以使用一个简单的REST API(或其他协议)，来随时查询你的模型。但是随着时间的推移，你需要定期根据新数据对模型进行再训练，并将更新后的版本推向生产。你必须掌握模型的版本控制，从一个模型优雅地转换到下一个模型，在出现问题时可能回滚到前一个模型，并且可能并行运行多个不同的模型来执行**A/B实验**。  如果你的产品获得了成功，你的服务可能会开始每秒获得大量的查询 **(QPS)**，并且它必须扩展来保证能够支持负载。正如我们将在本章中看到的，一个很好的扩大服务规模的解决方案是使用TF服务，这个服务可以在你自己的硬件基础设施上使用，也可以通过云服务(如谷歌云AI平台)使用。它将有效地为你的模型提供服务，处理优雅的模型转换等等。如果你使用云平台，你还会获得许多额外的特性，比如强大的监视工具。  此外，如果你有大量的训练数据和计算密集型模型，那么训练时间可能会非常长。如果你的产品需要快速适应变化，那么长时间的训练可能会让你的产品大打折扣(例如，想想一个新闻推荐系统在推广上周的新闻)。也许更重要的是，长时间的训练会阻止你尝试新的想法。在机器学习中(和其他许多领域一样)，很难提前知道哪些想法会奏效，所以你应该尽可能多、越快越好地进行尝试。一种加速训练的方法是使用硬件加速器，比如gpu或TPUs。为了更快地运行，你还可以跨多台机器训练模型，每台机器都配备了多个硬件加速器。我们将看到，TensorFlow简单而强大的分布式策略API让这件事变得容易。  在本章中，我们将看到如何去部署模型，首先是TF服务，然后是谷歌云AI平台。我们还将快速了解如何将模型部署到移动应用程序、嵌入式设备和web应用程序。最后，我们将讨论如何使用gpu加速计算，以及如何使用分布策略API训练跨多个设备和服务器的模型。在这一章中，有很多话题要讨论，现在就让我们开始吧！  Setup首先，我们导入几个公共模块，确保MatplotLib用inline方式绘制图形，并准备一个函数来保存这些图形。我们还检查是否安装了Python 3.5或更高版本(尽管Python 2.x可以工作，但是我们并不推荐，所以我们强烈建议你使用Python 3代替)，以及Scikit-Learn≥0.20和TensorFlow≥2.0。<jupyter_code># Python ≥3.5 is required
import sys
assert sys.version_info >= (3, 5)

# Scikit-Learn ≥0.20 is required
import sklearn
assert sklearn.__version__ >= "0.20"

try:
    # %tensorflow_version only exists in Colab.
    %tensorflow_version 2.x
    !echo "deb http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" > /etc/apt/sources.list.d/tensorflow-serving.list
    !curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | apt-key add -
    !apt update && apt-get install -y tensorflow-model-server
    !pip install -q -U tensorflow-serving-api
    IS_COLAB = True
except Exception:
    IS_COLAB = False

# TensorFlow ≥2.0 is required
import tensorflow as tf
from tensorflow import keras
assert tf.__version__ >= "2.0"

if not tf.test.is_gpu_available():
    print("No GPU was detected. CNNs can be very slow without a GPU.")
    if IS_COLAB:
        print("Go to Runtime > Change runtime and select a GPU hardware accelerator.")

# Common imports
import numpy as np
import os

# to make this notebook's output stable across runs
np.random.seed(42)
tf.random.set_seed(42)

# To plot pretty figures
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)

# Where to save the figures
PROJECT_ROOT_DIR = "."
CHAPTER_ID = "deploy"
IMAGES_PATH = os.path.join(PROJECT_ROOT_DIR, "images", CHAPTER_ID)
os.makedirs(IMAGES_PATH, exist_ok=True)

def save_fig(fig_id, tight_layout=True, fig_extension="png", resolution=300):
    path = os.path.join(IMAGES_PATH, fig_id + "." + fig_extension)
    print("Saving figure", fig_id)
    if tight_layout:
        plt.tight_layout()
    plt.savefig(path, format=fig_extension, dpi=resolution)<jupyter_output><empty_output><jupyter_text>19.1 Serving a TensorFlow Model—为TensorFlow模型提供服务 一旦你训练好了一个TensorFlow模型，你就可以轻松地在任何Python代码中使用它:如果它是一个tf.keras模型，我们只需要调用它的predict()方法!但是随着你的设备的发展，最好将模型包装在一个小服务中，它的唯一作用是做出预测，并让设备的其余部分查询它(比如通过REST 或者 gRPC API)。这将使你的模型与基础结构的其余部分分离开来,让它可以很容易地转换模型版本或根据需要扩大服务规模(独立于你的设备的其他部分),进行A / B实验,并确保你的所有软件组件依赖于相同的模型版本。它还简化了测试和开发等等。你可以使用任何你想要的技术来创建你自己的微服务(比如使用Flask library)，但是当你可以使用TF服务时，为什么要白费功夫呢？  19.1.1 Using TensorFlow Serving—使用TensorFlow服务 TF服务是一个非常有效的，经过实战检验的模型服务器，它是用c++编写的。它可以承受高负载，为你的模型的多个版本提供五福，通过查看模型存储库以自动部署最新版本，等等功能（可以在图 19-1中看到）。 ![1](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/19/214206-569367.png) 现在假设你已经使用tf.Keras训练了一个MNIST模型,并且你想把它部署到TF服务上。你要做的第一件事是导出这个模型为TensorFlow的SavedModel格式。<jupyter_code>(X_train_full, y_train_full), (X_test, y_test) = keras.datasets.mnist.load_data()
X_train_full = X_train_full[..., np.newaxis].astype(np.float32) / 255.
X_test = X_test[..., np.newaxis].astype(np.float32) / 255.
X_valid, X_train = X_train_full[:5000], X_train_full[5000:]
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
X_new = X_test[:3]
np.random.seed(42)
tf.random.set_seed(42)

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28, 1]),
    keras.layers.Dense(100, activation="relu"),
    keras.layers.Dense(10, activation="softmax")
])
model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.SGD(lr=1e-2),
              metrics=["accuracy"])
model.fit(X_train, y_train, epochs=10, validation_data=(X_valid, y_valid))
np.round(model.predict(X_new), 2)<jupyter_output><empty_output><jupyter_text>19.2.2 Exporting SavedModels—输出SavedModels TensorFlow提供了一个简单的tf.saved_model.save()函数来将模型导出为SavedModel格式。你所需要做的就是给该函数一个模型，指定它的名称和版本号，然后这个函数会保存模型的计算图及其权重：<jupyter_code>model_version = "0001"
model_name = "my_mnist_model"
model_path = os.path.join(model_name, model_version)
model_path<jupyter_output><empty_output><jupyter_text>在导出的最终模型中包含所有的预处理层通常是个好主意，因为这样一旦最终模型被部署到生产环境中，它就可以用它的自然形式来摄取数据。这样做就避免了在使用模型的应用程序中，单独进行预处理。在模型中捆绑预处理步骤还可以简化后期的更新，并且减小模型和所需的预处理步骤之间不匹配的风险。 一个SavedModel表示你的模型的一个版本。一个SavedModel包含一个TensorFlow模型，包括它的架构(一个计算图)和它的权重。它被存储为一个包含saved_model.pb文件的目录，该文件定义了计算图(表示为序列化的协议缓冲区)，以及一个包含变量值的变量子目录。对于包含大量权重的模型，这些变量值可以通过多个文件中进行分割。一个SavedModel还包括一个资产子目录，该目录可能包含额外的数据，例如词汇表文件、类名或此模型的一些示例实例。目录结构可以如下图表示(在这个例子中，我们没有使用资产):<jupyter_code>!rm -rf {model_name}

tf.saved_model.save(model, model_path)
for root, dirs, files in os.walk(model_name):
    indent = '    ' * root.count(os.sep)
    print('{}{}/'.format(indent, os.path.basename(root)))
    for filename in files:
        print('{}{}'.format(indent + '    ', filename))<jupyter_output>my_mnist_model/
    0001/
        saved_model.pb
        variables/
            variables.data-00000-of-00001
            variables.index
        assets/<jupyter_text>TensorFlow还附带了一个小的saved_model_cli命令行工具来检查SavedModels:<jupyter_code>!saved_model_cli show --dir {model_path}
!saved_model_cli show --dir {model_path} --tag_set serve
!saved_model_cli show --dir {model_path} --tag_set serve \
                      --signature_def serving_default
!saved_model_cli show --dir {model_path} --all<jupyter_output>MetaGraphDef with tag-set: 'serve' contains the following SignatureDefs:

signature_def['__saved_model_init_op']:
  The given SavedModel SignatureDef contains the following input(s):
  The given SavedModel SignatureDef contains the following output(s):
    outputs['__saved_model_init_op'] tensor_info:
        dtype: DT_INVALID
        shape: unknown_rank
        name: NoOp
  Method name is: 

signature_def['serving_default']:
  The given SavedModel SignatureDef contains the following input(s):
    inputs['flatten_2_input'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 28, 28, 1)
        name: serving_default_flatten_2_input:0
  The given SavedModel SignatureDef contains the following output(s):
    outputs['dense_5'] tensor_info:
        dtype: DT_FLOAT
        shape: (-1, 10)
        name: StatefulPartitionedCall:0
  Method name is: tensorflow/serving/predict<jupyter_text>SavedModel还可以包含一个或多个元图（metagraph）。一个元图是一个计算图加上一些函数签名定义(包括它们的输入和输出名称、类型和形状)。每个元图由一组标签进行标识。举个例子，你可能希望能拥有一个包含完整计算图的元图，它包括训练操作(比如这个元图可能被标记为“train”)，另一个元图包含一个仅包含预测操作的经过修剪的计算图，包括一些只适用于gpu的操作(这个元图可能被标记为“serve”、“gpu”)。但是，当你传递一个tf.keras模型到tf.saved_model.save()函数时,默认情况下,函数可以保存一个简单得多的SavedModel:这个函数可以保存一个标记“服务”的单独的元图,其中包含两个签名定义：一个初始化函数(称为__saved_model_init_op)和一个默认的服务函数(被称为serving_default)。当保存一个tf.keras模型，默认的服务函数响应为model.call()函数，这个函数当然可以进行预测。 saved_model_cli工具还可以用于进行预测(用于测试，而不是用于生产)。  假设你有一个NumPy数组(X_new)，其中包含你想要用来进行预测的三张手写数字图像。首先你需要将它们导出为NumPy的npy格式:  让我们将新实例写入一个' npy '文件，这样我们就可以轻松地将它们传递给我们的模型:<jupyter_code>np.save("my_mnist_tests.npy", X_new)
input_name = model.input_names[0]
input_name<jupyter_output><empty_output><jupyter_text>现在让我们使用' saved_model_cli '来对我们刚刚保存的实例进行预测:<jupyter_code>!saved_model_cli run --dir {model_path} --tag_set serve \
                     --signature_def serving_default    \
                     --inputs {input_name}=my_mnist_tests.npy
np.round([[1.1739199e-04, 1.1239604e-07, 6.0210604e-04, 2.0804715e-03, 2.5779348e-06,
           6.4079795e-05, 2.7411186e-08, 9.9669880e-01, 3.9654213e-05, 3.9471846e-04],
          [1.2294615e-03, 2.9207937e-05, 9.8599273e-01, 9.6755642e-03, 8.8930705e-08,
           2.9156188e-04, 1.5831805e-03, 1.1311053e-09, 1.1980456e-03, 1.1113169e-07],
          [6.4066830e-05, 9.6359509e-01, 9.0598064e-03, 2.9872139e-03, 5.9552520e-04,
           3.7478798e-03, 2.5074568e-03, 1.1462728e-02, 5.5553433e-03, 4.2495009e-04]], 2)<jupyter_output><empty_output><jupyter_text>这个工具的输出包含3个实例中每个实例的10个类概率。太棒了!现在你已经有了一个工作的SavedModel，下一步是安装TF服务。  19.2 Installing TensorFlow Serving—安装TensorFlow服务 安装TF服务的方法有很多:使用Docker镜像、使用system的包管理器、从source安装等等。让我们使用Docker选项，这种方法是TensorFlow团队强烈推荐的，因为它安装简单，不会干扰你的系统，并且提供了高性能。首先你需要安装Docker，然后下载官方TF的Docker镜像： 安装 [Docker](https://docs.docker.com/install/) 。然后运行:```bashdocker pull tensorflow/servingexport ML_PATH=$HOME/ml  or wherever this project isdocker run -it --rm -p 8500:8500 -p 8501:8501 \   -v "$ML_PATH/my_mnist_model:/models/my_mnist_model" \   -e MODEL_NAME=my_mnist_model \   tensorflow/serving```使用完毕后，按Ctrl-C关闭服务器。 或者，如果你安装了' tensorflow_model_server '(例如，如果你在Colab中运行这个笔记本)，那么以下3个单元格将启动服务器:<jupyter_code>os.environ["MODEL_DIR"] = os.path.split(os.path.abspath(model_path))[0]
%%bash --bg
nohup tensorflow_model_server \
     --rest_api_port=8501 \
     --model_name=my_mnist_model \
     --model_base_path="${MODEL_DIR}" >server.log 2>&1
!tail server.log<jupyter_output>2019-11-06 13:04:12.267136: I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2019-11-06 13:04:12.283035: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:202] Restoring SavedModel bundle.
2019-11-06 13:04:12.300096: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:151] Running initialization op on SavedModel bundle at path: /content/my_mnist_model/0002
2019-11-06 13:04:12.304438: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:311] SavedModel load for tags { serve }; Status: success. Took 39993 microseconds.
2019-11-06 13:04:12.304900: I tensorflow_serving/servables/tensorflow/saved_model_warmup.cc:105] No warmup data file found at /content/my_mnist_model/0002/assets.extra/tf_serving_warmup_requests
2019-11-06 13:04:12.305057: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: my_mnist_m[...]<jupyter_text>就像这样！TF服务正在运行。现在让我们回到Python并查询这个服务器，首先使用REST API，然后使用gRPC API。  19.2.1 Querying TF Serving through the REST API—通过REST API查询TF服务 让我们从创建查询开始。查询必须包含你想要调用的函数签名的名字，当然还有输入数据:<jupyter_code>import json

input_data_json = json.dumps({
    "signature_name": "serving_default",
    "instances": X_new.tolist(),
})<jupyter_output><empty_output><jupyter_text>请注意，JSON格式是100%基于文本的，所以X_new NumPy数组必须转换为Python列表，然后格式化为JSON:<jupyter_code>repr(input_data_json)[:1500] + "..."<jupyter_output><empty_output><jupyter_text>让我们通过发送HTTP POST请求将输入数据发送给TF服务。这可以通过使用请求库轻松完成(它不是Python的标准库的一部分，所以你需要首先安装它，比如使用pip): 现在让我们使用TensorFlow服务的REST API进行预测:<jupyter_code>import requests

SERVER_URL = 'http://localhost:8501/v1/models/my_mnist_model:predict'
response = requests.post(SERVER_URL, data=input_data_json)
response.raise_for_status() # raise an exception in case of error
response = response.json()
response.keys()<jupyter_output><empty_output><jupyter_text>响应是一个包含单独的“predictions”键的字典。对应的值是预测列表。这个列表是一个Python列表，所以让我们把它转换成一个NumPy数组，并把它包含的浮点数化简到第二个小数:<jupyter_code>y_proba = np.array(response["predictions"])
y_proba.round(2)<jupyter_output><empty_output><jupyter_text>REST API漂亮而简单，当输入和输出数据不是太大时，它工作得很好。此外，几乎任何客户机应用程序都可以在没有附加依赖项的情况下进行REST查询，然而其他协议并不总是那么容易获得。但是，它是基于JSON的，JSON是基于文本的，而且相当冗长。例如，我们必须将NumPy数组转换为Python列表，而每个浮点数最终都表示为字符串。这在序列化/反序列化时间(将所有浮点数转换回字符串)和有效负载大小方面都是非常低效的:许多浮点数最终使用超过15个字符表示，对于32位浮点数来说，这意味着超过120位!这将导致在传输大型NumPy阵列时出现高延迟和带宽占用。所以我们用gRPC代替。  19.2.2 Querying TF Serving through the gRPC API—通过gRPC API查询TF服务 gRPC API期望一个序列化的PredictRequest协议缓冲区作为输入，然后它会输出一个序列化的PredictResponse协议缓冲区。这些protobufs是tensorflow- serve -api库的一部分，必须安装(比如使用pip)。首先，让我们创建请求：<jupyter_code>from tensorflow_serving.apis.predict_pb2 import PredictRequest

request = PredictRequest()
request.model_spec.name = model_name
request.model_spec.signature_name = "serving_default"
input_name = model.input_names[0]
request.inputs[input_name].CopyFrom(tf.make_tensor_proto(X_new))<jupyter_output><empty_output><jupyter_text>这段代码创建了一个PredictRequest协议缓冲区，并且用张量协议缓冲区的形式填充所需的字段，包括模型名(前面定义的)、我们想要调用的函数签名，以及最后的输入数据。tf.make_tensor_proto()函数根据给定的张量或NumPy数组(在本例中是X_new)创建一个张量协议缓冲区。 接下来，我们会向服务器发送请求并获取其响应(为此你需要grpcio库，可以使用pip安装):<jupyter_code>import grpc
from tensorflow_serving.apis import prediction_service_pb2_grpc

channel = grpc.insecure_channel('localhost:8500')
predict_service = prediction_service_pb2_grpc.PredictionServiceStub(channel)
response = predict_service.Predict(request, timeout=10.0)
response<jupyter_output><empty_output><jupyter_text>代码非常简单:在导入之后，我们在TCP端口8500上创建到本地主机的gRPC通信通道，然后在此通道上创建gRPC服务，并使用它发送请求，超时时间为10秒(这并不意味着调用是同步的:它会阻塞，直到收到响应或超时时间结束)。在本例中，通道是不安全的(没有加密，没有身份验证)，但是gRPC和TensorFlow服务也支持SSL/TLS上的安全通道。 接下来，我们将PredictResponse协议缓冲区转换为张量:<jupyter_code>output_name = model.output_names[0]
outputs_proto = response.outputs[output_name]
y_proba = tf.make_ndarray(outputs_proto)
y_proba.round(2)<jupyter_output><empty_output><jupyter_text>如果运行这段代码并输出y_proba.numpy().round(2)，你会得到与前面完全相同的类概率估计值。这就是它的全部内容:只需几行代码，你现在就可以使用REST或gRPC远程访问你的TensorFlow模型。<jupyter_code>output_name = model.output_names[0]
outputs_proto = response.outputs[output_name]
shape = [dim.size for dim in outputs_proto.tensor_shape.dim]
y_proba = np.array(outputs_proto.float_val).reshape(shape)
y_proba.round(2)<jupyter_output><empty_output><jupyter_text>19.2.3 Deploying a new model version—部署一个新的模型版本 现在，让我们创建一个新的模型版本，并将SavedModel导出到my_mnist_model/0002目录，就像前面一样:<jupyter_code>np.random.seed(42)
tf.random.set_seed(42)

model = keras.models.Sequential([
    keras.layers.Flatten(input_shape=[28, 28, 1]),
    keras.layers.Dense(50, activation="relu"),
    keras.layers.Dense(50, activation="relu"),
    keras.layers.Dense(10, activation="softmax")
])
model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.SGD(lr=1e-2),
              metrics=["accuracy"])
history = model.fit(X_train, y_train, epochs=10, validation_data=(X_valid, y_valid))
model_version = "0002"
model_name = "my_mnist_model"
model_path = os.path.join(model_name, model_version)
model_path
tf.saved_model.save(model, model_path)
for root, dirs, files in os.walk(model_name):
    indent = '    ' * root.count(os.sep)
    print('{}{}/'.format(indent, os.path.basename(root)))
    for filename in files:
        print('{}{}'.format(indent + '    ', filename))<jupyter_output>my_mnist_model/
    0002/
        saved_model.pb
        variables/
            variables.data-00000-of-00001
            variables.index
        assets/
    0001/
        saved_model.pb
        variables/
            variables.data-00000-of-00001
            variables.index
        assets/<jupyter_text>**Warning**: 在TensorFlow服务加载新模型之前，你可能需要等待一分钟。 TensorFlow定期(延迟是可配置的)检查新模型版本。如果它找到了一个，它将自动地优雅地处理转换:默认情况下，它将用以前的模型版本回答挂起的请求(如果有的话)，同时用新版本处理新请求。一旦每个挂起的请求都得到了回答，先前的模型版本就会被卸载。你可以在TensorFlow服务日志中看到这一点:<jupyter_code>import requests

SERVER_URL = 'http://localhost:8501/v1/models/my_mnist_model:predict'
            
response = requests.post(SERVER_URL, data=input_data_json)
response.raise_for_status()
response = response.json()
response.keys()
y_proba = np.array(response["predictions"])
y_proba.round(2)<jupyter_output><empty_output><jupyter_text>这种方法提供了一个平滑的转换，但它可能使用太多的RAM(特别是GPU RAM，这些通常是有限的)。在这种情况下，你可以配置TF服务，来让它用以前的模型版本处理所有挂起的请求，并在加载和使用新的模型版本之前卸载它。此配置将避免同时加载两个模型版本，但该服务在短时间内会变得不可用。 如你所见，TF服务使部署新模型变得非常简单。此外，如果你发现版本2不能像你预期的那样工作，那么回滚到版本1就像删除my_mnist_model/0002目录一样简单。 如果你希望每秒都能够获得许多查询，那么你需要在多个服务器上部署TF服务，并且对查询进行负载平衡(参见图19-2)。这将需要跨这些服务器部署和管理许多TF服务容器。处理这个问题的一种方法是使用Kubernetes这样的工具，Kubernetes是一种开源系统，用于简化跨多个服务器的容器编排。如果你不想购买、维护和升级所有的硬件基础设施，那么你会希望在云平台上使用虚拟机，例如Amazon AWS、Microsoft Azure、谷歌云平台、IBM云、阿里巴巴云、Oracle云或其他平台即服务(PaaS)。 ![4](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/21/194200-203925.png) 管理所有虚拟机、处理容器配置(即使在Kubernetes的帮助下)、处理TF服务配置、调优和监视所有这些都可能是一项全职工作。幸运的是，一些服务提供商可以为你处理所有这些问题。在本章中，我们将使用谷歌云AI平台，因为它是目前唯一有TPUs的平台，它支持TensorFlow 2，它提供了一套很好的AI服务(例如AutoML, Vision API, Natural Language API)，它也是我最熟悉的一个。但在这个领域还有其他一些提供商，比如亚马逊AWS SageMaker和微软AI Platform，它们也有能力为TensorFlow模型提供服务。 现在让我们看看如何在云上服务我们出色的MNIST模型!  19.3 Deploy the model to Google Cloud AI Platform—在GCP AI平台上创建一个预测服务 在你可以部署一个模型之前，你可能需要进行一些设置: 1. 登录到你的谷歌帐户，然后去谷歌云平台(GCP)控制台(参见图19-3)。如果你没有谷歌帐户，那么你必须创建一个。![5](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/21/195246-116545.png)2. 如果你是第一次使用GCP，你将必须阅读并接受条款和条件。如果需要，请单击“游览控制台”。在撰写本文时，新用户可以免费试用，包括价值300美元的GCP积分，可以在12个月内使用。你只需要其中的一小部分来支付你将在本章中使用的服务。在注册免费试用时，你仍然需要创建一个支付档案，并输入你的信用卡号码:它是用于验证目的(可能是为了避免人们多次使用免费试用)，但是你不会需要付费。如果你被要求，那么你需要激活和升级你的帐户。3. 如果你以前使用过GCP，并且你的免费试用已经过期，那么你将在本章使用的服务将会花费你一些钱。在运行任何服务之前，请确保你理解并同意价格。如果服务费用超出你的预期，我将不承担任何责任!还要确保你的账单账户是活跃的。你需要进行检查，打开左边的导航菜单，然后点击计费，确保你已经设置了付款方式，并且计费账户是激活的。4. GCP中的每个资源都属于一个项目。这包括你可能使用的所有虚拟机、你存储的文件和你运行的训练工作。当你创建了一个帐户，GCP自动为你创建一个项目，称为我的第一个项目。如果你想，你可以通过进入项目设置改变它的显示名称:在导航菜单(在屏幕的左边)，选择 IAM & admin → Settings，更改项目的显示名称，然后单击保存。请注意，项目还有一个惟一的ID和编号。你可以在创建项目时选择项目ID，但不能在以后更改它。项目编号是自动生成的，不能更改。如果你想创建一个新项目，请单击页面顶部的项目名称，然后单击new project并输入项目ID。确保这个新项目的账单是激活的。 5. 现在你已经有了一个激活了账单的GCP帐户，你可以开始使用这些服务了。你需要的第一个是谷歌云存储(GCS):这里将放置SavedModels、训练数据等。在导航菜单中，向下滚动到Storage部分，然后单击Storage Browser。你所有的文件将放在一个或多个buckets中。单击Create Bucket并选择Bucket名(你可能需要首先激活存储API)。GCS为桶使用单一的全局名称空间，所以像“机器学习”这样的简单名称很可能无法使用。确保bucket名符合DNS命名约定，因为它可能在DNS记录中使用。此外，bucket名是公共的，所以不要在其中放入任何私有内容。通常使用你的域名或你的公司名称作为前缀以确保唯一性，或简单地使用随机数作为名称的一部分。选择你希望存储buckets的位置，其余的选项在默认情况下应该没问题。然后点击创建。6. 上传之前创建的my_mnist_model文件夹(包括一个或多个版本)到存储库。要做到这一点，只要去GCS浏览器，单击上传之前创建的my_mnist_model文件夹(包括一个或多个版本)到存储库。要做到这一点，只要去GCS浏览界面，点击bucket，然后将my_mnist_model文件夹从系统拖放到bucket中(参见图19-4)。或者，你可以单击“Upload folder”并选择要上传的my_mnist_model文件夹。默认情况下，SavedModel的最大大小是250 MB，但是也可以请求更高的配额。![5](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/21/200635-838853.png)7. 现在你需要配置AI平台(以前称为ML引擎)，这样它才能知道你想要使用哪个型号和版本。在导航菜单中，向下滚动到人工智能部分，点击AI平台→模型。单击Activate API(需要几分钟)，然后单击“Create model”。填写模型的详细信息(参见图19-5)，并单击Create。![7](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/21/200954-456971.png) 8. 现在你在AI平台上有了一个模型，你需要创建一个模型版本。在模型列表中，单击你刚刚创建的模型，然后单击Create version并填写版本细节(参见图19-6):设置名称、描述Python版本(3.5以上),(TensorFlow)框架,框架版本(2.0或1.13),ML运行时版本(2.0或1.13),机器类型(选择单核心CPU现在),模型路径GCS(这是实际的版本文件夹的完整路径,例如,gs: / / my-mnist-modelbucket / my_mnist_model / 0002 /),扩展(选择自动)和所有时刻运行的最小数量的TF服务容器。然后单击Save。![8](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/21/201340-480049.png) 祝贺，你已经在云上部署了你的第一个模型!因为你选择了自动规模，当每秒查询的数量增加时，AI平台将启动更多的TF服务容器，并且它将在查询之间实现负载平衡。如果QPS下降，它将自动停止容器。因此，成本直接与QPS相关(以及你选择的机器类型和你在GCS上存储的数据量)。这种定价模式对于偶尔使用的用户和使用高峰的服务，以及初创公司都非常有用:在初创公司真正开始运营之前，价格可以一直很低。 现在让我们查询这个预测服务!  19.4 Using the Prediction Service—使用预测服务 在底层，AI平台只运行TF服务，所以原则上，如果你知道要查询哪个URL，你可以使用与前面相同的代码。只有一个问题:GCP还负责加密和身份验证。加密是基于SSL/TLS的，身份验证是基于token的:在每个请求中都必须向服务器发送一个秘密的身份验证token。因此，在你的代码可以使用预测服务(或任何其他GCP服务)之前，它必须获得一个token。我们将很快看到如何做到这一点，但是首先你需要配置身份验证，并为你的应用程序提供对GCP的适当访问权限。你有两个身份验证选项：* 你的应用程序(将要进行查询预测服务的客户端代码)，可以使用使用你自己的谷歌登录名和密码的用户凭证进行身份验证。使用用户凭据，将给你的应用程序提供与GCP上完全相同的权限，这肯定超出了它的需要。此外，你还必须在应用程序中部署你的凭据，这样任何具有访问权限的人都可以窃取你的凭据并完全访问你的GCP帐户。简而言之，不要选择这个选项;只有在非常罕见的情况下才需要它(例如，当你的应用程序需要访问它的用户的GCP帐户时)。* 客户端代码可以使用服务帐户进行身份验证。这是一个代表应用程序的帐户，而不是用户。它通常被给予非常严格的访问权限:严格按照它所需要的权限，没有其他权限。这是推荐的选项。 所以，让我们为你的应用程序创建一个服务帐户:在导航菜单中，进入IAM &amp;admin服务帐户，然后单击Create Service Account，填写表单(服务帐户名称、ID、描述)，然后单击Create(参见图19-7)。接下来，你必须给这个帐户一些访问权限。选择ML Engine Developer role:这将允许服务帐户进行预测，仅此而已。还可以给予一些用户访问服务帐户(这是有用的,当你GCP用户帐户是一个组织的一部分,和你想授权其他用户在组织中部署的应用程序将基于此服务帐户或管理服务帐户本身)。接下来，单击Create Key导出服务帐户的私钥，选择JSON，然后单击Create。这将以JSON文件的形式下载私钥。一定要保密！![9](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/21/203558-904824.png) 太棒了!现在让我们编写一个查询预测服务的小脚本。谷歌提供了几个库来简化对其服务的访问： Google API Client Library这是基于OAuth 2.0(用于身份验证)的一个相当薄的层和REST。你可以与所有GCP服务，包括AI平台使用它。你可以使用pip安装它:这个库称为google-api-python-client。Google Cloud Client Libraries这里有一些级别更高的库:每个都专门用于特定的服务，比如GCS、Google BigQuery、Google Cloud Natural Language和Google Cloud Vision。所有这些库都可以使用pip安装(例如，GCS Client Library被称为google-cloud-storage)。当客户端库可用于给定的服务时，建议使用它而不是Google API Client Library，因为它实现了所有最佳实践，并且为了获得更好的性能，而且它通常使用gRPC而不是REST。 在撰写本文时，还没有用于AI平台的客户端库，因此我们将使用Google Cloud Client Libraries。它需要使用服务帐户的私钥;你可以通过设置GOOGLE_APPLICATION_CREDENTIALS环境变量来告诉它在哪里，可以是在启动脚本之前，也可以是在脚本中。 按照书中的说明将模型部署到谷歌Cloud AI平台，下载服务帐户的私钥并保存到my_service_account_private_key。并且更新project_id:<jupyter_code>project_id = "onyx-smoke-242003"<jupyter_output><empty_output><jupyter_text>接下来，你必须创建一个资源对象来包装对预测服务的访问:<jupyter_code>import googleapiclient.discovery

os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "my_service_account_private_key.json"
model_id = "my_mnist_model"
model_path = "projects/{}/models/{}".format(project_id, model_id)
model_path += "/versions/v0001/" # if you want to run a specific version
ml_resource = googleapiclient.discovery.build("ml", "v1").projects()<jupyter_output><empty_output><jupyter_text>注意,你可以添加/versions/0001(或任何其他版本号)到model_path来指定你想查询的版本:在一小群用户在发布之前广泛(这就是所谓的金丝雀)进行A/B测试或测试新版本，可能是有用的。接下来，让我们写一个小函数，它将使用资源对象来调用预测服务并取回预测:<jupyter_code>def predict(X):
    input_data_json = {"signature_name": "serving_default",
                       "instances": X.tolist()}
    request = ml_resource.predict(name=model_path, body=input_data_json)
    response = request.execute()
    if "error" in response:
        raise RuntimeError(response["error"])
    return np.array([pred[output_name] for pred in response["predictions"]])<jupyter_output><empty_output><jupyter_text>该函数接受一个包含输入图像的NumPy数组，并准备一个字典，客户端库将把这个字典转换为JSON格式(正如前面所做的)。然后它准备一个预测请求，并执行它;如果响应包含错误，则会引发异常，否则它会提取每个实例的预测并将它们捆绑在NumPy数组中。让我们看看它是否有效:<jupyter_code>Y_probas = predict(X_new)
np.round(Y_probas, 2)<jupyter_output><empty_output><jupyter_text>是的!你现在有了一个运行在云上的很好的预测服务，它可以自动扩展到任意数量的QPS，而且你可以在任何地方安全地查询它。此外，当你不使用它的时候，你几乎不用花任何钱:你只需要为使用GCS的每千兆字节每月支付几美分。你还可以使用谷歌Stackdriver获得详细的日志和指标。但是，如果你想将模型部署到移动应用程序或者到嵌入式设备，那么你该怎么办呢?  19.5 Deploying a Model to a Mobile or Embedded Device—将模型部署到移动设备或嵌入式设备 如果你需要将模型部署到移动设备或嵌入式设备上，那么一个大的模型可能需要很长时间才能下载并使用过多的RAM和CPU，这些情况都会导致应用程序无法响应、设备发热并耗尽电池。为了避免这种情况，你需要在不牺牲太多准确性的前提下，制造出一款适合移动、轻便、高效的模型。TFLite库提供了几个工具来帮助你将模型部署到移动设备和嵌入式设备，主要有三个目的： * 减小模型大小，缩短下载时间，减少内存使用。* 减少每次预测所需的计算量，以减少延迟、电池使用和加热。* 调整模型以适应特定于设备的约束。 为了减小模型大小，TFLite的模型转换器可以接受一个SavedModel，并压缩它以获得基于FlatBuffers的一个更轻的格式。这是谷歌最初为游戏创建的一个高效的跨平台序列化库(有点像协议缓冲区)。它的设计使你可以直接将FlatBuffers加载到RAM中而不进行任何预处理:这减少了加载时间和内存占用。一旦模型被加载到移动设备或嵌入式设备中，TFLite解释器就会执行它来做出预测。下面是如何将SavedModel转换为FlatBuffer并将其保存为.tflite文件的方法 converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_path)  tflite_model = converter.convert()  with open("converted_model.tflite", "wb") as f:   f.write(tflite_model) 转换器还优化了模型，同时缩小了模型并减少了它的延迟。它删除了所有不需要进行预测的操作(比如训练操作)，并尽可能优化计算;例如，3 × a + 4 × a + 5 × a将被转换为(3 + 4 + 5) × a。它还尝试在任何可能的情况下融合操作。举个例子，只要可能，批处理标准化层最终会被折叠到前一层的加法和乘法操作中。为了更好地了解TFLite可以在多大程度上优化一个模型，下载一个预先训练过的TFLite模型，解压存档，然后打开优秀的Netron图形可视化工具，上传.pb文件来查看原始模型。这是个复杂的大图形，对吧?接下来，打开优化后的.flite模型，惊叹于它的美丽。 另一种可以减少模型的大小(除了简单地使用较小的神经网络架构)的方法是使用较小的bit-widths:举个例子,如果你使用half-floats(16位)而不是常规floats(32位),模型规模将缩小2倍，在(一般较小的)精度下降的成本下。此外，训练将会更快，而且你将使用大约一半的GPU RAM。 TFLite转换器通过量化模型权重到定点的8位整数，可以获得更好的效果!与使用32位浮点数相比，这将导致模型的大小减少四倍。最简单的方法叫做训练后量化:它只在训练后量化权重，使用一种相当基本但有效的对称量化技术。它能够找到最大的绝对权值m，然后将浮点范围-m到+m映射到固定点(整数)范围-127到+127。例如(参见图19-8)，如果权重范围从-1.5到+0.8，那么字节-127、0和+127将分别对应于浮点数-1.5、0.0和+1.5。请注意，在使用对称量化时0.0总是映射到0(还请注意，不会使用字节值+68到+127，因为它们映射到大于+0.8的浮点数)。 ![10](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/101921-804296.png) 要执行训练后量化，只需在调用convert()方法之前将OPTIMIZE_FOR_SIZE添加到转换器优化列表中 converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE] 这项技术大大减小了模型的尺寸，因此下载和存储速度更快。然而，在运行时，量化的权重在使用之前会被转换回浮点数(这些恢复的浮点数与原始浮点数并不完全相同，但也不会相差太远，因此精度损失通常是可以接受的)。为了避免一直重新计算它们，回收的浮点数被缓存，因此不会减少RAM的使用。并且计算速度也没有降低。 减少延迟和功耗的最有效的方法，是对激活进行量化，这样计算就可以完全用整数完成，而不需要任何浮点操作。即使使用相同的位宽(例如，使用32位整数而不是32位浮点数)，整数计算使用更少的CPU周期，消耗更少的能源，产生更少的热量。如果你同时也减少位宽(例如，减少到8位整数)，你可以得到巨大的加速。此外，一些神经网络加速器设备(如Edge TPU)只能处理整数，因此必须对权重和激活进行完全量化。这可以在训练后完成;它需要校准步骤找到的最大绝对值激活,所以你需要提供一个代表性样本的训练数据TFLite(它不需要是巨大的),它将通过模型处理数据，并度量量化所需的激活统计量。 量化的主要问题是它失去了一点准确性:它相当于给权重和激活添加了噪声。如果精确度下降太严重，那么你可能需要使用量化感知训练。这意味着在模型中加入伪量化操作，以便在训练过程中学会忽略量化噪声;最终的权重对于量化来说会更稳健。此外，在训练过程中自动完成校准步骤，简化了整个过程 我已经解释了TFLite的核心概念，但要编写移动应用程序或嵌入式程序，还需要另外一本书。幸运的是，有这样一本书:如果你想了解更多关于为移动设备和嵌入式设备构建TensorFlow应用程序的知识，请查阅O’Reilly的书TinyML: Machine Learning with TensorFlow on Arduino and Ultra-LowPower Micro-Controllers，作者是<NAME> (TFLite团队的负责人)和<NAME>。 接下来，我们将看到如何使用gpu来加速计算!  19.6 Using GPUs to Speed Up Computations—使用gpu加速计算 在第11章中，我们讨论了一些可以大大加快训练速度的技术:更好的权重初始化、批处理标准化、复杂的优化器等等。但即使有了所有这些技术，在一台有单一CPU的机器上训练一个大型神经网络可能需要几天甚至几周的时间。 在这一节中，我们将看看如何通过使用gpu来加速你的模型。我们还将看到如何在多个设备上划分计算，包括CPU和多个GPU设备(参见图19-9)。现在，我们将在一台机器上运行所有的东西，但在本章的后面，我们将讨论如何跨多个服务器分布计算。 ![11](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/104701-488344.png) 多亏了gpu，你可以不用等待几天或几周来完成一个训练算法，而只需要等待几分钟或几个小时。这不仅节省了大量的时间，而且还意味着你可以更容易地试验各种模型，并经常根据新数据重新训练你的模型。 第一步是得到你的手一个GPU。有两种选择:要么购买自己的GPU，要么使用云上配置GPU的虚拟机。让我们从第一个选项开始  19.6.1 Getting Your Own GPU—拥有自己的GPU 如果你选择购买GPU卡，那么花一些时间来做出正确的选择。<NAME>写了一篇很好的博客文章来帮助你做出选择，并且他定期更新:我鼓励你仔细阅读。在写这篇文章的时候，TensorFlow只支持具有3.5+ CUDA计算能力的Nvidia卡(当然也包括谷歌的TPUs)，但它可能会将其支持扩展到其他制造商。此外，虽然TPUs目前只能在GCP上使用，但很有可能在不久的将来类似tpu的卡片就会上市，TensorFlow可能会支持它们。简而言之，一定要检查TensorFlow的文档，看看现在它支持什么设备。 如果你想要一个Nvidia GPU卡，你需要安装适当的Nvidia驱动程序和几个Nvidia库。其中包括计算统一设备架构库(CUDA)，它允许开发人员使用支持CUDAenabled的gpu进行各种计算(不仅仅是图形加速)，以及CUDA深度神经网络库(CUDA Deep Neural Network library, CUDA是一种用于DNNs的gpu加速的原语库。cuDNN提供了常用DNN计算的优化实现，如激活层、归一化、向前和向后卷积、池(见第14章)。它是英伟达深度学习SDK的一部分(注意，下载它需要创建一个英伟达开发者帐户)。TensorFlow使用CUDA和cuDNN来控制GPU卡和加速计算(见图19-10)。 ![12](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/105201-630616.png) 一旦你安装了GPU卡和所有必需的驱动程序和库，你可以使用nvidia-smi命令检查CUDA是否正确安装。它列出了可用的GPU卡，以及在每张卡上运行的进程: ![13](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/105319-270200.png) 在写这篇文章的时候，你还需要安装TensorFlow的GPU版本(tensorflow-gpu库);但是，目前正在进行的工作是为cpu和GPU机器制定统一的安装程序，所以请查看安装文档，看看应该安装哪个库。在任何情况下，由于正确地安装每个必需的库都有点费时，而且需要技巧(如果你没有安装正确的库版本，情况就会一团糟)，TensorFlow提供了一个Docker映像，其中包含你需要的所有内容。然而，为了让Docker容器能够访问GPU，你仍然需要在主机上安装Nvidia驱动程序。 要检查TensorFlow是否真的看到了gpu，运行以下测试:<jupyter_code>tf.test.is_gpu_available()
tf.test.gpu_device_name()
tf.test.is_built_with_cuda()
from tensorflow.python.client.device_lib import list_local_devices

devices = list_local_devices()
devices<jupyter_output><empty_output><jupyter_text>is_gpu_available()函数的作用是:检查至少有一个GPU可用。gpu_device_name()函数给出了第一个GPU名称:默认情况下，操作将在该GPU上运行。list_physical_devices()函数的作用是:返回所有可用的GPU设备的列表(本例中没有)。  现在，如果你不想花时间和金钱买你自己的GPU卡呢?你只需要使用云上的GPU VM。  19.6.2 Using a GPU-Equipped Virtual Machine—使用配备gpu的虚拟机 现在所有主流的云平台都提供GPU虚拟机，其中一些预先配置了你需要的所有驱动程序和库(包括TensorFlow)。谷歌云平台强制执行各种GPU配额，包括全球和每个地区:你不能在没有谷歌事先授权的情况下创建数千个GPU 虚拟机。默认情况下，全球GPU配额为零，所以你不能使用任何GPU虚拟机。因此，你需要做的第一件事就是请求更高的全球配额。在GCP控制台中，打开导航菜单，进入IAM &amp→ Quotas,点击Metric，点击None，取消所有位置的勾选，搜索GPU，选择GPU(所有区域)，查看相应的配额。如果此配额值为零(或单纯不足以满足你的需要)，那么选中它旁边的框(它应该是唯一选中的)并单击Edit quota。填写所请求的信息，然后单击Submit request。你的配额申请可能需要几个小时(或几天)才能得到处理并(通常)被接受。  默认情况下，每个区域和每个GPU类型都有一个GPU配额。你也可以要求增加这些指标:点击Metric，选择None取消所有指标，搜索GPU，选择你想要的GPU类型(例如，NVIDIA P4 GPU)。然后点击Location下拉菜单，点击None取消选中所有指标，然后点击你想要的位置;选中要更改的配额旁边的复选框，然后单击“Edit quotas”以提交请求。 一旦你的GPU配额请求被批准，你可以立即创建一个虚拟机配备一个或多个GPU使用谷歌云AI平台的Deep学习虚拟机映像:转到https://homl.info/dlvm ，单击查看控制台，然后单击“Launch on Compute Engine”并填写虚拟机配置表单。注意，有些位置没有所有类型的GPU，有些根本没有GPU(如果有的话，改变位置以查看可用gpu的类型)。确保选择TensorFlow 2.0作为框架，并在第一次启动时自动检查安装NVIDIA GPU驱动程序。通过URL而不是SSH检查对JupyterLab的启用访问也是一个好主意:这将使在该GPU虚拟机上运行Jupyter笔记本变得非常容易，该虚拟机由JupyterLab提供动力(这是运行Jupyter笔记本的另一种web界面)。创建虚拟机后，向下滚动导航菜单到人工智能部分，然后单击AI平台笔记本。一旦Notebook实例出现在列表中(这可能需要几分钟的时间，所以偶尔单击Refresh直到它出现)，单击它打开的JupyterLab链接。这将在虚拟机上运行JupyterLab，并将浏览器连接到它。你可以在这个VM上创建笔记本并运行任何你想要的代码，并且享受它的GPU。 但是，如果你只是想进行一些快速的测试，或者简单地与你的同事共享笔记本，那么你应该尝试Colaboratory。  19.6.3 Colaboratory 访问GPU VM最简单和最便宜的方法是使用Colaboratory(或Colab，简称)。是免费的!只需访问https://colab.research.google.com/并创建一个新的python3笔记本:这将在你的谷歌驱动器上创建一个Jupyter notebook(或者，你可以打开GitHub或谷歌驱动器上的任何笔记本，或者你甚至可以上传自己的 notebooks)。Colab的用户界面与Jupyter的类似，除了你可以像谷歌文档一样共享和使用笔记本之外，还有一些其他的小区别(例如，你可以在代码中使用特殊的注释创建方便的小部件)。 当你打开一个Colab notebook，它运行在一个专门给你的免费的谷歌虚拟机，称为Colab Runtime。默认情况下，Runtime是cpu专用的，但你可以通过运行时→“更改运行时类型”，在“硬件加速器”下拉菜单中选择GPU，然后点击保存来改变。事实上，你甚至可以选择TPU!(是的，你确实可以免费使用TPU;我们将在本章后面讨论TPUs，所以现在只选择GPU。) 如果你使用相同的Runtime类型运行多个Colab笔记本(参见图19- 11)，它们将使用相同的Colab运行时。因此，如果一个写入文件，其他的将能够读取该文件。理解这样做的安全影响是很重要的:如果你运行一个不受信任的Colab笔记本，它可能读取其他笔记本产生的私有数据，然后将这些数据泄露给黑客。如果其中包含某些资源的私有访问密钥，那么黑客将获得对这些资源的访问权。此外，如果你在Colab运行时中安装了一个库，那么其他笔记本也将拥有该库。这取决于你想做什么，这可能是伟大的，也可能是恼人的(例如，这意味着你不能在不同的Colab笔记本上轻松地使用同一个库的不同版本)。 Colab确实有一些限制:正如FAQ所述，Colaboratory是用于交互使用的。长时间运行的后台计算，特别是在gpu上，可能会被停止。请不要使用Colaboratory加密货币挖掘。如果你将它闲置一段时间(~30分钟)，web界面将自动断开与Colab Runtime的连接。当你重新连接到ColabRuntime时，它可能已被重置，因此请确保始终下载你所关心的任何数据。即使你从未断开连接，Colab运行时也会在12小时后自动关闭，因为它不适用于长时间运行的计算。尽管存在这些限制，但它仍然是一个非常棒的工具，可以轻松地运行测试，快速获得结果，并与同事协作。  19.6.4 Managing the GPU RAM—管理GPU内存 默认情况下，当你第一次运行计算时，TensorFlow会自动抓取所有可用GPU中的所有内存。它这样做是为了限制GPU RAM碎片。这意味着，如果你试图启动第二个TensorFlow程序(或任何需要GPU的程序)，它将很快耗尽内存。这种情况并不像你想象的那样经常发生，因为你通常在一台机器上运行一个TensorFlow程序:通常是一个训练脚本、一个TF服务节点或一个Jupyter笔记本。如果你因为某些原因需要运行多个程序(例如，在同一台机器上并行地训练两个不同的模型)，那么你需要在这些进程之间更平均地分割GPU内存。 如果你的机器上有多个GPU卡，一个简单的解决方案是分配他们每一个单独的进程。为此，你可以设置CUDA_VISIBLE_DEVICES环境变量，以便每个进程只能看到适当的GPU卡。另外，将CUDA_DEVICE_ORDER环境变量设置为PCI_BUS_ID，以确保每个ID始终指向相同的GPU卡。例如，如果你有4个GPU卡，你可以启动两个程序，给他们每个分配两个GPU，通过在两个独立的终端窗口执行如下命令：  $ CUDA_DEVICE_ORDER=PCI_BUS_ID CUDA_VISIBLE_DEVICES=0,1 python3program_1.py $ CUDA_DEVICE_ORDER=PCI_BUS_ID CUDA_VISIBLE_DEVICES=3,2 python3program_2.py 程序1只会看到GPU卡0和1，分别命名为/ GPU:0和/ GPU:1，程序2只会看到GPU卡2和GPU: 3，分别命名为/ GPU:1和/ GPU:0(注意顺序)。一切都将正常工作(参见图19- 12)。当然，你也可以通过设置os在Python中定义这些环境变量。环境(“CUDA_DEVICE_ORDER”)和操作系统。环境["CUDA_VISIBLE_DEVICES"]，只要你在使用TensorFlow之前这样做。 ![15](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/153544-252362.png) 另一个选择是告诉TensorFlow抓取特定数量的GPU RAM。这必须在导入TensorFlow之后立即执行。例如，要使TensorFlow在每个GPU上只抓取2 GiB的RAM，你必须为每个物理GPU设备创建一个虚拟GPU设备(也称为逻辑GPU设备)，并将其内存限制设置为2 GiB(即2048 MiB) for gpu in tf.config.experimental.list_physical_devices("GPU"):tf.config.experimental.set_virtual_device_configuration(gpu,[tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2048)]) 现在(假设你有四个GPU，每个都有至少4 GiB的RAM)两个像这样的程序可以并行运行，每个都使用所有的四张GPU卡(图19-13)。![16](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/154122-39596.png) 如果你运行nvidia-smi命令同时运行两个程序，你应该看到每个进程在每个卡上有2 GiB的RAM:![17](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/154342-605453.png) 另一种选择是告诉TensorFlow只在需要内存时抓取内存(这也必须在导入TensorFlow后立即完成):  for gpu in tf.config.experimental.list_physical_devices("GPU"):tf.config.experimental.set_memory_growth(gpu, True) 另一种方法是将TF_FORCE_GPU_ALLOW_GROWTH环境变量设置为true。有了这个选项，TensorFlow一旦捕获了内存就永远不会释放内存(同样，为了避免内存碎片)，当然，程序结束时除外。使用此选项可能很难保证确定性的行为(例如，一个程序可能会因为另一个程序的内存使用量飙升而崩溃)，因此在生产中，你可能需要坚持使用前面的选项之一。但是，在某些情况下它非常有用:例如，当你使用一台机器运行多个Jupyter notebooks时，其中几个会使用TensorFlow。这就是为什么在Colab运行时将TF_FORCE_GPU_ALLOW_GROWTH环境变量设置为true的原因。 最后,在某些情况下,你可能希望GPU分割成两个或多个虚拟GPU—举个例子,如果你想测试一个分布算法(在本章的其余部分的代码示例,即使你只有一个单一的GPU也能够运行,如在Colab运行时)。下面的代码将第一个GPU分割成两个虚拟设备，每个虚拟设备有2 GiB RAM(同样，这必须在导入TensorFlow后立即完成)：  physical_gpus = tf.config.experimental.list_physical_devices("GPU")tf.config.experimental.set_virtual_device_configuration(physical_gpus[0],[tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2048),  tf.config.experimental.VirtualDeviceConfiguration(memory_limit=2048)]) 这两个虚拟设备将被调用/gpu:0和/gpu:1，你可以在它们上面放置操作和变量，就好像它们真的是两个独立的gpu一样。现在让我们看看TensorFlow如何决定应该在哪些设备上放置变量和执行操作。  19.6.5 Placing Operations and Variables on Devices—在设备上放置操作和变量 TensorFlow白皮书提供了一个友好的dynamic placer算法,自动在所有可用的设备分配操作,考虑到诸如在以前的图测量计算时间,为每个操作估计张量的输入和输出的大小,在每一个设备上可用的RAM数量,设备传输数据时的通信延迟,从用户那里得到提示和约束。在实践中，该算法的效率低于用户指定的一小组放置规则，因此TensorFlow团队最终放弃了动态放置器。 也就是说,tf.keras和tf.data通常能很好地处理操作和变量(例如，在GPU上进行大量计算，在CPU上进行数据预处理)。但是你也可以手动放置操作和变量在每个设备上，如果你想要拥有更多的控制:  * 如前所述，你通常希望将数据预处理操作放在CPU上，而将神经网络操作放在GPU上。* GPU通常有一个相当有限的通信带宽，因此避免不必要的数据传输进出GPU是重要的。* 对一台机器来说，添加更多的CPU RAM很简单,成本很小,所以通常有很多,但是GPU内存烤到GPU:这是一个昂贵的操作,因此资源有限。因此,如果一个变量在接下来的几个步骤不需要训练,它应该被放置在CPU上(例如,数据集通常是放CPU上)。 默认情况下，除了没有GPU内核的变量和操作:这些都被放在CPU上(命名/ CPU:0)外，所有的变量和操作都被放在第一个GPU上(命名/ GPU:0)。一个张量或变量的设备属性告诉你它被放置在哪个设备上：  >>> a = tf.Variable(42.0)>>> a.device'/job:localhost/replica:0/task:0/device:GPU:0'>>> b = tf.Variable(42)>>> b.device'/job:localhost/replica:0/task:0/device:CPU:0' 你现在可以安全地忽略前缀/job:localhost/replica:0/task:0(它允许你在使用TensorFlow集群时在其他机器上放置操作;我们将在本章的后面讨论工作、副本和任务)。如你所见，第一个变量被放置在GPU 0上，这是默认设备。然而，第二个变量被放在了CPU上:这是因为没有用于整型变量(或涉及整型张量的操作)的GPU内核，所以TensorFlow回到了CPU。 如果你想在不同的设备上执行操作，使用tf.device()context:  >>> with tf.device("/cpu:0"):... c = tf.Variable(42.0)...>>> c.device'/job:localhost/replica:0/task:0/device:CPU:0' 如果你显式地尝试在不存在或不存在内核的设备上放置操作或变量，那么你将会得到一个异常。然而，在某些情况下，你可能更喜欢回到CPU;例如，如果你的程序可以同时运行在只有cpu的机器和GPU机器上，你可能希望TensorFlow在只有cpu的机器上能够忽略你的tf.device("/ GPU:") 。要做到这一点，你可以在导入TensorFlow之后调用tf.config.set_soft_device_placement(True):当放置请求失败时，TensorFlow将返回到它的默认放置规则(如果存在GPU内核，则默认为GPU 0，否则为CPU 0)。 那么TensorFlow如何在多个设备上执行所有这些操作呢?  19.6.6 Parallel Execution Across Multiple Devices—跨多个设备并行执行 正如我们在第12章中看到的，使用TF函数的好处之一是并行性。让我们更仔细地看看这个特点。当TensorFlow运行一个TF函数时，它首先分析它的图，找出需要评估的操作列表，然后计算每个操作有多少依赖项。然后TensorFlow添加每个没有依赖关系的操作(比如原操作）到这个操作设备的评估队列(参见图19-14)。一旦计算了一个操作，依赖于它的每个操作的依赖计数器就会减少。一旦操作的依赖计数器达到0，它就被推到设备的计算队列中。一旦对TensorFlow所需的所有节点进行了计算，它将返回它们的输出。![18](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/163213-949164.jpeg) CPU评估队列中的操作被分配到一个称为inter-op thread pool的线程池。如果CPU有多个核心，那么这些操作将有效地并行评估。有些操作有多线程的CPU内核:这些内核将它们的任务分割成多个子操作，这些子操作被放置在另一个评估队列中，并被分派到第二个线程池，称为intra-op thread pool(由所有多线程CPU内核共享)。简而言之，多个操作和子操作可以在不同的CPU核心上并行评估。 对于GPU来说，事情就简单多了。GPU评估队列中的操作是按顺序评估的。然而，大多数操作都有多线程的GPU内核，通常由TensorFlow所依赖的库实现，例如CUDA和cuDNN。这些实现都有自己的线程池，而且它们通常会尽可能多地利用GPU线程(这就是为什么GPU中不需要操作inter-op thread pool的原因:每个操作已经占用了大多数GPU线程)。 例如，在图19-14中，操作A、B和C是源操作，因此它们可以立即被评估。操作A和B被放在CPU上，因此它们被发送到CPU的评估队列，然后它们被分派到inter-op thread pool，并立即并行地进行评估。操作A恰好有一个多线程内核;它的计算被分成三个部分，由intra-op thread pool并行执行。操作C进入GPU 0的计算队列，在本例中，它的GPU内核恰好使用cuDNN，它管理自己的intra-op thread pool，并在多个GPU线程上并行运行操作。假设C先结束。D和E的依赖计数器减小到0，所以这两个操作都被推到GPU的计算队列中，依次执行。注意，C只计算一次，即使D和E都依赖于它。假设下一个是B。然后F的依赖计数器从4递减到3，由于它不为0，它还不运行。一旦A、D、E完成，则F的依赖计数器达到0，被推到CPU的计算队列中进行计算。最后，TensorFlow返回请求的输出。 当TF函数修改有状态资源(比如变量)时，TensorFlow还会执行一个额外的魔法:它确保执行的顺序与代码中的顺序匹配，即使语句之间没有显式的依赖关系。举个例子，如果你的TF函数包含v.assign_add(1)后面跟着v.assign(v * 2)中，TensorFlow将确保这些操作按该顺序执行。 有了这些，你就有了在任何设备上运行任何操作所需的一切，并充分利用你的gpu的能力!这里有一些你可以做的事情:  * 你可以在它自己的GPU上并行训练几个模型:只需要为每个模型编写一个训练脚本并并行运行它们，设置CUDA_DEVICE_ORDER和CUDA_VISIBLE_DEVICES，这样每个脚本只能看到一个GPU设备。这对于超参数调优非常有用，因为你可以在具有不同超参数的并行多个模型中进行训练。如果你有一台机器，有两个GPU，在一个GPU上训练一个模型需要一个小时，那么并行训练两个模型，每个模型在自己专用的GPU上，只需要一个小时。简单多啦!* 你可以在单个GPU上训练一个模型，并在CPU上并行执行所有的预处理，使用dataset的prefetch()方法提前准备接下来的几批，这样当GPU需要它们时它们就已经准备好了(见第13章)。* 如果你的模型将两幅图像作为输入，然后使用两个CNN对它们进行处理，然后再将它们的输出合并到一起，那么如果你将每个CNN放在不同的GPU上，它可能会运行得更快。* 你可以创建一个有效的集成:只在每个GPU上放置一个不同的训练模型，这样你就可以更快地得到所有的预测，以产生集成的最终预测。 但是，如果你想要训练一个跨多个gpu的单一模型呢?  19.6.7 Training Models Across Multiple Devices—跨多个设备的模型训练 跨多个设备训练单个模型有两种主要方法:模型并行，即模型跨多个设备拆分;数据并行，即模型跨每个设备复制，每个副本在数据的一个子集上进行训练。在我们在多gpu上训练模型之前，让我们仔细看看这两个选项。  19.6.7.1 Model Parallelism—模型并行 到目前为止，我们已经在单个设备上训练了每个神经网络。如果我们想训练一个跨多种设备的神经网络会怎样?这需要将模型切成单独的块，并在不同的设备上运行每个块。不幸的是，这种模型并行性是非常棘手的，它实际上取决于神经网络的架构。对于完全连接的网络，这种方法通常不会有太多的收获(参见图19-15)。直观上，拆分模型的一种简单方法似乎是将每一层放在不同的设备上，但这并不可行，因为每一层都需要等待前一层的输出，然后才能执行任何操作。所以，也许你可以垂直地切割它—举个例子，每一层的左半部分在一个设备上，右半部分在另一个设备上?这稍微好一点，因为每一层的两半部分确实可以并行工作，但问题是下一层的每半部分都需要两半部分的输出，因此会有大量的跨设备通信(由虚线箭头表示)。这可能会完全抵消并行计算的好处，因为跨设备通信很慢(特别是当设备位于不同的机器上时)。![19](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/170959-261799.jpeg) 一些神经网络结构，如卷积神经网络(参见第14章)，包含的层只部分连接到较低的层，因此更容易高效地在设备间分配数据块(图19-16)。![20](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/171236-543709.jpeg) 深度递归神经网络(见第15章)可以在多个gpu之间更有效地分割。如果你把网络水平通过将每一层在不同的设备,并且输入序列的投喂到网络进行处理,然后在第一步只有一个设备将是活跃的(在序列的第一个值),在第二步两个活跃(第二层将处理的输出第一层第一个值,而第一层将处理第二个值),当信号传播到输出层,同时所有设备都是活跃的(图19-17)。在这里仍然有许多跨设备通信在进行，但是由于每个单元可能相当复杂，因此并行运行多个单元的好处(理论上)可能大于通信损失。然而，实际上，运行在单一GPU上的LSTM层的常规堆栈运行起来要快得多。![21](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/172015-937632.jpeg)简而言之，模型并行性可能会加速某些类型的神经网络的运行或训练，但并不是所有类型的神经网络，它需要特别注意和调优，比如确保最需要通信的设备在同一台机器上运行。让我们看看一个更简单、通常更有效的选项:数据并行。  19.6.7.2 Data Parallelism—数据并行 将神经网络的训练并行化的另一种方法是在每个设备上复制它，并在所有副本上同时运行每个训练步骤，对每个副本使用不同的小批处理。每个副本计算出的梯度然后被平均，并且结果被用来更新模型参数。这叫做数据并行性。这个想法有很多不同的版本，所以让我们看看最重要的一个。 使用镜像策略的数据并行：  镜像策略的数据并行可以说是最简单的方法，在所有GPU上完全镜像所有的模型参数，并且总是在每个GPU上应用完全相同的参数更新。这样，所有的副本总是保持完全相同。这被称为镜像策略，并且它被证明是非常高效的，特别是在使用单个机器时时(参见图19-18)。![22](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/172402-272999.jpeg)当使用这种方法时，棘手的部分是有效地计算所有gpu的所有梯度的平均值，并在所有gpu上分布结果。这可以使用AllReduce算法来完成，这是一种算法，其中多个节点协作高效地执行一个reduce操作(比如计算平均值、总和和最大值)，同时确保所有节点获得相同的最终结果。幸运的是，这类算法有现成的实现。 具有集中参数的数据并行：另一种方法是将模型参数存储在执行计算的GPU设备(称为workers)之外，例如在CPU(参见图19-19)。在分布式设置中，可以将所有参数放在一个或多个仅允许cpu的服务器上，这些服务器称为参数服务器，它们的唯一角色是托管和更新参数。![23](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/195253-333770.png)镜像策略要求在所有gpu上同步更新权重，而这种集中的方法允许同步或异步更新。让我们看看这两种选择的利弊。 * 同步更新：使用同步更新，聚合器将等待，直到所有梯度都可用，然后计算平均梯度并将其传递给优化器，优化器将更新模型参数。一旦一个副本完成了它的梯度计算，在继续处理下一个小批处理之前，它必须等待参数被更新。这样做的缺点是一些设备可能比其他设备慢，所以所有其他设备将不得不在每一步等待它们。此外，参数将几乎在同一时间(在应用梯度后立即)复制到每个设备，这可能会使参数服务器的带宽饱和。* 异步更新使用异步更新，每当一个副本完成了梯度的计算，它立即使用它们来更新模型参数。没有聚合(它删除了图19-19中的“mean”步骤)，也没有同步。副本独立于其他副本工作。由于不需要等待其他副本，因此这种方法每分钟运行更多的训练步骤。此外，虽然每一步仍需要将参数复制到每个设备上，但每个复制发生的时间不同，因此降低了带宽饱和的风险。带有异步更新的数据并行是一个很有吸引力的选择，因为它简单、没有同步延迟和更好地利用带宽。然而，尽管它在实践中工作得相当好，但它竟然能有效，这还是很令人惊讶!事实上,副本的时候已经完成了基于一些参数值计算梯度,这些参数将会被其他副本更新几次(平均N-1,如果有N副本),并不能保证计算梯度仍将指向正确的方向(参见图19-20)。当梯度严重过时时，它们被称为陈旧的梯度:它们会减慢收敛速度，引入噪声和抖动效应(学习曲线可能包含临时的振荡)，甚至会使训练算法发散。![24](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/200746-542229.png) 这里有有几种方法可以降低过时梯度的影响:* 降低学习率。* 丢弃过失梯度或将其缩小。* 调整小批量大小。* 在开始的前几个epoch(这称为预热阶段)，只使用一个副本。在训练的开始阶段，当梯度通常很大且参数还没有固定到成本函数的谷值时，过时梯度往往更具有破坏性，因此不同的副本可能会将参数推向完全不同的方向。谷歌Brain团队在2016年发表的一篇论文对各种方法进行了基准测试，发现使用一些备用副本的同步更新比使用异步更新更有效，不仅收敛速度更快，还能产生更好的模型。然而，这仍然是一个活跃的研究领域，所以你还不应该排除异步更新。 带宽饱和：  无论使用同步还是异步更新，具有集中参数的数据并行，仍然需要在每个训练步骤开始时，将模型参数从参数服务器传输到每个副本，以及在每个训练步骤结束时向另一个方向传输梯度。类似地，当使用镜像策略时，每个GPU产生的梯度将需要与其他GPU共享。不幸的是，总会出现这样的情况:增加一个额外的GPU根本不能提高性能，因为将数据移入和移出GPU RAM(在分布式设置中通过网络)所花费的时间将超过通过分摊计算负载所获得的加速。在这一点上，增加更多的gpu只会使带宽饱和恶化，实际上会减慢训练速度。 饱和度问题对于大密度模型来说更为严重，因为它们有很多参数和梯度需要传递。对于小型模型(但并行化增益有限)和大型稀疏模型(其梯度通常为零，因此可以有效地进行通信)，这种情况不太严重。谷歌大脑项目的发起者和领导者<NAME>报告说，对于密集模型，在50个gpu上进行计算时，通常加速25到40倍，而对于在500个gpu上训练的稀疏模型，加速300倍。如你所见，稀疏模型确实能更好地缩放。这里有几个具体的例子：* 神经机器翻译:在8个gpu上加速6倍* Inception/ImageNet：在50个gpu上加速32倍* RankBrain：在500个gpu上加速300倍 对于稠密模型，超过几十个gpu，或者对于稀疏模型，超过几百个gpu，饱和就会出现，性能会下降。有大量的研究在解决这个问题(探索点对点架构而不是集中参数服务器,使用有损压缩模型,优化副本需要通信的时间和内容,等等),所以在未来几年，并行神经网络可能会有很多进步。  同时，为了减弱饱和问题，你可能想要使用一些功能强大的GPU，而不是大量的弱GPU，并且你也应该在一些连接良好的服务器上分组你的GPU。你还可以尝试将浮点精度从32位(tf.float32)降低到16位(tf.bfloat16)。这将把需要传输的数据量减少一半，通常不会对收敛速度或模型的性能造成太大影响。最后，如果你使用集中的参数，你可以跨多个参数服务器对参数进行切分(拆分):添加更多的参数服务器将减少每个服务器上的网络负载，并限制带宽饱和的风险。  好了，现在让我们训练一个跨多个gpu的模型  19.7 Training at Scale Using the Distribution Strategies API—使用分配策略API进行大规模训练 许多模型可以在单一GPU上，甚至在CPU上很好地训练。但是如果训练太慢，你可以尝试将其分布在同一台机器上的多个gpu上。如果这仍然太慢，尝试使用更强大的gpu，或增加更多的gpu到机器。如果你的模型需要执行繁重的计算(比如大型矩阵乘法)，那么它将在强大的GPU上运行得更快，你甚至可以尝试在谷歌Cloud AI平台上使用TPUs，对于这类模型，TPUs通常运行得更快。但是如果你不能在同一台机器上安装更多的GPU,如果tpu不适合你(例如,也许你的模型没有从TPU中获得太多好处，或者你想使用自己的硬件基础设施),然后，你可以尝试跨多个服务器(每个服务器都有多个gpu)对它进行训练(如果这还不够，最后可以尝试添加一些模型并行，但这需要付出更多努力)。在本节中，我们将看到如何按模型规模训练模型，从同一台机器(或TPUs)上的多个GPU开始，然后转移到跨多台机器的多个GPU。 幸运的是，TensorFlow附带了一个非常简单的API，可以为你处理所有的复杂性:Distribution Strategies API。要跨所有可用GPU，使用镜像策略的数据并行训练Keras模型(目前是在一台机器上)，请创建一个MirroredStrategy对象，调用其scope()方法来获得分配的环境，并在该环境中包装模型的创建和编译。然后调用模型的fit()方法：<jupyter_code>keras.backend.clear_session()
tf.random.set_seed(42)
np.random.seed(42)
def create_model():
    return keras.models.Sequential([
        keras.layers.Conv2D(filters=64, kernel_size=7, activation="relu",
                            padding="same", input_shape=[28, 28, 1]),
        keras.layers.MaxPooling2D(pool_size=2),
        keras.layers.Conv2D(filters=128, kernel_size=3, activation="relu",
                            padding="same"), 
        keras.layers.Conv2D(filters=128, kernel_size=3, activation="relu",
                            padding="same"),
        keras.layers.MaxPooling2D(pool_size=2),
        keras.layers.Flatten(),
        keras.layers.Dense(units=64, activation='relu'),
        keras.layers.Dropout(0.5),
        keras.layers.Dense(units=10, activation='softmax'),
    ])
batch_size = 100
model = create_model()
model.compile(loss="sparse_categorical_crossentropy",
              optimizer=keras.optimizers.SGD(lr=1e-2),
              metrics=["accuracy"])
model.fit(X_train, y_train, epochs=10,
          validation_data=(X_valid, y_valid), batch_size=batch_size)
keras.backend.clear_session()
tf.random.set_seed(42)
np.random.seed(42)

distribution = tf.distribute.MirroredStrategy()

# Change the default all-reduce algorithm:
#distribution = tf.distribute.MirroredStrategy(
#    cross_device_ops=tf.distribute.HierarchicalCopyAllReduce())

# Specify the list of GPUs to use:
#distribution = tf.distribute.MirroredStrategy(devices=["/gpu:0", "/gpu:1"])

# Use the central storage strategy instead:
#distribution = tf.distribute.experimental.CentralStorageStrategy()

#resolver = tf.distribute.cluster_resolver.TPUClusterResolver()
#tf.tpu.experimental.initialize_tpu_system(resolver)
#distribution = tf.distribute.experimental.TPUStrategy(resolver)

with distribution.scope():
    model = create_model()
    model.compile(loss="sparse_categorical_crossentropy",
                  optimizer=keras.optimizers.SGD(lr=1e-2),
                  metrics=["accuracy"])
batch_size = 100 # must be divisible by the number of workers
model.fit(X_train, y_train, epochs=10,
          validation_data=(X_valid, y_valid), batch_size=batch_size)<jupyter_output><empty_output><jupyter_text>tf.keras是分布式感知的，所以在这个s MirroredStrategy的环境中，它知道它必须在所有可用的GPU设备上复制所有变量和操作。注意，fit()方法将自动在所有副本中分割每个训练批处理，因此批处理大小必须能被副本的数量整除，这一点很重要。就这些!训练通常会比使用单一设备快得多，而且代码的变化也非常小。 一旦你完成了对模型的训练，你就可以使用它来高效地进行预测:调用predict()方法，它将自动地将所有副本进行批处理分割，从而并行地进行预测(同样，批处理大小必须能被副本的数量整除)。<jupyter_code>model.predict(X_new)<jupyter_output><empty_output><jupyter_text>如果调用模型 save()方法，它将被保存为常规模型，而不是具有多个副本的镜像模型。所以当你加载它的时候，它会像一个普通的模型一样运行，在一个单一的设备上(默认的GPU 0，或者如果没有GPU则使用CPU)。如果你想加载一个模型并在所有可用设备上运行它，你必须在分配环境中调用keras.models.load_model()。  with distribution.scope():mirrored_model = keras.models.load_model("my_mnist_model.h5")  如果你只想使用所有可用GPU设备的一个子集，你可以将列表传递给MirroredStrategy的构造器:  distribution = tf.distribute.MirroredStrategy(["/gpu:0", "/gpu:1"])<jupyter_code>keras.backend.clear_session()
tf.random.set_seed(42)
np.random.seed(42)

K = keras.backend

distribution = tf.distribute.MirroredStrategy()

with distribution.scope():
    model = create_model()
    optimizer = keras.optimizers.SGD()

with distribution.scope():
    dataset = tf.data.Dataset.from_tensor_slices((X_train, y_train)).repeat().batch(batch_size)
    input_iterator = distribution.make_dataset_iterator(dataset)
    
@tf.function
def train_step():
    def step_fn(inputs):
        X, y = inputs
        with tf.GradientTape() as tape:
            Y_proba = model(X)
            loss = K.sum(keras.losses.sparse_categorical_crossentropy(y, Y_proba)) / batch_size

        grads = tape.gradient(loss, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))
        return loss

    per_replica_losses = distribution.experimental_run(step_fn, input_iterator)
    mean_loss = distribution.reduce(tf.distribute.ReduceOp.SUM,
                                    per_replica_losses, axis=None)
    return mean_loss

n_epochs = 10
with distribution.scope():
    input_iterator.initialize()
    for epoch in range(n_epochs):
        print("Epoch {}/{}".format(epoch + 1, n_epochs))
        for iteration in range(len(X_train) // batch_size):
            print("\rLoss: {:.3f}".format(train_step().numpy()), end="")
        print()
batch_size = 100 # must be divisible by the number of workers
model.fit(X_train, y_train, epochs=10,
          validation_data=(X_valid, y_valid), batch_size=batch_size)<jupyter_output><empty_output><jupyter_text>默认情况下，对MirroredStrategy类使用NVIDIA集体通信库(NCCL)来执行AllReduce mean操作，但是你可以通过将cross_device_ops参数设置为tf.distribute.HierarchicalCopyAllReduce的一个实例来更改它，或者是tf.distribute.ReductionToOneDevice类一个实例。默认的NCCL选项基于tf.distribute.NcclAllReduce类，它通常更快，但这取决于GPU的数量和类型，因此你可能想尝试一下其他方法。  如果你想尝试使用集中式参数的数据并行，可以使用CentralStorageStrategy来替换MirroredStrategy:  distribution = tf.distribute.experimental.CentralStorageStrategy()你可以设置compute_devices参数来指定你想要使用作为worker的设备列表（默认情况下它将使用所有可用的GPU),你可以选择设置parameter_device参数来指定要存储设备的参数(默认情况下它将使用CPU，或者GPU如果有的话)。  现在让我们看看如何训练一个跨TensorFlow服务器集群的模型!  19.8 Training across multiple servers—跨多个服务器训练 一个TensorFlow集群是一组并行运行的TensorFlow进程，通常在不同的机器上，并相互通信以完成一些工作，例如训练或执行神经网络。集群中的每个TF进程称为一个“任务”(或一个“TF服务器”)。它有一个IP地址、一个端口和一个类型(也称为其角色或其任务)。类型可以是“worker”、“chief”、“ps”(参数服务器)或“evaluator”:* 每个**worker**执行计算，通常在一台有一个或多个GPU的机器上。* **主chief**也执行计算，但它也处理额外的工作，如编写TensorBoard日志或保存检查点。集群中只有一个主节点。如果没有指定chief，那么第一个worker就是chief。* 一个**参数服务器** (ps)只跟踪变量值，它通常在只支持cpu的机器上。此类型的任务只用ParameterServerStrategy进行。* 一个**evaluator**显然负责评估。一个集群中通常只有一个求值器。具有相同类型的一组任务通常称为“job”。例如，“worker”job是所有worker的集合。要启动一个TensorFlow集群，你必须首先指定它。这意味着定义所有的任务(IP地址、TCP端口和类型)。例如，下面的集群规范定义了一个具有3个任务(2个工作人员和1个参数服务器)的集群。它是一个字典，每个任务有一个键，值是任务地址列表:```{    "worker": ["my-worker0.example.com:9876", "my-worker1.example.com:9876"],    "ps": ["my-ps0.example.com:9876"]}```集群中的每个任务都可能与服务器中的每个其他任务通信，因此请确保配置你的防火墙，以授权这些机器之间在这些端口上的所有通信(如果你在每台机器上使用相同的端口，通常会更简单)。当一个任务启动时，需要告诉它是哪一个:它的类型和索引(任务索引也称为任务id)。一次指定所有内容(包括集群规范和当前任务的类型和id)的常见方法是在启动程序之前设置' TF_CONFIG '环境变量。它必须是一个json编码的字典，包含一个集群规范(在“cluster”键下)，以及要开始的任务的类型和索引(在“task”键下)。例如，下面的“TF_CONFIG”环境变量定义了一个简单的集群，该集群有2个worker和1个参数服务器，见图19-21，并指定要启动的任务是第一个worker。![25](https://cdn.jsdelivr.net/gh/sadggdsa/typora-plugins-win-img@master/typora202008/24/210931-161278.png) 一般来说，每台机器上只有一个任务，但是如本例所示，如果你愿意，你可以在同一台机器上配置多个任务(如果它们共享相同的GPU，请确保适当地分割RAM，如前所述)。 当你开始一项任务,你必须给它集群规范,你还必须告诉它，它的类型和索引是什么(例如,worker 0)。最简单的方法来指定所有一次(集群规范和当前任务类型和索引)设置开始TensorFlow TF_CONFIG环境变量。它必须是一个json编码的字典，包含集群规范(在“cluster”键下)和当前任务的类型和索引(在“task”键下)。例如，下面的TF_CONFIG环境变量使用我们刚刚定义的集群，并指定要启动的任务是第一个worker<jupyter_code>import os
import json

os.environ["TF_CONFIG"] = json.dumps({
    "cluster": {
        "worker": ["my-work0.example.com:9876", "my-work1.example.com:9876"],
        "ps": ["my-ps0.example.com:9876"]
    },
    "task": {"type": "worker", "index": 0}
})
print("TF_CONFIG='{}'".format(os.environ["TF_CONFIG"]))<jupyter_output>TF_CONFIG='{"cluster": {"worker": ["my-work0.example.com:9876", "my-work1.example.com:9876"], "ps": ["my-ps0.example.com:9876"]}, "task": {"type": "worker", "index": 0}}'<jupyter_text>一些平台(例如谷歌Cloud ML Engine)会自动为你设置这个环境变量。 然后你可以编写一个简短的Python脚本来启动任务。相同的脚本可以在每台机器上使用，因为它将加载' TF_CONFIG '变量，它将告诉它开始哪个任务:<jupyter_code>import tensorflow as tf

resolver = tf.distribute.cluster_resolver.TFConfigClusterResolver()
worker0 = tf.distribute.Server(resolver.cluster_spec(),
                               job_name=resolver.task_type,
                               task_index=resolver.task_id)<jupyter_output><empty_output><jupyter_text>另一种指定集群规范的方法是直接在Python中，而不是通过环境变量:<jupyter_code>cluster_spec = tf.train.ClusterSpec({
    "worker": ["127.0.0.1:9901", "127.0.0.1:9902"],
    "ps": ["127.0.0.1:9903"]
})<jupyter_output><empty_output><jupyter_text>然后，你只需向服务器传递集群规范并指示其类型和索引，就可以启动服务器。让我们启动剩下的两个任务(记住，一般情况下，每台机器只能启动一个任务;我们在localhost上启动了3个任务，只是为了这个代码示例的目的):<jupyter_code>#worker1 = tf.distribute.Server(cluster_spec, job_name="worker", task_index=1)
ps0 = tf.distribute.Server(cluster_spec, job_name="ps", task_index=0)
os.environ["TF_CONFIG"] = json.dumps({
    "cluster": {
        "worker": ["127.0.0.1:9901", "127.0.0.1:9902"],
        "ps": ["127.0.0.1:9903"]
    },
    "task": {"type": "worker", "index": 1}
})
print(repr(os.environ["TF_CONFIG"]))<jupyter_output>'{"cluster": {"worker": ["127.0.0.1:9901", "127.0.0.1:9902"], "ps": ["127.0.0.1:9903"]}, "task": {"type": "worker", "index": 1}}'<jupyter_text>出奇的简单吧!首先，你需要为每个任务适当地设置TF_CONFIG环境变量。这里不应该有参数服务器(删除集群规范中的ps键)，并且通常需要每台机器有一个worker。确保你为每个任务设置了不同的任务索引。最后，在每个worker上运行以下训练代码：<jupyter_code>distribution = tf.distribute.experimental.MultiWorkerMirroredStrategy()

keras.backend.clear_session()
tf.random.set_seed(42)
np.random.seed(42)

os.environ["TF_CONFIG"] = json.dumps({
    "cluster": {
        "worker": ["127.0.0.1:9901", "127.0.0.1:9902"],
        "ps": ["127.0.0.1:9903"]
    },
    "task": {"type": "worker", "index": 1}
})
#CUDA_VISIBLE_DEVICES=0 

with distribution.scope():
    model = create_model()
    model.compile(loss="sparse_categorical_crossentropy",
                  optimizer=keras.optimizers.SGD(lr=1e-2),
                  metrics=["accuracy"])
import tensorflow as tf
from tensorflow import keras
import numpy as np

# At the beginning of the program (restart the kernel before running this cell)
distribution = tf.distribute.experimental.MultiWorkerMirroredStrategy()

(X_train_full, y_train_full), (X_test, y_test) = keras.datasets.mnist.load_data()
X_train_full = X_train_full[..., np.newaxis] / 255.
X_test = X_test[..., np.newaxis] / 255.
X_valid, X_train = X_train_full[:5000], X_train_full[5000:]
y_valid, y_train = y_train_full[:5000], y_train_full[5000:]
X_new = X_test[:3]

n_workers = 2
batch_size = 32 * n_workers
dataset = tf.data.Dataset.from_tensor_slices((X_train[..., np.newaxis], y_train)).repeat().batch(batch_size)
    
def create_model():
    return keras.models.Sequential([
        keras.layers.Conv2D(filters=64, kernel_size=7, activation="relu",
                            padding="same", input_shape=[28, 28, 1]),
        keras.layers.MaxPooling2D(pool_size=2),
        keras.layers.Conv2D(filters=128, kernel_size=3, activation="relu",
                            padding="same"), 
        keras.layers.Conv2D(filters=128, kernel_size=3, activation="relu",
                            padding="same"),
        keras.layers.MaxPooling2D(pool_size=2),
        keras.layers.Flatten(),
        keras.layers.Dense(units=64, activation='relu'),
        keras.layers.Dropout(0.5),
        keras.layers.Dense(units=10, activation='softmax'),
    ])

with distribution.scope():
    model = create_model()
    model.compile(loss="sparse_categorical_crossentropy",
                  optimizer=keras.optimizers.SGD(lr=1e-2),
                  metrics=["accuracy"])

model.fit(dataset, steps_per_epoch=len(X_train)//batch_size, epochs=10)
# Hyperparameter tuning

# Only talk to ps server
config_proto = tf.ConfigProto(device_filters=['/job:ps', '/job:worker/task:%d' % tf_config['task']['index']])
config = tf.estimator.RunConfig(session_config=config_proto)
# default since 1.10
strategy.num_replicas_in_sync<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11421438601965986, 'fraction_numerical': 0.0372132938055859, 'mean_word_length': 9.554343351173323, 'pattern_counts': {'":': 38, '<': 123, '<?xml version=': 0, '>': 148, 'https://': 26, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 14, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '31', 'dir': 'jupyter-structured-clean-dedup', 'id': '544278', 'license': 'Apache-2.0', 'n_tokens_mistral': 32668, 'n_tokens_neox': 35115, 'n_words': 1636, 'path': '19_Training and Deploying TensorFlow Models at Scale.ipynb', 'repo_name': 'sadggdsa/Hands-on-Machine-Learning-2edition', 'size': '51297'}