LoginSignup
1
2

databricksでコンペにチャレンジ

Last updated at Posted at 2024-02-16

第2回 金融データ活用チャレンジ で最終評価49位となったモデルを紹介する

image-12.png

image-13.png

0. databricksの便利機能

databricksを使ってみた

1. 提供いただいた実行環境(MLクラスタ)

1.1. pythonバージョン

image-10.png

1.2. ライブラリバージョン

image-11.png

2. モデル概要

2.1. feature engineering

2.1.1. 年,月,曜日を特徴量(カテゴリカル変数)に入れる

年はclipしない(2074年はそのまま使う)

def setup_date(df):
  for column in DATE_COLUMNS:
    date_val = pd.to_datetime(df[column])
    df['{0}_year'.format(column)] = date_val.dt.year
    df['{0}_month'.format(column)] = date_val.dt.month
    df['{0}_dow'.format(column)] = date_val.dt.day_of_week
  return df

2.1.2. ApprovalDateとDisbursementDateの差日数を特徴量に入れる

def setup_days_between(df):
  days = pd.to_datetime(df['ApprovalDate']) - pd.to_datetime(df['DisbursementDate'])
  days = (days.dt.days // 30 * 30).clip(lower=-10000,upper=24000)
  df['days'] = days.values
  return df

2.1.3. 線形回帰モデル(LinearRegressionなど)の戦力化

差日数は決定木系モデルの場合はそのままで使えるが線形回帰系モデルではもう一段階加工しないと戦力にならないので差日数の値でグループ化(groupby)しMIS_Statusの割合と紐づける
具体的には

class DaysProbaTransformer(BaseEstimator, TransformerMixin):
  """
  daysの値(目的変数確率値)変換
  """
  def __init__(self):
    self.na_proba = 0.0
    self.proba_dict={}
    return
  def fit(self, X, y=None):
    # 差日付がnanの場合のMIS_Statusの割合を計算
    self.na_proba = X[X['NA_Date']==1][TARGET_COLUMN].mean()
    grouped = train_prepared.groupby('days')
    for key, group in grouped:
      self.proba_dict[key] = group[TARGET_COLUMN].mean()
    return self
  def transform(self, X):
    X['days'] = X['days'].apply(lambda x:self.proba_dict[x] if x in self.proba_dict else self.na_proba)
    return X

リークにならないよう常に訓練用データのみからMIS_Statusの割合を生成する
検証用データも混ぜてしまうとリーク効果で異常に成績のよい検証結果が出てしまう

2.1.4. 金額のカテゴリ変数化

'DisbursementGross','GrAppv','SBA_Appv'は線形回帰系モデルの戦力化のためにカテゴリ変数化する
ただし分布(ヒストグラム)は生の値は以下のように指数的であるので計算式で説明変数をエンコードし平滑化する
image-14.png
↓↓↓ モジュラー

alpha = train['DisbursementGross'].quantile(0.5)
def modular(x, alpha):
  val = ((x - alpha) / (x + alpha)) // 0.01 * 0.01
  return np.clip(val, -1.0, 0.75)
train['DisbursementGross'].apply(lambda x:modular(x,alpha)).plot.hist(bins=50)

image-15.png
正規分布の方が扱いやすければ
↓↓↓ 対数

train['DisbursementGross'].apply(lambda x:np.log(x+1)).plot.hist(bins=50)

image-16.png
あとは説明変数を0.1刻みとかでカテゴリ化する

2.2. アルゴリズム

2.2.1. アルゴリズムの選定

pycaretでAUCの成績の良い順にアルゴリズムを選定してもらう
image-17.png

2.2.2. ハイパーパラメータチューニング

xgboostは、AutoMLにチューニングを丸投げ

2.2.3. 選定モデルを使ったstacking ensemble

cbt_mod = make_pipeline(FillNATransformer(CAT_COLUMNS),ChoiceColumnsTransformer(TRAIN_COLUMNS),
                        CatBoostClassifier(random_state=SEED, verbose=0, min_child_samples=86,cat_features=CAT_COLUMNS,))
lda_mod = make_pipeline(CountUnitTransformer(),DaysProbaTransformer(),MoneyProbaTransformer(),ChoiceColumnsTransformer(TRAIN_COLUMNS_WITH_DAYS),VCTransformer(VC_COLUMNS),
                        DropByCorrTransformer(),
                        LinearDiscriminantAnalysis(covariance_estimator=None, n_components=None,
                            priors=None, shrinkage=0.4, solver='lsqr',
                            store_covariance=False, tol=0.0001))
lr_mod = make_pipeline(CountUnitTransformer(),DaysProbaTransformer(),MoneyProbaTransformer(),ChoiceColumnsTransformer(TRAIN_COLUMNS_WITH_DAYS),VCTransformer(VC_COLUMNS),
                        DropByCorrTransformer(),
                        LogisticRegression(random_state=SEED,max_iter=2000))
lgb_mod = make_pipeline(CountUnitTransformer(),
                        ModularTransformer(MONEY_COLUMNS),
                        ChoiceColumnsTransformer(TRAIN_COLUMNS_WITH_DAYS),
                        VCTransformer(VC_COLUMNS),
                        LGBMClassifier(random_state=SEED, verbosity=-1, min_child_samples=86))
gbc_mod = make_pipeline(CountUnitTransformer(),
                        ModularTransformer(MONEY_COLUMNS),
                        ChoiceColumnsTransformer(TRAIN_COLUMNS_WITH_DAYS),
                        NMFTransformer('Job',['NoEmp','CreateJob','RetainedJob'],1),
                        VCTransformer(VC_COLUMNS),FillNATransformer(['days'],na_val=0),
                        GradientBoostingClassifier(random_state=SEED, max_features='sqrt', min_samples_leaf=86))
ext_mod = make_pipeline(CountUnitTransformer(),
                        ModularTransformer(MONEY_COLUMNS),
                        ChoiceColumnsTransformer(TRAIN_COLUMNS_WITH_DAYS),
                        VCTransformer(VC_COLUMNS),FillNATransformer(['days'],na_val=0),
                        ExtraTreesClassifier(random_state=SEED, min_samples_leaf=86))
stk_estimators=[
    #('lgb', lgb_mod),
    #('rf', rf_mod),
    ('cbt_s', copy.deepcopy(cbt_mod)),
    ('lda_s', copy.deepcopy(lda_mod)),
    ('lgb_s', copy.deepcopy(lgb_mod)),
    ('ext_s', copy.deepcopy(ext_mod)),
    ('lr_s', copy.deepcopy(lr_mod)),
]

stk_fin_estimator=CalibratedClassifierCV(LinearSVC(random_state=SEED,max_iter=5000))

estimators = [
    #('vot',VotingClassifier(vot_estimators,voting='soft')),
    ('stk',StackingClassifier(stk_estimators,stack_method='predict_proba',final_estimator=stk_fin_estimator)),
]

2.3. モデルの学習と検証

2.3.1. Cross Validation

RepeatedStratifiedKFold(n_repeats=4, n_splits=12, random_state=SEED)

2.3.2. 評価:AUC

image-18.png

2.3.3. 評価:F1(average='macro)

predict_probaの算出値に対してthresholdを0.5~0.85までずらしながら結果を評価

f1_score(y_true, y_pred >= threshold, average='macro')

image-19.png

2.3. submission.csvの作成

検証データでF1スコアが0.68以上のモデルのみを選定しvoting ensemble

pred = pd.DataFrame()
for i, (score,[iter, name, model]) in enumerate(zip(df_score['f1_0.82'],models)):
  if score > 0.68:
    pred[i] = model.predict_proba(test_prepared)[:,1]
pred_final = (pred[pred.columns].apply(np.mean,axis=1) > 0.82).astype(int)
pred_final.value_counts()/len(pred_final)

2.4. ソース

databricksで動かしてください

3. まとめ

databricksという非常に強力な開発環境のおかげで色々なモデルを検証できました
結果的に49位という望外な結果に大満足しています
事務局ならびにdatabricks関係者にお礼申し上げます

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2