post Image
アワビの年齢を予測する

はじめに

こんにちは、tomoです。

前回記事「キノコの毒の有無を予測する」に引き続き、分析記事の投稿です。
前回は分類問題を扱いましたが、今回は連続値問題を扱います。

分析対象を探す

オープンデータで面白そうなデータがないか探します。

見つけました。
スクリーンショット 2018-09-17 1.05.51.png
Abalone Data Set

「Data Set Information」に記載されているように、アワビの長さや重さと行った特徴からアワビの年齢を予測します。

データを分析する

それではアワビのデータを分析していきます。

1.データの整形

まずはダウンロードしてきたデータを読み込み、列名を設定します。(列名はサイトで確認可能)

import pandas as pd

#データを読み込む
abalone_data = pd.read_csv('ダウンロードしたデータのパス')

#列名を設定
abalone_data.columns = ['Sex','Length','Diameter','Height','Whole weight','Shucked weight',
                        'Viscera weight','Shell weight','Rings']

#データの内容を確認
abalone_data.head()

スクリーンショット 2018-09-17 1.25.21.png

データ配布元の「Attribute Information」を確認すると、毎年貝に1.5個の輪がかかることが分かります。
つまり、Rings列を1.5で割った数字が年齢になります。

Age列を追加し、Rings列を削除します。

#Ringsを1.5で割った列を年齢として追加
abalone_data['Age'] = abalone_data['Rings']/1.5

#Ringsを削除
abalone_data = abalone_data.drop('Rings', axis=1)

#データの内容を確認
abalone_data.head()

スクリーンショット 2018-09-17 1.28.32.png
Rings列が削除され、Age列が追加されているのが確認できます。

次に、性別列がカテゴリデータのため、ダミー変数化します。

#性別列をダミー変数化
abalone_data = pd.get_dummies(abalone_data, drop_first=True, columns=['Sex'])

#データの内容を確認
abalone_data.head()

スクリーンショット 2018-09-17 1.32.50.png
ダミー変数化できています。

2.データの特性を掴む

さて、データの整形が一通り終わりました。
ここからはデータの特性を見ていきます。

まずは、散布図行列で特徴量のペアの相関関係を可視化します。

import matplotlib.pyplot as plt
import seaborn as sns

#散布図行列で特徴量のペアの相関関係を可視化
sns.pairplot(abalone_data)
plt.tight_layout()
plt.show()

image.png

可視化できました。
例えば、LengthとDiameterの相関が強いのが分かります。
長さと直径なので、相関が強いのは納得できます。

Ageの相関を見ると、Length、Diameter、Shell weightとの相関が少し強いように見えます。
Heightは外れ値の存在によって相関関係を確認しにくいですね。

そこで、数値で相関の強さを確認できるよう、相関行列を計算します。

#相関係数を計算
abalone_data_corr = abalone_data.corr()

#ヒートマップを表示
plt.figure(figsize=(10, 10))

hm = sns.heatmap(abalone_data_corr,
                 cbar=True,
                 annot=True,
                 square=True,
                 annot_kws={'size':15},
                 fmt='.2f')

plt.tight_layout()
plt.show()

image.png

AgeはShell weightとの相関が最も強いことが分かります。
また、Heightとの相関もあることが確認できます。

3.モデルの構築

ここからはモデルを構築していきます。

3-1.単線形回帰

まずは、Ageと相関の強いShell weightで単線形回帰を実施します。

#Shell weightを説明変数に、Ageを目的変数に設定
X = abalone_data['Shell weight'].values
y = abalone_data['Age'].values

データをトレーニングデータ7割、テストデータ3割に分けます。

from sklearn.model_selection import train_test_split

#トレーニングデータとテストデータに分割
(X_train, X_test, y_train, y_test) = train_test_split(X, y, test_size=0.3, random_state=0)

#意図した通りにデータが分割されているか確認
print(X_train.shape,X_test.shape,y_train.shape,y_test.shape)

(2923,) (1253,) (2923,) (1253,)

いけてます。
続いて、データをsklearnで扱えるよう2次元データに整えます。

