覚え書きブログ

Deep Learning覚え書き(Binary化)

Deep Learningのパラメータをバイナリ―化(-1と1にエンコード)することにより、必要なメモリ容量を抑え検出を高速化する方法がある。
有名なバイナリ―化方法としては、下記の2つが知られている。

1)BinaryConnect:パラメータ自体は連続値で持っておいて、forward時に決定的または確率的に重みパラメータwを+1または-1に変換する。決定的な変換方法とは、wが0以上の場合+1、0未満の場合-1に変換するものである。一方、確率的な変換方法とは、確率p=σ(w)(hard sigmoid関数)に従って+1にし、確率1-pに従って-1に変換するものである。つまり、wが正に大きいほど+1になる確率が高くなる。backward時はバイナリ化は行わないものの、wの更新時に、wの値が[-1,1]になるようにclippingの操作を入れている。

詳細については、下記の論文を参照。
BinaryConnect: Training Deep Neural Networks with binary weights during propagations
Matthieu Courbariaux, Yoshua Bengio, Jean-Pierre David
http://arxiv.org/abs/1511.00363

2)Binarized Neural Networks:forward時のバイナリ化の方法や、backward時はバイナリ化を行わずclippingを用いる点などは、Binary Connectと基本的には同じ。異なる点は、forward時に、convなどの線形変換の後に、batch normalizationを行い分布を正規化した後に、再度バイナリ化を行うところである。ReLUなどの活性化関数をかける場合は、その後に再度バイナリ化を行うので、各層の出力(conv層の場合は、特徴マップ)がバイナリになる。
さらに、backward時は、勾配の推定にstraight-through estimatorというのを採用しており、絶対値が1以下の勾配のみ用いて更新する。

詳細については、下記の論文を参照。
Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1
Matthieu Courbariaux, Itay Hubara, Daniel Soudry, Ran El-Yaniv, Yoshua Bengio
http://arxiv.org/abs/1602.02830

以下は論文からのpsuedo codeの抜粋。
f:id:hirotaka_hachiya:20160808211134p:plain

岡野原氏が、chainerでBinarized Neural Networksを線形関数用に実装したものを下記で公開している。
https://github.com/hillbig/binary_net

下記のように、full connected層に実装してみた。

class MnistCNN_binaryLinear(chainer.Chain):

    """An example of convolutional neural network for MNIST dataset.

    """

    def __init__(self, channel=1, c1=16, c2=32, c3=64, f1=256, \
                 f2=512, filter_size1=3, filter_size2=3, filter_size3=3):
        super(MnistCNN_binaryLinear, self).__init__(
            conv1=L.Convolution2D(channel, c1, filter_size1),
            conv2=L.Convolution2D(c1, c2, filter_size2),
            conv3=L.Convolution2D(c2, c3, filter_size3),
            l1=link_binary_linear.BinaryLinear(f1, f2),
            l2=link_binary_linear.BinaryLinear(f2, 10),
            bnorm1=L.BatchNormalization(c1),
            bnorm2=L.BatchNormalization(c2),
            bnorm3=L.BatchNormalization(c3),
            bnorm4=L.BatchNormalization(f2),
            bnorm5=L.BatchNormalization(10)
        )

    def __call__(self, x):
        # param x --- chainer.Variable of array

        x.data = x.data.reshape((len(x.data), 1, 28, 28))

        h = F.relu(self.bnorm1(self.conv1(x)))
        h = F.max_pooling_2d(h, 2)
        h = F.relu(self.bnorm2(self.conv2(h)))
        h = F.max_pooling_2d(h, 2)
        h = F.relu(self.bnorm3(self.conv3(h)))
        h = F.max_pooling_2d(h, 2)
        h = bst.bst((self.bnorm4(self.l1(h))))
        y = self.bnorm5(self.l2(h))
        return y

以下が、バイナリ化しない場合とバイナリ化した場合の結果である。

バイナリ化なし:
f:id:hirotaka_hachiya:20160807204804p:plain

バイナリ化あり:
f:id:hirotaka_hachiya:20160807205936p:plain

バイナリ化しているのにも関わらず、そこまで精度は落ちていないのがわかる。