{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "CCQY7jpBfMur" }, "source": [ "##### Copyright 2018 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "id": "z6X9omPnfO_h" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "2QQJJyDzqGRb" }, "source": [ "# Eager Execution\n" ] }, { "cell_type": "markdown", "metadata": { "id": "B1xdylywqUSX" }, "source": [ "\n", " \n", " \n", "
\n", " Run in Google Colab\n", " \n", " View source on GitHub\n", "
" ] }, { "cell_type": "markdown", "metadata": { "id": "B1xdylywqUSX" }, "source": [ "> Note: This is an archived TF1 notebook. These are configured\n", "to run in TF2's \n", "[compatbility mode](https://www.tensorflow.org/guide/migrate)\n", "but will run in TF1 as well. To use TF1 in Colab, use the\n", "[%tensorflow_version 1.x](https://colab.research.google.com/notebooks/tensorflow_version.ipynb)\n", "magic." ] }, { "cell_type": "markdown", "metadata": { "id": "EGjDcGxIqEfX" }, "source": [ "TensorFlow's eager execution is an imperative programming environment that\n", "evaluates operations immediately, without building graphs: operations return\n", "concrete values instead of constructing a computational graph to run later. This\n", "makes it easy to get started with TensorFlow and debug models, and it\n", "reduces boilerplate as well. To follow along with this guide, run the code\n", "samples below in an interactive `python` interpreter.\n", "\n", "Eager execution is a flexible machine learning platform for research and\n", "experimentation, providing:\n", "\n", "* *An intuitive interface*—Structure your code naturally and use Python data\n", " structures. Quickly iterate on small models and small data.\n", "* *Easier debugging*—Call ops directly to inspect running models and test\n", " changes. Use standard Python debugging tools for immediate error reporting.\n", "* *Natural control flow*—Use Python control flow instead of graph control\n", " flow, simplifying the specification of dynamic models.\n", "\n", "Eager execution supports most TensorFlow operations and GPU acceleration. For a\n", "collection of examples running in eager execution, see:\n", "[tensorflow/contrib/eager/python/examples](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples).\n", "\n", "Note: Some models may experience increased overhead with eager execution\n", "enabled. Performance improvements are ongoing, but please\n", "[file a bug](https://github.com/tensorflow/tensorflow/issues) if you find a\n", "problem and share your benchmarks." ] }, { "cell_type": "markdown", "metadata": { "id": "RBAeIwOMrYk8" }, "source": [ "## Setup and basic usage\n" ] }, { "cell_type": "markdown", "metadata": { "id": "48P3-8q4qEfe" }, "source": [ "To start eager execution, add `` to the beginning of\n", "the program or console session. Do not add this operation to other modules that\n", "the program calls." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "7aFsD8csqEff" }, "outputs": [], "source": [ "import tensorflow.compat.v1 as tf" ] }, { "cell_type": "markdown", "metadata": { "id": "x_G1zZT5qEfh" }, "source": [ "Now you can run TensorFlow operations and the results will return immediately:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "5hien2IEqwLQ" }, "outputs": [], "source": [ "tf.executing_eagerly()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "9gsI54pbqEfj" }, "outputs": [], "source": [ "x = [[2.]]\n", "m = tf.matmul(x, x)\n", "print(\"hello, {}\".format(m))" ] }, { "cell_type": "markdown", "metadata": { "id": "ajFn6qsdqEfl" }, "source": [ "Enabling eager execution changes how TensorFlow operations behave—now they\n", "immediately evaluate and return their values to Python. `tf.Tensor` objects\n", "reference concrete values instead of symbolic handles to nodes in a computational\n", "graph. Since there isn't a computational graph to build and run later in a\n", "session, it's easy to inspect results using `print()` or a debugger. Evaluating,\n", "printing, and checking tensor values does not break the flow for computing\n", "gradients.\n", "\n", "Eager execution works nicely with [NumPy](http://www.numpy.org/). NumPy\n", "operations accept `tf.Tensor` arguments. TensorFlow\n", "[math operations](https://www.tensorflow.org/api_guides/python/math_ops) convert\n", "Python objects and NumPy arrays to `tf.Tensor` objects. The\n", "`tf.Tensor.numpy` method returns the object's value as a NumPy `ndarray`." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "sTO0_5TYqz1n" }, "outputs": [], "source": [ "a = tf.constant([[1, 2],\n", " [3, 4]])\n", "print(a)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Dp14YT8Gq4r1" }, "outputs": [], "source": [ "# Broadcasting support\n", "b = tf.add(a, 1)\n", "print(b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "69p3waMfq8cQ" }, "outputs": [], "source": [ "# Operator overloading is supported\n", "print(a * b)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Ui025t1qqEfm" }, "outputs": [], "source": [ "# Use NumPy values\n", "import numpy as np\n", "\n", "c = np.multiply(a, b)\n", "print(c)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Tq_aFRzWrCua" }, "outputs": [], "source": [ "# Obtain numpy value from a tensor:\n", "print(a.numpy())\n", "# => [[1 2]\n", "# [3 4]]" ] }, { "cell_type": "markdown", "metadata": { "id": "H08f9ss9qEft" }, "source": [ "## Dynamic control flow\n", "\n", "A major benefit of eager execution is that all the functionality of the host\n", "language is available while your model is executing. So, for example,\n", "it is easy to write [fizzbuzz](https://en.wikipedia.org/wiki/Fizz_buzz):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "0fudRMeUqEfu" }, "outputs": [], "source": [ "def fizzbuzz(max_num):\n", " counter = tf.constant(0)\n", " max_num = tf.convert_to_tensor(max_num)\n", " for num in range(1, max_num.numpy()+1):\n", " num = tf.constant(num)\n", " if int(num % 3) == 0 and int(num % 5) == 0:\n", " print('FizzBuzz')\n", " elif int(num % 3) == 0:\n", " print('Fizz')\n", " elif int(num % 5) == 0:\n", " print('Buzz')\n", " else:\n", " print(num.numpy())\n", " counter += 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "P2cKknQWrJLB" }, "outputs": [], "source": [ "fizzbuzz(15)" ] }, { "cell_type": "markdown", "metadata": { "id": "7kA-aC3BqEfy" }, "source": [ "This has conditionals that depend on tensor values and it prints these values\n", "at runtime." ] }, { "cell_type": "markdown", "metadata": { "id": "zn3mp-j6rjnk" }, "source": [ "## Build a model\n", "\n", "Many machine learning models are represented by composing layers. When\n", "using TensorFlow with eager execution you can either write your own layers or\n", "use a layer provided in the `tf.keras.layers` package.\n", "\n", "While you can use any Python object to represent a layer,\n", "TensorFlow has `tf.keras.layers.Layer` as a convenient base class. Inherit from\n", "it to implement your own layer:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "kvXiJsmyqEfz" }, "outputs": [], "source": [ "class MySimpleLayer(tf.keras.layers.Layer):\n", " def __init__(self, output_units):\n", " super(MySimpleLayer, self).__init__()\n", " self.output_units = output_units\n", "\n", " def build(self, input_shape):\n", " # The build method gets called the first time your layer is used.\n", " # Creating variables on build() allows you to make their shape depend\n", " # on the input shape and hence removes the need for the user to specify\n", " # full shapes. It is possible to create variables during __init__() if\n", " # you already know their full shapes.\n", " self.kernel = self.add_variable(\n", " \"kernel\", [input_shape[-1], self.output_units])\n", "\n", " def call(self, input):\n", " # Override call() instead of __call__ so we can perform some bookkeeping.\n", " return tf.matmul(input, self.kernel)" ] }, { "cell_type": "markdown", "metadata": { "id": "eo3qgrVCqEf2" }, "source": [ "Use `tf.keras.layers.Dense` layer instead of `MySimpleLayer` above as it has\n", "a superset of its functionality (it can also add a bias).\n", "\n", "When composing layers into models you can use `tf.keras.Sequential` to represent\n", "models which are a linear stack of layers. It is easy to use for basic models:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "VrfLnhNPqEf3" }, "outputs": [], "source": [ "model = tf.keras.Sequential([\n", " tf.keras.layers.Dense(10, input_shape=(784,)), # must declare input shape\n", " tf.keras.layers.Dense(10)\n", "])" ] }, { "cell_type": "markdown", "metadata": { "id": "Dms3mduTqEf6" }, "source": [ "Alternatively, organize models in classes by inheriting from `tf.keras.Model`.\n", "This is a container for layers that is a layer itself, allowing `tf.keras.Model`\n", "objects to contain other `tf.keras.Model` objects." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "MwWxQmNOqEf7" }, "outputs": [], "source": [ "class MNISTModel(tf.keras.Model):\n", " def __init__(self):\n", " super(MNISTModel, self).__init__()\n", " self.dense1 = tf.keras.layers.Dense(units=10)\n", " self.dense2 = tf.keras.layers.Dense(units=10)\n", "\n", " def call(self, input):\n", " \"\"\"Run the model.\"\"\"\n", " result = self.dense1(input)\n", " result = self.dense2(result)\n", " result = self.dense2(result) # reuse variables from dense2 layer\n", " return result\n", "\n", "model = MNISTModel()" ] }, { "cell_type": "markdown", "metadata": { "id": "a639YaF4qEf-" }, "source": [ "It's not required to set an input shape for the `tf.keras.Model` class since\n", "the parameters are set the first time input is passed to the layer.\n", "\n", "`tf.keras.layers` classes create and contain their own model variables that\n", "are tied to the lifetime of their layer objects. To share layer variables, share\n", "their objects." ] }, { "cell_type": "markdown", "metadata": { "id": "8huKpuuAwICq" }, "source": [ "## Eager training" ] }, { "cell_type": "markdown", "metadata": { "id": "mp2lCCZYrxHd" }, "source": [ "### Computing gradients\n", "\n", "[Automatic differentiation](https://en.wikipedia.org/wiki/Automatic_differentiation)\n", "is useful for implementing machine learning algorithms such as\n", "[backpropagation](https://en.wikipedia.org/wiki/Backpropagation) for training\n", "neural networks. During eager execution, use `tf.GradientTape` to trace\n", "operations for computing gradients later.\n", "\n", "`tf.GradientTape` is an opt-in feature to provide maximal performance when\n", "not tracing. Since different operations can occur during each call, all\n", "forward-pass operations get recorded to a \"tape\". To compute the gradient, play\n", "the tape backwards and then discard. A particular `tf.GradientTape` can only\n", "compute one gradient; subsequent calls throw a runtime error." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "7g1yWiSXqEf-" }, "outputs": [], "source": [ "w = tf.Variable([[1.0]])\n", "with tf.GradientTape() as tape:\n", " loss = w * w\n", "\n", "grad = tape.gradient(loss, w)\n", "print(grad) # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)" ] }, { "cell_type": "markdown", "metadata": { "id": "vkHs32GqweYS" }, "source": [ "### Train a model\n", "\n", "The following example creates a multi-layer model that classifies the standard\n", "MNIST handwritten digits. It demonstrates the optimizer and layer APIs to build\n", "trainable graphs in an eager execution environment." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "38kymXZowhhz" }, "outputs": [], "source": [ "# Fetch and format the mnist data\n", "(mnist_images, mnist_labels), _ = tf.keras.datasets.mnist.load_data()\n", "\n", "dataset = tf.data.Dataset.from_tensor_slices(\n", " (tf.cast(mnist_images[...,tf.newaxis]/255, tf.float32),\n", " tf.cast(mnist_labels,tf.int64)))\n", "dataset = dataset.shuffle(1000).batch(32)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "rl1K8rOowmwT" }, "outputs": [], "source": [ "# Build the model\n", "mnist_model = tf.keras.Sequential([\n", " tf.keras.layers.Conv2D(16,[3,3], activation='relu'),\n", " tf.keras.layers.Conv2D(16,[3,3], activation='relu'),\n", " tf.keras.layers.GlobalAveragePooling2D(),\n", " tf.keras.layers.Dense(10)\n", "])" ] }, { "cell_type": "markdown", "metadata": { "id": "fvyk-HgGwxwl" }, "source": [ "Even without training, call the model and inspect the output in eager execution:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "BsxystjBwxLS" }, "outputs": [], "source": [ "for images,labels in dataset.take(1):\n", " print(\"Logits: \", mnist_model(images[0:1]).numpy())" ] }, { "cell_type": "markdown", "metadata": { "id": "Y3PGa8G7qEgB" }, "source": [ "While keras models have a builtin training loop (using the `fit` method), sometimes you need more customization. Here's an example, of a training loop implemented with eager:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "bzRhM7JDnaEG" }, "outputs": [], "source": [ "optimizer = tf.train.AdamOptimizer()\n", "\n", "loss_history = []" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "0m1xAXrmqEgJ" }, "outputs": [], "source": [ "for (batch, (images, labels)) in enumerate(dataset.take(400)):\n", " if batch % 10 == 0:\n", " print('.', end='')\n", " with tf.GradientTape() as tape:\n", " logits = mnist_model(images, training=True)\n", " loss_value = tf.losses.sparse_softmax_cross_entropy(labels, logits)\n", "\n", " loss_history.append(loss_value.numpy())\n", " grads = tape.gradient(loss_value, mnist_model.trainable_variables)\n", " optimizer.apply_gradients(zip(grads, mnist_model.trainable_variables),\n", " global_step=tf.train.get_or_create_global_step())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "5vG5ql_2vYB5" }, "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "\n", "plt.plot(loss_history)\n", "plt.xlabel('Batch #')\n", "plt.ylabel('Loss [entropy]')" ] }, { "cell_type": "markdown", "metadata": { "id": "kKpOlHPLqEgl" }, "source": [ "### Variables and optimizers\n", "\n", "`tf.Variable` objects store mutable `tf.Tensor` values accessed during\n", "training to make automatic differentiation easier. The parameters of a model can\n", "be encapsulated in classes as variables.\n", "\n", "Better encapsulate model parameters by using `tf.Variable` with\n", "`tf.GradientTape`. For example, the automatic differentiation example above\n", "can be rewritten:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "nnQLBYmEqEgm" }, "outputs": [], "source": [ "class Model(tf.keras.Model):\n", " def __init__(self):\n", " super(Model, self).__init__()\n", " self.W = tf.Variable(5., name='weight')\n", " self.B = tf.Variable(10., name='bias')\n", " def call(self, inputs):\n", " return inputs * self.W + self.B\n", "\n", "# A toy dataset of points around 3 * x + 2\n", "NUM_EXAMPLES = 2000\n", "training_inputs = tf.random_normal([NUM_EXAMPLES])\n", "noise = tf.random_normal([NUM_EXAMPLES])\n", "training_outputs = training_inputs * 3 + 2 + noise\n", "\n", "# The loss function to be optimized\n", "def loss(model, inputs, targets):\n", " error = model(inputs) - targets\n", " return tf.reduce_mean(tf.square(error))\n", "\n", "def grad(model, inputs, targets):\n", " with tf.GradientTape() as tape:\n", " loss_value = loss(model, inputs, targets)\n", " return tape.gradient(loss_value, [model.W, model.B])\n", "\n", "# Define:\n", "# 1. A model.\n", "# 2. Derivatives of a loss function with respect to model parameters.\n", "# 3. A strategy for updating the variables based on the derivatives.\n", "model = Model()\n", "optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)\n", "\n", "print(\"Initial loss: {:.3f}\".format(loss(model, training_inputs, training_outputs)))\n", "\n", "# Training loop\n", "for i in range(300):\n", " grads = grad(model, training_inputs, training_outputs)\n", " optimizer.apply_gradients(zip(grads, [model.W, model.B]),\n", " global_step=tf.train.get_or_create_global_step())\n", " if i % 20 == 0:\n", " print(\"Loss at step {:03d}: {:.3f}\".format(i, loss(model, training_inputs, training_outputs)))\n", "\n", "print(\"Final loss: {:.3f}\".format(loss(model, training_inputs, training_outputs)))\n", "print(\"W = {}, B = {}\".format(model.W.numpy(), model.B.numpy()))" ] }, { "cell_type": "markdown", "metadata": { "id": "rPjb8nRWqEgr" }, "source": [ "## Use objects for state during eager execution\n", "\n", "With graph execution, program state (such as the variables) is stored in global\n", "collections and their lifetime is managed by the `tf.Session` object. In\n", "contrast, during eager execution the lifetime of state objects is determined by\n", "the lifetime of their corresponding Python object.\n", "\n", "### Variables are objects\n", "\n", "During eager execution, variables persist until the last reference to the object\n", "is removed, and is then deleted." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "A2boS674qEgs" }, "outputs": [], "source": [ "if tf.config.list_physical_devices('GPU'):\n", " with tf.device(\"gpu:0\"):\n", " v = tf.Variable(tf.random_normal([1000, 1000]))\n", " v = None # v no longer takes up GPU memory" ] }, { "cell_type": "markdown", "metadata": { "id": "scMjg6L6qEgv" }, "source": [ "### Object-based saving\n", "\n", "`tf.train.Checkpoint` can save and restore `tf.Variable`s to and from\n", "checkpoints:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "7z5xRfdHzZOQ" }, "outputs": [], "source": [ "x = tf.Variable(10.)\n", "checkpoint = tf.train.Checkpoint(x=x)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "IffrUVG7zyVb" }, "outputs": [], "source": [ "x.assign(2.) # Assign a new value to the variables and save.\n", "checkpoint_path = './ckpt/'\n", "checkpoint.save('./ckpt/')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "eMT9koCoqEgw" }, "outputs": [], "source": [ "x.assign(11.) # Change the variable after saving.\n", "\n", "# Restore values from the checkpoint\n", "checkpoint.restore(tf.train.latest_checkpoint(checkpoint_path))\n", "\n", "print(x) # => 2.0" ] }, { "cell_type": "markdown", "metadata": { "id": "vbFnP-yLqEgx" }, "source": [ "To save and load models, `tf.train.Checkpoint` stores the internal state of objects,\n", "without requiring hidden variables. To record the state of a `model`,\n", "an `optimizer`, and a global step, pass them to a `tf.train.Checkpoint`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "hWZHyAXMqEg0" }, "outputs": [], "source": [ "import os\n", "import tempfile\n", "\n", "model = tf.keras.Sequential([\n", " tf.keras.layers.Conv2D(16,[3,3], activation='relu'),\n", " tf.keras.layers.GlobalAveragePooling2D(),\n", " tf.keras.layers.Dense(10)\n", "])\n", "optimizer = tf.train.AdamOptimizer(learning_rate=0.001)\n", "checkpoint_dir = tempfile.mkdtemp()\n", "checkpoint_prefix = os.path.join(checkpoint_dir, \"ckpt\")\n", "root = tf.train.Checkpoint(optimizer=optimizer,\n", " model=model,\n", " optimizer_step=tf.train.get_or_create_global_step())\n", "\n", "root.save(checkpoint_prefix)\n", "root.restore(tf.train.latest_checkpoint(checkpoint_dir))" ] }, { "cell_type": "markdown", "metadata": { "id": "3yoD0VJ7qEg3" }, "source": [ "### Object-oriented metrics\n", "\n", "`tf.metrics` are stored as objects. Update a metric by passing the new data to\n", "the callable, and retrieve the result using the `tf.metrics.result` method,\n", "for example:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "9ccu0iAaqEg5" }, "outputs": [], "source": [ "m = tf.keras.metrics.Mean(\"loss\")\n", "m(0)\n", "m(5)\n", "m.result() # => 2.5\n", "m([8, 9])\n", "m.result() # => 5.5" ] }, { "cell_type": "markdown", "metadata": { "id": "BE8cXErYqEg8" }, "source": [ "#### Summaries and TensorBoard\n", "\n", "[TensorBoard](https://tensorflow.org/tensorboard) is a visualization tool for\n", "understanding, debugging and optimizing the model training process. It uses\n", "summary events that are written while executing the program.\n", "\n", "TensorFlow 1 summaries only work in eager mode, but can be run with the `compat.v2` module:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "3PXAJB1GqEg9" }, "outputs": [], "source": [ "from tensorflow.compat.v2 import summary\n", "\n", "global_step = tf.train.get_or_create_global_step()\n", "\n", "logdir = \"./tb/\"\n", "writer = summary.create_file_writer(logdir)\n", "writer.set_as_default()\n", "\n", "for _ in range(10):\n", " global_step.assign_add(1)\n", " # your model code goes here\n", " summary.scalar('global_step', global_step, step=global_step)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "6TJSs_wG8Spg" }, "outputs": [], "source": [ "!ls tb/" ] }, { "cell_type": "markdown", "metadata": { "id": "xEL4yJe5qEhD" }, "source": [ "## Advanced automatic differentiation topics\n", "\n", "### Dynamic models\n", "\n", "`tf.GradientTape` can also be used in dynamic models. This example for a\n", "[backtracking line search](https://wikipedia.org/wiki/Backtracking_line_search)\n", "algorithm looks like normal NumPy code, except there are gradients and is\n", "differentiable, despite the complex control flow:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "L518n5dkqEhE" }, "outputs": [], "source": [ "def line_search_step(fn, init_x, rate=1.0):\n", " with tf.GradientTape() as tape:\n", " # Variables are automatically recorded, but manually watch a tensor\n", " tape.watch(init_x)\n", " value = fn(init_x)\n", " grad = tape.gradient(value, init_x)\n", " grad_norm = tf.reduce_sum(grad * grad)\n", " init_value = value\n", " while value > init_value - rate * grad_norm:\n", " x = init_x - rate * grad\n", " value = fn(x)\n", " rate /= 2.0\n", " return x, value" ] }, { "cell_type": "markdown", "metadata": { "id": "gieGOf_DqEhK" }, "source": [ "### Custom gradients\n", "\n", "Custom gradients are an easy way to override gradients in eager and graph\n", "execution. Within the forward function, define the gradient with respect to the\n", "inputs, outputs, or intermediate results. For example, here's an easy way to clip\n", "the norm of the gradients in the backward pass:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "-OwwsWUAqEhK" }, "outputs": [], "source": [ "@tf.custom_gradient\n", "def clip_gradient_by_norm(x, norm):\n", " y = tf.identity(x)\n", " def grad_fn(dresult):\n", " return [tf.clip_by_norm(dresult, norm), None]\n", " return y, grad_fn" ] }, { "cell_type": "markdown", "metadata": { "id": "JPLDHkF_qEhN" }, "source": [ "Custom gradients are commonly used to provide a numerically stable gradient for a\n", "sequence of operations:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "24WiLROnqEhO" }, "outputs": [], "source": [ "def log1pexp(x):\n", " return tf.log(1 + tf.exp(x))\n", "\n", "class Grad(object):\n", " def __init__(self, f):\n", " self.f = f\n", "\n", " def __call__(self, x):\n", " x = tf.convert_to_tensor(x)\n", " with tf.GradientTape() as tape:\n", " tape.watch(x)\n", " r = self.f(x)\n", " g = tape.gradient(r, x)\n", " return g" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "I98MFBraTCYx" }, "outputs": [], "source": [ "grad_log1pexp = Grad(log1pexp)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "n8fq69r9-B-c" }, "outputs": [], "source": [ "# The gradient computation works fine at x = 0.\n", "grad_log1pexp(0.).numpy()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "_VFSU0mG-FSp" }, "outputs": [], "source": [ "# However, x = 100 fails because of numerical instability.\n", "grad_log1pexp(100.).numpy()" ] }, { "cell_type": "markdown", "metadata": { "id": "-VcTR34rqEhQ" }, "source": [ "Here, the `log1pexp` function can be analytically simplified with a custom\n", "gradient. The implementation below reuses the value for `tf.exp(x)` that is\n", "computed during the forward pass—making it more efficient by eliminating\n", "redundant calculations:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Q7nvfx_-qEhS" }, "outputs": [], "source": [ "@tf.custom_gradient\n", "def log1pexp(x):\n", " e = tf.exp(x)\n", " def grad(dy):\n", " return dy * (1 - 1 / (1 + e))\n", " return tf.log(1 + e), grad\n", "\n", "grad_log1pexp = Grad(log1pexp)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "5gHPKMfl-Kge" }, "outputs": [], "source": [ "# As before, the gradient computation works fine at x = 0.\n", "grad_log1pexp(0.).numpy()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "u38MOfz3-MDE" }, "outputs": [], "source": [ "# And the gradient computation also works at x = 100.\n", "grad_log1pexp(100.).numpy()" ] }, { "cell_type": "markdown", "metadata": { "id": "rnZXjfQzqEhV" }, "source": [ "## Performance\n", "\n", "Computation is automatically offloaded to GPUs during eager execution. If you\n", "want control over where a computation runs you can enclose it in a\n", "`tf.device('/gpu:0')` block (or the CPU equivalent):" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Ac9Y64H-qEhX" }, "outputs": [], "source": [ "import time\n", "\n", "def measure(x, steps):\n", " # TensorFlow initializes a GPU the first time it's used, exclude from timing.\n", " tf.matmul(x, x)\n", " start = time.time()\n", " for i in range(steps):\n", " x = tf.matmul(x, x)\n", " # tf.matmul can return before completing the matrix multiplication\n", " # (e.g., can return after enqueing the operation on a CUDA stream).\n", " # The x.numpy() call below will ensure that all enqueued operations\n", " # have completed (and will also copy the result to host memory,\n", " # so we're including a little more than just the matmul operation\n", " # time).\n", " _ = x.numpy()\n", " end = time.time()\n", " return end - start\n", "\n", "shape = (1000, 1000)\n", "steps = 200\n", "print(\"Time to multiply a {} matrix by itself {} times:\".format(shape, steps))\n", "\n", "# Run on CPU:\n", "with tf.device(\"/cpu:0\"):\n", " print(\"CPU: {} secs\".format(measure(tf.random_normal(shape), steps)))\n", "\n", "# Run on GPU, if available:\n", "if tf.config.list_physical_devices('GPU'):\n", " with tf.device(\"/gpu:0\"):\n", " print(\"GPU: {} secs\".format(measure(tf.random_normal(shape), steps)))\n", "else:\n", " print(\"GPU: not found\")" ] }, { "cell_type": "markdown", "metadata": { "id": "RLw3IS7UqEhe" }, "source": [ "A `tf.Tensor` object can be copied to a different device to execute its\n", "operations:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "ny6LX2BVqEhf" }, "outputs": [], "source": [ "if tf.config.list_physical_devices('GPU'):\n", " x = tf.random_normal([10, 10])\n", "\n", " x_gpu0 = x.gpu()\n", " x_cpu = x.cpu()\n", "\n", " _ = tf.matmul(x_cpu, x_cpu) # Runs on CPU\n", " _ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0" ] }, { "cell_type": "markdown", "metadata": { "id": "oA_qaII3-p6c" }, "source": [ "### Benchmarks\n", "\n", "For compute-heavy models, such as\n", "[ResNet50](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples/resnet50)\n", "training on a GPU, eager execution performance is comparable to graph execution.\n", "But this gap grows larger for models with less computation and there is work to\n", "be done for optimizing hot code paths for models with lots of small operations." ] }, { "cell_type": "markdown", "metadata": { "id": "TjMTyFIp-sSE" }, "source": [ "## Work with graphs\n", "\n", "While eager execution makes development and debugging more interactive,\n", "TensorFlow graph execution has advantages for distributed training, performance\n", "optimizations, and production deployment. However, writing graph code can feel\n", "different than writing regular Python code and more difficult to debug.\n", "\n", "For building and training graph-constructed models, the Python program first\n", "builds a graph representing the computation, then invokes `Session.run` to send\n", "the graph for execution on the C++-based runtime. This provides:\n", "\n", "* Automatic differentiation using static autodiff.\n", "* Simple deployment to a platform independent server.\n", "* Graph-based optimizations (common subexpression elimination, constant-folding, etc.).\n", "* Compilation and kernel fusion.\n", "* Automatic distribution and replication (placing nodes on the distributed system).\n", "\n", "Deploying code written for eager execution is more difficult: either generate a\n", "graph from the model, or run the Python runtime and code directly on the server." ] }, { "cell_type": "markdown", "metadata": { "id": "hll3ZbE5qEhh" }, "source": [ "### Write compatible code\n", "\n", "The same code written for eager execution will also build a graph during graph\n", "execution. Do this by simply running the same code in a new Python session where\n", "eager execution is not enabled.\n", "\n", "Most TensorFlow operations work during eager execution, but there are some things\n", "to keep in mind:\n", "\n", "* Use `tf.data` for input processing instead of queues. It's faster and easier.\n", "* Use object-oriented layer APIs—like `tf.keras.layers` and\n", " `tf.keras.Model`—since they have explicit storage for variables.\n", "* Most model code works the same during eager and graph execution, but there are\n", " exceptions. (For example, dynamic models using Python control flow to change the\n", " computation based on inputs.)\n", "* Once eager execution is enabled with `tf.enable_eager_execution`, it\n", " cannot be turned off. Start a new Python session to return to graph execution.\n", "\n", "It's best to write code for both eager execution *and* graph execution. This\n", "gives you eager's interactive experimentation and debuggability with the\n", "distributed performance benefits of graph execution.\n", "\n", "Write, debug, and iterate in eager execution, then import the model graph for\n", "production deployment. Use `tf.train.Checkpoint` to save and restore model\n", "variables, this allows movement between eager and graph execution environments.\n", "See the examples in:\n", "[tensorflow/contrib/eager/python/examples](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/contrib/eager/python/examples).\n" ] }, { "cell_type": "markdown", "metadata": { "id": "sPoSUqmL-ts5" }, "source": [ "### Use eager execution in a graph environment\n", "\n", "Selectively enable eager execution in a TensorFlow graph environment using\n", "`tfe.py_func`. This is used when `` has *not*\n", "been called." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "Lks-3LB0qEhi" }, "outputs": [], "source": [ "def my_py_func(x):\n", " x = tf.matmul(x, x) # You can use tf ops\n", " print(x) # but it's eager!\n", " return x\n", "\n", "with tf.Session() as sess:\n", " x = tf.placeholder(dtype=tf.float32)\n", " # Call eager function in graph!\n", " pf = tf.py_func(my_py_func, [x], tf.float32)\n", "\n", " sess.run(pf, feed_dict={x: [[2.0]]}) # [[4.0]]" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "eager.ipynb", "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 0 }