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 |
|
---|
22 | using System;
|
---|
23 | using System.Collections.Generic;
|
---|
24 | using System.Linq;
|
---|
25 | using System.Windows.Forms;
|
---|
26 | using HeuristicLab.Collections;
|
---|
27 | using HeuristicLab.Common;
|
---|
28 | using HeuristicLab.Core;
|
---|
29 | using HeuristicLab.Data;
|
---|
30 | using HeuristicLab.Encodings.RealVectorEncoding;
|
---|
31 | using HeuristicLab.Optimization;
|
---|
32 | using HeuristicLab.Parameters;
|
---|
33 | using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
|
---|
34 | using HeuristicLab.Problems.DataAnalysis;
|
---|
35 |
|
---|
36 | namespace HeuristicLab.GoalSeeking {
|
---|
37 | [Item("Goal seeking problem (multi-objective)", "Represents a single objective optimization problem which uses configurable regression models to evaluate targets from a given dataset.")]
|
---|
38 | [Creatable("Problems")]
|
---|
39 | [StorableClass]
|
---|
40 | public sealed class MultiObjectiveGoalSeekingProblem : MultiObjectiveBasicProblem<RealVectorEncoding>, IGoalSeekingProblem {
|
---|
41 | private const string ModifiableDatasetParameterName = "Dataset";
|
---|
42 | private const string ProblemDataParameterName = "ProblemData";
|
---|
43 | private const string ControllableParametersParameterName = "ControllableParameters";
|
---|
44 | private const string ControllableParameterBoundsParameterName = "ControllableParameterBounds";
|
---|
45 | private const string TargetGoalsParameterName = "TargetGoals";
|
---|
46 | private const string TargetsParameterName = "Targets";
|
---|
47 | private const string ModelCollectionParameterName = "ModelCollection";
|
---|
48 | private const string RowParameterName = "Row";
|
---|
49 | // these parameters are used by the pareto folding analyzer
|
---|
50 | private const string QualitySumCutoffParameterName = "QualitySumCutoff";
|
---|
51 |
|
---|
52 | #region parameters
|
---|
53 | public IFixedValueParameter<DoubleValue> QualitySumCutoffParameter {
|
---|
54 | get { return (IFixedValueParameter<DoubleValue>)Parameters[QualitySumCutoffParameterName]; }
|
---|
55 | }
|
---|
56 | public IValueParameter<IRegressionProblemData> ProblemDataParameter {
|
---|
57 | get { return (IValueParameter<IRegressionProblemData>)Parameters[ProblemDataParameterName]; }
|
---|
58 | }
|
---|
59 | public IValueParameter<CheckedItemList<StringValue>> ControllableParametersParameter {
|
---|
60 | get { return (IValueParameter<CheckedItemList<StringValue>>)Parameters[ControllableParametersParameterName]; }
|
---|
61 | }
|
---|
62 | public IValueParameter<DoubleMatrix> ControllableParameterBoundsParameter {
|
---|
63 | get { return (IValueParameter<DoubleMatrix>)Parameters[ControllableParameterBoundsParameterName]; }
|
---|
64 | }
|
---|
65 | public IFixedValueParameter<ItemCollection<IRegressionModel>> ModelCollectionParameter {
|
---|
66 | get { return (IFixedValueParameter<ItemCollection<IRegressionModel>>)Parameters[ModelCollectionParameterName]; }
|
---|
67 | }
|
---|
68 | public IValueParameter<CheckedItemList<StringValue>> TargetsParameter {
|
---|
69 | get { return (IValueParameter<CheckedItemList<StringValue>>)Parameters[TargetsParameterName]; }
|
---|
70 | }
|
---|
71 | public IValueParameter<DoubleMatrix> TargetGoalsParameter {
|
---|
72 | get { return (IValueParameter<DoubleMatrix>)Parameters[TargetGoalsParameterName]; }
|
---|
73 | }
|
---|
74 | public IFixedValueParameter<IntValue> RowParameter {
|
---|
75 | get { return (IFixedValueParameter<IntValue>)Parameters[RowParameterName]; }
|
---|
76 | }
|
---|
77 | #endregion
|
---|
78 |
|
---|
79 | #region parameter properties
|
---|
80 | private IItemCollection<IRegressionModel> ModelCollection {
|
---|
81 | get { return ModelCollectionParameter.Value; }
|
---|
82 | }
|
---|
83 | public DoubleMatrix TargetGoals {
|
---|
84 | get { return TargetGoalsParameter.Value; }
|
---|
85 | set { TargetGoalsParameter.Value = value; }
|
---|
86 | }
|
---|
87 | public double QualitySumCutoff {
|
---|
88 | get { return QualitySumCutoffParameter.Value.Value; }
|
---|
89 | set { QualitySumCutoffParameter.Value.Value = value; }
|
---|
90 | }
|
---|
91 | #endregion
|
---|
92 |
|
---|
93 | #region IProcessParameterOptimizationProblem properties
|
---|
94 | [Storable]
|
---|
95 | private IRegressionProblemData problemData;
|
---|
96 | public IRegressionProblemData ProblemData {
|
---|
97 | get { return problemData; }
|
---|
98 | set {
|
---|
99 | if (value == null || value == problemData) return;
|
---|
100 | var variables = value.Dataset.DoubleVariables.ToList();
|
---|
101 | if (Models.Any()) {
|
---|
102 | var targets = Models.Select(x => x.TargetVariable);
|
---|
103 | var hashset = new HashSet<string>(variables);
|
---|
104 | foreach (var target in targets) {
|
---|
105 | if (!hashset.Contains(target)) {
|
---|
106 | throw new ArgumentException(string.Format("Incompatible problem data. Target \"{0}\" is missing.", target));
|
---|
107 | }
|
---|
108 | }
|
---|
109 | }
|
---|
110 | problemData = value;
|
---|
111 | dataset = new ModifiableDataset(variables, variables.Select(x => new List<double> { ProblemData.Dataset.GetDoubleValue(x, Row) }));
|
---|
112 | ProblemDataParameter.Value = ProblemData;
|
---|
113 | UpdateControllableParameters();
|
---|
114 | UpdateTargetList();
|
---|
115 | }
|
---|
116 | }
|
---|
117 |
|
---|
118 | public int Row {
|
---|
119 | get { return RowParameter.Value.Value; }
|
---|
120 | set { RowParameter.Value.Value = value; }
|
---|
121 | }
|
---|
122 |
|
---|
123 | public IEnumerable<IRegressionModel> Models {
|
---|
124 | get { return ModelCollectionParameter.Value; }
|
---|
125 | }
|
---|
126 |
|
---|
127 | #region targets
|
---|
128 | public ICheckedItemList<StringValue> TargetList {
|
---|
129 | get { return TargetsParameter.Value; }
|
---|
130 | set { TargetsParameter.Value = (CheckedItemList<StringValue>)value; }
|
---|
131 | }
|
---|
132 | // convenience method
|
---|
133 | private IEnumerable<string> ActiveTargets {
|
---|
134 | get { return TargetList.CheckedItems.Select(x => x.Value.Value); }
|
---|
135 | }
|
---|
136 | #endregion
|
---|
137 |
|
---|
138 | #region parameters
|
---|
139 | public ICheckedItemList<StringValue> ControllableParameters {
|
---|
140 | get { return ControllableParametersParameter.Value; }
|
---|
141 | set { ControllableParametersParameter.Value = (CheckedItemList<StringValue>)value; }
|
---|
142 | }
|
---|
143 | // convenience method
|
---|
144 | private IEnumerable<string> ActiveParameters {
|
---|
145 | get { return ControllableParameters.CheckedItems.Select(x => x.Value.Value); }
|
---|
146 | }
|
---|
147 | public DoubleMatrix ControllableParameterBounds {
|
---|
148 | get { return ControllableParameterBoundsParameter.Value; }
|
---|
149 | set { ControllableParameterBoundsParameter.Value = value; }
|
---|
150 | }
|
---|
151 | #endregion
|
---|
152 | #endregion
|
---|
153 |
|
---|
154 | #region IProcessParameterOptimizationProblem methods
|
---|
155 | #region models
|
---|
156 | public IEnumerable<double> GetEstimatedGoalValues(IEnumerable<double> parameterValues, bool round = false) {
|
---|
157 | var ds = (ModifiableDataset)dataset.Clone();
|
---|
158 | foreach (var parameter in ActiveParameters.Zip(parameterValues, (p, v) => new { Name = p, Value = v })) {
|
---|
159 | ds.SetVariableValue(parameter.Value, parameter.Name, 0);
|
---|
160 | }
|
---|
161 | var rows = new[] { 0 }; // actually just one row
|
---|
162 |
|
---|
163 | var estimatedValues =
|
---|
164 | round ? ActiveTargets.Select(t => RoundToNearestStepMultiple(GetModels(t).Average(m => m.GetEstimatedValues(ds, rows).Single()), GetTargetStepSize(t)))
|
---|
165 | : ActiveTargets.Select(t => GetModels(t).Average(m => m.GetEstimatedValues(ds, rows).Single()));
|
---|
166 | return estimatedValues;
|
---|
167 | }
|
---|
168 |
|
---|
169 | public void AddModel(IRegressionModel model) {
|
---|
170 | var target = model.TargetVariable;
|
---|
171 | CheckIfDatasetContainsTarget(target);
|
---|
172 | ModelCollection.Add(model);
|
---|
173 | OnModelsChanged(this, EventArgs.Empty);
|
---|
174 | }
|
---|
175 |
|
---|
176 | // method which throws an exception that can be caught in the event handler if the check fails
|
---|
177 | private void CheckIfDatasetContainsTarget(string target) {
|
---|
178 | if (dataset.DoubleVariables.All(x => x != target))
|
---|
179 | throw new ArgumentException(string.Format("Model target \"{0}\" does not exist in the dataset.", target));
|
---|
180 | }
|
---|
181 |
|
---|
182 | public void RemoveModel(IRegressionModel model) {
|
---|
183 | ModelCollection.Remove(model);
|
---|
184 | OnModelsChanged(this, EventArgs.Empty);
|
---|
185 | }
|
---|
186 |
|
---|
187 | public event EventHandler ModelsChanged;
|
---|
188 | private void OnModelsChanged(object sender, EventArgs args) {
|
---|
189 | var changed = ModelsChanged;
|
---|
190 | if (changed == null) return;
|
---|
191 | changed(sender, args);
|
---|
192 | }
|
---|
193 | #endregion
|
---|
194 |
|
---|
195 | #region targets
|
---|
196 | public bool GetTargetActive(string target) {
|
---|
197 | var item = TargetList.SingleOrDefault(x => x.Value == target);
|
---|
198 | if (item == null)
|
---|
199 | throw new ArgumentException(string.Format("SetTargetActive: Invalid target name {0}", target));
|
---|
200 | return TargetList.ItemChecked(item);
|
---|
201 | }
|
---|
202 |
|
---|
203 | public void SetTargetActive(string target, bool active) {
|
---|
204 | var item = TargetList.SingleOrDefault(x => x.Value == target);
|
---|
205 | if (item == null)
|
---|
206 | throw new ArgumentException(string.Format("SetTargetActive: Invalid target name {0}", target));
|
---|
207 | TargetList.SetItemCheckedState(item, active);
|
---|
208 | OnTargetsChanged(this, EventArgs.Empty);
|
---|
209 | }
|
---|
210 |
|
---|
211 | public double GetTargetGoal(string target) {
|
---|
212 | if (!IsValidTarget(target))
|
---|
213 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
214 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
215 | return TargetGoals[i, 0];
|
---|
216 | }
|
---|
217 |
|
---|
218 | public void SetTargetGoal(string target, double goal) {
|
---|
219 | if (!IsValidTarget(target))
|
---|
220 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
221 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
222 | TargetGoals[i, 0] = goal;
|
---|
223 | OnTargetsChanged(this, EventArgs.Empty);
|
---|
224 | }
|
---|
225 |
|
---|
226 | public double GetTargetWeight(string target) {
|
---|
227 | if (!IsValidTarget(target))
|
---|
228 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
229 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
230 | return TargetGoals[i, 1];
|
---|
231 | }
|
---|
232 |
|
---|
233 | public void SetTargetWeight(string target, double weight) {
|
---|
234 | if (!IsValidTarget(target))
|
---|
235 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
236 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
237 | TargetGoals[i, 1] = weight;
|
---|
238 | OnTargetsChanged(this, EventArgs.Empty);
|
---|
239 | }
|
---|
240 |
|
---|
241 | public double GetTargetVariance(string target) {
|
---|
242 | if (!IsValidTarget(target))
|
---|
243 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
244 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
245 | return TargetGoals[i, 2];
|
---|
246 | }
|
---|
247 |
|
---|
248 | public void SetTargetVariance(string target, double variance) {
|
---|
249 | if (!IsValidTarget(target))
|
---|
250 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
251 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
252 | TargetGoals[i, 2] = variance;
|
---|
253 | OnTargetsChanged(this, EventArgs.Empty);
|
---|
254 | }
|
---|
255 |
|
---|
256 | public double GetTargetStepSize(string target) {
|
---|
257 | if (!IsValidTarget(target))
|
---|
258 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
259 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
260 | return TargetGoals[i, 3];
|
---|
261 | }
|
---|
262 |
|
---|
263 | public void SetTargetStepSize(string target, double stepSize) {
|
---|
264 | if (!IsValidTarget(target))
|
---|
265 | throw new ArgumentException(string.Format("The target variable name \"{0}\" does not exist in the dataset.", target));
|
---|
266 | int i = TargetGoals.RowNames.TakeWhile(x => x != target).Count();
|
---|
267 | TargetGoals[i, 3] = stepSize;
|
---|
268 | OnTargetsChanged(this, EventArgs.Empty);
|
---|
269 | }
|
---|
270 |
|
---|
271 | public event EventHandler TargetsChanged;
|
---|
272 | private void OnTargetsChanged(object sender, EventArgs args) {
|
---|
273 | var changed = TargetsChanged;
|
---|
274 | if (changed == null) return;
|
---|
275 | changed(sender, args);
|
---|
276 | }
|
---|
277 | #endregion // targets
|
---|
278 |
|
---|
279 | #region process parameters
|
---|
280 | /// <summary>
|
---|
281 | /// Returns the parameter bounds (min and max) and the step size for the specified parameter
|
---|
282 | /// </summary>
|
---|
283 | /// <param name="parameterName"></param>
|
---|
284 | /// <returns>A double array containing the values (min, max, step) in this order</returns>
|
---|
285 | public double[] GetParameterBounds(string parameterName) {
|
---|
286 | var index = ControllableParameters.TakeWhile(x => x.Value != parameterName).Count();
|
---|
287 | if (index < ControllableParameters.Count) {
|
---|
288 | var min = ControllableParameterBounds[index, 0];
|
---|
289 | var max = ControllableParameterBounds[index, 1];
|
---|
290 | var step = ControllableParameterBounds[index, 2];
|
---|
291 | return new[] { min, max, step };
|
---|
292 | }
|
---|
293 | throw new ArgumentException(string.Format("GetParameterBounds: Unknown parameter {0}.", parameterName));
|
---|
294 | }
|
---|
295 |
|
---|
296 | public void SetParameterBounds(string parameterName, double min, double max, double step) {
|
---|
297 | int i = ControllableParameterBounds.RowNames.TakeWhile(x => x != parameterName).Count();
|
---|
298 | if (i < ControllableParameterBounds.Rows) {
|
---|
299 | ControllableParameterBounds[i, 0] = min;
|
---|
300 | ControllableParameterBounds[i, 1] = max;
|
---|
301 | ControllableParameterBounds[i, 2] = step;
|
---|
302 | UpdateEncoding();
|
---|
303 | OnParametersChanged(this, EventArgs.Empty);
|
---|
304 | } else {
|
---|
305 | throw new ArgumentException(string.Format("SetParameterBounds: Invalid parameter name {0}", parameterName));
|
---|
306 | }
|
---|
307 |
|
---|
308 | }
|
---|
309 |
|
---|
310 | public double GetParameterStepSize(string parameter) {
|
---|
311 | int i = ControllableParameterBounds.RowNames.TakeWhile(x => x != parameter).Count();
|
---|
312 | if (i < ControllableParameterBounds.Rows)
|
---|
313 | return ControllableParameterBounds[i, 2];
|
---|
314 | throw new ArgumentException(string.Format("GetParameterStepSize: Invalid parameter name {0}", parameter));
|
---|
315 | }
|
---|
316 |
|
---|
317 | public void SetParameterStepSize(string parameter, double stepSize) {
|
---|
318 | int i = ControllableParameterBounds.RowNames.TakeWhile(x => x != parameter).Count();
|
---|
319 | if (i < ControllableParameterBounds.Rows) {
|
---|
320 | ControllableParameterBounds[i, 2] = stepSize;
|
---|
321 | OnParametersChanged(this, EventArgs.Empty);
|
---|
322 | return;
|
---|
323 | }
|
---|
324 | throw new ArgumentException(string.Format("SetParameterStepSize: Invalid parameter name {0}", parameter));
|
---|
325 | }
|
---|
326 |
|
---|
327 | public bool GetParameterActive(string parameter) {
|
---|
328 | var item = ControllableParameters.SingleOrDefault(x => x.Value == parameter);
|
---|
329 | if (item == null)
|
---|
330 | throw new ArgumentException(string.Format("GetParameterActive: Invalid target name {0}", parameter));
|
---|
331 | return ControllableParameters.ItemChecked(item);
|
---|
332 | }
|
---|
333 |
|
---|
334 | public void SetParameterActive(string parameter, bool active) {
|
---|
335 | var item = ControllableParameters.SingleOrDefault(x => x.Value == parameter);
|
---|
336 | if (item == null)
|
---|
337 | throw new ArgumentException(string.Format("SetParameterActive: Invalid target name {0}", parameter));
|
---|
338 | ControllableParameters.SetItemCheckedState(item, active);
|
---|
339 | OnParametersChanged(this, EventArgs.Empty);
|
---|
340 | }
|
---|
341 |
|
---|
342 | public void SetControllableParameters(IEnumerable<string> parameterNames) {
|
---|
343 | ControllableParameters = new CheckedItemList<StringValue>();
|
---|
344 | foreach (var v in parameterNames) {
|
---|
345 | ControllableParameters.Add(new StringValue(v), false);
|
---|
346 | }
|
---|
347 | ControllableParameters.CheckedItemsChanged += ControllableParameters_OnItemsChanged;
|
---|
348 | ControllableParameterBounds = new DoubleMatrix(ControllableParameters.Count, 3);
|
---|
349 | ControllableParameterBounds.RowNames = GetControllableParameters();
|
---|
350 | ControllableParameterBounds.ColumnNames = new[] { "Min", "Max", "Step" };
|
---|
351 |
|
---|
352 | for (int i = 0; i < ControllableParameters.Count; ++i) {
|
---|
353 | var itemName = ControllableParameters[i].Value;
|
---|
354 | var values = ProblemData.Dataset.GetReadOnlyDoubleValues(itemName).Where(x => !double.IsNaN(x) && !double.IsInfinity(x)).ToList();
|
---|
355 | if (!values.Any()) continue;
|
---|
356 |
|
---|
357 | // add a 20% margin to allow the optimization algorithm more freedom of exploration
|
---|
358 | ControllableParameterBounds[i, 0] = 0.8 * values.Min(); // min
|
---|
359 | ControllableParameterBounds[i, 1] = 1.2 * values.Max(); // max
|
---|
360 | ControllableParameterBounds[i, 2] = 1e-6; // step
|
---|
361 | }
|
---|
362 | OnParametersChanged(this, EventArgs.Empty);
|
---|
363 | }
|
---|
364 |
|
---|
365 | public IEnumerable<string> GetControllableParameters() {
|
---|
366 | return ControllableParameters.Select(x => x.Value);
|
---|
367 | }
|
---|
368 |
|
---|
369 | public event EventHandler ParametersChanged;
|
---|
370 | private void OnParametersChanged(object sender, EventArgs args) {
|
---|
371 | var changed = ParametersChanged;
|
---|
372 | if (changed == null) return;
|
---|
373 | changed(sender, args);
|
---|
374 | }
|
---|
375 | #endregion // process parameters
|
---|
376 | #endregion // IGoalSeekingProblem methods
|
---|
377 |
|
---|
378 | #region data members
|
---|
379 | [Storable]
|
---|
380 | private ModifiableDataset dataset; // modifiable dataset
|
---|
381 |
|
---|
382 | [Storable]
|
---|
383 | private bool[] maximization;
|
---|
384 | public override bool[] Maximization {
|
---|
385 | get { return maximization ?? new bool[] { false }; }
|
---|
386 | }
|
---|
387 |
|
---|
388 | public ValueParameter<BoolArray> MaximizationParameter {
|
---|
389 | get { return (ValueParameter<BoolArray>)Parameters["Maximization"]; }
|
---|
390 | }
|
---|
391 | #endregion
|
---|
392 |
|
---|
393 | #region constructors
|
---|
394 | [StorableConstructor]
|
---|
395 | private MultiObjectiveGoalSeekingProblem(bool deserializing) : base(deserializing) { }
|
---|
396 |
|
---|
397 | private MultiObjectiveGoalSeekingProblem(MultiObjectiveGoalSeekingProblem original, Cloner cloner) : base(original, cloner) {
|
---|
398 | this.dataset = cloner.Clone(original.dataset);
|
---|
399 | this.problemData = cloner.Clone(original.problemData);
|
---|
400 |
|
---|
401 | RegisterEvents();
|
---|
402 | }
|
---|
403 |
|
---|
404 | public override IDeepCloneable Clone(Cloner cloner) {
|
---|
405 | return new MultiObjectiveGoalSeekingProblem(this, cloner);
|
---|
406 | }
|
---|
407 |
|
---|
408 | [StorableHook(HookType.AfterDeserialization)]
|
---|
409 | private void AfterDeserialization() {
|
---|
410 | if (Parameters.ContainsKey("Accuracy"))
|
---|
411 | Parameters.Remove("Accuracy");
|
---|
412 |
|
---|
413 | if (!Parameters.ContainsKey(QualitySumCutoffParameterName)) {
|
---|
414 | Parameters.Add(new FixedValueParameter<DoubleValue>(QualitySumCutoffParameterName, new DoubleValue(0.2)));
|
---|
415 | QualitySumCutoffParameter.Hidden = true;
|
---|
416 | }
|
---|
417 |
|
---|
418 | if (ProblemData == null && Parameters.ContainsKey(ProblemDataParameterName)) {
|
---|
419 | ProblemData = ProblemDataParameter.Value;
|
---|
420 | }
|
---|
421 |
|
---|
422 | if (!Parameters.ContainsKey(ModifiableDatasetParameterName)) {
|
---|
423 | Parameters.Add(new ValueParameter<IDataset>(ModifiableDatasetParameterName, dataset) { Hidden = true });
|
---|
424 | }
|
---|
425 |
|
---|
426 | // backwards-compatibility
|
---|
427 | if (Parameters.ContainsKey("Models")) {
|
---|
428 | var solutions = ((IFixedValueParameter<ItemCollection<IRegressionSolution>>)Parameters["Models"]).Value;
|
---|
429 | var models = new ItemCollection<IRegressionModel>();
|
---|
430 | foreach (var solution in solutions) {
|
---|
431 | var model = solution.Model;
|
---|
432 | model.TargetVariable = solution.ProblemData.TargetVariable;
|
---|
433 | models.Add(model);
|
---|
434 | }
|
---|
435 | if (Parameters.ContainsKey(ModelCollectionParameterName))
|
---|
436 | Parameters.Remove(ModelCollectionParameterName);
|
---|
437 | Parameters.Add(new FixedValueParameter<ItemCollection<IRegressionModel>>(ModelCollectionParameterName, models));
|
---|
438 | }
|
---|
439 |
|
---|
440 | RegisterEvents();
|
---|
441 | }
|
---|
442 |
|
---|
443 | public MultiObjectiveGoalSeekingProblem() {
|
---|
444 | Parameters.Add(new ValueParameter<IRegressionProblemData>(ProblemDataParameterName, new RegressionProblemData()));
|
---|
445 | Parameters.Add(new ValueParameter<IDataset>(ModifiableDatasetParameterName, dataset) { Hidden = true });
|
---|
446 | Parameters.Add(new ValueParameter<CheckedItemList<StringValue>>(ControllableParametersParameterName));
|
---|
447 | Parameters.Add(new ValueParameter<CheckedItemList<StringValue>>(TargetsParameterName));
|
---|
448 | Parameters.Add(new ValueParameter<DoubleMatrix>(ControllableParameterBoundsParameterName));
|
---|
449 | Parameters.Add(new FixedValueParameter<ItemCollection<IRegressionModel>>(ModelCollectionParameterName, new ItemCollection<IRegressionModel>()));
|
---|
450 | Parameters.Add(new ValueParameter<DoubleMatrix>(TargetGoalsParameterName)); // model target weights
|
---|
451 | Parameters.Add(new FixedValueParameter<IntValue>(RowParameterName));
|
---|
452 | Parameters.Add(new FixedValueParameter<DoubleValue>(QualitySumCutoffParameterName, new DoubleValue(0.2)));
|
---|
453 |
|
---|
454 | QualitySumCutoffParameter.Hidden = true;
|
---|
455 |
|
---|
456 | // when the problem is created, the problem data parameter will be set to a default value
|
---|
457 | // set the internal property to the same value
|
---|
458 | ProblemData = ProblemDataParameter.Value;
|
---|
459 |
|
---|
460 | UpdateControllableParameters();
|
---|
461 | UpdateTargetList();
|
---|
462 | RegisterEvents();
|
---|
463 | }
|
---|
464 | #endregion
|
---|
465 |
|
---|
466 | public override double[] Evaluate(Individual individual, IRandom random) {
|
---|
467 | var vector = individual.RealVector();
|
---|
468 | vector.ElementNames = ActiveParameters;
|
---|
469 |
|
---|
470 | int i = 0;
|
---|
471 | // round vector according to parameter step sizes
|
---|
472 | foreach (var parameter in ControllableParameters.CheckedItems) {
|
---|
473 | var step = ControllableParameterBounds[parameter.Index, 2];
|
---|
474 | vector[i] = RoundToNearestStepMultiple(vector[i], step);
|
---|
475 | ++i;
|
---|
476 | }
|
---|
477 | var estimatedValues = GetEstimatedGoalValues(vector, round: true);
|
---|
478 | var qualities = TargetList.CheckedItems.Zip(estimatedValues, (t, v) => new { Name = t.Value.Value, Index = t.Index, EstimatedValue = v })
|
---|
479 | .Select(target => {
|
---|
480 | var goal = TargetGoals[target.Index, 0];
|
---|
481 | var weight = TargetGoals[target.Index, 1];
|
---|
482 | var variance = TargetGoals[target.Index, 2];
|
---|
483 | return weight * Math.Pow(target.EstimatedValue - goal, 2) / variance;
|
---|
484 | });
|
---|
485 | return qualities.ToArray();
|
---|
486 | }
|
---|
487 |
|
---|
488 | public override void Analyze(Individual[] individuals, double[][] qualities, ResultCollection results, IRandom random) {
|
---|
489 | var matrix = FilterFrontsByQualitySum(individuals, qualities, Math.Max(QualitySumCutoff, qualities.Min(x => x.Sum())));
|
---|
490 | const string resultName = "Pareto Front Solutions"; // disclaimer: not really a pareto front
|
---|
491 | if (!results.ContainsKey(resultName)) {
|
---|
492 | results.Add(new Result(resultName, matrix));
|
---|
493 | } else {
|
---|
494 | results[resultName].Value = matrix;
|
---|
495 | }
|
---|
496 | base.Analyze(individuals, qualities, results, random);
|
---|
497 | }
|
---|
498 |
|
---|
499 | private DoubleMatrix FilterFrontsByQualitySum(Individual[] individuals, double[][] qualities, double qualitySumCutoff) {
|
---|
500 | var activeParameters = ActiveParameters.ToList();
|
---|
501 | var activeTargets = ActiveTargets.ToList();
|
---|
502 | var filteredModels = new List<double[]>();
|
---|
503 | var rowNames = new List<string>();
|
---|
504 | // build list of column names by combining target and parameter names (with their respective original and estimated values)
|
---|
505 | var columnNames = new List<string> { "Quality Sum" };
|
---|
506 | foreach (var target in activeTargets) {
|
---|
507 | columnNames.Add(target);
|
---|
508 | columnNames.Add(target + " (estimated)");
|
---|
509 | }
|
---|
510 | foreach (var controllableParameter in activeParameters) {
|
---|
511 | columnNames.Add(controllableParameter);
|
---|
512 | columnNames.Add(controllableParameter + " (estimated)");
|
---|
513 | columnNames.Add(controllableParameter + " (deviation)");
|
---|
514 | }
|
---|
515 | // filter models based on their quality sum; remove duplicate models
|
---|
516 | var dec = new DoubleEqualityComparer(); // comparer which uses the IsAlmost method for comparing floating point numbers
|
---|
517 | for (int i = 0; i < individuals.Length; ++i) {
|
---|
518 | var qualitySum = qualities[i].Sum();
|
---|
519 | if (qualitySum > qualitySumCutoff)
|
---|
520 | continue;
|
---|
521 | var vector = individuals[i].RealVector();
|
---|
522 | var estimatedValues = GetEstimatedGoalValues(vector).ToList();
|
---|
523 | var rowValues = new double[columnNames.Count];
|
---|
524 | rowValues[0] = qualitySum;
|
---|
525 | int offset = 1;
|
---|
526 | for (int j = 0; j < activeTargets.Count * 2; j += 2) {
|
---|
527 | int k = j + offset;
|
---|
528 | var goal = GetTargetGoal(activeTargets[j / 2]);
|
---|
529 | rowValues[k] = goal; // original value
|
---|
530 | rowValues[k + 1] = estimatedValues[j / 2]; // estimated value
|
---|
531 | }
|
---|
532 | offset += activeTargets.Count * 2;
|
---|
533 | for (int j = 0; j < activeParameters.Count * 3; j += 3) {
|
---|
534 | int k = j + offset;
|
---|
535 | rowValues[k] = problemData.Dataset.GetDoubleValue(columnNames[k], Row);
|
---|
536 | rowValues[k + 1] = vector[j / 3];
|
---|
537 | rowValues[k + 2] = rowValues[k + 1] - rowValues[k];
|
---|
538 | }
|
---|
539 | if (!filteredModels.Any(x => x.SequenceEqual(rowValues, dec))) {
|
---|
540 | rowNames.Add((i + 1).ToString());
|
---|
541 | filteredModels.Add(rowValues);
|
---|
542 | }
|
---|
543 | }
|
---|
544 | var matrix = new DoubleMatrix(filteredModels.Count, columnNames.Count) { RowNames = rowNames, ColumnNames = columnNames, SortableView = true };
|
---|
545 | for (int i = 0; i < filteredModels.Count; ++i) {
|
---|
546 | for (int j = 0; j < filteredModels[i].Length; ++j) {
|
---|
547 | matrix[i, j] = filteredModels[i][j];
|
---|
548 | }
|
---|
549 | }
|
---|
550 | return matrix;
|
---|
551 | }
|
---|
552 |
|
---|
553 | #region event handlers
|
---|
554 | private void RegisterEvents() {
|
---|
555 | ProblemDataParameter.ValueChanged += OnProblemDataChanged;
|
---|
556 | ModelCollectionParameter.Value.ItemsAdded += ModelCollection_OnItemsAdded;
|
---|
557 | ModelCollectionParameter.Value.ItemsRemoved += ModelCollection_OnItemsRemoved;
|
---|
558 | RowParameter.Value.ValueChanged += OnRowChanged;
|
---|
559 | ControllableParameters.CheckedItemsChanged += ControllableParameters_OnItemsChanged;
|
---|
560 | ControllableParameterBounds.ItemChanged += ControllableParameterBounds_ItemChanged;
|
---|
561 | }
|
---|
562 |
|
---|
563 | private void OnRowChanged(object o, EventArgs e) {
|
---|
564 | // set variables in the modifiable dataset according to the new row
|
---|
565 | foreach (var v in dataset.DoubleVariables)
|
---|
566 | dataset.SetVariableValue(ProblemData.Dataset.GetDoubleValue(v, Row), v, 0);
|
---|
567 | // set the correct targets
|
---|
568 | UpdateTargetList();
|
---|
569 | }
|
---|
570 |
|
---|
571 | private void OnProblemDataChanged(object o, EventArgs e) {
|
---|
572 | try {
|
---|
573 | ProblemData = ProblemDataParameter.Value;
|
---|
574 | }
|
---|
575 | catch (ArgumentException exception) {
|
---|
576 | MessageBox.Show(exception.Message, "Update Problem Data", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
---|
577 | ProblemDataParameter.Value = problemData;
|
---|
578 | }
|
---|
579 | }
|
---|
580 |
|
---|
581 | private void ModelCollection_OnItemsAdded(object sender, CollectionItemsChangedEventArgs<IRegressionModel> e) {
|
---|
582 | if (e.Items == null) return;
|
---|
583 |
|
---|
584 | var collection = (IObservableCollection<IRegressionModel>)sender;
|
---|
585 | var newItems = e.Items.ToList();
|
---|
586 |
|
---|
587 | foreach (var model in e.Items) {
|
---|
588 | try {
|
---|
589 | CheckIfDatasetContainsTarget(model.TargetVariable);
|
---|
590 | }
|
---|
591 | catch (ArgumentException exception) {
|
---|
592 | MessageBox.Show(exception.Message, "Add Model", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
---|
593 | newItems.Remove(model);
|
---|
594 | collection.Remove(model);
|
---|
595 | }
|
---|
596 | }
|
---|
597 | UpdateTargetList();
|
---|
598 | OnModelsChanged(this, EventArgs.Empty);
|
---|
599 | }
|
---|
600 |
|
---|
601 | private void ModelCollection_OnItemsRemoved(object sender, CollectionItemsChangedEventArgs<IRegressionModel> e) {
|
---|
602 | if (e.Items == null) return;
|
---|
603 | UpdateTargetList();
|
---|
604 | OnModelsChanged(this, EventArgs.Empty);
|
---|
605 | }
|
---|
606 |
|
---|
607 | private void ControllableParameters_OnItemsChanged(object o, CollectionItemsChangedEventArgs<IndexedItem<StringValue>> e) {
|
---|
608 | UpdateEncoding();
|
---|
609 | }
|
---|
610 |
|
---|
611 | private void ControllableParameterBounds_ItemChanged(object o, EventArgs e) {
|
---|
612 | UpdateEncoding();
|
---|
613 | }
|
---|
614 | #endregion
|
---|
615 |
|
---|
616 | #region helper methods
|
---|
617 | private void UpdateControllableParameters() {
|
---|
618 | if (ProblemData == null) return;
|
---|
619 | var variablesUsedForPrediction = ModelCollection.Any()
|
---|
620 | ? ModelCollection.SelectMany(x => x.VariablesUsedForPrediction).Distinct()
|
---|
621 | : ProblemData.Dataset.DoubleVariables;
|
---|
622 | SetControllableParameters(variablesUsedForPrediction);
|
---|
623 | }
|
---|
624 |
|
---|
625 | private void UpdateTargetList() {
|
---|
626 | if (ProblemData == null) return;
|
---|
627 | if (!Models.Any()) {
|
---|
628 | TargetGoals = new DoubleMatrix();
|
---|
629 | maximization = new[] { false };
|
---|
630 | MaximizationParameter.Value = (BoolArray)new BoolArray(maximization).AsReadOnly();
|
---|
631 | TargetList = new CheckedItemList<StringValue>();
|
---|
632 | return;
|
---|
633 | }
|
---|
634 |
|
---|
635 | var targetNames = Models.Select(x => x.TargetVariable).Distinct().ToList();
|
---|
636 | var oldTargetGoals = (DoubleMatrix)TargetGoals.Clone();
|
---|
637 | var oldRowIndices = oldTargetGoals.RowNames.Select((x, i) => new { x, i }).ToDictionary(x => x.x, x => x.i);
|
---|
638 | TargetGoals = new DoubleMatrix(targetNames.Count, 4);
|
---|
639 | TargetGoals.RowNames = targetNames;
|
---|
640 | TargetGoals.ColumnNames = new[] { "Goal", "Weight", "Variance", "Step size" };
|
---|
641 |
|
---|
642 | TargetList = new CheckedItemList<StringValue>();
|
---|
643 | for (int i = 0; i < targetNames.Count; ++i) {
|
---|
644 | TargetList.Add(new StringValue(targetNames[i]), true);
|
---|
645 | int rowIndex;
|
---|
646 | if (oldRowIndices.TryGetValue(targetNames[i], out rowIndex)) {
|
---|
647 | for (int j = 0; j < TargetGoals.Columns; ++j)
|
---|
648 | TargetGoals[i, j] = oldTargetGoals[rowIndex, j];
|
---|
649 | } else {
|
---|
650 | TargetGoals[i, 0] = ProblemData.Dataset.GetDoubleValue(targetNames[i], Row);
|
---|
651 | TargetGoals[i, 1] = 1.0;
|
---|
652 | TargetGoals[i, 2] = ProblemData.Dataset.GetReadOnlyDoubleValues(targetNames[i]).Variance();
|
---|
653 | TargetGoals[i, 3] = 1e-6;
|
---|
654 | }
|
---|
655 | }
|
---|
656 | maximization = new bool[targetNames.Count];
|
---|
657 | MaximizationParameter.Value = (BoolArray)new BoolArray(maximization).AsReadOnly();
|
---|
658 | }
|
---|
659 |
|
---|
660 | private void UpdateEncoding() {
|
---|
661 | var activeParameters = ActiveParameters.ToList();
|
---|
662 | if (Encoding == null)
|
---|
663 | Encoding = new RealVectorEncoding(activeParameters.Count);
|
---|
664 | else
|
---|
665 | Encoding.Length = activeParameters.Count;
|
---|
666 |
|
---|
667 | Encoding.Bounds = new DoubleMatrix(activeParameters.Count, 2); // only two columns: min and max
|
---|
668 | Encoding.Bounds.RowNames = activeParameters;
|
---|
669 | Encoding.Bounds.ColumnNames = new[] { "Min.", "Max." };
|
---|
670 |
|
---|
671 | int i = 0;
|
---|
672 | foreach (var item in ControllableParameters.CheckedItems) {
|
---|
673 | var index = item.Index;
|
---|
674 | Encoding.Bounds[i, 0] = ControllableParameterBounds[index, 0];
|
---|
675 | Encoding.Bounds[i, 1] = ControllableParameterBounds[index, 1];
|
---|
676 | ++i;
|
---|
677 | }
|
---|
678 | }
|
---|
679 |
|
---|
680 | private bool IsValidTarget(string target) {
|
---|
681 | return TargetList.Any(x => x.Value == target);
|
---|
682 | }
|
---|
683 | private static double RoundToNearestStepMultiple(double value, double step) {
|
---|
684 | return step * (long)Math.Round(value / step);
|
---|
685 | }
|
---|
686 | private IEnumerable<IRegressionModel> GetModels(string target) {
|
---|
687 | return ModelCollection.Where(x => x.TargetVariable == target);
|
---|
688 | }
|
---|
689 | private class DoubleEqualityComparer : IEqualityComparer<double> {
|
---|
690 | public bool Equals(double x, double y) { return x.IsAlmost(y); }
|
---|
691 | public int GetHashCode(double obj) { return obj.GetHashCode(); }
|
---|
692 | }
|
---|
693 | #endregion
|
---|
694 | }
|
---|
695 | }
|
---|