PyTorch深層学習⑧ロジスティック回帰:理論編
前回は、線形回帰をPyTorchで実装しました。今回はロジスティック回帰について解説します。
線形回帰との違いはなんでしょうか。
「回帰」という名前がついてはいますが、やっていることは「分類」になります。
ロジスティック回帰とは
二項分類
ロジスティック回帰に関しては例を紹介した方がわかりやすいでしょう。
例えば、あるメールの文章からそのメールがスパムであるかどうかを予測します。これはロジスティック回帰の問題として考えることができます。
つまり、ロジスティック回帰は、「はい」「いいえ」の答えを期待する質問に答えるのにための手法です。
他にも、クレジットカードの取引のデータからそれが詐欺かどうかを判断するのも「はい」「いいえ」の二つの可能性のどちらかを選ぶことになるのでロジスティック回帰の問題として考えることができます。
「この患者は特定の病気を持っていますか?」といった質問も同様です。
つまりは、ロジスティック回帰では、与えられたデータを二つの可能なカテゴリ(クラス、Class)のどちらか一つに分類しています。よって、問題のタイプとしては、二項分類(Binary Classification)になります。
では、二つの可能性をどのように区別して判断するのでしょうか。
確率を予測
ロジスティック回帰では、直接に「はい」「いいえ」を予測するわけではありません。ロジスティック回帰は、データを見てそれが特定のクラスに属する確率を計算します。
先ほどの例で言えば、
メールがスパムである確率
取引が詐欺である確率
患者が病気である確率
などになります。
そして、その確率が特定の閾値を超えた場合(例えば50%を超えた場合)、ロジスティック回帰はそのメールをスパム、その取引を詐欺、その患者を病気と判断します。
問題によりますが、ロジスティック回帰は、さまざまな特性(年齢、性別、購入履歴など)を考慮に入れて、これらの確率を計算します。これにより、より具体的かつ個別化された予測が可能となります。
ここからは具体的に、扱う問題を定義して解説します。
問題定義
正解データ
二つのカテゴリであればなんでも良いので、次のようなサンプルデータを考えます。

カテゴリを次のように定義します。
🔵 クラス1
🔴 クラス2
これで各点がクラス1である確率を計算できれば良いわけです。確率が50%より大きかったらクラス1であると判断していることにしましょう。
入力値と出力値
では、確率を予測するために使える情報はなんでしょうか。上図を見ると点がある位置によってクラスが分かれているようです。
そこで$${x_1}$$と$${x_2}$$による位置を入力値としてクラス1である確率を予測するとします。つまり、下図のようにクラスが分からないデータを入力値として各点のクラスを予測することになります。

