Обработка моделей ONNX / ML.NET с универсальными интерфейсами

2

Я борюсь с проблемой, связанной с 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() { }
    }
}

Привет свен

  • 0
    Проблема здесь, кажется, об отражении в глубокой объектной модели с обширными обобщениями. ML.NET просто является той стадией, на которой это разворачивается. Пожалуйста, создайте минимальный воспроизводимый пример, чтобы кому-то было проще помочь вам. Вы могли бы даже найти решение в процессе.
  • 0
    Да, это правда, основная проблема не имеет ничего общего с фреймворком, но фреймворк является причиной того, что другие возможные решения, такие как dynamic , не работают, поэтому я подумал, что я буду использовать этот пример для «большей картины». Но я думаю, что я поделюсь с большим количеством людей более общим примером, поэтому постараюсь представить его позже.
Показать ещё 2 комментария
Теги:
reflection
.net-core
ml.net
onnx

1 ответ

2
Лучший ответ

Хорошая работа на 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[].

  • 0
    Сначала я должен поблагодарить вас за этот ответ и отличное объяснение! К сожалению, у меня не было времени сегодня, но я надеюсь, что найду время, чтобы наконец реализовать это завтра. Единственное, в чем я сейчас не совсем уверен, это как обработать создание этого входного значения экземпляра genericInputClass. На самом деле genericInputClass содержит несколько полей, которые я должен установить перед вызовом предиката. Но я знаю имена полей только во время выполнения и должен сделать некоторые сопоставления раньше. Я прокомментирую еще раз и приму ответ, как только найду время для его реализации, думаю, я смогу решить его отсюда. Спасибо!
  • 0
    Я просто попробовал кое-что на этом минимальном примере, и все заработало. Activator.CreateInstance() для создания экземпляра, inputValue.GetType().GetFields() для получения полей классов и field.SetMethod.Invoke(inputValue, new object[] {fields value}) для установки значения определенного поля должно это сейчас. THX за вашу помощь!
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню