{ "cells": [ { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "W_tvPdyfA-BL" }, "source": [ "##### Copyright 2018 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "cellView": "form", "colab": {}, "colab_type": "code", "id": "0O_LFhwSBCjm" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "PWUmcKKjtwXL" }, "source": [ "# Hub with Keras\n", "\n", "\u003ctable class=\"tfo-notebook-buttons\" align=\"left\"\u003e\n", " \u003ctd\u003e\n", " \u003ca target=\"_blank\" href=\"https://www.tensorflow.org/tutorials/images/hub_with_keras\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" /\u003eView on TensorFlow.org\u003c/a\u003e\n", " \u003c/td\u003e\n", " \u003ctd\u003e\n", " \u003ca target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/docs/blob/master/site/en/tutorials/images/hub_with_keras.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" /\u003eRun in Google Colab\u003c/a\u003e\n", " \u003c/td\u003e\n", " \u003ctd\u003e\n", " \u003ca target=\"_blank\" href=\"https://github.com/tensorflow/docs/blob/master/site/en/tutorials/images/hub_with_keras.ipynb\"\u003e\u003cimg src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" /\u003eView source on GitHub\u003c/a\u003e\n", " \u003c/td\u003e\n", "\u003c/table\u003e" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "crU-iluJIEzw" }, "source": [ "[TensorFlow Hub](http://tensorflow.org/hub) is a way to share pretrained model components. See the [TensorFlow Module Hub](https://tfhub.dev/) for a searchable listing of pre-trained models.\n", "\n", "This tutorial demonstrates:\n", "\n", "1. How to use TensorFlow Hub with `tf.keras`.\n", "1. How to do image classification using TensorFlow Hub.\n", "1. How to do simple transfer learning." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "CKFUvuEho9Th" }, "source": [ "## Setup" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "7RVsYZLEpEWs" }, "source": [ "### Imports" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "IuuiFCJKL33d" }, "outputs": [], "source": [ "!pip install tensorflow_hub" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "OGNpmn43C0O6" }, "outputs": [], "source": [ "from __future__ import absolute_import, division, print_function\n", "\n", "import matplotlib.pylab as plt\n", "\n", "import tensorflow as tf\n", "import tensorflow_hub as hub\n", "\n", "from tensorflow.keras import layers\n", "\n", "tf.VERSION" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "ApBqGprKnyPm" }, "source": [ "### Dataset\n", "\n", " For this example we'll use the TensorFlow flowers dataset: " ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "yU3xd71Zx-dz" }, "outputs": [], "source": [ "data_root = tf.keras.utils.get_file(\n", " 'flower_photos','https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz',\n", " untar=True)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "DfjX-Qdan4hL" }, "source": [ "The simplest way to load this data into our model is using `tf.keras.preprocessing.image.ImageDataGenerator`:\n", "\n", "All of TensorFlow Hub's image modules expect float inputs in the `[0, 1]` range. Use the `ImageDataGenerator`'s `rescale` parameter to achieve this. \n", "\n", "The image size will be handles later." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "Dy8PqP5zunEx" }, "outputs": [], "source": [ "image_generator = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1/255)\n", "image_data = image_generator.flow_from_directory(str(data_root))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "tbzxwOEqoEDf" }, "source": [ "The resulting object is an iterator that returns `image_batch, label_batch` pairs." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "7pQ57Og5vNrf" }, "outputs": [], "source": [ "for image_batch,label_batch in image_data:\n", " print(\"Image batch shape: \", image_batch.shape)\n", " print(\"Labe batch shape: \", label_batch.shape)\n", " break" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "s4YuF5HvpM1W" }, "source": [ "## An ImageNet classifier" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "xEY_Ow5loN6q" }, "source": [ "### Download the classifier\n", "\n", "Use `hub.module` to load a mobilenet, and `tf.keras.layers.Lambda` to wrap it up as a keras layer.\n", "\n", "Any [image classifier url from tfhub.dev](https://tfhub.dev/s?module-type=image-classification) will work here." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "cellView": "form", "colab": {}, "colab_type": "code", "id": "feiXojVXAbI9" }, "outputs": [], "source": [ "classifier_url = \"https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/classification/2\" #@param {type:\"string\"}" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "y_6bGjoPtzau" }, "outputs": [], "source": [ "def classifier(x):\n", " classifier_module = hub.Module(classifier_url)\n", " return classifier_module(x)\n", " \n", "IMAGE_SIZE = hub.get_expected_image_size(hub.Module(classifier_url))" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "6qRth0vpCQ-N" }, "outputs": [], "source": [ "classifier_layer = layers.Lambda(classifier, input_shape = IMAGE_SIZE+[3])\n", "classifier_model = tf.keras.Sequential([classifier_layer])\n", "classifier_model.summary()" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "NJexLFYK_kjK" }, "source": [ "Rebuild the data generator, with the output size set to match what's expected by the module." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "CqlYCG_S_de3" }, "outputs": [], "source": [ "image_data = image_generator.flow_from_directory(str(data_root), target_size=IMAGE_SIZE)\n", "for image_batch,label_batch in image_data:\n", " print(\"Image batch shape: \", image_batch.shape)\n", " print(\"Labe batch shape: \", label_batch.shape)\n", " break" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "nQHo3yPb5W5H" }, "source": [ "When using Keras, TFHub modules need to be manually initialized." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "sCp6eFKH5JJ7" }, "outputs": [], "source": [ "import tensorflow.keras.backend as K\n", "sess = K.get_session()\n", "init = tf.global_variables_initializer()\n", "\n", "sess.run(init)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "pwZXaoV0uXp2" }, "source": [ "### Run it on a single image" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "TQItP1i55-di" }, "source": [ "Download a single image to try the model on." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "w5wDjXNjuXGD" }, "outputs": [], "source": [ "import numpy as np\n", "import PIL.Image as Image\n", "\n", "grace_hopper = tf.keras.utils.get_file('image.jpg','https://storage.googleapis.com/download.tensorflow.org/example_images/grace_hopper.jpg')\n", "grace_hopper = Image.open(grace_hopper).resize(IMAGE_SIZE)\n", "grace_hopper " ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "BEmmBnGbLxPp" }, "outputs": [], "source": [ "grace_hopper = np.array(grace_hopper)/255.0\n", "grace_hopper.shape" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "0Ic8OEEo2b73" }, "source": [ "Add a batch dimension, and pass the image to the model." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "EMquyn29v8q3" }, "outputs": [], "source": [ "result = classifier_model.predict(grace_hopper[np.newaxis, ...])\n", "result.shape\n" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "NKzjqENF6jDF" }, "source": [ "The result is a 1001 element vector of logits, rating the probability of each class for the image.\n", "\n", "So the top class ID can be found with argmax:" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "rgXb44vt6goJ" }, "outputs": [], "source": [ "predicted_class = np.argmax(result[0], axis=-1)\n", "predicted_class" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "YrxLMajMoxkf" }, "source": [ "### Decode the predictions\n", "\n", "We have the predicted class ID, \n", "Fetch the `ImageNet` labels, and decode the predictions" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "ij6SrDxcxzry" }, "outputs": [], "source": [ "labels_path = tf.keras.utils.get_file('ImageNetLabels.txt','https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')\n", "imagenet_labels = np.array(open(labels_path).read().splitlines())" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "uzziRK3Z2VQo" }, "outputs": [], "source": [ "plt.imshow(grace_hopper)\n", "plt.axis('off')\n", "predicted_class_name = imagenet_labels[predicted_class]\n", "_ = plt.title(\"Prediction: \" + predicted_class_name)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "hCVnyAacog0P" }, "source": [ "### Run it on a batch of images" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "eoZcigLU4mMz" }, "source": [ "Now run the classifier on the image batch." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "7YRLzmPV3BcE" }, "outputs": [], "source": [ "result_batch = classifier_model.predict(image_batch)" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "fIQkSI_Vt2Ne" }, "outputs": [], "source": [ "labels_batch = imagenet_labels[np.argmax(result_batch, axis=-1)]\n", "labels_batch" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "WqORYNSPv0-W" }, "source": [ "Now check how these predictions line up with the images:" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "zqZPX67Zv0E0" }, "outputs": [], "source": [ "plt.figure(figsize=(10,9))\n", "for n in range(30):\n", " plt.subplot(6,5,n+1)\n", " plt.imshow(image_batch[n])\n", " plt.title(labels_batch[n])\n", " plt.axis('off')\n", "_ = plt.suptitle(\"ImageNet predictions\")" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "n_oFmEk_rT4M" }, "source": [ "See the `LICENSE.txt` file for image attributions.\n", "\n", "The results are far from perfect, but reasonable considering that these are not the classes the model was trained for (except \"daisy\")." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "amfzqn1Oo7Om" }, "source": [ "## Simple transfer learning" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "K-nIpVJ94xrw" }, "source": [ "Using tfhub it is simple to retrain the top layer of the model to recognize the classes in our dataset." ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "JzV457OXreQP" }, "source": [ "### Download the headless model\n", "\n", "TensorFlow Hub also distributes models without the top classification layer. These can be used to easily do transfer learning.\n", "\n", "Any [image feature vector url from tfhub.dev](https://tfhub.dev/s?module-type=image-feature-vector) will work here." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "cellView": "form", "colab": {}, "colab_type": "code", "id": "4bw8Jf94DSnP" }, "outputs": [], "source": [ "feature_extractor_url = \"https://tfhub.dev/google/imagenet/mobilenet_v2_100_224/feature_vector/2\" #@param {type:\"string\"}" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "sgwmHugQF-PD" }, "source": [ "Create the module, and check the expected image size:" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "5wB030nezBwI" }, "outputs": [], "source": [ "def feature_extractor(x):\n", " feature_extractor_module = hub.Module(feature_extractor_url)\n", " return feature_extractor_module(x)\n", "\n", "IMAGE_SIZE = hub.get_expected_image_size(hub.Module(feature_extractor_url))" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "NcCQXruuGDeT" }, "source": [ "Ensure the data generator is generating images of the expected size:" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "QxdX-YanF6K2" }, "outputs": [], "source": [ "image_data = image_generator.flow_from_directory(str(data_root), target_size=IMAGE_SIZE)\n", "for image_batch,label_batch in image_data:\n", " print(\"Image batch shape: \", image_batch.shape)\n", " print(\"Labe batch shape: \", label_batch.shape)\n", " break" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "CwkdntqxGL4a" }, "source": [ "Wrap the module in a keras layer." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "KRO5SWRaFXvY" }, "outputs": [], "source": [ "features_extractor_layer = layers.Lambda(feature_extractor, input_shape=IMAGE_SIZE+[3])" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "CtFmF7A5E4tk" }, "source": [ "Freeze the variables in the feature extractor layer, so that the training only modifies the new classifier layer." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "Jg5ar6rcE4H-" }, "outputs": [], "source": [ "features_extractor_layer.trainable = False" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "RPVeouTksO9q" }, "source": [ "### Attach a classification head\n", "\n", "Now wrap the hub layer in a `tf.keras.Sequential` model, and add a new classification layer." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "mGcY27fY1q3Q" }, "outputs": [], "source": [ "model = tf.keras.Sequential([\n", " features_extractor_layer,\n", " layers.Dense(image_data.num_classes, activation='softmax')\n", "])\n", "model.summary()" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "iDDHRyJlK3Yg" }, "source": [ "Initialize the TFHub module." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "je_-_Sv0JdT8" }, "outputs": [], "source": [ "init = tf.global_variables_initializer()\n", "sess.run(init)" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "uppwC81ksYBx" }, "source": [ "Test run a single batch, to see that the result comes back with the expected shape." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "BgYI5G3ZFPWS" }, "outputs": [], "source": [ "result = model.predict(image_batch)\n", "result.shape" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "OHbXQqIquFxQ" }, "source": [ "### Train the model\n", "\n", "Use compile to configure the training process:" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "3n0Wb9ylKd8R" }, "outputs": [], "source": [ "model.compile(\n", " optimizer=tf.train.AdamOptimizer(), \n", " loss='categorical_crossentropy',\n", " metrics=['accuracy'])" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "58-BLV7dupJA" }, "source": [ "Now use the `.fit` method to train the model.\n", "\n", "To keep this example short train just a single epoch. To visualize the training progress during that epoch, use a custom callback to log the loss and accuract of each batch. " ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "Ju7kgDinF-qp" }, "outputs": [], "source": [ "class CollectBatchStats(tf.keras.callbacks.Callback):\n", " def __init__(self):\n", " self.batch_losses = []\n", " self.batch_acc = []\n", " \n", " def on_batch_end(self, batch, logs=None):\n", " self.batch_losses.append(logs['loss'])\n", " self.batch_acc.append(logs['acc'])" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "EyMDJxt2HdHr" }, "outputs": [], "source": [ "steps_per_epoch = image_data.samples//image_data.batch_size\n", "batch_stats = CollectBatchStats()\n", "model.fit((item for item in image_data), epochs=1, \n", " steps_per_epoch=steps_per_epoch,\n", " callbacks = [batch_stats])" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "Kd0N272B9Q0b" }, "source": [ "Now after, even just a few training iterations, we can already see that the model is making progress on the task." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "A5RfS1QIIP-P" }, "outputs": [], "source": [ "plt.figure()\n", "plt.ylabel(\"Loss\")\n", "plt.xlabel(\"Training Steps\")\n", "plt.ylim([0,2])\n", "plt.plot(batch_stats.batch_losses)\n", "\n", "plt.figure()\n", "plt.ylabel(\"Accuracy\")\n", "plt.xlabel(\"Training Steps\")\n", "plt.ylim([0,1])\n", "plt.plot(batch_stats.batch_acc)\n" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "kb__ZN8uFn-D" }, "source": [ "### Check the predictions\n", "\n", "To redo the plot from before, first get the ordered list of class names:" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "nrkw6XDqELa8" }, "outputs": [], "source": [ "label_names = sorted(image_data.class_indices.items(), key=lambda pair:pair[1])\n", "label_names = np.array([key.title() for key, value in label_names])\n", "label_names" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "4Olg6MsNGJTL" }, "source": [ "Run the image batch through the model and comvert the indices to class names." ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "Oq5Y06-d9xI_" }, "outputs": [], "source": [ "result_batch = model.predict(image_batch)\n", "\n", "labels_batch = label_names[np.argmax(result_batch, axis=-1)]\n", "labels_batch" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "CkGbZxl9GZs-" }, "source": [ "Plot the result" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "wC_AYRJU9NQe" }, "outputs": [], "source": [ "plt.figure(figsize=(10,9))\n", "for n in range(30):\n", " plt.subplot(6,5,n+1)\n", " plt.imshow(image_batch[n])\n", " plt.title(labels_batch[n])\n", " plt.axis('off')\n", "_ = plt.suptitle(\"Model predictions\")" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "uRcJnAABr22x" }, "source": [ "## Export your model\n", "\n", "Now that you've trained the model, export it as a saved model:" ] }, { "cell_type": "code", "execution_count": 0, "metadata": { "colab": {}, "colab_type": "code", "id": "PLcqg-RmsLno" }, "outputs": [], "source": [ "export_path = tf.contrib.saved_model.save_keras_model(model, \"./saved_models\")\n", "export_path" ] }, { "cell_type": "markdown", "metadata": { "colab_type": "text", "id": "TYZd4MNiV3Rc" }, "source": [ "This saved model can loaded for inference later, or converted to [TFLite](https://www.tensorflow.org/lite/convert/) or [TFjs](https://github.com/tensorflow/tfjs-converter).\n", "\n" ] } ], "metadata": { "accelerator": "GPU", "colab": { "collapsed_sections": [ "W_tvPdyfA-BL" ], "name": "Hub with Keras.ipynb", "private_outputs": true, "provenance": [], "toc_visible": true, "version": "0.3.2" }, "kernelspec": { "display_name": "Python 3", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 0 }