Kerasのchannels_last, channels_firstという2つの概念

本日は小話をご紹介します。

Kerasでは、様々なライブラリに接続して、機械学習の
そんな中で、設定も柔軟に変更することが可能です。

https://keras.io/ja/backend/

2次元の画像データをnumpyで扱う際には、
画像のサイズ(縦×横)とチャンネル数(グレースケール or RGB)の情報に格納すると思います。

channels_last・channels_firstでは、値の順番が異なります。

channels_last = [縦, 横, チャンネル数]
channels_first = [チャンネル数, 縦, 横]

チャンネルが先頭にくる場合と、末尾にくる場合両方設定可能なのです。
これは使用するライブラリの設定によって、異なってくるので、順番が変わって処理する場合もある。くらいに覚えておくと良いかもしれません。

https://keras.io/ja/backend/

Kerasで機械学習したmodelを保存する方法

今回は、ドキュメントを見れば載っている情報を。

https://keras.io/ja/getting-started/faq/#keras-model

Kerasでmodelを保存する方法は2種類あります。
【A】モデルのアーキテクチャ・モデルの重みを一括で保存
【B】モデルのアーキテクチャ・モデルの重みを別々に保存

【A】モデルのアーキテクチャ・モデルの重みを一括で保存

from keras.models import load_model
#モデルの保存
model.save('my_model.h5') 
#モデルのロード
model = load_model('my_model.h5')

【B】モデルのアーキテクチャ・モデルの重みを別々に保存

モデルのアーキテクチャ

#モデルのアーキテクチャのみ保存
#JSON / YAMLファイル どちらでも可
json_string = model.to_json() 
yaml_string = model.to_yaml() 

#モデルのアーキテクチャのみロード
#JSON / YAMLファイル どちらでも可
from keras.models import model_from_json 
model = model_from_json(json_string) 

from keras.models import 
model_from_yaml model = model_from_yaml(yaml_string)
#重みのみ保存
model.save_weights('my_model_weights.h5')
#重みのみロード
model.load_weights('my_model_weights.h5')

以上で、以上で一括で保存する方法と、別々に保存する方法でした。
ほとんどの機会で、【A】モデルのアーキテクチャ・モデルの重みを一括で保存
で対処しております。

Kerasで pix2pixをアプリケーション化するまでに実施したことver2

前回の記事の続きを書いていきます。

前回までで、既存のpix2pixモデルを活用して、トレーニングと検証データを生成することが出来ました。
今回は、学習モデルを保存して、外部のnewデータでpix2pixで予測データを出力していきたいと思います。

引き続き、細かい部分の理解は疎い状態ですが、既存のコードを参考にさせて貰いつついきたいと思います。

大きな流れとしては、
( 1 ):外部の画像データをDataSetへ変換
( 2 ):学習完了したタイミングで、generator modelを活用して、(1)の画像データを用いてgeneratorで画像変換を実行

( 1 ):外部の画像データをDataSetへ変換

imagesSubDataフォルダを作成して、画像データを入れます。

非常に煩雑、かつ、img2h5.pyとほぼ同等ですが、
img2h5_vakidationDataset.py を新しくファイルとして追加します。
※コードは下記

import numpy as np
import glob
import argparse
import h5py
import matplotlib.pyplot as plt

