modelType: "logistic_regression" — a single-layer linear model: dot-product the customer’s feature vector with a learned weight vector, add a bias, push through a sigmoid. Probably the most-deployed classifier in production decisioning systems for a reason: cheap to train, cheap to score, easy to defend.
When to use
- You have ≥ 1k labeled outcomes and numeric features — the linear weighted-sum structure benefits from numeric inputs (categoricals need one-hot).
- You need calibrated probabilities for budget pacing or expected-value calculations — the sigmoid output is calibrated within the linear region.
- You’re comparing against a Bayesian baseline — logistic and Bayesian are the two “first-real-model” picks. Train both, A/B test them via
shadowModelKeys[].
gradient_boosted instead.
The math
maxIterations passes over the data (default 100).
Fixture config
0.930 for the standard test customer. Highest contribution: credit_score × 760 × 0.005 = 3.8 (raw); next income × 95000 × 0.00002 = 1.9.
Training
POST /api/v1/algorithm-models/{id}/train runs batch gradient descent over the observed interactions, minimizing binary cross-entropy to converge the weights. The training routine’s hyperparameters live on model.config:
Before extracting samples, training merges schema-table customer enrichment (ds_* tables) into each interaction’s feature bag — the same bulk enrichment load the Bayesian, gradient-boosted, and online-learner trainers use. A logistic model whose predictors reference schema columns (e.g. credit_score from a customer schema) therefore trains on those features even when the interaction rows’ context doesn’t carry them. On key collision, the interaction row’s own context wins over the enriched attributes. (Previously, schema-column predictors produced zero usable samples and training silently fell back to the metrics-only path while still reporting a successful train.)
Real fit vs. metrics-only fallback. The engine fits genuine weights (consuming
learningRate and maxIterations) only when it has ≥ 20 usable labeled rows with both classes present (at least one positive and one negative outcome). Below that — too little signal to fit a stable model — training falls back to a metrics-only pass: it records evaluation metrics but leaves the weight vector unchanged rather than producing a degenerate all-one-class model. Collect more labeled outcomes across both classes to cross the threshold.How the L2 penalty is wired. The batch fitter reads
learningRate and maxIterations from config directly, and resolves the L2 penalty strength (λ) from the enum-style config:regularization: "l2"→ λ =regularizationStrength(default1.0)regularization: "none"→ λ =0(no penalty)regularization: "l1"→ not implemented in the batch fitter, which only applies an L2 penalty. The engine logs a warning and applies λ =0rather than mislabeling the result as L1-regularized. Use"l2"if you want a penalty.- A numeric
regularizationvalue is still honored directly as λ (legacy back-compat), and takes precedence overregularizationStrength.
regularization: "l2", regularizationStrength: 1.0) is therefore L2-regularized with λ = 1.0. Raise regularizationStrength to penalize large weights harder; set regularization: "none" to fit without a penalty.segment="Gold" to 1 if present, 0 if absent. Multi-valued categoricals (segment ∈ ) need 4 binary features.
Score interpretation
score∈[0, 1]— calibrated probability.explanations[]— per-feature contributionweight × value, sorted by absolute magnitude. Positive contributions push toward responding.
Pitfalls
- Categoricals treated as scalars —
segment = 3for Gold is nonsense (no ordinal relationship). Always one-hot expand. - Unscaled features —
credit_score(300–850) andincome(0–500000) on the same model dominateage(18–80). Standardize to z-scores or min-max normalize before training, otherwise weights for small-magnitude features get pushed to zero by L2. - Multicollinearity — heavily correlated features split the credit; explanations become misleading. Drop one of each correlated pair.
- Missing intercept — leaving
biasat 0 forces every score through the origin. Always include the bias term. - Class imbalance — if positive rate is 1% and the loss is unweighted, the model learns to always predict “negative”. Use class weighting or downsample negatives in training.
Lifecycle & cadence
Logistic regression is offline-retrain only. The weight vector and AUC are recomputed byexecuteRetrain against accumulated interaction_history. With autoLearn: false (the default), the model stays frozen — weights from the last manual training run remain in modelState.weights forever and metricsHistory doesn’t grow.
To enable: PUT { "autoLearn": true, "learnMode": "scheduled", "learnSchedule": "24h" }. Nightly is the standard cadence; drop to "6h" if your underlying distribution shifts within a day, raise to "7d" for very stable workloads. Each retrain writes a fresh entry to metricsHistory[] and updates lastTrainedAt + lastLearnedAt. See Learning cadence and Model lifecycle.
Cross-reference
- Algorithm Selection Guide.
- Bayesian — natural baseline comparison.
- Gradient Boosted Trees — pick this instead when interactions matter.