TensorFlow メソッド、shape をいじる系メモ

TensorFlow で shapeを変えるようなメソッドをメモる。

shape を触る系のメソッド、だいたい複数同時に使うことが多く、いちいちページをいったりきたりして見るのが面倒になる。
メソッド単体がなにかわからない時はぐぐればいいけど、あのメソッドなんだっけって時とか組み合わせてどうなるんだっけってのがややこしい。一体今どんな shape なのかちゃんと把握したい。
あと、もう覚えちゃうことで複雑な shape に対する苦手意識を消し去りたい。

後半にちょっとだけ逆引きもいれた。

雑な部分もあるので間違ってるのがあったらご指摘ください。

# TensorFlow バージョン

`1.5` (多分 `1.8` でも問題ないはず...)

# メソッド

## [`tf.reshape`](https://www.tensorflow.org/api_docs/python/tf/reshape)

```py3
tf.reshape(
    tensor: Tensor,
    shape: Tensor[int32, int64]],
    name: str=None
)
```

`tensor`で与えられた Tensor を、`shape`で与えられた`TensorShape`に変換する。
`tensor`の各 dimension の size の和と、`shape`の合計が一緒じゃないとエラーになる。
`shape`のどれかの要素に`-1`を与えると、 flat な1次元にする。性質上、1つの index にしか指定できない。
`shape`に`[]`を与えると、スカラーにして返す。

**公式Example**

```py3
# tensor 't' is [1, 2, 3, 4, 5, 6, 7, 8, 9]
# tensor 't' has shape [9]
reshape(t, [3, 3]) ==> [[1, 2, 3],
                        [4, 5, 6],
                        [7, 8, 9]]

# tensor 't' is [[[1, 1], [2, 2]],
#                [[3, 3], [4, 4]]]
# tensor 't' has shape [2, 2, 2]
reshape(t, [2, 4]) ==> [[1, 1, 2, 2],
                        [3, 3, 4, 4]]

# tensor 't' is [[[1, 1, 1],
#                 [2, 2, 2]],
#                [[3, 3, 3],
#                 [4, 4, 4]],
#                [[5, 5, 5],
#                 [6, 6, 6]]]
# tensor 't' has shape [3, 2, 3]
# pass '[-1]' to flatten 't'
reshape(t, [-1]) ==> [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6]

# -1 can also be used to infer the shape

# -1 is inferred to be 9:
reshape(t, [2, -1]) ==> [[1, 1, 1, 2, 2, 2, 3, 3, 3],
                         [4, 4, 4, 5, 5, 5, 6, 6, 6]]
# -1 is inferred to be 2:
reshape(t, [-1, 9]) ==> [[1, 1, 1, 2, 2, 2, 3, 3, 3],
                         [4, 4, 4, 5, 5, 5, 6, 6, 6]]
# -1 is inferred to be 3:
reshape(t, [ 2, -1, 3]) ==> [[[1, 1, 1],
                              [2, 2, 2],
                              [3, 3, 3]],
                             [[4, 4, 4],
                              [5, 5, 5],
                              [6, 6, 6]]]

# tensor 't' is [7]
# shape `[]` reshapes to a scalar
reshape(t, []) ==> 7
```

## [`tf.reduce_xxx`](https://www.tensorflow.org/api_docs/python/tf/reduce_sum)

shape 変えるメソッドではなく list の計算系のメソッドだけど結果的に shape が変わる。
いっぱいある。

`reduce_all`, `reduce_any`, `reduce_join`, `reduce_logsumexp`, `reduce_max`
`reduce_mean`, `reduce_min`, `reduce_prod`, `reduce_sum`

`reduce_sum` の例で説明する。見てないけど演算が違うだけで使い方は他も一緒だと思う。

```py3
tf.reduce_sum(
    input_tensor: Tensor,
    axis: int=None,
    keepdims: bool=None,
    name: str=None,
    reduction_indices=None,
    keep_dims=None
)
```

`reduction_indices`は duplicated で`axis`を使う。
`keep_dims`は duplicated で`keepdims`を使う(なんだこの差分)。