from keras.preprocessing.image import load_img, img_to_array
import cv2

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('--inpath', '-i', required=True)
    parser.add_argument('--outpath', '-o', required=True)
    parser.add_argument('--trans', '-t', default='gray')
    args = parser.parse_args()

    finders = glob.glob(args.inpath+'/*')
    print(finders)
    imgs = []
    gimgs = []
    for finder in finders:
        files = glob.glob(finder+'/*')
        for imgfile in files:
            img = load_img(imgfile)
            imgarray = img_to_array(img)
            #サイズの変換
            imgarray = cv2.resize(imgarray, (128, 128))
            imgs.append(imgarray)

            #画像保存
            #plt.imshow(imgarray)    
            #plt.savefig("imgs.png")
            
            if args.trans=='gray':
                grayimg = load_img(imgfile, grayscale=True)
                grayimgarray = img_to_array(grayimg)
                gimgs.append(grayimgarray)
            elif args.trans=='canny':
                grayimg = cv2.cvtColor(cv2.imread(imgfile), cv2.COLOR_BGR2GRAY)
                #サイズの変換
                grayimg = cv2.resize(grayimg, (128, 128))

                gray_canny_xy = cv2.Canny(grayimg, 128,128 )
                gray_canny_xy = cv2.bitwise_not(gray_canny_xy)
                gimgs.append(gray_canny_xy.reshape(128,128,1))
                
                #画像を表示
                #plt.imshow(gray_canny_xy) #追加
                #plt.show()

        '''
        #画像確認のために、画像データを保存
        numberimgs = len(imgs) - 1
        for i in range(0,numberimgs):
            plt.imshow(imgs[i])    
            #plt.savefig("gimgs.png")
            p = str(i)
            plt.savefig("imgs"+p+".png")
        numbergimgs = len(gimgs) - 1
        for i in range(0,numbergimgs):
            plt.imshow(gimgs[i].squeeze()) #理解出来ていないが、squeeze()することで出来た
            p = str(i)
            plt.savefig("gimgs"+p+".png")
        '''


    perm = np.random.permutation(len(imgs)) #print(perm) -->配列返している
    imgs = np.array(imgs)[perm] #print(imgs.shape)
    gimgs = np.array(gimgs)[perm]
    threshold = len(imgs)//10*9
    vimgs = imgs[threshold:] #閾値を境に分割
    vgimgs = gimgs[threshold:] #同上
    imgs = imgs[:threshold] #同上
    gimgs = gimgs[:threshold] #同上
    print('shapes')
    print('gen imgs : ', imgs.shape) #print(len(imgs)) --> 画像個数
    print('raw imgs : ', gimgs.shape)
    print('val gen  : ', vimgs.shape)
    print('val raw  : ', vgimgs.shape)

    outh5 = h5py.File(args.outpath+'.hdf5', 'w')
    outh5.create_dataset('train_data_gen', data=imgs) #imgs => 薄い色合い
    outh5.create_dataset('train_data_raw', data=gimgs) #gimgs => 黄色と輪郭
    outh5.create_dataset('val_data_gen', data=vimgs)
    outh5.create_dataset('val_data_raw', data=vgimgs)
    outh5.flush()
    outh5.close()


if __name__=='__main__':
    main()

コマンドを実行して、
datasetSubimages.hd5 ファイルという画像データが生成されます。

python img2h5_vakidationDataset.py -i imagesSubData/ -o datasetSubimages -t canny

( 2 ):学習完了したタイミングで、generator modelを活用して、(1)の画像データを用いてgeneratorで画像変換を実行

pix2pix.py ファイルの中にコードを追加していきます。

■関数の追加
plot_Subgenerated_batchは、
白黒の画像のみを有力して、pix2pixで色付きに変換した画像を生成するための関数です。

# 画像生成,保存
def plot_Subgenerated_batch(X_raw, generator_model, batch_size, suffix):
    X_gen = generator_model.predict(X_raw)
    X_raw = my_inverse_normalization(X_raw)
    X_gen = my_inverse_normalization(X_gen)

    Xs = to3d(X_raw[:5])
    Xg = to3d(X_gen[:5])
    Xs = np.concatenate(Xs, axis=1)
    Xg = np.concatenate(Xg, axis=1)
    XX = np.concatenate((Xs,Xg), axis=0)    

    print(X_gen)
    print(X_gen.shape)

    plt.imshow(XX)
    plt.axis('off')
    plt.savefig("./figures/current_batch_"+suffix+".png")
    plt.clf()
    plt.close()

■def my_train(args):に処理を追加していきます。

①:imagesSubDataをloadするための処理

    # load data
    procImage_sub, rawImage_sub, procImage_val_sub, rawImage_val_sub = my_load_data(args.datasetpath_sub)
    print('procImage.shape : ', procImage_sub.shape)
    print('rawImage.shape : ', rawImage_sub.shape)
    print('procImage_val.shape : ', procImage_val_sub.shape)
    print('rawImage_val.shape : ', rawImage_val_sub.shape)

