Python
機械学習
MachineLearning
データ分析
Kaggle

Kaggle まとめ: BOSCH (intro + forum discussion)

More than 1 year has passed since last update.

はじめに

過去に参加したKaggleの情報をアップしていきます.
ここでは,BOSCHのデータ紹介とフォーラムでの目立った議論をピックアップします.
コンペ優勝者のコードや役立つKernelに関しては,Kaggleまとめ:BOSCH(winner)Kaggleまとめ:BOSCH(kernels)にまとめており,こちらは概要とディスカッションのまとめになります.
(フォーラムの内容は後々追記していきます.)

本記事はPython2.7, numpy 1.11, scipy 0.17, scikit-learn 0.18, matplotlib 1.5, seaborn 0.7, pandas 0.17を使用しています.
jupyter notebook上で動作確認済みです.(%matplotlib inlineは適当に修正してください)
サンプルスクリプトを実行した際にエラー等あった場合は,コメントいただけると助かります.

目次

  1. 概要
  2. 評価指標
  3. データの紹介
  4. フォーラム
  5. 参考

背景

logo

BOSCHは世界的な機械系部品メーカーです.
世界各地に自社の製造工場を所持しており,当然ながら完成品の中には稀に不良品が含まれています.
この不良品がもたらす経済損失は大きく,不良品の発生原因は複雑です.
そこで今回の目的は,BOSCHの持つ製造工場から得られた観測データを元に,不良品と良品どちらができるかを予測することとなります.

今回の特徴的な点としては,以下が挙げられます.

  • 極端な二値分類問題: サンプル比が1000:1
  • 数値ファイル,タイムスタンプ,カテゴリデータの3ファイルからなる
  • 大量の変数: 1,000 variables
  • 大量のサンプル数: 1,000,000 samples
  • 欠損値: 数値データの96%以上が欠損値
  • 全てのデータが匿名化されている

2. 評価指標

今回の評価指標はMCCになります.

$$ MCC = \frac{(TP * TN) - (FP * FN)}{\sqrt{(TP + FP)(TP + FN)(TN + FP)(TN + FN)}}, $$

ラベルデータは,不良品の発生確率は1000個に1個程度です.
例えば,全てが良品と予測すれば正答率は99%を超えます.
F measureもしくはMCCを用いることで,このような極端な不均一データの評価を適切に行うことができます.

また,提出ファイルの形式は,Id, Responseの対応をCSVで表現します.

Id,Response
1,0
2,1
3,0
etc.

3. データの紹介

今回のデータは3種類あります.

  • numeric data
  • categorical data
  • date data

不良品が発生した場合は'Response'=1,良品の場合は'Response'=0となっています.
全てのデータは(非常に)大容量かつ匿名化されており,カラム名は全て「製造ライン_ステーション_特徴量」で表現されています.
例えば,"L3_S36_F3939"は,3番目のライン,36番目のステーション,3939番目の特徴量に関する数値データということになります.

numeric data

データが大きすぎるため,numericデータをreadするだけでも,ラップトップではプログラムが止まります.
そこでまずは,カラム名とサンプル数だけをチェックしていきます.
TRAIN_NUMERICはtrain_numeric.csvへのパスです.

check_numeric.py
import numpy as np
import pandas as pd

numeric_cols = pd.read_csv(TRAIN_NUMERIC, nrows = 1).columns.values
print numeric_cols
print 'cols.shape: ', numeric_cols.shape

F0 = pd.read_csv(TRAIN_NUMERIC, usecols=(numeric_cols[:2].tolist() + ['Response']))
print 'F0.shape: ', F0.shape

実行例はこんな感じ.

array(['Id', 'L0_S0_F0', 'L0_S0_F2', 'L0_S0_F4', 'L0_S0_F6', 'L0_S0_F8',
       'L0_S0_F10', 'L0_S0_F12', 'L0_S0_F14', 'L0_S0_F16', 'L0_S0_F18',
       'L0_S0_F20', 'L0_S0_F22', 'L0_S1_F24', 'L0_S1_F28', 'L0_S2_F32',
       'L0_S2_F36', 'L0_S2_F40', 'L0_S2_F44', 'L0_S2_F48', 'L0_S2_F52',
       'L0_S2_F56', 'L0_S2_F60', 'L0_S2_F64', 'L0_S3_F68', 'L0_S3_F72',
       .....
       'L3_S50_F4245', 'L3_S50_F4247', 'L3_S50_F4249', 'L3_S50_F4251',
       'L3_S50_F4253', 'L3_S51_F4256', 'L3_S51_F4258', 'L3_S51_F4260',
       'L3_S51_F4262', 'Response'], dtype=object)
