ニューラルネットワークによるボストン住宅価格の推定

作成: 日大生産工 MA 大前

  • これは、学部3, 4年生向けた、ニューラルネットワークを体験するためのコードです。
  • Google Collaborately環境での使用を想定しています。何それという場合は、大前に聞いてください。

これは、大前研究室の卒業研究テーマの学習資料として作成されたものです。他のゼミ生がこのページを参考にして卒業研究を行う場合は、必ず事前に相談してください。異なる研究室なのに、同じテーマが卒業研究概要集に並んでいると、問題になるかもしれません。

0. 前準備

おまじないとして、以下を実行しておいてください。

In [1]:
import numpy as np
from sklearn.datasets import load_boston
import matplotlib.pyplot as plt
import scipy.stats as st
from sklearn.neural_network import MLPRegressor

def MinMaxStand_Xtrain_Xtest(fX_train, fX_test):
    
    # trainデータについて、各特徴量の最大値・最小値取得
    MaxVal, MinVal = [], []
    for i in range(0, len(fX_train[0, :])):
        MaxVal.append(np.max(fX_train[:,i]))
        MinVal.append(np.min(fX_train[:,i]))

    # trainデータの規格化
    newX_train = np.zeros(np.shape(fX_train))
    for i in range(0, len(fX_train[:,0])):
         for j in range(0, len(fX_train[0, :])):
                newX_train[i, j] = (fX_train[i, j] - MinVal[j]) / (MaxVal[j] - MinVal[j])
                
    # testデータの規格化
    newX_test = np.zeros(np.shape(fX_test))
    for i in range(0, len(fX_test[:,0])):
         for j in range(0, len(fX_test[0, :])):
                newX_test[i, j] = (fX_test[i, j] - MinVal[j]) / (MaxVal[j] - MinVal[j])

    # 規格化trainデータ、規格化testデータ、train最大値、train最小値を返却
    return newX_train, newX_test, MaxVal, MinVal

1. ボストン住宅価格データセットの取得

まずは、今回分析に使うデータセットをロードします。これは、ボストン住宅価格に関するデータセットで、どのような地域だと、住宅価格が安いのか、高いのかなど、分析することができます。

In [2]:
Boston_dataset = load_boston(return_X_y=False)
Boston_dataset.keys() # key一覧取得
Out[2]:
dict_keys(['data', 'target', 'feature_names', 'DESCR', 'filename'])

Boston_datasetの中には、['data', 'target', 'feature_names', 'DESCR', 'filename'] というデータがあることがわかりました。

まずは、targetとラベルづけられたデータを取り出し、BD_Yに代入してみます。

In [3]:
BD_Y = Boston_dataset["target"]
print("BD_Yのデータサイズ: ", np.shape(BD_Y))
print("・54番目: ", BD_Y[54])
print("・80番目: ", BD_Y[80])
BD_Yのデータサイズ:  (506,)
・54番目:  18.9
・80番目:  28.0

targetというデータは、506個の数字の集まりであることがわかりました。また、54番目のデータは18.9、80番目のデータは28であることがわかりました。これは、

  • 54番目の地域の住宅価格(中央値): 18.9 * 1000 [USD(米ドル)]
  • 80番目の地域の住宅価格(中央値): 28.0 * 1000 [USD(米ドル)]

ということを意味しています。イメージとしては、

  • ボストンが506区画に細かく分けられている。
  • 1区画ごとに住宅がたくさんある
  • その区画の住宅価格の中央値が、BD_Yに保存されている。
  • BD_Y[54]と書けば、54番目の地域の住宅価格を見ることができる

となります。

続いて、dataとラベルづけられたデータを取り出してみます。

In [4]:
BD_X = Boston_dataset["data"]
print("BD_Xのデータサイズ: ", np.shape(BD_X))
BD_Xのデータサイズ:  (506, 13)

取り出したデータは、506行、13列のサイズを有していることがわかりました。

  • 行: 上から下へ、縦に数えていくもの
  • 列: 左から右へ、横に数えていくもの

