ロジスティック回帰の復習

##はじめに

ロジスティック回帰は分類問題で初めに勉強する手法です。
その導出方法や実装例はググればたくさん見つかります。
僕も最適化の方法を教科書で勉強し、<code>scikit-learn</code>で動かしてみて分かった気になっていました。

しかし、実データに適用してみて、最適化の数式や動かし方は勉強したけれど、実際にどのようにパラメータが更新されるかよく分かっていなかったと気付きました。

なので、今回は「**どのようにパラメータが更新されるか**」について復習します。
（ほとんど自分用のメモです。。）

##誤差関数

今回は簡単のため、二値分類問題を想定します。

二値分類の誤差関数は以下の「交差エントロピー型誤差関数」で表せます。

``` math
\log L = -\sum_{i=1}^N \left(y^{(i)}\log f_\theta(x^{(i)})+(1-y^{(i)})\log(1-f_\theta(x^{(i)}))\right)
```

ここで、$f_\theta(x^{(i)})=\sigma(\theta^{\rm{T}}x^{(i)})$ であり、入力 $x^{(i)}$ に対する予測値を表します。

さて、これをパラメータ $\theta$ の各値で微分すると以下のようになります。

```math
\frac{\partial\log L}{\partial \theta_j}=\sum_{i=1}^N\left(f_\theta(x^{(i)})-y^{(i)}\right)x_j^{(i)}
```

このように予測値と正解ラベルの誤差のようなシンプルな形になります。

※微分の式変形に関しては[ここ](http://tkengo.github.io/blog/2016/06/16/yaruo-machine-learning6/)の解説が丁寧でした。

簡単な勾配法の場合、パラメータ更新式は以下のように書けます。

```math
\begin{eqnarray}
\theta_j&=&\theta_j-\eta\left(\frac{\partial\log L}{\partial \theta_j}\right)\\
\theta_j&=&\theta_j-\eta\left(\sum_{i=1}^N\left(f_\theta(x^{(i)})-y^{(i)}\right)x_j^{(i)}\right)
\end{eqnarray}

```

##パラメータの更新
それでは実際に簡単な値で更新を確認してみます。

せっかくなので、<code>python</code>で簡単な勾配法による更新を実装します。

まず、準備としては以下のようにシグモイド関数と勾配計算を行う関数を用意します。

```py
import numpy as np

def sigmoid(theta, x):
    return 1 / (1 + np.exp(-np.dot(x, theta)))

def grad(y, theta, x):
    return np.dot(sigmoid(theta, x) - y, x)

def update(y, theta, x, eta=0.1):
    return theta - eta * grad(y, theta, x)
```

勾配計算は行列でまとめて計算します。

以下を入力データとして用意します。

```py
x = np.array([[0, 1], [0, 1], [0, 0], [1, 1],
              [1, 1], [0, 0], [1, 0]])
y = np.array([1, 1, 1, 1, 0, 0, 0])
```

今回は2変数とします。

パラメータですが、簡単のため全て「0.5」で初期化しておきます。

```py
theta = np.ones(2) * 0.5
```

今回の入力データは何れもダミー変数を想定しています。
表で書くと以下の通りです。

| $x_1$ | $x_2$| $y$ |
|:-:|:-:|:-:|
|  0 | 1  | 1  |
| 0  | 1  | 1  |
|  0 | 0  | 1  |
|  1 | 1  | 1  |
|  1 | 1  | 0  |
|  0 | 0  | 0  |
|  1 | 0  | 0  |

$x_1$ は性別（0：女性、1：男性）のダミー変数、
$x_2$ は大人・子供（0：子供、1：大人）のダミー変数と、
$y$ はある商品のレビュー結果（0：嫌い、1：好き）と仮定します。
この場合、データから

 - 女性の方がこの商品を好む
 - 大人の方がこの商品を好む
 
という傾向が分かります。
ダミー変数の場合、パラメータの値は以下のように解釈できます。

- 男性の場合、女性より $f_\theta(x)$ （予測結果）が小さくなる。（$\theta_1$ はマイナスになる。）
- 大人の場合、子供より $f_\theta(x)$ （予測結果）が小さくなる。（$\theta_2$ はプラスなる。）

では、勾配法によってパラメータがそのように更新されるかを確認します。

$\theta_1$ に関して1つ目の観測データでの更新を考えます。
勾配は以下のようになります。
$(f_\theta(x_1)-y)\cdot x_1=(f_\theta(x_1)-y)\cdot 0=0$
誤差は計算していませんが、$x_1$ が0なので、勾配は**0**になります。
このことからパラメータ更新に関して以下のことが言えます。

>
**正解ラベルに関わらず、変数の値が0の観測データは、その変数の更新に影響しない。**


当たり前といえば当たり前ですが、これを知っていないとパラメータの推定結果に戸惑うこともあります。
では、観測データが0でないデータで考えます。
まず、各勾配は以下のように計算できます。
$(f_\theta(x_4)-y_4)\cdot x_4=\sigma(1\times0.5+1\times0.5)=(0.73-1)\cdot 1=-0.27$
$(f_\theta(x_5)-y_5)\cdot x_5=\sigma(1\times0.5+1\times0.5)=(0.73-0)\cdot 1=0.73$
$(f_\theta(x_7)-y_7)\cdot x_7=\sigma(1\times0.5+0\times0.5)=(0.62-0)\cdot 1=0.62$
$x_4$ の場合、勾配は「**負**」の方向なのでパラメータを大きくします。
$x_5,x_7$ の場合、勾配は「**正**」の方向なのでパラメータを小さくします。

$\theta_2$ に関しても同様の計算を行います。

すると、更新結果は以下のようになります。

```py
update(y, theta, x)
# array([ 0.39154235,  0.52929642])
```
$x_1$は小さく、$x_2$ は大きくなっています。 

値を繰り返し更新してみます。

```py
for _ in range(100):
    theta = update(y, theta, x, eta=0.01)
# array([-0.34901291,  0.84218628])
```
想定したパラメータに更新されているのが分かります。

## さいごに
今回は、ロジスティック回帰のパラメータ更新に関して復習しました。
パラメータの更新に主眼を置いたので、更新アルゴリズムはずいぶんと簡素化しています。
実際はニュートン法などの最適化アルゴリズムが使われることが多いので、今回のような簡単な勾配計算はあまり使われないです。