diff --git a/src/Microsoft.ML.Core/Prediction/ITrainer.cs b/src/Microsoft.ML.Core/Prediction/ITrainer.cs index cd8c6d12c8..6e04a30e6f 100644 --- a/src/Microsoft.ML.Core/Prediction/ITrainer.cs +++ b/src/Microsoft.ML.Core/Prediction/ITrainer.cs @@ -74,6 +74,8 @@ public interface IModelCombiner TPredictor CombineModels(IEnumerable models); } + public delegate void SignatureModelCombiner(PredictionKind kind); + /// /// Weakly typed interface for a trainer "session" that produces a predictor. /// diff --git a/src/Microsoft.ML.FastTree/Microsoft.ML.FastTree.csproj b/src/Microsoft.ML.FastTree/Microsoft.ML.FastTree.csproj index 0441b77e48..2a701542f0 100644 --- a/src/Microsoft.ML.FastTree/Microsoft.ML.FastTree.csproj +++ b/src/Microsoft.ML.FastTree/Microsoft.ML.FastTree.csproj @@ -57,6 +57,7 @@ + diff --git a/src/Microsoft.ML.FastTree/TreeEnsemble/RegressionTree.cs b/src/Microsoft.ML.FastTree/TreeEnsemble/RegressionTree.cs index 8e4a9ed7b8..d2edeb796a 100644 --- a/src/Microsoft.ML.FastTree/TreeEnsemble/RegressionTree.cs +++ b/src/Microsoft.ML.FastTree/TreeEnsemble/RegressionTree.cs @@ -105,22 +105,21 @@ public RegressionTree(byte[] buffer, ref int position) LteChild = buffer.ToIntArray(ref position); GtChild = buffer.ToIntArray(ref position); SplitFeatures = buffer.ToIntArray(ref position); - int[] categoricalNodeIndices = buffer.ToIntArray(ref position); - CategoricalSplit = GetCategoricalSplitFromIndices(categoricalNodeIndices); - if (categoricalNodeIndices?.Length > 0) + byte[] categoricalSplitAsBytes = buffer.ToByteArray(ref position); + CategoricalSplit = categoricalSplitAsBytes.Select(b => b > 0).ToArray(); + if (CategoricalSplit.Any(b => b)) { CategoricalSplitFeatures = new int[NumNodes][]; CategoricalSplitFeatureRanges = new int[NumNodes][]; - foreach (var index in categoricalNodeIndices) + for (int index = 0; index < NumNodes; index++) { - Contracts.Assert(CategoricalSplit[index]); - CategoricalSplitFeatures[index] = buffer.ToIntArray(ref position); - CategoricalSplitFeatureRanges[index] = buffer.ToIntArray(ref position, 2); + CategoricalSplitFeatureRanges[index] = buffer.ToIntArray(ref position); } } Thresholds = buffer.ToUIntArray(ref position); + RawThresholds = buffer.ToFloatArray(ref position); _splitGain = buffer.ToDoubleArray(ref position); _gainPValue = buffer.ToDoubleArray(ref position); _previousLeafValue = buffer.ToDoubleArray(ref position); @@ -144,6 +143,23 @@ private bool[] GetCategoricalSplitFromIndices(int[] indices) return categoricalSplit; } + private bool[] GetCategoricalSplitFromBytes(byte[] indices) + { + bool[] categoricalSplit = new bool[NumNodes]; + if (indices == null) + return categoricalSplit; + + Contracts.Assert(indices.Length <= NumNodes); + + foreach (int index in indices) + { + Contracts.Assert(index >= 0 && index < NumNodes); + categoricalSplit[index] = true; + } + + return categoricalSplit; + } + /// /// Create a Regression Tree object from raw tree contents. /// @@ -192,7 +208,7 @@ internal RegressionTree(int[] splitFeatures, Double[] splitGain, Double[] gainPV LeafValues = leafValues; CategoricalSplitFeatures = categoricalSplitFeatures; CategoricalSplitFeatureRanges = new int[CategoricalSplitFeatures.Length][]; - for(int i= 0; i < CategoricalSplitFeatures.Length; ++i) + for (int i = 0; i < CategoricalSplitFeatures.Length; ++i) { if (CategoricalSplitFeatures[i] != null && CategoricalSplitFeatures[i].Length > 0) { @@ -500,6 +516,7 @@ public virtual int SizeInBytes() NumNodes * sizeof(int) + CategoricalSplit.Length * sizeof(bool) + Thresholds.SizeInBytes() + + RawThresholds.SizeInBytes() + _splitGain.SizeInBytes() + _gainPValue.SizeInBytes() + _previousLeafValue.SizeInBytes() + @@ -514,22 +531,22 @@ public virtual void ToByteArray(byte[] buffer, ref int position) LteChild.ToByteArray(buffer, ref position); GtChild.ToByteArray(buffer, ref position); SplitFeatures.ToByteArray(buffer, ref position); + CategoricalSplit.Length.ToByteArray(buffer, ref position); foreach (var split in CategoricalSplit) Convert.ToByte(split).ToByteArray(buffer, ref position); if (CategoricalSplitFeatures != null) { - foreach (var splits in CategoricalSplitFeatures) - splits.ToByteArray(buffer, ref position); - } - - if (CategoricalSplitFeatureRanges != null) - { - foreach (var ranges in CategoricalSplitFeatureRanges) - ranges.ToByteArray(buffer, ref position); + Contracts.AssertValue(CategoricalSplitFeatureRanges); + for (int i = 0; i < CategoricalSplitFeatures.Length; i++) + { + CategoricalSplitFeatures[i].ToByteArray(buffer, ref position); + CategoricalSplitFeatureRanges[i].ToByteArray(buffer, ref position); + } } Thresholds.ToByteArray(buffer, ref position); + RawThresholds.ToByteArray(buffer, ref position); _splitGain.ToByteArray(buffer, ref position); _gainPValue.ToByteArray(buffer, ref position); _previousLeafValue.ToByteArray(buffer, ref position); diff --git a/src/Microsoft.ML.FastTree/TreeEnsemble/TreeEnsembleCombiner.cs b/src/Microsoft.ML.FastTree/TreeEnsemble/TreeEnsembleCombiner.cs new file mode 100644 index 0000000000..a8cce616d1 --- /dev/null +++ b/src/Microsoft.ML.FastTree/TreeEnsemble/TreeEnsembleCombiner.cs @@ -0,0 +1,115 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System.Collections.Generic; +using Microsoft.ML.Runtime; +using Microsoft.ML.Runtime.FastTree.Internal; +using Microsoft.ML.Runtime.Internal.Calibration; + +[assembly: LoadableClass(typeof(TreeEnsembleCombiner), null, typeof(SignatureModelCombiner), "Fast Tree Model Combiner", "FastTreeCombiner")] + +namespace Microsoft.ML.Runtime.FastTree.Internal +{ + public sealed class TreeEnsembleCombiner : IModelCombiner, IPredictorProducing> + { + private readonly IHost _host; + private readonly PredictionKind _kind; + + public TreeEnsembleCombiner(IHostEnvironment env, PredictionKind kind) + { + _host = env.Register("TreeEnsembleCombiner"); + switch (kind) + { + case PredictionKind.BinaryClassification: + case PredictionKind.Regression: + case PredictionKind.Ranking: + _kind = kind; + break; + default: + throw _host.ExceptUserArg(nameof(kind), $"Tree ensembles can be either of type {nameof(PredictionKind.BinaryClassification)}, " + + $"{nameof(PredictionKind.Regression)} or {nameof(PredictionKind.Ranking)}"); + } + } + + public IPredictorProducing CombineModels(IEnumerable> models) + { + _host.CheckValue(models, nameof(models)); + + var ensemble = new Ensemble(); + int modelCount = 0; + int featureCount = -1; + bool binaryClassifier = false; + foreach (var model in models) + { + modelCount++; + + var predictor = model; + _host.CheckValue(predictor, nameof(models), "One of the models is null"); + + var calibrated = predictor as CalibratedPredictorBase; + double paramA = 1; + if (calibrated != null) + { + _host.Check(calibrated.Calibrator is PlattCalibrator, + "Combining FastTree models can only be done when the models are calibrated with Platt calibrator"); + predictor = calibrated.SubPredictor; + paramA = -(calibrated.Calibrator as PlattCalibrator).ParamA; + } + var tree = predictor as FastTreePredictionWrapper; + if (tree == null) + throw _host.Except("Model is not a tree ensemble"); + foreach (var t in tree.TrainedEnsemble.Trees) + { + var bytes = new byte[t.SizeInBytes()]; + int position = -1; + t.ToByteArray(bytes, ref position); + position = -1; + var tNew = new RegressionTree(bytes, ref position); + if (paramA != 1) + { + for (int i = 0; i < tNew.NumLeaves; i++) + tNew.SetOutput(i, tNew.LeafValues[i] * paramA); + } + ensemble.AddTree(tNew); + } + + if (modelCount == 1) + { + binaryClassifier = calibrated != null; + featureCount = tree.InputType.ValueCount; + } + else + { + _host.Check((calibrated != null) == binaryClassifier, "Ensemble contains both calibrated and uncalibrated models"); + _host.Check(featureCount == tree.InputType.ValueCount, "Found models with different number of features"); + } + } + + var scale = 1 / (double)modelCount; + + foreach (var t in ensemble.Trees) + { + for (int i = 0; i < t.NumLeaves; i++) + t.SetOutput(i, t.LeafValues[i] * scale); + } + + switch (_kind) + { + case PredictionKind.BinaryClassification: + if (!binaryClassifier) + return new FastTreeBinaryPredictor(_host, ensemble, featureCount, null); + + var cali = new PlattCalibrator(_host, -1, 0); + return new FeatureWeightsCalibratedPredictor(_host, new FastTreeBinaryPredictor(_host, ensemble, featureCount, null), cali); + case PredictionKind.Regression: + return new FastTreeRegressionPredictor(_host, ensemble, featureCount, null); + case PredictionKind.Ranking: + return new FastTreeRankingPredictor(_host, ensemble, featureCount, null); + default: + _host.Assert(false); + throw _host.ExceptNotSupp(); + } + } + } +} diff --git a/src/Microsoft.ML.FastTree/Utils/ToByteArrayExtensions.cs b/src/Microsoft.ML.FastTree/Utils/ToByteArrayExtensions.cs index e3aa3cce83..5763e3c9dd 100644 --- a/src/Microsoft.ML.FastTree/Utils/ToByteArrayExtensions.cs +++ b/src/Microsoft.ML.FastTree/Utils/ToByteArrayExtensions.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Text; +using Microsoft.ML.Runtime.Internal.Utilities; namespace Microsoft.ML.Runtime.FastTree.Internal { @@ -290,7 +291,7 @@ public static string ToString(this byte[] buffer, ref int position) public static int SizeInBytes(this byte[] a) { - return sizeof(int) + a.Length * sizeof(byte); + return sizeof(int) + Utils.Size(a) * sizeof(byte); } public static void ToByteArray(this byte[] a, byte[] buffer, ref int position) @@ -314,7 +315,7 @@ public static byte[] ToByteArray(this byte[] buffer, ref int position) public static int SizeInBytes(this short[] a) { - return sizeof(int) + a.Length * sizeof(short); + return sizeof(int) + Utils.Size(a) * sizeof(short); } public unsafe static void ToByteArray(this short[] a, byte[] buffer, ref int position) @@ -353,7 +354,7 @@ public unsafe static short[] ToShortArray(this byte[] buffer, ref int position) public static int SizeInBytes(this ushort[] a) { - return sizeof(int) + a.Length * sizeof(ushort); + return sizeof(int) + Utils.Size(a) * sizeof(ushort); } public unsafe static void ToByteArray(this ushort[] a, byte[] buffer, ref int position) @@ -392,12 +393,12 @@ public unsafe static ushort[] ToUShortArray(this byte[] buffer, ref int position public static int SizeInBytes(this int[] array) { - return sizeof(int) + array.Length * sizeof(int); + return sizeof(int) + Utils.Size(array) * sizeof(int); } public unsafe static void ToByteArray(this int[] a, byte[] buffer, ref int position) { - int length = a.Length; + int length = Utils.Size(a); length.ToByteArray(buffer, ref position); fixed (byte* tmpBuffer = buffer) @@ -415,6 +416,9 @@ public unsafe static int[] ToIntArray(this byte[] buffer, ref int position) public unsafe static int[] ToIntArray(this byte[] buffer, ref int position, int length) { + if (length == 0) + return null; + int[] a = new int[length]; fixed (byte* tmpBuffer = buffer) @@ -433,7 +437,7 @@ public unsafe static int[] ToIntArray(this byte[] buffer, ref int position, int public static int SizeInBytes(this uint[] array) { - return sizeof(int) + array.Length * sizeof(uint); + return sizeof(int) + Utils.Size(array) * sizeof(uint); } public unsafe static void ToByteArray(this uint[] a, byte[] buffer, ref int position) @@ -472,7 +476,7 @@ public unsafe static uint[] ToUIntArray(this byte[] buffer, ref int position) public static int SizeInBytes(this long[] array) { - return sizeof(int) + array.Length * sizeof(long); + return sizeof(int) + Utils.Size(array) * sizeof(long); } public unsafe static void ToByteArray(this long[] a, byte[] buffer, ref int position) @@ -511,7 +515,7 @@ public unsafe static long[] ToLongArray(this byte[] buffer, ref int position) public static int SizeInBytes(this ulong[] array) { - return sizeof(int) + array.Length * sizeof(ulong); + return sizeof(int) + Utils.Size(array) * sizeof(ulong); } public unsafe static void ToByteArray(this ulong[] a, byte[] buffer, ref int position) @@ -550,7 +554,7 @@ public unsafe static ulong[] ToULongArray(this byte[] buffer, ref int position) public static int SizeInBytes(this MD5Hash[] array) { - return sizeof(int) + array.Length * MD5Hash.SizeInBytes(); + return sizeof(int) + Utils.Size(array) * MD5Hash.SizeInBytes(); } public static void ToByteArray(this MD5Hash[] a, byte[] buffer, ref int position) @@ -577,7 +581,7 @@ public unsafe static MD5Hash[] ToUInt128Array(this byte[] buffer, ref int positi public static int SizeInBytes(this float[] array) { - return sizeof(int) + array.Length * sizeof(float); + return sizeof(int) + Utils.Size(array) * sizeof(float); } public unsafe static void ToByteArray(this float[] a, byte[] buffer, ref int position) @@ -616,7 +620,7 @@ public unsafe static float[] ToFloatArray(this byte[] buffer, ref int position) public static int SizeInBytes(this double[] array) { - return sizeof(int) + array.Length * sizeof(double); + return sizeof(int) + Utils.Size(array) * sizeof(double); } public unsafe static void ToByteArray(this double[] a, byte[] buffer, ref int position) @@ -655,6 +659,8 @@ public unsafe static double[] ToDoubleArray(this byte[] buffer, ref int position public static int SizeInBytes(this double[][] array) { + if (Utils.Size(array) == 0) + return sizeof(int); return sizeof(int) + array.Sum(x => x.SizeInBytes()); } @@ -683,7 +689,7 @@ public static double[][] ToDoubleJaggedArray(this byte[] buffer, ref int positio public static long SizeInBytes(this string[] array) { long length = sizeof(int); - for (int i = 0; i < array.Length; ++i) + for (int i = 0; i < Utils.Size(array); ++i) { length += array[i].SizeInBytes(); } @@ -692,8 +698,8 @@ public static long SizeInBytes(this string[] array) public static void ToByteArray(this string[] a, byte[] buffer, ref int position) { - a.Length.ToByteArray(buffer, ref position); - for (int i = 0; i < a.Length; ++i) + Utils.Size(a).ToByteArray(buffer, ref position); + for (int i = 0; i < Utils.Size(a); ++i) { a[i].ToByteArray(buffer, ref position); } diff --git a/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs b/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs index 91513e0e74..9bc183cd10 100644 --- a/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs +++ b/test/Microsoft.ML.Predictor.Tests/TestPredictors.cs @@ -11,6 +11,11 @@ namespace Microsoft.ML.Runtime.RunTests { + using Microsoft.ML.Runtime.Data; + using Microsoft.ML.Runtime.EntryPoints; + using Microsoft.ML.Runtime.FastTree; + using Microsoft.ML.Runtime.FastTree.Internal; + using System.Linq; using Xunit; using Xunit.Abstractions; using TestLearners = TestLearnersBase; @@ -382,7 +387,7 @@ public void WeightingFastForestRegressionPredictorsTest() }); Done(); } - + [Fact] [TestCategory("Binary")] [TestCategory("FastTree")] @@ -392,7 +397,7 @@ public void FastTreeBinaryClassificationTest() { var learners = new[] { TestLearners.FastTreeClassfier, TestLearners.FastTreeDropoutClassfier, TestLearners.FastTreeBsrClassfier, TestLearners.FastTreeClassfierDisk }; - var binaryClassificationDatasets = new List { TestDatasets.breastCancerPipe}; + var binaryClassificationDatasets = new List { TestDatasets.breastCancerPipe }; foreach (var learner in learners) { foreach (TestDataset dataset in binaryClassificationDatasets) @@ -402,6 +407,132 @@ public void FastTreeBinaryClassificationTest() Done(); } + + [Fact] + public void TestTreeEnsembleCombiner() + { + var dataPath = GetDataPath("breast-cancer.txt"); + var inputFile = new SimpleFileHandle(Env, dataPath, false, false); +#pragma warning disable 0618 + var dataView = ImportTextData.ImportText(Env, new ImportTextData.Input { InputFile = inputFile }).Data; +#pragma warning restore 0618 + + var fastTrees = new IPredictorModel[3]; + for (int i = 0; i < 3; i++) + { + fastTrees[i] = FastTree.TrainBinary(Env, new FastTreeBinaryClassificationTrainer.Arguments + { + FeatureColumn = "Features", + NumTrees = 5, + NumLeaves = 4, + LabelColumn = DefaultColumnNames.Label, + TrainingData = dataView + }).PredictorModel; + } + CombineAndTestTreeEnsembles(dataView, fastTrees); + } + + [Fact] + public void TestTreeEnsembleCombinerWithCategoricalSplits() + { + var dataPath = GetDataPath("adult.tiny.with-schema.txt"); + var inputFile = new SimpleFileHandle(Env, dataPath, false, false); +#pragma warning disable 0618 + var dataView = ImportTextData.ImportText(Env, new ImportTextData.Input { InputFile = inputFile }).Data; +#pragma warning restore 0618 + + var cat = CategoricalTransform.Create(Env, + new CategoricalTransform.Arguments() + { + Column = new[] + { + new CategoricalTransform.Column() { Name = "Features", Source = "Categories" } + } + }, dataView); + var fastTrees = new IPredictorModel[3]; + for (int i = 0; i < 3; i++) + { + fastTrees[i] = FastTree.TrainBinary(Env, new FastTreeBinaryClassificationTrainer.Arguments + { + FeatureColumn = "Features", + NumTrees = 5, + NumLeaves = 4, + CategoricalSplit = true, + LabelColumn = DefaultColumnNames.Label, + TrainingData = cat + }).PredictorModel; + } + CombineAndTestTreeEnsembles(cat, fastTrees); + } + + private void CombineAndTestTreeEnsembles(IDataView idv, IPredictorModel[] fastTrees) + { + var combiner = new TreeEnsembleCombiner(Env, PredictionKind.BinaryClassification); + + var fastTree = combiner.CombineModels(fastTrees.Select(pm => pm.Predictor as IPredictorProducing)); + + var data = RoleMappedData.Create(idv, RoleMappedSchema.CreatePair(RoleMappedSchema.ColumnRole.Feature, "Features")); + var scored = ScoreModel.Score(Env, new ScoreModel.Input() { Data = idv, PredictorModel = new PredictorModel(Env, data, idv, fastTree) }).ScoredData; + Assert.True(scored.Schema.TryGetColumnIndex("Score", out int scoreCol)); + Assert.True(scored.Schema.TryGetColumnIndex("Probability", out int probCol)); + Assert.True(scored.Schema.TryGetColumnIndex("PredictedLabel", out int predCol)); + + var scoredArray = new IDataView[3]; + var scoreColArray = new int[3]; + var probColArray = new int[3]; + var predColArray = new int[3]; + for (int i = 0; i < 3; i++) + { + scoredArray[i] = ScoreModel.Score(Env, new ScoreModel.Input() { Data = idv, PredictorModel = fastTrees[i] }).ScoredData; + Assert.True(scoredArray[i].Schema.TryGetColumnIndex("Score", out scoreColArray[i])); + Assert.True(scoredArray[i].Schema.TryGetColumnIndex("Probability", out probColArray[i])); + Assert.True(scoredArray[i].Schema.TryGetColumnIndex("PredictedLabel", out predColArray[i])); + } + + var cursors = new IRowCursor[3]; + using (var curs = scored.GetRowCursor(c => c == scoreCol || c == probCol || c == predCol)) + using (cursors[0] = scoredArray[0].GetRowCursor(c => c == scoreColArray[0] || c == probColArray[0] || c == predColArray[0])) + using (cursors[1] = scoredArray[1].GetRowCursor(c => c == scoreColArray[1] || c == probColArray[1] || c == predColArray[1])) + using (cursors[2] = scoredArray[2].GetRowCursor(c => c == scoreColArray[2] || c == probColArray[2] || c == predColArray[2])) + { + var scoreGetter = curs.GetGetter(scoreCol); + var probGetter = curs.GetGetter(probCol); + var predGetter = curs.GetGetter(predCol); + var scoreGetters = new ValueGetter[3]; + var probGetters = new ValueGetter[3]; + var predGetters = new ValueGetter[3]; + for (int i = 0; i < 3; i++) + { + scoreGetters[i] = cursors[i].GetGetter(scoreColArray[i]); + probGetters[i] = cursors[i].GetGetter(probColArray[i]); + predGetters[i] = cursors[i].GetGetter(predColArray[i]); + } + + float score = 0; + float prob = 0; + var pred = default(DvBool); + var scores = new float[3]; + var probs = new float[3]; + var preds = new DvBool[3]; + while (curs.MoveNext()) + { + scoreGetter(ref score); + probGetter(ref prob); + predGetter(ref pred); + for (int i = 0; i < 3; i++) + { + Assert.True(cursors[i].MoveNext()); + scoreGetters[i](ref scores[i]); + probGetters[i](ref probs[i]); + predGetters[i](ref preds[i]); + } + Assert.Equal(score, 0.4 * scores.Sum() / 3, 5); + Assert.Equal(prob, 1 / (1 + Math.Exp(-score)), 6); + Assert.True(pred.IsTrue == score > 0); + } + } + } + [Fact] [TestCategory("Binary")] [TestCategory("FastTree")] @@ -651,7 +782,7 @@ public void RegressorSdcaTest() Done(); } -#region "Regressor" + #region "Regressor" #if OLD_TESTS // REVIEW: Port these tests? /// @@ -962,7 +1093,7 @@ public void RegressorSyntheticDuplicatedOlsTest() } #endif -#endregion + #endregion /// ///A test for FR ranker @@ -1042,7 +1173,7 @@ public IList GetDatasetsForCalibratorTest() public void DefaultCalibratorPerceptronTest() { var datasets = GetDatasetsForCalibratorTest(); - RunAllTests( new[] { TestLearners.perceptronDefault }, datasets, new string[] { "cali={}" }, "nocalibration"); + RunAllTests(new[] { TestLearners.perceptronDefault }, datasets, new string[] { "cali={}" }, "nocalibration"); Done(); } @@ -1054,7 +1185,7 @@ public void DefaultCalibratorPerceptronTest() public void PAVCalibratorPerceptronTest() { var datasets = GetDatasetsForCalibratorTest(); - RunAllTests( new[] { TestLearners.perceptronDefault }, datasets, new[] { "cali=PAV" }, "PAVcalibration"); + RunAllTests(new[] { TestLearners.perceptronDefault }, datasets, new[] { "cali=PAV" }, "PAVcalibration"); Done(); } @@ -1066,7 +1197,7 @@ public void PAVCalibratorPerceptronTest() public void RandomCalibratorPerceptronTest() { var datasets = GetDatasetsForCalibratorTest(); - RunAllTests( new[] { TestLearners.perceptronDefault }, datasets, new string[] { "numcali=200" }, "calibrateRandom"); + RunAllTests(new[] { TestLearners.perceptronDefault }, datasets, new string[] { "numcali=200" }, "calibrateRandom"); Done(); } @@ -1078,7 +1209,7 @@ public void RandomCalibratorPerceptronTest() public void NoCalibratorLinearSvmTest() { var datasets = GetDatasetsForCalibratorTest(); - RunAllTests( new[] { TestLearners.linearSVM }, datasets, new string[] { "cali={}" }, "nocalibration"); + RunAllTests(new[] { TestLearners.linearSVM }, datasets, new string[] { "cali={}" }, "nocalibration"); Done(); } @@ -1090,7 +1221,7 @@ public void NoCalibratorLinearSvmTest() public void PAVCalibratorLinearSvmTest() { var datasets = GetDatasetsForCalibratorTest(); - RunAllTests( new[] { TestLearners.linearSVM }, datasets, new string[] { "cali=PAV" }, "PAVcalibration"); + RunAllTests(new[] { TestLearners.linearSVM }, datasets, new string[] { "cali=PAV" }, "PAVcalibration"); Done(); }