Sansan Builders Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

歴史をたどってディープラーニングを学ぶ 第四回 LeNetを作ってコンボリューショナルネットを学ぶ

こんにちは、DeepLearning老人こと糟谷勇児です。

会社では老人ですが、地域活動コミュニティでは若者扱いされギャップに驚いています。

 

そんなわけで今回もDeepLearningを学んでいきたいと思います。

前回はパーセプトロンを多層化することで画像認識の精度がちょっと上がるところを見てきました。

しかし、精度としてはまだまだ。 今回は畳み込み構造を持つコンボリューショナルニューラルネットを作って精度を上げていきたいと思います。  

ところで、私が新卒で会社に入ったころは、HAAR like 特徴量をAdaboostでカスケードした物体認識の手法が一般的でした。

その手法では、物体を判別するフィルタを20層など複数層重ねて、判別機を作り、画像上を走査して、物体を検出します。

 f:id:kasuya_ug:20200218201204p:plain

フィルタを複数層重ねるという意味ではDeepLearningと似ていますね。

しかしDeepLearningが出てきた当時、若い人に

「DeepLearningでは画像を一枚入れれば(走査しなくても)、どこにあるか含めて判定してくれるんですよ」

と言われて、そんな魔法みたいなことあるか? と驚きました。

 

勉強してみると、コンボリューショナルニューラルネットでは、判別機を走査する代わりに、中にフィルタを走査する機構を持っていて類似のことをしており、魔法でなく地続きな手法であると感じます。

コンボリューショナルネットの歴史は長く、古くは福島先生のネオコグニトロンなどがあり、第二回で言及したAlexNetも畳み込みをしています。

ただ、AlexNetはちょっと複雑でまだ難しいので、今回は1998年のLeNetを作っていきたいと思います。

 

http://yann.lecun.com/exdb/publis/pdf/lecun-01a.pdf

LeNet、社内でもルネットとリーネットと読む人がいますがどっちが正しいですかね。 どなたか詳しい方、教えてください。

LeNetの構成は下図のようになります。

f:id:kasuya_ug:20200214183657p:plain
Yann LeCun et al. Gradient-Based Learning Applied to Document Recognitionより引用

Convolutionsと書かれた層ではフィルタを走査する処理が入ります。 フィルタの各マスの値と画素値を掛け算して、次の層の値とします。 このフィルタの値をバックプロパゲーションで更新していきます。

f:id:kasuya_ug:20200218201325p:plain

わかりやすいように一次元で書いてみました。

f:id:kasuya_ug:20200218201347p:plain

畳み込みと言っても、全結合のニューラルネットにおいて、たまたま、結合重みが近傍以外ゼロで、どのニューロンからの重みも同じ位置関係の時には等しいものが学習されれば畳み込みと同じことが起こるので、畳み込みは全結合に制約を加えた特別な場合と考えることもできるはずです。

f:id:kasuya_ug:20200218232631p:plain
こういう全結合のパーセプトロンが学習できれば結果は同じ(矢印の色が同じものはすべて同じ重みの値)
つまり自由度的には今までより下がっているともいえるので、本当にこれで性能が上がるのでしょうか?(疑り深い)

早速C#で作って検証していきます。 数式はこちらのブログを参考にさせていただき、プログラムを作成しました。

http://www.visionsociety.jp/vision/vol29-1/29-1_1.pdf

ブログにも

実際、ネットや文献上で見られる多くのCNNの実装は、Theano (pythonのライブラリ)の自動微分機能を使っていたり、MATLABの組み込み関数を使っているものがほとんどです。

とあり、実際このページ以外で数式がしっかり書かれているサイトはあまりないようです。 非常にありがたいことです。

工夫したのは以下の点です。

  • MaxPooling層の微分はマックスに選ばれたニューロン以外はゼロ
  • マックスの部分では上の値がそのまま降りてくるということで、Maxを計算するときにMaxになったものを覚えておく

f:id:kasuya_ug:20200214190720p:plain
max pooling層のバックプロパゲーションの式(上記ブログより引用)

  Convolution層のバックプロパゲーションの計算では、フィルタで加算されたニューロンに上から誤差が下りてくるのですが、 strideなども考えると数式で毎回計算するのは大変なので、mapを事前に作っておくことにしました。

f:id:kasuya_ug:20200214191551p:plain
Convolution層のバックプロパゲーションの計算式(上記ブログより引用)

        public static Dictionary<int, List<(int,int)>> CreateConnectionMap(int length, int filterSize, int stride)
        {
            var connectionMap = new Dictionary<int, List<(int,int)>>();
            for (var i = 0; i < length; i++)
            {
                connectionMap.Add(i, new List<(int,int)>());
            }

            for (var i = 0; i + filterSize < length; i += stride)
            {
                for (var j = 0; j < filterSize; j++)
                {
                    connectionMap[i + j].Add((i / stride, j));

                }
            }
            return connectionMap;
        }

バックプロパゲーションの計算は安定の6重ループです。

            for (var p = 0; p < underPlane; p++)
            {
                for (var q = 0; q < numFilter; q++)
                {
                    for (var i = 0; i < underHeight; i++)
                    {
                        var connectionX = connectionMapX[i];
                        for (var j = 0; j < underWidth; j++)
                        {
                            var connectionY = connectionMapY[j];

                            foreach ((int x, int s) in connectionX)
                            {
                                foreach ((int y, int t) in connectionY)
                                {
                                    dEdyNext[P][i, j] += dEdy.data[underPlane * q + p][y, x] * filters[q][t, s];
                                }           
                            }
                        }
                    }
                }
            }

ソースコードは700行ぐらいですが結構作るの大変でした。。 ちなみに原著にはMaxPooling層はなく、サブサンプリングするとされていますが、AlexNetを作るときに備えてMaxPoolingを使うことにしました。 また、原著では2回目のConvolution層で上の層と結合をしていない部分があります。 それがうまく働いているらしいですが、そこも未実装です。 全結合層では論文通りSigmoid関数を活性化関数として用いることにしました。 Convolution層、MaxPooling層、全結合層はそれぞれLayerを継承したクラスにして、自由に組み替えられるように作っています。

早速、学習させてみましょう。

前回と同じくcifer10の船と飛行機を学習させてみます。 以下は学習結果になります。 前回同様2000枚ずつの画像で学習させてみましたが、精度は多層パーセプトロン(73%程度)とあまり変わりませんでした。 また学習が進んで二乗誤差が減れば減るほど精度が下がっていってしまいます。つまり過学習しています。 そこで今回は4000枚ずつの画像で学習させてみました。 そうすると、多層パーセプトロンよりかなり精度が上がった83.4%になりました。

f:id:kasuya_ug:20200218201057p:plain

フィルタを走査する機構を加えることで、画像の持つ局所性をうまく特徴化できたのだと思います。 もっといろいろ検証していきたいですね。
次回、

  • 画像を増やしたら精度はどう変わるか
  • 活性化関数をReLUに変えたら精度や実行時間はどう変わるか
  • どんなフィルタが学習されているのかとか
  • 多層パーセプトロンも4000枚学習させたらもっと良かったのではないか

など検証していきたいと思います。
あと、今の学習はすごく遅い(学習に2時間ぐらいかかる)ので、高速化もしていきたいと思います。 LeNetの持つパラメーター(調整する重み)の数は12万ほどなのですが、AlexNetは6000万のパラメーターがあるといいます。 今の速度では到底終わりません。

DeepLearningの壁を感じつつ今回は以上となります。


▼本シリーズのほかの記事はこちら

buildersbox.corp-sansan.com

© Sansan, Inc.