CustomMappingCatalog.CustomMapping<TSrc,TDst>(TransformsCatalog, Action<TSrc,TDst>, String, SchemaDefinition, SchemaDefinition) Method

Definition

Create a CustomMappingEstimator<TSrc,TDst>, which applies a custom mapping of input columns to output columns.

public static Microsoft.ML.Transforms.CustomMappingEstimator<TSrc,TDst> CustomMapping<TSrc,TDst> (this Microsoft.ML.TransformsCatalog catalog, Action<TSrc,TDst> mapAction, string contractName, Microsoft.ML.Data.SchemaDefinition inputSchemaDefinition = null, Microsoft.ML.Data.SchemaDefinition outputSchemaDefinition = null) where TSrc : class, new() where TDst : class, new();
static member CustomMapping : Microsoft.ML.TransformsCatalog * Action<'Src, 'Dst (requires 'Src : null and 'Src : (new : unit -> 'Src) and 'Dst : null and 'Dst : (new : unit -> 'Dst))> * string * Microsoft.ML.Data.SchemaDefinition * Microsoft.ML.Data.SchemaDefinition -> Microsoft.ML.Transforms.CustomMappingEstimator<'Src, 'Dst (requires 'Src : null and 'Src : (new : unit -> 'Src) and 'Dst : null and 'Dst : (new : unit -> 'Dst))> (requires 'Src : null and 'Src : (new : unit -> 'Src) and 'Dst : null and 'Dst : (new : unit -> 'Dst))
<Extension()>
Public Function CustomMapping(Of TSrc As {Class, New}, TDst As {Class, New}) (catalog As TransformsCatalog, mapAction As Action(Of TSrc, TDst), contractName As String, Optional inputSchemaDefinition As SchemaDefinition = null, Optional outputSchemaDefinition As SchemaDefinition = null) As CustomMappingEstimator(Of TSrc, TDst)

Type Parameters

TSrc

The class defining which columns to take from the incoming data.

TDst

The class defining which new columns are added to the data.

Parameters

catalog
TransformsCatalog

The transform catalog

mapAction
Action<TSrc,TDst>

The mapping action. This must be thread-safe and free from side effects. If the resulting transformer needs to be save-able, the class defining mapAction should implement CustomMappingFactory<TSrc,TDst> and needs to be decorated with CustomMappingFactoryAttributeAttribute with the provided contractName. The assembly containing the class should be registered in the environment where it is loaded back using RegisterAssembly(Assembly, Boolean).

contractName
String

The contract name, used by ML.NET for loading the model. If null is specified, resulting transformer would not be save-able.

inputSchemaDefinition
SchemaDefinition

Additional parameters for schema mapping between TSrc and input data. Useful when dealing with annotations.

outputSchemaDefinition
SchemaDefinition

Additional parameters for schema mapping between TDst and output data. Useful when dealing with annotations.

Returns

Examples

using System;
using System.Collections.Generic;
using Microsoft.ML;

namespace Samples.Dynamic
{
    public static class CustomMapping
    {
        public static void Example()
        {
            // Create a new ML context, for ML.NET operations. It can be used for
            // exception tracking and logging, as well as the source of randomness.
            var mlContext = new MLContext();

            // Get a small dataset as an IEnumerable and convert it to an IDataView.
            var samples = new List<InputData>
            {
                new InputData { Age = 26 },
                new InputData { Age = 35 },
                new InputData { Age = 34 },
                new InputData { Age = 28 },
            };
            var data = mlContext.Data.LoadFromEnumerable(samples);

            // We define the custom mapping between input and output rows that will
            // be applied by the transformation.
            Action<InputData, CustomMappingOutput > mapping =
                (input, output) => output.IsUnderThirty = input.Age < 30;

            // Custom transformations can be used to transform data directly, or as
            // part of a pipeline of estimators. Note: If contractName is null in
            // the CustomMapping estimator, any pipeline of estimators containing
            // it, cannot be saved and loaded back. 
            var pipeline = mlContext.Transforms.CustomMapping(mapping, contractName:
                null);

            // Now we can transform the data and look at the output to confirm the
            // behavior of the estimator. This operation doesn't actually evaluate
            // data until we read the data below.
            var transformer = pipeline.Fit(data);
            var transformedData = transformer.Transform(data);

            var dataEnumerable = mlContext.Data.CreateEnumerable<TransformedData>(
                transformedData, reuseRowObject: true);

            Console.WriteLine("Age\t IsUnderThirty");
            foreach (var row in dataEnumerable)
                Console.WriteLine($"{row.Age}\t {row.IsUnderThirty}");

            // Expected output:
            // Age      IsUnderThirty
            // 26       True
            // 35       False
            // 34       False
            // 28       True
        }