つまり、入力値(各点の位置)を入れると予測値(クラス1の確率)を出力するモデルを考えます。
$$
\begin{aligned}
\text{入力値:}\ \boldsymbol{x} &= \begin{bmatrix}
x_1 \\
x_2
\end{bmatrix} \\
\\
\text{予測値:} \ \hat{p} &= \text{モデル}( \boldsymbol{x})
\end{aligned}
$$
$${p}$$は確率ですが、予測値なので^を付けて$${\hat{p}}$$としています。
もし、$${\hat{p} > 0.5}$$ならクラス1、さもなければクラス2と予測します。
では、どうすれば各点の位置からクラス1の確率を計算することができるのでしょうか。
解決アプローチ
線形回帰
この問題は線形回帰で解決できるでしょうか。
$$
\begin{aligned}
\text{入力値:}\ \boldsymbol{x} &= \begin{bmatrix}
x_1 \\
x_2
\end{bmatrix} \\
\\
\text{出力値:} \ z &= \text{線形回帰モデル}( \boldsymbol{x})
\end{aligned}
$$
単純な線形回帰モデルを考えます。二つの入力値$${x_1, x_2}$$のそれぞれに重みを掛けて足し合わせてバイアスを加えます。
それを次のように定義します。
$$
\begin{aligned}
z &= W \boldsymbol{x} + b \\
&= \begin{bmatrix} w_1 & w_2 \end{bmatrix}
\begin{bmatrix}
x_1 \\
x_2
\end{bmatrix} + b \\
&= w_1 x_1 + w_2 x_2 + b
\end{aligned}
$$
これはPyTorchの nn.Linear を使えば計算できます。入力値が二つで出力が一つなので以下のように定義できます。
model = nn.Linear(2, 1)ただし、このままだと問題が生じます。
線形回帰モデルの出力$${z}$$は範囲に制限のない実数なので、そのままでは確率として解釈することができません。
$$
-\infty < z < \infty
$$
つまり、問題は線形回帰モデルは任意の値を出力できるのに対し、確率の値は 0 と 1 の間に制限される必要があることです。
したがって、何らかの方法で出力$${z}$$を確率に対応させる要があります。
ロジット
逆に、確率から制限の無い実数値に対応させる方法を考えてみます。
つまり、確率が高いほど、正の無限大に向かう値にマッピングします。また、確率が低いほど、負の無限大に向かう値にマッピングします。
そこでまず、オッズを考えます。オッズはクラス1の確率$${p}$$に対して次のように定義します。
$$
\text{オッズ} = \dfrac{p}{1-p}
$$
このように、オッズはクラス1の確率$${p}$$とクラス1でない確率(クラス2の確率)$${1 - p}$$の比になっています。
クラス1のオッズは、確率$${p}$$が1に近づくと正の無限大に近づきます。確率からオッズへの変換は単調であり、確率が増加するにつれてオッズも増加します。また、逆に、オッズが減少すると確率も減少することになります。
したがって、確率とオッズの間で相互に変換できます。
しかし、確率$${p}$$が0に近づくとオッズも0に近づくので、このままでは確率$${p}$$を無制限の実数に対応させることはできません。
そこで、オッズの自然対数を計算します。 この値をロジット(logit)と呼びます。
$$
\text{logit} = \log \text{odds} = \log \dfrac{p}{1-p}
$$
この値の範囲は、負の無限大から正の無限大までになります。
これは、次のように理解できます。オッズは確率$${p=0.5}$$で1となります。また、確率$${p}$$が0.5より小さくなるとオッズの値は1より小さくなります。確率$${p}$$が0に近づくとオッズも0に近づきます。このため、オッズの対数であるロジットは確率$${p}$$が0.5より小さくなると負の無限大へと向かって減少していきます。

こうすることで確率$${p}$$が0から1の値を取ると、対応するロジットは負の無限大から正の無限大の値を取ることになります。また、逆のマッピングも可能です。
よって、オッズとロジットの間の変換も単調であり、相互に変換が可能です。ということは、確率とオッズとロジットの間で相互に変換が可能となります。
つまり、線形回帰モデルからの出力$${z}$$をロジットと見做せば、$${z}$$から確率$${p}$$への変換が可能であることがわかります。
シグモイド
ロジットから確率への変換式を求めましょう。
線形回帰モデルからの出力$${z}$$をもう一度ここに定義します。
$$
z = W \boldsymbol{x} + b
$$
これをロジットとみなします。ロジットは以下のように定義されていました。
$$
\begin{aligned}
\text{logit} &= \log \text{odds} \\
&= \log \dfrac{p}{1-p}
\end{aligned}
$$
よって、
$$
z = \log \dfrac{p}{1-p}
$$
両辺に指数関数$${e}$$を適用すると、
$$
e^z = \dfrac{p}{1-p}
$$
確率$${p}$$の式として書き直すと、
$$
p = \dfrac{1}{1 + e^{-z}}
$$
これはロジットを確率の値にマッピングする式であり、シグモイド(Sigmoid)関数と呼びます。シグモイド関数はよくシグマ$${\sigma}$$を使って表現されます。
$$
p = \sigma(z) = \dfrac{1}{1 + e^{-z}}
$$

