FPGA
python3
Polyphony

ニューラルネットワークへの序章

今年に入って生活の中に猫が入ってきた。猫のしぐさを観察し、その姿を浮世絵の中に見つけるたびに、この小動物と人間の関係の近さを感じざるを得ない。しゃもじにかかわる話は特にない。

そこで唐突にニューラルネットの話になる。

数式とにらめっこをし sigmoid 関数を Python で実装したりすると世の中の流れが少しわかってくる。学習では TensorFlow が便利だ。Windows PC の上のバーチャルな Ubuntu (GPUなし)でもそこそこの早さだ。中身を理解するなら tyni-dnn が有益だった。自分でもレイヤーを追加できそうだ。

Polyphony でもそこを目指そう。

浮動小数点あるいは量子化という話は横に置いておいて、XOR で実装可能という二値化 NN を(C がない)目指すことにする。実装方法は PYNQ-BNN を参考にしよう。といっても、本格てな NN をつくるには少々スキルも必要そうだ。32x8 の演算でクリスマスを迎える準備をしよう。

import を使ったパラメタ

xor32_8_data.py
from polyphony.typing import List, bit32, bit8, bit6

DATA_W:List[bit32] = [ 1, 2, 3, 4, 5, 6, 7, 8 ]
DATA_th:List[bit6] = [ 1, 2, 3, 4, 5, 6, 7, 8 ]

Polyphony は Python の import が使える。この機能により、モデル本体とパラメタを切り離すことが出来る。

ビットのカウントにはテーブルを一部使い効率化しよう。

count_table.py
m polyphony.typing import List, bit32, bit8, bit6, bit

xbit4_to_n:List[bit6] = [
    0,         # 0000
    1,         # 0001
    1,         # 0010
    2,         # 0011
    1,         # 0100
    2,         # 0101
    2,         # 0110
    3,         # 0111
    1,         # 1000
    2,         # 1001
    2,         # 1010
    3,         # 1011
    2,         # 1100
    3,         # 1101
    3,         # 1110
    4          # 1111
]

肝心の xor (掛け算の代用)のソースも掲げる。unroll を使っているので Polyphony の devel ブランチを必要とする。unroll を削れば 0.3.2 でもコンパイルできる。

xor32x8.py
import polyphony
from polyphony import testbench
from polyphony.typing import List, bit32, bit8, bit6, bit4, bit
from polyphony import unroll

from xor32_8_data import DATA_W, DATA_th
from count_table import xbit4_to_n

def count_bit32(x:bit32)->bit6:
    mask:bit32 = 0xF
    shift_n = 0
    sum = 0
    for i in unroll(range(8)):
        sum += xbit4_to_n[((x & mask) >> shift_n)]
        mask <<= 4
        shift_n += 4

    return sum

def nn_xor32_8(x:bit32)->bit8:
    rv_xor8:bit8 = 0
    for wi in unroll(range(len(DATA_W))):
        tmp_x:bit6 = count_bit32(x ^ DATA_W[wi])

        rv_xor8 <<= 1
        rv_xor8 |= 0 if tmp_x < DATA_th[wi] else 1

    return rv_xor8

@testbench
def test():
    rv = nn_xor32_8(0x32)
    print(rv)
    #print('{:08x}'.format(rv))

test()

おまじないよろしくいつでも full unroll に頼ると巨大な回路を生成して使い物にならないだろう。unroll の省略可能な第2引数で unroll のまとめあげる数を指定可能だ。適切と思われる数字を入れれば回路規模と性能のバランスをとることが可能だ。適切な回数は何か?それこそ遺伝的アルゴリズムなどで自動中抽出して欲しいものだ。

いまのままでは xor の計算が出来ただけだ。まだまだニューラルネット・ワークとは言えない。しかし、学習パラメタをつくりこめば MNIST に適応できそうな感触はある。

次のステップはモデルの自動生成か?モデルの記述で比較的簡単のものにそぼれば Python コードのジェネレータはそれほど時間を掛けずにできるかもしれない。しかし、それでは長い目で見たらだめそうなこともわかっている。一度、モデルを Python のコードに落とすことで情報が欠落してしまうだろう。モデルからフローグラフを考えそれを直接 IR に変換する機構が必要だ。

それを実現する手立ては、大手企業が提供しているグラフ・コンパイラとでもいうツールに近くなるだろう。Polyphony は通常の CPU に対するコンパイラではなく RTL を生成するコンパイラだ。だから、実は内部にグラフを取り扱う機構がふんだんに入っている。工夫次第では CNN にマッチするのではないかとにらんでいる。