Edited at

Fashion MNIST


Fashion MNIST の説明

サンプルは 28×28 グレースケール画像で、10 クラスのラベルと関連付けられている。各画像は、高さ28ピクセル、幅28ピクセル、合計784ピクセルである。各ピクセルはそれに関連付けられた単一のピクセル値を持ち、そのピクセルの明るさまたは暗さを示す。数字が大きいほど暗くなる。


ラベル

各訓練とテスト・サンプルは以下のラベル群の一つに割り当てられている :

ラベル : 記述

0 : T-shirt/top

1 : Trouser

2 : Pullover

3 : Dress

4 : Coat

5 : Sandal

6 : Shirt

7 : Sneaker

8 : Bag

9 : Ankle boot

tf-tutorial_fashion-mnist_sprite.png

A dataset of Zalando's article images consisting of a training set of 60,000 examples and a test set of 10,000 examples. Each example is a 28x28 grayscale image, associated with a label from 10 classes. Fashion-MNIST is intended to serve as a direct drop-in replacement of the original MNIST dataset for benchmarking machine learning algorithms.


Fashion MNIST データセットのインポート

TensorFlow から直接 Fashion MNIST にアクセスし、データを単にインポートしてロードすることができる。


fashion_mnist = keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()


データの観察

モデルを訓練する前にデータセットのフォーマットを調べてみよう。


images

train_images.shape, test_images.shape

#出力結果
((60000, 28, 28), (10000, 28, 28))

train_imagesには60,000 画像があることを示し、各画像は 28 x 28 ピクセルとして表わされていることが分かる。test_imagesには10,000 画像があることを示し、各画像は 28 x 28 ピクセルとして表わされていることが分かる


labels

train_labels.shape, test_labels.shape

#出力結果
((60000,), (10000,))

np.unique(train_labels)
#出力結果
array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], dtype=uint8)

labelsは0~9までで構成されており、imagesに対応する数が存在することが確認できる。


データの前処理


正規化

ネットワークを訓練する前にデータは前処理をする。訓練セットのtrain_images[0]を調べてみよう。

plt.figure()

plt.imshow(train_images[0])
plt.colorbar()
plt.gca().grid(False)

ダウンロード.png

画像データは行列の形式で表現することができる。画像は各位置ごとの明るさを指定すれば表現できるからある。行列の各要素が、その位置の明るさを表す数値になっている。

train_images[0]