import numpy as np

#sklearnで扱えるよう次元を調整
X_train = X_train[:, np.newaxis]
X_test = X_test[:, np.newaxis]

#意図した通りに次元が整っているか確認
print(X_train.shape,X_test.shape,y_train.shape,y_test.shape)

(2923, 1) (1253, 1) (2923,) (1253,)

うまくいっています。

それでは、作成したテストデータで単線形回帰を実施します。

from sklearn.linear_model import LinearRegression

#単線形回帰を実施
lr = LinearRegression()
lr.fit(X_train, y_train)

回帰直線を可視化します。
なお、LinearRegressionのscoreはR^2で出力されます。(式は公式サイトを参照)

#回帰直線を可視化
plt.scatter(X_test, y_test, c='steelblue', edgecolor='white', s=70)
plt.plot(X_test, lr.predict(X_test), color='black', lw=2)
plt.xlabel('Shell weight')
plt.ylabel('Age')

#R^2を表示
print("R^2:",lr.score(X_test, y_test))

R^2: 0.374373674432

image.png

R^2は0.374となりました。
グラフを見て分かるように、多くの点が回帰直線からある程度ズレていますね。

3-2.多変量線形回帰

次は、他の特徴も活用して多変量の線形回帰を実施します。

#Age以外の列を説明変数に設定
X = abalone_data.drop('Age', axis=1)
(X_train, X_test, y_train, y_test) = train_test_split(X, y, test_size=0.3, random_state=0)

print(X_train.shape,X_test.shape,y_train.shape,y_test.shape)

(2923, 9) (1253, 9) (2923,) (1253,)

説明変数の単位が同一でないため、説明変数を標準化します。

from sklearn.preprocessing import StandardScaler

#データの標準化
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
X_test_std = sc.transform(X_test)

データの準備が完了したため、多変量の線形回帰を実施します。


#多変量線形回帰を実施
lr = LinearRegression()
lr.fit(X_train_std, y_train)

#R^2を表示
print("R^2:",lr.score(X_test_std, y_test))

R^2: 0.513407324872

0.513まで上がりました。

3-3.多変量線形回帰(ノイズ除去)

次は、ノイズが支配的(相関が小さい)と考えられる特徴を削除し、線形回帰を実施します。

#ノイズが支配的と考えられる(相関が小さい)特徴を削除し、線形回帰を実施
X_clean = abalone_data.drop(['Age','Sex_I','Sex_M'], axis=1)
(X_train_clean, X_test_clean, y_train_clean, y_test_clean) = train_test_split(X_clean, y, test_size=0.3, random_state=0)

print(X_train_clean.shape,X_test_clean.shape,y_train_clean.shape,y_test_clean.shape)

(2923, 7) (1253, 7) (2923,) (1253,)

from sklearn.preprocessing import StandardScaler

#データの標準化
sc = StandardScaler()
sc.fit(X_train_clean)
X_train_clean_std = sc.transform(X_train_clean)
X_test_clean_std = sc.transform(X_test_clean)

#多変量の線形回帰を実施
lr = LinearRegression()
lr.fit(X_train_clean_std, y_train_clean)

#R^2を表示
print("R^2:",lr.score(X_test_clean_std, y_test_clean))

R^2: 0.505505382027

R^2が0.008下がりました。
ノイズだと考えていたのが違ったのでしょうか、ここは理解が必要です。

3-4.パイプラインの導入

パイプラインを導入し、標準化から学習までまとめてできるようにします。(前述の標準化の作業が不要になります。)


from sklearn.pipeline import make_pipeline

#パイプラインで線形回帰モデルを構築
pipe_linear = make_pipeline(StandardScaler(),
                       LinearRegression())

3-5.グリッドサーチの導入

続いて、網羅的な探索によって最適なパラメータを探すことができるグリッドサーチを導入します。
グリッドサーチにパイプラインをかませることで、最適なパラメータの探索だけでなく、標準化から学習までまとめてできるようになります。

param_gridの内容を網羅的に組み合わせて検証しますが、今は一旦対象外とし、線形回帰を実施します。


