Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Proposal: Sweepable API - #5993

Merged
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi
Oct 12, 2022
Merged

Proposal: Sweepable API#5993
michaelgsharp merged 2 commits into
dotnet:mainfrom
LittleLittleCloud:u/xiaoyun/sweepableApi

Conversation

@LittleLittleCloud

@LittleLittleCloudLittleLittleCloud commented Nov 2, 2021

Copy link
Copy Markdown
Member

This is the initial design/proposal doc for a Sweepable API.

I would appreciate it if you could each review it and give me your feedback.

@LittleLittleCloudLittleLittleCloud added the AutoML.NET Automating various steps of the machine learning process label Nov 2, 2021
@codecov

codecovBot commented Nov 2, 2021

Copy link
Copy Markdown

Codecov Report

❗ No coverage uploaded for pull request base (main@b31b7ca). Click here to learn what that means.
The diff coverage is n/a.

@@ Coverage Diff @@## main #5993 +/- ##
=======================================
Coverage ? 68.54% =======================================
Files ? 1146 Lines ? 245450 Branches ? 25637 =======================================
Hits ? 168240 Misses ? 70502 Partials ? 6708 
FlagCoverage Δ
Debug68.54% <0.00%> (?)
production63.32% <0.00%> (?)
test88.67% <0.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

@@ -0,0 +1,162 @@
# AutoML.Net Sweepable API proposal

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ericstj@eerhardt@michaelgsharp@justinormont

Hey All!
This proposal is to bring over the lower layers of our tooling's AutoML. All of the techniques that we worked with MSR to use in tooling (NNI, FLAML) are built on top of this.

@LittleLittleCloud is proposing that we bring over these lower layers first and then bring over the actual NNI+FLAML based AutoML next or upgrade AutoMl.NET to be the NNI+FLAML based approach. I think it's possible we could keep or nearly keep the same AutoMl.NET API.

I'm open to keeping these layers internal if we need to ... but sweeping is a common thing to do in ML world. We've had several community members come up with their own ways to sweep over models and parameters. This would just expose our method of doing it.

The end goal is to only have one ML.NET AutoML.

@JakeRadMSFTJakeRadMSFTNov 3, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jwood803 ( I saw your like ) we'd love to hear your feedback by as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, @JakeRadMSFT. I definitely like having this API! This helps make it more on part with scikit-learn that has grid search. Plus, I believe this will help model creators get better models.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to keeping these layers internal if we need to

My suggestion would be to start with them internal, and ensure it can meet the existing AutoML scenarios. Then make them public as we go when we have data that supports they are needed to be public.

The end goal is to only have one ML.NET AutoML.

💯

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, if we want to start from internal first, a smooth start without breaking change can be search space, either through package reference or source code. The reasons are

  • search space has the least dependency. It only depends on newtonsoft, which makes it easy to introduce.
  • after introducing, we can use it to create search space for existing trainers in AutoML.Net. Which provides us a chance to improve the performance of AutoML.Net experiments by using larger, better search space in hpo as well.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a fine plan, including keeping as internal for a bit.

If it fits within your methods, I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params). For AutoML․NET, due to non-great reasons, we kept a duplicate copy. Traveling with the component is more clean since the ranges show up (and disappear) with the availability of the component. For instance, as the user includes the NuGet for FastTree, the ranges are immediately available. In addition the ranges can be created inline with the new component.

Some historical perspective, there is also a command-line sweeper in ML․NET, which may be working (anything untested is assumed non-functional). This does let users sweep an individual (or sets of) components including fully switching out portions of the pipeline. The MAML command-line sweeper was very powerful, but hard to use. Mentioned previously: #5019 (comment)

@eerhardteerhardtNov 10, 2021

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It only depends on newtonsoft

If possible, can we use System.Text.Json?

I'd recommend having the default sweeping space { range, scaling, data type } as part of the component being swept (as current params)

I agree that this is a good strategy. It's better to keep the defaults with the component/trainer.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, can we use System.Text.Json?

Currently no because auto-binding experience depends heavily on JToken. However, there's a plan to remove this dependency so in the future maybe.

```csharp
public class Option
{
[Range(2, 32768, init: 2, logBase: true)]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to have a reflection based solution to start? Or would the "weakly-typed" API solve most of the scenarios:

varss=newSearchSpace();ss.Add("WindowSize",newUniformIntOption(2,32768,true,2));ss.Add("SeriesLength",newChoiceOption(2,3,4));ss.Add("UseSoftmax",newChoiceOption(true,false));ss.Add("AnotherOption",ss.Clone())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason I ask is: I would not add multiple ways to do something right away. Instead, make the "core" thing first, and the simpler ones can be built later, if necessary.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reflection API (let's call it strong-typed correspond with "weakly-typed") is just a handy way for creating search space and it's built on top of "weakly-typed". So if we must make a choice at the beginning I would pick "weakly-typed" to implement/migrate first.

Comment threaddocs/specs/ML.Net Sweepable API.md
.Append(context.BinaryClassification.Calibrators.Naive(labelColumnName: @"Survived", scoreColumnName: @"Score"));
```

After sweepable pipeline is created, one can call `BuildTrainingPipeline` to convert it to a ml.net pipeline.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the type of the pipeline be the same as a normal ML.NET pipeline? What happens if someone tries to run it without calling that method first?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it won't be the same ML.Net pipeline. And user needs to call BuildTrainingPipeline with passed parameter to convert it to an ML.Net pipeline and only after that, they can use Fit/Transform to train their model.

I feel like that BuildTrainingPipeline might be too detailed an API to be exposed. And it will be better if it can be wrapped and only exposes a Run method. We can discuss it later though.

Console.WriteLine($"trial {i++}");

