Permalink
Browse files

Adding brute force optimization.

Largely untested at this time.
  • Loading branch information...
1 parent 62fe2d6 commit 5197d083a00de065067a7950a690a47d49f3e1f4 @knaveofdiamonds committed Feb 20, 2012
View
@@ -1,5 +1,17 @@
require 'tealeaves/moving_average'
require 'tealeaves/seasonal_components'
+require 'tealeaves/forecast'
require 'tealeaves/naive_forecast'
require 'tealeaves/single_exponential_smoothing_forecast'
+require 'tealeaves/brute_force_optimization'
require 'tealeaves/exponential_smoothing_forecast'
+
+module TeaLeaves
+ def self.optimal_model(time_series, period)
+ BruteForceOptimization.new(time_series, period).optimize
+ end
+
+ def self.forecast(time_series, period, periods_ahead=nil)
+ optimal_model(time_series, period).predict(periods_ahead)
+ end
+end
@@ -0,0 +1,84 @@
+module TeaLeaves
+ class BruteForceOptimization
+ INITIAL_PARAMETER_VALUES = [0.0, 0.2, 0.4, 0.6, 0.8, 1.0].freeze
+
+ def initialize(time_series, period, opts={})
+ @time_series = time_series
+ @period = period
+ @opts = opts
+ end
+
+
+ def optimize
+ [0.1, 0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625].inject(optimum(initial_models)) do |model, change|
+ improve_model(model, change)
+ end
+ end
+
+ def initial_test_parameters(opts={})
+ parameters = []
+ INITIAL_PARAMETER_VALUES.each do |alpha|
+ parameters << {:alpha => alpha, :seasonality => :none, :trend => :none}
+
+ unless opts[:seasonality] == :none && opts[:trend] == :none
+ INITIAL_PARAMETER_VALUES.each do |b|
+ parameters << {:alpha => alpha, :beta => b, :seasonality => :none, :trend => :additive}
+ parameters << {:alpha => alpha, :beta => b, :seasonality => :none, :trend => :multiplicative}
+ parameters << {:alpha => alpha, :gamma => b, :trend => :none, :seasonality => :additive}
+ parameters << {:alpha => alpha, :gamma => b, :trend => :none, :seasonality => :multiplicative}
+
+ INITIAL_PARAMETER_VALUES.each do |gamma|
+ [:additive, :multiplicative].each do |trend|
+ [:additive, :multiplicative].each do |seasonality|
+ parameters << {
+ :alpha => alpha,
+ :beta => b,
+ :gamma => gamma,
+ :trend => trend,
+ :seasonality => seasonality
+ }
+ end
+ end
+ end
+ end
+ end
+ end
+
+ parameters
+ end
+
+ private
+
+ def improve_model(model, change)
+ trend_operations = model.trend == :none ? [nil] : [:+, :-, nil]
+ season_operations = model.seasonality == :none ? [nil] : [:+, :-, nil]
+ permutations = [:+, :-, nil].product(trend_operations, season_operations)
+ optimum(permutations.map do |(op_1,op_2,op_3)|
+ new_opts = {}
+ set_value(new_opts, :alpha, model, op_1, change)
+ set_value(new_opts, :beta, model, op_2, change)
+ set_value(new_opts, :gamma, model, op_3, change)
+ model.improve(new_opts)
+ end)
+ end
+
+ def set_value(hsh, key, model, op, change)
+ unless op.nil?
+ new_value = model.send(key).send(op, change)
+ if new_value >= 0.0 && new_value <= 1.0
+ hsh[key] = new_value
+ end
+ end
+ end
+
+ def optimum(models)
+ models.min_by(&:mean_squared_error)
+ end
+
+ def initial_models
+ initial_test_parameters.map do |parameters|
+ ExponentialSmoothingForecast.new(@time_series, @period, parameters)
+ end
+ end
+ end
+end
@@ -68,6 +68,8 @@ def apply(forecast, parameters, n)
end
end
+ attr_reader :alpha, :beta, :gamma, :trend, :seasonality
+
def initialize(time_series, period, opts={})
@time_series = time_series
@period = period
@@ -88,6 +90,11 @@ def initialize(time_series, period, opts={})
calculate_one_step_ahead_forecasts
end
+ def improve(opts)
+ new_opts = {:alpha => @alpha, :beta => @beta, :gamma => @gamma, :trend => @trend, :seasonality => @seasonality}.merge(opts)
+ self.class.new(@time_series, @period, new_opts)
+ end
+
attr_reader :model_parameters
def initial_level
@@ -114,7 +121,7 @@ def initial_parameters
def predict(n=nil)
if n.nil?
- forecast(@model_parameters)
+ forecast(@model_parameters).first
else
(1..n).map {|i| forecast(@model_parameters, i).first }
end
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe TeaLeaves::BruteForceOptimization do
+ it "should have 1014 initial test models" do
+ described_class.new([1,2,3,4], 1).initial_test_parameters.size.should == 1014
+ end
+
+ it "should produce an initial model" do
+
+ end
+end

0 comments on commit 5197d08

Please sign in to comment.