Skip to content

OneVersusAllModelParameters Strongly Typed #4013

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 19 commits into from

Conversation

michaelgsharp
Copy link
Contributor

Fixes #2467

We used to remove all Type information when we constructed our OneVersusAllModelParameters. This prevented the users from access the inner model without run-time casting.

This PR makes the OneVersusAllModelParameters strongly typed, and adds a strongly typed version of the OneVersusAllTrainer as it uses the OneVersusAllModelParameters. This change is no longer a breaking change to the current public api.

@@ -12,8 +12,7 @@ namespace Microsoft.ML
/// <see cref="ITrainer"/> and <see cref="IPredictor"/> it is still useful, but for things based on
/// the <see cref="IEstimator{TTransformer}"/> idiom, it is inappropriate.
/// </summary>
[BestFriend]
internal enum PredictionKind
public enum PredictionKind
Copy link
Member

Choose a reason for hiding this comment

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

These types aren't intended to be public. Can you keep them internal?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately not if we want the OneVersusAllModelParameters to be strongly typed and keep the backwards api compatibility. Since the original type was of IPredictorProducing<float>, once I type it that way then IPredictorProducing<T>, IPredictor, and PredictionKind have to all become public as well. The only other thought I had was that maybe I could change the type of the backwards compatible OneVersusAllModelParameters to be of type object instead of IPredictorProducing<T>. This will still keep the backwards API compatibility and would allow me to make those classes internal again.

Copy link
Member

Choose a reason for hiding this comment

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

Another option would be to not have the base class be generic. Have class OneVersusAllModelParametersBase and class OneVersusAllModelParameters : OneVersusAllModelParametersBase and class OneVersusAllModelParameters<T> : OneVersusAllModelParametersBase.

These IPredictor types are not meant to be public. We explicitly made them internal in 1.0 because they shouldn't be exposed to the user. So we will have to find another way here.

Copy link
Contributor Author

@michaelgsharp michaelgsharp Jul 18, 2019

Choose a reason for hiding this comment

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

I thought about that approach initially, but there are enough Strongly typed parameters in there I didn't think it would work. Let me test it and see what I can do there. If not, we can always change the type to object since that will work and not change anything about the api.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Alright, I have made the change following your suggestions, so now the interfaces are back to being internal. Thanks for your feedback!

Copy link
Member

Choose a reason for hiding this comment

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

PredictionKind should remain internal.

