2019年3月27日水曜日

SonyのNeural Network Libraries(NNabla)でFCNもどきを動かす



 いろいろなことを同時に走らせて、全部中途半端になっている中の人です。マウスの設計とFPGA入門、バイトの教材開発を同時に進めるのはやめておきましょう。前回に引き続き今回もNeural Network Libraries(NNabla)でFCNを動かす話です。
 今回は実装編ですが学習編と結構似たコードになるので、学習用のコードから流用できます。やったぜ。  とりあえず必要なライブラリ群を読み込みます。
#-*-coding:utf-8-*-

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF

import os
import sys
import cv2
import numpy as np

 学習ではsolversやinitializer、データセットを載せるためのdata_iteratorをimportしていましたが、今回は不要なので削除。データセット(npz)の読み込みはこの前と同じものを使用します。学習時になかったものとしては各ピクセルに割り当てられたラベルを画像に変換する関数を実装しました。(もっと頭良く組めるはずなんですが、僕には難しいです。)

def label2img(label):
    """
    * 1 | road        | blue 
    * 2 | out of road | green
    * 3 | line        | red
    * 4 | backgrownd  | black
    * 5 | object      | yellow
    * 6 | other rane  | white
    *
    * out   : 3-dimensional numpy array([ch][y][x])
    * buff  : 3-dimensional numpy array([1][y][x])
    * label : 4-dimensional numpy array([1][6][y][x])
    * color : [[B,G,R]]
    """
    color = np.array([[255,0,0],[0,255,0],[0,0,255],[0,0,0],[0,255,255],[255,255,255]])
    buff = np.argmax(label, axis = 1)
    out = np.zeros((3,buff.shape[1],buff.shape[2]))
    
    for i in range(len(color)):
        out[0][buff[0] == i] = color[i][0]
        out[1][buff[0] == i] = color[i][1]
        out[2][buff[0] == i] = color[i][2]

    return out.astype(np.uint8)

 ネットワークは学習時と同じものを使用してください。ここまでで必要な関数がそろいました。では実際に推論を走らせるコードに移ります。


try:
    #Loading Dataset
    train, teach = load_data(NPZ,"img","img_test")
    
    #Set Params to network
    nn.clear_parameters()
    x = nn.Variable(train[0:1].shape)
    y = network(x, test=True)
    t = nn.Variable(teach[0:1].shape)
    print("x:",x.shape, "y:", y.shape, "t:", t.shape)

    #Search if Params file or not
    if os.path.exists(param_file) == True:
        nn.load_parameters(param_file)
    else:
        print("Parameter file was not found!!!")
        sys.exit()

    #Test
    for i in range(train.shape[0]):
        x.d, t.d = train[i:i+1], teach[i:i+1]
        y.forward()

        input_img = x.d[0].transpose(1,2,0).astype(np.uint8)

        predict_img = y.d
        predict_img = label2img(predict_img).transpose(1,2,0)
        
        ground_truth_img = t.d
        ground_truth_img = label2img(ground_truth_img).transpose(1,2,0)
        
        show_img = np.concatenate((input_img, 
                                   predict_img, 
                                   ground_truth_img,
                                   ),axis=1)
        cv2.imshow("imshow", show_img[::-1,:,:])
        
        cv2.waitKey(1)

except:
    import traceback
    traceback.print_exc()

finally:
    input(">>")
    cv2.destroyAllWindows()

 load_data関数で推論させるデータを取り込みます。今回は入力画像、推論結果に加えて教師画像も載せたいので、ついでに取り込みます。
 その後パラメータの読み込みまでは学習時とほぼ同じです。学習済みパラメータの読み込み部のみ、パラメータがなかったらプログラムを終了できるように変えてあります。
 for分の中では実際に推論をしています。t.dに教師画像を入れてありますが、推論のみを行うのであれば不要になります。
 また推論自体はy.forward()で行われ、y.dに推論結果が入っています。あとの部分はcv2での画像表示に関する部分です。そこまで難しくはないはずですが、cv2で画像を表示させる場合、データ構造がNNabla側の(1, color, row, col)から(row, col, color)に代わるのでそれをimg2label関数とnp.transposeで変換しています。

 ここまでで推論部分が完成しました。学習部分さえできればその応用で推論ができるのは結構楽ですね(Chainerも似てるけど)。あとはおまけでGPUあたりのお話ができればいいかなぁ。
