NNabla(Neural Network Libraries)で、学習済のパラメータを保存することと、保存したパラメータをLOADして学習無しの推論だけやってみます。
以下の記事でニューラルネットワークコンソールで構築したモデルと学習済パラメータをNNablaで再利用して、推論だけを行うのを試してみますよと予告しているのですが、今回はその練習を兼ねて、パラメータの保存と再現の感覚をつかんでおこうという趣旨です。
なお、以後の説明は下の記事の手順でインストールと必要なモジュールがインストールされている前提でやります。
まずNNabla環境でIDLE
仮想環境を作っている方は、コマンドプロンプトを立ち上げて、仮想環境をActivateします。(例「activate nnabla」)
nnabla環境で、IDLEを起動します。
IDLEコンソールで、File>New File で新規にエディタを開いておいてください。
まず、importを書き込みます。
#① NNabla関連モジュールのインポート
import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import nnabla.solvers as S
from nnabla.utils.data_iterator import data_iterator_simple#② NNabla関連以外のモジュールのインポート
import numpy as np
from sklearn.datasets import load_digits
実行するには以下の処理を追加する必要があります。
- データを処理可能な形式に加工する関数を定義する
- 損失グラフを構築する関数を定義する
- 学習する「training」関数を定義する
- ネットワーク関数を定義する。
- データ・セットを読み込み処理可能な形式に加工する
- ネットワーク関数を使ってネットワークを構築する
- 学習する関数を使って学習を実行する
- 推論して結果を表示する
- 学習済のパラメータをファイルに保存する。
そして、学習済のパラメータをファイルから読み込んで推論だけ行う時は、上記の「学習する関数を使って学習を実行する」の部分が、以下に変わることになります。
- 学習済パラメータをファイルから読み込む(LOADする)
さて、各ブロック毎にソースを書いていきます。
データを処理可能な形式に加工する関数を定義する
データはscikit_learnに用意されている「Digits」という手書き数字データを使います。
digitsは、scikit_learnに用意されている命令を使って簡単にロードできますが、NNablaで使うためには、data_iterator型にしてやらないといけませんので、それをする処理を関数化しておきます。
#③ tiny_digits.pyから転載し、ちょっと加工したデータ整形function
def data_iterator_tiny_digits(data,target, batch_size=64, shuffle=False, rng=None):
def load_func(index):
"""Loading an image and its label"""
img = data[index]
label = target[index]
return img[None], np.array([label]).astype(np.int32)
return data_iterator_simple(load_func, target.shape[0], batch_size, shuffle, rng, with_file_cache=False)
損失グラフを構築する関数を定義する
損失率を計算する部分は、別関数にしておきます。
理由は後でも説明しますが、そうしておかないと、学習済パラメータの再利用で推論だけを行う・・ということができなくなります。
今回は10種類の数字の分類問題なので、「categorical_cross_entropy」を使います。
#④ 損失グラフを構築する関数を定義する
def loss(y, t):
loss_f = F.mean(F.categorical_cross_entropy(y,t))
return loss_f
学習する「training」関数を定義する
学習する「training」関数を定義しておきます。
#⑤ トレーニング関数を定義する
def training(xt,tt,data_t,loss_t,steps,learning_rate):
solver = S.Sgd(learning_rate)
solver.set_parameters(nn.get_parameters()) # Set parameter variables to be updatd.
for i in range(steps):
xt.d, tt.d = data_t.next()
loss_t.forward()
solver.zero_grad()
loss_t.backward()
solver.weight_decay(1e-5)
solver.update()
if i % 100 == 0: # Print for each 10 iterations
print(str(i) +":loss:" + str(loss_t.d))
ここは、ほぼ定形みたいなもので、実際上記も前回のチュートリアルで使ったものからのコピペです。
ネットワーク関数を定義する。
基本シンプルなCNNです。
注意点があるとすれば、SOFTMAXで終わらせているところですかね。
ここに、損失関数(categorical_cross_entropy等)まで組み込んでしまうと、あとで保存したパラメータをファイルだけ読み込んで推論だけしようとした時に、networkのアウトプットが損失率になってしまったりして、うまくいかなくて苦しむことになりますから要注意です。
#⑥ ニューラルネットを構築する関数を定義する
def network(x):
with nn.parameter_scope("cnn"):
with nn.parameter_scope("conv1"):
h = F.tanh(PF.batch_normalization(
PF.convolution(x, 4, (3, 3), pad=(1, 1), stride=(2, 2))))
with nn.parameter_scope("conv2"):
h = F.tanh(PF.batch_normalization(
PF.convolution(h, 8, (3, 3), pad=(1, 1))))
h = F.average_pooling(h, (2, 2))
with nn.parameter_scope("fc3"):
h = F.tanh(PF.affine(h, 32))
with nn.parameter_scope("classifier"):
h = PF.affine(h,10)
h = F.softmax(h)
return h
データ・セットを読み込み処理可能な形式に加工する
digitsをロードして学習用データとテスト用データにわけて加工します。
1500件までを学習用、それ以降をテスト用(おおよそ200件強)にします。
#⑦ 実行開始:scikit_learnでdigits(8✕8サイズ)データを取得し、NNablaで処理可能に整形する
np.random.seed(0)
digits = load_digits(n_class=10)
train_images = digits.images[:1500]
train_target = digits.target[:1500]
test_images = digits.images[1500:]
test_target = digits.target[1500:]
b_size = 64
train_data = data_iterator_tiny_digits(train_images,train_target, batch_size=b_size, shuffle=True)
test_data = data_iterator_tiny_digits(test_images,test_target, batch_size=b_size, shuffle=False)
分割したdigitsのデータはそのままでは学習等に使えないので、前で定義した関数を使って、NNablaで利用できるData_Iteratorに変換します。
ネットワーク関数を使ってネットワークを構築する
とても重要なことがひとつだけあります。
nn.clear_parameters() の位置です。
必ず、network()を構築する前のこの位置でなければなりません。
なんか、後ろの方で学習実行の前とか、学習済パラメータをロードする直前とかに呼びたくなったりしますけど、これを後ろで読んでしまうと、まったく学習がすすまなかったり、ロードしたパラメータがきかなかったりして、かなり、ハマります。
#⑧ ニューラルネットワークを構築する
nn.clear_parameters()
img, label = train_data.next()
x = nn.Variable(img.shape)
t = nn.Variable(label.shape)
y = network(x)
_loss = loss(y, t)
学習する関数を使って学習を実行する
ここで、学習済パラメータをロードする処理も書いておきます。
ただ、一回目の実行の時はパラメータファイルが存在しなくて、エラーで落ちてしまうので、最初の学習の時はコメントアウトしておきます。
#⑨ 学習する
param_file = "my_parameters.h5"
#nn.load_parameters(param_file);
learning_rate = 1e-1
training(x,t,train_data,_loss,3000,learning_rate)
他は、まあ、見ての通りですね。
推論して結果を表示する
推論して、結果を比較してマッチした件数をカウントして正解率を表示します。
ミニバッチでbatch_size分まとめて処理されるので、テストデータ全体のサイズをbatch_sizeで割った回数分繰り返しています。
#⑩ 推論し、最後に正確さを求めて表示する
total_cnt = 0
mch = 0
for i in range(test_data.size // b_size):
x.d, t.d = test_data.next()
y.forward()
for p in range(len(t.d)):
total_cnt += 1
if t.d[p] == y.d.argmax(axis=1)[p] :
mch += 1
学習済のパラメータをファイルに保存する。
学習済のパラメータをファイルに保存します。
保存するファイル名は、前で定義した「param_file = "my_parameters.h5"」です。
ソースコードと同じフォルダに上記の名前でファイルができます。
あわせて、結果表示も行います。
nn.save_parameters(param_file)
print("match/all_validations:"+str(mch)+"/"+str(total_cnt))
print("Accuracy:{}".format(mch / total_cnt))
これでOKです。
保存して、F5キーを押して、実行してみます。
89.45%・・まあ、こんなもんでしょう。
パラメータが保存されたメッセージもちゃんとでているので、OKです。
今度は、学習済パラメータで推論のみ実行する
じゃあ、学習済パラメータをLOADして、推論だけやってみます。
#⑨ 学習する
param_file = "my_parameters.h5"
nn.load_parameters(param_file);
learning_rate = 1e-1
#training(x,t,train_data,_loss,3000,learning_rate)
変更点はひとつだけ。
load_parametersのコメントをはずして、trainingをコメントアウトするだけです。
さて、保存して実行してみます。
おお!
ちゃんと学習済パラメータを使って推論されてますね。
OKです。
これを応用すれば、ニューラルネットワークコンソールで学習したのも、同じように推論できそうですね。
関連記事
NNabla関連の記事一覧はこちらです。
ニューラルネットワークコンソール関連の記事一覧はこちらです。