简体   繁体   English

ML.NET动态培训/测试课程

[英]dynamic training/test classes with ML.NET

This is a follow up from the question here Dynamic classes/objects ML.net's PredictionMoadel<TInput, TOutput> Train() 这是来自此处问题的后续动态类/对象ML.net的PredictionMoadel <TInput,TOutput> Train()

My system cannot use a predefined class at compile time, therefore I tried to feed a dynamic class into ML.NET like below 我的系统在编译时无法使用预定义的类,因此我尝试将动态类输入ML.NET,如下所示

    // field data type
    public class Field
    {
        public string FieldName { get; set; }
        public Type FieldType { get; set; }
    }

    // dynamic class helper
    public class DynamicClass : DynamicObject
    {
        private readonly Dictionary<string, KeyValuePair<Type, object>> _fields;

        public DynamicClass(List<Field> fields)
        {
            _fields = new Dictionary<string, KeyValuePair<Type, object>>();
            fields.ForEach(x => _fields.Add(x.FieldName,
                new KeyValuePair<Type, object>(x.FieldType, null)));
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            if (_fields.ContainsKey(binder.Name))
            {
                var type = _fields[binder.Name].Key;
                if (value.GetType() == type)
                {
                    _fields[binder.Name] = new KeyValuePair<Type, object>(type, value);
                    return true;
                }
                else throw new Exception("Value " + value + " is not of type " + type.Name);
            }
            return false;
        }

        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = _fields[binder.Name].Value;
            return true;
        }
    }

    private static void Main(string[] args)
    {
        var fields = new List<Field>
        {
            new Field {FieldName = "Name", FieldType = typeof(string)},
            new Field {FieldName = "Income", FieldType = typeof(float)}
        };

        dynamic obj1 = new DynamicClass(fields);
        obj1.Name = "John";
        obj1.Income = 100f;

        dynamic obj2 = new DynamicClass(fields);
        obj2.Name = "Alice";
        obj2.Income = 200f;

        var trainingData = new List<dynamic> {obj1, obj2};

        var env = new LocalEnvironment();
        var schemaDef = SchemaDefinition.Create(typeof(DynamicClass));
        schemaDef.Add(new SchemaDefinition.Column(null, "Name", TextType.Instance));
        schemaDef.Add(new SchemaDefinition.Column(null, "Income", NumberType.R4));
        var trainDataView = env.CreateStreamingDataView(trainingData, schemaDef);

        var pipeline = new CategoricalEstimator(env, "Name")
            .Append(new ConcatEstimator(env, "Features", "Name"))
            .Append(new FastTreeRegressionTrainer(env, "Income", "Features"));

        var model = pipeline.Fit(trainDataView);
    }

and got the error: "'No field or property with name 'Name' found in type 'System.Object'". 并收到错误:“在'System.Object'类型中找不到'名称为'Name'的字段或属性”。 I tried generating the class using Reflection only to run into the same problem. 我尝试使用反射生成类,只是遇到了同样的问题。

Is there a workaround? 有解决方法吗? Thanks 谢谢

Dynamic class doesn't actually create a class definition but it rather provides you with dynamic object. 动态类实际上并不创建类定义,而是为您提供动态对象。

I looked at the code for SchemaDefinition.Create() it needs an actual class definition to build the schema. 我查看了SchemaDefinition.Create()的代码,它需要一个实际的类定义来构建模式。 So your options are to create and load a class definition dynamically. 因此,您的选择是动态创建和加载类定义。

You can create your class as string with all dynamic properties and compile it using Microsoft compiler services aka Roslyn . 您可以将类创建为具有所有动态属性的字符串,然后使用Microsoft编译器服务aka Roslyn对其进行编译。 See here . 这里 This will generate an assembly (in memory as memory stream or on file system) with your dynamic type. 这将使用您的动态类型生成一个程序集(在内存中作为内存流或在文件系统上)。

