【问题标题】:Handling ONNX/ML.NET models with generic interfaces使用通用接口处理 ONNX/ML.NET 模型
【发布时间】:2019-03-05 17:32:01
【问题描述】:

我在这里遇到了一个与 ML.NET 相关的问题,希望有人可以帮助我。

我正在开发一个(.NET 核心)应用程序,它使用在编译时输入未知的 ONNX 模型。到目前为止我做了什么:

我能够在运行时编译一个包含输入类定义的程序集并加载这个定义:

        var genericSampleAssembly =
            AssemblyLoadContext.Default.LoadFromAssemblyPath("/app/storage/sample.dll");
        Type genericInputClass = genericSampleAssembly.GetType("GenericInterface.sample");

我还可以使用反射来训练具有动态创建的 Inputtype 的模型:

        MethodInfo genericCreateTextLoader = typeof(TextLoaderSaverCatalog).GetMethods()
            .Where(_ => _.Name == "CreateTextLoader")
            .Single(_ => _.GetParameters().Length == 6)
            .MakeGenericMethod(_genericInputClass);

        TextLoader reader = genericCreateTextLoader.Invoke(_mlContext.Data, new object[] { _mlContext.Data, false, ',', true, true, false}) as TextLoader;

        IDataView trainingDataView = reader.Read("sample.txt");
        var debug = trainingDataView.Preview();

        var pipeline = _mlContext.Transforms.Concatenate("Features", _featureNamesModel
            .AppendCacheCheckpoint(_mlContext)
            .Append(_mlContext.Regression.Trainers.StochasticDualCoordinateAscent(labelColumn: "Label",
                featureColumn: "Features")));

        ITransformer model = pipeline.Fit(trainingDataView);

但我现在无法做出预测,因为我不知道如何调用 PredictionEngine。我能够获得该 CreatePredictionEngine 方法的通用版本,但现在不知道如何将该返回对象转换为 PredictionEngine 并最终调用 Predict 方法:

        MethodInfo genericCreatePredictionEngineMethod = typeof(PredictionEngineExtensions).GetMethods()
            .Single(_ => _.Name == "CreatePredictionEngine")
            .MakeGenericMethod(new Type[] { genericInputClass, typeof(GenericPrediction)});

        var predictionEngine = genericCreatePredictionEngineMethod.Invoke(_model, new object[] {_model, _mlContext, null, null});

predictionEngine 在这种情况下属于 Type 对象,但我需要将其转换为 PredictionEngine<genericInputClass, GenericPrediction> 之类的东西,而 genericInputClass 是来自动态创建的程序集的类,GenericPrediction 是一个简单的类,只有一个输出在编译时知道。

所以缺少的是这样的:

        MethodInfo genericCreatePredictionEngineMethod = typeof(PredictionEngineExtensions).GetMethods()
            .Single(_ => _.Name == "CreatePredictionEngine")
            .MakeGenericMethod(new Type[] { genericInputClass, typeof(GenericPrediction)});

        PredictionEngine<genericInputClass, GenericPrediction> predictionEngine = genericCreatePredictionEngineMethod.Invoke(_model, new object[] {_model, _mlContext, null, null}) as PredictionEngine<genericInputClass, GenericPrediction>;

        float prediction = predictionEngine.Predict(genericInputClass inputValue);

有没有人遇到过类似的问题或有其他提示?

我可能错过了一些行,因为我复制/粘贴并很快简化了它。万一有什么遗漏,我稍后会提供。

编辑:我构建了一个最小的例子来展示基本问题。正如 cmets dynamic 中所述,由于 ML.NET 方法,这是不可能的。

using System;
using System.Linq;
using System.Runtime.Loader;


namespace ReflectionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // Example with a known Type
            var extendedClass = new DummyExtendedClass();
            SampleGenericClass<String> sampleGenericClass = extendedClass.SampleGenericExtensionMethod<String>();
            sampleGenericClass.SampleMethod("");

            // At compile time unknown Type - In reality the loaded dll is compiled during runtime
            var runtimeCompiledSampleAssembly =
                AssemblyLoadContext.Default.LoadFromAssemblyPath("C:/Program Files (x86)/Reference Assemblies/Microsoft/Framework/.NETCore/v4.5/System.IO.dll");
            var compileTimeUnknownClass = runtimeCompiledSampleAssembly.GetType("System.IO.TextReader");

            var reflectedExtensionMethod = typeof(Extensions).GetMethods()
                .Single(_=>_.Name== "SampleGenericExtensionMethod")
                .MakeGenericMethod(new[] {compileTimeUnknownClass});

            var howToCastThis = reflectedExtensionMethod.Invoke(extendedClass, new object[] {extendedClass});

            // whats missing:
            // howToCastThis is of Type object but should be SampleGenericClass<System.IO.TextReader>
            // I need to be able to call howToCastThis.SampleMethod(new System.IO.TextReader)
            // I thought this might work via reflecting SampleMethod and MakeGenericMethod

            Console.ReadKey();
        }
    }

    public sealed class SampleGenericClass<T>
    {
        public void SampleMethod(T typeInput)
        {
            Console.WriteLine($"Invoking method worked! T is of type {typeof(T)}");
        }
    }

    public static class Extensions
    {
        public static SampleGenericClass<T> SampleGenericExtensionMethod<T>(this DummyExtendedClass extendedClass)
        {
            Console.WriteLine($"Invoking extension method worked! T is of type {typeof(T)}");
            return new SampleGenericClass<T>();
        }
    }

    public class DummyExtendedClass
    {
        public DummyExtendedClass() { }
    }
}

