はじめに
Notes on machine learning のニューラルネットワークに関するメモのPart 2の部分を追っていきます。
前回は線形回帰でしたが、今回はロジスティック回帰です。
前回の記事:
www.madopro.net
ロジスティック回帰
今回は2クラス分類問題を考えます。2次元の入力に対し、
が所属するクラス
を予測します。
予測にはロジスティック回帰というモデルを使用します。Part 1では入力と出力がともに1次元のモデルを考えましたが、今回は次のように入力が2次元、出力
は1次元のモデルを考えます。
このモデルの出力は、入力
がクラス
に属している確率を表すこととします。つまり
と表現できます。
重みの学習に使用する入力の用意
前回同様、人工的に入力データを用意します。下記画像のように、2次元平面の(-1, 0)周辺に散らばっている赤クラス()と、(1, 0)周辺に散らばっている青クラス(
)の2クラスを用意します。
上記のデータを生成するには、次のようなコードを使用します。
# 2クラス分類: 青(t = 1)と赤(t = 0) num_of_samples_per_class = 20 red_mean = [-1, 0] # 赤クラスは(-1, 0)周辺に分布 blue_mean = [1, 0] # 青クラスは(1, 0)周辺に分布 std_dev = 1.2 # 2クラスのサンプルを生成 x_red = np.random.randn(num_of_samples_per_class, 2) * std_dev + red_mean x_blue = np.random.randn(num_of_samples_per_class, 2) * std_dev + blue_mean # 生成したサンプルのマージ X = np.vstack((x_red, x_blue)) # サンプルのクラス t = np.vstack((np.zeros((num_of_samples_per_class, 1)), np.ones((num_of_samples_per_class, 1))))
これで、Xには入力の値、tには正解ラベルが格納されている状態になりました。
モデルの出力
モデルの出力は、入力
に重み
を乗じ、ロジスティック関数
を適用してあげることで得られます。
この時のロジスティック関数は次のように定義します。
これを素直にPythonに落としていくと次のようになります。
# ロジスティック関数の定義 def logistic(z): return 1 / (1 + np.exp(-z)) # ロジスティック回帰のモデルを定義 def nn(x, w): return logistic(x.dot(w.T))
モデルの出力は、入力がクラス
である確率でしたので、
が0.5以下の時は
が属するクラスは
,
が0.5より大きいときは
が属するクラスは
と予測することができます。
# モデルの出力をもとにクラスtを予測する関数 def nn_predict(x, w): return np.around(nn(x, w))
コスト関数
ロジスティック関数のコスト関数には、クロスエントロピーが使用されることが多いです。
クロスエントロピーを最小化することは、尤度を最大化することと同等のようです。*1
クロスエントロピーは次のように定義されます。
# コスト関数 (クロスエントロピー) def cost(y, t): return -np.sum(np.multiply(t, np.log(y)) + np.multiply((1 - t), np.log(1 - y)))
最初に用意した入力Xと正解ラベルtにを使用して、コスト関数を可視化すると次のようになります。色が濃い部分が値が小さい部分となります。この色が濃い部分に入るような重みを見つけるために最適化を行っていくことになります。
最急降下法
Part 1同様、最急降下法を使用してコスト関数を最適化していきます。
具体的な数式の導出過程は長くなるので省略してしまいますが、重みの更新は以下のように行います。
ただし、は次のように表せます。
これもコードに落とすと次のようになります。
# 勾配の定義 def gradient(w, x, t): return (nn(x, w) - t).T * x # Δw def delta_w(w_k, x, t, learning_rate): return learning_rate * gradient(w_k, x, t)
それでは今までのコードを使って重みの更新をしていきます。
# 初期重みをセット w = np.asmatrix([-4, -2]) # 学習率 learning_rate = 0.05 # 最急降下法のイテレーションを開始 num_of_iterations = 10 w_iter = [w] # 後の可視化のために、イテレーションごとの重みをここに保存します for i in range(num_of_iterations): dw = delta_w(w, X, t, learning_rate) w = w - dw w_iter.append(w)
イテレーションが進むにつれて重みがどのように更新されていくかを可視化したものが下図になります。
初期重みから、色が濃い部分に向かって重みが最適化されている様子が確認できます。
学習結果の可視化
以上の学習で得られた重みを使用して、入力
がどちらのクラスに分類されるかを可視化します。
# 入力(x_1, x_2)を[-4, 4]の範囲で可視化するため、[-4, 4]を200分割したメッシュを用意します num_of_xs = 200 xs1 = np.linspace(-4, 4, num=num_of_xs) xs2 = np.linspace(-4, 4, num=num_of_xs) xx, yy = np.meshgrid(xs1, xs2) # 各点でモデルの出力がどちらのクラスになるかを、 # nn_predict関数を使用して予測します。 classification_plane = np.zeros((num_of_xs, num_of_xs)) for i in range(num_of_xs): for j in range(num_of_xs): classification_plane[i, j] = nn_predict(np.asmatrix([xx[i, j], yy[i, j]]), w) cmap = ListedColormap([ colorConverter.to_rgba('r', alpha=0.30), colorConverter.to_rgba('b', alpha=0.30)]) # 予測したクラスと、元の入力をプロットしてあげます plt.contourf(xx, yy, classification_plane, cmap=cmap) plt.plot(x_red[:, 0], x_red[:, 1], 'ro', label='target red') plt.plot(x_blue[:, 0], x_blue[:, 1], 'bo', label='target blue') plt.grid() plt.legend(loc=2) plt.xlabel('$x_1$', fontsize=15) plt.ylabel('$x_2$', fontsize=15) plt.title('red vs. blue classification boundary') plt.show()
上記コードを実行すると次の図が得られます。確かに、初めに生成した入力を大体分類できるような重みが学習できていることが確認できます。
GitHub
今回使用したJupyter Notebookは下記においてあります。
github.com