cols.shape:  (970,)
F0.shape:  (1183747, 2)

Idはdateファイル,categoryファイルと紐付けされたパラメータです.
不良品の説明変数が968個あることがわかります.
また,サンプル数は1,183,747個と非常に大きいことがわかります.
各変数には次のような実数と欠損値が入っています.

              Id  L0_S0_F0  Response
0              4     0.030         0
1              6       NaN         0
2              7     0.088         0
3              9    -0.036         0

categorical data

同様にcategoricalデータを見て見ます.
TRAIN_CATはtrain_categorical.csvへのパスです.

check_category.py
cat_cols = pd.read_csv(TRAIN_CAT, nrows = 1).columns.values
print 'cat_cols: ', cat_cols
print 'cat_cols.shape: ', cat_cols.shape

cats = pd.read_csv(TRAIN_CAT, usecols=(cat_cols[:2].tolist()))
print 'cats.shape: ', cats.shape
print cats

実行結果です.

cat_cols: ['Id' 'L0_S1_F25' 'L0_S1_F27' ..., 'L3_S49_F4237' 'L3_S49_F4239'
 'L3_S49_F4240']

cat_cols.shape:  (2141,)

cats.shape:  (1183747, 2)

              Id L0_S1_F25
0              4       NaN
1              6       NaN
2              7       NaN
3              9       NaN
4             11       NaN
5             13       NaN
6             14       NaN
7             16       NaN
8             18       NaN

サンプル数はnumeric_dataと同じ,変数の数が2141と2倍近く増えています.
categoryデータには'Response'は入っていません.

date data

最後にdateファイルを見ていきます.
TRAIN_DATEはtrain_date.csvへのパスです.

check_date.py
date_cols = pd.read_csv(TRAIN_DATE, nrows = 1).columns.values
date = pd.read_csv(TRAIN_DATE, usecols=(date_cols[:2].tolist()))

print 'date_cols.shape: ', date_cols.shape
print date_cols
print 'date.shape: ', date.shape
print date

実行結果です.

date_cols.shape:  (1157,)
['Id' 'L0_S0_D1' 'L0_S0_D3' ..., 'L3_S51_D4259' 'L3_S51_D4261'
 'L3_S51_D4263']
date.shape:  (1183747, 2)
              Id  L0_S0_D1
0              4     82.24
1              6       NaN
2              7   1618.70
3              9   1149.20
4             11    602.64
5             13   1331.66

変数の数が1157と若干numericより増えています.サンプル数は同じです.
"L0_S0_D1"のように,変数名の最後がFからDへと変化しています.
例えば,L0_S0_D1はL0_S0_F0のタイムスタンプ,L0_S0_D3はL0_S0_F2のタイムスタンプを意味しています.
なぜnumeric dataより変数が増加しているかは調べていません.

4. Forum

フォーラムを見回って見つけた,目立ったやり取りを紹介します.
直接的な解法や,フォーラムで出てきたサンプルプログラムは別の記事にまとめてあるため,ここではノウハウや一般的な議論へ焦点を当てます.

4.1. 未知の多変数データがある場合に,最初にすべきこと

データはあるし,ラベルもある.でも最初にやることがわからない.という方に対し,参考になる投稿がありました.

Picture1.png

  1. 生データの一部でも良いので,まずはテーブルで可視化する.この時に,欠損値や閾値以上の数値データを色付けし,データ全体をざっと目視で確認する.
  2. 推定したいラベルの累積分布を,全ての変数に対して同じグラフへ描写に作成する.この時,何かしらのパターンがないかチェックする.
  3. XGBoostのfeature_importanceや,gini, entropyなど,何かしらの情報量基準を用いて重要な変数をピックアップする.
  4. 3で選択した特徴量の全組み合わせを散布図を用いてプロットする.

2~4について,Kaggleまとめ:BOSCH(kernels)の4. EDA of importance featuresにて具体的なコードとともに紹介しています.

2のサンプルコードを作成しました.

violinplotとして全ての変数をファイル出力します.
パスはお好みで設定してください.

from scipy import stats
import pandas as pd
import numpy as np
import matplotlib as mpl
mpl.use('Agg')
import matplotlib.pyplot as plt
import seaborn as sns


DATA_DIR = "../input"


TRAIN_NUMERIC = "{0}/train_numeric.csv".format(DATA_DIR)
TEST_NUMERIC = "{0}/test_numeric.csv".format(DATA_DIR)

COL_BATCH = 100
numeric_cols = pd.read_csv(TRAIN_NUMERIC, nrows = 1).columns.values

