TensorFlow tips - LasagneのNINLayerを移植

## はじめに
（非常にニッチな内容で恐縮ですが．）

**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 の動き
ドキュメントの説明で動作がはっきりしなかったので，小さいデータでテストを行った．

```py
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)
```

以下が，計算結果の出力．

```text
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" である点に注意して，移植してみた．


```py
# 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 コード全体は，以下の通り．

```py
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
- Lasagne documentation  
http://lasagne.readthedocs.io/en/latest/index.html
- TensorFlow documentation  
https://www.tensorflow.org/