ニューラルネットワークの実装(分類)

tensorflow

本章では、ディープラーニングフレームワーク TensorFlow を使ってディープラーニングの基礎と使い方を学んでいきます。TensorFlow とは、Google 社開発している機械学習のためのエンドツーエンドのオープンソースプラットフォームであり、こちらで活発に開発がされています。公式サイトには、チュートリアルや事例、初学者の方がゼロから学ぶためのストーリーも紹介されていますのでご覧ください。

TensorFlow が人気が高い理由として、大きく次の 3 点が挙げられます。

  • 初心者にも使いやすいインターフェースで作られている
  • ユーザー数が世界で多いため、世界中の人からの情報が集まるコミュニティがある
  • エッジデバイスへの連携や分散処理、学習可視化ソフトなどモデル作成以外の部分も包括的なエコシステムがある

使いやすさ、ユーザー数の多さ、取り囲むエコシステムの豊富さが人気の理由だと言えます。

また、TensorFlow では高レベルな API である Keras をモデル構築で使用します。使いやすいものと今は思ってください。簡単にモデルの構築方法として、

が使用することができます。Functional API や Subclassing API を使うと、とても柔軟に複雑なモデルも記述することが可能なのですが、本章から複数続いていく TensorFlow シリーズは、入門編として Sequential API のみを使用してモデル構築していきます。また、今後様々な記法を紹介していきます。

本章の構成

  • TensorFlow の基礎
  • データセットの準備
  • モデルの定義
  • モデルの学習
  • 学習済みモデルの保存と推論

TensorFlow の基礎

TensorFlow は読み込む際に tf として省略することが慣例になっています。早速読み込んでみましょう。バージョンの確認もします。

import tensorflow as tf
      
# バージョンの確認
print(tf.__version__)
      
2.2.0-rc1

TensorFlow を扱っていく上で、モデル構築には TensorFlow 内部に組み込まれている Keras を使用して実装を行っていきます。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
      

ニューラルネットワークモデルの構築では、

  1. データセットの準備
  2. モデルの構築
  3. モデルの学習
  4. 学習済みモデルの精度を評価
  5. 学習済みモデルを用いて推論

以上の 5 点の流れを把握しながら、先に進んでいきましょう。

データセットの準備

本章では、こちらに格納されている wine_class.csv を使用してクラス分類の実装を練習してみましょう。ダウンロードが終われば、アップロードしてください。どのような形式でデータを準備しておくと良いのかといった参考になるため、格納されている生のデータも確認しておきましょう。

31_5

改めて分類とは、上記の図の通り、カテゴリ毎に分けられる境界線を見つける問題でした。

今回のデータセットは CSV ファイルで用意されているため、データの取り扱いは Pandas を使って行います。ファイルのアップロードから始めていきましょう。

# ファイルのアップロード
from google.colab import files
uploaded = files.upload()
      
# データの読み込み
df = pd.read_csv('wine_class.csv')
      
# データの表示(先頭3件)
df.head(3)
      
Class Alcohol Ash Alcalinity of ash Magnesium Total phenols Flavanoids Nonflavanoid phenols Color intensity Hue Proline
0 1 14.23 2.43 15.6 127 2.80 3.06 0.28 5.64 1.04 1065
1 1 13.20 2.14 11.2 100 2.65 2.76 0.26 4.38 1.05 1050
2 1 13.16 2.67 18.6 101 2.80 3.24 0.30 5.68 1.03 1185

データを見てみると、0 番目のカラムにワインの等級が、1 番目以降のカラムにアルコール等のワインの特徴が入っていることがわかります。今回はこのワイン等級を正解データとして、他の特徴を元に分類を行っていきます。

入力変数と目的変数に切り分け

教師あり学習を行う上での最初のステップとして、入力変数と目的変数の切り分けがあります。毎回行うため、スムーズに出来るように練習しておきましょう。

目的変数は、df['列名'] で一列を取り出すことができます。入力変数は、目的変数で指定した以外の列を使用することになりますので、Pandas の df.drop()選択したくない列名を引数に入力し、axis で行方向 (axis=0) なのか、列方向 (axis=1) なのかを指定します。

# 目的変数
t = df['Class']
# 入力変数
x = df.drop('Class', axis=1)
      

正しく切り分けられているかデータの中身を表示して確認しておきます。

x.head(3)
      
