ランダム分割で 70%、GroupKFold で 45% ―― CNC 工具摩耗データで見る交差検証のデータ漏えい

  • python
  • 機械学習
  • データ分析
  • scikitlearn
  • 製造業

工具が摩耗しているかどうかを、加工機の信号から当てる。そんな二値分類を公開データで組んでみたら、正解率が 70% になりました。悪くない数字です。ただ、この 70% が、学習に一度も使っていない加工でも出る数字なのかは気になりました。

結論から書くと、出ませんでした。測り方を変えただけで、同じモデルの正解率は 45% まで落ちます。この記事は、その 25 ポイントの差がどこから来たのかを手元で確かめた記録です。原因はモデルでもデータの質でもなく、train と test の分け方でした。

使ったデータ

ミシガン大学 SMART Lab が公開している CNC 加工のデータセットを使いました。ワックスのブロックを削る加工を 18 回おこない、そのあいだの加工機のドライブ信号を約 100 ミリ秒ごとに記録したものです。18 回のうち 10 回は摩耗した工具、8 回は新品の工具で削っています。ラベルは摩耗あり / なしの 2 クラスです。

信号は生の振動ではなく、各軸と主軸の出力電流・出力電力といった、比較的ゆっくりした制御信号です。私はここから各軸・主軸の電流と電力の 5 系統を選び、短い窓(40 点ぶん、約 4 秒)に切って、窓ごとに時間領域の特徴量(RMS・尖度・波高率・peak-to-peak)を計算しました。窓は半分ずつ重ねています。最終的に 849 個の窓、20 次元の特徴量です。分類器はランダムフォレスト(300 本、乱数シード固定)を使いました。

素直に測ると 70%

まずは全 849 個の窓を、層化した 5 分割交差検証(StratifiedKFold)にかけます。これで正解率 0.701 でした。

ただ、この測り方には落とし穴があります。窓は同じ 1 回の加工から数十個ずつ切り出していて、しかも半分ずつ重ねているので、隣り合う窓はほとんど同じ波形です。シャッフルすると、ある実験の似た窓が学習側と評価側の両方に入ります。その実験は送り速度も締め付け圧も材料の削れ方も、その回に固有です。モデルは摩耗を学ばなくても、「これはあの実験の窓だ」と分類できるだけで評価側を当てられます。事実上、学習で見た加工を評価でも見ていることになります。これがデータ漏えい(leakage)です。

左のランダム分割では、3つの実験を色で表した窓が、train行とtest行の両方に同じ色で混ざっている。右のグループ分割では、実験1と2はtrainに、実験3はtestにと、各実験がまるごと片側だけに置かれている

実験ごとに分けると 45%

正しくは、窓ではなく実験の単位で分けます。ある実験の窓は、まるごと学習側か評価側のどちらか一方だけに置きます。評価に回った実験は、モデルが一度も見ていない加工になります。scikit-learn なら GroupKFold で、窓に「どの実験由来か」というグループ番号を渡すだけです。

こうして測り直すと、正解率は 0.449 に落ちました。

同じモデルを2通りの分け方で評価した棒グラフ。ランダム分割は0.701、グループ分割は0.449。多数クラス基準にあたる0.54の水平線も引かれ、2本の棒のあいだに0.252の差がleakageとして矢印で示されている

差の 0.252 が、ランダム分割による過大評価の目安です。厳密な漏えい量そのものではなく、この評価設定で精度がどれだけ高く見積もられていたかを示す数字です。使ったデータも特徴量もモデルも同じで、変えたのは分け方だけでした。乱数シードを 5 通り振っても、ランダム分割は 0.70〜0.72、実験ごとの分割は 0.43〜0.46 で、差は毎回 +0.25〜+0.28 です。特定の分け方をたまたま引いた結果ではありません。

この 45% は失敗ではない

45% はかなり低い数字です。多い方の摩耗ありに全部そろえるだけの多数クラス基準(約 54%)すら下回っています。accuracy は不均衡なデータでは甘い指標ですが、macro-F1(約 0.43)や balanced accuracy(約 0.44)で見ても同じで、どれも多数クラス基準を超えません。でもこれは実装のバグではなく、このデータで正直に測ればこうなる、というだけだと思います。

主な理由は、加工が 18 回しかなく、送り速度や締め付け圧も回ごとに違うことです。摩耗の影響と、加工条件の違いの影響を、モデルが分離しきれていません。5 分割の GroupKFold では 1 fold に 3〜4 実験しか入らないので、0.449 という値自体も分け方で振れます。それを確かめるために、18 実験を 1 つずつ評価に回す Leave-One-Group-Out もやってみました。

18実験を1つずつ伏せたときの、その実験に対する正解率の棒グラフ。摩耗ありの回は赤、新品の回は青。伏せる実験によって当たりは0から1まで大きくばらつき、赤い棒は総じて高く、青い棒はほとんどが0.5の線より下にある

評価に回す実験によって、正解率は 0 から 1 まで大きくばらつきます。しかも摩耗ありの回(赤)はよく当たり、新品の回(青)はほとんど当たっていません。モデルは摩耗あり側に偏っていて、新品クラスの再現率は 3 割ほどしかありません。この偏りは、窓をシャッフルした 70% では見えず、実験ごとに分けて初めて出てきました。

この話はどこでも起きる

工具摩耗に限った話ではありません。データが自然なまとまりを持つとき(患者ごと、機械ごと、個体ごと、セッションごと、ユーザごと)、そのまとまりを無視してランダムに分けると、まとまりに固有のクセが train と test の両方に入り込みます。以前ベアリングの記事で、個体ごとに分ける交差検証を使ったのも同じ理由でした(振動と電流を比べた回)。

先に決めるべきは、本番で相手にするデータが、学習時と同じグループの続きなのか、それとも初めて見るグループなのか、です。本番が初めて見る機械や初めて見る患者なら、評価もグループ単位で分けないと、出てくる数字は本番より高く見えます。scikit-learn には GroupKFold・LeaveOneGroupOut・StratifiedGroupKFold があります。

おわりに

データ漏えいは、気づかずに作り込むのも、確かめるのも簡単です。高い正解率が出たときほど、その値が初めて見るデータでも出るのかを一度確認しておくと安全だと思います。

コードは GitHub に置きました(logicia32/leakage-lab)。データは再配布できないライセンスなので、ダウンロード手順だけ README に書いてあります。手元の zip を渡せば、この記事の数字と図がそのまま再現できます。

この記事は Zenn に初出したものを加筆・補足したものです ── Zenn の元記事を見る