.. index:: ! broadcasting .. _nbayes2-broadcasting: ブロードキャスト ================ それでは,いよいよ本題のブロードキャストの説明に移ります. ブロードキャストとは, :attr:`ndim` や :attr:`shape` が異なる入力配列の間で,これらの属性を自動的に統一する機能です. :attr:`ndim` と :attr:`shape` を統一することで,それらの入力配列間で要素ごとの演算が可能になり,その次元数と大きさが統一された出力配列に演算結果を得ることができます. この機能により,例えば,次元数や大きさの異なる配列 :obj:`a` , :obj:`b` ,および :obj:`c` があったとき,これらの配列の要素ごとの和を,明示的に変換を指示しなくても ``a + b + c`` のように簡潔な形で書けるようになります. `公式サイトのブロードキャストの規則 `_ は次のとおりです. .. 1. All input arrays with ndim smaller than the input array of largest ndim, have 1’s prepended to their shapes. 2. The size in each dimension of the output shape is the maximum of all the input sizes in that dimension. 3. An input can be used in the calculation if its size in a particular dimension either matches the output size in that dimension, or has value exactly 1. 4. If an input has a dimension size of 1 in its shape, the first data entry in that dimension will be used for all calculations along that dimension. In other words, the stepping machinery of the ufunc will simply not step along that dimension (the stride will be 0 for that dimension). 1. 次元数 :attr:`ndim` が最大の入力配列より次元数 :attr:`ndim` が小さい全ての入力配列は, :attr:`shape` の先頭に 1 を加えて次元数を統一する. 2. 出力配列の :attr:`shape` の各次元の大きさは,入力配列のその次元の大きさのうちの最大のものにします. 3. 入力配列の各次元の大きさが,出力配列の対応する次元の大きさと一致するか,1 である場合にその入力配列を計算で利用できます. 4. 入力配列のある次元の大きさが 1 であるとき,その最初の要素の値をその次元の全ての計算で利用します. すなわち,ユニバーサル関数の読み出し機構は,その軸では読み出し位置を動かしません(その次元の移動幅を0にします). これらの規則のうち,第1規則は次元数の統一に関するもので,それ以外は要素間の対応付けに関するものです. それぞれについて順に説明します. .. _nbayes2-broadcasting-ndim: 次元数の統一 ------------ 第1規則は,出力配列の次元数 :attr:`ndim` の値を決定し, :attr:`ndim` に変更のあった入力配列の大きさ :attr:`shape` を変更する方法を定めるものです. 出力配列の :attr:`ndim` は,入力配列のうち最大のものになります. 例えば,0次元のスカラー,1次元のベクトル,そして2次元の行列の三つの入力配列があったとき,出力配列の :attr:`ndim` はこれらの中で最大の 2 となります. :attr:`ndim` を増やした入力配列では, :attr:`shape` の先頭,すなわち第0次元の位置に,大きさ 1 の次元を,必要な数だけ追加します. 例えば,入力配列が,次のスカラー :obj:`a` ,ベクトル :obj:`b` ,そして行列 :obj:`c` の三つである場合, .. code-block:: ipython In [38]: a = np.array(100) In [39]: a Out[39]: array(100) In [40]: b = np.array([10, 20, 30]) In [41]: b Out[42]: array([10, 20, 30]) In [43]: c = np.array([[1, 2, 3], [4, 5, 6]]) In [44]: c Out[44]: array([[1, 2, 3], [4, 5, 6]]) :attr:`ndim` は全て 2 に統一され, :attr:`shape` は次のようになります. .. csv-table:: :header-rows: 1 入力配列, 統一前 :attr:`shape`, 統一後 :attr:`shape` :obj:`a`, "``()``", "``(1, 1)``" :obj:`b`, "``(3,)``", "``(1, 3)``" :obj:`c`, "``(2, 3)``", "``(2, 3)``" 0次元の :obj:`a` では, :attr:`shape` の先頭に 1 を2個追加して,全ての次元で大きさが 1 の :attr:`shape` になります. 1次元の :obj:`b` では,元の大きさ3の第0次元の前に,大きさ 1 の次元を挿入します. すなわち,統一後に :attr:`ndim` が 2 になる行列では,ベクトルは横ベクトルとなります. 2次元の :obj:`c` は, :attr:`ndim` は変更されないので, :attr:`shape` も変更されません. このように自動的に次元数を統一する機構が備わっていますが,コードが分かりにくくなることもよくあります. そのため,実用的には,後述の :ref:`nbayes2-distclass` の例のように, :const:`np.newaxis` などを用いて明示的に次元数を統一して利用することをお薦めします. .. _nbayes2-broadcasting-match: 要素の対応付け -------------- ここまでで,次元数を統一し, :attr:`shape` を修正しました. その後は,第2規則で出力配列の :attr:`shape` を決定し,第3規則で演算要素の対応付けが可能かどうかを判定し,第4規則で実際の演算でどの要素対応付けるかを決定します. 出力配列の :attr:`shape` の決定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 第2規則により,入力配列の :attr:`shape` の同じ次元の配列の大きさを比較し,そのうち最大のものを出力配列のその次元の大きさとします. 例えば,上記の入力配列 :obj:`a` , :obj:`b` ,および :obj:`c` の場合は次の図のようになります. .. image:: ../Fig/broadcast-match1.* :width: 8.32 cm :align: center 青色の第0次元の大きさを比較すると :obj:`a` では 1, :obj:`b` も 1,そして :obj:`c` も 2 なので,これら三つのうちで最大の 2 が出力配列の第0次元の大きさとなります. 同様にオレンジ色の第1次元では, :obj:`c` の 3 が最大なので,出力配列の第1次元の大きさは 3 となります. よって,出力配列の :attr:`shape` は ``(2, 3)`` となります. 3次元以上の配列についても同様です. 例えば, :attr:`shape` が ``(2, 1, 1)`` の配列 :obj:`d` と ``(1, 3, 5)`` の :obj:`e` があった場合には,次の図のように出力配列の :attr:`shape` は ``(2, 3, 5)`` となります. .. image:: ../Fig/broadcast-match2.* :width: 8 cm :align: center .. index:: broadcastable ブロードキャスト可能性の判定 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 第3規則により,出力配列と各入力配列の :attr:`shape` を比較し,要素の対応付けが可能かどうかを判定します. 全ての入力配列の,全ての次元で,その大きさが 1 であるか,もしくはその次元の大きさが出力配列の対応する次元と等しい場合にブロードキャスト可能 (broadcastable) であるといい,要素の対応付けが可能となります. 上記の :obj:`a` , :obj:`b` ,および :obj:`c` の例では, :obj:`a` の :attr:`shape` は ``(1, 1)`` で,どの次元でも大きさが 1 なのでブロードキャスト可能です. :obj:`b` の :attr:`shape` は ``(1, 3)`` で,出力配列の :attr:`shape` ``(2, 3)`` と比較すると, :obj:`b` の第0次元の大きさは 1 なのでブロードキャスト可能性の条件を満たし,第1次元の 3 も出力配列の第1次元の大きさ 3 と等しいのでやはり条件を満たすためブロードキャスト可能です. :obj:`c` の :attr:`shape` は出力配列のそれと同じなのでブロードキャスト可能です. よって,全ての入力配列がブロードキャスト可能なため,全体でもブロードキャスト可能となります. 同様に,配列 :obj:`d` と :obj:`e` の例でも, :obj:`e` の :attr:`shape` ``(2, 1, 1)`` は出力配列の :attr:`shape` ``(2, 3, 5)`` と比べると,大きさは第0次元は一致し,入力配列のその他の次元では 1 なのでブロードキャスト可能です. :obj:`e` の :attr:`shape` ``(1, 3, 5)`` は第0次元では大きさが 1 で,その他は出力配列と一致するためブロードキャスト可能です. よって全ての入力配列がブロードキャスト可能なので,全体でもブロードキャスト可能になります. ブロードキャスト可能でない例も挙げておきます. 入力配列の :attr:`shape` が ``(1, 2, 5)`` で,出力配列の :attr:`shape` が ``(3, 3, 5)`` のときには,第0次元と第2次元はブロードキャスト可能性の条件を満たします. しかし,入力配列の第1次元の大きさは 2 であり,これは 1 でもなく,かつ出力配列の第1次元の大きさ 3 とも一致しないため条件を満たさないので,ブロードキャスト可能ではありません. :ref:`nbayes2-fit2-fitif-try` 節の例は, :obj:`y` の :attr:`shape` が ``(n_samples,)`` であるのに対し, ``np.arange(n_classes)`` の :attr:`shape` は ``(n_classes,)`` ですので,出力配列の :attr:`shape` は一般に ``(n_samples,)`` となります. すると, ``np.arange(n_classes)`` の大きさは 1 でもなく,出力配列の大きさとも一致しないためブロードキャスト可能でなくなり,この実装は動作しませんでした. ブロードキャストの実行 ^^^^^^^^^^^^^^^^^^^^^^ 最後の第4規則により,各入力配列の要素を対応付けます. この要素の対応付けは,次元数統一後の入力配列の:attr:`shape` に基づいて,次のように行います: * 入力配列のある次元の大きさが,出力配列のそれと一致している場合では,統合後の入力配列の:attr:`shape` のその次元の大きさはそのまま変わりません. * 入力配列のある次元の大きさが 1 である場合では,その一つの要素を,その次元では全ての演算で利用し続けます. 後者の場合,一つの要素の値を,その次元の全ての要素にブロードキャストして(拡散して)用いるので,この配列の自動操作の仕組みはブロードキャストと呼ばれています. この規則について,上記の三つの配列 :obj:`a` , :obj:`b` ,および :obj:`c` を例にとり,次の図を用いて説明します. .. image:: ../Fig/broadcast-cast1.* :width: 8.8 cm :align: center この図では,第0次元は行数,第1次元は列数に対応しています. 出力配列の :attr:`shape` は ``(2, 3)`` であるため,どの入力配列もこの :attr:`shape` に統一されます. 次元数統一後の :obj:`a` の :attr:`shape` は ``(1, 1)`` でしたが,これは図中の :obj:`a` の青色の箱で表示しています. 第0次元の大きさは 1 なので,1行目の値が2行目でも利用されます. 第1次元の大きさも 1 なので,1列目の値が2列目以降でも利用されます. よって, :obj:`a` では, ``a[0, 0]`` の値が,全ての要素に対する演算で利用されます. :obj:`b` では,第0次元の大きさは 1 なので,第1行目の値が第2行目でも利用されますが,第1次元の大きさは出力配列と同じ大きさなのでそれぞれの列の値が利用されます. よって, :obj:`b` では,第0行目の値が第1行目の演算でも利用されます. :obj:`c` は,第0次元と第1次元のどちらでも,その大きさは出力配列と等しいので,それぞれの要素の値がそのまま演算で利用されます. このように,全ての入力配列の :attr:`shape` を統一すれば,あとは同じ位置の要素ごとに演算ができます. :obj:`d` と :obj:`e` の場合についても,次の図を用いて説明します. .. image:: ../Fig/broadcast-cast2.* :width: 6.88 cm :align: center この図では,第0次元は手前から奥に増加し,第1次元が行,第2次元が列に対応しています. この例では,出力配列の :attr:`shape` は ``(2, 3, 5)`` でした. 次元数統一後の :obj:`d` の :attr:`shape` は ``(2, 1, 1)`` であるため,第0次元のみ要素の値をそのまま用い,第1次元と第2次元では ``d[:, 0, 0]`` の値を演算に用います. すなわち,手前と奥の両方のそれぞれ行列で,左上の要素の値を用いて演算します. :obj:`e` の :attr:`shape` は ``(1, 3, 5)`` であるため,今度は第0次元では手前の行列の値を奥の行列で用いて,他の次元ではその要素の値をそのまま用いて演算します. すなわち,手前の配列を奥の配列にあたかも複製して利用するようになります. こうして,入力配列の :attr:`shape` を統一した後は,要素ごとに演算ができるようになります. 以上で,配列の :attr:`shape` を操作する方法と,形式的なブロードキャストの規則とを説明しました. 次の節では,ブロードキャストを利用して,分布の計算を実装します.