Alcohol Ash Alcalinity of ash Magnesium Total phenols Flavanoids Nonflavanoid phenols Color intensity Hue Proline
0 14.23 2.43 15.6 127 2.80 3.06 0.28 5.64 1.04 1065
1 13.20 2.14 11.2 100 2.65 2.76 0.26 4.38 1.05 1050
2 13.16 2.67 18.6 101 2.80 3.24 0.30 5.68 1.03 1185
# 形の確認
x.shape
      
(178, 10)

.shape を使うことで、サンプル数 (178) と入力変数の数 (10) を確認することができます。

Keras で計算できるデータの形式に変換

Keras で計算を行うために、下記の 2 点を満たしているか確認を行っておきましょう。こちらが指定された形式となっていない場合、学習の際にエラーが出てしまいます。

  • 入力変数や目標値が NumPy の ndarray で定義されているか
  • 分類の場合、ラベルが 0 から始まっているか

初学者がよく遭遇しやすいエラーなので、最初は意識しましょう。こちらはフレームワークを使用する上で、必要なルールになりますので、便利なツールを使用するにはこれらを覚える必要があると今は受け入れていきましょう。

NumPy に変換

pd.read_csv で読み込んだ場合、Pandas の DataFrame となっています。

type(x)
      
pandas.core.frame.DataFrame

DataFrame の .values 属性を使用すると NumPy の ndarray を取得できます。こちらは、頻出するので覚えましょう。

type(x.values)
      
numpy.ndarray

目標値を 0 からに変換

t の値を確認してみましょう。np.unique を使って、変数に含まれる値からユニークな値(被りがない値)を出力します。

# ユニークな値を確認
np.unique(t)
      
array([1, 2, 3])

今回は t が 1 から始まって 3 で終わっているため、1, 2, 3 というラベルが割り振られた 3 クラスの分類であることがわかります。目標値は 0 から始める必要があるため、1, 2, 30, 1, 2 としましょう。

どのように値を変更するかというと NumPy では、全体に対する引き算もサポートしているため、t - 1 とすればラベル全体に 1 が引かれ、0, 1, 2 となります。この機能は NumPy のブロードキャストと呼ばれます。

# ラベルを 0 から始める
t = t.values - 1
x = x.values
      
 t, type(t)
      
(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, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]), numpy.ndarray)
x, type(x)
      
(array([[1.423e+01, 2.430e+00, 1.560e+01, ..., 5.640e+00, 1.040e+00, 1.065e+03], [1.320e+01, 2.140e+00, 1.120e+01, ..., 4.380e+00, 1.050e+00, 1.050e+03], [1.316e+01, 2.670e+00, 1.860e+01, ..., 5.680e+00, 1.030e+00, 1.185e+03], ..., [1.327e+01, 2.260e+00, 2.000e+01, ..., 1.020e+01, 5.900e-01, 8.350e+02], [1.317e+01, 2.370e+00, 2.000e+01, ..., 9.300e+00, 6.000e-01, 8.400e+02], [1.413e+01, 2.740e+00, 2.450e+01, ..., 9.200e+00, 6.100e-01, 5.600e+02]]), numpy.ndarray)

学習用データとテスト用データに分割

次に、学習用とテスト用にデータセットを分割します。これはディープラーニング問わず、機械学習全般で使用する考え方です。

使い慣れた scikit-learn に用意されている sklearn.model_selection.train_test_split を使用しましょう。今回は 2 分割とし、学習データを 70%、テストデータを 30% とします。

from sklearn.model_selection import train_test_split

# 学習データとテストデータの分割
x_train, x_test, t_train, t_test = train_test_split(x, t, train_size=0.7, random_state=0)
      
x_train.shape, x_train.dtype, x_test.shape, x_test.dtype
      
((124, 10), dtype('float64'), (54, 10), dtype('float64'))
t_train.shape, t_train.dtype, t_test.shape, t_test.dtype
      
((124,), dtype('int64'), (54,), dtype('int64'))

ニューラルネットワークの学習データのデータ型は 32 bit にするのが多く見受けられます。現在の 64bit からデータ型を変更する処理をキャストといいます。float 型と int 型ともに 32bit にキャストしましょう。np.array の引数でデータ型を指定できますので np.float32np.int32 とします。

# 32 bit にキャスト
x_train = np.array(x_train, np.float32)
x_test = np.array(x_train, np.float32)
t_train = np.array(t_train, np.int32)
t_test = np.array(t_train, np.int32)
      

モデルの定義

モデルの定義では、最もシンプルな tf.keras.models.Sequential モデルを構築します。使い方はシンプルで以下の通りです。

  1. tf.keras.models.Sequential() を用意する
  2. 内部に tf.keras.layers.層の名前() を積み重ねて記述する
  3. 変数 (model) に渡してインスタンス化を行う

