5
10

More than 3 years have passed since last update.

重回帰分析の原理原則

Last updated at Posted at 2020-03-30

はじめに

前回の記事では、回帰分析の1つである単回帰分析について書きました。
この記事では、変数同士の相関関係から変動を予測することができる重回帰分析について書きます。

重回帰分析はどのような時に使われるのか。

重回帰分析は、マーケティングで使われる分析手法のひとつです。複数の変数を持つデータの関連性を明らかにします。
例えば、店舗面積/従業員数/商圏人口/広告費から、来客数を予測したり、
他にも、部屋の広さ/駅からの距離/犯罪発生率から、家賃を予測します。
このように、複数の要因から1つの値を予測するときに、重回帰分析を利用します。

また、重回帰分析では、それぞれの要因が予測したい値に与える影響度合を数値で表現できます
そのため、要因分析としても使われます。店舗面積/従業員数/商圏人口/広告費それぞれが、来店客数を増やす要因なのか、減らす要因なのかが分かります。
要因分析をすることで、来店客数を増やす有効なパラメータを定量的に評価することができ、マーケティングでも使われています。

重回帰分析は、様々な分野において、変動する値を予測するためや、要因分析のために使われています。
この記事では、重回帰分析の原理原則を理解することを目的とし、どのようなアルゴリズムで回帰係数を算出しているのかを紐解きます。

まずは、回帰分析の説明をします。

回帰分析とは

回帰分析とは、結果となる数値と要因となる数値の関係を数式(回帰式)で表す統計的手法です。
例えば、あるクラスのAさん〜Dさんの「勉強時間」と「テストの点数」のデータがあります。

単回帰のトレーニングデータ表.png

このデータを視覚化すると、下のようなグラフになります。
横軸が勉強時間、縦軸がテストの点数を表しています。
勉強時間が長くなるほど、テストの点数が上がっている傾向がわかります。

無題のスプレッドシート_-_Google_スプレッドシート.png

4人のデータを用いて、「勉強時間」と「テストの点数」の関係を数式で表します。
「勉強時間」と「テストの点数」の関係が線形であると仮定して、データに当てはまる直線を下記のように引きます。

点数と勉強時間の関係(回帰式なし).png

黒の直線は下の式(回帰式)で表せます。

y = 5.7x + 51 

この式(回帰式)の意味は、


(点数)= 5.7 × (学習時間) + 51

となります。
学習時間が0のとき、51点となり、1時間勉強するごとに5.7点ずつ点数が上がっていることを意味しています。
この式(回帰式)を利用することで、勉強時間から、他の生徒がどの程度の点数を獲得できるかが予測できるようになります。
例えば、勉強時間が6時間のEさんの点数は、

\begin{align}
(Eさんの点数) &= 5.7× (学習時間) + 51 \\
&= 5.7 × 6 + 51 \\
&= 85(点)
\end{align}

と求めることができます。

無題のスプレッドシート_-_Google_スプレッドシート-2.png

このように、回帰分析は、結果となる数値と要因となる数値の関係を回帰式で表します。
また、要因となる数値を説明変数、結果となる数値を目的変数といいます。
今回の例でいうと、「勉強時間」から「点数」を予測したので、「勉強時間」が説明変数で、「点数」が目的変数となります。

回帰分析のうち、1つの目的変数を1つの説明変数で予測するものを単回帰分析と言います。
そして、1つの目的変数を2つ以上の説明変数で予測するものを重回帰分析と言います。

単回帰分析

y = a + b_1x_1 \\

重回帰分析

y = a + b_1x_1 + b_2x_2 + ・・・ + b_nx_n \\
y:目的変数 \\
a:切片 \\
b:回帰係数 \\
x:説明変数 

