読者です 読者をやめる 読者になる 読者になる

MATHGRAM

主に数学とプログラミング、時々趣味について。

chainerに復帰したくてFCN実装した

Deep Learning OpenCV chainer python 機械学習 画像処理

もともと, chainerユーザーだった僕ですが, 5月くらいにKerasを使い出してからchainerにはあまり触れてきませんでした. しかし先日の分散処理の発表なども含め, バージョンアップが早く, 常に速度の向上を図っているchainerにはもう1度触れておきたいと思い, 今回ハンドリングとしてFCNを実装した次第です. (ちょうどFCNを使ってやりたいこともあったしね.)

レポジトリはここに置いときます.

GitHub - k3nt0w/chainer_fcn: Implementation FCN via chainer

あと学習済みパラメータもここに置いときます. chainerってついてる方です. お間違いのないように.

ぱらめーたー - Google ドライブ

FCNは以前Kerasでも実装していて, FCNそのものの説明もこちらに詳しくしているので, もし"FCNってなんじゃい“って方がおりましたら, こちらを参照してください.

www.mathgram.xyz

いやーしかし・・・, chainer使いやすすぎだろ!! 誰だよ,
あれ...?chainerよりkerasの方が書きやすくね? - MATHGRAM
なんて言ったの!死んで詫びろ!
昔使っていた貯金はありますが, 今回の実装には3時間もかかってないと思います. しかもほとんどは画像の出力方法の部分. ただ僕が使っていた時には無かったtrainerの部分は全く触れずに実装しちゃいました. 必要性を感じたら勉強しよう.

keras2も気になりますが, chainerもいっぱい使っていこうと思います.

目次

環境

実験方法

実験にはPASCAL VOC2012のSegmentationClassを使いました. クラス数は背景を含めて21. trainデータは, VOCのtrain&valを全部使っちゃってて約2500枚, epochは100, batchsizeは5です.

境界線には-1を当てて計算から除外しました. 任意のサイズの画像をinputできますが, 速度向上のため256に統一しています. 予測は任意のサイズいけます.

また今回もこの論文からそのまま実装しました. 厳密にはFCN-8sの実装となっています.

実験結果

lossはまだまだ下がりきってないけど, AWSの請求が半端なくなりそうなので, とりあえずここで区切る.

あとtrainデータにのみ予測を行なっています. お叱りを受けそうですが, 今回の目的はFCNの精度を上げることではないので許してください. むしろいいGPUマシン持っている方, fine-tuningしてくれたらくそ喜びます.

人間

f:id:ket-30:20170218120353p:plain

f:id:ket-30:20170218115535p:plain

バス

f:id:ket-30:20170218115050p:plain

クルマ

f:id:ket-30:20170218120620p:plain

Kerasの時もそうだったけど, 犬って難しいのかな? f:id:ket-30:20170218120815p:plain

おまけ

VGG16のpretrain modelを使う

今回はなぜか1から学習させてしまいましたが, FCN−8sはほとんどVGGと同じ構成なので学習済みのパラメータを使うことができます. モデルの定義をVGGとその出力を受け取る部分に分ければいいだけですね.

インデックスカラーについて

前回の実装では, PASCALVOCの画像をRGBに変換し, 色ごとに自分でクラスを分けるというクッソだるいことしていましたが, なんとインデックスカラーなるものがこの世には存在するようです.

インデックスカラー - Wikipedia

VOCのSegmentationClassに存在する画像はこのインデックスカラーによって着色されたもので, pngで画像を読み込めばそのままクラスを表すarrayになってくれるんです.

画像で説明するとこんな感じ. f:id:ket-30:20170218124136p:plain

当たり前な人にとっては当たり前なのかもしれませんが, 多分1人で勉強してるとなかなか分からなかったりすると思います. この情報だけで実装にかかる時間は何倍も短縮できますからね. この情報が誰かの役に立つことを望みます.

アルファブレンドについて

今回の出力は元画像にsegmentationした画像を重ねているのですが, こういうのをアルファブレンドって言ったりするそうです.

アルファブレンド - Wikipedia

こういう画像そのものの学習を自分は怠っていると感じました, pngとかjpgとかちゃんとわかってないしなぁ. もっと基本的なところから勉強して, いずれ記事にまとめたいところ.

PILでアルファブレンドする方法がわからなかったので, そこだけopencv使っています.

o = cv2.imread("out/original.jpg", 1)
p = cv2.imread("out/pred.png", 1)

