マサムネの部屋

Tensorflow でMNISTを学習して勾配情報を取り出す

パッとtensorflow を使ってみたいと思った時に、一連の事が出来るよう解説しています。1GitHub にnotebookを置いています。
Tensorflow で勾配情報を取り出すのに、keras.backend を使う方法がありますが、(勾配情報を取り出す際に限らず)良くやってしまうミスについても解説します。

記事で使っているソースコードはgithub に置いてあります。
https://github.com/msamunetogetoge

スポンサーリンク

python 環境の構築

そもそもpythonのコードを書いて走らせる環境が無いかもしれません。そんな時は、google colab を使います。以下の記事からどうぞ。

Google Colab によるpython環境構築
機械学習を始めるにあたって、環境を作るのが第一の壁になります。既に作られたライブラリが沢山あるので、python がオススメです。Google が提供するサービスを使うことで、python 環境が簡単に作れます。さらに、性能の良いパソコンを計算に使うことが出来るようになります。

Tennsorflow 経由でkerasを使う

google colab にはtensorflow もkeras もインストールされているのでimport するだけで直ぐに使えます。使うライブラリをimport しましょう。

from tensorflow import keras
import numpy as np
import matplotlib.pyplot as plt #グラフを描くためのライブラリです。

mnist と呼ばれるデータを使います。mnist には 0から9までの手書き画像が沢山入っています。

mnist = keras.datasets.mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
print("それぞれのサイズは、トレーニングデータ={}, テストデータ={}, ラベルの形は{}".format(train_images.shape , test_images.shape, train_labels.shape))
#それぞれのサイズは、トレーニングデータ=(60000, 28, 28), テストデータ=(10000, 28, 28), ラベルの形は(60000,)

トレーニング用のデータが28×28の画像60000枚、テスト用には10000枚の画像が用意されています。 実務でもこのくらいの量の良質なデータが欲しいものです。ラベルはone-hot-encodingされていないようです。必要なら別途しておきましょう。
画像データの前処理だけしておきます。画像データは0から255までの整数から出来ていますが、255で割って0から1のデータに変換します。

train_images = train_images / 255.0
test_images = test_images / 255.0

モデルの作成と評価


簡単なモデルを作りましょう。keras のSequential は枝分かれの無いモデルを簡単に作る事が出来ます。計算を早く終わらせる為に、畳み込みは使いません。

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

中間層一つのニューラルネットワークモデルです。ニューラルネットワークについては、以下の記事で解説しています。

ニューラルネットワークの話
本当にお話程度の事しか書かないとは思いませんでした。おすすめの本のリンクを貼っておきます。 マサムネも読んでいる定番です。数学の人には細部が書いてなかったり当たり前の事を長々と書いていたりで物足りないかもしれませ...


モデルを作ったらmodel.compileします。ここでオプティマイザーや損失関数を決めます。また、metrics で、ログに残しておきたい評価値を指定できます。”accuracy” とか”mean_squared_error” の事です。略称の”acc”とか、”mse”とかでも指定できます。今回はオプティマイザーはadam, 損失関数はスパースクロスエントロピーです。
compile したら、.fit 関数でモデルの学習を開始します。バッチサイズやエポック数はここで指定します。学習が上手くいかない時に自動で切り上げたりする、callback もここで指定します。

model.compile(optimizer='adam', 
              loss='sparse_categorical_crossentropy',
              metrics=['acc'])
h=model.fit(train_images, train_labels,
            validation_data=(test_images, test_labels),batch_size=20,
            epochs=5)

学習が終わったら、損失関数のグラフを描いてモデルを評価します。

import seaborn as sns
sns.set()
sns.lineplot(x=range(1, len(history)+1), y=history.loss, label="loss")
sns.lineplot(x=range(1, len(history)+1), y=history.val_loss, label="val_loss")
plt.xlabel("Epock")
学習の様子

普通は5epock で切り上げるのはありませんが、2種類のloss が同じように減衰していればモデルが良い感じに学習していると判断します。

keras backend function で勾配情報を取り出す

モデルの途中の値を見たいとか(CNNで画像がどう変わっていくか見たいとか), 勾配の情報を監視したいとか、そんなときがあると思います。 その時は、keras のbackend を使います。

class_output = model.output[:,5]  # Tensor / クラススコア
grad_tensor = K.gradients(class_output, model.input)[0]  # Tensor / クラススコアに対する入力の勾配
grad_func = K.function([model.input], [grad_tensor])  #  勾配の値を算出するための関数
x= np.expand_dims(train_images[0], axis=0)  #画像自体は大きさが(28,28) なので(1,28,28)にする
gradient = grad_func([x])[0][0]  # ndarray: 算出された勾配の値

このコードでgradient に勾配情報が保存されます。 保存される勾配は、最初の層の、全体の関数のものである事に注意してください。2 任意の層のweight とbias に関する勾配は、tensor board で可視化出来ますが、数値として取り出す簡単な方法は分かりません。教えてください。3

keras を使う時の注意とbackend を使う時に起こるエラー

良くやってしますミスを書いておきます。
keras をtensorflow 経由で使う時は、from tensorflow import keras のように、from tensorflowを入れなくてはなりません。実際、勾配情報を取り出すときに以下のように書くとエラーが起きます。

import keras.backend as K
class_output = model.output[:,5]  # Tensor / クラススコア
grad_tensor = K.gradients(class_output, model.input)[0]  # Tensor / クラススコアに対する入力の勾配
grad_func = K.function([model.input], [grad_tensor])  #  勾配の値を算出するための関数
x= np.expand_dims(train_images[0], axis=0)  #画像自体は大きさが(28,28) なので(1,28,28)にする
gradient = grad_func([x])[0][0]  # ndarray: 算出された勾配の値

もう一つ良くやるミスを書いておきます。これは原因不明なので、理由が分かる人は教えてほしいです。keras の関数には作ったモデルの出力を取り出す model.output と model .outputs があります。
outputs を使うと、勾配情報を取り出すことが出来ません。

#エラーが出るコード
class_output = model.outputs[:,5]  # Tensor / クラススコア
grad_tensor = K.gradients(class_output, model.input)[0]  # Tensor / クラススコアに対する入力の勾配
grad_func = K.function([model.input], [grad_tensor])  # 勾配の値を算出するための関数
gradient = grad_func([x])[0][0]  # ndarray: 算出された勾配の値

mdoel.output とmodel.outputs をprint すると、確かに違うものが出力されていることは分かりますが、どう違うのかは良く分かってません。

まとめ

  1. 殆ど自分用のメモですが。