Python

Numpyのブロードキャストとは

By 2021年9月5日No Comments

この記事では、Python外部ライブラリNumpyの機能である、ブロードキャストについて、何をしているのか、どんなメリットがあるのかについて解説していきます。

Numpyのブロードキャストとは

Numpy配列ndarrayには、ブロードキャストという機能が備わっています。これは、次元や要素数が異なる配列の演算において、実行時に自動的に配列を拡張したり、値を補うことで計算可能にしてくれる機能です。具体的な例を見てみましょう。

まず、配列サイズが2行3列で要素がすべて0のa、1行3列のbの演算をしてみます。

import numpy as np
a = np.zeros((3, 3)) # 引数に配列サイズを渡すことで要素0の配列を作れます
print(a)
# [[0. 0. 0.]
#  [0. 0. 0.]
#  [0. 0. 0.]]

b = np.array([1, 2, 3])
print(b)
# [1 2 3]

print(a + b)
# [[1. 2. 3.]
#  [1. 2. 3.]
#  [1. 2. 3.]]

このように、基本的に、行列の和は同じサイズでないと計算されないのですが、ブロードキャスト機能のおかげで計算することができました。

この機能があることで、同じ要素を繰り返し入力して配列のサイズを合わせてから演算を行わせる、といった余計な手間を省くことができます。

また、一般的にPythonで各計算を記述することで、コード量を多くするよりも、NumPyに処理を任せたほうが、実行速度は速くなる傾向にあります。さらに、メモリの使用量を節約するといったメリットもあります。

ブロードキャストのルール

ここではブロードキャスト機能においてどのような処理、過程をおいて計算がされているのかを見ていきましょう。ブロードキャストのルールを、簡潔にでもいいので覚えておくとコードを読みやすくなったり、書く際にはシンプルかつ高速なプログラムにすることができます。

ルール1. 次元を揃える

次元が低い配列の先頭にサイズ1の配列を加える。

ルール2. 各次元の長さを揃える

各次元において最も要素数が多いものに合わせる。ただし例外あり。

以上の2つのルールに従ってブロードキャストを使うことができます。いくつかの例を示します。

1次元配列x、2次元配列yを例にします。

x = np.array([1])
print(x)
# [1]
print(x.shape)
y = np.zeros((1, 2))
print(y)
# [[0. 0.]]

print(x + y)
# [[1. 1.]]

まず、この例の場合、ルール1によって次元の低い配列であるxの配列サイズが(1)から(1, 1)に変わります。

x: (1) → (1, 1),  x = [1, 1]

この例ではこの際、もとの配列の要素である1がコピーされるのでxは[1, 1]として計算され、[0, 0]と足すことで[1, 1]が出力されます。今回の例はルール1時点で要素数が一致したため、ルール2は適用されませんでした。

先程の3×3の配列a, 1×3の配列bの場合はどうなるか見てみます。

a = np.zeros((3, 3))
print(a)
# [[0. 0. 0.]
#  [0. 0. 0.]
#  [0. 0. 0.]]

b = np.array([1, 2, 3])
print(b)
# [1 2 3]

print(a + b)
# [[1. 2. 3.]
#  [1. 2. 3.]
#  [1. 2. 3.]]

まず、aもbも2次元配列なのでルール1は適用されません。次にルール2によって、要素数の少ないbが(1, 3)から(3, 3)に拡張されます。それに伴って以下のように値がコピーされます。

b: (1, 3) → (3, 3)

b = [[1, 2, 3]

       [1, 2, 3]

       [1, 2, 3]]

このようにして配列のサイズが揃い計算を行うことができます。

最後に次元数、要素数ともに異なる例について見てみましょう。pは3次元配列、qは2次元配列です。

p = np.zeros((2, 3, 4))
print(p)
# [[[0. 0. 0. 0.]
#   [0. 0. 0. 0.]
#   [0. 0. 0. 0.]]
#
#  [[0. 0. 0. 0.]
#   [0. 0. 0. 0.]
#   [0. 0. 0. 0.]]]


q = np.array([[1, 2, 3, 4]])
print(q)
# [[1 2 3 4]]

print(p + q)
# [[[1. 2. 3. 4.]
#   [1. 2. 3. 4.]
#   [1. 2. 3. 4.]]

#  [[1. 2. 3. 4.]
#   [1. 2. 3. 4.]
#   [1. 2. 3. 4.]]]

今回の場合、まず次元数が少ないqがルール1に則って(1, 4)から(1, 1, 4)になります。次にルール2に則って要素数が(2, 3, 4)に拡張されます。それに伴って以下のように値がコピーされます。

q: (1, 4) → (1, 1, 4) → (2, 3, 4)

q = [[[1, 2, 3, 4]

        [1, 2, 3, 4]

        [1, 2, 3, 4]]

       [[1, 2, 3, 4]

        [1, 2, 3, 4]

        [1, 2, 3, 4]]]

ブロードキャストできない場合

ルール2の例外の場合について解説します。次元のサイズが異なっていてどちらの配列のサイズも1でないとブロードキャストできずにエラーとなります。これは次のような配列同士の場合です。

s = np.zeros((2, 3, 4))
print(s)
[[[0. 0. 0. 0.]
  [0. 0. 0. 0.]
  [0. 0. 0. 0.]]

[[0. 0. 0. 0.]
  [0. 0. 0. 0.]
  [0. 0. 0. 0.]]]

t = np.array([[1, 2, 3]])
print(t)
[[1 2 3]]

print(s + t)
ValueError: operands could not be broadcast together with shapes (2,3,4) (1,3)

この場合、sは(2, 3, 4)、tは(1, 3)のサイズで、まずはルール1によってtのサイズが(1, 1, 3)になりますが、ルール2適用時に拡張させる要素数は1でないとブロードキャストすることができず、エラーが出されます。また、これは直感的に考えてもt = [[1, 2, 3]]をどのように拡張させても(2, 3, 4)のサイズに合わせられないことがわかると思います。

掛け算におけるブロードキャスト

掛け算においてもブロードキャスト機能が使われます。* 演算子を使って要素ごとに掛け算をすることができます。

f = np.array([1, 2, 3])
print(f)
# [0 1 2]

print(f.shape)
# (3,)

g = np.array([3])
print(g)
# [3]

print(f * g)
# [3 6 9]

このようにブロードキャスト機能が利用されることで直感的に掛け算を行うことができます。

まとめ

今回は、Numpy配列を演算する場合における機能である、ブロードキャストについて解説しました。ブロードキャストについて理解し、プログラムに落とし込めるようになると、多くのメリットがあるため、ぜひマスターして使いこなせるようにしましょう。

Udemyの動画学習でもPythonを勉強しよう!

「平日の夜の勉強会には時間が間に合わなくて参加できない」「通勤時間のわずかな隙間時間を勉強時間にあてたい」「本ではよく分からないところを動画で理解を深めたい」そんなあなたはUdemyの動画学習がお勧めです!

UdemyのPythonおすすめ33講座レビューリスト
HOSL

Author HOSL

More posts by HOSL