前回の続きです。
前回の記事はこちら。
Summary of the previous article(前回のあらまし)
前回は、kaggle の House Prices の課題に対して、簡単にできる「新しい特徴量の作成と、不要と思われる特徴量の削除」を行いました。
結果としてScoreが、0.15160
から0.15140
へと微改善しました。
(微改善ってなんだ)
Today's result
そして今回は、数値で表現されている特徴量(カラム)に対する操作を行います。
具体的には、一部の数値データ特徴量を、カテゴリ変数(文字列)化します。
やったこと | score | |
---|---|---|
前回 | - | 0.15140 |
今回 | 一部数値特徴量のカテゴリ化 | 0.14987 |
Why?
最初この操作を本やネット上で見かけたとき、なぜ数値を文字列化するのか、なぜこれが効果があるのかわかっていませんでしたが、自分なりに考えた意味合いを記載しておきます。
なぜ数値データをわざわざ文字列データに変換するかというと、数値データにもかかわらず、
- 数値の大小関係や数値間の間隔に特段意味がない
場合があるからではないでしょうか。
こういった数値データを文字列化することによって、そのカラムにおける数値の大小関係や、数値ごとの間隔幅といった、意味をなさない情報をモデルから除去することができます。
さて、実際にやったことを見ていきます(非常に簡単です)
Part.1
1. source cord
1-0. same as the previous cord
またまた、前回と同じソースをこの「1-0.」にまとめておきます(^ワ^*)
今回ここで、やっていることは、
- モジュール読み込み
- データ読み込み
- 特徴量の追加と削除
です。
# 1. import modules
import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
from xgboost import XGBClassifier, XGBRegressor
# 2. load data
import os
for dirname, _, filenames in os.walk('/kaggle/input'):
for filename in filenames:
print(os.path.join(dirname, filename))
train = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/train.csv')
test = pd.read_csv('/kaggle/input/house-prices-advanced-regression-techniques/test.csv')
# 3-1. add new column
train['totalRoomsSF'] = train['1stFlrSF'] + train['2ndFlrSF'] + train['TotalBsmtSF']
test['totalRoomsSF'] = test['1stFlrSF'] + test['2ndFlrSF'] + test['TotalBsmtSF']
# 3-2. drop ID column
train_ID = train['Id']
test_ID = test['Id']
train = train.drop(['Id'], axis=1)
test = test.drop(['Id'], axis=1)
1-1. cast some integer columns
ここで今回の変更点の一つ目ですが、3つのカラムが数値としての大小や間隔にさほど意味がないようなので、これらを文字列化します。
ターゲットは、MSSubClass
、YrSold
、MoSold
です。
# 4. cast columns
train['MSSubClass'] = train['MSSubClass'].apply(str)
train['YrSold'] = train['YrSold'].astype(str)
train['MoSold'] = train['MoSold'].astype(str)
test['MSSubClass'] = test['MSSubClass'].apply(str)
test['YrSold'] = test['YrSold'].astype(str)
test['MoSold'] = test['MoSold'].astype(str)
MSSubClass
については、ここ(kaggle)を見てもらえるとわかりますが、明らかに数値の大小関係や間隔が、その実際の意味合いと関係ありません。
YrSold
とMoSold
については、販売された年と月です。
これが数値として意味がないかといわれると、個人的には迷いました。
ですが、落ち着いて考えてみると、1月や2月と比べて12月の方が何か物理的に大きい(もしくは小さい)といったことはないですし、1月と3月の差が、5月と7月の差と同程度になるような実質的な意味合いもなさそうなので、やはりこれもカテゴリ変数化してよさそうです。
1-2. same as the previous cord
続いて、前回と同じコードになっているところはこの1-2
にまとめておきます。
5.説明変数と目的変数の分割
6.学習データとテストデータの分割
# 5. split data into explanatory variable(説明変数) and response variable(目的変数)
tmp_train_x = train.drop('SalePrice', axis=1)
tmp_train_y = train['SalePrice']
# 6. split data into training data and test data
x_train, x_test, y_train, y_test = \
train_test_split(tmp_train_x, tmp_train_y, test_size=0.20, random_state=0)
1-3. add the target column for Label Encoding
今回、Label Encodingの対象となるカラムが増えます。
なぜかというと、、、文字列はXGBoostに投入できないから....
あれ、せっかく数値を文字列に変換したのに、また数値に戻すの?
っていう話ですが、、、まあそこは試しにそのままやってみましょう!!
# 7. LabelEncoding
from sklearn.preprocessing import LabelEncoder
x_train_label_encoded = x_train.copy()
x_test_label_encoded = x_test.copy()
# ※注意
# NOTE: trainingデータとtestデータ両方を同じルールでEncodingするために、
# 結合した状態でEncodingにかける
df_all_data = pd.concat([x_train, x_test, test])
not_expected_type_column_names = train.columns[train.dtypes == 'object']
for col_name in encode_target_columns:
target_all_data_column = df_all_data[col_name].fillna('NaN')
le = LabelEncoder()
le.fit(target_all_data_column)
target_train_column = x_train[col_name].fillna('NaN')
target_test_column = x_test[col_name].fillna('NaN')
x_train_label_encoded[col_name] = le.transform(target_train_column)
x_test_label_encoded[col_name] = le.transform(target_test_column)
1-4. same as the previous cord
ここは前回と一緒です
8.学習model作成
9.価格予測
# 8. create model(学習)
model = XGBRegressor(n_estimators=20, random_state=0)
model.fit(x_train_label_encoded, y_train)
# 9. prediction(推論)
predict_result_for_tr_data = model.predict(x_train_label_encoded)
predict_result = model.predict(x_test_label_encoded)
# 学習データをpredictした結果
rmse_of_train = np.sqrt(mean_squared_error(predict_result_for_tr_data, y_train))
rmse_of_train
8181.078833664238 (前回: 7681.997640750206) # 点数悪化してるが....
# テストデータをpredictした結果
rmse_of_test = np.sqrt(mean_squared_error(predict_result, y_test))
rmse_of_test
35119.73177043754 (前回: 30411.796986629754) # ひどく悪化してる...大丈夫か?
1-5. Encode test data for submission
最後ですが、提出用用データの特徴量についても、今回カテゴリ変数化したデータをLabel Encodingしてあげる必要があるので、ここの処理はほんの少し変わります。。
# 10. create csv for submission
test_encoded = test.copy()
# 提出用データについても、今回変換対象としたcolumn(encode_target_columns)をencode
for col_name in encode_target_columns:
target_all_data_column = df_all_data[col_name].fillna('NaN')
le = LabelEncoder()
le.fit(target_all_data_column)
target_test_column = test[col_name].fillna('NaN')
test_encoded[col_name] = le.transform(target_test_column)
predict_submission = model.predict(test_encoded)
submission = pd.DataFrame(
{'Id': test['Id'], 'SalePrice': predict_submission}
)
submission.to_csv('submission_new.csv', index=False)
この結果、スコアは0.15140
から0.14987
へと改善されました!
学習データや検証用データでの成績(RSME)は悪化しているので、これくらいは誤差なのだろうか...
Next step
次は、Label Encoding
しているところをOne Hot Encoding
に置き換えてみようと思う。
今回の(4)では、せっかくカテゴリ変数化したカラムを、わざわざLabel Encodingで再度数値に戻してしまっていた。
このせいで、おそらく若干ではあるが、数値の大小・順序関係がXGBoostによって考慮されてしまい、結果として何らかの悪影響が出ている可能性がある。
数値の大小・順序関係が残っていることによる影響を完全に除去することで、成績が改善するかどうかを見たい。
(もし改善した場合は、Label EncodingとXGBoostはもしかしたら相性が悪いと言えるかもしれない)
そのさらに次(6)は、外れ値の除去に挑戦してみたいと思っているのですが、気が変わるかもしれません₍₍ (ง ˘ω˘ )ว ⁾⁾スヤッスヤッ
References
- 参考文献
Kaggleで勝つデータ分析の技術 - 今回主に参考にした記事
kaggleの住宅価格予測問題(House Prices)を解いてみる
シリーズ一覧
No | New Trial | Score | Link | Note |
---|---|---|---|---|
1 | xgboost | 0.15663 | こちら | |
2 | Label Encoding | 0.15160 | こちら | |
3 | Add and delete column | 0.15140 | こちら | |
4 | Make integer into categorical | 0.14987 | - | 本記事 |
5 | One hot Encoding | 0.14835 | こちら | |
6 | Hyper parameters tuning | 0.13948 | こちら | |
7 | Logarithimic transformation | 0.13347 | 記事未作成 |