# 0.6, 0.4 は比です.
pred = cv2.addWeighted(o, 0.6, p, 0.4, 0.0)

参考

今回もたくさんのブログ, レポジトリを参考にさせていただきました.
ありがとうございました.

chainerそのものとFCNの参考
https://github.com/yusuketomoto/chainer-fast-neuralstyle https://github.com/pfnet/PaintsChainer
memo: Fully Convolutional Networks 〜 Chainerによる実装 〜

PILでは透化の合成ができなかったけど, このへん試しました.
Python + Pillow(PIL)で、透過したpng画像を作成する - Symfoware 画像処理についてあれこれ: Python Imaging Libraryを使用して画像を重ね合わせる

結果的にはこちらの方法です. ありがとうございました.
Python3 OpenCV3で平均値画像(Alpha Blending)を作成 | from umentu import stupid

[Keras] backend functionを使いこなそう!

Deep Learning keras python tips 機械学習

みなさん, keraってますか.

今回はloss関数やlayerの実装に欠かせない, backend functionをまとめていきます.

ただし自分が主に使ってる関数のみ紹介するので, 絶対Document読む方がいいですよ. これ以外にも色々ありますからね.

こいつを使いこなして, どんどんオリジナルのlayerなどを実装していきましょう!

インポート

まずバックエンド関数をインポートしましょう.

from keras import backend as K

# arrayを使うので, numpyもimportします.
import numpy as np

今回はこれだけしか使いません.
Kを使って自由にテンソルを扱っていきましょう!

numpy.arrayからテンソルを作る: K.variable

そもそもテンソルを用意しないと演算もくそもありません.

A = np.asarray([
        [1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]
    ])

B = np.asarray([
        [9, 8, 7],
        [6, 5, 4],
        [3, 2, 1]
    ])

こいつらをバックエンドで扱えるテンソルに変えるにはK.variableを使います.

A = K.variable(A)
B = K.variable(B)

これで準備オッケー. 今回Aくん, Bくんにはかなり働いてもらうよ.

テンソルの中身を見る: K.get_value, K.eval

テンソルの中身を確認したい. そんな時はK.get_value.

K.get_value(A)

出力

array([[ 1.,  2.,  3.],
       [ 4.,  5.,  6.],
       [ 7.,  8.,  9.]], dtype=float32)

K.evalも同じです.

和: +

要素ごとの足し算. 要素の合計ではないです. 気をつけて.
これは + 演算子オーバーロードされています.

K.get_value(A + B)

出力

array([[ 10.,  10.,  10.],
       [ 10.,  10.,  10.],
       [ 10.,  10.,  10.]], dtype=float32)

intやfloatも足せます.

K.get_value(A + 1)

出力

array([[  2.,   3.,   4.],
       [  5.,   6.,   7.],
       [  8.,   9.,  10.]], dtype=float32)

差: -

引き算も同じ, もちろんint, floatいけます.

K.get_value(A - B)

出力

array([[-8., -6., -4.],
       [-2.,  0.,  2.],
       [ 4.,  6.,  8.]], dtype=float32)

積: *

要素ごとに掛け算, 行列の積ではないので注意. int, floatもいけますよ.

K.get_value(A * B)

出力

array([[  9.,  16.,  21.],
       [ 24.,  25.,  24.],
       [ 21.,  16.,   9.]], dtype=float32)

商: /

要素ごとに割り算. int, floatもいけます.

K.get_value(A / B)

出力

array([[ 0.11111111,  0.25      ,  0.42857143],
       [ 0.66666669,  1.        ,  1.5       ],
       [ 2.33333325,  4.        ,  9.        ]], dtype=float32)

合計: K.sum

こっちが要素の合計.

K.get_value(K.sum(A))

出力

45.0

K.sumはテンソルを返すので, 合計をlossに足したい!なんて時はlossもテンソルとして扱うためK.sumだけで問題ないです.

※ intを足しても問題ないんですけどね

結構大事なのが, 軸を指定できる点.

画像を扱っていると, RGBのチャネルごとに何らかの操作をしたいときがありますしね.

行ごとの和

K.get_value(K.sum(A, axis=0))
array([ 12.,  15.,  18.], dtype=float32)

列ごとの和

K.get_value(K.sum(A, axis=1))
array([  6.,  15.,  24.], dtype=float32)

平均: K.mean

要素の平均. lossの実装でめっちゃ使う. まじでめっちゃ使う.