この tf.keras.layers.層の名前() の部分で全結合層 (Dense) や活性化関数 (Activation)などを追加していきます。以下のコード、コメントを参考にモデルを構築していきましょう。

モデル構築の前に、乱数のシードの固定も行います。Python の乱数、random 関数、 numpy そして tensorflow を固定します。乱数が固定され、CPU であれば再現性の確保が可能です。GPU を使用する場合、再現性の確保ができません(2020 年 4 月現在)。

import os, random

def reset_seed(seed=0):
    os.environ['PYTHONHASHSEED'] = '0'
    random.seed(seed) # random関数のシードを固定
    np.random.seed(seed) # numpyのシードを固定
    tf.random.set_seed(seed) # tensorflowのシードを固定
      

モデルを定義していきたいのですが、モデルを構築するときに頻出する 2 つを紹介します。

  • tensorflow.keras.models
    • モデルインスタンスの立ち上げやモデルの保存を担当
  • tensorflow.keras.layers
    • 全結合層 (Dense) などニューラルネットワークの層 (layer) の定義を担当

最初はこの 2 つを抑えておきましょう。

from tensorflow.keras import models, layers
      

今回構築するモデルは、入力層のノード数が 10、出力層のノード数が 3 のモデルです。入力変数と目的変数の数は問題設定から決まっているため間違えないようにしましょう。

中間層のノード数だけは、人間側で自由に設計する必要があります。今回は適当に中間層(隠れ層)のノード数を 10 とし、実装してみます。この値を大きくすればするほど、パラメータ数が増加し、小さくすればするほど、パラメータ数は減少します。試行錯誤を繰り返し、良い値を決めることになります。

モデルの層を定義

本節で定義したいモデル構造はこちらになります。最初に頭の中でイメージから作っていきましょう。

31_2

上記でもお伝えしましたが、層を定義する際に Keras では tf.keras.layers を使用します。この中に全結合層や次の章で扱う畳み込み層が定義されています。

models.Sequential 内部に定義したい層をリスト形式で入れていきます。他にも .add() を使った方法もあります。

今回は上図で表した全結合層のみを使ったモデルを構築します。Keras では全結合層を layers.Dense() と名前が付けられています。いくつか設定しなければいけない引数があり、

  • layers.Dense(units, activation, input_shape)
    • units : 出力の次元数
    • activation : 使用する活性化関数
    • input_shape : 入力の次元数

が主に使用する引数になります。input_shape はモデルを構築するときの最初の層のみに必要ですが、その他の層では自動的に入力形状を受け渡してくれます。

それではモデル構築していきましょう。

# シードの固定
reset_seed(0)

# モデルの構築
model = models.Sequential([
    layers.Dense(units=10, activation='relu', input_shape=(10,)),
    layers.Dense(units=3, activation='softmax')
])
      

以下のように .add() メソッドを使って、用意しておいたモデルの箱 (models.Sequential()) の中に、層 (layers.Dense()) を追加していくという方法もよくネットで見かけることもあります。どちらも同じ操作になるので、使いやすい方法を選んでください。

# モデルの構築
# model = models.Sequential()
# model.add(layers.Dense(units=10, activation='relu', input_shape=(10,)))
# model.add(layers.Dense(units=3, activation='softmax'))
      

こちらでモデル構築完了です。しかし、2 つ目の層の活性化関数 (activation) で softmax を使用しています。ですので、実際のモデル構造としては以下のイメージになります。

31_3

ソフトマックス関数は主に分類で使用される活性化関数であり、

a(u)=exp(u)m=1Mexp(um)\begin{array}{c} a(u) = \frac{\exp(u)}{\sum_{m=1}^{M} \exp(u_m)} \end{array}

で表されます。ここで、MM は変換後の出力のノード数であり、exp()\exp(\cdot) は自然対数 ee を底とした指数関数を表しています。機能としては出力される値の総和を 11 とすることが目的であり、それぞれの出力ノードの値は [0,1][0, 1] の間で値を取ります。

現時点では、分類には出力層の活性化関数にソフトマックス関数が使われる程度の認識で構いません。理解度が深まったときに振り返りましょう。

モデルの学習プロセスを定義

31_4

