このブログの以下の記事で、最小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関数を利用して算出できる。そのプログラムと実行結果は、以下の通り。
%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の範囲内に収まり、正規化戻しを行うと元に戻ることが確認できる。
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)
さらに、入力データを正規化/正規化戻しをグラフ化した結果は、以下の通り。
%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乗法と最急降下法を用いて回帰直線を求めた結果は、以下の通り。
%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の値を計算し、グラフ化した結果は、以下の通り。
%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に揃えておく手法のことをいう。






