SIGMA-SE Tech Blog

SIGMA-SE Tech Blog

Python - AI : 重みに対する損失関数の勾配法と実装サンプル

目的

この記事では、重みに対する勾配法(勾配降下法)の実装サンプルを記載する。

概念の説明と実装サンプル

重みに対する勾配法とは

重みは、正解に対して入力値がどれだけ影響するかを示す重要度を表す。

重みという言葉からは全くイメージできない意味を持つが、これは英語のWeighを直訳し、重みとなっているからであり、本来は大切さ価値重要性という意味で命名されている。

そして、この重み(重要度)は、一般的にWeighの \(w\) を取り、\(w_{0}\)、\(w_{1}\)、\(w_{2}\) …で表現され、評価的に具体的な数値を入れてみて損失結果を計るためのパラメータとなる。

ニューラルネットワークでは、この重みに対して、Python - AI : 勾配降下法の実装サンプル で解説した勾配降下法を実施し、最適な重みを求めていく。

また、下記に示す行列のよう色々なパターンの重みを行列計算で一斉に偏微分し、最適な重みを求めていく。

\[ W = \begin{pmatrix} w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} \\ \end{pmatrix} \]
\[ \frac{∂L}{∂W} = \begin{pmatrix} \frac{∂L}{∂w_{11}} & \frac{∂L}{∂w_{12}} & \frac{∂L}{∂w_{13}} \\ \frac{∂L}{∂w_{21}} & \frac{∂L}{∂w_{22}} & \frac{∂L}{∂w_{23}} \\ \end{pmatrix} \]

\(W\) は、一斉に偏微分しようとしている2行3列の重み達。
\(L\) は、対象となる損失関数で \(\displaystyle \frac{∂L}{∂W}\) の各要素で偏微分し、損失関数\(L\) の勾配を求めている。

以降は、この勾配を求める実装サンプルについて記載する。

重みに対する勾配のPython実装サンプル

以下、参考文献『ゼロから作るDeep Learning』から提供されている ch04/gradient_simplenet.py を用いたサンプル解説をしていく。

※ サンプルコードは、下記Gitからダウンロードする。
Git(deep-learning-from-scratch): https://github.com/oreilly-japan/deep-learning-from-scratch

ここでは、より分かりやすく説明するため、ch04/gradient_simplenet.pysimpleNetクラスを実装するにあたりimportされている下記3つの関数をあえてPython対話モードで定義する形で記載する。

  • ソフトマックス関数:common/functions.pyのsoftmax関数
    ※ ソフトマックス関数の一般的な定義は下記ページを参考。
    Python - AI : 活性化関数の実装サンプルまとめ(ステップ、シグモイド、ReLU、恒等関数、ソフトマックス関数) > ソフトマックス関数

    $ python
    >>> import numpy as np
    >>>
    >>> def softmax(x):
    ...     if x.ndim == 2:
    ...         x = x.T
    ...         x = x - np.max(x, axis=0)
    ...         y = np.exp(x) / np.sum(np.exp(x), axis=0)
    ...         return y.T
    ...     x = x - np.max(x)
    ...     return np.exp(x) / np.sum(np.exp(x))
    ...
    >>>
    
  • 交差エントロピー誤差:common/functions.py の cross_entropy_error関数
    ※ 交差エントロピー誤差の処理内容については、下記ページを参考。
    Python - AI : 損失関数(2乗和誤差、交差エントロピー誤差)と実装サンプル > ソフトマックス関数 Python - AI : 交差エントロピー誤差のミニバッチ学習と実装サンプル

    >>> # 上記対話モードの続き
    >>> def cross_entropy_error(y, t):
    ...     if y.ndim == 1:
    ...         t = t.reshape(1, t.size)
    ...         y = y.reshape(1, y.size)
    ...
    ...     if t.size == y.size:
    ...         t = t.argmax(axis=1)
    ...
    ...     batch_size = y.shape[0]
    ...     return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
    ...
    >>>
    
  • 勾配処理:common/gradient.pyのnumerical_gradient関数
    ※ 勾配の処理内容は下記ページの勾配関数(num_gradient)を参考。
    Python - AI : 偏微分と勾配の実装サンプル

    >>> # 上記対話モードの続き
    >>> def numerical_gradient(f, x):
    ...     h = 1e-4
    ...     grad = np.zeros_like(x)
    ...
    ...     it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    ...
    ...     while not it.finished:
    ...         idx = it.multi_index
    ...         tmp_val = x[idx]
    ...         x[idx] = float(tmp_val) + h
    ...         fxh1 = f(x)
    ...
    ...         x[idx] = tmp_val - h
    ...         fxh2 = f(x)
    ...         grad[idx] = (fxh1 - fxh2) / (2*h)
    ...
    ...         x[idx] = tmp_val
    ...         it.iternext()
    ...
    ...     return grad
    ...
    >>>
    
  • 重みに対する勾配処理:3つの関数を呼び出した形でch04/gradient_simplenet.pysimpleNetクラスを実装

    >>> # 上記対話モードの続き
    >>> import sys, os
    >>>
    >>> class simpleNet:
    ...     def __init__(self):
    ...         self.W = np.random.randn(2,3)
    ...
    ...     def predict(self, x):
    ...         return np.dot(x, self.W)
    ...
    ...     def loss(self, x, t):
    ...         z = self.predict(x)
    ...         y = softmax(z)
    ...         loss = cross_entropy_error(y, t)
    ...
    ...         return loss
    ...
    >>>
    

    \(x\) は、入力データで \(t\) が教師データ

    predict関数は、入力データ \(x\) と__init__で設定した仮(ランダム)の重みパラメータself.W評価結果(積)を返す。

    loss関数は、predict関数、softmax関数を実施した \(y\) と教師データ \(t\) の損失関数(交差エントロピー誤差:cross_entropy_error関数)を返す。

