Python
Theano
TensorFlow
Lasagne

TensorFlow tips - LasagneのNINLayerを移植

More than 1 year has passed since last update.

はじめに

(非常にニッチな内容で恐縮ですが.)

Theano + Lasagne で書かれていた,とあるGAN(Generative Adversarial Networks)コードをTensorFlowに移植していたところ,"lasagne.layers.NINLayer" というものに出くわした.Lasagneのドキュメントを見ると,

Network-in-network layer. Like DenseLayer, but broadcasting across all trailing dimensions beyond the 2nd. This results in a convolution operation with filter size 1 on all trailing dimensions. Any number of trailing dimensions is supported, so NINLayer can be used to implement 1D, 2D, 3D, ... convolutions.

「2番めの次元を越える次数に対して,"broadcasting" する密結合レイヤーみたいなレイヤー」とのこと.Network-in-network layerという名前から,最初,身構えたが,とても簡単にTensorFlowで書けたので紹介したい.

(プログラミング環境:
- Python: 3.5.2
- Theano: 0.9.0
- Lasagne: 0.2.dev1
- TensorFlow: 1.2.0 )

Theano + Lasagne の動き

ドキュメントの説明で動作がはっきりしなかったので,小さいデータでテストを行った.

import numpy as np
import theano
import theano.tensor as T

import lasagne
from lasagne.layers import InputLayer, NINLayer

# variables
x = T.tensor4('x')
y = T.matrix('y')

# shared variable
w_np= np.array([[1., 2., 3], [2., 2., 2.]], dtype=np.float32)
ws = theano.shared(w_np, name='w')

# layers
l_in = InputLayer((1, 2, 5, 5))  # image size = 3 
l1 = NINLayer(l_in, num_units=3, W=ws, b=None,
              nonlinearity=None)
l_in.input_var = x
y = lasagne.layers.get_output(l1)

# theano function
mylayer = theano.function(
    inputs=[x],
    outputs=y,
    allow_input_downcast=True
)

x_np = np.ones([1, 2, 5, 5]) * 0.1
y_np = mylayer(x_np)
y_np = np.asarray(y_np)

print('x_np.shape = ', x_np.shape)
print('x_np = \n', x_np)
print(' ')
print('y_np.shape = ', y_np.shape)
print('y_np = \n', y_np)

以下が,計算結果の出力.

x_np.shape =  (1, 2, 5, 5)
x_np = 
 [[[[ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]]

  [[ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]
   [ 0.1  0.1  0.1  0.1  0.1]]]]

y_np.shape =  (1, 3, 5, 5)
y_np = 
 [[[[ 0.30000001  0.30000001  0.30000001  0.30000001  0.30000001]
   [ 0.30000001  0.30000001  0.30000001  0.30000001  0.30000001]
   [ 0.30000001  0.30000001  0.30000001  0.30000001  0.30000001]
   [ 0.30000001  0.30000001  0.30000001  0.30000001  0.30000001]
   [ 0.30000001  0.30000001  0.30000001  0.30000001  0.30000001]]

  [[ 0.40000001  0.40000001  0.40000001  0.40000001  0.40000001]
   [ 0.40000001  0.40000001  0.40000001  0.40000001  0.40000001]
   [ 0.40000001  0.40000001  0.40000001  0.40000001  0.40000001]
   [ 0.40000001  0.40000001  0.40000001  0.40000001  0.40000001]
   [ 0.40000001  0.40000001  0.40000001  0.40000001  0.40000001]]

  [[ 0.5         0.5         0.5         0.5         0.5       ]
   [ 0.5         0.5         0.5         0.5         0.5       ]
   [ 0.5         0.5         0.5         0.5         0.5       ]
   [ 0.5         0.5         0.5         0.5         0.5       ]
   [ 0.5         0.5         0.5         0.5         0.5       ]]]]

上の入出力データをながめれば,レイヤーの機能が把握できるかと思う.2次元に展開される各ピクセルデータを入力し,チャンネル方向(depth-wise) に重みを,上の例では,w= [[1., 2., 3], [2., 2., 2.]] を乗ずるという計算である.入力を2チャンネル,重みを [2 x 3] の形としたので,出力は 3チャンネルとなる.また上コードでは,入力データは all... 0.1 だが,実際には,画像データなので,任意の値が入ってくる.

TensorFlow に移植したコード

"Theano" では,画像データは,"channel-1st" (実際には,バッチ処理の都合上,2番めにチャンネルが来る)で扱い,"TensorFlow” では "channel-last" である点に注意して,移植してみた.

# NIN (network in network) like function
def lasagne_nin_like(x, w):
    '''
      args.:
        input shape:    [None, pixel, pixel, input_channel]
        output shape:   [None, pixel, pixel, output_channel]
    '''
    input_ch = tf.shape(x)[-1]      # eq. 2
    output_ch = tf.shape(w)[-1]     # eq. 3
    net = tf.reshape(x, [-1, input_ch])
    net = tf.matmul(net, w)
    y = tf.reshape(net, [-1, 5, 5, output_ch])

    return y

y = lasagne_nin_like(x, W)

入力値をチャンネル数だけ残してフラットな形に tf.reshape() し,マトリクス乗算後に,元の形に戻すだけである.予想以上に簡単に移植できた.

TensorFlow コード全体は,以下の通り.

import numpy as np
import tensorflow as tf


# tensorflow placeholders
x = tf.placeholder(tf.float32, [None, 5, 5, 2])


# shared variable
w_np= np.array([[1., 2., 3], [2., 2., 2.]])
W = tf.Variable(w_np, dtype=tf.float32)

# NIN (network in network) like function
def lasagne_nin_like(x, w):
    '''
      args.:
        input shape:    [None, pixel, pixel, input_channel]
        output shape:   [None, pixel, pixel, output_channel]
    '''
    input_ch = tf.shape(x)[-1]      # eq. 2
    output_ch = tf.shape(w)[-1]     # eq. 3
    net = tf.reshape(x, [-1, input_ch])
    net = tf.matmul(net, w)
    y = tf.reshape(net, [-1, 5, 5, output_ch])

    return y

y = lasagne_nin_like(x, W)

# tensorflow session
init = tf.global_variables_initializer()
sess = tf.InteractiveSession()
sess.run(init)


x_np = np.ones([1, 5, 5, 2], dtype=np.float32) * 0.1
y_np = sess.run(y, feed_dict={x: x_np})

print('x_np.shape = ', x_np.shape)

# output should be transposed to compare theano's result.
# 計算結果比較のため,4Dテンソルの転置 (ch-last -> ch-1st) を行う
y_np = np.transpose(y_np, (0, 3, 1, 2))

print('y_np.shape = ', y_np.shape)
print('y_np = ', y_np)

感想

「案ずるより産むが易し」そのままの状況である.考察として,簡単に移植できた理由は,TensorFlowが "channel-last"の規則に従っているからと考えられる.逆に Theano + Lasagne では,1行でデータを(チャネル基準で)フラット化できない不便さがあったため,lasagne.layers.NINLayer を準備したのかもしれない.

(表題 「NINLayerを移植」は,かなり大げさなタイトルでした...)

参考 Web Site