層の深さや活性化関数を定義したら、他にも学習に必要な目的関数や最適化手法などの詳細設定をします。Keras では model.compile() で学習プロセスを構成し、3 つの重要な引数を取ります。

  • optimizer:最適化手法を指定
    • tf.keras.optimizers モジュールからインスタンスを渡す
    • 代表例として SGDAdam があります
  • loss:目的関数を指定
    • tf.keras.losses モジュールからインスタンスを渡す
    • 回帰:平均ニ乗誤差 MeanSquaredError : MSE
    • 分類:SparseCategoricalCrossentropy or BinaryCrossentropy
  • metrics:学習中にモニタリングする評価指標を指定
    • tf.keras.metrics モジュールからインスタンスを渡す
    • 回帰:MeanAbsoluteError
    • 分類:Accuracy

今回は最適化手法に SGD を使用し、目的関数には 3 クラス分類のため、SparseCategoricalCrossentropy、評価指標には Accuracy として正解率を指定します。

Keras ではより簡単に使用するために、モジュールからインスタンスを渡す方法の他に、文字列で渡す方法があります。本章では実装のしやすさを重視し、文字列で渡す方法を使用します。

# モデルのコンパイル
model.compile(optimizer='sgd',
               loss='sparse_categorical_crossentropy',
               metrics=['accuracy'])

# こちらでも同じ
# model.compile(optimizer=tf.keras.optimizers.SGD(),
#                loss=tf.keras.losses.SparseCategoricalCrossentropy(),
#                metrics=[tf.keras.metrics.Accuracy()])
      

モデルの学習

構築したモデルの学習を実行するには model.fit を使います。重要な引数は以下の 3 点です。

  • epochs:入力データ全体に対する 1 つの反復回数を指定します
  • batch_size:データをバッチにスライスし、バッチごとに反復処理されます。その各バッチのサイズを指定します
  • validation_data:検証データのパフォーマンスをモニタリングするために、(入力値, 目標値) のタプルを渡すと各エポック終わりに渡された推論モードで lossmetrics の値を表示します

次章で詳しく説明しますので、本章では実装することをゴールにして一度学習を実行しましょう。

# モデルの学習
history = model.fit(x_train, t_train,
          batch_size=10,
          epochs=10,
          validation_data=(x_test, t_test))
      
Epoch 1/10 13/13 [==============================] - 0s 12ms/step - loss: 471.6185 - accuracy: 0.2984 - val_loss: 1.0982 - val_accuracy: 0.3952 Epoch 2/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0977 - accuracy: 0.3952 - val_loss: 1.0970 - val_accuracy: 0.3952 Epoch 3/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0979 - accuracy: 0.3952 - val_loss: 1.0965 - val_accuracy: 0.3952 Epoch 4/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0976 - accuracy: 0.3952 - val_loss: 1.0960 - val_accuracy: 0.3952 Epoch 5/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0960 - accuracy: 0.3952 - val_loss: 1.0953 - val_accuracy: 0.3952 Epoch 6/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0952 - accuracy: 0.3952 - val_loss: 1.0946 - val_accuracy: 0.3952 Epoch 7/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0954 - accuracy: 0.3952 - val_loss: 1.0942 - val_accuracy: 0.3952 Epoch 8/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0955 - accuracy: 0.3952 - val_loss: 1.0938 - val_accuracy: 0.3952 Epoch 9/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0948 - accuracy: 0.3952 - val_loss: 1.0934 - val_accuracy: 0.3952 Epoch 10/10 13/13 [==============================] - 0s 3ms/step - loss: 1.0946 - accuracy: 0.3952 - val_loss: 1.0930 - val_accuracy: 0.3952

はじめてのディープラーニングの学習が終わりました。実現場では、もちろんさらに複雑なモデルを構築したり、データの前処理などを大量に行うことになるのですが、第一ステップとしてはこちらで大枠の把握は完了です。

学習済みモデルの精度を確認

正解率 (accuracy) と目的関数の値 (loss) が途中からほとんど変化しておらず、学習が上手く進んていません。文字で見てもなんとなく分かるのですが、評価しやすいようにするために文字ではなく直感的にグラフで可視化してみましょう。

学習過程の値は history.history に格納されています。

# 学習過程
history.history
      
{'loss': [471.6185302734375, 1.09772527217865, 1.097879409790039, 1.0975836515426636, 1.0960195064544678, 1.0952260494232178, 1.0954293012619019, 1.0954753160476685, 1.094836950302124, 1.0945507287979126], 'accuracy': [0.2983871102333069, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827], 'val_loss': [1.0981855392456055, 1.0970168113708496, 1.0964851379394531, 1.0960479974746704, 1.0952891111373901, 1.0946414470672607, 1.0941823720932007, 1.0938136577606201, 1.0934263467788696, 1.0930137634277344], 'val_accuracy': [0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827, 0.39516130089759827]}

