.. index:: broadcasting .. _nbayes2-distclass: クラスの分布の学習 ================== 前節のブロードキャストの機能を用いると, :obj:`for` ループを用いなくても,複数の要素に対する演算をまとめて行うことができます. この節では,その方法を,単純ベイズの学習でのクラスの分布の計算の実装を通じて説明します. 例として, :ref:`nbayes2-fit2-fitif` 節で紹介した,クラス分布の計算の比較演算を用いた次の実装を,ブロードキャスト機能を用いた実装に書き換えます. .. code-block:: python nY = np.zeros(n_classes, dtype=int) for yi in range(n_classes): for i in range(n_samples): if y[i] == yi: nY[yi] += 1 .. _nbayes2-distclass-general: 書き換えの一般的な手順 ---------------------- :obj:`for` ループによる実装をブロードキャストを用いて書き換える手順について,多くの人が利用している方針は見当たりません. そこで,ここでは著者が採用している手順を紹介します. 1. 出力配列の次元数を :obj:`for` ループの数とします. 2. 各 :obj:`for` ループごとに,出力配列の次元を割り当てます. 3. 計算に必要な配列の生成します.このとき,ループ変数がループに割り当てた次元に対応するようにします. 4. 冗長な配列を整理統合します. 5. 要素ごとの演算をユニバーサル関数の機能を用いて実行します. 6. :func:`np.sum` などの集約演算を適用して,最終結果を得ます. それでは,上記のコードを例として,これらの手順を具体的に説明します. .. _nbayes2-distclass-assign: ループ変数の次元への割り当て ---------------------------- 手順の段階1と2により,各ループを次元に割り当てます. 例題のコードでは, :obj:`for` ループは2重なので,出力配列の次元数を 2 とします. ループは外側の :obj:`yi` と内側の :obj:`i` の二つで,これらに次元を一つずつ割り当てます. ここでは,第 0 次元に :obj:`i` のループを,第 1 次元に :obj:`yi` のループを割り当てておきます. 表にまとめると次のようになります. .. csv-table:: :header-rows: 1 次元, ループ変数, 大きさ, 意味 0, :obj:`i` , :obj:`n_samples` , 事例 1, :obj:`yi` , :obj:`n_classes` , クラス .. _nbayes2-distclass-arygen: 計算に必要な配列の生成 ---------------------- 段階3では,要素ごとの演算に必要な配列を生成します. :obj:`for` ループ内で行う配列の要素間演算は次の比較演算です. .. code-block:: python y[i] == yi 左辺の ``y[i]`` と,右辺の :obj:`yi` に対応する配列が必要になります. これらについて,段階2で割り当てた次元にループ変数対応するようにした配列を作成します. 左辺の ``y[i]`` では,ループ変数 :obj:`i` で指定した位置の配列 :obj:`y` の値が必要になります. このループ変数 :obj:`i` に関するループを見てみます. .. code-block:: python for i in range(n_samples): このループ変数 :obj:`i` は ``0`` から ``n_samples - 1`` までの整数をとります. これらの値を含む配列は ``np.arange(n_samples)`` により生成できます. 次に,これらの値が,ループ変数 :obj:`i` に段階2で割り当てた次元 0 の要素になり,他の次元の大きさは 1 になるようにします. これは, :ref:`nbayes2-shape` で紹介した :attr:`shape` の操作技法を用いて次のように実装できます. .. code-block:: python ary_i = np.arange(n_samples)[:, np.newaxis] 第0次元の ``:`` により, ``np.arange(n_samples)`` の内容を第0次元に割り当て,第1次元は ``np.newaxis`` により大きさ 1 となるように設定します. ループ変数 :obj:`i` で指定した位置の配列 :obj:`y` の値 ``y[i]`` は次のコードにより得ることができます. .. code-block:: python ary_y = y[ary_i] このコードにより, :obj:`ary_i` と同じ :attr:`shape` で,その要素が ``y[i]`` であるような配列を得ることができます. 右辺のループ変数 :obj:`yi` についての次のループも同様に処理します. .. code-block:: python for yi in range(n_classes): この変数は ``0`` から ``n_classes - 1`` までの整数をとり,第1次元に割り当てられているので,この変数に対応する配列は次のようになります. .. code-block:: python ary_yi = np.arange(n_classes)[np.newaxis, :] 第0次元には大きさ 1 の次元を設定し,第1次元の要素には ``np.arange(n_classes)`` の内容を割り当てています. 以上で,比較演算に必要な配列 :obj:`ary_y` と :obj:`ary_yi` が得られました. .. _nbayes2-distclass-redundant: 冗長な配列の整理 ---------------- 段階4では,冗長な配列を整理します. :obj:`ary_y` は, :obj:`ary_i` を展開すると次のようになります. .. code-block:: python ary_y = y[np.arange(n_samples)[:, np.newaxis]] 配列の :attr:`shape` を変えてから :obj:`y` 中の値を取り出す代わりに,先に :obj:`y` の値を取り出してから :attr:`shape` を変更するようにすると次のようになります. .. code-block:: python ary_y = (y[np.arange(n_samples)])[:, np.newaxis] ここで :obj:`y` の大きさは :obj:`n_samples` であることから, ``y[np.arange(n_samples)]`` は :obj:`y` そのものです. このことをふまえると :obj:`ary_y` は,次のように簡潔に生成できます. .. code-block:: python ary_y = y[:, np.newaxis] 以上のことから, :obj:`ary_i` を生成することなく目的の :obj:`ary_y` を生成できるようになりました. この冗長なコードの削除は次のループの書き換えと対応付けて考えると分かりやすいかもしれません. 次のループ変数 :obj:`i` を使って :obj:`y` 中の要素を取り出すコード .. code-block:: python for i in range(n_samples): val_y = y[i] は, :obj:`for` ループで :obj:`y` の要素を順に参照する次のコードと同じ :obj:`val_y` の値を得ることができます. .. code-block:: python for val_y in y: pass これらのコードは,それぞれ,ループ変数配列を用いた ``y[ary_i]`` と :obj:`y` の値を直接参照する ``y[:, np.newaxis]`` とに対応します. .. _nbayes2-distclass-elementwise: 要素ごとの演算と集約演算 ------------------------ 段階5では要素ごとの演算を行います. 元の実装では要素ごとの演算は ``y[i] == yi`` の比較演算だけでした. この比較演算を,全ての :obj:`i` と :obj:`yi` について実行した結果をまとめた配列は次のコードで計算できます. .. code-block:: python cmp_y = (ary_y == ary_yi) :obj:`ary_y` と :obj:`ary_yi` の :attr:`shape` はそれぞれ ``(n_samples, 1)`` と ``(1, n_classes)`` で一致していません. しかし,ブロードキャストの機能により, ``ary_y[:, 0]`` の内容と, ``ary_yi[0, :]`` の内容を,繰り返して比較演算利用するため,明示的に繰り返しを記述しなくても目的の結果を得ることができます. .. index:: aggregation 最後の段階6は集約演算です. 集約 (aggregation) とは,複数の値の代表値,例えば総和,平均,最大などを求めることです. :ref:`nbayes2-fit2-fitif-ufunc` で述べたように,比較結果が真である組み合わせは :func:`np.sum` によって計算できます. ここで問題となるのは,単純に ``np.sum(cmp_y)`` とすると配列全体についての総和になってしまいますが,計算したい値は :obj:`yi` がそれぞれの値をとるときの,全ての事例についての和でであることです. そこで, :func:`np.sum` 関数の :obj:`axis` 引数を指定します. ここでは,事例に対応するループ変数 :obj:`i` を次元0に割り当てたので, ``axis=0`` と指定します. .. code-block:: python nY = np.sum(cmp_y, axis=0) 以上の実装をまとめて書くと次のようになります. .. code-block:: python ary_y = y[:, np.newaxis] ary_yi = np.arange(n_classes)[np.newaxis, :] cmp_y = (ary_y == ary_yi) nY = np.sum(cmp_y, axis=0) 途中での変数への代入をしないようにすると,次の1行のコードで同じ結果を得ることができます. .. code-block:: python nY = np.sum(y[:, np.newaxis] == np.arange(n_classes)[np.newaxis, :], axis=0) .. _nbayes2-distclass-prob: クラスの確率の計算 ------------------ :class:`NaiveBayes1` の実装では,各クラスごとの標本数 :obj:`nY` を,総標本数 :obj:`n_samples` で割って,クラスの確率を計算しました. .. code-block:: python self.pY_ = np.empty(n_classes, dtype=float) for i in range(n_classes): self.pY_[i] = nY[i] / n_samples この処理も,除算演算子 ``/`` にユニバーサル関数の機能があるため次のように簡潔に実装できます [#]_ . .. code-block:: python self.pY_ = nY / n_samples .. only:: not latex .. rubric:: 注釈 .. [#] 整数だけでなく浮動小数点に対する除算でも,切り捨てした整数で除算の結果を得るには :func:`np.floor_divide` を用います. .. index:: floor_divide .. function:: np.floor_divide(x1, x2[, out]) = Return the largest integer smaller or equal to the division of the inputs. Python2 では除算の結果は,整数同士の場合では,結果は切り捨てした整数でした. そのため,整数同士の除算で実数の結果を得たい場合には :func:`np.true_divide` 関数を用います. .. index:: true_divide .. function:: np.true_divide(x1, x2[, out]) = Returns a true division of the inputs, element-wise.