Skip to content

Latest commit

 

History

History
970 lines (827 loc) · 24.6 KB

File metadata and controls

970 lines (827 loc) · 24.6 KB

➕ Cross Features

Cross Features in KDP

Capture powerful interactions between features to uncover hidden patterns in your data.

📋 Overview

Cross features model the interactions between input features, unlocking patterns that individual features alone might miss. They're especially powerful for capturing relationships like "product category × user location" or "day of week × hour of day" that drive important outcomes in your data.

🔗

Feature Interaction

Capture complex relationships between features

🎯

Pattern Discovery

Uncover hidden correlations in your data

Efficient Processing

Optimized for large-scale feature crosses

🎛️

Fixed Width

Each cross adds one column, whatever the cardinality

🧠 How Cross Features Work

Cross Features Architecture

KDP crosses two columns by hashing the pair of raw values into a fixed number of bins, and appends that bin index to the output as a single column.

🔄

Feature Combination

Merging values from different features

#️⃣

Hashing

Mapping each pair into one of nr_bins buckets

🧮

One Extra Column

The bin index, appended to the categorical block

🔍

Pattern Discovery

Finding non-linear relationships between features

📝 Basic Usage

from kdp import PreprocessingModel, FeatureType

# Define your features. Both sides of a cross must be categorical: the pair of
# raw values is hashed, so the columns have to be strings or integers.
features = {
    "product_category": FeatureType.STRING_CATEGORICAL,
    "user_country": FeatureType.STRING_CATEGORICAL,
    "age_group": FeatureType.STRING_CATEGORICAL
}

# Create a preprocessor with cross features
preprocessor = PreprocessingModel(
    path_data="customer_data.csv",
    features_specs=features,

    # Define crosses as (feature1, feature2, nr_bins)
    feature_crosses=[
        ("product_category", "user_country", 32),  # pairs hashed into 32 bins
        ("age_group", "user_country", 16)          # pairs hashed into 16 bins
    ]
)

# Each cross adds exactly one column to the output, holding the bin index of
# the (feature1, feature2) pair -- a value in [0, nr_bins).

⚙️ Key Configuration Parameters

Parameter Description Default Suggested Range
feature1 First feature to cross. Must be declared in features_specs and be a string or integer column - Any categorical feature name
feature2 Second feature to cross, under the same rules - Any categorical feature name
nr_bins Number of hash buckets the pair is mapped into. Bigger means fewer collisions between distinct pairs - Around the number of pairs you expect to see

🛠️ Cross Feature Types

Categorical × Categorical

The most common type, capturing relationships between discrete features:

from kdp import FeatureType, PreprocessingModel

# Creating categorical crosses
preprocessor = PreprocessingModel(
    features_specs={
        "product_category": FeatureType.STRING_CATEGORICAL,
        "user_country": FeatureType.STRING_CATEGORICAL
    },
    feature_crosses=[
        ("product_category", "user_country", 32)
    ]
)
</div>

Categorical × Bucketed Numerical

A numeric column cannot be crossed directly -- hashing needs discrete values, and a float column is refused when the preprocessor is built. Bucket it into a categorical column of your own first:

import pandas as pd
from kdp import FeatureType, PreprocessingModel

# Turn the numeric column into bands, then cross the bands
frame = pd.read_csv("products.csv")
frame["price_band"] = pd.cut(
    frame["price"],
    bins=[0, 10, 50, 200, float("inf")],
    labels=["budget", "standard", "premium", "luxury"],
).astype(str)
frame.to_csv("products_banded.csv", index=False)

preprocessor = PreprocessingModel(
    path_data="products_banded.csv",
    features_specs={
        "product_category": FeatureType.STRING_CATEGORICAL,
        "price_band": FeatureType.STRING_CATEGORICAL,
    },
    feature_crosses=[
        ("product_category", "price_band", 32)
    ]
)
</div>

Date Crosses

A DateFeature is one column that expands into cyclical encodings inside the model; there are no separate <name>_hour or <name>_day_of_week features to cross. Derive the components you want to cross as their own categorical columns:

import pandas as pd
from kdp import FeatureType, PreprocessingModel

frame = pd.read_csv("transactions.csv")
stamps = pd.to_datetime(frame["transaction_time"])
frame["transaction_day_of_week"] = stamps.dt.day_name()
frame["transaction_hour"] = stamps.dt.hour.astype(str)
frame.to_csv("transactions_parts.csv", index=False)

