NumPyでロジスティック回帰を実装する
ディープラーニング以前 (〜2010年) の機械学習について、はじパタを使って整理しています。
今回は「第6章 線形識別関数」を参考にしながら、ロジスティック回帰をNumPyで実装してみます。
- 作者: 平井有三
- 出版社/メーカー: 森北出版
- 発売日: 2012/07/31
- メディア: 単行本(ソフトカバー)
- 購入: 1人 クリック: 7回
- この商品を含むブログ (5件) を見る
ロジスティック回帰
実装に入る前に、ロジスティック回帰の理論を整理します。ここでは、2値 に分類するタスクを考えます。
シグモイド関数
まずクラス に属する確率を計算するための定式化を行います。
- 2値分類ですので、 の確率と の確率の合計は1です
入力 (列ベクトル) が与えられたときにクラス に分類される事後確率は以下となります。
このときaを導入すると、
事後確率 は以下のシンプルな式になります。
をシグモイド関数といいます。シグモイド関数の値は [0, 1] となりますので、確率を表現するために好都合な関数です。
このシグモイド関数の逆関数をロジット関数といいます。
ロジスティック回帰モデル
次にaの計算方法を定義します。
ここで重み (列ベクトル) を導入し で表現します。
ロジスティック回帰モデルは、二項分布をリンク関数とした一般化線形モデルに分類されます。こちらの投稿も参考にしてみてください。緑本とstatsmodelsを使って、他の一般化線形モデルと比較しています。
交差エントロピー誤差関数
最後に、重み を教師データから獲得するために、最適化すべき尤度関数を定義します。
あるクラスに分類される確率で表現できる2値分類のため、尤度関数として交差エントロピー誤差関数を導入します。
- は教師データ数
- は教師データ
- は入力 に対して予測した確率
この尤度関数を対数化して、負の符号をつけます。
に代入します。
これを で微分します。この が0となるように を学習していきます。
最急降下法
学習方法として、今回は最急降下法で実装します。 は更新量をコントロールするためのハイパパラメータです。
- 初期化: ランダムな重み を設定
- で勾配 を計算
- <- で重みを更新
- 収束するまで2.3.を繰り返す
NumPyで実装
scikit-learn付属のirisデータセットを使って、上を実装してみます。
- 入力 は3次元 (萼片の長さ・幅とバイアス)
- 重み も3次元
- setosaとversicolorのデータのみを使う
- versicolorの場合に とする
import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import load_iris # irisデータセットのロード iris = load_iris() # setosaとversicolorの萼片の長さ・幅のみを使う X = iris.data[iris.target != 2][:, :2].T X = np.concatenate([X, np.ones((1, X.shape[1]))]) # バイアス項 t = iris.target[iris.target != 2] # 分布をチェック plt.scatter(X[0][t == 0], X[1][t == 0], label=iris.target_names[0]) plt.scatter(X[0][t == 1], X[1][t == 1], label=iris.target_names[1]) plt.legend() plt.show()
学習用のコードは以下です。上の式をナイーブに実装しています。
# wを乱数で初期化 def initialize_w(): return np.random.normal(0, size=3).reshape((3, 1)) # versicolorの確率を予測 def predict_probability(w, X): return (np.exp(np.dot(w.T, X)) / (1.0 + np.exp(np.dot(w.T, X)))).reshape(-1) # 損失関数 def loss_function(w, X, t): loss = 0 for i in range(t.shape[0]): loss += t[i]*np.dot(w.T, X[:, i]) - np.log(1 + np.exp(np.dot(w.T, X[:, i]))) return -loss # 損失関数の勾配 def gradient_loss(w, X, t): y = predict_probability(w, X) n = t.shape[0] loss = np.zeros_like(w) for i in range(n): loss += ((y[i] - t[i]) * X[:, i])[:, np.newaxis] return loss # 最急降下法で学習 def gradient_descent(X, t, alpha=0.001): w_t = initialize_w() loss_t = loss_function(w_t, X, t) w_history, loss_history = [w_t], [loss_t] for i in range(1000000): # lossに変化がなくなるまでループする w_t -= gradient_loss(w_t, X, t) * alpha loss_t = loss_function(w_t, X, t) # 収束判定 if loss_t >= loss_history[-1]: break w_history.append(w_t) loss_history.append(loss_t) return w_history, loss_history # 学習 w_history, loss_history = gradient_descent(X, t)
損失関数の値の推移はこちらで、最初にぐっと下がって、あとはダラダラと少しずつ下げていってることがわかります。
得られた重みを使って分類させてみましたところ、精度100%で完全に分離できていることを確認できました。
# 重みの確認 w_hat = w_history[-1] print(w_hat) # [[ 12.30194381] # [-12.92596168] # [-26.27142723]] # 得られた重みで確率を計算 y_probability = predict_probability(w_hat, X) # 精度 y = np.where(y_probability > 0.5, 1, 0) sum(y == t) / t.shape[0] # 1.0
まとめ
今回はNumPyでナイーブなロジスティック回帰を実装しました。