Python {Article028}

ようこそ「Python」へ...

Pythonでディープラーニング 超入門: TensorFlow+Kerasで数字のイメージを認識させるには【Deep Learning】

ここではPythonのライブラリTensorFlow+Kerasを使用して手書きの数字の画像(28x28のイメージ)を認識させるデープラーニング(Deep Learning)について解説します。 ここで使用するイメージはMNLISTを使用します。 「MNLIST」とは画像処理システムの学習に使用される手書き数字画像のデータベースを意味します。 この画像データには7万件の手書きの数字0-9が格納されています。 この7万件の画像データからパターン学習して画像の数字を認識させます。 動物を認識したり、人間の顔認識のAIは基本的にはここで解説するような処理を行って認識させています。

ここで解説するDeep Learningは、 「データのロード ▶ データの標準化・正規化 ▶ モデル作成&コンパイル ▶ 学習 ▶ 評価 ▶ 予測」 の手順で行います。 Machine Learningも基本的に同じ手順で行います。

巷ではよくAI(Artificial Intelligence)、ML(Machine Learning)、DL(Deep Learning)という言葉を聞きますが、 AIはMLとDLを含んだ総称です。そしてMLはDLを含んでいます。 そしてDLはNeural Networks(ニューラルネットワーク)を使用しています。 MLとDLはAIのサブセットということになります。

MLは主にExcelで表現できるようなデータに適しています。 たとえば、売上データ、COVIC-19の感染者データなどはExcelで表現できるのでMLに向いています。 DLは主にExcelで表現できないようなデータに適しています。 たとえば、画像データ、音声データなどはExcelで表現できないのでDLに向いています。 いずれの場合も、データが0から1の数値に変換できるものはML, DLで利用できます。 ここでは画像データを扱うのでDLを使うことになります。

今回のディープラーニング超入門ではTensorFlow, Kerasを使用します。 これらのライブラリを使用できるように 「記事(Article024:Jupter Notebook)」と「記事(Article025: Visual Studio Code)」 を参照して事前に開発環境を準備しておいてください。

ここではVisula Studio Code(VSC)の「Python Interactive window」 を使用してJupter Notebookのような環境で説明します。 VSCを通常の環境からインタラクティブな環境に切り換えるにはコードを記述するときコメント「# %%」を入力します。 詳しい、操作手順については「ここ」 を参照してください。 インタラクティブな環境では、Pythonの「print(), plt.show()」などを使う必要がないので掲載しているコードでは省略しています。 VSCで通常の環境で使用するときは、必要に応じて「print(), plt.show()」等を追加してください。

この記事では、Pandas、Matplotlibのライブラリを使用しますので 「記事(Article001) | 記事(Article002) | 記事(Article003) | 記事(Article004)」 を参照して事前にインストールしておいてください。 Pythonのコードを入力するときにMicrosoftのVisula Studio Codeを使用します。 まだ、インストールしていないときは「記事(Article001)」を参照してインストールしておいてください。

説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。

click image to zoom!
AI
click image to zoom!
Model
click image to zoom!
Layer

