├── .gitignore ├── LICENSE ├── README.md ├── codelab_1_NN_Numpy.ipynb ├── codelab_2_tensorflow_graph.ipynb ├── codelab_3_tensorflow_nn.ipynb ├── codelab_4_tensorboard.ipynb ├── codelab_5_simple_cnn.py ├── codelab_6_image_manipulation ├── Fabio.jpg ├── Lab Image Manipulation.ipynb ├── Lenna.jpg └── mandrill.jpg └── codelab_7_ml_engine ├── 1. ML Engine - Training.ipynb ├── 2. ML Engine - Deployment.ipynb ├── cifar10 ├── .gitignore ├── README.md ├── config.yaml ├── predict_test.json ├── setup.py └── trainer │ ├── __init__.py │ ├── eval.py │ ├── input.py │ ├── model.py │ └── task.py └── emojify ├── .gitignore ├── Makefile ├── README.md ├── emojify.py ├── requirements.in ├── requirements.txt ├── static ├── css │ └── index.css └── uploads │ └── .gitignore └── templates └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .ipynb_checkpoints/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Thinking Machines Data Science 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # deeplearningworkshop 2 | Codelab exercises for the DOST Deep Learning Workshop July 2017 3 | -------------------------------------------------------------------------------- /codelab_2_tensorflow_graph.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Tensorflow Fundamental Computational Graph\n", 8 | "\n", 9 | "Tensorflow Core layer. Building computational graphs!" 10 | ] 11 | }, 12 | { 13 | "cell_type": "code", 14 | "execution_count": 31, 15 | "metadata": {}, 16 | "outputs": [ 17 | { 18 | "name": "stdout", 19 | "output_type": "stream", 20 | "text": [ 21 | "(, )\n" 22 | ] 23 | } 24 | ], 25 | "source": [ 26 | "import tensorflow as tf\n", 27 | "\n", 28 | "node1 = tf.constant(3.0, dtype=tf.float32) \n", 29 | "node2 = tf.constant(4.0) #dtype float32 is a default \n", 30 | "print(node1, node2)" 31 | ] 32 | }, 33 | { 34 | "cell_type": "markdown", 35 | "metadata": {}, 36 | "source": [ 37 | "In Tensorflow Core, nodes are operations or variables. They take in some tensors, and output tensors.\n", 38 | "\n", 39 | "Nodes do not return a value, and are not evaluated until specifically run inside a Session." 40 | ] 41 | }, 42 | { 43 | "cell_type": "code", 44 | "execution_count": 17, 45 | "metadata": {}, 46 | "outputs": [ 47 | { 48 | "name": "stdout", 49 | "output_type": "stream", 50 | "text": [ 51 | "[3.0, 4.0]\n" 52 | ] 53 | } 54 | ], 55 | "source": [ 56 | "sess = tf.Session()\n", 57 | "print( sess.run([node1, node2]))" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "We can perform operations like so:" 65 | ] 66 | }, 67 | { 68 | "cell_type": "code", 69 | "execution_count": 4, 70 | "metadata": {}, 71 | "outputs": [ 72 | { 73 | "name": "stdout", 74 | "output_type": "stream", 75 | "text": [ 76 | "7.0\n" 77 | ] 78 | } 79 | ], 80 | "source": [ 81 | "node3 = tf.add(node1, node2)\n", 82 | "print sess.run(node3)" 83 | ] 84 | }, 85 | { 86 | "cell_type": "markdown", 87 | "metadata": {}, 88 | "source": [ 89 | "Instead of one data type for variables, Tensorflow breaks things into two types: \n", 90 | "\n", 91 | "1. `placeholder` - a promise to provide a value later. Use to feed training data into the graph. \n", 92 | "2. `variable` - must be initialized with some value. Use for trainable variables like `W` weights" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": 5, 98 | "metadata": {}, 99 | "outputs": [ 100 | { 101 | "name": "stdout", 102 | "output_type": "stream", 103 | "text": [ 104 | "7.5\n", 105 | "7.5\n", 106 | "[ 6. 4.]\n" 107 | ] 108 | } 109 | ], 110 | "source": [ 111 | "# creating the tiniest graph\n", 112 | "a = tf.placeholder(tf.float32)\n", 113 | "b = tf.placeholder(tf.float32)\n", 114 | "adder_node = tf.add(a,b)\n", 115 | "\n", 116 | "print(sess.run(adder_node, feed_dict = {a:3, b:4.5})) \n", 117 | "print(sess.run(adder_node, {a:3, b:4.5}))\n", 118 | "print(sess.run(adder_node, {a:[2,1], b:[4,3]})) # arrays are tensors too" 119 | ] 120 | }, 121 | { 122 | "cell_type": "markdown", 123 | "metadata": {}, 124 | "source": [ 125 | "Going back to our `WX + b` linear model, we would use `variable` for `W` and `b`, and `placeholder` for the features `X`" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 28, 131 | "metadata": {}, 132 | "outputs": [ 133 | { 134 | "name": "stdout", 135 | "output_type": "stream", 136 | "text": [ 137 | "None\n" 138 | ] 139 | } 140 | ], 141 | "source": [ 142 | "# initialize the graph \n", 143 | "W = tf.Variable([0.3], dtype=tf.float32)\n", 144 | "b = tf.Variable([-.3], dtype=tf.float32)\n", 145 | "x = tf.placeholder(tf.float32)\n", 146 | "linear_model = W * x + b\n", 147 | "\n", 148 | "# must run to initialize all the variables in this graph \n", 149 | "# the global_variables_initializer() must be run within the context of a session \n", 150 | "init = tf.global_variables_initializer() \n", 151 | "print(sess.run(init))" 152 | ] 153 | }, 154 | { 155 | "cell_type": "code", 156 | "execution_count": 25, 157 | "metadata": {}, 158 | "outputs": [ 159 | { 160 | "name": "stdout", 161 | "output_type": "stream", 162 | "text": [ 163 | "[ 0.69999999 1.70000005 2.70000005 3.70000005]\n" 164 | ] 165 | } 166 | ], 167 | "source": [ 168 | "# run the computational graph\n", 169 | "print(sess.run(linear_model, {x:[1,2,3,4]}))" 170 | ] 171 | }, 172 | { 173 | "cell_type": "markdown", 174 | "metadata": {}, 175 | "source": [ 176 | "You can reassign a values in a `variable` which you'll need to do every time you update any parameters." 177 | ] 178 | }, 179 | { 180 | "cell_type": "code", 181 | "execution_count": 30, 182 | "metadata": {}, 183 | "outputs": [ 184 | { 185 | "name": "stdout", 186 | "output_type": "stream", 187 | "text": [ 188 | "[ 0.69999999 1.70000005 2.70000005 3.70000005]\n" 189 | ] 190 | } 191 | ], 192 | "source": [ 193 | "updateW = tf.assign(W, [1.0])\n", 194 | "print(sess.run(updateW * x + b, {x:[1,2,3,4]}))" 195 | ] 196 | }, 197 | { 198 | "cell_type": "markdown", 199 | "metadata": {}, 200 | "source": [ 201 | "## Compare and Contrast: np and tf Computational Graphs\n", 202 | "\n", 203 | "Go through this code for a computational graph in numpy and recreate it in TensorFlow. Flip back to the graph itself and let's work out the math first.\n", 204 | "\n", 205 | "Credit to CS231n Winter 2017 Lecture 8 for the exercise" 206 | ] 207 | }, 208 | { 209 | "cell_type": "markdown", 210 | "metadata": {}, 211 | "source": [ 212 | "### Numpy Version" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": null, 218 | "metadata": { 219 | "collapsed": true 220 | }, 221 | "outputs": [], 222 | "source": [ 223 | "import numpy as np\n", 224 | "np.random.seed(0)\n", 225 | "\n", 226 | "# data dimensions N*D\n", 227 | "N, D = 3, 4 \n", 228 | "\n", 229 | "# input features \n", 230 | "x = np.random.randn(N, D)\n", 231 | "y = np.random.randn(N, D)\n", 232 | "z = np.random.randn(N, D)\n", 233 | "\n", 234 | "# computational nodes\n", 235 | "a = x * y\n", 236 | "b = a + z\n", 237 | "c = np.sum(b)\n", 238 | "\n", 239 | "# all the gradients\n", 240 | "grad_c = 1.0\n", 241 | "grad_b = grad_c * np.ones((N,D))\n", 242 | "grad_a = grad_b.copy()\n", 243 | "grad_z = grad_b.copy()\n", 244 | "grad_x = grad_a * y \n", 245 | "grad_y = grad_a * x" 246 | ] 247 | }, 248 | { 249 | "cell_type": "markdown", 250 | "metadata": {}, 251 | "source": [ 252 | "### TensorFlow Version\n" 253 | ] 254 | }, 255 | { 256 | "cell_type": "code", 257 | "execution_count": 14, 258 | "metadata": {}, 259 | "outputs": [ 260 | { 261 | "name": "stdout", 262 | "output_type": "stream", 263 | "text": [ 264 | "c node value 6.71701\n", 265 | "x gradient value [[ 0.76103771 0.12167501 0.44386324 0.33367434]\n", 266 | " [ 1.49407911 -0.20515826 0.3130677 -0.85409576]\n", 267 | " [-2.55298972 0.65361857 0.86443621 -0.74216503]]\n", 268 | "y gradient value [[ 1.76405239 0.40015721 0.97873801 2.24089313]\n", 269 | " [ 1.867558 -0.97727787 0.95008844 -0.1513572 ]\n", 270 | " [-0.10321885 0.41059852 0.14404356 1.45427346]]\n", 271 | "z gradient value [[ 1. 1. 1. 1.]\n", 272 | " [ 1. 1. 1. 1.]\n", 273 | " [ 1. 1. 1. 1.]]\n" 274 | ] 275 | } 276 | ], 277 | "source": [ 278 | "import numpy as np\n", 279 | "import tensorflow as tf\n", 280 | "\n", 281 | "np.random.seed(0)\n", 282 | "\n", 283 | "# data dimensions N*D\n", 284 | "N, D = 3, 4 \n", 285 | "\n", 286 | "# placeholders for data feeds\n", 287 | "x = tf.placeholder(tf.float32)\n", 288 | "y = tf.placeholder(tf.float32)\n", 289 | "z = tf.placeholder(tf.float32)\n", 290 | "\n", 291 | "# nodes \n", 292 | "a = x * y\n", 293 | "b = a + z\n", 294 | "c = tf.reduce_sum(b)\n", 295 | "\n", 296 | "# gradients, so much easier!\n", 297 | "grad_x, grad_y, grad_z = tf.gradients(c, [x,y,z])\n", 298 | "\n", 299 | "\n", 300 | "# run the computational graph\n", 301 | "with tf.Session() as sess: \n", 302 | " \n", 303 | " # creating dummy training data\n", 304 | " values = {\n", 305 | " x: np.random.randn(N, D),\n", 306 | " y: np.random.randn(N, D),\n", 307 | " z: np.random.randn(N, D),\n", 308 | " }\n", 309 | " \n", 310 | " # running the graph! \n", 311 | " out = sess.run([c, grad_x, grad_y, grad_z], feed_dict=values)\n", 312 | " c_val, grad_x_val, grad_y_val, grad_z_val = out\n", 313 | " \n", 314 | " print \"c node value\", c_val\n", 315 | " print \"x gradient value\", grad_x_val\n", 316 | " print \"y gradient value\", grad_y_val\n", 317 | " print \"z gradient value\", grad_z_val" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "## Questions\n", 325 | "\n", 326 | "1. What's going on in tf.gradients? \n", 327 | "2. How many times does this graph compute? " 328 | ] 329 | } 330 | ], 331 | "metadata": { 332 | "kernelspec": { 333 | "display_name": "Python 3", 334 | "language": "python", 335 | "name": "python3" 336 | }, 337 | "language_info": { 338 | "codemirror_mode": { 339 | "name": "ipython", 340 | "version": 3 341 | }, 342 | "file_extension": ".py", 343 | "mimetype": "text/x-python", 344 | "name": "python", 345 | "nbconvert_exporter": "python", 346 | "pygments_lexer": "ipython3", 347 | "version": "3.6.1" 348 | } 349 | }, 350 | "nbformat": 4, 351 | "nbformat_minor": 2 352 | } 353 | -------------------------------------------------------------------------------- /codelab_3_tensorflow_nn.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Building a Two Layer Neural Network in TensorFlow\n", 8 | "\n", 9 | "Use built-in Deep Learning Classifier" 10 | ] 11 | }, 12 | { 13 | "cell_type": "markdown", 14 | "metadata": {}, 15 | "source": [ 16 | "## Recall our Spiral Dataset\n", 17 | "\n", 18 | "from earlier today" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": 9, 24 | "metadata": {}, 25 | "outputs": [ 26 | { 27 | "data": { 28 | "image/png": "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\nQmCC\n", 29 | "text/plain": [ 30 | "" 31 | ] 32 | }, 33 | "metadata": {}, 34 | "output_type": "display_data" 35 | } 36 | ], 37 | "source": [ 38 | "import numpy as np\n", 39 | "import matplotlib.pyplot as plt\n", 40 | "\n", 41 | "N = 100 # points per class\n", 42 | "D = 2 # dimensionality at 2 so we can eyeball it\n", 43 | "K = 3 # number of classes\n", 44 | "\n", 45 | "X = np.zeros((N*K, D)) # generate an empty matrix to hold X features\n", 46 | "y = np.zeros(N*K, dtype='int32') # switching this to int32 \n", 47 | "\n", 48 | "# for 3 classes, evenly generates spiral arms\n", 49 | "for j in xrange(K):\n", 50 | " ix = range(N*j, N*(j+1))\n", 51 | " r = np.linspace(0.0,1,N) #radius\n", 52 | " t = np.linspace(j*4, (j+1)*4, N) + np.random.randn(N)*0.2 # theta\n", 53 | " X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]\n", 54 | " y[ix] = j\n", 55 | "\n", 56 | "plt.scatter(X[:,0], X[:,1], c=y, s=20, cmap=plt.cm.Spectral)\n", 57 | "plt.show()" 58 | ] 59 | }, 60 | { 61 | "cell_type": "markdown", 62 | "metadata": {}, 63 | "source": [ 64 | "## TensorFlow\n", 65 | "\n", 66 | "Let's create a DNNClassifier using TF's built-in classifier, and evaluate its accuracy. " 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 24, 72 | "metadata": {}, 73 | "outputs": [ 74 | { 75 | "name": "stdout", 76 | "output_type": "stream", 77 | "text": [ 78 | "INFO:tensorflow:Using default config.\n", 79 | "INFO:tensorflow:Using config: {'_save_checkpoints_secs': 600, '_num_ps_replicas': 0, '_keep_checkpoint_max': 5, '_tf_random_seed': None, '_task_type': None, '_environment': 'local', '_is_chief': True, '_cluster_spec': , '_tf_config': gpu_options {\n", 80 | " per_process_gpu_memory_fraction: 1\n", 81 | "}\n", 82 | ", '_task_id': 0, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_evaluation_master': '', '_keep_checkpoint_every_n_hours': 10000, '_master': ''}\n", 83 | "WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:1362: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 84 | "Instructions for updating:\n", 85 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 86 | "INFO:tensorflow:Create CheckpointSaverHook.\n", 87 | "INFO:tensorflow:Saving checkpoints for 2001 into /tmp/spiral_model/model.ckpt.\n", 88 | "INFO:tensorflow:loss = 1.26742, step = 2001\n", 89 | "INFO:tensorflow:global_step/sec: 754.962\n", 90 | "INFO:tensorflow:loss = 1.08413, step = 2101\n", 91 | "INFO:tensorflow:Saving checkpoints for 2200 into /tmp/spiral_model/model.ckpt.\n", 92 | "INFO:tensorflow:Loss for final step: 0.982373.\n", 93 | "WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:1362: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 94 | "Instructions for updating:\n", 95 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 96 | "INFO:tensorflow:Create CheckpointSaverHook.\n", 97 | "INFO:tensorflow:Saving checkpoints for 2201 into /tmp/spiral_model/model.ckpt.\n", 98 | "INFO:tensorflow:loss = 0.981605, step = 2201\n", 99 | "INFO:tensorflow:global_step/sec: 906.702\n", 100 | "INFO:tensorflow:loss = 0.922959, step = 2301\n", 101 | "INFO:tensorflow:global_step/sec: 908.883\n", 102 | "INFO:tensorflow:loss = 0.88959, step = 2401\n", 103 | "INFO:tensorflow:Saving checkpoints for 2500 into /tmp/spiral_model/model.ckpt.\n", 104 | "INFO:tensorflow:Loss for final step: 0.870644.\n", 105 | "WARNING:tensorflow:From /usr/local/lib/python2.7/dist-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:1362: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 106 | "Instructions for updating:\n", 107 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 108 | "INFO:tensorflow:Starting evaluation at 2017-07-09-09:15:01\n", 109 | "INFO:tensorflow:Evaluation [1/1]\n", 110 | "INFO:tensorflow:Finished evaluation at 2017-07-09-09:15:02\n", 111 | "INFO:tensorflow:Saving dict for global step 2500: accuracy = 0.613333, auc = 0.812869, global_step = 2500, loss = 0.8705\n", 112 | "WARNING:tensorflow:Skipping summary for global_step, must be a float or np.float32.\n", 113 | "\n", 114 | " Accuracy: 0.613333\n", 115 | "\n" 116 | ] 117 | } 118 | ], 119 | "source": [ 120 | "import tensorflow as tf\n", 121 | "\n", 122 | "# what should the classifier expect in terms of features\n", 123 | "feature_columns = [tf.contrib.layers.real_valued_column(\"\", dimension=D)]\n", 124 | "\n", 125 | "# defining the actual classifier \n", 126 | "dnn_spiral_classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,\n", 127 | " activation_fn = tf.nn.softmax, # softmax activation \n", 128 | " optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01), #GD with LR of 0.01\n", 129 | " hidden_units = [10], # one hidden layer, containing 10 neurons\n", 130 | " n_classes = K, # K target classes \n", 131 | " model_dir=\"/tmp/spiral_model\") # directory for saving model checkpoints\n", 132 | "\n", 133 | "# turn data into tensors to feed into the computational graph\n", 134 | "# honestly input_fn could also handle these as np.arrays but this is here to show you that the tf.constant operation can run on np.array input\n", 135 | "def get_inputs(): \n", 136 | " X_tensor = tf.constant(X)\n", 137 | " y_tensor = tf.constant(y)\n", 138 | " return X_tensor, y_tensor\n", 139 | "\n", 140 | "# fit the model\n", 141 | "dnn_spiral_classifier.fit(input_fn=get_inputs, steps=200)\n", 142 | "\n", 143 | "# interestingsly, you can continue training the model by continuing to call fit\n", 144 | "dnn_spiral_classifier.fit(input_fn=get_inputs, steps=300)\n", 145 | "\n", 146 | "\n", 147 | "#evaluating the accuracy \n", 148 | "accuracy_score = dnn_spiral_classifier.evaluate(input_fn=get_inputs, \n", 149 | " steps=1)[\"accuracy\"]\n", 150 | "\n", 151 | "print(\"\\n Accuracy: {0:f}\\n\".format(accuracy_score))" 152 | ] 153 | }, 154 | { 155 | "cell_type": "markdown", 156 | "metadata": { 157 | "collapsed": true 158 | }, 159 | "source": [ 160 | "## Notice the following:\n", 161 | "\n", 162 | "The higher level library vastly simplied the following mechanics:\n", 163 | "\n", 164 | "1. tf.session management\n", 165 | "2. training the model \n", 166 | "3. running evaluation loops\n", 167 | "4. feeding data into the model\n", 168 | "5. generating predictions from the model\n", 169 | "6. saving the model in a checkpoint file\n", 170 | "\n", 171 | "For most use cases, it's likely that the many common models built into to tf will be able to solve your problem. You'll have to do model tuning by figuring out the correct parameters. Building your computational graph node by doing isn't likely needed unless you're doing academic research or working with very specialized datasets where default performance plataues.'" 172 | ] 173 | }, 174 | { 175 | "cell_type": "markdown", 176 | "metadata": {}, 177 | "source": [ 178 | "## Look at the checkpoints\n", 179 | "Poke inside the /tmp/spiral_model/ directory to see how the checkpoint data is stored. What's contained in these files?" 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": 14, 185 | "metadata": {}, 186 | "outputs": [ 187 | { 188 | "name": "stdout", 189 | "output_type": "stream", 190 | "text": [ 191 | "checkpoint\r\n", 192 | "\u001b[0m\u001b[01;34meval\u001b[0m/\r\n", 193 | "events.out.tfevents.1499589900.datalab-server-ldln8\r\n", 194 | "events.out.tfevents.1499589903.datalab-server-ldln8\r\n", 195 | "events.out.tfevents.1499589929.datalab-server-ldln8\r\n", 196 | "events.out.tfevents.1499589932.datalab-server-ldln8\r\n", 197 | "graph.pbtxt\r\n", 198 | "model.ckpt-1000.data-00000-of-00001\r\n", 199 | "model.ckpt-1000.index\r\n", 200 | "model.ckpt-1000.meta\r\n", 201 | "model.ckpt-500.data-00000-of-00001\r\n", 202 | "model.ckpt-500.index\r\n", 203 | "model.ckpt-500.meta\r\n", 204 | "model.ckpt-501.data-00000-of-00001\r\n", 205 | "model.ckpt-501.index\r\n", 206 | "model.ckpt-501.meta\r\n", 207 | "model.ckpt-700.data-00000-of-00001\r\n", 208 | "model.ckpt-700.index\r\n", 209 | "model.ckpt-700.meta\r\n", 210 | "model.ckpt-701.data-00000-of-00001\r\n", 211 | "model.ckpt-701.index\r\n", 212 | "model.ckpt-701.meta\r\n" 213 | ] 214 | } 215 | ], 216 | "source": [ 217 | "%ls '/tmp/spiral_model/'" 218 | ] 219 | }, 220 | { 221 | "cell_type": "markdown", 222 | "metadata": {}, 223 | "source": [ 224 | "## Predicting on a new value\n", 225 | "\n", 226 | "Let's classify a new point" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": 28, 232 | "metadata": {}, 233 | "outputs": [ 234 | { 235 | "name": "stdout", 236 | "output_type": "stream", 237 | "text": [ 238 | "New Samples, Class Predictions: [1, 0]\n", 239 | "\n" 240 | ] 241 | } 242 | ], 243 | "source": [ 244 | "def new_points():\n", 245 | " return np.array([[1.0, 1.0],\n", 246 | " [-1.5, -1.0]], dtype = np.int32)\n", 247 | "\n", 248 | "predictions = list(dnn_spiral_classifier.predict(input_fn=new_points))\n", 249 | "\n", 250 | "print(\n", 251 | " \"New Samples, Class Predictions: {}\\n\"\n", 252 | " .format(predictions))" 253 | ] 254 | }, 255 | { 256 | "cell_type": "markdown", 257 | "metadata": {}, 258 | "source": [ 259 | "## Digging into the DNNClassifier\n", 260 | "\n", 261 | "The DNNClassifier is one fo the (Estimators)[https://www.tensorflow.org/api_guides/python/contrib.learn#Estimators] available in the tf.contrib.learn libary. Other estimators include: \n", 262 | "\n", 263 | "* KMeansClustering\n", 264 | "* DNNRegressor\n", 265 | "* LinearClassifier\n", 266 | "* LinearRegressor\n", 267 | "* LogisticRegressor\n", 268 | "\n", 269 | "Each one of these can perform various actions on the graph, including: \n", 270 | "\n", 271 | "* evaluate \n", 272 | "* infer \n", 273 | "* train\n", 274 | "\n", 275 | "Each one of these can read in batched input data with types including:\n", 276 | "\n", 277 | "* pandas data\n", 278 | "* real values columns from an input\n", 279 | "* real valued columns from an input function (what we used above) \n", 280 | "* batches\n", 281 | "\n", 282 | "Keep an eye on the documentation and updates. TensorFlow is under constant development. Things change very quickly! " 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": null, 288 | "metadata": { 289 | "collapsed": true 290 | }, 291 | "outputs": [], 292 | "source": [ 293 | "# watch out for this, tf.classifier.evaluate is going to be deprecated, so keep an eye out for a long-term solution to calculating accuracy\n", 294 | "accuracy_score = dnn_spiral_classifier.evaluate(input_fn=get_inputs, \n", 295 | " steps=1)[\"accuracy\"]" 296 | ] 297 | }, 298 | { 299 | "cell_type": "markdown", 300 | "metadata": {}, 301 | "source": [ 302 | "# Exercises\n", 303 | "\n", 304 | "Get into the (TensorFlow API docs)[https://www.tensorflow.org/api_docs/python/tf]. Try the following and see how it impacts the final scores\n", 305 | "\n", 306 | "1. Change the activation function to a ReLU \n", 307 | "2. Change the optimization function to stochastic gradient descent, then change it again to Adagrad\n", 308 | "3. Add more steps to training\n", 309 | "4. Add more layers\n", 310 | "5. Increase the number of neurons in each hidden layer\n", 311 | "6. Change the learning rate to huge and tiny values\n", 312 | "\n", 313 | "\n", 314 | "/\n", 315 | "\n", 316 | "\n", 317 | "### Gold Star Challenge\n", 318 | "\n", 319 | "Reimplement the Spiral Classifier as a 2 Layer Neural Network in TensorFlow Core " 320 | ] 321 | }, 322 | { 323 | "cell_type": "code", 324 | "execution_count": null, 325 | "metadata": { 326 | "collapsed": true 327 | }, 328 | "outputs": [], 329 | "source": [ 330 | "# sample code to use for the gold star challenge from https://www.tensorflow.org/get_started/get_started \n", 331 | "\n", 332 | "import numpy as np\n", 333 | "import tensorflow as tf\n", 334 | "\n", 335 | "# Model parameters\n", 336 | "W = tf.Variable([.3], dtype=tf.float32)\n", 337 | "b = tf.Variable([-.3], dtype=tf.float32)\n", 338 | "# Model input and output\n", 339 | "x = tf.placeholder(tf.float32)\n", 340 | "linear_model = W * x + b\n", 341 | "y = tf.placeholder(tf.float32)\n", 342 | "# loss\n", 343 | "loss = tf.reduce_sum(tf.square(linear_model - y)) # sum of the squares\n", 344 | "# optimizer\n", 345 | "optimizer = tf.train.GradientDescentOptimizer(0.01)\n", 346 | "train = optimizer.minimize(loss)\n", 347 | "# training data\n", 348 | "x_train = [1,2,3,4]\n", 349 | "y_train = [0,-1,-2,-3]\n", 350 | "# training loop\n", 351 | "init = tf.global_variables_initializer()\n", 352 | "sess = tf.Session()\n", 353 | "sess.run(init) # reset values to wrong\n", 354 | "for i in range(1000):\n", 355 | " sess.run(train, {x:x_train, y:y_train})\n", 356 | "\n", 357 | "# evaluate training accuracy\n", 358 | "curr_W, curr_b, curr_loss = sess.run([W, b, loss], {x:x_train, y:y_train})\n", 359 | "print(\"W: %s b: %s loss: %s\"%(curr_W, curr_b, curr_loss))" 360 | ] 361 | } 362 | ], 363 | "metadata": { 364 | "kernelspec": { 365 | "display_name": "Python 3", 366 | "language": "python", 367 | "name": "python3" 368 | }, 369 | "language_info": { 370 | "codemirror_mode": { 371 | "name": "ipython", 372 | "version": 3 373 | }, 374 | "file_extension": ".py", 375 | "mimetype": "text/x-python", 376 | "name": "python", 377 | "nbconvert_exporter": "python", 378 | "pygments_lexer": "ipython3", 379 | "version": "3.6.1" 380 | } 381 | }, 382 | "nbformat": 4, 383 | "nbformat_minor": 2 384 | } 385 | -------------------------------------------------------------------------------- /codelab_4_tensorboard.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Babysitting the Learning Process with TensorBoard\n", 8 | "\n", 9 | "Even with TensorFlow's built in Estimators, there are a huge number of hyperparameters to tune and tweak. This is the black magic part of neural networks. Sometimes things just work, and there's no great analytical reason why. This makes it really key for us to be able to sit on top on the model as it learns.\n", 10 | "\n", 11 | "TensorFlow provides [TensorBoard](https://www.tensorflow.org/get_started/summaries_and_tensorboard), a visualization layer on top of the training process. " 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 1, 17 | "metadata": { 18 | "ExecuteTime": { 19 | "end_time": "2017-12-15T07:22:11.546382Z", 20 | "start_time": "2017-12-15T15:22:10.725810+08:00" 21 | }, 22 | "collapsed": false 23 | }, 24 | "outputs": [ 25 | { 26 | "data": { 27 | "image/png": "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\n", 28 | "text/plain": [ 29 | "" 30 | ] 31 | }, 32 | "metadata": {}, 33 | "output_type": "display_data" 34 | } 35 | ], 36 | "source": [ 37 | "# We really like this spiral dataset\n", 38 | "\n", 39 | "import numpy as np\n", 40 | "import matplotlib.pyplot as plt\n", 41 | "\n", 42 | "N = 1000 # points per class\n", 43 | "D = 2 # dimensionality at 2 so we can eyeball it\n", 44 | "K = 3 # number of classes\n", 45 | "\n", 46 | "X = np.zeros((N*K, D)) # generate an empty matrix to hold X features\n", 47 | "y = np.zeros(N*K, dtype='int32') # switching this to int32 \n", 48 | "\n", 49 | "# for 3 classes, evenly generates spiral arms\n", 50 | "for j in range(K):\n", 51 | " ix = range(N*j, N*(j+1))\n", 52 | " r = np.linspace(0.0,1,N) #radius\n", 53 | " t = np.linspace(j*4, (j+1)*4, N) + np.random.randn(N)*0.2 # theta\n", 54 | " X[ix] = np.c_[r*np.sin(t), r*np.cos(t)]\n", 55 | " y[ix] = j\n", 56 | "\n", 57 | "plt.scatter(X[:,0], X[:,1], c=y, s=20, cmap=plt.cm.Spectral)\n", 58 | "plt.show()" 59 | ] 60 | }, 61 | { 62 | "cell_type": "markdown", 63 | "metadata": {}, 64 | "source": [ 65 | "## A much more highly optimized DNNClassifier\n", 66 | "\n", 67 | "We repeat the setup in our old notebook" 68 | ] 69 | }, 70 | { 71 | "cell_type": "code", 72 | "execution_count": 2, 73 | "metadata": { 74 | "ExecuteTime": { 75 | "end_time": "2017-12-15T07:22:15.327975Z", 76 | "start_time": "2017-12-15T15:22:11.550287+08:00" 77 | }, 78 | "collapsed": false 79 | }, 80 | "outputs": [ 81 | { 82 | "name": "stdout", 83 | "output_type": "stream", 84 | "text": [ 85 | "INFO:tensorflow:Using default config.\n", 86 | "INFO:tensorflow:Using config: {'_save_checkpoints_secs': 600, '_num_ps_replicas': 0, '_keep_checkpoint_max': 5, '_tf_random_seed': None, '_task_type': None, '_environment': 'local', '_is_chief': True, '_cluster_spec': , '_tf_config': gpu_options {\n", 87 | " per_process_gpu_memory_fraction: 1.0\n", 88 | "}\n", 89 | ", '_task_id': 0, '_save_summary_steps': 100, '_save_checkpoints_steps': None, '_evaluation_master': '', '_keep_checkpoint_every_n_hours': 10000, '_master': ''}\n", 90 | "WARNING:tensorflow:From /Users/jiah/anaconda/lib/python2.7/site-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:1362: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 91 | "Instructions for updating:\n", 92 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 93 | "INFO:tensorflow:Create CheckpointSaverHook.\n", 94 | "INFO:tensorflow:Saving checkpoints for 3151 into /tmp/spiral_model_learning/model.ckpt.\n", 95 | "INFO:tensorflow:loss = 0.0293235, step = 3151\n", 96 | "INFO:tensorflow:Saving checkpoints for 3200 into /tmp/spiral_model_learning/model.ckpt.\n", 97 | "INFO:tensorflow:Loss for final step: 0.0281759.\n", 98 | "WARNING:tensorflow:From /Users/jiah/anaconda/lib/python2.7/site-packages/tensorflow/contrib/learn/python/learn/estimators/head.py:1362: scalar_summary (from tensorflow.python.ops.logging_ops) is deprecated and will be removed after 2016-11-30.\n", 99 | "Instructions for updating:\n", 100 | "Please switch to tf.summary.scalar. Note that tf.summary.scalar uses the node name instead of the tag. This means that TensorFlow will automatically de-duplicate summary names based on the scope they are created in. Also, passing a tensor or list of tags to a scalar summary op is no longer supported.\n", 101 | "INFO:tensorflow:Starting evaluation at 2017-12-15-07:22:15\n", 102 | "INFO:tensorflow:Evaluation [1/1]\n", 103 | "INFO:tensorflow:Finished evaluation at 2017-12-15-07:22:15\n", 104 | "INFO:tensorflow:Saving dict for global step 3200: accuracy = 0.997333, auc = 0.999982, global_step = 3200, loss = 0.0281636\n", 105 | "WARNING:tensorflow:Skipping summary for global_step, must be a float or np.float32.\n" 106 | ] 107 | } 108 | ], 109 | "source": [ 110 | "import tensorflow as tf\n", 111 | "\n", 112 | "# what should the classifier expect in terms of features\n", 113 | "feature_columns = [tf.contrib.layers.real_valued_column(\"\", dimension=D)]\n", 114 | "\n", 115 | "# defining the actual classifier \n", 116 | "dnn_spiral_classifier = tf.contrib.learn.DNNClassifier(feature_columns=feature_columns,\n", 117 | " activation_fn = tf.nn.relu, \n", 118 | " hidden_units = [20,10], # one hidden layer, containing 10 neurons\n", 119 | " n_classes = K, # K target classes \n", 120 | " model_dir=\"/tmp/spiral_model_learning\") # directory for saving model checkpoints\n", 121 | "\n", 122 | "# turn data into tensors to feed into the computational graph\n", 123 | "# honestly input_fn could also handle these as np.arrays but this is here to show you that the tf.constant operation can run on np.array input\n", 124 | "def get_inputs(): \n", 125 | " X_tensor = tf.constant(X)\n", 126 | " y_tensor = tf.constant(y)\n", 127 | " return X_tensor, y_tensor\n", 128 | "\n", 129 | "\n", 130 | "# fit the model\n", 131 | "dnn_spiral_classifier.fit(input_fn=get_inputs, steps=50)\n", 132 | "\n", 133 | "#evaluating the accuracy \n", 134 | "accuracy_score = dnn_spiral_classifier.evaluate(input_fn=get_inputs, \n", 135 | " steps=1)[\"accuracy\"]" 136 | ] 137 | }, 138 | { 139 | "cell_type": "markdown", 140 | "metadata": {}, 141 | "source": [ 142 | "## Launching TensorBoard\n", 143 | "\n", 144 | "We'll share a quick two liner that allows you to get your Tensorboard up and running so you can look around at its features." 145 | ] 146 | }, 147 | { 148 | "cell_type": "code", 149 | "execution_count": 3, 150 | "metadata": { 151 | "ExecuteTime": { 152 | "end_time": "2017-12-15T07:22:19.510435Z", 153 | "start_time": "2017-12-15T15:22:15.340679+08:00" 154 | }, 155 | "collapsed": false 156 | }, 157 | "outputs": [ 158 | { 159 | "data": { 160 | "text/html": [ 161 | "