#出力結果
array([[ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 41, 188, 103,
54, 48, 43, 87, 168, 133, 16, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 1, 0, 0, 0, 49, 136, 219, 216, 228, 236,
255, 255, 255, 255, 217, 215, 254, 231, 160, 45, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 14, 176, 222, 224, 212, 203, 198, 196,
200, 215, 204, 202, 201, 201, 201, 209, 218, 224, 164, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 188, 219, 200, 198, 202, 198, 199, 199,
201, 196, 198, 198, 200, 200, 200, 200, 201, 200, 225, 41, 0,
0, 0],
[ 0, 0, 0, 0, 51, 219, 199, 203, 203, 212, 238, 248, 250,
245, 249, 246, 247, 252, 248, 235, 207, 203, 203, 222, 140, 0,
0, 0],...

この画像が0~255の整数によって表現されていることが分かる。ここで画像の正規化をして0から1の範囲に収める。

訓練セットとテストセットが同じ方法で前処理をする。

train_images = train_images / 255.0

test_images = test_images / 255.0

処理後の様子


train_images[0]

#出力結果

array([[0. , 0. , 0. , 0. , 0. ,
0.00392157, 0. , 0. , 0. , 0. ,
0.16078431, 0.7372549 , 0.40392157, 0.21176471, 0.18823529,
0.16862745, 0.34117647, 0.65882353, 0.52156863, 0.0627451 ,
0. , 0. , 0. , 0. , 0. ,
0. , 0. , 0. ],
[0. , 0. , 0. , 0.00392157, 0. ,
0. , 0. , 0.19215686, 0.53333333, 0.85882353,
0.84705882, 0.89411765, 0.9254902 , 1. , 1. ,
1. , 1. , 0.85098039, 0.84313725, 0.99607843,
0.90588235, 0.62745098, 0.17647059, 0. , 0. ,
0. , 0. , 0. ],
[0. , 0. , 0. , 0. , 0. ,
0.05490196, 0.69019608, 0.87058824, 0.87843137, 0.83137255,
0.79607843, 0.77647059, 0.76862745, 0.78431373, 0.84313725,
0.8 , 0.79215686, 0.78823529, 0.78823529, 0.78823529,
0.81960784, 0.85490196, 0.87843137, 0.64313725, 0. ,
0. , 0. , 0. ]...

0から1の範囲になっていることが分かる。

訓練セットからの最初の 36 画像を表示して各画像の下にクラス名を示す。

figure1.png


モデルの構築

以下のように多層ニューラルネットモデル(MLP)のモデルを作る。


model = keras.Sequential([
keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(128, activation=tf.nn.relu),
keras.layers.Dense(10, activation=tf.nn.softmax)
])


コンパイル

モデルが訓練のために幾つかの設定をする。Kerasではモデル定義後はそれで終わりではなく、誤差関数や最適化手法、評価基準を組み入れてコンパイルする必要がある。コンパイル後モデル構築が完了する。

model.compile(optimizer=tf.train.AdamOptimizer(),

loss='sparse_categorical_crossentropy',
metrics=['accuracy'])


学習の実行

モデルは画像とラベルを結びつけることを学習する。

Kerasではmodelのfit関数を使うことで簡単に学習させることができる。

model.fit(train_images, train_labels, epochs=5)


精度を評価する

Kerasではfit関数実行時に出力変数を設定することでエポックごとの損失値や精度を確認することができる。損失値とは誤差関数の値である。これを最小化する事によって精度の高い分類を行うことができる。

```python

test_loss, test_acc = model.evaluate(test_images, test_labels)

print('Test accuracy:', test_acc)

```

10000/10000 [==============================] - 0s 22us/step

Test accuracy: 0.8684


予測

ここで、モデルはテストセットの各画像に対するラベルを予測した。test_labels[0]の予測をみてみよう。

predictions = model.predict(test_images)

predictions[0]
#出力結果
9

np.argmax(predictions[0])
#出力結果

9

この画像はアンクルブーツ(class_names[9])であることを最も信頼している。そしてこれが正しいことを見るためにtest_labelsを確認する

test_labels[0]

#出力結果
9

これはうまく出来ているようだ。

他のものも見てみよう

正しい予測ラベルは緑色で正しくない予測ラベルは赤色であらわす。

tf-tutorial_fashion-mnist_output_46_1.png

SandalとSneaker,PillOverとCoatを間違えているようだ。間違え方としては人間から見ても納得できるものである。


層を深く設定してみる

model2 = keras.Sequential([

keras.layers.Flatten(input_shape=(28, 28)),
keras.layers.Dense(900, activation=tf.nn.relu),
keras.layers.Dense(1000, activation=tf.nn.relu),
keras.layers.Dense(500, activation=tf.nn.relu),
keras.layers.Dense(10, activation=tf.nn.softmax)
])


model2.compile(optimizer=tf.train.AdamOptimizer(),
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])

Epoch 1/5

60000/60000 [==============================] - 47s 791us/step - loss: 0.2740 - acc: 0.8969

Epoch 2/5

60000/60000 [==============================] - 41s 688us/step - loss: 0.2633 - acc: 0.9012

Epoch 3/5

60000/60000 [==============================] - 41s 684us/step - loss: 0.2535 - acc: 0.9039

Epoch 4/5

60000/60000 [==============================] - 42s 695us/step - loss: 0.2437 - acc: 0.9077

Epoch 5/5

60000/60000 [==============================] - 41s 689us/step - loss: 0.2326 - acc: 0.9123


test_loss, test_acc = model2.evaluate(test_images, test_labels)
print('Test accuracy:', test_acc)

10000/10000 [==============================] - 2s 243us/step

Test accuracy: 0.8846

確かに精度が良くなっていることが分かる。


学習の様子

ダウンロード (1).png


epoch数を上げてみる。

# モデルを訓練する
hist2 = model2.fit(train_images, train_labels, epochs=10)

Epoch 1/10

60000/60000 [==============================] - 52s 873us/step - loss: 0.2753 - acc: 0.8969

Epoch 2/10

60000/60000 [==============================] - 58s 972us/step - loss: 0.2622 - acc: 0.9024

Epoch 3/10

60000/60000 [==============================] - 56s 925us/step - loss: 0.2487 - acc: 0.9059

Epoch 4/10

60000/60000 [==============================] - 54s 895us/step - loss: 0.2406 - acc: 0.9090

Epoch 5/10

60000/60000 [==============================] - 52s 875us/step - loss: 0.2336 - acc: 0.9106

Epoch 6/10

60000/60000 [==============================] - 53s 877us/step - loss: 0.2243 - acc: 0.9138

Epoch 7/10

60000/60000 [==============================] - 56s 932us/step - loss: 0.2192 - acc: 0.9166

Epoch 8/10

60000/60000 [==============================] - 56s 929us/step - loss: 0.2087 - acc: 0.9209

Epoch 9/10

60000/60000 [==============================] - 56s 925us/step - loss: 0.2061 - acc: 0.9214

Epoch 10/10

60000/60000 [==============================] - 55s 920us/step - loss: 0.1939 - acc: 0.9251


test_loss, test_acc = model2.evaluate(test_images, test_labels)

print('Test accuracy:', test_acc)

10000/10000 [==============================] - 3s 251us/step

Test accuracy: 0.884

epochあまり、変わっていないことが分かる。エポック数とは、「一つの訓練データを何回繰り返して学習させるか」の数のことです。Deep Learningのようにパラメータの数が多いものになると、訓練データを何回も繰り返して学習させないとパラメータをうまく学習できないません(逆にやりすぎると過学習を起こす)。多すぎずに少なすぎないエポック数を指定することによって、パラメーターをうまく学習させることができる。学習の最終的な目標は、「汎化性能があるパラメータ集合を見つけること」です。そのため、訓練データへの精度が高く、且つ予測精度が高くなるように学習させてやることが重要です。よって、過学習を起こさずに、かつ訓練精度と予測精度が共に良いような、そんなエポック数を見つけることができれば(以下の図参照)、良いエポック数ということになります。ということで、上のような状況が良いとわかっているのであれば、学習がある程度進んだ段階で学習を打ち切る「Early Stopping」というものがあります。

これは、簡単に言うと、「学習が進んで精度の向上がこれ以上見込めないとなったら、そこで学習を止める」という方法です。学習時に「訓練用のデータ」と「学習を止めるかということを判断するバリデーション用のデータ」に分けておき、そのデータを用いて学習を止めるかどうかという判定をします。

ダウンロード (2).png

ほとんど平行線であることからこのあと同様な学習を続けても正解率が高くなっていくとは言い難い。


可視化

# 混同行列を出力

# testデータに対して行うことに注意
from sklearn.metrics import confusion_matrix as cm
result = model.predict(test_images).argmax(axis=1)
cm(test_labels, result) # y_testはOne-Hot表現にする前のデータ形式に注意

array([[786, 0, 15, 11, 4, 1, 180, 0, 3, 0],

[ 8, 960, 1, 21, 4, 0, 5, 1, 0, 0],

[ 12, 0, 789, 10, 107, 1, 81, 0, 0, 0],

[ 20, 3, 17, 908, 16, 0, 32, 0, 4, 0],

[ 1, 0, 73, 53, 809, 0, 63, 0, 1, 0],

[ 0, 0, 0, 0, 0, 935, 0, 35, 0, 30],

[ 80, 0, 75, 25, 63, 0, 751, 0, 6, 0],

[ 0, 0, 0, 0, 0, 6, 0, 961, 0, 33],

[ 6, 0, 4, 3, 6, 4, 8, 7, 962, 0],

[ 0, 0, 0, 0, 0, 2, 1, 31, 0, 966]], dtype=int64)

ダウンロード (4).png


混合行列の観察

間違いが多いものを一つ見てみよう

ラベル : 記述

0 : T-shirt/top

1 : Trouser

2 : Pullover

3 : Dress

4 : Coat

5 : Sandal

6 : Shirt

7 : Sneaker

8 : Bag

9 : Ankle boot


  • 0と6
    T-shirt/top と Shirt

T-shirt/top

ダウンロード (8).png ダウンロード (7).png

Shirt

ダウンロード (11).png

 上の様子から考えられるのは、T-shirt/top を一つにしていることが一つ問題だと考えらる。そもそも「シャツ」はTシャツもワイシャツも含む広い概念である。本来の定義上は「腰までの長さのある衣服」は全てシャツになるはずである。

 Tシャツは襟が無いものという特徴があり、topというものもまとめてしまったのかもしれない。しかし、コンピューターではそんなこと理解できない。それよりも、画像上では袖の部分のほうが大事だと思う。T-shirt/top では袖の部分の差が出すぎている。これでは学習に間違いが多くなると考えられる。


転移学習

転移学習とは、端的に言えばある領域で学習させたモデルを、別の領域に適応させる技術だ。具体的には、広くデータが手に入る領域で学習させたモデルを少ないデータしかない領域に適応させたり、シミュレーター環境で学習させたモデルを現実に適応させたりする技術である。この転移学習の可能性は、NIPS 2016 Tutorialにて、Courseraの講師であるAndrew Ng先生が言及されている。機械学習の成功を今後推進するのは教師なし学習、そして最近目覚ましい進化を遂げている強化学習でもなく、転移学習である、と。Web上には、AlexNetやGoogLeNetなどの有名なモデルが学習済みの状態で配布されており、凄いメンバーたちが多くの時間を費やして作ったモデルなのでこれを利用することは効率が良い。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f32353939302f33313766623361612d653864322d366365362d303837632d6663313565613932613935352e706e67.png


今回のモデル

Keras内蔵のVGG16訓練済みモデルを用いる。

VGG16は2014年のILSVRC(ImageNet Large Scale Visual Recognition Challenge)で提案された畳み込み13層とフル結合3層の計16層から成る畳み込みニューラルネットワーク。層の数が多いだけで一般的な畳み込みニューラルネットと大きな違いはなく、同時期に提案されたGoogLeNetに比べるとシンプルでわかりやすい。

from tensorflow.python.keras.applications.vgg16 import VGG16

model = VGG16()

前処理

 基本的には教材と同様にすすめていくが、二点工夫が必要である。

 一つ目はVGG16では入力する画像がカラー画像である必要がある。しかし、今回はグレースケール画像への変換が必要である。

 二つ目は、画像のサイズを操作する処理を行う。VGG16モデルが画像を読み込めるようにリサイズする。自分の場合、下でうまくいった。

python resize_img_list.append(cv2.cvtColor((cv2.resize(画像名, (32, 32))), cv2.COLOR_GRAY2RGB))

あとは、学習済みモデルに加える全結合層部分を接続を接続してあげればよい。

# 必要なパラメータの追加

input_height = 32
input_width = 32
n_class = 10

# 最終層はノード数がクラスラベルの数に一致していないのでスライシングで取り除く
top_model = Sequential()
top_model.add(Flatten(input_shape=base_model.output_shape[1:]))
top_model.add(Dense(256))
top_model.add(Activation('relu'))
top_model.add(Dropout(0.5))
top_model.add(Dense(n_class))
top_model.add(Activation('softmax'))

# base_modelとtop_modelを接続
from keras.models import Model
model = Model(input=base_model.input, output=top_model(base_model.output))
# 畳み込み層の重みを固定(学習させない)
for layer in model.layers[:15]:
layer.trainable = False

# モデルのコンパイル
model.compile(loss='categorical_crossentropy',
optimizer=SGD(lr=0.0001),
metrics=['accuracy'])

batch_size = 100
n_epoch = 1
#one-hotエンコーディング
Y_train = np.identity(10)[y_train].astype('i')
Y_test = np.identity(10)[y_test].astype('i')

hist = model.fit(X_train,
Y_train,
epochs=n_epoch,
validation_data=(X_test, Y_test),
verbose=1,
batch_size=batch_size)


結果

今回は、ローカルでやるとあまりにも時間がかかるのでクラウドColaboratory を利用する。

loss for trainingはtrainデータの様子、loss for validationはtestデータの様子をあらわす。

/usr/local/lib/python2.7/dist-packages/ipykernel_launcher.py:17: UserWarning: Update your Model call to the Keras 2 API: Model(outputs=Tensor("se..., inputs=Tensor("in...)

Train on 60000 samples, validate on 10000 samples

Epoch 1/1

60000/60000 [==============================] - 1637s 27ms/step - loss: 2.1888 - acc: 0.4260 - val_loss: 1.1218 - val_acc: 0.6654


epoch数を30にしてみる

model_acc.png

model_loss.png

スクリーンショット 2018-12-29 21.25.18.png

グラフの様子からまだまだ学習を進めれば、lossが減りそうである。


epoch数を50にしてみる

acc2.png

loss2.png

スクリーンショット 2018-12-29 22.28.58.png

loss for training が loss for validation と、エポック数が25くらいで交差していることからこのあたりで過学習が起こり始めていることが分かる。


混合行列

mat2.png

0と6に加え2と4も間違いが多い。2と4はニューラルネットモデルの間違いでいったようにPullOverとCoatのであり、人間からも間違いに納得がいく。また、今回のクラスは大きくグループに分けると靴、バック、衣服になると思う。それぞれ靴、バック、衣服のグループ同士の間違いが少ないこともわかる。ニューラルネットモデルでもその傾向はみられた。これもまた、納得のいくものである。

5 : Sandal

7 : Sneaker

8 : Bag

9 : Ankle boot


考察

ニューラルネットワークとVGG16(CNN)の転移学習の違いが結果から分かることをまとめる。まず、一回目の学習でニューラルネットワークに比べてVGG16(CNN)の転移学習の精度が低いことが分かる。その後、VGG16(CNN)は着実に精度を高めていっている。これはVGG16(CNN)はかなり丁寧に細かく学習していることが原因であると考えられる。もともと28×28のグレースケール画像に対しては少し学習が行き過ぎていると考えられる。ただし、一般物体認識など「汎用性」が求めらる場合だと適していると言える。今回は最終的な結果もそれほど差はつかなかったが、世の中のデータは難易度と抽象度が高い。今回の経験でモデルの選択はデータによって適切に判断する必要があると思った。