Skip to content

Internalization of TensorFlowUtils.cs and refactored TensorFlowCatalog. #2672

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

Merged
merged 14 commits into from
Mar 1, 2019

Conversation

zeahmed
Copy link
Contributor

@zeahmed zeahmed commented Feb 20, 2019

This PR fixes #2552 and also fixes #2572.

The discussion in these issues are taken as initial feedback to create this PR. The PR groups all the TensorFlow related operations into TensorFlowCatalog. This catalog now appears as a separate catalog in TransformsCatalog e.g. ScoreTensorFlowModel that appeared under Transforms catalog as

mlContext.Transforms.ScoreTensorFlowModel

now appears under TensorFlow as

mlContext.Transforms.TensorFlow.ScoreTensorFlowModel

How breaking is this change at this stage?

Also, the PR also include following methods in TensorFlow catalog.

public static DataViewSchema GetModelSchema(this TransformsCatalog.TensorFlowTransforms catalog, string modelLocation)
    => TensorFlowUtils.GetModelSchema(CatalogUtils.GetEnvironment(catalog), modelLocation);

public static IEnumerable<(string, string, DataViewType, string[])> GetModelNodes(this TransformsCatalog.TensorFlowTransforms catalog, string modelLocation)
    => TensorFlowUtils.GetModelNodes(CatalogUtils.GetEnvironment(catalog), modelLocation);

public static TensorFlowModelInfo LoadTensorFlowModel(this TransformsCatalog.TensorFlowTransforms catalog, string modelLocation)
    => TensorFlowUtils.LoadTensorFlowModel(CatalogUtils.GetEnvironment(catalog), modelLocation);

There is also a suggestion to add these methods into DataCatalog. Feedback is requested in this regard.

CC: @TomFinley, @yaeldekel, @rogancarr, @Ivanidzo4ka

@codecov
Copy link

codecov bot commented Feb 20, 2019

Codecov Report

Merging #2672 into master will decrease coverage by <.01%.
The diff coverage is 80%.

@@            Coverage Diff             @@
##           master    #2672      +/-   ##
==========================================
- Coverage   71.54%   71.54%   -0.01%     
==========================================
  Files         801      801              
  Lines      141855   141863       +8     
  Branches    16120    16120              
==========================================
+ Hits       101485   101489       +4     
- Misses      35920    35925       +5     
+ Partials     4450     4449       -1
Flag Coverage Δ
#Debug 71.54% <80%> (-0.01%) ⬇️
#production 67.83% <62.5%> (-0.01%) ⬇️
#test 85.73% <88.23%> (ø) ⬆️
#Resolved

@codecov
Copy link

codecov bot commented Feb 20, 2019

Codecov Report

Merging #2672 into master will increase coverage by 0.02%.
The diff coverage is 95.29%.

@@            Coverage Diff             @@
##           master    #2672      +/-   ##
==========================================
+ Coverage   71.66%   71.68%   +0.02%     
==========================================
  Files         809      809              
  Lines      142378   142401      +23     
  Branches    16119    16112       -7     
==========================================
+ Hits       102030   102078      +48     
+ Misses      35915    35890      -25     
  Partials     4433     4433
Flag Coverage Δ
#Debug 71.68% <95.29%> (+0.02%) ⬆️
#production 67.92% <100%> (+0.02%) ⬆️
#test 85.86% <91.3%> (+0.01%) ⬆️
Impacted Files Coverage Δ
...st/Microsoft.ML.Tests/Scenarios/TensorflowTests.cs 100% <100%> (ø) ⬆️
...ensorFlow.StaticPipe/TensorFlowStaticExtensions.cs 100% <100%> (ø) ⬆️
...est/Microsoft.ML.Tests/TensorFlowEstimatorTests.cs 98.2% <100%> (ø) ⬆️
src/Microsoft.ML.TensorFlow/TensorflowCatalog.cs 100% <100%> (+16.66%) ⬆️
src/Microsoft.ML.TensorFlow/TensorflowTransform.cs 79.76% <100%> (-0.44%) ⬇️
src/Microsoft.ML.TensorFlow/TensorFlowModel.cs 100% <100%> (ø)
...rosoft.ML.TensorFlow/TensorFlow/TensorflowUtils.cs 65.36% <100%> (+7.44%) ⬆️
...cenariosWithDirectInstantiation/TensorflowTests.cs 91.67% <90.69%> (-0.09%) ⬇️
src/Microsoft.ML.Maml/MAML.cs 24.75% <0%> (-1.46%) ⬇️
...ML.Transforms/Text/StopWordsRemovingTransformer.cs 85.53% <0%> (-0.17%) ⬇️
... and 9 more

