Going Faraway

渡辺遼遠の雑記帳

【Deep Learning with Python】Boston Housing data (番外編)

前回からの続き。前回は、ボストン郊外の不動産物件に関するデータを元に、その地域の住宅価格をディープラーニングで予測するというサンプルを動かしてみた。しかし、結果を見ただけではあまりにもブラックボックスすぎるので、少しだけディープラーニングのモデルとデータそのものを調査してみたい。

学習したニューラルネットのパラメーター(重み)は、次の関数を使って取得することができる。

>>> w = model.get_weights()
>>> print(w)
[array([[  1.89665943e-01,   6.76303506e-02,  -1.39802471e-01,
         -3.92748147e-01,  -1.18188962e-01,   1.32170185e-01,
          7.21744308e-03,  -2.46567175e-01,  -2.09330872e-01,
         -4.52335358e-01,   1.95683718e-01,  -2.12740257e-01,
         -2.55996019e-01,   5.20668104e-02,  -1.04660824e-01,
          1.62942894e-02,  -3.40786546e-01,   5.63649461e-03,
         -4.61874083e-02,  -3.72445881e-02,   5.90579249e-02,
         -1.81242540e-01,   1.27426997e-01,  -8.46400037e-02,
         -7.04784617e-02,  -2.00169012e-01,  -2.92712718e-01,
         -2.11299419e-01,  -3.25609922e-01,  -2.05949292e-01,
          4.42221835e-02,   4.28706445e-02,   1.23807281e-01,
         -1.86553150e-01,  -6.58548903e-03,  -2.20981717e-01,
         -1.25058562e-01,  -1.67351410e-01,  -3.25124830e-01,
         -2.76255041e-01,  -1.35442063e-01,   2.48270392e-01,
         -7.80096650e-02,  -3.53677630e-01,   1.83140397e-01,
         -9.01148394e-02,   3.61068577e-01,   3.04216117e-01,
         -2.11137474e-01,  -1.11838944e-01,   1.49960369e-01,
         -1.57415003e-01,   1.39597610e-01,  -4.57416521e-03,
          4.08897176e-02,  -3.75599861e-01,  -5.54241473e-03,
         -7.18339346e-03,  -2.24389568e-01,   3.27147963e-03,
         -6.52354211e-02,  -1.09974429e-01,   6.16966793e-03,
          3.89479995e-02],
(省略)

こんな値が5000個以上続く。なるほど、何も分からない。

重みの全体的な傾向を見るために、各層のノードごとに重みの二乗和を取ってプロットしてみたものが以下の図。

plt.xkcd() # 手書き風グラフ

for i in range(len(model.layers)):
    # ニューラルネットの各層について、重みの絶対値の総和をプロット
    w1 = model.layers[i].get_weights()[0]
    plt.figure()
    plt.plot(range(1, len(w1)+1), (w1**2).sum(axis=1), 'o-')
    plt.show()

f:id:liaoyuan:20180205212629p:plainf:id:liaoyuan:20180205212634p:plainf:id:liaoyuan:20180205212637p:plain
ニューラルネットの重みの絶対値の総和

この値が大きいほど、入力値が次の層に与える影響が大きくなることを意味するのだそうだ。

隠れ層の重みの意味はあまりよく分からないが、入力層に注目してみると7、10、13番目の項目が比較的大きな寄与をしていることが分かる。

前回の記事を見て、特に重みの値が大きい13番目の説明変数を意味をみると、これはLSTATすなわち低所得者の割合を表したものである。部屋数や築年数といった物件自体のデータよりも、所得の寄与が大きいというのは意外に感じる。低所得者が多い場合、そもそも住居費として支出される金額自体が少なくなるのかもしれない。ちなみに、7番目の説明変数は「1940年よりも前に建てられた家屋の割合」、10番目は「10,000ドルあたりの所得税率」を表すようだ。

そんなわけで、一番寄与の大きい説明変数LSTATと目的変数の関係を調べるために、この2つのデータから散布図を作成してみる。

from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

# 13番目の説明変数と目的変数を散布図としてプロット
plt.title("Boston House Prices")
plt.ylabel("Price ($1000)")
plt.xlabel("lower status of the population (%)")

plt.scatter(train_data[:,12], train_targets, s=7)
plt.show()

f:id:liaoyuan:20180205213145p:plain

低所得者の割合が増加すると、住宅価格も穏やかに低下していく傾向が見える。

回帰分析を用いたBoston Housing dataの予測

ディープラーニング本からは脱線するけど、LSTATと住宅価格の関係を回帰分析して、結果を比較してみたい。まずは、簡単に1次式で線形回帰する。

from sklearn import linear_model
from sklearn.metrics import mean_absolute_error

LSTAT = train_data[:,12] # 低所得者の割合

# 線形回帰
Liner = linear_model.LinearRegression()
Liner.fit(LSTAT.reshape(-1,1), train_targets)

# 回帰式 y=ax+b
a = Liner.coef_
b = Liner.intercept_
y = a*LSTAT + b

# 散布図と予測グラフをプロット
plt.title("Boston House Prices")
plt.ylabel("Price ($1000)")
plt.xlabel("lower status of the population (%)")
plt.scatter(LSTAT, train_targets, s=7)
plt.plot(LSTAT, y, "r")

# テストデータに対して、回帰式のパラメータa, bを用いて予測
test_pred = a*test_data[:,12] + b

そして、平均絶対誤差(mean absolute error)を計算してみる。テスト誤差は4110ドル程度で、当然だが前回のディープラーニングの誤差2550ドルよりも悪い。

>>> print(mean_absolute_error(train_targets, y)) # 訓練誤差
    4.57378020095 
>>> print(mean_absolute_error(test_targets, test_pred)) # テスト誤差
    4.11751840112

f:id:liaoyuan:20180205213734p:plain

更に、多項式回帰を試してみる。scikit-learnではPolynomialFeaturesという多項式基底を作成できる関数があるので、これを用いる。なお、pipelineを使えば基底の作成とモデルの学習までの処理をまとめることができる。pipelineについてはこちらのブログを参照した。

from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline

LSTAT = LSTAT.reshape(-1,1)

# 多項式回帰
regr = Pipeline([
    ('poly', PolynomialFeatures(degree=5)),
    ('linear', linear_model.LinearRegression())
])
regr.fit(LSTAT, train_targets)

# 回帰曲線を計算
xt = np.linspace(0.0, 40.0, num=1000).reshape((1000, 1))
yt = regr.predict(xt)

# 散布図と回帰曲線をプロット
plt.scatter(LSTAT, train_targets, s=7)
plt.plot(xt, yt, c="r")
plt.title("Boston House Prices")
plt.ylabel("Price ($1000)")
plt.xlabel("lower status of the population (%)")
plt.ylim(0,52)

多項式基底の次元をいくつか試してテスト誤差が小さくなるように選んだところ、5次式を用いた場合で以下の値となった。いくら貧困率が高くても住宅価格が0になってしまうことはないだろうから若干過学習気味だけど、分析のベースラインとしてはそれほど悪くないかもしれない。

>>> print(mean_absolute_error(test_targets, test_pred))
    3.934188344856719

f:id:liaoyuan:20180205213805p:plain

もちろん、ディープラーニングによる予測と同じ条件で比較するなら、全ての説明変数を用いて重回帰分析やらをする必要があるのだろうけど、更に脱線しそうなのでこのあたりで止めておきます。