深層学習といえば、その膨大な計算量を贖うためのGPUと、それを駆動するためのGPUプログラミングが必須と見られがちだが。
ニューラルネットワークの量子化についての最近の研究の進展と、その重要性 - SmartNews Engineering Blog
https://developer.smartnews.com/blog/2017/03/neural-network-quantization/
で述べられているように、各ニューロンのパラメータなどを浮動小数点数ではなく、表現範囲が1bit程度の整数で表すことで、GPUではなく通常のCPUプログラミングで深層学習が可能になる。
1bit程度の整数ならば
Linux Parallel Processing HOWTO: SIMD Within A Register(例えば MMX を利用)
http://linuxjf.osdn.jp/JFdocs/Parallel-Processing-HOWTO-4.html
のテクニックを使って、C言語での整数プログラミングの範囲内で多くのパラメータを一括処理できるからだ。
先述のBlogで述べられているように、学習済みのニューラルネットワークについては、出力やパラメータを1bit化しても十分に動作することがわかっているが。
その学習済みのニューラルネットワークを得るのは、1bit程度の整数で可能だろうか?
ニューラルネットワークに学習させる手法としては、当該ニューラルネットワークの出力の微分を計算して勾配を求める
バックプロパゲーション - Wikipedia
https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%82%AF%E3%83%97%E3%83%AD%E3%83%91%E3%82%B2%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3
が典型であり、先述のBlogで紹介されている研究も、この手法を無理やり使っている。
どこが無理やりかというと、出力が1bit化されていて0と1しかなく連続な値をとれないので、本当は微分可能ではない点だ。
先述のBlogより
アクティベーションを二値化するのは、アクティベーションにステップ関数を適用してから次のレイヤーに入力することほぼ(=小さい側の値が0ではなく-1なのがステップ関数と少し違う)と同じですから、そういう非線形な関数を適用している、と数式上で真面目に考えてバックプロパゲーションの計算を行うと、ほとんどいたるところで勾配が0になってしまいます。
そこで、forward時はステップ関数(しつこいけどちょっと違う)なんだけど、backward時には(-1, -1)から(1, 1)の間を直線でつないだ関数を考えます。
これをStraight Through Estimator (STE)と言います。
なのでこれらの研究では、出力が0から1に切り替わる不連続点とその周囲を、0と1とを結ぶ直線に置き換えて近似している。
この近似により、勾配の値が実数の形で出てくるので、あとは通常の深層学習と同じ手法が使えるのだが。
勾配の値が実数≒浮動小数点数である、その通常の深層学習の手法ではGPUが必須なわけで、GPUプログラミングを避けたい自分としては美味しくない。
代わりに、学生の頃に物理を学んだ自分としては、不連続点での微分に
ディラックのデルタ関数 - Wikipedia
https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%A3%E3%83%A9%E3%83%83%E3%82%AF%E3%81%AE%E3%83%87%E3%83%AB%E3%82%BF%E9%96%A2%E6%95%B0
を使い、このデルタ関数の近似によって勾配を得る手法を使ってみた。
すると、近似方法を上手く選べば、得られた勾配分布は-∞,0,+∞の三値しか取らないことに気付いた。
デルタ関数の近似方法の一つとして、
正規分布 - Wikipedia
https://ja.wikipedia.org/wiki/%E6%AD%A3%E8%A6%8F%E5%88%86%E5%B8%83
の分散値を0に近づけた極限がある。
これを使って得られる勾配分布は、無限に大きくなる数を底とし不連続点からの距離の二乗を冪指数とする関数を、当該ニューロンが各シナプスから受け取った各入力の重み付き総和に適用したものになり。
底が無限に大きくなるため、不連続点からの距離に少しでも差がある勾配の比は、片方が0で無い限りは無限に大きくなる。
なので、この勾配分布に正規化を掛け、シナプスからの重み付き総和が不連続点に最も近いニューロンの勾配を適当な有限値にしようとすると、それ以外のニューロンの勾配は無限に大きい値で除算されて0になってしまうのだ。
おまけに、無限値に加減算・乗除算を掛けても無限値のままなので、乗除算が不要になる。
普通のCPUでも乗除算命令は加減算やビット演算より遅いし、FPGAや組み込み向けのような極小CPUに至っては乗除算命令が省かれているのが普通だ。
パラメータの更新などに欠かせない乱数には
メルセンヌ・ツイスタ - Wikipedia
https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%AB%E3%82%BB%E3%83%B3%E3%83%8C%E3%83%BB%E3%83%84%E3%82%A4%E3%82%B9%E3%82%BF
のようにビット演算だけで高品質なものがあるので、-∞,0,+∞の三値化と合わせれば、RISC-VのRV32I命令セットのような低コストの演算だけで、深層学習ができることになる。
機械学習でお馴染みの
MNISTデータベース
http://yann.lecun.com/exdb/mnist/
で試したところ、275回もデータベースを舐めさせたとはいえ、7層全結合ニューラルネットワークでも訓練用データの3/4を学習できている。
勾配伝搬に浮動小数点数を用いる通常のニューラルネットワークでも7層での学習は困難を伴うことを考えれば、-∞,0,+∞の勾配伝搬三値化は十分実用に耐えるはずだ。
この7層用と動作確認向けに4層のコードをGitHubに上げたので
https://github.com/abo-junghichi/ternary-backprop
興味のある向きはコードを覗いてみてほしい。