読者です 読者をやめる 読者になる 読者になる

Obey Your MATHEMATICS.

機械学習関連の純粋数学や実験など

【TFLearn】非線形ネットワーク VS 線形ネットワーク【データ解析入門】

明けましておめでとうございます。今年もなんちゃらです。



今年はこのブログでも理論だけではなくて実験に関することもやっていけたらなあ、と思っている次第であります。

その実験第一弾にふさわしい、素晴らしい記事、と行きたいところですが凄くショボい内容ですのでご容赦ください。


テーマはタイトルにある通り


非線形ニューラルネットワーク VS 線形ニューラルネットワーク


です。


本当にその問題に非線形ネットワークが必要ですか??????


と言う問いかけです。TFLearnで実装していきます。*1

せっかくなのでデータ解析初心者の方の参考になるかもしれない*2ので、前処理からのpythonのコードも一緒に貼り付けて行きたいと思います。

§1. データ前処理

今回使うデータセットKaggleに落ちている

House Sales in King County, USA

と言うデータです。家の価格+各種家に関する条件のデータが入っており、家の価格予測モデルを作る回帰問題の練習ができます。変に雑なデータだったりはしないので、入門にもってこいです。*3


まずダウンロードして、テストデータとトレーニングデータを分けて保存しましょう。
20%をテストデータとしておきます。

import pandas as pd
import numpy as np
import csv as csv

all_df = pd.read_csv("kc_house_data.csv", header=0)
size_rows = len(all_df.index)
train_df = all_df[:int(size_rows*0.8)]
test_df = all_df[int(size_rows*0.8):]
train_df.to_csv('house_train.csv')
test_df.to_csv('house_test.csv')


次に前処理していきます。まずデータを読み込んで

・'bathroom'の数値に小数点が混じってたりするので四捨五入
・不要な列

を削除します:

train_df = pd.read_csv("house_train.csv", header=0)
test_df = pd.read_csv("house_test.csv", header=0)

#bathroomに小数点が入っているやつあるので四捨五入して整数値に
train_df["bathrooms"] = train_df["bathrooms"].apply(np.int64)

#'Unnamed: 0' 'id' 'date', zipcode' 'lat' 'long', 'waterfront'---> 削除
train_df = train_df.drop(['Unnamed: 0', 'id', 'date', 'zipcode', 'lat', 'long','waterfront'], axis=1 )
test_df = test_df.drop(['Unnamed: 0', 'id', 'date', 'zipcode', 'lat', 'long', 'waterfront'], axis=1 )


次に'yr_renovated'についての処理です。

リノベーションされている場合はその年数が、されていない場合は'0'が割当られています。

が、このまま扱うわけには行かないので、リノベーションされていないものには建設年('yr_bulit')をそのまま放り込みます。

# yr_renovated' --> してないものには'yr_built' をコピー。 
train_df.loc[(train_df.yr_renovated == 0 ),"yr_renovated"] = train_df.loc[(train_df.yr_renovated ==0), "yr_built" ]
test_df.loc[(test_df.yr_renovated == 0 ),"yr_renovated"] = test_df.loc[(test_df.yr_renovated ==0), "yr_built" ]


最後に目的変数と説明変数をわけます。

まず、トレーニングデータに関しては平均を引き、標準偏差で割って正規化し、別々の変数に格納します。

#トレーニングデータの平均と標準偏差退避 -> トレーニングデータを正規化
train_df_means = train_df.mean().values 
train_df_stds = train_df.std().values
X_train = train_df.apply(lambda x: (x-x.mean())/x.std(), axis=0).fillna(0).values[0::,1::]
Y_train = train_df.apply(lambda x: (x-x.mean())/x.std(), axis=0).fillna(0).values[0::,0]

#(ネットワークに放り込むために)Y_の型をベクトルからテンソルに変換
Y_train = np.reshape(Y_train,(-1,1))


上のコードの最初の二行で、平均と標準偏差を退避させた理由は次のとおりです:

正規化した後のトレーニングデータを使ってモデルを作るので、”トレーニングデータの”標準偏差&平均を使って正規化した空間における予測モデルが出来上がります。

ですのでテストデータを放り込んで元のスケールでの予測値を得るためには、まず