Now you are only half way there. 现在您只有一半了。 To get your dynamic type from dynamic assembly you need to load it in the App Domain. 要从动态程序集获取动态类型,您需要将其加载到App Domain中。 See this post. 看到这篇文章。 Once the assembly is loaded you can use ' Activator.CreateInstance() ' if it's same domain or if it's your custom domain then you would need yourDomain.CreateInstanceAndUnwrap() to create the object out of dynamically generated Class and to get the type use Assembly.GetType() . 程序集加载后,如果它是同一个域,或者是您的自定义域,则可以使用Activator.CreateInstance() ,则需要yourDomain.CreateInstanceAndUnwrap()从动态生成的类中创建对象并使用Assembly.GetType()来获取类型Assembly.GetType()

Few sample here, A little out of date but will get you on your feet if you are up for this. 这里的样本很少,有些过时了,但是如果您愿意的话,可以让您满意。 See CompilerEngine and CompilerService to compile and load the assembly. 请参阅CompilerEngineCompilerService来编译和加载程序集。

Other options : Refelection.Emit() but it requires a great deal of IL level coding. 其他选项Refelection.Emit()但是它需要大量的IL级别编码。 See this post . 看到这篇文章

For those attempting to do this, I have a working solution that creates the schema and can be used to train data dynamically. 对于那些尝试这样做的人,我有一个可行的解决方案,可以创建架构并可以用来动态训练数据。

First, grab the code for DynamicTypeProperty and DynamicType from my other answer here . 首先,从这里的其他答案中获取DynamicTypeProperty和DynamicType的代码。

The following code will create a schema dynamically: 以下代码将动态创建模式:

var properties = new List<DynamicTypeProperty>()
{
    new DynamicTypeProperty("SepalLength", typeof(float)),
    new DynamicTypeProperty("SepalWidth", typeof(float)),
    new DynamicTypeProperty("PetalLength", typeof(float)),
    new DynamicTypeProperty("PetalWidth", typeof(float)),
};

// create the new type
var dynamicType = DynamicType.CreateDynamicType(properties);
var schema = SchemaDefinition.Create(dynamicType);

You'll then need to create list with the required data. 然后,您需要使用所需数据创建列表。 This is done as follows: 这样做如下:

var dynamicList = DynamicType.CreateDynamicList(dynamicType);

// get an action that will add to the list
var addAction = DynamicType.GetAddAction(dynamicList);

// call the action, with an object[] containing parameters in exact order added
addAction.Invoke(new object[] {1.1, 2.2, 3.3, 4.4});
// call add action again for each row.

Then you'll need to create an IDataView with the data, this requires using reflection, or the trainers won't infer the correct type. 然后,您需要使用数据创建一个IDataView,这需要使用反射,否则培训师将无法推断出正确的类型。

            var mlContext = new MLContext();
            var dataType = mlContext.Data.GetType();
            var loadMethodGeneric = dataType.GetMethods().First(method => method.Name =="LoadFromEnumerable" && method.IsGenericMethod);
            var loadMethod = loadMethodGeneric.MakeGenericMethod(dynamicType);
            var trainData = (IDataView) loadMethod.Invoke(mlContext.Data, new[] {dynamicList, schema});

You then, should be able to run the trainData through your pipeline. 然后,您应该能够通过管道运行trainData

Good luck. 祝好运。

Right now I'm using a dummy place holder like this as a workaround 现在我正在使用这样的虚拟占位符作为解决方法

    public class TrainingSample
    {
        public string TextField1;
        public string TextField2;
        public string TextField3;
        public string TextField4;
        public string TextField5;

        public float FloatField1;
        public float FloatField2;
        public float FloatField3;
        public float FloatField4;
        public float FloatField5;
        public float FloatField6;
        public float FloatField7;
        public float FloatField8;
        public float FloatField9;
        public float FloatField10;
        public float FloatField11;
        public float FloatField12;
        public float FloatField13;
        public float FloatField14;
        public float FloatField15;
    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM