はじめに
『ゼロから作るDeep Learning 4 ――強化学習編』の独学時のまとめノートです。初学者の補助となるようにゼロつくシリーズの4巻の内容に解説を加えていきます。本と一緒に読んでください。
この記事は、7.3節の内容です。DeZeroを利用して線形回帰の学習を実装します。
【前節の内容】
【他の記事一覧】
【この記事の内容】
7.2 線形回帰
DeZero
ライブラリを使って、線形回帰の学習を実装します。DeZeroフレームワークについては「『ゼロから作るDeep Learning 3』の学習ノート:記事一覧 - からっぽのしょこ」を参照してください。
利用するライブラリを読み込みます。
# ライブラリを読み込み import numpy as np from dezero import Variable import dezero.functions as F # 追加ライブラリ import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation
更新推移をアニメーションで確認するのにmatplotlib
のモジュールを利用します。不要であれば省略してください。
7.2.1 トイ・データセット
まずは、学習に利用するトイ・データセットを作成します。
乱数を使ってデータセットを作成します。
# データ数を指定 N = 100 # トイ・データを生成 x_data = np.random.rand(N) y_data = 5 + 2 * x_data + np.random.rand(N) print(np.round(x_data[:10], 2)) print(np.round(y_data[:10], 2))
[0.89 0.8 0.83 0.44 0.75 0.95 0.86 0.63 0.25 0.47]
[7.03 6.96 7.34 6.38 6.61 7.04 7.69 6.54 6.43 6.92]
0から1の一様乱数をnp.random.rand()
で生成して、データを作成します。
データセットを散布図で確認します。
# トイ・データセットの散布図を作成 plt.figure(figsize=(8, 6), facecolor='white') plt.scatter(x_data, y_data) plt.xlabel('x') plt.ylabel('y') plt.title('N=' + str(N), loc='left') plt.suptitle('$x_n \sim U(0, 1),\ y_n = 2 x_n + 5 + r_n,\ r_n \sim U(0, 1)$', fontsize=15) plt.grid() plt.show()
このデータに合うような直線を求めます。($x$の係数の2が重み、5に一様乱数の期待値0.5を加えた5.5がバイアスの真の値なんだと思う。)
7.2.3 線形回帰の実装
次は、勾配降下法による線形回帰の学習を実装します。Linear
クラスや線形回帰については「ステップ42:線形回帰の実装【ゼロつく3のノート(実装)】 - からっぽのしょこ」、2乗和誤差については「4.2.1:2乗和誤差の実装【ゼロつく1のノート(実装)】 - からっぽのしょこ」や「ステップ42:平均2乗誤差の逆伝播の導出【ゼロつく3のノート(数学)】 - からっぽのしょこ」を参照してください。
勾配降下法により損失関数(平均2乗誤差)が最小となるパラメータを求めます。
# 試行回数を指定 iters = 100 # 学習率を指定 lr = 0.1 # トイ・データセットをVariableインスタンス化 x = Variable(x_data.reshape(N, 1)) y = Variable(y_data.reshape(N, 1)) # パラメータを初期化 W = Variable(np.zeros((1, 1))) # 重み b = Variable(np.zeros(1)) # パラメータ # 推移の確認用のリストを初期化 trace_loss = [] trace_W = [W.data.item()] trace_b = [b.data.item()] # 繰り返し学習 for i in range(iters): # 予測値(線形変換)を計算 y_pred = F.linear(x, W, b) # 損失を計算 loss = F.mean_squared_error(y, y_pred) # 勾配を初期化 W.cleargrad() b.cleargrad() # 勾配(逆伝播)を計算 loss.backward() # 勾配降下法により値を更新 W.data -= lr * W.grad.data b.data -= lr * b.grad.data # 更新値を保存 trace_loss.append(loss.data) trace_W.append(W.data.item()) trace_b.append(b.data.item()) # 損失を表示 print('iter '+str(i+1) + ': loss='+str(loss.data))
iter 1: loss=43.491510711393914
iter 2: loss=23.802291836166432
iter 3: loss=13.054705383037911
iter 4: loss=7.1877490128664645
iter 5: loss=3.9848037739746314
(省略)
iter 96: loss=0.09034900821461835
iter 97: loss=0.09021797442828561
iter 98: loss=0.09009019172859968
iter 99: loss=0.08996557945266682
iter 100: loss=0.08984405893892493
処理の内容については本を参照してください。
学習したパラメータを使って、データごとの予測値と平均2乗誤差を計算します。
# 予測値を計算 y_pred = F.linear(x, W, b) print(np.round(y_pred.data[:10].flatten(), 3)) # 損失を計算 last_loss = F.mean_squared_error(y, y_pred).data print(last_loss)
[7.355 7.151 7.222 6.327 7.036 7.497 7.279 6.765 5.916 6.413]
0.08972555347748817
最終的なパラメータW, b
における損失は、trace_loss
に含まれていないので、計算してlast_loss
とします。
損失関数の推移をグラフで確認します。
# 損失関数の推移を作図 plt.figure(figsize=(8, 6), facecolor='white') plt.plot(np.arange(iters+1), trace_loss+[last_loss]) plt.xlabel('iteration') plt.ylabel('loss') plt.suptitle('Mean Squared Error', fontsize=20) plt.title('lr='+str(lr), loc='left') plt.grid() plt.show()
最後の値last_loss
をリストに格納して、過去の値trace_loss
と+
演算子で結合して描画します。
損失が0に近付いていくことから学習が進んでいるのが分かります。
回帰直線を計算します。
# 回帰直線を計算 x_line = np.linspace(0, 1, num=101) y_line = F.linear(x_line.reshape((-1, 1)), W, b).data.flatten() print(np.round(x_line[:10], 3)) print(np.round(y_line[:10], 3))
[0. 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09]
[5.343 5.366 5.388 5.411 5.433 5.456 5.479 5.501 5.524 5.547]
x軸の値x_line
を作成して、要素ごとに予測値を計算します。内積を計算できるように形状を合わせる必要があります。
トイ・データセットの散布図に、回帰直線を重ねて描画します。
# 残差を計算 y_residual = y_pred.data.flatten() - y_data # パラメータラベルを作成 param_label = 'iter:' + str(iters) param_label += ', lr=' + str(lr) param_label += ', W='+str(np.round(W.data.item(), 2)) param_label += ', b='+str(np.round(b.data.item(), 2)) param_label += ', loss='+str(np.round(last_loss, 3)) # 回帰直線を作図 plt.figure(figsize=(12, 9), facecolor='white') plt.quiver(x_data, y_data, np.repeat(0, N), y_residual, scale_units='xy', scale=1, units='dots', width=0.1, headwidth=0.1, fc='none', ec='gray', linewidth=1.5, linestyle=':', label='residual') # 残差 plt.scatter(x_data, y_data, label='data') # データ plt.plot(x_line, y_line, color='red', label='predict') # 回帰直線 plt.xlabel('x') plt.ylabel('y') plt.suptitle('Linear Regression', fontsize=20) plt.title(param_label, loc='left') plt.grid() plt.legend() plt.show()
データごとに観測値と予測値の差を計算して、plt.quiver()
で線分を描画します。
回帰直線の推移をアニメーションで確認します。
・作図コード(クリックで展開)
# 図を初期化 fig = plt.figure(figsize=(12, 9), facecolor='white') fig.suptitle('Linear Regression', fontsize=20) # 作図処理を関数として定義 def update(i): # 前フレームのグラフを初期化 plt.cla() # i回目のパラメータを取得 W = np.array([[trace_W[i]]]) b = np.array([trace_b[i]]) # 予測値を計算 y_pred = F.linear(x, W, b) # 残差を計算 y_residual = y_pred.data.flatten() - y_data # 回帰直線を計算 y_line = F.linear(x_line.reshape((-1, 1)), W, b).data.flatten() # パラメータラベルを作成 param_label = 'iter:' + str(i) param_label += ', lr=' + str(lr) param_label += ', W='+str(np.round(trace_W[i], 3)) param_label += ', b='+str(np.round(trace_b[i], 3)) if i < iters: param_label += ', loss='+str(np.round(trace_loss[i], 5)) else: param_label += ', loss='+str(np.round(last_loss, 5)) # 回帰直線を描画 plt.quiver(x_data, y_data, np.repeat(0, N), y_residual, scale_units='xy', scale=1, units='dots', width=0.1, headwidth=0.1, fc='none', ec='gray', linewidth=1.5, linestyle=':', label='residual') # 残差 plt.scatter(x_data, y_data, label='data') # データ plt.plot(x_line, y_line, color='red', label='predict') # 回帰直線 plt.xlabel('x') plt.ylabel('y') plt.title(param_label, loc='left') plt.grid() plt.legend() #plt.ylim(0, 10) # gif画像を作成 ani = FuncAnimation(fig=fig, func=update, frames=iters+1, interval=100) # gif画像を保存 ani.save('LinearRegression.gif')
フレームごとにtrace_*
からi
番目の値を取り出して回帰直線を描画する処理を関数update()
として定義して、FuncAnimation()
でアニメーション(gif画像)を作成します。
データへの当てはまりがよくなる様子を確認できます。
パラメータの更新値の推移をグラフで確認します。
# パラメータの更新値の推移を作図 plt.figure(figsize=(8, 6), facecolor='white') plt.plot(np.arange(iters+1), trace_W, label='W') plt.plot(np.arange(iters+1), trace_b, label='b') plt.xlabel('iteration') plt.ylabel('value') plt.suptitle('parameters', fontsize=20) plt.title('lr='+str(lr), loc='left') plt.grid() plt.legend() plt.show()
(重みが2、バイアスが5.5に近付いていますし、始めの解釈でいいですよね。)
この節では、線形回帰を実装しました。次節では、ニューラルネットワークによる非線形回帰を実装します。
参考文献
おわりに
ほぼ同じ内容を書いたことがある気がしますがまぁ、、
【次節の内容】