numpyの配列の四則演算（２）ブロードキャスト

以前、「numpyの行列の四則演算」を書きました。２次元までについて書きましたが、多次元の場合も考えます。

# 演算の可否
numpyで配列の次元や要素数の異なる配列同士の四則演算の可否について考えます。
以下の規則で考えます。

（１）次元が異なる場合は、次元が小さい方の前に要素数として1を埋め次元を一致させます。
（２）要素数が1の場合、反対側の同じ次元の要素数とする。両方とも要素数が1の場合はそのまま。
（３）両方の配列の各要素数が一致すれば演算可能

具体的に見ていきましょう。

```python
import numpy as np
```

３次元の(2,3,4)の配列を考えます。

```python
X234 = np.array([[[ 1, 2, 3, 4],[ 5, 6, 7, 8],[ 9,10,11,12]],
                 [[13,14,15,16],[17,18,19,20],[21,22,23,24]]])
print("X234 = ")
print(X234)
print("X234.shape = " + str(X234.shape))
```

```
X234 = 
[[[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]

 [[13 14 15 16]
  [17 18 19 20]
  [21 22 23 24]]]
X234.shape = (2, 3, 4)
```

X234との演算を考えます。

* 1次元

```python
Y1 = np.array([1])
print("Y1=")
print(Y1)
print("Y1.shape=" + str(Y1.shape))
```

```
Y1=
[1]
Y1.shape=(1,)
```

最初に足りない次元数分1で埋めます。
(1) → (1,1,1)
1の部分を相手の各次元の要素数に合わせます。
(1,1,1) → (2,3,4)

今後以下のように表します。
元の要素 → 1を埋める → 1を変換
(1) → (1,1,1) → (2,3,4)
演算する行列と一致するため演算が可能です。

* 2次元

２次元配列の各組合せを考えます。
変換後に(2,3,4)となれば演算可能です。

元の要素 → 1を埋める → 1を変換
(1,1) →　(1,1,1) → (2,3,4) OK
(1,2) →　(1,1,2) → (2,3,2) NG
(1,3) →　(1,1,3) → (2,3,3) NG
(1,4) →　(1,1,4) → (2,3,4) OK

(2,1) →　(1,2,1) → (2,2,4) NG
(2,2) →　(1,2,2) → (2,2,2) NG
(2,3) →　(1,2,3) → (2,2,3) NG
(2,4) →　(1,2,4) → (2,2,4) NG

(3,1) →　(1,3,1) → (2,3,4) OK
(3,2) →　(1,3,2) → (2,3,2) NG
(3,3) →　(1,3,3) → (2,3,3) NG
(3,4) →　(1,3,4) → (2,3,4) OK

(4,1) →　(1,4,1) → (2,4,4) NG
(4,2) →　(1,4,2) → (2,4,2) NG
(4,3) →　(1,4,3) → (2,4,3) NG
(4,4) →　(1,4,4) → (2,4,4) NG

どうですか？簡単でしょう。順を追って考えれば難しくありません。
(2,3,4)と演算可能な２次元の配列は、(1,1)、(1,4)、(3,1)、(3,4)です。

例です。
演算可能な場合

```python
Y34 = np.array([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print("Y34=")
print(Y34)
print("Y34.shape=" + str(Y34.shape))
X234 + Y34
```

```
Y34=
[[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
Y34.shape=(3, 4)
array([[[ 2,  4,  6,  8],
        [10, 12, 14, 16],
        [18, 20, 22, 24]],

       [[14, 16, 18, 20],
        [22, 24, 26, 28],
        [30, 32, 34, 36]]])
```

演算不可の場合-エラーになります。

```python
Y33 = np.array([[1,2,3],[4,5,6],[7,8,9]])
print("Y33=")
print(Y33)
print("Y33.shape=" + str(Y33.shape))
X234 + Y33
```

```
Y33=
[[1 2 3]
 [4 5 6]
 [7 8 9]]
Y33.shape=(3, 3)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-81-d7943ce73006> in <module>()
      3 print(Y33)
      4 print("Y33.shape=" + str(Y33.shape))
----> 5 X234 + Y33

ValueError: operands could not be broadcast together with shapes (2,3,4) (3,3) 
```