です。つまり、縦506、横13サイズの変数ということになります。506とは、ボストンを細かく分けたそれぞれの区画のどれかを意味していることは、想像しやすいと思います。それ一つ一つに、13個の情報が付与されていることになります。この情報は、以下の並びで保存されています。

In [5]:
print(Boston_dataset["feature_names"])
['CRIM' 'ZN' 'INDUS' 'CHAS' 'NOX' 'RM' 'AGE' 'DIS' 'RAD' 'TAX' 'PTRATIO'
 'B' 'LSTAT']

13個の英語が出てきました。これだけみてもよくわかりませんので、以下にまとめておきます。

  • 0番目: CRIM 犯罪の発生のしやすさ
  • 1番目: ZN 住居の密集具合
  • 2番目: INDUS 非小売業(サービス業)に与えられた土地の割合
  • 3番目: CHAS チャールズ川に接しているかどうか(0: No, 1: Yes)
  • 4番目: NOX 窒素濃度(空気の汚さ)
  • 5番目: RM 住居の部屋数
  • 6番目: AGE 1940年以前に建てられた住宅の割合
  • 7番目: DIS 雇用センターまでの距離
  • 8番目: RAD 大きな道路へのアクセスのしやすさ
  • 9番目: TAX 固定資産税の大きさ
  • 10番目: PTRATIO 教師あたりの生徒数
  • 11番目: B 黒人の比率が、63%から離れている度合い。Bkを黒人の比率としたとき、B = 1000(Bk – 0.63)^2 という定義
  • 12番目: LSTAT 低所得者の比率(← 差別につながらないように、十分注意してください)

ざっくり書いています。詳しく知りたい場合は、調べてみてください。

例えば、BD_X[54. 3]と記載すると、区画54の、3番目の情報(チャールズ川に接しているかどうか)が取り出されます。

In [6]:
print("区画54はチャールズ川に接しているか: ", BD_X[54, 3])
print("区画54の犯罪の起こりやすさ: ", BD_X[54, 0])

print("\n区画142はチャールズ川に接しているか: ", BD_X[142, 3])
print("区画142の犯罪の起こりやすさ: ", BD_X[142, 0])
区画54はチャールズ川に接しているか:  0.0
区画54の犯罪の起こりやすさ:  0.0136

区画142はチャールズ川に接しているか:  1.0
区画142の犯罪の起こりやすさ:  3.32105

このように、

  • 区画54は川に接しておらず、犯罪が少ない。
  • 区画142はチャールズ川に接しており、犯罪が多い。

などの情報が見て取れます。 ここからわかるように、BD_Xには、506区画に対する13個の情報がまとめられています。

機械学習の分野では、このようなデータを特徴量と呼びます。 13個の情報は、506区画の特徴を表現するため、このような呼び方をするわけです。

2. 教師データ、テストデータの作成

続いて、BD_XからBD_Yを予測する、つまり、「犯罪の発生のしやすさなど13個の情報」から「住宅価格」を予測するモデルの構築について考えます。この場合、506個分すべてのデータで予測するモデルを構築しようとすると、精度評価を行うことができないという問題が生じます。予測モデルを作る大きな目的は、正解を知っているデータを予測することではなく、これから新たに発生する、未知のデータを予測することとなります。したがって、予測モデルを作った後の精度評価は、モデルの構築に使っていないデータを使用して行うことが重要となります。

このため、

  • 教師データ(train): 予測モデルを作るためのデータ
  • テストデータ(test): 作った予測モデルが未知のデータにどれだけ適合するのか、評価するためのデータ

に分割します。BD_XとBD_Yがあり、それが教師データ、テストデータに分けられるわけですから、合計4つのデータが出来上がります。

  • Y_train: 教師データの住宅価格
  • Y_test: テストデータの住宅価格
  • nX_train: 教師データの特徴量(13個の情報)
  • nX_test: テストデータの特徴量(13個の情報)

今回は、506個のうち80%を教師データ、20%をテストデータに振り分けてみます。