実装サンプルの実行確認

以降、上記simpleNetの実行例を基に解説する。

  • インスタンスの結果確認

    >>> # 上記対話モードの続き
    >>> net = simpleNet()
    >>> print(net.W)
    [[ 0.49236891 -1.2239298  -1.13722119]
    [ 0.05405365 -0.79897152 -0.08066587]]
    >>>
    

    simpleNet()により、2x3行列のランダムな数値が生成される。

  • 評価結果(積)の確認

    >>> # ↑↑↑ 上記対話モードの続き
    >>> x = np.array([0.6, 0.9])
    >>> p = net.predict(x)
    >>> print(p)
    [ 1.05414809 0.63071653 1.1328074  ]
    >>> np.argmax(p)
    2
    >>>
    

    predict(x)により、入力データ \([0.6, 0.9]\) と重みパラメータnet.Wの評価結果(積)を算出している。 (最大インデックスは、2)

  • 損失関数の結果確認

    >>> # 上記対話モードの続き
    >>> t = np.array([0, 0, 1])
    >>> net.loss(x, t)
    0.92806853663411326
    >>>
    

    上記で最大インデックスとなった\(2\)が正解ラベルとなる状態で損失関数の結果は、約\(0.93\)

  • 勾配の結果確認

    >>> # 上記対話モードの続き
    >>> def f(W):
    ...     return net.loss(x, t)
    ...
    >>> dW = numerical_gradient(f, net.W)
    >>> print(dW)
    [[ 0.21924763  0.14356247 -0.36281009 ]
    [ 0.32887144  0.2153437 -0.544211514]]
    >>>
    

    net.loss(x, t)f(W)とし、勾配処理(numerical_gradient)を実施している。

    f(W)Wは、勾配処理:common/gradient.pyのnumerical_gradient関数の\((A)\)、\((B)\)と整合性が取れるように定義したもの。

  • 結果から見る結論

    重みパラメータ \(w_{11}\) と重みパラメータ \(w_{23}\) にスポットを当てた結果を見る。

    \(w_{11}\) が \(h\)分増加すると、\(\displaystyle \frac{∂L}{∂W}\) の \(\displaystyle \frac{∂L}{∂w_{11}}\) は、約\(0.2\)となり、\(0.2\)増加している。

    一方、\(w_{23}\) は \(h\) 分増加すると、\(\displaystyle \frac{∂L}{∂W}\) の \(\displaystyle \frac{∂L}{∂w_{23}}\) が約\(-0.5\)となり、\(0.5\)減少している。

    よって、 重みパラメータ \(w_{11}\) は、マイナス方向に。
    重みパラメータ \(w_{23}\) は、プラス方向更新すべきという結論となる。

    ※ 以上の要領で重みパラメータをより損失が少ない重みパラメータへ更新していくことが目的。

参考文献

  • 斎藤 康毅(2018)『ゼロから作るDeep Learning - Pythonで学ぶディープラーニングの理論と実装』株式会社オライリー・ジャパン


Copyright SIGMA-SE All Rights Reserved.
s-hama@sigma-se.jp