for n_ in range(len(numeric_cols)/COL_BATCH):
    cols = numeric_cols[(n_*COL_BATCH):(n_*COL_BATCH+COL_BATCH)].tolist()
    train = pd.read_csv(TRAIN_NUMERIC, index_col = 0, usecols=(cols + ['Response']))
    X_neg, X_pos = train[train['Response'] == 0].iloc[:, :-1], train[train['Response']==1].iloc[:, :-1]

    BATCH_SIZE = 10
    dummy = []
    source = train.drop('Response', axis=1)

    for n in list(range(0, train.shape[1], BATCH_SIZE)):
        data = source.iloc[:, n:n+BATCH_SIZE]
        data_cols = data.columns.tolist()
        dummy.append(pd.melt(pd.concat([data, train.Response], axis=1), id_vars = 'Response', value_vars = data_cols))

    FIGSIZE = (3*(BATCH_SIZE),4*(COL_BATCH/BATCH_SIZE))
    _, axs = plt.subplots(len(dummy), figsize = FIGSIZE)
    for data, ax in zip(dummy, axs):
        v_plots = sns.violinplot(x = 'variable',  y = 'value', hue = 'Response', data = data, ax = ax, split =True)
    v_plots.get_figure().savefig("violin_{0}.jpg".format(n_))

4の散布図作成について

今回のような欠損値を多分に含んだデータは,そのまま散布図として表示することができません.そこで,各変数を中央値で保管してからプロットします.
以下,サンプルです.

import pandas as pd
import numpy as np
import seaborn as sns

features_names = [
    'L0_S11_F298', 'L1_S24_F1672', 'L1_S24_F766', 'L1_S24_F1844',
    'L1_S24_F1632', 'L1_S24_F1723', 'L1_S24_F1846', 'L1_S25_F2761',
    'L1_S25_F2193'
]
features = pd.read_csv(TRAIN_NUMERIC, index_col = 0, usecols=(features_names + ['Response'])).reset_index()
for f in features.columns[:-1]:
    features[f][np.isnan(features[f])] = features[f].median()

X_neg, X_pos = features[features['Response'] == 0], features[features['Response']==1]
volumes = len(X_pos) if len(X_pos)<len(X_neg) else len(X_neg)
features = pd.concat([X_pos, X_neg]).reset_index(drop=True)
g = sns.pairplot(features, hue="Response", vars=test.columns.tolist()[:-1], markers='.')

4.2. データが巨大すぎる場合の対処法

今回のデータは前処理を工夫することでかなり縮小できるようです.
(8G程度のラップトップでも実行可能とのこと)

ディスカッションその1

Picture1.png

a) カテゴリデータは重複が含まれるため,duplicate dataをドロップする
b) カーネル紹介の記事でも説明していますが,dateファイルは各ステーション内の95%以上が重複しています.これらをドロップすることでdate featuresを用いることができるようになります.
c) 全てのnumericデータを使用する

aとbは今回のデータを解析した結果わかることです.
一般的な解析アプローチだけでなく,個々のデータの個性に合わせたアプローチが大事ということがよくわかります.
cは全てのnumericデータを使用するとのことですが,僕のPCは停止しました.そのままpandas.readを使用するような方法では対処できなさそうです.

ディスカッションその2

Picture1.png

raddarのコメントと同様で,前処理(完全相関を示す特徴量・重複した特徴量の削除)を行うことで,計算コスト込みで8GB memoryでも実行できるとのこと.そこまで洗練された前処理は実現することができませんでした.
優勝者のコードで理解できることを期待します.

4.3. 相関ヒートマップの活用とデータ生成

大量の変数があるものの,生データでは目立った判断材料がなさそうです.そこで,データ間の相関から特徴量を生成します.別の記事で,不良品が発生した際の相関係数の差分をヒートマップで可視化する方法について説明しました.
相関係数のヒートマップから分類問題へ役立つ変数を見つける

この方法で,不良品が発生する時に相関関係が崩れる変数の組み合わせを探し,PCA(主成分分析)で新たな変数とします.
4.1でも説明した通り,欠損値を大量に含むため,先に中央値で補完しておきます.
この時,補完した部分を1, 補完していない部分を0とした,新たなパラメータを生成しておいても良いかもしれません.

4.4. ディープラーニングを用いて分類問題を解く

最近Kaggleでは,Tensorflowを元にしたKerasが非常に活発に活用されています.今回のような極端な不均一データでは,サンプリング数の調整やドロップアウトを用いても,ディープラーニングの過学習が発生しやすく向いていないようです.
それでもKerasでアプローチしてみたいという人がいましたので,もし後で時間があれば説明を追記していきます.