このブログの以下の記事で、最小2乗法と最急降下法を用いて回帰直線を求めている。
上記記事のように回帰直線を求める際、入力データが大きいと計算が難しくなるが、「正規化」や「標準化」の手法を利用すれば、入力データの値をあらかじめ小さく揃えておくことができる。今回は、回帰直線を求める際に入力データを正規化してみたので、そのサンプルプログラムを共有する。
正規化とは、入力データの値の範囲を0~1に揃えておく手法で、入力データのx座標を正規化するには、以下の公式を利用する。
出所:正規化・標準化を徹底解説
なお、入力データのy座標も、以下の公式で正規化できる。
\[
\begin{eqnarray}
y_{norm}^i = \frac{y^i – y_{min}}{y_{max} – y_{min}}
\end{eqnarray}
\]
また、入力データのx座標を正規化した値を元に戻すには、先ほどの公式を変形した、以下の式を利用する。
\[
\begin{eqnarray}
\frac{x^i – x_{min}}{x_{max} – x_{min}} &=& x_{norm}^i \\
x^i – x_{min} &=& x_{norm}^i(x_{max} – x_{min}) \\
x^i &=& x_{norm}^i(x_{max} – x_{min}) + x_{min}
\end{eqnarray}
\]
同様に、入力データのy座標を正規化した値を元に戻すには、以下の式を利用する。
\[
\begin{eqnarray}
y^i = y_{norm}^i(y_{max} – y_{min}) + y_{min}
\end{eqnarray}
\]
入力データの最大値・最小値は、NumPyのmax関数・min関数を利用して算出できる。そのプログラムと実行結果は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | %matplotlib inline import numpy as np import matplotlib.pyplot as plt # 入力データの読み込み input_data = np.array([[33,352], [33,324], [34,338], [34,317], [35,341], [35,360], [34,339], [32,329], [28,283], [35,372], [33,342], [28,262], [32,328], [33,326], [35,354], [30,294], [29,275], [32,336], [34,354], [35,368]]) # x座標、y座標の抜き出し input_data_x = input_data[:, 0] input_data_y = input_data[:, 1] # 入力データの値を散布図で表示 plt.scatter(input_data_x, input_data_y) plt.title("input_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() plt.show() # x座標、y座標それぞれの最大値・最小値を表示 input_data_x_max = input_data_x.max() input_data_x_min = input_data_x.min() input_data_y_max = input_data_y.max() input_data_y_min = input_data_y.min() print("x座標、y座標それぞれの最大値・最小値") print("x座標の最大値 : " + str(input_data_x_max)) print("x座標の最小値 : " + str(input_data_x_min)) print("y座標の最大値 : " + str(input_data_y_max)) print("y座標の最小値 : " + str(input_data_y_min)) |
また、先ほどの正規化/正規化戻しの式を利用して、入力データを正規化/正規化戻しを行った結果は、以下の通りで、正規化した入力データは0~1の範囲内に収まり、正規化戻しを行うと元に戻ることが確認できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | import numpy as np def normalize(input_data): max_val = input_data.max() min_val = input_data.min() if max_val == min_val: return [] else: return (input_data - min_val) / (max_val - min_val) def rev_normalize(input_data_norm, max_val, min_val): if max_val == min_val: return [] else: return input_data_norm * (max_val - min_val) + min_val # 入力データの読み込み input_data = np.array([[33,352], [33,324], [34,338], [34,317], [35,341], [35,360], [34,339], [32,329], [28,283], [35,372], [33,342], [28,262], [32,328], [33,326], [35,354], [30,294], [29,275], [32,336], [34,354], [35,368]]) # x座標、y座標の抜き出し input_data_x = input_data[:, 0] input_data_y = input_data[:, 1] # x座標、y座標をそれぞれ正規化 input_data_x_norm = normalize(input_data_x) input_data_y_norm = normalize(input_data_y) # x座標、y座標(正規化後)を元に戻す input_data_x_rev = rev_normalize(input_data_x_norm , input_data_x.max(), input_data_x.min()) input_data_y_rev = rev_normalize(input_data_y_norm , input_data_y.max(), input_data_y.min()) # 正規化したx座標、y座標の組み合わせを設定 input_data_norm = np.column_stack([input_data_x_norm,input_data_y_norm]) # 正規化前後・正規化戻し前後の値を確認 print("*** 正規化前後・正規化戻し前後の各値 ***") print("*** xの値(正規化前) ***") print(input_data_x) print("*** xの値(正規化後) ***") print(input_data_x_norm) print("*** xの値(正規化戻し後) ***") print(input_data_x_rev) print() print("*** yの値(正規化前) ***") print(input_data_y) print("*** yの値(正規化後) ***") print(input_data_y_norm) print("*** yの値(正規化戻し後) ***") print(input_data_y_rev) print() print("*** x,yの組み合わせ(正規化後) ***") print(input_data_norm) |
さらに、入力データを正規化/正規化戻しをグラフ化した結果は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | %matplotlib inline import numpy as np import matplotlib.pyplot as plt def normalize(input_data): max_val = input_data.max() min_val = input_data.min() if max_val == min_val: return [] else: return (input_data - min_val) / (max_val - min_val) def rev_normalize(input_data_norm, max_val, min_val): if max_val == min_val: return [] else: return input_data_norm * (max_val - min_val) + min_val # 入力データの読み込み input_data = np.array([[33,352], [33,324], [34,338], [34,317], [35,341], [35,360], [34,339], [32,329], [28,283], [35,372], [33,342], [28,262], [32,328], [33,326], [35,354], [30,294], [29,275], [32,336], [34,354], [35,368]]) # x座標、y座標の抜き出し input_data_x = input_data[:, 0] input_data_y = input_data[:, 1] # x座標、y座標をそれぞれ正規化 input_data_x_norm = normalize(input_data_x) input_data_y_norm = normalize(input_data_y) # x座標、y座標(正規化後)を元に戻す input_data_x_rev = rev_normalize(input_data_x_norm , input_data_x.max(), input_data_x.min()) input_data_y_rev = rev_normalize(input_data_y_norm , input_data_y.max(), input_data_y.min()) # 入力データの値を散布図で表示 plt.scatter(input_data_x, input_data_y) plt.title("input_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() plt.show() # 入力データを正規化した値を散布図で表示 plt.scatter(input_data_x_norm, input_data_y_norm) plt.title("input_data_norm") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(-0.5, 1.5) plt.ylim(-0.5, 1.5) plt.grid() plt.show() # 入力データを正規化し戻した値を散布図で表示 plt.scatter(input_data_x_rev, input_data_y_rev) plt.title("input_data_rev") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() plt.show() |
次に、入力データを正規化した後で、最小2乗法と最急降下法を用いて回帰直線を求めた結果は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | %matplotlib inline import numpy as np import matplotlib.pyplot as plt def normalize(input_data): max_val = input_data.max() min_val = input_data.min() if max_val == min_val: return [] else: return (input_data - min_val) / (max_val - min_val) def rev_normalize(input_data_norm, max_val, min_val): if max_val == min_val: return [] else: return input_data_norm * (max_val - min_val) + min_val def da_f(a, b, input_data): ret = 0 input_data_cnt = input_data.shape[0] for tmp in range(input_data_cnt): tmp_x = input_data[tmp, 0] tmp_y = input_data[tmp, 1] ret = ret + (( a * tmp_x + b - tmp_y ) * tmp_x) / input_data_cnt return ret def db_f(a, b, input_data): ret = 0 input_data_cnt = input_data.shape[0] for tmp in range(input_data_cnt): tmp_x = input_data[tmp, 0] tmp_y = input_data[tmp, 1] ret = ret + ( a * tmp_x + b - tmp_y ) / input_data_cnt return ret # 入力データの読み込み input_data = np.array([[33,352], [33,324], [34,338], [34,317], [35,341], [35,360], [34,339], [32,329], [28,283], [35,372], [33,342], [28,262], [32,328], [33,326], [35,354], [30,294], [29,275], [32,336], [34,354], [35,368]]) # x座標、y座標の抜き出し input_data_x = input_data[:, 0] input_data_y = input_data[:, 1] # x座標、y座標をそれぞれ正規化 input_data_x_norm = normalize(input_data_x) input_data_y_norm = normalize(input_data_y) input_data_norm = np.column_stack([input_data_x_norm,input_data_y_norm]) # a,b(正規化後)の初期値 a_norm = 1 b_norm = 1 # 学習率η eta = 0.001 # 最急降下法を10,000回分繰り返した場合を確認 for num in range(10000): a_norm = a_norm - eta * da_f(a_norm, b_norm, input_data_norm) b_norm = b_norm - eta * db_f(a_norm, b_norm, input_data_norm) # a,b(正規化後)の値を表示 print("*** a,b(正規化後)の値 ***") print("a_norm = " + str(a_norm) + ", b_norm = " + str(b_norm)) # 入力データの値(正規化後)を散布図で表示 plt.scatter(input_data_x_norm, input_data_y_norm) plt.title("input_data_norm") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(-0.5, 1.5) plt.ylim(-0.5, 1.5) plt.grid() # 算出した直線(y_norm = a_norm * x_norm + b_norm)を追加で表示 x_norm = np.linspace(-0.5, 1.5, 1000) y_norm = a_norm * x_norm + b_norm plt.plot(x_norm, y_norm, label='y = a_norm * x + b_norm', color='darkviolet') plt.legend() plt.show() # 入力データの値(正規化前)を散布図で表示 plt.scatter(input_data_x, input_data_y) plt.title("input_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() # 算出した直線(y = a_norm * x + b_norm)を正規化後の値に戻し、追加で表示 x_rev = rev_normalize(x_norm, input_data_x.max(), input_data_x.min()) y_rev = rev_normalize(y_norm, input_data_y.max(), input_data_y.min()) plt.plot(x_rev, y_rev, label='y=ax+b', color='darkviolet') plt.legend() plt.show() |
なお、最小2乗法と最急降下法を用いて回帰直線を求める方法については、以下の記事を参照のこと。
さらに、正規化後の回帰直線(\(y=ax+b\))の\(a\),\(b\)の値も算出することができる。その算出方法は、以下の通り。
\[
\begin{eqnarray}
\frac{y – y_{min}}{y_{max} – y_{min}} &=& \frac{a_{norm}(x – x_{min})}{x_{max} – x_{min}} + b_{norm} \\
\frac{y – y_{min}}{y_{max} – y_{min}} &=& \frac{a_{norm}x – a_{norm}x_{min}}{x_{max} – x_{min}} + b_{norm} \\
\frac{y – y_{min}}{y_{max} – y_{min}} &=& \frac{a_{norm}x}{x_{max} – x_{min}} – \frac{a_{norm}x_{min}}{x_{max} – x_{min}} + b_{norm} \\
y – y_{min} &=& \frac{a_{norm}(y_{max} – y_{min})}{x_{max} – x_{min}}x – \frac{a_{norm}x_{min}(y_{max} – y_{min})}{x_{max} – x_{min}} + b_{norm}(y_{max} – y_{min}) \\
y &=& \frac{a_{norm}(y_{max} – y_{min})}{x_{max} – x_{min}}x – \frac{a_{norm}x_{min}(y_{max} – y_{min})}{x_{max} – x_{min}} + b_{norm}(y_{max} – y_{min}) + y_{min}
\end{eqnarray}
\]
以上より、\(a\),\(b\)の値は以下のようになる。
\[
\begin{eqnarray}
a &=& \frac{a_{norm}(y_{max} – y_{min})}{x_{max} – x_{min}} \\
b &=& -\frac{a_{norm}x_{min}(y_{max} – y_{min})}{x_{max} – x_{min}} + b_{norm}(y_{max} – y_{min}) + y_{min}
\end{eqnarray}
\]
実際に、正規化後の回帰直線(y=ax+b)のa,bの値を計算し、グラフ化した結果は、以下の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | %matplotlib inline import numpy as np import matplotlib.pyplot as plt def normalize(input_data): max_val = input_data.max() min_val = input_data.min() if max_val == min_val: return [] else: return (input_data - min_val) / (max_val - min_val) def da_f(a, b, input_data): ret = 0 input_data_cnt = input_data.shape[0] for tmp in range(input_data_cnt): tmp_x = input_data[tmp, 0] tmp_y = input_data[tmp, 1] ret = ret + (( a * tmp_x + b - tmp_y ) * tmp_x) / input_data_cnt return ret def db_f(a, b, input_data): ret = 0 input_data_cnt = input_data.shape[0] for tmp in range(input_data_cnt): tmp_x = input_data[tmp, 0] tmp_y = input_data[tmp, 1] ret = ret + ( a * tmp_x + b - tmp_y ) / input_data_cnt return ret # 入力データの読み込み input_data = np.array([[33,352], [33,324], [34,338], [34,317], [35,341], [35,360], [34,339], [32,329], [28,283], [35,372], [33,342], [28,262], [32,328], [33,326], [35,354], [30,294], [29,275], [32,336], [34,354], [35,368]]) # x座標、y座標の抜き出し input_data_x = input_data[:, 0] input_data_y = input_data[:, 1] # x座標、y座標をそれぞれ正規化 input_data_x_norm = normalize(input_data_x) input_data_y_norm = normalize(input_data_y) input_data_norm = np.column_stack([input_data_x_norm,input_data_y_norm]) # a,b(正規化後)の初期値 a_norm = 1 b_norm = 1 # 学習率η eta = 0.001 # 最急降下法を10,000回分繰り返した場合を確認 for num in range(10000): a_norm = a_norm - eta * da_f(a_norm, b_norm, input_data_norm) b_norm = b_norm - eta * db_f(a_norm, b_norm, input_data_norm) print("*** a,b(正規化後)の値 ***") print("a_norm = " + str(a_norm) + ", b_norm = " + str(b_norm)) # 入力データの値(正規化後)を散布図で表示 plt.scatter(input_data_x_norm, input_data_y_norm) plt.title("input_data_norm") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(-0.5, 1.5) plt.ylim(-0.5, 1.5) plt.grid() # 算出した直線(y_norm = a_norm * x_norm + b_norm)を追加で表示 x_norm = np.linspace(-0.5, 1.5, 1000) y_norm = a_norm * x_norm + b_norm plt.plot(x_norm, y_norm, label='y = a_norm * x + b_norm', color='darkviolet') plt.legend() plt.show() # 入力データの値(正規化前)の最大値・最小値を算出 x_max = input_data_x.max() x_min = input_data_x.min() y_max = input_data_y.max() y_min = input_data_y.min() # 算出した直線(y = a_norm * x + b_norm)を正規化戻し後の、a,bの値を算出 a = a_norm * (y_max - y_min) / (x_max - x_min) b = -a_norm * x_min * (y_max-y_min)/(x_max-x_min) + b_norm * (y_max-y_min) + y_min print("*** a,b(正規化前)の値 ***") print("a = " + str(a) + ", b = " + str(b)) # 入力データの値(正規化前)を散布図で表示 plt.scatter(input_data_x, input_data_y) plt.title("input_data") plt.xlabel("x", size=14) plt.ylabel("y", size=14) plt.xlim(26, 36) plt.ylim(0, 450) plt.grid() # 算出した直線(y = ax + b)を追加で表示 x_rev_line = np.linspace(26, 36, 1000) y_rev_line = a*x_rev_line + b plt.plot(x_rev_line, y_rev_line, label='y=ax+b', color='darkviolet') plt.legend() plt.show() |
要点まとめ
- 回帰直線を求める際、入力データが大きいと計算が難しくなるが、「正規化」や「標準化」の手法を利用すれば、入力データの値をあらかじめ小さく揃えておくことができる。
- 正規化とは、入力データの値の範囲を0~1に揃えておく手法のことをいう。