【AIプログラミング】ビニングでボストン住宅価格の回帰、ビニングについて勉強

1_プログラミング

ビニングなるものに出会いました。

ビニングはbinningで、特徴量をいくつかのビンに分けるんだね。

機械学習で、データ自体を色々いじって新しい特徴量を作り出したりすることを、特徴量エンジニアリングというらしです。今回は、ビニングという手法を勉強しました。ぱっと見、理解に苦しむ内容なのですが、プログラムを作成してみたら意外となるほど、と感じました。ボストンの住宅価格データの部屋数「RM」を利用して視覚化しながら勉強しました。

こんな人の役に立つかも

・機械学習プログラミングの勉強をしている人

・ビニングを勉強している人

・scikit-learnでビニングのプログラミングを勉強している人

スポンサーリンク

ビニングのイメージ

線形回帰は、特徴量が1つだけだとただの直線になってしまいます。たくさんの特徴がある場合には、線形回帰はどんどんと強力になっていきますが、データによってはそうでない時もあります。

ビニングは、一つの特徴量を一定間隔のビンに分割して、それぞれの区間を違う特徴量とみなして重回帰をすることで、一つの特徴量に複雑な線を引くことができます。

ビニング

図の左は、線形回帰の単回帰になります。リッジやLassoで傾き具合をデータに適合しすぎないようにしていました。右の図が、今回やろうとしているビニングのイメージです。

ビニングは、離散化とも呼ばれるようだね。

確かに、アナログな連続的な線から、階段状のデジタルな線になっています。

単回帰のプログラムを思い出す

ボストンの住宅価格データのRMのみを利用して、単回帰を行うプログラムです。

以前の記事もご参考ください。

from sklearn.datasets import load_boston
from sklearn import linear_model
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

panda_box = load_boston()

X = panda_box.data
y = panda_box.target

#0:CRIM 1:ZN 2:INDUS 3:CHAS 4:NOX 5:RM 6:AGE 7:DIS 8:RAD 9:TAX 10:PTRATIO 11:B 12:LSTAT
X = X[:,[5]]

#訓練データとテストデータに分割(テストデータ25%)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.25, random_state=0)

train_test_splitのパラメータ、「random_state」を設定することで、乱数の種の数値を固定することで、毎回同じ分割ができるようにしました。(前回からの変更点)以前のプログラムでは、train_test_splitを実行するたびに違うデータ分割となってしまったのが、何回実行しても、同じ結果になりました。

#Ridge回帰を作成して訓練させる(alpha=0のため、普通の線形回帰)
reg = linear_model.Ridge(alpha=0).fit(X_train,y_train)

print("訓練データへの決定係数 :{:.3f}" .format(reg.score(X_train, y_train)))
print("テストデータへの決定係数:{:.3f}" .format(reg.score(X_test, y_test)))

fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(X_train, y_train, color="green")
ax.plot(X_train, reg.predict(X_train), color='orange' , linestyle = "dotted")
ax.set_xlabel("RM")
ax.set_ylabel("MEDV")

以前との違いとして、以前は、coef_とintercept_の値で愚直に計算していましたが、predictすれば普通にy軸の値がもとまるので、「ax.plot(X_train, reg.predict(X_train))」とすることで、回帰の線を描くことができるように変更しています。

オレンジの線が、線形回帰での予測線になるね。

ビニングを行う

ビニングを行う流れとして、

1.データのどこをビンとするかを決める

2.データを所属するビンの番号に変更する

3.特徴量として配列化する(ワンホットエンコーディングと呼ぶらしい)

というデータ変換を行うことで、ビニングされたデータとします。

データのどこをビンとするかを決める

numpyのlinspaceで行います。今回のRMデータは、3〜9までの範囲になるため、linspaceの範囲を3と9としました。また、今回はシンプルに見やすくするため、分割数を6としています。3〜9までの6個の分割点を得ることができます。

import numpy as np
bins = np.linspace(3,9,6)
print(bins)
[3.  4.2 5.4 6.6 7.8 9. ]

この分割境界の6個の数字は、ビンの範囲と対応してきます。

ビン1:「3~4.2」

ビン2:「4.2~5.4」

ビン3:「5.4~6.6」

ビン4:「6.6~7.8」

ビン5:「7.8~9」

のような範囲となります。

データを所属するビンの番号に変更する

numpyのdigitalizeで行います。これも、動作させて、どのようにデータが変化するのかを確認するとわかります。

