入力を非線形変換したパーセプトロンでも基底変換を実験しましたが、ロジスティック回帰でも試してみます。パーセプトロンで試したときは、変換は適当に決めてしまいましたが、今回はガウス基底を使ってみます。名前がついてるってだけで、適当に選んだことには違いないですが。
まず基本に立ち返って、基底関数を使って入力を非線形変換すると何が嬉しいのかを確認してみます。書籍「パターン認識と機械学習(上)」にわかりやすい図が載っていましたので、図を再現するようなコードを書いてみました。
左のグラフが、元のデータ点の分布です。これをガウス基底
を使って変換したものが右のグラフです。基底の中心は、としています。左のグラフの緑の線が基底の等高線です。基底の中心からの距離(のようなもの)が新たな軸となりますので、元のデータでは離れた位置に分布している青色の点も、の軸で見れば、中心からの距離が同じなので、基底変換後は縦軸ではかたまった分布となっています。一方の軸で見れば、距離が離れていますから基底変換後の横軸では離れた位置に分布しています。このような変換を行うことで、では線形分離できなかった分布が、では線形分離することができるようになりました。複雑な分布のデータも、適切な変換を行うことで線形識別のモデルが適用できるということですね。
ということで、以下のような線形で分離できないようなデータもロジスティック回帰で識別できることが確認できました。 基底は16個使っていて、中心やは適当に決めたんですが、この取り方によっては計算がうまくいかないことがありました。調べてみると、シグモイド関数のオーバーフローが原因のようでした。プログラムの動作を追って原因を見つけたのではなく、webで調べて解決したんですが。この辺りはいろいろ対策や工夫があるようです。まあまずはこういう現象を実際に体験できたことが重要ですね。計算がうまくいかないとき、シグモイド関数への入力を調べみるっていう発想ができるので。というか数値計算では常識レベルの話なのかな。
今回のコードです。
# 入力を非線形変換したロジスティック回帰 import matplotlib.pyplot as plt import numpy as np from matplotlib import cm # ガウス基底 def gaussian_basis(x1, x2, mu1, mu2): s = 1.0 return np.exp(-((x1 - mu1)**2 + (x2 - mu2)**2)/(2.0*(s**2))) # 基底変換 def phi(x1, x2): return np.array([gaussian_basis(x1, x2, mu0, mu1) for (mu0, mu1) in center]) # 判別結果 def f(x1, x2): if sigmoid(np.dot(w.T, phi(x1, x2))) < 0.5: return 0 else: return 1 # 交差エントロピー誤差 def Ew(y, t): ew = 0 for i in range(N): print(y[i]) ew -= np.log(t[i]*y[i] + (1-t[i])*(1-y[i])) return ew # 教師データの属するクラスを返す def teaching(x, y): if ((x-0.5)**2 + (y-0.5)**2 < 0.25**2 and x < 0.5) or ((x-0.5)**2 + (y-0.5)**2 > 0.25**2 and x > 0.5) : return 0 else: return 1 # シグモイド関数 def sigmoid(a): return 1.0/(1 + np.exp(-a)) # 重み付け行列Rを計算 def R_(y): R_ij = np.empty(N) for i in range(N): R_ij[i] = y[i] * (1 - y[i]) return np.diag(R_ij) # ランダムシードを固定 np.random.seed(0) # 訓練データ数 N = 200 # 入力次元数M M0 = 4 M = M0**2 # クラス数 K = 2 # 教師データ作成 x = np.random.uniform(0, 1, [N, 2]) t = np.vectorize(teaching)(x[:, 0], x[:, 1]) # 1 of K符号 T = np.eye(K)[t] plt.xlim(0, 1) plt.ylim(0, 1) plt.scatter(x[t == 0, 0], x[t == 0, 1], color="blue", alpha=0.5) plt.scatter(x[t == 1, 0], x[t == 1, 1], color="red", alpha=0.5) plt.show() # ガウス基底の中心点 center = np.array([[x0, x1] for x0 in range(M0) for x1 in range(M0)]) # 行列X(ガウス基底を通す) X = np.empty([N, M]) for i in range(N): X[i, :] = phi(x[i, 0], x[i, 1]) # パラメータw初期化 w = np.random.uniform(-1, 1, M) # 出力y y = np.vectorize(sigmoid)(np.dot(w, X.T)) # 重み付け行列R R = R_(y) # ヘッセ行列 H = np.dot(np.dot(X.T, R), X) # ニュートン法 for i in range(7): w -= np.dot(np.dot(np.linalg.inv(H), X.T), (y -t)) # yを再計算 y = np.vectorize(sigmoid)(np.dot(w, X.T)) # R,Hを再計算 R = R_(y) H = np.dot(np.dot(X.T, R), X) # グラフ表示用の判別結果 a, b = np.meshgrid(np.linspace(0, 1, 500), np.linspace(0, 1, 500)) vec_f = np.vectorize(f) c = vec_f(a, b) plt.xlim(0, 1) plt.ylim(0, 1) plt.scatter(x[t == 0, 0], x[t == 0, 1], color="blue", alpha=0.5) plt.scatter(x[t == 1, 0], x[t == 1, 1], color="red", alpha=0.5) plt.contourf(a, b, c, alpha=0.2, cmap=cm.coolwarm) plt.show()