©2018 shts All Right Reserved.

2019年3月9日土曜日

SonyのNeural Network Libraries(NNabla)でFCNもどきを学習させる







 研究室配属も無事終了した中の人です。研究室の本格始動は4月からなので、3月中はマウスの設計やFPGA、NNablaと戯れたいと思います。さて、前の投稿ではNeural Network Libraries(NNabla)でnumpy-arrayの読み込みを行いました。そこで今回は読み込んだデータを使用して、FCNもどきの学習を行いたいと思います。

今回のコードもこちらのサイトを参考に実装していきます。

NNabla(Neural Network Libraries)のシンプルチュートリアルをPython3.5でやってみる。

NNablaで学習を行う場合、
  • データセットの読み込み
  • モデルの構築
  • 損失関数の定義
  • 学習部の実装
が必要になります(この辺はほかのライブラリとあまり変わらない)。まずはデータセットの読み込み、と見せかけて必要なライブラリ群のimportとデータセットを入れたファイル名、パラメータファイルから

import nnabla as nn
import nnabla.functions as F
import nnabla.parametric_functions as PF
import nnabla.solvers as S
import nnabla.initializer as I
from nnabla.utils.data_iterator import data_iterator_simple

import os
import numpy as np

NPZ = "data/img2train_data_test.npz"
param_file = "nnabla_parameters.h5"

 データセットの読み込みはこんな感じで実装
#Read .npz File
def load_data(npz, input="img", teacher="img_test"):
    print("loading dataset for training")
    #loading data from NPZ file
    with np.load(npz) as data:
        tmp_train = data[input]
        tmp_train_label = data[teacher]
    
    print(tmp_train.shape)
    print(tmp_train_label.shape)

    return tmp_train, tmp_train_label

#Loading Data
def data_iterator_my_dateset(train, teach, batch_size=8, shuffle=False, rng=None):
    def load_func(index):
         """Loading an image and its label"""
         img = train[index]
         label = teach[index]
         return img, label
    return data_iterator_simple(load_func, train.shape[0], batch_size, shuffle, rng, with_file_cache=False)

 .npzでデータセットを保持している関係で、それを読み込む関数を実装しました。下のデータセットの取り込みは前回と同じ。
 モデルはこんな感じの奴。
#Define network
def network(x):
    with nn.parameter_scope("cnn"):
        with nn.parameter_scope("conv0"):
            h = F.relu(PF.convolution(x, 6, (2,2)))
        with nn.parameter_scope("deconv0"):
            h = F.relu(PF.deconvolution(h, 3, (2,2)))
        with nn.parameter_scope("conv1"):
            h = F.relu(PF.convolution(x, 6, (4,8), stride=(2,4)))
        with nn.parameter_scope("conv2"):
            h = F.relu(PF.convolution(h, 16, (9,9), stride=(3,3)))
        with nn.parameter_scope("deconv2"):
            h = F.relu(PF.deconvolution(h, 16, (9,9), stride=(3,3)))
        with nn.parameter_scope("deconv1"):
            h = PF.deconvolution(h, 6, (4,8), stride=(2,4))
    return h

 Raspberry Piでそこそこ速く動かそうと思うと、こうなりました。ここはもっと手を入れる予定。損失関数はSigmoid Cross Entropyを使用。

#Define Loss Function
def loss_func(y, t):
    loss_f = F.sigmoid_cross_entropy(y, t)
    return loss_f
実の話をすると損失関数に最初はSoftmax Cross Entropyを使っていたのですが、要素(今回はピクセル)ごとのクラス分類がうまくいきませんでした。(ここで1週間溶ける)
 そこで公式のDCGAN実装を見たところ Sigmoid Cross Entropyを使っていたので、使ってみたところうまく学習できました。やったね!
(1つのピクセルが各ラベルに属するかどうかを見ているのでこれでもよいってだけで、Softmax Cross Entropyでも原理的にはできるはず)(参考:損失関数の紹介 - renom.jp)
 学習部はこんな感じ。
