【问题标题】:ML.NET Create Prediction Engine using Dynamic ClassML.NET 使用动态类创建预测引擎
【发布时间】:2021-06-27 19:56:12
【问题描述】:

我有一个 ML.NET 应用程序,我必须在编译后动态创建接口 IDataView 以用于训练。我找到了这个thread,我已经能够成功地为训练数据集创建一个动态接口,然后用它来训练一个模型。当我尝试使用相同的界面以使用该训练有素的模型创建预测时,我的问题就出现了。 docs show,您应该创建一个预测引擎,您必须在其中定义输入和输出类类型才能创建引擎。比如:

mlContext.Model.CreatePredictionEngine<TSrc,TDst>(ITransformer, DataViewSchema)

其中 TSrcTDst 是编译时已知的类类型。我的问题是我在编译时不知道输入类类型的结构,必须为输入数据源创建一个动态接口。由于参数已知,因此可以定义输出类对象,但我不确定如何处理动态输入。

我想我可以尝试在界面上使用类似 GetType() 的东西,但它说“隐式类型的变量不能有多个声明符”。我的简化示例如下所示:

public class ModelOutput
{
    public string PredictedLabel { get; set; }
    public float[] Score { get; set; }
}

public class MakePrediction
{
    protected void Solve(IDataView data, ITransformer model)
    {
        var mlContext = new MLContext();
        var engine = mlContext.Model.CreatePredictionEngine<data.GetType(), ModelOutput>(model, data.Schema);
    }
}

【问题讨论】:

    标签: c# ml.net


    【解决方案1】:

    可以生成一个包含 DataViewSchema 中列出的所有字段的运行时类。这将允许您创建一个 PredictionEngine。

    您无法直接创建 PredictionEngine,您必须调用它。下面是一些示例代码:

            // Create runtime type from fields and types in a DataViewSchema
            var runtimeType = ClassFactory.CreateType(dataViewSchema);
    
            dynamic dynamicPredictionEngine;
            var genericPredictionMethod = mlContext.Model.GetType().GetMethod("CreatePredictionEngine", new[] { typeof(ITransformer), typeof(DataViewSchema) });
            var predictionMethod = genericPredictionMethod.MakeGenericMethod(runtimeType, typeof(PricePrediction));
            dynamicPredictionEngine = predictionMethod.Invoke(mlContext.Model, new object[] { model, dataViewSchema });
    

    要实际使用 PredictionEngine (dynamicPredictionEngine),请使用类似于此的调用:

    var predictMethod = dynamicPredictionEngine.GetType().GetMethod("Predict", new[] { runtimeType });
    var predict = predictMethod.Invoke(dynamicPredictionEngine, new[] { inputObject });
    

    我使用了来自this wonderful example of creating runtime classes 的源代码的修改副本(上面的ClassFactory)。我的副本接受 DataViewSchema 来自动生成所需的类。该代码如下:

    using Microsoft.ML;
    using System;
    using System.Linq;
    using System.Reflection;
    using System.Reflection.Emit;
    
    public static class ClassFactory
    {
        private static AssemblyName _assemblyName;
    
        public static object CreateObject(string[] PropertyNames, Type[] Types)
        {
            _assemblyName = new AssemblyName("DynamicInput");
    
            if (PropertyNames.Length != Types.Length)
            {
                Console.WriteLine("The number of property names should match their corresponding types number");
            }
    
            TypeBuilder DynamicClass = CreateTypeBuilder();
            CreateConstructor(DynamicClass);
            for (int ind = 0; ind < PropertyNames.Count(); ind++)
                CreateProperty(DynamicClass, PropertyNames[ind], Types[ind]);
            Type type = DynamicClass.CreateType();
    
            return Activator.CreateInstance(type);
        }
    
        public static Type CreateType(DataViewSchema dataViewSchema)
        {
            _assemblyName = new AssemblyName("DynamicInput");
    
            TypeBuilder DynamicClass = CreateTypeBuilder();
            CreateConstructor(DynamicClass);
            foreach (var item in dataViewSchema)
            {
                CreateProperty(DynamicClass, item.Name, item.Type.RawType);
            }
    
            return DynamicClass.CreateType(); 
        }
    
        private static TypeBuilder CreateTypeBuilder()
        {
            AssemblyBuilder assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(_assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
            TypeBuilder typeBuilder = moduleBuilder.DefineType(_assemblyName.FullName
                                , TypeAttributes.Public |
                                TypeAttributes.Class |
                                TypeAttributes.AutoClass |
                                TypeAttributes.AnsiClass |
                                TypeAttributes.BeforeFieldInit |
                                TypeAttributes.AutoLayout
                                , null);
            return typeBuilder;
        }
    
        private static void CreateConstructor(TypeBuilder typeBuilder)
        {
            typeBuilder.DefineDefaultConstructor(MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName);
        }
    
        private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propertyType)
        {
            FieldBuilder fieldBuilder = typeBuilder.DefineField("_" + propertyName, propertyType, FieldAttributes.Private);
    
            PropertyBuilder propertyBuilder = typeBuilder.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, null);
            MethodBuilder getPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, propertyType, Type.EmptyTypes);
            ILGenerator getIl = getPropMthdBldr.GetILGenerator();
    
            getIl.Emit(OpCodes.Ldarg_0);
            getIl.Emit(OpCodes.Ldfld, fieldBuilder);
            getIl.Emit(OpCodes.Ret);
    
            MethodBuilder setPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName,
                  MethodAttributes.Public |
                  MethodAttributes.SpecialName |
                  MethodAttributes.HideBySig,
                  null, new[] { propertyType });
    
            ILGenerator setIl = setPropMthdBldr.GetILGenerator();
            Label modifyProperty = setIl.DefineLabel();
            Label exitSet = setIl.DefineLabel();
    
            setIl.MarkLabel(modifyProperty);
            setIl.Emit(OpCodes.Ldarg_0);
            setIl.Emit(OpCodes.Ldarg_1);
            setIl.Emit(OpCodes.Stfld, fieldBuilder);
    
            setIl.Emit(OpCodes.Nop);
            setIl.MarkLabel(exitSet);
            setIl.Emit(OpCodes.Ret);
    
            propertyBuilder.SetGetMethod(getPropMthdBldr);
            propertyBuilder.SetSetMethod(setPropMthdBldr);
        }
    }
    

    【讨论】:

    • 谢谢迈克尔。这看起来非常有用。但是,当我尝试拨打dynamicPredictionEngine.Predict(inputObject) 时,我收到了Runtime Binder Exception。它说它有无效的论点。但是,我使用的输入对象是一个 IDataView(多行)。使用与inputObject相同的模式创建runtimetype变量,所以我认为它会有效......但它似乎失败了。有什么想法吗?
    • 是的,我刚刚遇到了同样的问题。我使用运行预测的工作方法编辑了上面的答案。
    【解决方案2】:

    查找 MethodInfo.MakeGenericMethod()

    【讨论】:

    • 你能详细说明一下这个答案吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多