Cybersecurity

Feature Engineering in the Age of AI

Python Neural Networks LLMs GPT RAG Pandas NumPy Scikit-learn Classification Transformers Embeddings Vector Search Time Series Feature Engineering Ensemble Methods Hashing
1,089 words Includes Code
Feature Engineering in the Age of AI comparing classical hand-crafted features with modern embeddings and LLM-derived features
Key Takeaway: Classical feature engineering uses domain expertise to create interpretable features. Embeddings capture semantic meaning automatically. LLM-derived features generate structured data from unstructured text. The best modern pipelines often combine all three approaches.

Feature Engineering in the Age of AI

For decades, feature engineering meant manually crafting numeric variables from raw data. An experienced data scientist would spend weeks creating features like price_per_square_foot, days_since_last_purchase, or interaction_term_age_income.

Then embeddings arrived. A single call to a pre-trained model could generate 768 dimensions of semantic meaning from a text snippet.

Then LLMs arrived. A prompt could extract structured features from unstructured documents in seconds.

Which approach should you use? The answer depends on your data, your constraints, and your goals.

Three approaches to feature engineering: Classical (manual formulas), Embeddings (neural network encoding), and LLM-Derived (prompt-based extraction) with tradeoffs

Classical Feature Engineering

Classical feature engineering transforms raw data into numeric features using domain knowledge and mathematical formulas.

Examples

import pandas as pd
import numpy as np

# Raw data
df = pd.DataFrame({
    'price': [200000, 350000, 180000],
    'sqft': [1200, 1800, 950],
    'age_years': [25, 10, 40],
    'bedrooms': [3, 4, 2],
    'neighborhood': ['A', 'B', 'A']
})

# Classical feature engineering
df['price_per_sqft'] = df['price'] / df['sqft']
df['bedrooms_per_sqft'] = df['bedrooms'] / df['sqft']
df['age_squared'] = df['age_years'] ** 2  # Non-linear
df['is_new'] = (df['age_years'] < 5).astype(int)
df['price_x_bedrooms'] = df['price'] * df['bedrooms']  # Interaction

Strengths

Strength Why It Matters
Interpretable You can explain why each feature exists
Fast inference No model calls needed at prediction time
Low memory 5-50 features, not thousands
Works everywhere Any ML framework, any deployment

Limitations

  • Requires deep domain expertise
  • Cannot capture complex patterns automatically
  • Time-consuming to create and maintain
  • Limited to patterns you can think of

Embedding-Based Features

Embeddings are dense vector representations learned by neural networks. They capture semantic meaning that classical features miss.

Text Embeddings

from sentence_transformers import SentenceTransformer

# Load a pre-trained embedding model
model = SentenceTransformer('all-MiniLM-L6-v2')

# Raw text data
texts = [
    "The product works great and arrived on time",
    "Terrible quality, broke after one day",
    "Average product, nothing special"
]

# Generate embeddings (384 dimensions)
embeddings = model.encode(texts)
print(f"Shape: {embeddings.shape}")  # (3, 384)

# Use as features in ML model
from sklearn.ensemble import RandomForestClassifier

# embeddings now contains 384 features per text
# X = embeddings, y = sentiment labels

Image Embeddings

import torch
from torchvision import models, transforms
from PIL import Image

# Load pre-trained ResNet (remove classification head)
resnet = models.resnet50(pretrained=True)
feature_extractor = torch.nn.Sequential(*list(resnet.children())[:-1])

# Preprocessing
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406],
                       std=[0.229, 0.224, 0.225])
])

# Extract features (2048 dimensions)
img = Image.open('photo.jpg')
img_tensor = transform(img).unsqueeze(0)
features = feature_extractor(img_tensor)
print(f"Shape: {features.shape}")  # (1, 2048, 1, 1)

Strengths

Strength Why It Matters
Captures semantics "happy" and "joyful" are similar
Works with any modality Text, images, audio, video
Pre-trained knowledge No need to train from scratch
Transfer learning Works with small datasets

Limitations

  • High dimensionality (128-3072 features)
  • Hard to interpret individual dimensions
  • Requires GPU for large models
  • May not capture domain-specific patterns

LLM-Derived Features

LLMs can generate structured features from unstructured data using prompts. This is the newest approach.

Sentiment and Emotion Extraction

import openai
import json

