NumPyのブロードキャストは非常に強力な機能です。だから、なんとなく雰囲気で使うのはもったいない。ルールはシンプルですので、一度だけしっかり理解しましょう。NumPyの使い勝手が格段に向上するはずです。ブロードキャストあってのNumPyです。
0.「ゼロから作るDeep Learning」のポイント整理シリーズ
この記事は「ゼロから作るDeep Learning」のポイント整理シリーズの記事です。シリーズの名前のとおり私の自習用のメモです。書籍では説明されない基礎文法や補足・関連事項などを説明しており、書籍がなくてもわかる内容になっています。
1.用語の整理
以下、配列に関する用語をまとめた図です。これらの用語を使って説明します。

2.形状が同じ配列同士の算術計算
ブロードキャストの説明をする前に、形状が同じ配列同士の算術計算が簡便に記述できるNumPyの機能を説明します。
コード01は、リストx、リストyの同じ位置にある要素の差を求めるコードです。for文を2重の入れ子にし、要素を1つずつ計算してリストzに代入します。ごくごく普通の計算方法です。
#コード01
x = [[0, 1], [2, 3]]
y = [[1, 1], [1, 1]]
z = [[0, 0], [0, 0]]
for i in range(2):
for j in range(2):
z[i][j] = x[i][j] - y[i][j]
print(z)
#出力01
[[-1, 0], [1, 2]]
しかし、リストではコード02のような計算はできません。
#コード02
x = [[0, 1], [2, 3]]
y = [[1, 1], [1, 1]]
z = x - y
print(z)
#出力02
TypeError: unsupported operand type(s) for -: 'list' and 'list'
しかし、NumPyではこれができてしまうのです。これだけでも十分に便利な機能です。
#コード03
import numpy as np
x = np.array([[0, 1], [2, 3]])
y = np.array([[1, 1], [1, 1]])
z = x - y
print(z)
#出力03
[[-1 0]
[ 1 2]]
NumPyに、この機能がなければ以下のような記述になります。
#コード04
import numpy as np
x = np.array([[0, 1], [2, 3]])
y = np.array([[1, 1], [1, 1]])
z = np.zeros((2, 2), dtype = 'int32')
for i in range(2):
for j in range(2):
z[i, j] = x[i, j] - y[i, j]
print(z)
ブロードキャストの機能は上記の機能を更に強力にパワーアップする機能です。本来は同じ形状同士でしか計算できない機能を拡張し、自動で配列の形状をそろえる機能なのです。
3.ブロードキャストのルール
ブロードキャストとは、自動で同じ形状にそろえる機能ですが、どんな配列同士も形状がそろえられる訳ではありません。
ブロードキャストとは、以下の2つのルールに従って配列の形状をそろえます。このルールを理解していなければ、ブロードキャストの機能を使いこなせません。なお、赤字の部分が注意する点です。
- 次元の長さが1の部分を複写し自動で長さをそろえる
- 配列の前方に自動で長さ1の次元を追加する
言葉だけでは分からないと思いますので、具体例で説明します。
4.次元の長さが1の部分を複写し自動で長さをそろえる
まずは1つめのルールである「次元の長さが1の部分を複写し自動で長さをそろえる」について説明します。次元数が同じですが形状が違う配列の算術演算の例を紹介します。
(1) 形状(4, 2)と形状(1, 2)の配列の和
形状(4, 2)の配列xと形状(1, 2)の配列yの和について考えます。このままでは計算できないため、ブロードキャストの機能が自動で働きます。

xとyは、次元の長さが1の部分を複写し長さをそろえることで形状をそろえることができます。具体的には下図のように[8, 9]を複写し、yの0番目の次元を1→4にします。

コードは以下の通りです。
#コード05
import numpy as np
x = np.array([[0,1],[2,3],[4,5],[6,7]])
y = np.array([[8,9]])
print(x + y)
#出力05
[[ 8 10]
[10 12]
[12 14]
[14 16]]
(2) 形状(4, 2)と形状(3, 2)の配列の和
形状(4, 2)の配列xと形状(3, 2)の配列yの和ではブロードキャストの機能は働きません。なぜなら、次元の長さが1の部分がないからです。
実際、yの要素を複写して次元をそろえようとしても次元の長さが1ではないため難しいことがわかります。

