Raspberry PiにTensorFlowをインストールして基本的な分類サンプル実行まで試した

ディープラーニングを学ぶ最初の一歩としてTensorFlowのインストールとチュートリアルにあるKerasハイレベルAPIを使った基本的な分類のサンプルを試したので資料としてまとめてみました。(資料としてはもはや何番煎じかもわからないぐらいですが...)

まだまだ理解も浅いため説明はかなりざっくりです(;´Д｀)

コマンドはRaspberry Pi 3BにRaspbian9.0がインストールされた環境で実行しています。

インストール手順およびコードサンプルは2018.08.23時点の公式サイトから引用したものです。[[1]](https://www.tensorflow.org/install/install_raspbian)[[2]](https://www.tensorflow.org/tutorials/keras/basic_classification)

#### Attribution

> Portions of this page are modifications based on work created and [shared by Google](https://developers.google.com/readme/policies/) and used according to terms described in the [Creative Commons 3.0 Attribution License](http://creativecommons.org/licenses/by/3.0/).


# TensorFlowのインストール

TensorFlowはPython3.4+または2.7+が必要ですが、この手順では標準でインストールされているPython3(3.5)を利用します。

ライブラリのインストールにはpipを使用します。
PythonとPipのバージョン確認。

```
$ python3 -V
Python 3.5.3
$ pip3 -V
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.5)
```

ATLASをインストールします。ATLASはNumpyが依存する線形代数ライブラリです。TensorFlowのインストールを始める前にインストールされている必要があります。

```
$ sudo apt install libatlas-base-dev
```

TensorFlowをインストールします。
ネットワーク環境によっては `http.client.RemoteDisconnected: Remote end closed connection without response` エラーで失敗するかもしれません。その場合は単純にリトライすれば大丈夫です。

```
$ pip3 install tensorflow
```

インストールが完了したら、検証を行います。python3のコンソールを立ち上げてコマンドを実行します。

```
$ python3
>>> import tensorflow as tf
>>> hello = tf.constant('Hello, TensorFlow!')
>>> sess = tf.Session()
>>> print(sess.run(hello))
```

実行してみるとimportのところで下記のようなWARNINGが表示されます。
[Python3.4を使えば回避できるみたい](https://qiita.com/ktooi/items/48a8d18839cae7246c0b)ですがとりあえず警告だけで動作に影響はなさそうなのでこのまま続行します。

```
/usr/lib/python3.5/importlib/_bootstrap.py:222: RuntimeWarning: compiletime version 3.4 of module 'tensorflow.python.framework.fast_tensor_util' does not match runtime version 3.5
  return f(*args, **kwds)
/usr/lib/python3.5/importlib/_bootstrap.py:222: RuntimeWarning: builtins.type size changed, may indicate binary incompatibility. Expected 432, got 412
  return f(*args, **kwds)
```

# Kerasを使った基本的な分類のサンプル実行

Kerasは高水準の機械学習ライブラリです。
バックエンドとしてTensorFlowやCNTK, Thernoをサポートしていて、学習モデルを容易に構築できるような機能を提供しています。

このサンプルではFashion-MNISTのデータを分類します。
コードの全体は最後にあります。

## ライブラリのインポート

tenosrflow, keras, nympy, matplotlibをインポートします。

```
import tensorflow as tf
from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt
```

## Fashion-MINISTデータのロード

学習用データと評価用データをロードします。
学習用データは機械学習のモデル構築に、評価用データは構築したモデルが正しく画像を分類できるかどうかを検証するために使用します。

```
fashion_mnist = keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
```
ロードしたデータはNumPy配列に格納されます。
train_imagesとtrain_labelsは学習用のデータです。60,000件の学習データが含まれています。test_imagesとtest_labelsには10,000件の検証用データが含まれています。

train_imagesとtest_imagesにはシャツやズボン、サンダルなどの画像データをピクセル単位で格納したデータが配列として格納されています。

## データの確認

試しにtrain_dataのインデックス0をダンプしてみるとこんな感じになっています。
配列の全体像を見てみると、これが何の画像なのかなんとなくわかりますね。

```
>>> print("train_images[0]=%s" % train_images[0])
train_images[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   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   1   0   0  13  73   0    0   1   4   0   0   0   0   1   1   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   3   0  36 136 127  62   54   0   0   0   1   3   4   0   0   3]
 [  0   0   0   0   0   0   0   0   0   0   0   0   6   0 102 204 176 134  144 123  23   0   0   0   0  12  10   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0   0 155 236 207 178  107 156 161 109  64  23  77 130  72  15]
 [  0   0   0   0   0   0   0   0   0   0   0   1   0  69 207 223 218 216  216 163 127 121 122 146 141  88 172  66]
 [  0   0   0   0   0   0   0   0   0   1   1   1   0 200 232 232 233 229  223 223 215 213 164 127 123 196 229   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0 183 225 216 223 228  235 227 224 222 224 221 223 245 173   0]
 [  0   0   0   0   0   0   0   0   0   0   0   0   0 193 228 218 213 198  180 212 210 211 213 223 220 243 202   0]
 [  0   0   0   0   0   0   0   0   0   1   3   0  12 219 220 212 218 192  169 227 208 218 224 212 226 197 209  52]
 [  0   0   0   0   0   0   0   0   0   0   6   0  99 244 222 220 218 203  198 221 215 213 222 220 245 119 167  56]
 [  0   0   0   0   0   0   0   0   0   4   0   0  55 236 228 230 228 240  232 213 218 223 234 217 217 209  92   0]
 [  0   0   1   4   6   7   2   0   0   0   0   0 237 226 217 223 222 219  222 221 216 223 229 215 218 255  77   0]
 [  0   3   0   0   0   0   0   0   0  62 145 204 228 207 213 221 218 208  211 218 224 223 219 215 224 244 159   0]
 [  0   0   0   0  18  44  82 107 189 228 220 222 217 226 200 205 211 230  224 234 176 188 250 248 233 238 215   0]
 [  0  57 187 208 224 221 224 208 204 214 208 209 200 159 245 193 206 223  255 255 221 234 221 211 220 232 246   0]
 [  3 202 228 224 221 211 211 214 205 205 205 220 240  80 150 255 229 221  188 154 191 210 204 209 222 228 225   0]
 [ 98 233 198 210 222 229 229 234 249 220 194 215 217 241  65  73 106 117  168 219 221 215 217 223 223 224 229  29]
 [ 75 204 212 204 193 205 211 225 216 185 197 206 198 213 240 195 227 245  239 223 218 212 209 222 220 221 230  67]
 [ 48 203 183 194 213 197 185 190 194 192 202 214 219 221 220 236 225 216  199 206 186 181 177 172 181 205 206 115]
 [  0 122 219 193 179 171 183 196 204 210 213 207 211 210 200 196 194 191  195 191 198 192 176 156 167 177 210  92]
 [  0   0  74 189 212 191 175 172 175 181 185 188 189 188 193 198 204 209  210 210 211 188 188 194 192 216 170   0]
 [  2   0   0   0  66 200 222 237 239 242 246 243 244 221 220 193 191 179  182 182 181 176 166 168  99  58   0   0]
 [  0   0   0   0   0   0   0  40  61  44  72  41  35   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   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]]
```

それに対応するtrain_labelとtest_labelには0から9までの数値が入っています。
数値にはそれぞれ下記配列の値に対応した意味があります。

```
class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 
               'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
```

次のようなコードを挿入すると、Matplotlibを使ってトレーニング用画像データの一部を表示できます。

```
plt.figure(figsize=(10,10))
for i in range(25):
    plt.subplot(5,5,i+1)
    plt.xticks([])
    plt.yticks([])
    plt.grid(False)
    plt.imshow(train_images[i], cmap=plt.cm.binary)
    plt.xlabel(class_names[train_labels[i]])
plt.show()
```

トレーニング用、検証用画像データの各ピクセルのデータ型をfloatに変換して、かつ0-1の範囲に収まるように変換します。

```
train_images = train_images / 255.0
test_images = test_images / 255.0
```

## 学習モデルの構築

モデルを構築します。
Sequentialはlayerを重ねたネットワークを作ります。コンストラクタに配列として渡すか `.add()` メソッドで層を追加することが出来ます。[[3]](https://keras.io/ja/getting-started/sequential-model-guide/)

```
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)
])
```

最初の層`tf.keras.layers.Flatten`では28x28の2次元配列として渡された入力データを28*28=784の1次元配列に変換しています。この層ではデータの変換のみ行い、学習は行いません。

2番目と3番目`keras.layers.Dense`は通常の全結合ニューラルネットワークレイヤーです。
最初のDenseは128の出力層を持つノード(ニューロン)です。2番目のDenseは10の出力を持つsoftmax層です。softmaxは活性化関数の一つで、出力である10個のクラスそれぞれに該当する確率を出します。確率の総和は1になります。該当する確率が一番高いクラスに一番大きな数値が入ります。

モデルにいくつかのパラーメータを与えてコンパイルします。

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

 - loss : 損失関数を指定します。損失関数はどの程度正しく学習出来ているかを示す指標です。学習中この値が小さくなるように調整します。
 - optimizer : 最適化アルゴリズムを指定します。現在のデータと損失関数をもとに学習モデルを調整する方法です。
 - metrics : 訓練やテストの際にモデルを評価するための関数を指定しています。

## 学習モデルのトレーニング

`fit`関数を呼び出してトレーニングを開始します。
エポックには学習の繰り返しを指定します。1エポックで渡されたデータ全体を処理します。

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

トレーニングを開始すると進捗に従って次のような出力が表示されます。
エポックごとに損失関数と精度が更新されていきます。学習モデルの正しさを示す指標である損失関数(loss)の値は繰り返しごとに減少しています。精度は最終的に89%まで上昇しています。

```
Epoch 1/5
60000/60000 [==============================] - 43s 710us/step - loss: 0.4972 - acc: 0.8246
Epoch 2/5
60000/60000 [==============================] - 41s 677us/step - loss: 0.3754 - acc: 0.8656
Epoch 3/5
60000/60000 [==============================] - 41s 675us/step - loss: 0.3371 - acc: 0.8778
Epoch 4/5
60000/60000 [==============================] - 40s 674us/step - loss: 0.3122 - acc: 0.8860
Epoch 5/5
60000/60000 [==============================] - 41s 676us/step - loss: 0.2943 - acc: 0.8925
```

## テストデータを使ったモデルの評価

テスト用データでモデルを評価してみます。

```
test_loss, test_acc = model.evaluate(test_images, test_labels)
print("test data evaluation: loss %s, test accuracy %s" % (test_loss, test_acc));
```

実行結果は次のようになります。
精度はトレーニング用データを使って学習した際よりも低くなっており、過学習と呼ばれるトレーニングデータに過度に適応した状態になっているといえます。

```
10000/10000 [==============================] - 3s 335us/step
test loss 0.361139899456501, test accuracy 0.8679
```

次に同じデータを使ってテスト用データのラベルを予測した配列を取得してみます。

```
predictions = model.predict(test_images)
```

`predictions`には出力層の情報が配列として格納されていて、10個のラベルそれぞれに対して該当する確率が格納されます。
サンプルとして最初のデータを見てみます。

```
predictions[0] : [9.1500706e-06 5.3606036e-06 1.4961908e-07 1.0200664e-07 2.5860416e-07
 4.4479379e-03 5.5967148e-06 3.9337743e-02 2.2374363e-04 9.5596874e-01]
np.argmax(predictions[0]) : 9
```

配列の9番目(Ankle boot)に該当する確率が一番高くなっています。
正解データであるtest_labelsの最初のデータも9になっているので正しく画像を認識できているようです。

```
test_labels[0] : 9
```

最後にテストデータの先頭25件を正しく認識できているかUIに表示します。
ここまでのコードを含めた全体像は次のとおりです。

```
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import tensorflow as tf
from tensorflow import keras

import numpy as np

import matplotlib.pyplot as plt

print("TensorFlow %s" % tf.__version__)

print("Loading Fashion MNIST dataset...", end="")
fashion_mnist = keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
print("done")
print(" - Training dataset, images: %s, labels: %s" % (train_images.shape, len(train_labels)))
print(" - Test dataset, images: %s, labels: %s" % (test_images.shape, len(test_labels)))

class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 
               'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']

train_images = train_images / 255.0
test_images = test_images / 255.0

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)
])

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

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

test_loss, test_acc = model.evaluate(test_images, test_labels)
print("test loss %s, test accuracy %s" % (test_loss, test_acc));

predictions = model.predict(test_images)

print("predictions[0] : %s" % predictions[0])
print("np.argmax(predictions[0]) : %s" % np.argmax(predictions[0]))
print("test_labels[0] : %s" % test_labels[0])

def plot_image(i, predictions_array, true_label, img):
  predictions_array, true_label, img = predictions_array[i], true_label[i], img[i]
  plt.grid(False)
  plt.xticks([])
  plt.yticks([])
  
  plt.imshow(img, cmap=plt.cm.binary)

  predicted_label = np.argmax(predictions_array)
  if predicted_label == true_label:
    color = 'blue'
  else:
    color = 'red'
  
  plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
                                100*np.max(predictions_array),
                                class_names[true_label]),
                                color=color)

def plot_value_array(i, predictions_array, true_label):
  predictions_array, true_label = predictions_array[i], true_label[i]
  plt.grid(False)
  plt.xticks([])
  plt.yticks([])
  thisplot = plt.bar(range(10), predictions_array, color="#777777")
  plt.ylim([0, 1]) 
  predicted_label = np.argmax(predictions_array)
 
  thisplot[predicted_label].set_color('red')
  thisplot[true_label].set_color('blue')

num_rows = 5
num_cols = 3
num_images = num_rows*num_cols
plt.figure(figsize=(2*2*num_cols, 2*num_rows))
for i in range(num_images):
  plt.subplot(num_rows, 2*num_cols, 2*i+1)
  plot_image(i, predictions, test_labels, test_images)
  plt.subplot(num_rows, 2*num_cols, 2*i+2)
  plot_value_array(i, predictions, test_labels)

plt.show()
```

## おまけ

パラメーターを変えてどうなるか試してみました。

 - epochs (繰り返し数) を倍にしてみる

```
> model.fit(train_images, train_labels, epochs=10)
...
Epoch 10/10
60000/60000 [==============================] - 41s 677us/step - loss: 0.2367 - acc: 0.9111
10000/10000 [==============================] - 3s 326us/step
test loss 0.3561813975453377, test accuracy 0.8779
```

トレーニング時、テスト時ともに1-2%精度が改善しています。

- 隠れ層を一つ増やしてみる

```
> model = keras.Sequential([
>     keras.layers.Flatten(input_shape=(28, 28)),
>     keras.layers.Dense(128, activation=tf.nn.relu),
>     keras.layers.Dense(256, activation=tf.nn.relu),
>     keras.layers.Dense(10, activation=tf.nn.softmax)
> ])
...
Epoch 5/5
60000/60000 [==============================] - 54s 905us/step - loss: 0.2835 - acc: 0.8945
10000/10000 [==============================] - 4s 411us/step
test loss 0.3537039315700531, test accuracy 0.8707
```

こちらもちょっとだけ精度が改善しています。

 - オプティマイザを変えてみる。オプティマイザはサンプルにあるtf.train名前空間のものだけでなく、keras.optimizers名前空間のものも使用できるようです。[4]

```
> model.compile(optimizer=keras.optimizers.Nadam(), 
>               loss='sparse_categorical_crossentropy',
>               metrics=['accuracy'])
...
Epoch 5/5
60000/60000 [==============================] - 66s 1ms/step - loss: 0.2884 - acc: 0.8941
10000/10000 [==============================] - 3s 340us/step
test loss 0.357051433801651, test accuracy 0.8718
```

Nadamにしたところちょっとだけ精度が改善しています。
いろいろ試してみましたが、SDGで実行した際の精度が少し低くなり、それ以外は似たような結果でした。

---

[1] https://www.tensorflow.org/install/install_raspbian
[2] https://www.tensorflow.org/tutorials/keras/basic_classification
[3] https://keras.io/ja/getting-started/sequential-model-guide/
[4] https://keras.io/ja/optimizers/