[17737] | 1 | using HeuristicLab.Collections;
|
---|
| 2 | using HeuristicLab.Common;
|
---|
| 3 | using HeuristicLab.Problems.DataAnalysis;
|
---|
| 4 | using System;
|
---|
| 5 | using System.Globalization;
|
---|
| 6 | using System.Linq;
|
---|
| 7 |
|
---|
| 8 | namespace HeuristicLab.Algorithms.DataAnalysis.FastFunctionExtraction {
|
---|
| 9 |
|
---|
| 10 | internal struct SimpleBasisFunction : ISimpleBasisFunction {
|
---|
| 11 | public double Exponent { get; set; }
|
---|
| 12 | public NonlinearOperator Operator { get; set; }
|
---|
[17779] | 13 | public bool IsDenominator { get; set; }
|
---|
[17737] | 14 | public string Feature { get; set; }
|
---|
| 15 | public double Threshold { get; set; } // only relevant for hinge function
|
---|
[17779] | 16 | public bool HasExponent => !(Exponent == 1.0);
|
---|
[17737] | 17 | public bool HasOperator => Operator != NonlinearOperator.None;
|
---|
[17779] | 18 | public int Complexity => Operator == NonlinearOperator.GT_Hinge || Operator == NonlinearOperator.LT_Hinge ? 3 : 1;
|
---|
[17737] | 19 |
|
---|
[17779] | 20 | public SimpleBasisFunction(string feature, double exponent = 1, NonlinearOperator op = NonlinearOperator.None, bool isNominator = true, double threshold = 0) {
|
---|
[17737] | 21 | Feature = feature ?? throw new ArgumentNullException(nameof(feature));
|
---|
| 22 | Exponent = exponent;
|
---|
| 23 | Operator = op;
|
---|
[17779] | 24 | IsDenominator = isNominator;
|
---|
| 25 | Threshold = threshold;
|
---|
[17737] | 26 | }
|
---|
| 27 |
|
---|
| 28 | public IBasisFunction DeepCopy() {
|
---|
[17779] | 29 | return new SimpleBasisFunction(Feature, Exponent, Operator, IsDenominator, Threshold);
|
---|
[17737] | 30 | }
|
---|
| 31 |
|
---|
| 32 | public override string ToString() {
|
---|
| 33 | var culture = new CultureInfo("en-US");
|
---|
| 34 | string str = $"\"{Feature}\"";
|
---|
| 35 |
|
---|
| 36 | if (HasExponent) {
|
---|
| 37 | if (Exponent.IsAlmost((double)-1 / 2)) str = $"1 / SQRT({str})";
|
---|
| 38 | else if (Exponent.IsAlmost((double)1 / 2)) str = $"SQRT({str})";
|
---|
| 39 | else if (Exponent.IsAlmost(1)) str = $"1 / {str}";
|
---|
| 40 | else if (Exponent.IsAlmost(2)) str = $"SQR({str})";
|
---|
| 41 | else if (Exponent.IsAlmost(3)) str = $"CUBE({str})";
|
---|
| 42 | else str = $"({str}) ^ ({Exponent.ToString(culture)})";
|
---|
| 43 | }
|
---|
| 44 |
|
---|
| 45 | if (!HasOperator) return str;
|
---|
| 46 |
|
---|
| 47 | var thr = Threshold.ToString(culture);
|
---|
[17779] | 48 | if (Operator == NonlinearOperator.LT_Hinge) {
|
---|
[17737] | 49 | var expr = $"{str} {((Threshold >= 0) ? " - " + Math.Abs(Threshold).ToString(culture) : " + " + Math.Abs(Threshold).ToString(culture))}";
|
---|
| 50 | str = $"IF(GT(0, {expr}), 0, {expr})";
|
---|
[17779] | 51 | } else if (Operator == NonlinearOperator.GT_Hinge) {
|
---|
[17737] | 52 | var expr = $"{thr} - {str}";
|
---|
| 53 | str = $"IF(GT(0, {expr}), 0, {expr})";
|
---|
| 54 | } else {
|
---|
| 55 | str = $"{opToStr.GetByFirst(Operator)}({str})";
|
---|
| 56 | }
|
---|
| 57 | return str;
|
---|
| 58 | }
|
---|
| 59 |
|
---|
[17779] | 60 | public double[] Evaluate(IRegressionProblemData data) {
|
---|
[17737] | 61 | var exp = Exponent;
|
---|
| 62 | // exponentVals : e.g. "x3^2"
|
---|
| 63 | var exponentVals = data.Dataset.GetDoubleValues(Feature)
|
---|
| 64 | .Select(val => Math.Pow(val, exp))
|
---|
| 65 | .ToArray();
|
---|
| 66 | // vals: e.g. "log(x3^2)
|
---|
| 67 | var vals = Eval(exponentVals);
|
---|
| 68 |
|
---|
[17779] | 69 | if (!IsDenominator) {
|
---|
[17737] | 70 | var y = data.TargetVariableValues;
|
---|
| 71 | vals = vals.Zip(y, (a, b) => -a * b).ToArray();
|
---|
| 72 | }
|
---|
| 73 | return vals;
|
---|
| 74 | }
|
---|
| 75 |
|
---|
| 76 | private double[] Eval(double[] x) {
|
---|
| 77 | var thr = this.Threshold;
|
---|
| 78 | switch (Operator) {
|
---|
| 79 | case NonlinearOperator.None:
|
---|
| 80 | return x;
|
---|
| 81 | case NonlinearOperator.Abs:
|
---|
| 82 | return x.Select(val => Math.Abs(val)).ToArray();
|
---|
| 83 | case NonlinearOperator.Log:
|
---|
| 84 | return x.Select(val => Math.Log(val)).ToArray();
|
---|
| 85 | case NonlinearOperator.Sin:
|
---|
| 86 | return x.Select(val => Math.Sin(val)).ToArray();
|
---|
| 87 | case NonlinearOperator.Cos:
|
---|
| 88 | return x.Select(val => Math.Cos(val)).ToArray();
|
---|
[17779] | 89 | case NonlinearOperator.GT_Hinge:
|
---|
[17737] | 90 | return x.Select(val => Math.Max(0, thr - val)).ToArray();
|
---|
[17779] | 91 | case NonlinearOperator.LT_Hinge:
|
---|
[17737] | 92 | return x.Select(val => Math.Max(0, val - thr)).ToArray();
|
---|
| 93 | default:
|
---|
| 94 | throw new Exception("Unimplemented operator: " + Operator.ToString());
|
---|
| 95 | }
|
---|
| 96 | }
|
---|
| 97 |
|
---|
| 98 | private static readonly BidirectionalDictionary<NonlinearOperator, string> opToStr = new BidirectionalDictionary<NonlinearOperator, string>() {
|
---|
| 99 | { NonlinearOperator.Abs, "ABS"},
|
---|
| 100 | { NonlinearOperator.Log, "LOG"},
|
---|
| 101 | { NonlinearOperator.Sin, "SIN"},
|
---|
| 102 | { NonlinearOperator.Cos, "COS"}
|
---|
| 103 | };
|
---|
| 104 | }
|
---|
| 105 | }
|
---|