sel_bins = np.digitize(X_train, bins=bins)
print(sel_bins.shape)
print(X_train[0])
print(sel_bins[0])

X_trainの一個目のデータ(X_train[0])が5.605なので、5.4〜6.6の間に所属します。sel_binsの1個目データ(sel_bins[0])にはビン3を示す、3という数値が入ります。

(379, 1)
[5.605]
[3]

全てのデータ(379個)に対してこのようにビンの番号へ変換する処理が行われます。ちなみに、この時点で、グラフ化すると、データがビン番号に変化したので、離散的なグラフになります。ここからも、離散化の意味が伝わってきます。

fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(sel_bins, y_train)
RMをビン番号に分類

特徴量として配列化する

ワンホットエンコーディングというらしいのですが、ここも、scikit-learnのOneHotEncoderという機能で変換することができます。

ワンホットエンコーディングは、下の図のようにデータを変換します。

3はビン数の配列の3番目に1を入れる
from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse=False)
encoder.fit(sel_bins)
bin_data = encoder.transform(sel_bins)

#ワンホットエンコーディングの3行目まで確認してみる
bin_data[:3]

sklearnのpreprocessingから、OneHotEncoderをimportします。

そして、エンコーダ作成後、エンコーダーに先ほどのビン番号に変換したデータ「sel_bins」を入れて訓練(fit)させます。

最後に、訓練させたencorderに、「sel_bins」を入れて「transform」させることで、「bin_data」という変数に、先ほどのようなワンホットエンコーディングの形式の配列が得られます。

array([[0., 0., 1., 0., 0.],
       [0., 0., 1., 0., 0.],
       [0., 0., 0., 1., 0.]])

線形モデルを学習させる

最後の訓練は、このビン毎に分割したデータを学習させます。これは、ビン数分のカテゴリ変数の(0または1で決定する特徴量)特徴量を訓練させるのに似ていると思います。

#リッジ回帰(線形回帰)で訓練
reg = linear_model.Ridge(alpha=0).fit(bin_data, y_train)

lineという変数に、X軸方向の数値、3〜9を1000分割した数値の配列を作成しておきます。reshapeで二次元配列の形に変換しておきます。

line_binnedには、ワンホットエンコーディングに変換したline変数を入れておきます。

line = np.linspace(3,9,1000,endpoint=False).reshape(-1,1)
line_binned = encoder.transform(np.digitize(line, bins=bins))
#print(line_binned)

fig = plt.figure()
ax = fig.add_subplot(111)
ax.scatter(X_train, y_train, color="green")
#Xに3~9の1000分割した数値、Yに訓練させたモデルにXを3~9の1000分の1づつ増加した時の値を計算させる。
ax.plot(line, reg.predict(line_binned), color='orange' , linestyle = "solid")
ax.vlines(bins, 0, 50, alpha=0.4)
ax.set_xlabel("RM")
ax.set_ylabel("MEDV")
6つのビンでビニングした時のRMへの予測線

グラフを描く時、少しややこしかったのが、

「ax.plot(line, reg.predict(line_binned), color=’orange’ , linestyle = “solid”)」のところです。

predictでy軸をプロットするのですが、lineデータをビン化したものを入れます。そうすることで、lineの配列(正確には2次元配列)の全ての数値に対してYの値が出せます。

スコアを比較してみる

まずは、普通の線形回帰です。

normal_reg = linear_model.Ridge(alpha=0).fit(X_train, y_train)
print("通常データの精度")
print(normal_reg.score(X_test,y_test))
通常データの精度
0.4679000543136782

次に、ビニングしたものの精度です。テストデータも訓練データ同様、ワンホットエンコーディング形式にしないと、モデルがデータを受け付けません。

print("5分割したビニングの精度")
#テストデータもビニング
#データをビンの番号に変換
sel_bins_test = np.digitize(X_test, bins=bins)
#ホットエンコーディング化
encoder_test = OneHotEncoder(sparse=False)
encoder.fit(sel_bins_test)
bin_data_test = encoder.transform(sel_bins_test)
#テストデータをビンにしたもので精度を評価
print(reg.score(bin_data_test,y_test))
5分割したビニングの精度
0.4778030179010067

若干精度が上昇しているようです。ビンの数を増やせば、このRMの特徴量に対しては、もっと複雑なモデルを作成することができそうです。

ビニングの分割数を増やすとどうなるか試してみようかな。

ビニングの処理をひとまとめに関数化できないかな?

いつかやってみよう。

ビニングの続きの記事はこちらもご参考ください。

タイトルとURLをコピーしました