設問と適用される場面
リアルタイム不正検知スコアリングのためのML特徴量パイプラインを設計してください。トレーニングセットは過去12か月間にわたり、オンラインシステムは約5,000予測/秒を処理します。各予測には約50個の特徴量が必要で、特徴量取得のレイテンシバジェットはp99で5ミリ秒です。これらの数値はトレードオフを議論するためのキャパシティ前提であり、業界のデフォルト値ではありません。
特徴量には、イベントストリームからの1時間の取引金額集計や、毎日更新されるアカウント作成後経過日数(account age)などが含まれます。一部のイベントは遅れて到着し、履歴データはバックフィルされる可能性があり、モデルの更新によって特徴量の定義が変更されることもあります。特定時点で正確な(point-in-time-correct)トレーニングデータを構築し、低レイテンシでオンライン値を配信し、欠損や古いデータを処理し、新しいバージョンをロールアウトして、オフラインとオンラインのセマンティクスが一致していることを証明する方法を説明してください。
この設問は、データエンジニアリング、機械学習エンジニアリング、MLプラットフォームの面接に適用されます。目的は「フィーチャーストアを追加する」と暗記した内容を暗唱することではありません。特徴量の定義、計算、ストレージ、配信、検証を1つの追跡可能な経路として結びつけることです。
面接官が評価しているポイント
第1に、候補者がストレージアーキテクチャを描く前に特徴量のセマンティクスを定義できるかという点です。優れた回答では、エンティティキー、データ型、ソース、変換バージョン、イベント時刻、利用可能時刻、集計ウィンドウ、鮮度要件、デフォルトポリシー、オーナーを特徴量コントラクト(feature contract)に含めます。オフラインとオンラインの両システムは、名前から意味を推測するのではなく、そのコントラクトを実装します。
第2に、候補者が時間について正しく論理的思考ができるかという点です。event_atはビジネスドメインでイベントがいつ発生したかを示し、available_atはシステムがそれを実際にいつ認識したかを示します。イベントは予測前に発生していたものの到着が予測後になった場合、サービスはそのイベントを利用できなかったはずです。event_at <= prediction_atのみをチェックするトレーニング結合(join)は、遅延した情報やバックフィルされた情報を過去に引き込んでしまう可能性があります。
第3に、候補者が異なるアクセスパターンに対して適切な計算とストレージを選択できるかという点です。オフラインのトレーニングには時間履歴と特定時点結合(point-in-time joins)が必要ですが、オンラインサービングでは通常、エンティティの最新値が必要です。ウィンドウ集計は事前計算と実体化(materialization)に適しています。現在のリクエストにのみ依存する安価な特徴量は、オンデマンドで計算できます。オフラインとオンラインの実行エンジンは異なっていても構いませんが、セマンティクスは一致している必要があり、その等価性をデータで実証しなければなりません。
第4に、候補者が安全なバージョンロールアウトと障害時の振る舞いを設計できるかという点です。モデルは自身が消費する特徴量セットのバージョンを固定(pin)すべきであり、カナリアリリースやロールバック期間中は新旧バージョンが共存できなければなりません。欠損はゼロを意味するわけではありません。古いデータ、ストアの停止、互換性のないバージョンには、それぞれ明示的なポリシーが必要です。
最後に、面接官は実行可能な検証を求めています。コントラクトチェック、ゴールデンレコード、過去の特定時点テスト、サンプリングされたオンライン特徴量ベクトル、オフラインリプレイ、遅延・順不同イベントの注入、バックフィルおよびロールバックの訓練、さらにレイテンシ、鮮度、欠損率、パリティのメトリクスです。
最初に明確にすべき質問
- 予測のタイミングとラベルウィンドウはどのようなものか? トランザクション到着時にスコアリングが行われ、チャージバックのラベルはその後30日かけて確定すると仮定します。ラベル情報が特徴量に逆流してはなりません。
- オンラインのレイテンシバジェットには何が含まれるか? p99で5ミリ秒というのは特徴量取得のみを対象としているのか、ネットワーク、シリアライゼーション、推論も含まれるのか?この回答では特徴量取得のバジェットとして扱います。
- 各特徴量はどの程度新しい必要があるか? 1時間のトランザクション合計には分単位の鮮度が必要かもしれませんが、アカウントの経過日数は日次の更新で許容される場合があります。すべての特徴量に単一のTTLを適用すべきではありません。
- イベントの遅延、重複、順不同はどの程度発生し得るか? これにより、ウォーターマーク、重複排除、再計算、オンライン上書きルールが決まります。
- 履歴バックフィルは「当時知られていた状態」を再現すべきか、それとも「修正された最新の真実」を再現すべきか? 過去のサービングを再現するには前者が必要であり、事後分析には後者が必要な場合があります。これらを1つのデータセットに混在させることはできません。
- 欠損または古いオンラインデータに対してビジネスはどのように縮退(degrade)すべきか? 不正検知システムでは、すべての欠損値をゼロにするのではなく、ベースラインモデル、人的レビュー、またはより安全側の判定を採用する場合があります。
- モデルと特徴量はどのように独立してデプロイされるか? 複数のバージョンを並行して実体化できるか、旧バージョンがどのくらいの期間保持されるか、ロールバック対象は何かを知る必要があります。
- どのようなインフラがすでに存在するか? トラフィックの少ない単一のバッチ専用モデルの場合、共有されたバージョン管理付き変換とイミュータブルなスナップショットで十分な場合があります。最初から完全な特徴量プラットフォームを前提とすべきではありません。
30秒で答えるフレームワーク
「私はまず、エンティティキー、2つのクロック、ウィンドウ、バージョン、鮮度要件を含む特徴量コントラクトを作成します。Rawイベントはイミュータブルに保ち、同じ定義からオフライン履歴と最新のオンライン値を生成します。トレーニングサンプルでは、prediction_atでの特定時点結合を使用し、event_atとavailable_atの両方で制約をかけることで、後から到着したデータが過去に紛れ込まないようにします。モデルはfeature_set_versionを固定し、新旧バージョンを並行して実体化させ、トラフィックを切り替える前にシャドー比較を行います。実際のオンライン特徴量ベクトルをサンプリングし、同じ定義を用いてRawイベントからオフラインでリプレイし、値、欠損状態、鮮度を特徴量ごとに比較します。また、遅延イベント、バックフィル、ストア障害、ロールバックを注入して検証します。」
ステップごとの詳細解説
ステップ1:各特徴量の定義を実行可能なコントラクトにする。
すべての特徴量に対して、少なくとも以下のフィールドを登録します。
| コントラクトフィールド | 目的 |
|---|---|
| エンティティおよび結合キー | 特徴量がアカウント、デバイス、トランザクションのどれに属するかを示し、誤った結合を防ぐ |
| 型とスキーマ | 書き込み前に型のドリフトや不正なNullを検出する |
| ソースと変換バージョン | トレーニングとサービングで同一の計算を再構築できるようにする |
event_atとavailable_at | イベントの発生とシステムによる認識可能時刻を分離する |
| ウィンドウとTTL/鮮度 | 集計境界と、値が古いと見なされる条件を定義する |
| デフォルトおよび縮退ポリシー | 欠損、古いデータ、障害状態にビジネス上の意味を与える |
| オフライン/オンライン可用性 | 履歴トレーニングおよび低レイテンシ配信が必要かどうかを示す |
| オーナー | 品質アラートと変更レビューの責任者を割り当てる |
一度リリースされた特徴量名は、暗黙的に定義を変更してはなりません。もしtxn_amount_sum_1hが『承認されたトランザクション』から『試行されたすべてのトランザクション』に変更される場合は、新しいバージョンまたは新しい特徴量名を公開します。古いモデルが知らずに新しい意味を消費してはなりません。
ステップ2:オフラインとオンラインのパスを同じファクトから開始する。
トランザクション、アカウントの変更、その他のソースイベントは、まずイミュータブルなログまたはリプレイ可能な履歴レイヤーに入力されます。ストリーム処理は高鮮度のウィンドウ特徴量を計算し、バッチ処理は緩やかに変化するディメンションや履歴バックフィルを計算します。両方のパスが完全な時間履歴をオフラインストアに書き込み、エンティティごとの最新の有効値をオンラインストアに実体化します。
「同じ定義」であるために、バッチジョブとストリームジョブが同じ言語を使用する必要はありません。共有された宣言的変換または共有コードを優先します。2つの実装が必要な場合は、ゴールデンレコードとリプレイパリティがリリースの前提条件(リリースゲート)となります。フィーチャーストアはこれらの制約を整理するものであり、2つの実装間の乖離を自動的に解消するわけではありません。
オンラインの書き込みはべき等でなければなりません。重複によって集計が2回加算されないよう、イベントには安定したIDを付与します。最新値を書き込む際は、特徴量のタイムスタンプとバージョンを比較し、過去の遅延結果がより新しい値を上書きしないようにします。ウィンドウ特徴量には、明示的な許容遅延インターバル、ウォーターマーク、修正ルールも必要です。
ステップ3:真に特定時点で正確なトレーニングデータを構築する。
すべてのトレーニングサンプルにはprediction_atがあります。同一エンティティに対して、基本的なas-of結合はevent_at <= prediction_atを満たす最新の特徴量バージョンを選択します。イベントが遅延したりバックフィルされたりする可能性がある場合は、さらにavailable_at <= prediction_atも満たす必要があります。
eligible_feature = same_entity
AND event_at <= prediction_at
AND available_at <= prediction_at
selected_feature = latest eligible_feature by event_at, then available_at2つ目の条件が重要です。月曜日に発生したトランザクションが水曜日に到着し、過去の予測が火曜日に行われたとします。これはビジネス時間としては過去ですが、システムの知識としては未来です。データプラットフォームがavailable_atを保持できない場合は、その時点のイミュータブルなスナップショットまたはオンライン特徴量ログを使用してください。今日の修正済みテーブルを、過去のサービングが実際に参照した状態として提示してはなりません。
「当時知られていた状態(as-known)」と「最新の修正状態(latest corrected)」は、明示的なデータセットモードとすべきです。前者は過去の特定の瞬間にモデルが見ることができたものを再現し、後者は照合や事後分析をサポートします。ラベルは個別に処理されます。成熟した観察ウィンドウを持つサンプルのみがトレーニングに入り、ラベル生成データが特徴量結合に関与することは決してありません。
ステップ4:実体化(materialization)と読み取り時計算(compute-on-read)の選択。
1時間のトランザクション金額や7日間のデバイスカウントなどのウィンドウ集計は、計算コストが高く鮮度への要求も厳しいため、ストリームからインクリメンタルに計算して実体化します。アカウントの経過日数などの低頻度な特徴量はバッチで更新できます。現在のリクエストにのみ依存し、トレーニング履歴を必要としない安価な特徴量はオンデマンドで計算でき、ストレージと同期の対象範囲を縮小できます。
オンラインストアは、リクエスト時に履歴をスキャンするのではなく、エンティティキーとfeature_set_versionによって最新値を取得します。オフラインストアは、トレーニング、バックフィル、監査のために時系列を保持します。取得APIは値とともに、特徴量のタイムスタンプ、計算バージョン、欠損状態を返すか記録し、サービング側で古さや非互換性を検出できるようにすべきです。
ステップ5:欠損、古いデータ、停止時の振る舞いを定義する。
各特徴量または特徴量グループには独自の鮮度バジェットがあります。読み取り時にprediction_at - feature_timestampを計算し、そのバジェットを超えた場合は値を古い(stale)とマークします。欠損、古いデータ、正当な数値ゼロは、それぞれ異なる3つの状態です。トレーニングデータは、サービングと同じ方法で欠損を表現しなければなりません。
リスクの度合いによって縮退方法を決定します。クリティカルでない特徴量は、モデルのトレーニングに含まれていたデフォルト値を使用できます。短時間古くなることが許容される特徴量は、前回の値を使用できます。重要な不正検知特徴量が利用できない場合は、その特徴量に依存しないモデルにルーティングするか、手動レビューに回すか、またはより保守的な判断を下します。システムが長期間にわたって暗黙のうちにデフォルト値に依存し続けないよう、すべての縮退理由をログに記録します。
ステップ6:ロールバックを担保しつつバージョンをロールアウトする。
モデル成果物は、特徴量名、スキーマ、変換バージョンを含むfeature_set_versionを固定します。v2をリリースするには、v1とv2を並行して実体化します。同じエンティティをシャドー読み取りし、過去の事例をリプレイします。カバレッジ、鮮度、値の分布、特徴量ごとの差分が基準を満たしたら、v2を消費するモデルをデプロイします。ロールバック期間が終了するまでv1を保持します。
スキーマの互換性ルールも明示的でなければなりません。オプション特徴量の追加は後方互換性があるかもしれませんが、特徴量の削除、型の変更、セマンティクスの変更には通常、新しいバージョンが必要です。モデルのデプロイ前に、オンラインストアに必要なバージョンとカバレッジがすでに存在することを確認してください。先にモデルをデプロイして後から特徴量のバックフィルを追いつかせるようなことはしてはいけません。
ステップ7:オンラインサービングの実績データを用いてパリティを検証する。
オンラインリクエストをサンプリングし、エンティティ、prediction_at、feature_set_version、各特徴量の値、特徴量のタイムスタンプ、欠損/古い状態、最終的なモデルバージョンを記録します。モデルのスコアのみを記録していると、不一致が値、時間の境界、バージョンのいずれに起因するものかを特定できなくなります。
検証プロセスはイミュータブルなソースイベントから開始し、同じ定義バージョンを使用し、同じ予測時点でオフラインベクトルを再構築して、特徴量ごとに比較します。
- 値が一致していること(浮動小数点特徴量については定義された許容誤差の範囲内であること)
- 欠損、デフォルト、古い状態が一致していること
- イベントウィンドウの境界とタイムゾーンが一致していること
- オンラインの特徴量バージョンがモデルの宣言と一致していること
- 遅延、重複、順不同、バックフィルされたイベントが決定的(deterministic)にリプレイされること
リリース前に、オンラインストアの利用不可状態、一部キーの欠損、データタイムアウト、v2からv1へのロールバックを人為的に注入します。ランタイムでは、p50/p95/p99の取得レイテンシ、実体化の遅延(materialization lag)、欠損率、デフォルト適用率、古いデータの割合、バージョン不一致率、リプレイ不一致率を監視します。モデルパフォーマンスの監視は結果を表面化させることはできますが、このような特徴量レベルの証拠の代わりにはなりません。
優れた回答例
「私はデータベースを選択する前に、特徴量コントラクトを定義します。各特徴量について、エンティティキー、型、ソース、変換バージョン、イベント時刻、システム利用可能時刻、ウィンドウ、鮮度バジェット、縮退ルールを登録します。Rawイベントはイミュータブルな履歴レイヤーに送られます。ストリーム処理は1時間のトランザクション金額など高鮮度の集計を処理し、バッチ処理はアカウントディメンションとバックフィルを処理します。両者がオフラインに時間履歴を書き込み、オンラインにエンティティごとの最新の有効値を実体化します。
トレーニングサンプルでは、特定時点結合のアンカーとしてprediction_atを使用します。特徴量はevent_at <= prediction_atとavailable_at <= prediction_atの両方を満たす必要があります。これにより、以前に発生していたもののまだ到着していなかったレコードが除外されます。過去のサービングを再現するためにas-knownデータセットを使用し、修正済みデータは事後分析用に個別に保持します。チャージバックのラベルは30日間のウィンドウが確定した後にのみ入り、ラベルソースが特徴量パイプラインに入り込むことはありません。
オンラインサービングは、タイムスタンプや状態とともに、エンティティとfeature_set_versionによって約50個の値を取得します。ウィンドウ集計は事前計算され、リクエスト時のみ必要な安価な特徴量はオンデマンドで計算されます。安定したイベントIDによって重複更新が防止され、過去の遅延バージョンがより新しいオンライン値を上書きすることはできません。各特徴量には独自の鮮度バジェットがあります。欠損、古いデータ、ゼロは区別して保持されます。重要な不正検知特徴量が利用できない場合、サービングは暗黙的にゼロで埋めるのではなく、検証済みのベースラインモデルまたは手動レビューを使用します。
モデル成果物はその特徴量セットバージョンを固定します。v2の場合、v1とv2を並行して実体化し、シャドー読み取りとリプレイ比較を実行し、カバレッジがゲートを通過した後にのみモデルを移行し、ロールバックのためにv1を保持します。パリティを証明するために、時刻とバージョンを含む実際のオンライン特徴量ベクトルをサンプリングし、イミュータブルなイベントから同じベクトルをオフラインで再構築して、すべての値と状態を比較します。リリースゲートは、遅延や順不同のイベント、バックフィル、ストア障害、バージョンロールバックもカバーします。ランタイムメトリクスには、取得p99、実体化遅延、欠損/古いデータの割合、バージョン不一致、リプレイ不一致が含まれます。
トラフィックの少ないバッチモデルが1つだけの場合、私は1つのバージョン管理された変換とイミュータブルなトレーニングスナップショットから始めます。完全な特徴量プラットフォームを導入するのは、複数のモデルが真に特徴量を共有し、履歴取得と低レイテンシ配信の両方を必要とする場合のみです。」
よくある間違い
- セマンティクスを定義する前にオンラインデータベースを選択する → 低レイテンシのストレージであっても、同名特徴量が異なる意味を持つことを防ぐことはできません → まず実行可能な特徴量コントラクトを作成する。
- 履歴結合で
event_atのみを使用する → 後から到着したレコードが過去に紛れ込みます →available_atも制約するか、当時の履歴スナップショットを再現する。 - 共有フィーチャーストアがあればパリティが保証されると思い込む → バッチパスとストリームパスで異なるウィンドウ、デフォルト値、タイムゾーンが使用される可能性があります → 共有定義、ゴールデンレコード、リプレイによって等価性を証明する。
- 遅延結果がオンライン値を上書きすることを許してしまう → 古いウィンドウがエンティティの状態を過去に戻してしまう可能性があります → 特徴量のタイムスタンプとバージョンを比較し、書き込みをべき等にする。
- すべての欠損値をゼロに変換する → ゼロが正当な値である場合もあり、障害がモデルへの入力シグナルになってしまいます → 欠損、デフォルト、古いデータ、実際のゼロを区別する。
- リリース済みの特徴量を直接インプレースで編集する → バージョン変更なしに古いモデルが新しいセマンティクスを消費してしまいます → 新しいバージョンを公開し、モデルの依存関係を固定する。
- 特徴量のバックフィルが完了する前にモデルをデプロイする → 初期のトラフィックで欠損やバージョンの混在が発生します → まず実体化し、カバレッジを検証してからモデルを移行する。
- オフラインとオンラインの分布のみを比較する → 分布が類似していても、誤ったエンティティ結合やウィンドウ境界が隠れている可能性があります → 同一リクエストに対して各特徴量をリプレイして比較する。
- 予測スコアのみをログに記録する → 障害の原因を値、時間、バージョンのいずれかに特定することができません → 実際のベクトルとそのメタデータをサンプリングする。
- すべてのユースケースに対して完全な特徴量プラットフォームを構築する → 単一のバッチモデルには不要な複雑さを持ち込むことになります → 共有、履歴、レイテンシの実際のニーズに応じて機能を導入する。
フォローアップの質問
フォローアップ1:なぜイベント時刻と利用可能時刻を統合できないのか?
イベント時刻は「ビジネスドメインでいつそれが発生したか」に答え、利用可能時刻は「システムがいつそれを知ったか」に答えます。遅延イベント、手動修正、バックフィルによってこれらは乖離します。過去の予測を再現するには両方の時間境界が必要です。そうでなければ、モデルは当時利用できなかった情報を使用してしまいます。同期的到着が監査可能で確実な保証である場合は等しくなることもありますが、その前提をデフォルトの事実として扱ってはなりません。
フォローアップ2:バッチ処理とストリーム処理は完全に同じコードを共有しなければならないか?
コードの共有は乖離を減らすため望ましいですが、唯一の有効な設計ではありません。実行エンジン、状態管理、またはパフォーマンスの要件により、2つの実装が必要になる場合があります。その場合は、コントラクトとテストデータを共有し、ゴールデンケース、境界条件、履歴イベントのリプレイによって等価性を検証し、乖離テストをリリースゲートとします。
フォローアップ3:遅延イベントはオンラインウィンドウ特徴量をどのように修正すべきか?
まず許容遅延とウィンドウクローズのルールを定義します。そのインターバル内のイベントは重複排除され、影響を受けるウィンドウを更新できますが、オンライン値を上書きできるのはより新しい特徴量バージョンのみです。インターバル外のイベントは修正ジョブまたはバックフィルジョブに入ります。過去のトレーニングデータが変更されるかどうかはデータセットモードに依存します。as-knownは過去のサービングを再現し、correctedは最新の真実を反映します。これらは個別に保存してください。
フォローアップ4:特徴量を実体化するかオンデマンドで計算するかをどのように決定するか?
計算コスト、再利用性、鮮度、取得レイテンシ、パリティリスクを比較します。多数の履歴イベントにまたがり、高度に再利用され、レイテンシに敏感なウィンドウ集計は、実体化の有力な候補です。履歴トレーニングの必要がなく、リクエスト時のみに依存する安価な特徴量は、読み取り時計算(compute-on-read)の良い候補です。単一リクエストのCPU時間だけでなく、バックフィルコストや障害の影響範囲も含めて検討してください。
フォローアップ5:サービングを中断せずに特徴量定義を変更するにはどうすればよいか?
新しい特徴量セットバージョンをリリースし、新旧バージョンを並行して実体化します。スキーマとカバレッジを検証し、オンラインエンティティをシャドー読み取りし、トラフィックのごく一部を新しいモデルに移行する前にモデルへの影響を比較します。古いモデルは旧バージョンに固定されたままであり、古いデータはロールバック期間を通じて維持されます。同じ名前のままインプレースでセマンティクスを置き換えてはなりません。
フォローアップ6:オフラインとオンラインの浮動小数点結果が完全一致しない場合はどうするか?
許容可能な数値誤差とセマンティクスの相違を区別します。特徴量ごとに絶対許容誤差または相対許容誤差を宣言し、Null、タイムゾーン、丸め、ウィンドウ境界について同一のルールを使用します。エンティティや境界による系統的な差異はエラーとして扱う必要があります。広範で大雑把なグローバル許容誤差を使用して、誤った結合、精度の切り捨て、集計順序の違いを隠蔽してはなりません。
フォローアップ7:オンラインフィーチャーストアが利用できない場合、サービングは失敗すべきか、それとも縮退すべきか?
決定はリスクと復旧可能性に依存します。低リスクのレコメンデーションでは、キャッシュやベースラインランキングを一時的に使用できます。高リスクの不正検知では、手動レビューへの転送、保守的なルールの適用、または安全に評価できないリクエストの拒否を行います。この戦略はトレーニングや訓練に反映され、理由がログに記録され、障害モードが通常運用化しないように期間とトラフィックの制限を設ける必要があります。
フォローアップ8:新しいパイプラインがトレーニング・サービングスキューを削減したことをどのように証明するか?
通常、欠損、ウィンドウ境界、遅延、バックフィルのケースをカバーする過去のリクエストを選択し、実際のオンラインベクトルとバージョンを保存します。イミュータブルなイベントから同じprediction_atでオフライン上に再構築し、すべての値と状態を比較します。ローンチ後も同様の照合サンプリングを継続し、不一致率が事前定義されたゲートを下回り続けることを義務付けます。分布プロットやモデルメトリクスはこの証拠を補完するものですが、同一リクエストのリプレイに代わることはできません。