簡単にまとめると「シグモイド関数でロジットを0から1の値に圧縮する」とも言えますが、確率とオッズとロジットの関係が分かっているとより意味がはっきりします。モデルがより大きなロジットの値を予測することが、より高い確率を予測することに対応しているわけです。
また、シグモイド関数をロジスティック関数と呼ぶこともあります。よく、シグモイド関数とロジスティック関数は同じ意味で使われますが、実際にはロジスティック関数は次のように汎用な定義になっています。
$$
f(x) = \dfrac{L}{1 + e^{-k(x - x_0)}}
$$
ロジスティック関数もシグモイドと同じように上図のようなS字の曲線を描きます。ただし、$${L, k, x_0}$$の値によってはロジスティック関数からの出力は0から1の間に収まるとは限りませんし、中間点や曲線の傾きも変わってきます。
$${x_0}$$は中間点
$${L}$$は$${f(x)}$$の最大値
$${k}$$は曲線の傾きに影響
$${L=1, k=1, x_0 = 0}$$の場合には、ロジスティック関数はシグモイド関数になります。
PyTorchでは、nn.Sigmoid をシグモイド関数として使うことができます。
ロジスティック回帰
これまでの議論をまとめるとロジスティック回帰は線形回帰とシグモイド関数を組み合わせたモデルになります。
$$
\begin{aligned}
\text{入力値:}\ \boldsymbol{x} &= \begin{bmatrix}
x_1 \\
x_2
\end{bmatrix} \\
\\
\text{ロジット:} \ z &= \text{線形回帰モデル}( \boldsymbol{x}) \\
\\
W &= \begin{bmatrix}
w_1 & w_2
\end{bmatrix} \\
z & = W \boldsymbol{x} + b \\
\\
\hat{p} &= \sigma(z)
\end{aligned}
$$
これで、$${\hat{p} > 0.5}$$ならクラス1、さもなければクラス2と予測できます。
あとは、損失関数を決めて訓練するのみです。
損失関数
ベルヌーイ分布
二項分類は要するにコインの表が出る確率を予測するのと同じです。コインの表をクラス1とし、裏をクラス2と考えれば違いはありません。フェアなコインの場合は表・裏の確率はどちらも50%になりますが、その他の問題ではこの確率は状況により様々です。
一般に、このような二値に関する確率分布をベルヌーイ分布(Bernoulli Distribution)と呼びます。
ベルヌーイ分布は、次のように定義されます。
$$
\text{Ber}(\hat{p}, y) = \hat{p}^y (1 - \hat{p})^{(1-y)}
$$
ここで、$${y=1}$$はクラス1、$${y=0}$$はクラス2を意味します。これはケースに分けて考えるとわかりやすくなります。
$$
\text{Ber}(\hat{p}, y) =
\begin{cases}
\hat{p} &\text{ if } y = 1 \\
1 - \hat{p} &\text{ if } y = 0
\end{cases}
$$
例えば、クラス1が60%の確率をもつベルヌーイ分布をグラフにしたら下図になります。