/// </summary>
/// <param name="catalog">The transform's catalog.</param>
/// <param name="modelLocation">Location of the TensorFlow model.</param>
public static IEnumerable<(string, string, DataViewType, string[])> GetModelNodes(this TransformsCatalog.TensorFlowTransforms catalog, string modelLocation)
Copy link

@yaeldekel yaeldekel Feb 20, 2019

Choose a reason for hiding this comment

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

GetModelNodes [](start = 76, length = 13)

This doesn't need to be a part of the public API. #Resolved

Copy link
Contributor Author

@zeahmed zeahmed Feb 21, 2019

Choose a reason for hiding this comment

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

It is being use here.

foreach (var (name, opType, type, inputs) in TensorFlowUtils.GetModelNodes(new MLContext(), args[0]))

If I don't expose it like this then I will have to make the internal one public. What do you suggest?


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

@yaeldekel
Copy link

yaeldekel commented Feb 20, 2019

    public DataViewSchema GetModelSchema()

I would make this internal (and GetInputSchema as well) and just expose them through the TensorflowCatalog. #Resolved


Refers to: src/Microsoft.ML.TensorFlow/TensorFlowModelInfo.cs:46 in e5eef19. [](commit_id = e5eef19, deletion_comment = False)

@yaeldekel
Copy link

    public DataViewSchema GetModelSchema()

If you do this, then this class doesn't need to have an IHostEnvironment member, because you can use CatalogUtils.GetEnvironment(catalog).


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


Refers to: src/Microsoft.ML.TensorFlow/TensorFlowModelInfo.cs:46 in e5eef19. [](commit_id = e5eef19, deletion_comment = False)

/// </summary>
/// <param name="catalog">The transform's catalog.</param>
/// <param name="modelLocation">Location of the TensorFlow model.</param>
public static DataViewSchema GetModelSchema(this TransformsCatalog.TensorFlowTransforms catalog, string modelLocation)
Copy link

@yaeldekel yaeldekel Feb 21, 2019

Choose a reason for hiding this comment

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

GetModelSchema [](start = 37, length = 14)

I think GetModelSchema should take a TensorFlowModelInfo instead of a string
(so that users can use the already loaded model they got from LoadTensorFlowModel). #Resolved

/// <summary>
/// List of operations for using TensorFlow model.
/// </summary>
public TensorFlowTransforms TensorFlow { get; }
Copy link
Contributor

@TomFinley TomFinley Feb 21, 2019

Choose a reason for hiding this comment

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

TensorFlowTransforms [](start = 15, length = 20)

Please do not do this. Otherwise we will have an empty property unless someone imports the nuget, which is confusing and undesirable.

Please follow instead the pattern that we see in image processing. You'll note that we do not have an empty image processing nuget. Rather they are added to this catalog. Similar with ONNX scoring. You'll note that these both have extensions on this TransformsCatalog catalog. What we don't have are empty properties "Images" and "ONNX" littering our central object that users interact with to instantiate components.

This is defensible since we can take someone directly importing a nuget as a strong signal that they want to actually use those transforms. #Resolved

/// </summary>
/// <param name="catalog">The transform's catalog.</param>
/// <param name="tensorFlowModel">The pre-loaded TensorFlow model. Please see <see cref="LoadTensorFlowModel(TransformsCatalog.TensorFlowTransforms, string)"/> to know more about loading model into memory.</param>
public static DataViewSchema GetInputSchema(this TransformsCatalog.TensorFlowTransforms catalog, TensorFlowModelInfo tensorFlowModel)
Copy link
Contributor

@TomFinley TomFinley Feb 21, 2019

Choose a reason for hiding this comment

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

GetInputSchema [](start = 37, length = 14)

This API does not seem like any C# API I've seen. It seems more like a C API with a bunch of static functions that you call with a pointer to a structure, rather than anything like what I'd expect in an actual object oriented property. Why is not everything you've added as an extension method, except possibly the creation of what you call now this TensorFlowModelInfo, just a property or a method of that TensorFlowModelInfo? #Resolved

Choose a reason for hiding this comment

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

