[SIGNATE練習問題]自動車の評価をやってみた#2 〜Metric Learningしてみた〜

前回の試しにXGBoostでやってみた記事から、距離学習を入れてみた。

※前回記事：[[SIGNATE練習問題]自動車の評価をやってみた](https://qiita.com/DataSkywalker/items/a1d54d2b717b4e17289d)

# Label EncodingとOne-hot Encodingの違い
前回はいきなりOne-hot Encodingで、カテゴリ変数化したが
Label Encodingによるカテゴリ変数化でも良いのでは？と思われたかもしれない。
という訳で、今回はまずLabel EncodingとOne-hot Encodingの2つを
検証してみる。

### ライブラリを読み込み
```python
#ライブラリインポート
import numpy as np
import pandas as pd
import pandas_profiling as pdpf
import matplotlib.pyplot as plt
from tqdm import tqdm_notebook
import umap
import metric_learn
from scipy.sparse.csgraph import connected_components
from xgboost import XGBRFClassifier
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
```
### データ読み込み
```python
#train data
train = pd.read_csv('./train.tsv',sep='\t')
#test data
test = pd.read_csv('./test.tsv', sep='\t')
```

### 目的変数と説明変数に分離
```python
#説明変数
train_df = train.drop(['id','class'], axis=1)
#test用の説明変数
test_df = test.drop('id', axis=1)
#目的変数
y = train['class']

#確認
print("train_df:",train_df.shape,
      "y:",y.shape,
      "test_df :",test_df.shape)
```
### TrainとTestを合体
```python
# trainとtestでカテゴリーが異なるとダメだから、trainとtestをマージ
length = train.shape[0]

df = pd.concat([train_df, test_df], axis=0)
print(df.columns)
```
ようやく本題！
# Label Encoding
```python
df_le = pd.concat([train_df, test_df], axis=0)

for i in list(df_le.columns):
    la_en = LabelEncoder()
    print(str(i))
    la_en = la_en.fit(df_le[i])
    la_en.fit(df_le[i])
    df_le[i] = la_en.transform(df_le[i])

train_le = df_le.iloc[:length,:]
test_le = df_le.iloc[length:,:]
display(df_le)
```
Label Encodingでは、それぞれ'0','1','2','3'などで、ただただカテゴリ変数化される。

|  | buying | maint | doors | persons | lug_boot | safety |
|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| 0 | 1 | 2 | 2 | 0 | 2 | 1 |
| 1 | 1 | 0 | 2 | 2 | 2 | 2 |
| 2 | 3 | 0 | 0 | 0 | 2 | 2 |
| 3 | 0 | 0 | 2 | 2 | 0 | 2 |
| 4 | 0 | 0 | 2 | 0 | 1 | 0 |
| ... | ... | ... | ... | ... | ... | ... |
| 859 | 1 | 1 | 2 | 0 | 0 | 0 |
| 860 | 2 | 1 | 0 | 0 | 0 | 2 |
| 861 | 3 | 3 | 2 | 0 | 0 | 2 |
| 862 | 0 | 3 | 1 | 1 | 2 | 1 |
| 863 | 1 | 0 | 2 | 0 | 2 | 0 |

# One-hot Encoding
一方、One-hot表現では、それぞれが[0,0,1],[0,1,0],[1,0,0]のように独立にラベル変数化されることがメリットである。
※ただのLabel Encodingでは、今回の['persons](乗員数)変数では
0:2人、1:4人、2:Moreとなるが、2人とMoreの平均が4人となってしまう。(ってことはMoreは6人??)

この様な独立なラベルは、独立なまま特徴量にしたいからOne-hot Encodingを使う

```python
df_oe = pd.concat([train_df, test_df], axis=0)
df_dummy = pd.get_dummies(df_oe, prefix=df.columns)

train_oe = df_dummy.iloc[:length,:]
test_oe = df_dummy.iloc[length:,:]

display(df_dummy)
```
<img width="1005" alt="スクリーンショット 2019-09-09 23.29.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358315/c19d7094-8833-5365-4293-ff3bf4a91778.png">

# 目的変数もラベル化
```python
#LabelEncoderのインスタンスを生成
le = LabelEncoder()
#ラベルを覚えさせる
le = le.fit(["unacc", "acc", "good", "vgood"])
#ラベルを整数に変換
y_label = le.transform(y)
y_label_i = le.inverse_transform(y_label)
```

# XGBoostで学習
```python
X_train_le, X_test_le, y_train_le, y_test_le = train_test_split(train_le,y_label,test_size = .2,random_state = 666,stratify = y_label)

X_train_oe, X_test_oe, y_train_oe, y_test_oe = train_test_split(train_oe,y_label,test_size = .2,random_state = 666,stratify = y_label)

import xgboost as xgb
from sklearn.metrics import accuracy_score

#### Label Encoding ####
dtrain = xgb.DMatrix(X_train_le, label=y_train_le,
                     feature_names=X_train_le.columns)
dvalid = xgb.DMatrix(X_test_le, label=y_test_le,
                    feature_names=train_le.columns)
dtest = xgb.DMatrix(test_le,
                    feature_names=train_le.columns)
xgb_params = {
    'objective': 'multi:softmax',
    'num_class': 4,
    'eval_metric': 'mlogloss',
}

evals = [(dtrain, 'train'), (dvalid, 'eval')]
evals_result = {}

bst_le = xgb.train(xgb_params,
                dtrain,
                num_boost_round=100,
                early_stopping_rounds=10,
                evals=evals,
                evals_result=evals_result
                )

#### One-hot Encoding ####
dtrain_oe = xgb.DMatrix(X_train_oe, label=y_train_oe,
                     feature_names=X_train_oe.columns)
dvalid_oe = xgb.DMatrix(X_test_oe, label=y_test_oe,
                    feature_names=train_oe.columns)
dtest_oe = xgb.DMatrix(test_oe,
                    feature_names=train_oe.columns)
xgb_params = {
    'objective': 'multi:softmax',
    'num_class': 4,
    'eval_metric': 'mlogloss',
}

evals = [(dtrain_oe, 'train'), (dvalid_oe, 'eval')]
evals_result = {}

bst_oe = xgb.train(xgb_params,
                dtrain_oe,
                num_boost_round=100,
                early_stopping_rounds=10,
                evals=evals,
                evals_result=evals_result
                )
```

# 結果
微々たる差だが、One-hotが優勢っぽい

<img width="970" alt="スクリーンショット 2019-09-09 23.36.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358315/a5dc185d-c7f6-edd7-efc3-2c31b20415fa.png">

# 特徴量増加のためMetric Learningをトライ
微妙な差で勝利したOne-hot版の説明変数を使用。
まずは、どんなデータになっているかを次元圧縮によって可視化(UMAPを使用)

```python
%time embedding = umap.UMAP().fit(train_oe)
plt.scatter(embedding.embedding_[:,0],
            embedding.embedding_[:,1],
            c=y_label,
            cmap='plasma')
plt.colorbar()
plt.savefig('./fig/one-hot_umap.png', bbox_inches='tight')
plt.show()
```
![one-hot_umap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358315/4fd726d3-48bf-ff21-47d4-a4c1bbcfc160.png)

今回は、この変数をscikit-learnのmetric learnを使って次元圧縮。

```python
met_le = metric_learn.LMNN(k=5)
met_le.fit(train_oe, y_label)
%time X_met = met_le.transform(train_oe)
%time X_met_test = met_le.transform(test_oe)

%time met_embedding = umap.UMAP().fit(X_met)
%time test_embedding = met_embedding.transform(X_met_test)

plt.scatter(met_embedding.embedding_[:,0],
            met_embedding.embedding_[:,1],
            c=y_label,
            cmap='plasma')
plt.colorbar()
plt.savefig('./fig/metric_umap.png', bbox_inches='tight')
plt.show()
```
![metric_umap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358315/cbaddcad-91b0-e345-8356-3197311a50f6.png)

何もしない時よりも綺麗に分かれてる！気がする。
この分布上にTest用のデータを並べてみる。

```python
plt.scatter(met_embedding.embedding_[:,0],
            met_embedding.embedding_[:,1],
            c=y_label,
            cmap='plasma',
            label='train')
plt.colorbar()
plt.scatter(test_embedding[:,0],
            test_embedding[:,1],
            c='gold',
            edgecolor='black',
            linewidth=.7,
            marker='*',
            s=50,
            label='test')
plt.legend()
plt.savefig('./fig/metric_test_on.png', bbox_inches='tight')
plt.show()
```

![metric_test_on.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358315/0395bc34-f89e-71ad-5b93-bd935d165ca2.png)

これだけでもなんとなく分離できそう...??

# 座標位置と距離を新たな特徴量として追加
元々のOne-hot encodingの説明変数に、今回作ってみた特徴量を追加。

```python

X_met_df = pd.DataFrame(X_met,
                        columns=np.arange(0,21,1).astype(str))

X_met_test_df = pd.DataFrame(X_met_test,
                             columns=np.arange(0,21,1).astype(str))

embe = pd.DataFrame(met_embedding.embedding_,
                    columns=['locate_x', 'locate_y'])

test_embe = pd.DataFrame(test_embedding,
                         columns=['locate_x', 'locate_y'])

met_oe = pd.concat([train_oe, X_met_df], axis=1)
met_oe = pd.concat([met_oe, embe], axis=1)

met_test_oe = pd.concat([test_oe, X_met_test_df], axis=1)
met_test_oe = pd.concat([met_test_oe, test_embe], axis=1)

print(met_oe.shape, met_test_oe.shape)

# (864, 44) (864, 44)
```

# 再度XGBoostで学習

```python
X_train_me, X_test_me, y_train_me, y_test_me = train_test_split(met_oe,
                                                                y_label, 
                                                                test_size = .2,
                                                                random_state = 666,
                                                                stratify = y_label)
dtrain_me = xgb.DMatrix(X_train_me,
                        label=y_train_me,
                        feature_names=X_train_me.columns)
dvalid_me = xgb.DMatrix(X_test_me,
                        label=y_test_me,
                        feature_names=X_train_me.columns)
dtest_me = xgb.DMatrix(met_test_oe,
                       feature_names=met_test_oe.columns)

xgb_params = {
    'objective': 'multi:softmax',
    'num_class': 4,
    'eval_metric': 'mlogloss',
}

evals = [(dtrain_me, 'train'), (dvalid_me, 'eval')]
evals_result = {}

bst_me = xgb.train(xgb_params,
                dtrain_me,
                num_boost_round=100,
                early_stopping_rounds=10,
                evals=evals,
                evals_result=evals_result
                )
```
ちなみに、特徴量寄与度はこんな感じ

```python
_, ax = plt.subplots(figsize=(12, 8))
xgb.plot_importance(bst_me,
                    ax=ax,
                    importance_type='weight',
                    show_values=True)
plt.show()
```

![feature.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358315/cf99b426-ac96-101d-c1f3-93ad79388cf8.png)

座標位置が効いていそう...

# 結果出力

```python
y_pred_me = bst_me.predict(dtest_me)
y_pred_me = y_pred_me.astype(np.int)
y_pred_me_i = le.inverse_transform(y_pred_me)

submit_me = pd.read_csv('./sample_submit.csv', sep=',',header=None)
submit_me.loc[:,1] = y_pred_me_i
submit_me.to_csv("test_submit_me.csv", index = None, header = None)
```

<img width="961" alt="スクリーンショット 2019-09-09 23.47.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358315/35d68446-0b72-da00-d532-b9359a49d813.png">

あれ！！！なんか精度下がっている！！
過学習しているのか！？

もう少し真面目に特徴量を増やすのと、過学習防止に5-foldでの検証など
まだまだやる余地はたくさんありそう。

でもMetric Learningによる特徴量生成はうまくいく可能性がありそう。

to be continued.