* 3次元

3次元は、すべての組み合わせは書ききれないため演算可能な例を示します。
次元数が同じため、1を埋める必要はありません。

元の要素 → 1を変換
(1,1,1) → (2,3,4)
(1,1,4) → (2,3,4)
(1,3,1) → (2,3,4)
(1,3,4) → (2,3,4)
(2,1,1) → (2,3,4)
(2,1,4) → (2,3,4)
(2,3,1) → (2,3,4)
(2,3,4) → (2,3,4)

簡単に言えば、完全に一致するか、どこかの要素数が1の場合です。

# 演算結果
次元数や要素数が異なる場合、どのように補完されるか考えてみます。

* 1次元目の補完

(1,3,4)の場合は、1次元目が補完され(2,3,4)として計算されます。
足し算した結果を確認します。

```python
Y134 = np.array([[[ 1, 2, 3, 4],[ 5, 6, 7, 8],[ 9,10,11,12]]])
print("Y134 = ")
print(Y134)
print("Y134.shape=" + str(Y134.shape))
X234 + Y134
```

```
Y134 = 
[[[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]]
Y134.shape=(1, 3, 4)
array([[[ 2,  4,  6,  8],
        [10, 12, 14, 16],
        [18, 20, 22, 24]],

       [[14, 16, 18, 20],
        [22, 24, 26, 28],
        [30, 32, 34, 36]]])
```

逆にしても演算可能です。（当然ですが、引き算と割り算は逆にすると結果が異なります。）

```python
Y134 + X234
```

```
array([[[ 2,  4,  6,  8],
        [10, 12, 14, 16],
        [18, 20, 22, 24]],

       [[14, 16, 18, 20],
        [22, 24, 26, 28],
        [30, 32, 34, 36]]])
```

どのように変換されたでしょうか？
shapeが(1,3,4)の場合、１次元目を2にする必要があります。1次元目を繰り返します。
確かめてみましょう。

```python
Y1x234 = np.array([[[ 1, 2, 3, 4],[ 5, 6, 7, 8],[ 9,10,11,12]],
                   [[ 1, 2, 3, 4],[ 5, 6, 7, 8],[ 9,10,11,12]]])
print("Y1x234=")
print(Y1x234)
print("Y1x234.shape=" + str(Y1x234.shape))
X234 + Y1x234
```

```
Y1x234=
[[[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]

 [[ 1  2  3  4]
  [ 5  6  7  8]
  [ 9 10 11 12]]]
Y1x234.shape=(2, 3, 4)
array([[[ 2,  4,  6,  8],
        [10, 12, 14, 16],
        [18, 20, 22, 24]],

       [[14, 16, 18, 20],
        [22, 24, 26, 28],
        [30, 32, 34, 36]]])
```

同じ結果になりました。

* 2次元目の補完

(2,1,4)の場合は、2次元目が補完され(2,3,4)として計算されます。
足し算した結果を確認します。

```python
Y214 = np.array([[[ 1, 2, 3, 4]],
                 [[ 5, 6, 7, 8]]])
print("Y214 = ")
print(Y214)
print("Y214.shape=" + str(Y214.shape))
X234 + Y214
```

```
Y214 = 
[[[1 2 3 4]]

 [[5 6 7 8]]]
Y214.shape=(2, 1, 4)
array([[[ 2,  4,  6,  8],
        [ 6,  8, 10, 12],
        [10, 12, 14, 16]],

       [[18, 20, 22, 24],
        [22, 24, 26, 28],
        [26, 28, 30, 32]]])
```

どのように変換されたでしょうか？
shapeが(2,1,4)の場合、2次元目を3にする必要があります。2次元目を3回繰り返します。
確かめてみましょう。

```python
Y21x34 = np.array([[[ 1, 2, 3, 4],[ 1, 2, 3, 4],[ 1, 2, 3, 4]],
                   [[ 5, 6, 7, 8],[ 5, 6, 7, 8],[ 5, 6, 7, 8]]])
print("Y21x34 = ")
print(Y21x34)
print("Y21x34.shape=" + str(Y21x34.shape))
X234 + Y21x34
```