preprocessor = PreprocessingModel(
    path_data="transactions_parts.csv",
    features_specs={
        "transaction_time": FeatureType.DATE,
        "transaction_day_of_week": FeatureType.STRING_CATEGORICAL,
        "transaction_hour": FeatureType.STRING_CATEGORICAL,
    },
    # Cross day of week with hour of day
    feature_crosses=[
        ("transaction_day_of_week", "transaction_hour", 16)
    ]
)
</div>

Multiple Crosses

Combine multiple cross features to capture complex interactions:

from kdp import FeatureType, PreprocessingModel

# Creating multiple crosses
preprocessor = PreprocessingModel(
    features_specs={
        "product_category": FeatureType.STRING_CATEGORICAL,
        "user_country": FeatureType.STRING_CATEGORICAL,
        "device_type": FeatureType.STRING_CATEGORICAL,
        "age_group": FeatureType.STRING_CATEGORICAL
    },
    # Define multiple crosses to capture different interactions
    feature_crosses=[
        ("product_category", "user_country", 32),
        ("device_type", "user_country", 16),
        ("product_category", "age_group", 24)
    ]
)
</div>

💡 Advanced Cross Feature Techniques

🔍 Attention Over Crosses

Crossed columns join the feature set, so tabular attention weighs them alongside everything else:

# Attention runs over the whole feature set, crosses included
from kdp import PreprocessingModel, FeatureType

preprocessor = PreprocessingModel(
    path_data="data.csv",
    features_specs={
        "product_id": FeatureType.STRING_CATEGORICAL,
        "user_id": FeatureType.STRING_CATEGORICAL,
    },
    feature_crosses=[("product_id", "user_id", 32)],
    tabular_attention=True,
    tabular_attention_heads=4,
    tabular_attention_placement="all_features"
)
</div>

🧠 Three-Way Interactions

feature_crosses takes pairs. Cover a three-way interaction with its pairs:

from kdp import FeatureType, PreprocessingModel

# Each cross is a pair. For three-way interactions, cross every pair and let
# the model combine them -- a cross cannot be crossed again.
preprocessor = PreprocessingModel(
    path_data="data.csv",
    features_specs={
        "product_category": FeatureType.STRING_CATEGORICAL,
        "user_location": FeatureType.STRING_CATEGORICAL,
        "time_of_day": FeatureType.STRING_CATEGORICAL,
    },
    feature_crosses=[
        ("product_category", "user_location", 32),
        ("product_category", "time_of_day", 32),
        ("user_location", "time_of_day", 32),
    ]
)
</div>

🔧 Real-World Examples

E-commerce Recommendations

# Cross features for e-commerce recommendations
from kdp import PreprocessingModel, FeatureType
from kdp.features import CategoricalFeature, DateFeature

preprocessor = PreprocessingModel(
    path_data="ecommerce_data.csv",
    features_specs={
        # User features
        "user_segment": FeatureType.STRING_CATEGORICAL,
        "user_device": FeatureType.STRING_CATEGORICAL,

        # Product features
        "product_category": CategoricalFeature(
            name="product_category",
            feature_type=FeatureType.STRING_CATEGORICAL,
            embedding_size=32
        ),
        "product_price_range": FeatureType.STRING_CATEGORICAL,

        # Temporal features. The date column feeds the model its cyclical
        # encodings; the two categorical columns beside it are what the crosses
        # use, because a cross needs discrete values.
        "browse_time": DateFeature(
            name="browse_time"
        ),
        "browse_is_weekend": FeatureType.STRING_CATEGORICAL,
        "browse_hour": FeatureType.STRING_CATEGORICAL
    },

    # Define crosses for recommendation patterns
    feature_crosses=[
        # User segment × product category (what segments like what categories)
        ("user_segment", "product_category", 48),

        # Device × price range (mobile users prefer different price points)
        ("user_device", "product_price_range", 16),

        # Temporal × product (weekend browsing patterns)
        ("browse_is_weekend", "product_category", 32),

        # Time of day × product (morning vs evening preferences)
        ("browse_hour", "product_category", 32)
    ]
)
</div>

Fraud Detection

# Cross features for fraud detection
from kdp import PreprocessingModel, FeatureType
from kdp.features import NumericalFeature, DateFeature

