6.10. ブロードキャスト

二項演算子が形状が完全には一致しない2つの配列を受け取ったとき、numpy はエラーを送出しません。代わりにブロードキャストします。ブロードキャストは、2つの形状に互換性があるかどうか、そして互換性がある場合に小さい方をどのように仮想的に引き伸ばして大きい方に合わせるかを決定する一連の小さなルールです。

6.10.1. ルール

2つのオペランドが形状 AB を持つとき、numpy は2つのステップでそれらを処理します。

  1. ランクを合わせる。 一方のオペランドが他方より軸の数が少ない場合、numpy は両方の形状の軸数が同じになるまで、その形状の先頭にサイズ1の軸を仮想的にパディングします。形状 (3,) の1次元オペランドが形状 (2, 3) の2次元オペランドと組み合わされると、(2, 3) に対して (1, 3) になります。

  2. 各軸をチェックする。 長さの等しくなった形状を軸ごとに見ていき、各サイズのペアは次の2つの条件のいずれかを満たさなければなりません。サイズが等しいか、または一方が1であることです。サイズ1の軸は、演算のために他方のサイズに仮想的に引き伸ばされます。(2, 3) に対するペア (1, 3) は互換性があります。最初の軸は1(2に引き伸ばされる)を持ち、2番目の軸は一致する(3 == 3)ためです。結果の形状は (2, 3) になります。

いずれかの軸のペアがどちらの条件も満たさない場合、形状には互換性がなく、演算子は ValueError を送出します。

6.10.2.

スカラー対任意の配列。 スカラーは形状 (1,) のように振る舞い、あらゆるものに引き伸ばされます:

a = np.array([[1, 2, 3], [4, 5, 6]], dtype=np.float)
a + 10                 # (2, 3) + scalar -> (2, 3)

2次元行列に対する1次元ベクトル。 ルール1は (3,)(1, 3) にするためにサイズ1の軸を先頭に追加します。ルール2はその行を a の各列に沿って下に引き伸ばします:

row = np.array([100, 200, 300], dtype=np.float)
a + row                # (2, 3) + (3,) -> (2, 3)

長さの等しい2つの1次元配列は要素ごとに加算されます -- ブロードキャストは不要です:

np.arange(4) + np.arange(4)

行ベクトルに対する列ベクトルは2次元の「外側」形状を生成します。(4, 1)(3,) の組み合わせは、ランクの先頭追加後に (1, 3) に対する (4, 1) となり、ルール2が各オペランドをそのサイズ1の軸に沿って引き伸ばします:

x = np.array([1, 2, 3, 4]).reshape((4, 1))     # column
y = np.array([10, 20, 30])                      # row
x + y                                           # (4, 3) matrix

同じ形状ルールが、arctan2() を含む任意の2引数 ufunc に適用されます:

np.arctan2(y, 1.0)
np.arctan2(y, x)

6.10.3. ブロードキャストが割り当てないもの

引き伸ばしは仮想的です。numpy は両方のオペランドを一緒に走査し、小さい方をコピーするのではなくブロードキャスト軸に沿って再読み込みします。短い方の配列のデータがメモリ内で複製されることは決してありません。

メモリにとって重要なのは出力配列のサイズです。a + rowa の形状の出力を割り当てるのであって、a の形状と row の形状を足したものではありません。それでも、長いブロードキャストの連鎖は大きな中間結果を生成することがあります。

6.10.4. ブロードキャストがうまくいかないとき

典型的な失敗は、どちらにも引き伸ばすサイズ1の軸がなく、サイズが食い違う2つの形状です。例えば (3, 4)(4, 3) です。ルール2は3を4に合わせられないため、numpyValueError を送出します。

より微妙な問題は、ブロードキャストは成功するものの、アプリケーションが意図した方法ではない場合です。(5,)(5, 1) が典型的なケースです。ランクの先頭追加が (5,)(1, 5) に変え、それが (5, 1) に対してブロードキャストされて (5, 5) 行列、つまり2つのベクトルの外側組み合わせを生成します。アプリケーションがおそらく望んでいた長さ5の要素ごとの結果ではありません。疑わしい場合は両側で shape を出力し、reshape()transpose() に手を伸ばす前にルールを一つずつ追ってください。