From 7f4141df4ef2c144e0da4d36ef3fbbe1fdc3c04a Mon Sep 17 00:00:00 2001 From: Richard Liaw Date: Mon, 14 Oct 2019 14:18:52 -0700 Subject: [PATCH] [docs] Pictures for all the Examples (#5859) * image * plot resnet * hyperparam * fixup_pictures * custom_direct --- doc/examples/overview.rst | 8 +- doc/examples/plot_example-a3c.rst | 4 + doc/examples/plot_hyperparameter.py | 3 + doc/examples/plot_pong_example.py | 4 + doc/examples/plot_resnet.rst | 103 --------- doc/examples/resnet/cifar_input.py | 116 ---------- doc/examples/resnet/resnet_main.py | 257 ---------------------- doc/examples/resnet/resnet_model.py | 317 --------------------------- doc/source/custom_directives.py | 13 +- doc/source/images/a3c.png | Bin 0 -> 24984 bytes doc/source/images/hyperparameter.png | Bin 0 -> 25069 bytes doc/source/images/pong-arch.svg | 1 + doc/source/images/pong.png | Bin 0 -> 6046 bytes doc/source/index.rst | 1 - 14 files changed, 24 insertions(+), 803 deletions(-) delete mode 100644 doc/examples/plot_resnet.rst delete mode 100644 doc/examples/resnet/cifar_input.py delete mode 100644 doc/examples/resnet/resnet_main.py delete mode 100644 doc/examples/resnet/resnet_model.py create mode 100644 doc/source/images/a3c.png create mode 100644 doc/source/images/hyperparameter.png create mode 100644 doc/source/images/pong-arch.svg create mode 100644 doc/source/images/pong.png diff --git a/doc/examples/overview.rst b/doc/examples/overview.rst index a69e9a9db..b11112d2c 100644 --- a/doc/examples/overview.rst +++ b/doc/examples/overview.rst @@ -3,18 +3,22 @@ Examples Overview .. customgalleryitem:: :tooltip: Build a simple parameter server using Ray. + :figure: /images/param_actor.png :description: :doc:`/auto_examples/plot_parameter_server` .. customgalleryitem:: :tooltip: Asynchronous Advantage Actor Critic agent using Ray. + :figure: /images/a3c.png :description: :doc:`/auto_examples/plot_example-a3c` .. customgalleryitem:: :tooltip: Simple parallel asynchronous hyperparameter evaluation. + :figure: /images/hyperparameter.png :description: :doc:`/auto_examples/plot_hyperparameter` .. customgalleryitem:: :tooltip: Parallelizing a policy gradient calculation on OpenAI Gym Pong. + :figure: /images/pong.png :description: :doc:`/auto_examples/plot_pong_example` .. customgalleryitem:: @@ -25,10 +29,6 @@ Examples Overview :tooltip: Implementing a simple news reader using Ray. :description: :doc:`/auto_examples/plot_newsreader` -.. customgalleryitem:: - :tooltip: Using Ray to train ResNet across multiple GPUs. - :description: :doc:`/auto_examples/plot_resnet` - .. customgalleryitem:: :tooltip: Implement a simple streaming application using Ray’s actors. :description: :doc:`/auto_examples/plot_streaming` diff --git a/doc/examples/plot_example-a3c.rst b/doc/examples/plot_example-a3c.rst index be620f959..779175d85 100644 --- a/doc/examples/plot_example-a3c.rst +++ b/doc/examples/plot_example-a3c.rst @@ -25,6 +25,10 @@ To run the application, first install **ray** and then some dependencies: pip install opencv-python-headless pip install scipy + +.. image:: ../images/a3c.png + :align: center + You can run the code with .. code-block:: bash diff --git a/doc/examples/plot_hyperparameter.py b/doc/examples/plot_hyperparameter.py index aa5224a8f..6874a9017 100644 --- a/doc/examples/plot_hyperparameter.py +++ b/doc/examples/plot_hyperparameter.py @@ -9,6 +9,9 @@ This script will demonstrate how to use two important parts of the Ray API: using ``ray.remote`` to define remote functions and ``ray.wait`` to wait for their results to be ready. +.. image:: ../images/hyperparameter.png + :align: center + .. important:: For a production-grade implementation of distributed hyperparameter tuning, use `Tune`_, a scalable hyperparameter tuning library built using Ray's Actor API. diff --git a/doc/examples/plot_pong_example.py b/doc/examples/plot_pong_example.py index 4c2b381de..ec9fec1a0 100644 --- a/doc/examples/plot_pong_example.py +++ b/doc/examples/plot_pong_example.py @@ -14,6 +14,10 @@ then be passed back to each Ray actor for more gradient calculation. This application is adapted, with minimal modifications, from Andrej Karpathy's `source code`_ (see the accompanying `blog post`_). +.. image:: ../images/pong-arch.svg + :align: center + + To run the application, first install some dependencies. .. code-block:: bash diff --git a/doc/examples/plot_resnet.rst b/doc/examples/plot_resnet.rst deleted file mode 100644 index f0fbd2e7d..000000000 --- a/doc/examples/plot_resnet.rst +++ /dev/null @@ -1,103 +0,0 @@ -ResNet -====== - -This code uses ResNet to do data parallel training -across multiple GPUs using Ray. View the `code for this example`_. - -To run the example, you will need to install `TensorFlow`_ (at -least version ``1.0.0``). Then you can run the example as follows. - -First download the CIFAR-10 or CIFAR-100 dataset. - -.. code-block:: bash - - # Get the CIFAR-10 dataset. - curl -o cifar-10-binary.tar.gz https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz - tar -xvf cifar-10-binary.tar.gz - - # Get the CIFAR-100 dataset. - curl -o cifar-100-binary.tar.gz https://www.cs.toronto.edu/~kriz/cifar-100-binary.tar.gz - tar -xvf cifar-100-binary.tar.gz - -Then run the training script that matches the dataset you downloaded. - -.. code-block:: bash - - # Train Resnet on CIFAR-10. - python ray/doc/examples/resnet/resnet_main.py \ - --eval_dir=/tmp/resnet-model/eval \ - --train_data_path=cifar-10-batches-bin/data_batch* \ - --eval_data_path=cifar-10-batches-bin/test_batch.bin \ - --dataset=cifar10 \ - --num_gpus=1 - - # Train Resnet on CIFAR-100. - python ray/doc/examples/resnet/resnet_main.py \ - --eval_dir=/tmp/resnet-model/eval \ - --train_data_path=cifar-100-binary/train.bin \ - --eval_data_path=cifar-100-binary/test.bin \ - --dataset=cifar100 \ - --num_gpus=1 - -To run the training script on a cluster with multiple machines, you will need -to also pass in the flag ``--address=
``, where -``
`` is the address of the Redis server on the head node. - -The script will print out the IP address that the log files are stored on. In -the single-node case, you can ignore this and run tensorboard on the current -machine. - -.. code-block:: bash - - python -m tensorflow.tensorboard --logdir=/tmp/resnet-model - -If you are running Ray on multiple nodes, you will need to go to the node at the -IP address printed, and run the command. - -The core of the script is the actor definition. - -.. code-block:: python - - @ray.remote(num_gpus=1) - class ResNetTrainActor(object): - def __init__(self, data, dataset, num_gpus): - # data is the preprocessed images and labels extracted from the dataset. - # Thus, every actor has its own copy of the data. - # Set the CUDA_VISIBLE_DEVICES environment variable in order to restrict - # which GPUs TensorFlow uses. Note that this only works if it is done before - # the call to tf.Session. - os.environ['CUDA_VISIBLE_DEVICES'] = ','.join([str(i) for i in ray.get_gpu_ids()]) - with tf.Graph().as_default(): - with tf.device('/gpu:0'): - # We omit the code here that actually constructs the residual network - # and initializes it. Uses the definition in the Tensorflow Resnet Example. - - def compute_steps(self, weights): - # This method sets the weights in the network, runs some training steps, - # and returns the new weights. self.model.variables is a TensorFlowVariables - # class that we pass the train operation into. - self.model.variables.set_weights(weights) - for i in range(self.steps): - self.model.variables.sess.run(self.model.train_op) - return self.model.variables.get_weights() - -The main script first creates one actor for each GPU, or a single actor if -``num_gpus`` is zero. - -.. code-block:: python - - train_actors = [ResNetTrainActor.remote(train_data, dataset, num_gpus) for _ in range(num_gpus)] - -Then the main loop passes the same weights to every model, performs -updates on each model, averages the updates, and puts the new weights in the -object store. - -.. code-block:: python - - while True: - all_weights = ray.get([actor.compute_steps.remote(weight_id) for actor in train_actors]) - mean_weights = {k: sum([weights[k] for weights in all_weights]) / num_gpus for k in all_weights[0]} - weight_id = ray.put(mean_weights) - -.. _`TensorFlow`: https://www.tensorflow.org/install/ -.. _`code for this example`: https://github.com/ray-project/ray/tree/master/doc/examples/resnet diff --git a/doc/examples/resnet/cifar_input.py b/doc/examples/resnet/cifar_input.py deleted file mode 100644 index d52a53115..000000000 --- a/doc/examples/resnet/cifar_input.py +++ /dev/null @@ -1,116 +0,0 @@ -"""CIFAR dataset input module, with the majority taken from -https://github.com/tensorflow/models/tree/master/resnet. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import tensorflow as tf - - -def build_data(data_path, size, dataset): - """Creates the queue and preprocessing operations for the dataset. - - Args: - data_path: Filename for cifar10 data. - size: The number of images in the dataset. - dataset: The dataset we are using. - - Returns: - queue: A Tensorflow queue for extracting the images and labels. - """ - image_size = 32 - if dataset == "cifar10": - label_bytes = 1 - label_offset = 0 - elif dataset == "cifar100": - label_bytes = 1 - label_offset = 1 - depth = 3 - image_bytes = image_size * image_size * depth - record_bytes = label_bytes + label_offset + image_bytes - - def load_transform(value): - # Convert these examples to dense labels and processed images. - record = tf.reshape(tf.decode_raw(value, tf.uint8), [record_bytes]) - label = tf.cast( - tf.slice(record, [label_offset], [label_bytes]), tf.int32) - # Convert from string to [depth * height * width] to - # [depth, height, width]. - depth_major = tf.reshape( - tf.slice(record, [label_bytes], [image_bytes]), - [depth, image_size, image_size]) - # Convert from [depth, height, width] to [height, width, depth]. - image = tf.cast(tf.transpose(depth_major, [1, 2, 0]), tf.float32) - return (image, label) - - # Read examples from files in the filename queue. - data_files = tf.gfile.Glob(data_path) - data = tf.data.FixedLengthRecordDataset( - data_files, record_bytes=record_bytes) - data = data.map(load_transform) - data = data.batch(size) - iterator = data.make_one_shot_iterator() - return iterator.get_next() - - -def build_input(data, batch_size, dataset, train): - """Build CIFAR image and labels. - - Args: - data_path: Filename for cifar10 data. - batch_size: Input batch size. - train: True if we are training and false if we are testing. - - Returns: - images: Batches of images of size - [batch_size, image_size, image_size, 3]. - labels: Batches of labels of size [batch_size, num_classes]. - - Raises: - ValueError: When the specified dataset is not supported. - """ - image_size = 32 - depth = 3 - num_classes = 10 if dataset == "cifar10" else 100 - images, labels = data - num_samples = images.shape[0] - images.shape[0] % batch_size - dataset = tf.data.Dataset.from_tensor_slices( - (images[:num_samples], labels[:num_samples])) - - def map_train(image, label): - image = tf.image.resize_image_with_crop_or_pad(image, image_size + 4, - image_size + 4) - image = tf.random_crop(image, [image_size, image_size, 3]) - image = tf.image.random_flip_left_right(image) - image = tf.image.per_image_standardization(image) - return (image, label) - - def map_test(image, label): - image = tf.image.resize_image_with_crop_or_pad(image, image_size, - image_size) - image = tf.image.per_image_standardization(image) - return (image, label) - - dataset = dataset.map(map_train if train else map_test) - dataset = dataset.batch(batch_size) - dataset = dataset.repeat() - if train: - dataset = dataset.shuffle(buffer_size=16 * batch_size) - images, labels = dataset.make_one_shot_iterator().get_next() - images = tf.reshape(images, [batch_size, image_size, image_size, depth]) - labels = tf.reshape(labels, [batch_size, 1]) - indices = tf.reshape(tf.range(0, batch_size, 1), [batch_size, 1]) - labels = tf.sparse_to_dense( - tf.concat([indices, labels], 1), [batch_size, num_classes], 1.0, 0.0) - - assert len(images.get_shape()) == 4 - assert images.get_shape()[0] == batch_size - assert images.get_shape()[-1] == 3 - assert len(labels.get_shape()) == 2 - assert labels.get_shape()[0] == batch_size - assert labels.get_shape()[1] == num_classes - if not train: - tf.summary.image("images", images) - return images, labels diff --git a/doc/examples/resnet/resnet_main.py b/doc/examples/resnet/resnet_main.py deleted file mode 100644 index 3d0089d77..000000000 --- a/doc/examples/resnet/resnet_main.py +++ /dev/null @@ -1,257 +0,0 @@ -"""ResNet training script, with some code from -https://github.com/tensorflow/models/tree/master/resnet. -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import argparse -import os -import numpy as np -import ray -import tensorflow as tf - -import cifar_input -import resnet_model - -# Tensorflow must be at least version 1.2.0 for the example to work. -tf_major = int(tf.__version__.split(".")[0]) -tf_minor = int(tf.__version__.split(".")[1]) -if (tf_major < 1) or (tf_major == 1 and tf_minor < 2): - raise Exception("Your Tensorflow version is less than 1.2.0. Please " - "update Tensorflow to the latest version.") - -parser = argparse.ArgumentParser(description="Run the ResNet example.") -parser.add_argument( - "--dataset", - default="cifar10", - type=str, - help="Dataset to use: cifar10 or cifar100.") -parser.add_argument( - "--train_data_path", - default="cifar-10-batches-bin/data_batch*", - type=str, - help="Data path for the training data.") -parser.add_argument( - "--eval_data_path", - default="cifar-10-batches-bin/test_batch.bin", - type=str, - help="Data path for the testing data.") -parser.add_argument( - "--eval_dir", - default="/tmp/resnet-model/eval", - type=str, - help="Data path for the tensorboard logs.") -parser.add_argument( - "--eval_batch_count", - default=50, - type=int, - help="Number of batches to evaluate over.") -parser.add_argument( - "--num_gpus", - default=0, - type=int, - help="Number of GPUs to use for training.") -parser.add_argument( - "--redis-address", - default=None, - type=str, - help="The Redis address of the cluster.") - -FLAGS = parser.parse_args() - -# Determines if the actors require a gpu or not. -use_gpu = 1 if int(FLAGS.num_gpus) > 0 else 0 - - -@ray.remote -def get_data(path, size, dataset): - # Retrieves all preprocessed images and labels using a tensorflow queue. - # This only uses the cpu. - os.environ["CUDA_VISIBLE_DEVICES"] = "" - with tf.device("/cpu:0"): - dataset = cifar_input.build_data(path, size, dataset) - sess = tf.Session() - images, labels = sess.run(dataset) - sess.close() - return images, labels - - -@ray.remote(num_gpus=use_gpu) -class ResNetTrainActor(object): - def __init__(self, data, dataset, num_gpus): - if num_gpus > 0: - os.environ["CUDA_VISIBLE_DEVICES"] = ",".join( - [str(i) for i in ray.get_gpu_ids()]) - hps = resnet_model.HParams( - batch_size=128, - num_classes=100 if dataset == "cifar100" else 10, - min_lrn_rate=0.0001, - lrn_rate=0.1, - num_residual_units=5, - use_bottleneck=False, - weight_decay_rate=0.0002, - relu_leakiness=0.1, - optimizer="mom", - num_gpus=num_gpus) - - # We seed each actor differently so that each actor operates on a - # different subset of data. - if num_gpus > 0: - tf.set_random_seed(ray.get_gpu_ids()[0] + 1) - else: - # Only a single actor in this case. - tf.set_random_seed(1) - - with tf.device("/gpu:0" if num_gpus > 0 else "/cpu:0"): - # Build the model. - images, labels = cifar_input.build_input(data, hps.batch_size, - dataset, True) - self.model = resnet_model.ResNet(hps, images, labels, "train") - self.model.build_graph() - config = tf.ConfigProto(allow_soft_placement=True) - config.gpu_options.allow_growth = True - sess = tf.Session(config=config) - self.model.variables.set_session(sess) - init = tf.global_variables_initializer() - sess.run(init) - self.steps = 10 - - def compute_steps(self, weights): - # This method sets the weights in the network, trains the network - # self.steps times, and returns the new weights. - self.model.variables.set_weights(weights) - for i in range(self.steps): - self.model.variables.sess.run(self.model.train_op) - return self.model.variables.get_weights() - - def get_weights(self): - # Note that the driver cannot directly access fields of the class, - # so helper methods must be created. - return self.model.variables.get_weights() - - -@ray.remote -class ResNetTestActor(object): - def __init__(self, data, dataset, eval_batch_count, eval_dir): - os.environ["CUDA_VISIBLE_DEVICES"] = "" - hps = resnet_model.HParams( - batch_size=100, - num_classes=100 if dataset == "cifar100" else 10, - min_lrn_rate=0.0001, - lrn_rate=0.1, - num_residual_units=5, - use_bottleneck=False, - weight_decay_rate=0.0002, - relu_leakiness=0.1, - optimizer="mom", - num_gpus=0) - with tf.device("/cpu:0"): - # Builds the testing network. - images, labels = cifar_input.build_input(data, hps.batch_size, - dataset, False) - self.model = resnet_model.ResNet(hps, images, labels, "eval") - self.model.build_graph() - config = tf.ConfigProto(allow_soft_placement=True) - config.gpu_options.allow_growth = True - sess = tf.Session(config=config) - self.model.variables.set_session(sess) - init = tf.global_variables_initializer() - sess.run(init) - - # Initializing parameters for tensorboard. - self.best_precision = 0.0 - self.eval_batch_count = eval_batch_count - self.summary_writer = tf.summary.FileWriter(eval_dir, sess.graph) - # The IP address where tensorboard logs will be on. - self.ip_addr = ray.services.get_node_ip_address() - - def accuracy(self, weights, train_step): - # Sets the weights, computes the accuracy and other metrics - # over eval_batches, and outputs to tensorboard. - self.model.variables.set_weights(weights) - total_prediction, correct_prediction = 0, 0 - model = self.model - sess = self.model.variables.sess - for _ in range(self.eval_batch_count): - summaries, loss, predictions, truth = sess.run( - [model.summaries, model.cost, model.predictions, model.labels]) - - truth = np.argmax(truth, axis=1) - predictions = np.argmax(predictions, axis=1) - correct_prediction += np.sum(truth == predictions) - total_prediction += predictions.shape[0] - - precision = 1.0 * correct_prediction / total_prediction - self.best_precision = max(precision, self.best_precision) - precision_summ = tf.Summary() - precision_summ.value.add(tag="Precision", simple_value=precision) - self.summary_writer.add_summary(precision_summ, train_step) - best_precision_summ = tf.Summary() - best_precision_summ.value.add( - tag="Best Precision", simple_value=self.best_precision) - self.summary_writer.add_summary(best_precision_summ, train_step) - self.summary_writer.add_summary(summaries, train_step) - tf.logging.info("loss: %.3f, precision: %.3f, best precision: %.3f" % - (loss, precision, self.best_precision)) - self.summary_writer.flush() - return precision - - def get_ip_addr(self): - # As above, a helper method must be created to access the field from - # the driver. - return self.ip_addr - - -def train(): - num_gpus = FLAGS.num_gpus - if FLAGS.redis_address is None: - ray.init(num_gpus=num_gpus) - else: - ray.init(redis_address=FLAGS.redis_address) - train_data = get_data.remote(FLAGS.train_data_path, 50000, FLAGS.dataset) - test_data = get_data.remote(FLAGS.eval_data_path, 10000, FLAGS.dataset) - # Creates an actor for each gpu, or one if only using the cpu. Each actor - # has access to the dataset. - if FLAGS.num_gpus > 0: - train_actors = [ - ResNetTrainActor.remote(train_data, FLAGS.dataset, num_gpus) - for _ in range(num_gpus) - ] - else: - train_actors = [ResNetTrainActor.remote(train_data, FLAGS.dataset, 0)] - test_actor = ResNetTestActor.remote(test_data, FLAGS.dataset, - FLAGS.eval_batch_count, FLAGS.eval_dir) - print("The log files for tensorboard are stored at ip {}.".format( - ray.get(test_actor.get_ip_addr.remote()))) - step = 0 - weight_id = train_actors[0].get_weights.remote() - acc_id = test_actor.accuracy.remote(weight_id, step) - # Correction for dividing the weights by the number of gpus. - if num_gpus == 0: - num_gpus = 1 - print("Starting training loop. Use Ctrl-C to exit.") - try: - while True: - all_weights = ray.get([ - actor.compute_steps.remote(weight_id) for actor in train_actors - ]) - mean_weights = { - k: (sum(weights[k] for weights in all_weights) / num_gpus) - for k in all_weights[0] - } - weight_id = ray.put(mean_weights) - step += 10 - if step % 200 == 0: - # Retrieves the previously computed accuracy and launches a new - # testing task with the current weights every 200 steps. - acc = ray.get(acc_id) - acc_id = test_actor.accuracy.remote(weight_id, step) - print("Step {}: {:.6f}".format(step - 200, acc)) - except KeyboardInterrupt: - pass - - -if __name__ == "__main__": - train() diff --git a/doc/examples/resnet/resnet_model.py b/doc/examples/resnet/resnet_model.py deleted file mode 100644 index 06d19e64d..000000000 --- a/doc/examples/resnet/resnet_model.py +++ /dev/null @@ -1,317 +0,0 @@ -"""ResNet model with most of the code taken from -https://github.com/tensorflow/models/tree/master/resnet. - -Related papers: -https://arxiv.org/pdf/1603.05027v2.pdf -https://arxiv.org/pdf/1512.03385v1.pdf -https://arxiv.org/pdf/1605.07146v1.pdf -""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from collections import namedtuple -import numpy as np - -import tensorflow as tf -from tensorflow.python.training import moving_averages - -import ray -import ray.experimental.tf_utils - -HParams = namedtuple( - "HParams", "batch_size, num_classes, min_lrn_rate, lrn_rate, " - "num_residual_units, use_bottleneck, weight_decay_rate, " - "relu_leakiness, optimizer, num_gpus") - - -class ResNet(object): - """ResNet model.""" - - def __init__(self, hps, images, labels, mode): - """ResNet constructor. - - Args: - hps: Hyperparameters. - images: Batches of images of size [batch_size, image_size, - image_size, 3]. - labels: Batches of labels of size [batch_size, num_classes]. - mode: One of 'train' and 'eval'. - """ - self.hps = hps - self._images = images - self.labels = labels - self.mode = mode - - self._extra_train_ops = [] - - def build_graph(self): - """Build a whole graph for the model.""" - self.global_step = tf.Variable(0, trainable=False) - self._build_model() - if self.mode == "train": - self._build_train_op() - else: - # Additional initialization for the test network. - self.variables = ray.experimental.tf_utils.TensorFlowVariables( - self.cost) - self.summaries = tf.summary.merge_all() - - def _stride_arr(self, stride): - """Map a stride scalar to the stride array for tf.nn.conv2d.""" - return [1, stride, stride, 1] - - def _build_model(self): - """Build the core model within the graph.""" - - with tf.variable_scope("init"): - x = self._conv("init_conv", self._images, 3, 3, 16, - self._stride_arr(1)) - - strides = [1, 2, 2] - activate_before_residual = [True, False, False] - if self.hps.use_bottleneck: - res_func = self._bottleneck_residual - filters = [16, 64, 128, 256] - else: - res_func = self._residual - filters = [16, 16, 32, 64] - - with tf.variable_scope("unit_1_0"): - x = res_func(x, filters[0], filters[1], self._stride_arr( - strides[0]), activate_before_residual[0]) - for i in range(1, self.hps.num_residual_units): - with tf.variable_scope("unit_1_%d" % i): - x = res_func(x, filters[1], filters[1], self._stride_arr(1), - False) - - with tf.variable_scope("unit_2_0"): - x = res_func(x, filters[1], filters[2], self._stride_arr( - strides[1]), activate_before_residual[1]) - for i in range(1, self.hps.num_residual_units): - with tf.variable_scope("unit_2_%d" % i): - x = res_func(x, filters[2], filters[2], self._stride_arr(1), - False) - - with tf.variable_scope("unit_3_0"): - x = res_func(x, filters[2], filters[3], self._stride_arr( - strides[2]), activate_before_residual[2]) - for i in range(1, self.hps.num_residual_units): - with tf.variable_scope("unit_3_%d" % i): - x = res_func(x, filters[3], filters[3], self._stride_arr(1), - False) - with tf.variable_scope("unit_last"): - x = self._batch_norm("final_bn", x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._global_avg_pool(x) - - with tf.variable_scope("logit"): - logits = self._fully_connected(x, self.hps.num_classes) - self.predictions = tf.nn.softmax(logits) - - with tf.variable_scope("costs"): - xent = tf.nn.softmax_cross_entropy_with_logits( - logits=logits, labels=self.labels) - self.cost = tf.reduce_mean(xent, name="xent") - self.cost += self._decay() - - if self.mode == "eval": - tf.summary.scalar("cost", self.cost) - - def _build_train_op(self): - """Build training specific ops for the graph.""" - num_gpus = self.hps.num_gpus if self.hps.num_gpus != 0 else 1 - # The learning rate schedule is dependent on the number of gpus. - boundaries = [int(20000 * i / np.sqrt(num_gpus)) for i in range(2, 5)] - values = [0.1, 0.01, 0.001, 0.0001] - self.lrn_rate = tf.train.piecewise_constant(self.global_step, - boundaries, values) - tf.summary.scalar("learning rate", self.lrn_rate) - - if self.hps.optimizer == "sgd": - optimizer = tf.train.GradientDescentOptimizer(self.lrn_rate) - elif self.hps.optimizer == "mom": - optimizer = tf.train.MomentumOptimizer(self.lrn_rate, 0.9) - - apply_op = optimizer.minimize(self.cost, global_step=self.global_step) - train_ops = [apply_op] + self._extra_train_ops - self.train_op = tf.group(*train_ops) - self.variables = ray.experimental.tf_utils.TensorFlowVariables( - self.train_op) - - def _batch_norm(self, name, x): - """Batch normalization.""" - with tf.variable_scope(name): - params_shape = [x.get_shape()[-1]] - - beta = tf.get_variable( - "beta", - params_shape, - tf.float32, - initializer=tf.constant_initializer(0.0, tf.float32)) - gamma = tf.get_variable( - "gamma", - params_shape, - tf.float32, - initializer=tf.constant_initializer(1.0, tf.float32)) - - if self.mode == "train": - mean, variance = tf.nn.moments(x, [0, 1, 2], name="moments") - - moving_mean = tf.get_variable( - "moving_mean", - params_shape, - tf.float32, - initializer=tf.constant_initializer(0.0, tf.float32), - trainable=False) - moving_variance = tf.get_variable( - "moving_variance", - params_shape, - tf.float32, - initializer=tf.constant_initializer(1.0, tf.float32), - trainable=False) - - self._extra_train_ops.append( - moving_averages.assign_moving_average( - moving_mean, mean, 0.9)) - self._extra_train_ops.append( - moving_averages.assign_moving_average( - moving_variance, variance, 0.9)) - else: - mean = tf.get_variable( - "moving_mean", - params_shape, - tf.float32, - initializer=tf.constant_initializer(0.0, tf.float32), - trainable=False) - variance = tf.get_variable( - "moving_variance", - params_shape, - tf.float32, - initializer=tf.constant_initializer(1.0, tf.float32), - trainable=False) - tf.summary.histogram(mean.op.name, mean) - tf.summary.histogram(variance.op.name, variance) - # elipson used to be 1e-5. Maybe 0.001 solves NaN problem in deeper - # net. - y = tf.nn.batch_normalization(x, mean, variance, beta, gamma, - 0.001) - y.set_shape(x.get_shape()) - return y - - def _residual(self, - x, - in_filter, - out_filter, - stride, - activate_before_residual=False): - """Residual unit with 2 sub layers.""" - if activate_before_residual: - with tf.variable_scope("shared_activation"): - x = self._batch_norm("init_bn", x) - x = self._relu(x, self.hps.relu_leakiness) - orig_x = x - else: - with tf.variable_scope("residual_only_activation"): - orig_x = x - x = self._batch_norm("init_bn", x) - x = self._relu(x, self.hps.relu_leakiness) - - with tf.variable_scope("sub1"): - x = self._conv("conv1", x, 3, in_filter, out_filter, stride) - - with tf.variable_scope("sub2"): - x = self._batch_norm("bn2", x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._conv("conv2", x, 3, out_filter, out_filter, [1, 1, 1, 1]) - - with tf.variable_scope("sub_add"): - if in_filter != out_filter: - orig_x = tf.nn.avg_pool(orig_x, stride, stride, "VALID") - orig_x = tf.pad( - orig_x, - [[0, 0], [0, 0], [0, 0], [(out_filter - in_filter) // 2, - (out_filter - in_filter) // 2]]) - x += orig_x - - return x - - def _bottleneck_residual(self, - x, - in_filter, - out_filter, - stride, - activate_before_residual=False): - """Bottleneck residual unit with 3 sub layers.""" - if activate_before_residual: - with tf.variable_scope("common_bn_relu"): - x = self._batch_norm("init_bn", x) - x = self._relu(x, self.hps.relu_leakiness) - orig_x = x - else: - with tf.variable_scope("residual_bn_relu"): - orig_x = x - x = self._batch_norm("init_bn", x) - x = self._relu(x, self.hps.relu_leakiness) - - with tf.variable_scope("sub1"): - x = self._conv("conv1", x, 1, in_filter, out_filter / 4, stride) - - with tf.variable_scope("sub2"): - x = self._batch_norm("bn2", x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._conv("conv2", x, 3, out_filter / 4, out_filter / 4, - [1, 1, 1, 1]) - - with tf.variable_scope("sub3"): - x = self._batch_norm("bn3", x) - x = self._relu(x, self.hps.relu_leakiness) - x = self._conv("conv3", x, 1, out_filter / 4, out_filter, - [1, 1, 1, 1]) - - with tf.variable_scope("sub_add"): - if in_filter != out_filter: - orig_x = self._conv("project", orig_x, 1, in_filter, - out_filter, stride) - x += orig_x - - return x - - def _decay(self): - """L2 weight decay loss.""" - costs = [] - for var in tf.trainable_variables(): - if var.op.name.find(r"DW") > 0: - costs.append(tf.nn.l2_loss(var)) - - return tf.multiply(self.hps.weight_decay_rate, tf.add_n(costs)) - - def _conv(self, name, x, filter_size, in_filters, out_filters, strides): - """Convolution.""" - with tf.variable_scope(name): - n = filter_size * filter_size * out_filters - kernel = tf.get_variable( - "DW", [filter_size, filter_size, in_filters, out_filters], - tf.float32, - initializer=tf.random_normal_initializer( - stddev=np.sqrt(2.0 / n))) - return tf.nn.conv2d(x, kernel, strides, padding="SAME") - - def _relu(self, x, leakiness=0.0): - """Relu, with optional leaky support.""" - return tf.where(tf.less(x, 0.0), leakiness * x, x, name="leaky_relu") - - def _fully_connected(self, x, out_dim): - """FullyConnected layer for final output.""" - x = tf.reshape(x, [self.hps.batch_size, -1]) - w = tf.get_variable( - "DW", [x.get_shape()[1], out_dim], - initializer=tf.uniform_unit_scaling_initializer(factor=1.0)) - b = tf.get_variable( - "biases", [out_dim], initializer=tf.constant_initializer()) - return tf.nn.xw_plus_b(x, w, b) - - def _global_avg_pool(self, x): - assert x.get_shape().ndims == 4 - return tf.reduce_mean(x, [1, 2]) diff --git a/doc/source/custom_directives.py b/doc/source/custom_directives.py index 4d7e84778..3b33c268a 100644 --- a/doc/source/custom_directives.py +++ b/doc/source/custom_directives.py @@ -11,6 +11,8 @@ try: except NameError: FileNotFoundError = IOError +# This is not a top level item in the directory, so we use `../` to refer +# to images located at the top level. GALLERY_TEMPLATE = """ .. raw:: html @@ -18,7 +20,7 @@ GALLERY_TEMPLATE = """ .. only:: html - .. figure:: {thumbnail} + .. figure:: ../{thumbnail} {description} @@ -71,12 +73,13 @@ class CustomGalleryItemDirective(Directive): if "figure" in self.options: env = self.state.document.settings.env rel_figname, figname = env.relfn2path(self.options["figure"]) - thumbnail = os.path.join("_static/thumbs/", - os.path.basename(figname)) - os.makedirs("_static/thumbs", exist_ok=True) + thumb_dir = os.path.join(env.srcdir, "_static/thumbs/") + os.makedirs(thumb_dir, exist_ok=True) + image_path = os.path.join(thumb_dir, os.path.basename(figname)) + sphinx_gallery.gen_rst.scale_image(figname, image_path, 400, 280) - sphinx_gallery.gen_rst.scale_image(figname, thumbnail, 400, 280) + thumbnail = os.path.relpath(image_path, env.srcdir) else: thumbnail = "/_static/img/thumbnails/default.png" diff --git a/doc/source/images/a3c.png b/doc/source/images/a3c.png new file mode 100644 index 0000000000000000000000000000000000000000..111c34b248b60cd03afcf017432fd57809bab899 GIT binary patch literal 24984 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 \ No newline at end of file diff --git a/doc/source/images/pong.png b/doc/source/images/pong.png new file mode 100644 index 0000000000000000000000000000000000000000..18ae66df0ff96252e92901cdf473185075be5eab GIT binary patch literal 6046 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 literal 0 HcmV?d00001 diff --git a/doc/source/index.rst b/doc/source/index.rst index 5583ecc14..ebacdb0b0 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -263,7 +263,6 @@ Getting Involved auto_examples/plot_newsreader.rst auto_examples/plot_hyperparameter.rst auto_examples/plot_pong_example.rst - auto_examples/plot_resnet.rst auto_examples/plot_streaming.rst auto_examples/plot_parameter_server.rst auto_examples/plot_example-a3c.rst