🧠

【Python】ロジスティック回帰を使ってクラスの確率を予測するモデルの構築Part2

2024/03/24に公開

はじめに

この記事は株式会社インプレスの「Python機械学習プログラミング Pytorch&scilit-learn編」を読んで、私が学習したことをまとめています。
今回は3章3節の「ロジスティック回帰を使ってクラスの確率を予測するモデルの構築」を読んで学んだことをまとめていきます。
※まとめている中で、思った以上にボリュームがあったので、細かく記事を分けています。今回は「3.3.2 ロジスティック損失関数を使ってモデルの重みを学習する」を読んで学んだことをまとめています。

Part1の「3.3.1 ロジスティック回帰と条件付き確率」をまとめた記事こちらからどうぞ。


【リンク紹介】
【一覧】Python機械学習プログラミング Pytorch&scilit-learn編
これまで書いたシリーズ記事一覧

前回はロジスティック回帰アルゴリズムにおいて、活性化関数として与えられるシグモイド関数\sigma_3と、シグモイド関数をふまえた閾値関数の再定義を行いました。

今回はADALINEのときと同様に、モデルのパラメータを学習させるための損失関数L_2として対数尤度(ゆうど)関数(log-likelihood function)を定義します(※正確にはL_2とは、この対数尤度関数にマイナスをつけたもののことを指します)。ADALINEの平均二乗誤差による損失関数L_1と明確に区別するために添え字を使って決定関数\sigmaと同様に定義します。

定義

基本的な希望の定義は【Python】ADALINE(フルバッチ勾配降下法)と学習の収束を参考にしてください。

\boldsymbol{w}:重み
b:バイアスユニット
\boldsymbol{x}^{(j)}:ある特徴量のj番目の変数(1 \leq j \leq n, n:自然数)
y^{(j)}\boldsymbol{x}^{(j)}に対応する正解値

対数尤度関数

\boldsymbol{x}^{(j)}が与えられたときの正解値がy^{(j)}となる条件付き確率はp(y^{(j)} | x^{(j)}, \boldsymbol{w}, b)と表せます。

ここで、各jに対する条件付き確率は互いに独立であると仮定し、各条件下においてy^{(1)}, y^{(2)}, \cdots, y^{(j)}, \cdots, y^{(n)}が同時に起こる確率は

\begin{alignat*}{2} & p((y^{(1)}, y^{(2)}, \cdots, y^{(j)}, \cdots, y^{(n)}) | (\boldsymbol{x}^{(1)}, \boldsymbol{x}^{(2)}, \cdots, \boldsymbol{x}^{(j)}, \cdots, \boldsymbol{x}^{(n)} ); \boldsymbol{w},b ) \\ =& p(y^{(1)} | \boldsymbol{x}^{(1)}; \boldsymbol{w},b) \times p(y^{(2)} | \boldsymbol{x}^{(2)}; \boldsymbol{w},b) \times \cdots \times p(y^{(n)} | \boldsymbol{x}^{(n)}; \boldsymbol{w},b) \\ =& \prod_{j}^{n} p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b) \end{alignat*}

です。ここで、p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b)について、

y^{(j)} \in \{ 0, 1 \}

であり、また【Python】ロジスティック回帰を使ってクラスの確率を予測するモデルの構築Part1より

p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b) = \begin{cases} \sigma_{3} (z^{(j)}) & (y^{(j)} = 1) \\ 1 - \sigma_{3} (z^{(j)}) & (y^{(j)} = 0) \\ \end{cases}

と表すことができます。このとき定義より0 \leq \sigma_{3} (z^{(j)}) \leq 1は明らかです。
以上のことから、y^{(j)}ベルヌーイ分布に従うため

p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b) = (\sigma_{3} (z^{(j)}))^{y^{(j)}} (1 - \sigma_{3} (z^{(j)}))^{1 - y^{(j)}}

が成り立ちます。ゆえに

\begin{alignat*}{2} \prod_{j}^{n} p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b) &= \prod_{j}^{n} (\sigma_{3} (z^{(j)}))^{y^{(j)}} (1 - \sigma_{3} (z^{(j)}))^{1 - y^{(j)}} \end{alignat*}

が成り立ちます。

ここで、\displaystyle\prod_{j}^{n} p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b)の対数を取ったものを対数尤度関数といいます。

\log \prod_{j}^{n} p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b)

損失関数L_2

対数尤度関数にマイナスを付与した式

- \log \prod_{j}^{n} p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b)

損失関数L_2とします。

損失関数の定義は以上になりますが、上の形のままだと扱いづらいので以下のように式変形を行います。