`input_tensor`で与えられた Tensor の各成分の和を計算する。
`axis` が与えられた場合、その次元について和を計算し、その次元が `unstack` された Tensor を返す。
`keepdims=True`にすると、元の次元を保持したまま和を計算する。例参照。

 **公式Example** 

```py3
x = tf.constant([[1, 1, 1], [1, 1, 1]])
tf.reduce_sum(x)  # 6
tf.reduce_sum(x, 0)  # [2, 2, 2]
tf.reduce_sum(x, 1)  # [3, 3]
tf.reduce_sum(x, 1, keepdims=True)  # [[3], [3]]
tf.reduce_sum(x, [0, 1])  # 6
```

## [`tf.expand_dims`](https://www.tensorflow.org/api_docs/python/tf/expand_dims)

```py3
tf.expand_dims(
    input: Tensor,
    axis: int,
    name=None,
    dim: int=None
)
```

`input`で与えられた Tensor について、`axis`で与えられた次元に1つ次元を追加する。
`dim`は duplicated で、`axis`を使う。
例えば single element を batch 化するのに使える。画像の例だと、`[height, width, channels]`を`axis=0`に入れることで`[1, height, width, channels]`にするなど。
`tf.tile`と合わせて、「ある index に次元を増やして、その次元についてN個 tile する」みたいに使われることもある。
画像の例だと、 

`[height, width, channels]` -> 
`tf.expand_dims(images, 0)` -> 
`[1, height, width, channels]` -> 
`tf.tile(images, [batch_size, 1, 1, 1])` -> 
`[batch_size, height, width, channels]`

 **公式Example** 

```py3
# 't' is a tensor of shape [2]
tf.shape(tf.expand_dims(t, 0))  # [1, 2]
tf.shape(tf.expand_dims(t, 1))  # [2, 1]
tf.shape(tf.expand_dims(t, -1))  # [2, 1]

# 't2' is a tensor of shape [2, 3, 5]
tf.shape(tf.expand_dims(t2, 0))  # [1, 2, 3, 5]
tf.shape(tf.expand_dims(t2, 2))  # [2, 3, 1, 5]
tf.shape(tf.expand_dims(t2, 3))  # [2, 3, 5, 1]
```

## [`tf.tile`](https://www.tensorflow.org/api_docs/python/tf/tile)

```py3
tf.tile(
    input: Tensor,
    multiples: Tensor[int32, int64],
    name=None
)
```

`input`で与えられた Tensor を、`multiples`で与えられた1次元ベクトル分敷き詰めた Tensor を返す。
`input.dims(i)` に `multiples[i]`が対応する。
上述の通り、`tf.expand_dims`と合わせて使うことも。

batch_size を他から取ってきて同じ数 tile することが多そう。下記は`expand_dims`した後に batch_size 分 tile する例。

```py3
encoder_state = ...  # [batch_size, ...]
new_state = ...  # [1, hidden_unit_num]
new_state = tf.expand_dims(new_state, 0)  # [1, 1, hidden_unit_num]
new_state = tf.tile(new_state, [encoder_state.shape[0], 1, 1])  # [batch_size, 1, hidden_unit_num]
```
 **Example** 

```py3
# 't' is a tensor of [a b c d]
tf.tile(t, [2])
# [a b c d a b c d]

# 't2' is a tensor of shape [2, 3, 5]
tf.shape(tf.tile(t2, [3, 1, 1]))  
# [6, 3, 5]
```

## [`tf.concat`](https://www.tensorflow.org/api_docs/python/tf/concat)

```py3
tf.concat(
    values: Tensor,
    axis: Tensor[int],
    name='concat'
)
```

concat する。
ここらへんの axis と出力の対応がまだ頭の中ですぐにつかない。。

 **公式Example** 