まだ確認しづらいため、Pandas の DataFrame 形式に変換します。

# Pandas 形式
result = pd.DataFrame(history.history)
result.head()
      
loss accuracy val_loss val_accuracy
0 471.618530 0.298387 1.098186 0.395161
1 1.097725 0.395161 1.097017 0.395161
2 1.097879 0.395161 1.096485 0.395161
3 1.097584 0.395161 1.096048 0.395161
4 1.096020 0.395161 1.095289 0.395161

可視化するためにはいつも Matplotlib を使っていましたが、Pandas には内部で Matplotlib が入っており、.plot() メソッドを使用するとすぐに可視化することができます。

# 目的関数の値
result[['loss', 'val_loss']].plot();
      
<Figure size 432x288 with 1 Axes>
# 正解率
result[['accuracy', 'val_accuracy']].plot();
      
<Figure size 432x288 with 1 Axes>

グラフで見たほうが数字で見るよりも直感的に理解することができます。残念ながら今回出来上がったモデルはあまり良いモデルではないと言えそうです。

モデル精度の向上

データセットの準備、モデルの定義、モデルの学習までの一連の流れを説明しましたが、まだ高い精度を得ることはできていません。ここからが試行錯誤の段階で、作成したモデルを現場で活用できるように改善していく必要があります。

まず精度を向上させる方法として、バッチノーマリゼーション (Batch Normalization) が挙げられます。

バッチノーマリゼーションでは、ミニバッチごとに平均 x¯\bar x と 標準偏差 σ\sigma を求め、

xs=xx¯σx^=αxs+β\begin{aligned} x_s &= \frac{x-\bar x}{\sigma} \\\\ \hat x &= \alpha x_s + \beta \end{aligned}

のように x^\hat x へと各変数ごとに変換を行います。ここで、α\alphaβ\beta はパラメータであり、単純な正規化のように平均 0、標準偏差 1 とするのではなく、平均 β\beta、標準偏差 α\alpha となるように変換を行います。必ずしも平均 0、標準偏差 1 が良いとは限らないためです。

実装としては、各バッチ毎に平均と標準偏差を定めて標準化を行うといった非常に簡単な手法なのですが、こちらを層に加えることで各変数感のスケールによる差を吸収できます。

それでは、バッチノーマリゼーションを加えて試してみましょう。tf.keras.layers.BatchNormalization で適用できます。

# シードの固定
reset_seed(0)

# モデルの構築
model = tf.keras.models.Sequential([
    tf.keras.layers.BatchNormalization(input_shape=(10,)),
    tf.keras.layers.Dense(10, activation='relu'),
    tf.keras.layers.Dense(3, activation='softmax')
])

# モデルのコンパイル
model.compile(optimizer='sgd',
               loss='sparse_categorical_crossentropy',
               metrics=['accuracy'])

# モデルの学習
history = model.fit(x_train, t_train,
          batch_size=10,
          epochs=50,
          validation_data=(x_test, t_test))
      