from sklearn.model_selection import GridSearchCV

#グリッドサーチで線形回帰を実施
gs = GridSearchCV(estimator=pipe_linear,
                  param_grid={},
                  scoring='r2',
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)

#R^2を表示
print('R^2:',gs.score(X_test,y_test))

R^2: 0.513407324872

「3-2.多変量線形回帰」と同じ結果になることを確認できました。

3-6.Lasso回帰

それでは、パイプラインをかませたグリッドサーチを活用し、Lasso回帰を実施します。

#パイプラインをLasso回帰(L1)で構築

from sklearn.linear_model import Lasso

pipe_lasso = make_pipeline(StandardScaler(),
                       Lasso())

最適化対象のパラメータ候補を作成するため、まずはパラメータを確認します。

#パラメータを確認
pipe_lasso.get_params()

スクリーンショット 2018-09-17 10.37.02.png

ドサっと出力されましたが、各パラメータの詳細は公式サイトで確認できます。

正則化の強さである’alpha’を最適化対象のパラメータとしてグリッドサーチを実施します。

#パラメータの設定
param_grid = {'lasso__alpha':[0.001,0.01,0.1,1,10,100,1000]}

#グリッドサーチを実施
gs = GridSearchCV(estimator=pipe_lasso, 
                  param_grid=param_grid,
                  scoring='r2',
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)

#R^2、ベストパラメータを表示
print('R^2:',gs.score(X_test,y_test))
print('ベストパラメータ:',gs.best_params_)

R^2: 0.513681792426
ベストパラメータ: {‘lasso__alpha’: 0.001}

スコアが若干伸びました。
また、候補の中で最も良いパラメータは「’lasso__alpha’=0.001」であることが分かります。

3-7.Ridge回帰

続いて、Ridge回帰を実施します。

#パイプラインをRidge回帰(L2)で構築

from sklearn.linear_model import Ridge

pipe_ridge = make_pipeline(StandardScaler(),
                       Ridge())

#パラメータを確認
pipe_ridge.get_params()

パラメータの出力は略します。
Lasso回帰と同様に、正則化の強さである’alpha’をパラメータとします。

#パラメータの設定
param_grid = {'ridge__alpha':[0.001,0.01,0.1,1,10,100,1000]}

#グリッドサーチを実施
gs = GridSearchCV(estimator=pipe_ridge, 
                  param_grid=param_grid,
                  scoring='r2',
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)

#R^2、ベストパラメータを表示
print('R^2:',gs.score(X_test,y_test))
print('ベストパラメータ',gs.best_params_)

R^2: 0.513407679539
ベストパラメータ {‘ridge__alpha’: 0.001}

Lassoの方が精度が高いですね。

3-8.ElasticNet回帰

続いて、LassoとRidgeをミックスしたElasticNet回帰を実施します。

#パイプラインをElasticNet回帰で構築

from sklearn.linear_model import ElasticNet

pipe_elasticnet = make_pipeline(StandardScaler(),
                       ElasticNet())

#パラメータを確認
pipe_elasticnet.get_params()

正則化の強さである’alpha’に加え、L1ペナルティとL2ペナルティの比率である’l1_ratio’をパラメータとします。

#パラメータの設定
param_grid = {'elasticnet__alpha':[0.001,0.01,0.1,1,10,100,1000],
              'elasticnet__l1_ratio':[0.1,0.3,0.5,0.7,0.9]}

#グリッドサーチを実施
gs = GridSearchCV(estimator=pipe_elasticnet, 
                  param_grid=param_grid,
                  scoring='r2',
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)

#R^2、ベストパラメータを表示
print('R^2:',gs.score(X_test, y_test))
print('ベストパラメータ:',gs.best_params_)

R^2: 0.513733130315
ベストパラメータ: {‘elasticnet_alpha’: 0.001, ‘elasticnet_l1_ratio’: 0.9}

最も高いスコアとなりました。

3-9.回帰木

続いて、回帰木を実施します。

#回帰木を構築

from sklearn.tree import DecisionTreeRegressor

#回帰木は標準化不要
estimator = DecisionTreeRegressor()