#Define Training Function
def training(xt,tt,data,loss,steps):
     solver = S.Adam()
     solver.set_parameters(nn.get_parameters()) # Set parameter variables to be updatd.
         
     for i in range(steps):
         xt.d, tt.d = data.next()
         solver.zero_grad() # Initialize gradients of all parameters to zero.
         loss.forward()
         loss.backward()
         solver.update()
         if i % 100 == 0: # Print for each 10 iterations
              print(str(i) +":" + str(np.mean(loss.d)))


 教師データ読み込み→とりあえず勾配を初期化→推論する→誤差逆伝播→パラメータを更新、を繰り返す感じ。後はこれらを実行していく形になります。

try:
    #Loading Dataset
    train, teach = load_data(NPZ,"img","img_test")
    data = data_iterator_my_dateset(train, teach, batch_size=30, shuffle=True)

    #Set Params to network
    nn.clear_parameters()
    img, label = data.next()

    x = nn.Variable(img.shape)
    y = network(x)
    t = nn.Variable(label.shape)
    loss = loss_func(y, t)

    #Search if Params file or not
    if os.path.exists(param_file) == True:
        yn = input("parameter is exist! use this? y/n : ")
        if yn == "Y" or yn == "y":
            nn.load_parameters(param_file)

    #Training
    training(x,t,data,loss,500)
    
except:
    import traceback
    traceback.print_exc()

finally:
    #Save Params
    nn.save_parameters(param_file)
    input(">>")

パラメータのクリアをモデル構築前にするのを忘れなければうまく動くはず...です。次回は実装になるはず。この記事長すぎるのでは?
©2018 shts All Right Reserved.

2019年3月1日金曜日

SonyのNeural Network Libraries(NNabla)でnumpy_arrayの読み込み



研究室がいまだ決まってない(後数時間なんですけどね)中の人です。前に投稿したSTM32でduty比をDMAで転送する話でも少し触れた、SonyのNeural Network Libraries(NNabla)を使って、Fully Convolutional Networksをやってみようと思います。



 NNablaは元々Sony内部で使われていたライブラリを公開したものらしいです。(詳しくはここを見てくれ)実の話をすると、本来はNeural Network Console(NNC)を使いたかったのですが(GUIでネットワークを記述するとか面白そう)、npzファイルで保存されている手持ちのChainer向けデータセットを転用したかったので今回はNNablaを使ってみることにしました。
 NNablaは組込み向けを意識しているのか、量子化に関するレイヤーが標準で実装されているので、今までChainerでやってきたやつを転用しようと考えた次第です。

 とりあえずチュートリアルと先人たちの解説を読みあさりつつ、サンプルをやってみました。今回はPython3.6のIDLEを使用しているので、公式チュートリアルコピペだと動かないことがありましたが、IDLEで実行している人がいました。ラッキー


NNabla(Neural Network Libraries)のシンプルチュートリアルをPython3.5でやってみる。


 このコードを参考にしてtiny_digitsによる学習はできたのですが、今度は独自のデータセットに対応させる必要があります。
いろいろ考えても進まないので、このサンプルコードで使用されているtiny_digitsのデータ構造を見ていきます。...といっても実態はnumpy-ndarrayなので、shapeで配列の各次元の要素数を確認しました。(参考:The Digit Dataset)

入力(image):(1797,8,8)
出力(target):(1797,)

ここで、オレオレデータセットのフォーマットを確認すると、

入力:((データ数),3,32,64)
出力:((データ数),6,32,64)

おっ、これだったらオレオレデータセットも読み込めそう...なのか?次元数違うけど大丈夫か...?というわけで、NNablaの公式リファレンスに飛び込んでみる。すると、

nnabla.utils.data_iterator.data_iterator_simple

の欄にload_funcの例が載っており、それが(ch, h, w)の画像の読み出しになっていました。これはオレオレデータセットの構造とほぼ同じなのでいけそうです。
 とりあえずそれを参考にしつつデータ読み込みの関数を書いてみました。

def data_iterator_my_dateset(train, teach, batch_size=8, shuffle=False, rng=None):
    def load_func(index):
         """Loading an image and its label"""
         img = train[index]
         label = teach[index]
         return img, label
    return data_iterator_simple(load_func, train.shape[0], batch_size, shuffle, rng, with_file_cache=False)

とりあえずデータ読み込みはこれでできました。やったぜ。この辺の話をやっている人をインターネッツでは見かけなかったので、結構苦労しました。(これだけで3h吹き飛んでる)
 多分今度の話は学習部分(というよりも使い方がよくわからなかったsoftmax_cross_entropy)になりそうです。


©2018 shts All Right Reserved.