Epoch 1/50 13/13 [==============================] - 0s 8ms/step - loss: 1.1150 - accuracy: 0.4113 - val_loss: 6.6873 - val_accuracy: 0.2823 Epoch 2/50 13/13 [==============================] - 0s 3ms/step - loss: 0.9671 - accuracy: 0.5242 - val_loss: 4.8825 - val_accuracy: 0.4194 Epoch 3/50 13/13 [==============================] - 0s 3ms/step - loss: 0.8989 - accuracy: 0.6371 - val_loss: 4.2317 - val_accuracy: 0.5968 Epoch 4/50 13/13 [==============================] - 0s 3ms/step - loss: 0.8247 - accuracy: 0.6452 - val_loss: 3.8477 - val_accuracy: 0.5968 Epoch 5/50 13/13 [==============================] - 0s 3ms/step - loss: 0.8019 - accuracy: 0.6774 - val_loss: 3.5677 - val_accuracy: 0.5887 Epoch 6/50 13/13 [==============================] - 0s 3ms/step - loss: 0.6577 - accuracy: 0.8065 - val_loss: 3.2647 - val_accuracy: 0.5806 Epoch 7/50 13/13 [==============================] - 0s 3ms/step - loss: 0.5698 - accuracy: 0.8548 - val_loss: 2.9946 - val_accuracy: 0.5726 Epoch 8/50 13/13 [==============================] - 0s 4ms/step - loss: 0.5303 - accuracy: 0.8710 - val_loss: 2.7347 - val_accuracy: 0.5806 Epoch 9/50 13/13 [==============================] - 0s 3ms/step - loss: 0.4622 - accuracy: 0.8710 - val_loss: 2.4315 - val_accuracy: 0.5806 Epoch 10/50 13/13 [==============================] - 0s 3ms/step - loss: 0.5235 - accuracy: 0.8065 - val_loss: 2.1811 - val_accuracy: 0.5887 Epoch 11/50 13/13 [==============================] - 0s 3ms/step - loss: 0.4766 - accuracy: 0.8226 - val_loss: 1.8823 - val_accuracy: 0.5887 Epoch 12/50 13/13 [==============================] - 0s 3ms/step - loss: 0.4506 - accuracy: 0.8790 - val_loss: 1.6257 - val_accuracy: 0.5968 Epoch 13/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3735 - accuracy: 0.9113 - val_loss: 1.3925 - val_accuracy: 0.6048 Epoch 14/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3884 - accuracy: 0.9113 - val_loss: 1.1859 - val_accuracy: 0.6694 Epoch 15/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3372 - accuracy: 0.9113 - val_loss: 1.0094 - val_accuracy: 0.6935 Epoch 16/50 13/13 [==============================] - 0s 3ms/step - loss: 0.4454 - accuracy: 0.8548 - val_loss: 0.8410 - val_accuracy: 0.7097 Epoch 17/50 13/13 [==============================] - 0s 4ms/step - loss: 0.3330 - accuracy: 0.9032 - val_loss: 0.7090 - val_accuracy: 0.7581 Epoch 18/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3839 - accuracy: 0.8710 - val_loss: 0.6052 - val_accuracy: 0.7903 Epoch 19/50 13/13 [==============================] - 0s 3ms/step - loss: 0.4638 - accuracy: 0.8710 - val_loss: 0.5266 - val_accuracy: 0.8226 Epoch 20/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2732 - accuracy: 0.9032 - val_loss: 0.4553 - val_accuracy: 0.8468 Epoch 21/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2982 - accuracy: 0.9032 - val_loss: 0.3951 - val_accuracy: 0.8710 Epoch 22/50 13/13 [==============================] - 0s 3ms/step - loss: 0.4120 - accuracy: 0.9194 - val_loss: 0.3526 - val_accuracy: 0.8871 Epoch 23/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3219 - accuracy: 0.9032 - val_loss: 0.3106 - val_accuracy: 0.9194 Epoch 24/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3138 - accuracy: 0.9113 - val_loss: 0.2830 - val_accuracy: 0.9274 Epoch 25/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3366 - accuracy: 0.8710 - val_loss: 0.2569 - val_accuracy: 0.9274 Epoch 26/50 13/13 [==============================] - 0s 4ms/step - loss: 0.2489 - accuracy: 0.9274 - val_loss: 0.2378 - val_accuracy: 0.9274 Epoch 27/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3115 - accuracy: 0.8952 - val_loss: 0.2218 - val_accuracy: 0.9274 Epoch 28/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2431 - accuracy: 0.9435 - val_loss: 0.2058 - val_accuracy: 0.9435 Epoch 29/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2250 - accuracy: 0.9274 - val_loss: 0.1953 - val_accuracy: 0.9435 Epoch 30/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2329 - accuracy: 0.9274 - val_loss: 0.1858 - val_accuracy: 0.9435 Epoch 31/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2498 - accuracy: 0.9435 - val_loss: 0.1768 - val_accuracy: 0.9435 Epoch 32/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2215 - accuracy: 0.9274 - val_loss: 0.1656 - val_accuracy: 0.9516 Epoch 33/50 13/13 [==============================] - 0s 3ms/step - loss: 0.1809 - accuracy: 0.9516 - val_loss: 0.1556 - val_accuracy: 0.9516 Epoch 34/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2038 - accuracy: 0.9194 - val_loss: 0.1469 - val_accuracy: 0.9516 Epoch 35/50 13/13 [==============================] - 0s 4ms/step - loss: 0.2325 - accuracy: 0.9516 - val_loss: 0.1393 - val_accuracy: 0.9597 Epoch 36/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2832 - accuracy: 0.9274 - val_loss: 0.1333 - val_accuracy: 0.9597 Epoch 37/50 13/13 [==============================] - 0s 3ms/step - loss: 0.1941 - accuracy: 0.9516 - val_loss: 0.1283 - val_accuracy: 0.9597 Epoch 38/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2173 - accuracy: 0.9435 - val_loss: 0.1251 - val_accuracy: 0.9597 Epoch 39/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2012 - accuracy: 0.9355 - val_loss: 0.1199 - val_accuracy: 0.9677 Epoch 40/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2609 - accuracy: 0.9032 - val_loss: 0.1158 - val_accuracy: 0.9677 Epoch 41/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3399 - accuracy: 0.9516 - val_loss: 0.1158 - val_accuracy: 0.9677 Epoch 42/50 13/13 [==============================] - 0s 3ms/step - loss: 0.1929 - accuracy: 0.9274 - val_loss: 0.1131 - val_accuracy: 0.9677 Epoch 43/50 13/13 [==============================] - 0s 3ms/step - loss: 0.1476 - accuracy: 0.9516 - val_loss: 0.1103 - val_accuracy: 0.9677 Epoch 44/50 13/13 [==============================] - 0s 4ms/step - loss: 0.1795 - accuracy: 0.9597 - val_loss: 0.1078 - val_accuracy: 0.9677 Epoch 45/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2199 - accuracy: 0.9113 - val_loss: 0.1056 - val_accuracy: 0.9677 Epoch 46/50 13/13 [==============================] - 0s 3ms/step - loss: 0.3254 - accuracy: 0.8871 - val_loss: 0.1034 - val_accuracy: 0.9677 Epoch 47/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2245 - accuracy: 0.9355 - val_loss: 0.1020 - val_accuracy: 0.9758 Epoch 48/50 13/13 [==============================] - 0s 3ms/step - loss: 0.1482 - accuracy: 0.9597 - val_loss: 0.0990 - val_accuracy: 0.9758 Epoch 49/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2680 - accuracy: 0.9194 - val_loss: 0.0973 - val_accuracy: 0.9758 Epoch 50/50 13/13 [==============================] - 0s 3ms/step - loss: 0.2459 - accuracy: 0.9113 - val_loss: 0.0949 - val_accuracy: 0.9758
# 正解率と損失を Pandas の形式に変換
result_batchnorm = pd.DataFrame(history.history)