/// <summary>
/// Class that holds the static create methods for the <see cref="OneVersusAllModelParameters"/> classes.
/// </summary>
public class OneVersusAllModelParametersBuilder {
Copy link
Member

Choose a reason for hiding this comment

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

This class doesn't need to be public. It doesn't contain any public members.

Also, it can be marked as a static class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good points. I will change those.

/// This cannot be used to turn a non calibrated binary classification estimator into its calibrated version. If that is required, use <see cref="OneVersusAllTrainerTyped{TSubPredictor, TCalibrator}"/> instead.
/// </summary>
/// <typeparam name="T"></typeparam>
public sealed class OneVersusAllTrainerTyped<T> : OneVersusAllTrainerBase<OneVersusAllModelParametersBase<T>> where T : class
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure having Typed at the end of this class name is the best. Why can't it just be OneVersusAllTrainer<T>?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The problem is that the only difference between the original method and this one are the optional parameters. So if people are only specifying the required parameters, which is the most common case in our code base, then C# doesn't know which method to call. Isn't that a binary breaking change? So I chose to make this method have a different name. Do you have any other ideas on how to resolve it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same question about the other method I added in. I called it OneVersusAllUnCalibratedToCalibratedTyped, but since it has 2 type parameters that have to be manually specified, its name can be anything and it wont have conflicts. I just named it this to be very clear on what it does, though I am happy to change it as well if you have thoughts on this too.

Copy link
Member

Choose a reason for hiding this comment

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

This isn't a method, this is a class name. The class name and the method don't need to match 1-1.

If we need to suffix the method name with Typed or similar, that is fine. But the class doesn't need to have the suffix on it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see. I was thinking of the Trainers Catalog when I responded. I have fixed the class names and will push those changes. Thanks!

/// [!code-csharp[OneVersusAll](~/../docs/samples/docs/samples/Microsoft.ML.Samples/Dynamic/Trainers/MulticlassClassification/OneVersusAll.cs)]
/// ]]></format>
/// </example>
public static OneVersusAllTrainer<TModel> OneVersusAllTyped<TModel>(this MulticlassClassificationCatalog.MulticlassClassificationTrainers catalog,
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you tested the new OneVersusAllTyped to see that you can match the scores of the existing one?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep! I added and modified the tests in the OvaTest.cs to show that the scores match exactly.

Copy link
Contributor

Choose a reason for hiding this comment

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

The test which directly compares the score of the non-typed to the newly created typed version, is the perfect unit test. Thanks.

}
//.Append(ML.MulticlassClassification.Trainers.OneVersusAllUnCalibratedToCalibratedTyped<LinearBinaryModelParameters, PlattCalibrator>(sdcaTrainer))
Copy link
Contributor

Choose a reason for hiding this comment

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

Extraneous comment.

/// the binary classification estimator specified by <paramref name="binaryEstimator"/>.This method works with binary classifiers that
/// are not calibrated and need to be calibrated before use. Due to the type of estimator changing (from uncalibrated to calibrated), you must manually
/// specify both the type of the model and the type of the calibrator. If your classifier is already calibrated or it does not need to be, use the
/// <see cref="OneVersusAllTyped{TModel}"/> method instead.
Copy link
Contributor

Choose a reason for hiding this comment

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

May want to explicitly state:
If you want to retrieve the model parameters, you will want to use this strongly typed OneVersusAllTrainer.

And perhaps a note on the non-strongly typed which says similar.

@@ -528,15 +528,15 @@ internal sealed class ParameterMixingCalibratedModelParameters<TSubModel, TCalib
where TSubModel : class
where TCalibrator : class, ICalibrator
{
private readonly IPredictorWithFeatureWeights<float> _featureWeights;
private readonly TSubModel _featureWeights;

internal ParameterMixingCalibratedModelParameters(IHostEnvironment env, TSubModel predictor, TCalibrator calibrator)
: base(env, RegistrationName, predictor, calibrator)
{
Host.Check(predictor is IParameterMixer<float>, "Predictor does not implement " + nameof(IParameterMixer<float>));
Host.Check(calibrator is IParameterMixer, "Calibrator does not implement " + nameof(IParameterMixer));
Host.Assert(predictor is IPredictorWithFeatureWeights<float>);

Choose a reason for hiding this comment

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

Assert [](start = 17, length = 6)

Can this be a check instead of assert?

Choose a reason for hiding this comment

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

On second thought, why does this class need to have a generic type?


In reply to: 310376340 [](ancestors = 310376340)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This class needs to be generic to get the typed predictor and calibrator out of it. Otherwise OVA would just return this and it wouldn't be useful at all as far as getting the model goes. And I didn't do anything with the assert, so I am not sure why they did it that way in the beginning.

Choose a reason for hiding this comment

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

This specific class is internal anyway, so if OVA uses it users will not be able to get the typed calibrator anyway. But I don't think OVA uses this class, does it?


In reply to: 310687802 [](ancestors = 310687802)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So when OVA is returning the sub model parameters, if the model is calibrated it will return an instance of the CalibratedModelParametersBase. This class is public, and still allows access to the model parameters and the calibrator used for the training. Any changes made in this file were to facilitate this CalibratedModelParametersBase and returning the correct type, while still keeping the derived classes abstract.

@codemzs
Copy link
Member

codemzs commented Oct 3, 2019

@michaelgsharp Closing this because this is not actively worked on.

@codemzs codemzs closed this Oct 3, 2019
@michaelgsharp michaelgsharp deleted the OvaTyping branch December 3, 2019 22:38
@ghost ghost locked as resolved and limited conversation to collaborators Mar 21, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

OvaModelParameters is not strongly-typed
5 participants