In [7]:
# 506の数値の中で、80%に位置する数字を取得(506 * (8/10) = 404.80 = 404)
split_th = int(len(Boston_dataset["target"])*(8/10))

# 予測対象側(住宅価格)のデータセット分割
Y_train = BD_Y[:split_th]
Y_test = BD_Y[split_th:]

# 特徴量側(犯罪の発生のしやすさなど)のデータセット分割
temp_X_train = BD_X[:split_th, :]
temp_X_test = BD_X[split_th:, :]

# 特徴量を、0〜1範囲に規格化
nX_train, nX_test, MinV, MaxV = MinMaxStand_Xtrain_Xtest(temp_X_train, temp_X_test)

# サイズチェック
print("教師データ(住宅価格)のサイズ: ", np.shape(Y_train))
print("教師データ(特徴量)のサイズ: ", np.shape(nX_train))
print("テストデータ(住宅価格)のサイズ: ", np.shape(Y_test))
print("テストデータ(特徴量)のサイズ: ", np.shape(nX_test))
教師データ(住宅価格)のサイズ:  (404,)
教師データ(特徴量)のサイズ:  (404, 13)
テストデータ(住宅価格)のサイズ:  (102,)
テストデータ(特徴量)のサイズ:  (102, 13)

506件分のデータが、教師データは404件分、テストデータは102件分として分割されたことがわかりました。

3. ニューラルネットワークの学習

ここでは、予測モデルの構築手法の一つである、ニューラルネットワークについて簡単に説明します。今回構築するニューラルネットワークは、13個の情報を与えると、住宅価格を教えてくれる、という構造をしています(つまり、入力側が13次元、出力側が1次元となります)。

ニューラルネットワークを構築する際、重要なパラメータとして、学習回数学習係数があります。すごくざっくりと説明すると、

  • 学習回数: ニューラルネットワークが学習を行う際、思考を何回変化させるか
  • 学習係数: 1回の学習に対する、思考の変化の大きさ(細かな学習か、大雑把な学習か)

という意味となります。一つの例として、

  • 学習回数: 10000回
  • 学習係数: 0.001

でニューラルネットワークを定義してみます。

In [8]:
# ↓ ★学習回数と学習係数を、課題に応じて自分の好きな値に変更
MT = 10000 # 学習回数
LR = 0.001 # 学習係数

# 以下は、変更しないことを推奨
NNreg = MLPRegressor(
    hidden_layer_sizes=(5, ), #中間層
    max_iter = MT, # 学習回数
    learning_rate_init = LR, # 学習係数
    random_state = 0,
    verbose=False
)

これでモデルの定義が終わりました。これを学習させるには、以下のように記載します。

In [9]:
NNreg.fit(nX_train, Y_train) 
# 学習回数が多いと、1分間程度、時間がかかる場合があります。
Out[9]:
MLPRegressor(hidden_layer_sizes=(5,), max_iter=10000, random_state=0)

fitに与えている2つの変数は、

  • nX_train: 教師データの、特徴量(13個の情報)
  • Y_train: 教師データの、住宅価格

ですから、教師データのみを使って、13個の情報から、住宅価格を予測するような学習をしてください、という意味になります。頑張って学習をしているので、特に学習回数が多い場合は、少し時間がかかることがあります。プログラムの連打などをせずに、終わるまで待ってください。

4. 教師データ(train)の誤差測定

続いて、構築したニューラルネットワークの予測誤差を推定してみます。データは、教師データを利用します。教師データの誤差を下げるような学習をした訳ですので、ある程度、誤差が低いことが想定されます。

教師データは404件でした。このうち、3件分の予測値と正解値を見てみます。

In [10]:
pred_Y_train = NNreg.predict(nX_train) # 教師データ(nX_train)を代入した場合の予測値を取得

for i in range(3):
    print(i, "件目の住宅: ")
    print("__予測値: ", np.round(pred_Y_train[i], 2))
    print("__正解値: ", Y_train[i])
    temp_err = np.abs(Y_train[i] - np.round(pred_Y_train[i], 2))
    print("__誤差: ", np.round(temp_err, 2))
    print("")
