-1

I want to make a machine learning api for use with a web application, the field names will be passed to the api with their data types.

Currently I am making a class at runtime with the code provided in this answer: https://stackoverflow.com/a/3862241

The problem arises when I need to call the ML.NET PredictionFunction, I can't pass in the types for the generic function since they are made at runtime. I've tried using reflection to call it however it seems to not be able to find the function.

NOTE: Right now the docs for ML.NET is being updated for 0.9.0 so it is unavailable.

What I've tried is this (minimal):

Type[] typeArgs = { generatedType, typeof(ClusterPrediction) };
object[] parametersArray = { mlContext }; // value

MethodInfo method = typeof(TransformerChain).GetMethod("MakePredictionFunction");
if (method == null) { // Using PredictionFunctionExtensions helps here
  Console.WriteLine("Method not found!");
}
MethodInfo generic = method.MakeGenericMethod(typeArgs);
var temp = generic.Invoke(model, parametersArray);

The full (revised and trimmed) source (for more context): Program.cs

namespace Generic {
  class Program {
    public class GenericData {
      public float SepalLength;
      public float SepalWidth;
      public float PetalLength;
      public float PetalWidth;
    }
    public class ClusterPrediction {
      public uint PredictedLabel;
      public float[] Score;
    }

    static void Main(string[] args) {
      List<Field> fields = new List<Field>() {
                new Field(){ name="SepalLength", type=typeof(float)},
                new Field(){ name="SepalWidth", type=typeof(float)},
                new Field(){ name="PetalLength", type=typeof(float)},
                new Field(){ name="PetalWidth", type=typeof(float)},
            };
      var generatedType = GenTypeBuilder.CompileResultType(fields);

      var mlContext = new MLContext(seed: 0);
      TextLoader textLoader = mlContext.Data.TextReader(new TextLoader.Arguments() {
        Separator = ",",
        Column = new[]
        {
          new TextLoader.Column("SepalLength", DataKind.R4, 0),
          new TextLoader.Column("SepalWidth", DataKind.R4, 1),
          new TextLoader.Column("PetalLength", DataKind.R4, 2),
          new TextLoader.Column("PetalWidth", DataKind.R4, 3)
        }
      });
      IDataView dataView = textLoader.Read(Path.Combine(Environment.CurrentDirectory, "Data", "flowers.txt"););

      var pipeline = mlContext.Transforms
        .Concatenate("Features", "SepalLength", "SepalWidth", "PetalLength", "PetalWidth")
        .Append(mlContext.Clustering.Trainers.KMeans("Features", clustersCount: 3));
      var model = pipeline.Fit(dataView);

      Type[] typeArgs = { generatedType, typeof(ClusterPrediction) };
      object[] parametersArray = { mlContext }; // value

      MethodInfo method = typeof(TransformerChain).GetMethod("MakePredictionFunction");
      if (method == null) { // Using PredictionFunctionExtensions helps here
        Console.WriteLine("Method not found!");
      }
      MethodInfo generic = method.MakeGenericMethod(typeArgs);
      var temp = generic.Invoke(model, parametersArray);

      var prediction = temp.Predict(new GenericData {SepalLength = 5.6f, SepalWidth = 2.5f,
                                                     PetalLength = 3.9f, PetalWidth = 1.1f});
    }
  }
}
Paprika
  • 117
  • 2
  • 10
  • 1
    I'm not sure what exactly the question is. Is the problem that `method == null` i.e. the method can't be found on `TransformerChain`? – JohanP Jan 09 '19 at 22:04
  • 2
    Please provide [MCVE] - clearly that `TransformerChain` (whatever it is) does not have a method you are looking for... But there is no way to know why without real sample. – Alexei Levenkov Jan 09 '19 at 22:07
  • What makes you think `TransformerChain` has a _MakePredictionFunction_ method? (I assume this is the ML.NET `Microsoft.ML.Data.TransformerChain` type) –  Jan 09 '19 at 22:11
  • Thanks for the reply, everyone. I've added the full source. – Paprika Jan 09 '19 at 22:12
  • From what I can gather, `MakePredictionFunction` sits on `model` and not `TransformerChain`? – JohanP Jan 09 '19 at 22:13
  • `MakePredictionFunction` is an extension method. Behind the scenes, it's a static function defined on `PredictionFunctionExtensions` – Kevin Gosse Jan 09 '19 at 22:17
  • 1
    @Paprika You've now shown tons of *irrelevant* code. You should be showing a *minimal* reproducible example. Show what needs to be there to reproduce the problem, no more. – Servy Jan 09 '19 at 22:18
  • I used TransformerChain because that's what Monodevelop says the model variable is. I also tried getting the type. `Type modelType = model.GetType(); MethodInfo method = modelType.GetMethod("MakePredictionFunction");` – Paprika Jan 09 '19 at 22:21
  • I guess you confused `TransformerChain` with `TransformerChain`... `TransformerChain` is a static class (object instances or variables cannot have this type), whereas `TransformerChain` is an instantiable class. But still, even `TransformerChain` does not have a `MakePredictionFunction ` method (and it is a sealed class, too). So, you can't query the type for the method. –  Jan 09 '19 at 22:43
  • 1
    However, as Kevin Grosse pointed out, `MakePredictionFunction` is an extension method that can be applied to `ITransformer` instances, and `TransformerChain` is implementing `ITransformer`. Note that this extension method is not part of the `TransformerChain` type definition, thus you would also get `null` when querying the `TransformerChain` type. If you need the MethodInfo of an extension method, you need to query the type that declares the extension method. In case of `MakePredictionFunction` you would need to query the `PredictionFunctionExtensions` type... –  Jan 09 '19 at 22:50
  • 2
    @elgonzo you probably should convert your comment into an answer... Or maybe we should close this as dup of "call extension via reflection" (like https://stackoverflow.com/questions/1452261/how-do-i-invoke-an-extension-method-using-reflection) – Alexei Levenkov Jan 09 '19 at 23:00
  • @AlexeiLevenkov, i guess closing should be fine. There was a minor confusion about `TransformerChain` and `TransformerChain`, but the core of the problem is indeed how to find an extension method through reflection... –  Jan 09 '19 at 23:19
  • Possible duplicate of [How do I invoke an extension method using reflection?](https://stackoverflow.com/questions/1452261/how-do-i-invoke-an-extension-method-using-reflection) –  Jan 09 '19 at 23:22
  • for a generic call to work you must know statically what the type is....if you know what the type is, can't you just cast it...if you don't know the type...then you can't do anything...(its been a while since I used dynamic....and I've only used it about twice ever...I also know nothing about the problem domain) – MrD at KookerellaLtd Jan 10 '19 at 11:19

2 Answers2

0

Try reading your test data in an IDataView, than pass that IDataView to model.Transform();

That should insert the Score and the PredictedLabel as separate columns in your test data.

amy8374
  • 1,450
  • 3
  • 17
  • 26
  • This seems to be a good answer, it allows me to remove the need for a generated class entirely and get the data straight from the IDataView. Thank you! – Paprika Jan 09 '19 at 23:47
0

It seems, when trying to reflect for the MakePredictionFunction, you confused the TransformerChain<TLastTransformer> type (which is an instantiable generic type) with the static class TransformerChain.

But even reflecting upon TransformerChain<TLastTransformer> will not succeed, because MakePredictionFunction is not a method declared by this type. Rather, MakePredictionFunction is an extension method declared in the static class PredictionFunctionExtensions⁽¹⁾.

Thus, to get the MethodInfo for MakePredictionFunction, try this:

MethodInfo method = typeof(PredictionFunctionExtensions).GetMethod("MakePredictionFunction");



⁽¹⁾ I am not 100% certain which namespace PredictionFunctionExtensions resides in. Searching the ML.NET 0.9.0 API documentation, it seems it resides in the Microsoft.ML.Runtime.Data namespace. But trying to visit the actual documentation page for MakePredictionFunction currently only results in a 404 error, so there is a chance that this information might perhaps not be accurate (i am no ML.NET user, so i can't verify) :-(