# 目的関数の値
result_batchnorm[['loss', 'val_loss']].plot();
      
<Figure size 432x288 with 1 Axes>
# 正解率
result_batchnorm[['accuracy', 'val_accuracy']].plot();
      
<Figure size 432x288 with 1 Axes>

飛躍的に正解率が向上しました。正解率の値が良くなっていれば成功です。

このようにディープラーニングでは、Batch Normalization を含めた細かな精度向上のポイントがあるため、今後も調べながら進めていきましょう。TensorFlow では、多くの機能がすでに実装されているため、上記のコードのように少し付け加えるだけでその効果を検証できるため、非常に便利です。

学習済みモデルの保存と推論

学習が終わると、学習済みモデルが得られます。TensorFlow では、2 つの方法でモデルの保存およびロードを行えます。

  • 高レベル API:model.save & tf.keras.models.load_model
  • 低レベル API:tf.saved_model.save & tf.saved_model.load

今回は前者の方法を指定し、保存形式が HDF5 と呼ばれるファイルフォーマットを取ります。HDF5 形式では、モデル全体を 1 つのファイルに保存することができ、モデルのアーキテクチャとモデルの重みなどの情報が入っています。

学習済みモデルには、save メソッドがついており、model.save(filepath, save_format) を指定します。

  • filepath : 保存するファイルの場所と名前を指定
  • save_format : HDF5Saved Model のどちらかかを指定

それでは保存しましょう。

# モデルの保存
model.save(filepath='wine_model.h5', save_format='h5')
      

これで学習済みモデルの保存は完了です。

学習済みモデルのロード

それでは、先程保存したファイルを使用し、モデルを再構築します。models.load_model() を使用します。

# モデルの読み込み
loaded_model = tf.keras.models.load_model('wine_model.h5')
      

予測値の計算

本来であれば学習を終えたモデルは新しく取得したデータに対して推論しますが、今回は試しに学習で使用したデータセットの最初のサンプルに対する予測値を計算してみましょう。

# データの準備
sample = x_train[0]
sample.shape
      
(10,)

学習済みのモデルに新しく入力変数を入れて、値を出力する順伝播の計算を行うには .predict() メソッドを使用します。

# 予測値の計算
loaded_model.predict(sample)
      