def extract_features_with_llm(text: str) -> dict:
    """Use LLM to extract structured features from text."""
    
    prompt = f"""Extract the following features from this review:
    
Text: "{text}"

Return JSON with these fields:
- sentiment: positive/negative/neutral
- emotion: joy/anger/sadness/surprise/disgust
- urgency: high/medium/low
- topic: product/service/delivery/price
- key_phrases: list of 2-3 key phrases
"""
    
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    
    return json.loads(response.choices[0].message.content)

# Example
features = extract_features_with_llm(
    "My order arrived damaged and customer support was unhelpful"
)
# {'sentiment': 'negative', 'emotion': 'anger', 
#  'urgency': 'high', 'topic': 'delivery',
#  'key_phrases': ['arrived damaged', 'unhelpful support']}

Batch Feature Extraction

import pandas as pd
from tqdm import tqdm

def batch_extract_features(texts: list, batch_size=10) -> pd.DataFrame:
    """Extract features for multiple texts efficiently."""
    
    results = []
    for i in tqdm(range(0, len(texts), batch_size)):
        batch = texts[i:i+batch_size]
        for text in batch:
            features = extract_features_with_llm(text)
            results.append(features)
    
    return pd.DataFrame(results)

# Extract features for 1000 reviews
df_features = batch_extract_features(df['review_text'].tolist())

Strengths

Strength Why It Matters
Zero-shot capability No training data needed
Complex patterns Understands context and nuance
Structured output Returns usable features directly
Flexible prompts Easy to adjust feature definitions

Limitations

  • API cost per sample ($0.01-0.10+ per call)
  • Latency at inference (100ms-2s per sample)
  • Non-deterministic outputs
  • Rate limits and quotas

When to Use Each Approach

Scenario Best Approach Why
Tabular data (finance, healthcare) Classical Interpretable, fast, domain-specific
Text classification Embeddings Captures semantics, pre-trained
Image recognition Embeddings Pre-trained vision models
Unstructured document parsing LLM-derived Flexible extraction, zero-shot
Real-time predictions Classical + Embeddings Fast inference, no API calls
Prototyping LLM-derived Quick iteration, no training data
Production at scale Classical + Embeddings Cost-effective, deterministic

The Hybrid Approach

The most effective modern pipelines combine all three:

import pandas as pd
import numpy as np
from sentence_transformers import SentenceTransformer

# Classical features
df['text_length'] = df['text'].str.len()
df['word_count'] = df['text'].str.split().str.len()
df['avg_word_length'] = df['text'].apply(
    lambda x: np.mean([len(w) for w in x.split()])
)

# Embedding features
model = SentenceTransformer('all-MiniLM-L6-v2')
embeddings = model.encode(df['text'].tolist())
embedding_df = pd.DataFrame(
    embeddings, 
    columns=[f'emb_{i}' for i in range(embeddings.shape[1])]
)

# Combine all features
X = pd.concat([df[['text_length', 'word_count', 'avg_word_length']], 
                embedding_df], axis=1)

print(f"Final feature count: {X.shape[1]}")
# 387 features (3 classical + 384 embeddings)

Feature Store and Caching

For production systems, cache computed features:

import hashlib
import pickle
from pathlib import Path

class FeatureCache:
    def __init__(self, cache_dir='features_cache'):
        self.cache_dir = Path(cache_dir)
        self.cache_dir.mkdir(exist_ok=True)
    
    def get_key(self, text: str) -> str:
        return hashlib.md5(text.encode()).hexdigest()
    
    def get_features(self, text: str):
        key = self.get_key(text)
        cache_file = self.cache_dir / f'{key}.pkl'
        
        if cache_file.exists():
            with open(cache_file, 'rb') as f:
                return pickle.load(f)
        
        # Compute and cache
        embedding = model.encode([text])[0]
        with open(cache_file, 'wb') as f:
            pickle.dump(embedding, f)
        
        return embedding

Try It Yourself

Further Reading

💬 Discuss this topic on BestWordz Community

Conclusion

Feature engineering has evolved from a purely manual process to a multi-tool approach. Classical features remain essential for interpretable, fast, and cost-effective models. Embeddings capture semantic meaning that no human could manually encode. LLM-derived features provide flexible, zero-shot extraction from unstructured data.

The key insight: these approaches are not mutually exclusive. The best modern pipelines combine classical domain knowledge with embedding power and LLM flexibility.

Start with classical features when you have domain expertise. Add embeddings for semantic understanding. Use LLM-derived features for prototyping and complex extraction tasks. Cache everything in production.

Feature engineering is no longer just about creating features. It is about choosing the right method for the right data.

💬 Discuss on BestWordz Community

Join the conversation about Python, Neural Networks, LLMs on the BestWordz Community forum.

Visit Forum →