TadaoYamaokaの日記

山岡忠夫Homeで公開しているプログラムの開発ネタを中心に書いていきます。

Chainerで計算グラフの可視化

Chainerの計算グラフの可視化機能を使ったことなかったので使ってみた。

将棋AIのPolicy networkとValue networkを結合したWide ResNetを可視化してみた。

dotファイル出力

Visualization of Computational Graph — Chainer 3.0.0 documentation
このページの説明通り、モデルを構築し順伝播を実行し、出力のVariableを使用して、.dotファイルを作成する。

import numpy as np
from chainer import Variable
from chainer import Chain
import chainer.functions as F
import chainer.links as L
import chainer.computational_graph as c

FEATURES1_NUM=62
FEATURES2_NUM=57
MAX_MOVE_LABEL_NUM=101
k = 192
dropout_ratio = 0.1
fcl = 256 # fully connected layers
class PolicyValueNetwork(Chain):
    def __init__(self):
        super(PolicyValueNetwork, self).__init__(
            l1_1_1=L.Convolution2D(in_channels = FEATURES1_NUM, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l1_1_2=L.Convolution2D(in_channels = FEATURES1_NUM, out_channels = k, ksize = 1, pad = 0, nobias = True),
            l1_2=L.Convolution2D(in_channels = FEATURES2_NUM, out_channels = k, ksize = 1, nobias = True), # pieces_in_hand
            l2=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l3=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l4=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l5=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l6=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l7=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l8=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l9=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l10=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l11=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1, nobias = True),
            l12=L.Convolution2D(in_channels = k, out_channels = MAX_MOVE_LABEL_NUM, ksize = 1, nobias = True),
            l12_2=L.Bias(shape=(9*9*MAX_MOVE_LABEL_NUM)),
            # value network
            l12_v=L.Convolution2D(in_channels = k, out_channels = MAX_MOVE_LABEL_NUM, ksize = 1, nobias = True),
            l12_2_v=L.Bias(shape=(9*9*MAX_MOVE_LABEL_NUM)),
            l13=L.Linear(9*9*MAX_MOVE_LABEL_NUM, fcl),
            l14=L.Linear(fcl, 1),
            norm1=L.BatchNormalization(k),
            norm2=L.BatchNormalization(k),
            norm3=L.BatchNormalization(k),
            norm4=L.BatchNormalization(k),
            norm5=L.BatchNormalization(k),
            norm6=L.BatchNormalization(k),
            norm7=L.BatchNormalization(k),
            norm8=L.BatchNormalization(k),
            norm9=L.BatchNormalization(k),
            norm10=L.BatchNormalization(k)
        )

    def __call__(self, x1, x2):
        u1_1_1 = self.l1_1_1(x1)
        u1_1_2 = self.l1_1_2(x1)
        u1_2 = self.l1_2(x2)
        u1 = u1_1_1 + u1_1_2 + u1_2
        # Residual block
        h1 = F.relu(self.norm1(u1))
        h2 = F.dropout(F.relu(self.norm2(self.l2(h1))), ratio=dropout_ratio)
        u3 = self.l3(h2) + u1
        # Residual block
        h3 = F.relu(self.norm3(u3))
        h4 = F.dropout(F.relu(self.norm4(self.l4(h3))), ratio=dropout_ratio)
        u5 = self.l5(h4) + u3
        # Residual block
        h5 = F.relu(self.norm5(u5))
        h6 = F.dropout(F.relu(self.norm6(self.l6(h5))), ratio=dropout_ratio)
        u7 = self.l7(h6) + u5
        # Residual block
        h7 = F.relu(self.norm7(u7))
        h8 = F.dropout(F.relu(self.norm8(self.l8(h7))), ratio=dropout_ratio)
        u9 = self.l9(h8) + u7
        # Residual block
        h9 = F.relu(self.norm9(u9))
        h10 = F.dropout(F.relu(self.norm10(self.l10(h9))), ratio=dropout_ratio)
        u11 = self.l11(h10) + u9
        # output
        h12 = self.l12(u11)
        h12_1 = self.l12_2(F.reshape(h12, (len(h12.data), 9*9*MAX_MOVE_LABEL_NUM)))
        # value network
        h12_v = self.l12_v(u11)
        h12_2 = F.relu(self.l12_2_v(F.reshape(h12_v, (len(h12_v.data), 9*9*MAX_MOVE_LABEL_NUM))))
        h13 = F.relu(self.l13(h12_2))
        return h12_1, self.l14(h13)

model = PolicyValueNetwork()

features1 = np.empty((1, FEATURES1_NUM, 9, 9), dtype=np.float32)
features2 = np.empty((1, FEATURES2_NUM, 9, 9), dtype=np.float32)
x1 = Variable(features1)
x2 = Variable(features2)

y1, y2 = model(x1, x2)

g = c.build_computational_graph([y1, y2])
with open('graph.dot', 'w') as o:
    o.write(g.dump())

dotファイルの可視化

dotファイルは、Graphvizを使って可視化する。

Graphvizをインストール後、binディレクトリをPATHに追加する。
以下のコマンドでdotをpngに変換する。

dot -Tpng graph.dot -o graph.png
生成されたpngファイル

f:id:TadaoYamaoka:20171025215710p:plain

灰色の横長の8角形が値、青の四角がFunctionを示している。
値は、畳み込みのパラメータはW:、バイアスはb:が頭に付いているので区別できる。

GUIツール

また、pngに変換しなくても、Graphvizに含まれるgveditというGUIツールを使うと、dotファイルを開いて表示できる。
f:id:TadaoYamaoka:20171025220435p:plain


TensorBoardと比べると見やすいとは言えないが、コーディングしたネットワークに誤りがないかの確認にはなると思う。