source: branches/HeuristicLab.GoalSeekingProblem/HeuristicLab.GoalSeekingProblem/3.4/MultiObjectiveGoalSeekingProblem.cs @ 14379

Last change on this file since 14379 was 14379, checked in by bburlacu, 4 years ago

#2679: Use an item list for models in the goal seeking problems instead of an item collection. Update encoding instead of creating a new one when inputs are changed.

File size: 13.5 KB
Line 
1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Linq;
25using HeuristicLab.Collections;
26using HeuristicLab.Common;
27using HeuristicLab.Core;
28using HeuristicLab.Data;
29using HeuristicLab.Encodings.RealVectorEncoding;
30using HeuristicLab.Optimization;
31using HeuristicLab.Parameters;
32using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
33using HeuristicLab.Problems.DataAnalysis;
34
35namespace HeuristicLab.GoalSeeking {
36  [Item("Goal seeking problem (multi-objective)", "Represents a single objective optimization problem which uses configurable regression models to evaluate targets from a given dataset.")]
37  [Creatable("Problems")]
38  [StorableClass]
39  public sealed class MultiObjectiveGoalSeekingProblem : MultiObjectiveBasicProblem<RealVectorEncoding>, IGoalSeekingProblem {
40    #region parameter names
41    private const string ModifiableDatasetParameterName = "Dataset";
42    private const string InputsParameterName = "Inputs";
43    private const string GoalsParameterName = "Goals";
44    private const string ModelsParameterName = "Models";
45    private const string QualitySumCutoffParameterName = "QualitySumCutoff";
46    #endregion
47
48    #region parameters
49    public IValueParameter<CheckedItemList<InputParameter>> InputsParameter {
50      get { return (IValueParameter<CheckedItemList<InputParameter>>)Parameters[InputsParameterName]; }
51    }
52    public IValueParameter<CheckedItemList<GoalParameter>> GoalsParameter {
53      get { return (IValueParameter<CheckedItemList<GoalParameter>>)Parameters[GoalsParameterName]; }
54    }
55    public IFixedValueParameter<ItemList<IRegressionModel>> ModelsParameter {
56      get { return (IFixedValueParameter<ItemList<IRegressionModel>>)Parameters[ModelsParameterName]; }
57    }
58    public IFixedValueParameter<DoubleValue> QualitySumCutoffParameter {
59      get { return (IFixedValueParameter<DoubleValue>)Parameters[QualitySumCutoffParameterName]; }
60    }
61    #endregion
62
63    #region IGoalSeekingProblem implementation
64    public IEnumerable<IRegressionModel> Models {
65      get { return ModelsParameter.Value; }
66    }
67
68    public IEnumerable<GoalParameter> Goals {
69      get { return GoalsParameter.Value; }
70    }
71
72    public IEnumerable<InputParameter> Inputs {
73      get { return InputsParameter.Value; }
74    }
75
76    public void AddModel(IRegressionModel model) {
77      var models = ModelsParameter.Value;
78      models.Add(model);
79      GoalSeekingUtil.RaiseEvent(this, ModelsChanged);
80    }
81
82    public void RemoveModel(IRegressionModel model) {
83      var models = ModelsParameter.Value;
84      models.Remove(model);
85      GoalSeekingUtil.RaiseEvent(this, ModelsChanged);
86    }
87
88    public void Configure(IRegressionProblemData problemData, int row) {
89      GoalSeekingUtil.Configure(Goals, Inputs, problemData, row);
90    }
91
92    public IEnumerable<double> GetEstimatedGoalValues(IEnumerable<double> parameterValues, bool round = false) {
93      var ds = (ModifiableDataset)dataset.Clone();
94      foreach (var parameter in ActiveInputs.Zip(parameterValues, (p, v) => new { Name = p.Name, Value = v })) {
95        ds.SetVariableValue(parameter.Value, parameter.Name, 0);
96      }
97      var rows = new[] { 0 }; // actually just one row
98      var estimatedValues =
99        round ? ActiveGoals.Select(t => RoundToNearestStepMultiple(GetModels(t.Name).Average(m => m.GetEstimatedValues(ds, rows).Single()), t.Step))
100              : ActiveGoals.Select(t => GetModels(t.Name).Average(m => m.GetEstimatedValues(ds, rows).Single()));
101      return estimatedValues;
102    }
103
104    public event EventHandler ModelsChanged;
105    public event EventHandler TargetsChanged;
106    public event EventHandler ParametersChanged;
107    #endregion
108
109    private IEnumerable<GoalParameter> ActiveGoals {
110      get { return Goals.Where(x => x.Active); }
111    }
112    private IEnumerable<InputParameter> ActiveInputs {
113      get { return Inputs.Where(x => x.Active); }
114    }
115    private double QualitySumCutoff {
116      get { return QualitySumCutoffParameter.Value.Value; }
117    }
118
119    [Storable]
120    private ModifiableDataset dataset; // modifiable dataset
121
122    [Storable]
123    private bool[] maximization;
124    public override bool[] Maximization {
125      get { return maximization ?? new bool[] { false }; }
126    }
127
128    public ValueParameter<BoolArray> MaximizationParameter {
129      get { return (ValueParameter<BoolArray>)Parameters["Maximization"]; }
130    }
131
132    #region constructors
133    [StorableConstructor]
134    private MultiObjectiveGoalSeekingProblem(bool deserializing) : base(deserializing) { }
135
136    private MultiObjectiveGoalSeekingProblem(MultiObjectiveGoalSeekingProblem original, Cloner cloner) : base(original, cloner) {
137      this.dataset = cloner.Clone(original.dataset);
138
139      RegisterEvents();
140    }
141
142    public override IDeepCloneable Clone(Cloner cloner) {
143      return new MultiObjectiveGoalSeekingProblem(this, cloner);
144    }
145
146    [StorableHook(HookType.AfterDeserialization)]
147    private void AfterDeserialization() {
148      RegisterEvents();
149    }
150
151    public MultiObjectiveGoalSeekingProblem() {
152      dataset = new ModifiableDataset();
153      Parameters.Add(new ValueParameter<IDataset>(ModifiableDatasetParameterName, dataset) { Hidden = true });
154      Parameters.Add(new ValueParameter<CheckedItemList<InputParameter>>(InputsParameterName));
155      Parameters.Add(new ValueParameter<CheckedItemList<GoalParameter>>(GoalsParameterName));
156      Parameters.Add(new FixedValueParameter<ItemList<IRegressionModel>>(ModelsParameterName, new ItemList<IRegressionModel>()));
157      Parameters.Add(new FixedValueParameter<DoubleValue>(QualitySumCutoffParameterName, new DoubleValue(0.2)));
158      QualitySumCutoffParameter.Hidden = true;
159      EncodingParameter.Hidden = true;
160      EvaluatorParameter.Hidden = true;
161      SolutionCreatorParameter.Hidden = true;
162      MaximizationParameter.Hidden = true;
163      RegisterEvents();
164    }
165    #endregion
166
167    public override double[] Evaluate(Individual individual, IRandom random) {
168      var vector = individual.RealVector();
169      vector.ElementNames = ActiveInputs.Select(x => x.Name);
170      int i = 0;
171      // round vector according to parameter step sizes
172      foreach (var parameter in ActiveInputs) {
173        vector[i] = RoundToNearestStepMultiple(vector[i], parameter.Step);
174        ++i;
175      }
176      var estimatedValues = GetEstimatedGoalValues(vector, round: true);
177      var qualities = ActiveGoals.Zip(estimatedValues, (t, v) => new { Target = t, EstimatedValue = v })
178                                 .Select(x => x.Target.Weight * Math.Pow(x.EstimatedValue - x.Target.Goal, 2) / x.Target.Variance);
179      return qualities.ToArray();
180    }
181
182    #region pareto analyzer
183    public override void Analyze(Individual[] individuals, double[][] qualities, ResultCollection results, IRandom random) {
184      var matrix = FilterFrontsByQualitySum(individuals, qualities, Math.Max(QualitySumCutoff, qualities.Min(x => x.Sum())));
185      const string resultName = "Pareto Front Solutions"; // disclaimer: not really a pareto front
186      if (!results.ContainsKey(resultName)) {
187        results.Add(new Result(resultName, matrix));
188      } else {
189        results[resultName].Value = matrix;
190      }
191      base.Analyze(individuals, qualities, results, random);
192    }
193
194    private DoubleMatrix FilterFrontsByQualitySum(Individual[] individuals, double[][] qualities, double qualitySumCutoff) {
195      var activeParameters = ActiveInputs.ToList();
196      var activeGoals = ActiveGoals.ToList();
197      var filteredModels = new List<double[]>();
198      var rowNames = new List<string>();
199      // build list of column names by combining target and parameter names (with their respective original and estimated values)
200      var columnNames = new List<string> { "Quality Sum" };
201      foreach (var target in activeGoals) {
202        columnNames.Add(target.Name);
203        columnNames.Add(target.Name + " (estimated)");
204      }
205      foreach (var parameter in activeParameters) {
206        columnNames.Add(parameter.Name);
207        columnNames.Add(parameter.Name + " (estimated)");
208        columnNames.Add(parameter.Name + " (deviation)");
209      }
210      // filter models based on their quality sum; remove duplicate models
211      var dec = new DoubleEqualityComparer(); // comparer which uses the IsAlmost method for comparing floating point numbers
212      for (int i = 0; i < individuals.Length; ++i) {
213        var qualitySum = qualities[i].Sum();
214        if (qualitySum > qualitySumCutoff)
215          continue;
216        var vector = individuals[i].RealVector();
217        var estimatedValues = GetEstimatedGoalValues(vector).ToList();
218        var rowValues = new double[columnNames.Count];
219        rowValues[0] = qualitySum;
220        int offset = 1;
221        for (int j = 0; j < activeGoals.Count * 2; j += 2) {
222          int k = j + offset;
223          var goal = activeGoals[j / 2].Goal;
224          rowValues[k] = goal; // original value
225          rowValues[k + 1] = estimatedValues[j / 2]; // estimated value
226        }
227        offset += activeGoals.Count * 2;
228        for (int j = 0; j < activeParameters.Count * 3; j += 3) {
229          int k = j + offset;
230          rowValues[k] = activeParameters[j / 3].Value;
231          rowValues[k + 1] = vector[j / 3];
232          rowValues[k + 2] = rowValues[k + 1] - rowValues[k];
233        }
234        if (!filteredModels.Any(x => x.SequenceEqual(rowValues, dec))) {
235          rowNames.Add((i + 1).ToString());
236          filteredModels.Add(rowValues);
237        }
238      }
239      var matrix = new DoubleMatrix(filteredModels.Count, columnNames.Count) { RowNames = rowNames, ColumnNames = columnNames, SortableView = true };
240      for (int i = 0; i < filteredModels.Count; ++i) {
241        for (int j = 0; j < filteredModels[i].Length; ++j) {
242          matrix[i, j] = filteredModels[i][j];
243        }
244      }
245      return matrix;
246    }
247    #endregion
248
249    #region event handlers
250    private void RegisterEvents() {
251      ModelsParameter.Value.ItemsAdded += ModelCollection_ItemsChanged;
252      ModelsParameter.Value.ItemsRemoved += ModelCollection_ItemsChanged;
253      GoalsParameter.Value.CheckedItemsChanged += GoalSeekingUtil.Goals_CheckedItemsChanged;
254      InputsParameter.Value.CheckedItemsChanged += GoalSeekingUtil.Inputs_CheckedItemsChanged;
255
256      foreach (var input in Inputs)
257        input.Changed += InputParameterChanged;
258
259      foreach (var goal in Goals)
260        goal.Changed += GoalParameterChanged;
261    }
262
263    private void ModelCollection_ItemsChanged(object sender, CollectionItemsChangedEventArgs<IndexedItem<IRegressionModel>> e) {
264      if (e.Items == null || !e.Items.Any()) return;
265      GoalSeekingUtil.UpdateInputs(InputsParameter.Value, Models, InputParameterChanged);
266      GoalSeekingUtil.UpdateEncoding(Encoding, ActiveInputs);
267      dataset = Inputs.Any() ? new ModifiableDataset(Inputs.Select(x => x.Name), Inputs.Select(x => new List<double> { x.Value })) : new ModifiableDataset();
268      GoalSeekingUtil.UpdateTargets(GoalsParameter.Value, Models, GoalParameterChanged);
269      GoalSeekingUtil.RaiseEvent(this, ModelsChanged);
270    }
271
272    private void InputParameterChanged(object sender, EventArgs args) {
273      var inputParameter = (InputParameter)sender;
274      var inputs = InputsParameter.Value;
275      if (inputs.ItemChecked(inputParameter) != inputParameter.Active)
276        inputs.SetItemCheckedState(inputParameter, inputParameter.Active);
277      GoalSeekingUtil.UpdateEncoding(Encoding, ActiveInputs);
278    }
279
280    private void GoalParameterChanged(object sender, EventArgs args) {
281      var goalParameter = (GoalParameter)sender;
282      var goals = GoalsParameter.Value;
283      if (goals.ItemChecked(goalParameter) != goalParameter.Active)
284        goals.SetItemCheckedState(goalParameter, goalParameter.Active);
285    }
286    #endregion
287
288    #region helper methods
289    // method which throws an exception that can be caught in the event handler if the check fails
290    private void CheckIfDatasetContainsTarget(string target) {
291      if (dataset.DoubleVariables.All(x => x != target))
292        throw new ArgumentException(string.Format("Model target \"{0}\" does not exist in the dataset.", target));
293    }
294    private static double RoundToNearestStepMultiple(double value, double step) {
295      return step * (long)Math.Round(value / step);
296    }
297    private IEnumerable<IRegressionModel> GetModels(string target) {
298      return Models.Where(x => x.TargetVariable == target);
299    }
300    private class DoubleEqualityComparer : IEqualityComparer<double> {
301      public bool Equals(double x, double y) { return x.IsAlmost(y); }
302      public int GetHashCode(double obj) { return obj.GetHashCode(); }
303    }
304    #endregion
305  }
306}
Note: See TracBrowser for help on using the repository browser.