7.

NumPy ブロードキャスト完全ガイド

編集
この記事の要点
  • ブロードキャストとは、形状の異なる配列同士で自動的に次元を拡張して演算する仕組み
  • ルール: 末尾の次元から照合し、サイズが「1」または「同じ」なら適合
  • 典型例: (3,) + 5 → 全要素に 5 を加算、(3,1) + (1,3)(3,3) を生成
  • np.newaxis / reshape で次元を追加してブロードキャスト可能化
  • 実体メモリは拡張されないため、大きな配列でもメモリ効率が良い

ブロードキャストの最小例

import numpy as np

# 配列 + スカラー (最も単純なブロードキャスト)
a = np.array([1, 2, 3])
b = a + 5
print(b)    # [6 7 8]
# 内部的に [5,5,5] を作って加算するイメージ
# 実際にはメモリ拡張はしない

# 行ベクトル + 列ベクトル
row = np.array([1, 2, 3])           # shape (3,)
col = np.array([[10], [20], [30]])  # shape (3, 1)

result = row + col
print(result)
# [[11 12 13]
#  [21 22 23]
#  [31 32 33]]
# row が (1, 3) と (3, 1) → 共通の (3, 3) に展開

ブロードキャスト規則

NumPy は次のルールで「2 つの形状を合わせる」ことを試みます:

  1. 形状の末尾の次元から順に比較
  2. 次元数が違う場合、足りない側の先頭に 1 を補う
  3. 各次元について、サイズが 同じどちらかが 1 なら適合
  4. サイズが 1 の側を、もう一方のサイズに「仮想的に」拡張
  5. どちらの条件も満たさない次元があれば ValueError
shape Ashape B結果判定
(3,)スカラー(3,)OK
(3,)(3,1)(3,3)OK
(3,4)(4,)(3,4)OK (末尾 4 が一致、先頭に 1 補う)
(3,4)(3,)エラーNG (末尾 4 と 3 が不一致)
(3,4)(3,1)(3,4)OK (末尾 1 → 4 に展開)
(2,3,4)(4,)(2,3,4)OK
(2,3,4)(3,4)(2,3,4)OK
(2,3,4)(2,1,4)(2,3,4)OK

typical なパターン

パターン1: 各列に異なる値を足す

import numpy as np

a = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])              # (3, 3)

offsets = np.array([10, 20, 30])       # (3,)

# offsets が (1, 3) として全行に適用される
print(a + offsets)
# [[11 22 33]
#  [14 25 36]
#  [17 28 39]]

パターン2: 各行に異なる値を足す

import numpy as np

a = np.array([[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]])              # (3, 3)

row_offsets = np.array([[100],
                        [200],
                        [300]])         # (3, 1)

print(a + row_offsets)
# [[101 102 103]
#  [204 205 206]
#  [307 308 309]]

パターン3: 各行を平均で正規化

import numpy as np

data = np.array([[1.0, 2.0, 3.0],
                 [4.0, 5.0, 6.0]])     # (2, 3)

# 行ごとの平均 (axis=1) を求めると (2,) → keepdims=True で (2, 1) に
mean = data.mean(axis=1, keepdims=True) # (2, 1)
normalized = data - mean
# [[-1.  0.  1.]
#  [-1.  0.  1.]]

パターン4: 外積を計算

import numpy as np

x = np.array([1, 2, 3, 4])               # (4,)
y = np.array([10, 20, 30])               # (3,)

# x を (4, 1)、y を (1, 3) にしてブロードキャスト
outer = x[:, np.newaxis] * y[np.newaxis, :]
# (4, 3)
# [[ 10  20  30]
#  [ 20  40  60]
#  [ 30  60  90]
#  [ 40  80 120]]

# 同等
outer2 = np.outer(x, y)

np.newaxis で次元追加

import numpy as np

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

# 行ベクトル化 (1, 3)
row = a[np.newaxis, :]
# = a.reshape(1, 3)
# = a[None, :]               ← None でも同じ

# 列ベクトル化 (3, 1)
col = a[:, np.newaxis]
# = a.reshape(3, 1)
# = a[:, None]

print(row.shape, col.shape)   # (1, 3) (3, 1)

# よくある: 距離行列の計算
points = np.array([[0, 0], [1, 0], [0, 1]])    # (3, 2)
diff = points[:, np.newaxis, :] - points[np.newaxis, :, :]
# (3, 1, 2) - (1, 3, 2) → (3, 3, 2)
dist = np.sqrt((diff ** 2).sum(axis=-1))        # (3, 3)

ブロードキャストでよくあるエラー

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

原因: (3,4) の末尾 4 と (3,) の末尾 3 が一致せず、かつどちらも 1 でない。

import numpy as np

a = np.zeros((3, 4))
b = np.array([1, 2, 3])     # (3,)

# ❌ a + b → ValueError
# 末尾の次元 4 vs 3 が不一致

# ✅ 解決1: b を列ベクトルに
print(a + b[:, np.newaxis])  # (3, 4)

# ✅ 解決2: そもそも b のサイズが間違っているなら直す
b = np.array([1, 2, 3, 4])
print(a + b)                  # (3, 4)

メモリ効率

ブロードキャストは仮想的に拡張するだけで、実際に拡張したサイズのメモリは確保しません。これにより、巨大な配列でも効率的に演算できます。

import numpy as np

# 良い: ブロードキャスト
a = np.ones((10000, 10000))   # 800 MB
b = np.arange(10000)           # 80 KB
c = a + b                      # b は仮想的に (10000, 10000) 扱い
# 追加メモリは b の 80 KB のみ

# 悪い: 明示的に拡張
b_expanded = np.tile(b, (10000, 1))  # ← 800 MB を新規確保
c = a + b_expanded                    # 結果も 800 MB
# 合計で 2.4 GB 必要

明示的ブロードキャストの可視化

import numpy as np

a = np.array([1, 2, 3])     # (3,)
b = np.array([[10], [20]])  # (2, 1)

# どんな形状にブロードキャストされるか
shape = np.broadcast_shapes(a.shape, b.shape)
print(shape)                # (2, 3)

# 実際の展開を見たい場合
ab, bb = np.broadcast_arrays(a, b)
print(ab.shape, bb.shape)   # (2, 3) (2, 3)

FAQ

Q: ブロードキャストはどこまで自動的に効く?
A: 二項演算 (+, -, *, /) と多くの ufunc (np.add, np.sin 等) に効きます。np.dot@ 行列積では効きません (専用ルール)。

Q: keepdims=True とは?
A: sum / mean / max 等で「集計後の次元を残す」オプション。これを付けると後段のブロードキャストが楽になります。

Q: ブロードキャストすると遅いことはある?
A: 通常は速いですが、極端に大きな仮想拡張は CPU キャッシュミスを引き起こします。最終結果が GB 単位になるなら、ループや一括処理を見直してください。

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 配列の作成
  2. 多次元配列の作成
  3. 要素の参照
  4. 要素の追加
  5. 要素の更新
  6. 要素の削除
  7. ブロードキャスト
  8. 多次元配列の構造の確認
  9. 多次元配列を1次元配列に変換
  10. 1次元配列を多次元配列に変換
  11. 要素の範囲指定
  12. 平均値の算出
  13. 行列を結合する方法