0 件目の住宅: 
__予測値:  30.65
__正解値:  24.0
__誤差:  6.65

1 件目の住宅: 
__予測値:  26.31
__正解値:  21.6
__誤差:  4.71

2 件目の住宅: 
__予測値:  31.26
__正解値:  34.7
__誤差:  3.44

正解と予測が、似ていることがわかります。3件分だと全体的な評価にならないので、教師データすべての平均誤差を求めてみましょう。

In [11]:
Err_train = np.mean(np.abs(Y_train - pred_Y_train))
print("教師データの平均誤差: ", np.round(Err_train, 2))
教師データの平均誤差:  3.89

これを1000倍したものがUSDですから、だいたい4000ドル程度(40万円くらい?)の誤差があることがわかります。住宅価格の40万円程度のズレですから、そこまで悪くもない気がします。

続いて、もっと素朴に教師データの誤差を検証してみます。404件分のデータの予測値と正解値にがある訳で、それが一致している方が望ましいことになります。すなわち、404件分のデータについて、横軸を正解値、縦軸を予測値として散布図を描いたとき、対角線上にデータが集まると、良い予測と判断することができます。これを確認してみましょう。

In [12]:
plt.scatter(x=Y_train, y=pred_Y_train)
plt.ylabel("Prediction") # 予測値
plt.xlabel("Ground truth") # 正解値
plt.title("Training data")
plt.grid(True)
plt.show()

train_cor = st.pearsonr(Y_train, pred_Y_train)
print("(教師データ)予測-実測の相関係数: ", np.round(train_cor[0], 3))
(教師データ)予測-実測の相関係数:  0.815

横軸(Ground truth)が正解値、縦軸(Prediction)がニューラルネットワークの予測値です。右肩上がりの対角線上に集まっているということは、高価な住宅を高価と予測できていること、安価な住宅を安価と予測できていることになります。

また、対角成分に集中しているかどうかチェックするため、相関係数も出しています。 相関が1に近いほど、うまく予測できていることになります。 また、相関係数が0に近い、あるいはマイナスの場合、右肩上がりではないわけですから、全然ダメということになります。 今回は0.815ですからとても性能が良いことを意味します。 → 精度評価は、誤差(実測と予測の引き算)でもできますし、相関係数でもできます。好きな方を使いましょう。

総合的に見て、教師データに関しては、誤差が少ないと解釈できます。

5. テストデータ(test)の誤差測定

さて、教師データの誤差は少ないことがわかりました。しかしそれは喜んでいいことではありません。ニューラルネットワークは、教師データの誤差を下げることを目的とした学習をしている訳ですから、これが低いのは、いわば当たり前のことにすぎません。

本来、うまく予測できていることが望ましいのは、教師データに存在しない未知のデータに対してとなります。 今回は、学習に使用していないテストデータがありますから、それを利用して誤差測定をしてみます。

まずは、テストデータ3件分についてみてみます。

In [13]:
pred_Y_test = NNreg.predict(nX_test) # テストデータ(nX_test)を代入した場合の予測値を取得

for i in range(3):
    print(i, "件目の住宅: ")
    print("__予測値: ", np.round(pred_Y_test[i], 2))
    print("__正解値: ", Y_test[i])
    temp_err = np.abs(Y_test[i] - np.round(pred_Y_test[i], 2))
    print("__誤差: ", np.round(temp_err, 2))
    print("")
0 件目の住宅: 
__予測値:  6.21
__正解値:  8.5
__誤差:  2.29

1 件目の住宅: 
__予測値:  8.13
__正解値:  5.0
__誤差:  3.13

2 件目の住宅: 
__予測値:  6.57
__正解値:  11.9
__誤差:  5.33

0件目の誤差はやや大きく、1, 2件目の誤差は少なくなりました。

これだけではうまく解釈できないので、テストデータすべての平均誤差を算出してみます。

