使用通用接口处理 ONNX/ML.NET 模型
Handling ONNX/ML.NET models with generic interfaces
我在这里遇到了一个 ML.NET 相关的问题,希望有人可以帮助我。
我正在开发一个(.NET 核心)应用程序,它使用在编译时输入未知的 ONNX 模型。到目前为止我做了什么:
我能够在运行时编译包含输入 class 定义的程序集并加载此定义:
var genericSampleAssembly =
AssemblyLoadContext.Default.LoadFromAssemblyPath("/app/storage/sample.dll");
Type genericInputClass = genericSampleAssembly.GetType("GenericInterface.sample");
我还可以使用反射使用动态创建的输入类型训练模型:
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
是动态创建的程序集中的 class并且 GenericPrediction
是一个简单的 class,具有一个我在编译时知道的输出。
所以缺少的是:
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);
有没有人有类似的问题或有任何其他提示?
我可能漏掉了一些行,因为我 copy/pasted 并很快地简化了它。如有遗漏,稍后补上
编辑:我构建了一个最小示例来说明基本问题。正如评论中提到的,由于 ML.NET 方法,dynamic
是不可能的。
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() { }
}
}
您好
斯文
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[]
参数中的实际内容。
我在这里遇到了一个 ML.NET 相关的问题,希望有人可以帮助我。
我正在开发一个(.NET 核心)应用程序,它使用在编译时输入未知的 ONNX 模型。到目前为止我做了什么:
我能够在运行时编译包含输入 class 定义的程序集并加载此定义:
var genericSampleAssembly =
AssemblyLoadContext.Default.LoadFromAssemblyPath("/app/storage/sample.dll");
Type genericInputClass = genericSampleAssembly.GetType("GenericInterface.sample");
我还可以使用反射使用动态创建的输入类型训练模型:
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
是动态创建的程序集中的 class并且 GenericPrediction
是一个简单的 class,具有一个我在编译时知道的输出。
所以缺少的是:
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);
有没有人有类似的问题或有任何其他提示?
我可能漏掉了一些行,因为我 copy/pasted 并很快地简化了它。如有遗漏,稍后补上
编辑:我构建了一个最小示例来说明基本问题。正如评论中提到的,由于 ML.NET 方法,dynamic
是不可能的。
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() { }
}
}
您好 斯文
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[]
参数中的实际内容。