1.”トレーニングデータの説明変数の”標準偏差&平均を使ってテストデータの説明変数の正規化をする。

2.そのテストデータの説明変数を学習モデルに入れて、(トレーニングデータの標準偏差&平均で正規化された空間での)予測値を出す。

3.その予測値を、トレーニングデータの目的変数での正規化を逆変換して実際の予測値を出す。


と言うステップを踏む事になります。2&3についてはネットワークのコードの所で出てくるので、今は1の処理をしておきます;

#テスト説明変数データを上の正規化定数を使って正規化
X_test = test_df.values[0::,1::]
for i in range(X_test.shape[1]):
	X_test[0::,i] = (X_test[0::,i] - train_df_means[i+1])/train_df_stds[i+1]

#テストの目的変数は答え合わせに使うだけなので正規化の必要なし。後の便宜上行ベクトルで
Y_test = test_df.values[0::,0]
Y_test = np.reshape(Y_test,(1,-1))

§2. ネットワークの構成

前処理が終わったので、モデルを構築します。

TFLearnによるネットワークの構成は以下のとおりです。活性化関数は'tanh'にします。

#3層DNN
with tf.Graph().as_default():
	three = tflearn.input_data(shape=[None,14])
	three = tflearn.fully_connected(three, 150, activation='tanh')
	three = tflearn.fully_connected(three, 1, activation='tanh')
	three = tflearn.regression(three, optimizer='adam', learning_rate=0.0005, loss='mean_square')
	three_model = tflearn.DNN(three, tensorboard_verbose=0)
	f_number = random.randint(1,100000000)
	three_model.fit(X_train, Y_train, validation_set=0.1, batch_size=700, n_epoch=100) 
	three_dnn_output = np.array(three_model.predict(X_test))*float(train_df_stds[0]) + train_df_means[0]
	three_dnn_output = three_dnn_output.T


#8層DNN 
with tf.Graph().as_default():
	eight_net = tflearn.input_data(shape=[None,14])
	eight_net = tflearn.fully_connected(eight_net, 30, activation='tanh')
	eight_net = tflearn.fully_connected(eight_net, 40, activation='tanh')
	eight_net = tflearn.fully_connected(eight_net, 40, activation='tanh')
	eight_net = tflearn.fully_connected(eight_net, 150, activation='tanh')
	eight_net = tflearn.fully_connected(eight_net, 30, activation='tanh')
	eight_net = tflearn.fully_connected(eight_net, 10, activation='tanh')
	eight_net = tflearn.fully_connected(eight_net, 1, activation='tanh')
	eight_net = tflearn.regression(eight_net, optimizer='adam', learning_rate=0.0005, loss='mean_square')
	eight_model = tflearn.DNN(eight_net, tensorboard_verbose=0)
	eight_model.fit(X_train, Y_train, validation_set=0.1, batch_size=700, n_epoch=100) 
	eight_dnn_output = np.array(eight_model.predict(X_test))*float(train_df_stds[0]) + train_df_means[0]
	eight_dnn_output = eight_dnn_output.T

#10層DNN 
with tf.Graph().as_default():
	ten = tflearn.input_data(shape=[None,14])
	ten = tflearn.fully_connected(ten, 20, activation='tanh')
	ten = tflearn.fully_connected(ten, 30, activation='tanh')
	ten = tflearn.fully_connected(ten, 50, activation='tanh')
	ten = tflearn.fully_connected(ten, 150, activation='tanh')
	ten = tflearn.fully_connected(ten, 60, activation='tanh')
	ten = tflearn.fully_connected(ten, 40, activation='tanh')
	ten = tflearn.fully_connected(ten, 20, activation='tanh')
	ten = tflearn.fully_connected(ten, 8, activation='tanh')
	ten = tflearn.fully_connected(ten, 1, activation='tanh')
	ten = tflearn.regression(ten, optimizer='adam', learning_rate=0.001, loss='mean_square')
	ten_model = tflearn.DNN(ten, tensorboard_verbose=0)
	ten_model.fit(X_train, Y_train, validation_set=0.1, batch_size=700, n_epoch=100) 
	ten_dnn_output = np.array(ten_model.predict(X_test))*float(train_df_stds[0]) + train_df_means[0]
	ten_dnn_output = ten_dnn_output.T

