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 = default, Microsoft.ML.Data.SchemaDefinition outputSchemaDefinition = default) 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 = Nothing, Optional outputSchemaDefinition As SchemaDefinition = Nothing) 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. In versions v1.5-preview2 and earlier, 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

CustomMappingEstimator<TSrc,TDst>

Examples

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

namespace Samples.Dynamic
{
    public static class CustomMapping
    {
        // This example shows how to define and apply a custom mapping of input
        // columns to output columns without defining a contract. Since a contract
        // is not defined, the pipeline containing this mapping cannot be saved and
        // loaded back.
        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
    {
        // This example shows how to define and apply a custom mapping of input
        // columns to output columns with a contract name. The contract name is
        // used in the CustomMappingFactoryAttribute that decorates the custom
        // mapping action. The pipeline containing the custom mapping can then be
        // saved to disk, and it can be loaded back after the assembly containing
        // the custom mapping action is registered.
        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. The contractName must be provided
            // in order for a pipeline containing a CustomMapping estimator to be
            // saved and loaded back. The contractName must be the same as in the
            // CustomMappingFactoryAttribute used to decorate the custom action
            // defined by the user.
            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.    
            // This is necessary only in versions v1.5-preview2 and earlier
            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
    {
        // This example shows how custom mapping actions can be performed on custom data
        // types that ML.NET doesn't know yet. The example tells a story of how two alien
        // bodies are merged to form a super alien with a single body.
        //
        // Here, the type AlienHero represents a single alien entity with a member "Name"
        // of type string and members "One" and "Two" of type AlienBody. It defines a custom
        // mapping action AlienFusionProcess that takes an AlienHero and "fuses" its two
        // AlienBody members to produce a SuperAlienHero entity with a "Name" member of type
        // string and a single "Merged" member of type AlienBody, where the merger is just
        // the addition of the various members of AlienBody.
        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 ML.NET, 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 TEN.LM, 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 example.
        //
        // The type members represent the characteristics of an alien body that will
        // be merged in the AlienFusionProcess.
        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. This attribute
        // defines how class AlienBody is registered in ML.NET's type system. In this
        // case, AlienBody is registered as DataViewAlienBodyType in ML.NET. The RaceId
        // property allows different members of type AlienBody to be registered with
        // different types in ML.NEt (see usage in class AlienHero).
        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), this);
            }

            public override bool Equals(DataViewTypeAttribute other)
            {
                if (other is AlienTypeAttributeAttribute alienTypeAttributeAttribute)
                    return RaceId == alienTypeAttributeAttribute.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 example. 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.
        //
        // This type represents a "Hero" Alien that is a single entity with two bodies.
        // The "Hero" undergoes a fusion process defined in AlienFusionProcess to
        // become a SuperAlienHero with a single body that is a merger of the two
        // bodies.
        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 = name;
                One = new AlienBody(age, height, weight, handCount);
                Two = new AlienBody(anotherAge, anotherHeight, anotherWeight,
                    anotherHandCount);
            }
        }

        // Type of AlienBody in ML.NET's type system. This is the data view type that
        // will represent AlienBody in ML.NET's type system when it is registered as
        // such in AlienTypeAttributeAttribute.
        // 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).
        // This is a "fused" alien whose body is a merger of the two bodies
        // of AlienHero.
        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;
            }
        }
    }
}

Applies to