TensorBoard was started successfully with pid 18475. Click here to access it.

" 162 | ] 163 | }, 164 | "metadata": {}, 165 | "output_type": "display_data" 166 | } 167 | ], 168 | "source": [ 169 | "from google.datalab.ml import TensorBoard\n", 170 | "tb_id = TensorBoard.start(\"/tmp/spiral_model_learning\")" 171 | ] 172 | }, 173 | { 174 | "cell_type": "markdown", 175 | "metadata": {}, 176 | "source": [ 177 | "Notice that the accuracy only contains one point. That's because we only ran `dnn_spiral_classifier.fit` and `evaluate` once and didn't tell it to record data during intermediary steps.\n", 178 | "\n", 179 | "**WARNING: If you try it out on local...** \n", 180 | "... the link leads to a 404. It will lead you to an address like http://localhost:8888/_proxy/123 . You can access your Tensorboard using the port number at the end, e.g. http://localhost:123/ \n" 181 | ] 182 | }, 183 | { 184 | "cell_type": "markdown", 185 | "metadata": {}, 186 | "source": [ 187 | "## Adding points" 188 | ] 189 | }, 190 | { 191 | "cell_type": "markdown", 192 | "metadata": {}, 193 | "source": [ 194 | "We create some more points by looping over the fitting. This creates ten more points." 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 4, 200 | "metadata": { 201 | "ExecuteTime": { 202 | "end_time": "2017-12-15T07:22:36.996865Z", 203 | "start_time": "2017-12-15T15:22:19.514096+08:00" 204 | }, 205 | "collapsed": false 206 | }, 207 | "outputs": [], 208 | "source": [ 209 | "# (optional) restrict logging level to errors, to keep things a little quieter\n", 210 | "tf.logging.set_verbosity(tf.logging.ERROR)\n", 211 | "\n", 212 | "for epoch in range(10):\n", 213 | " dnn_spiral_classifier.fit(input_fn=get_inputs, steps=100)\n", 214 | " dnn_spiral_classifier.evaluate(input_fn=get_inputs, steps=1)[\"accuracy\"]" 215 | ] 216 | }, 217 | { 218 | "cell_type": "markdown", 219 | "metadata": { 220 | "ExecuteTime": { 221 | "end_time": "2017-12-15T07:16:15.156071Z", 222 | "start_time": "2017-12-15T15:16:15.150872+08:00" 223 | } 224 | }, 225 | "source": [ 226 | "Refresh your Tensorboard to see the additional points.\n", 227 | "\n", 228 | "**If you don't see any changes,** clear the cache or reopen in an incognito window. \n", 229 | "\n", 230 | "The full multi-step logging setup is available in the [official docs](https://www.tensorflow.org/get_started/summaries_and_tensorboard). It was changing every few months at the time this tutorial was created, so we chose not to go into detail for this section." 231 | ] 232 | }, 233 | { 234 | "cell_type": "code", 235 | "execution_count": 5, 236 | "metadata": { 237 | "ExecuteTime": { 238 | "end_time": "2017-12-15T07:22:37.006004Z", 239 | "start_time": "2017-12-15T15:22:37.000218+08:00" 240 | }, 241 | "collapsed": false 242 | }, 243 | "outputs": [], 244 | "source": [ 245 | "# The following line will wipe your tmp records if you want to \"reset\" everything while configuring your Tensorboard. Use it with caution!\n", 246 | "do_I_want_to_reset = False\n", 247 | "if do_I_want_to_reset:\n", 248 | " tf.gfile.DeleteRecursively('/tmp/spiral_model_learning/')" 249 | ] 250 | } 251 | ], 252 | "metadata": { 253 | "anaconda-cloud": {}, 254 | "kernelspec": { 255 | "display_name": "Python [default]", 256 | "language": "python", 257 | "name": "python2" 258 | }, 259 | "language_info": { 260 | "codemirror_mode": { 261 | "name": "ipython", 262 | "version": 2 263 | }, 264 | "file_extension": ".py", 265 | "mimetype": "text/x-python", 266 | "name": "python", 267 | "nbconvert_exporter": "python", 268 | "pygments_lexer": "ipython2", 269 | "version": "2.7.12" 270 | }, 271 | "toc": { 272 | "colors": { 273 | "hover_highlight": "#DAA520", 274 | "running_highlight": "#FF0000", 275 | "selected_highlight": "#FFD700" 276 | }, 277 | "moveMenuLeft": true, 278 | "nav_menu": { 279 | "height": "81px", 280 | "width": "252px" 281 | }, 282 | "navigate_menu": true, 283 | "number_sections": true, 284 | "sideBar": true, 285 | "threshold": 4, 286 | "toc_cell": false, 287 | "toc_section_display": "block", 288 | "toc_window_display": false 289 | } 290 | }, 291 | "nbformat": 4, 292 | "nbformat_minor": 2 293 | } 294 | -------------------------------------------------------------------------------- /codelab_5_simple_cnn.py: -------------------------------------------------------------------------------- 1 | # SIMPLE MNIST CNN 2 | # Source: https://www.tensorflow.org/tutorials/layers 3 | 4 | def cnn_model_fn(features, labels, mode): 5 | """Model function for CNN.""" 6 | # Input Layer 7 | input_layer = tf.reshape(features, [-1, 28, 28, 1]) 8 | 9 | # Convolutional Layer #1 10 | conv1 = tf.layers.conv2d( 11 | inputs=input_layer, 12 | filters=32, 13 | kernel_size=[5, 5], 14 | padding="same", 15 | activation=tf.nn.relu) 16 | 17 | # Pooling Layer #1 18 | pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2) 19 | 20 | # Convolutional Layer #2 and Pooling Layer #2 21 | conv2 = tf.layers.conv2d( 22 | inputs=pool1, 23 | filters=64, 24 | kernel_size=[5, 5], 25 | padding="same", 26 | activation=tf.nn.relu) 27 | pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2) 28 | 29 | # Dense Layer 30 | pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64]) 31 | dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu) 32 | dropout = tf.layers.dropout( 33 | inputs=dense, rate=0.4, training=mode == learn.ModeKeys.TRAIN) 34 | 35 | # Logits Layer 36 | logits = tf.layers.dense(inputs=dropout, units=10) 37 | 38 | loss = None 39 | train_op = None 40 | 41 | # Calculate Loss (for both TRAIN and EVAL modes) 42 | if mode != learn.ModeKeys.INFER: 43 | onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10) 44 | loss = tf.losses.softmax_cross_entropy( 45 | onehot_labels=onehot_labels, logits=logits) 46 | 47 | # Configure the Training Op (for TRAIN mode) 48 | if mode == learn.ModeKeys.TRAIN: 49 | train_op = tf.contrib.layers.optimize_loss( 50 | loss=loss, 51 | global_step=tf.contrib.framework.get_global_step(), 52 | learning_rate=0.001, 53 | optimizer="SGD") 54 | 55 | # Generate Predictions 56 | predictions = { 57 | "classes": tf.argmax( 58 | input=logits, axis=1), 59 | "probabilities": tf.nn.softmax( 60 | logits, name="softmax_tensor") 61 | } 62 | 63 | # Return a ModelFnOps object 64 | return model_fn_lib.ModelFnOps( 65 | mode=mode, predictions=predictions, loss=loss, train_op=train_op) 66 | -------------------------------------------------------------------------------- /codelab_6_image_manipulation/Fabio.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkingmachines/deeplearningworkshop/7985aff1cea8b6dcd7d6abd3f266a07346ff8c04/codelab_6_image_manipulation/Fabio.jpg -------------------------------------------------------------------------------- /codelab_6_image_manipulation/Lenna.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkingmachines/deeplearningworkshop/7985aff1cea8b6dcd7d6abd3f266a07346ff8c04/codelab_6_image_manipulation/Lenna.jpg -------------------------------------------------------------------------------- /codelab_6_image_manipulation/mandrill.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkingmachines/deeplearningworkshop/7985aff1cea8b6dcd7d6abd3f266a07346ff8c04/codelab_6_image_manipulation/mandrill.jpg -------------------------------------------------------------------------------- /codelab_7_ml_engine/1. ML Engine - Training.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": true 7 | }, 8 | "source": [ 9 | "# Training a CNN model with the CIFAR-10 dataset in ML Engine\n", 10 | "\n", 11 | "The trainer package source is inside the `cifar10` directory. It was based from [Tensorflow's CNN tutorial](https://github.com/tensorflow/models/tree/master/tutorials/image/cifar10) and one of the [Datalab image classification example](https://github.com/googledatalab/pydatalab/tree/master/solutionbox/image_classification/mltoolbox/image/classification)." 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "## Enable the ML Engine API\n", 19 | "\n", 20 | "We need to enable the ML Engine API since it isn't by default.\n", 21 | "\n", 22 | "1. Head back to the [web console](https://console.cloud.google.com).\n", 23 | "2. Search for \"API Manager\" using the bar on the top middle of the page.\n", 24 | "3. Select __Library__ from the sidebar.\n", 25 | "4. Search for \"ML Engine\" and select __Google Cloud Machine Learning Engine__.\n", 26 | "5. Click __ENABLE__" 27 | ] 28 | }, 29 | { 30 | "cell_type": "markdown", 31 | "metadata": {}, 32 | "source": [ 33 | "## Build the trainer package" 34 | ] 35 | }, 36 | { 37 | "cell_type": "code", 38 | "execution_count": null, 39 | "metadata": { 40 | "collapsed": true, 41 | "scrolled": true 42 | }, 43 | "outputs": [], 44 | "source": [ 45 | "%%bash\n", 46 | "cd cifar10\n", 47 | "\n", 48 | "# Clean old builds\n", 49 | "rm -rf build dist\n", 50 | "\n", 51 | "# Build wheel distribution\n", 52 | "python setup.py bdist_wheel --universal\n", 53 | "\n", 54 | "# Check the built package\n", 55 | "ls -al dist" 56 | ] 57 | }, 58 | { 59 | "cell_type": "markdown", 60 | "metadata": {}, 61 | "source": [ 62 | "## Submit the training job to ML Engine" 63 | ] 64 | }, 65 | { 66 | "cell_type": "code", 67 | "execution_count": null, 68 | "metadata": { 69 | "collapsed": true 70 | }, 71 | "outputs": [], 72 | "source": [ 73 | "%%bash\n", 74 | "cd cifar10\n", 75 | "\n", 76 | "# Set some variables\n", 77 | "JOB_NAME=cifar10_train_$(date +%s)\n", 78 | "BUCKET_NAME=dost_deeplearning_cifar10 # Change this to your own!\n", 79 | "TRAINING_PACKAGE_PATH=dist/trainer-0.0.0-py2.py3-none-any.whl\n", 80 | "\n", 81 | "# Submit the job through the gcloud tool\n", 82 | "gcloud ml-engine jobs submit training \\\n", 83 | " $JOB_NAME \\\n", 84 | " --region us-east1 \\\n", 85 | " --job-dir gs://$BUCKET_NAME/$JOB_NAME \\\n", 86 | " --packages $TRAINING_PACKAGE_PATH \\\n", 87 | " --module-name trainer.task \\\n", 88 | " --config config.yaml" 89 | ] 90 | }, 91 | { 92 | "cell_type": "markdown", 93 | "metadata": {}, 94 | "source": [ 95 | "It will take a few minutes for ML Engine to provision a training instance for our job. While that's happening, let's talk about pricing!" 96 | ] 97 | }, 98 | { 99 | "cell_type": "markdown", 100 | "metadata": {}, 101 | "source": [ 102 | "## TensorBoard" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": null, 108 | "metadata": { 109 | "collapsed": true 110 | }, 111 | "outputs": [], 112 | "source": [ 113 | "import os.path\n", 114 | "from google.datalab.ml import TensorBoard\n", 115 | "\n", 116 | "bucket_path = 'gs://dost_deeplearning_cifar10' # Change this to your own bucket\n", 117 | "job_name = 'cifar10_train_1499874404' # Change this to your own job name\n", 118 | "train_dir = os.path.join(bucket_path, job_name, 'train')\n", 119 | "\n", 120 | "TensorBoard.start(train_dir)" 121 | ] 122 | }, 123 | { 124 | "cell_type": "markdown", 125 | "metadata": {}, 126 | "source": [ 127 | "## Now what?\n", 128 | "\n", 129 | "Training will finish in around 8-9 hours. __Make sure your training job is running properly before going!__\n", 130 | "\n", 131 | "We will deploy our trained model tomorrow and integrate it with a web app to run predictions on arbitrary images.\n", 132 | "\n", 133 | "### 🌟 Challenge\n", 134 | "\n", 135 | "Ideally, you'd want to evaluate your model every X steps while training to get a log of your accuracy values.\n", 136 | "\n", 137 | "There's an `eval.py` module in the trainer package that's a slightly modified copy of `cifar_eval.py` from the TensorFlow CIFAR-10 tutorial. We're not using it yet though. Try adding this evaluation step and re-running your training job. __Don't stop our previous training job!__\n", 138 | "\n", 139 | "TIP: You can add the evaluation step as a hook in our `MonitoredTrainingSession`. Take a look at `_LoggerHook` for an example." 140 | ] 141 | } 142 | ], 143 | "metadata": { 144 | "kernelspec": { 145 | "display_name": "Python 2", 146 | "language": "python", 147 | "name": "python2" 148 | }, 149 | "language_info": { 150 | "codemirror_mode": { 151 | "name": "ipython", 152 | "version": 2 153 | }, 154 | "file_extension": ".py", 155 | "mimetype": "text/x-python", 156 | "name": "python", 157 | "nbconvert_exporter": "python", 158 | "pygments_lexer": "ipython2", 159 | "version": "2.7.13" 160 | } 161 | }, 162 | "nbformat": 4, 163 | "nbformat_minor": 2 164 | } 165 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/2. ML Engine - Deployment.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": { 6 | "collapsed": true 7 | }, 8 | "source": [ 9 | "# Deploying a trained model in ML Engine\n", 10 | "\n", 11 | "After training our CNN model, we can now deploy it to ML Engine and run our predictions on the cloud!" 12 | ] 13 | }, 14 | { 15 | "cell_type": "markdown", 16 | "metadata": {}, 17 | "source": [ 18 | "## Deploy a version from your trained model" 19 | ] 20 | }, 21 | { 22 | "cell_type": "code", 23 | "execution_count": null, 24 | "metadata": { 25 | "collapsed": true 26 | }, 27 | "outputs": [], 28 | "source": [ 29 | "%%bash\n", 30 | "cd cifar10\n", 31 | "\n", 32 | "MODEL_NAME=\"cifar10\"\n", 33 | "VERSION_NAME=\"v1\"\n", 34 | "JOB_DIR=\"gs://dost_deeplearning_cifar10/cifar10_train_1499931245\" # Change this to your own\n", 35 | "\n", 36 | "gcloud ml-engine models create $MODEL_NAME\n", 37 | "gcloud ml-engine versions create \\\n", 38 | " $VERSION_NAME \\\n", 39 | " --model $MODEL_NAME \\\n", 40 | " --origin $JOB_DIR/model" 41 | ] 42 | }, 43 | { 44 | "cell_type": "markdown", 45 | "metadata": {}, 46 | "source": [ 47 | "## Predict with your deployed model\n", 48 | "\n", 49 | "Let's try predicting with our deployed model! We've prepared a input json instance containing an image of a frog for testing." 50 | ] 51 | }, 52 | { 53 | "cell_type": "code", 54 | "execution_count": null, 55 | "metadata": { 56 | "collapsed": true 57 | }, 58 | "outputs": [], 59 | "source": [ 60 | "%%bash\n", 61 | "cd cifar10\n", 62 | "\n", 63 | "MODEL_NAME=\"cifar10\"\n", 64 | "VERSION_NAME=\"v1\"\n", 65 | "\n", 66 | "gcloud ml-engine predict \\\n", 67 | " --model $MODEL_NAME \\\n", 68 | " --version $VERSION_NAME \\\n", 69 | " --json-instances predict_test.json" 70 | ] 71 | }, 72 | { 73 | "cell_type": "markdown", 74 | "metadata": {}, 75 | "source": [ 76 | "It should output __6__ which is the label index for the frog class." 77 | ] 78 | }, 79 | { 80 | "cell_type": "markdown", 81 | "metadata": {}, 82 | "source": [ 83 | "## Emojify\n", 84 | "\n", 85 | "Let's run a web application that will use our deployed model to \"emojify\" arbitrary images!" 86 | ] 87 | }, 88 | { 89 | "cell_type": "markdown", 90 | "metadata": {}, 91 | "source": [ 92 | "### Install dependencies" 93 | ] 94 | }, 95 | { 96 | "cell_type": "code", 97 | "execution_count": null, 98 | "metadata": { 99 | "collapsed": true 100 | }, 101 | "outputs": [], 102 | "source": [ 103 | "!pip install -r emojify/requirements.txt" 104 | ] 105 | }, 106 | { 107 | "cell_type": "markdown", 108 | "metadata": {}, 109 | "source": [ 110 | "### Run server" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": { 117 | "collapsed": true 118 | }, 119 | "outputs": [], 120 | "source": [ 121 | "import os\n", 122 | "import subprocess\n", 123 | "import IPython\n", 124 | "from google.datalab.utils import pick_unused_port\n", 125 | "\n", 126 | "port = pick_unused_port()\n", 127 | "\n", 128 | "# Config is reckoned from env vars\n", 129 | "env = {\n", 130 | " 'PROJECT_ID': 'dost-deeplearning', # Change this to your project id\n", 131 | " 'MODEL_NAME': 'cifar10',\n", 132 | " 'PORT': str(port),\n", 133 | "}\n", 134 | "\n", 135 | "args = ['python', 'emojify/emojify.py']\n", 136 | "subprocess.Popen(args, env=env)\n", 137 | " \n", 138 | "url = '/_proxy/%d/' % port\n", 139 | "html = 'Running emojify! Click here to access it.' % url\n", 140 | "IPython.display.display_html(html, raw=True)" 141 | ] 142 | } 143 | ], 144 | "metadata": { 145 | "kernelspec": { 146 | "display_name": "Python 2", 147 | "language": "python", 148 | "name": "python2" 149 | }, 150 | "language_info": { 151 | "codemirror_mode": { 152 | "name": "ipython", 153 | "version": 2 154 | }, 155 | "file_extension": ".py", 156 | "mimetype": "text/x-python", 157 | "name": "python", 158 | "nbconvert_exporter": "python", 159 | "pygments_lexer": "ipython2", 160 | "version": "2.7.13" 161 | } 162 | }, 163 | "nbformat": 4, 164 | "nbformat_minor": 2 165 | } 166 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/.gitignore: -------------------------------------------------------------------------------- 1 | *.egg-info 2 | *.pyc 3 | /build 4 | /cifar10_* 5 | /dist 6 | /venv/ 7 | __pycache__/ 8 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/README.md: -------------------------------------------------------------------------------- 1 | # Deploying a CIFAR-10 CNN in ML Engine 2 | 3 | Based from [Tensorflow's CNN tutorial](https://github.com/tensorflow/models/tree/master/tutorials/image/cifar10) and [mltoolbox_datalab_image_classification](https://github.com/googledatalab/pydatalab/tree/master/solutionbox/image_classification/mltoolbox/image/classification). 4 | 5 | 1. Build package wheel 6 | 7 | ```sh 8 | rm -r build dist 9 | python setup.py bdist_wheel --universal 10 | ``` 11 | 12 | 2. Submit training job to ML Engine 13 | 14 | ```sh 15 | gcloud ml-engine jobs submit training \ 16 | $JOB_NAME \ 17 | --region $REGION \ 18 | --job-dir $JOB_DIR \ 19 | --packages $TRAINING_PACKAGE_PATH \ 20 | --module-name trainer.task \ 21 | --config config.yaml 22 | ``` 23 | 24 | 3. Deploy a model version to ML Engine 25 | 26 | ```sh 27 | gcloud ml-engine models create $MODEL_NAME 28 | gcloud ml-engine versions create \ 29 | $VERSION_NAME \ 30 | --model $MODEL_NAME \ 31 | --origin $JOB_DIR/model 32 | ``` 33 | 34 | 4. Predict with deployed model 35 | 36 | ```sh 37 | gcloud ml-engine predict \ 38 | --model $MODEL_NAME \ 39 | --version $VERSION_NAME \ 40 | --json-instances $JSON_INSTANCES 41 | ``` 42 | 43 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/config.yaml: -------------------------------------------------------------------------------- 1 | trainingInput: 2 | scaleTier: CUSTOM 3 | masterType: complex_model_s 4 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/predict_test.json: -------------------------------------------------------------------------------- 1 | {"inputs": {"b64": "/9j/4AAQSkZJRgABAQEASABIAAD/4QkgaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjUuMCI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiLz4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+/+0ALFBob3Rvc2hvcCAzLjAAOEJJTQQlAAAAAAAQ1B2M2Y8AsgTpgAmY7PhCfv/iAmRJQ0NfUFJPRklMRQABAQAAAlRsY21zAhAAAG1udHJSR0IgWFlaIAfQAAgACwATADMAO2Fjc3BBUFBMAAAAAG5vbmUAAAAAAAAAAAAAAAAAAAAAAAD21gABAAAAANMtbGNtcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACmNwcnQAAAD8AAAAMmRlc2MAAAEwAAAAkHd0cHQAAAHAAAAAFGJrcHQAAAHUAAAAFHJUUkMAAAHoAAAADmdUUkMAAAH4AAAADmJUUkMAAAIIAAAADnJYWVoAAAIYAAAAFGdYWVoAAAIsAAAAFGJYWVoAAAJAAAAAFHRleHQAAAAAQ29weXJpZ2h0IDIwMDAgQWRv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"}} 2 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='trainer', 5 | version='0.0.0', 6 | packages=['trainer'], 7 | include_package_data=True, 8 | install_requires=['tensorflow'], 9 | ) 10 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/trainer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thinkingmachines/deeplearningworkshop/7985aff1cea8b6dcd7d6abd3f266a07346ff8c04/codelab_7_ml_engine/cifar10/trainer/__init__.py -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/trainer/eval.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Evaluation for CIFAR-10. 17 | 18 | Accuracy: 19 | cifar10_train.py achieves 83.0% accuracy after 100K steps (256 epochs 20 | of data) as judged by cifar10_eval.py. 21 | 22 | Speed: 23 | On a single Tesla K40, cifar10_train.py processes a single batch of 128 images 24 | in 0.25-0.35 sec (i.e. 350 - 600 images /sec). The model reaches ~86% 25 | accuracy after 100K steps in 8 hours of training time. 26 | 27 | Usage: 28 | Please see the tutorial and website for how to download the CIFAR-10 29 | data set, compile the program and train the model. 30 | 31 | http://tensorflow.org/tutorials/deep_cnn/ 32 | """ 33 | from __future__ import absolute_import, division, print_function 34 | 35 | import math 36 | import time 37 | from datetime import datetime 38 | 39 | import numpy as np 40 | import tensorflow as tf 41 | 42 | from . import model 43 | 44 | FLAGS = tf.app.flags.FLAGS 45 | 46 | tf.app.flags.DEFINE_string('eval_dir', '/tmp/cifar10_eval', 47 | """Directory where to write event logs.""") 48 | tf.app.flags.DEFINE_string('eval_data', 'test', 49 | """Either 'test' or 'train_eval'.""") 50 | tf.app.flags.DEFINE_string('checkpoint_dir', '/tmp/cifar10_train', 51 | """Directory where to read model checkpoints.""") 52 | tf.app.flags.DEFINE_integer('eval_interval_secs', 60 * 5, 53 | """How often to run the eval.""") 54 | tf.app.flags.DEFINE_integer('num_examples', 10000, 55 | """Number of examples to run.""") 56 | tf.app.flags.DEFINE_boolean('run_once', False, 57 | """Whether to run eval only once.""") 58 | 59 | 60 | def eval_once(saver, summary_writer, top_k_op, summary_op): 61 | """Run Eval once. 62 | 63 | Args: 64 | saver: Saver. 65 | summary_writer: Summary writer. 66 | top_k_op: Top K op. 67 | summary_op: Summary op. 68 | """ 69 | with tf.Session() as sess: 70 | ckpt = tf.train.get_checkpoint_state(FLAGS.checkpoint_dir) 71 | if ckpt and ckpt.model_checkpoint_path: 72 | # Restores from checkpoint 73 | saver.restore(sess, ckpt.model_checkpoint_path) 74 | # Assuming model_checkpoint_path looks something like: 75 | # /my-favorite-path/cifar10_train/model.ckpt-0, 76 | # extract global_step from it. 77 | global_step = ckpt.model_checkpoint_path.split('/')[-1].split('-')[-1] 78 | else: 79 | print('No checkpoint file found') 80 | return 81 | 82 | # Start the queue runners. 83 | coord = tf.train.Coordinator() 84 | try: 85 | threads = [] 86 | for qr in tf.get_collection(tf.GraphKeys.QUEUE_RUNNERS): 87 | threads.extend(qr.create_threads(sess, coord=coord, daemon=True, 88 | start=True)) 89 | 90 | num_iter = int(math.ceil(FLAGS.num_examples / FLAGS.batch_size)) 91 | true_count = 0 # Counts the number of correct predictions. 92 | total_sample_count = num_iter * FLAGS.batch_size 93 | step = 0 94 | while step < num_iter and not coord.should_stop(): 95 | predictions = sess.run([top_k_op]) 96 | true_count += np.sum(predictions) 97 | step += 1 98 | 99 | # Compute precision @ 1. 100 | precision = true_count / total_sample_count 101 | print('%s: precision @ 1 = %.3f' % (datetime.now(), precision)) 102 | 103 | summary = tf.Summary() 104 | summary.ParseFromString(sess.run(summary_op)) 105 | summary.value.add(tag='Precision @ 1', simple_value=precision) 106 | summary_writer.add_summary(summary, global_step) 107 | except Exception as e: # pylint: disable=broad-except 108 | coord.request_stop(e) 109 | 110 | coord.request_stop() 111 | coord.join(threads, stop_grace_period_secs=10) 112 | 113 | 114 | def evaluate(): 115 | """Eval CIFAR-10 for a number of steps.""" 116 | with tf.Graph().as_default() as g: 117 | # Get images and labels for CIFAR-10. 118 | eval_data = FLAGS.eval_data == 'test' 119 | images, labels = model.inputs(eval_data=eval_data) 120 | 121 | # Build a Graph that computes the logits predictions from the 122 | # inference model. 123 | logits = model.inference(images) 124 | 125 | # Calculate predictions. 126 | top_k_op = tf.nn.in_top_k(logits, labels, 1) 127 | 128 | # Restore the moving average version of the learned variables for eval. 129 | variable_averages = tf.train.ExponentialMovingAverage( 130 | model.MOVING_AVERAGE_DECAY) 131 | variables_to_restore = variable_averages.variables_to_restore() 132 | saver = tf.train.Saver(variables_to_restore) 133 | 134 | # Build the summary operation based on the TF collection of Summaries. 135 | summary_op = tf.summary.merge_all() 136 | 137 | summary_writer = tf.summary.FileWriter(FLAGS.eval_dir, g) 138 | 139 | while True: 140 | eval_once(saver, summary_writer, top_k_op, summary_op) 141 | if FLAGS.run_once: 142 | break 143 | time.sleep(FLAGS.eval_interval_secs) 144 | 145 | 146 | def main(argv=None): # pylint: disable=unused-argument 147 | model.maybe_download_and_extract() 148 | if tf.gfile.Exists(FLAGS.eval_dir): 149 | tf.gfile.DeleteRecursively(FLAGS.eval_dir) 150 | tf.gfile.MakeDirs(FLAGS.eval_dir) 151 | evaluate() 152 | 153 | 154 | if __name__ == '__main__': 155 | tf.app.run() 156 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/trainer/input.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Routine for decoding the CIFAR-10 binary file format.""" 17 | 18 | from __future__ import absolute_import, division, print_function 19 | 20 | import os 21 | 22 | import tensorflow as tf 23 | 24 | from six.moves import xrange # pylint: disable=redefined-builtin 25 | 26 | # Process images of this size. Note that this differs from the original CIFAR 27 | # image size of 32 x 32. If one alters this number, then the entire model 28 | # architecture will change and any model would need to be retrained. 29 | IMAGE_SIZE = 24 30 | 31 | # Global constants describing the CIFAR-10 data set. 32 | NUM_CLASSES = 10 33 | NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = 50000 34 | NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = 10000 35 | 36 | 37 | def read_cifar10(filename_queue): 38 | """Reads and parses examples from CIFAR10 data files. 39 | 40 | Recommendation: if you want N-way read parallelism, call this function 41 | N times. This will give you N independent Readers reading different 42 | files & positions within those files, which will give better mixing of 43 | examples. 44 | 45 | Args: 46 | filename_queue: A queue of strings with the filenames to read from. 47 | 48 | Returns: 49 | An object representing a single example, with the following fields: 50 | height: number of rows in the result (32) 51 | width: number of columns in the result (32) 52 | depth: number of color channels in the result (3) 53 | key: a scalar string Tensor describing the filename & record number 54 | for this example. 55 | label: an int32 Tensor with the label in the range 0..9. 56 | uint8image: a [height, width, depth] uint8 Tensor with the image data 57 | """ 58 | 59 | class CIFAR10Record(object): 60 | pass 61 | result = CIFAR10Record() 62 | 63 | # Dimensions of the images in the CIFAR-10 dataset. 64 | # See http://www.cs.toronto.edu/~kriz/cifar.html for a description of the 65 | # input format. 66 | label_bytes = 1 # 2 for CIFAR-100 67 | result.height = 32 68 | result.width = 32 69 | result.depth = 3 70 | image_bytes = result.height * result.width * result.depth 71 | # Every record consists of a label followed by the image, with a 72 | # fixed number of bytes for each. 73 | record_bytes = label_bytes + image_bytes 74 | 75 | # Read a record, getting filenames from the filename_queue. No 76 | # header or footer in the CIFAR-10 format, so we leave header_bytes 77 | # and footer_bytes at their default of 0. 78 | reader = tf.FixedLengthRecordReader(record_bytes=record_bytes) 79 | result.key, value = reader.read(filename_queue) 80 | 81 | # Convert from a string to a vector of uint8 that is record_bytes long. 82 | record_bytes = tf.decode_raw(value, tf.uint8) 83 | 84 | # The first bytes represent the label, which we convert from uint8->int32. 85 | result.label = tf.cast( 86 | tf.strided_slice(record_bytes, [0], [label_bytes]), tf.int32) 87 | 88 | # The remaining bytes after the label represent the image, which we reshape 89 | # from [depth * height * width] to [depth, height, width]. 90 | depth_major = tf.reshape( 91 | tf.strided_slice(record_bytes, [label_bytes], 92 | [label_bytes + image_bytes]), 93 | [result.depth, result.height, result.width]) 94 | # Convert from [depth, height, width] to [height, width, depth]. 95 | result.uint8image = tf.transpose(depth_major, [1, 2, 0]) 96 | 97 | return result 98 | 99 | 100 | def _generate_image_and_label_batch(image, label, min_queue_examples, 101 | batch_size, shuffle): 102 | """Construct a queued batch of images and labels. 103 | 104 | Args: 105 | image: 3-D Tensor of [height, width, 3] of type.float32. 106 | label: 1-D Tensor of type.int32 107 | min_queue_examples: int32, minimum number of samples to retain 108 | in the queue that provides of batches of examples. 109 | batch_size: Number of images per batch. 110 | shuffle: boolean indicating whether to use a shuffling queue. 111 | 112 | Returns: 113 | images: Images. 4D tensor of [batch_size, height, width, 3] size. 114 | labels: Labels. 1D tensor of [batch_size] size. 115 | """ 116 | # Create a queue that shuffles the examples, and then 117 | # read 'batch_size' images + labels from the example queue. 118 | num_preprocess_threads = 16 119 | if shuffle: 120 | images, label_batch = tf.train.shuffle_batch( 121 | [image, label], 122 | batch_size=batch_size, 123 | num_threads=num_preprocess_threads, 124 | capacity=min_queue_examples + 3 * batch_size, 125 | min_after_dequeue=min_queue_examples) 126 | else: 127 | images, label_batch = tf.train.batch( 128 | [image, label], 129 | batch_size=batch_size, 130 | num_threads=num_preprocess_threads, 131 | capacity=min_queue_examples + 3 * batch_size) 132 | 133 | # Display the training images in the visualizer. 134 | tf.summary.image('images', images) 135 | 136 | return images, tf.reshape(label_batch, [batch_size]) 137 | 138 | 139 | def distorted_inputs(data_dir, batch_size): 140 | """Construct distorted input for CIFAR training using the Reader ops. 141 | 142 | Args: 143 | data_dir: Path to the CIFAR-10 data directory. 144 | batch_size: Number of images per batch. 145 | 146 | Returns: 147 | images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. 148 | labels: Labels. 1D tensor of [batch_size] size. 149 | """ 150 | filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i) 151 | for i in xrange(1, 6)] 152 | for f in filenames: 153 | if not tf.gfile.Exists(f): 154 | raise ValueError('Failed to find file: ' + f) 155 | 156 | # Create a queue that produces the filenames to read. 157 | filename_queue = tf.train.string_input_producer(filenames) 158 | 159 | # Read examples from files in the filename queue. 160 | read_input = read_cifar10(filename_queue) 161 | reshaped_image = tf.cast(read_input.uint8image, tf.float32) 162 | 163 | height = IMAGE_SIZE 164 | width = IMAGE_SIZE 165 | 166 | # Image processing for training the network. Note the many random 167 | # distortions applied to the image. 168 | 169 | # Randomly crop a [height, width] section of the image. 170 | distorted_image = tf.random_crop(reshaped_image, [height, width, 3]) 171 | 172 | # Randomly flip the image horizontally. 173 | distorted_image = tf.image.random_flip_left_right(distorted_image) 174 | 175 | # Because these operations are not commutative, consider randomizing 176 | # the order their operation. 177 | # NOTE: since per_image_standardization zeros the mean and makes 178 | # the stddev unit, this likely has no effect see tensorflow#1458. 179 | distorted_image = tf.image.random_brightness(distorted_image, 180 | max_delta=63) 181 | distorted_image = tf.image.random_contrast(distorted_image, 182 | lower=0.2, upper=1.8) 183 | 184 | # Subtract off the mean and divide by the variance of the pixels. 185 | float_image = tf.image.per_image_standardization(distorted_image) 186 | 187 | # Set the shapes of tensors. 188 | float_image.set_shape([height, width, 3]) 189 | read_input.label.set_shape([1]) 190 | 191 | # Ensure that the random shuffling has good mixing properties. 192 | min_fraction_of_examples_in_queue = 0.4 193 | min_queue_examples = int(NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN * 194 | min_fraction_of_examples_in_queue) 195 | print ('Filling queue with %d CIFAR images before starting to train. ' 196 | 'This will take a few minutes.' % min_queue_examples) 197 | 198 | # Generate a batch of images and labels by building up a queue of examples. 199 | return _generate_image_and_label_batch(float_image, read_input.label, 200 | min_queue_examples, batch_size, 201 | shuffle=True) 202 | 203 | 204 | def inputs(eval_data, data_dir, batch_size): 205 | """Construct input for CIFAR evaluation using the Reader ops. 206 | 207 | Args: 208 | eval_data: bool, indicating if one should use the train or eval data set. 209 | data_dir: Path to the CIFAR-10 data directory. 210 | batch_size: Number of images per batch. 211 | 212 | Returns: 213 | images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. 214 | labels: Labels. 1D tensor of [batch_size] size. 215 | """ 216 | if not eval_data: 217 | filenames = [os.path.join(data_dir, 'data_batch_%d.bin' % i) 218 | for i in xrange(1, 6)] 219 | num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN 220 | else: 221 | filenames = [os.path.join(data_dir, 'test_batch.bin')] 222 | num_examples_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_EVAL 223 | 224 | for f in filenames: 225 | if not tf.gfile.Exists(f): 226 | raise ValueError('Failed to find file: ' + f) 227 | 228 | # Create a queue that produces the filenames to read. 229 | filename_queue = tf.train.string_input_producer(filenames) 230 | 231 | # Read examples from files in the filename queue. 232 | read_input = read_cifar10(filename_queue) 233 | reshaped_image = tf.cast(read_input.uint8image, tf.float32) 234 | 235 | height = IMAGE_SIZE 236 | width = IMAGE_SIZE 237 | 238 | # Image processing for evaluation. 239 | # Crop the central [height, width] of the image. 240 | resized_image = tf.image.resize_image_with_crop_or_pad(reshaped_image, 241 | height, width) 242 | 243 | # Subtract off the mean and divide by the variance of the pixels. 244 | float_image = tf.image.per_image_standardization(resized_image) 245 | 246 | # Set the shapes of tensors. 247 | float_image.set_shape([height, width, 3]) 248 | read_input.label.set_shape([1]) 249 | 250 | # Ensure that the random shuffling has good mixing properties. 251 | min_fraction_of_examples_in_queue = 0.4 252 | min_queue_examples = int(num_examples_per_epoch * 253 | min_fraction_of_examples_in_queue) 254 | 255 | # Generate a batch of images and labels by building up a queue of examples. 256 | return _generate_image_and_label_batch(float_image, read_input.label, 257 | min_queue_examples, batch_size, 258 | shuffle=False) 259 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/trainer/model.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | """Builds the CIFAR-10 network. 17 | 18 | Summary of available functions: 19 | 20 | # Compute input images and labels for training. If you would like to run 21 | # evaluations, use inputs() instead. 22 | inputs, labels = distorted_inputs() 23 | 24 | # Compute inference on the model inputs to make a prediction. 25 | predictions = inference(inputs) 26 | 27 | # Compute the total loss of the prediction with respect to the labels. 28 | loss = loss(predictions, labels) 29 | 30 | # Create a graph to run one step of training with respect to the loss. 31 | train_op = train(loss, global_step) 32 | """ 33 | # pylint: disable=missing-docstring 34 | from __future__ import absolute_import, division, print_function 35 | 36 | import os 37 | import re 38 | import sys 39 | import tarfile 40 | 41 | import tensorflow as tf 42 | 43 | from six.moves import urllib 44 | 45 | from . import input 46 | 47 | FLAGS = tf.app.flags.FLAGS 48 | 49 | # Basic model parameters. 50 | tf.app.flags.DEFINE_integer('batch-size', 128, 51 | """Number of images to process in a batch.""") 52 | tf.app.flags.DEFINE_string('data-dir', '/tmp/cifar10_data', 53 | """Path to the CIFAR-10 data directory.""") 54 | tf.app.flags.DEFINE_boolean('use-fp16', False, 55 | """Train the model using fp16.""") 56 | 57 | # Global constants describing the CIFAR-10 data set. 58 | IMAGE_SIZE = input.IMAGE_SIZE 59 | NUM_CLASSES = input.NUM_CLASSES 60 | NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN = input.NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN 61 | NUM_EXAMPLES_PER_EPOCH_FOR_EVAL = input.NUM_EXAMPLES_PER_EPOCH_FOR_EVAL 62 | 63 | 64 | # Constants describing the training process. 65 | MOVING_AVERAGE_DECAY = 0.9999 # The decay to use for the moving average. 66 | NUM_EPOCHS_PER_DECAY = 350.0 # Epochs after which learning rate decays. 67 | LEARNING_RATE_DECAY_FACTOR = 0.1 # Learning rate decay factor. 68 | INITIAL_LEARNING_RATE = 0.1 # Initial learning rate. 69 | 70 | # If a model is trained with multiple GPUs, prefix all Op names with tower_name 71 | # to differentiate the operations. Note that this prefix is removed from the 72 | # names of the summaries when visualizing a model. 73 | TOWER_NAME = 'tower' 74 | 75 | DATA_URL = 'http://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz' 76 | 77 | RESHAPE_SIZE = 4 * 24 * 24 78 | 79 | 80 | def _activation_summary(x): 81 | """Helper to create summaries for activations. 82 | 83 | Creates a summary that provides a histogram of activations. 84 | Creates a summary that measures the sparsity of activations. 85 | 86 | Args: 87 | x: Tensor 88 | Returns: 89 | nothing 90 | """ 91 | # Remove 'tower_[0-9]/' from the name in case this is a multi-GPU training 92 | # session. This helps the clarity of presentation on tensorboard. 93 | tensor_name = re.sub('%s_[0-9]*/' % TOWER_NAME, '', x.op.name) 94 | tf.summary.histogram(tensor_name + '/activations', x) 95 | tf.summary.scalar(tensor_name + '/sparsity', tf.nn.zero_fraction(x)) 96 | 97 | 98 | def _variable_on_cpu(name, shape, initializer): 99 | """Helper to create a Variable stored on CPU memory. 100 | 101 | Args: 102 | name: name of the variable 103 | shape: list of ints 104 | initializer: initializer for Variable 105 | 106 | Returns: 107 | Variable Tensor 108 | """ 109 | with tf.device('/cpu:0'): 110 | dtype = tf.float16 if FLAGS.use_fp16 else tf.float32 111 | var = tf.get_variable(name, shape, initializer=initializer, dtype=dtype) 112 | return var 113 | 114 | 115 | def _variable_with_weight_decay(name, shape, stddev, wd): 116 | """Helper to create an initialized Variable with weight decay. 117 | 118 | Note that the Variable is initialized with a truncated normal distribution. 119 | A weight decay is added only if one is specified. 120 | 121 | Args: 122 | name: name of the variable 123 | shape: list of ints 124 | stddev: standard deviation of a truncated Gaussian 125 | wd: add L2Loss weight decay multiplied by this float. If None, weight 126 | decay is not added for this Variable. 127 | 128 | Returns: 129 | Variable Tensor 130 | """ 131 | dtype = tf.float16 if FLAGS.use_fp16 else tf.float32 132 | var = _variable_on_cpu( 133 | name, 134 | shape, 135 | tf.truncated_normal_initializer(stddev=stddev, dtype=dtype)) 136 | if wd is not None: 137 | weight_decay = tf.multiply(tf.nn.l2_loss(var), wd, name='weight_loss') 138 | tf.add_to_collection('losses', weight_decay) 139 | return var 140 | 141 | 142 | def distorted_inputs(): 143 | """Construct distorted input for CIFAR training using the Reader ops. 144 | 145 | Returns: 146 | images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. 147 | labels: Labels. 1D tensor of [batch_size] size. 148 | 149 | Raises: 150 | ValueError: If no data_dir 151 | """ 152 | if not FLAGS.data_dir: 153 | raise ValueError('Please supply a data_dir') 154 | data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin') 155 | images, labels = input.distorted_inputs(data_dir=data_dir, 156 | batch_size=FLAGS.batch_size) 157 | if FLAGS.use_fp16: 158 | images = tf.cast(images, tf.float16) 159 | labels = tf.cast(labels, tf.float16) 160 | return images, labels 161 | 162 | 163 | def inputs(eval_data): 164 | """Construct input for CIFAR evaluation using the Reader ops. 165 | 166 | Args: 167 | eval_data: bool, indicating if one should use the train or eval data set. 168 | 169 | Returns: 170 | images: Images. 4D tensor of [batch_size, IMAGE_SIZE, IMAGE_SIZE, 3] size. 171 | labels: Labels. 1D tensor of [batch_size] size. 172 | 173 | Raises: 174 | ValueError: If no data_dir 175 | """ 176 | if not FLAGS.data_dir: 177 | raise ValueError('Please supply a data_dir') 178 | data_dir = os.path.join(FLAGS.data_dir, 'cifar-10-batches-bin') 179 | images, labels = input.inputs(eval_data=eval_data, data_dir=data_dir, 180 | batch_size=FLAGS.batch_size) 181 | if FLAGS.use_fp16: 182 | images = tf.cast(images, tf.float16) 183 | labels = tf.cast(labels, tf.float16) 184 | return images, labels 185 | 186 | 187 | def inference(images): 188 | """Build the CIFAR-10 model. 189 | 190 | Args: 191 | images: Images returned from distorted_inputs() or inputs(). 192 | 193 | Returns: 194 | Logits. 195 | """ 196 | # We instantiate all variables using tf.get_variable() instead of 197 | # tf.Variable() in order to share variables across multiple GPU training runs. 198 | # If we only ran this model on a single GPU, we could simplify this function 199 | # by replacing all instances of tf.get_variable() with tf.Variable(). 200 | # 201 | # conv1 202 | with tf.variable_scope('conv1') as scope: 203 | kernel = _variable_with_weight_decay('weights', 204 | shape=[5, 5, 3, 64], 205 | stddev=5e-2, 206 | wd=0.0) 207 | conv = tf.nn.conv2d(images, kernel, [1, 1, 1, 1], padding='SAME') 208 | biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.0)) 209 | pre_activation = tf.nn.bias_add(conv, biases) 210 | conv1 = tf.nn.relu(pre_activation, name=scope.name) 211 | _activation_summary(conv1) 212 | 213 | # pool1 214 | pool1 = tf.nn.max_pool(conv1, ksize=[1, 3, 3, 1], strides=[1, 2, 2, 1], 215 | padding='SAME', name='pool1') 216 | # norm1 217 | norm1 = tf.nn.lrn(pool1, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, 218 | name='norm1') 219 | 220 | # conv2 221 | with tf.variable_scope('conv2') as scope: 222 | kernel = _variable_with_weight_decay('weights', 223 | shape=[5, 5, 64, 64], 224 | stddev=5e-2, 225 | wd=0.0) 226 | conv = tf.nn.conv2d(norm1, kernel, [1, 1, 1, 1], padding='SAME') 227 | biases = _variable_on_cpu('biases', [64], tf.constant_initializer(0.1)) 228 | pre_activation = tf.nn.bias_add(conv, biases) 229 | conv2 = tf.nn.relu(pre_activation, name=scope.name) 230 | _activation_summary(conv2) 231 | 232 | # norm2 233 | norm2 = tf.nn.lrn(conv2, 4, bias=1.0, alpha=0.001 / 9.0, beta=0.75, 234 | name='norm2') 235 | # pool2 236 | pool2 = tf.nn.max_pool(norm2, ksize=[1, 3, 3, 1], 237 | strides=[1, 2, 2, 1], padding='SAME', name='pool2') 238 | 239 | # local3 240 | with tf.variable_scope('local3') as scope: 241 | # Move everything into depth so we can perform a single matrix multiply. 242 | reshape = tf.reshape(pool2, [-1, RESHAPE_SIZE]) 243 | dim = reshape.get_shape()[1].value 244 | weights = _variable_with_weight_decay('weights', shape=[dim, 384], 245 | stddev=0.04, wd=0.004) 246 | biases = _variable_on_cpu('biases', [384], tf.constant_initializer(0.1)) 247 | local3 = tf.nn.relu(tf.matmul(reshape, weights) + biases, name=scope.name) 248 | _activation_summary(local3) 249 | 250 | # local4 251 | with tf.variable_scope('local4') as scope: 252 | weights = _variable_with_weight_decay('weights', shape=[384, 192], 253 | stddev=0.04, wd=0.004) 254 | biases = _variable_on_cpu('biases', [192], tf.constant_initializer(0.1)) 255 | local4 = tf.nn.relu(tf.matmul(local3, weights) + biases, name=scope.name) 256 | _activation_summary(local4) 257 | 258 | # linear layer(WX + b), 259 | # We don't apply softmax here because 260 | # tf.nn.sparse_softmax_cross_entropy_with_logits accepts the unscaled logits 261 | # and performs the softmax internally for efficiency. 262 | with tf.variable_scope('softmax_linear') as scope: 263 | weights = _variable_with_weight_decay('weights', [192, NUM_CLASSES], 264 | stddev=1/192.0, wd=0.0) 265 | biases = _variable_on_cpu('biases', [NUM_CLASSES], 266 | tf.constant_initializer(0.0)) 267 | softmax_linear = tf.add(tf.matmul(local4, weights), biases, name=scope.name) 268 | _activation_summary(softmax_linear) 269 | 270 | return softmax_linear 271 | 272 | 273 | def loss(logits, labels): 274 | """Add L2Loss to all the trainable variables. 275 | 276 | Add summary for "Loss" and "Loss/avg". 277 | Args: 278 | logits: Logits from inference(). 279 | labels: Labels from distorted_inputs or inputs(). 1-D tensor 280 | of shape [batch_size] 281 | 282 | Returns: 283 | Loss tensor of type float. 284 | """ 285 | # Calculate the average cross entropy loss across the batch. 286 | labels = tf.cast(labels, tf.int64) 287 | cross_entropy = tf.nn.sparse_softmax_cross_entropy_with_logits( 288 | labels=labels, logits=logits, name='cross_entropy_per_example') 289 | cross_entropy_mean = tf.reduce_mean(cross_entropy, name='cross_entropy') 290 | tf.add_to_collection('losses', cross_entropy_mean) 291 | 292 | # The total loss is defined as the cross entropy loss plus all of the weight 293 | # decay terms (L2 loss). 294 | return tf.add_n(tf.get_collection('losses'), name='total_loss') 295 | 296 | 297 | def _add_loss_summaries(total_loss): 298 | """Add summaries for losses in CIFAR-10 model. 299 | 300 | Generates moving average for all losses and associated summaries for 301 | visualizing the performance of the network. 302 | 303 | Args: 304 | total_loss: Total loss from loss(). 305 | Returns: 306 | loss_averages_op: op for generating moving averages of losses. 307 | """ 308 | # Compute the moving average of all individual losses and the total loss. 309 | loss_averages = tf.train.ExponentialMovingAverage(0.9, name='avg') 310 | losses = tf.get_collection('losses') 311 | loss_averages_op = loss_averages.apply(losses + [total_loss]) 312 | 313 | # Attach a scalar summary to all individual losses and the total loss; do the 314 | # same for the averaged version of the losses. 315 | for l in losses + [total_loss]: 316 | # Name each loss as '(raw)' and name the moving average version of the loss 317 | # as the original loss name. 318 | tf.summary.scalar(l.op.name + '_raw', l) 319 | tf.summary.scalar(l.op.name, loss_averages.average(l)) 320 | 321 | return loss_averages_op 322 | 323 | 324 | def train(total_loss, global_step): 325 | """Train CIFAR-10 model. 326 | 327 | Create an optimizer and apply to all trainable variables. Add moving 328 | average for all trainable variables. 329 | 330 | Args: 331 | total_loss: Total loss from loss(). 332 | global_step: Integer Variable counting the number of training steps 333 | processed. 334 | Returns: 335 | train_op: op for training. 336 | """ 337 | # Variables that affect learning rate. 338 | num_batches_per_epoch = NUM_EXAMPLES_PER_EPOCH_FOR_TRAIN / FLAGS.batch_size 339 | decay_steps = int(num_batches_per_epoch * NUM_EPOCHS_PER_DECAY) 340 | 341 | # Decay the learning rate exponentially based on the number of steps. 342 | lr = tf.train.exponential_decay(INITIAL_LEARNING_RATE, 343 | global_step, 344 | decay_steps, 345 | LEARNING_RATE_DECAY_FACTOR, 346 | staircase=True) 347 | tf.summary.scalar('learning_rate', lr) 348 | 349 | # Generate moving averages of all losses and associated summaries. 350 | loss_averages_op = _add_loss_summaries(total_loss) 351 | 352 | # Compute gradients. 353 | with tf.control_dependencies([loss_averages_op]): 354 | opt = tf.train.GradientDescentOptimizer(lr) 355 | grads = opt.compute_gradients(total_loss) 356 | 357 | # Apply gradients. 358 | apply_gradient_op = opt.apply_gradients(grads, global_step=global_step) 359 | 360 | # Add histograms for trainable variables. 361 | for var in tf.trainable_variables(): 362 | tf.summary.histogram(var.op.name, var) 363 | 364 | # Add histograms for gradients. 365 | for grad, var in grads: 366 | if grad is not None: 367 | tf.summary.histogram(var.op.name + '/gradients', grad) 368 | 369 | # Track the moving averages of all trainable variables. 370 | variable_averages = tf.train.ExponentialMovingAverage( 371 | MOVING_AVERAGE_DECAY, global_step) 372 | variables_averages_op = variable_averages.apply(tf.trainable_variables()) 373 | 374 | with tf.control_dependencies([apply_gradient_op, variables_averages_op]): 375 | train_op = tf.no_op(name='train') 376 | 377 | return train_op 378 | 379 | 380 | def maybe_download_and_extract(): 381 | """Download and extract the tarball from Alex's website.""" 382 | dest_directory = FLAGS.data_dir 383 | if not os.path.exists(dest_directory): 384 | os.makedirs(dest_directory) 385 | filename = DATA_URL.split('/')[-1] 386 | filepath = os.path.join(dest_directory, filename) 387 | if not os.path.exists(filepath): 388 | def _progress(count, block_size, total_size): 389 | sys.stdout.write('\r>> Downloading %s %.1f%%' % (filename, 390 | float(count * block_size) / float(total_size) * 100.0)) 391 | sys.stdout.flush() 392 | filepath, _ = urllib.request.urlretrieve(DATA_URL, filepath, _progress) 393 | print() 394 | statinfo = os.stat(filepath) 395 | print('Successfully downloaded', filename, statinfo.st_size, 'bytes.') 396 | extracted_dir_path = os.path.join(dest_directory, 'cifar-10-batches-bin') 397 | if not os.path.exists(extracted_dir_path): 398 | tarfile.open(filepath, 'r:gz').extractall(dest_directory) 399 | 400 | 401 | def decode_and_resize(image_bytes): 402 | image = tf.image.decode_jpeg(image_bytes) 403 | image = tf.image.resize_image_with_crop_or_pad(image, IMAGE_SIZE, IMAGE_SIZE) 404 | image = tf.image.per_image_standardization(image) 405 | image = tf.reshape(image, [IMAGE_SIZE, IMAGE_SIZE, 3]) 406 | return image 407 | 408 | 409 | def build_prediction_graph(): 410 | image_bytes = tf.placeholder(tf.string, shape=[None]) 411 | 412 | image = tf.map_fn(decode_and_resize, image_bytes, back_prop=False, 413 | dtype=tf.float32) 414 | 415 | # Build a Graph that computes the logits predictions from the 416 | # inference model. 417 | logits = inference(image) 418 | 419 | # Calculate predictions. 420 | labels = tf.argmax(logits, 1) 421 | 422 | inputs = { 423 | 'image_bytes': image_bytes, 424 | } 425 | outputs = { 426 | 'label_index': labels, 427 | } 428 | return inputs, outputs 429 | 430 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/cifar10/trainer/task.py: -------------------------------------------------------------------------------- 1 | # Copyright 2015 The TensorFlow Authors. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | # ============================================================================== 15 | 16 | from __future__ import absolute_import, division, print_function 17 | 18 | import json 19 | import os 20 | import time 21 | from datetime import datetime 22 | 23 | import tensorflow as tf 24 | from tensorflow.python.saved_model import builder as saved_model_builder 25 | from tensorflow.python.saved_model import signature_def_utils, tag_constants 26 | 27 | from . import model 28 | 29 | FLAGS = tf.app.flags.FLAGS 30 | 31 | tf.app.flags.DEFINE_string('job-dir', '/tmp/cifar10', 32 | """A Google Cloud Storage path in which to store""" 33 | """training outputs and other data needed for""" 34 | """training.""") 35 | tf.app.flags.DEFINE_integer('log-frequency', 10, 36 | """How often to log results to the console.""") 37 | tf.app.flags.DEFINE_integer('max-steps', 100000, 38 | """Number of batches to run.""") 39 | 40 | 41 | def start_server(cluster, task): 42 | return tf.train.Server(cluster, job_name=task.type, task_index=task.index) 43 | 44 | 45 | def train(cluster=None, task=None): 46 | train_dir = os.path.join(FLAGS.job_dir, 'train') 47 | model_dir = os.path.join(FLAGS.job_dir, 'model') 48 | 49 | if cluster: 50 | server = start_server(cluster, task) 51 | master = server.target 52 | device_fn = tf.train.replica_device_setter(cluster=cluster) 53 | config = tf.ConfigProto(log_device_placement=False) 54 | else: 55 | master = '' 56 | device_fn = '' 57 | config = None 58 | 59 | with tf.Graph().as_default(): 60 | global_step = tf.contrib.framework.get_or_create_global_step() 61 | 62 | # Get images and labels for CIFAR-10. 63 | # Force input pipeline to CPU:0 to avoid operations sometimes ending up on 64 | # GPU and resulting in a slow down. 65 | with tf.device('/cpu:0'): 66 | images, labels = model.distorted_inputs() 67 | 68 | with tf.device(device_fn): 69 | # Build a Graph that computes the logits predictions from the 70 | # inference model. 71 | logits = model.inference(images) 72 | 73 | # Calculate loss. 74 | loss = model.loss(logits, labels) 75 | 76 | # Build a Graph that trains the model with one batch of examples and 77 | # updates the model parameters. 78 | train_op = model.train(loss, global_step) 79 | 80 | class _LoggerHook(tf.train.SessionRunHook): 81 | """Logs loss and runtime.""" 82 | 83 | def begin(self): 84 | self._step = -1 85 | self._start_time = time.time() 86 | 87 | def before_run(self, run_context): 88 | self._step += 1 89 | return tf.train.SessionRunArgs(loss) # Asks for loss value. 90 | 91 | def after_run(self, run_context, run_values): 92 | if self._step % FLAGS.log_frequency == 0: 93 | current_time = time.time() 94 | duration = current_time - self._start_time 95 | self._start_time = current_time 96 | 97 | loss_value = run_values.results 98 | examples_per_sec = FLAGS.log_frequency * FLAGS.batch_size / duration 99 | sec_per_batch = float(duration / FLAGS.log_frequency) 100 | 101 | format_str = ('%s: step %d, loss = %.2f (%.1f examples/sec; %.3f ' 102 | 'sec/batch)') 103 | print (format_str % (datetime.now(), self._step, loss_value, 104 | examples_per_sec, sec_per_batch)) 105 | 106 | hooks = [tf.train.StopAtStepHook(last_step=FLAGS.max_steps), 107 | tf.train.NanTensorHook(loss), _LoggerHook()] 108 | 109 | with tf.train.MonitoredTrainingSession( 110 | master=master, 111 | is_chief=task.type == 'master', 112 | checkpoint_dir=train_dir, 113 | hooks=hooks, 114 | config=config, 115 | ) as mon_sess: 116 | while not mon_sess.should_stop(): 117 | mon_sess.run(train_op) 118 | # FIXME: Add eval 119 | 120 | export(train_dir, model_dir) 121 | 122 | 123 | def export(train_dir, model_dir): 124 | with tf.Session(graph=tf.Graph()) as sess: 125 | inputs, outputs = model.build_prediction_graph() 126 | signature_def_map = { 127 | 'serving_default': signature_def_utils.predict_signature_def( 128 | inputs, outputs), 129 | } 130 | 131 | # Restore the moving average version of the learned variables for eval. 132 | variable_averages = tf.train.ExponentialMovingAverage( 133 | model.MOVING_AVERAGE_DECAY) 134 | variables_to_restore = variable_averages.variables_to_restore() 135 | saver = tf.train.Saver(variables_to_restore) 136 | 137 | ckpt = tf.train.get_checkpoint_state(train_dir) 138 | if ckpt and ckpt.model_checkpoint_path: 139 | # Restores from checkpoint 140 | saver.restore(sess, ckpt.model_checkpoint_path) 141 | else: 142 | print('No checkpoint file found') 143 | return 144 | 145 | builder = saved_model_builder.SavedModelBuilder(model_dir) 146 | builder.add_meta_graph_and_variables(sess, [tag_constants.SERVING], 147 | signature_def_map=signature_def_map) 148 | builder.save(False) 149 | 150 | 151 | def main(argv=None): 152 | model.maybe_download_and_extract() 153 | 154 | tf_config = os.environ.get('TF_CONFIG') 155 | 156 | # If TF_CONFIG is not available run local 157 | if not tf_config: 158 | return train() 159 | 160 | tf_config_json = json.loads(tf_config) 161 | 162 | cluster_data = tf_config_json.get('cluster') 163 | cluster = tf.train.ClusterSpec(cluster_data) if cluster_data else None 164 | 165 | task_data = tf_config_json.get('task', {}) 166 | task = type('TaskSpec', (object,), task_data) 167 | 168 | if task.type == 'ps': 169 | server = start_server(cluster, task) 170 | return server.join() 171 | elif task.type in ['master', 'worker']: 172 | return train(cluster, task) 173 | else: 174 | raise ValueError('Invalid task_type %s' % (task.type,)) 175 | 176 | 177 | if __name__ == '__main__': 178 | tf.app.run() 179 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /venv/ 3 | __pycache__/ 4 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: venv requirements.txt 3 | venv/bin/pip-sync 4 | venv: 5 | python3 -m venv venv 6 | venv/bin/pip install pip-tools 7 | requirements.txt: requirements.in 8 | venv/bin/pip-compile requirements.in > requirements.txt -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/README.md: -------------------------------------------------------------------------------- 1 | # Emojify 2 | 3 | A web service that tries to determine the appropriate emoji for a given image. It uses a CNN model trained with the CIFAR-10 dataset deployed in ML Engine to run predictions. 4 | 5 | ## Setup 6 | 7 | ```sh 8 | make 9 | source venv/bin/activate 10 | export PROJECT_ID= 11 | export MODEL_NAME= 12 | ``` 13 | 14 | ## Run 15 | 16 | ```sh 17 | # Dev 18 | python emojify.py 19 | # Prod 20 | gunicorn emojify -b 0.0.0.0:8000 21 | ``` 22 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/emojify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import os.path 6 | from base64 import b64encode 7 | from io import BytesIO 8 | 9 | from werkzeug.utils import secure_filename 10 | 11 | from flask import Flask, abort, render_template, request, url_for 12 | from googleapiclient import discovery 13 | from oauth2client.client import GoogleCredentials 14 | from PIL import Image 15 | 16 | app = application = Flask(__name__) 17 | 18 | 19 | LABELS = '''✈️ 20 | 🚗 21 | 🐦 22 | 🐱 23 | 🦄 24 | 🐶 25 | 🐸 26 | 🐴 27 | 🛳 28 | 🚚 29 | '''.split() 30 | 31 | 32 | def get_b64_image_bytes(file): 33 | im = Image.open(file) 34 | crop_size = min(im.size) 35 | crop_width = (im.width - crop_size) / 2 36 | crop_height = (im.height - crop_size) / 2 37 | crop_box = (crop_width, crop_height, crop_width + crop_size, crop_height + 38 | crop_size) 39 | im = im.crop(crop_box) 40 | im = im.resize((32, 32), resample=Image.LANCZOS) 41 | b = BytesIO() 42 | im.save(b, 'JPEG') 43 | image_bytes = b.getvalue() 44 | return b64encode(image_bytes).decode() 45 | 46 | 47 | def predict(instance): 48 | credentials = GoogleCredentials.get_application_default() 49 | service = discovery.build('ml', 'v1', credentials=credentials) 50 | project_id = os.getenv('PROJECT_ID') 51 | model_name = os.getenv('MODEL_NAME') 52 | name = 'projects/{}/models/{}'.format(project_id, model_name) 53 | response = service.projects().predict( 54 | name=name, 55 | body={'instances': instance} 56 | ).execute() 57 | if 'error' in response: 58 | abort(500, response['error']) 59 | return response['predictions'][0] 60 | 61 | 62 | @app.route('/', methods=['GET', 'POST']) 63 | def index(): 64 | context = {} 65 | if request.method == 'POST': 66 | image_file = request.files['image'] 67 | if not image_file.mimetype.startswith('image'): 68 | abort(400) 69 | image_filename = secure_filename(image_file.filename) 70 | image_file.save(os.path.join('static', 'uploads', image_filename)) 71 | image_url = url_for('static', 72 | filename=os.path.join('uploads', image_filename)) 73 | b64_image_bytes = get_b64_image_bytes(image_file.stream) 74 | instance = {'inputs': {'b64': b64_image_bytes}} 75 | label_index = predict(instance).get('outputs') 76 | label = LABELS[label_index] 77 | context.update({ 78 | 'image_url': image_url, 79 | 'label': label or 'IDK', 80 | }) 81 | return render_template('index.html', **context) 82 | 83 | 84 | if __name__ == '__main__': 85 | app.run(host='0.0.0.0', port=int(os.getenv('PORT'))) 86 | 87 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/requirements.in: -------------------------------------------------------------------------------- 1 | flask 2 | google-api-python-client 3 | gunicorn 4 | oauth2client 5 | pillow 6 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/requirements.txt: -------------------------------------------------------------------------------- 1 | # 2 | # This file is autogenerated by pip-compile 3 | # To update, run: 4 | # 5 | # pip-compile --output-file requirements.txt requirements.in 6 | # 7 | click==6.7 # via flask 8 | flask==0.12.3 9 | google-api-python-client==1.6.2 10 | gunicorn==19.7.1 11 | httplib2==0.10.3 # via google-api-python-client, oauth2client 12 | itsdangerous==0.24 # via flask 13 | jinja2==2.9.6 # via flask 14 | markupsafe==1.0 # via jinja2 15 | oauth2client==4.1.2 16 | olefile==0.44 # via pillow 17 | pillow==4.2.1 18 | pyasn1-modules==0.0.9 # via oauth2client 19 | pyasn1==0.2.3 # via oauth2client, pyasn1-modules, rsa 20 | rsa==3.4.2 # via oauth2client 21 | six==1.10.0 # via google-api-python-client, oauth2client 22 | uritemplate==3.0.0 # via google-api-python-client 23 | werkzeug==0.12.2 # via flask 24 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/static/css/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | min-height: 100%; 3 | background: #24C6DC; /* fallback for old browsers */ 4 | background: -webkit-linear-gradient(to bottom, #514A9D, #24C6DC); /* Chrome 10-25, Safari 5.1-6 */ 5 | background: linear-gradient(to bottom, #514A9D, #24C6DC); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */ 6 | } 7 | html, body { 8 | font: 20px/1.5 sans-serif; 9 | } 10 | body { 11 | min-height: 100vh; 12 | } 13 | body, input, button { 14 | font: inherit; 15 | } 16 | body { 17 | margin: 0; 18 | text-align: center; 19 | display: flex; 20 | flex-direction: column; 21 | justify-content: center; 22 | } 23 | h1 { 24 | font-size: 6rem; 25 | margin: 0; 26 | } 27 | main { 28 | width: 100%; 29 | max-width: 20rem; 30 | margin: 0 auto; 31 | padding: 3rem 0; 32 | } 33 | p { 34 | margin: 0 0 1.5rem; 35 | } 36 | p:last-child { 37 | margin-bottom: 0; 38 | } 39 | input[type=file] { 40 | height: 3rem; 41 | overflow: hidden; 42 | } 43 | input[type=file]::-webkit-file-upload-button { 44 | display: block; 45 | background: white; 46 | border: 0; 47 | border-radius: 1.5rem; 48 | padding: 0 1.5rem; 49 | line-height: 3; 50 | text-transform: uppercase; 51 | letter-spacing: 0.1rem; 52 | } 53 | button { 54 | background: gold; 55 | border: 0; 56 | border-radius: 1.5rem; 57 | padding: 0 1.5rem; 58 | line-height: 3; 59 | text-transform: uppercase; 60 | letter-spacing: 0.1rem; 61 | } 62 | input[type=file], button, img { 63 | box-sizing: border-box; 64 | width: 100%; 65 | } 66 | .label { 67 | font-size: 6rem; 68 | text-transform: uppercase; 69 | letter-spacing: 0.1rem; 70 | } 71 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/static/uploads/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /codelab_7_ml_engine/emojify/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Emojify 7 | 8 | 9 |
10 | {% if label %} 11 |

{{ label }}

12 | {% else %} 13 |

🔮

14 | {% endif %} 15 |
16 | {% if image_url %} 17 |

18 | {% endif %} 19 |

20 |

21 |
22 |
23 | --------------------------------------------------------------------------------