各種ハイパーパラメータはちょこっと実験していじっただけで、テキトーです。ご容赦下さい。

各ネットワークの下から2行目で上で述べた(2)と(3)を実行しています。

今回のテーマは 非線形 VS 線形 なので、'tanh' を全部 'linear'に変えたものも用意しておきます。

最後に保存用のコードも一応。

three_dnn_margin = np.reshape(three_dnn_output - Y_test,(1,-1))
three_dnn_output = np.reshape(three_dnn_output,(1,-1))
eight_dnn_margin = np.reshape(eight_dnn_output - Y_test,(1,-1))
eight_dnn_output = np.reshape(eight_dnn_output,(1,-1))
ten_dnn_margin = np.reshape(ten_dnn_output - Y_test,(1,-1))
ten_dnn_output = np.reshape(ten_dnn_output,(1,-1))
result_array = np.concatenate((Y_test, three_dnn_output,eight_dnn_output, ten_dnn_output, three_dnn_margin, eight_dnn_margin, ten_dnn_margin), axis=0)
result_df = pd.DataFrame(result_array.T, columns=['Actual_price','three_dnn_output','eight_dnn_output','ten_dnn_output','three_dnn_margin', 'eight_dnn_margin', 'ten_dnn_margin'])
summary = result_df.describe()
with open('non_linear.html', 'w') as fo:
	fo.write(summary.to_html())


さあ実験の準備が整いました。


§3. 実験結果

まず、tensorboradで見る学習過程はこんな感じになりました。

f:id:mathetake:20170110225321p:plain


なんと上3つが非線形下3つが線形、と言う結果になりました。

損失関数&Validation Errorを見る限り圧倒的線形ネットワークの勝利です。*4


さて、本当に線形が勝ったのか、テストデータに対する予測結果も見てみましょう。*5

まず非線形ネットワークのサマリーがこんな感じです。

f:id:mathetake:20170110225758p:plain

各列の説明をしますと、左から

テストデータ(つまり正解) - 3層非線形の予測値 - 8層非線形の予測値 -10層非線形の予測値 - 3層非線形の予測誤差 - 8層非線形の予測誤差 - 10層非線形の予測誤差


と言う感じです。どのネットワークもまずまず、と言った感じでしょうけど、分散がちょっと小さすぎるような気がします。



さて、お待ちかね線形ネットワークの結果はと言うとこんな感じです。


f:id:mathetake:20170110230200p:plain


まあ、もうこれはもうどう考えても線形ネットワークに軍配があがるでしょう。パチパチパチ。

確かに一見良さ気な数値が出ていますが、、、、

よく見ると、資産価格のminがマイナスになったりしてます。これは不良モデルと言わざるを得ない…。

追記2016/1/11

データの対数変換をしてから線形ネットワークを上と同じ条件で学習させたら良い結果がでました:

f:id:mathetake:20170111204602p:plain

素晴らしい。

§4.終わりに

今回は

線形ネットワーク VS 非線形ネットワーク

と言うテーマで前処理から実験までごくごく単純に同様の実験設定でやってみました。

結果として線形ネットワークは今回の問題設定(資産価格(>0)の予測)上不良モデルになってしまったわけですが、*6

それ以外を除けば

線形ネットワークも意外と使えるかもよ??

と言うメッセージを伝えたかったわけです。

線形ネットワークの有効さの裏付けとして

ほとんどのデータセットに対して損失関数の極小値=大域最小値

と言う結果があり、以前の記事:

mathetake.hatenablog.com

で紹介しました。


チューニングも楽ですし、非線形を試す前に是非一度線形ネットワーク如何ですか??*7

*1: ニューラルネットワークうんぬんの前に、線形回帰やらSVCやらやれよ!と思った方、御尤もでございます。

*2:クソコードなのは仕様です。

*3:逆にリアリティが無いと言えば無いかもしれません。

*4:もちろん、もっとチューニングすれば非線形が勝ったりするかもしれません、が、ただの練習データ・セットなので気力がありません。

*5:もっと見やすい表作れば良かったですね。せめて誤差を%表示とか、ね。

*6:目標値のlogを取ってから正規化して回帰したらもっと良い結果になるかもしれません。

*7:実態はただのアファイン変換列ですが…。