Class: Semian::SimpleExponentialSmoother
- Inherits:
-
Object
- Object
- Semian::SimpleExponentialSmoother
- Defined in:
- lib/semian/simple_exponential_smoother.rb
Overview
SimpleExponentialSmoother implements Simple Exponential Smoothing (SES) for forecasting a stable baseline error rate in adaptive circuit breakers.
SES focuses on the level component only (no trend or seasonality), using the formula:
smoothed = alpha * value + (1 - alpha) * previous_smoothed
Key characteristics:
-
Drops extreme values above cap to prevent outliers from distorting the forecast
-
Runs in two periods: low confidence (first 30 minutes) and high confidence (after 30 minutes)
-
During the low confidence period, we converge faster towards observed value than during the high confidence period
-
The choice of alphas follows the following criteria:
-
During low confidence:
-
If we are observing 2x our current estimate, we need to converge towards it in 30 minutes
-
If we are observing 0.5x our current estimate, we need to converge towards it in 5 minutes
-
-
During high confidence:
-
If we are observing 2x our current estimate, we need to converge towards it in 1 hour
-
If we are observing 0.5x our current estimate, we need to converge towards it in 10 minutes
-
The following code snippet can be used to calculate the alphas: def find_alpha(name, start_point, multiplier, convergence_duration)
target = start_point * multiplier
desired_distance = 0.003
alpha_ceil = 0.5
alpha_floor = 0.0
alpha = 0.25
while true
smoothed_value = start_point
step_size = convergence_duration / 10
converged_too_fast = false
10.times do |step|
step_size.times do
smoothed_value = alpha * target + (1 - alpha) * smoothed_value
end
if step < 9 and (smoothed_value - target).abs < desired_distance
converged_too_fast = true
end
end
if converged_too_fast
alpha_ceil = alpha
alpha = (alpha + alpha_floor) / 2
next
end
if (smoothed_value - target).abs > desired_distance
alpha_floor = alpha
alpha = (alpha + alpha_ceil) / 2
next
end
break
end
print "#{name} is #{alpha}\n"
end
initial_error_rate = 0.05
find_alpha(“low confidence upward convergence alpha”, initial_error_rate, 2, 1800) find_alpha(“low confidence downward convergence alpha”, initial_error_rate, 0.5, 300) find_alpha(“high confidence upward convergence alpha”, initial_error_rate, 2, 3600) find_alpha(“high confidence downward convergence alpha”, initial_error_rate, 0.5, 600)
Constant Summary collapse
- LOW_CONFIDENCE_ALPHA_UP =
0.0017
- LOW_CONFIDENCE_ALPHA_DOWN =
0.078
- HIGH_CONFIDENCE_ALPHA_UP =
0.0009
- HIGH_CONFIDENCE_ALPHA_DOWN =
0.039
- LOW_CONFIDENCE_THRESHOLD_MINUTES =
30
Instance Attribute Summary collapse
-
#alpha ⇒ Object
readonly
Returns the value of attribute alpha.
-
#cap_value ⇒ Object
readonly
Returns the value of attribute cap_value.
-
#initial_value ⇒ Object
readonly
Returns the value of attribute initial_value.
-
#observations_per_minute ⇒ Object
readonly
Returns the value of attribute observations_per_minute.
-
#smoothed_value ⇒ Object
readonly
Returns the value of attribute smoothed_value.
Instance Method Summary collapse
- #add_observation(value) ⇒ Object
- #forecast ⇒ Object
-
#initialize(cap_value:, initial_value:, observations_per_minute:) ⇒ SimpleExponentialSmoother
constructor
A new instance of SimpleExponentialSmoother.
- #reset ⇒ Object
- #state ⇒ Object
Constructor Details
#initialize(cap_value:, initial_value:, observations_per_minute:) ⇒ SimpleExponentialSmoother
Returns a new instance of SimpleExponentialSmoother.
86 87 88 89 90 91 92 93 |
# File 'lib/semian/simple_exponential_smoother.rb', line 86 def initialize(cap_value:, initial_value:, observations_per_minute:) @alpha = LOW_CONFIDENCE_ALPHA_DOWN # Start with low confidence, converging down @cap_value = cap_value @initial_value = initial_value @observations_per_minute = observations_per_minute @smoothed_value = initial_value @observation_count = 0 end |
Instance Attribute Details
#alpha ⇒ Object (readonly)
Returns the value of attribute alpha.
84 85 86 |
# File 'lib/semian/simple_exponential_smoother.rb', line 84 def alpha @alpha end |
#cap_value ⇒ Object (readonly)
Returns the value of attribute cap_value.
84 85 86 |
# File 'lib/semian/simple_exponential_smoother.rb', line 84 def cap_value @cap_value end |
#initial_value ⇒ Object (readonly)
Returns the value of attribute initial_value.
84 85 86 |
# File 'lib/semian/simple_exponential_smoother.rb', line 84 def initial_value @initial_value end |
#observations_per_minute ⇒ Object (readonly)
Returns the value of attribute observations_per_minute.
84 85 86 |
# File 'lib/semian/simple_exponential_smoother.rb', line 84 def observations_per_minute @observations_per_minute end |
#smoothed_value ⇒ Object (readonly)
Returns the value of attribute smoothed_value.
84 85 86 |
# File 'lib/semian/simple_exponential_smoother.rb', line 84 def smoothed_value @smoothed_value end |
Instance Method Details
#add_observation(value) ⇒ Object
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/semian/simple_exponential_smoother.rb', line 95 def add_observation(value) raise ArgumentError, "value must be non-negative, got: #{value}" if value < 0 return @smoothed_value if value > cap_value @observation_count += 1 low_confidence = @observation_count < (@observations_per_minute * LOW_CONFIDENCE_THRESHOLD_MINUTES) converging_up = value > @smoothed_value @alpha = if low_confidence converging_up ? LOW_CONFIDENCE_ALPHA_UP : LOW_CONFIDENCE_ALPHA_DOWN else converging_up ? HIGH_CONFIDENCE_ALPHA_UP : HIGH_CONFIDENCE_ALPHA_DOWN end @smoothed_value = (@alpha * value) + ((1.0 - @alpha) * @smoothed_value) @smoothed_value end |
#forecast ⇒ Object
115 116 117 |
# File 'lib/semian/simple_exponential_smoother.rb', line 115 def forecast @smoothed_value end |
#reset ⇒ Object
130 131 132 133 134 135 |
# File 'lib/semian/simple_exponential_smoother.rb', line 130 def reset @smoothed_value = initial_value @observation_count = 0 @alpha = LOW_CONFIDENCE_ALPHA_DOWN self end |
#state ⇒ Object
119 120 121 122 123 124 125 126 127 128 |
# File 'lib/semian/simple_exponential_smoother.rb', line 119 def state { smoothed_value: @smoothed_value, alpha: @alpha, cap_value: @cap_value, initial_value: @initial_value, observations_per_minute: @observations_per_minute, observation_count: @observation_count, } end |