②:画像データをgeneratorへ入力するためのデータセットへ変形
 :学習完了した後のgeneratorに対して、画像データの入力→出力
※def my_train(args):の一番した部分に追加

    perm_sub = np.random.permutation(rawImage_sub.shape[0])
    X_procImage_sub = procImage_sub[perm_sub] #[perm]でランダム配列を作り、画像データの順番を変更
    X_rawImage_sub  = rawImage_sub[perm_sub]
    X_procImageIter_sub = [X_procImage_sub[i:i+args.batch_size] for i in range(0, rawImage_sub.shape[0], args.batch_size)] #batch_sizeに合わせて画像データのサンプリング
    X_rawImageIter_sub  = [X_rawImage_sub[i:i+args.batch_size] for i in range(0, rawImage_sub.shape[0], args.batch_size)]
    idx = np.random.choice(procImage_val_sub.shape[0], args.batch_size)
    X_gen_target_sub, X_gen_sub = procImage_val_sub[idx], rawImage_val_sub[idx] #要は順番をランダムにしているだけだよね.

    #plot_generated_batch(X_gen_target_sub, X_gen_sub, generator_model, args.batch_size, "LastData") #検証
    plot_Subgenerated_batch(X_gen_sub, generator_model, args.batch_size, "LastData") #検証
    #generator_model.save("./savemodel_gen.h5")

■def main():に処理を追加していきます。

下記を、追加

parser.add_argument(‘–datasetpath_sub’, ‘-s’, type=str, required=True)

■下記のコマンドで実行

python pix2pix.py -d datasetimages.hdf5 -s datasetSubimages.hdf5 

そうすると、途中までは通常通りの学習が進んでいき、
最終的に学習が完了したタイミングで、学習完了した後のgeneratorに対して、画像変換が行われる処理が最後に実行されます。

figuresフォルダの中に、current_batch_LastData.pngという画像データが生成されていることでしょう。

Kerasで pix2pixをアプリケーション化するまでに実施したことver1

今回は、複数記事に跨いで書いていきます。
機械学習を勉強し始めてから歴が浅いのですが、少しづつ分かってきたところで、
せっかくだからアプリケーション化したいなと思いました。

学習方法として、完璧に理解する前に目の前で動くものを作ってから、徐々に細かい部分まで、理解していくことが効果的だと思っています。

そこで、pix2pixの細かいモデル部分は理解しきれていないが、アプリケーション化するまでのプロセスを書いていきます。

記事は下記の3部構成です。

・pix2pixを使って検証データを出力
・学習モデルを保存して、外部のnewデータでpix2pixで変換

・学習モデルを使って、generatorにpredictで画像生成
・Webアプリケーションへ組み込む

今回、超絶に参考にさせて頂いたのが下記でpix2pixのコードを紹介してくれている方のコードを参考にしました。

https://toxweblog.toxbe.com/2017/12/24/keras-%E3%81%A7-pix2pix-%E3%82%92%E5%AE%9F%E8%A3%85/

githubのソースはこちらです。

https://github.com/tommyfms2/pix2pix-keras-byt

pix2pixを使って検証データを出力

今回は、特段プログラムを追加する必要がありません。
上記のgithub上で指定されている通り進めていけば問題ありません。

( 1 ):上記のgithubからデータをDownload or cloneしてください。

( 2 ):フォルダ内の一番上の階層(pycacheと同列)にimagesフォルダーを追加
 (フォルダ名は何でもいいですが、コマンドでの指定する名称も変える必要が出てくるので、ひとまずimagesでいきましょう。)

( 3 ):imagesフォルダーの中に画像を格納
 (これは、pix2pixのモデルを学習するための画像です。)

( 4 ):ターミナルを使って、
   pix2pix-keras-byt-masterまで移動します。

( 5 ):ターミナルで、
   python img2h5.py -i images/ -o datasetimages -t canny
※ここで、データセットが作成されます。

( 6 ):ターミナルで、
   python pix2pix.py -d datasetimages.hdf5
※figuresフォルダーが作成されて、ここにトレーニングデータと、検証データとの両方がepoch毎に出力されます。

以上です。
それでは、次回は学習モデルを保存して、外部のnewデータでpix2pixで予測データを出力を書いていきたいと思います。