        // Defines only the column to be generated by the custom mapping
        // transformation in addition to the columns already present.
        private class CustomMappingOutput
        {
            public bool IsUnderThirty { get; set; }
        }

        // Defines the schema of the input data.
        private class InputData
        {
            public float Age { get; set; }
        }

        // Defines the schema of the transformed data, which includes the new column
        // IsUnderThirty.
        private class TransformedData : InputData
        {
            public bool IsUnderThirty { get; set; }
        }
    }
}
using System;
using System.Collections.Generic;
using Microsoft.ML;
using Microsoft.ML.Transforms;

namespace Samples.Dynamic
{
    public static class CustomMappingSaveAndLoad
    {
        public static void Example()
        {
            // Create a new ML context, for ML.NET operations. It can be used for
            // exception tracking and logging, as well as the source of randomness.
            var mlContext = new MLContext();

            // Get a small dataset as an IEnumerable and convert it to an IDataView.
            var samples = new List<InputData>
            {
                new InputData { Age = 26 },
                new InputData { Age = 35 },
                new InputData { Age = 34 },
                new InputData { Age = 28 },
            };
            var data = mlContext.Data.LoadFromEnumerable(samples);

            // Custom transformations can be used to transform data directly, or as
            // part of a pipeline of estimators.
            var pipeline = mlContext.Transforms.CustomMapping(new
                IsUnderThirtyCustomAction().GetMapping(), contractName:
                "IsUnderThirty");

            var transformer = pipeline.Fit(data);

            // To save and load the CustomMapping estimator, the assembly in which
            // the custom action is defined needs to be registered in the
            // environment. The following registers the assembly where
            // IsUnderThirtyCustomAction is defined.    
            mlContext.ComponentCatalog.RegisterAssembly(typeof(
                IsUnderThirtyCustomAction).Assembly);

            // Now the transform pipeline can be saved and loaded through the usual
            // MLContext method. 
            mlContext.Model.Save(transformer, data.Schema, "customTransform.zip");
            var loadedTransform = mlContext.Model.Load("customTransform.zip", out 
                var inputSchema);

            // Now we can transform the data and look at the output to confirm the
            // behavior of the estimator. This operation doesn't actually evaluate
            // data until we read the data below.
            var transformedData = loadedTransform.Transform(data);

            var dataEnumerable = mlContext.Data.CreateEnumerable<TransformedData>(
                transformedData, reuseRowObject: true);

            Console.WriteLine("Age\tIsUnderThirty");
            foreach (var row in dataEnumerable)
                Console.WriteLine($"{row.Age}\t {row.IsUnderThirty}");

            // Expected output:
            // Age      IsUnderThirty
            // 26       True
            // 35       False
            // 34       False
            // 28       True
        }

        // The custom action needs to implement the abstract class
        // CustomMappingFactory, and needs to have attribute
        // CustomMappingFactoryAttribute with argument equal to the contractName
        // used to define the CustomMapping estimator which uses the action.
        [CustomMappingFactoryAttribute("IsUnderThirty")]
        private class IsUnderThirtyCustomAction : CustomMappingFactory<InputData,
            CustomMappingOutput>
        {
            // We define the custom mapping between input and output rows that will
            // be applied by the transformation.
            public static void CustomAction(InputData input, CustomMappingOutput
                output) => output.IsUnderThirty = input.Age < 30;

            public override Action<InputData, CustomMappingOutput> GetMapping()
                => CustomAction;
        }

        // Defines only the column to be generated by the custom mapping
        // transformation in addition to the columns already present.
        private class CustomMappingOutput
        {
            public bool IsUnderThirty { get; set; }
        }

