Featured image of post 機械学習におけるデータ品質QAの再考

機械学習におけるデータ品質QAの再考

目次

背景

  • 機械学習におけるデータとデータソースの再考でTrain・Val・Diag・Testの役割分担や独立性の設計を扱ったが、その設計を正しく行っても、データそのものにバグがあれば意味がない
  • データ品質QAはそれ単体で1つのテーマとして扱うだけの重みがあるため、この記事として独立させる

データ品質の6つの次元

DMBOK2(データマネジメント知識体系第2版)では、データ品質を以下の6つの独立した次元で評価する。

次元英語定義チェック例
正確性Accuracyデータが現実の実体や事実を正しく表しているか住所が実際の郵便番号・都道府県と一致しているか
完全性Completeness必要なデータが欠損なく揃っているか顧客レコードの必須項目(氏名・メール・電話)が空欄でないか
一貫性Consistency複数のデータソース間・同一テーブル内で矛盾がないかCRMと基幹システムで同一顧客の名称表記が一致しているか
適時性Timelinessデータが必要なタイミングで最新の状態にあるか在庫数が当日の出荷・入荷を反映しているか
有効性Validityデータが定義されたドメイン・フォーマット・ルールに従っているかメールアドレスが「@」を含む正規形式であるか
一意性Uniqueness重複したレコードが存在しないか同一顧客が2件登録されていないか
  • 正確性と有効性は紛らわしいが、問うている対象が違う。正確性は「現実との一致」、有効性は「定義(フォーマット・ルール)との一致」を問う
  • 例えば、存在しない郵便番号を入力した場合、フォーマットとしては正しい(有効性はOK)が、現実の郵便番号ではない(正確性はNG)、という食い違いが起こりうる

品質管理の4フェーズ

品質問題への対応は、プロファイリング→検出・分類→修正→検証という4つのフェーズで回す。

フェーズ1: プロファイリング

フェーズ2: 検出・分類

  • プロファイリング結果をもとに、品質問題を上記の6次元で分類する
  • 各問題の影響度(ビジネス・モデル性能への影響)と件数を掛け合わせて、対応の優先度を決定する
品質問題の種類対応する次元例
欠損値・NULL完全性必須の電話番号が空欄
形式エラー有効性「2026/13/01」のような不正日付
範囲逸脱有効性・正確性年齢が「-5」や「200」
重複レコード一意性同一顧客が2件登録されている
システム間の不整合一貫性CRMと請求システムで住所が異なる
更新遅延適時性退職者情報がシステムに残存
現実との乖離正確性引越し後も旧住所のまま

フェーズ3: 修正

  • 欠損値補完、形式標準化、重複レコードの名寄せ、参照データとの照合、削除などを行う
  • 修正前のバックアップ取得は必須。一括訂正は効率が良い一方、本来正しかったケースまで巻き込んで壊すリスクがあるため、修正範囲は慎重に見極める

フェーズ4: 検証

  • 修正後、品質基準を達成しているかを再チェックする
  • 一度きりの検証で終わらせず、継続的なモニタリングパイプラインを構築するのが理想
  • 本記事のデータパイプラインのテストコードの回帰テスト・盲検サンプリングとWilson信頼区間による定期監視が、このフェーズに相当する

最小構成でのミニテスト

  • 数百万パターンにもなるような大きな予測対象を扱う場合、いきなり全件で学習・評価するのではなく、まず1件だけを使ってデータ生成・学習・テストを一通り試すのも有効
  • パイプライン全体(データ生成→学習→推論)を最小構成で一度通してみて、勘所を掴むのが目的
  • 1件だけの学習すら成功しない(lossが下がりきらない)なら、パターン数を増やす前にやり方自体を疑うべき
  • 逆に1件で学習できるなら、そのまま件数を横展開していけばよい
  • 機械学習分野では一般に「overfit a single batch」と呼ばれる、学習パイプラインのsanity checkの定番手法

データビュアーを作る

  • seedデータのデータビュアーは作っておくとよい
  • 大量データの品質チェックを高速化するのが目的で、データが多くなるほどチェックの負担が大きくなるため、ビュアーの有無で作業効率が大きく変わる

そもそもデータセットの確認を先にする

  • ゴミを入れたらゴミが出るだけなので、モデル改善の前にまずデータそのものをチェックするべき
  • Trainデータが一部間違っていた結果、精度が出ていないだけという初歩的なミスもある
  • そもそも分母のデータは正常か、評価のサンプルサイズは足りているか、エラーの例は妥当かをチェックしたうえで、モデルのパラメータを増やすなどの改善フローに入るべき
  • 特にAIによって生成されたデータは、確実にチェックが必要
  • 可視化した結果、現実的に高精度な分類が厳しいと分かることもある。その場合は、モデルを大きくする以外に、前提の制約自体を変えて境界線がはっきり出るように要件とデータセットを見直すという選択肢もある

データのバグを発見するためのコード

  • 内部矛盾スキャン: 同じ入力に対して異なるラベルが記録されているケースを機械的に全件抽出し、その組だけをレビューする。ランダムサンプリングよりずっと高い効率で誤りを見つけられる
