TensorFlow-Slimを逆にたどっていきながら理解する【データセット編】

TensorFlowの便利モジュール「Slim」ですが、使用するデータセットにはお決まりがありますので、まとめておきます。

なお、学習の仕方に関しては[こちら](https://qiita.com/mine820/items/42dfe5ed7249e239406c)を参照してください。

# 前提情報
TensorFlow-Slimは「tensorflow/contrib/slim」にあります。
そこで、すでに

```python
import tensorflow as tf
import tensorflow.contrib.slim as slim
```
が行われているものとします。

# 呼ばれ方
通常は以下のように呼ばれて使用されます。

```python
# 2次元畳み込み
net = slim.conv2d(images, 20, [5,5], scope='conv1')

# 途中省略

# one-hot表現に変換
one_hot_labels = slim.one_hot_encoding(
    labels,
    dataset.num_classes)
# クロスエントロピー（softmax）
slim.losses.softmax_cross_entropy(
    net,
    one_hot_labels)
```

「images」と「labels」がデータセットの組になります。
それぞれ内容や作り方を見ていきます。

## 内容
まず「images」ですが、以下のような構造のnumpy配列（float32）になっています。
※画像の場合

(バッチサイズ, 縦, 横, チャンネル)

一方「labels」の方は、以下のような構造のnumpy配列（int64）になっています。

（バッチサイズ）

## 作り方
### バッチ分割
まずバッチサイズの配列にするには、以下の関数を使用します。

```python
images, labels = tf.train.batch(
    [image, label],
    batch_size=batch_size,
    allow_smaller_final_batch=True)
```

「image」は（縦、横、チャンネル）のnumpy配列、「label」は値になります。
指定したバッチサイズで区切られたデータを作成します。

さらに、「image」と「label」を見ていきます。

### 「image」と「label」の取得
```python
data_provider = slim.dataset_data_provider.DatasetDataProvider(dataset)

image, label = data_provider.get(['image', 'label'])
```

「dataset」から、「image」と「label」を取り出しています。
これだけではよくわからないので、「dataset」が何なのか見ていきます。

### 「dataset」とは
データセットの情報を保持しています。
以下の関数で作られます。

```python
slim.dataset.Dataset(
      data_sources=file_pattern,
      reader=reader,
      decoder=decoder,
      num_samples=_SPLITS_TO_SIZES[split_name],
      num_classes=_NUM_CLASSES,
      items_to_descriptions=_ITEMS_TO_DESCRIPTIONS,
      labels_to_names=labels_to_names)
```

「data_sources」は読み込むデータセットファイル名になります。
今回は事前に作成したこととします。（詳細に関しては省略）
事前に作成しない場合は、ここで取得した内容と同じものを作成すればOKです。
なお、通常は学習用（'train'）／評価用（'test'）／ラベル用（値と文字列の組み合わせ）と3ファイル用意します。

画像ごとに

- image/encoded　：画像データ
- image/format　：画像フォーマット
- image/class/label　：ラベル
- image/height　：高さ
- image/width　：幅

が設定されています。

「reader」と「decoder」は、通常それぞれ「tf.TFRecordReader」「slim.tfexample_decoder.TFExampleDecoder(keys_to_features, items_to_handlers)」になります。

「keys_to_features」には

```python
keys_to_features = {
    'image/encoded': tf.FixedLenFeature((), tf.string, default_value=''),
    'image/format': tf.FixedLenFeature((), tf.string, default_value='raw'),
    'image/class/label': tf.FixedLenFeature(
        [1], tf.int64, default_value=tf.zeros([1], dtype=tf.int64)),
}
```
といった感じで読み込む内容が定義されており、「items_to_handlers」には、

```python
items_to_handlers = {
    'image': slim.tfexample_decoder.Image(shape=[28, 28, 1], channels=1),
    'label': slim.tfexample_decoder.Tensor('image/class/label', shape=[]),
}
```
といった感じで、読み込むデータの種類が定義されています。
（ここで「image」と「label」に分けています）

「image/encoded」と「image/format」には、バイナリデータを「tf.train.BytesList()」および「tf.train.Feature()」で変換した値が入っています。
「image/class/label」には、バイナリデータを「tf.train.Int64List()」および「tf.train.Feature()」で変換した値が入っています。

「num_samples」には、画像数を設定します。

```python
_SPLITS_TO_SIZES = {'train': 60000, 'test': 10000}
```
今回は「train」の方を使用します。

「num_classes」はクラス数になります。
例えばMNISTであれは10種類にクラス分けしますので、10となります。

「items_to_descriptions」は画像データとラベルの説明文を設定します。
例えばこんな感じになります。

```python
_ITEMS_TO_DESCRIPTIONS = {
    'image': 'A [28 x 28 x 1] grayscale image.',
    'label': 'A single integer between 0 and 9',
}
```

最後に「labels_to_names」ですが、これはラベル用のファイルから読み込んだ、値と文字列の組み合わせになります。

# ポイント
この時点ではまだファイルは読み込んでおらず、「slim.learning.train()」等で読み込みます。
一旦ファイルにしておかないやり方だと、そのあたりがうまくいかないので、素直にデータセットを事前に作成して、Slimの関数で読み込むようにしたほうがよいです。

なお、このデータセットの作成／読み込み方法は、すでにお決まりのようになっているので、サンプル等からコピーして、使用するデータに合わせて修正するのがよいかと思います。
[ここ](https://github.com/mnoukhov/tf-slim-mnist)の

- model.py [データセット読み込み]
- datasets/mnist.py [データセット読み込み]
- datasets/dataset_utils.py [共通]
- datasets/download_and_convert_mnist.py [データセット作成]

あたりが参考になります。