(3) 形状(4, 2)と形状(2, 2)の配列の和
次に形状(4, 2)の配列xと形状(2, 2)の配列yの和を考えてみましょう。

今度は、下図のように調整可能なようにも思えますが、機能を複雑にしないためにも、ブロードキャストでは長さが1の部分しか長さを自動調整できません。

(4) 形状(4, 2)と形状(4, 1)の配列の和
次に形状(4, 2)の配列xと形状(4, 1)の配列yの和を考えてみましょう。これは、yの1番目の次元の長さが1であるため、これを2にすれば形状をそろえられます。

下の図のように、同じ値を複写することによって形状をそろえます。

(5) 形状(4, 2)と形状(1, 4)の配列の和
注意しなければならないのがこのケースです。形状(4, 2)の配列xと形状(1, 4)は形状を書き出して比較すれば形状をそろえられないことがわかりますが、うっかりしないようにしましょう。
配列xと配列yの0番目の次元はそろえられますが、1番目の次元は2と4なのでそろえられません。

5.配列の前方に自動で長さ1の次元を追加する
ブロードキャストの2つめのルールとして、配列の前方に自動で次元を追加する機能があります。後方には追加できないことに注意しましょう。
(1) 形状(4, 2)と形状(2, )の配列の和
形状(4, 2)の配列xと形状(2, )の配列yの和では、次元数が異なるのでyの次元を増やす必要があります。

具体的には、まず配列の前方に長さ1の次元を追加します。次元の追加では要素は複写されません。
長さ1の次元を前方に追加したあとに、次元の長さを1→4にすることで、配列yの形状を(4, 2)にそろえることができます。

上記のような手順で配列の形状をそろえることによって、計算が可能になります。

(2) 形状(4, 2)と形状(4, )の配列の和
形状(4, 2)の配列xと形状(4, )の配列yの和は、次元をそろえることができません。配列yの前方に長さ1の次元を追加しても、形状(4, 2)と形状(1, 4)は1番目の次元の長さが2, 4なので形状をそろえられません。

なお、下の図のように後方に次元を追加することはできません。

(3) 形状(4, 2)の配列とスカラーの和
形状(4, 2)の配列とスカラーの和も形状をそろえることができます。スカラーは配列ではなく、ただの値のことです。

まず、スカラーは次元がない状態ですので、スカラーに次元を追加し形状(1, )の配列にします。
あとは同様に前方に長さ1の次元を追加し、次元の長さが1の部分を複写することくりかえし、形状をそろえることが可能です。

つまり、スカラーを加える計算は、配列の全ての要素に同じ値を加える結果になります。

(4) 形状(2, 2, 4)と形状(1, )の配列の和
ここまでの説明で、どのようなブロードキャストでも理解することが可能です。
それでは練習として、形状(4, 2)の配列xと形状(1, )の配列yの和を考えてみましょう。

形状をそろえる手順は下図のように複雑になりますが、形状(1, )の配列を形状(2, 2, 3)の配列にそろえることができます。

なお、実際には下図のような手順で考える方が分かりやすいと思います。
まず、前方に長さ1の次元を2つ追加し、次に各次元の長さをそろえます。

計算結果は以下の通りです。

6.練習問題
それでは、練習問題を解いてブロードキャストに慣れましょう。問題によってはブロードキャストできず、エラーになるものもあります。
(1) 問題1
コード06の出力結果を答えよ。
#コード06
import numpy as np
x = np.array([[0], [1]])
y = np.array([[2, 3]])
print(x + y)

解答の表示・非表示の切り替え
※ブラウザによっては最初から表示されてしまいます。(Google Chrome推奨)
以下、出力結果です。
#出力06
[[2 3]
[3 4]]
下図のように、配列x、配列yの次元の長さが1の部分は、互いに長さをそろえることが可能です。

