{ "cells": [ { "cell_type": "markdown", "metadata": { "id": "djUvWu41mtXa" }, "source": [ "##### Copyright 2019 The TensorFlow Authors." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "cellView": "form", "id": "su2RaORHpReL" }, "outputs": [], "source": [ "#@title Licensed under the Apache License, Version 2.0 (the \"License\");\n", "# you may not use this file except in compliance with the License.\n", "# You may obtain a copy of the License at\n", "#\n", "# https://www.apache.org/licenses/LICENSE-2.0\n", "#\n", "# Unless required by applicable law or agreed to in writing, software\n", "# distributed under the License is distributed on an \"AS IS\" BASIS,\n", "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n", "# See the License for the specific language governing permissions and\n", "# limitations under the License." ] }, { "cell_type": "markdown", "metadata": { "id": "NztQK2uFpXT-" }, "source": [ "# TensorBoard で画像データを表示する\n", "\n", "\n", " \n", " \n", " \n", " \n", "
TensorFlow.org で表示Google Colab で実行GitHub でソースを表示ノートブックをダウンロード
" ] }, { "cell_type": "markdown", "metadata": { "id": "eDXRFe_qp5C3" }, "source": [ "## 概要\n", "\n", "**TensorFlow Image Summary API** を使用すると、テンソルと任意の画像のログと TensorBoard での表示を簡単に行えるようになります。入力データをサンプリングして調べる場合や、[レイヤーの重み](http://cs231n.github.io/understanding-cnn/)や[生成されたテンソル](https://hub.packtpub.com/generative-adversarial-networks-using-keras/)を視覚化する場合に非常に有用です。また、診断データを画像としてログすることもできるため、モデル開発時に役立ちます。\n", "\n", "このチュートリアルでは、Image Summary API を使用してテンソルを画像として視覚化する方法を学習します。また、任意の画像からテンソルに変換し、それを TensorBoard で視覚化する方法も学習します。モデルのパフォーマンスを理解しやすいように、Image Summary を使用する単純な実際の例を使って作業します。\n" ] }, { "cell_type": "markdown", "metadata": { "id": "dG-nnZK9qW9z" }, "source": [ "## セットアップ" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "id": "3U5gdCw_nSG3" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TensorFlow 2.x selected.\n" ] } ], "source": [ "try:\n", " # %tensorflow_version only exists in Colab.\n", " %tensorflow_version 2.x\n", "except Exception:\n", " pass\n", "\n", "# Load the TensorBoard notebook extension.\n", "%load_ext tensorboard" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "id": "1qIKtOBrqc9Y" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "TensorFlow version: 2.2\n" ] } ], "source": [ "from datetime import datetime\n", "import io\n", "import itertools\n", "from packaging import version\n", "\n", "import tensorflow as tf\n", "from tensorflow import keras\n", "\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import sklearn.metrics\n", "\n", "print(\"TensorFlow version: \", tf.__version__)\n", "assert version.parse(tf.__version__).release[0] >= 2, \\\n", " \"This notebook requires TensorFlow 2.0 or above.\"" ] }, { "cell_type": "markdown", "metadata": { "id": "Tq0gyXOGZ3-h" }, "source": [ "# Fashion-MNIST データセットをダウンロードする\n", "\n", "[Fashion-MNIST](https://github.com/zalandoresearch/fashion-mnist) データセットの画像を分類する、簡単なニューラルネットワークを構築しましょう。このデータセットには、ファッション製品に関する 70,000 個の 28x28 グレースケール画像が含まれています。7,000 個の画像を含むカテゴリが全 10 個あります。\n", "\n", "まず、データをダウンロードします。" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "id": "VmEQwCon3i7m" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz\n", "32768/29515 [=================================] - 0s 0us/step\n", "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz\n", "26427392/26421880 [==============================] - 0s 0us/step\n", "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz\n", "8192/5148 [===============================================] - 0s 0us/step\n", "Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz\n", "4423680/4422102 [==============================] - 0s 0us/step\n" ] } ], "source": [ "# Download the data. The data is already divided into train and test.\n", "# The labels are integers representing classes.\n", "fashion_mnist = keras.datasets.fashion_mnist\n", "(train_images, train_labels), (test_images, test_labels) = \\\n", " fashion_mnist.load_data()\n", "\n", "# Names of the integer classes, i.e., 0 -> T-short/top, 1 -> Trouser, etc.\n", "class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', \n", " 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']" ] }, { "cell_type": "markdown", "metadata": { "id": "qNsjMY0364j4" }, "source": [ "## 1 つの画像を視覚化する\n", "\n", "Image Summary API の動作を理解するために、トレーニングセットの最初のトレーニング画像を単純に TensorBoard ログすることにします。\n", "\n", "これを行う前に、トレーニングデータの形状を調べてみましょう。" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "id": "FxMPcdmvBn9t" }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Shape: (28, 28)\n", "Label: 9 -> Ankle boot\n" ] } ], "source": [ "print(\"Shape: \", train_images[0].shape)\n", "print(\"Label: \", train_labels[0], \"->\", class_names[train_labels[0]])" ] }, { "cell_type": "markdown", "metadata": { "id": "4F8zbUKfBuUt" }, "source": [ "データセットの各画像の形状は、高さと幅を表す形状 (28, 28) の階数 2 テンソルです。\n", "\n", "しかし、`tf.summary.image()` には `(batch_size, height, width, channels)` を含む階数 4 のテンソルが必要であるため、形状を変更する必要があります。\n", "\n", "1 つの画像のみをログしているため、`batch_size` は 1 となります。画像はグレースケールであるため、`channels` を 1 とします。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "5yPh-7EWB8IK" }, "outputs": [], "source": [ "# Reshape the image for the Summary API.\n", "img = np.reshape(train_images[0], (-1, 28, 28, 1))" ] }, { "cell_type": "markdown", "metadata": { "id": "JAdJDY3FCCwt" }, "source": [ "これで画像をログし、TensorBoard で表示する準備が整いました。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "IJNpyVyxbVtT" }, "outputs": [], "source": [ "# Clear out any prior log data.\n", "!rm -rf logs\n", "\n", "# Sets up a timestamped log directory.\n", "logdir = \"logs/train_data/\" + datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n", "# Creates a file writer for the log directory.\n", "file_writer = tf.summary.create_file_writer(logdir)\n", "\n", "# Using the file writer, log the reshaped image.\n", "with file_writer.as_default():\n", " tf.summary.image(\"Training data\", img, step=0)" ] }, { "cell_type": "markdown", "metadata": { "id": "rngALbRogXe6" }, "source": [ "では、TensorBoard を使用して画像を調べてみましょう。UI が読み込まれるまで数秒待ちます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "T_X-wIy-lD9f" }, "outputs": [], "source": [ "%tensorboard --logdir logs/train_data" ] }, { "cell_type": "markdown", "metadata": { "id": "c8n8YqGlT3-c" }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "id": "34enxJjjgWi7" }, "source": [ "「Time Series」ダッシュボードに、今ログした画像が表示されます。「ankle boot(アンクルブーツ)」です。\n", "\n", "画像は見やすいようにデフォルトのサイズに調整されています。スケーリングなしの元の画像を表示する場合は、右側の「Settings」パネル下の「Show actual image size」のチェックをオンにしてください。\n", "\n", "明るさやコントラストのスライダを動かして、画像のピクセルにどのような影響があるかを確認します。" ] }, { "cell_type": "markdown", "metadata": { "id": "bjACE1lAsqUd" }, "source": [ "## 複数の画像を視覚化する\n", "\n", "1 つのテンソルをログするのはうまくいきましたが、複数のトレーニングサンプルをログする場合はどうすればよいのでしょうか。\n", "\n", "データを `tf.summary.image()` に渡す際に、ログする画像数を指定するだけです。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "iHUjCXbetIpb" }, "outputs": [], "source": [ "with file_writer.as_default():\n", " # Don't forget to reshape.\n", " images = np.reshape(train_images[0:25], (-1, 28, 28, 1))\n", " tf.summary.image(\"25 training data examples\", images, max_outputs=25, step=0)\n", "\n", "%tensorboard --logdir logs/train_data" ] }, { "cell_type": "markdown", "metadata": { "id": "Fr6LFQG9UD6z" }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "id": "c-7sZs3XuBBy" }, "source": [ "## 任意の画像をログする\n", "\n", "[matplotlib](https://matplotlib.org/) が生成する画像など、テンソルでない画像を視覚化する場合はどうでしょうか。\n", "\n", "プロットをテンソルに変換するボイラープレートコードのようなものが必要となりますが、それを通過すればこの問題はクリアです。\n", "\n", "次のコードでは、matplotlib の `subplot()` 関数を使用して最初の 25 個の画像を適切なグリッドとしてログし、その後で、そのグリッドを TensorBoard で表示します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "F5U_5WKt8bdQ" }, "outputs": [], "source": [ "# Clear out prior logging data.\n", "!rm -rf logs/plots\n", "\n", "logdir = \"logs/plots/\" + datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n", "file_writer = tf.summary.create_file_writer(logdir)\n", "\n", "def plot_to_image(figure):\n", " \"\"\"Converts the matplotlib plot specified by 'figure' to a PNG image and\n", " returns it. The supplied figure is closed and inaccessible after this call.\"\"\"\n", " # Save the plot to a PNG in memory.\n", " buf = io.BytesIO()\n", " plt.savefig(buf, format='png')\n", " # Closing the figure prevents it from being displayed directly inside\n", " # the notebook.\n", " plt.close(figure)\n", " buf.seek(0)\n", " # Convert PNG buffer to TF image\n", " image = tf.image.decode_png(buf.getvalue(), channels=4)\n", " # Add the batch dimension\n", " image = tf.expand_dims(image, 0)\n", " return image\n", "\n", "def image_grid():\n", " \"\"\"Return a 5x5 grid of the MNIST images as a matplotlib figure.\"\"\"\n", " # Create a figure to contain the plot.\n", " figure = plt.figure(figsize=(10,10))\n", " for i in range(25):\n", " # Start next subplot.\n", " plt.subplot(5, 5, i + 1, title=class_names[train_labels[i]])\n", " plt.xticks([])\n", " plt.yticks([])\n", " plt.grid(False)\n", " plt.imshow(train_images[i], cmap=plt.cm.binary)\n", " \n", " return figure\n", "\n", "# Prepare the plot\n", "figure = image_grid()\n", "# Convert to image and log\n", "with file_writer.as_default():\n", " tf.summary.image(\"Training data\", plot_to_image(figure), step=0)\n", "\n", "%tensorboard --logdir logs/plots" ] }, { "cell_type": "markdown", "metadata": { "id": "o_tIghRsXY7S" }, "source": [ "" ] }, { "cell_type": "markdown", "metadata": { "id": "vZx70BC1zhgW" }, "source": [ "## 画像分類器を構築する\n", "\n", "綺麗な写真をプロットするためではなく、機械学習を行うためにこのチュートリアルを行っているわけですから、この作業を実際の例に適用してみましょう。\n", "\n", "画像の要約を使用して、Fashion-MNIST データセットの簡単な分類器をトレーニングしながらモデルがどれほどうまく機能しているかを把握することにします。\n", "\n", "まず、非常に単純なモデルを作成してコンパイルします。オプティマイザと損失関数をセットアップしましょう。コンパイルのステップでは分類器の精度も合わせてログすることを指定します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "R74hPWJHzgvZ" }, "outputs": [], "source": [ "model = keras.models.Sequential([\n", " keras.layers.Flatten(input_shape=(28, 28)),\n", " keras.layers.Dense(32, activation='relu'),\n", " keras.layers.Dense(10, activation='softmax')\n", "])\n", "\n", "model.compile(\n", " optimizer='adam', \n", " loss='sparse_categorical_crossentropy',\n", " metrics=['accuracy']\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "SdT_PpZB1UMn" }, "source": [ "分類器をトレーニングする際、[混同行列](https://en.wikipedia.org/wiki/Confusion_matrix)を見ると役に立ちます。混同行列では、分類器がテストデータどどのように実行しているかを詳しく知ることができます。\n", "\n", "混同行列を計算する関数を定義しましょう。[Scikit-learn](https://scikit-learn.org/stable/auto_examples/model_selection/plot_confusion_matrix.html) 関数を使えば簡単にこれを行え、その上で、matplotlib を使ってプロットすることができます。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "rBiXP8-UO8t6" }, "outputs": [], "source": [ "def plot_confusion_matrix(cm, class_names):\n", " \"\"\"\n", " Returns a matplotlib figure containing the plotted confusion matrix.\n", "\n", " Args:\n", " cm (array, shape = [n, n]): a confusion matrix of integer classes\n", " class_names (array, shape = [n]): String names of the integer classes\n", " \"\"\"\n", " figure = plt.figure(figsize=(8, 8))\n", " plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)\n", " plt.title(\"Confusion matrix\")\n", " plt.colorbar()\n", " tick_marks = np.arange(len(class_names))\n", " plt.xticks(tick_marks, class_names, rotation=45)\n", " plt.yticks(tick_marks, class_names)\n", "\n", " # Compute the labels from the normalized confusion matrix.\n", " labels = np.around(cm.astype('float') / cm.sum(axis=1)[:, np.newaxis], decimals=2)\n", "\n", " # Use white text if squares are dark; otherwise black.\n", " threshold = cm.max() / 2.\n", " for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n", " color = \"white\" if cm[i, j] > threshold else \"black\"\n", " plt.text(j, i, labels[i, j], horizontalalignment=\"center\", color=color)\n", "\n", " plt.tight_layout()\n", " plt.ylabel('True label')\n", " plt.xlabel('Predicted label')\n", " return figure" ] }, { "cell_type": "markdown", "metadata": { "id": "6lOAl_v26QGq" }, "source": [ "これで、分類器をトレーニングしながら混同行列を定期的にログする準備が整いました。\n", "\n", "ここでは、次の項目を行います。\n", "\n", "1. 基本的なメトリックをログする [Keras TensorBoard コールバック](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/TensorBoard)を作成する\n", "2. エポックが終了するたびに混同行列をログする [Keras LambdaCallback](https://www.tensorflow.org/api_docs/python/tf/keras/callbacks/LambdaCallback) を作成する\n", "3. 両方のコールバックが渡されるようにし、Model.fit() を使ってモデルをトレーニングする\n", "\n", "トレーニングが進むにつれ、下にスクロールして TensorBoard の起動を確認します。" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "utd-vH6hn5RY" }, "outputs": [], "source": [ "# Clear out prior logging data.\n", "!rm -rf logs/image\n", "\n", "logdir = \"logs/image/\" + datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n", "# Define the basic TensorBoard callback.\n", "tensorboard_callback = keras.callbacks.TensorBoard(log_dir=logdir)\n", "file_writer_cm = tf.summary.create_file_writer(logdir + '/cm')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "bXQ7-9CF0TPA" }, "outputs": [], "source": [ "def log_confusion_matrix(epoch, logs):\n", " # Use the model to predict the values from the validation dataset.\n", " test_pred_raw = model.predict(test_images)\n", " test_pred = np.argmax(test_pred_raw, axis=1)\n", "\n", " # Calculate the confusion matrix.\n", " cm = sklearn.metrics.confusion_matrix(test_labels, test_pred)\n", " # Log the confusion matrix as an image summary.\n", " figure = plot_confusion_matrix(cm, class_names=class_names)\n", " cm_image = plot_to_image(figure)\n", "\n", " # Log the confusion matrix as an image summary.\n", " with file_writer_cm.as_default():\n", " tf.summary.image(\"epoch_confusion_matrix\", cm_image, step=epoch)\n", "\n", "# Define the per-epoch callback.\n", "cm_callback = keras.callbacks.LambdaCallback(on_epoch_end=log_confusion_matrix)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "id": "k6CV7dy-oJZu" }, "outputs": [], "source": [ "# Start TensorBoard.\n", "%tensorboard --logdir logs/image\n", "\n", "# Train the classifier.\n", "model.fit(\n", " train_images,\n", " train_labels,\n", " epochs=5,\n", " verbose=0, # Suppress chatty output\n", " callbacks=[tensorboard_callback, cm_callback],\n", " validation_data=(test_images, test_labels),\n", ")" ] }, { "cell_type": "markdown", "metadata": { "id": "o7PnxGf8Ur6F" }, "source": [ "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "id": "6URWgszz9Jut" }, "source": [ "トレーニングと検証の両方のセットで、精度が上昇して言えるのがわかります。これは良い兆しではありますが、データの特定のサブセットで実行しているモデルはどうなっているでしょうか。\n", "\n", "「Time Series」ダッシュボードを下にスクロールし、ログした混同行列を可視化してみましょう。「Settings」パネルの下にある「Show actual image size」をオンにして、混同行列をフルサイズで表示します。\n", "\n", "デフォルトでは、このダッシュボードには最後にログされたステップまたはエポックの画像要約が表示されます。スライダーを使用して早期の混同行列を表示します。トレーニングが進むにつれ、行列が大きく変化しているのがわかります。暗めのマスが斜めに連なり、ほかの行列は 0 に近くマスの色も白くなっています。つまり、トレーニングが進むにつれ、分類器が改善しているということです。よくできました!\n", "\n", "混同行列は、この単純なモデルにいくつかの問題があることを示しています。うまく進んではいるものの、シャツ、Tシャツ、プルオーバーが混同されているため、モデルの改善が必要です。\n", "\n", "関心のある方は、このモデルを[畳み込みネットワーク](https://medium.com/tensorflow/hello-deep-learning-fashion-mnist-with-keras-50fcff8cd74a)(CNN)で改善してみてください。" ] } ], "metadata": { "colab": { "collapsed_sections": [], "name": "image_summaries.ipynb", "toc_visible": true }, "kernelspec": { "display_name": "Python 3", "name": "python3" } }, "nbformat": 4, "nbformat_minor": 0 }