では、どのようにしてインプットしたデータから、回帰式を算出しているのか。
それは、回帰式をコスト関数で算出したコストで評価し、最急降下法で、コストが小さい回帰係数を算出します。
コスト関数や、最急降下法を初めて聞く方は、前回の記事を一読した方がわかりやすいかもしれません。(参考:単回帰分析の原理原則

本記事では、重回帰分析におけるコスト関数と最急降下法について述べていきます。
それでは、物理の点数、科学の点数のデータから数学の点数を予測するモデルケースを通して、重回帰分析を実装していきます。

データの確認

CSVファイルを読み込み、データを確認します。

今回使用する学習データはこちらをクリック

training_data_multipleLiner.csv
Pysics Science Statistics Math
64 67 69 68
74 74 63 67
60 59 55 57
84 88 89 91
80 88 78 82
75 68 68 63
66 59 55 65
77 71 66 73
70 71 60 67
89 84 80 85
73 76 69 82
78 74 63 67
67 63 67 57
80 81 83 74
72 75 73 83
68 63 76 71
81 89 80 85
64 66 59 69
79 81 79 87
70 76 69 71
76 80 68 80
71 64 75 66
81 77 84 73
85 83 87 90
61 67 70 61
74 71 75 73
85 82 78 84
60 60 57 52
83 88 80 92
85 80 76 87
84 86 86 80
82 79 88 83
68 66 60 71
89 85 88 91
85 90 85 98
68 66 62 61
74 76 82 77
76 75 84 81
62 68 70 60
64 67 68 73
65 66 59 58
64 57 56 57
73 74 63 69
65 65 69 69
87 91 97 97
62 60 61 57
77 69 62 70
77 76 75 70
89 97 94 99
63 56 59 62
74 69 62 75
72 67 67 67
69 77 81 73
88 86 95 86
77 84 75 87
83 80 77 80
77 77 80 74
84 83 86 87
77 79 69 83
66 74 71 74
74 69 79 75
67 75 78 73
83 85 85 93
72 65 73 69
70 76 73 83
71 70 74 75
89 94 82 86
82 86 85 89
86 80 88 75
85 89 100 97
89 83 79 76
77 74 74 71
79 83 93 89
75 71 79 78
68 66 68 67
64 56 57 58
66 60 55 57
81 76 80 80
68 72 71 79
71 70 75 72
62 54 56 54
80 85 93 85
80 84 86 83
66 62 67 58
73 77 69 74
70 70 71 67
62 55 62 56
62 70 76 76
74 67 65 65
80 83 78 78
90 84 83 90
84 76 78 70
84 87 96 92
78 81 80 78
90 92 81 89
60 68 55 65
86 85 80 93
72 64 59 69
69 63 62 57

import numpy as np
import pandas as pd

data = np.loadtxt('./training_data_multipleLiner.csv',delimiter=",",skiprows =1)
df = pd.DataFrame(data,columns=['物理','科学','統計','数学'])
物理 科学 統計 数学
0 64.0 67.0 69.0 68.0
1 74.0 74.0 63.0 67.0
2 60.0 59.0 55.0 57.0
98 69.0 63.0 62.0 57.0

こちらのデータセットには、99人分の物理、科学、統計、数学のテストの点数の情報が入っています。
今回は、物理の点数と科学の点数から、数学の点数と予測する重回帰分析をしていきます。
つまり、説明変数物理の点数科学の点数として、目的変数数学の点数とします。

初めに、説明変数と目的変数の関係を視覚化します。
x軸を物理の点数、y軸を科学の点数、z軸を数学の点数でデータをプロットします。

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 点数をプロットしてみる
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(df['物理'],df['科学'],df['数学'],color='#ef1234')
ax.set_xlabel('Pysics')
ax.set_ylabel('Science')
ax.set_zlabel('Math')
plt.show()

物理科学数学.png

なんとなく、科学が高い人が数学も高そうな感じがしますが、正直よくわかりませんね。
このグラフからは、数学の点数を予測するのに、物理と科学、どちらの点数が重要なのか、どのくらい関係があるのかまではわかりません。

そこで重回帰分析により、下記の式の回帰係数と切片を算出し、物理の点数と科学の点数が、数学の点数に与える影響度合いを分析していきます。

(数学の点数) = 切片 + b_1×(物理の点数) + b_2(科学の点数) \\
b:回帰係数 \\

データの前処理

元のデータをそのまま利用して、学習することも可能です。
(ここで言う学習は、データから回帰係数と切片を算出することを指しています。)
一般的に、各特徴量間でデータのスケールが異なる場合、事前にデータのスケールを調整したほうが、パラメータに偏りがなくなるので、より効率的に学習が行えるようになります。

今回の例で言うと、テストの点数なので最低点は0点、最高100点ですが、それぞれの教科の難易度により平均点や分散などが変わってきます。

そこでデータのスケールを合わせるだけでなく、各特徴量を標準正規分布に従うように変換する標準化を行います。

機械学習の手法は「入力のそれぞれの特徴量がどう変化したら、出力がどう変化するのか」を見ているので、各特徴量のばらつき度合い(標準偏差)を揃えておくことで、各特徴量の動きに対する感度を平等に見ることができるようになるわけです。

データの標準化を行う。

標準化は下記に式で行います。
具体的には、各特徴量で、平均を0、標準偏差を1になるように調整します。

x = \frac{x - x_{mean}}{x_{std}} \\
x_{mean}:xの平均値 \\
x_{std}:xの標準偏差 \\
# 正規化(Z-score Normalization)
def norm(X):
    X_norm = np.zeros((X.shape[0],X.shape[1]))
    mean = np.zeros((1,X.shape[1]))
    std = np.zeros((1,X.shape[1]))
    for i in range(X.shape[1]):
        mean[:,i] = np.mean(X[:,i])
        std[:,i] = np.std(X[:,i])
        X_norm[:,i] = (X[:,i] - float(mean[:,i])) / float(std[:,i])
    return X_norm,mean,std

標準化を行う関数を定義したので、データの標準化を行います。
また、標準化ができているのか、平均と標準偏差を確認して見ましょう。

# 説明変数はX、目的変数はy
X=data[:,:2]
y=data[:,3]

# 標準化
X_norm,mean,std = norm(X)

# 標準化の確認
print("Xの平均値 = {}".format(X_norm.mean()))
print("Xの標準偏差 = {}".format(X_norm.std()))

ちゃんと標準化できていますね。

# Xの平均値 = 2.0634448134447354e-16
# Xの標準偏差 = 0.9999999999999999

コスト関数

コスト関数(平均二乗誤差)は、ある回帰式が学習データとどれだけフィットしているかを定量的に評価します。
今回は、説明変数は科学の点数と物理の点数の2種類ですので、下記の式のようになります。

y = w_0 + w_1x_1 + w_2x_2 \\
\begin{align}
y&:目的変数 \\
w_0&:切片 \\
w_1,w_2&:回帰係数 \\
x&:説明変数 
\end{align}

例えば、(w0,w1,w2) = (0,0,0) のときは、下図のような平面になります。

000.png

コスト関数を数式で表すと下記の式になります。

\begin{align}
J(w_0,w_1,w_2) &= \frac{1}{2m}\sum_{i=1}^{m}(\hat{y}-y)^2 
\end{align}

また、回帰式をベクトルで表すと、

\begin{align}
\hat{y} &= w_0 + w_1x_1 + w_2x_2 \\
&= XW
\end{align}
X=\left[
\begin{matrix} x_0 & x_1 & x_2 \end{matrix}
\right]
,
W=\left[
\begin{matrix} w_0 \\ w_1 \\ w_2 \end{matrix}
\right]
(x_0 = 1)

と表せます。

数式で見ると難しそうに見えますが、やっていることは単純です。
まず、yは、学習データの目的変数(数学の点数)です。下の図で言うと、赤点のことです。
次に、y^は、学習データの説明変数(物理・科学の点数)に回帰係数をかけた予測値です。下の図で言うと、青い平面です。

000-3.png

コスト関数は、それぞれの点における予測値と、実際の答えの差(残差)を計算し、全ての残差を合計した値となります。(正確には、個数の2倍で割っています。)

000-2.png

ちなみに上図のコストを計算すると、2882となります。
次に、(w0,w1,w2) = (50,3,3) のときを考察して見ます。
5033.png

先ほどよりは、若干データに近づいていることがわかります。
この時のコストを計算すると、552となります。
先ほどが2882だったのに対して、552と、コストが大幅に減ったことがわかります。
このように、コスト関数を用いることで、ある回帰式が学習データとどれだけフィットしているかを定量的に評価することができます。

それでは実装していきます。
まずは、切片の計算を工夫するために、先ほど標準化した説明変数に切片項(x0)を追加します。
切片項(x0)は1です。

また、(w0,w1,w2)の初期値を(0,0,0)と定義します。

# 切片項を追加
X_padded = np.column_stack((np.ones((len(y),1)),X_norm))

# モデル式のパラメータ初期化
weight_int = np.zeros((3,1))

次に、コスト関数を定義します。

# コスト関数
def cost(X,y,weight):
    m = len(y)
    J = 0
    y_hut = X.dot(weight)
    diff = np.power((y_hut - np.transpose([y])),2)
    J = (1.0 /(2*m)) * diff.sum(axis = 0)
    return J

これでコスト関数を定義できました。
例えば先ほど(w0,w1,w2) = (50,3,3)のときのコストは、下記のように求められます。

# コスト関数
print(cost(X_padded,y,[50,3,3])[0])
# 552

これで、ある回帰式が、学習データにどれほどフィットしているかを定量的に評価できるようになりました。
あとは、コストが最小となるような回帰係数を、最急降下法で求めていきます。

最急降下法

最急降下法は、関数を最小にするようなパラメーターを求めるためのアルゴリズムです。
今回はコストが最小になる回帰係数(w0,w1,w2)を求めます。

コスト関数は下記の式でした。

\begin{align}
J(w_0,w_1,w_2) &= \frac{1}{2m}\sum_{i=1}^{m}(\hat{y}-y)^2 \\
 &= \frac{1}{2m}\sum_{i=1}^{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)^2
\end{align}
\begin{align}
m &: データ数  \\
\hat{y} &: 予測値 (\hat{y}=w_0x_0 + w_1x_1 + w_2x_2) \\
y &: 実測値
\end{align}

コスト関数をw0で偏微分すると、

\begin{align}
\frac{\partial }{\partial w_0}J(w_0,w_1,w_2) &= \frac{\partial }{\partial w_0}\frac{1}{2m}\sum_{i=1}^{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)^2 \\
&= \frac{1}{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)
\end{align}

コスト関数をw1で偏微分すると、

\begin{align}
\frac{\partial }{\partial w_1}J(w_0,w_1,w_2) &= \frac{\partial }{\partial w_1}\frac{1}{2m}\sum_{i=1}^{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)^2 \\
&= \frac{1}{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)x_1
\end{align}

コスト関数をw2で偏微分すると、

\begin{align}
\frac{\partial }{\partial w_1}J(w_0,w_1,w_2) &= \frac{\partial }{\partial w_1}\frac{1}{2m}\sum_{i=1}^{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)^2 \\
&= \frac{1}{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)x_2
\end{align}

と求めることができます。
偏微分をすると、コスト関数の傾きがわかります。
コストが小さくなるように、w0,w1を更新したいので、元々の回帰係数から偏微分で得られた値を引きます。

\begin{align}
w_0 &:= w_0 - \alpha\frac{1}{m}(w_0x_0 + w_1x_1 + w_2x_2 - y) \\
w_1 &:= w_1 - \alpha\frac{1}{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)x_1 \\
w_2 &:= w_1 - \alpha\frac{1}{m}(w_0x_0 + w_1x_1 + w_2x_2 - y)x_2
\end{align}
\begin{align}
\alpha &:学習率 \\
m &:データ数 \\
x &:説明変数 \\
y &:目的変数 \\
\end{align}

αは学習率と呼ばれ、回帰係数を更新する値の大きさを調整します。
学習率は、大きすぎると回帰係数が収束しない可能性があり、また、小さすぎると、回帰係数の収束に時間がかかってしまいます。
今回は学習率=0.01でと決めて、進めていきます。

では、最急降下法を実装していきます。

# 最急降下法
def gradientDescent(X,y,weight,alpha,iterations):
    m=len(y)
    J_history = np.zeros((iterations,1))
    weight_history = []

    for i in range(iterations):
        weight = weight - alpha * (1.0/m) * np.transpose(X).dot(X.dot(weight) - np.transpose([y]))
        weight_history.append(weight)
        J_history[i] = cost(X,y,weight)
        print(alpha * (1.0/m) * np.transpose(X).dot(X.dot(weight) - np.transpose([y])))
    return weight,J_history,weight_history

今回は、500回の回帰係数の更新する中で、コストがどのように推移するかを可視化するために、J_historyを追加しています。
また、更新された回帰係数を保持するために、weight_historyを追加しています。
次にα、学習回数の初期値を設定します。

# 学習率と学習回数(イテレーション)
alpha = 0.01
num_iters = 500

それではコスト関数と最急降下法を用いて、モデルの学習をしていきます。

weight,J_history,weight_history = gradientDescent(X_padded,y,weight_int,alpha,num_iters)
print(weight)
# [[74.57745626]
# [ 3.29981383]
# [ 7.16228608]]

回帰係数を求めることができました。
つまり、数学の点数は下記の式で表すことが出来ます。

(数学の点数)= 3.29×(標準化した物理の点数) + 7.16×(標準化した科学の点数) + 74.5 

どちらの回帰係数も正であるので、物理や科学の点数が高い方が数学の点数が高い傾向がわかります。
また、数学の点数には、物理の点数よりも科学の点数の方が、2倍以上関係があることを示しています。

また、回帰式と学習データを視覚化すると、

# 実測値の描画
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(data[:,0],data[:,1],data[:,3],color='red')

# 回帰平面の描画
X,Y = np.meshgrid(np.arange(data[:,0].min(),data[:,0].max(),1),np.arange(data[:,1].min(),data[:,1].max(),1))
X_norm = (X-float(mean[:,0])) / float(std[:,0])
Y_norm = (Y-float(mean[:,1])) / float(std[:,1])
Z = weight[0] + weight[1] * X_norm + weight[2] * Y_norm
ax.plot_surface(X,Y,Z,alpha=0.5)

# ラベルを追加
ax.set_xlabel('Pysics')
ax.set_ylabel('Science')
ax.set_zlabel('Math')
plt.show()

47_4_5.png

このように、学習データにフィットした平面を描くことができました。

また、コストの推移も確認してみましょう。

# コストと学習回数のグラフ
plt.plot(range(J_history.size),J_history,"-b",linewidth=0.5)
plt.xlabel("Number of Iterations")
plt.ylabel("Cost J")
plt.grid(True)
plt.show()

コスト推移.png

縦軸がコストで、横軸が学習回数を表しています。
学習を繰り返すごとに、コストが下がっているのがわかります。

学習の推移を確認してみる。

500回、回帰係数を更新しました。その履歴がweight_historyに格納されているので、確認してみます。

nums = [0,99,199,299,399,499]

for num in nums:
    weight = weight_history[num]
    # 実測値の描画
    fig = plt.figure()
    ax = Axes3D(fig)
    ax.scatter(data[:,0],data[:,1],data[:,3],color='red')

    # 回帰平面の描画
    X,Y = np.meshgrid(np.arange(data[:,0].min(),data[:,0].max(),1),np.arange(data[:,1].min(),data[:,1].max(),1))
    X_norm = (X-float(mean[:,0])) / float(std[:,0])
    Y_norm = (Y-float(mean[:,1])) / float(std[:,1])
    Z = weight[0] + weight[1] * X_norm + weight[2] * Y_norm
    ax.plot_surface(X,Y,Z,alpha=0.5)

    # ラベルを追加
    ax.set_xlabel('Pysics')
    ax.set_ylabel('Science')
    ax.set_zlabel('Math')
    plt.savefig('{}回目.png'.format(num))

学習回数が1回、100回、200回、300回、400回、500回のときを描画します。

学習回数が1回のとき、(w0,w1,w2) = (0.75,0.09,0.10)となり、下記のようになります。
0回目.png
まだ学習を1度しかしていないため、初期の平面から動きがほとんどありませんね。

学習回数が100回のとき、(w0,w1,w2) = (47.5,3.95,4.93)となり、下記のようになります。
99回目.png
100回の学習で、平面が大きく傾いてきました。
傾きはなんとなく良さそうですが、高さ(切片)が足りてないようです。

学習回数が200回のとき、(w0,w1,w2) = (65.0,4.18,6.03)となり、下記のようになります。
199回目.png
パッとみた感じだと、これで良さそうに見えます。
学習回数が200回のときを見ると、コストは大幅に減少しています。

コスト推移.png

学習回数が300回のとき、(w0,w1,w2) = (71.3,3.91,6.51)となり、下記のようになります。
299回目.png
4.18あった物理の回帰係数が、3.91に下がりました。
ここまで上昇傾向にあった係数が下がりました。微調整が行われています。

学習回数が400回のとき、(w0,w1,w2) = (73.7,3.59,6.86)となり、下記のようになります。
399回目.png

学習回数が500回のとき、(w0,w1,w2) = (74.5,3.29,7.16)となり、最終的に下記の通りに落ち着きます。
499回目.png

まとめ

本記事では、重回帰分析についてまとめました。
重回帰分析の魅力は、関連性がわからないデータから、回帰係数という形で正・負の関連度合を算出できる点だと思います。
次は、重回帰分析を発展させたリッジ回帰やLasso回帰、Elastic Netにも取り組んでみたいと思ってます。

5
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
10