畳み込みニューラルネットワーク:CNN (Convolutional neural network) の実装に取り掛かります(以降は、畳み込みニューラルネットワークをCNNと記載することにします)。 「ゼロから作る Deep Learning 」テキストの最終課題と言えるのがこのCNN。今回の記事はMNIST認識プログラムでのCNN実装準備として、新しく登場する処理とそのクラスレーヤを個々に紹介することにしました。(CNNを実装したMNIST認識プログラムは次記事で紹介します。)なお、テキスト第6章の「学習に関するテクニック」の記事は省略しています。紹介テクニックの多くが、ここでの Deep Learning (初級レベル)の理解を深め、もう少し後の方がいいように思われたからです。
さて、ここまでの Deep Learning ではMNISTイメージデータ28x28画素を単なる1次元の784素子配列データとして扱っていまいた。イメージ像を形成する2次元データとして画素の位置関係は直接的に学習に使われていません。(間接的には使われていたかもしれません)。 CNNでは画像データに新たな処理層として、2次元配列の畳み込み層(Convolution Layer)とプーリング層(Pooling Layer)を導入します。イメージデータを2元データとしての演算によって、2次元特徴の抽出を行い画像認識精度を高める効果を持たせました。
以下、独自の発想も少し加えた実装化プログラムを紹介しす。追加する処理層としては、①フィルタ演算(畳み込み演算)、②バイアス演算、③パディング処理、④プーリング演算と⑤切り出し演算(quarry演算)です。quarry演算はテキストのim2col関数に相当します。処理演算はMNIST認識での実装を目標としたクラス関数としています。
【1】CNNのクラス演算(forward処理)
1)フィルタ(畳み込み演算)
フィルタは2次元データから特徴抽出をして、その特徴情報を持ったデータを新たに作る処理といえます。今、高さ x_h、幅 x_w を持つ2次元配列データ x に高さ f_h 、幅 f_w の2次元データを持つ f による畳み込み演算をします。2次元データ x からフィルタサイズのブロックを切り出してフィルタとの積の総和を計算する演算です。切り出すブロックは端からストライド(stride)値だけずらします。最終的には(4次元データ) × (4次元フィルタ )の処理ですが、まずは(2次元データ)x(2次元フィルタ)から進めます。
(1) 2次元入力 X 2次元フィルタ
基本実例として4×4の入力データに対して、2x2のフィルタをストライドを1として処理した場合です。出力は3x3になります。
少し詳細に説明すると、入力in_x(4,4)からフィルタf(2,2)のサイズと同じ2x2のブロックを左上端から切り出します。ストライド1なら9つ切り出しデータが出来ます。それぞれの切り出しデータとフィルタの一致する位置の積について総和を求めます。その値が出力の要素となります。分解した様子がFig.XIII-2です。
Fig.XIII-1をプロットするプログラム(Program 1)です。 プロットの関数、フィルタ関数、メイン部の順に出来ています。(Fig.XIII-2のプロットプログラムは少し変更しただけなので紹介は省略。)
(2) 3次元入力 X 3次元フィルタ
画像データにチャンネル c の次元を追加します。画像のカラー情報(3原色や透明度など)に当たる情報ということです。MNISTでは白黒のグレー階層なのでチャンネル数は1ですが、入力データはチャンネルを追加して3次元配列となります。フィルタもこれに対応してチャンネル c(入力のチャンネルと同じ数)を追加します。これで、色データ毎にフィルタ値を与えることが出来ることになるかと考えられます。
チャンネル数を3とした場合の例がFig.XIII-3です。
フィルタによる畳み込み演算はチャンネルを含めた積算値となります。入力xとフィルタfのチャンネル数は3で同じでが、出力データのチャンネル数は1になります。フィルタ処理をすると、入力データのチャンネル情報は出力の2次元配列中に埋め込まれていきます。
(3) 3次元入力 X 4次元フィルタ
フィルタは画像の特徴抽出となるので、複数の特徴を抽出する複数個のフィルタ設定をします。複数のフィルタを設定するとフィルタは4次元配列となります。Fig.XIII-3のフィルタを2つにした場合です。
入力データとフィルタのチャンネル数は同じ3ですが、出力データのチャンネル数は2になります。複数フィルタ処理をすると出力のチャンネルはフィルタ別のインデックスに置き換えられてます。
(4) 4次元入力 X 4次元フィルタ
最後にバッチ処理の場合です。フィルタ処理を複数の入力データ毎に行いますので、出力されるデータは同じバッチ数を持ちます。上と同じく、出力のチャンネル数はフィルタ数に置き換わります。
Fig.XIII-5をプロットしたプログラム(Program 2)です。program1をベースにして、フィルタ関数と切り出し関数をクラスに変更しています。 Fig.XIII-3とFig.XIII-4もデータを変えてプロットできます。
2)パディング
次に、新しいレーヤはパディングです。パディングは2次元データの端部(上下左右)に値 0 データを付け加えます。パディングにはフィルタで減少するデータ数を補う役目があります(テキストではそのように説明している)。 その一方で、端数データの救済の意味があると考えています。上記のフィルタ処理では入力データの端のデータが余って切り捨てられる場合があり、両端のデータについてはフィルタに掛けられる回数が内側のデータより少なく、結果に対する影響度が低いといえます。両端部の外にデータを付けることで、端のデータも生かせるようになります。Fig.XIII-5のフィルタ処理にて、周囲にパディングを1つしたのがFig.XIII-6です。
Fig.XIII-5と比べて、中央部の値には変化はありませんが、周辺部には新しい値が出てきています。後の処理にも若干の差異が発生ます。パディングする数はストライド値以下でいいと考えられます。なお、MNISTデータでは多くのデータの周辺部が0値ですので、パディングの効果はあまりないと思われます。
<padding class> クラスとしての実装は以下です。
3)バイアス
CNNのバイアスはフィルタ毎のバイアスとなります。フィルタ処理ではフィルタの枚数がチャンネル(配列2番目)の情報に入ります。CNNのバイアスは入力データの1番目(バッチ)と3,4番目(2次元配列)に対して同じバイアス値を加算する処理です。
Fig.XIII-7の例では、重なってる手前の1枚目には1、後ろの2枚目に2を加算しています。
<filter bias class> の実装は以下です。
4)プーリング
プーリングはフィルターと同じ様にブロック切り出し処理をして、そのブロック内での最大値をそのブロックの代表値とする処理です。
<pooling class> はこの様になります。
これで、新しい処理のクラスが出来ました。ここまでのクラスプログラムを統合すると、program 3 となります(Fig.XIII-8のプロットをする)。
<program 3>
メイン部では①初期設定、②データ配列の設定、③レーヤクラスのパラメータ設定とオブジェクトの生成 ④レーヤオブジェクトでのforward処理を順次実行 ⑤結果のプロットとなっています。
【2】CNN演算の誤差逆伝搬
次に、CNNクラスメソッドとして誤差逆伝搬法の偏微分値算出が出来るようにします。逆伝搬法なので演算手順も出力側から逆にして遡ります。まずはプーリングレーヤから各レーヤの偏微分値メソッドを紹介しますが、初期値として、プーリングレーヤの出力要素の偏微分値をすべて1に仮定しています。
1)プーリングレーヤ(pooling_layer)の偏微分
プーリング処理はブロックの最大値のみを残して他のデータは捨てます。従ってレーヤのみでの偏微分値はブロック内の最大値については1、その他は0となります。
class <pooling_layer> の backwardメソッド
切り出しレーヤ(quarry_layer)クラスの偏微分処理の設定も行います。
class <quarry_layer> の backwardメソッド
次のFig.XIII-9がプーリングレーヤのforward出力側での偏微分値をすべての要素について1とした場合の誤差逆伝搬メソッドの結果です。
プーリング処理はプーリングブロック内の最大値を選ぶ処理です。Fig.XIII-8のforwardメソッドでは大きな数値はブロックで最大値となり、複数回出現します(155や309などの値)。逆伝搬ではこの最大値になった回数(最大4回)が反映されています。なお、今回の例ではbackwardによる結果はバッチ間(Fig.XII-9の上下)で同じになりますが、これはバッチ間で同じ比率のデータを使ったためです。異なる入力イメージによっては異なる結果になりますが、値の大小については平滑化されると考えられます。
2)フィルタバイアスレーヤ(filter_bias_layer)の偏微分
フィルタバイアスレーヤでの逆伝搬では入力とバイアスの2つの偏微分値を求ます。バイアス関数だけの入力に対する偏微分値は1なので、逆伝搬してくる偏微分値(grad_y)と同じ値が入力側に偏微分値(grad_x)として伝達します。一方、バイアス値に対する関数だけの偏微分も1ですが、フィルタバイアスはフィルタ毎にその要素全体にバイアス値を加算します。従って、誤差逆伝搬では出力側要素の合計がフィルタバイアスの最終出力に対する偏微分値となります。今回の例では、2(バッチ)x4(高さ)x4(幅)=32個分の合計がバイアスレーヤのバイアス偏微分値となります。
class <filter_bias_layer> の backwardメソッド
今回の例では、Fig.Xiii-10の様になり、2(バッチ)x4(高さ)x4(幅)=32要素の合計がバイアスレーヤのバイアス偏微分値となります。(すべての配列の初期値を1にしたので、バッチと配列の数32にも一致する)
3)フィルタレーヤ(filter_layer)の偏微分
フィルタレーヤの逆伝搬では入力とフィルタの2つの偏微分値を求めます。フィルタ処理のforwardメソッドでは、切り出したブロックの配列とフィルタ配列のdot積を求めています。dot積の誤差逆伝搬法での偏微分値の算出方法は「重み付き和」レーヤの偏微分計算方法を転用することにします。「重み付き和」レーヤの偏微分計算は前回の記事で確認をしているのでここでは省略します。
class <filter_layer> の backwardメソッド
この様になります。フィルタに対する偏微分値が大きくなることが分かります。
4)パディングレーヤ(padding_layer)の偏微分
パディングの逆伝搬は単にパディングした周辺のデータを削るだけとなります。
全レーヤの誤差逆伝搬メソッドが出来上がりましたので、パディングレーヤの逆伝搬の様子をプロットしたプログラムの全体です。
< Program 4 >
【3】まとめ と あとがき
1. まとめ
CNN(Convolutional neural network)のレーヤクラスを作りました。レーヤの処理が具体的に見える様にしてみました。簡単な数値での処理でしたが、①フィルタ処理をすると、チャンネルの情報(色など)はフィルタ別の情報(フィルタ枚数分の情報)に置き換わる。②プーリングレーヤの偏微分値では入力データ間(バッチ間)の強度(大小)の影響は少なく、入力データ内の大小関係が逆伝搬でへの影響が強くなる(最大値の要素が強い)。③フィルタやフィルタバイアスは、forward処理が影響する要素が多いことから、偏微分値ではその偏微分値が逆に大きな変動を受けることになる。 など、理解に役立ちました。
プログラムの作成上は、レーヤ処理をステップ毎に分けたので、演算処理の内容は割と簡単になりました。一方で、配列次元の調整(reshape)や、関数処理のための配列順番の調整(transpose)には注意が必要です。
2.参考文献 / 参考リンク
[※] このブログは教本として、『ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装』 [ 斎藤 康毅 ] 【出版社: オライリージャパン 発売日: 2016/9/24 】 (ISBN-10:4873117585 ISBN-13:978-4873117584) を使っています。
[※] pythonの教本として何かまとまった1冊を持っているといいです。 ネットで調べれば細かい所や、実例を沢山見ることができますが、導入として「何を」調べたいのかを知るにはまとまった教本が必要です。最近は『独学プログラマー Python言語の基本から仕事のやり方まで [ コーリー・アルソフ ] 』を参考にしています。
3.あとがき
先回の記事からまたまた時間がかなり経ちました。CNNは簡単な処理かなと思っていたら、配列の調整やFigプロットに意外と手間取ってしまいました。Pythonの使い方もだいぶ修練して来ました。次はMNISTの認識実装になりまが、今回のCNNが正しく動くこと確認しているので、MNIST 実装は楽だと思います。今度は、それほどの期間を開けずにMNISTの認識実装を紹介できると思います。
2023年12月26日 公開
*🐭*🐍*🐭*🐍*🐭*🐍*🐭*🐍*🐭*
0コメント