K.get_value(K.mean(A))

出力

5.0

こっちもテンソルを返す. またmeanもsum同様にaxisを設定できます.

行列積: K.dot

こっちが行列積

K.get_value(K.dot(A, B))

出力

array([[  30.,   24.,   18.],
       [  84.,   69.,   54.],
       [ 138.,  114.,   90.]], dtype=float32)

型の取得: K.int_shape

これは画像のshapeとって割る時とかに結構使う. ちなみにtensorflowでのみ動作します.
(つか悪いことは言わないからバックエンドはtensorfowを使え)

K.int_shape(A)

出力

(3, 3)

tuppleで返ってくるので注意.

要素の数を数える: K.count_params

明示的にint_shapeで受け取らなくても, 単に要素数を取ってくることもできる.

K.count_params(A)

出力

9

次元数の取得: K.ndim

次元の間違いがないよう, assertでエラー吐かせるために使ったりする.

K.ndim(A)

出力

2

np.zeros的なやつ: K.zeros

正直使ったことはないw 同様にK.onesK.eye(単位行列)もあるよ.

K.zeros((3,3))

出力

array([[ 0.,  0.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.]], dtype=float32)

転置行列: K.transpose

K.transpose(A)

出力

array([[ 1.,  4.,  7.],
       [ 2.,  5.,  8.],
       [ 3.,  6.,  9.]], dtype=float32)

テンソルの欲しいところだけ集める: K.gataher

ここだけ欲しいって時に使う. 列指定はできないんじゃないかな. ちょっと確かなことわかってません.

K.get_value(K.gather(A, [0,2]))

出力

array([[ 1.,  2.,  3.],
       [ 7.,  8.,  9.]], dtype=float32)

reshape: K.reshape

np.reshapeはほんとよく使いますよね. Kでも一緒です.

K.get_value(K.reshape(A, (1,9)))

出力

array([[ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9.]], dtype=float32)

要素のクリップ: K.clip

画像を扱う時はクリップも結構使うんじゃないでしょうか.
全ての値を設定した最小値と最大値以内に書き換えます.

K.get_value(K.clip(A, 2, 5))

出力

array([[ 2.,  2.,  3.],
       [ 4.,  5.,  5.],
       [ 5.,  5.,  5.]], dtype=float32)

要素を逆順に並べる: K.reverse

(追記: 2017/2/14)

K.reverse(x, axes=-1)

(batch, h, w, ch)のテンソルのRGBをBGRに変える時に使いました.

テンソルのslice

バックエンドとは関係ないですが, sliceは重要なので紹介しときます.

2行目のみ抽出する.

K.get_value(A[:,1])

出力

array([ 2.,  5.,  8.], dtype=float32)

(1,1)を指定する.

K.get_value(A[1,1])

出力

5.0

このように, 特に難しい操作は入らずにテンソルのsliceは可能です.

ほかの基本演算たち

sin, cos, exp, abs, logとか, まぁ有名な演算はもちろん全部あります. めんどいんで, 書きませんがこいつらは全部要素ごとです.

まとめ

今回はバックエンドの演算を中身を確認しながらまとめてみました.

lossの実装で必要なのは, 多分sumとmeanが主に使う関数じゃないでしょうか. あとはint_shapeとか結構使うかな.

これさえ扱えれば, Kerasの実装の幅は跳ね上がるので, Document見ながらbackendの使い方に慣れていきましょう. 僕も勉強中です!

以上です.

[Keras] パラメータの更新をさせないfreezeに関する実験

keras python Deep Learning tips

突然ですが, 一昨日のことです.

GANのKerasによる実装の中で使われていたfreezeについて質問をしてみました.

なぜこのような質問をしたかというと, 以前Sequentialで組んだモデルに対しmodel.trainable = Falseを使い層をfreezeさせようとしたのですが, summary()が出すnon-trainable params の値が変わらない, という結果になっていたからです.

問題点を具体的に見てみましょう.

modelA = Sequential([
    Dense(32, input_dim=784),
    Activation('relu'),
    Dense(10),
    Activation('softmax'),
])

modelB = Sequential([
    Dense(100, input_dim=10),
    Activation('relu'),
    Dense(10),
    Activation('softmax'),
])

# freeze
modelB.trainable = False

# コンパイルしないと反映されないので, 適当にコンパイルする.
modelB.compile(optimizer="adam", loss="categorical_crossentropy")

