THE長文日記

長文とか短文とかのクレームは一切受け付けません

カプセルネットの動的ルーティングをnumpyで書いてみた

 出張から帰ってきて頭がボケボケしてるので、カプセルネットの最重要ポイントである動的ルーティング部分をnumpyで書いてみた。あくまで二次元のカプセルネットだけど


def dyn_routing(inputs, Affine_W,num_capsule,input_num_capsule,routings=3):
    inputs_expand = np.expand_dims(inputs, 1)
    inputs_tiled = np.tile(inputs_expand, [1, num_capsule, 1, 1])
    print(inputs_tiled.shape)
    plot_tensor(inputs_tiled,'inputs_tiled.png')

    print(Affine_W.shape,inputs_tiled.shape)
    inputs_hat = np.zeros_like(inputs_tiled)
    for i in range(inputs_hat.shape[1]):
        for j in range(inputs_hat.shape[2]):#二次元のアフィン変換
            inputs_hat[0,i,j,0] = Affine_W[i,j,0,0]*inputs_tiled[0,i,j,0]+Affine_W[i,j,0,1]*inputs_tiled[0,i,j,1]
            inputs_hat[0,i,j,1] = Affine_W[i,j,1,0]*inputs_tiled[0,i,j,0]+Affine_W[i,j,1,1]*inputs_tiled[0,i,j,1]

    plot_tensor(inputs_hat[0],'inputs_hat.png',alpha=0.005)

    b = np.zeros(shape=[np.shape(inputs_hat)[0], num_capsule, input_num_capsule])

    for r in range(routings):
        e_x = np.exp(b - np.max(b))
        c = e_x / e_x.sum() #softmax
        plot_tensor(c[0],'c_%d.png'%r,vector=True,alpha=1.0)

        outputs = np.zeros_like(inputs_hat)

        def scalar_squash(x, axis=-1):
            s_squared_norm = np.sum(np.square(x), axis, keepdims=True)
            scale = s_squared_norm / (1 + s_squared_norm) / np.sqrt(s_squared_norm + 1e-07)
            return scale * x


        for i in range(inputs_hat.shape[1]):
            for j in range(inputs_hat.shape[2]):#Cを掛ける
                outputs[0,i,j,0] = c[0,i,j]*inputs_hat[0,i,j,0]
                outputs[0,i,j,1] = c[0,i,j]*inputs_hat[0,i,j,1]

        outputs = scalar_squash(outputs)
        plot_tensor(outputs[0],'outputs_%d.png'%r,alpha=2.0)

        if r < routings - 1:
            for i in range(inputs_hat.shape[1]):
                for j in range(inputs_hat.shape[2]):#二次元の内積
                    b[0,i,j] += outputs[0,i,j,0]*inputs_hat[0,i,j,1]
                    b[0,i,j] += outputs[0,i,j,0]*inputs_hat[0,i,j,1]

        plot_tensor(b[0],'b_%d.png'%r,alpha=1.0)
    
    return outputs

 ヒントン先生はなんで8次元とか16次元とかのベクトルを使いたがるのか

 当然、MNISTくらいなら二次元でも十分性能が出る


 しかし解せないのはなんでむしろ畳み込みより簡単なはずの処理に「カプセルネットの欠点としては、計算に時間が掛かる」とされているんだろう。


 ベクトルが16次元になると内積は確かに複雑になるがそこまで言うほどか・・・?


 畳み込みに比べてベクトル表現はなぜ良いのか

 当然だが、ベクトルは面積に依存しない。

 

 畳み込みはカーネルの大きさがニューラルネットの大きな制約になっていた。大きすぎても小さすぎてもいけないが、その結果、PaintsChainerみたいな自動着色タスクでは、アニメのようなベタ塗りができなかった。なぜならベタで塗られた場所(というか塗られていない場所)は畳み込みフィルタが認識できない場所だったからだ。


 ベクトル表現の場合、額面通りの性能であれば、そういう制約からは開放されるはずだ。


 ヒントン先生の論文だとカプセルから画像を再構成するのに(懲りずに)全結合層を二層使っているが、これは「逆畳込みとかアップサンプリングとかは冗談でしかないのだよ」というある種の皮肉なのか本気なのかがよくわからない。


 ニューラルネットの進化が長らく停滞していた遠因のひとつとして、二層のニューラルネットは一層や三層のニューラルネットよりも性能が高い、という事実がある。


 この問題のややこしいところは、一層より二層がイイのはすぐわかったのだが、二層より三層はよくないということにみんなが気づくまで時間がかかったことにある。


 なぜなら、三層のネットワークというのは、後ろ二層だけみれば(高性能な)二層のネットワークであり、層をいくら増やしても性能が上がらないけれども、学習が維持できているように「見えてしまう」のは、結局、お尻の二層が凄まじい性能を発揮してしまうからだ。


 したがってヒントン先生はかならずお尻に二層の全結合を付けたがるわけで、これがもはやアイデンティティの証明なのかもしれないが、どうも座りが悪く思えてしまうのは気のせいだろうか。


 カプセルネットについて知りたいことはまだまだあるが、バイトのTくんあたりはこれをJuliaで実装するんだと息巻いてる。まあきっと速いんだろうね。複素数がCより速い言語だから。


 どうもカプセルネットはヒントン先生の「こうありたい」という思いがまだ先行している感じで、ほんとうにその実力がどうなのか(いやまあMNISTとCifarで凄いということはだいたいわかるわけだが)についてはまだ未知数である・・・ということに今はしておこう。でないとイマイマは困る人も出てくるだろう。


 実際、カプセルネットでMNISTの学習に必要だったデータ量は従来の1/100である。これだけでもカプセルネットの存在価値はある。世の中には臨床データなど、欲しくても大量のデータが手にはいらないケースは枚挙に暇がないわけで。


 ヒントン先生がいうように、物体の三次元的な把握をカプセルネットでできるとして、それがシーングラフの再構成に近い方法だとしたらこれは控えめに言ってちょっと革命的な事象である。


 今年のNIPSはカプセルブームになるのだろうか。

 ただ、ヒントン先生の言ってることが正しいのか(これを疑うなどとんでもないと言われそうだが)、確認するためにはまだいろんなデータセットで試す必要がある。


 そもそも本当にそういうものであればObject DetectionにもPix2Pixのようなものにも応用可能だろうし、そうなったときにカプセルネットは真価を発揮できる。


 カプセルネットの論文はarxivにまだ15本くらいしか載ってないので、今がチャンスという気がする


 ただし、オモシロすぎて論文とか書いてる暇がないという問題がある

 でも研究者になったので論文くらい書かなくては


 うーむ 論文っていってもどのレベルが論文なのかねえ