Я борюсь с проблемой, связанной с ML.NET, и надеюсь, что кто-то может мне помочь.
Я разрабатываю (ядро .NET) приложение, которое использует модели ONNX, входные данные которых неизвестны во время компиляции. Что я сделал до сих пор:
Я могу скомпилировать сборку во время выполнения, которая содержит определение входного класса и загрузить это определение:
var genericSampleAssembly =
AssemblyLoadContext.Default.LoadFromAssemblyPath("/app/storage/sample.dll");
Type genericInputClass = genericSampleAssembly.GetType("GenericInterface.sample");
Также я могу тренировать модель с этим динамически созданным типом ввода, используя Reflection:
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
имеет тип объекта в этом случае, но мне нужно привести его к чему-то вроде 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);
У кого-нибудь была похожая проблема или есть другие намеки?
Я мог пропустить некоторые строки, потому что я скопировал/вставил и упростил это довольно быстро. На случай, если чего-то не хватает, я предоставлю это позже.
РЕДАКТИРОВАТЬ: я построил минимальный пример, чтобы показать основную проблему. Как уже упоминалось в комментариях, 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() { }
}
}
Привет свен
Хорошая работа на MCVE. Я был в состоянии вызвать SampleMethod
; Оказывается, это не так уж и много, и, вероятно, менее сложно, чем вы себе представляли.
В вашем примере объект, который вы получаете, howToCastThis
, уже howToCastThis
типа. Пока вы начинаете с этого типа экземпляра, вам не нужно использовать MakeGenericMethod
.
Допустим, у вас есть экземпляр объекта compileTimeTypeUnknownInstance
для параметра, который вы хотите передать в SampleMethod
. Поскольку System.IO.TextReader
является абстрактным, compileTimeTypeUnknownInstance
должен иметь конкретный TextReader
-derived. При соблюдении этих условий выполняются следующие работы:
var sampleMethod = howToCastThis.GetType().GetMethods()
.Single(mi => mi.Name == "SampleMethod");
sampleMethod.Invoke(howToCastThis, new object[] { compileTimeTypeUnknownInstance });
SampleMethod
сообщает, что T
имеет тип System.Text.TextReader
.
И снова, howToCastThis
имеет тип с закрытым построением, поэтому, как и метод, который вы хотите.
Примечание. Хотя это и не тот случай, метод MakeGenericMethod
типа может вводить дополнительные аргументы типа, поэтому вам все равно придется вызывать MakeGenericMethod
для закрытия метода в этом случае.
Теперь, если бы я попытался перевести это в вашу ситуацию, я думаю, это выглядело бы примерно так:
var predictMethod = predictionEngine.GetType().GetMethods()
.Single(mi => mi.Name == "Predict");
float prediction = (float)predictMethod.Invoke(predictionEngine, new object[] { inputValue });
Я не уверен насчет синтаксиса в вашем псевдокодовом вызове Predict
. Я предположил, что inputValue
был единственным параметром, а genericInputClass
был там только для указания того, что это аргумент типа в типе с закрытым построением. Если это было неправильно, вам нужно выяснить, что на самом деле идет в этом аргументе object[]
.
Activator.CreateInstance()
для создания экземпляра, inputValue.GetType().GetFields()
для получения полей классов и field.SetMethod.Invoke(inputValue, new object[] {fields value})
для установки значения определенного поля должно это сейчас. THX за вашу помощь!
dynamic
, не работают, поэтому я подумал, что я буду использовать этот пример для «большей картины». Но я думаю, что я поделюсь с большим количеством людей более общим примером, поэтому постараюсь представить его позже.