NumPy

【NumPy】ブロードキャスト(broadcast)の基本

【NumPy】ブロードキャスト(broadcast)の基本

NumPyの特徴的な機能であるブロードキャスト(broadcast)の基本的な考え方を紹介します。

ブロードキャスト(broadcast)の基本

NumPyの特徴的な機能としてブロードキャスト(broadcast)という機能があります。ブロードキャストは、NumPyが高速にベクトル演算するユニバーサル関数(ufunc)の土台となる機能です。ユニバーサル関数は、例えばnp.addnp.multiplyのような関数のことです。

ユニバーサル関数(ufunc)については「ユニバーサル関数(ufuncs)を用いた配列(ndarray)の計算」を参考にしてください。

この記事では、ブロードキャストの基本について説明します。

ブロードキャスト(broadcast)の基本的な考え方

ブロードキャストの基本的な考え方について、簡単な例を使いつつ見ていきましょう。

ユニバーサル関数のベクトル計算

ユニバーサル関数(ufunc)のベクトル計算のイメージを説明します。以下は同じサイズのNumPyの配列(ndarray)を足し算するプログラムです。

import numpy as np

x1 = np.arange(1, 6)
print(f'x1 = {x1}')
x2 = np.arange(6, 11)
print(f'x2 = {x2}')

print(f'x1 + x2 = {x1 + x2}')
【実行結果】
x1 = [1 2 3 4 5]
x2 = [ 6  7  8  9 10]
x1 + x2 = [ 7  9 11 13 15]

ユニバーサル関数では、ベクトル演算をしますので各要素ごと演算が行われます。イメージ図にしてみると以下のようになります。今回は足し算(+)の例ですが、他の演算でも考え方は同じです。

ユニバーサル関数(ufunc) ベクトル計算

ブロードキャスト例(1次元)

上記で説明した例は、配列形状が一致している場合でした。配列の形状(shape)が異なる場合についてもみてみましょう。以下は配列と数値を足しているプログラムです。このような場合に、ブロードキャストの機能が動作します。

import numpy as np

x1 = np.arange(1, 6)
print(f'x1 = {x1}')

print(f'x1 + 5 = {x1 + 5}')
【実行結果】
x1 = [1 2 3 4 5]
x1 + 5 = [ 6  7  8  9 10]

上記結果を見るとx1に各要素にそれぞれ5が足し算されていることが分かります。この時、NumPyは5という数字を拡張して、x1と同じ形状になる配列にしてからベクトル計算をしています。イメージにすると以下のようになります。

このようにNumPyが内部的に形状を拡張して対応することをブロードキャストと言います。

ブロードキャスト(broadcast)1次元

ブロードキャスト例(2次元:行方向)

2次元配列の場合も見てみましょう。以下の例は、np.eye5×5の単位行列を作成しています。足し算するのは、1次元の1~5までの数字が入った配列です。

import numpy as np

x1 = np.eye(5)
print(f'x1 = \n{x1}')
x2 = np.arange(1, 6)
print(f'x2 = {x2}')

print(f'x1 + x2 = \n{x1 + x2}')
【実行結果】
x1 = 
[[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]
x2 = [1 2 3 4 5]
x1 + x2 = 
[[2. 2. 3. 4. 5.]
 [1. 3. 3. 4. 5.]
 [1. 2. 4. 4. 5.]
 [1. 2. 3. 5. 5.]
 [1. 2. 3. 4. 6.]]

上記例では、行方向のサイズが異なっているので、x2の配列を行方向に拡張して、x1と同じ形状にして計算がされます。イメージは、以下のようになります。

ブロードキャスト(broadcast)2次元 行方向

ブロードキャスト例(2次元:列方向)

列方向に拡張される例も見てみましょう。

import numpy as np

x1 = np.eye(5)
print(f'x1 = \n{x1}')
x2 = np.arange(1, 6)[:, np.newaxis]
print(f'x2 = {x2}')

print(f'x1 + x2 = \n{x1 + x2}')
【実行結果】
x1 = 
[[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]
x2 = [[1]
 [2]
 [3]
 [4]
 [5]]
x1 + x2 = 
[[2. 1. 1. 1. 1.]
 [2. 3. 2. 2. 2.]
 [3. 3. 4. 3. 3.]
 [4. 4. 4. 5. 4.]
 [5. 5. 5. 5. 6.]]

上記の例では、列方向のサイズが異なっているので、x2の配列を列方向に拡張して、x1と同じ形状にして計算がされます。イメージは、以下のようになります。

ブロードキャスト(broadcast)2次元 列方向

ブロードキャスト(broadcast)のルール

これまで見てきた例は非常にシンプルな例です。

より詳細なブロードキャストの具体的なルールに興味がある方は、公式ドキュメントのGeneral Broadcasting Rulesに記載がありますので参考にしてください。

NumPyのユニバーサル関数(ufunc)ではブロードキャストという機能で内部的に形状を拡張して対応していることを意識してみてください。

Note

ブロードキャストの公式ドキュメントはこちらを参照してください。

まとめ

NumPyの特徴的な機能であるブロードキャスト(broadcast)の基本的な考え方を紹介しました。

ブロードキャストは、NumPyのnp.addnp.multiplyといったユニバーサル関数(ufunc)が、内部的に形状を拡張して演算に対応することを言います。

ブロードキャストはNumPyの高速演算を支える重要な考え方の一つですので、是非基本的な内容を理解してもらえたらと思います。