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の抜粋。
岡野原氏が、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
以下が、バイナリ化しない場合とバイナリ化した場合の結果である。
バイナリ化なし:
バイナリ化あり:
バイナリ化しているのにも関わらず、そこまで精度は落ちていないのがわかる。