[:x] Ndarrayや配列における、thresholdの考え方。

今回は、thresholdの操作を紹介します。
(正式な呼称なのか知らない…)

使用目的は、データの分割や、データの一部を抽出するときのために使います。
実際の式変形を見てみましょう。

a = np.arange(24)

a = np.arange(24)
print(a)

threshold = 10

b = a[:threshold]
print(b)
c = a[threshold:]
print(c)

#a
[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23]
#b
[0 1 2 3 4 5 6 7 8 9]
#c
[10 11 12 13 14 15 16 17 18 19 20 21 22 23]

numpyで0~23の数字が格納されたndarray型のデータを作成しました。
b, c, では、コロンがついている場所が違います。

b –> [:threshold] #thresholdよりも左側のデータ
[0 1 2 3 4 5 6 7 8 9]

c –> [threshold:] #thresholdよりも右側のデータ
[10 11 12 13 14 15 16 17 18 19 20 21 22 23]

続いて、画像データで見てみましょう。
画像が3枚、480×640のサイズ、3チャンネル(RGB)の画像が格納されたsumImageデータがあるとします。

sumImage.shape
#(3, 480, 640, 3)

それでは、sumImageデータを基にしてthresholdでのデータ分割、抽出の方法を見ていきましょう。

sumImage.shape[:1]
#(3)

(3, 480, 640, 3)の中で、1つ目のデータのみをデータ抽出しています。
※画像データが移行された訳ではない。shapeの情報を参照しているのみ。

cc = sumImage.shape[:3]
#(3, 480, 640)

同じく、3つ目よりも前の情報を取得しています。

bb = sumImage.shape[-3:]
#(480, 640, 3)

マイナスを使うことが出います。
マイナスは後ろから、という指定です。

では、shapeの情報だけではなく、画像データも分割、抽出することが出来るのでしょうか。

ff = sumImage[1:]
gg = ff.shape
#(2, 480, 640, 3)

1つ目以降の画像データ、2つ目と3つ目の画像データを含む(2, 480, 640, 3)が抽出されました。

Anacondaの中のライブラリバージョンを変更する方法

Anacondaの中にインストールしているライブラリのバージョンを変更する方法を紹介します。
とても簡単です。

Anaconda-Navigatorを開いてください。
HomeのAppications onで操作したい仮想環境を選択して下さい。
Environmentを見ると、インストール済みのライブラリを一覧を見ることが出来ます。

ライブラリの、左側にあるチェックボタンをクリック

「Mark for specific version installation」でインストールしたい、バージョンを選択します。

以上です。
とても簡単です。

Tensorflow2.xで、1.xのバージョンを使う方法

以前、Tensorflow1.xについての記事を書きました。

Tensorflow1.xを用いる方法は幾つかありますが、
今回は Tensorflow2.x importした後に、部分的に1.xの関数などを利用したい場合です。

結論からいうと、
tf.compat.v1
を付けることです。

https://www.tensorflow.org/api_docs/python/tf/compat/v1

■例

tf.compat.v1.get_default_graph()

https://www.tensorflow.org/api_docs/python/tf/compat/v1/get_default_graph

2.xに随時移行していきましょう。

Kerasで複数のmodelを結合する方法

転移学習をしようとした際に、modelを結合するという、概念を知りました。
簡単ではありますが、modelを結合する方法について書いていきます。

Kerasでのmodel結合を考える際には、
functional APIの考えが参考になりました。

■functional APIでKerasを始めてみよう
https://keras.io/ja/getting-started/functional-api-guide/

functional APIでmodelを作成する手順は
・Inputを定義
・layerの記載
・Modelを定義

■functional APIでModelを作成

実際に記載してみましょう。
modelは、ただDenseを結合していくだけです。

#ライブラリをimport
from tensorflow import keras
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Dense, Dropout, Flatten, Input

#inputの定義
inputs = Input(shape=model.output_shape[1:])

#layerの記載
X = Dense(100, activation='relu')(inputs)
X = Dense(100, activation='relu')(X)
prediction = Dense(2,activation='softmax')(X)

#Modelの定義
modelvol2 = Model(inputs=inputs , outputs=prediction)

