{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Introduction\n\n**Main Topic**\nThis notebook is for anyone who want to build a tfrecords dataset using [Cassava Leaf Disease competition datasets](https://www.kaggle.com/c/cassava-leaf-disease-classification)\n\nI'll implement how to build train and validation tfrecords\n\n\n**References**\n- **Tensorflow Official Docs** [TFRecord and tf.train.Example](https://www.tensorflow.org/tutorials/load_data/tfrecord)\n- **Yaroslav Isaienkov:** [Cassava Leaf Disease - Exploratory Data Analysis](https://www.kaggle.com/ihelon/cassava-leaf-disease-exploratory-data-analysis)\n- **Jesse Mostipak, Phil Culliton:** [Getting Started: TPUs + Cassava Leaf Disease](https://www.kaggle.com/jessemostipak/getting-started-tpus-cassava-leaf-disease)\n- **H. Noh, A. Araujo, J. Sim, T. Weyand and B. Han:** [Deep Local and Global Image Features Implement](https://github.com/tensorflow/models/tree/master/research/delf)"},{"metadata":{},"cell_type":"markdown","source":"# Set up environment"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"import os\nimport tensorflow as tf\nimport numpy as np\nimport pandas as pd\nimport functools\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom glob import glob","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"import os\nimport tensorflow as tf\nimport numpy as np\nimport pandas as pd\nimport functools\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom glob import glob"},{"metadata":{"trusted":true},"cell_type":"code","source":"cfg = {\n    'data_path': '../input/cassava-leaf-disease-classification',\n    'train_prefix': 'train',\n    'valid_prefix': 'valid',\n    'validation_ratio': 0.2,\n    'num_shards' : 4\n}\n\ntrain_df = pd.read_csv(os.path.join(cfg['data_path'], 'train.csv'))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Split train-validation list"},{"metadata":{"trusted":true},"cell_type":"code","source":"TRAIN_LISTS, VALID_LISTS = train_test_split(train_df, test_size=cfg['validation_ratio'], random_state=5)\nTRAIN_LISTS = TRAIN_LISTS.reset_index()\nVALID_LISTS = VALID_LISTS.reset_index()\nprint(f'train: {len(TRAIN_LISTS)}, validation: {len(VALID_LISTS)}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Set up `tf.train.Example`\n\n### Data types for `tf.train.Example`\n\nFundamentally, a `tf.train.Example` is a `{\"string\": tf.train.Feature}` mapping.\n\nThe `tf.train.Feature` message type can accept one of the following three types (See the [`.proto` file](https://github.com/tensorflow/tensorflow/blob/master/tensorflow/core/example/feature.proto) for reference). Most other generic types can be coerced into one of these:\n\n1. `tf.train.BytesList` (the following types can be coerced)\n\n  - `string`\n  - `byte`\n\n1. `tf.train.FloatList` (the following types can be coerced)\n\n  - `float` (`float32`)\n  - `double` (`float64`)\n\n1. `tf.train.Int64List` (the following types can be coerced)\n\n  - `bool`\n  - `enum`\n  - `int32`\n  - `uint32`\n  - `int64`\n  - `uint64`"},{"metadata":{},"cell_type":"markdown","source":"In order to convert a standard TensorFlow type to a `tf.train.Example`-compatible `tf.train.Feature`, you can use the shortcut functions below. Note that each function takes a scalar input value and returns a `tf.train.Feature` containing one of the three `list` types above:"},{"metadata":{"trusted":true},"cell_type":"code","source":"# The following functions can be used to convert a value to a type compatible\n# with tf.train.Example.\n\ndef _bytes_feature(value):\n  \"\"\"Returns a bytes_list from a string / byte.\"\"\"\n  if isinstance(value, type(tf.constant(0))):\n    value = value.numpy() # BytesList won't unpack a string from an EagerTensor.\n  return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))\n\ndef _float_feature(value):\n  \"\"\"Returns a float_list from a float / double.\"\"\"\n  return tf.train.Feature(float_list=tf.train.FloatList(value=[value]))\n\ndef _int64_feature(value):\n  \"\"\"Returns an int64_list from a bool / enum / int / uint.\"\"\"\n  return tf.train.Feature(int64_list=tf.train.Int64List(value=[value]))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Note: To stay simple, this example only uses scalar inputs. The simplest way to handle non-scalar features is to use `tf.io.serialize_tensor` to convert tensors to binary-strings. Strings are scalars in tensorflow. Use `tf.io.parse_tensor` to convert the binary-string back to a tensor."},{"metadata":{},"cell_type":"markdown","source":"# Define write_tfrecord\nYou can set [shards](https://www.tensorflow.org/api_docs/python/tf/data/TFRecordDataset#shard) user `num_shards` if you want."},{"metadata":{"trusted":true},"cell_type":"code","source":"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': _bytes_feature(image_string),\n  }\n\n  return tf.train.Example(features=tf.train.Features(feature=feature))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Define `_write_tfrecord`\nYou can set [shards](https://www.tensorflow.org/api_docs/python/tf/data/TFRecordDataset#shard) user `num_shards` if you want."},{"metadata":{"trusted":true},"cell_type":"code","source":"def _write_tfrecord(output_prefix, file_list, num_shards=cfg['num_shards']):\n    spacing = np.linspace(0, len(file_list), num_shards + 1, dtype=np.int)\n    \n    for shard in range(num_shards):\n        output_file = f'{output_prefix}-00{shard + 1}-00{num_shards}.tfrec'\n        print('Processing shard ', shard + 1, ' and writing file ', output_file)\n        \n        with tf.io.TFRecordWriter(output_file) as writer:\n            for i in range(spacing[shard], spacing[shard + 1]):\n                image_string = open(os.path.join(cfg['data_path'], 'train_images', file_list['image_id'][i]), 'rb').read()\n                tf_example = image_example(image_string, file_list['label'][i])\n                writer.write(tf_example.SerializeToString())","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"_write_tfrecord(cfg['train_prefix'], TRAIN_LISTS)\n_write_tfrecord(cfg['valid_prefix'], VALID_LISTS)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# load tfrecords\n\nYou can customize below modules to build tfrecords"},{"metadata":{"trusted":true},"cell_type":"code","source":"class _DataAugmentationParams(object):\n  \"\"\"Default parameters for augmentation.\"\"\"\n  # The following are used for training.\n  min_object_covered = 0.1\n  aspect_ratio_range_min = 3. / 4\n  aspect_ratio_range_max = 4. / 3\n  area_range_min = 0.08\n  area_range_max = 1.0\n  max_attempts = 100\n  update_labels = False\n  # 'central_fraction' is used for central crop in inference.\n  central_fraction = 0.875\n\n  random_reflection = False\n  input_rows = 224\n  input_cols = 224","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def NormalizeImages(images, pixel_value_scale=0.5, pixel_value_offset=0.5):\n  ## Normalize pixel values in image.\n  images = tf.cast(images, tf.float32)\n  normalized_images = tf.math.divide(\n      tf.subtract(images, pixel_value_offset), pixel_value_scale)\n  return normalized_images\n\n\ndef _ImageNetCrop(image):\n  ##Imagenet-style crop with random bbox and aspect ratio.\n  params = _DataAugmentationParams()\n  bbox = tf.constant([0.0, 0.0, 1.0, 1.0], dtype=tf.float32, shape=[1, 1, 4])\n  (bbox_begin, bbox_size, _) = tf.image.sample_distorted_bounding_box(\n      tf.shape(image),\n      bounding_boxes=bbox,\n      min_object_covered=params.min_object_covered,\n      aspect_ratio_range=(params.aspect_ratio_range_min,\n                          params.aspect_ratio_range_max),\n      area_range=(params.area_range_min, params.area_range_max),\n      max_attempts=params.max_attempts,\n      use_image_if_no_bounding_boxes=True)\n  cropped_image = tf.slice(image, bbox_begin, bbox_size)\n  cropped_image.set_shape([None, None, 3])\n\n  cropped_image = tf.image.resize(\n      cropped_image, [params.input_rows, params.input_cols], method='area')\n  if params.random_reflection:\n    cropped_image = tf.image.random_flip_left_right(cropped_image)\n\n  return cropped_image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def _ParseFunction(example, name_to_features, image_size, augmentation):\n  \"\"\"Parse a single TFExample to get the image and label and process the image.\n  Args:\n    example: a `TFExample`.\n    name_to_features: a `dict`. The mapping from feature names to its type.\n    image_size: an `int`. The image size for the decoded image, on each side.\n    augmentation: a `boolean`. True if the image will be augmented.\n  Returns:\n    image: a `Tensor`. The processed image.\n    label: a `Tensor`. The ground-truth label.\n  \"\"\"\n  parsed_example = tf.io.parse_single_example(example, name_to_features)\n  # Parse to get image.\n  image = parsed_example['image']\n  image = tf.io.decode_jpeg(image)\n  image = NormalizeImages(\n      image, pixel_value_scale=128.0, pixel_value_offset=128.0)\n  if augmentation:\n    image = _ImageNetCrop(image)\n  else:\n    image = tf.image.resize(image, [image_size, image_size])\n    image.set_shape([image_size, image_size, 3])\n  # Parse to get label.\n  label = parsed_example['label']\n\n  return image, label","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def CreateDataset(file_pattern,\n                  image_size=224,\n                  batch_size=32,\n                  augmentation=False,\n                  seed=0):\n  \"\"\"Creates a dataset.\n  Args:\n    file_pattern: str, file pattern of the dataset files.\n    image_size: int, image size.\n    batch_size: int, batch size.\n    augmentation: bool, whether to apply augmentation.\n    seed: int, seed for shuffling the dataset.\n  Returns:\n     tf.data.TFRecordDataset.\n  \"\"\"\n\n  filenames = tf.io.gfile.glob(file_pattern)\n\n  dataset = tf.data.TFRecordDataset(filenames)\n  dataset = dataset.repeat().shuffle(buffer_size=100, seed=seed)\n\n  # Create a description of the features.\n  feature_description = {\n    'height': tf.io.FixedLenFeature([], tf.int64),\n    'width': tf.io.FixedLenFeature([], tf.int64),\n    'depth': tf.io.FixedLenFeature([], tf.int64),\n    'label': tf.io.FixedLenFeature([], tf.int64),\n    'image': tf.io.FixedLenFeature([], tf.string),\n}  \n\n  customized_parse_func = functools.partial(\n      _ParseFunction,\n      name_to_features=feature_description,\n      image_size=image_size,\n      augmentation=augmentation)\n  dataset = dataset.map(customized_parse_func)\n  dataset = dataset.batch(batch_size)\n\n  return dataset","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_dataset = CreateDataset('train-*')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"print(train_dataset)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"for image, label in train_dataset.take(1):\n    plt.imshow(image[0])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}