[Ubiquant Market Prediction]k-meansによるレジーム推定

こんにちは、tmrtjと申します。

私は現在株式会社音圧爆上げくんという会社に所属しており、プロKagglerとして活動しています。


私は現在プロKagglerとして、業務の一環で、現在Kaggleで行われているコンペの一つである[Ubiquant Market Prediction](https://www.kaggle.com/c/ubiquant-market-prediction)に参加しています。



また、Kaggle上にCodeとして同じ内容の記事を英語で投稿しています。
リンクは[こちら](https://www.kaggle.com/tmrtj9999/regime-classification-by-k-means)です。

# レジーム
金融市場のデータ分析においてよく使われる用語として「レジーム」というものがあります。レジームとは相場環境のことを指します。一般に、相場環境は時期によって異なることが知られており、上昇トレンドが継続している相場、あまり変動のない相場などに分けられます。このレジームがわかると、強気相場であれば普段の相場環境よりも上昇しやすいなどの推測ができ、予測に役立ちます。

相場のレジームを推定する方法は種々ありますが、今回は教師なし学習の一つであるk-means法によるクラスタリングを用いて、レジーム推定を行いました。

# 実装

まずはtrainデータを読み込んでDataFrameにします。
```reduce_mem_usage(df)```　はメモリを節約できるような変換をかける関数です。

```
import os
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
%matplotlib inline

import warnings
warnings.filterwarnings('ignore')

import lightgbm as lgbm
from lightgbm import *

df = pd.read_parquet('../input/ubiquant-parquet/train_low_mem.parquet')

def reduce_mem_usage(df):
  
    start_mem = df.memory_usage().sum() / 1024**2
    print('Memory usage of dataframe is {:.2f} MB'.format(start_mem))
    
    for col in df.columns:
        col_type = df[col].dtype
        
        if col_type != object:
            c_min = df[col].min()
            c_max = df[col].max()
            if str(col_type)[:3] == 'int':
                if c_min > np.iinfo(np.int8).min and c_max < np.iinfo(np.int8).max:
                    df[col] = df[col].astype(np.int8)
                elif c_min > np.iinfo(np.int16).min and c_max < np.iinfo(np.int16).max:
                    df[col] = df[col].astype(np.int16)
                elif c_min > np.iinfo(np.int32).min and c_max < np.iinfo(np.int32).max:
                    df[col] = df[col].astype(np.int32)
                elif c_min > np.iinfo(np.int64).min and c_max < np.iinfo(np.int64).max:
                    df[col] = df[col].astype(np.int64)  
            else:
                if c_min > np.finfo(np.float16).min and c_max < np.finfo(np.float16).max:
                    df[col] = df[col].astype(np.float16)
                elif c_min > np.finfo(np.float32).min and c_max < np.finfo(np.float32).max:
                    df[col] = df[col].astype(np.float32)
                else:
                    df[col] = df[col].astype(np.float64)
        else:
            df[col] = df[col].astype('category')

    end_mem = df.memory_usage().sum() / 1024**2
    
    print('Memory usage after optimization is: {:.2f} MB'.format(end_mem))
    print('Decreased by {:.1f}%'.format(100 * (start_mem - end_mem) / start_mem))
     
    return df

df = reduce_mem_usage(df)
```


データフレームをtrain, val, testデータに分けます。


```
features = [f'f_{i}' for i in range(300)]
target = 'target'

df_features = df[features]

X_train, X, Y_train, Y = train_test_split(df_features, df[target], train_size=0.6, shuffle=False)

X_val, X_test, Y_val, Y_test = train_test_split(X, Y, train_size=0.5, shuffle=False)
```
<br>
k-meansの最適なクラスタ数を探索します。

```

from sklearn.cluster import KMeans
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
from IPython.display import display

# Elbow Method
wcss = []

for i in range(1, 10):
    kmeans = KMeans(n_clusters = i, init = 'k-means++', max_iter = 300, n_init = 30, random_state = 0)
    kmeans.fit(X_train.head(10000))
    wcss.append(kmeans.inertia_)


plt.plot(range(1, 10), wcss)
plt.title('The elbow method')
plt.xlabel('Number of clusters')
plt.ylabel('WCSS') 
plt.show()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2465043/ce13b0b3-ef54-2efa-2ec8-591b7cad9727.png)
<br>
エルボー法では傾きがなだらかになっているところを最適なクラスタと考えます。この場合はクラスタ数５が最適です。

クラスタ数5が最適と分かったので、n_clusters=5でモデリングをし、その結果を特徴量に加えます。
<br>



```
# モデリング
clf = KMeans(n_clusters=5, random_state=0)
clf.fit(X_train.head(10000))


clf.labels_



y_pred_train = clf.predict(X_train)
X_train['k-means'] = y_pred_train
y_pred_val = clf.predict(X_val)
X_val['k-means'] = y_pred_val
y_pred_test = clf.predict(X_test)
X_test['k-means'] = y_pred_test
```

<br>
LightGBMを用います。

```
import warnings
import numpy as np
import lightgbm as lgb
from scipy.stats import pearsonr

warnings.filterwarnings('ignore')

lgb_train = lgb.Dataset(X_train, Y_train)
lgb_eval = lgb.Dataset(X_val, Y_val, reference=lgb_train)

params = {'seed': 1,
           'objective': "regression",
           'learning_rate': 0.02,
           'bagging_fraction': 0.2,
           'bagging_freq': 1,
           'feature_fraction': 0.3,
           'max_depth': 5,
           'min_child_samples': 50,
           'num_leaves': 64}
        
        
gbm = lgb.train(params,
                lgb_train,
                num_boost_round=19450815,
                valid_sets=lgb_eval,
                #verbose_eval=False,
                early_stopping_rounds=10,
                )


Y_pred = gbm.predict(X_test, num_iteration=gbm.best_iteration)

score_tuple = pearsonr(Y_test, Y_pred)
score = score_tuple[0]
print(f"Validation Pearsonr score : {score:.4f}")
```
<br>

**結果**

```
Validation Pearsonr score : 0.1158
```


<br>
特徴量重要度を表示します。


```
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

#特徴量の重要度
feature = gbm.feature_importance(importance_type='gain')

#特徴量の重要度を上から順に出力する
f = pd.DataFrame({'number': range(0, len(feature)),
             'feature': feature[:]})
f2 = f.sort_values('feature',ascending=False)

#特徴量の名前
label = X_train.columns[0:]

#特徴量の重要度順（降順）
indices = np.argsort(feature)[::-1]

for i in range(len(feature)):
    print(str(i + 1) + "   " + str(label[indices[i]]) + "   " + str(feature[indices[i]]))

plt.title('Feature Importance')
plt.bar(range(len(feature)),feature[indices], color='lightblue', align='center')
plt.xticks(range(len(feature)), label[indices], rotation = 90)
plt.xlim([-1, len(feature)])
plt.tight_layout()
plt.show()
```

<br>

**結果**

```
// ... 略

281   f_66   536.768759727478
282   k-means   535.7907552719116
283   f_24   528.5250220298767
284   f_224   524.9294509887695

// ... 略
```



特徴量重要度を確認すると、確かにk-means法による特徴量（'k-means'）が効いていることがわかります。

# さいごに
株式会社音圧爆上げくんではプロKagglerを募集しています。
ご興味のある方はぜひ以下のリンクをご覧ください。
[Wantedlyリンク](https://www.wantedly.com/projects/748285)