```
Y21x34 = 
[[[1 2 3 4]
  [1 2 3 4]
  [1 2 3 4]]

 [[5 6 7 8]
  [5 6 7 8]
  [5 6 7 8]]]
Y21x34.shape=(2, 3, 4)
array([[[ 2,  4,  6,  8],
        [ 6,  8, 10, 12],
        [10, 12, 14, 16]],

       [[18, 20, 22, 24],
        [22, 24, 26, 28],
        [26, 28, 30, 32]]])
```

同じ結果になりました。

* 3次元目の補完

(2,3,1)の場合は、3次元目が補完され(2,3,4)として計算されます。
足し算した結果を確認します。

```python
Y231 = np.array([[[ 1],[ 2],[ 3]],
                 [[ 4],[ 5],[ 6]]])
print("Y231 = ")
print(Y231)
print("Y231.shape=" + str(Y231.shape))
X234 + Y231
```

```
Y231 = 
[[[1]
  [2]
  [3]]

 [[4]
  [5]
  [6]]]
Y231.shape=(2, 3, 1)
array([[[ 2,  3,  4,  5],
        [ 7,  8,  9, 10],
        [12, 13, 14, 15]],

       [[17, 18, 19, 20],
        [22, 23, 24, 25],
        [27, 28, 29, 30]]])
```

どのように変換されたでしょうか？
shapeが(2,3,1)の場合、3次元目を4にする必要があります。3次元目を4回繰り返します。
確かめてみましょう。

```python
Y231x4 = np.array([[[ 1, 1, 1, 1],[ 2, 2, 2, 2],[ 3, 3, 3, 3]],
                   [[ 4, 4, 4, 4],[ 5, 5, 5, 5],[ 6, 6, 6, 6]]])
print("Y231x4=")
print(Y231x4)
print("Y231x4.shape=" + str(Y231x4.shape))
X234 + Y231x4
```

```
Y231x4=
[[[1 1 1 1]
  [2 2 2 2]
  [3 3 3 3]]

 [[4 4 4 4]
  [5 5 5 5]
  [6 6 6 6]]]
Y231x4.shape=(2, 3, 4)
array([[[ 2,  3,  4,  5],
        [ 7,  8,  9, 10],
        [12, 13, 14, 15]],

       [[17, 18, 19, 20],
        [22, 23, 24, 25],
        [27, 28, 29, 30]]])
```

やはり、同じ結果になりました。

要素数1の部分を拡大すればよいわけです。

# その他
3次元の場合を考えましたが、2次元の場合、4次元以上の場合も同様です。
また、利用することはまれかと思われますが、両方に1があっても同じです。

```python
Y231 = np.array([[[ 1],[ 2],[ 3]],
                 [[ 4],[ 5],[ 6]]])
print("Y231 = ")
print(Y231)
print("Y231.shape=" + str(Y231.shape))
```

```
Y231 = 
[[[1]
  [2]
  [3]]

 [[4]
  [5]
  [6]]]
Y231.shape=(2, 3, 1)
```

```python
Y214 = np.array([[[ 1, 2, 3, 4]],
                 [[ 5, 6, 7, 8]]])
print("Y214 = ")
print(Y214)
print("Y214.shape=" + str(Y214.shape))
```

```
Y214 = 
[[[1 2 3 4]]

 [[5 6 7 8]]]
Y214.shape=(2, 1, 4)
```

```python
Y231 + Y214
```

```
array([[[ 2,  3,  4,  5],
        [ 3,  4,  5,  6],
        [ 4,  5,  6,  7]],

       [[ 9, 10, 11, 12],
        [10, 11, 12, 13],
        [11, 12, 13, 14]]])
```

# まとめ
法則を再掲します。

（１）次元が異なる場合は、次元が小さい方の前に要素数として1を埋め次元を一致させます。
（２）要素数が1の場合、反対側の同じ次元の要素数とする。両方とも要素数が1の場合はそのまま。
（３）両方の配列の各要素数が一致すれば演算可能

例えば、画像の場合、(データ,高さ,幅,チャネル)の4次元とします。
(高さ,幅,チャネル)の3次元であれば演算可能です。チャネルを串刺しで演算する場合(高さ,幅)では演算できないため、(高さ,幅,1)とします。

落ち着いて考えると何も難しいことはありません。