一覧へ

「ブロードキャスティング — 多次元配列演算のルール」

「NumPyでサイズが異なる配列同士で演算が可能である理由、ブロードキャスティングのルールを視覚的に理解します。」

中級
|
10
|
検証済み (2026-07)
ブロードキャスティングNumPyshape互換性配列演算ベクトル化
進捗0/17 (0%)

ブロードキャスティング — 多次元配列演算ルール

このトピックを完了すると、次のことが理解できるようになります。

NumPyのブロードキャスティングが存在する理由を理解し、3つのルールを適用して演算が可能かどうかを判断し、ブロードキャスティングを使用して効率的なコードを作成できるようになります。


ブロードキャスティングが必要なのはなぜですか?

python
import numpy as np
# すべての要素に10を追加したい
arr = np.array([1, 2, 3, 4, 5])
result = arr + 10
print(result) # [11 12 13 14 15]

arrは5つの要素を持ち、10はスカラー(1つの要素)です。サイズが異なるにもかかわらず、加算が可能です。これは、NumPyが10[10, 10, 10, 10, 10]自動的に拡張するためです。この自動拡張がブロードキャスティングです。

ブロードキャスティングがない場合:

python
# 毎回、次のようなコードを書く必要があります
result = arr + np.full(5, 10) # 非効率

同じ形状での演算 — 基本

サイズが同じ場合、要素ごとの演算になります。

python
a = np.array([1, 2, 3])
b = np.array([10, 20, 30])
print(a + b) # [11 22 33]
print(a * b) # [10 40 90]

これは2次元配列にも適用されます。

python
A = np.array([[1, 2], [3, 4]])
B = np.array([[10, 20], [30, 40]])
print(A + B)
# [[11 22]
# [33 44]]

ブロードキャスティングルール — 3つのルール

サイズが異なる場合、NumPyは3つのルールを順番に適用します。

ルール1:次元数が異なる場合、小さい配列の先頭に1を追加します。

python
a = np.array([[1, 2, 3], # shape: (2, 3)
[4, 5, 6]])
b = np.array([10, 20, 30]) # shape: (3,)
# ルール1:bの形状(3,)を(1, 3)に拡張
# ルール2を適用した後:(1, 3) -> (2, 3)
print(a + b)
# [[11 22 33]
# [14 25 36]]

ルール2:サイズが1の次元を、他の配列と一致するように拡張します。

python
a = np.array([[1, 2, 3]]) # shape: (1, 3)
b = np.array([[10], # shape: (3, 1)
[20],
[30]])
# a: (1, 3) -> 行方向に拡張 -> (3, 3)
# b: (3, 1) -> 列方向に拡張 -> (3, 3)
print(a + b)
# [[11 12 13]
# [21 22 23]
# [31 32 33]]

ルール3:サイズが異なり、どちらの側も1でない場合、エラーになります。

python
a = np.array([1, 2, 3]) # shape: (3,)
b = np.array([10, 20]) # shape: (2,)
# 3 vs 2 — どちらの側も1ではない -> エラー!
# a + b -> ValueError: operands could not be broadcast together

視覚的に理解する

text
(4, 3) + (3,)    -> OK!
a:  [[1 2 3]      b: [10 20 30]
     [4 5 6]          ↓ ルール1: (1, 3)
     [7 8 9]          ↓ ルール2: (4, 3)
     [0 1 2]]
                  b': [[10 20 30]
                       [10 20 30]
                       [10 20 30]
                       [10 20 30]]

結果: [[11 22 33]
         [14 25 36]
         [17 28 39]
         [10 21 32]]
text
(3, 1) + (1, 4)  -> OK!  結果の形状: (3, 4)
a: [[1]           b: [[10 20 30 40]]
    [2]               ↓ ルール2: (3, 4)
    [3]]          b': [[10 20 30 40]
    ↓ ルール2           [10 20 30 40]
a': [[1 1 1 1]        [10 20 30 40]]
     [2 2 2 2]
     [3 3 3 3]]

結果: [[11 12 13 14]
         [12 13 14 15]
         [13 14 15 16]]

実践的なパターン — 正規化