TensorFlow+Kerasを使用して手書きの画像を認識させる

  1. 手書きの画像データをロードする

    Visual Studio Codeを起動したら行1-18をコピペして実行します。 行3-6ではPythonのライブラリを取り込んでいます。 行10-11ではmnistのインスタントを生成して、mnistのload_data()メソッドで手書きの数字画像をロードしています。 戻り値はtrainとtestに分割されて格納されます。 x_trainには6万件の手書きの数字画像データ、x_testには1万件の手書きの数字画像データが格納されます。 y_trainにはx_trainの手書きの数字画像データに対応した実際の数値(0-9)が格納されます。 同様にy_testにはy_trainの手書きの数字画像に対応した実際の数値(0-9)が格納されます。
    # Import the libraries
    # %%
    import numpy as np
    import matplotlib.pyplot as plt
    import tensorflow as tf
    from tensorflow.python.keras.layers.core import Activation
    #print(tf.__version__)
    
    # Load 28x28 images of hand-written digits 0-9
    mnist = tf.keras.datasets.mnist     
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    #print(x_train.shape)
    #print(x_test.shape)
    #print(x_train[0])
    #plt.imshow(x_train[0], cmap=plt.cm.binary)
    #plt.show()
    #plt.imshow(x_train[1], cmap=plt.cm.binary)
    #plt.show()
    click image to zoom!
    図1-1
    図1-1ではVisual Studio Code(VSC)のインタラクティブウィンドウから行12-14を実行させています。 x_trainには28x28の数字の画像データが6万件格納されているのがわかります。 そしてx_testには28x28のBitパターンで数字の画像データが1万件格納されているのがわかります。 「x_train[0]」ではx_trainの配列要素から先頭の数字のデータを表示しています。 数字の画像データが28x28 Bitで構成されているのが分かります。 Bitパターンだと人間にはよく理解できないので図1-2でMatplotlibで可視化しています。


    click image to zoom!
    図1-2
    図1-2ではインタラクティブウィンドウから行15-18を実行してx_train[0]とx_train[1]の画像データをMatplotlibのimshow()メソッドで可視化しています。 手書きの数字「5」と「0」が表示されています。
  2. 画像データをNormalizeする

    行2-3ではnormalize()メソッドでx_trainとx_testに格納されている数字の画像データを処理しやすいように標準化・正規化しています。 標準化・正規化後の画像は図2で表示しています。
    # Normalize images
    x_train = tf.keras.utils.normalize(x_train, axis=1)
    x_test = tf.keras.utils.normalize(x_test, axis=1)
    
    #plt.imshow(x_train[0], cmap=plt.cm.binary)
    #plt.show()
    #plt.imshow(x_train[1], cmap=plt.cm.binary)
    #plt.show()
    click image to zoom!
    図2
    図2はインタラクティブウィンドウから行5-8を実行した画像です。 Normalize後のx_train[0]とx_train[1]の画像が表示されています。 図1-2と比較すると画像の数字が薄くなっています。
  3. ModelにLayerを追加してCompileする

    行2ではSequential()メソッドでSequential型のModelのインスタントを生成しています。 Kerasにはさまざまなモデルが用意されていますがここでは「Sequential」型を使用します。 「Sequential」型のモデルでは入力データは各Layerに順番に転送されます。

    行3-6ではModelにLayerを4個追加しています。 Layerの概念は図3-3をご覧ください。 行3で追加するLayerはInput Layer、 行4-5で追加するLayerはHidden Layer、 行6で追加するLayerはOutput Layerになります。 行3のInput LayerではFlatten()メソッドで手書きの数字画像をフラットにしています。 フラットの概念は図3-1と図3-2で解説しています。

    行4-5のHidden Layerでは「Dense」型のLayerを追加しています。 KerasにはさまざまなLayer型が用意されていますがここでは「Dense」型のLayerを使用します。 Dense()メソッドの引数に「units=」を指定していますがこれは図3-3の「○:Perceptron」の最小単位です。 Dense()メソッドの引数に「activation=」を指定していますが、 ここでは活性化関数として「tf.nn.relu」を指定しています。 ReLu関数はHidden Layerでもっとも頻繁に利用される活性化関数です。 入力が0未満の場合は出力は0、入力が0以上のときは入力をそのまま出力します。

    行6のOutput Layerでは「Dense」型のLayerを追加しています。 Dense()メソッドの引数「activation=」には「tf.nn.softmax」を指定しています。 SoftMax関数は一般にOutput Layerで使用されます。 入力に関わらず出力は 0.0 ~ 1.0 の範囲をとり出力の総和が 1.0 となります。

    行8-10ではModelのcompile()メソッドでモデルをコンパイルしています。 compile()メソッドの引数「optimizer=」には最適化アルゴリズムとして「adam」を指定しています。 最適化アルゴリズムには「SGD, Ftrl, Adam, Adamax, Nadam,...」などが指定できます。 引数「loss=」には損失関数として「sparse_categorical_crossentropy」を指定しています。 一般に画像データの解析にはこのオプションが利用されます。 最後の引数「metrics=」には損失関数の補足情報として「'accuracy'」を指定しています。
    # Create a Model & Compile
    model = tf.keras.models.Sequential()
    model.add(tf.keras.layers.Flatten())    # 28*28 images => convert to flat images
    model.add(tf.keras.layers.Dense(units=128, activation=tf.nn.relu))
    model.add(tf.keras.layers.Dense(units=128, activation=tf.nn.relu))
    model.add(tf.keras.layers.Dense(units=10, activation=tf.nn.softmax))
    
    model.compile(optimizer='adam', 
        loss='sparse_categorical_crossentropy', 
        metrics=['accuracy'])
    click image to zoom!
    図3-1
    図3-1は行3のFlatten()メソッドを図で解説したものです。 この図では3x3のパターンになっていますが28x28に読み替えてください。 この図のように28x28のBitパターンが連続したフラットなBitパターンに変換されます。
    click image to zoom!
    図3-2
    図3-2は行3のFlatten()メソッドの処理をBitパターンではなく画像で解説したものです。 この図のカラー(色)を数字の「0-9」に読み替えてください。 この図のように数字が連続して配置されるようになります。
    click image to zoom!
    図3-3
    図3-3はModelのLayerの概念図です。 この図のModelはInput Layer, Hidden Layer, Output Layerから構成されています。 Hidden Layerには2個のLayerがあります。 ここでも図にようにInput Layer、2個のHidden Layer、Output Layerを追加しています。

    「Sequential」型のモデルでは○の「Perceptron」がInput Layer ▶ Hidden Layer ▶ Output Layerの順番に転送されます。
  4. Fit(学習)させる

    行2ではModelのfit()メソッドで学習データを使用して学習させています。 fit()メソッドの引数1には「x_tran」、引数2には「y_train」を指定します。 x_trainには6万件の手書き数字の画像データ(28x28のBitパターン)が格納されています。 y_trainには6万件の数値(0-9)が格納されています。 y_trainに格納されている数値はx_trainの画像の数字に対応します。 詳細は図4-2をご覧ください。 引数3の「epochs=」には学習回数を指定します。 ここでは「3」を指定しているので3回学習します。
    # Fit (train)
    model.fit(x_train, y_train, epochs=3)
    # plt.imshow(x_train[0], cmap=plt.cm.binary)
    # plt.show()
    # plt.imshow(x_train[1], cmap=plt.cm.binary)
    # plt.show()
    # print(y_train)
    click image to zoom!
    図4-1
    図4は行2のModelのfit()メソッドの経過画像です。 fit()の引数に「epochs=3」の指定があるのでx_train、y_trainのデータを使用して3回学習しています。 x_trainには6万件の手書き数字の画像データ(28x28のBitパターン)、y_trainには6万件の画像データに対応した数値(0-9)が格納されています。


    click image to zoom!
    図4-2
    図4-2ではx_trainに格納されている手書き数字の画像データの1番目と2番目の画像データを可視化しています。 さらにy_trainに格納されている数値(0-9)を表示しています。
  5. Evaluate(評価)する

    行2ではModelのevaluate()メソッドで学習の効果を評価しています。 evaluate()メソッドの引数1には「x_test」、引数2には「y_test」を指定します。 x_testには1万件の手書き画像が28x28のBitパターンで格納されています。 y_testには1万件の数値(0-9)が格納されています。 y_testの数値はx_testの画像データの数字に対応します。

    行3ではevaluate()の戻り値「loss」と「accuracy」を表示しています。 実際の実際結果は図5を参照してください。 行4-7ではx_test[0]とx_test[1]に格納されている手書き数字の画像データ(28x28のBitパターン)をMatplotlibで可視化しています。 行8ではy_testの内容を表示しています。
    # Evaluate the model
    val_loss, val_acc = model.evaluate(x_test, y_test)
    print(val_loss, val_acc)
    plt.imshow(x_test[0], cmap=plt.cm.binary)
    plt.show()
    plt.imshow(x_test[1], cmap=plt.cm.binary)
    plt.show()
    print(y_test)
    click image to zoom!
    図5
    図5はModelのevaluate()メソッドの結果(戻り値)を表示しています。 lossが「0.1146」、accuracyが「0.9729」と表示されています。 x_test[0]とx_test[1]には手書きの数字7と数字2の画像データ(28x28のBitパターン)が格納されています。 y_testには手書きの画像データに対応した数値(0-9)が格納されています。 x_testとy_testにはそれぞれ1万件のデータが格納されています。
  6. Modelを保存して再ロードしてみる

    行2では学習させたモデルをModelのsave()メソッドで保存しています。 このように学習結果を保存することによりされを再ロードして使用することができます。 行3ではModelのload_model()メソッドで保存した学習モデルをロードしています。 Modelのpredict()メソッドではここで再ロードしたモデルを使用します。
    # Save the model & reload the model
    model.save('data/model/epic_num_reader.model')
    new_model = tf.keras.models.load_model('data/model/epic_num_reader.model')
    click image to zoom!
    図6
    図6はModelを保存して再ロードした実行結果です。
  7. Predict(予測)する

    行2ではModelのpredict()メソッドで予測しています。 predict()メソッドの引数1には「x_test」を指定します。 x_testには1万件の手書き画像データ(28x28のBitパターン)が格納されています。 「predictions」にはpredict()メソッドの戻り値が格納されます。 「predictions」には1万件の予測結果が数値で格納されています。

    predictionsの各要素「predictions[0],...」には10個の数値が格納されています。 この「10」は、Output Layerで追加したDenseの引数「units=10」により決定します。 各要素に格納されている10個の数値がMaxの数値が予測した数字になります。 詳細は図7-1と図7-2を参照してください。
    # Predict test image
    predictions = new_model.predict([x_test])
    print(x_test.shape)        
    print(predictions.shape)   
    print(predictions)
    
    # print(np.argmax(predictions[0]))
    # plt.imshow(x_test[0], cmap=plt.cm.binary)
    # plt.show()
    # print(np.argmax(predictions[1]))
    # plt.imshow(x_test[1], cmap=plt.cm.binary)
    # plt.show()
    click image to zoom!
    図7-1
    図7-1はModelのpredict()メソッドの実行結果の画面です。 predict()の戻り値predictionsには1万件の予測結果が数値で格納されています。 predictionsの各要素「predictions[0],..」には10個の数値が格納されています。 Maxの数値が予測した数値になります。 図7-2でMaxの数値を表示しています。


    click image to zoom!
    図7-2
    図7-2ではprediction[0]とprediction[1]に格納されている10個の数値からMaxの数値を検索して表示しています。 また、x_test[0]とx_test[1]に格納されている手書数字の画像(28x28のBitパターン)をMatplotlibで可視化しています。 手書数字の画像と予測値が一致していることが分かります。 つまり、手書きの数字が認識されたことになります。
  8. ここで解説したコードをまとめて掲載

    最後にここで解説したすべてのコードをまとめて掲載しましたので参考にしてください。
    
    # Article028_Deep Learning with Python TensorFlow and Keras Tutorial(1).py
    # %%
    # Import the libraries
    import numpy as np
    import matplotlib.pyplot as plt
    import tensorflow as tf
    from tensorflow.python.keras.layers.core import Activation
    #print(tf.__version__)
    
    # %%
    
    # Load 28x28 images of hand-written digits 0-9
    mnist = tf.keras.datasets.mnist    
    (x_train, y_train), (x_test, y_test) = mnist.load_data()
    # print(x_train.shape)
    # print(x_test.shape)
    # print(x_train[0])
    
    plt.imshow(x_train[0], cmap=plt.cm.binary)
    plt.show()
    plt.imshow(x_train[1], cmap=plt.cm.binary)
    plt.show()
    
    # %%
    
    # Normalize images
    x_train = tf.keras.utils.normalize(x_train, axis=1)
    x_test = tf.keras.utils.normalize(x_test, axis=1)
    
    # plt.imshow(x_train[0], cmap=plt.cm.binary)
    # plt.show()
    # plt.imshow(x_train[1], cmap=plt.cm.binary)
    # plt.show()
    
    # %%
    
    # Create a Model & Compile
    model = tf.keras.models.Sequential()
    model.add(tf.keras.layers.Flatten())    # 28*28 images => convert to flat images
    model.add(tf.keras.layers.Dense(units=128, activation=tf.nn.relu))
    model.add(tf.keras.layers.Dense(units=128, activation=tf.nn.relu))
    model.add(tf.keras.layers.Dense(units=10, activation=tf.nn.softmax))
    #model.add(tf.keras.layers.Dense(units=15, activation=tf.nn.softmax))
    
    model.compile(optimizer='adam', 
        loss='sparse_categorical_crossentropy', 
        metrics=['accuracy'])
    
    # %%
    
    # Fit (train)
    model.fit(x_train, y_train, epochs=3)
    plt.imshow(x_train[0], cmap=plt.cm.binary)
    plt.show()
    plt.imshow(x_train[1], cmap=plt.cm.binary)
    plt.show()
    print(y_train)
    
    # %%
    
    # Evaluate the model
    val_loss, val_acc = model.evaluate(x_test, y_test)
    print(val_loss, val_acc)
    plt.imshow(x_test[0], cmap=plt.cm.binary)
    plt.show()
    plt.imshow(x_test[1], cmap=plt.cm.binary)
    plt.show()
    print(y_test)
    
    # %%
    
    # Save the model & reload the model
    model.save('data/model/epic_num_reader.model')
    new_model = tf.keras.models.load_model('data/model/epic_num_reader.model')
    
    # %%
    
    # Predict test image
    predictions = new_model.predict([x_test])
    print(x_test.shape)         # (10000, 28, 28)
    print(predictions.shape)    # (10000, 10)
    print(predictions)
    
    print(np.argmax(predictions[0]))
    plt.imshow(x_test[0], cmap=plt.cm.binary)
    plt.show()
    print(np.argmax(predictions[1]))
    plt.imshow(x_test[1], cmap=plt.cm.binary)
    plt.show()
    
    # %%