はじめに
時系列データを使った回帰問題にチャレンジしたことはあったのですが、分類問題はまだない。。。
というわけで、本稿では、二値分類問題にチャレンジします。
しかも、(カテゴリ値の処理方法も体験してみたかったので)カテゴリ値を含むデータで。
過去の投稿記事でもLightGBMを利用しているので、同じくLightGBMを利用して分析してゆきます。
本稿で紹介すること
- データの収集および整形
- 予測モデルの作成
- 予測精度の確認
- 特徴量重要度の確認(可視化)
本稿で紹介しないこと
- 特徴量設計
- ハイパーパラメータのチューニング
分析環境の各種情報
筆者は、いつもながらDockerコンテナとしてJupyterを起動しています。
- Python 3.8.8
- pip 21.3.1
- pandas 1.4.0
- numpy 1.22.1
- scikit-learn 1.0.2
- lightgbm 3.3.2
データの収集
こちらのサイトで公開されているCSVファイルを利用しました。
以下、上述サイトから抜粋、データに関する記載です。目的変数はIncome列、二値です。
An individual’s annual income results from various factors. Intuitively, it is influenced by the individual’s education level, age, gender, occupation, and etc.
This is a widely cited KNN dataset. I encountered it during my course, and I wish to share it here because it is a good starter example for data pre-processing and machine learning practices.
Fields
The dataset contains 16 columns
Target filed: Income
-- The income is divide into two classes: <=50K and >50K
Number of attributes: 14
-- These are the demographics and other features to describe a person
We can explore the possibility in predicting income level based on the individual’s personal information.
データの整形
最も時間を割いたのは、このStepでした。有志の記事をいくつか参考にしながら、何とか動かせたというのが正直な感想です。
結局、どうするべきか否かがいまいち分からず。。。途方に暮れました。
いろいろ調べる過程1で、Categorical Feature、one-hot encoding、Label Encodingというキーワードは出てきたのですが、、、
最終的な方針として、**「手作業でカテゴリ変数(String)を0始まりの整数(Int)に変換」**としました。
例えば、性別に対しての整形処理はこんな感じ。これをベースに説明変数の計8カラムを整形しました。
gender = ['Female', 'Male']
{v:i for i, v in enumerate(gender)}
#{'Female': 0, 'Male': 1}
df['gender'] = df['gender'].map({v:i for i, v in enumerate(gender)}).astype(int)
ちなみに、目的変数も考え方は同じ。正解('>50K'なら1、'<=50K'なら0)を割り当てました。
income_map = {'<=50K':0, '>50K':1}
df['income'] = df['income'].map(income_map).astype(int)
予測モデルの作成
データの分割ですが、時系列データではないためsklearn.model_selection.train_test_splitを使いました。
LightGBMを利用した大きな流れは、以下の記事に掲載のPythonコード2を参考にさせていただきました。
X = df.drop(['income'], axis=1)
y = df['income']
X_train, X_test , y_train, y_test = train_test_split(X , y , test_size=0.2, shuffle=False)
X_train, X_valid, y_train, y_valid = train_test_split(X_train, y_train, test_size=0.2, shuffle=False)
# データセットを生成する
lgb_train = lgb.Dataset(X_train, y_train)
lgb_valid = lgb.Dataset(X_valid, y_valid, reference=lgb_train)
# LightGBM のハイパーパラメータ
params = {
# 二値分類問題
'objective': 'binary',
# AUC の最大化を目指す
'metric': 'auc',
# Fatal の場合出力
'verbosity': -1,
# 乱数シード
'seed': 31,
# 学習率
'learning_rate': 0.02,
}
# 上記のパラメータでモデルを学習する
model = lgb.train(params, lgb_train, valid_sets=lgb_valid,
verbose_eval=50, # 50イテレーション毎に学習結果出力
num_boost_round=10000, # 最大イテレーション回数指定
early_stopping_rounds=100
)
予測精度の確認
こちらのサイトで公開されているPythonコード3を参考にしました。
以下のPythonコードでROC曲線と混合行列を確認しました。
ほぼ原文ままで、変数名とAPI参照を直したのみです。
# テストデータを予測する
y_pred = model.predict(X_test, num_iteration=model.best_iteration)
# AUCを計算
fpr, tpr, thresholds = roc_curve(np.asarray(y_test), y_pred)
print("AUC", auc(fpr, tpr))
# ROC曲線をプロット
plt.plot(fpr, tpr, label='ROC curve (area = %.2f)' %auc(fpr, tpr))
plt.legend()
plt.title('ROC curve')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.grid(True)
plt.show()
# accuracy, precisionを計算
acc = accuracy_score(np.asarray(y_test), np.round(y_pred))
precision = precision_score(np.asarray(y_test), np.round(y_pred))
print("accuracy", acc)
print("precision", precision)
# 混同行列をプロット
y_pred = np.round(y_pred)
cm = confusion_matrix(np.asarray(y_test), np.where(y_pred < 0.5, 0, 1))
cmp = ConfusionMatrixDisplay(cm, display_labels=[0,1])
cmp.plot(cmap=plt.cm.Blues)
plt.show()
特徴量重要度の確認(可視化)
特徴量重要度の確認ですが、lightgbm.plot_importanceを使いました。
以下のPythonコードで重要度の高い特徴量から可視化しました。
# 重要度としては「特徴量が分岐(ノード)の条件式で使用された回数」(=デフォルト)
lgb.plot_importance(model, figsize=(30, 15), max_num_features=30, importance_type='split')
# 重要度としては「特徴量がある分岐(ノード)において目的関数の改善に寄与した度合い」
lgb.plot_importance(model, figsize=(30, 15), max_num_features=30, importance_type='gain')
特に、2つ目の方式、gainの方をもう少し詳しく確認しました4。
結果について少し考察
上位の特徴(説明変数5)についてです。
改めてデータの概説を確認すると、家族構成(婚姻関係の有無)が大まかに分かる様子です。つまり、家庭があるから収入が高いのではなく、収入が高いから家庭をもてたのだろうな、、、と思いました。
relationship: Wife, Own-child, Husband, Not-in-family, Other-relative, Unmarried.
marital-status: Married-civ-spouse, Divorced, Never-married, Separated, Widowed, Married-spouse-absent, Married-AF-spouse.
他だと、
投資で利益を得ている人が多いのかな、、、と思いました。(これも、収入があるから投資に回しているのでは。。。)
教育を受けた期間が長いほど、収入の高い職に就いた人が多いのかな、、、これは当然というか納得感がありますね。(学歴社会だから。。。)
Notebook(Pythonコード)
githubで公開しました。このタイミングですが、参考サイトの方々に感謝申し上げます。
まとめ
カテゴリ値を含むデータでのLightGBMを使った分析の例をご紹介。
別ケース、「手作業でカテゴリ変数(String)を0始まりの整数(Int)に変換」後に「DataFrameの当該列のデータ型をカテゴリ(category)に変更」した場合でも予測モデルを作成しましたが、優位な精度差はありませんでした。(LightGBMの動きとしてはどうなんでしょう。。。もう少し深追いしてみた方が良さそう。)
Pythonプログラムだけでデータ分析を試みていますが、正直手間ですね。だからこそ、AutoMLという製品・サービスに一定の需要があるのだろうな、、、と感じました。
-
全体のまとめ | https://qiita.com/sinchir0/items/b038757e578b790ec96a#%E5%85%A8%E4%BD%93%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81 ↩
-
コード | https://qiita.com/d_desuyon/items/807e01311ad08570ee78#%E3%82%B3%E3%83%BC%E3%83%89 ↩
-
モデルの評価 | https://qiita.com/kt38k/items/3c0ee4251475b6407007#%E3%83%A2%E3%83%87%E3%83%AB%E3%81%AE%E8%A9%95%E4%BE%A1 ↩
-
特徴量重要度の算出 (2値分類編) | https://mathmatical22.xyz/2020/04/12/%E3%80%90%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%91%E3%80%91%E7%89%B9%E5%BE%B4%E9%87%8F%E9%87%8D%E8%A6%81%E5%BA%A6%E3%81%AE%E7%AE%97%E5%87%BA-lightgbm-%E3%80%90python%E3%80%91%E3%80%90%E6%A9%9F/ ↩
-
Adult Data Set | https://archive.ics.uci.edu/ml/datasets/adult ↩