databricksでコンペにチャレンジ

# [第2回 金融データ活用チャレンジ ](https://signate.jp/competitions/1325)で最終評価49位となったモデルを紹介する

![image-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/6d7570c1-8161-41e2-c890-1b9db7e61e1f.png)

![image-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/d3f0a9d8-b08e-64ee-7be3-46b36f063e11.png)

## 0. databricksの便利機能
[databricksを使ってみた](https://qiita.com/t-tsue/items/60e1ab929ecc826f5657)

## 1. 提供いただいた実行環境(MLクラスタ)
### 1.1. pythonバージョン
![image-10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/3470844d-467f-7ee8-93a7-a6e2735a31fd.png)
### 1.2. ライブラリバージョン
![image-11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/54020d11-20c6-34cf-db95-6ad9bb977d81.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/6e4b919b-261d-7351-8c34-a79ca4a2e20d.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/8651c0ce-6a89-ed3c-5fa5-9f084471f866.png)
正規分布の方が扱いやすければ
↓↓↓　対数
```
train['DisbursementGross'].apply(lambda x:np.log(x+1)).plot.hist(bins=50)
```
![image-16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/ae93a5e9-8618-ef15-3e15-e4f5dbc41e16.png)
あとは説明変数を0.1刻みとかでカテゴリ化する

### 2.2. アルゴリズム

#### 2.2.1. アルゴリズムの選定
pycaretでAUCの成績の良い順にアルゴリズムを選定してもらう
![image-17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/5aa66827-28f3-0432-4c8c-eef280580b90.png)

#### 2.2.2. ハイパーパラメータチューニング
[xgboostは、AutoMLにチューニングを丸投げ](https://qiita.com/t-tsue/items/a14e14bfcebbbd16f970)


#### 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/5bd867b2-f672-4ee3-8c09-38ff296da79e.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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3687201/7be75aaa-d081-c280-28c1-c38a22bc4a28.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で動かしてください](https://gist.github.com/t-tsue/c603229d8a30d98185198cc3aecc1425)

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