// convert sweepable pipeline to ml.net pipeline
var trainingPipeline = pipeline.BuildTrainingPipeline(context, param);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, but would there ever be a need to save the current state of the sweeper so that it can be stopped and then resumed at a later date?

@LittleLittleCloudLittleLittleCloudNov 22, 2021

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If sweeper supports that then yes. In fact continue training has already been supported in model builder cfo sweeper, it's just not exposed yet.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe for the future, but how about "import experiments"? That would help in case someone wants to distribute training to multiple nodes: a) save each experiment results to db b) before starting next iteration, fetch new from db and import

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@torronen
That's a wonderful suggestion, thanks!


## Current feedback
- the lambda function in `CreateSweepableEstimator` should not accept `MLContext` as first parameter.
- Should provide a higher level API for training similar with Experiment API in AutoML.Net.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should that higher level API be part of this though? Wouldn't that belong more in the AutoML side and less with the sweeper side since the sweeper is "lower level" and is expected to be more hands on?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup the higher level API will be along with exsiting AutoML Experiment API.

@torronen

torronen commented Dec 9, 2021

Copy link
Copy Markdown
Contributor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

From user perspective this looks great and promising! As I understand pipeline.SearchSpace will automatically keep track of the experiments, their results and parameters. So, basically, we could start each new tuning experiment by first running the best parameters from last experiment, then looping with tuner.Propose. Is it correct?

@torronen Quite close but not exactly accurate. Pipeline.SearchSpace will only define the boundary of parameters and and it's stateless. If you want to track parameter and their results under current api, you need to do it yourself. (save each parameter and their result in a dictionary or something..)

If possible, I'd also like to extract the results from pipeline.SearchSpace to help me get a better understanding of how the tuning affects performance. For example, I might want to plot a graph about how the number of trees affect FastForest to help me find optimal number considering training time vs. accuracy.

That's a great advice. It's also something that we want to heavily dive into on how to help users understand training process, like trend line of metric, importance of each parameter, etc etc...

@torronen

torronen commented Dec 10, 2021

Copy link
Copy Markdown
Contributor

@LittleLittleCloud ok I see. I assume Tuner needs to keep the results.

Something in general, the sweeping process is running through a loop. So it's very flexible on how you perform training and you can customize it into whatever way you want, like hold-out strategy, metric, or even training on multiple thread/on cloud...

// psudo code
// Input: Tuner, SearchSpace, SweepablePipeline, trainDataset, testDataset
foreach (var parameter in tuner.Propose())
{
var mlnetPipeline = SweepablePipeline.BuildTrainingPipeline(parameter)
// there're really no limit on how to train your model, the following example uses hold-out strategy
var model = mlnetPipeline.Fit(trainDataset)
var eval = model.Transform(testDataset)
// there're also no limit on how to calculate your metric, you can either use built-in optimize metric of define your own
var metric = calculateYourMetric(eval, truth)
// for some tuners they need to be aware of metric so as to propose the possible best parameter next time.
tuner.Update(parameter, metric)
}

Not sure if these are valid for this plan, but my next questions might be:

How to change the optimization metric?

optimization metric is a number calculated from predict and truth value so as long as you have access to predict and truth value (which is available) you can calculate whatever metric you want.

How to create custom optimization metric? See #5999 Maybe derived from other metrics (maybe as anonymous method?), or even create a mini-simulation. Based on my initial experiments with simulating real-life events, simulation results might be more sensitive to tuning than traditional metrics. For example, we may want to catch some of different types of malicious web requests to catch the bad actors. Same ML metrics may come for detecting 100% of all requests for a single type, but in this case we might only catch one bad actor. In a simulation we might want to see how early intruders are detected: it's not enough to detect intruder when the logout.

If ML.Net built-in optimization metric can't meet your request, you can define your own metric and calculate by yourself as long as you have predict and truth value. ML.Net DataFrame api might be helpful in this case.

How to create a new Tuner? Not sure if necessary. It can also be done without being this type of Tuner. Could be also one area where to make it easy for community to contribute their implementations as extension nugets, or even as part of Microsoft.ML if dev team thinks it is feasible.

You can implement ITuner interface to create your own tuner.

On a higher level API it will be not that easy to customize the entire training process. A fluent-style will be preferred here

var experiemnt = new AutoMLExperiment()
.AddPipeline(sweepablePipeline)
.AddTuner(tuner)
.AddMetric(async (predict, actual) => {// return metric})
.Train();

But you'll still have access to all low-level api so there'll be a way to customize the training.

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@torronen reply inline

@torronen

Copy link
Copy Markdown
Contributor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

@LittleLittleCloud

Copy link
Copy Markdown
MemberAuthor

@LittleLittleCloud BTW, does this proposal and search space API allow optimizing the booster in LightGBM? Or, should users run the tuning for DART, Gradient and Goss separately is they expect it to matter? Asking because it is an object, not value type, and the object has parameters inside it.

Or, maybe user can write his own if...else or switch depending on what the tuner wants to run next?

Yes you can, it really doesn't matter whether the property you want to sweep on is a value type or not, you can aways pass a factory method to create the trainer based on parameter sweeped from search space.

@michaelgsharp

Copy link
Copy Markdown
Contributor

@LittleLittleCloud Merging this in as the sweepable estimator itself is already checked in.

@michaelgsharp
michaelgsharp merged commit 20692fe into dotnet:mainOct 12, 2022
@ghostghost locked as resolved and limited conversation to collaborators Nov 12, 2022
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

AutoML.NETAutomating various steps of the machine learning process

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants

@LittleLittleCloud@torronen@michaelgsharp@jwood803@justinormont@eerhardt@JakeRadMSFT