```py3
t1 = [[1, 2, 3], [4, 5, 6]]
t2 = [[7, 8, 9], [10, 11, 12]]
tf.concat([t1, t2], 0)  # [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]
tf.concat([t1, t2], 1)  # [[1, 2, 3, 7, 8, 9], [4, 5, 6, 10, 11, 12]]

# tensor t3 with shape [2, 3]
# tensor t4 with shape [2, 3]
tf.shape(tf.concat([t3, t4], 0))  # [4, 3]
tf.shape(tf.concat([t3, t4], 1))  # [2, 6]

t1 = [[[1, 2], [2, 3]], [[4, 4], [5, 3]]]
t2 = [[[7, 4], [8, 4]], [[2, 10], [15, 11]]]
tf.concat([t1, t2], -1)
[[[ 1,  2,  7,  4],
  [ 2,  3,  8,  4]],

 [[ 4,  4,  2, 10],
  [ 5,  3, 15, 11]]]
```

## [`tf.squeeze`](https://www.tensorflow.org/api_docs/python/tf/squeeze)

```py3
tf.squeeze(
    input: Tensor,
    axis: Optional[List[int]]=None,
    name=None,
    squeeze_dims=None
)
```

size が1の dimension を消す。もしくは、`axis`が指定された場合は、size が 1 の指定された次元を消す。size が 1 でない場合はエラーになる。
`squeeze_dims` はduplicatedで`axis`を使う。

 **公式Example** 

```py3
# 't' is a tensor of shape [1, 2, 1, 3, 1, 1]
tf.shape(tf.squeeze(t))  # [2, 3]

# 't' is a tensor of shape [1, 2, 1, 3, 1, 1]
tf.shape(tf.squeeze(t, [2, 4]))  # [1, 2, 3, 1]
```

## [`tf.split`](https://www.tensorflow.org/api_docs/python/tf/split)

```py3
tf.split(
    value: Tensor,
    num_or_size_splits: Union[Tensor[int], Tensor[List[int]]],
    axis=0,
    num=None,
    name='split'
)
```

`num_or_size_splits` に `int` のスカラーが与えられた場合は、その数で `value` を split する。
`num_or_size_splits` に `List[int]` が与えられた場合は、各次元が対応する `List` の値になるように `value` を split する。
`axis` で指定した次元について行う。
`size_splits` の形から outputs の数が推測できない場合は、 `num` に指定する。

 **公式Example** 

```py3
# 'value' is a tensor with shape [5, 30]
# Split 'value' into 3 tensors with sizes [4, 15, 11] along dimension 1
split0, split1, split2 = tf.split(value, [4, 15, 11], 1)
tf.shape(split0)  # [5, 4]
tf.shape(split1)  # [5, 15]
tf.shape(split2)  # [5, 11]
# Split 'value' into 3 tensors along dimension 1
split0, split1, split2 = tf.split(value, num_or_size_splits=3, axis=1)
tf.shape(split0)  # [5, 10]
```


## [`tf.stack`](https://www.tensorflow.org/api_docs/python/tf/stack)

```py3
tf.stack(
    values: Tensor,
    axis: int=0,
    name: str='stack'
)
```

ランク `R` の Tensor のリストをくっつけて、ランク `(R+1)` の Tensor にする。
axis が与えられた場合は、その次元についてくっつける。デフォは0。例参照。

 **公式Example** 

```py3
x = tf.constant([1, 4])
y = tf.constant([2, 5])
z = tf.constant([3, 6])
tf.stack([x, y, z])  # [[1, 4], [2, 5], [3, 6]] (Pack along first dim.)
tf.stack([x, y, z], axis=1)  # [[1, 2, 3], [4, 5, 6]]
```

## [`tf.unstack`](https://www.tensorflow.org/api_docs/python/tf/unstack)

```py3
tf.unstack(
    value: Tensor,
    num: int=None,
    axis: int=0,
    name: str='unstack'
)
```

`tf.stack` の逆で、ランク `R` の Tensor の指定された次元で分割して、ランク `(R-1)` の Tensor のリストを作る。
`(A, B, C, D)` という Tensor が value で与えられた時、
`axis=0` で unstack すると、 `value[i, :, :, :]` のリスト(それぞれの shape は`(B, C, D)`)が返る。
`axis=1`で unstack すると、`value[:, i, :, :]`のリスト(それぞれの shape は`(A, C, D)`)が返る。

