統計

回帰直線を求める際にデータを正規化してみた

このブログの以下の記事で、最小2乗法と最急降下法を用いて回帰直線を求めている。

最小2乗法と最急降下法を用いて回帰直線を求めてみた 以下のように、入力データ\(x\),\(y\)の値が与えられた場合を考える。 このときの、各点との距離が最小になるような\...

上記記事のように回帰直線を求める際、入力データが大きいと計算が難しくなるが、「正規化」や「標準化」の手法を利用すれば、入力データの値をあらかじめ小さく揃えておくことができる。今回は、回帰直線を求める際に入力データを正規化してみたので、そのサンプルプログラムを共有する。

正規化とは、入力データの値の範囲を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)
入力データの正規化・正規化戻し
削除または保存していないWordドキュメントの復元方法【4DDiG Windowsデータ復元】ワード(Word)データ等のファイルを誤って削除してしまった場合は、通常はデータの復元ができませんが、4DDiGというソフトウェアを利用...

さらに、入力データを正規化/正規化戻しをグラフ化した結果は、以下の通り。

%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()
正規化・正規化戻し(グラフ化)



サラリーマン型フリーランスSEという働き方でお金の不安を解消しよう先日、「サラリーマン型フリーランスSE」という働き方を紹介するYouTube動画を視聴しましたので、その内容をご紹介します。 「サ...

次に、入力データを正規化した後で、最小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乗法と最急降下法を用いて回帰直線を求める方法については、以下の記事を参照のこと。

最小2乗法と最急降下法を用いて回帰直線を求めてみた 以下のように、入力データ\(x\),\(y\)の値が与えられた場合を考える。 このときの、各点との距離が最小になるような\...

さらに、正規化後の回帰直線(\(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()
回帰直線のa,bの計算

要点まとめ

  • 回帰直線を求める際、入力データが大きいと計算が難しくなるが、「正規化」や「標準化」の手法を利用すれば、入力データの値をあらかじめ小さく揃えておくことができる。
  • 正規化とは、入力データの値の範囲を0~1に揃えておく手法のことをいう。