---------------------------------------------------------------------------ValueError Traceback (most recent call last)<ipython-input-78-b1f9cfbff078> in <module>() ----> 1 loaded_model.predict(sample) /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training.py in predict(self, x, batch_size, verbose, steps, callbacks, max_queue_size, workers, use_multiprocessing) 1011 max_queue_size=max_queue_size, 1012 workers=workers, -> 1013 use_multiprocessing=use_multiprocessing) 1014 1015 def reset_metrics(self): /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_v2.py in predict(self, model, x, batch_size, verbose, steps, callbacks, max_queue_size, workers, use_multiprocessing, **kwargs) 496 model, ModeKeys.PREDICT, x=x, batch_size=batch_size, verbose=verbose, 497 steps=steps, callbacks=callbacks, max_queue_size=max_queue_size, --> 498 workers=workers, use_multiprocessing=use_multiprocessing, **kwargs) 499 500 /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_v2.py in _model_iteration(self, model, mode, x, y, batch_size, verbose, sample_weight, steps, callbacks, max_queue_size, workers, use_multiprocessing, **kwargs) 424 max_queue_size=max_queue_size, 425 workers=workers, --> 426 use_multiprocessing=use_multiprocessing) 427 total_samples = _get_total_number_of_samples(adapter) 428 use_sample = total_samples is not None /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_v2.py in _process_inputs(model, mode, x, y, batch_size, epochs, sample_weights, class_weights, shuffle, steps, distribution_strategy, max_queue_size, workers, use_multiprocessing) 644 standardize_function = None 645 x, y, sample_weights = standardize( --> 646 x, y, sample_weight=sample_weights) 647 elif adapter_cls is data_adapter.ListsOfScalarsDataAdapter: 648 standardize_function = standardize /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training.py in _standardize_user_data(self, x, y, sample_weight, class_weight, batch_size, check_steps, steps_name, steps, validation_split, shuffle, extract_tensors_from_dataset) 2381 is_dataset=is_dataset, 2382 class_weight=class_weight, -> 2383 batch_size=batch_size) 2384 2385 def _standardize_tensors(self, x, y, sample_weight, run_eagerly, dict_inputs, /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training.py in _standardize_tensors(self, x, y, sample_weight, run_eagerly, dict_inputs, is_dataset, class_weight, batch_size) 2408 feed_input_shapes, 2409 check_batch_axis=False, # Don't enforce the batch size. -> 2410 exception_prefix='input') 2411 2412 # Get typespecs for the input data and sanitize it if necessary. /tensorflow-2.1.0/python3.6/tensorflow_core/python/keras/engine/training_utils.py in standardize_input_data(data, names, shapes, check_batch_axis, exception_prefix) 580 ': expected ' + names[i] + ' to have shape ' + 581 str(shape) + ' but got array with shape ' + --> 582 str(data_shape)) 583 return data 584 ValueError: Error when checking input: expected batch_normalization_input to have shape (10,) but got array with shape (1,)

エラーが確認できました。ValueError: Error when checking input: expected batch_normalization_input to have shape (10,) but got array with shape (1,) とあります。

(10,) という形状を予想していたのに、(1,) として値が入力されていますよ、と書いてあります。注意してほしいのですが、推論で使用する際には、(バッチサイズ, 入力変数の数) という形式となっていないと上記のようなエラーが起きます。今回であれば、(1, 10) が望ましいデータの形と言えます。

バッチサイズは次章で紹介しますが、一度に計算するサンプルの数と思っていただければ大丈夫です。

# 形を変換
sample = sample.reshape(1, 10)
sample.shape
      
(1, 10)
# 予測値の計算
y = loaded_model.predict(sample)
y
      
array([[0.97033286, 0.00983891, 0.01982831]], dtype=float32)

3 つの値を出力するようにモデルを構築していたので上手く推論できました。結果としては、最も大きな値が入ったインデックスのみを取得したいです。このような場合にはnp.argmax() を使用し、取得します。

# 最も値の大きなラベルを取得
np.argmax(y)
      
0
# 正解ラベル
t_train[0]
      
0

このように学習済みモデルを使用して推論を実行できました。

本章では、TensorFlow の基礎として TensorFlow のモデル構築としての使用方法を主に紹介してきました。Keras を使用するとモデル構築も非常に簡単に行うことができることが体感できたかと思います。もちろん、実際にはさらに複雑なモデルを構築することになることも多いので、その場合には Functional API や Subclassing API を使用します。しかし、気軽に試してみたい場合などには本章の流れを振り返りながら Sequential API を積極的に使用していきましょう。次章では、回帰を扱います。

shareアイコン