Pythonのパの字も知らない人間がTensorFlowチュートリアルやりながら"完全に理解"していった記録

## まえがき
こんにちは、 Webエンジニアをやっている[ますみん](https://twitter.com/masumi_sugae)と申します。機械学習が巷では流行りに流行っていますね。自分も大学の講義でなんとなーくやった記憶があるのですが、もう既に覚えてません。さすがにチュートリアルくらいやっておかないと流行についていけないだろうという若干後ろめたい気持ちがドリブンにドリブンを重ねたので、今回はTensorFlowのニューラルネットワークのチュートリアルをやってみることにします。

### 注意
この記事は、Pythonを触ったこともない初心者がドキュメントあさりながらあーだこーだやった記録です。やったこと、思ったことをそのまま書いていますので、読みやすさは保証できかねます。

## 環境構築
何から手を付けたらいいのかすらよく分からないけれど、自分の中ではとりあえず「機械学習といえば[Python](https://www.python.org/) + [TensorFlow](https://www.tensorflow.org/)」っていうイメージなので、TensorFlowをインストールします。TensorFlowってGoogleが作ってるんですね。初めて知りました。

### Pythonバージョン確認
```shell:Pythonコマンド打ってみる
$ python
Python 2.7.16 (default, Apr 12 2019, 15:32:40)
```
Pythonは2.x系と3.x系で全然別モノだから気をつけろって誰かに言われた気もしますが、まだ触ったこともないのでそんなのは知りません。とりあえず既にMacに入っていた2.x系で進めることにします。

### pipのアップデート
[参考: 公式ドキュメント "Install TensorFlow 2"](https://www.tensorflow.org/install)

公式ドキュメントのインストール手順に従い、インストールを進めていきます。[GitHub](https://github.com/tensorflow/tensorflow/releases/tag/v2.1.0)によると、2020/01/12時点でのTensorFlowの最新Versionは2.1.0のようです。
Pythonではpip(Pip Installs Packages)というパッケージ管理ツールで各種パッケージをインストールできるようですね。TensorFlowもこのpipを使ってインストールします。ちなみにちなむと、pipはRubyでいうところのGem、PHPでいうところのComposerみたいなやつです。

まずはpip自体のアップデートをします。

```shell:pipのアップデート
$ pip install -U pip
DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7.
Collecting pip
  Downloading https://files.pythonhosted.org/packages/00/b6/9cfa56b4081ad13874b0c6f96af8ce16cfbc1cb06bedf8e9164ce5551ec1/pip-19.3.1-py2.py3-none-any.whl (1.4MB)
    100% |████████████████████████████████| 1.4MB 7.8MB/s
Installing collected packages: pip
  Found existing installation: pip 19.0.3
    Uninstalling pip-19.0.3:
      Successfully uninstalled pip-19.0.3
Successfully installed pip-19.3.1
```

いきなり悲しいメッセージが表示されました。なんと2020年1月1日でPython2.7はその生涯に幕を下ろしたとのこと😭pipも今後のバージョンアップでPython2.7のサポートを終了すると書いてありますね...。Macにはいっているのはガッツリ2.7系のPythonなので、これは困りました...。

それじゃあせっかくなのでTensorFlowがサポートしている中で最新のPythonをインストールしましょう。
2020/01/12時点でPython自体の最新版は[Ver. 3.9.0a2](https://www.python.org/downloads/release/python-390a2/)ですが、[TensorFlowでは3.4〜3.7系をサポートしている](https://www.tensorflow.org/install/pip?lang=python3#system-requirements)ようなので、3.7.x系で最新の安定版である[Ver. 3.7.6](https://www.python.org/downloads/release/python-376/)をインストールすることにします。

### Python 3.7.6のインストール
#### pyenvのインストール
[公式ドキュメント](https://www.python.jp/install/macos/index.html)に従って直接Pythonをインストールしてもよいのですが、後々またバージョンの違いに泣かされるのは嫌なので、Pythonのバージョン管理ツールを使ってインストールすることにします。
ツールはいろいろあるみたいですが、自分の周りでも結構触ってる人が多いので[pyenv](https://github.com/pyenv/pyenv)を使うことにしました。

[pyenvのREADME](https://github.com/pyenv/pyenv#installation)曰く、MacならHomebrewでインストールできるようですね。ではさっそくインストールしましょう🍺

```shell:pyenvのインストール
$ brew update
$ brew install pyenv
```

```shell:pyenvがインストール出来たか確認
$ pyenv
pyenv 1.2.16
...
```
pyenv 1.2.16が無事インストールされました！

#### pyenvでPython 3.7.6をインストール
インストールしといてアレなんですが、そもそもpyenvでインストールできるバージョンの中に本当にVer. 3.7.6が含まれているのかが分かりません。インストールできるバージョンを確認してみます。
まずはinstallコマンド自体のヘルプを確認してみます。

```shell:pyenv&nbsp;installコマンドのヘルプを確認
$ pyenv install -h
Usage: pyenv install [-f] [-kvp] <version>
       pyenv install [-f] [-kvp] <definition-file>
       pyenv install -l|--list
       pyenv install --version

  -l/--list          List all available versions
  -f/--force         Install even if the version appears to be installed already
  -s/--skip-existing Skip if the version appears to be installed already

  python-build options:

  -k/--keep          Keep source tree in $PYENV_BUILD_ROOT after installation
                     (defaults to $PYENV_ROOT/sources)
  -p/--patch         Apply a patch from stdin before building
  -v/--verbose       Verbose mode: print compilation status to stdout
  --version          Show version of python-build
  -g/--debug         Build a debug version

For detailed information on installing Python versions with
python-build, including a list of environment variables for adjusting
compilation, see: https://github.com/pyenv/pyenv#readme
``` 

どうやら `pyenv install -l` もしくは `pyenv install --list`でインストールできるバージョン一覧を表示できるようです。
ではこのコマンドを打って、 Ver. 3.7.6がインストールできるか確認してみましょう。

```shell:pyenvでインストール可能なPythonのバージョン一覧表示
$ pyenv install -l
Available versions:
  2.1.3
  2.2.3
...
  3.7.6
...
```

おお、ちゃんとインストールできるみたいですね！一安心です。では改めて、pyenvでVer. 3.7.6をインストールしましょう。

```shell:Python&nbsp;3.7.6をインストール
$ pyenv install 3.7.6
python-build: use openssl@1.1 from homebrew
python-build: use readline from homebrew
Downloading Python-3.7.6.tar.xz...
-> https://www.python.org/ftp/python/3.7.6/Python-3.7.6.tar.xz
Installing Python-3.7.6...
python-build: use readline from homebrew
python-build: use zlib from xcode sdk
Installed Python-3.7.6 to /Users/<ユーザー名>/.pyenv/versions/3.7.6
```

インストールが終わったようです。でもインストールしただけではまだバージョンが切り替わっていません。

```shell:Pythonのバージョン確認
$ python --version
Python 2.7.16
```

#### pyenvで使用するPythonのバージョンを指定
もう2.7系は使わないので、pyenvにてMac全体で使用するPythonのバージョンを3.7.6に指定してしまいましょう。

```shell:pyenvでglobalバージョンを3.7.6に指定
$ pyenv global 3.7.6
```

指定をしたので、再度シェルからPythonのバージョンを確認してみます。

```shell:Pythonのバージョン確認
$ python --version
Python 2.7.16
```

おや、バージョンが切り替わっていません。よくあるパスの問題ですね。`pyenvでインストールしたPython`にパスが通っておらず、`python`コマンドで使える状態になっていません。

#### pyenvでインストールしたPythonのありかにパスを通す

パスを通しましょう。自分はMacのshellをbashのままにしている([macOS Catalinaからはzshがデフォルトになっている](https://support.apple.com/ja-jp/HT208050))ので、bash_profileに以下を追記します。

```shell:~/.bash_profileに追記
export PYENV_ROOT="$HOME/.pyenv"    # /Users/<ユーザー名>/.pyenv
export PATH="$PYENV_ROOT/bin:$PATH" # パス一覧に最優先で $PYENV_ROOT/bin を追加(パスは左側が優先される)
eval "$(pyenv init -)"              # $PYENV_ROOT/shimsディレクトリ追加、指定したバージョンのPythonをshimsにコピーする。終わったら$PYENV_ROOT/shimsを$PATHに追加
```

追記が終わったら`source`コマンドで設定を反映させます。

```shell:~/.bash_profileに記述した設定を反映
$ source ~/.bash_profile
```

設定を反映したので、シェルからpythonコマンドのパスを確認してみます。

```shell:pythonコマンドのパス確認
$ which python
/Users/<ユーザー名>/.pyenv/shims/python
```

pyenvでインストールしたPythonの方に向いていますね！これでOKです。再度グローバルのバージョンを設定し、バージョンを確認してみます。

```shell:グローバルのPythonバージョン設定と確認

$ pyenv global 3.7.6
$ python --version
Python 3.7.6
```

ちゃんとバージョンが切り替わってますね！これでようやくTensorFlowのインストールに戻れます！

##### ちょっと脱線「パスって何？」
「パスって何？」っていう人は、「コマンド名を打った時に実際に実行されるファイルのありか」って覚えておけばOKです。
今回は`python`って打った時に`pyenvでインストールしたPython`を使いたかったのですが、`実際に実行されるファイルのありか`に`pyenvでインストールしたPythonのありか`が入っていなかったので使えなかった、ということになります。もちろん実行ファイル自体は存在していますので、実行ファイルを直指定すれば使うことができます。

```shell:pyenvでインストールしたPythonの実行ファイルを直指定してバージョン確認
$ ~/.pyenv/versions/3.7.6/bin/python --version
Python 3.7.6
```

### pipのアップデート
pipもpyenv配下のものを使うように変わったので、改めてアップデートをしておきます。

```shell:pipコマンドの実態のありかがpyenv配下になっている
$ pip --version
pip 19.2.3 from /Users/<ユーザー名>/.pyenv/versions/3.7.6/lib/python3.7/site-packages/pip (python 3.7)
```

```shell:pipのアップデート
$ pip install -U pip
Collecting pip
  Using cached https://files.pythonhosted.org/packages/00/b6/9cfa56b4081ad13874b0c6f96af8ce16cfbc1cb06bedf8e9164ce5551ec1/pip-19.3.1-py2.py3-none-any.whl
Installing collected packages: pip
  Found existing installation: pip 19.2.3
    Uninstalling pip-19.2.3:
      Successfully uninstalled pip-19.2.3
Successfully installed pip-19.3.1
```

アップデートが完了しました！

### TensorFlowをインストール
だいぶ遠回りをしてしまいましたが、ようやく諸問題が片付きました。ではpipでTensorFlowをインストールしてみましょう。
[TensorFlowの公式ドキュメント](https://www.tensorflow.org/install#download-a-package)曰く、

- 最新の安定版をインストールする場合 ... `pip install tensorflow`
- 最新の開発版をインストールする場合 ... `pip install tｆ−nightly`

とするようです。最新の安定版をインストールしたいので、今回は前者のコマンドになります。

```shell:TensorFlowをインストール
$ pip install tensorflow
```

インストールが完了しました！いい感じ！！

### Pythonスクリプトを実行してみる
全くの初心者なので、そもそもPythonをどうやって動かすのかよく分かっていません。なのでまず最初にHello Worldしてみたいと思います。
試しに`Hello, World`とだけ表示するスクリプトを書いてみます。Pythonでは変数の標準出力は[`print()`関数](https://docs.python.org/ja/3/library/functions.html#print)で行えるようです。

```python:sample.py
print('Hello, World')
```

このPythonスクリプトをコマンドラインから実行してみます。

```shell:sample.pyを実行
$ python sample.py
Hello, World
```

おおっ、Hello Worldができました！調子いいですね🥳
ちなみに`python`とだけ打つと、対話型のPython実行環境が立ち上がります。いちいちスクリプト書いて実行するの面倒ならこちらでもいいかもしれません。

## TensorFlowのチュートリアルをやってみる
[TensorFlow 公式ドキュメント "はじめてのニューラルネットワーク：分類問題の初歩"](https://www.tensorflow.org/tutorials/keras/classification?hl=ja)

さ、ようやく本題のTensorFlowのチュートリアルです😂
初心者向けチュートリアルの中に「身につけるモノの写真分類」といういかにもそれっぽいお題があったので、今回はそれをやってみることにします。ちなみにこのチュートリアルで取り上げられている機械学習の手法はニューラルネットワークといって、生物の神経回路に似せた構造になっています。

```python:tutorial.py
from __future__ import absolute_import, division, print_function, unicode_literals

# TensorFlow と tf.keras のインポート
import tensorflow as tf
from tensorflow import keras

# ヘルパーライブラリのインポート
import numpy as np
import matplotlib.pyplot as plt

print(tf.__version__)
```

ふむふむ。はじめにPythonの[インポートシステム](https://docs.python.org/ja/3/reference/import.html)というのを使って他のモジュールを使えるようにしているのですね。[fromはモジュールの中の特定属性だけimportするときに使う構文](https://qiita.com/tetutetuman/items/6e3d9ff6d797243c77c3)のようです。[`__future__`というモジュール](https://docs.python.org/ja/3/library/__future__.html?highlight=future#module-__future__)が提供しているいろいろな機能を使えるようにしているわけですね。

で次に、pipでさっきインストールしたtensorflowをインポートするわけですが、名前が長いから`tf`という名前でモジュールを扱えるようにしてある、と。なるほど。このへんはJavaScriptとかでも見るやつですね。PHPも`use as`で名前空間のエイリアス作成できますね。おんなじや！

次の行をみると、tensorflowの中にkerasという機能が盛り込まれているようですね。これもそのままインポートすると`tf.keras`ってなるはずですが、頻繁に使われるから`keras`だけで呼べるようにしてある、ということですかね。ふむ〜。

あとは同じですね。`numpy`を`np`で呼べるようにした上でインポート、`matplotlib.pyplot`を`plt`で呼べるようにした上でインポートしています。な〜んだ、初期化だけじゃんか！
そして最後に`tf.__version__`で、インストールしたTensorFlowのバージョンを表示して終了です。楽勝楽勝！🤩

では一旦これで実行してみましょう。

```shell:tutorial.pyの実行
$ python tutorial.py
Traceback (most recent call last):
  File "tutorial.py", line 9, in <module>
    import matplotlib.pyplot as plt
ModuleNotFoundError: No module named 'matplotlib'
```

お、楽勝かと思っていたらさっそくエラー。コピペでうまく動かない箇所が出てきました。`matplotlib`というパッケージがインストールされていないようですね。どうやらこの`matplotlib`というパッケージは標準パッケージではないので、pipでのインストールが必要だったみたいです。
numpyも標準パッケージではないようですが、こちらは[Pythonのインストールの仕方によっては同梱されてるらしい](https://qiita.com/t2y/items/2a3eb58103e85d8064b6)です。以前ちらっとだけPython触ったときにもしかすると入っちゃったのかな？(無知)

### matplotlibパッケージをインストール

```shell:pipでmatplotlibパッケージをインストール
$ pip install matplotlib
```

無事にインストールが完了したら、再度tutorial.pyを実行します。

```shell:tutorial.pyの実行
$ python tutorial.py
2.1.0
```

今度はうまくいきました！バージョン2.1.0のTensorFlowがインストールされていることを確認できました！

### ファッションMNISTデータセットのダウンロード
機械学習には、学習するためのデータセットが必要です。チュートリアルで使用するデータセットをダウンロードする行をスクリプトに追加しましょう。追記した行だけ表示すると、以下のようになります。

```python:tutorial.py
fashion_mnist = keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
```
[keras](https://keras.io/)っていうのはPythonで使えるニューラルネットワークライブラリのことらしく、TensorFlowにも組み込まれています。読み方は「ケラス」っぽいです（[公式ドキュメントで名前に触れている部分](https://keras.io/#why-this-name-keras)と、ギリシャ語表記の方の[Google翻訳結果](https://translate.google.com/?sxsrf=ACYBGNTYQDdKa4ksGxET6NX7PqcOp7IkZw:1578887608009&uact=5&um=1&ie=UTF-8&hl=ja&client=tw-ob#auto/ja/%CE%BA%CE%AD%CF%81%CE%B1%CF%82)）。TensorFlowでは、よく使われるデータセットはこの[tf.keras.datasets モジュールのAPI](https://www.tensorflow.org/api_docs/python/tf/keras/datasets)で呼び出せるようです。

ってか公式のドキュメント見てて思ったんですが、TensorFlow公式も自身のこと`tf`って略して表記するの推奨なんですね。まぁ確かに、何回もコード内に出てきたらちょっと見づらいかもです😳

まま、さっそくダウンロードをば。

```shell:ファッションMNISTデータセットのダウンロード
$ python tutorial.py
2.1.0
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-labels-idx1-ubyte.gz
32768/29515 [=================================] - 0s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/train-images-idx3-ubyte.gz
26427392/26421880 [==============================] - 2s 0us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-labels-idx1-ubyte.gz
8192/5148 [===============================================] - 0s 1us/step
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/t10k-images-idx3-ubyte.gz
4423680/4422102 [==============================] - 0s 0us/step
```

ダウンロードが完了しました！一度ダウンロードしてしまったあとは、もうダウンロードの必要はないみたいですね。
ちなみにダウンロードしてきたファイルは`~/.keras/datasets/<データセット名>/`に保存されます。[keras APIのドキュメントを見ると保存先のパスを渡せるみたい](https://keras.io/ja/datasets/)なので、TensorFlowで用意されたラッパーではなく元のAPIを使えば任意の場所に保存できます。

```shell:データセットの保存先
$ ls ~/.keras/datasets/fashion-mnist/
t10k-images-idx3-ubyte.gz	t10k-labels-idx1-ubyte.gz	train-images-idx3-ubyte.gz	train-labels-idx1-ubyte.gz
```

#### ちょっと脱線「MNISTデータセット(MNIST Database)って何？」
機械学習では、大量のデータ群を学習させることでモデルを作り出します。この大量のデータ群というのがミソで、数十というオーダーではまず足りません。さらに集めたデータ群は入力のフォーマットが揃うように前処理が必要となるため、データ集めだけで骨が折れるというのがこの分野でよく言われること(らしい)です。これでは初心者の参入障壁が爆上がりしてしまう(学習させられるだけのデータ群を用意できずに挫折する)ということで、初学者向けのチュートリアル等では予め用意されたデータセットを使うことが多いようです。
機械学習界隈で有名なデータセットの1つにNIST Databaseがあります。これはもともと[アメリカ国立標準技術研究所(NIST)](https://www.nist.gov/)の[SRD(Standard Reference Data)](https://www.nist.gov/srd)で頒布されていた手書き文字のデータ群であり、このNIST Databaseをより使いやすく改修したのが[MNIST Database(MNISTのMはModifiedのM)](http://yann.lecun.com/exdb/mnist/)です。MNIST Databaseは機械学習のいわばHello Worldといえます。TensorFlowの`tf.keras.datasets`モジュールでももちろんMNIST Databaseが使えます。
TensorFlowのチュートリアルでは、`単純かつ使われすぎてしまったMNISTデータセット`を置き換える目的で作られた[ファッションMNISTデータセット](https://github.com/zalandoresearch/fashion-mnist)を使用しています。

### 学習データの用意
機械学習の大枠の流れとしては、

1. モデルを作成
1. 学習データをモデルに流し、学習させる
1. 学習が済んだモデルにテストデータを流し、精度を確認する
1. 学習が済んだモデルに実データを流す

という感じです。ただモデルを作っただけではダメで、分類やらなんやらのコツを学習するための学習データ、モデルの精度を確認するためのテストデータが必要ということですね。ファッションMNISTデータセットには、学習データとテストデータの両方が含まれています。試しに以下を`tutorial.py`に追記して、学習データの１番目を表示してみます。

```python:tutorial.py(学習データを表示する)
plt.figure()
plt.imshow(train_images[0])
plt.colorbar()
plt.grid(False)
plt.show()
```

`plt`は `import matplotlib.pyplot as plt`でインポートした機能でした。[matplotlib](https://matplotlib.org/)はおそらくmatrix plot libraryの略称かと思われますが、要は行列に格納されたデータからいい感じの図を作ってくれるモジュールです。このモジュールのうち、`pyplot`っていう機能を使うやで〜ってことですね。公式曰く[pyplotはMATLABライクなインターフェースで図を描くことができる](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.html?highlight=pyplot#module-matplotlib.pyplot)とのことで、用意された関数やその使い方は確かにMATLABっぽさがある...かも。
新しい図を作成して、図に画像を貼り付けてカラーバー表示して、グリッド消して図を表示する。MATLABの`imagesc()`とかそのへんに近いですね。じゃあスクリプトを実行して学習データの１番目を表示してみましょう。

<img alt="image.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/7353c852-550a-d2a3-b595-d1871fd1abf8.png" width="400">

これは...ブーツですね！こんな感じで、0〜255の値が入った28px × 28pxの画像が60,000枚入っています。じゃあこれをさっそくニューラルネットワークに...といきたいところですが、それぞれの画素の値を0〜1の値にしてから突っ込みたいのでここでちょっとデータを加工。いわゆる前処理というやつですね。なるほど。

```python:tutorial.py(データの前処理)
train_images = train_images / 255.0
test_images = test_images / 255.0
```

なにげな〜く書いていますが、配列の各要素を一気に割り算できるあたり、やはりPythonだな〜って感じがします。PHPとかRubyとかだとfor文で要素を1つずつ割っていかないといけないですからね。Pythonだと行列計算しやすいっていうのはこういう実装がされているからなんだな〜としみじみ感じます。

さて、各データセットには「その画像が実際何の画像なのか」を表す`ラベル`が付いています。しかしラベルは文字列ではなく整数で格納されているので、クラス(それぞれの振る舞いを表すもの)の名称に衣料品の名前を付け、クラスとラベルとを対応付けます。

```python:tutorial.py(クラス名定義)
class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 
               'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
```

例えば`class_names[0]`は`T-shirt/top`、`class_names[1]`は`Trouser`(ズボンのこと)といった具合になります。

念の為、正しいクラス名になっているかどうかを確認しましょう。

```python:tutorial.py(クラス名の確認)
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()
```

先に結果を見てみるとこんな感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/cda65b81-ed07-35e2-4732-6369db3de6e1.png)

おお、チュートリアルページに載っているのと同じですね！(当たり前)
正しいラベルが振られていることが確認できました。後はモデルを作ってデータをブチ込むだけです！🥳

コードをちょっと追ってみます。といってもほぼ「matplotlib.pyplotのドキュメント読め」で終わる話ではあるんですが😉

[figsizeはインチ指定](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.figure.html#matplotlib.pyplot.figure)です。この場合10インチ * 10インチの図を作るってことですね。ドキュメントによると、デフォルトは6.4インチ * 4.8インチです。
[range()関数(正確には、作成後値が変更できない(イミュータブルという)Pythonの型)](https://docs.python.org/ja/3/library/stdtypes.html#typesseq-range)は、引数1こ(nとする)だけ取った場合`n未満を常に満たすようにして、0から1ずつカウントアップしたもの`を返します。先述の通りrangeは`型`扱いなので、配列とはちょっと違うみたいですね。といってもインデックス指定して要素取ってくる操作は配列のそれと何ら変わりありませんが。

for文まで来ました。[Pythonのfor文](https://docs.python.org/ja/3/tutorial/controlflow.html#for-statements)は結構独特な書き方するんですね🤨他の言語のcase文っぽさがあります。Pythonにおいてインデントは非常に重要らしく、for文でインデントがないとエラーになってしまいます。まぁRubyとかと違ってendとかないし、そりゃそうだよねって感じです。

で画像を1枚1枚図に貼り付けていきます。5行5列の配置場所に対し、左上から始まり右端へ向けて画像を貼っていき、右端にきたら次の行に進む、という感じですね。[plot()関数](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.plot.html#matplotlib.pyplot.plot)は1つの画像やグラフ表示、[subplot()関数](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.subplot.html#matplotlib.pyplot.subplot)は複数の画像やグラフを表示するときに使うようですね。

[xticks()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.xticks.html#matplotlib.pyplot.xticks)、[yticks()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.yticks.html#matplotlib.pyplot.yticks)はそれぞれグラフの縦軸の目盛り、横軸の目盛りの刻みを取得 or 設定します。空の配列を渡すことで目盛りなしに出来ます。今回は画像を貼るので目盛りはいらないですね。なので空配列を渡しています。グリッド表示もいらないので、[grid()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.grid.html#matplotlib.pyplot.grid)にはFalseを渡しています。[Pythonのbool型](https://docs.python.org/ja/3/library/stdtypes.html#bltin-boolean-values)って頭文字が大文字なんですね。[小文字でも判定できるようにするモジュール](https://docs.python.org/ja/3/distutils/apiref.html#distutils.util.strtobool)も用意されているようですが、使う時明示しないといけないのでちと面倒ですね。なんでこういう実装なんだろう。

imshow()はさっきやったので、[xlabel()](https://matplotlib.org/api/_as_gen/matplotlib.pyplot.xlabel.html#matplotlib.pyplot.xlabel)を見てみましょう。ってかgrid()もさっきあったな。まぁいいや。
xlabel()はx軸方向(今は横方向)に表示するラベルです。画像が属するクラスの名前を表示していますね。これをylabel()に変えると、y軸方向にラベルが表示されます。

参考までに、4行7列で画像を表示してy軸方向にラベルをつけるとこんな具合になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/2c4331a9-7541-723a-f238-d477bc3b024d.png)

だんだんわかってきたぞ！🥳

### モデルを構築する
ここからは主にkeras APIを使うことになるので、説明はしょった部分はAPIのドキュメントみて補完してください。

いよいよモデルの構築に入ります。このチュートリアルではニューラルネットワークを構築しているといいましたが、ニューラルネットワークにおいて重要なのが`層`と呼ばれる概念です。入力があったらなにかしら重み付けをして出力するものだと思ってもらえればいいかと。この層を順に積んで作るモデルを、kerasでは[Sequential](https://www.tensorflow.org/api_docs/python/tf/keras/Sequential)と呼んでいるようですね。

```python:tutorial.py(モデル構築)
model = keras.Sequential([
    keras.layers.Flatten(input_shape=(28, 28)),
    keras.layers.Dense(128, activation='relu'),
    keras.layers.Dense(10, activation='softmax')
])
```

モデルの中身がそれぞれ何をしているかは[チュートリアルのページ](https://www.tensorflow.org/tutorials/keras/classification?hl=ja)を是非見てほしい(逃げの姿勢!)のですが、平たく言うと

- 入力された画像(要素数28 * 28の2次元配列)を、1次元配列に変換(厳密にはこの層はニューラルネットワークの前処理をする層)
- 128個の人工ニューロンからなる全結合層を作り、ReLUという種類の伝達関数(活性化関数)に入力し値を加工、出力する
- 10個の人工ニューロンからなる全結合層を作り、ソフトマックスという種類の伝達関数(活性化関数)に入力し値を加工、出力する(出力は各ラベルに属する確率になる)

って感じです。まぁなんとなく分かったところで、モデルをコンパイルしてみましょう。

```python:tutorial.py(モデルのコンパイル指示)
model.compile(optimizer='adam', 
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
```

ここもなんとなく理解する程度にとどめておきます(ホントはこのあたりが一番面白いところだけど、ガッツリ数学なので)。ざっくり見ると、コンパイルには以下の3つが必要だということらしいですね。

- 損失関数（loss function) ... 誤差関数とも呼ばれるやつ。学習中のモデルの正確さを表す。値が低いほど正確。今回は[sparse_categorical_crossentropy](https://keras.io/ja/objectives/)というやつを使う
- オプティマイザ（optimizer） ... モデルの更新方法。今回は[Adam](https://qiita.com/deaikei/items/29d4550fa5066184329a#adam)というアルゴリズムを用いている。
- メトリクス（metrics） ... モデルの更新ステップで何を監視するかを指定。今回はモデルの正解率を監視する

なんのこっちゃって感じになってきましたね。でも「機械学習を使ってとりあえず何かする」だけなら、この辺はあまり深く追わなくてOKです。何層で何をして、どういう更新方法使って...とかいう部分は全て、正解率を上げるための変数の１つに過ぎません。より正解率の高いモデルを作りたくなったら、その時初めて数学を学べばよいでしょう。ということで、今はこの辺スルーでいきます😝

### モデルに学習させる
学習用のデータセットをモデルに読み込ませて、学習をさせます。ニューラルネットワークに大量の画像を突っ込み、「この画像はこのクラスに分類されるやで〜🤓」っていうのを覚えさせるわけですね。かんたんかんたん！やってみましょう。

```python:tutorial.py(モデルに学習させる)
model.fit(train_images, train_labels, epochs=5)
```

学習用のデータセットとラベルを使って、同じデータで5回学習させるわけですね。ふむふむ。いざ実行！

```shell:学習を実行する
$ python tutorial.py
2020-01-12 23:53:12.524687: I tensorflow/core/platform/cpu_feature_guard.cc:142] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
2020-01-12 23:53:12.684666: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x7ff1c0a736e0 initialized for platform Host (this does not guarantee that XLA will be used). Devices:
2020-01-12 23:53:12.684725: I tensorflow/compiler/xla/service/service.cc:176]   StreamExecutor device (0): Host, Default Version
Train on 60000 samples
Epoch 1/5
60000/60000 [==============================] - 4s 65us/sample - loss: 0.4982 - accuracy: 0.8242
Epoch 2/5
60000/60000 [==============================] - 3s 50us/sample - loss: 0.3763 - accuracy: 0.8648
Epoch 3/5
60000/60000 [==============================] - 3s 48us/sample - loss: 0.3373 - accuracy: 0.8773
Epoch 4/5
60000/60000 [==============================] - 3s 48us/sample - loss: 0.3142 - accuracy: 0.8849
Epoch 5/5
60000/60000 [==============================] - 3s 54us/sample - loss: 0.2967 - accuracy: 0.8902
```

「GPUではなくCPUで頑張って学習しちゃってるけど大丈夫か〜？」ってPythonに言われてるけど、大丈夫なので無視します。ちゃんと学習できてるみたいですね！素晴らしい！最終的には正解率89%くらいのモデルが出来上がりました！

### 正解率の評価
学習に使っていないテスト用データセットにも、ラベルがついています。これを学習が終わったモデルに流し込むことで、モデルにデータ突っ込んだら実際どのくらいの正解率になるのかを予測してみます。

```python:tutorial.py
test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
print('\nTest accuracy:', test_acc)
```

```shell:正解率の予測をしてみる
$ python tutorial.py
10000/10000 - 0s - loss: 0.3457 - accuracy: 0.8752

Test accuracy: 0.8752
```

ほほう、87.5%...学習の最後のステップよりちょっと正解率下がりましたね。チュートリアル曰く、この差はいわゆる**過学習**の一例とのことです。学習用データセットで正解率が高くなるような学習の仕方をしすぎたせいで、他のデータが入ってきたときにモデルが「オレが知ってるデータと違う...」と疑心暗鬼になってしまう状態のことですね。な〜るほど。まぁここも今はスルーして、とりあえず実際にデータ突っ込んで結果を見てみましょう。

### 学習が終わったモデルで新しいデータを分類してみる
とうとうチュートリアルのゴールにたどり着きました！完成したモデルにデータを投げて、ちゃんと分類されているか見てみましょう。

```python:tutorial.py(分類)
predictions = model.predict(test_images)
print(predictions[0])
print('\n This image shows ', class_names[np.argmax(predictions[0])])
```

```shell:分類の実行
$ python tutorial.py
[1.1950669e-06 2.0900355e-08 8.5691376e-08 2.0669324e-07 5.3152036e-07
 1.7759526e-02 7.3810802e-06 3.2051697e-02 1.6501182e-05 9.5016277e-01]

 This image shows  Ankle boot
```

数字だらけの配列は `入力された画像がそれぞれのラベルに分類される確率` を表しています。で、結果としてこのモデルは入力された画像群の1番目をブーツであると分類したわけですね。

確認のためにtest_images[0]を図示してみると...

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/155576/4738529f-d037-bf86-303b-053e249daa9f.png" width="500">

うむ、ブーツですね！！素晴らしい！！🥳
ということで、チュートリアルを終え無事に`身につけるモノ画像分類器`が完成しました！

基礎の基礎ができたので、今回はここまで！PythonもTensorFlowも完全に理解した！😆(初心者特有の謎の自信)
学習に使う関数とかは確かに難しいのですが、「機械学習でなんやかんやする」だけなら細かいロジック覚えなくてもまぁなんとかなりそうだなっていうことが分かりました。
ただどっかにも書きましたが、実際にモデル構築するときはとにかくデータセット作成がめちゃくちゃ大変だなって気がします。チュートリアルでは既存のデータセットを使ったので簡単に実装できますけど、これ1から自分でデータ用意するのまじで骨折れますね...ま、次は自前でなんか作ってみます🥳