streamlitとAutoMLでイマドキな機械学習に挑戦してみた。

#はじめに😋
streamlitハマってます。
クールですね。
そんなクールなstreamlitを、今回は簡単な特徴量エンジニアリングに使ってみました。

👇 kaggleにあるmercariの商品金額リコメンドアルゴリズムのコンペデータをいじってみました。
[Mercari Price Suggestion Challenge | https://www.kaggle.com/c/mercari-price-suggestion-challenge](https://www.kaggle.com/c/mercari-price-suggestion-challenge)
<img width="1008" alt="スクリーンショット 2020-06-12 0.05.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/f3fcbb96-2c1c-4fe3-2479-9b73a5844a82.png">

以下のツールを利用します。
###データ整形
pandas + streamlit
[streamlit | https://www.streamlit.io/](https://www.streamlit.io/)
<img width="1631" alt="スクリーンショット 2020-06-12 0.06.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/dd542cd8-afff-d75e-6fb0-ba29431b1e3f.png">

###ビジュアライズ ・ 学習
VARISTA
[varista | https://www.varista.ai/](https://www.varista.ai/)
<img width="1438" alt="スクリーンショット 2020-06-12 0.07.38.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/21160153-86d6-c2d5-5030-2b15211834a6.png">

#とりあえず脳死でぶち込んでみる🤮
列の型も欠損値もパラメータも何も考えずにとりあえず学習にかけられるのがVARISTAのいいところですよね
<img width="1570" alt="スクリーンショット 2020-06-12 1.11.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/1b13b58d-0f52-7691-72f5-05405c25ace6.png">
結果はこんな感じです。
<img width="1675" alt="スクリーンショット 2020-06-12 0.11.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/2ca27e43-8185-a6e5-f75f-af56b87e758c.png">
####スコアは低いですね。
RMSLEは0.569でした。 リーダーボードでのトップは0.377ほどでしたのでまだまだですね。
特徴をいじってどこまで上げられるかを検証してみます。

#ブランド、カテゴリの調整
次の作業を行っていきます。
1. 小規模ブランドのカット
2. カテゴリーのパース
3. 説明文の変換

<img width="896" alt="スクリーンショット 2020-06-12 0.17.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/c9530289-dfe9-1d80-42b3-5873d1727dab.png">
####streamlitをrunします。
データを読み込んで整形していきましょう。

```python
@st.cache
def load_data():
    __df = pd.read_table('/Users/tomoki/Desktop/workspace/AI/varista-data/mercari/train.tsv')
    return __df
```


##小規模ブランドのカット
4809ブランドあるうちの上位2500ブランドのみを残し、他はノンブランドとして扱います。

```python

@st.cache
def brand_cutting(df):
    NUM_BRANDS = 2500
    pop_brand = df['brand_name'].value_counts().index[:NUM_BRANDS]
    df.loc[~df['brand_name'].isin(pop_brand), 'brand_name'] = 'NO_BRAND'
    df['is_no_brand'] = 0
    return df
```
<img width="859" alt="スクリーンショット 2020-06-12 1.06.03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/349e6e1e-3dfd-ea52-49da-052a8ca44b1c.png">

##カテゴリーのパース
カテゴリは'Men/Tops/T-shirts'のように３つの小カテゴリーから構成されているため、splitしましょう。

```python

@st.cache
def parse_category(df):
    df['first_category'] = df['category_name'].str.split(pat="/", expand=True)[0]
    df['second_category'] = df['category_name'].str.split(pat="/", expand=True)[1]
    df['third_category'] = df['category_name'].str.split(pat="/", expand=True)[2]
    return df
```
<img width="951" alt="スクリーンショット 2020-06-12 1.06.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/6cc08772-6d74-968a-4482-16e0a58fd133.png">

##説明文の変換
以下の二つの特徴を追加します。
1. 説明欄が空かどうかのフラグ
2. 説明の文字数

```python
@st.cache
def description_enc(df):
    df['is_desc_empty'] = 0
    df.loc[df['item_description'] == 'No description yet', 'is_desc_empty'] = 1
    df['desc_length'] = df['item_description'].str.len()
    df.loc[df['is_desc_empty'] == 1, 'desc_length'] = 0
    return df
```
<img width="955" alt="スクリーンショット 2020-06-12 1.07.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/3e2754fb-51ee-f62b-d47f-dc5c42a21809.png">
<img width="527" alt="スクリーンショット 2020-06-12 1.08.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/0774cb4e-5109-b828-3275-8ec67e22046e.png">
できたデータをみてみましょう。

#調整後のデータ確認
追加された列
<img width="1310" alt="スクリーンショット 2020-06-12 1.10.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/94eeca09-143b-c30f-b802-cae6df56064a.png">
ヒストグラム
<img width="1266" alt="スクリーンショット 2020-06-12 1.13.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/e7184ff2-fc21-59b7-1992-a5a59991b17d.png">

##学習結果🎉
学習結果を見てみましょう 👀
<img width="1669" alt="スクリーンショット 2020-06-12 1.14.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/d87a9c38-060c-a207-e3c4-0e4a5252750a.png">
**スコアが33に上がりました！**
詳細を比較してみます。
【Before】
<img width="1182" alt="スクリーンショット 2020-06-12 1.14.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/23b94908-e639-d01e-0ae5-412b15af9e10.png">
【After】
<img width="1188" alt="スクリーンショット 2020-06-12 1.15.01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/6d6b9722-bf35-09fb-ef64-0a17000d6a98.png">
ターゲットのRMSLEが0.01改善されましたね。
これをすこしでもよくしていくためにkagglerの人たちは、血と涙を流して戦っているんですねー。

#さらに改良
昔バイトでリサイクルショップに務めていた経験を活かし、次なる試作を考えました。
**ブランドとカテゴリの組み合わせ**が値段に関係してくるのではないかと思ったわけです。

「Nike」っていうブランドだけだと、金額は予想できません
「スニーカー」ってだけでも難しいです。
ところが「Nikeのスニーカー」だと想像しやすくないですか？？

ということで列を追加します。

```python
@st.cache
def brand_cat_enc(df):
    df['brand*cat1'] = df['brand_name'] + '_' + df['first_category']
    df['brand*cat2'] = df['brand_name'] + '_' + df['second_category']
    df['brand*cat3'] = df['brand_name'] + '_' + df['third_category']
    return df
```
<img width="768" alt="スクリーンショット 2020-06-12 1.21.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/1f1350b3-7dcf-c5cd-a525-8f9a96bb0b56.png">

「ノーブランドジュエリー」、「ノーブランド携帯アクセサリ」いい感じな気がします。
<img width="1186" alt="スクリーンショット 2020-06-12 1.25.10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/e5567ce7-0487-70f5-655f-3cf9d2fe881b.png">


改めて学習させてみましょう。
##学習結果🎉
<img width="1673" alt="スクリーンショット 2020-06-12 1.26.28.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/cae4302a-a8ac-3a76-d4d9-5398ccfff0c7.png">
**わずかですがさらにスコアが上昇しました！**
注目すべきはFeatureImportanceですね。
<img width="651" alt="スクリーンショット 2020-06-12 1.27.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/3dfcde0b-f0a4-16ca-9ca9-c688e66fdd1a.png">
**「ブランド*カテゴリ3」毎の平均価格**が大きく影響してることがわかります。
追加した甲斐がありました。
【Before】
<img width="1182" alt="スクリーンショット 2020-06-12 1.14.48.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/23b94908-e639-d01e-0ae5-412b15af9e10.png">
【After】
<img width="1185" alt="スクリーンショット 2020-06-12 1.28.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/b651dd2b-b3e9-fcb5-94df-66f3e93468e3.png">
**0.569 -> 0.541**までRMSLEが改善されてます。

#おわり✋
検証スコアではありますがリーダーボードではこの辺りです。
<img width="1089" alt="スクリーンショット 2020-06-12 1.31.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/620435/0ec74787-c947-a7d1-e26b-07a4e26d0df9.png">
まだまだトップには及ばないので、カーネルでいろんな情報を得て、改善していこうと思います。

#コード全体✒️
```python

import pandas as pd
import numpy as np
import streamlit as st


@st.cache
def load_data():
    __df = pd.read_table('/Users/tomoki/Desktop/workspace/AI/varista-data/mercari/train.tsv')
    return __df

@st.cache
def brand_cutting(df):
    NUM_BRANDS = 2500
    pop_brand = df['brand_name'].value_counts().index[:NUM_BRANDS]
    df.loc[~df['brand_name'].isin(pop_brand), 'brand_name'] = 'NO_BRAND'
    df['is_no_brand'] = 0
    return df

@st.cache
def parse_category(df):
    df['first_category'] = df['category_name'].str.split(pat="/", expand=True)[0]
    df['second_category'] = df['category_name'].str.split(pat="/", expand=True)[1]
    df['third_category'] = df['category_name'].str.split(pat="/", expand=True)[2]
    return df

@st.cache
def description_enc(df):
    df['is_desc_empty'] = 0
    df.loc[df['item_description'] == 'No description yet', 'is_desc_empty'] = 1
    df['desc_length'] = df['item_description'].str.len()
    df.loc[df['is_desc_empty'] == 1, 'desc_length'] = 0
    return df

@st.cache
def brand_cat_enc(df):
    df['brand*cat1'] = df['brand_name'] + '_' + df['first_category']
    df['brand*cat2'] = df['brand_name'] + '_' + df['second_category']
    df['brand*cat3'] = df['brand_name'] + '_' + df['third_category']
    return df


def main():
    st.title('Mercari data encoding 🎁')
    __df = load_data().copy()
    st.text(__df.shape)
    st.write(__df.head())

    st.header('Brand cutting ✂️')

    __brand_num = __df['brand_name'].nunique()
    st.subheader('Brand num : {}'.format(__brand_num))
    st.subheader('The top 2,500 brands are the most popular brands.')

    __df = brand_cutting(__df)

    st.text('Pop brand : {}'.format(__df[__df['brand_name'] != 'NO_BRAND'].shape))
    st.write(__df[__df['brand_name'] != 'NO_BRAND'].head())
    st.text('No brand: {}'.format(__df[__df['brand_name'] == 'NO_BRAND'].shape))
    st.write(__df[__df['brand_name'] == 'NO_BRAND'].head())


    st.header('Parse category 🍕')
    __df = parse_category(__df)

    st.subheader('First category : {}cat'.format(__df['first_category'].nunique()))
    st.write(__df['first_category'].value_counts().head())

    st.subheader('Second category : {}cat'.format(__df['second_category'].nunique()))
    st.write(__df['second_category'].value_counts().head())

    st.subheader('Third category : {}cat'.format(__df['third_category'].nunique()))
    st.write(__df['third_category'].value_counts().head())

    st.header('Description encoding ⚡️')
    st.subheader('Empty flag')

    __df = description_enc(__df)

    st.text('Description empty: {}'.format(__df[__df['is_desc_empty'] == 1].shape))
    st.write(__df[__df['is_desc_empty'] == 1].head())
    st.subheader('Description length (If the description is blank, 0)')
    hist = pd.cut(__df['desc_length'], 24, duplicates='drop').value_counts(sort=False)
    st.text(list(hist.index))
    st.bar_chart(list(hist.values))

    st.header('Brand * Category')
    __df = brand_cat_enc(__df)
    st.subheader('Brand * First Categiry')
    st.write(__df['brand*cat1'].value_counts().head())
    st.subheader('Brand * Second Categiry')
    st.write(__df['brand*cat2'].value_counts().head())
    st.subheader('Brand * Third Categiry')
    st.write(__df['brand*cat3'].value_counts().head())


    __df.to_csv('mercari_train.csv', index=False)
    st.header('Save mercari_train.csv 🎉')


if __name__ == "__main__":
    main()