diff --git a/site/ja/tutorials/load_data/images.ipynb b/site/ja/tutorials/load_data/images.ipynb new file mode 100644 index 00000000000..65d23e50720 --- /dev/null +++ b/site/ja/tutorials/load_data/images.ipynb @@ -0,0 +1,1655 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "images.ipynb", + "version": "0.3.2", + "provenance": [], + "private_outputs": true, + "collapsed_sections": [], + "toc_visible": true + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + } + }, + "cells": [ + { + "metadata": { + "colab_type": "text", + "id": "mt9dL5dIir8X" + }, + "cell_type": "markdown", + "source": [ + "##### Copyright 2018 The TensorFlow Authors." + ] + }, + { + "metadata": { + "cellView": "form", + "colab_type": "code", + "id": "ufPx7EiCiqgR", + "colab": {} + }, + "cell_type": "code", + "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.\n" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "ucMoYase6URl" + }, + "cell_type": "markdown", + "source": [ + "# tf.dataを使って画像をロードする" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "_Wwu5SXZmEkB" + }, + "cell_type": "markdown", + "source": [ + "\n", + " \n", + " \n", + " \n", + "
\n", + " View on TensorFlow.org\n", + " \n", + " Run in Google Colab\n", + " \n", + " View source on GitHub\n", + "
" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "Oxw4WahM7DU9" + }, + "cell_type": "markdown", + "source": [ + "このチュートリアルでは、'tf.data'を使って画像データセットをロードする簡単な例を示します。\n", + "\n", + "このチュートリアルで使用するデータセットは、クラスごとに別々のディレクトリに別れた形で配布されています。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "hoQQiZDB6URn" + }, + "cell_type": "markdown", + "source": [ + "## 設定" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "DHz3JONNEHlj", + "colab": {} + }, + "cell_type": "code", + "source": [ + "from __future__ import absolute_import, division, print_function\n", + "\n", + "import tensorflow as tf\n", + "tf.enable_eager_execution()\n", + "tf.__version__" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "KT6CcaqgQewg", + "colab": {} + }, + "cell_type": "code", + "source": [ + "AUTOTUNE = tf.data.experimental.AUTOTUNE" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "rxndJHNC8YPM" + }, + "cell_type": "markdown", + "source": [ + "## データセットのダウンロードと検査" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "wO0InzL66URu" + }, + "cell_type": "markdown", + "source": [ + "### 画像の取得\n", + "\n", + "訓練を始める前に、ネットワークに認識すべき新しいクラスを教えるために画像のセットが必要です。最初に使うためのクリエイティブ・コモンズでライセンスされた花の画像のアーカイブを作成してあります。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "rN-Pc6Zd6awg", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import pathlib\n", + "data_root = tf.keras.utils.get_file('flower_photos','https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz', untar=True)\n", + "data_root = pathlib.Path(data_root)\n", + "print(data_root)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "rFkFK74oO--g" + }, + "cell_type": "markdown", + "source": [ + "218MBをダウンロードすると、花の画像のコピーが使えるようになっているはずです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "7onR_lWE7Njj", + "colab": {} + }, + "cell_type": "code", + "source": [ + "for item in data_root.iterdir():\n", + " print(item)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "4yYX3ZRqGOuq", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import random\n", + "all_image_paths = list(data_root.glob('*/*'))\n", + "all_image_paths = [str(path) for path in all_image_paths]\n", + "random.shuffle(all_image_paths)\n", + "\n", + "image_count = len(all_image_paths)\n", + "image_count" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "t_BbYnLjbltQ", + "colab": {} + }, + "cell_type": "code", + "source": [ + "all_image_paths" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "vkM-IpB-6URx" + }, + "cell_type": "markdown", + "source": [ + "### 画像の検査\n", + "\n", + "扱っている画像について知るために、画像のいくつかを見てみましょう。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "wNGateQJ6UR1", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import os\n", + "attributions = (data_root/\"LICENSE.txt\").open(encoding='utf-8').readlines()[4:]\n", + "attributions = [line.split(' CC-BY') for line in attributions]\n", + "attributions = dict(attributions)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "jgowG2xu88Io", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import IPython.display as display\n", + "\n", + "def caption_image(image_path):\n", + " image_rel = pathlib.Path(image_path).relative_to(data_root)\n", + " return \"Image (CC BY 2.0) \" + ' - '.join(attributions[str(image_rel)].split(' - ')[:-1])\n", + " " + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "YIjLi-nX0txI", + "colab": {} + }, + "cell_type": "code", + "source": [ + "for n in range(3):\n", + " image_path = random.choice(all_image_paths)\n", + " display.display(display.Image(image_path))\n", + " print(caption_image(image_path))\n", + " print()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "OaNOr-co3WKk" + }, + "cell_type": "markdown", + "source": [ + "### 各画像のラベルの決定" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "-weOQpDw2Jnu" + }, + "cell_type": "markdown", + "source": [ + "ラベルを一覧してみます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "ssUZ7Qh96UR3", + "colab": {} + }, + "cell_type": "code", + "source": [ + "label_names = sorted(item.name for item in data_root.glob('*/') if item.is_dir())\n", + "label_names" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "9l_JEBql2OzS" + }, + "cell_type": "markdown", + "source": [ + "ラベルにインデックスを割り当てます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "Y8pCV46CzPlp", + "colab": {} + }, + "cell_type": "code", + "source": [ + "label_to_index = dict((name, index) for index,name in enumerate(label_names))\n", + "label_to_index" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "VkXsHg162T9F" + }, + "cell_type": "markdown", + "source": [ + "ファイルとラベルのインデックスの一覧を作成します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "q62i1RBP4Q02", + "colab": {} + }, + "cell_type": "code", + "source": [ + "all_image_labels = [label_to_index[pathlib.Path(path).parent.name]\n", + " for path in all_image_paths]\n", + "\n", + "print(\"First 10 labels indices: \", all_image_labels[:10])" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "i5L09icm9iph" + }, + "cell_type": "markdown", + "source": [ + "### 画像の読み込みと整形" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "SbqqRUS79ooq" + }, + "cell_type": "markdown", + "source": [ + "TensorFlowには画像を読み込んで処理するために必要なツールが備わっています。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "jQZdySHvksOu", + "colab": {} + }, + "cell_type": "code", + "source": [ + "img_path = all_image_paths[0]\n", + "img_path" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "2t2h2XCcmK1Y" + }, + "cell_type": "markdown", + "source": [ + "以下は生のデータです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "LJfkyC_Qkt7A", + "colab": {} + }, + "cell_type": "code", + "source": [ + "img_raw = tf.read_file(img_path)\n", + "print(repr(img_raw)[:100]+\"...\")" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "opN8AVc8mSbz" + }, + "cell_type": "markdown", + "source": [ + "画像のテンソルにデコードします。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "Tm0tdrlfk0Bb", + "colab": {} + }, + "cell_type": "code", + "source": [ + "img_tensor = tf.image.decode_image(img_raw)\n", + "\n", + "print(img_tensor.shape)\n", + "print(img_tensor.dtype)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "3k-Of2Tfmbeq" + }, + "cell_type": "markdown", + "source": [ + "モデルに合わせてリサイズします。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "XFpz-3_vlJgp", + "colab": {} + }, + "cell_type": "code", + "source": [ + "img_final = tf.image.resize_images(img_tensor, [192, 192])\n", + "img_final = img_final/255.0\n", + "print(img_final.shape)\n", + "print(img_final.numpy().min())\n", + "print(img_final.numpy().max())\n" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "aCsAa4Psl4AQ" + }, + "cell_type": "markdown", + "source": [ + "このあと使用するために、簡単な関数にまとめます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "HmUiZJNU73vA", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def preprocess_image(image):\n", + " image = tf.image.decode_jpeg(image, channels=3)\n", + " image = tf.image.resize_images(image, [192, 192])\n", + " image /= 255.0 # normalize to [0,1] range\n", + "\n", + " return image" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "einETrJnO-em", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def load_and_preprocess_image(path):\n", + " image = tf.read_file(path)\n", + " return preprocess_image(image)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "3brWQcdtz78y", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "image_path = all_image_paths[0]\n", + "label = all_image_labels[0]\n", + "\n", + "plt.imshow(load_and_preprocess_image(img_path))\n", + "plt.grid(False)\n", + "plt.xlabel(caption_image(img_path))\n", + "plt.title(label_names[label].title())\n", + "print()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "n2TCr1TQ8pA3" + }, + "cell_type": "markdown", + "source": [ + "## `tf.data.Dataset`の構築" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "6H9Z5Mq63nSH" + }, + "cell_type": "markdown", + "source": [ + "### 画像のデータセット" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "GN-s04s-6Luq" + }, + "cell_type": "markdown", + "source": [ + "`tf.data.Dataset`を構築する最も簡単な方法は、`from_tensor_slices`メソッドを使うことです。\n", + "\n", + "文字列の配列をスライスすると、文字列のデータセットが出来上がります。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "6oRPG3Jz3ie_", + "colab": {} + }, + "cell_type": "code", + "source": [ + "path_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "uML4JeMmIAvO" + }, + "cell_type": "markdown", + "source": [ + "`output_shapes`と`output_types`という2つのフィールドが、データセット中の要素の中身を示しています。この場合には、バイナリ文字列というスカラーのセットです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "mIsNflFbIK34", + "colab": {} + }, + "cell_type": "code", + "source": [ + "print('shape: ', repr(path_ds.output_shapes))\n", + "print('type: ', path_ds.output_types)\n", + "print()\n", + "print(path_ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "ZjyGcM8OwBJ2" + }, + "cell_type": "markdown", + "source": [ + "`preprocess_image`をファイルパスのデータセットにマップすることで、画像を実行時にロードし整形する新しいデータセットを作成します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "D1iba6f4khu-", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_ds = path_ds.map(load_and_preprocess_image, num_parallel_calls=AUTOTUNE)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "JLUPs2a-lEEJ", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import matplotlib.pyplot as plt\n", + "\n", + "plt.figure(figsize=(8,8))\n", + "for n,image in enumerate(image_ds.take(4)):\n", + " plt.subplot(2,2,n+1)\n", + " plt.imshow(image)\n", + " plt.grid(False)\n", + " plt.xticks([])\n", + " plt.yticks([])\n", + " plt.xlabel(caption_image(all_image_paths[n]))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "P6FNqPbxkbdx" + }, + "cell_type": "markdown", + "source": [ + "### `(image, label)`のペアのデータセット" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "YgvrWLKG67-x" + }, + "cell_type": "markdown", + "source": [ + "同じ`from_tensor_slices`メソッドを使ってラベルのデータセットを作ることができます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "AgBsAiV06udj", + "colab": {} + }, + "cell_type": "code", + "source": [ + "label_ds = tf.data.Dataset.from_tensor_slices(tf.cast(all_image_labels, tf.int64))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "HEsk5nN0vyeX", + "colab": {} + }, + "cell_type": "code", + "source": [ + "for label in label_ds.take(10):\n", + " print(label_names[label.numpy()])" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "jHjgrEeTxyYz" + }, + "cell_type": "markdown", + "source": [ + "これらのデータセットは同じ順番なので、zipすることで`(image, label)`というペアのデータセットができます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "AOEWNMdQwsbN", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_label_ds = tf.data.Dataset.zip((image_ds, label_ds))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "yA2F09SJLMuM" + }, + "cell_type": "markdown", + "source": [ + "新しいデータセットの`shapes`と`types`は、それぞれのフィールドを示すシェイプと型のタプルです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "DuVYNinrLL-N", + "colab": {} + }, + "cell_type": "code", + "source": [ + "print('image shape: ', image_label_ds.output_shapes[0])\n", + "print('label shape: ', image_label_ds.output_shapes[1])\n", + "print('types: ', image_label_ds.output_types)\n", + "print()\n", + "print(image_label_ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "2WYMikoPWOQX" + }, + "cell_type": "markdown", + "source": [ + "注:`all_image_labels`や`all_image_paths`のような配列がある場合、`tf.data.dataset.Dataset.zip`メソッドの代わりとなるのは、配列のペアをスライスすることです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "HOFwZI-2WhzV", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = tf.data.Dataset.from_tensor_slices((all_image_paths, all_image_labels))\n", + "\n", + "# The tuples are unpacked into the positional arguments of the mapped function\n", + "# タプルは展開され、マップ関数の位置引数に割り当てられます\n", + "def load_and_preprocess_from_path_label(path, label):\n", + " return load_and_preprocess_image(path), label\n", + "\n", + "image_label_ds = ds.map(load_and_preprocess_from_path_label)\n", + "image_label_ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "vYGCgJuR_9Qp" + }, + "cell_type": "markdown", + "source": [ + "### 基本的な訓練手法" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "wwZavzgsIytz" + }, + "cell_type": "markdown", + "source": [ + "このデータセットを使ってモデルの訓練を行うには、データが\n", + "\n", + "* よくシャッフルされ\n", + "* バッチ化され\n", + "* 限りなく繰り返され\n", + "* バッチが出来るだけ早く利用できる\n", + "\n", + "ことが必要です。\n", + "\n", + "これらの特性は`tf.data`APIを使えば簡単に付け加えることができます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "uZmZJx8ePw_5", + "colab": {} + }, + "cell_type": "code", + "source": [ + "BATCH_SIZE = 32\n", + "\n", + "# シャッフルバッファのサイズをデータセットと同じに設定することで、データが完全にシャッフルされる\n", + "# ようにできます。\n", + "ds = image_label_ds.shuffle(buffer_size=image_count)\n", + "ds = ds.repeat()\n", + "ds = ds.batch(BATCH_SIZE)\n", + "# `prefetch`を使うことで、モデルの訓練中にバックグラウンドでデータセットがバッチを取得できます。\n", + "ds = ds.prefetch(buffer_size=AUTOTUNE)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "6JsM-xHiFCuW" + }, + "cell_type": "markdown", + "source": [ + "注意すべきことがいくつかあります。\n", + "\n", + "1. 順番が重要です。\n", + "\n", + " * `.repeat`の前に`.shuffle`すると、エポックの境界を越えて要素がシャッフルされます。(他の要素がすべて出現する前に2回出現する要素があるかもしれません)\n", + " * `.batch`の後に`.shuffle`すると、バッチの順番がシャッフルされますが、要素がバッチを越えてシャッフルされることはありません。\n", + "\n", + "1. 完全なシャッフルのため、`buffer_size`をデータセットと同じサイズに設定しています。データセットのサイズ未満の場合、値が大きいほど良くランダム化されますが、より多くのメモリーを使用します。\n", + "\n", + "1. シャッフルバッファがいっぱいになってから要素が取り出されます。そのため、大きな`buffer_size`が`Dataset`を使い始める際の遅延の原因になります。\n", + "\n", + "1. シャッフルされたデータセットは、シャッフルバッファが完全に空になるまでデータセットが終わりであることを伝えません。`.repeat`によって`Dataset`が再起動されると、シャッフルバッファが一杯になるまでもう一つの待ち時間が発生します。\n", + "\n", + "最後の問題は、`tf.data.Dataset.apply`メソッドを、融合された`tf.data.experimental.shuffle_and_repeat`関数と組み合わせることで対処できます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "Ocr6PybXNDoO", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = image_label_ds.apply(\n", + " tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))\n", + "ds = ds.batch(BATCH_SIZE)\n", + "ds = ds.prefetch(buffer_size=AUTOTUNE)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "GBBZMSuAmQVL" + }, + "cell_type": "markdown", + "source": [ + "### データセットをモデルにつなぐ\n", + "\n", + "`tf.keras.applications`からMobileNet v2のコピーを取得します。\n", + "\n", + "これを簡単な転移学習のサンプルに使用します。\n", + "\n", + "MobileNetの重みを訓練不可に設定します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "KbJrXn9omO_g", + "colab": {} + }, + "cell_type": "code", + "source": [ + "mobile_net = tf.keras.applications.MobileNetV2(input_shape=(192, 192, 3), include_top=False)\n", + "mobile_net.trainable=False" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "Y7NVWiLF3Vbf" + }, + "cell_type": "markdown", + "source": [ + "このモデルは、入力が`[-1,1]`の範囲に正規化されていることを想定しています。\n", + "\n", + "```\n", + "help(keras_applications.mobilenet_v2.preprocess_input)\n", + "```\n", + "\n", + "
\n",
+        "...\n",
+        "This function applies the \"Inception\" preprocessing which converts\n",
+        "the RGB values from [0, 255] to [-1, 1] \n",
+        "...\n",
+        "
" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "CboYya6LmdQI" + }, + "cell_type": "markdown", + "source": [ + "このため、データをMobileNetモデルに渡す前に、入力を`[0,1]`の範囲から`[-1,1]`の範囲に変換する必要があります。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "SNOkHUGv3FYq", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def change_range(image,label):\n", + " return 2*image-1, label\n", + "\n", + "keras_ds = ds.map(change_range)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "QDzZ3Nye5Rpv" + }, + "cell_type": "markdown", + "source": [ + "MobileNetは画像ごとに`6x6`の特徴量の空間を返します。\n", + "\n", + "バッチを1つ渡してみましょう。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "OzAhGkEK6WuE", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# シャッフルバッファがいっぱいになるまで、データセットは何秒かかかります。\n", + "image_batch, label_batch = next(iter(keras_ds))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "LcFdiWpO5WbV", + "colab": {} + }, + "cell_type": "code", + "source": [ + "feature_map_batch = mobile_net(image_batch)\n", + "print(feature_map_batch.shape)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "vrbjEvaC5XmU" + }, + "cell_type": "markdown", + "source": [ + "MobileNetをラップしたモデルを作り、出力層である`tf.keras.layers.Dense`の前に、`tf.keras.layers.GlobalAveragePooling2D`で空間の軸に沿って平均値を求めます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "X0ooIU9fNjPJ", + "colab": {} + }, + "cell_type": "code", + "source": [ + "model = tf.keras.Sequential([\n", + " mobile_net,\n", + " tf.keras.layers.GlobalAveragePooling2D(),\n", + " tf.keras.layers.Dense(len(label_names))])" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "foQYUJs97V4V" + }, + "cell_type": "markdown", + "source": [ + "期待したとおりの形状の出力が得られます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "1nwYxvpj7ZEf", + "colab": {} + }, + "cell_type": "code", + "source": [ + "logit_batch = model(image_batch).numpy()\n", + "\n", + "print(\"min logit:\", logit_batch.min())\n", + "print(\"max logit:\", logit_batch.max())\n", + "print()\n", + "\n", + "print(\"Shape:\", logit_batch.shape)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "pFc4I_J2nNOJ" + }, + "cell_type": "markdown", + "source": [ + "訓練手法を記述するためにモデルをコンパイルします。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "ZWGqLEWYRNvv", + "colab": {} + }, + "cell_type": "code", + "source": [ + "model.compile(optimizer=tf.train.AdamOptimizer(), \n", + " loss=tf.keras.losses.sparse_categorical_crossentropy,\n", + " metrics=[\"accuracy\"])" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "tF1mO6haBOSd" + }, + "cell_type": "markdown", + "source": [ + "訓練可能な変数は2つ、全結合層の`weights`と`bias`です。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "pPQ5yqyKBJMm", + "colab": {} + }, + "cell_type": "code", + "source": [ + "len(model.trainable_variables) " + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "kug5Wg66UJjl", + "colab": {} + }, + "cell_type": "code", + "source": [ + "model.summary()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "f_glpYZ-nYC_" + }, + "cell_type": "markdown", + "source": [ + "モデルを訓練します。\n", + "\n", + "普通は、エポックごとの本当のステップ数を指定しますが、ここではデモの目的なので3ステップだけとします。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "AnXPRNWoTypI", + "colab": {} + }, + "cell_type": "code", + "source": [ + "steps_per_epoch=tf.ceil(len(all_image_paths)/BATCH_SIZE).numpy()\n", + "steps_per_epoch" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "q_8sabaaSGAp", + "colab": {} + }, + "cell_type": "code", + "source": [ + "model.fit(ds, epochs=1, steps_per_epoch=3)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "UMVnoBcG_NlQ" + }, + "cell_type": "markdown", + "source": [ + "## 性能\n", + "\n", + "注:このセクションでは性能の向上に役立ちそうな簡単なトリックをいくつか紹介します。詳しくは、[Input Pipeline Performance](https://www.tensorflow.org/guide/performance/datasets)を参照してください。\n", + "\n", + "上記の単純なパイプラインは、エポックごとにそれぞれのファイルを一つずつ読み込みます。これは、CPUを使ったローカルでの訓練では問題になりませんが、GPUを使った訓練では十分ではなく、いかなる分散訓練でも使うべきではありません。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "oNmQqgGhLWie" + }, + "cell_type": "markdown", + "source": [ + "調査のため、まず、データセットの性能をチェックする簡単な関数を定義します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "_gFVe1rp_MYr", + "colab": {} + }, + "cell_type": "code", + "source": [ + "import time\n", + "\n", + "def timeit(ds, batches=2*steps_per_epoch+1):\n", + " overall_start = time.time()\n", + " # タイマーをスタートする前に、パイプラインの初期化の(シャッフルバッファを埋める)ため、\n", + " # バッチを1つ取得します\n", + " it = iter(ds.take(batches+1))\n", + " next(it)\n", + "\n", + " start = time.time()\n", + " for i,(images,labels) in enumerate(it):\n", + " if i%10 == 0:\n", + " print('.',end='')\n", + " print()\n", + " end = time.time()\n", + "\n", + " duration = end-start\n", + " print(\"{} batches: {} s\".format(batches, duration))\n", + " print(\"{:0.5f} Images/s\".format(BATCH_SIZE*batches/duration))\n", + " print(\"Total time: {}s\".format(end-overall_start))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "TYiOr4vdLcNX" + }, + "cell_type": "markdown", + "source": [ + "現在のデータセットの性能は次のとおりです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "ZDxLwMJOReVe", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = image_label_ds.apply(\n", + " tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))\n", + "ds = ds.batch(BATCH_SIZE).prefetch(buffer_size=AUTOTUNE)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "IjouTJadRxyp", + "colab": {} + }, + "cell_type": "code", + "source": [ + "timeit(ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "HsLlXMO7EWBR" + }, + "cell_type": "markdown", + "source": [ + "### キャッシュ" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "lV1NOn2zE2lR" + }, + "cell_type": "markdown", + "source": [ + "`tf.data.Dataset.cache`を使うと、エポックを越えて計算結果を簡単にキャッシュできます。特に、データがメモリに収まるときには効果的です。\n", + "\n", + "ここでは、画像が前処理(デコードとリサイズ)された後でキャッシュされます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "qj_U09xpDvOg", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = image_label_ds.cache()\n", + "ds = ds.apply(\n", + " tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))\n", + "ds = ds.batch(BATCH_SIZE).prefetch(buffer_size=AUTOTUNE)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "rdxpvQ7VEo3y", + "colab": {} + }, + "cell_type": "code", + "source": [ + "timeit(ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "usIv7MqqZQps" + }, + "cell_type": "markdown", + "source": [ + "メモリキャッシュを使う際の欠点のひとつは、実行の都度キャッシュを再構築しなければならないことです。このため、データセットがスタートするたびに同じだけ起動のための遅延が発生します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "eKX6ergKb_xd", + "colab": {} + }, + "cell_type": "code", + "source": [ + "timeit(ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "jUzpG4lYNkN-" + }, + "cell_type": "markdown", + "source": [ + "データがメモリに収まらない場合には、キャッシュファイルを使用します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "vIvF8K4GMq0g", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = image_label_ds.cache(filename='./cache.tf-data')\n", + "ds = ds.apply(\n", + " tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))\n", + "ds = ds.batch(BATCH_SIZE).prefetch(1)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "eTIj6IOmM4yA", + "colab": {} + }, + "cell_type": "code", + "source": [ + "timeit(ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "qqo3dyB0Z4t2" + }, + "cell_type": "markdown", + "source": [ + "キャッシュファイルには、キャッシュを再構築することなくデータセットを再起動できるという利点もあります。2回めがどれほど早いか見てみましょう。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "hZhVdR8MbaUj", + "colab": {} + }, + "cell_type": "code", + "source": [ + "timeit(ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "WqOVlf8tFrDU" + }, + "cell_type": "markdown", + "source": [ + "### TFRecord ファイル" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "y1llOTwWFzmR" + }, + "cell_type": "markdown", + "source": [ + "#### 生の画像データ\n", + "\n", + "TFRecordファイルは、バイナリの大きなオブジェクトのシーケンスを保存するための単純なフォーマットです。複数のサンプルを同じファイルに詰め込むことで、TensorFlowは複数のサンプルを一度に読み込むことができます。これは、特にGCSのようなリモートストレージサービスを使用する際の性能にとって重要です。\n", + "\n", + "最初に、生の画像データからTFRecordファイルを構築します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "EqtARqKuHQLu", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_ds = tf.data.Dataset.from_tensor_slices(all_image_paths).map(tf.read_file)\n", + "tfrec = tf.data.experimental.TFRecordWriter('images.tfrec')\n", + "tfrec.write(image_ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "flR2GXWFKcO1" + }, + "cell_type": "markdown", + "source": [ + "次に、TFRecordファイルを読み込み、以前定義した`preprocess_image`関数を使って画像のデコード/リフォーマットを行うデータセットを構築します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "j9PVUL2SFufn", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_ds = tf.data.TFRecordDataset('images.tfrec').map(preprocess_image)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "cRp1eZDRKzyN" + }, + "cell_type": "markdown", + "source": [ + "これを、前に定義済みのラベルデータセットとzipし、期待通りの`(image,label)`のペアを得ます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "7XI_nDU2KuhS", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = tf.data.Dataset.zip((image_ds, label_ds))\n", + "ds = ds.apply(\n", + " tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))\n", + "ds=ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "3ReSapoPK22E", + "colab": {} + }, + "cell_type": "code", + "source": [ + "timeit(ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "wb7VyoKNOMms" + }, + "cell_type": "markdown", + "source": [ + "これは、`cache`バージョンよりも低速です。前処理をキャッシュしていないからです。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "NF9W-CTKkM-f" + }, + "cell_type": "markdown", + "source": [ + "#### シリアライズしたテンソル" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "J9HzljSPkxt0" + }, + "cell_type": "markdown", + "source": [ + "前処理をTFRecordファイルに保存するには、前やったように前処理した画像のデータセットを作ります。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "OzS0Azukkjyw", + "colab": {} + }, + "cell_type": "code", + "source": [ + "paths_ds = tf.data.Dataset.from_tensor_slices(all_image_paths)\n", + "image_ds = paths_ds.map(load_and_preprocess_image)\n", + "image_ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "onWOwLpYlzJQ" + }, + "cell_type": "markdown", + "source": [ + "`.jpeg`文字列のデータセットではなく、これはテンソルのデータセットです。\n", + "\n", + "これをTFRecordファイルにシリアライズするには、まず、テンソルのデータセットを文字列のデータセットに変換します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "xxZSwnRllyf0", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = image_ds.map(tf.serialize_tensor)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "w9N6hJWAkKPC", + "colab": {} + }, + "cell_type": "code", + "source": [ + "tfrec = tf.data.experimental.TFRecordWriter('images.tfrec')\n", + "tfrec.write(ds)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "OlFc9dJSmcx0" + }, + "cell_type": "markdown", + "source": [ + "前処理をキャッシュしたことにより、データはTFRecordファイルから非常に効率的にロードできます。テンソルを使用する前にデシリアライズすることを忘れないでください。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "BsqFyTBFmSCZ", + "colab": {} + }, + "cell_type": "code", + "source": [ + "RESTORE_TYPE = image_ds.output_types\n", + "RESTORE_SHAPE = image_ds.output_shapes\n", + "\n", + "ds = tf.data.TFRecordDataset('images.tfrec')\n", + "\n", + "def parse(x):\n", + " result = tf.parse_tensor(x, out_type=RESTORE_TYPE)\n", + " result = tf.reshape(result, RESTORE_SHAPE)\n", + " return result\n", + "\n", + "ds = ds.map(parse, num_parallel_calls=AUTOTUNE)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "OPs_sLV9pQg5" + }, + "cell_type": "markdown", + "source": [ + "次にラベルを追加し、以前と同じような標準的な処理を適用します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "XYxBwaLYnGop", + "colab": {} + }, + "cell_type": "code", + "source": [ + "ds = tf.data.Dataset.zip((ds, label_ds))\n", + "ds = ds.apply(\n", + " tf.data.experimental.shuffle_and_repeat(buffer_size=image_count))\n", + "ds=ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n", + "ds" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "W8X6RmGan1-P", + "colab": {} + }, + "cell_type": "code", + "source": [ + "timeit(ds)" + ], + "execution_count": 0, + "outputs": [] + } + ] +} \ No newline at end of file diff --git a/site/ja/tutorials/load_data/tf_records.ipynb b/site/ja/tutorials/load_data/tf_records.ipynb new file mode 100644 index 00000000000..792c23847c3 --- /dev/null +++ b/site/ja/tutorials/load_data/tf_records.ipynb @@ -0,0 +1,1272 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "name": "tf_records.ipynb", + "version": "0.3.2", + "provenance": [], + "private_outputs": true, + "collapsed_sections": [ + "pL--_KGdYoBz" + ], + "toc_visible": true + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.5" + }, + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + } + }, + "cells": [ + { + "metadata": { + "colab_type": "text", + "id": "pL--_KGdYoBz" + }, + "cell_type": "markdown", + "source": [ + "##### Copyright 2018 The TensorFlow Authors." + ] + }, + { + "metadata": { + "cellView": "both", + "colab_type": "code", + "id": "uBDvXpYzYnGj", + "colab": {} + }, + "cell_type": "code", + "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." + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "HQzaEQuJiW_d" + }, + "cell_type": "markdown", + "source": [ + "# TFRecords と `tf.Example` の使用法\n", + "\n", + "\n", + " \n", + " \n", + " \n", + "
\n", + " View on TensorFlow.org\n", + " \n", + " Run in Google Colab\n", + " \n", + " View source on GitHub\n", + "
" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "3pkUd_9IZCFO" + }, + "cell_type": "markdown", + "source": [ + "データの読み込みを効率的にするには、データをシリアライズし、連続的に読み込めるファイルのセット(各ファイルは100-200MB)に保存することが有効です。データをネットワーク経由で流そうとする場合には、特にそうです。また、データの前処理をキャッシュする際にも役立ちます。\n", + "\n", + "TFRecord形式は、バイナリレコードの系列を保存するための単純な形式です。\n", + "\n", + "[プロトコルバッファ](https://developers.google.com/protocol-buffers/) は、構造化データを効率的にシリアライズする、プラットフォームや言語に依存しないライブラリです。\n", + "\n", + "プロトコルメッセージは`.proto`という拡張子のファイルで定義されます。メッセージ型を識別する最も簡単な方法です。\n", + "\n", + "`tf.Example`メッセージ(あるいはプロトコルバッファ)は、`{\"string\": value}`形式のマッピングを表現する柔軟なメッセージ型です。これは、TensorFlow用に設計され、[TFX](https://www.tensorflow.org/tfx/)のような上位レベルのAPIで共通に使用されています。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "Ac83J0QxjhFt" + }, + "cell_type": "markdown", + "source": [ + "このノートブックでは、`tf.Example`メッセージの作成、パースと使用法をデモし、その後、`tf.Example`メッセージをパースして、`.tfrecord`に書き出し、その後読み取る方法を示します。\n", + "\n", + "注:こうした構造は有用ですが必ずそうしなければならなというものではありません。[`tf.data`](https://www.tensorflow.org/guide/datasets) を使っていて、それでもなおデータの読み込みが訓練のボトルネックである場合でなければ、既存のコードをTFRecordsを使用するために変更する必要はありません。データセットの性能改善のヒントは、 [Data Input Pipeline Performance](https://www.tensorflow.org/guide/performance/datasets)を参照ください。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "WkRreBf1eDVc" + }, + "cell_type": "markdown", + "source": [ + "## 設定" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "Ja7sezsmnXph", + "colab": {} + }, + "cell_type": "code", + "source": [ + "from __future__ import absolute_import\n", + "from __future__ import division\n", + "from __future__ import print_function\n", + "\n", + "import tensorflow as tf\n", + "tf.enable_eager_execution()\n", + "\n", + "import numpy as np\n", + "import IPython.display as display" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "e5Kq88ccUWQV" + }, + "cell_type": "markdown", + "source": [ + "## `tf.Example`" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "VrdQHgvNijTi" + }, + "cell_type": "markdown", + "source": [ + "### `tf.Example`用のデータ型" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "lZw57Qrn4CTE" + }, + "cell_type": "markdown", + "source": [ + "基本的には`tf.Example`は`{\"string\": tf.train.Feature}`というマッピングです。\n", + "\n", + "`tf.train.Feature`メッセージ型は次の3つの型のうち1つをとることができます([.proto file](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/feature.proto)を参照)。一般的なデータ型の多くは、これらの型のいずれかに強制的に変換することができます。\n", + "\n", + "1. `tf.train.BytesList` (次の型のデータを扱うことが可能)\n", + " - `string`\n", + " - `byte` \n", + "1. `tf.train.FloatList` (次の型のデータを扱うことが可能)\n", + " - `float` (`float32`)\n", + " - `double` (`float64`) \n", + "1. `tf.train.Int64List` (次の型のデータを扱うことが可能)\n", + " - `bool`\n", + " - `enum`\n", + " - `int32`\n", + " - `uint32`\n", + " - `int64`\n", + " - `uint64`" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "_e3g9ExathXP" + }, + "cell_type": "markdown", + "source": [ + "通常のTensorFlowの型を`tf.Example`互換の `tf.train.Feature`に変換するには、次のショートカット関数を使うことができます。\n", + "\n", + "どの関数も、1個のスカラー値を入力とし、上記の3つの`list`型のうちの一つを含む`tf.train.Feature`を返します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "mbsPOUpVtYxA", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# 下記の関数を使うと値を tf.Exampleと互換性の有る型に変換できる\n", + "\n", + "def _bytes_feature(value):\n", + " \"\"\"string / byte 型から byte_listを返す\"\"\"\n", + " return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))\n", + "\n", + "def _float_feature(value):\n", + " \"\"\"float / double 型から float_listを返す\"\"\"\n", + " return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))\n", + "\n", + "def _int64_feature(value):\n", + " \"\"\"bool / enum / int / uint 型から Int64_listを返す\"\"\"\n", + " return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "Wst0v9O8hgzy" + }, + "cell_type": "markdown", + "source": [ + "注:単純化のため、このサンプルではスカラー値の入力のみを扱っています。スカラー値ではない特徴を扱う最も簡単な方法は、`tf.serialize_tensor`を使ってテンソルをバイナリ文字列に変換する方法です。TensorFlowでは文字列はスカラー値として扱います。バイナリ文字列をテンソルに戻すには、`tf.parse_tensor`を使用します。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "vsMbkkC8xxtB" + }, + "cell_type": "markdown", + "source": [ + "上記の関数の使用例を下記に示します。入力が様々な型であるのに対して、出力が標準化されていることに注目してください。入力が、強制変換できない型であった場合、例外が発生します。(例:`_int64_feature(1.0)`はエラーとなります。`1.0`が浮動小数点数であるためで、代わりに`_float_feature`関数を使用すべきです)" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "hZzyLGr0u73y", + "colab": {} + }, + "cell_type": "code", + "source": [ + "print(_bytes_feature(b'test_string'))\n", + "print(_bytes_feature(u'test_bytes'.encode('utf-8')))\n", + "\n", + "print(_float_feature(np.exp(1)))\n", + "\n", + "print(_int64_feature(True))\n", + "print(_int64_feature(1))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "nj1qpfQU5qmi" + }, + "cell_type": "markdown", + "source": [ + "メッセージはすべて`.SerializeToString` を使ってバイナリ文字列にシリアライズすることができます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "5afZkORT5pjm", + "colab": {} + }, + "cell_type": "code", + "source": [ + "feature = _float_feature(np.exp(1))\n", + "\n", + "feature.SerializeToString()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "laKnw9F3hL-W" + }, + "cell_type": "markdown", + "source": [ + "### `tf.Example` メッセージの作成" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "b_MEnhxchQPC" + }, + "cell_type": "markdown", + "source": [ + "既存のデータから`tf.Example`を作成したいとします。実際には、データセットの出処はどこでも良いのですが、1件の観測記録から`tf.Example`メッセージを作る手順は同じです。\n", + "\n", + "1. 観測記録それぞれにおいて、各値は上記の関数を使って3種類の互換性のある型のうち1つだけを含む`tf.train.Feature`に変換する必要があります。\n", + "\n", + "1. 次に、特徴の名前を表す文字列と、#1で作ったエンコード済みの特徴量を対応させたマップ(ディクショナリ)を作成します。\n", + "\n", + "1. #2で作成したマップを[Featuresメッセージ](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/feature.proto#L85)に変換します。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "4EgFQ2uHtchc" + }, + "cell_type": "markdown", + "source": [ + "このノートブックでは、NumPyを使ってデータセットを作成します。\n", + "\n", + "このデータセットには4つの特徴量があります。\n", + "- `False` または `True`を表す論理値。出現確率は等しいものとします。\n", + "- `[0,5)`の範囲から一様にサンプリングした整数値。\n", + "- 整数特徴量をインデックスとした文字列テーブルを使って生成した文字列特徴量\n", + "- 標準正規分布からサンプリングした浮動小数点数。\n", + "\n", + "サンプルは上記の分布から独立して同じ様に分布した10,000件の観測記録からなるものとします。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "CnrguFAy3YQv", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# データセットに含まれる観測結果の件数\n", + "n_observations = int(1e4)\n", + "\n", + "# ブール特徴量 FalseまたはTrueとしてエンコードされている\n", + "feature0 = np.random.choice([False, True], n_observations)\n", + "\n", + "# 整数特徴量 0以上 5未満の乱数\n", + "feature1 = np.random.randint(0, 5, n_observations)\n", + "\n", + "# バイト文字列特徴量\n", + "strings = np.array([b'cat', b'dog', b'chicken', b'horse', b'goat'])\n", + "feature2 = strings[feature1]\n", + "\n", + "# 浮動小数点数特徴量 標準正規分布から発生\n", + "feature3 = np.random.randn(n_observations)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "aGrscehJr7Jd" + }, + "cell_type": "markdown", + "source": [ + "これらの特徴量は、`_bytes_feature`, `_float_feature`, `_int64_feature`のいずれかを使って`tf.Example`互換の型に強制変換されます。その後、エンコード済みの特徴量から`tf.Example`メッセージを作成できます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "RTCS49Ij_kUw", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def serialize_example(feature0, feature1, feature2, feature3):\n", + " \"\"\"\n", + " Creates a tf.Example message ready to be written to a file.\n", + " ファイル出力可能なtf.Exampleメッセージを作成する\n", + " \"\"\"\n", + "\n", + " # 特徴量名とtf.Example互換データ型との対応ディクショナリを作成\n", + "\n", + " feature = {\n", + " 'feature0': _int64_feature(feature0),\n", + " 'feature1': _int64_feature(feature1),\n", + " 'feature2': _bytes_feature(feature2),\n", + " 'feature3': _float_feature(feature3),\n", + " }\n", + "\n", + " # tf.train.Exampleを用いて特徴メッセージを作成\n", + "\n", + " example_proto = tf.train.Example(features=tf.train.Features(feature=feature))\n", + " return example_proto.SerializeToString()" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "XftzX9CN_uGT" + }, + "cell_type": "markdown", + "source": [ + "例えば、データセットに`[False, 4, bytes('goat'), 0.9876]`という1つの観測記録があるとします。`create_message()`を使うとこの観測記録から`tf.Example`メッセージを作成し印字できます。上記のように、観測記録一つ一つが`Features`メッセージとして書かれています。`tf.Example` [メッセージ](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/example.proto#L88)は、この`Features` メッセージを包むラッパーに過ぎないことに注意してください。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "N8BtSx2RjYcb", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# データセットからの観測記録の例\n", + "\n", + "example_observation = []\n", + "\n", + "serialized_example = serialize_example(False, 4, b'goat', 0.9876)\n", + "serialized_example" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "_pbGATlG6u-4" + }, + "cell_type": "markdown", + "source": [ + "メッセージをデコードするには、`tf.train.Example.FromString`メソッドを使用します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "dGim-mEm6vit", + "colab": {} + }, + "cell_type": "code", + "source": [ + "example_proto = tf.train.Example.FromString(serialized_example)\n", + "example_proto" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "id": "ZHV-ff6YHtDG", + "colab_type": "text" + }, + "cell_type": "markdown", + "source": [ + "## TFRecordフォーマットの詳細\n", + "\n", + "TFRecordファイルにはレコードのシーケンスが含まれます。このファイルはシーケンシャル読み取りのみが可能です。\n", + "\n", + "それぞれのレコードには、データを格納するためのバイト文字列とデータ長、そして整合性チェックのためのCRC32C(Castagnoli多項式を使った32ビットのCRC)ハッシュ値が含まれます。\n", + "\n", + "各レコードのフォーマットは下記の通りです。\n", + "\n", + " uint64 長さ\n", + " uint32 長さのマスク済みcrc32ハッシュ値\n", + " byte data[長さ]\n", + " uint32 データのマスク済みcrc32ハッシュ値\n", + "\n", + "複数のレコードが結合されてファイルを構成します。CRCについては[ここ](https://en.wikipedia.org/wiki/Cyclic_redundancy_check)に說明があります。CRCのマスクは下記のとおりです。\n", + "\n", + " masked_crc = ((crc >> 15) | (crc << 17)) + 0xa282ead8ul\n", + " \n", + "注:TFRecordファイルを作るのに、`tf.Example`を使わなければならないということはありません。`tf.Example`は、ディクショナリをバイト文字列にシリアライズする方法の1つです。エンコードされた画像データや、(`tf.io.serialize_tensor`を使ってシリアライズされ、`tf.io.parse_tensor`で読み込まれる)シリアライズされたテンソルもあります。その他のオプションについては、`tf.io`モジュールを参照してください。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "y-Hjmee-fbLH" + }, + "cell_type": "markdown", + "source": [ + "## `tf.data`を使用したTFRecordファイル" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "GmehkCCT81Ez" + }, + "cell_type": "markdown", + "source": [ + "`tf.data`モジュールには、TensorFlowでデータを読み書きするツールが含まれます。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "1FISEuz8ubu3" + }, + "cell_type": "markdown", + "source": [ + "### TFRecordファイルの書き出し\n", + "\n", + "データをデータセットにする最も簡単な方法は`from_tensor_slices`メソッドです。\n", + "\n", + "配列に適用すると、このメソッドはスカラー値のデータセットを返します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "mXeaukvwu5_-", + "colab": {} + }, + "cell_type": "code", + "source": [ + "tf.data.Dataset.from_tensor_slices(feature1)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "f-q0VKyZvcad" + }, + "cell_type": "markdown", + "source": [ + "配列のタプルに適用すると、タプルのデータセットが返されます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "H5sWyu1kxnvg", + "colab": {} + }, + "cell_type": "code", + "source": [ + "features_dataset = tf.data.Dataset.from_tensor_slices((feature0, feature1, feature2, feature3))\n", + "features_dataset" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "m1C-t71Nywze", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# データセットから1つのサンプルだけを取り出すには`take(1)` を使います。\n", + "for f0,f1,f2,f3 in features_dataset.take(1):\n", + " print(f0)\n", + " print(f1)\n", + " print(f2)\n", + " print(f3)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "mhIe63awyZYd" + }, + "cell_type": "markdown", + "source": [ + "`Dataset`のそれぞれの要素に関数を適用するには、`tf.data.Dataset.map`メソッドを使用します。\n", + "\n", + "マップされる関数はTensorFlowのグラフモードで動作する必要があります。関数は`tf.Tensors`を処理し、返す必要があります。`create_example`のような非テンソル関数は、互換性のため`tf.py_func`でラップすることができます。\n", + "\n", + "`tf.py_func`を使用する際には、シェイプと型は取得できないため、指定する必要があります。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "apB5KYrJzjPI", + "colab": {} + }, + "cell_type": "code", + "source": [ + "def tf_serialize_example(f0,f1,f2,f3):\n", + " tf_string = tf.py_func(\n", + " serialize_example, \n", + " (f0,f1,f2,f3), # pass these args to the above function.\n", + " tf.string) # the return type is `tf.string`.\n", + " return tf.reshape(tf_string, ()) # The result is a scalar" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "CrFZ9avE3HUF" + }, + "cell_type": "markdown", + "source": [ + "この関数をデータセットのそれぞれの要素に適用します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "VDeqYVbW3ww9", + "colab": {} + }, + "cell_type": "code", + "source": [ + "serialized_features_dataset = features_dataset.map(tf_serialize_example)\n", + "serialized_features_dataset" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "p6lw5VYpjZZC" + }, + "cell_type": "markdown", + "source": [ + "TFRecordファイルに書き出します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "vP1VgTO44UIE", + "colab": {} + }, + "cell_type": "code", + "source": [ + "filename = 'test.tfrecord'\n", + "writer = tf.data.experimental.TFRecordWriter(filename)\n", + "writer.write(serialized_features_dataset)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "6aV0GQhV8tmp" + }, + "cell_type": "markdown", + "source": [ + "### TFRecordファイルの読み込み" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "o3J5D4gcSy8N" + }, + "cell_type": "markdown", + "source": [ + "`tf.data.TFRecordDataset`クラスを使ってTFRecordファイルを読み込むこともできます。\n", + "\n", + "`tf.data`を使ってTFRecordファイルを取り扱う際の詳細については、[こちら](https://www.tensorflow.org/guide/datasets#consuming_tfrecord_data)を参照ください。 \n", + "\n", + "`TFRecordDataset`を使うことは、入力データを標準化し、パフォーマンスを最適化するのに有用です。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "6OjX6UZl-bHC", + "colab": {} + }, + "cell_type": "code", + "source": [ + "filenames = [filename]\n", + "raw_dataset = tf.data.TFRecordDataset(filenames)\n", + "raw_dataset" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "6_EQ9i2E_-Fz" + }, + "cell_type": "markdown", + "source": [ + "この時点で、データセットにはシリアライズされた`tf.train.Example`メッセージが含まれています。データセットをイテレートすると、スカラーの文字列テンソルが返ってきます。\n", + "\n", + "`.take`メソッドを使って最初の10レコードだけを表示します。\n", + "\n", + "注:`tf.data.Dataset`をイテレートできるのは、Eager Executionが有効になっている場合のみです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "hxVXpLz_AJlm", + "colab": {} + }, + "cell_type": "code", + "source": [ + "for raw_record in raw_dataset.take(10):\n", + " print(repr(raw_record))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "W-6oNzM4luFQ" + }, + "cell_type": "markdown", + "source": [ + "これらのテンソルは下記の関数でパースできます。\n", + "\n", + "注:ここでは、`feature_description`が必要です。データセットはグラフ実行を使用するため、この記述を使ってシェイプと型を構築するのです。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "zQjbIR1nleiy", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# 特徴の記述\n", + "feature_description = {\n", + " 'feature0': tf.FixedLenFeature([], tf.int64, default_value=0),\n", + " 'feature1': tf.FixedLenFeature([], tf.int64, default_value=0),\n", + " 'feature2': tf.FixedLenFeature([], tf.string, default_value=''),\n", + " 'feature3': tf.FixedLenFeature([], tf.float32, default_value=0.0),\n", + "}\n", + "\n", + "def _parse_function(example_proto):\n", + " # 上記の記述を使って入力のtf.Exampleを処理\n", + " return tf.parse_single_example(example_proto, feature_description)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "gWETjUqhEQZf" + }, + "cell_type": "markdown", + "source": [ + "あるいは、`tf.parse example`を使ってバッチ全体を一度にパースします。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "AH73hav6Bnmg" + }, + "cell_type": "markdown", + "source": [ + "`tf.data.Dataset.map`メソッドを使って、データセットの各アイテムにこの関数を適用します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "6Ob7D-zmBm1w", + "colab": {} + }, + "cell_type": "code", + "source": [ + "parsed_dataset = raw_dataset.map(_parse_function)\n", + "parsed_dataset " + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "sNV-XclGnOvn" + }, + "cell_type": "markdown", + "source": [ + "Eager Execution を使ってデータセット中の観測記録を表示します。このデータセットには10,000件の観測記録がありますが、最初の10個だけ表示します。 \n", + "データは特徴量のディクショナリの形で表示されます。それぞれの項目は`tf.Tensor`であり、このテンソルの`numpy` 要素は特徴量を表します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "x2LT2JCqhoD_", + "colab": {} + }, + "cell_type": "code", + "source": [ + "for parsed_record in parsed_dataset.take(10):\n", + " print(repr(raw_record))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "Cig9EodTlDmg" + }, + "cell_type": "markdown", + "source": [ + "ここでは、`tf.parse_example` が`tf.Example`のフィールドを通常のテンソルに展開しています。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "jyg1g3gU7DNn" + }, + "cell_type": "markdown", + "source": [ + "## tf.python_ioを使ったTFRecordファイル" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "3FXG3miA7Kf1" + }, + "cell_type": "markdown", + "source": [ + "`tf.python_io`モジュールには、TFRecordファイルの読み書きのための純粋なPython関数も含まれています。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "CKn5uql2lAaN" + }, + "cell_type": "markdown", + "source": [ + "### TFRecordファイルの書き出し" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "LNW_FA-GQWXs" + }, + "cell_type": "markdown", + "source": [ + "次にこの10,000件の観測記録を`test.tfrecords`ファイルに出力します。観測記録はそれぞれ`tf.Example`メッセージに変換され、ファイルに出力されます。その後、`test.tfrecords`ファイルが作成されたことを確認することができます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "MKPHzoGv7q44", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# `tf.Example`観測記録をファイルに出力\n", + "with tf.python_io.TFRecordWriter(filename) as writer:\n", + " for i in range(n_observations):\n", + " example = serialize_example(feature0[i], feature1[i], feature2[i], feature3[i])\n", + " writer.write(example)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "EjdFHHJMpUUo", + "colab": {} + }, + "cell_type": "code", + "source": [ + "!ls" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "wtQ7k0YWQ1cz" + }, + "cell_type": "markdown", + "source": [ + "### TFRecordファイルの読み込み" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "utkozytkQ-2K" + }, + "cell_type": "markdown", + "source": [ + "モデルに入力にするため、このデータを読み込みたいとしましょう。\n", + "\n", + "次の例では、データをそのまま、`tf.Example`メッセージとしてインポートします。これは、ファイルが期待されるデータを含んでいるかを確認するのに役に立ちます。これは、また、入力データがTFRecordとして保存されているが、[この](https://www.tensorflow.org/guide/datasets#consuming_numpy_arrays)例のようにNumPyデータ(またはそれ以外のデータ型)として入力したい場合に有用です。このコーディング例では値そのものを読み取れるからです。\n", + "\n", + "入力ファイルの中のTFRecordをイテレートして、`tf.Example`メッセージを取り出し、その中の値を読み取って保存できます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "36ltP9B8OezA", + "colab": {} + }, + "cell_type": "code", + "source": [ + "record_iterator = tf.python_io.tf_record_iterator(path=filename)\n", + "\n", + "for string_record in record_iterator:\n", + " example = tf.train.Example()\n", + " example.ParseFromString(string_record)\n", + " \n", + " print(example)\n", + " \n", + " # Exit after 1 iteration as this is purely demonstrative.\n", + " # 純粋にデモであるため、イテレーションの1回目で終了\n", + " break" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "i3uquiiGTZTK" + }, + "cell_type": "markdown", + "source": [ + "(上記で作成した`tf.Example`型の)`example`オブジェクトの特徴量は(他のプロトコルバッファメッセージと同様に)ゲッターを使ってアクセス可能です。`example.features`は`repeated feature`メッセージを返し、`feature`メッセージをを取得すると(Pythonのディクショナリとして保存された)特徴量の名前と特徴量の値のマッピングが得られます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "-UNzS7vsUBs0", + "colab": {} + }, + "cell_type": "code", + "source": [ + "print(dict(example.features.feature))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "u1M-WrbqUUVW" + }, + "cell_type": "markdown", + "source": [ + "このディクショナリから、指定した値をディクショナリとして得ることができます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "2yCBu70IUb2H", + "colab": {} + }, + "cell_type": "code", + "source": [ + "print(example.features.feature['feature3'])" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "4dw6_OI9UiNZ" + }, + "cell_type": "markdown", + "source": [ + "次に、ゲッターを使って値にアクセスできます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "BdDYjDnDUlFe", + "colab": {} + }, + "cell_type": "code", + "source": [ + "print(example.features.feature['feature3'].float_list.value)" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "S0tFDrwdoj3q" + }, + "cell_type": "markdown", + "source": [ + "## ウォークスルー: 画像データの読み書き" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "rjN2LFxFpcR9" + }, + "cell_type": "markdown", + "source": [ + "以下は、TFRecordを使って画像データを読み書きする方法の例です。この例の目的は、データ(この場合は画像)を入力し、そのデータをTFRecordファイルに書き込んで、再びそのファイルを読み込み、画像を表示するという手順を最初から最後まで示すことです。\n", + "\n", + "これは、例えば、同じ入力データセットを使って複数のモデルを構築するといった場合に役立ちます。画像データをそのまま保存する代わりに、TFRecord形式に前処理しておき、その後の処理やモデル構築に使用することができます。\n", + "\n", + "まずは、雪の中の猫の[画像](https://commons.wikimedia.org/wiki/File:Felis_catus-cat_on_snow.jpg)と、ニューヨーク市にあるウイリアムズバーグ橋の [写真](https://upload.wikimedia.org/wikipedia/commons/f/fe/New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg)をダウンロードしましょう。" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "5Lk2qrKvN0yu" + }, + "cell_type": "markdown", + "source": [ + "### 画像の取得" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "3a0fmwg8lHdF", + "colab": {} + }, + "cell_type": "code", + "source": [ + "cat_in_snow = tf.keras.utils.get_file('320px-Felis_catus-cat_on_snow.jpg', 'https://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/Felis_catus-cat_on_snow.jpg/320px-Felis_catus-cat_on_snow.jpg')\n", + "williamsburg_bridge = tf.keras.utils.get_file('194px-New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg','https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg/194px-New_East_River_Bridge_from_Brooklyn_det.4a09796u.jpg')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "7aJJh7vENeE4", + "colab": {} + }, + "cell_type": "code", + "source": [ + "display.display(display.Image(filename=cat_in_snow))\n", + "display.display(display.HTML('Image cc-by: Von.grzanka'))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "KkW0uuhcXZqA", + "colab": {} + }, + "cell_type": "code", + "source": [ + "display.display(display.Image(filename=williamsburg_bridge))\n", + "display.display(display.HTML('source'))" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "VSOgJSwoN5TQ" + }, + "cell_type": "markdown", + "source": [ + "### TFRecordファイルの書き出し" + ] + }, + { + "metadata": { + "colab_type": "text", + "id": "Azx83ryQEU6T" + }, + "cell_type": "markdown", + "source": [ + "上記で行ったように、この特徴量を`tf.Example`と互換のデータ型にエンコードできます。この場合には、生画像のバイト文字列を特徴として保存するだけではなく、縦、横のサイズにチャネル数、更に画像を保存する際に猫の画像と橋の画像を区別するための`label`特徴量を付け加えます。猫の画像には`0`を、橋の画像には`1`を使うことにしましょう。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "kC4TS1ZEONHr", + "colab": {} + }, + "cell_type": "code", + "source": [ + "image_labels = {\n", + " cat_in_snow : 0,\n", + " williamsburg_bridge : 1,\n", + "}" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "c5njMSYNEhNZ", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# 猫の画像を使った例\n", + "image_string = open(cat_in_snow, 'rb').read()\n", + "\n", + "label = image_labels[cat_in_snow]\n", + "\n", + "# 関連する特徴量のディクショナリを作成\n", + "def image_example(image_string, label):\n", + " image_shape = tf.image.decode_jpeg(image_string).shape\n", + "\n", + " feature = {\n", + " 'height': _int64_feature(image_shape[0]),\n", + " 'width': _int64_feature(image_shape[1]),\n", + " 'depth': _int64_feature(image_shape[2]),\n", + " 'label': _int64_feature(label),\n", + " 'image_raw': _bytes_feature(image_string),\n", + " }\n", + "\n", + " return tf.train.Example(features=tf.train.Features(feature=feature))\n", + "\n", + "for line in str(image_example(image_string, label)).split('\\n')[:15]:\n", + " print(line)\n", + "print('...')" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "2G_o3O9MN0Qx" + }, + "cell_type": "markdown", + "source": [ + "ご覧のように、すべての特徴量が`tf.Example`メッセージに保存されました。上記のコードを関数化し、このサンプルメッセージを`images.tfrecords`ファイルに書き込みます。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "qcw06lQCOCZU", + "colab": {} + }, + "cell_type": "code", + "source": [ + "# 生の画像をimages.tfrecordsファイルに書き出す\n", + "# まず、2つの画像をtf.Exampleメッセージに変換し、\n", + "# 次に.tfrecordsファイルに書き出す\n", + "with tf.python_io.TFRecordWriter('images.tfrecords') as writer:\n", + " for filename, label in image_labels.items():\n", + " image_string = open(filename, 'rb').read()\n", + " tf_example = image_example(image_string, label)\n", + " writer.write(tf_example.SerializeToString())" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "code", + "id": "yJrTe6tHPCfs", + "colab": {} + }, + "cell_type": "code", + "source": [ + "!ls" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "jJSsCkZLPH6K" + }, + "cell_type": "markdown", + "source": [ + "### TFRecordファイルの読み込み\n", + "\n", + "これで、`images.tfrecords`ファイルができました。このファイルの中のレコードをイテレートし、書き込んだものを読み出します。このユースケースでは、画像を復元するだけなので、必要なのは生画像の文字列だけです。上記のゲッター、すなわち、`example.features.feature['image_raw'].bytes_list.value[0]`を使って抽出することができます。猫と橋のどちらであるかを決めるため、ラベルも使用します。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "M6Cnfd3cTKHN", + "colab": {} + }, + "cell_type": "code", + "source": [ + "raw_image_dataset = tf.data.TFRecordDataset('images.tfrecords')\n", + "\n", + "# 特徴量を記述するディクショナリを作成\n", + "image_feature_description = {\n", + " 'height': tf.FixedLenFeature([], tf.int64),\n", + " 'width': tf.FixedLenFeature([], tf.int64),\n", + " 'depth': tf.FixedLenFeature([], tf.int64),\n", + " 'label': tf.FixedLenFeature([], tf.int64),\n", + " 'image_raw': tf.FixedLenFeature([], tf.string),\n", + "}\n", + "\n", + "def _parse_image_function(example_proto):\n", + " # 入力のtf.Exampleのプロトコルバッファを上記のディクショナリを使って解釈\n", + " return tf.parse_single_example(example_proto, image_feature_description)\n", + "\n", + "parsed_image_dataset = raw_image_dataset.map(_parse_image_function)\n", + "parsed_image_dataset" + ], + "execution_count": 0, + "outputs": [] + }, + { + "metadata": { + "colab_type": "text", + "id": "0PEEFPk4NEg1" + }, + "cell_type": "markdown", + "source": [ + "TFRecordファイルから画像を復元しましょう。" + ] + }, + { + "metadata": { + "colab_type": "code", + "id": "yZf8jOyEIjSF", + "colab": {} + }, + "cell_type": "code", + "source": [ + "for image_features in parsed_image_dataset:\n", + " image_raw = image_features['image_raw'].numpy()\n", + " display.display(display.Image(data=image_raw))" + ], + "execution_count": 0, + "outputs": [] + } + ] +} \ No newline at end of file