        // Defines the schema of the input data.
        private class InputData
        {
            public float Age { get; set; }
        }

        // Defines the schema of the transformed data, which includes the new column
        // IsUnderThirty.
        private class TransformedData : InputData
        {
            public bool IsUnderThirty { get; set; }
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Data;

namespace Samples.Dynamic
{
    class CustomMappingWithInMemoryCustomType
    {
        static public void Example()
        {
            var mlContext = new MLContext();
            // Build in-memory data.
            var tribe = new List<AlienHero>() { new AlienHero("ML.NET", 2, 1000,
                2000, 3000, 4000, 5000, 6000, 7000) };

            // Build a ML.NET pipeline and make prediction.
            var tribeDataView = mlContext.Data.LoadFromEnumerable(tribe);
            var pipeline = mlContext.Transforms.CustomMapping(AlienFusionProcess
                .GetMapping(), contractName: null);

            var model = pipeline.Fit(tribeDataView);
            var tribeTransformed = model.Transform(tribeDataView);

            // Print out prediction produced by the model.
            var firstAlien = mlContext.Data.CreateEnumerable<SuperAlienHero>(
                tribeTransformed, false).First();

            Console.WriteLine("We got a super alien with name " + firstAlien.Name + 
                ", age " + firstAlien.Merged.Age + ", " + "height " + firstAlien
                .Merged.Height + ", weight  " + firstAlien.Merged.Weight + ", and "
                + firstAlien.Merged.HandCount + " hands.");

            // Expected output:
            //   We got a super alien with name Super Unknown, age 4002, height 6000, weight 8000, and 10000 hands.

            // Create a prediction engine and print out its prediction.
            var engine = mlContext.Model.CreatePredictionEngine<AlienHero,
                SuperAlienHero>(model);

            var alien = new AlienHero("TEN.LM", 1, 2, 3, 4, 5, 6, 7, 8);
            var superAlien = engine.Predict(alien);
            Console.Write("We got a super alien with name " + superAlien.Name +
                ", age " + superAlien.Merged.Age + ", height " + 
                superAlien.Merged.Height + ", weight " + superAlien.Merged.Weight +
                ", and " + superAlien.Merged.HandCount + " hands.");

            // Expected output:
            //   We got a super alien with name Super Unknown, age 6, height 8, weight 10, and 12 hands.
        }

        // A custom type which ML.NET doesn't know yet. Its value will be loaded as
        // a DataView column in this test.
        private class AlienBody
        {
            public int Age { get; set; }
            public float Height { get; set; }
            public float Weight { get; set; }
            public int HandCount { get; set; }

            public AlienBody(int age, float height, float weight, int handCount)
            {
                Age = age;
                Height = height;
                Weight = weight;
                HandCount = handCount;
            }
        }

        // DataViewTypeAttribute applied to class AlienBody members.
        private sealed class AlienTypeAttributeAttribute : DataViewTypeAttribute
        {
            public int RaceId { get; }

            // Create an DataViewTypeAttribute> from raceId to a AlienBody.
            public AlienTypeAttributeAttribute(int raceId)
            {
                RaceId = raceId;
            }

            // A function implicitly invoked by ML.NET when processing a custom
            // type. It binds a DataViewType to a custom type plus its attributes.
            public override void Register()
            {
                DataViewTypeManager.Register(new DataViewAlienBodyType(RaceId),
                    typeof(AlienBody), new[] { this });
            }

            public override bool Equals(DataViewTypeAttribute other)
            {
                if (other is AlienTypeAttributeAttribute)
                    return RaceId == ((AlienTypeAttributeAttribute)other).RaceId;
                return false;
            }

            public override int GetHashCode() => RaceId.GetHashCode();
        }

        // A custom class with a type which ML.NET doesn't know yet. Its value will
        // be loaded as a DataView row in this test. It will be the input of
        // AlienFusionProcess.MergeBody(AlienHero, SuperAlienHero).
        //
        // The members One> and Two" would be mapped to different types inside
        // ML.NET type system because they have different 
        // AlienTypeAttributeAttribute's. For example, the column type of One would
        // be DataViewAlienBodyType
        // with RaceId=100.
        // </summary>
        private class AlienHero
        {
            public string Name { get; set; }