preprocessor = PreprocessingModel(
    path_data="transactions.csv",
    features_specs={
        # Transaction features
        "transaction_amount": NumericalFeature(
            name="transaction_amount",
            feature_type=FeatureType.FLOAT_RESCALED,
            use_distribution_aware=True
        ),
        "merchant_category": FeatureType.STRING_CATEGORICAL,
        "payment_method": FeatureType.STRING_CATEGORICAL,

        # User features
        "user_country": FeatureType.STRING_CATEGORICAL,
        "account_age_days": FeatureType.FLOAT_NORMALIZED,

        # Time features, plus the discrete columns the crosses need: an hour
        # band and an amount band derived from the raw columns above.
        "transaction_time": DateFeature(
            name="transaction_time"
        ),
        "transaction_hour": FeatureType.STRING_CATEGORICAL,
        "amount_band": FeatureType.STRING_CATEGORICAL
    },

    # Cross features for fraud patterns
    feature_crosses=[
        # Country × merchant (unusual combinations)
        ("user_country", "merchant_category", 32),

        # Payment method × amount band (unusual methods for large amounts)
        ("payment_method", "amount_band", 24),

        # Hour × amount band (unusual times for large transactions)
        ("transaction_hour", "amount_band", 24),

        # Country × time (transactions from unusual locations at odd hours)
        ("user_country", "transaction_hour", 32)
    ],

    # Enable tabular attention for additional interaction discovery
    tabular_attention=True
)
</div>

📊 Model Architecture

graph TD A1[Feature 1] --> C[Pair the raw values] A2[Feature 2] --> C C --> D[Hash into nr_bins buckets] D --> E[Cast the bin index to float32] E --> F[One extra output column]
  style A1 fill:#e3f2fd,stroke:#64b5f6,stroke-width:2px
  style A2 fill:#e3f2fd,stroke:#64b5f6,stroke-width:2px
  style C fill:#e8f5e9,stroke:#66bb6a,stroke-width:2px
  style D fill:#fff8e1,stroke:#ffd54f,stroke-width:2px
  style E fill:#f3e5f5,stroke:#ce93d8,stroke-width:2px
  style F fill:#e8eaf6,stroke:#7986cb,stroke-width:2px

KDP pairs the two raw values, hashes the pair into one of nr_bins buckets, and appends that bin index to the output as a single float column alongside the categorical features.

💎 Pro Tips

🎯 Choose Meaningful Crosses

Focus on feature pairs with likely interactions based on domain knowledge:

  • Product × location (regional preferences)
  • Time × event (temporal patterns)
  • User × item (personalization)
  • Price × category (price sensitivity)

⚠️ Beware of Sparsity

Crosses between high-cardinality features produce many distinct pairs, and nr_bins decides how many of them share a bucket:

  • Too few bins and unrelated pairs collide into one value
  • Too many and most bins are never seen by the model
  • The columns feeding a cross can themselves use category_encoding="hashing" when they have many values

📏 Choosing nr_bins

The third element of the tuple is the number of hash buckets, not an embedding size:

  • Start near the number of pairs you actually expect to see
  • Small crosses (a handful of categories each): 8-32 bins
  • Larger crosses: a few times the distinct pair count, to keep collisions rare
  • The output width is one column per cross whatever you choose

🔄 Alternative Approaches

Consider other interaction modeling techniques alongside crosses:

  • Enable tabular_attention=True to automatically discover interactions
  • Use transfo_nr_blocks for more sophisticated feature relationships
  • Bucket a numeric column into bands to bring it into a cross

🔄 Comparing With Alternatives

Approach Pros Cons When to Use
Cross Features Explicit modeling of specific interactions Need to specify each interaction When you know which interactions matter
Tabular Attention Automatic discovery of interactions Less control When you're unsure which interactions matter
Transformer Blocks Most powerful interaction modeling Most computationally expensive For complex interaction patterns
Feature MoE Adaptive feature processing Higher complexity For heterogeneous feature sets

🔗 Related Topics