このような分布に従うデータからランダムに選んで結果を確かめることをベルヌーイ試行と呼びます。
二項分布
ベルヌーイ試行を複数回行なった結果は、二項分布に従います。二項分布の定義についてはこちらを参照してください。
例えば、ベルヌーイ試行を3回繰り返して、$${y_1=1, y_2=0, y_3=1}$$ という結果になったとします。各ベルヌーイ試行はランダムに選んだデータによるものなので、独立しています。つまり、1番目の結果が2番目の結果に影響するとことはありません。
よって、$${y_1=1, y_2=0, y_3=1}$$ という結果になった場合、その組み合わせに対する予測確率は次のように計算できます。
$$
\text{Ber}(\hat{p_1}, y_1=1) \times \text{Ber}(\hat{p_2}, y_2=0) \times \text{Ber}(\hat{p_3}, y_3=1)
$$
$${\hat{p_1}, \hat{p_2}, \hat{p_3}}$$は、それぞれのベルヌーイ試行に対してモデルがクラス1を予想した確率です。
例えば、$${\hat{p_1}=0.6, \hat{p_2}=0.2, \hat{p_3}=0.8}$$だと、
$$
\begin{aligned}
&\text{Ber}(\hat{p_1}, y=1) \times \text{Ber}(\hat{p_2}, y=0) \times \text{Ber}(\hat{p_3}, y=1) \\
&= 0.6 \times (1 - 0.2) \times 0.8 \\
&= 0.384
\end{aligned}
$$
となります。この確率が高いほど、予測の精度が良いことになります。
当然ですが、各ベルヌーイ試行に対して予測精度が高ければ全体としての予測精度が高くなります。
例えば、$${\hat{p_1}=0.99, \hat{p_2}=0.01, \hat{p_3}=0.99}$$だと、
$$
\begin{aligned}
&\text{Ber}(\hat{p_1}, y=1) \times \text{Ber}(\hat{p_2}, y=0) \times \text{Ber}(\hat{p_3}, y=1) \\
&= 0.99 \times (1 - 0.01) \times 0.99 \\
&\approx 0.97
\end{aligned}
$$
つまり、この値が大きくなるようにモデルが各入力値に対して正確な確率を予測できるように訓練すれば良いわけです。
バイナリクロスエントロピー
確率の値は1より小さく、また1 未満の値を何度も乗算すると非常に小さな数になったりすることがあります。コンピューターには維持できる精度に限界があるため、0になってしまう可能性があります。また、どれか一つでも正解のクラスの確率が0だと全体の確率が0になってしまいます。
よって、自然対数を取ることで掛け算を足し算に変更します。
ベルヌーイ試行を$${n}$$回繰り返したとすると、
$$
\log \text{Ber}(\hat{p_1}, y_1) \times \text{Ber}(\hat{p_2}, y_2) \times \dots \times \text{Ber}(\hat{p_n}, y_n) \\
= \log \text{Ber}(\hat{p_1}, y_1) + \log \text{Ber}(\hat{p_2}, y_2) + \dots + \log \text{Ber}(\hat{p_n}, y_n) \\
= \sum\limits_{i=1}^N \log \text{Ber}(\hat{p_i}, y_i)
$$
機械学習の損失関数は、その値を小さくするように訓練するので、マイナスを掛けます。また、$${n}$$が増えると損失値がどんどん膨らむので、平均を計算します。これをバイナリクロスエントロピー損失関数(Binary Cross-Entropy Loss)と呼びます。
$$
\text{BCELoss} = - \dfrac{1}{N} \sum\limits_{i=1}^N \log \text{Ber}(\hat{p_i}, y_i)
$$
この損失値が小さくなるようにモデルの訓練を行えばロジスティック回帰のモデルを最適化できることになります。
ちなみに、$${\text{Ber}(\hat{p_i}, y_i)}$$をさらに展開すると次のようになります。
$$
\begin{aligned}
\text{BCELoss} &= - \dfrac{1}{N} \sum\limits_{i=1}^N \log \text{Ber}(\hat{p_i}, y_i) \\
&= - \dfrac{1}{N} \sum\limits_{i=1}^N \log \hat{p_i}^{y_i} (1 - \hat{p_i})^{(1-y_i)} \\
&= - \dfrac{1}{N} \sum\limits_{i=1}^N \left[ \log \hat{p_i}^{y_i} + \log (1 - \hat{p_i})^{(1-y_i)} \right] \\
&= - \dfrac{1}{N} \sum\limits_{i=1}^N \biggl[ y_i \log \hat{p_i} + (1-y_i) \log (1 - \hat{p_i}) \biggr]
\end{aligned}
$$
随分と複雑に見えますが、心配ありません。PyTorchでは nn.BCELoss を使うとこの計算ができます。
次回はロジスティック回帰の実装を行います。
(続く)