\begin{alignat*}{2} L_2 (\boldsymbol{w}, b) &= - \log \prod_{j}^{n} p(y^{(j)} | \boldsymbol{x}^{(j)}; \boldsymbol{w},b) \\ &= - \log \prod_{j}^{n} (\sigma_{3} (z^{(j)}))^{y^{(j)}} (1 - \sigma_{3} (z^{(j)}))^{1 - y^{(j)}} \\ &= - \log \{ (\sigma_{3} (z^{(1)}))^{y^{(1)}} (1 - \sigma_{3} (z^{(1)}))^{1 - y^{(1)}} \times (\sigma_{3} (z^{(2)}))^{y^{(2)}} (1 - \sigma_{3} (z^{(2)}))^{1 - y^{(2)}} \times \cdots \times (\sigma_{3} (z^{(n)}))^{y^{(n)}} (1 - \sigma_{3} (z^{(n)}))^{1 - y^{(n)}} \} \\ &= - \{ \log (\sigma_{3} (z^{(1)}))^{y^{(1)}} (1 - \sigma_{3} (z^{(1)}))^{1 - y^{(1)}} + \log (\sigma_{3} (z^{(2)}))^{y^{(2)}} (1 - \sigma_{3} (z^{(2)}))^{1 - y^{(2)}} + \cdots + \log (\sigma_{3} (z^{(n)}))^{y^{(n)}} (1 - \sigma_{3} (z^{(n)}))^{1 - y^{(n)}} \} \\ &= - \sum_{j = 1}^{n} \left[ \log (\sigma_{3}(z^{(j)}))^{y^{(j)}} (1 - \sigma_{3} (z^{(j)}))^{1 - y^{(j)}} \right] \\ &= - \sum_{j = 1}^{n} \left[ \log (\sigma_{3} (z^{(j)}))^{y^{(j)}} + \log (1 - \sigma_{3} (z^{(j)}))^{1 - y^{(j)}} \right] \\ &= - \sum_{j = 1}^{n} \left[ y^{(j)} \log (\sigma_{3} (z^{(j)})) + (1 - y^{(j)}) \log (1 - \sigma_{3} (z^{(j)})) \right] \\ &= \underline{ \sum_{j = 1}^{n} \left[ - y^{(j)} \log (\sigma_{3} (z^{(j)})) - (1 - y^{(j)}) \log (1 - \sigma_{3} (z^{(j)})) \right]} \\ \end{alignat*}

損失関数L_2のグラフの描画

最後にロジスティック回帰の損失関数の値を具体的に示すグラフを描画していきます。

import

import matplotlib.pyplot as plt
import numpy as np

関数の定義

# シグモイド関数を定義
def sigmoid(z):
    return 1.0 / (1.0 + np.exp(-z))

# y = 1 の損失値を計算する関数
def loss_1(z):
    return - np.log(sigmoid(z))

# y = 0 の損失値を計算する関数
def loss_0(z):
    return - np.log(1 - sigmoid(z))

関数の実装

# 0.1間隔で-10以上10未満のデータを生成
z = np.arange(-10, 10, 0.1)

sigma_z = sigmoid(z)                # シグモイド関数をインスタンス化
c1      = [loss_1(x) for x in z]    # y = 1の損失値を計算する関数をインスタンス化
c0      = [loss_0(x) for x in z]    # y = 0の損失値を計算する関数をインスタンス化

グラフの描画

# 結果をプロット
plt.plot(sigma_z, c1, 
         label     = 'L(w, b) if y = 1')
plt.plot(sigma_z, c0, 
         label     = 'L(w, b) if y = 0', 
         linestyle = '--')

plt.ylim(0.0, 5.1)
plt.xlim([0, 1])
plt.xlabel('$\sigma(z)$')
plt.ylabel('$L_2(w, b)$')
plt.legend(loc = 'best')

# グラフを保存
plt.savefig('3-4.png')

plt.show()

グラフを読み解く

このグラフは次のように読むことができます。

  • クラス1であると正しく予測できた場合
    \sigma_3 (z)の値が高い(=クラス1である確率が高い)ほど、損失関数の値も0に近づく。

  • クラス0であると正しく予測できた場合
    \sigma_3 (z)の値が低い(=クラス0である確率が高い)ほど、損失関数の値も0に近づく。

これまでの議論の流れを追えており、かつグラフの読み取りが難なくできる人ならば自明ですが、慣れていない人はそれぞれ個の情報をつなぎ合わせることは意外と難しいものです。参考に【Python】ロジスティック回帰を使ってクラスの確率を予測するモデルの構築Part1の際に描画したシグモイド関数も下図のとおり掲載しておきますので、損失関数との関係を整理するのに役立ててください。

参考文献

\bf{\textcolor{red}{記事が役に立った方は「いいね」を押していただけると、すごく喜びます \ 笑}}
ご協力のほどよろしくお願いします。

Discussion