问候 斯文

【问题讨论】:

  • 这里的问题似乎与具有广泛泛型的深层对象模型中的反射有关。 ML.NET 恰好是它正在上演的舞台。请创建一个minimal reproducible example,以便他人更轻松地帮助您。您甚至可能会在此过程中找到解决方案。
  • 是的,没错,主要问题与框架无关,但框架是其他可能的解决方案(如dynamic)不起作用的原因,所以我想我会用这个例子“更大的图景”。但我想,我会用一个更通用的例子联系更多的人,所以我稍后会尝试提供它。
  • 不幸的是,由于对这个特定的库不熟悉,我很难跟踪您正在尝试做什么。我知道该怎么做,只是不知道如何应用它,因为我无法重现我的起点。
  • 我用一个最小的示例更新了帖子,您可能需要修改示例 dll 的路径。非常感谢任何解决此问题的帮助。

标签: c# reflection .net-core ml.net onnx


【解决方案1】:

在 MCVE 上做得很好。我能够调用SampleMethod;事实证明,它并没有太多的东西,而且它可能没有你想象的那么复杂。

在您的示例中,您获得的对象howToCastThis 是一种已经紧密构造的类型。只要从那个实例的类型开始,就不需要使用MakeGenericMethod

假设您有一个对象实例compileTimeTypeUnknownInstance,用于传递给SampleMethod 的参数。由于System.IO.TextReader 是抽象的,compileTimeTypeUnknownInstance 必须是具体的TextReader 派生类型。满足这些条件后,以下工作:

var sampleMethod = howToCastThis.GetType().GetMethods()
    .Single(mi => mi.Name == "SampleMethod");

sampleMethod.Invoke(howToCastThis, new object[] { compileTimeTypeUnknownInstance });

SampleMethod 报告 T 的类型为 System.Text.TextReader

同样,howToCastThis 是一个封闭构造类型,因此,您想要的方法也是如此。

注意:虽然这里不是这种情况,但是封闭构造类型中的方法可以引入额外的类型参数,因此在这种情况下您仍然必须调用 MakeGenericMethod 来封闭构造方法。

现在,如果我尝试将其转换为您的情况,我猜它看起来像这样:

var predictMethod = predictionEngine.GetType().GetMethods()
    .Single(mi => mi.Name == "Predict");

float prediction = (float)predictMethod.Invoke(predictionEngine, new object[] { inputValue });

我不确定您对Predict 的伪代码调用中的语法。我假设inputValue 是唯一的参数,而genericInputClass 只是表明它是封闭构造类型中的类型参数。如果这不正确,您需要弄清楚 object[] 参数中的实际内容。

【讨论】:

  • 首先我要感谢你的回答和很好的解释!不幸的是,我今天没有时间,但我希望明天我能找到时间最终实施。我现在唯一不完全确定的是如何处理该 genericInputClass 实例 inputValue 的创建。事实上,genericInputClass 包含几个我必须在调用 predict 之前设置的字段。但我只在运行时知道字段名称,并且之前必须做一些映射。一旦我有时间实施该问题,我将再次发表评论并接受答案,我想我可以从这里解决它。谢谢!
  • 我只是用那个最小的例子尝试了一些东西并让它工作。 Activator.CreateInstance() 创建实例,inputValue.GetType().GetFields() 获取类字段,field.SetMethod.Invoke(inputValue, new object[] {fields value}) 设置特定字段值现在应该这样做。感谢您的帮助!
  • 这是个好消息!我很高兴能帮上忙。
猜你喜欢
  • 2023-01-25
  • 1970-01-01
  • 2021-05-12
  • 1970-01-01
  • 2021-02-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-01
  • 1970-01-01
相关资源
最近更新 更多