#Modelの確認
modelvol2.summary()

_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
input_5 (InputLayer) [(None, 34, 34, 64)] 0 _________________________________________________________________ dense_11 (Dense) (None, 34, 34, 100) 6500 _________________________________________________________________ dense_12 (Dense) (None, 34, 34, 100) 10100 _________________________________________________________________ dense_13 (Dense) (None, 34, 34, 2) 202 =================================================================

■layerの記載に説明します。
layerインスタンス = layer内容( 結合するlayer )
という表記で作成していきます。

Sequential型であれば、
layer.add()で記載していく部分です。

■Modelの定義について説明します。
Model(inputs= XXX, outputs=YYY)で定義できます。
XXX・YYYにはlayerインスタンスを格納していきます。

predictionは最後の層です。が、
それまで継承してきた、Xも引き継いでいます。

・Layerの継承方法
・Modelの定義方法
が、modelを結合していく為に必要な考え方です。

modelを結合するために、もう一つmodelを作成します。

■Keras SequentialでModelを作成

modelvol1を作ります。
CNN型のmodel layerを並べました。

image_size = 150
modelvol1 = Sequential()
modelvol1.add(Conv2D(32,(3,3), activation='relu',input_shape=(image_size,image_size,3)))
modelvol1.add(MaxPooling2D(pool_size=(2,2)))
modelvol1.add(Conv2D(64,(3,3), activation='relu'))
modelvol1.add(MaxPooling2D(pool_size=(2,2)))
modelvol1.add(Dropout(0.25))

それでは、この2つのmodel を結合していきます。
結合していくときには、
・Layerの継承方法
・Modelの定義方法
を活用していきます。

新しく生成するmodelをmodelYとして作成していきます。

modelvol1 → modelvol2 の流れになるように結合していきます。

modelY = 
 Model(inputs=modelvol1.input , outputs=modelvol2(model.output))
modelY.summary()