In [14]:
Err_test = np.mean(np.abs(Y_test - pred_Y_test))
print("テストデータの平均誤差: ", np.round(Err_test, 2))
テストデータの平均誤差:  3.81

テストデータの誤差も教師データと同じくらいになりました。

最後に、テストデータにおいても、散布図を書いて、全体の傾向を確認してみましょう。

In [15]:
plt.scatter(x=Y_test, y=pred_Y_test)
plt.ylabel("Prediction") # 予測値
plt.xlabel("Ground truth") # 正解値
plt.title("Test data")
plt.grid(True)
plt.show()

test_cor = st.pearsonr(Y_test, pred_Y_test)
print("(テストデータ)予測-実測の相関係数: ", np.round(test_cor[0], 3))
(テストデータ)予測-実測の相関係数:  0.711

若干ふんわりしていますが、右肩上がりであることがわかります。ですので、安いものは安いと予測、高いものは高いと予測できていることがわかります。これは、学習に使っていないデータですから、未知のデータ(ニューラルネットワークが初めて見るデータ)でもある程度予測できることを示しています。

テストデータの相関係数は0.711ですから、教師データよりもわずかに低い結果となりました。初めて見るデータの予測ですから、教師データの制度よりも落ちることは、自然なことです。

6. 演習問題

  • 学習回数(MT, 星マークの箇所)をいろいろ変化させてください(最小1〜最大1万の範囲、好きな数字でok)。
  • 学習係数(LR 星マークの箇所)をいろいろ変化させてください(最小0.0001〜最大1の範囲、好きな数字でok)。
  • A. 学習係数、B. 学習回数、C. 教師データの誤差、D. テストデータの誤差を、表にまとめてください(誤差ではなく、相関係数を性能の指標にしてもokです)。
  • 表をみて、学習係数と学習回数が、教師データとテストデータの誤差に与える影響をまとめてください。
  • 今回の例では、学習係数と学習回数をいくつに設定したニューラルネットワークが望ましいと思うか、考えてください。

7. 卒業研究概要集への手引き

  • このテーマを卒業研究のテーマにしたい場合は、ここの対応をお願いします(必ず事前に相談してください)。
  • 以下、すべて含めて、A4用紙2枚にまとめてください。
  • 厳密に守る必要はありませんが、文章量の比率を目安にしてください(15%とは、A4用紙2枚分の15%という意味)。

1章 はじめに:(文章量の比率: 15%)

  • 回帰型ニューラルネットワークとは何か、それを用いたどのような先行研究があるか、まとめてください。
  • 先行研究は、簡単にで良いので、2, 3個紹介してください。
  • ニューラルネットワークを説明する図などがあってもいいかもしれません。

2章 特徴量と予測対象:(文章量の比率: 30%)

  • ボストン住宅価格データの概要について説明してください。
  • 何から(特徴量)、何を(予測対象)予測するしようとしているか、説明してください。

3章 ニューラルネットワークの実験:(文章量の比率: 40%)

  • この章では、以下の3.1や3.2で、演習問題でまとめたことを記載します。

3.1節 概要:

  • ニューラルネットワークのどのようなパラメータをどのように変化させ、誤差の変化を調べたいのか、簡単に説明してください。

3.2節 結果と考察:

  • (結果)3.1節で述べた実験で得られた結果を説明してください。
  • (考察)その結果から読み取れることを記載してください。
  • 結果は得られたものを淡々と説明し、個人の感想はいれない。逆に、考察は、自分の考えを書く。
  • 必要に応じて、図・表を作成してください。

4章 おわりに:(文章量の比率: 15%)

  • 今回の分析に対する感想などを記載してください。

参考文献:

参考にした資料を、2〜3件記載してください。以下、書き方の例です。

  • [1] 大前ほか, XXXに関する分析, XXX学会論文誌, 2020.
  • [2] XXXに関する情報, http://xxx.ddd.ttt.com, 2020年4月20日閲覧

本文中で引用する場合は「大前らはXXXを実施している [1]。また、〜」のように、どこで引用したのか明白にしてください。