Auto-Tuner Anatomy ③: Grid Search Engine — Optimizing Impact, Dampening, and Silence
Dissecting the core engine that optimizes Impact, Dampening, and Silence Penalty via Grid Search. Phase-based dynamic ranges, separation simulation, and the mathematical structure of Compound Score explained at the code level.
In the previous part: ② Signal Lift Anatomy, we covered signal discriminative power analysis. This part dissects the Auto-Tuner's heart — the Grid Search optimization engine.
1. What is Grid Search?
1.1 Principle
"Try changing the parameter slightly, and recommend the value that produces the best result."
That is all Grid Search does. No complex math required, intuitively understandable, and the rationale behind results can be clearly explained.
for candidate in search_range:
separation = simulate(replace current parameter with candidate)
if separation > best_record:
best_record = separation
optimal_value = candidate
recommendation = optimal_value
1.2 Why Not Use More Advanced Methods?
More sophisticated methods exist — Gradient Descent, Bayesian Optimization, Genetic Algorithms, etc. Here's why the Auto-Tuner sticks with Grid Search:
| Criterion | Grid Search | Gradient Descent |
|---|---|---|
| Explainability | ✅ "Maximum separation at this value" | ❌ "The value where the algorithm converged" |
| Global optimum guarantee | ✅ Exhaustive within range | ❌ Can get stuck in local optima |
| Differentiability required | ❌ Not needed | ✅ Required |
| Speed ((N+2)-dim) | ⚠️ Sequential 1-D | ✅ Simultaneous optimization |
The Auto-Tuner performs sequential 1-D optimization (coordinate descent). It ignores interaction effects, but each recommendation's rationale can be individually explained.
MCMC (Part 6) compensates for this limitation — simultaneous (N+2)-dimensional estimation ( = number of Impact types excluding No Signal).
2. Impact Optimization
2.1 Purpose
Impact is the weight of a signal. Game Changer's Impact = 5.0 means one Game Changer signal adds 5.0 to α.
Is 5.0 really optimal? Perhaps 4.0 yields higher separation, or 6.0 might be better.
2.2 Search Range: Phase-Based Dynamic Expansion
When data is scarce, searching a wide range risks overfitting. Range expands as Phase increases:
| Phase | Range | Example (current=5.0) | Rationale |
|---|---|---|---|
| 2 | ±20% | 4.0 ~ 6.0 | Small data, conservative |
| 3 | ±30% | 3.5 ~ 6.5 | Default range |
| 4 | ±40% | 3.0 ~ 7.0 | Sufficient data |
| 5 | ±50% | 2.5 ~ 7.5 | Wide exploration |
GRID_RANGE_RATIO_BY_PHASE = {
1 => BASE_GRID_RANGE_RATIO, # 0.20
2 => BASE_GRID_RANGE_RATIO, # 0.20
3 => 0.30,
4 => 0.40,
5 => 0.50
}.freeze
2.3 Grid Resolution
11 points are evenly distributed:
current=5.0, range=±30% → [3.5, 3.8, 4.1, 4.4, 4.7, 5.0, 5.3, 5.6, 5.9, 6.2, 6.5]
Why 11 points? A balance between resolution and speed. 5–6 points are too coarse, 50 would take 50× longer. 11 provides sufficient precision while running fast.
2.4 Single Simulation
For one candidate value:def optimize_impact(impact_type, current_value)
range = current_value * grid_range_ratio # Phase-based
candidates = (0..GRID_POINTS).map { |i|
low + (high - low) * i / GRID_POINTS
}
best_val = current_value
best_sep = current_separation[:separation]
candidates.each do |candidate|
override = { impact_type.id => candidate }
sep = simulate_separation(impact_override: override)
if sep > best_sep
best_sep = sep
best_val = candidate
end
end
{ current: current_value, recommended: best_val,
improvement: best_sep - current_separation[:separation] }
end
simulate_separation re-simulates all Won/Lost projects from scratch. A single call performs computations proportional to project count × activity count.
2.5 Minimum Improvement Threshold
If separation improvement is less than MIN_SEPARATION_IMPROVEMENT = 0.01, the current value is retained.
Why? An improvement of 0.003 is likely statistical noise. It could vanish with one more data point.
action: improvement >= MIN_SEPARATION_IMPROVEMENT ? 'adjust' : 'keep'
3. Compound Score: The Mathematics of Dampening
3.1 The Problem
Three signals emerged from a single meeting:
- Game Changer (5.0)
- Strong Affirmation (1.0)
- Moderate Affirmation (0.7)
Adding all three gives 6.7. But this is potentially redundant information from the same context. The customer was highly positive, causing all three signals to be observed — not three independent positive events.
3.2 Solution: MAX + Remaining × Dampening
def compound_with_dampening(scores, dampening)
return 0.0 if scores.empty?
sorted = scores.sort.reverse
# Strongest signal reflected at 100%
# Remaining reflected at dampening ratio
sorted[0] + sorted[1..].sum * dampening
end
With current dampening = 0.25:
Compound = 5.0 + (1.0 + 0.7) × 0.25
= 5.0 + 0.425
= 5.425
- dampening = 0: Only 5.0 is reflected (rest ignored)
- dampening = 0.25: 5.425 (slight reflection)
- dampening = 1.0: 6.7 (full reflection)
3.3 Why Separate MAX First?
The strongest signal is the dominant indicator. It should be fully reflected. The rest are confirmatory supplements and are discounted. This aligns with the information theory concept of marginal information.
3.4 Dampening Grid Search
def optimize_dampening
candidates = (0..GRID_POINTS).map { |i| i.to_f / GRID_POINTS } # [0.0, 0.09, ..., 1.0]
best_dampening = BayesianUpdate::SIGNAL_DAMPENING
best_sep = current_separation[:separation]
candidates.each do |d|
sep = simulate_separation(dampening_override: d)
if sep > best_sep
best_sep = sep
best_dampening = d
end
end
{ current: BayesianUpdate::SIGNAL_DAMPENING,
recommended: best_dampening,
improvement: best_sep - current_separation[:separation] }
end
4. Silence Penalty Optimization
4.1 The Penalty of Silence
If the customer hasn't been contacted for a certain period (default 14 days), a penalty is added to β:
Where:
unit_penalty= Weak Negation default value × silence_ratiocount= (elapsed days − gap) / interval + 1
4.2 Meaning of silence_ratio
If silence_ratio = 0.3 and Weak Negation = 0.4:
unit_penalty = 0.4 × 0.3 = 0.12
If the 14-day gap is exceeded by 3 intervals of 7 days:
β += 0.12 × 3 = 0.36
This provides the intuition: "equivalent to receiving 3 weak negative signals."
4.3 Grid Search
Silence ratio is also searched across 11 points in the 0.0–1.0 range:
[0.0, 0.09, 0.18, ..., 0.91, 1.0]
silence_ratio = 0 means no silence penalty; 1.0 imposes the full Weak Negation as penalty.
5. Simulation Engine Internals
5.1 simulate_separation Full Flow
simulate_separation(impact_override, dampening_override, silence_ratio_override)
├─ for each Won project:
│ └─ p_win = simulate_project(project, overrides)
├─ for each Lost project:
│ └─ p_win = simulate_project(project, overrides)
│
├─ won_avg = mean(Won p_win values)
├─ lost_avg = mean(Lost p_win values)
└─ return won_avg - lost_avg
5.2 simulate_project Step by Step
def simulate_project(project, impact_override, dampening, silence_ratio)
alpha = project.prior_alpha.to_f # ── ① Prior initial value
beta = project.prior_beta.to_f
activities.each do |activity| # ── ② Chronological activity traversal
swv = stage.swv.to_f # ── ③ Stage weight
# ── ④ Compound Score calculation
positive_compound, negative_compound =
simulate_compound_scores_fast(signal_ids, impacts, impact_override, dampening)
# ── ⑤ Silence penalty
if elapsed >= gap
count = ((elapsed - gap) / interval) + 1
beta += unit_penalty * count
end
# ── ⑥ α, β update
alpha += swv * positive_compound
beta += swv * negative_compound
end
alpha / (alpha + beta) # ── ⑦ Return P(Win)
end
① → ② → ③ → ④ → ⑤ → ⑥ → ⑦ — This is the complete lifecycle calculation for 1 project in EXAWin's Bayesian engine.
5.3 SWV (Stage Weight Value)
SWV is the per-stage weight. A signal at the Proposal stage is more decisive than one at Discovery. Higher SWV means the same signal has a larger impact on α/β.
6. Performance: Why It's Fast
The Auto-Tuner's Grid Search requires at most:
ImpactType count × GRID_POINTS × Project count × Activity count
= 9 × 11 × 100 × 20 = 198,000 operations
Adding Dampening (11 iterations) and Silence (11 iterations) simulations totals approximately 220,000 operations.
This is feasible because:
- Preloading: All data is in memory (0 DB queries)
- Simple arithmetic: Only multiplication, addition, and comparison (no matrix operations)
- 1-D search: An N-dimensional Grid would be , but sequential 1-D is
220,000 arithmetic operations in Ruby completes in under 1 second.
Next: ④ Threshold · k Anatomy — Youden J statistic, T optimization, k Grid Search.