#パラメータを確認
estimator.get_params()

木の分割基準である’criterion’、木の最大の深さである’max_depth’をパラメータとします。

#パラメータを設定
param_grid = {'criterion':['mse','friedman_mse','mae'],
              'max_depth': [2,3,4,5,6,7,8,9]}

#グリッドサーチを実施
gs = GridSearchCV(estimator=estimator, 
                  param_grid=param_grid,
                  scoring='r2',
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)

#R^2、ベストパラメータを表示
print('R^2:',gs.score(X_test,y_test))
print('ベストパラメータ:',gs.best_params_)

R^2: 0.495926857462
ベストパラメータ: {‘criterion’: ‘mse’, ‘max_depth’: 5}

線形回帰より低いスコアになりました。

3-10.ランダムフォレスト回帰

続いて、ランダムフォレスト回帰を実施します。

#ランダムフォレスト回帰を構築

from sklearn.ensemble import RandomForestRegressor

estimator = RandomForestRegressor()

#パラメータを確認
estimator.get_params()

木の分割基準である’criterion’、木の最大の深さである’max_depth’に加えて、ランダムフォレストで生成する識別器の数である’n_estimators’をパラメータとします。

#パラメータを設定
param_grid = {'criterion':['mse','friedman_mse','mae'],
              'max_depth': [2,3,4,5,6,7,8,9],
              'n_estimators': [1,10,50,100]}

#グリッドサーチを実施
gs = GridSearchCV(estimator=estimator, 
                  param_grid=param_grid,
                  scoring='r2',
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)

#R^2、ベストパラメータを表示
print('R^2:',gs.score(X_test,y_test))
print('ベストパラメータ:',gs.best_params_)

R^2: 0.549377687346
ベストパラメータ: {‘criterion’: ‘mse’, ‘max_depth’: 7, ‘n_estimators’: 100}

今までで最も高い精度になりました。

3-11.勾配ブースティング

次は勾配ブースティングを実施します。
XGboostが高速で推奨されていますが、今回はGradientBoostingRegressorを使用します。

#勾配ブースティングを構築

from sklearn.ensemble import GradientBoostingRegressor

estimator = GradientBoostingRegressor()

#パラメータを確認
estimator.get_params()

木の分割基準である’criterion’、木の最大の深さである’max_depth’、生成する識別器の数である’n_estimators’に加えて、各木の結果への影響度である’learning_rate’(小さいほどロバスト性が高く、増加させるほどロバスト性が高くなる’n_estimators’とトレードオフの関係にある)、損失関数である’loss’をパラメータとします。

#パラメータの設定(計算時間の都合上、パラメータは簡略化)
param_grid = {'criterion':['mse','friedman_mse','mae'],
              'learning_rate':[0.1,0.2],
              'max_depth':[3,4,5,6,7],
              'loss':['ls', 'lad', 'huber', 'quantile'],
              'n_estimators':  [75,100,125]}

#グリッドサーチを実施
gs = GridSearchCV(estimator=estimator, 
                  param_grid=param_grid,
                  scoring='r2',
                  n_jobs=-1)
gs = gs.fit(X_train, y_train)

#R^2、ベストパラメータを表示
print('R^2:',gs.score(X_test,y_test))
print('ベストパラメータ:',gs.best_params_)

R^2: 0.538796118218
ベストパラメータ: {‘criterion’: ‘mse’, ‘learning_rate’: 0.1, ‘loss’: ‘ls’, ‘max_depth’: 4, ‘n_estimators’: 75}

ランダムフォレストの方が高い精度になりました。(ただ、パラメータを増加させた場合どうなるのか気になります。)

おわりに

今回は連続値問題へのチャレンジでした。

記事を書く中で、感覚での理解となっている箇所(特にランダムフォレスト、勾配ブースティング)を痛感したので、また「統計学入門(赤本)」、「はじめてのパターン認識」あたりに戻って基礎から修行したいと思います。

最後までお読みいただきありがとうございました。
(記事にミスや改善点等ございましたらご指摘いただけると幸いです。)


『 機械学習 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。