_________________________________________________________________
Layer (type) Output Shape Param # =================================================================
conv2d_12_input (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________ conv2d_12 (Conv2D) (None, 148, 148, 32) 896 _________________________________________________________________ max_pooling2d_8 (MaxPooling2 (None, 74, 74, 32) 0 _________________________________________________________________ conv2d_13 (Conv2D) (None, 72, 72, 64) 18496 _________________________________________________________________ max_pooling2d_9 (MaxPooling2 (None, 36, 36, 64) 0 _________________________________________________________________ dropout_6 (Dropout) (None, 36, 36, 64) 0 _________________________________________________________________ model_10 (Model) (None, 36, 36, 2) 16802 =================================================================

これで、modelの結合は完了です。
>> model_10 (Model) (None, 36, 36, 2) 16802
の部分が、modelvol2で作成した部分です。

Model(inputs=modelvol1.input , outputs=modelvol2(model.output))

について少し解説します。
と、言っても、functional API部分の繰り返しになってしまいますが。

Modelの定義をしています。
( model結合と言っても、要は2つのmodelから、1つのmodelを生成している )

▼inputs
inputsは、modelvol1.inputの入力部分を

▼outputs
outputsは、modelのlayer部分を記載します。
実際に記載した内容は
modelvol2(model.output)です。
functional API部分でも記載した通り

layerインスタンス = layer内容( 結合するlayer )

というカタチで、継承していきます。
modelvol2(model.output)

これは、modelの最後部分に対して、modelvol2が継承するカタチになっています。
それがoutputsに入力されているのです。

これで、modelvol1 → modelvol2 の結合が完成しました。

functional APIでのmodelの作り方は、初めは避けていたのですが
転移学習を学んだり、modelについての理解を深めるためには、非常に重要な考え方でした。

Colaboratoryで、機械学習済みのModelを使い予測値を算出する。

機械学習をやり初めのときに疑問だったことは、
Colaboratoryなり、Anacondaなり使って、モデルを作ることが出来るんだけども、
どうやって、アプリケーションするのか?ということでした。

結論から言ってしまうと、modelデータを保存→ダウンロードして、他のアプリケーションに組み込むです。modelはmodelの形式や、重み・バイアスが、バイナリーデータとして格納されています。
他のアプリケーションで利用する際には、学習時に成形した入力形式が定まっているので、入力する際には注意が必要かと思います。

■Colaboratoryで、機械学習済みのModelを使う

それでは、簡単ではありますが、実際に使ってみます。
色々とやり方はあるかと思いますが、
今回は機械学習済みModelの、重み・バイアスを抽出して、予測値を算出していきます。

大まかな流れとしては、
・学習済みModelをUpload(ディレクトリへ格納)
・モデルの定義
・重み、バイアスのロード
・予測を実行

まずは、学習したModelをUploadします。

/content/model_data/ ディレクトリ配下に
modelDone.hdf5 というファイル名にてUploadする。

Modelを定義します。
これは、機械学習をした際と同じModelを定義します。
Kerasを活用するとします。(Libraryはimport済み)

#modelの定義
model = tf.keras.models.Sequential()
model.add(Flatten(input_shape=(28, 28)))
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.2))
model.add(Dense(10, activation='softmax'))

Modelに対して、重み・バイアスを読み込ませます。

model.load_weights('/content/model_data/modelDone.hdf5')

これで、学習した状態と同じ状態になります。

予測値を算出するには、学習したときと同じ入力形式で、データを入力し、予測を実行させて上げればよいのです。

predicted = model.predict(入力データ)
#変数 predicted に予測値が出力される。

KerasのCallbackをColaboratoryで試してみる

本日は、機械学習の学習中に操作をするCallbackについてです。

機械学習を勉強し初めの時に、混乱していたことの1つが、どんなデータのやり取りが行われているのか、分からない。と、言ったことでした。

特に、モデルの学習中には、一度学習をスタートすると、何だか分からないけれども指定したエポック数だけ処理されている。といった感覚でした。

そんな、中でモデルの学習中に、操作を行うことが出来る関数の集合がCallbackです。
詳細は、Kerasのオフィシャルで

■コールバック
https://keras.io/ja/callbacks/#modelcheckpoint

Callbackは、幾つかの目的の為に使われます。
・学習途中に、過学習を抑制するために、操作を停止したり、学習率を低下させる
・学習途中の内部の処理情報を可視化 or 保存させる

■KerasでCallbackをモデル学習にセットする方法

kerasでモデル学習する際には、
・model.fit()
・model.fit_generator()

2つのどちらかを利用すると思いまします。

2つの違いにはついては下記を参照

KerasでCallbackをモデル学習にセットさせる方法は、シンプルで
model.fit( callbacks= ) / model.fit_generator(callbacks=) の引数 callbacks= に対して、Keras.callbacks.Callback にあるインスタンスのリストを記載します。

例えば、よく使うのでいうと
ModelCheckpoint:学習中のモデルを保存することが出来る
EarlyStopping:学習中に値の変化が停止した時に訓練を終了出来る
TensorBoard:TensorBoardで可視化

■ColaboratoryでCallbackを用いて、学習中のモデルを保存

ModelCheckpointを使用して、学習中のモデルを保存してみましょう。

Colaboratoryで新規のフォルダを作成します。
フォルダ名称をlogとします。 パス : ‘/content/log’
このlogフォルダの中に格納していきます。

#保存するファイル名称の設定
ckpt_name = 'weights-{epoch:02d}-{loss:.2f}-{accuracy:.2f}-{val_loss:.2f}-{val_accuracy:.2f}-.hdf5'

log_dir='/content/log' # modelのデータ格納場所

#ModelCheckpointのインスタンス化
cbcheck = ModelCheckpoint(os.path.join(log_dir, ckpt_name),
                               monitor='val_acc', verbose=0,
                               save_best_only=False,
                               save_weights_only=True,
                               mode='auto', period=1)

この、ModelCheckpointをインスタンス化した、cbcheckを、fitの引数に設定します。

#色々と省略して
model.fit(callbacks= cbcheck)

そうすると、
log_dir=’/content/log’ # modelのデータ格納場所
の箇所にエポック毎に(period=1で設定)モデルが保存されております。

学習のモデルを操作できたり、可視化出来たりすることで
機械学習への距離感が少し縮まりました。
途中経過のモデルを活用して、predictしてみるのも面白いかと思います。