modelC = Sequential([modelA, modelB])

# modelCの内部情報を見る
modelC.summary()

出力

Layer (type)                     Output Shape          Param #     Connected to                     
====================================================================================================
sequential_5 (Sequential)        (None, 10)            25450                                        
____________________________________________________________________________________________________
sequential_6 (Sequential)        (None, 10)            2110        activation_8[0][0]               
====================================================================================================
Total params: 27,560
Trainable params: 27,560
Non-trainable params: 0
____________________________________________________________________________________________________

このようにNon-trainable params: 0となっていて一見 freezeは反映されていないように見えます.

まとめると, 僕は,

  • そもそもfreezeはlayerに使うもの
  • summary()を過信していた

の2点から, 上記のようなモデルの組み方では, 層のFreezeは行われないと思っていました.

そしたら, @tuntuku_syさんからこんなリプライが.

なんと!

issueの内容も実際にはFreezeできてそうってことでした. これはちゃんと理解しておかないと, 将来的に厄介なことになりそう!

ってことで実験しました.

とりあえず結果からいうとsummary()側のバグでした. 完全に自分の調査不足です. 少しでも疑問点があったらすぐに検証していく心構えが必要だなと感じました.

実験方法

とりあえず議論に上がっているmodel.trainableと, summary()に関して, どっちがバグってるのか確認したいと思います.
また層はDenseでのみ行うので, 他のlayerの場合は試していません. 注意してください.

summary()とは

model.summary()でfreezeされたパラメータ数を見ることができます.

from keras.models import Sequential
from keras.layers import Dense, Activation, Reshape, Input
from keras.layers.convolutional import UpSampling2D, Convolution2D
from keras.utils import np_utils
import numpy as np

model = Sequential([
    Dense(32, input_dim=784),
    Activation('relu'),
    Dense(10),
    Activation('softmax'),
])

model.compile(optimizer="adam", loss="categorical_crossentropy")
model.summary()

出力

____________________________________________________________________________________________________
Layer (type)                     Output Shape          Param #     Connected to                     
====================================================================================================
dense_22 (Dense)                 (None, 32)            25120       dense_input_5[0][0]              
____________________________________________________________________________________________________
activation_9 (Activation)        (None, 32)            0           dense_22[0][0]                   
____________________________________________________________________________________________________
dense_23 (Dense)                 (None, 10)            330         activation_9[0][0]               
____________________________________________________________________________________________________
activation_10 (Activation)       (None, 10)            0           dense_23[0][0]                   
====================================================================================================
Total params: 25,450
Trainable params: 25,450
Non-trainable params: 0
____________________________________________________________________________________________________

注目するべきは, 下の3行です. ここの数字と実際の重みに注目していきましょう.

get_weight()

今回重みを確認するために, 以下のような手段を取ろうと思います.

compare = np.equal(model.layers[2].get_weights()[0], model.layers[2].get_weights()[0])
np.all(compare)

出力

True

同じ引数を与えているので, Trueになります. fit()をすると…

w1 = model.layers[0].get_weights()[0]
model.fit(X, y)
w2 = model.layers[0].get_weights()[0]
compare = np.equal(w1, w2)
np.all(compare)

出力

False

もちろんFalseですね. 重みのarrayを受けて, 比較する. 至って単純な作業.

この二つの出力に注目しつつ実験します.

実験

概要

modelA に trainable=False としたmodelBをaddする.
結合したモデルにfitを使い, 学習前後でmodelBの重みを比較する.

コードと結果

# 学習前の重みをセットする
wa1 = modelA.layers[0].get_weights()[0]
wb1 = modelB.layers[0].get_weights()[0]
# 一応Deepcopy版も
wa1dc = deepcopy(modelB.layers[0].get_weights()[0])
wb1dc = deepcopy(modelB.layers[0].get_weights()[0])
#学習開始
modelC.compile(optimizer="adam", loss="categorical_crossentropy")
modelC.fit(X, y, nb_epoch=100)

# 学習後の重みを受け取る
wa2 = modelA.layers[0].get_weights()[0]
wb2 = modelB.layers[0].get_weights()[0]
# 比較する
compareA = np.equal(wa1, wa2)
compareB = np.equal(wb1, wb2)
print(np.all(compareA)) # False
print(np.all(compareB)) # True

出力

False
True

これはしっかりFreezeされてますね!ちゃんと理解できてよかったです. 僕もGANやろうかなー!

以上です.