Tom, I was under the impression that we wanted to have as much of the functionality as possible accessible from MLContext, and expose as few classes as possible, that's why I suggested exposing these methods here. Do you think the LoadTensorFlowModel API should still be an extension method on TransformsCatalog, or should we expose the TensorFlowUtils class for that?


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

Copy link
Contributor

@TomFinley TomFinley Feb 22, 2019

Choose a reason for hiding this comment

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

I don't think that's how we've been doing things at all. We use MLContext for things like component creation, but once you, to give the most conspicuous example, create an estimator, the creation of a transformer, the getting of information out of the transformer, the usage of the transformer to transform a data view, that does not involve the MLContext.

You create a model. The model is the object, you use objects by calling methods and properties on those objects, just like everything else. Recall the title of #1098 that is I'd say the central MLContext... one MLContext to create them all, not to use them all.

I gave examples above about how we don't structure our APIs as they are structured here, and I think my reasoning is pretty solid. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @TomFinley, I hope I addressed your concern here and other places. I am changing it resolved for now.


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

Copy link
Contributor

Choose a reason for hiding this comment

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

Marking unresolved... I do not see that anything has changed at all. TensorFlowModel has no meaningful methods on it, everything is still being exposed via these extension methods on top of properties.


In reply to: 259545815 [](ancestors = 259545815,259531678)

Copy link
Contributor Author

@zeahmed zeahmed Feb 27, 2019

Choose a reason for hiding this comment

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

Thanks @TomFinley for feedback. I have refactored the code according to the your comments.


In reply to: 260027159 [](ancestors = 260027159,259545815,259531678)

Copy link
Contributor

@TomFinley TomFinley left a comment

Choose a reason for hiding this comment

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

Hi @zeahmed, this requires two changes:

First, the API should be methods and properties on returned TensorFlowModelInfo (though please don't call it that -- TensorFlowModel will do). This will make it resemble things like the rest of ML.NET and every other C# library. The current system reminds me of how C libraries are structured.

Consider estimators, transforms, schema, etc. Note that we do not do this:

var trans = mlContext.Transforms.Fit(estimator, trainData);
var predData = mlContext.Transforms.Transform(trans, testData);
mlContext.Schema.GetColumn(mlContext.SchemaOperations.GetSchema(predData), 1);

We do this.

var trans = estimator.Fit(trainData);
var predData = trans.Transform(testData);
predData.Schema[1];

And we have been doing that and things like that since C++ was invented lo these many years ago. 😄 But seriously, we like our APIs looking like the latter, not the former. At the moment TensorFlowModelInfo is itself just a glorified string. Let's turn it into an actual object.

Second, get rid of TensorFlowCatalog. We should never have empty properties just lying around confusing people. Especially . MLContext.Model is far more appropriate. Remember: only one method here to get the "info," and ubsequent interactions and operations should be done on that object.

/// </summary>
public class TensorFlowModelInfo
public sealed class TensorFlowModelInfo
{
internal TFSession Session { get; }
public string ModelPath { get; }
Copy link

@yaeldekel yaeldekel Feb 21, 2019

Choose a reason for hiding this comment

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

public [](start = 8, length = 6)

I think this can also be internal. #Resolved

/// </summary>
public class TensorFlowModelInfo
public sealed class TensorFlowModelInfo
Copy link

@yaeldekel yaeldekel Feb 21, 2019

Choose a reason for hiding this comment

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

TensorFlowModelInfo [](start = 24, length = 19)

Would it be possible to rename this to TensorFlowModel? #Resolved

@@ -45,7 +45,7 @@ public static void Example()
// Load the TensorFlow model once.
// - Use it for quering the schema for input and output in the model
// - Use it for prediction in the pipeline.
var modelInfo = TensorFlowUtils.LoadTensorFlowModel(mlContext, modelLocation);
var modelInfo = mlContext.Transforms.LoadTensorFlowModel(modelLocation);
Copy link
Contributor

@TomFinley TomFinley Feb 22, 2019

Choose a reason for hiding this comment

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

mlContext.Transforms.LoadTensorFlowModel [](start = 28, length = 40)

This doesn't seem like a transform. This seems like you're loading a model, so should it be under "model?" #Resolved

Copy link
Contributor Author

@zeahmed zeahmed Feb 22, 2019

Choose a reason for hiding this comment

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

Sounds reasonable. I see some of the ONNX model related methods are available via model catalog. I will move it. Thanks!


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

@@ -45,7 +45,7 @@ public static void Example()
// Load the TensorFlow model once.
// - Use it for quering the schema for input and output in the model
// - Use it for prediction in the pipeline.
var modelInfo = TensorFlowUtils.LoadTensorFlowModel(mlContext, modelLocation);
var modelInfo = mlContext.Transforms.LoadTensorFlowModel(modelLocation);
var schema = modelInfo.GetModelSchema();
var featuresType = (VectorType)schema["Features"].Type;
Copy link

@yaeldekel yaeldekel Feb 22, 2019

Choose a reason for hiding this comment

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

Features [](start = 51, length = 8)

Can we add a sample that uses modelInfo.GetInputSchema() to find out what the name of the input node is?

#Resolved

Copy link
Contributor Author

@zeahmed zeahmed Feb 23, 2019

Choose a reason for hiding this comment

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

I see its being used at a couple of places in the tests e.g.

#Resolved

@@ -20,20 +19,20 @@ namespace Microsoft.ML.Transforms
/// </item>
/// </list>
/// </summary>
public class TensorFlowModelInfo
public sealed class TensorFlowModel
Copy link
Contributor

@TomFinley TomFinley Feb 25, 2019

Choose a reason for hiding this comment

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

I think possibly maybe there's some misunderstanding. Just to be explicit, I expect to see methods used to query the model to be on the model, that is, they will be just methods and properties of the model. Creatingtransformers, querying the schema, or whatever, will be here. The implication is that nearly everything in TensorflowCatalog will be moved out of there, and into here. (Except for loading the model, which of course must be on the model operations catalog.) #Resolved

@@ -59,7 +60,7 @@ public static class TensorflowCatalog
/// <param name="inputColumnName"> The name of the model input.</param>
/// <param name="outputColumnName">The name of the requested model output.</param>
public static TensorFlowEstimator ScoreTensorFlowModel(this TransformsCatalog catalog,
Copy link
Contributor

@TomFinley TomFinley Feb 25, 2019

Choose a reason for hiding this comment

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

ScoreTensorFlowModel [](start = 42, length = 20)

This should be an operation on the model. #Resolved

@@ -79,7 +80,7 @@ public static class TensorflowCatalog
/// </format>
/// </example>
public static TensorFlowEstimator ScoreTensorFlowModel(this TransformsCatalog catalog,
Copy link
Contributor

@TomFinley TomFinley Feb 25, 2019

Choose a reason for hiding this comment

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

ScoreTensorFlowModel [](start = 42, length = 20)

Likewise on the model.. #Resolved

@@ -102,7 +103,16 @@ public static class TensorflowCatalog
/// <param name="tensorFlowModel">The pre-loaded TensorFlow model.</param>
public static TensorFlowEstimator TensorFlow(this TransformsCatalog catalog,
Copy link
Contributor

@TomFinley TomFinley Feb 25, 2019

Choose a reason for hiding this comment

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

TensorFlow [](start = 42, length = 10)

So this I find pretty confusing. Do we create estimators via this method, or do we work through the model object? #Resolved

@TomFinley
Copy link
Contributor

TomFinley commented Feb 25, 2019

    public static TensorFlowEstimator TensorFlow(this TransformsCatalog catalog,

Just more broadly, should these be verbs? #Resolved


Refers to: src/Microsoft.ML.TensorFlow/TensorflowCatalog.cs:94 in fc188cd. [](commit_id = fc188cd, deletion_comment = False)

@yaeldekel
Copy link

    public static TensorFlowEstimator TensorFlow(this TransformsCatalog catalog,

Also, do we generally expose APIs creating estimators from Options? From what I've seen the signature is either all the arguments separately, or a params array of ColumnInfo objects.


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


Refers to: src/Microsoft.ML.TensorFlow/TensorflowCatalog.cs:94 in fc188cd. [](commit_id = fc188cd, deletion_comment = False)

@zeahmed
Copy link
Contributor Author

zeahmed commented Feb 27, 2019

    public static TensorFlowEstimator TensorFlow(this TransformsCatalog catalog,

I think we are exposing APIs with Options. e.g.

public static LightGbmBinaryTrainer LightGbm(this BinaryClassificationCatalog.BinaryClassificationTrainers catalog,
. Do you think it should not be exposed?


In reply to: 467552241 [](ancestors = 467552241,467198148)


Refers to: src/Microsoft.ML.TensorFlow/TensorflowCatalog.cs:94 in fc188cd. [](commit_id = fc188cd, deletion_comment = False)

@yaeldekel
Copy link

    public static TensorFlowEstimator TensorFlow(this TransformsCatalog catalog,

In TransformsCatalog we usually don't expose the Options.
But I think that we do need an extension method on TransformsCatalog to create a TensorFlowEstimator.


In reply to: 467668930 [](ancestors = 467668930,467552241,467198148)


Refers to: src/Microsoft.ML.TensorFlow/TensorflowCatalog.cs:94 in fc188cd. [](commit_id = fc188cd, deletion_comment = False)

@zeahmed
Copy link
Contributor Author

zeahmed commented Feb 27, 2019

    public static TensorFlowEstimator TensorFlow(this TransformsCatalog catalog,

@TomFinley's idea is not to have those methods in TransformsCatalog. Let me know @TomFinley what do you suggest?


In reply to: 467939019 [](ancestors = 467939019,467668930,467552241,467198148)


Refers to: src/Microsoft.ML.TensorFlow/TensorflowCatalog.cs:94 in fc188cd. [](commit_id = fc188cd, deletion_comment = False)

@TomFinley
Copy link
Contributor

TomFinley commented Feb 28, 2019

@TomFinley's idea is not to have those methods in TransformsCatalog. Let me know @TomFinley what do you suggest?

In reply to: 467939019 [](ancestors = 467939019,467668930,467552241,467198148)

Refers to: src/Microsoft.ML.TensorFlow/TensorflowCatalog.cs:94 in fc188cd. [](commit_id = fc188cd, deletion_comment = False)

There are I think two distinct things being discussed. One is the presence of advanced options. @yaeldekel is correct, we generally try to avoid having these advanced options where we can, but we definitely have it as part of the public surface area multiple places where it is unavoidable. It is more common in so-called trainer estimators rather than plain old featurizing estimators. If you can avoid it @zeahmed, I'd do so. But I could easily imagine that something as involved as an interface to TensorFlow might justify a bit more complexity.

The second question I think is about whether creation of the estimators should be on the transform catalog. I'd say in this case no, since we create the model out of MLContext, and once we have it, there's no longer any need to involve MLContext, so, let's not.

Nonetheless over the past week it's become clear to me that there is deep discomfort about this, so I'll try to address that, by telling you how I'm thinking about this. I tend to think of the introduction of MLContext as a mechanism by which we avoid throwing these IHostEnvironment objects around, when we create components that require an IHostEnvironment.

Now then, it just so happens that often these "initial components" are estimators, but I don't attach any special significance to that. We have plenty of cases where this is not the case: operations on Data often take and return IDataView directly, on Model we are often loading ITransformer, and so on).

So we see, naturally, this progression oftentimes, of MLContext operating over all of these things, as is appropriate in teh context.

IEstimator -> ITransformer -> IDataView

and sometimes this parallel structure for ingestion via loaders:

IDataLoaderEstimator -> IDataLoader -> IDataView

As far as I am aware we have hitherto always had the MLContext create components somewhere along this continuum, never (I think) outside of it. Yet in this case there is something happening before this continuum. We have this "chain of creation."

TensorFlowModel -> IEstimator -> ITransformer -> IDataView

I am saying, I think, that I don't view this chain as being in any way special to the more typical chain. Given that we're creating IEstimator out of this tensorflow model, there's no longer any reason to involve MLContext beyond that initial call to create the model -- that is, the creation of the estimator seems not to rely in any way on the context, so, it should not. The model object itself is perfectly adequate and equal to the task. Better, really. #Resolved

Copy link
Contributor

@TomFinley TomFinley left a comment

Choose a reason for hiding this comment

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

Thanks @zeahmed !

@yaeldekel
Copy link

yaeldekel commented Mar 1, 2019

    public sealed class Options : TransformInputBase

This can be internal now. #Resolved


Refers to: src/Microsoft.ML.TensorFlow/TensorflowTransform.cs:1002 in 7cd88ed. [](commit_id = 7cd88ed, deletion_comment = False)

Copy link

@yaeldekel yaeldekel left a comment

Choose a reason for hiding this comment

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

:shipit:

@zeahmed zeahmed merged commit f78af89 into dotnet:master Mar 1, 2019
@ghost ghost locked as resolved and limited conversation to collaborators Mar 24, 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.

TensorFlowUtils doesn't really fit with the 1.0 API Should TensorFlowModelInfo be public?
3 participants