Einstein DiscoveryでSalesforceのID列を特徴量にしてしまった苦い記憶【データエンジニアの失敗談】

あの時、Einstein DiscoveryのデータセットにSalesforceのレコードIDをそのまま説明変数として入れてしまった判断は、今となっては苦い記憶だ。

当時の判断と、なぜそうしてしまったのか

私が担当していたプロジェクトでは、案件の成約可能性を予測するEinstein Discoveryモデルを構築することになった。データエンジニアとして、私はSalesforceのOpportunityオブジェクトから主要なデータと、外部連携システムから取得した顧客の行動データを統合し、Discoveryに投入するデータセットを準備する役割を担っていた。 当時の私は、データセットの作成に際して「とにかく利用可能な情報を全て入れ込むべきだ」と考えていた。特にSalesforceのレコードID(例: `006xxxxxxxxxxxx`)は、各レコードを一意に識別するキーであり、データ統合の際の結合キーとしても頻繁に利用していたため、その重要性は認識していた。他のBIツールやデータウェアハウスでの分析では、IDをキーとしてドリルダウンしたり、特定のレコードに絞り込むことが多かったため、Discoveryでも何らかの「識別情報」として役に立つのではないか、という安易な発想があった。 また、Discovery自体が高度な機械学習プラットフォームであり、不要な特徴量は自動で無視してくれるだろう、という根拠のない期待も抱いていた。Data Prep (当時Dataflow) のRecipeで明示的に除外する手間を惜しんだのも事実だ。「後から問題があれば、そこで対応すればいい」という甘い考えがあった。

結果として、作成したデータセットには、以下のような項目が説明変数として含まれていた。

  • 案件ID (Opportunity.Id)
  • アカウントID (Account.Id)
  • 商談フェーズ (Opportunity.StageName)
  • 商談金額 (Opportunity.Amount)
  • リードソース (Opportunity.LeadSource)
  • その他、カスタム項目や外部データ由来の行動履歴集計値...

モデル構築後の問題点

データセットをDiscoveryに投入し、モデルの学習が完了した。結果を見た時、私はすぐに異変に気づいた。 「最も予測に寄与する因子」として、上位にSalesforceの「案件ID」が浮上してきたのだ。 当初、私は「おお、さすがDiscovery、ユニークな識別子からも何か洞察を得たのか!」と一瞬勘違いした。しかし、よくよく考えてみると、これはビジネスロジックとして完全に破綻している。案件ID自体が直接的に成約可能性を高める因子であるわけがない。これは単なる識別子であり、そのIDによって予測が変わるというのは、モデルが過学習を起こしているか、あるいはデータセットに何らかの深刻な構造的問題があることを示唆していた。 チーム内でモデルの結果を共有した際も、ビジネスサイドからは「なぜ案件IDが最も重要なのか?これを見て何を改善しろと?」という当然の疑問が呈され、明確な回答ができなかった。モデルの洞察(Insights)を見ても、案件IDと成約率の関連性を示すグラフはただのノイズにしか見えず、真にビジネスに役立つ示唆は得られなかった。 この結果を受けて、私たちはモデルの信頼性が低いと判断し、一からデータセットの見直しとモデルの再構築を行う羽目になった。

データエンジニアとしての反省と、今ならどうするか

この経験から、データエンジニアとしてEinstein Discovery、ひいては機械学習モデルのためのデータ準備において、いくつかの重要な教訓を得た。

特徴量エンジニアリングの意識

ID列は、通常、モデルの学習には不適切だ。これは、各レコードに一意に割り当てられるため、モデルがそのIDに過度に依存し、汎用性の低いモデル(過学習)を生み出す可能性が高いからだ。当時の私は、データウェアハウスやBIツールでの経験から、IDを「分析の切り口」として捉えすぎていたが、機械学習においては「意味のある特徴量」と「単なる識別子」を明確に区別する必要があった。

Data Prep (Recipe) での明示的な除外

「自動でやってくれるだろう」という期待は禁物だ。明示的に除外すべきものは除外する。当時使っていたData Prep (当時はDataflowだったが、現在はRecipeが主流) であれば、`drop`変換で簡単に不要な列を削除できたはずだ。
{
  "action": "drop",
  "parameters": {
    "columns": ["Id", "Opportunity.Id", "Account.Id"]
  }
}
あるいは、RecipeのGUI上で対象列のデータタイプを「識別子」として定義することもできる。これにより、Discoveryはモデル学習時にその列の扱い方を適切に判断してくれる。しかし、私の失敗は、そもそもその判断自体をDiscoveryに丸投げしようとした点にあった。

ビジネス要件と特徴量の関連付け

データセットを準備する前に、ビジネス側の予測したいこと、そしてその予測に寄与すると考えられる因子(特徴量)について、深く理解し、議論すべきだった。IDのような識別子は、特定のレコードを指し示すだけであり、そのレコードがなぜそうなるのか、という因果関係を説明するものではない。 今なら、データセット設計の初期段階で、以下を徹底する。
  1. 目的変数の明確化: 何を予測したいのか?(例: 成約/失注)
  2. ビジネス仮説に基づく特徴量候補の洗い出し: どのような情報が予測に役立つか?(例: 案件金額、フェーズ移行日数、営業担当者の経験、顧客の業界など)
  3. 識別子の排除: ID列は結合キーとしてのみ利用し、説明変数からは原則として除外する。特にSalesforceのシステムIDは予測モデルにとってノイズでしかないことが多い。
  4. 高カーディナリティカテゴリカル変数の検討: レコードIDほどではないにせよ、数千、数万といったユニーク値を持つカテゴリカル変数(例: 顧客名、詳細な商品コード)も、ID列と同様の問題を引き起こす可能性があるため、適切にエンコーディングするか、集約するか、あるいは除外するかを慎重に判断する。
Einstein Discoveryは強力なツールだが、それは適切なデータが与えられて初めて真価を発揮する。データエンジニアとして、その「適切なデータ」を供給する責任は重い。あの時の失敗は、その責任の重さを痛感する良い経験(と、今となっては言える)となった。 これは当時の自分向けのメモだ。

コメント