From 7ce50b35925af9fa0d2fd81da7ed0b9d17367e6b Mon Sep 17 00:00:00 2001 From: LittleLittleCloud Date: Mon, 2 Mar 2020 15:07:38 -0800 Subject: [PATCH 1/5] add GenerateSampleData in util --- src/Microsoft.ML.CodeGenerator/Utils.cs | 49 +++++++++++++++++++ .../Microsoft.ML.CodeGenerator.Tests.csproj | 1 + 2 files changed, 50 insertions(+) diff --git a/src/Microsoft.ML.CodeGenerator/Utils.cs b/src/Microsoft.ML.CodeGenerator/Utils.cs index 0eb75c8585..2ef42c3757 100644 --- a/src/Microsoft.ML.CodeGenerator/Utils.cs +++ b/src/Microsoft.ML.CodeGenerator/Utils.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -23,6 +24,54 @@ internal static string Sanitize(string name) return string.Join("", name.Select(x => Char.IsLetterOrDigit(x) ? x : '_')); } + internal static IDictionary GenerateSampleData(string inputFile, ColumnInferenceResults columnInference) + { + var mlContext = new MLContext(); + var textLoader = mlContext.Data.CreateTextLoader(columnInference.TextLoaderOptions); + var trainData = textLoader.Load(inputFile); + return Utils.GenerateSampleData(trainData, columnInference); + } + + internal static IDictionary GenerateSampleData(IDataView dataView, ColumnInferenceResults columnInference) + { + var featureColumns = dataView.Schema.AsEnumerable().Where(col => col.Name != columnInference.ColumnInformation.LabelColumnName); + var rowCursor = dataView.GetRowCursor(featureColumns); + + var sampleData = featureColumns.Select(column => new { key = Utils.Normalize(column.Name), val = "null" }).ToDictionary(x => x.key, x => x.val); + if (rowCursor.MoveNext()) + { + var getGetGetterMethod = typeof(Utils).GetMethod(nameof(Utils.GetValueFromColumn), BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + + foreach (var column in featureColumns) + { + var getGeneraicGetGetterMethod = getGetGetterMethod.MakeGenericMethod(column.Type.RawType); + string val = getGeneraicGetGetterMethod.Invoke(null, new object[] { rowCursor, column }) as string; + sampleData[Utils.Normalize(column.Name)] = val; + } + } + + return sampleData; + } + + internal static string GetValueFromColumn(DataViewRowCursor rowCursor, DataViewSchema.Column column) + { + T val = default; + var getter = rowCursor.GetGetter(column); + getter(ref val); + + // wrap string in quotes + if (typeof(T) == typeof(ReadOnlyMemory)) + { + return $"\"{val.ToString()}\""; + } + + if (val is null) + { + return "\"null\""; + } + return val.ToString(); + } + internal static string Normalize(string input) { //check if first character is int diff --git a/test/Microsoft.ML.CodeGenerator.Tests/Microsoft.ML.CodeGenerator.Tests.csproj b/test/Microsoft.ML.CodeGenerator.Tests/Microsoft.ML.CodeGenerator.Tests.csproj index bd4611427b..84df70b8a9 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/Microsoft.ML.CodeGenerator.Tests.csproj +++ b/test/Microsoft.ML.CodeGenerator.Tests/Microsoft.ML.CodeGenerator.Tests.csproj @@ -7,6 +7,7 @@ + From fa15f65f8417d3fd6d482614969c1aa1c82c50df Mon Sep 17 00:00:00 2001 From: LittleLittleCloud Date: Mon, 2 Mar 2020 16:13:36 -0800 Subject: [PATCH 2/5] add test --- .../RecommendationTrainerExtensions.cs | 1 + .../AzureAttachConsoleAppCodeGenerator.cs | 4 ++ .../CodeGenerator/CSharp/CodeGenerator.cs | 2 + .../Templates/Console/PredictProgram.cs | 66 ++++++------------- .../Templates/Console/PredictProgram.tt | 38 ++++------- src/Microsoft.ML.CodeGenerator/Utils.cs | 17 +++-- ...eCodeGeneratorTest.Program.cs.approved.txt | 26 +------- ...eCodeGeneratorTest.Program.cs.approved.txt | 26 +------- ...leAppProgramCSFileContentTest.approved.txt | 26 +------- ...ctionProgramCSFileContentTest.approved.txt | 26 +------- ...Contents_VerifyPredictProgram.approved.txt | 26 +------- ...PredictProgram_WithSampleData.approved.txt | 34 ++++++++++ .../ApprovalTests/TemplateTest.cs | 41 ++++++++++++ .../UtilTest.cs | 51 ++++++++++++++ 14 files changed, 183 insertions(+), 201 deletions(-) create mode 100644 test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt create mode 100644 test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs create mode 100644 test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs diff --git a/src/Microsoft.ML.AutoML/TrainerExtensions/RecommendationTrainerExtensions.cs b/src/Microsoft.ML.AutoML/TrainerExtensions/RecommendationTrainerExtensions.cs index 032790d96e..fa59a84594 100644 --- a/src/Microsoft.ML.AutoML/TrainerExtensions/RecommendationTrainerExtensions.cs +++ b/src/Microsoft.ML.AutoML/TrainerExtensions/RecommendationTrainerExtensions.cs @@ -18,6 +18,7 @@ public ITrainerEsitmator CreateInstance(MLContext mlContext, IEnumerable str.Name != _settings.LabelName).Select((str) => str.Name).ToList(); + var sampleResult = Utils.GenerateSampleData(_settings.TrainDataset, _columnInferenceResult); PredictProgram = new CSharpCodeFile() { File = new PredictProgram() @@ -94,6 +97,7 @@ public AzureAttachConsoleAppCodeGenerator(Pipeline pipeline, ColumnInferenceResu Separator = _columnInferenceResult.TextLoaderOptions.Separators.FirstOrDefault(), Target = _settings.Target, Features = featuresList, + SampleData = sampleResult, }.TransformText(), Name = "Program.cs", }; diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs index e8f488985e..8f9642c4b9 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs @@ -434,6 +434,7 @@ private string GeneratePredictProgramCSFileContent(string namespaceValue) { var columns = _columnInferenceResult.TextLoaderOptions.Columns; var featuresList = columns.Where((str) => str.Name != _settings.LabelName).Select((str) => str.Name).ToList(); + var sampleData = Utils.GenerateSampleData(_settings.TrainDataset, _columnInferenceResult); PredictProgram predictProgram = new PredictProgram() { TaskType = _settings.MlTask.ToString(), @@ -447,6 +448,7 @@ private string GeneratePredictProgramCSFileContent(string namespaceValue) AllowSparse = _columnInferenceResult.TextLoaderOptions.AllowSparse, Features = featuresList, Target = _settings.Target, + SampleData = sampleData, }; return predictProgram.TransformText(); } diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs index 640f0cb201..27e1200398 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs @@ -48,16 +48,24 @@ public virtual string TransformText() this.Write(this.ToStringHelper.ToStringWithCulture(TestDataPath)); this.Write("\";\r\n"); } - this.Write(@" - static void Main(string[] args) - { - // Create single instance of sample data from first line of dataset for model input - ModelInput sampleData = CreateSingleDataSample(DATA_FILEPATH); - - // Make a single prediction on the sample data and print results - var predictionResult = ConsumeModel.Predict(sampleData); - - Console.WriteLine(""Using model to make single prediction -- Comparing actual "); + this.Write("\r\n static void Main(string[] args)\r\n {\r\n // Create singl" + + "e instance of sample data from first line of dataset for model input\r\n"); + if(SampleData != null) { + this.Write(" ModelInput sampleData = new ModelInput()\r\n {\r\n"); + foreach(var kv in SampleData){ + this.Write(" "); + this.Write(this.ToStringHelper.ToStringWithCulture(kv.Key)); + this.Write("="); + this.Write(this.ToStringHelper.ToStringWithCulture(kv.Value)); + this.Write(",\r\n"); +} + this.Write(" };\r\n"); +}else{ + this.Write(" ModelInput sampleData = new ModelInput();\r\n"); +} + this.Write("\r\n\t\t\t// Make a single prediction on the sample data and print results\r\n\t\t\tvar pre" + + "dictionResult = ConsumeModel.Predict(sampleData);\r\n\r\n\t\t\tConsole.WriteLine(\"Using" + + " model to make single prediction -- Comparing actual "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(" with predicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); @@ -96,41 +104,8 @@ static void Main(string[] args) this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(" scores: [{String.Join(\",\", predictionResult.Score)}]\\n\\n\");\r\n"); } - this.Write(@" Console.WriteLine(""=============== End of process, hit any key to finish ===============""); - Console.ReadKey(); - } - - // Change this code to create your own sample data - #region CreateSingleDataSample - // Method to load single row of dataset to try a single prediction - private static ModelInput CreateSingleDataSample(string dataFilePath) - { - // Create MLContext - MLContext mlContext = new MLContext(); - - // Load dataset - IDataView dataView = mlContext.Data.LoadFromTextFile( - path: dataFilePath, - hasHeader : "); - this.Write(this.ToStringHelper.ToStringWithCulture(HasHeader.ToString().ToLowerInvariant())); - this.Write(",\r\n separatorChar : \'"); - this.Write(this.ToStringHelper.ToStringWithCulture(Regex.Escape(Separator.ToString()))); - this.Write("\',\r\n allowQuoting : "); - this.Write(this.ToStringHelper.ToStringWithCulture(AllowQuoting.ToString().ToLowerInvariant())); - this.Write(",\r\n allowSparse: "); - this.Write(this.ToStringHelper.ToStringWithCulture(AllowSparse.ToString().ToLowerInvariant())); - this.Write(@"); - - // Use first line of dataset as model input - // You can replace this with new test data (hardcoded or from end-user application) - ModelInput sampleForPrediction = mlContext.Data.CreateEnumerable(dataView, false) - .First(); - return sampleForPrediction; - } - #endregion - } -} -"); + this.Write(" Console.WriteLine(\"=============== End of process, hit any key to fin" + + "ish ===============\");\r\n Console.ReadKey();\r\n }\r\n }\r\n}\r\n"); return this.GenerationEnvironment.ToString(); } @@ -145,6 +120,7 @@ private static ModelInput CreateSingleDataSample(string dataFilePath) public bool HasHeader {get;set;} public IList Features {get;set;} internal CSharp.GenerateTarget Target {get;set;} +public IDictionary SampleData {get;set;} void CLI_Annotation() diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt index 5d01aaf401..3dc01b8d94 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt @@ -30,8 +30,17 @@ namespace <#= Namespace #>.ConsoleApp static void Main(string[] args) { - // Create single instance of sample data from first line of dataset for model input - ModelInput sampleData = CreateSingleDataSample(DATA_FILEPATH); + // Create single instance of sample data from first line of dataset for model input +<# if(SampleData != null) {#> + ModelInput sampleData = new ModelInput() + { +<# foreach(var kv in SampleData){ #> + <#= kv.Key #>=<#= kv.Value #>, +<#}#> + }; +<#}else{#> + ModelInput sampleData = new ModelInput(); +<#}#> // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); @@ -50,30 +59,6 @@ namespace <#= Namespace #>.ConsoleApp Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } - - // Change this code to create your own sample data - #region CreateSingleDataSample - // Method to load single row of dataset to try a single prediction - private static ModelInput CreateSingleDataSample(string dataFilePath) - { - // Create MLContext - MLContext mlContext = new MLContext(); - - // Load dataset - IDataView dataView = mlContext.Data.LoadFromTextFile( - path: dataFilePath, - hasHeader : <#= HasHeader.ToString().ToLowerInvariant() #>, - separatorChar : '<#= Regex.Escape(Separator.ToString()) #>', - allowQuoting : <#= AllowQuoting.ToString().ToLowerInvariant() #>, - allowSparse: <#= AllowSparse.ToString().ToLowerInvariant() #>); - - // Use first line of dataset as model input - // You can replace this with new test data (hardcoded or from end-user application) - ModelInput sampleForPrediction = mlContext.Data.CreateEnumerable(dataView, false) - .First(); - return sampleForPrediction; - } - #endregion } } <#+ @@ -88,4 +73,5 @@ public bool AllowSparse {get;set;} public bool HasHeader {get;set;} public IList Features {get;set;} internal CSharp.GenerateTarget Target {get;set;} +public IDictionary SampleData {get;set;} #> diff --git a/src/Microsoft.ML.CodeGenerator/Utils.cs b/src/Microsoft.ML.CodeGenerator/Utils.cs index 2ef42c3757..968bb289a0 100644 --- a/src/Microsoft.ML.CodeGenerator/Utils.cs +++ b/src/Microsoft.ML.CodeGenerator/Utils.cs @@ -26,10 +26,17 @@ internal static string Sanitize(string name) internal static IDictionary GenerateSampleData(string inputFile, ColumnInferenceResults columnInference) { - var mlContext = new MLContext(); - var textLoader = mlContext.Data.CreateTextLoader(columnInference.TextLoaderOptions); - var trainData = textLoader.Load(inputFile); - return Utils.GenerateSampleData(trainData, columnInference); + try + { + var mlContext = new MLContext(); + var textLoader = mlContext.Data.CreateTextLoader(columnInference.TextLoaderOptions); + var trainData = textLoader.Load(inputFile); + return Utils.GenerateSampleData(trainData, columnInference); + } + catch (Exception) + { + return null; + } } internal static IDictionary GenerateSampleData(IDataView dataView, ColumnInferenceResults columnInference) @@ -62,7 +69,7 @@ internal static string GetValueFromColumn(DataViewRowCursor rowCursor, DataVi // wrap string in quotes if (typeof(T) == typeof(ReadOnlyMemory)) { - return $"\"{val.ToString()}\""; + return $"\"{val.ToString().Replace("\"", "\\\"")}\""; } if (val is null) diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt index 0e3110cc17..2a883b7d6f 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt @@ -16,7 +16,7 @@ namespace Test.ConsoleApp static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input - ModelInput sampleData = CreateSingleDataSample(DATA_FILEPATH); + ModelInput sampleData = new ModelInput(); // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); @@ -40,29 +40,5 @@ namespace Test.ConsoleApp Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } - - // Change this code to create your own sample data - #region CreateSingleDataSample - // Method to load single row of dataset to try a single prediction - private static ModelInput CreateSingleDataSample(string dataFilePath) - { - // Create MLContext - MLContext mlContext = new MLContext(); - - // Load dataset - IDataView dataView = mlContext.Data.LoadFromTextFile( - path: dataFilePath, - hasHeader: true, - separatorChar: ',', - allowQuoting: true, - allowSparse: true); - - // Use first line of dataset as model input - // You can replace this with new test data (hardcoded or from end-user application) - ModelInput sampleForPrediction = mlContext.Data.CreateEnumerable(dataView, false) - .First(); - return sampleForPrediction; - } - #endregion } } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt index de7b111934..f79e5ae5f4 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt @@ -16,7 +16,7 @@ namespace CodeGenTest.ConsoleApp static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input - ModelInput sampleData = CreateSingleDataSample(DATA_FILEPATH); + ModelInput sampleData = new ModelInput(); // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); @@ -27,29 +27,5 @@ namespace CodeGenTest.ConsoleApp Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } - - // Change this code to create your own sample data - #region CreateSingleDataSample - // Method to load single row of dataset to try a single prediction - private static ModelInput CreateSingleDataSample(string dataFilePath) - { - // Create MLContext - MLContext mlContext = new MLContext(); - - // Load dataset - IDataView dataView = mlContext.Data.LoadFromTextFile( - path: dataFilePath, - hasHeader: true, - separatorChar: '\t', - allowQuoting: true, - allowSparse: true); - - // Use first line of dataset as model input - // You can replace this with new test data (hardcoded or from end-user application) - ModelInput sampleForPrediction = mlContext.Data.CreateEnumerable(dataView, false) - .First(); - return sampleForPrediction; - } - #endregion } } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt index 994e835f0d..9213ae7cd8 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt @@ -20,7 +20,7 @@ namespace TestNamespace.ConsoleApp static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input - ModelInput sampleData = CreateSingleDataSample(DATA_FILEPATH); + ModelInput sampleData = new ModelInput(); // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); @@ -35,29 +35,5 @@ namespace TestNamespace.ConsoleApp Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } - - // Change this code to create your own sample data - #region CreateSingleDataSample - // Method to load single row of dataset to try a single prediction - private static ModelInput CreateSingleDataSample(string dataFilePath) - { - // Create MLContext - MLContext mlContext = new MLContext(); - - // Load dataset - IDataView dataView = mlContext.Data.LoadFromTextFile( - path: dataFilePath, - hasHeader: true, - separatorChar: ',', - allowQuoting: true, - allowSparse: true); - - // Use first line of dataset as model input - // You can replace this with new test data (hardcoded or from end-user application) - ModelInput sampleForPrediction = mlContext.Data.CreateEnumerable(dataView, false) - .First(); - return sampleForPrediction; - } - #endregion } } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt index 994e835f0d..9213ae7cd8 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt @@ -20,7 +20,7 @@ namespace TestNamespace.ConsoleApp static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input - ModelInput sampleData = CreateSingleDataSample(DATA_FILEPATH); + ModelInput sampleData = new ModelInput(); // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); @@ -35,29 +35,5 @@ namespace TestNamespace.ConsoleApp Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } - - // Change this code to create your own sample data - #region CreateSingleDataSample - // Method to load single row of dataset to try a single prediction - private static ModelInput CreateSingleDataSample(string dataFilePath) - { - // Create MLContext - MLContext mlContext = new MLContext(); - - // Load dataset - IDataView dataView = mlContext.Data.LoadFromTextFile( - path: dataFilePath, - hasHeader: true, - separatorChar: ',', - allowQuoting: true, - allowSparse: true); - - // Use first line of dataset as model input - // You can replace this with new test data (hardcoded or from end-user application) - ModelInput sampleForPrediction = mlContext.Data.CreateEnumerable(dataView, false) - .First(); - return sampleForPrediction; - } - #endregion } } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt index e1c883ff06..7978c609a9 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt @@ -20,7 +20,7 @@ namespace TestNamespace.ConsoleApp static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input - ModelInput sampleData = CreateSingleDataSample(DATA_FILEPATH); + ModelInput sampleData = new ModelInput(); // Make a single prediction on the sample data and print results var predictionResult = ConsumeModel.Predict(sampleData); @@ -32,29 +32,5 @@ namespace TestNamespace.ConsoleApp Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } - - // Change this code to create your own sample data - #region CreateSingleDataSample - // Method to load single row of dataset to try a single prediction - private static ModelInput CreateSingleDataSample(string dataFilePath) - { - // Create MLContext - MLContext mlContext = new MLContext(); - - // Load dataset - IDataView dataView = mlContext.Data.LoadFromTextFile( - path: dataFilePath, - hasHeader: true, - separatorChar: ',', - allowQuoting: true, - allowSparse: true); - - // Use first line of dataset as model input - // You can replace this with new test data (hardcoded or from end-user application) - ModelInput sampleForPrediction = mlContext.Data.CreateEnumerable(dataView, false) - .First(); - return sampleForPrediction; - } - #endregion } } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt new file mode 100644 index 0000000000..f228f33e96 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt @@ -0,0 +1,34 @@ +// This file was auto-generated by ML.NET Model Builder. + +using System; +using System.IO; +using System.Linq; +using Microsoft.ML; +using Namespace.Model; + +namespace Namespace.ConsoleApp +{ + class Program + { + //Dataset to use for predictions + private const string DATA_FILEPATH = @"/path"; + + static void Main(string[] args) + { + // Create single instance of sample data from first line of dataset for model input + ModelInput sampleData = new ModelInput() + { + key1="key1", + key2="key2", + key3="key\"3", + }; + + // Make a single prediction on the sample data and print results + var predictionResult = ConsumeModel.Predict(sampleData); + + Console.WriteLine("Using model to make single prediction -- Comparing actual LabelName with predicted LabelName from sample data...\n\n"); + Console.WriteLine("=============== End of process, hit any key to finish ==============="); + Console.ReadKey(); + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs new file mode 100644 index 0000000000..406e7f1fa7 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ApprovalTests; +using ApprovalTests.Reporters; +using Microsoft.ML.CodeGenerator.Templates.Console; +using Microsoft.ML.TestFramework; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.ML.CodeGenerator.Tests +{ + public class TemplateTest : BaseTestClass + { + public TemplateTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + [UseReporter(typeof(DiffReporter))] + public void TestPredictProgram_WithSampleData() + { + var predictProgram = new PredictProgram() + { + SampleData = new Dictionary() + { + { "key1", "\"key1\"" }, + { "key2", "\"key2\"" }, + { "key3", "\"key\\\"3\"" }, + }, + TaskType = "null", + Features = new List(), + Namespace = "Namespace", + LabelName = "LabelName", + TrainDataPath = "/path", + Separator = ',' + }; + Approvals.Verify(predictProgram.TransformText()); + } + } +} diff --git a/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs b/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs new file mode 100644 index 0000000000..ac26a64f33 --- /dev/null +++ b/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.ML; +using Microsoft.ML.AutoML; +using Microsoft.ML.CodeGenerator.Utilities; +using Microsoft.ML.TestFramework; +using Xunit; +using Xunit.Abstractions; + +namespace Microsoft.ML.CodeGenerator.Tests +{ + public class UtilTest : BaseTestClass + { + public UtilTest(ITestOutputHelper output) : base(output) + { + } + + [Fact] + public void TestGenerateSampleData() + { + var data = new[] + { + new + { + Label = "label1", + STR = "feature1", + INT = 2, + DOUBLE = 1.2, + TrickySTR = "ab\"\';@#$%^&-++==", + } + }; + + var context = new MLContext(); + var dataView = context.Data.LoadFromEnumerable(data); + var columnInference = new ColumnInferenceResults() + { + ColumnInformation = new ColumnInformation() + { + LabelColumnName = "Label" + } + }; + + var sampleData = Utils.GenerateSampleData(dataView, columnInference); + Assert.Equal("\"feature1\"", sampleData["STR"]); + Assert.Equal("2", sampleData["INT"]); + Assert.Equal("1.2", sampleData["DOUBLE"]); + Assert.Equal("\"ab\\\"\';@#$%^&-++==\"", sampleData["TrickySTR"]); + } + } +} From 3c62092dfb15379375d38441012e2700d08173e6 Mon Sep 17 00:00:00 2001 From: LittleLittleCloud Date: Mon, 2 Mar 2020 16:37:54 -0800 Subject: [PATCH 3/5] fix some bugs --- .../AzureAttachConsoleAppCodeGenerator.cs | 2 -- .../CodeGenerator/CSharp/CodeGenerator.cs | 2 -- .../Templates/Console/PredictProgram.cs | 21 ++++--------------- .../Templates/Console/PredictProgram.tt | 12 ----------- src/Microsoft.ML.CodeGenerator/Utils.cs | 8 ++++++- ...eCodeGeneratorTest.Program.cs.approved.txt | 6 ------ ...eCodeGeneratorTest.Program.cs.approved.txt | 6 ------ ...leAppProgramCSFileContentTest.approved.txt | 6 ------ ...ctionProgramCSFileContentTest.approved.txt | 6 ------ ...Contents_VerifyPredictProgram.approved.txt | 6 ------ ...PredictProgram_WithSampleData.approved.txt | 6 ------ .../ApprovalTests/TemplateTest.cs | 1 - .../UtilTest.cs | 2 ++ 13 files changed, 13 insertions(+), 71 deletions(-) diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs index b6a78a8931..4b65641b36 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/AzureCodeGenerator/AzureAttachConsoleAppCodeGenerator.cs @@ -89,8 +89,6 @@ public AzureAttachConsoleAppCodeGenerator(Pipeline pipeline, ColumnInferenceResu TaskType = _settings.MlTask.ToString(), LabelName = _settings.LabelName, Namespace = _nameSpaceValue, - TestDataPath = _settings.TestDataset, - TrainDataPath = _settings.TrainDataset, AllowQuoting = _columnInferenceResult.TextLoaderOptions.AllowQuoting, AllowSparse = _columnInferenceResult.TextLoaderOptions.AllowSparse, HasHeader = _columnInferenceResult.TextLoaderOptions.HasHeader, diff --git a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs index 8f9642c4b9..796bdd6e44 100644 --- a/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs +++ b/src/Microsoft.ML.CodeGenerator/CodeGenerator/CSharp/CodeGenerator.cs @@ -440,8 +440,6 @@ private string GeneratePredictProgramCSFileContent(string namespaceValue) TaskType = _settings.MlTask.ToString(), LabelName = _settings.LabelName, Namespace = namespaceValue, - TestDataPath = _settings.TestDataset, - TrainDataPath = _settings.TrainDataset, HasHeader = _columnInferenceResult.TextLoaderOptions.HasHeader, Separator = _columnInferenceResult.TextLoaderOptions.Separators.FirstOrDefault(), AllowQuoting = _columnInferenceResult.TextLoaderOptions.AllowQuoting, diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs index 27e1200398..9dfe6ef1db 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs @@ -32,24 +32,13 @@ public virtual string TransformText() } else if(Target == CSharp.GenerateTarget.ModelBuilder){ MB_Annotation(); } - this.Write("\r\nusing System;\r\nusing System.IO;\r\nusing System.Linq;\r\nusing Microsoft.ML;\r\nusing" + - " "); + this.Write("\r\nusing System;\r\nusing "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write(".Model;\r\n\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); - this.Write(".ConsoleApp\r\n{\r\n class Program\r\n {\r\n //Dataset to use for prediction" + - "s \r\n"); -if(string.IsNullOrEmpty(TestDataPath)){ - this.Write(" private const string DATA_FILEPATH = @\""); - this.Write(this.ToStringHelper.ToStringWithCulture(TrainDataPath)); - this.Write("\";\r\n"); - } else{ - this.Write(" private const string DATA_FILEPATH = @\""); - this.Write(this.ToStringHelper.ToStringWithCulture(TestDataPath)); - this.Write("\";\r\n"); - } - this.Write("\r\n static void Main(string[] args)\r\n {\r\n // Create singl" + - "e instance of sample data from first line of dataset for model input\r\n"); + this.Write(".ConsoleApp\r\n{\r\n class Program\r\n {\r\n static void Main(string[] args)" + + "\r\n {\r\n // Create single instance of sample data from first lin" + + "e of dataset for model input\r\n"); if(SampleData != null) { this.Write(" ModelInput sampleData = new ModelInput()\r\n {\r\n"); foreach(var kv in SampleData){ @@ -112,8 +101,6 @@ public virtual string TransformText() public string TaskType {get;set;} public string Namespace {get;set;} public string LabelName {get;set;} -public string TestDataPath {get;set;} -public string TrainDataPath {get;set;} public char Separator {get;set;} public bool AllowQuoting {get;set;} public bool AllowSparse {get;set;} diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt index 3dc01b8d94..4f7a1f220c 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt @@ -12,22 +12,12 @@ <# } #> using System; -using System.IO; -using System.Linq; -using Microsoft.ML; using <#= Namespace #>.Model; namespace <#= Namespace #>.ConsoleApp { class Program { - //Dataset to use for predictions -<#if(string.IsNullOrEmpty(TestDataPath)){ #> - private const string DATA_FILEPATH = @"<#= TrainDataPath #>"; -<# } else{ #> - private const string DATA_FILEPATH = @"<#= TestDataPath #>"; -<# } #> - static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input @@ -65,8 +55,6 @@ namespace <#= Namespace #>.ConsoleApp public string TaskType {get;set;} public string Namespace {get;set;} public string LabelName {get;set;} -public string TestDataPath {get;set;} -public string TrainDataPath {get;set;} public char Separator {get;set;} public bool AllowQuoting {get;set;} public bool AllowSparse {get;set;} diff --git a/src/Microsoft.ML.CodeGenerator/Utils.cs b/src/Microsoft.ML.CodeGenerator/Utils.cs index 968bb289a0..cc391cd6c2 100644 --- a/src/Microsoft.ML.CodeGenerator/Utils.cs +++ b/src/Microsoft.ML.CodeGenerator/Utils.cs @@ -41,7 +41,7 @@ internal static IDictionary GenerateSampleData(string inputFile, internal static IDictionary GenerateSampleData(IDataView dataView, ColumnInferenceResults columnInference) { - var featureColumns = dataView.Schema.AsEnumerable().Where(col => col.Name != columnInference.ColumnInformation.LabelColumnName); + var featureColumns = dataView.Schema.AsEnumerable().Where(col => col.Name != columnInference.ColumnInformation.LabelColumnName && !columnInference.ColumnInformation.IgnoredColumnNames.Contains(col.Name)); var rowCursor = dataView.GetRowCursor(featureColumns); var sampleData = featureColumns.Select(column => new { key = Utils.Normalize(column.Name), val = "null" }).ToDictionary(x => x.key, x => x.val); @@ -76,6 +76,12 @@ internal static string GetValueFromColumn(DataViewRowCursor rowCursor, DataVi { return "\"null\""; } + + if (val is float) + { + return val.ToString() + "F"; + } + return val.ToString(); } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt index 2a883b7d6f..d7f5de8580 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt @@ -1,18 +1,12 @@ // This file was auto-generated by ML.NET Model Builder. using System; -using System.IO; -using System.Linq; -using Microsoft.ML; using Test.Model; namespace Test.ConsoleApp { class Program { - //Dataset to use for predictions - private const string DATA_FILEPATH = @"\path\to\file"; - static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt index f79e5ae5f4..29332171e6 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt @@ -1,18 +1,12 @@ // This file was auto-generated by ML.NET Model Builder. using System; -using System.IO; -using System.Linq; -using Microsoft.ML; using CodeGenTest.Model; namespace CodeGenTest.ConsoleApp { class Program { - //Dataset to use for predictions - private const string DATA_FILEPATH = @"/path/to/dataset"; - static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt index 9213ae7cd8..68244d69d8 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt @@ -5,18 +5,12 @@ //***************************************************************************************** using System; -using System.IO; -using System.Linq; -using Microsoft.ML; using TestNamespace.Model; namespace TestNamespace.ConsoleApp { class Program { - //Dataset to use for predictions - private const string DATA_FILEPATH = @"x:\dummypath\dummy_test.csv"; - static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt index 9213ae7cd8..68244d69d8 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt @@ -5,18 +5,12 @@ //***************************************************************************************** using System; -using System.IO; -using System.Linq; -using Microsoft.ML; using TestNamespace.Model; namespace TestNamespace.ConsoleApp { class Program { - //Dataset to use for predictions - private const string DATA_FILEPATH = @"x:\dummypath\dummy_test.csv"; - static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt index 7978c609a9..51515a3760 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt @@ -5,18 +5,12 @@ //***************************************************************************************** using System; -using System.IO; -using System.Linq; -using Microsoft.ML; using TestNamespace.Model; namespace TestNamespace.ConsoleApp { class Program { - //Dataset to use for predictions - private const string DATA_FILEPATH = @"x:\dummypath\dummy_test.csv"; - static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt index f228f33e96..2fd922ee54 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.TestPredictProgram_WithSampleData.approved.txt @@ -1,18 +1,12 @@ // This file was auto-generated by ML.NET Model Builder. using System; -using System.IO; -using System.Linq; -using Microsoft.ML; using Namespace.Model; namespace Namespace.ConsoleApp { class Program { - //Dataset to use for predictions - private const string DATA_FILEPATH = @"/path"; - static void Main(string[] args) { // Create single instance of sample data from first line of dataset for model input diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs index 406e7f1fa7..7cb2f26e71 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs @@ -32,7 +32,6 @@ public void TestPredictProgram_WithSampleData() Features = new List(), Namespace = "Namespace", LabelName = "LabelName", - TrainDataPath = "/path", Separator = ',' }; Approvals.Verify(predictProgram.TransformText()); diff --git a/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs b/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs index ac26a64f33..827c87497d 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs +++ b/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs @@ -27,6 +27,7 @@ public void TestGenerateSampleData() STR = "feature1", INT = 2, DOUBLE = 1.2, + FLOAT = 1.223E+10F, TrickySTR = "ab\"\';@#$%^&-++==", } }; @@ -45,6 +46,7 @@ public void TestGenerateSampleData() Assert.Equal("\"feature1\"", sampleData["STR"]); Assert.Equal("2", sampleData["INT"]); Assert.Equal("1.2", sampleData["DOUBLE"]); + Assert.Equal("1.223E+10F", sampleData["FLOAT"]); Assert.Equal("\"ab\\\"\';@#$%^&-++==\"", sampleData["TrickySTR"]); } } From 8b3620b2e439b8b4b6259925013981bfdf70c6e1 Mon Sep 17 00:00:00 2001 From: LittleLittleCloud Date: Tue, 3 Mar 2020 11:58:08 -0800 Subject: [PATCH 4/5] fix bugs, and add more tests --- .../Templates/Console/PredictProgram.cs | 18 +-- .../Templates/Console/PredictProgram.tt | 6 +- src/Microsoft.ML.CodeGenerator/Utils.cs | 30 ++++- ...eCodeGeneratorTest.Program.cs.approved.txt | 2 +- ...eCodeGeneratorTest.Program.cs.approved.txt | 2 +- ...leAppProgramCSFileContentTest.approved.txt | 2 +- ...ctionProgramCSFileContentTest.approved.txt | 2 +- ...Contents_VerifyPredictProgram.approved.txt | 2 +- .../CodeGenTests.cs | 12 +- .../UtilTest.cs | 113 +++++++++++++----- 10 files changed, 124 insertions(+), 65 deletions(-) diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs index 9dfe6ef1db..93aa8a68e8 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.cs @@ -67,27 +67,15 @@ public virtual string TransformText() this.Write("}\");\r\n"); } if("BinaryClassification".Equals(TaskType) ){ - this.Write("\t\t\tConsole.WriteLine($\"\\n\\nActual "); - this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); - this.Write(": {sampleData."); - this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); - this.Write("} \\nPredicted "); + this.Write("\t\t\tConsole.WriteLine($\"\\n\\nPredicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(": {predictionResult.Prediction}\\n\\n\");\r\n"); } else if("Regression".Equals(TaskType) || "Recommendation".Equals(TaskType)){ - this.Write("\t\t\tConsole.WriteLine($\"\\n\\nActual "); - this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); - this.Write(": {sampleData."); - this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); - this.Write("} \\nPredicted "); + this.Write("\t\t\tConsole.WriteLine($\"\\n\\nPredicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(": {predictionResult.Score}\\n\\n\");\r\n"); } else if("MulticlassClassification".Equals(TaskType)){ - this.Write("\t\t\tConsole.WriteLine($\"\\n\\nActual "); - this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); - this.Write(": {sampleData."); - this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); - this.Write("} \\nPredicted "); + this.Write("\t\t\tConsole.WriteLine($\"\\n\\nPredicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); this.Write(" value {predictionResult.Prediction} \\nPredicted "); this.Write(this.ToStringHelper.ToStringWithCulture(Utils.Normalize(LabelName))); diff --git a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt index 4f7a1f220c..5338f2f0ed 100644 --- a/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt +++ b/src/Microsoft.ML.CodeGenerator/Templates/Console/PredictProgram.tt @@ -40,11 +40,11 @@ namespace <#= Namespace #>.ConsoleApp Console.WriteLine($"<#= label #>: {sampleData.<#= Utils.Normalize(label) #>}"); <#}#> <#if("BinaryClassification".Equals(TaskType) ){ #> - Console.WriteLine($"\n\nActual <#= Utils.Normalize(LabelName) #>: {sampleData.<#= Utils.Normalize(LabelName) #>} \nPredicted <#= Utils.Normalize(LabelName) #>: {predictionResult.Prediction}\n\n"); + Console.WriteLine($"\n\nPredicted <#= Utils.Normalize(LabelName) #>: {predictionResult.Prediction}\n\n"); <#} else if("Regression".Equals(TaskType) || "Recommendation".Equals(TaskType)){#> - Console.WriteLine($"\n\nActual <#= Utils.Normalize(LabelName) #>: {sampleData.<#= Utils.Normalize(LabelName) #>} \nPredicted <#= Utils.Normalize(LabelName) #>: {predictionResult.Score}\n\n"); + Console.WriteLine($"\n\nPredicted <#= Utils.Normalize(LabelName) #>: {predictionResult.Score}\n\n"); <#} else if("MulticlassClassification".Equals(TaskType)){#> - Console.WriteLine($"\n\nActual <#= Utils.Normalize(LabelName) #>: {sampleData.<#= Utils.Normalize(LabelName) #>} \nPredicted <#= Utils.Normalize(LabelName) #> value {predictionResult.Prediction} \nPredicted <#= Utils.Normalize(LabelName) #> scores: [{String.Join(",", predictionResult.Score)}]\n\n"); + Console.WriteLine($"\n\nPredicted <#= Utils.Normalize(LabelName) #> value {predictionResult.Prediction} \nPredicted <#= Utils.Normalize(LabelName) #> scores: [{String.Join(",", predictionResult.Score)}]\n\n"); <#} #> Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); diff --git a/src/Microsoft.ML.CodeGenerator/Utils.cs b/src/Microsoft.ML.CodeGenerator/Utils.cs index cc391cd6c2..552f44fac4 100644 --- a/src/Microsoft.ML.CodeGenerator/Utils.cs +++ b/src/Microsoft.ML.CodeGenerator/Utils.cs @@ -69,7 +69,7 @@ internal static string GetValueFromColumn(DataViewRowCursor rowCursor, DataVi // wrap string in quotes if (typeof(T) == typeof(ReadOnlyMemory)) { - return $"\"{val.ToString().Replace("\"", "\\\"")}\""; + return $"@\"{val.ToString().Replace("\"", "\\\"")}\""; } if (val is null) @@ -79,7 +79,29 @@ internal static string GetValueFromColumn(DataViewRowCursor rowCursor, DataVi if (val is float) { - return val.ToString() + "F"; + var f = val as float?; + if (Single.IsNaN(f.GetValueOrDefault())) + { + return "Single.NaN"; + } + + if (Single.IsPositiveInfinity(f.GetValueOrDefault())) + { + return "Single.PositiveInfinity"; + } + + if (Single.IsNegativeInfinity(f.GetValueOrDefault())) + { + return "Single.NegativeInfinity"; + } + + return f?.ToString() + "F"; + } + + if (val is bool) + { + var f = val as bool?; + return f.GetValueOrDefault() ? "true" : "false"; } return val.ToString(); @@ -90,8 +112,8 @@ internal static string Normalize(string input) //check if first character is int if (!string.IsNullOrEmpty(input) && int.TryParse(input.Substring(0, 1), out int val)) { - input = "Col" + input; - return input; + input = "_" + input; + return Normalize(input); } switch (input) { diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt index d7f5de8580..54f234df1f 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureCodeGeneratorTest.Program.cs.approved.txt @@ -30,7 +30,7 @@ namespace Test.ConsoleApp Console.WriteLine($"Capital_loss: {sampleData.Capital_loss}"); Console.WriteLine($"Hours_per_week: {sampleData.Hours_per_week}"); Console.WriteLine($"Native_country: {sampleData.Native_country}"); - Console.WriteLine($"\n\nActual Label: {sampleData.Label} \nPredicted Label value {predictionResult.Prediction} \nPredicted Label scores: [{String.Join(",", predictionResult.Score)}]\n\n"); + Console.WriteLine($"\n\nPredicted Label value {predictionResult.Prediction} \nPredicted Label scores: [{String.Join(",", predictionResult.Score)}]\n\n"); Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt index 29332171e6..9164e5e151 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.AzureImageCodeGeneratorTest.Program.cs.approved.txt @@ -17,7 +17,7 @@ namespace CodeGenTest.ConsoleApp Console.WriteLine("Using model to make single prediction -- Comparing actual Label with predicted Label from sample data...\n\n"); Console.WriteLine($"ImageSource: {sampleData.ImageSource}"); - Console.WriteLine($"\n\nActual Label: {sampleData.Label} \nPredicted Label value {predictionResult.Prediction} \nPredicted Label scores: [{String.Join(",", predictionResult.Score)}]\n\n"); + Console.WriteLine($"\n\nPredicted Label value {predictionResult.Prediction} \nPredicted Label scores: [{String.Join(",", predictionResult.Score)}]\n\n"); Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt index 68244d69d8..5155d5cf2a 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.ConsoleAppProgramCSFileContentTest.approved.txt @@ -25,7 +25,7 @@ namespace TestNamespace.ConsoleApp Console.WriteLine($"col3: {sampleData.Col3}"); Console.WriteLine($"col4: {sampleData.Col4}"); Console.WriteLine($"col5: {sampleData.Col5}"); - Console.WriteLine($"\n\nActual Label: {sampleData.Label} \nPredicted Label: {predictionResult.Prediction}\n\n"); + Console.WriteLine($"\n\nPredicted Label: {predictionResult.Prediction}\n\n"); Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt index 68244d69d8..5155d5cf2a 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.PredictionProgramCSFileContentTest.approved.txt @@ -25,7 +25,7 @@ namespace TestNamespace.ConsoleApp Console.WriteLine($"col3: {sampleData.Col3}"); Console.WriteLine($"col4: {sampleData.Col4}"); Console.WriteLine($"col5: {sampleData.Col5}"); - Console.WriteLine($"\n\nActual Label: {sampleData.Label} \nPredicted Label: {predictionResult.Prediction}\n\n"); + Console.WriteLine($"\n\nPredicted Label: {predictionResult.Prediction}\n\n"); Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt index 51515a3760..58246e6e84 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/ConsoleCodeGeneratorTests.Recommendation_GenerateConsoleAppProjectContents_VerifyPredictProgram.approved.txt @@ -22,7 +22,7 @@ namespace TestNamespace.ConsoleApp Console.WriteLine("Using model to make single prediction -- Comparing actual Label with predicted Label from sample data...\n\n"); Console.WriteLine($"userId: {sampleData.UserId}"); Console.WriteLine($"movieId: {sampleData.MovieId}"); - Console.WriteLine($"\n\nActual Label: {sampleData.Label} \nPredicted Label: {predictionResult.Score}\n\n"); + Console.WriteLine($"\n\nPredicted Label: {predictionResult.Score}\n\n"); Console.WriteLine("=============== End of process, hit any key to finish ==============="); Console.ReadKey(); } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/CodeGenTests.cs b/test/Microsoft.ML.CodeGenerator.Tests/CodeGenTests.cs index 190d2872ee..ef85fd5a6b 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/CodeGenTests.cs +++ b/test/Microsoft.ML.CodeGenerator.Tests/CodeGenTests.cs @@ -139,16 +139,6 @@ public void TrainerComplexParameterTest() Assert.Equal(expectedUsings, actual.Item2[0]); } - [Fact] - public void NormalizeTest() - { - var testStrArray = new string[] { "Abc Abc", "abc ABC" }; - var expectedStrArray = new string[] { "Abc_Abc", "Abc_ABC" }; - for (int i = 0; i != expectedStrArray.Count(); ++i) - { - var actualStr = Microsoft.ML.CodeGenerator.Utilities.Utils.Normalize(testStrArray[i]); - Assert.Equal(expectedStrArray[i], actualStr); - } - } + } } diff --git a/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs b/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs index 827c87497d..401ae4b10b 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs +++ b/test/Microsoft.ML.CodeGenerator.Tests/UtilTest.cs @@ -1,15 +1,61 @@ using System; using System.Collections.Generic; +using System.IO; +using System.Linq; using System.Text; +using System.Threading.Tasks; using Microsoft.ML; using Microsoft.ML.AutoML; using Microsoft.ML.CodeGenerator.Utilities; +using Microsoft.ML.Data; using Microsoft.ML.TestFramework; using Xunit; using Xunit.Abstractions; namespace Microsoft.ML.CodeGenerator.Tests { + class TestClass + { + [LoadColumn(0)] + public string Label { get; set; } + + [LoadColumn(1)] + public string STR { get; set; } + + [LoadColumn(2)] + public string PATH { get; set; } + + [LoadColumn(3)] + public int INT { get; set; } + + [LoadColumn(4)] + public Double DOUBLE { get; set; } + + [LoadColumn(5)] + public float FLOAT { get; set; } + + [LoadColumn(6)] + public string TrickySTR { get; set; } + + [LoadColumn(7)] + public float SingleNan { get; set; } + + [LoadColumn(8)] + public float SinglePositiveInfinity { get; set; } + + [LoadColumn(9)] + public float SingleNegativeInfinity { get; set; } + + [LoadColumn(10)] + public string EmptyString { get; set; } + + [LoadColumn(11)] + public bool One { get; set; } + + [LoadColumn(12)] + public bool T { get; set; } + } + public class UtilTest : BaseTestClass { public UtilTest(ITestOutputHelper output) : base(output) @@ -17,37 +63,50 @@ public UtilTest(ITestOutputHelper output) : base(output) } [Fact] - public void TestGenerateSampleData() + public async Task TestGenerateSampleDataAsync() { - var data = new[] + var filePath = "sample.txt"; + using (var file = new StreamWriter(filePath)) { - new + await file.WriteLineAsync("Label,STR,PATH,INT,DOUBLE,FLOAT,TrickySTR,SingleNan,SinglePositiveInfinity,SingleNegativeInfinity,EmptyString,One,T"); + await file.WriteLineAsync("label1,feature1,/path/to/file,2,1.2,1.223E+10,ab\"\';@#$%^&-++==,NaN,Infinity,-Infinity,,1,T"); + await file.FlushAsync(); + file.Close(); + var context = new MLContext(); + var dataView = context.Data.LoadFromTextFile(filePath,separatorChar:',', hasHeader: true); + var columnInference = new ColumnInferenceResults() { - Label = "label1", - STR = "feature1", - INT = 2, - DOUBLE = 1.2, - FLOAT = 1.223E+10F, - TrickySTR = "ab\"\';@#$%^&-++==", - } - }; - - var context = new MLContext(); - var dataView = context.Data.LoadFromEnumerable(data); - var columnInference = new ColumnInferenceResults() + ColumnInformation = new ColumnInformation() + { + LabelColumnName = "Label" + } + }; + var sampleData = Utils.GenerateSampleData(dataView, columnInference); + Assert.Equal("@\"feature1\"", sampleData["STR"]); + Assert.Equal("@\"/path/to/file\"", sampleData["PATH"]); + Assert.Equal("2", sampleData["INT"]); + Assert.Equal("1.2", sampleData["DOUBLE"]); + Assert.Equal("1.223E+10F", sampleData["FLOAT"]); + Assert.Equal("@\"ab\\\"\';@#$%^&-++==\"", sampleData["TrickySTR"]); + Assert.Equal($"Single.NaN", sampleData["SingleNan"]); + Assert.Equal($"Single.PositiveInfinity", sampleData["SinglePositiveInfinity"]); + Assert.Equal($"Single.NegativeInfinity", sampleData["SingleNegativeInfinity"]); + Assert.Equal("@\"\"", sampleData["EmptyString"]); + Assert.Equal($"true", sampleData["One"]); + Assert.Equal($"true", sampleData["T"]); + } + } + + [Fact] + public void NormalizeTest() + { + var testStrArray = new string[] { "Abc Abc", "abc ABC", "12", "12.3", "1AB .C"}; + var expectedStrArray = new string[] { "Abc_Abc", "Abc_ABC", "_12", "_12_3", "_1AB__C" }; + for (int i = 0; i != expectedStrArray.Count(); ++i) { - ColumnInformation = new ColumnInformation() - { - LabelColumnName = "Label" - } - }; - - var sampleData = Utils.GenerateSampleData(dataView, columnInference); - Assert.Equal("\"feature1\"", sampleData["STR"]); - Assert.Equal("2", sampleData["INT"]); - Assert.Equal("1.2", sampleData["DOUBLE"]); - Assert.Equal("1.223E+10F", sampleData["FLOAT"]); - Assert.Equal("\"ab\\\"\';@#$%^&-++==\"", sampleData["TrickySTR"]); + var actualStr = Microsoft.ML.CodeGenerator.Utilities.Utils.Normalize(testStrArray[i]); + Assert.Equal(expectedStrArray[i], actualStr); + } } } } From f61e02befe6d5c80f85d3166582be5888a896721 Mon Sep 17 00:00:00 2001 From: LittleLittleCloud Date: Tue, 3 Mar 2020 14:19:40 -0800 Subject: [PATCH 5/5] fix test --- .../ApprovalTests/TemplateTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs index 7cb2f26e71..d4bb4ef9ca 100644 --- a/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs +++ b/test/Microsoft.ML.CodeGenerator.Tests/ApprovalTests/TemplateTest.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; using ApprovalTests; using ApprovalTests.Reporters; @@ -10,6 +11,7 @@ namespace Microsoft.ML.CodeGenerator.Tests { + [UseReporter(typeof(DiffReporter))] public class TemplateTest : BaseTestClass { public TemplateTest(ITestOutputHelper output) : base(output) @@ -18,6 +20,7 @@ public TemplateTest(ITestOutputHelper output) : base(output) [Fact] [UseReporter(typeof(DiffReporter))] + [MethodImpl(MethodImplOptions.NoInlining)] public void TestPredictProgram_WithSampleData() { var predictProgram = new PredictProgram()