(2) 問題2
コード07の出力結果を答えよ。
#コード07
import numpy as np
x = np.array([[0, 1, 2], [3, 4, 5]])
y = np.array([[6, 7]])
print(x + y)
解答の表示・非表示の切り替え
※ブラウザによっては最初から表示されてしまいます。(Google Chrome推奨)
形状(2, 3)と形状(1, 2)は1番目の次元の長さが3と2なので形状をそろえることができません。
#出力07
ValueError: operands could not be broadcast together with shapes (2,3) (1,2)

(3) 問題3
コード08の出力結果を答えよ。
#コード08
import numpy as np
x = np.array([[0, 1, 2], [3, 4, 5]])
y = np.array([6, 7])
print(x + y)
解答の表示・非表示の切り替え
※ブラウザによっては最初から表示されてしまいます。(Google Chrome推奨)
形状(2, 3)と形状(2,)では、配列yの形状を(2, )から(1, 2)にしたとしても、問題2と同様に1番目の次元の長さが3と2なので形状をそろえることができません。
#出力08
ValueError: operands could not be broadcast together with shapes (2,3) (2,)

(4) 問題4
コード09の出力結果を答えよ。
#コード09
import numpy as np
x = np.arange(2).reshape((2,1)) #[[0] [1]]
y = np.arange(3) #[0 1 2]
z = np.arange(6).reshape((3, 2, 1)) #[[[0] [1]] [[2] [3]] [[4] [5]]]
print(x + y + z)
解答の表示・非表示の切り替え
※ブラウザによっては最初から表示されてしまいます。(Google Chrome推奨)
3つの配列のブロードキャストも同様です。
#出力09
[[[0 1 2]
[2 3 4]]
[[2 3 4]
[4 5 6]]
[[4 5 6]
[6 7 8]]]
前方に長さ1の次元を追加したり、長さが1の次元を複写して長くすることでブロードキャストが可能です。

(5) 問題5
コード10の出力結果を答えよ。
#コード10
import numpy as np
x = np.arange(2).reshape((2,1)) #[[0] [1]]
y = np.arange(3) #[0 1 2]
z = np.arange(12).reshape((3, 2, 2)) #[[[0 1] [2 3]] [[4 5] [6 7]] [[8 9] [10 11]]]
print(x + y + z)
解答の表示・非表示の切り替え
※ブラウザによっては最初から表示されてしまいます。(Google Chrome推奨)
今度はブロードキャストができず、エラーになります。
#コード10
ValueError: operands could not be broadcast together with shapes (2,3) (3,2,2)
配列の次元数をそろえたとしても、2番目の次元の長さが1, 3, 2となるため、3と2をそろえることができません。

7.もう一度、ブロードキャストのルールを見てみよう
もういちど、ブロードキャストの2つのルールを見てみましょう。今度はこのルールの意味が理解できるのではないでしょうか。
- 次元の長さが1の部分を複写し自動で長さをそろえる
- 配列の前方に自動で長さ1の次元を追加する
私が実際に購入した教材のご紹介
以下、私が実際に購入したPythonの教材をまとめてみました。 Pythonを学習する上で、少しでもお役に立つことができればうれしいです。
・Python♪私が購入したPythonの書籍のレビュー
・UdemyのPythonの動画講座を書籍を買う感覚で購入してみた
その他
液晶ペンタブレットを買いました
(1) モバイルディスプレイを買うつもりだったのに激安ペンタブレット購入
以下、私が光回線を導入した時の記事一覧です。
(1) 2020年「光回線は値段で選ぶ」では後悔する ←宅内工事の状況も説明しています。
(2) NURO光の開通までWiFiルーターを格安レンタルできる
(3) NURO光の屋外工事の状況をご紹介。その日に開通!
(4) 光回線開通!実測するとNURO光はやっぱり速かった
(5) ネット上のNURO光紹介特典は個人情報がもれないの?