`tf.shape` と合わせて使うことで、shape の size を取得できる。よく使う。

```py3
# A is a tensor with shape [5, 2, 3]
shape_a = tf.shape(A)  # [5, 2, 3]
tf.unstack(shape_a)
# 5, 2, 3
# 一次元のリストだったので、分けて取れる
```

## [`tf.transpose`](https://www.tensorflow.org/api_docs/python/tf/transpose)

```py3
tf.transpose(
    a,
    perm=None,
    name='transpose',
    conjugate=False
)
```

第二引数の`perm`で元の行列である`a`の順序を与える。

値が移動前の index で、 その値のあるindex が移動後の index .
`perm=[0, 2, 1]` と与えたときの`perm[1]=2`は、`index=1`だったやつが`index=2`に移動するのではなく、`index=2`だったやつが`index=1`に移動する。4次元以上だとややこしいので注意。

**公式Example**

```py3
x = tf.constant([[1, 2, 3], [4, 5, 6]])
tf.transpose(x)  # [[1, 4]
                 #  [2, 5]
                 #  [3, 6]]

# Equivalently
tf.transpose(x, perm=[1, 0])  # [[1, 4]
                              #  [2, 5]
                              #  [3, 6]]

# If x is complex, setting conjugate=True gives the conjugate transpose
x = tf.constant([[1 + 1j, 2 + 2j, 3 + 3j],
                 [4 + 4j, 5 + 5j, 6 + 6j]])
tf.transpose(x, conjugate=True)  # [[1 - 1j, 4 - 4j],
                                 #  [2 - 2j, 5 - 5j],
                                 #  [3 - 3j, 6 - 6j]]

# 'perm' is more useful for n-dimensional tensors, for n > 2
x = tf.constant([[[ 1,  2,  3],
                  [ 4,  5,  6]],
                 [[ 7,  8,  9],
                  [10, 11, 12]]])

# Take the transpose of the matrices in dimension-0
# (this common operation has a shorthand `matrix_transpose`)
tf.transpose(x, perm=[0, 2, 1])  # [[[1,  4],
                                 #   [2,  5],
                                 #   [3,  6]],
                                 #  [[7, 10],
                                 #   [8, 11],
                                 #   [9, 12]]]
```

# 逆引き

## Tensor の shape のサイズを取得する

shape をとるには、`A.get_shape()` もしくは `tf.shape(A)` の2つの方法がある。
前者だと、データ依存せず静的に分かるものしか取れない。例えば placeholder で `None` を指定していると取れない。
後者は operation なので、`tf.session` で計算すれば取得できる。が、取得したものも Tensor なので `unstack` するとスカラーとして持てる。

```py3
# A is a tensor with shape [5, 2, 3]
shape_a = tf.shape(A)  # [5, 2, 3]
dim1, dim2, dim3 = tf.unstack(shape_a)
# Tensor(5), Tensor(2), Tensor(3) がそれぞれ入る
# shape_a が一次元のTensorだったので、 unstack すると分けて取れる
```

## ある Tensor を新たにN個分複製した一次元大きな Tensor を作る

例えば、 shape が `[10, 16, 128]` の Tensor `A` があるとする。128次元の特徴を16個(channelのように)持つデータが10個(batch_size)あるようなイメージ。
今、 Tensor `B` の shape が `[1, 128]` だとして、これと `A` の各データを内積とったり足したりした上でいろいろ操作して...みたいなことをするとなると、まとめて計算するために `B` を10個分複製して `[10, 1, 128]` にしたい。そういうとき。

まず `tf.expand_dims` して、その次元を `tf.tile` する。

```py3
encoder_state = ...  # [batch_size, max_length, hidden_unit_num]
new_state = ...  # [1, hidden_unit_num]
new_state = tf.expand_dims(new_state, 0)  # [1, 1, hidden_unit_num]
new_state = tf.tile(new_state, [encoder_state.shape[0], 1, 1])  # [batch_size, 1, hidden_unit_num]
```

本記事の `tf.expand_dims` や `tf.tile` にも別の例あり。