python
# 各列の平均を引いて、中心化する
data = np.array([[170, 60, 30],
[180, 75, 25],
[165, 55, 35],
[175, 70, 28]])
# shape: (4, 3) — 4人のデータ、3つの測定値(身長、体重、年齢)
col_mean = data.mean(axis=0) # shape: (3,) — 各列の平均
print(col_mean) # [172.5 65. 29.5]
centered = data - col_mean # (4, 3) - (3,) -> ブロードキャスティング!
print(centered)
# [[ -2.5 -5. 0.5]
# [ 7.5 10. -4.5]
# [ -7.5 -10. 5.5]
# [ 2.5 5. -1.5]]

Zスコア正規化

python
col_std = data.std(axis=0)
z_scores = (data - col_mean) / col_std # ブロードキャスティングを2回!

(4, 3) - (3,) -> OK、(4, 3) / (3,) -> OK。この単一のパターンで、データセット全体を正規化できます。


ループとの比較

python
# 悪い:Pythonループ — 遅い
data = np.random.rand(10000, 100)
mean = data.mean(axis=0)
result = np.zeros_like(data)
for i in range(data.shape[0]):
for j in range(data.shape[1]):
result[i, j] = data[i, j] - mean[j]
# 良い:ブロードキャスティング — 100倍以上高速
result = data - mean

ブロードキャスティングは、内部でCで実装されたベクトル演算です。Pythonループよりも数十倍から数百倍高速であり、コードは1行で済みます。


実践的なパターン — 距離行列

2つのポイントセット間のすべての距離を一度に計算します。

python
# 3つのポイント:(1, 0)、(2, 3)、(4, 1)
points = np.array([[1, 0], [2, 3], [4, 1]]) # shape: (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=2))
print(dist.round(2))
# [[0. 3.16 3.16]
# [3.16 0. 2.83]
# [3.16 2.83 0. ]]

np.newaxisを使用して次元を追加し、ブロードキャスティングを使用してすべてのペア間の差を一度に計算します。これは、二重のforループよりも数十倍高速です。


注意 — メモリの爆発

python
a = np.random.rand(10000, 1) # shape: (10000, 1)
b = np.random.rand(1, 10000) # shape: (1, 10000)
c = a + b # shape: (10000, 10000) — 1億個の要素!
# メモリ:10000 × 10000 × 8バイト = 800MB

ブロードキャスティングは概念的に拡張されますが、結果の配列は実際にメモリを占有します。(10000, 1) + (1, 10000)は、サイズ(10000, 10000)の配列を作成します。大量のデータの場合、結果の形状を事前に計算して、メモリ使用量を確認します。

python
# メモリ使用量を予測
result_shape = (10000, 10000)
dtype_size = 8 # float64 = 8バイト
memory_bytes = result_shape[0] * result_shape[1] * dtype_size
print(f"{memory_bytes / 1e6:.0f} MB") # 800 MB

形状の互換性の簡単なチェック

右から一致させ、各次元が同じであるか、どちらかの側が1である場合、互換性があります。

text
(4, 3) + (3,)      -> (4, 3) + (1, 3) -> OK  -> (4, 3)
(4, 3) + (4, 1)    -> OK  -> (4, 3)
(3, 1) + (1, 4)    -> OK  -> (3, 4)
(4, 3) + (2,)      -> (4, 3) + (1, 2) -> 3 vs 2 -> エラー
(2, 3, 4) + (3, 1) -> (2, 3, 4) + (1, 3, 1) -> OK -> (2, 3, 4)

まとめ

ルール説明
ルール1次元数が異なる場合、小さい配列の先頭に1を追加します。
ルール2サイズが1の次元を、他の配列と一致するように拡張します。
ルール3サイズが異なり、どちらの側も1でない場合、エラーになります。

ブロードキャスティングは、NumPyの最も強力な機能の1つです。データ正規化、距離計算、行列変換などを、forループなしで実行できます。重要なのは、形状を見て、すぐに「右から一致させ、同じか1であればOK」と判断することです。

同じ原則はpandasにも適用されます。DataFrame - Seriesのような演算は、列に沿ってブロードキャストされます。NumPyのブロードキャスティングを理解すると、自然にpandasのベクトル演算も理解できるようになります。

python
import pandas as pd
df = pd.DataFrame({"A": [10, 20, 30], "B": [40, 50, 60]})
means = df.mean() # Series: A=20, B=50
centered = df - means # ブロードキャスティング!各列から平均を引く
print(centered)
# A B
# 0 -10.0 -10.0
# 1 0.0 0.0
# 2 10.0 10.0

💬 質問・コメント

0件のコメント

ログインせずに投稿できます。ゲスト投稿は投稿者自身で編集・削除できません。

0/2000

読み込み中...