            [AlienTypeAttribute(100)]
            public AlienBody One { get; set; }

            [AlienTypeAttribute(200)]
            public AlienBody Two { get; set; }

            public AlienHero()
            {
                Name = "Unknown";
                One = new AlienBody(0, 0, 0, 0);
                Two = new AlienBody(0, 0, 0, 0);
            }

            public AlienHero(string name,
                int age, float height, float weight, int handCount,
                int anotherAge, float anotherHeight, float anotherWeight, int
                    anotherHandCount)
            {
                Name = "Unknown";
                One = new AlienBody(age, height, weight, handCount);
                Two = new AlienBody(anotherAge, anotherHeight, anotherWeight,
                    anotherHandCount);
            }
        }

        // Type of AlienBody in ML.NET's type system.
        // It usually shows up as DataViewSchema.Column.Type among IDataView.Schema.
        private class DataViewAlienBodyType : StructuredDataViewType
        {
            public int RaceId { get; }

            public DataViewAlienBodyType(int id) : base(typeof(AlienBody))
            {
                RaceId = id;
            }

            public override bool Equals(DataViewType other)
            {
                if (other is DataViewAlienBodyType otherAlien)
                    return otherAlien.RaceId == RaceId;
                return false;
            }

            public override int GetHashCode()
            {
                return RaceId.GetHashCode();
            }
        }

        // The output type of processing AlienHero using AlienFusionProcess
        // .MergeBody(AlienHero, SuperAlienHero).
        private class SuperAlienHero
        {
            public string Name { get; set; }

            [AlienTypeAttribute(007)]
            public AlienBody Merged { get; set; }

            public SuperAlienHero()
            {
                Name = "Unknown";
                Merged = new AlienBody(0, 0, 0, 0);
            }
        }

        // The implementation of custom mapping is MergeBody. It accepts AlienHero
        // and produces SuperAlienHero.
        private class AlienFusionProcess
        {
            public static void MergeBody(AlienHero input, SuperAlienHero output)
            {
                output.Name = "Super " + input.Name;
                output.Merged.Age = input.One.Age + input.Two.Age;
                output.Merged.Height = input.One.Height + input.Two.Height;
                output.Merged.Weight = input.One.Weight + input.Two.Weight;
                output.Merged.HandCount = input.One.HandCount + input.Two.HandCount;
            }

            public static Action<AlienHero, SuperAlienHero> GetMapping()
            {
                return MergeBody;
            }
        }

    }
}
using System;
using System.Collections.Generic;
using Microsoft.ML;

namespace Samples.Dynamic
{
    public static class CustomMapping
    {
        public static void Example()
        {
            // Create a new ML context, for ML.NET operations. It can be used for
            // exception tracking and logging, as well as the source of randomness.
            var mlContext = new MLContext();

            // Get a small dataset as an IEnumerable and convert it to an IDataView.
            var samples = new List<InputData>
            {
                new InputData { Age = 26 },
                new InputData { Age = 35 },
                new InputData { Age = 34 },
                new InputData { Age = 28 },
            };
            var data = mlContext.Data.LoadFromEnumerable(samples);

            // We define the custom mapping between input and output rows that will
            // be applied by the transformation.
            Action<InputData, CustomMappingOutput > mapping =
                (input, output) => output.IsUnderThirty = input.Age < 30;

            // Custom transformations can be used to transform data directly, or as
            // part of a pipeline of estimators. Note: If contractName is null in
            // the CustomMapping estimator, any pipeline of estimators containing
            // it, cannot be saved and loaded back. 
            var pipeline = mlContext.Transforms.CustomMapping(mapping, contractName:
                null);

            // Now we can transform the data and look at the output to confirm the
            // behavior of the estimator. This operation doesn't actually evaluate
            // data until we read the data below.
            var transformer = pipeline.Fit(data);
            var transformedData = transformer.Transform(data);

            var dataEnumerable = mlContext.Data.CreateEnumerable<TransformedData>(
                transformedData, reuseRowObject: true);

            Console.WriteLine("Age\t IsUnderThirty");
            foreach (var row in dataEnumerable)
                Console.WriteLine($"{row.Age}\t {row.IsUnderThirty}");

            // Expected output:
            // Age      IsUnderThirty
            // 26       True
            // 35       False
            // 34       False
            // 28       True
        }

