From 250321031be1cc3cb7411386549f6e10a2c88040 Mon Sep 17 00:00:00 2001 From: Gaizka Navarro Date: Mon, 18 Jan 2016 14:47:32 +0100 Subject: [PATCH 01/21] Added Managed wrapper to Eval interface Added CS Client for managed wrapper --- CNTK.sln | 64 +++++ Source/CSEvalClient/App.config | 6 + Source/CSEvalClient/CSEvalClient.csproj | 84 +++++++ Source/CSEvalClient/Program.cs | 79 +++++++ .../CSEvalClient/Properties/AssemblyInfo.cs | 36 +++ Source/EvalWrapper/EvalWrapper.vcxproj | 95 ++++++++ .../EvalWrapper/EvalWrapper.vcxproj.filters | 33 +++ Source/EvalWrapper/wrapper.cpp | 219 ++++++++++++++++++ 8 files changed, 616 insertions(+) create mode 100644 Source/CSEvalClient/App.config create mode 100644 Source/CSEvalClient/CSEvalClient.csproj create mode 100644 Source/CSEvalClient/Program.cs create mode 100644 Source/CSEvalClient/Properties/AssemblyInfo.cs create mode 100644 Source/EvalWrapper/EvalWrapper.vcxproj create mode 100644 Source/EvalWrapper/EvalWrapper.vcxproj.filters create mode 100644 Source/EvalWrapper/wrapper.cpp diff --git a/CNTK.sln b/CNTK.sln index ff28e294e..c23637e7d 100644 --- a/CNTK.sln +++ b/CNTK.sln @@ -685,96 +685,158 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ResNet", "ResNet", "{06D2C6 Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_34.ndl = Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_34.ndl EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EvalWrapper", "Source\EvalWrapper\EvalWrapper.vcxproj", "{EF766CAE-9CB1-494C-9153-0030631A6340}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSEvalClient", "Source\CSEvalClient\CSEvalClient.csproj", "{41E11A59-62B2-4927-A4F8-F40B1B612C6C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Debug|Any CPU.ActiveCfg = Debug|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Debug|x64.ActiveCfg = Debug|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Debug|x64.Build.0 = Debug|x64 + {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Release|Any CPU.ActiveCfg = Release|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Release|x64.ActiveCfg = Release|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Release|x64.Build.0 = Release|x64 + {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Debug|Any CPU.ActiveCfg = Debug|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Debug|x64.ActiveCfg = Debug|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Debug|x64.Build.0 = Debug|x64 + {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Release|Any CPU.ActiveCfg = Release|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Release|x64.ActiveCfg = Release|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Release|x64.Build.0 = Release|x64 + {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Debug|Any CPU.ActiveCfg = Debug|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Debug|x64.ActiveCfg = Debug|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Debug|x64.Build.0 = Debug|x64 + {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Release|Any CPU.ActiveCfg = Release|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Release|x64.ActiveCfg = Release|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Release|x64.Build.0 = Release|x64 + {EAD17188-072C-4726-B840-A769C36DAD1B}.Debug|Any CPU.ActiveCfg = Debug|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Debug|x64.ActiveCfg = Debug|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Debug|x64.Build.0 = Debug|x64 + {EAD17188-072C-4726-B840-A769C36DAD1B}.Release|Any CPU.ActiveCfg = Release|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Release|x64.ActiveCfg = Release|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Release|x64.Build.0 = Release|x64 + {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Debug|Any CPU.ActiveCfg = Debug|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Debug|x64.ActiveCfg = Debug|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Debug|x64.Build.0 = Debug|x64 + {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Release|Any CPU.ActiveCfg = Release|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Release|x64.ActiveCfg = Release|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Release|x64.Build.0 = Release|x64 + {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Debug|Any CPU.ActiveCfg = Debug|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Debug|x64.ActiveCfg = Debug|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Debug|x64.Build.0 = Debug|x64 + {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Release|Any CPU.ActiveCfg = Release|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Release|x64.ActiveCfg = Release|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Release|x64.Build.0 = Release|x64 + {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Debug|Any CPU.ActiveCfg = Debug|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Debug|x64.ActiveCfg = Debug|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Debug|x64.Build.0 = Debug|x64 + {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Release|Any CPU.ActiveCfg = Release|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Release|x64.ActiveCfg = Release|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Release|x64.Build.0 = Release|x64 + {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Debug|Any CPU.ActiveCfg = Debug|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Debug|x64.ActiveCfg = Debug|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Debug|x64.Build.0 = Debug|x64 + {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Release|Any CPU.ActiveCfg = Release|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Release|x64.ActiveCfg = Release|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Release|x64.Build.0 = Release|x64 + {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Debug|Any CPU.ActiveCfg = Debug|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Debug|x64.ActiveCfg = Debug|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Debug|x64.Build.0 = Debug|x64 + {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Release|Any CPU.ActiveCfg = Release|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Release|x64.ActiveCfg = Release|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Release|x64.Build.0 = Release|x64 + {B3DD765E-694E-4494-BAD7-37BBF2942517}.Debug|Any CPU.ActiveCfg = Debug|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Debug|x64.ActiveCfg = Debug|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Debug|x64.Build.0 = Debug|x64 + {B3DD765E-694E-4494-BAD7-37BBF2942517}.Release|Any CPU.ActiveCfg = Release|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Release|x64.ActiveCfg = Release|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Release|x64.Build.0 = Release|x64 + {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Debug|Any CPU.ActiveCfg = Debug|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Debug|x64.ActiveCfg = Debug|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Debug|x64.Build.0 = Debug|x64 + {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Release|Any CPU.ActiveCfg = Release|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Release|x64.ActiveCfg = Release|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Release|x64.Build.0 = Release|x64 + {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Debug|Any CPU.ActiveCfg = Debug|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Debug|x64.ActiveCfg = Debug|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Debug|x64.Build.0 = Debug|x64 + {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Release|Any CPU.ActiveCfg = Release|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Release|x64.ActiveCfg = Release|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Release|x64.Build.0 = Release|x64 + {014DA766-B37B-4581-BC26-963EA5507931}.Debug|Any CPU.ActiveCfg = Debug|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Debug|x64.ActiveCfg = Debug|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Debug|x64.Build.0 = Debug|x64 + {014DA766-B37B-4581-BC26-963EA5507931}.Release|Any CPU.ActiveCfg = Release|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Release|x64.ActiveCfg = Release|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Release|x64.Build.0 = Release|x64 + {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Debug|Any CPU.ActiveCfg = Debug|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Debug|x64.ActiveCfg = Debug|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Debug|x64.Build.0 = Debug|x64 + {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Release|Any CPU.ActiveCfg = Release|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Release|x64.ActiveCfg = Release|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Release|x64.Build.0 = Release|x64 + {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Debug|Any CPU.ActiveCfg = Debug|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Debug|x64.ActiveCfg = Debug|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Debug|x64.Build.0 = Debug|x64 + {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Release|Any CPU.ActiveCfg = Release|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Release|x64.ActiveCfg = Release|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Release|x64.Build.0 = Release|x64 + {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Debug|Any CPU.ActiveCfg = Debug|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Debug|x64.ActiveCfg = Debug|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Debug|x64.Build.0 = Debug|x64 + {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Release|Any CPU.ActiveCfg = Release|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Release|x64.ActiveCfg = Release|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Release|x64.Build.0 = Release|x64 + {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Debug|Any CPU.ActiveCfg = Debug|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Debug|x64.ActiveCfg = Debug|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Debug|x64.Build.0 = Debug|x64 + {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Release|Any CPU.ActiveCfg = Release|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Release|x64.ActiveCfg = Release|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Release|x64.Build.0 = Release|x64 + {E6646FFE-3588-4276-8A15-8D65C22711C1}.Debug|Any CPU.ActiveCfg = Debug|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Debug|x64.ActiveCfg = Debug|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Debug|x64.Build.0 = Debug|x64 + {E6646FFE-3588-4276-8A15-8D65C22711C1}.Release|Any CPU.ActiveCfg = Release|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Release|x64.ActiveCfg = Release|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Release|x64.Build.0 = Release|x64 + {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Debug|Any CPU.ActiveCfg = Debug|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Debug|x64.ActiveCfg = Debug|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Debug|x64.Build.0 = Debug|x64 + {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Release|Any CPU.ActiveCfg = Release|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Release|x64.ActiveCfg = Release|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Release|x64.Build.0 = Release|x64 + {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Debug|Any CPU.ActiveCfg = Debug|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Debug|x64.ActiveCfg = Debug|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Debug|x64.Build.0 = Debug|x64 + {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Release|Any CPU.ActiveCfg = Release|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Release|x64.ActiveCfg = Release|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Release|x64.Build.0 = Release|x64 + {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Debug|Any CPU.ActiveCfg = Debug|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Debug|x64.ActiveCfg = Debug|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Debug|x64.Build.0 = Debug|x64 + {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Release|Any CPU.ActiveCfg = Release|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Release|x64.ActiveCfg = Release|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Release|x64.Build.0 = Release|x64 + {EF766CAE-9CB1-494C-9153-0030631A6340}.Debug|Any CPU.ActiveCfg = Debug|x64 + {EF766CAE-9CB1-494C-9153-0030631A6340}.Debug|x64.ActiveCfg = Debug|x64 + {EF766CAE-9CB1-494C-9153-0030631A6340}.Debug|x64.Build.0 = Debug|x64 + {EF766CAE-9CB1-494C-9153-0030631A6340}.Release|Any CPU.ActiveCfg = Release|x64 + {EF766CAE-9CB1-494C-9153-0030631A6340}.Release|x64.ActiveCfg = Release|x64 + {EF766CAE-9CB1-494C-9153-0030631A6340}.Release|x64.Build.0 = Release|x64 + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|x64.ActiveCfg = Debug|x64 + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|x64.Build.0 = Debug|x64 + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|Any CPU.Build.0 = Release|Any CPU + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|x64.ActiveCfg = Release|x64 + {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -867,5 +929,7 @@ Global {850008BC-36B0-4A0A-BD0C-B6D5C2184227} = {6F4125B5-220F-4FB7-B6C4-85A966A0268C} {E6DC3B7D-303D-4A54-B040-D8DCF8C56E17} = {8C128B1D-87E0-4643-AB93-2581589AE425} {06D2C644-AE5F-4C30-A1F6-C78E2845AAB1} = {EF710C5A-E616-442A-889D-C997D39AF2E1} + {EF766CAE-9CB1-494C-9153-0030631A6340} = {DD043083-71A4-409A-AA91-F9C548DCF7EC} + {41E11A59-62B2-4927-A4F8-F40B1B612C6C} = {DD043083-71A4-409A-AA91-F9C548DCF7EC} EndGlobalSection EndGlobal diff --git a/Source/CSEvalClient/App.config b/Source/CSEvalClient/App.config new file mode 100644 index 000000000..8e1564635 --- /dev/null +++ b/Source/CSEvalClient/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Source/CSEvalClient/CSEvalClient.csproj b/Source/CSEvalClient/CSEvalClient.csproj new file mode 100644 index 000000000..b258148fd --- /dev/null +++ b/Source/CSEvalClient/CSEvalClient.csproj @@ -0,0 +1,84 @@ + + + + + Debug + AnyCPU + {41E11A59-62B2-4927-A4F8-F40B1B612C6C} + Exe + Properties + CSEvalClient + CSEvalClient + v4.5 + 512 + + + x64 + true + full + false + ..\..\x64\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\x64\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + bin\x64\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + true + + + + + + + + + + + + + + + + + + + + {ef766cae-9cb1-494c-9153-0030631a6340} + EvalWrapper + + + + + \ No newline at end of file diff --git a/Source/CSEvalClient/Program.cs b/Source/CSEvalClient/Program.cs new file mode 100644 index 000000000..c4728aba9 --- /dev/null +++ b/Source/CSEvalClient/Program.cs @@ -0,0 +1,79 @@ +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.MSR.CNTK; + +namespace CSEvalClient +{ + class Program + { + private static void Main(string[] args) + { + Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, @"..\..\Examples\Image\MNIST\Data\"); + Console.WriteLine("Current Directory: {0}", Environment.CurrentDirectory); + + Console.WriteLine("Creating Model Evaluator..."); + var model = new IEvaluateModelManagedF(); + + Console.WriteLine("Initializing Model Evaluator..."); + string config = GetConfig(); + model.Init(config); + + Console.WriteLine("Loading Model..."); + string modelFilePath = Path.Combine(Environment.CurrentDirectory, @"..\Output\Models\01_OneHidden"); + model.LoadModel(modelFilePath); + + var inputs = GetDictionary("features", 28 * 28, 255); + var outputs = GetDictionary("ol.z", 10, 100); + + Console.WriteLine("Press to begin evaluating."); + Console.ReadLine(); + + List outputList = null; + for (int i = 0; i < 10; i++) + { + Console.WriteLine("Evaluating Model..."); + outputList = model.Evaluate(inputs, "ol.z", 10); // return results + model.Evaluate(inputs, outputs); // Pass result structure + } + + Console.WriteLine("Destroying Model..."); + model.Destroy(); + + Console.WriteLine("Output contents:"); + foreach (var item in outputs) + { + Console.WriteLine(item); + } + + Console.WriteLine("Press to terminate."); + Console.ReadLine(); + } + + static Dictionary> GetDictionary(string key, int size, int maxValue) + { + return new Dictionary>() { { key, GetFloatArray(size, maxValue) } }; + } + + static string GetConfig() + { + string configFilePath = Path.Combine(Environment.CurrentDirectory, + @"..\Config\01_OneHidden.config"); + + var lines = System.IO.File.ReadAllLines(configFilePath); + return string.Join("\n", lines); + } + + static List GetFloatArray(int size, int maxValue) + { + Random rnd = new Random(); + return Enumerable.Range(1, size).Select(i => (float)rnd.Next(maxValue)).ToList(); + } + } +} diff --git a/Source/CSEvalClient/Properties/AssemblyInfo.cs b/Source/CSEvalClient/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e56d15336 --- /dev/null +++ b/Source/CSEvalClient/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("CSEvalClient")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("CSEvalClient")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("6ec08331-7554-4ebd-b663-b64ab6e719e2")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Source/EvalWrapper/EvalWrapper.vcxproj b/Source/EvalWrapper/EvalWrapper.vcxproj new file mode 100644 index 000000000..f2338cfb7 --- /dev/null +++ b/Source/EvalWrapper/EvalWrapper.vcxproj @@ -0,0 +1,95 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {EF766CAE-9CB1-494C-9153-0030631A6340} + v4.5 + ManagedCProj + EvalWrapper + + + + DynamicLibrary + true + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);..\Common\Include; + .dll + $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath) + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);..\Common\Include; + .dll + $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath) + + + + Level3 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + + + true + EvalDll.lib;%(AdditionalDependencies) + + + + + + + Level3 + WIN32;NDEBUG;%(PreprocessorDefinitions) + + + true + EvalDll.lib;%(AdditionalDependencies) + + + + + + + + + + + + + {482999d1-b7e2-466e-9f8d-2119f93eafd9} + + + + + + \ No newline at end of file diff --git a/Source/EvalWrapper/EvalWrapper.vcxproj.filters b/Source/EvalWrapper/EvalWrapper.vcxproj.filters new file mode 100644 index 000000000..a68d46eb6 --- /dev/null +++ b/Source/EvalWrapper/EvalWrapper.vcxproj.filters @@ -0,0 +1,33 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {f89d33a1-2be0-4dee-b681-3630c6595c18} + + + {46d2ad19-e3a7-4619-afa2-cc3b6cd5f751} + + + + + Source Files + + + + + Common\Include + + + \ No newline at end of file diff --git a/Source/EvalWrapper/wrapper.cpp b/Source/EvalWrapper/wrapper.cpp new file mode 100644 index 000000000..60c00130d --- /dev/null +++ b/Source/EvalWrapper/wrapper.cpp @@ -0,0 +1,219 @@ +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// +#include +#include +#include +#include +#include + +#include "Eval.h" + +#using +#using + +using namespace System; +using namespace System::Collections::Generic; +using namespace System::Collections; + +using namespace Microsoft::MSR::CNTK; + +namespace Microsoft { namespace MSR { namespace CNTK +{ + template + using GetEvalProc = void(*)(IEvaluateModel**); + + template + public ref class IEvaluateModelManaged + { + public: + IEvaluateModelManaged(String^ funcName) + { + pin_ptr dllname = PtrToStringChars("evaldll.dll"); + auto hModule = LoadLibrary(dllname); + + msclr::interop::marshal_context context; + const std::string func = context.marshal_as(funcName); + auto procAddress = GetProcAddress(hModule, func.c_str()); + auto getEvalProc = (GetEvalProc)procAddress; + pin_ptr *> p_eval = &m_eval; + getEvalProc(p_eval); + + //pin_ptr *> p_eval = &m_eval; + //GetEvalF(p_eval); + } + + void Init(String^ config) + { + msclr::interop::marshal_context context; + const std::string stdConfig = context.marshal_as(config); + + m_eval->Init(stdConfig); + } + + void Destroy() + { + m_eval->Destroy(); + } + + void LoadModel(String^ modelFileName) + { + pin_ptr stdModelPath = PtrToStringChars(modelFileName); + m_eval->LoadModel(stdModelPath); + } + + void Evaluate(Dictionary^>^ inputs, Dictionary^>^ outputs) + { + std::map*> stdInputs; + std::map*> stdOutputs; + std::pair*>* stdInput; + std::pair*>* stdOutput; + + for each (auto item in inputs) + { + pin_ptr key = PtrToStringChars(item.Key); + stdInput = new std::pair*>(key, CopyList(item.Value)); + stdInputs.insert(*stdInput); + } + + for each (auto item in outputs) + { + pin_ptr key = PtrToStringChars(item.Key); + stdOutput = new std::pair*>(key, CopyList(item.Value)); + stdOutputs.insert(*stdOutput); + } + + m_eval->Evaluate(stdInputs, stdOutputs); + + auto enumerator = outputs->Keys->GetEnumerator(); + for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e; ii++) + { + // All this, to get the key (output layer name) + std::vector* vector = (*ii).second; + std::vector &refVector = (*vector); + int index = 0; + enumerator.MoveNext(); + String^ key = enumerator.Current; + + // Copy output to CLI structure + for (std::vector::iterator ii = refVector.begin(), e = refVector.end(); ii != e; ii++) + { + outputs[key][index++] = *ii; + } + } + + // Release the used memory + for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e;) + { + delete (*ii).second; + stdInputs.erase(ii++); // Doesn't seem to release the memory, thus the delete stdInput call + } + delete stdInput; + + for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e;) + { + delete (*ii).second; + stdOutputs.erase(ii++); // Doesn't seem to release the memory + } + delete stdOutput; + } + + List^ Evaluate(Dictionary^>^ inputs, String^ outputKey, int outputSize) + { + std::map*> stdInputs; + std::map*> stdOutputs; + std::pair*>* stdInput; + + for each (auto item in inputs) + { + pin_ptr key = PtrToStringChars(item.Key); + stdInput = new std::pair*>(key, CopyList(item.Value)); + stdInputs.insert(*stdInput); + } + + pin_ptr key = PtrToStringChars(outputKey); + std::vector stdOutputVector(outputSize); + std::pair*> stdOutput(key, &stdOutputVector); + stdOutputs.insert(stdOutput); + + m_eval->Evaluate(stdInputs, stdOutputs); + + std::vector* vector = stdOutputs.begin()->second; + std::vector refVector = (*vector); + + List^ output = gcnew List(); + + // Copy output to CLI structure + for (std::vector::iterator ii = (*vector).begin(), e = (*vector).end(); ii != e; ii++) + { + output->Add(*ii); + } + + // Release the used memory + for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e;) + { + delete (*ii).second; + stdInputs.erase(ii++); // Doesn't seem to release the memory + } + delete stdInput; + + return output; + } + + private: + IEvaluateModel *m_eval; + + std::vector* CopyList(List^ list) + { + std::vector* lower = new std::vector(); + for each (auto item in list) + { + ElemType val = item; + lower->push_back(val); + } + + return lower; + } + }; + + public ref class IEvaluateModelManagedF : IEvaluateModelManaged + { + public: + IEvaluateModelManagedF::IEvaluateModelManagedF() + : IEvaluateModelManaged("GetEvalF") + { + } + }; + + public ref class IEvaluateModelManagedD : IEvaluateModelManaged + { + public: + IEvaluateModelManagedD::IEvaluateModelManagedD() + : IEvaluateModelManaged("GetEvalD") + { + } + }; + + void emit() + { + // This method tricks the compiler into emitting the methods of the classes + // Refer to https://msdn.microsoft.com/en-us/library/ms177213.aspx for an + // explanation to this insanity + IEvaluateModelManagedF f; + f.Init(""); + f.Evaluate(nullptr, nullptr); + f.Evaluate(nullptr, "", 0); + f.LoadModel(""); + f.Destroy(); + + + IEvaluateModelManagedD d; + d.Init(""); + d.Evaluate(nullptr, nullptr); + d.Evaluate(nullptr, "", 0); + d.LoadModel(""); + d.Destroy(); + } +}}} \ No newline at end of file From d582eb2701a88b042b745da254a8e85249f376ba Mon Sep 17 00:00:00 2001 From: Gaizka Navarro Date: Tue, 19 Jan 2016 11:47:16 +0100 Subject: [PATCH 02/21] Cleanup and commented for CR --- Source/CSEvalClient/CSEvalClient.csproj | 4 +- Source/CSEvalClient/Program.cs | 69 +++-- Source/EvalWrapper/wrapper.cpp | 361 ++++++++++++------------ 3 files changed, 227 insertions(+), 207 deletions(-) diff --git a/Source/CSEvalClient/CSEvalClient.csproj b/Source/CSEvalClient/CSEvalClient.csproj index b258148fd..5f1ac34be 100644 --- a/Source/CSEvalClient/CSEvalClient.csproj +++ b/Source/CSEvalClient/CSEvalClient.csproj @@ -33,7 +33,7 @@ true - bin\x64\Debug\ + ..\..\x64\Debug\ DEBUG;TRACE full x64 @@ -42,7 +42,7 @@ true - bin\x64\Release\ + ..\..\x64\Release\ TRACE true pdbonly diff --git a/Source/CSEvalClient/Program.cs b/Source/CSEvalClient/Program.cs index c4728aba9..a118a1d53 100644 --- a/Source/CSEvalClient/Program.cs +++ b/Source/CSEvalClient/Program.cs @@ -15,41 +15,50 @@ namespace CSEvalClient { private static void Main(string[] args) { - Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, @"..\..\Examples\Image\MNIST\Data\"); - Console.WriteLine("Current Directory: {0}", Environment.CurrentDirectory); - - Console.WriteLine("Creating Model Evaluator..."); - var model = new IEvaluateModelManagedF(); - - Console.WriteLine("Initializing Model Evaluator..."); - string config = GetConfig(); - model.Init(config); - - Console.WriteLine("Loading Model..."); - string modelFilePath = Path.Combine(Environment.CurrentDirectory, @"..\Output\Models\01_OneHidden"); - model.LoadModel(modelFilePath); - - var inputs = GetDictionary("features", 28 * 28, 255); - var outputs = GetDictionary("ol.z", 10, 100); - - Console.WriteLine("Press to begin evaluating."); - Console.ReadLine(); - - List outputList = null; - for (int i = 0; i < 10; i++) + try { + Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, @"..\..\Examples\Image\MNIST\Data\"); + Console.WriteLine("Current Directory: {0}", Environment.CurrentDirectory); + + Console.WriteLine("Creating Model Evaluator..."); + var model = new IEvaluateModelManagedF(); + + Console.WriteLine("Initializing Model Evaluator..."); + string config = GetConfig(); + model.Init(config); + + Console.WriteLine("Loading Model..."); + string modelFilePath = Path.Combine(Environment.CurrentDirectory, @"..\Output\Models\01_OneHidden"); + model.LoadModel(modelFilePath); + + var inputs = GetDictionary("features", 28 * 28, 255); + var outputs = GetDictionary("ol.z", 10, 100); + + Console.WriteLine("Press to begin evaluating."); + Console.ReadLine(); + Console.WriteLine("Evaluating Model..."); - outputList = model.Evaluate(inputs, "ol.z", 10); // return results + List outputList = null; + outputList = model.Evaluate(inputs, "ol.z", 10); // return results model.Evaluate(inputs, outputs); // Pass result structure + + Console.WriteLine("Destroying Model..."); + model.Destroy(); + model = null; + + Console.WriteLine("Output contents:"); + foreach (var item in outputs) + { + Console.WriteLine("Output layer: {0}", item.Key); + foreach (var entry in item.Value) + { + Console.WriteLine(entry); + } + } } - - Console.WriteLine("Destroying Model..."); - model.Destroy(); - - Console.WriteLine("Output contents:"); - foreach (var item in outputs) + catch (Exception ex) { - Console.WriteLine(item); + Console.WriteLine("Error: {0}", ex); } Console.WriteLine("Press to terminate."); diff --git a/Source/EvalWrapper/wrapper.cpp b/Source/EvalWrapper/wrapper.cpp index 60c00130d..a4899aebd 100644 --- a/Source/EvalWrapper/wrapper.cpp +++ b/Source/EvalWrapper/wrapper.cpp @@ -20,200 +20,211 @@ using namespace System::Collections; using namespace Microsoft::MSR::CNTK; -namespace Microsoft { namespace MSR { namespace CNTK +namespace Microsoft { +namespace MSR { +namespace CNTK { +// Used for retrieving the model appropriate for the element type (float / double) +template +using GetEvalProc = void(*)(IEvaluateModel**); + +/// Managed wrapper for the native evaluation model +template +public ref class IEvaluateModelManaged { - template - using GetEvalProc = void(*)(IEvaluateModel**); + typedef std::pair*> MapEntry; - template - public ref class IEvaluateModelManaged +public: + /// Initializes a new instance of the class. + /// Factory function name for retrieving the native model from the dll. + IEvaluateModelManaged(String^ funcName) { - public: - IEvaluateModelManaged(String^ funcName) + pin_ptr dllname = PtrToStringChars("evaldll.dll"); + auto hModule = LoadLibrary(dllname); + + msclr::interop::marshal_context context; + const std::string func = context.marshal_as(funcName); + auto procAddress = GetProcAddress(hModule, func.c_str()); + auto getEvalProc = (GetEvalProc)procAddress; + pin_ptr *> p_eval = &m_eval; + getEvalProc(p_eval); + } + + /// Initializes the model evaluation library with a CNTK configuration + void Init(String^ config) + { + msclr::interop::marshal_context context; + const std::string stdConfig = context.marshal_as(config); + + m_eval->Init(stdConfig); + } + + /// Destroys the model evaluation object + void Destroy() + { + m_eval->Destroy(); + } + + /// Loads a model file + /// The model file name to load + void LoadModel(String^ modelFileName) + { + pin_ptr stdModelPath = PtrToStringChars(modelFileName); + m_eval->LoadModel(stdModelPath); + } + + /// Evaluates the model against input data and retrieves the output layer data + /// + /// + void Evaluate(Dictionary^>^ inputs, Dictionary^>^ outputs) + { + std::map*> stdInputs; + std::map*> stdOutputs; + + for each (auto item in inputs) { - pin_ptr dllname = PtrToStringChars("evaldll.dll"); - auto hModule = LoadLibrary(dllname); - - msclr::interop::marshal_context context; - const std::string func = context.marshal_as(funcName); - auto procAddress = GetProcAddress(hModule, func.c_str()); - auto getEvalProc = (GetEvalProc)procAddress; - pin_ptr *> p_eval = &m_eval; - getEvalProc(p_eval); - - //pin_ptr *> p_eval = &m_eval; - //GetEvalF(p_eval); + pin_ptr key = PtrToStringChars(item.Key); + stdInputs.insert(MapEntry(key, CopyList(item.Value))); } - void Init(String^ config) + for each (auto item in outputs) { - msclr::interop::marshal_context context; - const std::string stdConfig = context.marshal_as(config); - - m_eval->Init(stdConfig); + pin_ptr key = PtrToStringChars(item.Key); + stdOutputs.insert(MapEntry(key, CopyList(item.Value))); } - void Destroy() + m_eval->Evaluate(stdInputs, stdOutputs); + + auto enumerator = outputs->Keys->GetEnumerator(); + for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e; ii++) { - m_eval->Destroy(); - } + // Retreive the layer key + enumerator.MoveNext(); + String^ key = enumerator.Current; - void LoadModel(String^ modelFileName) - { - pin_ptr stdModelPath = PtrToStringChars(modelFileName); - m_eval->LoadModel(stdModelPath); - } - - void Evaluate(Dictionary^>^ inputs, Dictionary^>^ outputs) - { - std::map*> stdInputs; - std::map*> stdOutputs; - std::pair*>* stdInput; - std::pair*>* stdOutput; - - for each (auto item in inputs) - { - pin_ptr key = PtrToStringChars(item.Key); - stdInput = new std::pair*>(key, CopyList(item.Value)); - stdInputs.insert(*stdInput); - } - - for each (auto item in outputs) - { - pin_ptr key = PtrToStringChars(item.Key); - stdOutput = new std::pair*>(key, CopyList(item.Value)); - stdOutputs.insert(*stdOutput); - } - - m_eval->Evaluate(stdInputs, stdOutputs); - - auto enumerator = outputs->Keys->GetEnumerator(); - for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e; ii++) - { - // All this, to get the key (output layer name) - std::vector* vector = (*ii).second; - std::vector &refVector = (*vector); - int index = 0; - enumerator.MoveNext(); - String^ key = enumerator.Current; - - // Copy output to CLI structure - for (std::vector::iterator ii = refVector.begin(), e = refVector.end(); ii != e; ii++) - { - outputs[key][index++] = *ii; - } - } - - // Release the used memory - for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e;) - { - delete (*ii).second; - stdInputs.erase(ii++); // Doesn't seem to release the memory, thus the delete stdInput call - } - delete stdInput; - - for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e;) - { - delete (*ii).second; - stdOutputs.erase(ii++); // Doesn't seem to release the memory - } - delete stdOutput; - } - - List^ Evaluate(Dictionary^>^ inputs, String^ outputKey, int outputSize) - { - std::map*> stdInputs; - std::map*> stdOutputs; - std::pair*>* stdInput; - - for each (auto item in inputs) - { - pin_ptr key = PtrToStringChars(item.Key); - stdInput = new std::pair*>(key, CopyList(item.Value)); - stdInputs.insert(*stdInput); - } - - pin_ptr key = PtrToStringChars(outputKey); - std::vector stdOutputVector(outputSize); - std::pair*> stdOutput(key, &stdOutputVector); - stdOutputs.insert(stdOutput); - - m_eval->Evaluate(stdInputs, stdOutputs); - - std::vector* vector = stdOutputs.begin()->second; - std::vector refVector = (*vector); - - List^ output = gcnew List(); + std::vector &refVector = *((*ii).second); + int index = 0; // Copy output to CLI structure - for (std::vector::iterator ii = (*vector).begin(), e = (*vector).end(); ii != e; ii++) + for (std::vector::iterator ii = refVector.begin(), e = refVector.end(); ii != e; ii++) { - output->Add(*ii); + outputs[key][index++] = *ii; } - - // Release the used memory - for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e;) - { - delete (*ii).second; - stdInputs.erase(ii++); // Doesn't seem to release the memory - } - delete stdInput; - - return output; } - private: - IEvaluateModel *m_eval; - - std::vector* CopyList(List^ list) + // Release the memory used + for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e; ii++) { - std::vector* lower = new std::vector(); - for each (auto item in list) - { - ElemType val = item; - lower->push_back(val); - } + delete (*ii).second; + } - return lower; - } - }; - - public ref class IEvaluateModelManagedF : IEvaluateModelManaged - { - public: - IEvaluateModelManagedF::IEvaluateModelManagedF() - : IEvaluateModelManaged("GetEvalF") + for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e; ii++) { + delete (*ii).second; } - }; - - public ref class IEvaluateModelManagedD : IEvaluateModelManaged - { - public: - IEvaluateModelManagedD::IEvaluateModelManagedD() - : IEvaluateModelManaged("GetEvalD") - { - } - }; - - void emit() - { - // This method tricks the compiler into emitting the methods of the classes - // Refer to https://msdn.microsoft.com/en-us/library/ms177213.aspx for an - // explanation to this insanity - IEvaluateModelManagedF f; - f.Init(""); - f.Evaluate(nullptr, nullptr); - f.Evaluate(nullptr, "", 0); - f.LoadModel(""); - f.Destroy(); - - - IEvaluateModelManagedD d; - d.Init(""); - d.Evaluate(nullptr, nullptr); - d.Evaluate(nullptr, "", 0); - d.LoadModel(""); - d.Destroy(); } -}}} \ No newline at end of file + + /// Evaluates the model against input data and retrieves the output layer data + /// + /// + /// + /// Results for specified layer + List^ Evaluate(Dictionary^>^ inputs, String^ outputKey, int outputSize) + { + std::map*> stdInputs; + std::map*> stdOutputs; + + // Prepare input + for each (auto item in inputs) + { + pin_ptr key = PtrToStringChars(item.Key); + stdInputs.insert(MapEntry(key, CopyList(item.Value))); + } + + // Prepare output buffer + pin_ptr key = PtrToStringChars(outputKey); + stdOutputs.insert(MapEntry(key, new std::vector(outputSize))); + + // Perform evaluation + m_eval->Evaluate(stdInputs, stdOutputs); + + // Copy output to CLI structure + List^ output = gcnew List(); + std::vector* vector = stdOutputs.begin()->second; + int count = 0; + for (std::vector::iterator ii = (*vector).begin(), e = (*vector).end(); ii != e && count < outputSize; ii++, count++) + { + output->Add(*ii); + } + + // Release the used memory + for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e; ii++) + { + delete (*ii).second; + } + + return output; + } + +private: + // Native model evaluation instance + IEvaluateModel *m_eval; + + /// Copies a list of element types from a CLI structure to a native structure + /// The CLI list of items + /// A native vector of items + std::vector* CopyList(List^ list) + { + std::vector* lower = new std::vector(); + for each (ElemType item in list) + { + lower->push_back(item); + } + + return lower; + } +}; + +/// Managed float-specific model evaluation class +/// This class is necessary due to how generics and templates work in CLR +public ref class IEvaluateModelManagedF : IEvaluateModelManaged +{ +public: + IEvaluateModelManagedF::IEvaluateModelManagedF() + : IEvaluateModelManaged("GetEvalF") + { + } +}; + +/// Managed double-specific model evaluation class +/// This class is necessary due to how generics and templates work in CLR +public ref class IEvaluateModelManagedD : IEvaluateModelManaged +{ +public: + IEvaluateModelManagedD::IEvaluateModelManagedD() + : IEvaluateModelManaged("GetEvalD") + { + } +}; + +// This method tricks the compiler into emitting the methods of the classes +// Refer to https://msdn.microsoft.com/en-us/library/ms177213.aspx for an +// explanation to this insanity +void emit() +{ + IEvaluateModelManagedF f; + f.Init(""); + f.Evaluate(nullptr, nullptr); + f.Evaluate(nullptr, "", 0); + f.LoadModel(""); + f.Destroy(); + + IEvaluateModelManagedD d; + d.Init(""); + d.Evaluate(nullptr, nullptr); + d.Evaluate(nullptr, "", 0); + d.LoadModel(""); + d.Destroy(); +} +} +} +} \ No newline at end of file From fa939cc6411aae3c1352d09e4bb1377fb146ab39 Mon Sep 17 00:00:00 2001 From: Gaizka Navarro Date: Wed, 20 Jan 2016 14:45:27 +0100 Subject: [PATCH 03/21] CR Fix - Switched copyright headers to MIT license CR Fix - Removed EvalDll.lib linking --- Source/CSEvalClient/Program.cs | 6 +++--- Source/EvalWrapper/EvalWrapper.vcxproj | 4 ++-- Source/EvalWrapper/wrapper.cpp | 5 ++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Source/CSEvalClient/Program.cs b/Source/CSEvalClient/Program.cs index a118a1d53..9e7ad548b 100644 --- a/Source/CSEvalClient/Program.cs +++ b/Source/CSEvalClient/Program.cs @@ -1,8 +1,8 @@ // -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. // + using System; using System.Collections.Generic; using System.IO; diff --git a/Source/EvalWrapper/EvalWrapper.vcxproj b/Source/EvalWrapper/EvalWrapper.vcxproj index f2338cfb7..4d8a91227 100644 --- a/Source/EvalWrapper/EvalWrapper.vcxproj +++ b/Source/EvalWrapper/EvalWrapper.vcxproj @@ -61,7 +61,7 @@ true - EvalDll.lib;%(AdditionalDependencies) + %(AdditionalDependencies) @@ -73,7 +73,7 @@ true - EvalDll.lib;%(AdditionalDependencies) + %(AdditionalDependencies) diff --git a/Source/EvalWrapper/wrapper.cpp b/Source/EvalWrapper/wrapper.cpp index a4899aebd..fff9585ae 100644 --- a/Source/EvalWrapper/wrapper.cpp +++ b/Source/EvalWrapper/wrapper.cpp @@ -1,7 +1,6 @@ // -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. // #include #include From 53de59c1c74b4307bd945ceb6df7724d4bc8bdc1 Mon Sep 17 00:00:00 2001 From: Gaizka Navarro Date: Tue, 19 Jan 2016 12:04:52 +0100 Subject: [PATCH 04/21] Added CLI Wrapper to native Evaluation Model Added C# Client for CLI wrapper --- CNTK.sln | 64 +------ Source/CSEvalClient/Program.cs | 88 --------- .../CSEvalClient/App.config | 0 .../CSEvalClient/CSEvalClient.csproj | 29 +-- Source/Extensibility/CSEvalClient/Program.cs | 133 ++++++++++++++ .../CSEvalClient/Properties/AssemblyInfo.cs | 15 +- .../EvalWrapper/EvalWrapper.vcxproj | 11 +- .../EvalWrapper/EvalWrapper.vcxproj.filters | 10 +- .../EvalWrapper/wrapper.cpp | 167 ++++++++++-------- 9 files changed, 251 insertions(+), 266 deletions(-) delete mode 100644 Source/CSEvalClient/Program.cs rename Source/{ => Extensibility}/CSEvalClient/App.config (100%) rename Source/{ => Extensibility}/CSEvalClient/CSEvalClient.csproj (69%) create mode 100644 Source/Extensibility/CSEvalClient/Program.cs rename Source/{ => Extensibility}/CSEvalClient/Properties/AssemblyInfo.cs (72%) rename Source/{ => Extensibility}/EvalWrapper/EvalWrapper.vcxproj (90%) rename Source/{ => Extensibility}/EvalWrapper/EvalWrapper.vcxproj.filters (62%) rename Source/{ => Extensibility}/EvalWrapper/wrapper.cpp (60%) diff --git a/CNTK.sln b/CNTK.sln index dfe960499..8e03caa06 100644 --- a/CNTK.sln +++ b/CNTK.sln @@ -505,8 +505,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CIFAR-10", "CIFAR-10", "{77 Examples\Image\Miscellaneous\CIFAR-10\32to64.txt = Examples\Image\Miscellaneous\CIFAR-10\32to64.txt Examples\Image\Miscellaneous\CIFAR-10\CIFAR_convert.py = Examples\Image\Miscellaneous\CIFAR-10\CIFAR_convert.py Examples\Image\Miscellaneous\CIFAR-10\CifarConverter.py = Examples\Image\Miscellaneous\CIFAR-10\CifarConverter.py - Examples\Image\Miscellaneous\CIFAR-10\Macros.ndl = Examples\Image\Miscellaneous\CIFAR-10\Macros.ndl Examples\Image\Miscellaneous\CIFAR-10\labelsmap.txt = Examples\Image\Miscellaneous\CIFAR-10\labelsmap.txt + Examples\Image\Miscellaneous\CIFAR-10\Macros.ndl = Examples\Image\Miscellaneous\CIFAR-10\Macros.ndl Examples\Image\Miscellaneous\CIFAR-10\readme.txt = Examples\Image\Miscellaneous\CIFAR-10\readme.txt EndProjectSection EndProject @@ -679,6 +679,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{83BFF5BF EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ResNet", "ResNet", "{06D2C644-AE5F-4C30-A1F6-C78E2845AAB1}" ProjectSection(SolutionItems) = preProject + Examples\Image\Miscellaneous\ImageNet\ResNet\create_eval_model.mel = Examples\Image\Miscellaneous\ImageNet\ResNet\create_eval_model.mel Examples\Image\Miscellaneous\ImageNet\ResNet\Macros.ndl = Examples\Image\Miscellaneous\ImageNet\ResNet\Macros.ndl Examples\Image\Miscellaneous\ImageNet\ResNet\ProjWeightsGen.py = Examples\Image\Miscellaneous\ImageNet\ResNet\ProjWeightsGen.py Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_152.config = Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_152.config @@ -687,159 +688,110 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ResNet", "ResNet", "{06D2C6 Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_34.ndl = Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_34.ndl Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_50.config = Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_50.config Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_50.ndl = Examples\Image\Miscellaneous\ImageNet\ResNet\ResNet_50.ndl - Examples\Image\Miscellaneous\ImageNet\ResNet\create_eval_model.mel = Examples\Image\Miscellaneous\ImageNet\ResNet\create_eval_model.mel EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EvalWrapper", "Source\EvalWrapper\EvalWrapper.vcxproj", "{EF766CAE-9CB1-494C-9153-0030631A6340}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensibility", "Extensibility", "{60F87E25-BC87-4782-8E20-1621AAEBB113}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSEvalClient", "Source\CSEvalClient\CSEvalClient.csproj", "{41E11A59-62B2-4927-A4F8-F40B1B612C6C}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EvalWrapper", "Source\Extensibility\EvalWrapper\EvalWrapper.vcxproj", "{EF766CAE-9CB1-494C-9153-0030631A6340}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSEvalClient", "Source\Extensibility\CSEvalClient\CSEvalClient.csproj", "{41E11A59-62B2-4927-A4F8-F40B1B612C6C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Debug|Any CPU.ActiveCfg = Debug|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Debug|x64.ActiveCfg = Debug|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Debug|x64.Build.0 = Debug|x64 - {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Release|Any CPU.ActiveCfg = Release|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Release|x64.ActiveCfg = Release|x64 {E6F26F9A-FF64-4F0A-B749-CD309EE357EE}.Release|x64.Build.0 = Release|x64 - {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Debug|Any CPU.ActiveCfg = Debug|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Debug|x64.ActiveCfg = Debug|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Debug|x64.Build.0 = Debug|x64 - {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Release|Any CPU.ActiveCfg = Release|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Release|x64.ActiveCfg = Release|x64 {928ABD1B-4D3B-4017-AEF1-0FA1B4467513}.Release|x64.Build.0 = Release|x64 - {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Debug|Any CPU.ActiveCfg = Debug|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Debug|x64.ActiveCfg = Debug|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Debug|x64.Build.0 = Debug|x64 - {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Release|Any CPU.ActiveCfg = Release|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Release|x64.ActiveCfg = Release|x64 {DE3C54E5-D7D0-47AF-A783-DFDCE59E7937}.Release|x64.Build.0 = Release|x64 - {EAD17188-072C-4726-B840-A769C36DAD1B}.Debug|Any CPU.ActiveCfg = Debug|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Debug|x64.ActiveCfg = Debug|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Debug|x64.Build.0 = Debug|x64 - {EAD17188-072C-4726-B840-A769C36DAD1B}.Release|Any CPU.ActiveCfg = Release|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Release|x64.ActiveCfg = Release|x64 {EAD17188-072C-4726-B840-A769C36DAD1B}.Release|x64.Build.0 = Release|x64 - {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Debug|Any CPU.ActiveCfg = Debug|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Debug|x64.ActiveCfg = Debug|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Debug|x64.Build.0 = Debug|x64 - {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Release|Any CPU.ActiveCfg = Release|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Release|x64.ActiveCfg = Release|x64 {4701E678-5E6F-470D-B348-9CD1A2C095D1}.Release|x64.Build.0 = Release|x64 - {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Debug|Any CPU.ActiveCfg = Debug|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Debug|x64.ActiveCfg = Debug|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Debug|x64.Build.0 = Debug|x64 - {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Release|Any CPU.ActiveCfg = Release|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Release|x64.ActiveCfg = Release|x64 {EB2BE26F-6BD4-4274-971F-86D080779DD1}.Release|x64.Build.0 = Release|x64 - {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Debug|Any CPU.ActiveCfg = Debug|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Debug|x64.ActiveCfg = Debug|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Debug|x64.Build.0 = Debug|x64 - {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Release|Any CPU.ActiveCfg = Release|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Release|x64.ActiveCfg = Release|x64 {A4FC3467-4787-43E8-BBC0-D79AE56B468D}.Release|x64.Build.0 = Release|x64 - {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Debug|Any CPU.ActiveCfg = Debug|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Debug|x64.ActiveCfg = Debug|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Debug|x64.Build.0 = Debug|x64 - {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Release|Any CPU.ActiveCfg = Release|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Release|x64.ActiveCfg = Release|x64 {482999D1-B7E2-466E-9F8D-2119F93EAFD9}.Release|x64.Build.0 = Release|x64 - {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Debug|Any CPU.ActiveCfg = Debug|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Debug|x64.ActiveCfg = Debug|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Debug|x64.Build.0 = Debug|x64 - {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Release|Any CPU.ActiveCfg = Release|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Release|x64.ActiveCfg = Release|x64 {60BDB847-D0C4-4FD3-A947-0C15C08BCDB5}.Release|x64.Build.0 = Release|x64 - {B3DD765E-694E-4494-BAD7-37BBF2942517}.Debug|Any CPU.ActiveCfg = Debug|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Debug|x64.ActiveCfg = Debug|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Debug|x64.Build.0 = Debug|x64 - {B3DD765E-694E-4494-BAD7-37BBF2942517}.Release|Any CPU.ActiveCfg = Release|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Release|x64.ActiveCfg = Release|x64 {B3DD765E-694E-4494-BAD7-37BBF2942517}.Release|x64.Build.0 = Release|x64 - {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Debug|Any CPU.ActiveCfg = Debug|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Debug|x64.ActiveCfg = Debug|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Debug|x64.Build.0 = Debug|x64 - {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Release|Any CPU.ActiveCfg = Release|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Release|x64.ActiveCfg = Release|x64 {D667AF32-028A-4A5D-BE19-F46776F0F6B2}.Release|x64.Build.0 = Release|x64 - {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Debug|Any CPU.ActiveCfg = Debug|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Debug|x64.ActiveCfg = Debug|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Debug|x64.Build.0 = Debug|x64 - {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Release|Any CPU.ActiveCfg = Release|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Release|x64.ActiveCfg = Release|x64 {1D5787D4-52E4-45DB-951B-82F220EE0C6A}.Release|x64.Build.0 = Release|x64 - {014DA766-B37B-4581-BC26-963EA5507931}.Debug|Any CPU.ActiveCfg = Debug|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Debug|x64.ActiveCfg = Debug|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Debug|x64.Build.0 = Debug|x64 - {014DA766-B37B-4581-BC26-963EA5507931}.Release|Any CPU.ActiveCfg = Release|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Release|x64.ActiveCfg = Release|x64 {014DA766-B37B-4581-BC26-963EA5507931}.Release|x64.Build.0 = Release|x64 - {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Debug|Any CPU.ActiveCfg = Debug|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Debug|x64.ActiveCfg = Debug|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Debug|x64.Build.0 = Debug|x64 - {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Release|Any CPU.ActiveCfg = Release|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Release|x64.ActiveCfg = Release|x64 {33D2FD22-DEF2-4507-A58A-368F641AEBE5}.Release|x64.Build.0 = Release|x64 - {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Debug|Any CPU.ActiveCfg = Debug|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Debug|x64.ActiveCfg = Debug|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Debug|x64.Build.0 = Debug|x64 - {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Release|Any CPU.ActiveCfg = Release|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Release|x64.ActiveCfg = Release|x64 {9A2F2441-5972-4EA8-9215-4119FCE0FB68}.Release|x64.Build.0 = Release|x64 - {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Debug|Any CPU.ActiveCfg = Debug|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Debug|x64.ActiveCfg = Debug|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Debug|x64.Build.0 = Debug|x64 - {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Release|Any CPU.ActiveCfg = Release|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Release|x64.ActiveCfg = Release|x64 {62836DC1-DF77-4B98-BF2D-45C943B7DDC6}.Release|x64.Build.0 = Release|x64 - {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Debug|Any CPU.ActiveCfg = Debug|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Debug|x64.ActiveCfg = Debug|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Debug|x64.Build.0 = Debug|x64 - {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Release|Any CPU.ActiveCfg = Release|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Release|x64.ActiveCfg = Release|x64 {CE429AA2-3778-4619-8FD1-49BA3B81197B}.Release|x64.Build.0 = Release|x64 - {E6646FFE-3588-4276-8A15-8D65C22711C1}.Debug|Any CPU.ActiveCfg = Debug|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Debug|x64.ActiveCfg = Debug|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Debug|x64.Build.0 = Debug|x64 - {E6646FFE-3588-4276-8A15-8D65C22711C1}.Release|Any CPU.ActiveCfg = Release|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Release|x64.ActiveCfg = Release|x64 {E6646FFE-3588-4276-8A15-8D65C22711C1}.Release|x64.Build.0 = Release|x64 - {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Debug|Any CPU.ActiveCfg = Debug|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Debug|x64.ActiveCfg = Debug|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Debug|x64.Build.0 = Debug|x64 - {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Release|Any CPU.ActiveCfg = Release|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Release|x64.ActiveCfg = Release|x64 {9BD0A746-0BBD-45B6-B81C-053F03C26CFB}.Release|x64.Build.0 = Release|x64 - {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Debug|Any CPU.ActiveCfg = Debug|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Debug|x64.ActiveCfg = Debug|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Debug|x64.Build.0 = Debug|x64 - {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Release|Any CPU.ActiveCfg = Release|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Release|x64.ActiveCfg = Release|x64 {731312A8-6DA3-4841-AFCD-57520BA1BF8E}.Release|x64.Build.0 = Release|x64 - {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Debug|Any CPU.ActiveCfg = Debug|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Debug|x64.ActiveCfg = Debug|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Debug|x64.Build.0 = Debug|x64 - {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Release|Any CPU.ActiveCfg = Release|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Release|x64.ActiveCfg = Release|x64 {668BEED5-AC07-4F35-B3AE-EE65A7F9C976}.Release|x64.Build.0 = Release|x64 - {EF766CAE-9CB1-494C-9153-0030631A6340}.Debug|Any CPU.ActiveCfg = Debug|x64 {EF766CAE-9CB1-494C-9153-0030631A6340}.Debug|x64.ActiveCfg = Debug|x64 {EF766CAE-9CB1-494C-9153-0030631A6340}.Debug|x64.Build.0 = Debug|x64 - {EF766CAE-9CB1-494C-9153-0030631A6340}.Release|Any CPU.ActiveCfg = Release|x64 {EF766CAE-9CB1-494C-9153-0030631A6340}.Release|x64.ActiveCfg = Release|x64 {EF766CAE-9CB1-494C-9153-0030631A6340}.Release|x64.Build.0 = Release|x64 - {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|Any CPU.Build.0 = Debug|Any CPU {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|x64.ActiveCfg = Debug|x64 {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Debug|x64.Build.0 = Debug|x64 - {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|Any CPU.Build.0 = Release|Any CPU {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|x64.ActiveCfg = Release|x64 {41E11A59-62B2-4927-A4F8-F40B1B612C6C}.Release|x64.Build.0 = Release|x64 EndGlobalSection @@ -934,7 +886,7 @@ Global {850008BC-36B0-4A0A-BD0C-B6D5C2184227} = {6F4125B5-220F-4FB7-B6C4-85A966A0268C} {E6DC3B7D-303D-4A54-B040-D8DCF8C56E17} = {8C128B1D-87E0-4643-AB93-2581589AE425} {06D2C644-AE5F-4C30-A1F6-C78E2845AAB1} = {EF710C5A-E616-442A-889D-C997D39AF2E1} - {EF766CAE-9CB1-494C-9153-0030631A6340} = {DD043083-71A4-409A-AA91-F9C548DCF7EC} - {41E11A59-62B2-4927-A4F8-F40B1B612C6C} = {DD043083-71A4-409A-AA91-F9C548DCF7EC} + {EF766CAE-9CB1-494C-9153-0030631A6340} = {60F87E25-BC87-4782-8E20-1621AAEBB113} + {41E11A59-62B2-4927-A4F8-F40B1B612C6C} = {60F87E25-BC87-4782-8E20-1621AAEBB113} EndGlobalSection EndGlobal diff --git a/Source/CSEvalClient/Program.cs b/Source/CSEvalClient/Program.cs deleted file mode 100644 index 9e7ad548b..000000000 --- a/Source/CSEvalClient/Program.cs +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. -// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Microsoft.MSR.CNTK; - -namespace CSEvalClient -{ - class Program - { - private static void Main(string[] args) - { - try - { - Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, @"..\..\Examples\Image\MNIST\Data\"); - Console.WriteLine("Current Directory: {0}", Environment.CurrentDirectory); - - Console.WriteLine("Creating Model Evaluator..."); - var model = new IEvaluateModelManagedF(); - - Console.WriteLine("Initializing Model Evaluator..."); - string config = GetConfig(); - model.Init(config); - - Console.WriteLine("Loading Model..."); - string modelFilePath = Path.Combine(Environment.CurrentDirectory, @"..\Output\Models\01_OneHidden"); - model.LoadModel(modelFilePath); - - var inputs = GetDictionary("features", 28 * 28, 255); - var outputs = GetDictionary("ol.z", 10, 100); - - Console.WriteLine("Press to begin evaluating."); - Console.ReadLine(); - - Console.WriteLine("Evaluating Model..."); - List outputList = null; - outputList = model.Evaluate(inputs, "ol.z", 10); // return results - model.Evaluate(inputs, outputs); // Pass result structure - - Console.WriteLine("Destroying Model..."); - model.Destroy(); - model = null; - - Console.WriteLine("Output contents:"); - foreach (var item in outputs) - { - Console.WriteLine("Output layer: {0}", item.Key); - foreach (var entry in item.Value) - { - Console.WriteLine(entry); - } - } - } - catch (Exception ex) - { - Console.WriteLine("Error: {0}", ex); - } - - Console.WriteLine("Press to terminate."); - Console.ReadLine(); - } - - static Dictionary> GetDictionary(string key, int size, int maxValue) - { - return new Dictionary>() { { key, GetFloatArray(size, maxValue) } }; - } - - static string GetConfig() - { - string configFilePath = Path.Combine(Environment.CurrentDirectory, - @"..\Config\01_OneHidden.config"); - - var lines = System.IO.File.ReadAllLines(configFilePath); - return string.Join("\n", lines); - } - - static List GetFloatArray(int size, int maxValue) - { - Random rnd = new Random(); - return Enumerable.Range(1, size).Select(i => (float)rnd.Next(maxValue)).ToList(); - } - } -} diff --git a/Source/CSEvalClient/App.config b/Source/Extensibility/CSEvalClient/App.config similarity index 100% rename from Source/CSEvalClient/App.config rename to Source/Extensibility/CSEvalClient/App.config diff --git a/Source/CSEvalClient/CSEvalClient.csproj b/Source/Extensibility/CSEvalClient/CSEvalClient.csproj similarity index 69% rename from Source/CSEvalClient/CSEvalClient.csproj rename to Source/Extensibility/CSEvalClient/CSEvalClient.csproj index 5f1ac34be..88bc65e24 100644 --- a/Source/CSEvalClient/CSEvalClient.csproj +++ b/Source/Extensibility/CSEvalClient/CSEvalClient.csproj @@ -7,33 +7,14 @@ {41E11A59-62B2-4927-A4F8-F40B1B612C6C} Exe Properties - CSEvalClient + Microsoft.MSR.CNTK.Extensibility.Managed.CSEvalClient CSEvalClient v4.5 512 - - x64 - true - full - false - ..\..\x64\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - true - ..\..\x64\Debug\ + ..\..\..\x64\Debug\ DEBUG;TRACE full x64 @@ -42,7 +23,7 @@ true - ..\..\x64\Release\ + ..\..\..\x64\Release\ TRACE true pdbonly @@ -54,11 +35,7 @@ - - - - diff --git a/Source/Extensibility/CSEvalClient/Program.cs b/Source/Extensibility/CSEvalClient/Program.cs new file mode 100644 index 000000000..f8605707b --- /dev/null +++ b/Source/Extensibility/CSEvalClient/Program.cs @@ -0,0 +1,133 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +// Program.cs -- main C# file that contains client code to call the CLI Wrapper class. +// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.MSR.CNTK.Extensibility.Managed.CSEvalClient +{ + /// + /// Program for running model evaluations using the CLIWrapper + /// + /// + /// This program is a managed client using the CLIWrapper to run the model evaluator in CTNK. + /// It uses one of the examples provided in CNTK for evaluating the model associated with the example. + /// In order to run this program the model must already exist in the example. To create the model, + /// first run the example in /Examples/Image/MNIST. Once the model file 01_OneHidden is created, + /// you can run this client. + /// This client shows two methods for obtaining the output results from the evaluation, the first as + /// return values from the Evaluate method call (which only returns a single layer output), and the second + /// by passing the allocated output layers to the evaluate method. + /// + class Program + { + /// + /// Program entry point + /// + /// Program arguments (ignored) + private static void Main(string[] args) + { + try + { + // The examples assume the executable is running from the data folder + // We switch the current directory to the data folder (assuming the executable is in the /x64/Debug|Release folder + Environment.CurrentDirectory = Path.Combine(Environment.CurrentDirectory, @"..\..\Examples\Image\MNIST\Data\"); + + Dictionary> outputs; + + using (var model = new IEvaluateModelManagedF()) + { + // Initialize model evaluator + string config = GetConfig(); + model.Init(config); + + // Load model + string modelFilePath = Path.Combine(Environment.CurrentDirectory, @"..\Output\Models\01_OneHidden"); + model.LoadModel(modelFilePath); + + // Generate random input values in the appropriate structure and size + var inputs = GetDictionary("features", 28*28, 255); + + // We can call the evaluate method and get back the results (single layer)... + // List outputList = model.Evaluate(inputs, "ol.z", 10); + + // ... or we can preallocate the structure and pass it in (multiple output layers) + outputs = GetDictionary("ol.z", 10, 1); + model.Evaluate(inputs, outputs); + } + + Console.WriteLine("--- Output results ---"); + foreach (var item in outputs) + { + Console.WriteLine("Output layer: {0}", item.Key); + foreach (var entry in item.Value) + { + Console.WriteLine(entry); + } + } + } + catch (Exception ex) + { + Console.WriteLine("Error: {0} \n {1}", ex, ex.InnerException != null ? ex.InnerException.Message : "No Inner Exception"); + } + + Console.WriteLine("Press to terminate."); + Console.ReadLine(); + } + + /// + /// Creates a Dictionary for input entries or output allocation + /// + /// The key for the mapping + /// The number of element entries associated to the key + /// The maximum value for random generation values + /// A dictionary with a single entry for the key/values + static Dictionary> GetDictionary(string key, int size, int maxValue) + { + var dict = new Dictionary>(); + if (key != string.Empty && size >= 0 && maxValue > 0) + { + dict.Add(key, GetFloatArray(size, maxValue)); + } + + return dict; + } + + /// + /// Reads the configuration file and returns the contents as a string + /// + /// The content of the configuration file + static string GetConfig() + { + string configFilePath = Path.Combine(Environment.CurrentDirectory, + @"..\Config\01_OneHidden.config"); + + var lines = System.IO.File.ReadAllLines(configFilePath); + return string.Join("\n", lines); + } + + /// + /// Creats a list of random numbers + /// + /// The size of the list + /// The maximum value for the generated values + /// A list of random numbers + static List GetFloatArray(int size, int maxValue) + { + List list = new List(); + if (size > 0 && maxValue >= 0) + { + Random rnd = new Random(); + list.AddRange(Enumerable.Range(1, size).Select(i => (float)rnd.Next(maxValue)).ToList()); + } + + return list; + } + } +} diff --git a/Source/CSEvalClient/Properties/AssemblyInfo.cs b/Source/Extensibility/CSEvalClient/Properties/AssemblyInfo.cs similarity index 72% rename from Source/CSEvalClient/Properties/AssemblyInfo.cs rename to Source/Extensibility/CSEvalClient/Properties/AssemblyInfo.cs index e56d15336..518eae801 100644 --- a/Source/CSEvalClient/Properties/AssemblyInfo.cs +++ b/Source/Extensibility/CSEvalClient/Properties/AssemblyInfo.cs @@ -1,16 +1,21 @@ -using System.Reflection; -using System.Runtime.CompilerServices; +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.md file in the project root for full license information. +// +// AssemblyInfo.cs -- Assembly information +// +using System.Reflection; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("CSEvalClient")] -[assembly: AssemblyDescription("")] +[assembly: AssemblyDescription("Managed client using managed wrapper for CNTK evaluation model")] [assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] +[assembly: AssemblyCompany("Microsoft")] [assembly: AssemblyProduct("CSEvalClient")] -[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyCopyright("Copyright © 2016 Microsoft. All rights reserved.")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] diff --git a/Source/EvalWrapper/EvalWrapper.vcxproj b/Source/Extensibility/EvalWrapper/EvalWrapper.vcxproj similarity index 90% rename from Source/EvalWrapper/EvalWrapper.vcxproj rename to Source/Extensibility/EvalWrapper/EvalWrapper.vcxproj index 4d8a91227..5297f3467 100644 --- a/Source/EvalWrapper/EvalWrapper.vcxproj +++ b/Source/Extensibility/EvalWrapper/EvalWrapper.vcxproj @@ -43,13 +43,13 @@ true - $(VC_IncludePath);$(WindowsSDK_IncludePath);..\Common\Include; + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)Source\Common\Include .dll $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath) false - $(VC_IncludePath);$(WindowsSDK_IncludePath);..\Common\Include; + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(SolutionDir)Source\Common\Include .dll $(SolutionDir)$(Platform)\$(Configuration)\;$(LibraryPath) @@ -82,12 +82,7 @@ - - - - - {482999d1-b7e2-466e-9f8d-2119f93eafd9} - + diff --git a/Source/EvalWrapper/EvalWrapper.vcxproj.filters b/Source/Extensibility/EvalWrapper/EvalWrapper.vcxproj.filters similarity index 62% rename from Source/EvalWrapper/EvalWrapper.vcxproj.filters rename to Source/Extensibility/EvalWrapper/EvalWrapper.vcxproj.filters index a68d46eb6..9e983c31c 100644 --- a/Source/EvalWrapper/EvalWrapper.vcxproj.filters +++ b/Source/Extensibility/EvalWrapper/EvalWrapper.vcxproj.filters @@ -5,14 +5,6 @@ {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - {f89d33a1-2be0-4dee-b681-3630c6595c18} @@ -26,7 +18,7 @@ - + Common\Include diff --git a/Source/EvalWrapper/wrapper.cpp b/Source/Extensibility/EvalWrapper/wrapper.cpp similarity index 60% rename from Source/EvalWrapper/wrapper.cpp rename to Source/Extensibility/EvalWrapper/wrapper.cpp index fff9585ae..336c7ba0a 100644 --- a/Source/EvalWrapper/wrapper.cpp +++ b/Source/Extensibility/EvalWrapper/wrapper.cpp @@ -2,6 +2,9 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.md file in the project root for full license information. // +// Wrapper.cpp -- Managed code wrapping the native EvaluateModel interface +// + #include #include #include @@ -16,19 +19,21 @@ using namespace System; using namespace System::Collections::Generic; using namespace System::Collections; - using namespace Microsoft::MSR::CNTK; namespace Microsoft { namespace MSR { namespace CNTK { +namespace Extensibility { +namespace Managed { + // Used for retrieving the model appropriate for the element type (float / double) template using GetEvalProc = void(*)(IEvaluateModel**); /// Managed wrapper for the native evaluation model template -public ref class IEvaluateModelManaged +public ref class IEvaluateModelManaged : IDisposable { typedef std::pair*> MapEntry; @@ -49,24 +54,29 @@ public: } /// Initializes the model evaluation library with a CNTK configuration + /// Model configuration entries void Init(String^ config) { + if (m_eval == nullptr) + { + throw gcnew ObjectDisposedException("Object has been disposed."); + } + msclr::interop::marshal_context context; const std::string stdConfig = context.marshal_as(config); m_eval->Init(stdConfig); } - /// Destroys the model evaluation object - void Destroy() - { - m_eval->Destroy(); - } - /// Loads a model file /// The model file name to load void LoadModel(String^ modelFileName) { + if (m_eval == nullptr) + { + throw gcnew ObjectDisposedException("Object has been disposed."); + } + pin_ptr stdModelPath = PtrToStringChars(modelFileName); m_eval->LoadModel(stdModelPath); } @@ -76,49 +86,57 @@ public: /// void Evaluate(Dictionary^>^ inputs, Dictionary^>^ outputs) { + if (m_eval == nullptr) + { + throw gcnew ObjectDisposedException("Object has been disposed."); + } + std::map*> stdInputs; std::map*> stdOutputs; - for each (auto item in inputs) + try { - pin_ptr key = PtrToStringChars(item.Key); - stdInputs.insert(MapEntry(key, CopyList(item.Value))); - } + std::vector>> sharedInputVectors; + std::vector>> sharedOutputVectors; - for each (auto item in outputs) - { - pin_ptr key = PtrToStringChars(item.Key); - stdOutputs.insert(MapEntry(key, CopyList(item.Value))); - } - - m_eval->Evaluate(stdInputs, stdOutputs); - - auto enumerator = outputs->Keys->GetEnumerator(); - for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e; ii++) - { - // Retreive the layer key - enumerator.MoveNext(); - String^ key = enumerator.Current; - - std::vector &refVector = *((*ii).second); - int index = 0; - - // Copy output to CLI structure - for (std::vector::iterator ii = refVector.begin(), e = refVector.end(); ii != e; ii++) + for each (auto item in inputs) { - outputs[key][index++] = *ii; + pin_ptr key = PtrToStringChars(item.Key); + shared_ptr> ptr = CopyList(item.Value); + sharedInputVectors.push_back(ptr); + stdInputs.insert(MapEntry(key, ptr.get())); + } + + for each (auto item in outputs) + { + pin_ptr key = PtrToStringChars(item.Key); + shared_ptr> ptr = CopyList(item.Value); + sharedOutputVectors.push_back(ptr); + stdOutputs.insert(MapEntry(key, ptr.get())); + } + + m_eval->Evaluate(stdInputs, stdOutputs); + + auto enumerator = outputs->Keys->GetEnumerator(); + for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e; ii++) + { + // Retrieve the layer key + enumerator.MoveNext(); + String^ key = enumerator.Current; + + std::vector &refVector = *((*ii).second); + int index = 0; + + // Copy output to CLI structure + for (std::vector::iterator ii = refVector.begin(), e = refVector.end(); ii != e; ii++) + { + outputs[key][index++] = *ii; + } } } - - // Release the memory used - for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e; ii++) + catch (Exception^) { - delete (*ii).second; - } - - for (std::map*>::iterator ii = stdOutputs.begin(), e = stdOutputs.end(); ii != e; ii++) - { - delete (*ii).second; + throw; } } @@ -129,39 +147,38 @@ public: /// Results for specified layer List^ Evaluate(Dictionary^>^ inputs, String^ outputKey, int outputSize) { - std::map*> stdInputs; - std::map*> stdOutputs; - - // Prepare input - for each (auto item in inputs) + List^ outputs = gcnew List(outputSize); + for (int i = 0; i < outputSize; i++) { - pin_ptr key = PtrToStringChars(item.Key); - stdInputs.insert(MapEntry(key, CopyList(item.Value))); + outputs->Add(*(gcnew ElemType)); } - // Prepare output buffer - pin_ptr key = PtrToStringChars(outputKey); - stdOutputs.insert(MapEntry(key, new std::vector(outputSize))); + Dictionary^>^ outputMap = gcnew Dictionary^>(); + outputMap->Add(outputKey, outputs); - // Perform evaluation - m_eval->Evaluate(stdInputs, stdOutputs); + Evaluate(inputs, outputMap); - // Copy output to CLI structure - List^ output = gcnew List(); - std::vector* vector = stdOutputs.begin()->second; - int count = 0; - for (std::vector::iterator ii = (*vector).begin(), e = (*vector).end(); ii != e && count < outputSize; ii++, count++) + return outputMap[outputKey]; + } + + ~IEvaluateModelManaged() + { + if (m_eval == nullptr) { - output->Add(*ii); + return; } - // Release the used memory - for (std::map*>::iterator ii = stdInputs.begin(), e = stdInputs.end(); ii != e; ii++) - { - delete (*ii).second; - } + this->!IEvaluateModelManaged(); + } - return output; +protected: + !IEvaluateModelManaged() + { + if (m_eval != nullptr) + { + m_eval->Destroy(); + m_eval = nullptr; + } } private: @@ -171,14 +188,16 @@ private: /// Copies a list of element types from a CLI structure to a native structure /// The CLI list of items /// A native vector of items - std::vector* CopyList(List^ list) + shared_ptr> CopyList(List^ list) { - std::vector* lower = new std::vector(); - for each (ElemType item in list) + shared_ptr> lower(new std::vector()); + if (list != nullptr) { - lower->push_back(item); + for each (ElemType item in list) + { + lower->push_back(item); + } } - return lower; } }; @@ -207,7 +226,7 @@ public: // This method tricks the compiler into emitting the methods of the classes // Refer to https://msdn.microsoft.com/en-us/library/ms177213.aspx for an -// explanation to this insanity +// explanation to this behavior void emit() { IEvaluateModelManagedF f; @@ -215,14 +234,14 @@ void emit() f.Evaluate(nullptr, nullptr); f.Evaluate(nullptr, "", 0); f.LoadModel(""); - f.Destroy(); IEvaluateModelManagedD d; d.Init(""); d.Evaluate(nullptr, nullptr); d.Evaluate(nullptr, "", 0); d.LoadModel(""); - d.Destroy(); +} +} } } } From 27289788662e3f84f1e309a34289d4707be0086d Mon Sep 17 00:00:00 2001 From: westonplatter Date: Mon, 25 Jan 2016 11:43:45 -0700 Subject: [PATCH 05/21] grammar edit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d57c75164..c1586895e 100644 --- a/README.md +++ b/README.md @@ -27,4 +27,4 @@ Amit Agarwal, Eldar Akchurin, Chris Basoglu, Guoguo Chen, Scott Cyphers, Jasha D ## Disclaimer -CNTK is in active use at Microsoft and constantly evolving. There will be bugs in places. +CNTK is in active use at Microsoft and constantly evolving. There will be bugs. From 7c280331bf0aebec569dbd6a8f14c16556762a99 Mon Sep 17 00:00:00 2001 From: pannous Date: Mon, 25 Jan 2016 20:00:53 +0100 Subject: [PATCH 06/21] Fixed https://github.com/Microsoft/CNTK/issues/15 --- Source/Math/ConvolutionEngine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Math/ConvolutionEngine.cpp b/Source/Math/ConvolutionEngine.cpp index 75fee1ee2..7c3e1cab8 100644 --- a/Source/Math/ConvolutionEngine.cpp +++ b/Source/Math/ConvolutionEngine.cpp @@ -487,7 +487,7 @@ std::unique_ptr> ConvolutionEngineFactory>(); } - RuntimeError("Not supported convolution engine type: %d.", engType); + RuntimeError("Not supported convolution engine type: %d.", (int)engType); } template class ConvolutionEngineFactory; From b0c3c131d68d322bf667d223b18f99092f3bbb5a Mon Sep 17 00:00:00 2001 From: Jon Dehdari Date: Tue, 26 Jan 2016 00:08:35 +0100 Subject: [PATCH 07/21] spellcheck CNTK-TechReport --- .../lyx/CNTKBook_ASRDecoder_Chapter.lyx | 8 ++--- .../lyx/CNTKBook_CNTK_Adv_Chapter.lyx | 10 +++--- .../lyx/CNTKBook_CNTK_Chapter.lyx | 36 +++++++++---------- .../lyx/CNTKBook_CNTK_Programmer_Chapter.lyx | 2 +- .../lyx/CNTKBook_CN_Chapter.lyx | 16 ++++----- .../lyx/CNTKBook_ExampleSetup_Chapter.lyx | 34 +++++++++--------- 6 files changed, 53 insertions(+), 53 deletions(-) diff --git a/Documentation/CNTK-TechReport/lyx/CNTKBook_ASRDecoder_Chapter.lyx b/Documentation/CNTK-TechReport/lyx/CNTKBook_ASRDecoder_Chapter.lyx index eb02b6818..8c9af1f69 100644 --- a/Documentation/CNTK-TechReport/lyx/CNTKBook_ASRDecoder_Chapter.lyx +++ b/Documentation/CNTK-TechReport/lyx/CNTKBook_ASRDecoder_Chapter.lyx @@ -743,7 +743,7 @@ To build the TIMIT graph, only three input files are needed: the model state \end_layout \begin_layout Standard -The scripts assume each context-indepenent phone is represented by a three +The scripts assume each context-independent phone is represented by a three state, left to right, hidden markov model. The names of these states should be in a \begin_inset Quotes eld @@ -756,7 +756,7 @@ model state map file that has one line for every model. The first column is the name of the model, and subsequent columns are the names of the states, in left to right order. - The transition probabilites between these states are stored in a separate + The transition probabilities between these states are stored in a separate \begin_inset Quotes eld \end_inset @@ -859,7 +859,7 @@ To decode, the following parameters to Argon should be specified: -graph, The decoder uses a Viterbi beam search algorithm, in which unlikely hypotheses are pruned at each frame. The -beam parameter prevents unlikely hypotheses from being pursued. - Any hypothesis that differes from the best hypothesis by more than this + Any hypothesis that differs from the best hypothesis by more than this amount will be be discarded. The -max-tokens parameter controls the number of active hypotheses. If the -beam parameter causes more than max-tokens hypotheses to be generated, @@ -872,7 +872,7 @@ The decoder uses a Viterbi beam search algorithm, in which unlikely hypotheses \begin_layout Standard The -graph parameter tells Argon which compiled decoding graph should be used. - The -lm should indicate an ARPA format ngram languag emodel. + The -lm should indicate an ARPA format ngram language model. \end_layout \begin_layout Standard diff --git a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Adv_Chapter.lyx b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Adv_Chapter.lyx index f5bcedbfa..5f783a745 100644 --- a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Adv_Chapter.lyx +++ b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Adv_Chapter.lyx @@ -705,7 +705,7 @@ After defining the network, it’s important to let CNTK know what the special It also needs to know the default output nodes, evaluation nodes and training criteria nodes. Note here the specification of the nodes that require special handling - (NodesReqMultiSeqHandling) when the network is evalauted or trained with + (NodesReqMultiSeqHandling) when the network is evaluated or trained with multiple sequences, e.g., when the network itself is an RNN or the model is trained with the sequence-level criterion. Since in these cases multiple sequences will be stitched together to improve @@ -2233,7 +2233,7 @@ RowStack \end_layout \begin_layout Standard -Concatnate rows of input matrices to form a bigger matrix. +Concatenate rows of input matrices to form a bigger matrix. The resulting matrix is a sumof(rows) by m1.cols matrix. It supports variable-length input. The syntax is @@ -2898,11 +2898,11 @@ labels - the ground truth labels. The first row is the ground truth output id. The second row is the ground truth class id. The third and fourth rows are the start (inclusive) and end (exclusive) - output ids corresponding to the ground trueth class id. + output ids corresponding to the ground truth class id. \end_layout \begin_layout Itemize -mainInputInfo - contains the main information to make the classfication +mainInputInfo - contains the main information to make the classification decision. It's an inputDim by T matrix. In language model, inputDim is often the hidden layer size. @@ -4422,7 +4422,7 @@ To integrate this new layer into the model, the inputs and outputs of the After the copy any node whose connected nodes were not copied will have those connections set to an invalid value. These need to be fixed in order to have a valid model. - Before a model can be saved CNTK first checkes to see if all nodes are + Before a model can be saved CNTK first checks to see if all nodes are correctly connected. \end_layout diff --git a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx index 6695f1d13..7ee856c27 100644 --- a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx +++ b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx @@ -965,7 +965,7 @@ CLASSLSTM : the class-based long short-term memory neural network. It uses sparse input, sparse parameter and sparse output. - This is often uesd for language modeling tasks. + This is often used for language modeling tasks. \end_layout \end_deeper @@ -1768,10 +1768,10 @@ numMiniBatch4LRSearch \end_inset -: the number of minibatches used to search the minibatch size whenin adaptive +: the number of minibatches used to search the minibatch size when in adaptive minibatch size mode. Default value is 500. - It's typically set to 10-20% of the total minibatches in an epochthis is + It's typically set to 10-20% of the total minibatches in an epoch this is shared with the search for learning rate in SearchBeforeEpoch mode. \end_layout @@ -1792,10 +1792,10 @@ autoAdjustMinibatch \end_inset : enable or disable whether minibatch size is adaptively adjusted. - Default value is false.Adapative minibatch sizing will begin on epochs starting - after user minbatch sizes expcitilyspecified are complete. + Default value is false.Adaptive minibatch sizing will begin on epochs starting + after user minibatch sizes expcitilyspecified are complete. For example if the userspecifed minibatchSize=256:1024, then 256 and 1024are - used in the first 2 Epochs and adaptive minibatchsizing is used aferwards + used in the first 2 Epochs and adaptive minibatchsizing is used afterwards \end_layout @@ -1814,7 +1814,7 @@ minibatchSizeTuningFrequency \end_inset -: The number of epochs to skip, on a periodic basis, beforedynamically adjusting +: The number of epochs to skip, on a periodic basis, before dynamically adjusting the minibatch size. Default value is 1. @@ -1835,7 +1835,7 @@ minibatchSizeTuningMax \end_inset -: The maximum size allowed for anadaptively adjusted minibatch size. +: The maximum size allowed for an adaptively adjusted minibatch size. Default value is 1048576. \end_layout @@ -2669,10 +2669,10 @@ rollingWindow option reads in all feature files and stores them on disk in one large temporary binary file. - The data is randomized by running a large rollowing window over the data + The data is randomized by running a large rolling window over the data in this file and randomizing the data within the window. This method produces more thorough randomization of the data but requires - a large temprorary file written to disk. + a large temporary file written to disk. The other option is \begin_inset Quotes eld \end_inset @@ -2798,14 +2798,14 @@ labels \end_inset are the default names used by the SimpleNetworkBuilder but if the network - is designed using the Network Descrition Language (NDL), then any names + is designed using the Network Description Language (NDL), then any names can be used, as long as they each have a corresponding node in the network. \end_layout \begin_layout Standard To specify continuous-valued features, e.g. MFCC's or log mel filterbank coefficients, the following parameters should - be included in the a confguration block: + be included in the a configuration block: \end_layout \begin_layout Itemize @@ -3378,7 +3378,7 @@ nbruttsineachrecurrentiter The reader arranges same-length input sentences, up to the specified limit, into each minibatch. For recurrent networks, trainer resets hidden layer activities only at - the begining of sentences. + the beginning of sentences. Activities of hidden layers are carried over to the next minibatch if an end of sentence is not reached. Using multiple sentences in a minibatch can speed up training processes. @@ -3425,7 +3425,7 @@ wordclass This is used for class-based language modeling. An example of the class information is below. The first column is the word index. - The second column is the number of occurances, the third column is the + The second column is the number of occurrences, the third column is the word, and the last column is the class id of the word. \begin_inset listings @@ -3795,7 +3795,7 @@ nbrUttsInEachRecurrentIter The reader arranges same-length input sentences, up to the specified limit, into each minibatch. For recurrent networks, trainer resets hidden layer activities only at - the begining of sentences. + the beginning of sentences. Activities of hidden layers are carried over to the next minibatch if an end of sentence is not reached. Using multiple sentences in a minibatch can speed up training processes. @@ -4999,7 +4999,7 @@ section \end_inset – the encoderReader and decoderReader are the readers for encoder and decoder. - Similary for encoderCVReader and decoderCVReader for validation set. + Similarly for encoderCVReader and decoderCVReader for validation set. \end_layout @@ -5365,7 +5365,7 @@ deviceId \begin_layout Standard CNTK supports CPU and GPU computation. - Users can determine what device to use by setting the deviceId papameter. + Users can determine what device to use by setting the deviceId parameter. The possible values are \end_layout @@ -5509,7 +5509,7 @@ traceLevel=0 # larger values mean more output The default value is 0 and specifies minimal output. The higher the number the more output can be expected. - Currently 0 (limited output), 1 (medium ouput) and 2 (verbose output) are + Currently 0 (limited output), 1 (medium output) and 2 (verbose output) are the only values supported. \end_layout diff --git a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Programmer_Chapter.lyx b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Programmer_Chapter.lyx index 0b3d6899d..fbe07a008 100644 --- a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Programmer_Chapter.lyx +++ b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Programmer_Chapter.lyx @@ -3186,7 +3186,7 @@ s().GetNumCols() != 1) \begin_layout Plain Layout - throw std::logic_error("The left value of ScaleNode must be a scarlar + throw std::logic_error("The left value of ScaleNode must be a scalar value."); \end_layout diff --git a/Documentation/CNTK-TechReport/lyx/CNTKBook_CN_Chapter.lyx b/Documentation/CNTK-TechReport/lyx/CNTKBook_CN_Chapter.lyx index a67f3cd95..78fbd8367 100644 --- a/Documentation/CNTK-TechReport/lyx/CNTKBook_CN_Chapter.lyx +++ b/Documentation/CNTK-TechReport/lyx/CNTKBook_CN_Chapter.lyx @@ -1816,11 +1816,11 @@ sly. In this algorithm, all the nodes whose children have not been computed are in the waiting set and those whose children are computed are in the ready set. - At the beginning, all non-leaf descendents of + At the beginning, all non-leaf descendants of \begin_inset Formula $root$ \end_inset - are in the waiting set and all leaf descendents are in the ready set. + are in the waiting set and all leaf descendants are in the ready set. The scheduler picks a node from the ready set based on some policy, removes it from the ready set, and dispatches it for computation. Popular policies include first-come/first-serve, shortest task first, and @@ -2015,7 +2015,7 @@ status open \begin_inset Formula $waiting$ \end_inset - is initialized to include all non-leaf descendents of + is initialized to include all non-leaf descendants of \begin_inset Formula $root$ \end_inset @@ -2061,7 +2061,7 @@ status open \begin_inset Formula $ready$ \end_inset - is initialized to include all leaf descendents of + is initialized to include all leaf descendants of \begin_inset Formula $root$ \end_inset @@ -3412,7 +3412,7 @@ status open \end_inset -Decide the order to compute the gradient at all descendents of +Decide the order to compute the gradient at all descendants of \begin_inset Formula $node$ \end_inset @@ -8892,7 +8892,7 @@ CRF \color none CRF stands for conditional random fields. This node does sequence-level training, using CRF criterion. - This node has three nputs. + This node has three inputs. The first is the label \family default \series bold @@ -10198,7 +10198,7 @@ reference "fig:CN-WithDelayNode" A simple way to do forward computation and backpropagation in a recurrent network is to unroll all samples in the sequence over time. Once unrolled, the graph is expanded into a DAG and the forward computation - and gradient calcalclation algorithms we just discussed can be directly + and gradient calculation algorithms we just discussed can be directly used. This means, however, all computation nodes in the CN need to be computed sample by sample and this significantly reduces the potential of parallelizatio @@ -10318,7 +10318,7 @@ key "StronglyConnectedComponents-Hopcroft+1983" in the CN and the CN is reduced to a DAG. All the nodes inside each loop (or composite node) can be unrolled over time and also reduced to a DAG. - For all these DAGs the forward computation and backprogation algorithms + For all these DAGs the forward computation and backpropagation algorithms we discussed in the previous sections can be applied. The detailed procedure in determining the forward computation order in the CN with arbitrary recurrent connections is described in Algorithm diff --git a/Documentation/CNTK-TechReport/lyx/CNTKBook_ExampleSetup_Chapter.lyx b/Documentation/CNTK-TechReport/lyx/CNTKBook_ExampleSetup_Chapter.lyx index d8a4cab35..a3aac49ce 100644 --- a/Documentation/CNTK-TechReport/lyx/CNTKBook_ExampleSetup_Chapter.lyx +++ b/Documentation/CNTK-TechReport/lyx/CNTKBook_ExampleSetup_Chapter.lyx @@ -97,7 +97,7 @@ ns. All examples are based on the TIMIT corpus for phonetic recognition but can easily be modified for use for large vocabulary continuous speech recogniti on. - The only significant change is that context-indepenent phonetic states + The only significant change is that context-independent phonetic states used in the TIMIT example would be replaced by context-dependent senone targets for large vocabulary tasks. We note that these examples are not meant to be representative of state @@ -146,10 +146,10 @@ SimpleNetworkBuilder will also be monitored during training using the evalCriterion parameter. The input data will be mean and variance normalized since applyMeanVarNorm has been set to true. - In addtion, if needPrior is set to true, the prior probablities of the + In addition, if needPrior is set to true, the prior probabilities of the labels will be computed and a ScaledLogLikelihood node in the network will be automatically created. - This is important if this netwok will be used to generate acoustic scores + This is important if this network will be used to generate acoustic scores in a speech recognition decoder. \end_layout @@ -838,7 +838,7 @@ SquareError Below is a snippet from the NDL file for this example. This autoencoder has three hidden layers including a middle bottleneck layer of 64 neurons. - A macro is defined to peform mean and variance normalization and it is + A macro is defined to perform mean and variance normalization and it is applied to both the input and target features. Also, \end_layout @@ -1123,7 +1123,7 @@ discriminative pre-training \begin_layout Standard It is well known that deep networks can be difficult to optimize, especially when a limited amount of training data is available. - As a result, a number of aproaches to initializing the parameters of these + As a result, a number of approaches to initializing the parameters of these networks have been proposed. One of these methods is known as discriminative pre-training. In this approach, a network with a single hidden layer is trained starting @@ -1526,7 +1526,7 @@ multi-task learning \begin_layout Standard One interesting approach to network training is multi-task learning, where - the network is trained to optmize two objective functions simultaneously. + the network is trained to optimize two objective functions simultaneously. This can be done in CNTK through the appropriate use of NDL. Let's assume that we have a network specified in NDL that has three hidden layers and output of the third hidden layer is defined as L3. @@ -1856,7 +1856,7 @@ TIMIT.statelist" \begin_layout Standard The NDL for constructing a network with these inputs and outputs can be done in a number of ways. - One way is to contruct a macro that constructs a layer that takes two inputs, + One way is to construct a macro that constructs a layer that takes two inputs, as follows: \end_layout @@ -1984,7 +1984,7 @@ L1 = SBFF2(featInput1, HiddenDim, FeatDim1, featInput2, FeatDim2) The rest of the hidden layers and the output layer with a cross entropy objective function would be the same as previous examples. - Notice that the names and dimensionality of the input adn output data have + Notice that the names and dimensionality of the input and output data have to the same in both the NDL model description and the reader configuration. \end_layout @@ -2623,7 +2623,7 @@ layerSizes \end_inset =10000:200:10000. - Sizes of input, hidden and ouput layers. + Sizes of input, hidden and output layers. Input layer size is equal to vocabulary size, hidden layer is normally in the range of 50 to 500, output layer size is the vocabulary size. \end_layout @@ -2640,7 +2640,7 @@ uniformInit \end_inset =true. - Whether to use uniformly randomizied values for initial paramter weights. + Whether to use uniformly randomized values for initial parameter weights. \end_layout \begin_layout Itemize @@ -2898,7 +2898,7 @@ learnRateDecreaseFactor \end_inset =0.5. - Learning rate decrese factor. + Learning rate decrease factor. \end_layout \end_deeper @@ -2963,7 +2963,7 @@ t word_class \end_layout \begin_layout Standard -word_id is a unique non-negative interger, frequency is the frequency of +word_id is a unique non-negative integer, frequency is the frequency of word (optional), word_string is the word string (low frequent words may be mapped to ), and word_class is the class id of word. Word class can be derived using frequency based heuristics @@ -4254,7 +4254,7 @@ wordContext \end_inset =0:1:2 : this specifies the time indices for forming a context window. - In this example, this setup coresponds to using the current input, the + In this example, this setup corresponds to using the current input, the next input, and the input after the next input for a context window of size 3. User can also use other cases such as wordcontext=0:-1:1 to form a context @@ -4344,7 +4344,7 @@ BOS \begin_inset Quotes erd \end_inset - : this specifies the symbol of sequence begining. + : this specifies the symbol of sequence beginning. \end_layout \begin_layout Itemize @@ -4634,7 +4634,7 @@ outputs:labels \end_inset : this specifies which nodes to output results. - These node names are pre-spefied in CNTK's simple network builder. + These node names are pre-specified in CNTK's simple network builder. The node \begin_inset Quotes eld \end_inset @@ -4643,7 +4643,7 @@ outputs \begin_inset Quotes erd \end_inset - is the node that output activies before softmax. + is the node that output activates before softmax. The node \begin_inset Quotes eld \end_inset @@ -4836,7 +4836,7 @@ output.rec.txt \begin_inset Quotes erd \end_inset - : the file name for writting decode results from LUSequenceWriter. + : the file name for writing decode results from LUSequenceWriter. \end_layout From 9c811912b1773874ec88c59c76134bd1ffa0b500 Mon Sep 17 00:00:00 2001 From: Jon Dehdari Date: Tue, 26 Jan 2016 00:25:38 +0100 Subject: [PATCH 08/21] spellcheck CNTK-TechReport --- Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx index 7ee856c27..398d81b24 100644 --- a/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx +++ b/Documentation/CNTK-TechReport/lyx/CNTKBook_CNTK_Chapter.lyx @@ -1771,7 +1771,7 @@ numMiniBatch4LRSearch : the number of minibatches used to search the minibatch size when in adaptive minibatch size mode. Default value is 500. - It's typically set to 10-20% of the total minibatches in an epoch this is + It's typically set to 10-20% of the total minibatches in an epoch. This is shared with the search for learning rate in SearchBeforeEpoch mode. \end_layout @@ -1792,8 +1792,8 @@ autoAdjustMinibatch \end_inset : enable or disable whether minibatch size is adaptively adjusted. - Default value is false.Adaptive minibatch sizing will begin on epochs starting - after user minibatch sizes expcitilyspecified are complete. + Default value is false. Adaptive minibatch sizing will begin on epochs starting + after user minibatch sizes explicitly specified are complete. For example if the userspecifed minibatchSize=256:1024, then 256 and 1024are used in the first 2 Epochs and adaptive minibatchsizing is used afterwards From 56a2a15f64676ea4c0e0a0a681a57b19a46f64c6 Mon Sep 17 00:00:00 2001 From: Chris Basoglu Date: Mon, 25 Jan 2016 20:53:43 -0800 Subject: [PATCH 09/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c1586895e..6315e42db 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ The figure below compares processing speed (frames processed per second) of CNTK If you used this toolkit or part of it to do your research, please cite the work as: -Amit Agarwal, Eldar Akchurin, Chris Basoglu, Guoguo Chen, Scott Cyphers, Jasha Droppo, Adam Eversole, Brian Guenter, Mark Hillebrand, Ryan Hoens, Xuedong Huang, Zhiheng Huang, Vladimir Ivanov, Alexey Kamenev, Philipp Kranen, Oleksii Kuchaiev, Wolfgang Manousek, Avner May, Bhaskar Mitra, Olivier Nano, Gaizka Navarro, Alexey Orlov, Hari Parthasarathi, Baolin Peng, Marko Radmilac, Alexey Reznichenko, Frank Seide, Michael L. Seltzer, Malcolm Slaney, Andreas Stolcke, Huaming Wang, Yongqiang Wang, Kaisheng Yao, Dong Yu, Yu Zhang, Geoffrey Zweig (in alphabetical order), ["An Introduction to Computational Networks and the Computational Network Toolkit"](http://research.microsoft.com/apps/pubs/?id=226641), Microsoft Technical Report MSR-TR-2014-112, 2014. +Amit Agarwal, Eldar Akchurin, Chris Basoglu, Guoguo Chen, Scott Cyphers, Jasha Droppo, Adam Eversole, Brian Guenter, Mark Hillebrand, T. Ryan Hoens, Xuedong Huang, Zhiheng Huang, Vladimir Ivanov, Alexey Kamenev, Philipp Kranen, Oleksii Kuchaiev, Wolfgang Manousek, Avner May, Bhaskar Mitra, Olivier Nano, Gaizka Navarro, Alexey Orlov, Hari Parthasarathi, Baolin Peng, Marko Radmilac, Alexey Reznichenko, Frank Seide, Michael L. Seltzer, Malcolm Slaney, Andreas Stolcke, Huaming Wang, Yongqiang Wang, Kaisheng Yao, Dong Yu, Yu Zhang, Geoffrey Zweig (in alphabetical order), ["An Introduction to Computational Networks and the Computational Network Toolkit"](http://research.microsoft.com/apps/pubs/?id=226641), Microsoft Technical Report MSR-TR-2014-112, 2014. ## Disclaimer From aace504d8ca52521aa7952e3f4f00f193b96bd0d Mon Sep 17 00:00:00 2001 From: Amit Date: Mon, 25 Jan 2016 21:55:48 -0800 Subject: [PATCH 10/21] Added -g and -rdynamic compiler/linker options for release mode builds on linux to enable call stack generation in release builds --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4b044c23a..d3aee1798 100644 --- a/Makefile +++ b/Makefile @@ -172,7 +172,8 @@ ifeq ("$(BUILDTYPE)","release") GENCODE_FLAGS := $(GENCODE_SM20) $(GENCODE_SM30) $(GENCODE_SM35) $(GENCODE_SM50) endif - CXXFLAGS += -O4 + CXXFLAGS += -g -O4 + LDFLAGS += -rdynamic CPPFLAGS += -DNDEBUG CUFLAGS += -O3 -use_fast_math -lineinfo $(GENCODE_FLAGS) endif From 0818f4f1f7f8cc7afda7f43d580101a53f797b3f Mon Sep 17 00:00:00 2001 From: stonebig Date: Tue, 26 Jan 2016 18:27:37 +0100 Subject: [PATCH 11/21] python 3 compatibility fix --- Examples/Image/MNIST/AdditionalFiles/mnist_convert.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/Image/MNIST/AdditionalFiles/mnist_convert.py b/Examples/Image/MNIST/AdditionalFiles/mnist_convert.py index bf05a0702..08ffb3980 100644 --- a/Examples/Image/MNIST/AdditionalFiles/mnist_convert.py +++ b/Examples/Image/MNIST/AdditionalFiles/mnist_convert.py @@ -7,9 +7,9 @@ import struct import numpy as np def loadData(src, cimg): - print 'Downloading ' + src + print ('Downloading ' + src) gzfname, h = urllib.urlretrieve(src, './delete.me') - print 'Done.' + print ('Done.') try: with gzip.open(gzfname) as gz: n = struct.unpack('I', gz.read(4)) From cafbf2d57200daf7f10e18193fd5236944413157 Mon Sep 17 00:00:00 2001 From: stonebig Date: Tue, 26 Jan 2016 18:33:10 +0100 Subject: [PATCH 12/21] python 3 compatibility fix --- .../Miscellaneous/CIFAR-10/CIFAR_convert.py | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Examples/Image/Miscellaneous/CIFAR-10/CIFAR_convert.py b/Examples/Image/Miscellaneous/CIFAR-10/CIFAR_convert.py index fdd65f2c1..2f1786315 100644 --- a/Examples/Image/Miscellaneous/CIFAR-10/CIFAR_convert.py +++ b/Examples/Image/Miscellaneous/CIFAR-10/CIFAR_convert.py @@ -38,33 +38,33 @@ def readBatch(src, outFmt): return np.hstack((np.reshape(d['labels'], (len(d['labels']), 1)), feat)) def loadData(src, outFmt): - print 'Downloading ' + src + print ('Downloading ' + src) fname, h = urllib.urlretrieve(src, './delete.me') - print 'Done.' + print ('Done.') try: - print 'Extracting files...' + print ('Extracting files...') with tarfile.open(fname) as tar: tar.extractall() - print 'Done.' - print 'Preparing train set...' + print ('Done.') + print ('Preparing train set...') trn = np.empty((0, NumFeat + 1)) for i in range(5): batchName = './cifar-10-batches-py/data_batch_{0}'.format(i + 1) trn = np.vstack((trn, readBatch(batchName, outFmt))) - print 'Done.' - print 'Preparing test set...' + print ('Done.') + print ('Preparing test set...') tst = readBatch('./cifar-10-batches-py/test_batch', outFmt) - print 'Done.' + print ('Done.') finally: os.remove(fname) return (trn, tst) def usage(): - print 'Usage: CIFAR_convert.py [-f ] \n where format can be either cudnn or legacy. Default is cudnn.' + print ('Usage: CIFAR_convert.py [-f ] \n where format can be either cudnn or legacy. Default is cudnn.') def parseCmdOpt(argv): if len(argv) == 0: - print "Using cudnn output format." + print ("Using cudnn output format.") return "cudnn" try: opts, args = getopt.getopt(argv, 'hf:', ['help', 'outFormat=']) @@ -78,7 +78,7 @@ def parseCmdOpt(argv): elif opt in ('-f', '--outFormat'): fmt = arg if fmt != 'cudnn' and fmt != 'legacy': - print 'Invalid output format option.' + print ('Invalid output format option.') usage() sys.exit(1) return fmt @@ -86,9 +86,9 @@ def parseCmdOpt(argv): if __name__ == "__main__": fmt = parseCmdOpt(sys.argv[1:]) trn, tst = loadData('http://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz', fmt) - print 'Writing train text file...' + print ('Writing train text file...') np.savetxt(r'./Train.txt', trn, fmt = '%u', delimiter='\t') - print 'Done.' - print 'Writing test text file...' + print ('Done.') + print ('Writing test text file...') np.savetxt(r'./Test.txt', tst, fmt = '%u', delimiter='\t') - print 'Done.' + print ('Done.') From cd93e6f2538559206bc2f9bd80879d9eceffd55c Mon Sep 17 00:00:00 2001 From: Sam Abrahams Date: Mon, 25 Jan 2016 13:07:01 -0800 Subject: [PATCH 13/21] Replace .docx documentation files with Markdown Went through and makes a GitHub flavor Markdown file for each .docx file in the Documentation/Documents folder. There will inevitably need to be changes and upgrades to this, but I think it's a good first step. As requested, this also removes the .docx files from Documentation/Documents as well. --- .../Documents/Configuration Files.docx | Bin 93512 -> 0 bytes .../Documents/Configuration Files.md | 1265 +++++++++++++++++ .../Documents/External Buffer Behavior.docx | Bin 26230 -> 0 bytes .../Documents/External Buffer Behavior.md | 69 + .../Documents/Model Editing Language.docx | Bin 58153 -> 0 bytes .../Documents/Model Editing Language.md | 750 ++++++++++ .../Network Description Language.docx | Bin 44296 -> 0 bytes .../Documents/Network Description Language.md | 900 ++++++++++++ 8 files changed, 2984 insertions(+) delete mode 100644 Documentation/Documents/Configuration Files.docx create mode 100644 Documentation/Documents/Configuration Files.md delete mode 100644 Documentation/Documents/External Buffer Behavior.docx create mode 100644 Documentation/Documents/External Buffer Behavior.md delete mode 100644 Documentation/Documents/Model Editing Language.docx create mode 100644 Documentation/Documents/Model Editing Language.md delete mode 100644 Documentation/Documents/Network Description Language.docx create mode 100644 Documentation/Documents/Network Description Language.md diff --git a/Documentation/Documents/Configuration Files.docx b/Documentation/Documents/Configuration Files.docx deleted file mode 100644 index 60803a77135e00b082f7b330a0a2e172780c042b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 93512 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

+%C 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⋙$1#xB<+ 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 diff --git a/Documentation/Documents/Configuration Files.md b/Documentation/Documents/Configuration Files.md new file mode 100644 index 000000000..ef447fc26 --- /dev/null +++ b/Documentation/Documents/Configuration Files.md @@ -0,0 +1,1265 @@ +# Configuration Files + +## Overview + + +The Computational Network ToolKit (CNTK) consists of a number of components to complete machine learning task. These include Network Builders, Network Trainers, Data Readers, and various other components. Most of these components need some configuration information available in order to function, and these configuration parameters are provided through configuration files in CNTK. + +### Configuration Data + +Configuration files are collections of name-value pairs. The configuration data can be one of the following types: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
config typeExamplesDescription
SimpledeviceId=autoA single value is assigned to the configuration parameter
Array + +
minibatchSize=256:512:512:512:1024
+minibatchSize=256:512*3:1024
+files=(;c:\data.txt;c:\labels.txt)
+
+ +
+
    +
  • a configuration parameter is assigned an array of values which need not be of a uniform type
  • +
  • a ‘:’ is the default separator for arrays, but may be changed by enclosing the array values in parenthesis and placing the new separator character immediately following the open parenthesis
  • +
  • an ‘*’ repeat character allows a particular value to be repeated multiple times in the array
  • +
+
Parameter Set + +
section1=[id=1;size=256]
section2=[
  subsection=[string="hi";num=5]
  value=1e-10
  array=10:"this is a test":1.25
]
+
+ +
+
    +
  • Parameter sets contain sets of configuration parameters of any type
  • +
  • Parameter sets can be nested
  • +
  • The default separator for parameter sets is the ‘;’ if multiple items are included on one line
  • +
  • Line separators also serve as separators for items
  • +
+
+ +### Organization + +In CNTK configuration files Parameter Sets are organized in a hierarchal fashion. The actual data values are not evaluated until a CNTK components requests the value. When a value is requested, by a component, it will search that components section of the configuration file, if the value is not found, it will continue looking in the parent parameter set and continue looking in parent parameter sets until the parameter is found, or the top level of the configuration hierarchy is reached without a match. + +### Default Values + +Most parameters in configuration files have a default value which will be used if no configuration value is specified. If there is no default value and the value cannot be found in a search, an exception will be displayed and the program will exit. + +### Repeated Values + +If a parameter name is specified more than once, the last value set to that value is the one that will be maintained. The only exception to this is in parameter sets, which are surrounded by ‘\[‘ square braces ‘\]’, in these cases the values inside the braces are considered to be a parameter set, and will be added to the currently existing parameter set. For example: + +``` +params=[a=1;b=2;c=3] +params=[c=5;d=6;e=7] +``` + +is effectively equal to: + +``` +params=[a=1;b=2;c=5;d=6;e=7] +``` + +this “append” processing is not used for arrays elements, and the entire array will be replaced if it is set multiple times. + +### Comments + +The ‘\#’ character signifies the beginning of a comment, everything that occurs after the ‘\#’ is ignored. The ‘\#’ must be preceded by whitespace or be at the beginning of the line to be interpreted as a comment. The following are valid comments, and an example of a value that will not be interpreted as a comment: + +``` +#commands used will be appended the stderr name to create a path +stderr=c:\cntk\log\cntk # "_mnistTrain_mnistTest.log" would be appended +# the parameter below is set to infinity, the ‘#’ in ‘1#INF’ is not a comment marker +var = 1#INF +``` + +## Example + +This section will go through a sample configuration file that creates a simple DNN (Deep Neural Network), trains the network and then tests the results. If you would rather see the Comprehensive documentation of configuration parameters skip to the Configuration Reference section. + +Here is a simple example of a configuration file: + +``` +# sample configuration file for CNTK command=mnistTrain:mnistTest #global parameters, all commands use these values unless overridden at a higher level precision=float deviceId=auto #commands used will be appended the stderr name to create a path stderr=c:\cntk\log\cntk # “_mnistTrain_mnistTest.log” would be appended traceLevel=0 # larger values mean more output ndlMacros=C:\cntk\config\DefaultMacros.ndl modelPath=c:\cntk\model\sample.dnn labelMappingFile=c:\cntk\data\mnist\labels.map mnistTrain=[ action=train minibatchSize=32 epochSize=60000 NDLNetworkBuilder=[ networkDescription=c:\cntk\config\sample.ndl run=ndlMacroUse ] SGD=[ #modelPath - moved to root level to share with mnistTest learningRatesPerMB=0.001 maxEpochs=50 ] reader=[ readerType=UCIFastReader file=c:\cntk\data\mnist\mnist_train.txt features=[ dim=784 start=1 ] labels=[ dim=1 start=0 labelDim=10 ] ] ] mnistTest=[ action=eval maxEpochs=1 epochSize=10000 minibatchSize=1000 reader=[ readerType=UCIFastReader randomize=None file=c:\data\mnist\mnist_test.txt features=[ dim=784 start=1 ] labels=[ dim=1 start=0 labelDim=10 ] ] ] +``` + +### Commands and actions + +The first thing at the top of the configuration is the command: + +``` +command=mnistTrain:mnistTest +``` + +This command instructs CNTK to execute the **mnistTrain** section of the config file, followed by mnistTest. Each of these Config sections has an action associated with it: + +``` +mnistTrain=[ action=train … +``` +The **mnistTrain** section will execute the **train** action, and the **mnistTest** section will execute **eval**. The names of the sections is arbitrary, but the configuration parameter names must be command and action. + +### Precision + +The accuracy and speed of obtaining results in any machine learning experiment will be affected by the precision of the floating point values that are used in creating the model. The precision is specified as follows: + +``` +precision=float +``` + +The other option is double and will be more precise, but slower depending on your hardware. Most GPU hardware is much faster using float precision, but some experiments require the added precision of double. + +### Device Identifier + +CNTK supports CPU and GPU computation, and the determination of which device should be used is based on the **deviceId** parameter. The default value and the value used in the example config is: + +``` +deviceId=auto +``` + +This setting will pick the best device available on the machine. If multiple GPUs are available, it will choose the fastest, least busy GPU, and a CPU if no usable GPUs can be found. The following settings can be used for **deviceId**: + +Value | Description +---|--- +auto | choose the best GPU, or CPU if no usable GPU is available. +cpu | use the CPU for all computation +0 | The device Identifier (as used in CUDA) for the GPU device you wish to use (1, 2, etc.) +all | Use all the available GPU devices (will use PTask engine if more than one GPU is present) +\*2 | Will use the 2 best GPUs, any number up to the number of available GPUs on the machine can be used. (will use PTask engine) + +### Log Files + +Log files are redirection of the normal standard error output. All log information is sent to standard error, and will appear on the console screen unless the stderr parameter is defined, or some other form of user redirection is active. The stderr parameter defines the directory and the prefix for the log file. The suffix is defined by what commands are being run. As an example if “abc” is the setting “abc\_mnistTrain.log” would be the log file name. It is important to note that this file is overwritten on subsequent executions if the stderr parameter and the command being run are identical. + +``` +#commands used will be appended the stderr name to create a path stderr=c:\cntk\log\cntk # “_mnistTrain_mnistTest.log” would be appended traceLevel=0 # larger values mean more output +``` + +The **traceLevel** parameter is uniformly used by the code in CNTK to specify how much extra output (verbosity) is desired. The default value is 0 (zero) and specifies minimal output, the higher the number the more output can be expected. Currently 0-limited output, 1-medium ouput, 2-verbose output are the only values supported. + +### Top Level Parameters + +It is often advantageous to set some values at the top level of the config file. This is because config searches start with the target section and continue the search to higher level sections. If the same parameter is used in multiple sections putting the parameter at a higher level where both sections can share it can be a good idea. In our example the following parameters are used by both the train and the test step: + +``` +ndlMacros=C:\cntk\config\DefaultMacros.ndl modelPath=c:\cntk\model\sample.dnn labelMappingFile=c:\cntk\data\mnist\labels.map +``` + +It can also be advantageous to specify parameters that often change all in one area, rather than separated into the sections to which the parameters belong. These commonly modified parameters can even be placed in a separate file if desired. See the layered config files in the reference section for more information. + +### Command Section + +A command section is a top level section of the configuration file that contains an action parameter. The command parameter at the root level determines which command sections will be executed, and in what order. The **mnistTrain** command section uses the **train** **action** to train a Deep Neural Network (DNN) using Stochastic Gradient Descent (SGD). It uses the Network Description Language (NDL) to define the network, and the UCIFastReader component to obtain its data. All the necessary information to complete this training is included in the command section. There are many other parameters that could be provided, but most have reasonable default values that have been found to provide good results for many datasets. + +Under the command section there may be one or more sub-sections. These sub-sections vary by the **action** specified in the command section. In our example we used the **train action**, which includes the following subsections: + +sub-section | Options | Description +---|---|--- +**Network Builder** | SimpleNetworkBuilder | Creates simple layer-based networks with various options + | NDLNetworkBuilder | Create a network defined in NDL (Network Description Language). This allows arbitrary networks to be created and offers more control over the network structure. +**Trainer** | SGD | Stochastic Gradient Descent trainer, currently this is the only option provided with CNTK +**Reader** | UCIFastReader | Reads the text-based UCI format, which contains labels and features combined in one file + | HTKMLFReader | Reads the HTK/MLF format files, often used in speech recognition applications + | BinaryReader | Reads files in a CNTK Binary format. It is also used by UCIFastReader to cache the dataset in a binary format for faster processing. + | SequenceReader | Reads text-based files that contain word sequences, for predicting word sequences. + | LUSequenceReader | Reads text-based files that contain word sequences, used for language understanding. + +For the Network Builder and the Trainer the existence of the sub-section name tells the train action which component to use. For example, **NDLNetworkBuilder** is specified in our example, so CNTK will use the NDL Network Builder to define the network. Similarly **SGD** is specified, so that trainer will be used. The reader sub-section is a little different, and is always called **reader**, the **readerType** parameter in the sub-section defines which reader will actually be used. Readers are implemented as separate DLLs, and the name of the reader is also the name of the DLL file that will be loaded. + +``` +mnistTrain=[ action=train minibatchSize=32 epochSize=60000 NDLNetworkBuilder=[ networkDescription=c:\cntk\config\sample.ndl run=ndlMacroUse ] SGD=[ #modelPath - moved to root level to share with mnistTest learningRatesPerMB=0.001 maxEpochs=50 ] reader=[ readerType=UCIFastReader file=c:\cntk\data\mnist\mnist_train.txt features=[ dim=784 start=1 ] labels=[ dim=1 start=0 labelDim=10 ] ] ] +``` + +The rest of the parameters in the mnistTrain Command Section are briefly explained here, more details about the parameters available for each component are available in the Configuration Reference section of this document. + +### SGD Parameters + +The parameters at the top of the command section are actually SGD parameters. These parameters have moved up a level in the configuration mainly to provide easier visibility to the parameters. Configuration searches continue searching parents until the root of the configuration is reached or the parameter is found. + +``` +minibatchSize=32 +epochSize=60000 +``` + +**minibatchSize** is the number of records that will be taken from the dataset and processed at once. There is often a tradeoff in training accuracy and the size of the minibatch. Particularly for GPUs, a larger minibatch is usually better, since the GPUs are most efficient when doing operations on large chunks of data. For large dataset values that are powers of 2 are most often specified, 512 and 1024 are often good choices to start with. Since the MNIST dataset is so small, we have chosen a smaller minibatch size for the example. + +**epochSize** is the number of dataset records that will be processed in a training pass. It is most often set to be the same as the dataset size, but can be smaller or larger that the dataset. It defaults to the size of the dataset if not present in the configuration file. It can also be set to zero for SGD, which has the same meaning. + +``` +SGD=[ #modelPath - moved to root level to share with mnistTest learningRatesPerMB=0.001 maxEpochs=50 ] +``` + +**modelPath** is the path to the model file, and will be the name used when a model is completely trained. For epochs prior to the final model a number will be appended to the end signifying the epoch that was saved (i.e. myModel.dnn.5). These intermediate files are important to allow the training process to restart after an interruption. Training will automatically resume at the first non-existent epoch when training is restarted. + +**learningRatesPerMB** is the learning rate per minibatch used by SGD to update parameter matrices. This parameter is actually an array, and can be used as follows: 0.001\*10:0.0005 to specify that for the first 10 epochs 0.001 should be used and then 0.0005 should be used for the remainder of the epochs. + +**maxEpochs** is the total number of epochs that will be run before the model is considered complete. + +### NDLNetworkBuilder Parameters + +The NDL (Network Description Language) Network Builder component will be used by this config because the NDLNetworkBuilder section is present. Had there been a SimpleNetworkBuilder section instead, that network builder would be used. + +**networkDescription** is the file path of the NDL script to execute. If there is no networkDescription file specified then the NDL is assumed to be in the same configuration file as the NDLNetworkBuilder subsection, specified with the “run” parameter. Note that only one file path may be specified via the “networkDescription” parameter; to load multiple files of macros, use the “ndlMacros” parameter. + +**run** is the section containing the NDL that will be executed. If using an external file via the “networkDescription” parameter, as in the example, the **run** parameter identifies the section in that file that will be executed as an NDL script. This parameter overrides any **run** parameters that may already exist in the file. If no **networkDescription** file is specified, **run** identifies a section in the current configuration file. It must exist where a regular configuration search will find it (peer or closer to the root of the hierarchy) + +**load** specifies what sections of NDL to load. Multiple sections can be specified via a “:” separated list. The sections specified by **load** are generally used to define macros, for use by the **run** section. If using an external file via the **networkDescription** parameter, as in the example, the **load** parameter identifies the section(s) in that file to load. This parameter overrides any **load** parameters that may already exist in the file. If no **networkDescription** file is specified, **load** identifies a section in the current configuration file. It must exist where a regular configuration search will find it (peer or closer to the root of the hierarchy) + +**ndlMacros** is the file path where NDL macros may be loaded. This parameter is usually used to load a default set of NDL macros that can be used by all NDL scripts. Multiple NDL files, each specifying different sets of macros, can be loaded by specifying a “+” separated list of file paths for this “ndlMacros” parameters. In order to share this parameter with other Command Sections which also expect an “ndlMacros” parameter (eg, for MEL scripts), one should define it at the root level of the configuration file. + +**randomSeedOffset** is a parameter which allows you to run an experiment with a different random initializations to the learnable parameters which are meant to be initialized randomly (either uniform or Gaussian). Whatever non-negative number is specified via “randomSeedOffset” will be added to the seed which would have otherwise been used. The default value is 0. + +### Reader Parameters + +The reader section is used for all types of readers and the **readerType** parameter identifies which reader will be used. For our example, we are using the UCIFastReader, which reads text-based UCI format data. The format of UCI data is a line of space-delimited floating point feature and label values for each data record. The label information is either at the beginning or the end of each line, if label information is included in the dataset. + +``` +readerType=UCIFastReader +``` + +Each of the readers uses the same interface into CNTK, and each reader is implemented in a separate DLL. There are many parameters in the reader section that are used by all the different types of readers, and some are specific to a particular reader. Our example reader section is as follows: + +``` +reader=[ readerType=UCIFastReader file=c:\cntk\data\mnist\mnist_train.txt features=[ dim=784 start=1 ] labels=[ dim=1 start=0 labelDim=10 ] ] +``` + +The two sub-sections in the reader section identify two different data sets. In our example they are named **features** and **labels**, though any names could be used. These names need to match the names used in the NDL network definition Inputs in our example, so the correct definition is used for each input dataset. Each of these sections for the UCIFastReader have the following parameters: + +**dim** is the number of columns of data that are contained in this dataset + +**start** is the column (zero-based) where the data columns start for this dataset + +**file** is the file that contains the dataset. This parameter has been moved up from the features and labels subsections, because UCIFastReader requires the file be the same, and moving up a level is an excellent way to make sure this restriction is met. + +**labelDim** is the number of possible label values that are possible for the dataset, and belongs in any label section. In MNIST this value is 10, because MNIST is a number recognition application, and there are only 10 possible digits that can be recognized + +**labelMappingFile** is the path to a file that lists all the possible label values, one per line, which might be text or numeric. The line number is the identifier that will be used by CNTK to identify that label. In our example this file has been moved to the root level to share with other Command Sections. In this case, it’s important that the Evaluation Command Section share the same label mapping as the trainer, otherwise, the evaluation results will not be accurate. + +The final Command section in the example is the **mnistTest** section. This section takes the trained model and tests it against a separate test dataset. All the parameters that appear in this section also appeared in the **mnistTrain** section. + +### Command Line syntax + +The config file can be specified on the command line when launching the Computational Network Process (cn.exe): + +``` +cn.exe configFile=config.txt +``` + +This will load the requested configuration file, and execute any command section listed in the **command** parameters in the configuration file. In our example it will execute **mnistTrain** followed by **mnistTest**. + +### Configuration override + +It is common to have a configuration that can be used as a base configuration, and modify only a few parameters for each experimental run. This can be done in a few different ways, one of which is to override settings on the command line. For example if I wanted to override the model file path, I could simply modify my command line: + +``` +cn.exe configFile=config.txt stderr=c:\temp\newpath +``` + +this will override the current setting for stderr, which is defined at the root level of the configuration file, with the new value. If a parameter inside a command section needs to be modified, the section also needs to be specified. For example, if I wanted to change the minibatchSize for an experiment from the command line: + +``` +cn.exe configFile=config.txt mnistTrain=[minibatchSize=256] +``` + +or to modify the data file used for an experiment: + +``` +cn.exe configFile=config.txt mnistTrain=[reader=[file=mynewfile.txt]] +``` + +Another way to do this is with layered configuration files: + +### Layered Configuration Files + +Instead of overriding some parts of a configuration file using command line parameters, one can also specify multiple configuration files, where the latter files override the earlier ones. This allows a user to have a “master” configuration file, and then specify, in a separate configuration file, which parameters of the master they would like to override for a given run of CNTK. This can be accomplished by either specifying a ‘+’ separated list of configuration files, or by using the “configFile=” tag multiple times. The following are equivalent: + +``` +cn.exe configFile=config1.txt+config2.txt +cn.exe configFile=config1.txt configFile=config2.txt +``` + +If config2.txt contains the string “mnistTrain=\[reader=\[file=mynewfile.txt\]\]”, then both of these commands would be equivalent to: + +``` +cn.exe configFile=config1.txt mnistTrain=[reader=[file=mynewfile.txt]] +``` + +Note that the value of a variable is always determined by the last time it is assigned. It is also possible to mix command-line parameters, and layered configuration files, in arbitrary combinations. For example: + +``` +cn.exe configFile=config1.txt+config2.txt var1=value configFile=config3.txt +``` + +This would process these configuration parameters in the order they appear on the command line. + +### Including Configuration Files + +In addition being able to specify multiple configuration files at the command line, a user can “include” one configuration file within another. For example, if the first line of config2.txt was “include=config1.txt”, then simply running “cn.exe configFile=config2.txt” would be equivalent to running “cn.exe configFile=config1.txt+config2.txt” (where in this latter case, config2.txt doesn’t contain the “include” statement). Note that these include statements can appear anywhere inside a configuration file; wherever the include statement appears, that is where the specified configuration file will be “included”. Including a configuration file is equivalent to pasting the contents of that file at the location of the include statement. Include statements are resolved recursively (using a depth-first search), meaning that if configFileA.txt includes configFileB.txt, and configFileB.txt includes configFileC.txt, then the full chain will be resolved, and configFileC.txt will effectively be included in configFileA.txt. If a configuration file is included multiple times (eg, ‘A’ includes ‘B’ and ‘C’, and ‘B’ also includes ‘C’), then it will effectively only be included the first time it is encountered. + +### Stringize variables + +While layered configuration files allow users to reuse configuration files across experiments, this can still be a cumbersome process. For each experiment, a user might have to override several parameters, some of which might be long file paths (eg, ‘stderr’, ‘modelPath’, ‘file’, etc). The “stringize” functionality can make this process much easier. It allows a user to specify configuration like the following: + +``` +command=SpeechTrain stderr=$Root$\$RunName$.log speechTrain=[ modelPath=$Root$\$RunName$.model SGD=[ reader=[ features=[ type=Real dim=$DataSet1_Dim$ file=$DataSet1_Features$ ]]]] +``` + +Here, “Root”,“RunName”, “DataSet1\_Dim”, and “DataSet1\_Features” are variables specified elsewhere in the configuration (at a scope visible from the point at which they are used). When interpreting this configuration file, the parser would replace every string of the form “$VarName$” with the string “VarValue”, where “VarValue” represents the value of the variable called “VarName”. The variable resolution process is recursive; for example, if A=$B$, B=$C$, and C=HelloWorld.txt, then A would be resolved as “HelloWorld.txt”. +Notice that because it is equivalent for a user to specify the value of a variable in a configuration file vs. at the command line, the values for these variables can be specified in either location. Recall that the value of a variable is determined by the last time it is assigned, whether that happens to be in a configuration file, or on the command line. Thus, if “Root” is defined in config1.txt, but overridden at the command-line, then the value specified at the command-line would be the one used to resolve instances of $Root$ in configFile1.txt. One useful feature is that if ‘stderr’ or ‘modelPath’ point to directories which do not exist, these directories will be created by CNTK; this allows you to specify something like: “stderr=$Root$\\$RunName$\\$RunName$.log”, even if the directory “$Root$\\$RunName$” doesn’t exist. + +## User Reference + +This section is intended as a reference to all the possible configuration settings used in CNTK. See the example section for usage by example. + +### Parameter Search + +Config files are hierarchal organizations of Configuration Parameter Sets, a collection of name-value pairs. Any value that is expected to occur in a parameter set can alternately be defined at a higher level. The search for a parameter name will continue through parent parameter sets until it is resolved, or not found. + +Default values are often assigned to parameters, these values will be used should the search for the parameter fail. If no default is available, the parameter is a required parameter, and an error will occur if it is not provided. + +If a parameter occurs more than once in a given parameter set, the last occurrence of that value will have precedence. + +### Commands and actions + +There must be a top-level command parameter, which defines the commands that will be executed in the configuration file. Each command references a Command section of the file, which must contain an action parameter defining the operation that section will perform: + +``` +command=mnistTrain:mnistTest mnistTrain=[ action=train … ] mnistTest=[ action=eval … ] +``` + +This snippet will execute the **mnistTrain** section which executes the **train** action, followed by the **mnistTest** section. + +### Command sections + +The following actions are currently supported in the CNTK. The command sections that contain these action properties also require other configuration settings. The names contained in square braces (i.e. \[reader\]) are configuration sections, and values in curly braces (i.e. {true}) are default values used when the parameter is not specified: + +- **train** – Train a model + + - \[Reader\] – reader configuration section to read the dataset + + - \[Trainer\] – trainer configuration section, currently SGD is the only trainer supported + + - \[Network Builder\] – network builder configuration section, the method of creating the network + + - \[cvReader\] – (optional) reader configuration section for cross-validation data + + - makeMode-\[{true},false\] – start from scratch even if an interrupted training session exists (default true) + +- **test, eval** – Evaluate/Test a model for accuracy, usually with a test dataset + + - \[Reader\] – reader configuration section to read the test dataset + +- **createLabelMap** – creates a label mapping file from the dataset for readers that support it. Currently UCIFastReader is the only reader that supports this action. + + - section – the section name (usually a *train* section) which has the reader sub-section that will be used to generate the label mapping file. The labelMappingFile property in this reader section will be written to with the results of the map file generation. + + - minibatchSize – the minibatch size to use when creating the label mapping file + +- **edit** – execute an Model Editing Language (MEL) script. + + - editPath – the path to the Model Editing Language (MEL) script to be executed + + - ndlMacros - the path to the Network Definition Language (NDL) macros file that will be loaded and usable in the MEL script. + +- **testUnroll** – Evaluate/Test a model for accuracy, by unrolling it + + - \[reader\] - reader configuration section to read the test dataset + + - minibatchSize – the minibatch size to use when reading and processing the dataset + + - epochSize – {0} size of epoch, if not specified or set to zero entire dataset will be read once. + + - modelPath – path to the model file to evaluate + + - path2EvalResults – optional, if provided evaluation results will be dumped to this file + +- **adapt** – adapt an already Trained model, supports KL divergence regularization + + - \[Reader\] – reader configuration section to read the dataset + + - \[Trainer\] – trainer configuration section, currently SGD is the only trainer supported + + - \[cvReader\] – (optional) reader configuration section for cross-validation data + + - makeMode-\[{true},false\] – start from scratch even if an interrupted training session exists (default true) + + - originalModelFileName – file name for the model that will be adapted + + - refNodeName – name of the node in the computational network which will be used for KL divergence regularization (see SGD section for additional parameters required) + +- **cv** – Use Cross Validation to evaluate a series of epoch model for the best results + + - \[reader\] - reader configuration section to read the test dataset + + - minibatchSize – the minibatch size to use when reading and processing the dataset + + - epochSize – {0} size of epoch, if not specified or set to zero entire dataset will be read once. + + - modelPath – path to the model file to evaluate, epoch files have the epoch number appended to the end of this path name + + - crossValidationInterval – array of 3 integers identifying the starting epoch, epoch increment and final epoch to evaluate. + + - sleepTimeBetweenRuns – how many seconds to wait between runs + + - numMBsToShowResult – after how many minibatches should intermediate results be shown? + + - evalNodeNames – an array of one or more node names to evaluate + +- **write** – Write the output of a network to a file + + - \[reader\] - reader configuration section to read the dataset + + - \[writer\] – writer configuration section to the data writer for output data. If this value is not specified the outputPath parameter will be used. + + - minibatchSize – the minibatch size to use when reading and processing the dataset + + - epochSize – {0} size of epoch, if not specified or set to zero entire dataset will be read once. + + - modelPath – path to the model file we are using to process the input data + + - outputPath – output path to write file in a text based format. Either the writer config, or the outputPath will be used. + + - outputNodeNames – an array of one or more output node names to be written to a file + +- **dumpnode** – Dump the node(s) to an output file. Note: this can also be accomplished in MEL with greater control. + + - modelPath – path to the model file containing the nodes to dump + + - nodeName – the name of the node to be written to a file, if not specified all nodes will be dumped + + - outputFile – path to the output file, will be generated in the same file as the modelPath if not specified. + + - printValues – \[{true}, false\] prints the values associated with a node if applicable. + +The following table identifies the options for sub-section types associated with each of the action types: + +sub-section | Options | Description +---|---|--- +**Network Builder** | SimpleNetworkBuilder | Creates simple layer-based networks with various options + | NDLNetworkBuilder | Create a network defined in NDL (Network Description Language). This allows arbitrary networks to be created and offers more control over the network structure. +**Trainer** | SGD | Stochastic Gradient Descent trainer, currently this is the only option provided with CNTK +**Reader** | UCIFastReader | Reads the text-based UCI format, which contains labels and features combined in one file + | HTKMLFReader | Reads the HTK/MLF format files, often used in speech recognition applications + | BinaryReader | Reads files in a CNTK Binary format. It is also used by UCIFastReader to cache the dataset in a binary format for faster processing. + | SequenceReader | Reads text-based files that contain word sequences, for predicting word sequences. + | LUSequenceReader | Reads text-based files that contain word sequences, used for language understanding. | + +### Top Level Parameters + +- **command** – an array of Command sections (which contain action parameters) that should be executed + +- **precision** – \[float, double\], required parameter. Float values (32-bit floating point) is usually faster on most hardware, but less precise. This applies to all floating point values in CNTK. + +- **deviceId** – \[{auto}, cpu, \#, all, \*\#\], default is auto. Which hardware device should be used for an action. This is used at lower levels but is often defined at the top level. + +Value | Description +---|--- +auto | choose the best GPU, or CPU if no usable GPU is available. +cpu | use the CPU for all computation +0 | The device Identifier (as used in CUDA) for the GPU device you wish to use (1, 2, etc.) +all | Use all the available GPU devices (will use PTask engine if more than one GPU is present) +\*2 | Will use the 2 best GPUs, any number up to the number of available GPUs on the machine can be used. (will use PTask engine) + +- **stderr** – optional path to where the log files will be stored. This is a redirection of stderr to a file, if not specified the output will be output to the normal stderr device (usually the console). The path here defines the directory and the prefix for the log file. The suffix is defined by what commands are being run and will be appended to create the file name. For example if “stderr=c:\\abc” and “command=mnistTrain” the log file would be named “c:\\abc\_mnistTrain.log”. + +- **traceLevel** – \[{0}\] the level of output to stderr that is desired. The higher the number the more output can be expected. Currently 0-limited output, 1-medium output, 2-verbose output are the only values supported. + +### Network Builders + +Network builders provide a way to create a network. There are two network builders currently supported, SimpleNetworkBuilder and NDLNetworkBuilder. The sub-sections with one of these names define which network builder will be used for the train action. + +#### SimpleNetworkBuilder + +#### NDLNetworkBuilder + +The NDL (Network Description Language) Network Builder component takes a config section written in NDL and interprets it to create a model. For more information on NDL please refer to the Network Description Language section of the document. + +- **networkDescription –** (optional) file path of the NDL script to execute, the run parameter in this subsection can be used to override the run parameter in the file if desired. If there is no networkDescription file specified then the NDL is assumed to be in the same configuration file as the NDLNetworkBuilder subsection. + +- **run** – (optional) the section containing the NDL that will be executed. If using an external file the run parameter may already exist in that file and identifies the sections in that file that will be executed as NDL scripts. This parameter in NDLNetworkBuilder section will override those settings. If no networkDescription file is specified, a section with the given name will be searched for in the current configuration file. It must exist where a regular configuration search will find it (peer or closer to the root of the hierarchy) + +- **load** – (optional) the section(s) in the same file that contain NDL macros to be loaded. If specified t must exist where a regular configuration search will find it (peer or closer to the root of the hierarchy) + +- **ndlMacros** – (optional) path to an NDL macros file, normally defined at the root level so it could be shared with other Command Sections. This parameter is usually used to load a default set of NDL macros that can be used by all NDL scripts. + +### Trainers + +#### SGD + +### Readers + +The readers all share the same section name, which is **reader**. The **readerType** parameter identifies which reader will be used. + +``` +readerType=UCIFastReader +``` + +Each of the readers uses the same interface into CNTK, and each reader is implemented in a separate DLL. CNTK takes the **readerType** parameter value and appends “.dll” and dynamically loads that DLL to read data. This interface to data readers allows for new data formats to be supported in CNTK simply by writing a new reader component. For more information on the reader interface, and how to write a reader, refer to the Programmer documentation section. + +There are many parameters in the reader section that are used by all the different types of readers, and others are specific to a particular reader. There are sub-sections under the reader section which are used to define the data records to be read. For UCIFastReader these look like: + +``` +reader=[ readerType=UCIFastReader file=c:\cntk\data\mnist\mnist_train.txt features=[ dim=784 start=1 ] labels=[ dim=1 start=0 labelDim=10 ] +] +``` + +The sub-sections in the reader section identify the different data records. In our example they are named **features** and **labels**, though any names could be used. If NDL was used to create the network, these section names should match the names of the input nodes in the NDL network definition. These names will be used as the matrix names passed back from the reader and name matching ensures the correct records are assigned to the correct inputs. + +#### UCIFastReader + +UCIFastReader reads text-based UCI format data. The format of UCI data is a line of space-delimited floating point feature and label values for each data record. The label information is either at the beginning or the end of each line, if label information is included in the dataset. + +The following parameters can be used to customize the behavior of the reader: + +- **randomize** – \[{Auto}, None, \#\] the randomization range (number of records to randomize across) for randomizing the input. This needs to be an integral factor of the epochSize and an integral multiple of minibatch size. Setting it to Auto will let CNTK find something that works. + +- **minibatchMode** – \[{Partial},Full\] the mode for minibatchs when the end of the epoch is reached. In partial minibatch mode, if the remaining records are less than a full minibatch, only those read will be returned (a partial minibatch). I Full minibatch mode, no partial minibatches will be returned, instead those records will be skipped. + +Each of the data record sub-sections have the following parameters: + +- **dim** - the number of columns of data that are contained in this data record + +- **start** -the column (zero-based) where the data columns start for this data record + +- **file** - the file that contains the dataset. This parameter may be moved up to the reader section level to ensure the file is the same for all the sections, as UCIFastReader requires. + +In addition if the data record sub-section is defining labels the following parameters need to be defined: + +- **labelDim** – the number of possible label values that are possible for the dataset. For example, for the MNIST dataset this value is 10, because MNIST is a number recognition application, and there are only 10 possible digits that can be recognized + +- **labelMappingFile** – the path to a file that lists all the possible label values, one per line, which might be text or numeric. The line number is the identifier that will be used by CNTK to identify that label. This parameter is often moved to the root level to share with other Command Sections. For example, it’s important that the Evaluation Command Section share the same label mapping as the trainer, otherwise, the evaluation results will not be accurate. + +- **labelType** – \[{Category},Regression,None\] the type of label + +The UCIFastReader is a text-based reader, and though it is optimized for speed, is still significantly slower than reading binary files. To improve training speed that may be limited by the data reader the content of the dataset can be cached the first time through the dataset, and subsequently read from a binary cache saved to disk. UCIFastReader uses BinaryReader and BinaryWriter to make this cache and to read from it. BinaryReader and BinaryWriter support multiple datasets in a single file, so data read from multiple files can all be cached in a single file. Please refer to BinaryWriter to see a sample of how to setup a UCIFastReader with caching. + +#### HTKMLFReader + +HTKMLFReader reads files in the HTK/MLF format, which is used for speech datasets. The reader has two different modes, one for training and evaluating datasets and another for processing a dataset and producing another dataset. + +For training and evaluation the following need to be defined: + +- **randomize** – \[{auto},None,\#\] randomization range used for randomizing data. Auto automatically picks a randomization range, None does no randomization, and a specific number sets the range to that number. + +- **readMethod** – \[{blockRandomize},rollingWindow\] the method of randomization that will occur. Block randomize randomizes in a block format and does not require extra disk storage. RollingWindow creates a temporary file and randomizes data anywhere within a rolling window around the current file location. + +- **framemode** – \[{true}, false\] is the reader reading frames, or utterances + +- **minibatchMode** – \[{Partial},Full\] the mode for minibatchs when the end of the epoch is reached. In partial minibatch mode, if the remaining records are less than a full minibatch, only those read will be returned (a partial minibatch). I Full minibatch mode, no partial minibatches will be returned, instead those records will be skipped. + +- **readAhead** – \[true,{false}\] have the reader read ahead in another thread. NOTE: some known issues with this feature + +- **verbosity** – \[0-9\] default is ‘2’. The amount of information that will be displayed while the reader is running. + +- **addEnergy** – {0} the number of energy elements that will be added to each frame (initialized to zero). This only functions if readMethod=rollingWindow. + +- **unigram** – (optional) path to unigram file + +- **\[input**\] – subsection that holds all the input subsections + subsections with arbitrary names occur under the input subsection, which will contain: + + - **dim** – dimension of the input data + + - **type** – \[{real},Category\] type of input + + - **file** – input file path + +- **\[output\]** – subsection that holds all the output subsections + subsections with arbitrary names occur under the output subsection, which will contain: + + - **dim** – dimension of the input data + + - **type** – \[{real},Category\] type of input + + - **file** – input file path + + - **labelMappingFile** – (optional) state list to the labelMappingFile + + - **labelToTargetMappingFile** – (optional) filename for the labelToTargetMappingFile + + - **targetDim** – dimension of the target if labelToTargetMapping is desired. + +The following two sections can currently be used instead of input and output subsections if there is only one input and one output. However, the previous syntax is recommended + +- **\[features\]** – subsection defining the features data, must use this name + + - **dim** – dimension of the features data + + - **file** – path to the “.scp” script file that lists the locations of feature data + +- **\[labels\]** – subsection defining labels data, must use this name + + - **labelDim** – dimension of the labels data + + - **file** – path to the MLF file describing the labels + + - **labelMappingFile** – (optional) state list to the labelMappingFile + + - **labelToTargetMappingFile** – (optional) filename for the labelToTargetMappingFile + + - **targetDim** – dimension of the target if labelToTargetMapping is desired. + +For dataset processing the following parameters are used: + +- **\[input**\] – subsection that holds all the input subsections + subsections with arbitrary names occur under the input subsection, which will contain: + + - **dim** – dimension of the input data + + - **file** – input file path + +- **\[write\]** – subsection that holds all the output subsections + subsections with arbitrary names occur under the output subsection, which will contain: + + - **dim** – dimension of the input data + + - **path** – output file path + + - **ext** – {mfc} file extention to use for output files + + - **type** – (optional) if type=scaledLogLikelihood output will be scaled by Prior + + - **nodeName** – node name for output to be captured + + - **scpFile** – (optional) file name for SCP file if desired + +#### SequenceReader + +SequenceReader is a reader that reads text string. It is mostly often used for language modeling tasks. An example of the text string is as follows: + +``` + pierre N years old will join the board as a nonexecutive director nov. N mr. is chairman of n.v. the dutch publishing group +``` + +Symbol </s> is used to denote both beginning and ending of a sentence. However, this symbol can be specified by beginSequence and endSequence. + +The parameters used for the SequenceReader are shared with other reader types. However, it has some unique parameters as follows: + +- randomize – \[None, Auto\] the mode for whether doing sentence randomization of the whole corpus. + +- Nbruttsineachrecurrentiter – this set the maximum number of allowed sentences in each minibatch. + +- Wordclass – word class information. This is used for class-based language modeling. + +- File – the corpus file. + +A subsection is for input label information. + +- lableIn – the section for input label. It contains the following setups + + - labelDim – the input vocabulary size + + - beginSequence – the sentence beginning symbol + + - endSequence – the sentence ending symbol + +- labels – the section for output label. In the language modeling case, it is the same as labelIn. + +#### LUSequenceReader + +LUSequenceReader is similar to SequenceReader. It however is used for language understanding tasks which have input and output strings that are different. The content of an example file is listed below + +``` +BOS O i O want O to O fly O from O boston B-fromloc.city_name at O 1110 B-arrive_time.time in O the O morning B-arrive_time.period_of_day EOS O +``` + +consists of some unique setups as follows: + +The LUSequenceReader assumes that the last column is the label and all other columns are inputs. The beginning and ending of a sentence are specified using beginning and ending symbols. In the above example, they are BOS and EOS, respectively, for the beginning and ending symbols. + +The LUSequenceReader has some unique setups as follows: + +- wordContext – this specifies a context window. For example, wordContext=0:1:2 specifies a context window of 3. In this context window, it reads input at a current time, the next time and the time after the next time. Another example would be wordContext=0:-1. In this example, LUSequencReader reads a context window of 2 that consist of the current input and the immediate last input. + +- Nbruttsineachrecurrentiter – this specifies the maximum number of sentences in a minibatch. + +- Unk – this specifies the symbol to represent unseen input symbols. Usually, this symbol is “unk”. + +- Wordmap – this specifies a file that maps inputs to other inputs. This is useful if the user wants to map some inputs to unknown symbols. For example: + +``` + buy buy trans +``` + +- File – the corpus file + +A subsection is for input label information. + +- lableIn – the section for input label. It contains the following setups + + - usewordmap – \[Ture, False\] specifies if using word map to map input words to other input words. + + - beginSequence – the sentence beginning symbol + + - endSequence – the sentence ending symbol + + - token – token file contains a list of input words. Their orders are not important. + +- labels – the section for output label. In the language modeling case, it is the same as labelIn. + + - Token – token file contains a list of output labels. Their order is not important as long as the tokens are unique. + +#### BinaryReader + +BinaryReader is a reader that uses a hierarchal file format to store potentially multiple datasets in an efficient way. It uses memory mapped files with a moving window of viewable data to support files larger than can be held in memory at once. More details about the file format are in the BinaryWriter Section. + +The parameters used for the binary reader are quite simple as most of the required information concerning the file is contained in the file headers. The binary reader will also be called when a configuration is setup to cache UCIFastReader if the binary cached file exists. + +The following parameters can be used to customize the behavior of the reader: + +- **minibatchMode** – \[{Partial},Full\] the mode for minibatchs when the end of the epoch is reached. In partial minibatch mode, if the remaining records are less than a full minibatch, only those read will be returned (a partial minibatch). I Full minibatch mode, no partial minibatches will be returned, instead those records will be skipped. + +- **file** – array of files paths to load. Each file may contain one or more datasets. The dataset names used when the file was created will be used when the file is read. + +### Writers + +Writers are used in a similar way to Readers in CNTK. Writers are often implemented in the same DLL as the Reader for the same format. Just as in the reader case, the writer is dynamically load based on the name in the writerType parameter: + +``` +writerType=BinaryReader # NOTE: BinaryReader.dll also implements BinaryWriter +``` + +#### BinaryWriter + +BinaryWriter is an implementation of a hierarchal file format the mirrors the configuration file. I uses memory mapped files to enable large files that do not fit in memory to be written and read using a moving window into the file. The binary writer is also used as a Cache mechanism for UCIFastReader to allow for much faster access to data after the dataset has been read once. + +The following is an example of a BinaryWriter definition. Since it is most commonly used as a cache for UCIFastReader, this definition is show as a UCIFastReader cache. The parameters needed for BinaryWriter are in bold type below: + +``` + # Parameter values for the reader with cache reader=[ # reader to use readerType=UCIFastReader # if writerType is set, we will cache to a binary file # if the binary file exists, we will use it instead of parsing this file writerType=BinaryReader miniBatchMode=Partial randomize=Auto windowSize=10000 #### write definition wfile=c:\data\mnist\mnist_train.bin #wsize - inital size of the file in MB # if calculated size would be bigger, that is used instead wsize=256 #wrecords - number of records we should allocate space for in the file # files cannot be expanded, so this should be large enough. wrecords=60000 features=[ dim=784 start=1 file=c:\data\mnist\mnist_train.txt ### write definition #wsize=200 #wfile=c:\data\mnist\mnist_train_features.bin sectionType=data ] labels=[ dim=1 start=0 file=c:\data\mnist\mnist_train.txt labelMappingFile=c:\temp\labels.txt labelDim=10 labelType=Category #### Write definition #### # sizeof(unsigned) which is the label index type #wsize=10 #wfile=c:\data\mnist\mnist_train_labels.bin elementSize=4 wref=features sectionType=labels mapping=[ #redefine number of records for this section, #since we don't need to save it for each data record wrecords=10 #variable size so use an average string size elementSize=10 sectionType=labelMapping ] category=[ dim=10 #elementSize=sizeof(ElemType) is default sectionType=categoryLabels ] ] ] +] +``` + +The example above shows all the parameters necessary to create a Binary file with BinaryWriter: + +- **writerType** – The definition of the writer to use. The CNTK code takes this name and appends “.DLL” to dynamically load the DLL and access the writer implementation. BinaryWriter is implemented in the same DLL as BinaryReader, so BinaryReader is the correct value for this setting. + +- **windowSize** – the size of the moving window that will be used to access the data in the file. BinaryReader and BinaryWriter both us memory mapped files to support extremely large datasets which cannot be contained in memory. This window size is the minimum amount that will be present in memory at one time. + +- **wfile** – the filename and path to the binary file. This can appear at the base of the hierarchy (as in this case) which means all sub-sections defined will be saved in a single file. Alternately, separate files can be defined for different sub-sections. The commented out sections in the feature and labels sections show how separate binary files could be saved. Simply comment out the based definition and uncomment the sub-section definitions and two separate files will be created. + +- **wsize** – used in conjunction with wfile, defines how large (in Megabytes) the initial filesize should be. It must be large enough to contain all data or an error will occur. Once the file is completely written it will shrink down to its actual size. + +- **wrecords** – the number of records that will be written to disk. Different subsections can override this value if the number of records for a subsection are different (as is the case for the label mapping table subsection) + +> Each subsection in the configuration will create a corresponding subsection in the binary file. The name used for the subsection in the configuration file will be saved for each subsection, and will be name used to refer to the data when it is read later. Each subsection must have a few parameters: + +- **sectionType** – the type of section this config section is describing. The possibilities are: + +**Section Type** | **Description** +---|--- +Data | data section (floating point values) +Labels | label data (floating point values). For category labels the integer part of this floating point value will be interpreted as an index into the label mapping table. +LabelMapping | label mapping table (array of strings) +Stats | data statistics. can compute the following statistics across the entire dataset: `sum:count:mean:variance:stdDev:max:min:range:rootMeanSquare` +CategoryLabels | labels in category format (floating point type - all zeros with a single 1.0 per column that matches the mapping table) This format is directly usable in this form for training, so it can be stored in this form. + +- **elementSize** – size in bytes of the elements. If this value is not specified, it will default to the sizeof(ElemType), where ElemType is either float or double based on the precision specified in the configuration file. + +- **wref** – A reference to the section that holds the data referenced by these labels. It is often best to store both the labels and the data in the same file so they remain associated with each other. If separate label and data binary files that were generated at different times are used together an error will occur (as they are likely not aligned to each other) + +- **dim** – used for categoryLabels format sections. It contains the number of columns of category data, which will contain all zeros except for a single 1.0 for each row. + +## Programmer Reference + +This section covers topics that are of interest to those who wish to modify the code and use the provided classes in there code. The first section covers configuration files and their use from a programmer’s perspective. The second section covers Reader/Writer interfaces for data input/output. The third section covers the CNTKMath library, and the last section covers using PTask to enable a computation node to participate in multi-GPU processing. + +### Configuration Files + +Configuration files, and easy manipulation of these files is a main feature of CNTK. The configuration files make the users life much easier, and the programmer interface is also quite simple to use. The programmer interface to the configuration files is contained in a few C++ classes and focuses on “just-in-time” evaluation of the parameter values. The idea is simple, leave the configuration values in string format until they actually need to be parsed into some other form. + +#### Configuration Formats + +The following is a summary of the different formats the configuration classes can support: + +Config Type | C++ type | Format | Notes +---|---|---|--- +integer | int, long, short, size_t | [-]###### | Optional sign and numeric digits. All signed and unsigned integer numeric types +floating=point | float, double | [-]####.#####[e{+-}###] | Numeric digits with a decimal point, optional sign,optional scientific notation +string | std::wstring, std::string | Any valid character | If contained in an array or dictionary and the default separator is contained in the string (i.e. c:\temp in an array) use alternate separator. +boolean | bool | T/True/1, F/False/0 | True or False values, may be specified by existence or absence of a boolean parameter with no ‘=’ or value after the parameter name +array | ConfigArray | + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Config typeC++ typeFormatNotes
integerint, long, short, size_t[-]######Optional sign and numeric digits. All signed and unsigned integer numeric types
floating-pointfloat, double[-]####.#####[e{+-}###]Numeric digits with a decimal point, optional sign, optional scientific notation
stringstd::wstring, wtd::stringAny valid characterIf contained in an array or dictionary and the default separator is contained in the string (i.e. c:\temp in an array) use alternate separator.
booleanbool +
    +
  • T, True, 1
  • +
  • F, False, 0
  • +
+
True or False values, may be specified by existence or absence of a boolean parameter with no ‘=’ or value after the parameter name
arrayConfigArray +
    +
  • value:value:value
  • +
  • value:value*#:value
  • +
  • {|value|value|value}
  • +
  • {|value|value*#|valve}
  • + +
  • + +
    {
    value
    value
    value*#
    }
    + +
  • + +
+
Multiple values in an array are separated by colons ‘:’. A value may be repeated multiple times with the ‘*’ character followed by an integer (the # in the examples). Values in an array may be of any supported type and need not be uniform. The values in a vector can also be surrounded by curly braces ‘{}’, braces are required if new lines are used as separators. An alternate separation character can be specified immediately following the opening brace if desired.
dictionaryConfigParameters +
    +
  • + +
    parameter1=value1;
    +parameter2=value2;
    +boolparam
    + +
  • +
  • + +
    [#parameter1=value1#parameter2=value2#boolparam]
    + +
  • +
  • + +
    [
    parameter1=value1
    parameter2=value2
    boolparam
    ]
    
    + +
  • +
+
Multiple parameters grouped together in a dictionary. The contents of the dictionary are each named values and can be of different types. Dictionaries can be used to create a configuration hierarchy. When specified on the same line a ‘;’ semicolon is used as the default separator. The values can optionally be surrounded by square braces ‘[]’. Braces are required when using newlines as separators in a config file. An unnamed dictionary is also allowed in the case of an array of dictionaries. An alternate separation character can be specified immediately following the opening brace if desired.
+ + + +#### Configuration classes + +There are three main classes that are used to access configuration files. *ConfigParameters* and *ConfigArray* contain instances of *ConfigValue*. The main definitions are as follows: + +``` +class ConfigValue : public std::string class ConfigParameters : public ConfigParser, public ConfigDictionary class ConfigArray:public ConfigParser, public std::vector +``` + +##### ConfigValue + +The key class that is used to allow the JIT evaluation of configuration strings is the *ConfigValue* class. This class inherits from std::string, and stores an optional configuration path string, which is mainly used for error messages. It contains many cast operators that do the actual parsing of the string value into the target type on demand. + +##### ConfigParameters + +Configuration files are mainly made up of a hierarchy of Configuration Sets (*ConfigParameters*), which are dictionaries of *ConfigValue*. This class provides access to the configuration values and automatically searches up the hierarchy of ConfigParameter classes if a value is not found on the current level. The hierarchy is maintained by the order of class instantiations on the stack. ConfigParameters should only be created on the stack. + +In configuration files the ‘name=value’ named pair are usually separated by newlines. However, they also can be separated by other characters and placed on the same line. The default separator for ConfigParmeters is a ‘;’ (semicolon). This can be overridden by placing the alternate separator character immediately following the opening brace. For example ‘\[|’ causes ‘|’ to be the separator for that ConfigParameter instance: + +``` +name=[|parameter1=value1|parameter2=value2|parameter3=value3] +``` + +There are two types of ConfigParameters type accessors: + +**value = config(“name”)** – which will return the named parameter cast to the type of the value parameter. If the named configuration parameter does not exist an exception will be thrown. + +**value = config(“name”, “defaultValue”)** – returns the named parameter, if it doesn’t exist returns defaultValue. + +**config.Exists(“name”)** – returns the existence of a named value in the *ConfigParameters* instance. + +To insert elements into a *ConfigParameters* variable the following methods can be used: + +**config.Insert(“name”, value)** – insert a new value into the existing *ConfigParameters* instance. If the value already exists, it will be replaced unless the value is itself another *ConfigParameters* instance, or string representation surrounded by ‘\[\]’ square braces, in which case the parameters are “merged”. + +**config.Insert(value)** – insert the passed string into the dictionary, it is expected to be in ‘name=value’ format. + +##### ConfigArray + +This type inherits from std::vector<ConfigValue> and can be used to hold arrays of values. Since ConfigValue is a just-in-time evaluation type. The values in the array need not be homogeneous, as long as the code that evaluates the array in the end knows how to interpret the values. + +In a ConfigArray the array values are normally separated by the default separator character, which is a ‘:’ (colon). However, they also can be separated by other characters and or place each value on a separate line. The default separator can be overridden by placing the alternate separator character immediately following the opening brace. For example ‘{|’ causes ‘|’ to be the separator for a ConfigArray instance: + +``` +array={|c:\temp\new.txt|12*3|1e-12} +``` + +A value may be repeated multiple times with the ‘\*’ character followed by an integer. In the above example, there are 5 elements in the array, with three ‘12’ values occupying the center 3 positions. + +The values in a ConfigArray can be accessed just like values in a normal std::vector type, but the automatic type conversion of ConfigValue will still be in affect. + +#### Other Useful Configuration Methods + +Another convenient method that exists for both ConfigParameters and ConfigArray types is a method to load a config file into an existing instance. It is implemented in ConfigParser, which both of these classes inherit from: + +``` +config.LoadConfigFile(path.c_str()); +``` + +To use this method with a ConfigArray, the file can simply contain a list of values each on their own line and they will be read into the ConfigArray. More complex types can also be contained in the array using the config syntax discussed earlier in this document. An array can contain other arrays as well as ConfigParameters. + +ConfigArray instances can also be converted to argvector<T> instances simply by assigning them. Care should be taken to assign to a local variable, and not just passing as a parameter due to lifetime issues, as follows: + +``` +ConfigArray configLearnRatesPerMB = config("learningRatesPerMB"); argvector learnRatesPerMB = configLearnRatesPerMB; +``` + +ConfigParameters and ConfigArray instances are very flexible, but require parsing every time a value is accessed. argvector<T> ,on the other hand, parses once and then accesses values as a standard vector. + +#### Configuration Program Example + +Some sample code that would parse the example configuration file given at the beginning of this document follows. This is a revised version of actual code in CNTK: + +``` +#include "commandArgUtil.h" // process the command void DoCommand(const ConfigParameters& config) { ConfigArray command = config("command"); for (int i=0; i < command.size(); i++) { //get the configuration parameters that match the command ConfigParameters commandParams=config(command[i]); ConfigArray action = commandParams("action","train"); // determine the action to perform, and do it for (int j=0; j < action.size(); j++) { if (action[j] == "train") DoTrain(commandParams); else if (action[j] == "test" || action[j] == "eval") DoEval(commandParams); else throw runtime_error("unknown action: " + action[j] + " in command set: " + command[i]); } } } void DoTrain(const ConfigParameters& config) { ConfigParameters configSGD=config("SGD"); ConfigParameters readerConfig = config("reader"); IComputationNetBuilder* netBuilder = NULL; ConfigParameters configNDL = config("NDLNetworkBuilder"); netBuilder = (IComputationNetBuilder*)new NDLBuilder(configNDL); DataReader* dataReader = new DataReader(readerConfig); ConfigArray learningRatesPerMBStr = configSGD("learningRatesPerMB", ""); floatargvector learningRatesPerMB = learningRatesPerMBStr; ConfigArray minibatchSize = configSGD("minibatchSize", "256"); size_t epochSize = configSGD("epochSize", "0"); if (epochSize == 0) { epochSize = requestDataSize; } size_t maxEpochs = configSGD("maxEpochs"); wstring modelPath = configSGD("modelPath"); int traceLevel = configSGD("traceLevel", "0"); SGD = sgd(learningRatesPerMB, minibatchSize, epochSize, maxEpochs, modelPath, traceLevel); sgd.Train(netBuilder, dataReader); delete netBuilder; delete dataReader; } +``` + +The code above is very easy to code, you simply delare a config, or basic type variable on the stack and assign something from a ConfigParameters class to that variable (i.e. int i = config(”setting”,”default”). Both parameters with defaults and those that don’t are used in the sample code above. The ConfigValue class takes care of parsing the value to be the correct type, and is returned by config() references above. + +The Config classes are meant to be used on the stack as shown in this example. Storing them in member variables or allocating using ‘new’ or other methods is not supported. The reason for this is an internal pointer is used to link to parent instances of config classes. This allows us to trace “up the stack” and look at all the config parameters that exist at a higher level. Since our search traverses up the stack, we need to ensure that all the parent configuration classes still exist, which is guaranteed if all config parameters are stack allocated and have lifetimes that extend past any children. + +### Data Interfaces + +CNTK was designed with the idea that data input and output would need to transpire in many different formats. So data interfaces were designed in an attempt to cover various data needs. Currently there are two data interfaces designed, one for input and the other for output called IDataReader and IDataWriter respectively. The reader/writer code is housed in separate DLLs that which are dynamically loaded to provide data services. This allows the user to simply change a configuration setting and have a different reader provide the data. + +Other possible scenarios are also enabled by using a common interface, for example one reader/writer can act as a cache for another slower reader. UCIFastReader is a text-based reader, and though it is very fast, there is still a significant amount of overhead to parsing, so BinaryWriter/BinaryReader can act as a cache for UCIFastReader. The caching code is currently implemented in UCIFastReader. + +The five readers and one writer provided with CNTK all use these same interfaces and each is housed in its own DLL. CNTK loads the DLL and looks for exported functions that will return the interface of interest. The functions are defined as follows: + +``` +extern "C" DATAREADER_API void GetReaderF(IDataReader** preader); extern "C" DATAREADER_API void GetReaderD(IDataReader** preader); extern "C" DATAWRITER_API void GetWriterF(IDataWriter** pwriter); extern "C" DATAWRITER_API void GetWriterD(IDataWriter** pwriter); +``` + +each reader or writer DLL exports the appropriate functions, and will return the interface when called. The following sections defined the interfaces: + +#### Reader Interface + +``` +/ Data Reader interface // implemented by DataReader and underlying classes template class DATAREADER_API IDataReader { public: typedef std::string LabelType; typedef unsigned LabelIdType; virtual void Init(const ConfigParameters& config) = 0; virtual void Destroy() = 0; virtual void StartMinibatchLoop(size_t mbSize, size_t epoch, size_t requestedEpochSamples=requestDataSize) = 0; virtual bool GetMinibatch(std::map*>& matrices) = 0; virtual const std::map& GetLabelMapping(const std::wstring& sectionName) = 0; virtual void SetLabelMapping(const std::wstring& sectionName, const std::map& labelMapping) = 0; virtual bool GetData(const std::wstring& sectionName, size_t numRecords, void* data, size_t& dataBufferSize, size_t recordStart) = 0; virtual bool DataEnd(EndDataType endDataType) = 0; // Recursive network specific methods virtual size_t NumberSlicesInEachRecurrentIter() = 0; virtual void SetNbrSlicesEachRecurrentIter(const size_t) = 0; virtual void ReloadLabels() = 0; virtual void SaveLabels() = 0; virtual void SetSentenceEndInBatch(vector &sentenceEnd)=0; }; +``` + +The methods are as follows: + +- **Init** – initialize the reader from a set of ConfigurationParameters. See the reader documentation for elements of readers that should be similar across all types. + +- **Destroy** – the “destructor” for the reader. Since we are being called from external code we use an explicit method rather than a normal c++ destructor, but the intent is the same. + +- **StartMinibatchLoop** – Starts the minibatch loop with the following parameters: + + - **mbSize** – minibatch size + + - **epoch** – epoch number we are currently processing + + - **requestedEpochSize –** the number of records in an epoch. This value is not required to be the same as the dataset size, it also could be larger or smaller. If the datasetSize is not known the constant requestDataSize can be used to request a single pass through the dataset equal the epochSize. + +- **GetMinibatch –** Get the minibatch data + + - **matrices –** a dictionary that maps from the matrix name to the actual matrix. The names of the matrices in the dictionary should be equal to the subsections in the reader configurations. + + - returns – true if there is more data, false if end of epoch is reached. + +- **GetLabelMapping –** Get the label map from the reader + + - **sectionName –** the section which contains the label map, if applicable. Some readers do not need a section name if only one label map is supported + + - **returns –** the label map from labelId (integer) to label (std::string) + +- **SetLabelMapping –** Set the label map for the reader + + - **sectionName –** the section which is assigned to the label mapping, if applicable. Some readers do not need a section name, they generally only support one label map. + + - **labelMapping –** the labelMap that is being set + +- **GetData –** Get some data from a predefined section + + - **sectionName –** the section which contains the data + + - **numRecords –** the number of records to read + + - **data –** pointer to the data buffer, must have enough room to hold the data + + - **dataBufferSize –** size of the buffer, if zero is passed in, or null is passed for the data pointer the number of bytes required in the buffer will returned in this variable + + - **recordStart –** the record to start reading from + +- **DataEnd** – Are we at the end of a data section? + + - **endDataType** – type of ending we are checking (Dataset, Epoch, Sentence) + + - **returns** – true or false + +- NumberSlicesInEachRecurrentIter + +- SetNbrSlicesEachRecurrentIter + +- ReloadLabels + +- SaveLabels + +- SetSentenceEndInBatch + +#### Writer Interface + +``` +// Data Writer interface // implemented by some DataWriters template class DATAWRITER_API IDataWriter { public: typedef std::string LabelType; typedef unsigned LabelIdType; virtual void Init(const ConfigParameters& config) = 0; virtual void Destroy() = 0; virtual void GetSections(std::map& sections) = 0; virtual bool SaveData(size_t recordStart, const std::map& matrices, size_t numRecords, size_t datasetSize, size_t byteVariableSized) = 0; virtual void SaveMapping(std::wstring saveId, const std::map& labelMapping) = 0; }; +``` + +The methods are as follows: + +- **Init** – initialize the writer from a set of ConfigurationParameters. See the writer documentation for an example of BinaryWriter. + +- **Destroy** – the “destructor” for the writer. Since we are being called from external code we use an explicit method rather than a normal c++ destructor, but the intent is the same. + +- **GetSections** – Gets the sections that are available in the file to write to: + + - **sections** – sections that are defined to write to + +- **SaveData** – Save data to the file + + - **recordStart –** the record to start reading from + + - **matrices –** a dictionary that maps from the section name to the data pointers. The names of the sections in the dictionary should be equal to the sections returned by GetSections(). + + - **numRecords –** number of records to write + + - **datasetSize –** size of the dataset + + - **byteVariableSized –** for variable sized data, the number of bytes used by the data + +- **SaveMapping –** save the mapping table + + - **saveId –** the section name or other id where the mapping will be saved + + - **labelMapping –** the label map from labelId (integer) to label (std::string) + +### CNTKMath Library + +The CNTK Math library is implemented in the DLL CNTKMath.dll and provides a library of math routines for dealing with matrix operations. The library supports CPU and GPU computation with sparse and dense matrix formats. + +The library contains a wrapper class called Matrix<ElemType> (where ElemType is float or double) that hides the differences between the multiple matrix implementations and takes care of data transfers between the GPU and CPU. GPUs and CPUs have different memory spaces, and copying data between them is necessary to access or modify the data from either device. The library attempts to keep data on the GPU as much as possible if a GPU is being used. + +When data is accessed or modified from the CPU, if the data is currently on the GPU the matrix will automatically be relocated to the CPU, and relocated back when the GPU attempts to access or modify the data. Currently the entire matrix object is transferred, so care should be taken when accessing matrix data from the CPU. + +The library uses BLAS libraries from NVidia for the GPU (CuBLAS) and AMD for the CPU (AMCL). Other third party libraries that are used include CuRand (for random number generation) and CuSparse (for sparse matrix implementations), + +### PTask support + +PTask is a library used in CTNK to enable multiple GPU computation on a single machine. PTask uses the concept of a “Tasks organized in a filter graph. It allows fully asynchronous operation of the tasks, each only depending on inputs being available to execute. PTask distributes the tasks across the available hardware and handles data transfers. + +CTNK is organized in a different fashion with Computation Nodes. However, each node has two methods that do all the computation work: EvaluateThisNode() and ComputeInputPartial(), which can be used as the “Tasks”. However, since Tasks can be executed asynchronously, they need to be stateless. To enable these methods as task a static version of each method that takes all inputs and outputs as parameters are created. The class methods simply call these “Task” functions with the class variables for their implementation. + +The PTaskGraphBuilder component takes a computation network and transforms it into a filter graph. In order to do this work it requires the parameter description for each of the tasks. Since C++ does not have a reflection mechanism as in available in C\# and some other languages, a class method has been introduced to ComputationNode to provide this information. The method GetPTaskDescriptor() provides this information to PTaskGraphBuilder so it can build the graph. + +The following is an example of a GetPTaskDescriptor() implementation. This function returns a TaskDescriptor class containing all the parameter and other information necessary to build the filter graph for a particular node. This node is the “TimesNode” and does a matrix multiply. The following implementation of the two important member functions are: + +``` +virtual void EvaluateThisNode() { EvaluateThisNodeS(FunctionValues(), Inputs(0)->FunctionValues(), Inputs(1)->FunctionValues()); } virtual void ComputeInputPartial(const size_t inputIndex) { if (inputIndex > 1) throw std::invalid_argument("Times operation only takes two inputs."); if (inputIndex == 0) //left derivative { ComputeInputPartialLeft(Inputs(1)->FunctionValues(), Inputs(0)->GradientValues(), GradientValues()); } else //right derivative { ComputeInputPartialRight(Inputs(0)->FunctionValues(), Inputs(1)->GradientValues(), GradientValues()); } } +``` + +The GPTaskDescriptor() method describes the necessary parameter information for each method. Each node has a FunctionValue matrix and a GradientValue matrix associated with it, and the descriptor methods identify which values are needed, and if they come from the current node or one of its inputs as follows: + +``` +// GetTaskDescriptor - Get a task descriptor for this node // taskType - task type we are generating a task for virtual TaskDescriptor* GetPTaskDescriptor(TaskType taskType, size_t inputIndex=0) const { TaskDescriptor* descriptor = new TaskDescriptor(this, taskType, inputIndex); switch(taskType) { case taskComputeInputPartial: descriptor->FunctionParam(1-inputIndex, paramOptionsInput); descriptor->GradientParam(inputIndex, paramOptionsInput | paramOptionsOutput | paramOptionsInitialize); descriptor->GradientParam(); descriptor->SetFunction( (inputIndex?(FARPROC)ComputeInputPartialRight:(FARPROC)ComputeInputPartialLeft)); break; case taskEvaluate: descriptor->FunctionParam(); descriptor->FunctionParam(0, paramOptionsInput); descriptor->FunctionParam(1, paramOptionsInput); descriptor->SetFunction((FARPROC)EvaluateThisNodeS); break; default: assert(false); throw std::logic_error("Unsupported task requested"); } return descriptor; } +``` + +For the Evaluate method, the first parameter is an output to the FunctionValue matrix of the current node. + +``` +descriptor->FunctionParam(); +``` + +The default value for this method is “current node, output” so no parameters are needed. The next two parameters are inputs and are the function values from the two inputs: + +``` +descriptor->FunctionParam(0, paramOptionsInput); descriptor->FunctionParam(1, paramOptionsInput); +``` + +The last call passes a pointer to the task function: + +``` +descriptor->SetFunction((FARPROC)EvaluateThisNodeS); +``` + +and the descriptor is complete. The two ComputeInputPartial task function parameters are very similar. Depending on the inputIndex, the values are switched. The first parameter is an input of the function value of one of the inputs, and the second is an output value to the gradient matrix of the other input: + +``` +descriptor->FunctionParam(1-inputIndex, paramOptionsInput); descriptor->GradientParam(inputIndex, paramOptionsInput | paramOptionsOutput | paramOptionsInitialize); +``` + +The second parameter is interesting because it is required to retain it value from one call to the next, this is done in a filter graph by having a parameter be input and output at the same time, meaning it updates itself. There is a clear distinction between values that need to be maintained and those that are transcient in a filter graph, and this idiom is how we instruct PTaskGraphBuilder to retain the value. The Initialize option is also necessary so on the first iteration the matrix will be cleared out (zeros). + +The last parameter is the gradient matrix of the current node, and is an input (defaults for this function). + +``` +descriptor->GradientParam(); +``` + +Lastly, the task functions must be set. They are different based on which input we are computing the gradient for: + +``` +descriptor->SetFunction((inputIndex ? (FARPROC)ComputeInputPartialRight : (FARPROC)ComputeInputPartialLeft)); +``` + +For reference the three task functions are as follows: + +``` +static void WINAPI ComputeInputPartialLeft(Matrix& inputFunctionValues, Matrix& inputGradientValues, const Matrix& gradientValues) static void WINAPI ComputeInputPartialRight(Matrix& inputFunctionValues, Matrix& inputGradientValues, const Matrix& gradientValues) static void WINAPI EvaluateThisNodeS(Matrix& functionValues, const Matrix& input0, const Matrix& input1) ``` + +### NDL classes and processing + +The ability to describe a network architecture in NDL (Network Description Language) is one of the major features of CNTK. However, it is not immediately obvious to the developer looking at the code how it all works. This is meant to shed a little light on the inner workings of NDL processing in CNTK. + +NDL is based on the same configuration parser that is used for config files and MEL (Model Editing Language). While this is convenient to share code, it also makes things a little less clear when viewing the code. The configuration file classes, MEL class, and NDL classes all inherit from ConfigParser, which provides the basic parsing, bracket matching, and other common features (quote handling, etc.). The parsing engine implemented in ConfigParser calls back to a virtual method called ParseValue() when it has a token that needs to be interpreted. So ParseValue() in the NDLScript class is the main location where interpretation of tokens takes place. + +NDL supports Macros, which makes things much more convenient, but a bit messier for the developer to deal with. All the macros are parsed and stored in a Global script so they can be accessed by any NDL script. It also means that you don’t want to load or define a set of macros more than once, or you will get “function already exists” errors. + +The processing of NDL proceeds through the following phases: + +1. Parsing the script + +2. Evaluation – initial pass – create ComputationNodes for all NDLNodes that require it + +3. Evaluation – second pass – connect the inputs of the ComputationNodes + +4. Validate the network – This also has the side effect or allocating all the matrix classes to their correct dimensions, and computing dimensions derived from input nodes + +5. Evaluation – final pass – All operations that must have the matrix values present occur here. For example, matrix initialization happens here + +There is a helper class in NDLUtil, which will take care of executing through these various phases. It also tracks how far along in the current processing phase a script has progressed. Processing can continue statement by statement as needed. This is the want in-line NDL is processed. + +Now a little more detail is in order for these layers: + +#### Parsing + +- The script in question is first parsed, and as each Macro Definition, macro call, parameter, variable, or function call is encountered an NDLNode is created. This NDLNode describes the entity and a reference is stored in the NDLScript class which owns it so it can be freed at some later point in time. + +- If the NDLNode is an executable statement, it will be added in order to the list of statements to execute + +- All variable names used in a script will be added to a symbol table in the NDLScript class that owns the NDLNode. + +- If the NDLNode is a macro or function call its parameters will be parsed and added to a parameter list in the NDLNode. Note that parameters may actually be other function and macro calls. The actual parameter names used in the call and the names used in the macro that will be called are recorded. + +- If the NDLNode is a macro, it will have its own NDLScript, and contain its own list of executable statements. It will also be stored in the Global Script repository (just a global NDLScript class) + +#### Evaluation – initial pass + +- The main purpose of this pass is to create a Computation Node for every NDL node that requires one. Effectively every “Function call” in NDL maps to a Computation Node. The full “dot path” will be the name of the node in the Computational Network. + +- Each pass evaluates the entire script, but only certain actions are performed based on what pass is being executed. So though all the parameters are evaluated in NDL, only Function Calls will create computation nodes. + +#### Evaluation – second pass + +- This pass goes through the entire evaluation process again, but this time all Computation Network nodes should already exist. The main purpose of this pass is to hook up all the inputs between nodes. + +- Doing this in a separate pass allows nodes to be referenced before they are actually defined in the NDL Script. This is a necessary feature for Recursive Neural Networks with a DelayNode. + +- Having a separate pass allows inline-NDL to support DelayNodes, and “Just-in-time” execution of inline-NDL can occur, where depending on the MEL command that is being executed, we can evaluate the NDL to the appropriate level. Some MEL commands need a “final-state” network to excute safely, others may only require the initial pass to be completed. The initial pass is executed when the inline-NDL is encountered, and how much evaluation has happened for each node is tracked. + +- At the end of this pass the computational network is fully connected and complete + +#### Validation + +Validation multiple purposes: + +- Ensure that the network is fully connected and that all necessary network nodes exist + +- Ensure that the dimensions of the matrices passed to nodes are compatible with the nodes + +- Calculates dimensions that depend on input dimensions. This is a feature of Convolutional Networks to make them easier to define. + +- Allocate the memory for the matrices + +- Ensure the special nodes (CriteriaNode, Features, Labels, Output, etc.) exist if required for this network + +#### Evaluation – final pass + +- This pass does special processing that requires the matrices to exist. As an example there is an optional parameter for the Parameter() function that allows a parameter to be initialized in various ways (zero, random values, from a file), this requires the matrix to be there, so it is done in the final pass. + +#### Evaluation Process + +Now that the entire has been explained, a little more detail on how each Evaluation pass occurs seems relavant. + +- First the NDLScript::Evaluate() method is called. This take a few parameters the nodeEvaluator (which creates the actual Computation Nodes), and a baseName, and which pass we are on. + +- NDLScript::Evaluate() loops through it’s list of NDL statements and calls NodeEvaluator::Evaluate() of NodeEvaluator::EvaluateMacro() on each of them with the current baseName + +- For Macros EvaluateMacro() is called. This takes care of getting the arguments into the target macros symbol table with the correct actual parameters names associated with them. And calls the NDLScript::Evaluate() on the macro script, it also appends to the baseName so it’s still correct. + +- In subsequent passes, the node is looked up using the same name instead of created + +- The parameters (if any) to the node are evaluated in the first two passes, and in the second pass are assigned as inputs to the computation nodes. + +- In the second pass optional parameters are also processed. The “tag” optional parameter must be processed to identify features, labels, etc. + +- “dotNames” are currently handled separately, whenever one is referenced the baseName of the parent is added as a prefix to the current scoped name and looked up directly in the computational network. + +- There is an “evalValue” that gets set whenever a computational node is resolved to be associated with an NDLNode. However, when macros are called, this will usually be the wrong value, since it will hold the last time the macro was called, not necessarily the instance desired. To alleviate this issue, evalValues are always cleared out on every macro call and the values are used immediately after evaluation, otherwise the values may be different than expected. + +Though this explanation may not be complete hopefully it has been instructive, and if all else fails, trace through it in the debugger, and it will likely help. diff --git a/Documentation/Documents/External Buffer Behavior.docx b/Documentation/Documents/External Buffer Behavior.docx deleted file mode 100644 index a28df31835defd95daca8e9b89595f214f3dbf78..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26230 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 Mlh9o{{=M_;f4Cu|H2?qr diff --git a/Documentation/Documents/External Buffer Behavior.md b/Documentation/Documents/External Buffer Behavior.md new file mode 100644 index 000000000..2136845c8 --- /dev/null +++ b/Documentation/Documents/External Buffer Behavior.md @@ -0,0 +1,69 @@ +# ExternalBuffer in Matrix class + +There are at least 4 different implementations of the Matrix class that have over time diverged in their implementation in respect to how the external buffer case is handled. The external buffer case is when the matrix class does not actually own it's own memory and is pointing to an external buffer that is managed separately. A deviceID of MANAGEDEXTERN used to be the way this was done, however we have now moved to setting a flag m_externalBuffer in the common header to signify an eternal buffer. We have two instances of this in our code today: + +1. Column Slices were implemented using this feature. The idea is you only want to reference a portion of a full matrix, but don't want to copy the contents to a new matrix for efficiency reasons. In this case the slice can be modified just like a real matrix, and it is the programmers responsibility to ensure that the lifetime of the underlying matrix is longer than any of it's slices. NOTE: lifetime management is not taken care of for you, so be careful +2. PTask Buffers - PTask is our solution for using multiple GPUs. It uses a filter graph based approach for accelerating GPU applications. PTask executes a graph and calls each of it's tasks as the inputs are available. In CNTK most of these inputs are numeric arrays with an associated Matrix header metadata. We wrap the buffer in a Matrix shell with external buffers set, and call the normal processing methods. + +Both of these uses are similar, but slightly different as well. We believe that we can use the same implementations to satisfy both sets of needs. So here are the definitions: + +```c++ +Matrix(const size_t numRows, const size_t numCols, ElemType *pArray, const size_t matrixFlags=matrixFlagNormal, +short deviceId=AUTOPLACEMATRIX, const size_t nnz=0); +``` + +* Matrix constructor that constructs a matrix from a buffer pointer and some flags. The behavior depends on the flags. In all cases dimensions, format (from the matrixFlags), deviceId and nnz (for sparse representations) are copied: + * matrixFlagDontOwnBuffer - in this case the pArray pointer is set as the m_pArray of the matrix and m_externalBuffer = true + * matrixFlagSetValueOnDevice - if set this signifies that the buffer is on the proper device, but needs to be copied to newly allocated space for the m_pArray, m_externalBuffer = false + * neither set - the buffer is on the CPU and device memory is allocated and then the buffer is copied over, m_externalBuffer = false + +```c++ +Matrix(const Matrix& deepCopyFrom, short deviceId=AUTOPLACEMATRIX); //copy constructor, deep copy +``` + +* Matrix constructor that constructs a matrix from an existing matrix, Dimensions, format, and other elements are also copied: + * deepCopyFrom - regardless of if m_externalBuffer is set or not, a new buffer is allocated and the contents of the deepCopyFrom are copied to the new buffer. m_externalBuffer = false; + * NOTE: use move constructor or SetValue with matrixFlagDontOwnBuffer if an externalBuffer at the destination is desired + +```c++ +Matrix& operator=(const Matrix& deepCopyFrom); //assignment operator, deep copy +``` + +* assignment operator copies from one matrix to another. In all cases , dimensions, format, and other members are copied, m_externalBuffer is left unchanged, and copy of the buffer is buffer content only: + * destination normal, deepCopyFrom is external - destination is resized as necessary and then copy. + * destination is external, deepCopyFrom can be either - If the destination would require a resize, an exception is thrown, otherwise copy. + +```c++ +Matrix(Matrix&& moveFrom); //move constructor, shallow copy +``` + +* constructor with move semantics copies from one matrix to another: + * moveFrom is bitwise copied to the newly created matrix. So it is an exact copy of previous matrix (which is going to be discarded without destructors running) + +```c++ +Matrix& operator=(Matrix&& moveFrom); //move operator, shallow copy +``` + +* assignment operator with move semantics copies from one matrix to another: + * destination normal - In this case existing buffers are freed, and then everything is bitwise copied (including m_externalBuffer flag). + * destination is external - bitwise copy over everything (including m_externalBuffer flag) + +```c++ +void SetValue(const Matrix& deepCopyFrom); +``` + +* Straight copy from one buffer to another, irrespective of m_external flags, which remain unchanged. If the destination is not large enough, it will be resized. If buffer mismatch occurs and the destination is m_externalBuffer, it will throw an exception. + +```c++ +void SetValue(const size_t numRows, const size_t numCols, ElemType *pArray, +const size_t matrixFlags=matrixFlagNormal, int deviceId=MANAGEDEXTERN); +``` + +* SetValue with a buffer pointer copies the contents of that buffer to the matrix, resizing the destination as necessary. Also sets the format (through a mask of the matrixFlags) and deviceId of the matrix: + * matrixFlagDontOwnBuffer set, destination normal - Free the contents of the current array buffer, replace pointer, dimensions, m_externalBuffer = true + * matrixFlagDontOwnBuffer set, destination external - replace pointer and dimensions, m_externalBuffer = true + * matrixFlagSetValueOnDevice set, destination normal - the buffer is on the proper device, resize destination as necessary, set the dimensions and copy buffer to the current array, m_externalBuffer = false + * matrixFlagSetValueOnDevice set, destination external - the buffer is on the proper device, throw if dimensions are incompatible, set the dimensions and copy buffer content to the current array location, m_externalBuffer = false + * no flags set, destination normal - the buffer is on the CPU, resize destination as necessary, set the dimensions and copy buffer to the current array, m_externalBuffer = false + * no flags set, destination external - the buffer is on the CPU, throw if dimensions are incompatible, set the dimensions and copy buffer content to the current array location, m_externalBuffer = false + diff --git a/Documentation/Documents/Model Editing Language.docx b/Documentation/Documents/Model Editing Language.docx deleted file mode 100644 index f02b3eb7a83d3036795537a8ab1e5ed14d8c8651..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 58153 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