機械学習（ディープラーニング）初心者のためのデータセットにまつわる話

# はじめに

大学３年生の後期にディープラーニングのプログラムを書き始めてから、すでに１年と半年が経とうとしているリッキーです。（書籍などで概念についての勉強を始めてからは約２年経過しようとしています。）

ディープラーニングを学び始めてから今に至るまでに沢山の「引っ掛かりポイント」がありました。そしてそれらのポイントに焦点を当てた記事はあまり見かけることがありませんでした。おそらく、現在においてディープラーニングを実装している方々は、プログラム技術や数学的知識がそれなりに備わっている人が大半であるからだと思われます。

しかし、現在は第３次人工知能ブームと言われ、これからどんどん人工知能に興味を持った大学生がディープラーニングの学習を始めると考えられます。


そのため、この記事はそんな**「ディープラーニングに興味を持って勉強を始めて見たけど、よくわからないところがある」**と言うディープラーニング初心者がおそらく一番陥るであろう、独自データセットの読み込みにまつわる解説を行なっていきます。

## 環境

　この記事は以下の環境で作業を行なっていきます。フレームワークはTensorFlowを用いていますが、コードを適宜変更していただければChainerやNNablaでも全く問題ないです。

- OS: Windows10
- CPU: Intel(R) Core(TM) i7-6700k
- GPU: GTX1080
- メモリ: 32.0GiB
- ディスク: SSD 256GiB
- 仮想環境: Anaconda(Python3.5)
- フレームワーク: TensorFlow1.4

#データセットMNISTの構造

##なぜ独自データセットの作成が難しいのか

そもそも、なぜ独自のデータセットの作成に躓くのか。それは、各種フレームワーク（TensorFlowやChainer）のサンプルで用いられるMNISTのデータセットは「よくわからない一行のコードでなぜかデータセットとして読み込まれるから」だと思います。ここで機械学習に詳しい知人や友人がいれば質問ができるのですが、独学で機械学習をおこなっている人は「MNISTが勝手に読み込まれているけどデータの中身はどうなっているんだ？」となってしまうのです。

##MNISTの中身をのぞいてみる

MNISTがよくわからない一番の原因は、MNISTの構造が良くわからないことだを思います。そのため、TensorFlowサンプルの一部を使ってMNISTを導入し、その中身を見ていきます。なお、TensorFlowの導入は[公式のチュートリアル（英語）](https://www.tensorflow.org/install/)が非常にわかりやすいです。

###MNISTの中身
コマンドプロンプト（Macで言うターミナル）で確認をしています。

```
(TensorFlow)>python
Python 3.5.4 |Anaconda, Inc.| (default, Nov  8 2017, 14:34:30) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from tensorflow.examples.tutorials.mnist import input_data
>>> mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
Extracting MNIST_data/train-images-idx3-ubyte.gz
Extracting MNIST_data/train-labels-idx1-ubyte.gz
Extracting MNIST_data/t10k-images-idx3-ubyte.gz
Extracting MNIST_data/t10k-labels-idx1-ubyte.gz
>>> batch_xs, batch_ys = mnist.train.next_batch(100)
>>> batch_xs.shape
(100, 784)
>>> batch_xs
array([[ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       ...,
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.],
       [ 0.,  0.,  0., ...,  0.,  0.,  0.]], dtype=float32)
>>> batch_ys.shape
(100, 10)
>>> batch_ys
array([[ 0.,  0.,  0.,  0.,  0.,  0.,  1.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.],
       ＜省略＞
       [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  1.]])
```
最初の2行、つまり

```python
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
```
この部分でTensorFlowサンプル内のMNISTをインポートした後に、mnist変数に格納しています。
そして次の一行

```python
batch_xs, batch_ys = mnist.train.next_batch(100)
```
ここで、x（学習データ）y（ラベル）をそれぞれ100個ずつ取り出して格納しています。
それらの中身を見ていくと、xは1枚当たり784個のピクセルデータを100枚分保持していることになり、yは正解となるインデックスの値のみ1にした0~9の配列を100個分持っていることになります。

そしてチュートリアルでは、それらのデータを

```python
sess.run(train_step, feed_dict={x: batch_xs, y_: batch_ys})
```
ｘとｙの辞書にして渡しています。


つまり独自のデータセットの場合では、画像を読み込んだ後に（枚数、ピクセルデータ）の配列にして渡せばよいということがわかります。

#カラー画像をCNNで用いるとき

しかし、画像データを１次元配列としてこのまま使用することは最善ではありません。CNN（畳み込みニューラルネットワーク）では、画像は２次元のまま使用することが非常に多いです。そのため、[TensorFlowのCNNチュートリアル](https://www.tensorflow.org/get_started/mnist/pros)では、ネットワークの初めに、

```python
x_image = tf.reshape(x, [-1, 28, 28, 1])
```
とすることで、２次元配列に変換しています。

しかし、わざわざネットワークの中で変形するのならば、そもそも画像の読み込み時に２次元配列で読み込んだ方がわかりやすいです。そこで、Pillowを使って画像を読み込んでみます。

##Pillowで画像の読み込み
今回使用する画像は、この画像です。
![ad_cal.jpg](https://qiita-image-store.s3.amazonaws.com/0/145723/88512892-15ad-8ae0-448f-788835f75d1f.jpeg)

```
(TensorFlow) >python
Python 3.5.4 |Anaconda, Inc.| (default, Nov  8 2017, 14:34:30) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from PIL import Image
>>> import numpy as np
>>> img = Image.open("ad_cal.jpg")
>>> img_array = np.array(img)
>>> img_array.shape
(225, 600, 3)
```
（この後の操作が行いやすいように、numpy配列にしています。）
これでPillowを用いて画像を読み込んだときは（縦、横、チャンネル数）の形になることがわかります。
後は、読み込んだものたちを

```python
x.append(img_array)
```
と、連結させれば、（入力枚数、縦、横、チャンネル数）となります。

しかし、実は各種ディープラーニングのフレームワークでは、入力するデータの形は（枚数、チャンネル数、縦、横）であることが多いです。そのために、配列の入れ替えを行っていかなければいけません。そこで、

```python
x = x.transpose(0,3,1,2)
```
とします。

これにより、もともとのｘの０番目（枚数）を最初に持っていき、３番目（チャンネル数）を次に、、、というように入れ替えを行っています。これによって、学習する画像の準備が完了します。

#最後に
以上で独自データセットを準備するための基礎知識の解説は終わりです。
なるべくわかりやすく説明したつもりでしたが、いかがだったでしょうか？

機械学習は「すごそうなことができそうだ！」と強く興味がひかれるものである反面、ある程度のプログラムの知識がなければ、かなり早い段階で壁にぶつかってしまう、初見さんお断りな一面も持っていると僕は考えています。だからこそ、この記事がその壁を乗り越える、僕からのクリスマスプレゼントになれば幸いです。

そしてこの記事を見て少しでも機械学習に興味を持った方は、新年を迎えるこのタイミングで新しく機械学習に挑戦されてはいかがでしょうか？


ちなみに僕は今日、22歳の誕生日です。
誕生日なのに一歩も外に出ずにこの記事を書いています。
・・・誕生プレゼントにたくさんの「いいね」がほしいなぁ