        // Defines only the column to be generated by the custom mapping
        // transformation in addition to the columns already present.
        private class CustomMappingOutput
        {
            public bool IsUnderThirty { get; set; }
        }

        // Defines the schema of the input data.
        private class InputData
        {
            public float Age { get; set; }
        }

        // Defines the schema of the transformed data, which includes the new column
        // IsUnderThirty.
        private class TransformedData : InputData
        {
            public bool IsUnderThirty { get; set; }
        }
    }
}
using System;
using System.Collections.Generic;
using Microsoft.ML;
using Microsoft.ML.Transforms;

namespace Samples.Dynamic
{
    public static class CustomMappingSaveAndLoad
    {
        public static void Example()
        {
            // Create a new ML context, for ML.NET operations. It can be used for
            // exception tracking and logging, as well as the source of randomness.
            var mlContext = new MLContext();

            // Get a small dataset as an IEnumerable and convert it to an IDataView.
            var samples = new List<InputData>
            {
                new InputData { Age = 26 },
                new InputData { Age = 35 },
                new InputData { Age = 34 },
                new InputData { Age = 28 },
            };
            var data = mlContext.Data.LoadFromEnumerable(samples);

            // Custom transformations can be used to transform data directly, or as
            // part of a pipeline of estimators.
            var pipeline = mlContext.Transforms.CustomMapping(new
                IsUnderThirtyCustomAction().GetMapping(), contractName:
                "IsUnderThirty");

            var transformer = pipeline.Fit(data);

            // To save and load the CustomMapping estimator, the assembly in which
            // the custom action is defined needs to be registered in the
            // environment. The following registers the assembly where
            // IsUnderThirtyCustomAction is defined.    
            mlContext.ComponentCatalog.RegisterAssembly(typeof(
                IsUnderThirtyCustomAction).Assembly);

            // Now the transform pipeline can be saved and loaded through the usual
            // MLContext method. 
            mlContext.Model.Save(transformer, data.Schema, "customTransform.zip");
            var loadedTransform = mlContext.Model.Load("customTransform.zip", out 
                var inputSchema);

            // Now we can transform the data and look at the output to confirm the
            // behavior of the estimator. This operation doesn't actually evaluate
            // data until we read the data below.
            var transformedData = loadedTransform.Transform(data);

            var dataEnumerable = mlContext.Data.CreateEnumerable<TransformedData>(
                transformedData, reuseRowObject: true);

            Console.WriteLine("Age\tIsUnderThirty");
            foreach (var row in dataEnumerable)
                Console.WriteLine($"{row.Age}\t {row.IsUnderThirty}");

            // Expected output:
            // Age      IsUnderThirty
            // 26       True
            // 35       False
            // 34       False
            // 28       True
        }

        // The custom action needs to implement the abstract class
        // CustomMappingFactory, and needs to have attribute
        // CustomMappingFactoryAttribute with argument equal to the contractName
        // used to define the CustomMapping estimator which uses the action.
        [CustomMappingFactoryAttribute("IsUnderThirty")]
        private class IsUnderThirtyCustomAction : CustomMappingFactory<InputData,
            CustomMappingOutput>
        {
            // We define the custom mapping between input and output rows that will
            // be applied by the transformation.
            public static void CustomAction(InputData input, CustomMappingOutput
                output) => output.IsUnderThirty = input.Age < 30;

            public override Action<InputData, CustomMappingOutput> GetMapping()
                => CustomAction;
        }

        // Defines only the column to be generated by the custom mapping
        // transformation in addition to the columns already present.
        private class CustomMappingOutput
        {
            public bool IsUnderThirty { get; set; }
        }

        // Defines the schema of the input data.
        private class InputData
        {
            public float Age { get; set; }
        }

        // Defines the schema of the transformed data, which includes the new column
        // IsUnderThirty.
        private class TransformedData : InputData
        {
            public bool IsUnderThirty { get; set; }
        }
    }
}

Applies to