Katkat

Implement a Text-Embedding Recommender Training Pipeline

LinkedInMachine Learning Engineer · Technical Screen

Last updated

Tests the ability to structure an end-to-end, leakage-resistant training pipeline for a text-embedding recommender, covering profile flattening, embedding caching, temporal splits, binary classification, and artifact serialization.

Scenario

You are tasked with building a modular, local machine learning training pipeline for an engagement recommendation system on a professional social network. The goal is to predict whether a member will click (1) or skip (0) an item (e.g., an article or post) based on semantic text representations of their profile and the item's content.

Input Schemas

Your pipeline receives three in-memory collections of records:

  • members: List of dicts with fields member_id (str), headline (str), skills (list of str), updated_at (int timestamp).
  • items: List of dicts with fields item_id (str), title (str), body (str), created_at (int timestamp).
  • interactions: List of dicts with fields member_id (str), item_id (str), timestamp (int), and clicked (int: 0 or 1).

Assume you are provided an external embedding utility function encoder.embed(texts: list[str]) -> np.ndarray which yields fixed-size vector representations (e.g., dimension D = 128).

Pipeline Requirements

Implement or pseudocode a class RecommenderTrainingPipeline that fulfills the following specifications:

  1. Text Serialization & Validation: Define a deterministic strategy to convert nested member profiles (headline + skills) and item records (title + body) into clean, single text strings per entity. Validate and handle missing fields gracefully.
  2. Embedding Cache: Many interactions share the same members and items. Implement an in-memory caching mechanism so that unique text strings are embedded at most once.
  3. Temporal Splitting: Prevent temporal data leakage. Split interactions chronologically using a cut-off timestamp (e.g., 80% train, 20% validation) rather than a uniform random split.
  4. Feature Assembly & Modeling: For each interaction, construct feature vectors from the member and item embeddings (e.g., concatenation, element-wise difference, and Hadamard product: [u, v, |u - v|, u * v]). Train a binary classification model (e.g., Logistic Regression or a lightweight MLP).
  5. Evaluation & Artifact Export: Compute validation metrics (ROC-AUC and Log Loss). Package the trained model, feature configuration, and encoder metadata into an exportable dictionary/bundle suitable for downstream inference.

Expected Interface

class RecommenderTrainingPipeline:
    def __init__(self, encoder, train_ratio: float = 0.8):
        self.encoder = encoder
        self.train_ratio = train_ratio
        self.cache = {}

    def prepare_data(
        self, members: list[dict], items: list[dict], interactions: list[dict]
    ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
        """Validates, caches embeddings, generates feature vectors, and returns temporal train/val splits: (X_train, y_train, X_val, y_val)."""
        pass

    def train(self, X_train: np.ndarray, y_train: np.ndarray):
        """Trains the binary classification model."""
        pass

    def evaluate(self, X_val: np.ndarray, y_val: np.ndarray) -> dict[str, float]:
        """Computes AUC-ROC and Log Loss on validation data."""
        pass

    def export_artifact(self, filepath: str) -> None:
        """Serializes model parameters and preprocessing metadata for inference."""
        pass

Hints

Hint 1

To avoid future data leaking into the past, sort the interactions strictly by timestamp and split by a threshold timestamp rather than shuffling indices.

Hint 2

Hash or normalize the flattened string representations of members and items to use as cache keys so duplicate text across entities is embedded only once.

Hint 3

Combining member and item embeddings via element-wise difference and Hadamard product alongside concatenation captures interaction signals that simple vector concatenation might miss in linear models.

Follow-up questions

  • How would you ensure point-in-time correctness if members update their profiles frequently over the course of the interaction log?
  • If the item inventory scales to tens of millions of records, how would you transition this architecture from a late-fusion classifier to a dual-tower model for low-latency retrieval?
  • How would you handle cold-start members or items where text fields are empty or poorly populated?