1
2
3
4
5
6
7
8
9
def find_label_conflicts(dataset):
    seen = {}
    conflicts = []
    for sample in dataset:
        key = sample["input"]
        if key in seen and seen[key] != sample["label"]:
            conflicts.append((key, seen[key], sample["label"]))
        seen[key] = sample["label"]
    return conflicts
  • スキーマ検証: 必須フィールドの欠落・型の不一致・値域の逸脱などを機械的にチェックする
  • 統計的な外れ値検出: 文字数・トークン数・クラス比率などの分布を可視化し、極端な外れ値を確認する

既知パターンはフィルター、未知パターンはランダムサンプリング

  • 問題を発見する方法は、大きく2つに分かれる
1
2
既知パターン(過去に見つかったバグの型など) → フィルター・シグネチャスキャンで全件検出
未知パターン(まだ気づいていない種類の問題)   → ランダムサンプリングで無作為に見つける
  • フィルターは、既に分かっている不具合の型に一致するものを漏れなく拾うのが得意だが、そもそも想定していないパターンの不具合は検出できない
  • ランダムサンプリングは、未知のパターンに気づける可能性がある一方、件数を絞ると見逃しも多く、網羅性では劣る
  • どちらか一方では不十分で、両方を役割分担として使い分ける必要がある。詳しくはフリガナデータをクレンジングする時に直した項目のメモでも扱っている
  • ランダムサンプリングで見つかった未知パターンは、原因を特定した時点で「既知パターン」に変わる。そうなれば、次はフィルターの側に組み込み、以降は全件検出できるようにする
  • つまり、ランダムサンプリングで見つけた不具合を、フィルターとして恒久化し、さらに後述のパイプラインの修正・回帰テストへ反映するというサイクルが、データ品質QAの基本的な回し方になる

データパイプラインのテストコード

  • データを生成・変換するパイプライン自体にもバグが入り込むため、通常のソフトウェアと同様にテストが必要
  • 過去に見つかったデータバグは、回帰テストとして固定化しておく
1
2
3
4
def test_known_bug_regression():
    # 過去に発見された特定の変換バグの再発を防ぐ
    result = normalize(raw_input)
    assert result == expected_output
  • 盲検サンプリングとWilson信頼区間を使い、パイプライン適用後のデータのエラー率を定期的に監視する
$$ \hat{p} = \frac{x}{n}, \qquad \text{95\%CIはサンプル数}n\text{が小さいほど広くなる} $$

重複除去(Dedupe)

  • データセットは基本的に複数のデータソースから集めることが多く、その過程で同じサンプルが重複して混入する可能性がある
  • 重複データは単なる水増しになるだけでなく、見かけ上のサンプルサイズを実態以上に大きく見せ、独立性の前提も崩してしまう
  • 重複チェックには、少なくとも以下の軸がある
    • 元データ・データソース間の重複: 複数の生データソースをマージする際、同じサンプルが重複して入り込むのを防ぐ
    • 単一データセット内の重複: 同じサンプルがTrainなどの中で複数回カウントされていないか
    • データセット間の重複: 元データソースを分ける設計をしていても、実際にTrain・Val・Diag・Test間で重複が混入していないかを機械的に確認する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def find_duplicates(dataset):
    seen = {}
    duplicates = []
    for sample in dataset:
        key = hash(sample["input"])
        if key in seen:
            duplicates.append((seen[key], sample["id"]))
        else:
            seen[key] = sample["id"]
    return duplicates
  • 完全一致だけでなく、表記ゆれなどによる「ほぼ重複」も見逃しやすいため、正規化してから比較する、あるいは類似度ベースの重複検出も併用するとよい

データクレンジング

  • 実際に遭遇したデータクレンジングの具体的な問題・方法論は、別記事フリガナデータをクレンジングする時に直した項目のメモで詳しくまとめている
  • 大きく分けると、文字・表記レベル、読み表記レベル、意味・文脈レベル、分割・アライメントレベル、ソース・アノテーション品質レベル、分布・統計レベル、実装・インフラレベルという複数のレベルで問題が起こりうる
  • 個別のバグ分類以上に汎用性が高いのは、盲検サンプリング・独立クロスチェック・ノイズフロアの計測といった、データを直す際の方法論そのもの

まとめ

  • データ品質は、正確性・完全性・一貫性・適時性・有効性・一意性という6つの独立した次元で評価できる。正確性(現実との一致)と有効性(定義との一致)は特に混同しやすい
  • 品質問題への対応は、プロファイリング(現状把握)→検出・分類(6次元への分類と優先度づけ)→修正(補完・標準化・名寄せ)→検証(再チェックと継続的モニタリング)という4フェーズのサイクルで回す
  • 役割分担・独立性を正しく設計しても、データ自体にバグがあれば意味がない。最小構成でのミニテストやデータビュアーなど、品質チェックの基本動作から始め、まずデータそのものを確認する
  • 問題を発見する方法は、既知パターンに対するフィルター・シグネチャスキャンと、未知パターンに対するランダムサンプリングという役割分担で使い分ける
  • ランダムサンプリングで見つけた不具合は、原因が分かればフィルターとして恒久化し、パイプラインの回帰テストへ反映するというサイクルが、品質QAの基本的な回し方
  • 複数のデータソースから集める以上、重複データの混入は避けられないため、データセット内・データセット間の両方で重複チェックが必要

参考文献

Built with Hugo
テーマ Stack は Jimmy によって設計されています。