<style> /* Base styling */ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; margin: 0; padding: 0; } /* Feature header */ .feature-header { background: linear-gradient(135deg, #2196f3 0%, #1976d2 100%); border-radius: 10px; padding: 30px; margin: 30px 0; box-shadow: 0 4px 6px rgba(0,0,0,0.1); color: white; } .feature-title h2 { margin-top: 0; font-size: 28px; } .feature-title p { font-size: 18px; margin-bottom: 0; opacity: 0.9; } /* Overview card */ .overview-card { background-color: #fff; border-radius: 10px; padding: 20px 25px; margin: 20px 0; box-shadow: 0 2px 5px rgba(0,0,0,0.05); border-left: 4px solid #2196f3; } .overview-card p { margin: 0; font-size: 16px; } /* Key benefits */ .key-benefits { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; margin: 30px 0; } .benefit-card { background-color: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; display: flex; flex-direction: column; align-items: center; text-align: center; } .benefit-card:hover { transform: translateY(-5px); box-shadow: 0 8px 16px rgba(0,0,0,0.1); } .benefit-icon { font-size: 2.5em; margin-bottom: 15px; } .benefit-card h3 { margin: 0 0 10px 0; color: #2196f3; } .benefit-card p { margin: 0; } /* Architecture diagram */ .architecture-diagram { background-color: white; border-radius: 10px; padding: 20px; margin: 30px 0; box-shadow: 0 4px 8px rgba(0,0,0,0.05); text-align: center; } .architecture-image { max-width: 100%; border-radius: 5px; } .diagram-caption { margin-top: 20px; text-align: center; font-style: italic; } /* Approaches container */ .approaches-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; margin: 30px 0; } .approach-card { background-color: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; display: flex; flex-direction: column; align-items: center; text-align: center; } .approach-card:hover { transform: translateY(-5px); box-shadow: 0 8px 16px rgba(0,0,0,0.1); } .approach-icon { font-size: 2.5em; margin-bottom: 15px; } .approach-card h3 { margin: 0 0 10px 0; color: #2196f3; } .approach-card p { margin: 0; } /* Code containers */ .code-container { background-color: #f8f9fa; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 5px rgba(0,0,0,0.1); margin: 20px 0; } .code-container pre { margin: 0; padding: 20px; } /* Tables */ .table-container { margin: 30px 0; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 8px rgba(0,0,0,0.05); } .config-table { width: 100%; border-collapse: collapse; } .config-table th { background-color: #e3f2fd; padding: 15px; text-align: left; font-weight: 600; border-bottom: 2px solid #2196f3; } .config-table td { padding: 12px 15px; border-bottom: 1px solid #eaecef; } .config-table tr:nth-child(even) { background-color: #f8f9fa; } .config-table tr:hover { background-color: #e3f2fd; } /* Features container */ .features-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px; margin: 30px 0; } .feature-type-card { background-color: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; } .feature-type-card:hover { transform: translateY(-5px); box-shadow: 0 8px 16px rgba(0,0,0,0.1); } .feature-type-card h3 { margin-top: 0; color: #2196f3; } /* Power features */ .power-features { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px; margin: 30px 0; } .power-feature-card { background-color: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; } .power-feature-card:hover { transform: translateY(-5px); box-shadow: 0 8px 16px rgba(0,0,0,0.1); } .power-feature-card h3 { margin-top: 0; color: #2196f3; } /* Examples */ .examples-container { display: grid; grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); gap: 20px; margin: 30px 0; } .example-card { background-color: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; } .example-card:hover { transform: translateY(-5px); box-shadow: 0 8px 16px rgba(0,0,0,0.1); } .example-card h3 { margin-top: 0; color: #2196f3; } /* Pro tips */ .pro-tips-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; margin: 30px 0; } .pro-tip-card { background-color: #fff; border-radius: 10px; padding: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.05); transition: transform 0.3s ease, box-shadow 0.3s ease; } .pro-tip-card:hover { transform: translateY(-5px); box-shadow: 0 8px 16px rgba(0,0,0,0.1); } .pro-tip-card h3 { margin-top: 0; color: #2196f3; } .pro-tip-card p { margin-bottom: 10px; } .pro-tip-card ul { margin: 0; padding-left: 20px; } .pro-tip-card li { margin-bottom: 5px; } /* Related topics */ .related-topics { display: flex; flex-wrap: wrap; gap: 15px; margin: 30px 0; } .topic-link { display: flex; align-items: center; padding: 10px 15px; background-color: #f3e5f5; border-radius: 8px; text-decoration: none; color: #333; box-shadow: 0 2px 5px rgba(0,0,0,0.05); transition: background-color 0.3s ease, transform 0.3s ease; } .topic-link:hover { background-color: #e1bee7; transform: translateY(-2px); } .topic-icon { font-size: 1.2em; margin-right: 10px; } /* Navigation */ .nav-container { display: flex; justify-content: space-between; margin: 40px 0; } .nav-button { display: flex; align-items: center; padding: 10px 15px; background-color: #f8f9fa; border-radius: 8px; text-decoration: none; color: #333; box-shadow: 0 2px 5px rgba(0,0,0,0.1); transition: background-color 0.3s ease, transform 0.3s ease; } .nav-button:hover { background-color: #f3e5f5; transform: translateY(-2px); } .nav-button.prev { padding-left: 10px; } .nav-button.next { padding-right: 10px; } .nav-icon { font-size: 1.2em; margin: 0 8px; } /* Responsive adjustments */ @media (max-width: 768px) { .key-benefits, .approaches-container, .features-container, .power-features { grid-template-columns: 1fr; } } </style>