File: Standard\LinearModelParameters.cs
Web Access
Project: src\src\Microsoft.ML.StandardTrainers\Microsoft.ML.StandardTrainers.csproj (Microsoft.ML.StandardTrainers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ML;
using Microsoft.ML.Calibrators;
using Microsoft.ML.Data;
using Microsoft.ML.Internal.Utilities;
using Microsoft.ML.Model;
using Microsoft.ML.Model.OnnxConverter;
using Microsoft.ML.Model.Pfa;
using Microsoft.ML.Numeric;
using Microsoft.ML.Runtime;
using Microsoft.ML.Trainers;
using Microsoft.ML.Transforms;
using Newtonsoft.Json.Linq;
 
// This is for deserialization from a model repository.
[assembly: LoadableClass(typeof(IPredictorProducing<float>), typeof(LinearBinaryModelParameters), null, typeof(SignatureLoadModel),
    "Linear Binary Executor",
    LinearBinaryModelParameters.LoaderSignature)]
 
// This is for deserialization from a model repository.
[assembly: LoadableClass(typeof(LinearRegressionModelParameters), null, typeof(SignatureLoadModel),
    "Linear Regression Executor",
    LinearRegressionModelParameters.LoaderSignature)]
 
// This is for deserialization from a model repository.
[assembly: LoadableClass(typeof(PoissonRegressionModelParameters), null, typeof(SignatureLoadModel),
    "Poisson Regression Executor",
    PoissonRegressionModelParameters.LoaderSignature)]
 
namespace Microsoft.ML.Trainers
{
    /// <summary>
    /// Base class for linear model parameters.
    /// </summary>
    public abstract class LinearModelParameters : ModelParametersBase<float>,
        IValueMapper,
        ICanSaveInIniFormat,
        ICanSaveInTextFormat,
        ICanSaveInSourceCode,
        ICanGetSummaryAsIRow,
        ICanSaveSummary,
        IPredictorWithFeatureWeights<float>,
        IFeatureContributionMapper,
        ICalculateFeatureContribution,
        ISingleCanSavePfa,
        ISingleCanSaveOnnx
    {
        [BestFriend]
        private protected readonly VBuffer<float> Weight;
 
        // _weightsDense is not persisted and is used for performance when the input instance is sparse.
        private VBuffer<float> _weightsDense;
        private readonly object _weightsDenseLock;
 
        private sealed class WeightsCollection : IReadOnlyList<float>
        {
            private readonly LinearModelParameters _pred;
 
            public int Count => _pred.Weight.Length;
 
            public float this[int index]
            {
                get
                {
                    Contracts.CheckParam(0 <= index && index < Count, nameof(index), "Out of range");
                    float value = 0;
                    _pred.Weight.GetItemOrDefault(index, ref value);
                    return value;
                }
            }
 
            public WeightsCollection(LinearModelParameters pred)
            {
                Contracts.AssertValue(pred);
                _pred = pred;
            }
 
            public IEnumerator<float> GetEnumerator()
            {
                return _pred.Weight.Items(all: true).Select(iv => iv.Value).GetEnumerator();
            }
 
            IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
        }
 
        /// <summary> The predictor's feature weight coefficients.</summary>
        public IReadOnlyList<float> Weights => new WeightsCollection(this);
 
        /// <summary> The predictor's bias term.</summary>
        public float Bias { get; protected set; }
 
        private readonly DataViewType _inputType;
 
        bool ICanSavePfa.CanSavePfa => true;
 
        bool ICanSaveOnnx.CanSaveOnnx(OnnxContext ctx) => true;
 
        /// <summary>
        /// Used to determine the contribution of each feature to the score of an example by <see cref="FeatureContributionCalculatingTransformer"/>.
        /// For linear models, the contribution of a given feature is equal to the product of feature value times the corresponding weight.
        /// </summary>
        FeatureContributionCalculator ICalculateFeatureContribution.FeatureContributionCalculator => new FeatureContributionCalculator(this);
 
        /// <summary>
        /// Constructs a new linear predictor.
        /// </summary>
        /// <param name="env">The host environment.</param>
        /// <param name="name">Component name.</param>
        /// <param name="weights">The weights for the linear model. The i-th element of weights is the coefficient
        /// of the i-th feature. Note that this will take ownership of the <see cref="VBuffer{T}"/>.</param>
        /// <param name="bias">The bias added to every output score.</param>
        internal LinearModelParameters(IHostEnvironment env, string name, in VBuffer<float> weights, float bias)
            : base(env, name)
        {
            Host.CheckParam(FloatUtils.IsFinite(weights.GetValues()), nameof(weights), "Cannot initialize linear predictor with non-finite weights");
            Host.CheckParam(FloatUtils.IsFinite(bias), nameof(bias), "Cannot initialize linear predictor with non-finite bias");
 
            Weight = weights;
            Bias = bias;
            _inputType = new VectorDataViewType(NumberDataViewType.Single, Weight.Length);
 
            if (Weight.IsDense)
                _weightsDense = Weight;
            else
                _weightsDenseLock = new object();
        }
 
        private protected virtual bool SaveAsOnnx(OnnxContext ctx, string[] outputs, string featureColumn)
        {
            Host.CheckValue(ctx, nameof(ctx));
            Host.Check(Utils.Size(outputs) >= 1);
 
            const int minimumOpSetVersion = 9;
            ctx.CheckOpSetVersion(minimumOpSetVersion, "LinearModel");
 
            string opType = "LinearRegressor";
            string scoreVarName = (Utils.Size(outputs) >= 2) ? outputs[1] : outputs[0]; // Get Score from PredictedLabel and/or Score columns
            var node = ctx.CreateNode(opType, new[] { featureColumn }, new[] { scoreVarName }, ctx.GetNodeName(opType));
            // Selection of logit or probit output transform. enum {'NONE', 'LOGIT', 'PROBIT}
            node.AddAttribute("post_transform", "NONE");
            node.AddAttribute("targets", 1);
            node.AddAttribute("coefficients", Weight.DenseValues());
            node.AddAttribute("intercepts", new float[] { Bias });
            return true;
        }
 
        private protected LinearModelParameters(IHostEnvironment env, string name, ModelLoadContext ctx)
            : base(env, name, ctx)
        {
            // *** Binary format ***
            // Float: bias
            // int: number of features (weights)
            // int: number of indices
            // int[]: indices
            // int: number of weights
            // Float[]: weights
            // bool: has model stats
            // (Conditional) LinearModelParameterStatistics: stats
 
            Bias = ctx.Reader.ReadFloat();
            Host.CheckDecode(FloatUtils.IsFinite(Bias));
 
            int len = ctx.Reader.ReadInt32();
            Host.Assert(len > 0);
 
            int cind = ctx.Reader.ReadInt32();
            Host.CheckDecode(0 <= cind && cind < len);
            var indices = ctx.Reader.ReadIntArray(cind);
 
            // Verify monotonicity of indices.
            int prev = -1;
            for (int i = 0; i < cind; i++)
            {
                Host.CheckDecode(prev < indices[i]);
                prev = indices[i];
            }
            Host.CheckDecode(prev < len);
 
            int cwht = ctx.Reader.ReadInt32();
            // Either there are as many weights as there are indices (in the
            // sparse case), or (in the dense case) there are no indices and the
            // number of weights is the length of the vector. Note that for the
            // trivial predictor it is quite legal to have 0 in both counts.
            Host.CheckDecode(cwht == cind || (cind == 0 && cwht == len));
 
            var weights = ctx.Reader.ReadFloatArray(cwht);
            Host.CheckDecode(Utils.Size(weights) == 0 || weights.All(x => FloatUtils.IsFinite(x)));
 
            if (cwht == 0)
                Weight = VBufferUtils.CreateEmpty<float>(len);
            else
                Weight = new VBuffer<float>(len, Utils.Size(weights), weights, indices);
 
            _inputType = new VectorDataViewType(NumberDataViewType.Single, Weight.Length);
            WarnOnOldNormalizer(ctx, GetType(), Host);
 
            if (Weight.IsDense)
                _weightsDense = Weight;
            else
                _weightsDenseLock = new object();
        }
        [BestFriend]
        private protected override void SaveCore(ModelSaveContext ctx)
        {
            base.SaveCore(ctx);
 
            // *** Binary format ***
            // Float: bias
            // int: number of features (weights)
            // int: number of indices
            // int[]: indices
            // int: number of weights
            // Float[]: weights
            // bool: has model stats
            // (Conditional) LinearModelParameterStatistics: stats
 
            ctx.Writer.Write(Bias);
            ctx.Writer.Write(Weight.Length);
            ctx.Writer.WriteIntArray(Weight.GetIndices());
            ctx.Writer.WriteSingleArray(Weight.GetValues());
        }
 
        JToken ISingleCanSavePfa.SaveAsPfa(BoundPfaContext ctx, JToken input)
        {
            Host.CheckValue(ctx, nameof(ctx));
            Host.CheckValue(input, nameof(input));
 
            const string typeName = "LinearPredictor";
            JToken typeDecl = typeName;
            if (ctx.Pfa.RegisterType(typeName))
            {
                JObject type = new JObject();
                type["type"] = "record";
                type["name"] = typeName;
                JArray fields = new JArray();
                JObject jobj = null;
                fields.Add(jobj.AddReturn("name", "coeff").AddReturn("type", PfaUtils.Type.Array(PfaUtils.Type.Double)));
                fields.Add(jobj.AddReturn("name", "const").AddReturn("type", PfaUtils.Type.Double));
                type["fields"] = fields;
                typeDecl = type;
            }
            JObject predictor = new JObject();
            predictor["coeff"] = new JArray(Weight.DenseValues());
            predictor["const"] = Bias;
            var cell = ctx.DeclareCell("LinearPredictor", typeDecl, predictor);
            var cellRef = PfaUtils.Cell(cell);
            return PfaUtils.Call("model.reg.linear", input, cellRef);
        }
 
        bool ISingleCanSaveOnnx.SaveAsOnnx(OnnxContext ctx, string[] outputs, string featureColumn)
        {
            return SaveAsOnnx(ctx, outputs, featureColumn);
        }
 
        // Generate the score from the given values, assuming they have already been normalized.
        private protected virtual float Score(in VBuffer<float> src)
        {
            if (src.IsDense)
            {
                var weights = Weight;
                return Bias + VectorUtils.DotProduct(in weights, in src);
            }
            EnsureWeightsDense();
            return Bias + VectorUtils.DotProduct(in _weightsDense, in src);
        }
 
        private protected virtual void GetFeatureContributions(in VBuffer<float> features, ref VBuffer<float> contributions, int top, int bottom, bool normalize)
        {
            if (features.Length != Weight.Length)
                throw Contracts.Except("Input is of length {0} does not match expected length  of weights {1}", features.Length, Weight.Length);
 
            var weights = Weight;
            features.CopyTo(ref contributions);
            VectorUtils.MulElementWise(in weights, ref contributions);
            VectorUtils.SparsifyNormalize(ref contributions, top, bottom, normalize);
        }
 
        private void EnsureWeightsDense()
        {
            if (_weightsDense.Length == 0 && Weight.Length > 0)
            {
                Contracts.AssertValue(_weightsDenseLock);
                lock (_weightsDenseLock)
                {
                    if (_weightsDense.Length == 0 && Weight.Length > 0)
                        Weight.CopyToDense(ref _weightsDense);
                }
            }
        }
 
        DataViewType IValueMapper.InputType
        {
            get { return _inputType; }
        }
 
        DataViewType IValueMapper.OutputType
        {
            get { return NumberDataViewType.Single; }
        }
 
        ValueMapper<TIn, TOut> IValueMapper.GetMapper<TIn, TOut>()
        {
            Contracts.Check(typeof(TIn) == typeof(VBuffer<float>));
            Contracts.Check(typeof(TOut) == typeof(float));
 
            ValueMapper<VBuffer<float>, float> del =
                (in VBuffer<float> src, ref float dst) =>
                {
                    if (src.Length != Weight.Length)
                        throw Contracts.Except("Input is of length {0}, but predictor expected length {1}", src.Length, Weight.Length);
                    dst = Score(in src);
                };
            return (ValueMapper<TIn, TOut>)(Delegate)del;
        }
 
        /// <summary>
        /// Combine a bunch of models into one by averaging parameters
        /// </summary>
        private protected void CombineParameters(IList<IParameterMixer<float>> models, out VBuffer<float> weights, out float bias)
        {
            Type type = GetType();
 
            Contracts.Check(type == models[0].GetType(), "Submodel for parameter mixer has the wrong type");
            var first = (LinearModelParameters)models[0];
 
            weights = default(VBuffer<float>);
            first.Weight.CopyTo(ref weights);
            bias = first.Bias;
 
            for (int i = 1; i < models.Count; i++)
            {
                var m = models[i];
                Contracts.Check(type == m.GetType(), "Submodel for parameter mixer has the wrong type");
 
                var sub = (LinearModelParameters)m;
                var subweights = sub.Weight;
                VectorUtils.Add(in subweights, ref weights);
                bias += sub.Bias;
            }
            VectorUtils.ScaleBy(ref weights, (float)1 / models.Count);
            bias /= models.Count;
        }
 
        void ICanSaveInTextFormat.SaveAsText(TextWriter writer, RoleMappedSchema schema)
        {
            Host.CheckValue(writer, nameof(writer));
            Host.CheckValue(schema, nameof(schema));
 
            SaveSummary(writer, schema);
        }
 
        void ICanSaveInSourceCode.SaveAsCode(TextWriter writer, RoleMappedSchema schema)
        {
            Host.CheckValue(writer, nameof(writer));
            Host.CheckValue(schema, nameof(schema));
 
            var weights = Weight;
            LinearPredictorUtils.SaveAsCode(writer, in weights, Bias, schema);
        }
 
        [BestFriend]
        private protected abstract void SaveSummary(TextWriter writer, RoleMappedSchema schema);
 
        void ICanSaveSummary.SaveSummary(TextWriter writer, RoleMappedSchema schema) => SaveSummary(writer, schema);
 
        private protected virtual DataViewRow GetSummaryIRowOrNull(RoleMappedSchema schema)
        {
            var names = default(VBuffer<ReadOnlyMemory<char>>);
            AnnotationUtils.GetSlotNames(schema, RoleMappedSchema.ColumnRole.Feature, Weight.Length, ref names);
            var subBuilder = new DataViewSchema.Annotations.Builder();
            subBuilder.AddSlotNames(Weight.Length, (ref VBuffer<ReadOnlyMemory<char>> dst) => names.CopyTo(ref dst));
            var colType = new VectorDataViewType(NumberDataViewType.Single, Weight.Length);
            var builder = new DataViewSchema.Annotations.Builder();
            builder.AddPrimitiveValue("Bias", NumberDataViewType.Single, Bias);
            builder.Add("Weights", colType, (ref VBuffer<float> dst) => Weight.CopyTo(ref dst), subBuilder.ToAnnotations());
            return AnnotationUtils.AnnotationsAsRow(builder.ToAnnotations());
        }
 
        DataViewRow ICanGetSummaryAsIRow.GetSummaryIRowOrNull(RoleMappedSchema schema) => GetSummaryIRowOrNull(schema);
 
        private protected virtual DataViewRow GetStatsIRowOrNull(RoleMappedSchema schema) => null;
 
        DataViewRow ICanGetSummaryAsIRow.GetStatsIRowOrNull(RoleMappedSchema schema) => GetStatsIRowOrNull(schema);
 
        private protected abstract void SaveAsIni(TextWriter writer, RoleMappedSchema schema, ICalibrator calibrator = null);
 
        void ICanSaveInIniFormat.SaveAsIni(TextWriter writer, RoleMappedSchema schema, ICalibrator calibrator) => SaveAsIni(writer, schema, calibrator);
 
        void IHaveFeatureWeights.GetFeatureWeights(ref VBuffer<float> weights)
        {
            Weight.CopyTo(ref weights);
        }
 
        ValueMapper<TSrc, VBuffer<float>> IFeatureContributionMapper.GetFeatureContributionMapper<TSrc, TDstContributions>(int top, int bottom, bool normalize)
        {
            Contracts.Check(typeof(TSrc) == typeof(VBuffer<float>));
            Contracts.Check(typeof(TDstContributions) == typeof(VBuffer<float>));
 
            ValueMapper<VBuffer<float>, VBuffer<float>> del =
                (in VBuffer<float> src, ref VBuffer<float> dstContributions) =>
                {
                    GetFeatureContributions(in src, ref dstContributions, top, bottom, normalize);
                };
            return (ValueMapper<TSrc, VBuffer<float>>)(Delegate)del;
        }
    }
 
    /// <summary>
    /// The model parameters class for linear binary trainer estimators.
    /// </summary>
    public sealed partial class LinearBinaryModelParameters : LinearModelParameters,
        ICanGetSummaryInKeyValuePairs,
        IParameterMixer<float>
    {
        internal const string LoaderSignature = "Linear2CExec";
        internal const string RegistrationName = "LinearBinaryPredictor";
 
        private const string ModelStatsSubModelFilename = "ModelStats";
        public readonly ModelStatisticsBase Statistics;
 
        private static VersionInfo GetVersionInfo()
        {
            return new VersionInfo(
                modelSignature: "LINEAR2C",
                // verWrittenCur: 0x00010001, // Initial
                // verWrittenCur: 0x00020001, // Fixed sparse serialization
                verWrittenCur: 0x00020002, // Added model statistics
                verReadableCur: 0x00020001,
                verWeCanReadBack: 0x00020001,
                loaderSignature: LoaderSignature,
                loaderAssemblyName: typeof(LinearBinaryModelParameters).Assembly.FullName);
        }
 
        /// <summary>
        /// Constructs a new linear binary predictor.
        /// </summary>
        /// <param name="env">The host environment.</param>
        /// <param name="weights">The weights for the linear model. The i-th element of weights is the coefficient
        /// of the i-th feature. Note that this will take ownership of the <see cref="VBuffer{T}"/>.</param>
        /// <param name="bias">The bias added to every output score.</param>
        /// <param name="stats"></param>
        internal LinearBinaryModelParameters(IHostEnvironment env, in VBuffer<float> weights, float bias, ModelStatisticsBase stats = null)
            : base(env, RegistrationName, in weights, bias)
        {
            Contracts.AssertValueOrNull(stats);
            Statistics = stats;
        }
 
        private LinearBinaryModelParameters(IHostEnvironment env, ModelLoadContext ctx)
            : base(env, RegistrationName, ctx)
        {
            // For model version earlier than 0x00020001, there is no model statisitcs.
            if (ctx.Header.ModelVerWritten <= 0x00020001)
                return;
 
            // *** Binary format ***
            // (Base class)
            // LinearModelParameterStatistics: model statistics (optional, in a separate stream)
            try
            {
                LinearModelParameterStatistics stats;
                ctx.LoadModelOrNull<LinearModelParameterStatistics, SignatureLoadModel>(Host, out stats, ModelStatsSubModelFilename);
                Statistics = stats;
            }
            catch (Exception)
            {
                ModelStatisticsBase stats;
                ctx.LoadModelOrNull<ModelStatisticsBase, SignatureLoadModel>(Host, out stats, ModelStatsSubModelFilename);
                Statistics = stats;
            }
        }
 
        internal static IPredictorProducing<float> Create(IHostEnvironment env, ModelLoadContext ctx)
        {
            Contracts.CheckValue(env, nameof(env));
            env.CheckValue(ctx, nameof(ctx));
            ctx.CheckAtModel(GetVersionInfo());
            var predictor = new LinearBinaryModelParameters(env, ctx);
            ICalibrator calibrator;
            ctx.LoadModelOrNull<ICalibrator, SignatureLoadModel>(env, out calibrator, @"Calibrator");
            if (calibrator == null)
                return predictor;
            if (calibrator is IParameterMixer)
                return new ParameterMixingCalibratedModelParameters<LinearBinaryModelParameters, ICalibrator>(env, predictor, calibrator);
            return new SchemaBindableCalibratedModelParameters<LinearBinaryModelParameters, ICalibrator>(env, predictor, calibrator);
        }
 
        private protected override void SaveCore(ModelSaveContext ctx)
        {
            // *** Binary format ***
            // (Base class)
            // LinearModelParameterStatistics: model statistics (optional, in a separate stream)
 
            base.SaveCore(ctx);
            ctx.SetVersionInfo(GetVersionInfo());
 
            Contracts.AssertValueOrNull(Statistics);
            if (Statistics != null)
                ctx.SaveModel(Statistics, ModelStatsSubModelFilename);
        }
 
        private protected override PredictionKind PredictionKind => PredictionKind.BinaryClassification;
 
        /// <summary>
        /// Combine a bunch of models into one by averaging parameters
        /// </summary>
        IParameterMixer<float> IParameterMixer<float>.CombineParameters(IList<IParameterMixer<float>> models)
        {
            VBuffer<float> weights;
            float bias;
            CombineParameters(models, out weights, out bias);
            return new LinearBinaryModelParameters(Host, in weights, bias);
        }
 
        private protected override void SaveSummary(TextWriter writer, RoleMappedSchema schema)
        {
            Host.CheckValue(schema, nameof(schema));
 
            // REVIEW: Would be nice to have the settings!
            var weights = Weight;
            writer.WriteLine(LinearPredictorUtils.LinearModelAsText("Linear Binary Classification Predictor", null, null,
                in weights, Bias, schema));
 
            Statistics?.SaveText(writer, schema.Feature.Value, 20);
        }
 
        ///<inheritdoc/>
        IList<KeyValuePair<string, object>> ICanGetSummaryInKeyValuePairs.GetSummaryInKeyValuePairs(RoleMappedSchema schema)
        {
            Host.CheckValue(schema, nameof(schema));
 
            var weights = Weight;
            List<KeyValuePair<string, object>> results = new List<KeyValuePair<string, object>>();
            LinearPredictorUtils.SaveLinearModelWeightsInKeyValuePairs(in weights, Bias, schema, results);
            Statistics?.SaveSummaryInKeyValuePairs(schema.Feature.Value, int.MaxValue, results);
            return results;
        }
 
        private protected override DataViewRow GetStatsIRowOrNull(RoleMappedSchema schema)
        {
            if (Statistics == null)
                return null;
            var names = default(VBuffer<ReadOnlyMemory<char>>);
            AnnotationUtils.GetSlotNames(schema, RoleMappedSchema.ColumnRole.Feature, Weight.Length, ref names);
            var meta = Statistics.MakeStatisticsMetadata(schema, in names);
            return AnnotationUtils.AnnotationsAsRow(meta);
        }
 
        private protected override void SaveAsIni(TextWriter writer, RoleMappedSchema schema, ICalibrator calibrator = null)
        {
            Host.CheckValue(writer, nameof(writer));
            Host.CheckValue(schema, nameof(schema));
            Host.CheckValueOrNull(calibrator);
 
            var weights = Weight;
            writer.Write(LinearPredictorUtils.LinearModelAsIni(in weights, Bias, this,
                schema, calibrator as PlattCalibrator));
        }
    }
 
    /// <summary>
    /// Model parameters for regression.
    /// </summary>
    public abstract class RegressionModelParameters : LinearModelParameters
    {
        [BestFriend]
        private protected RegressionModelParameters(IHostEnvironment env, string name, in VBuffer<float> weights, float bias)
             : base(env, name, in weights, bias)
        {
        }
 
        [BestFriend]
        private protected RegressionModelParameters(IHostEnvironment env, string name, ModelLoadContext ctx)
            : base(env, name, ctx)
        {
        }
 
        private protected override PredictionKind PredictionKind => PredictionKind.Regression;
 
        /// <summary>
        /// Output the INI model to a given writer
        /// </summary>
        private protected override void SaveAsIni(TextWriter writer, RoleMappedSchema schema, ICalibrator calibrator)
        {
            if (calibrator != null)
                throw Host.ExceptNotImpl("Saving calibrators is not implemented yet.");
 
            Host.CheckValue(writer, nameof(writer));
            Host.CheckValue(schema, nameof(schema));
 
            // REVIEW: For Poisson should encode the exp operation in the ini as well, bug 2433.
            var weights = Weight;
            writer.Write(LinearPredictorUtils.LinearModelAsIni(in weights, Bias, this, schema, null));
        }
    }
 
    /// <summary>
    /// Model parameters for linear regression.
    /// </summary>
    public sealed class LinearRegressionModelParameters : RegressionModelParameters,
        IParameterMixer<float>,
        ICanGetSummaryInKeyValuePairs
    {
        internal const string LoaderSignature = "LinearRegressionExec";
        internal const string RegistrationName = "LinearRegressionPredictor";
 
        private static VersionInfo GetVersionInfo()
        {
            return new VersionInfo(
                modelSignature: "LIN RGRS",
                // verWrittenCur: 0x00010001, // Initial
                verWrittenCur: 0x00020001, // Fixed sparse serialization
                verReadableCur: 0x00020001,
                verWeCanReadBack: 0x00020001,
                loaderSignature: LoaderSignature,
                loaderAssemblyName: typeof(LinearRegressionModelParameters).Assembly.FullName);
        }
 
        /// <summary>
        /// Constructs a new linear regression model from trained weights.
        /// </summary>
        /// <param name="env">The host environment.</param>
        /// <param name="weights">The weights for the linear model. The i-th element of weights is the coefficient
        /// of the i-th feature. Note that this will take ownership of the <see cref="VBuffer{T}"/>.</param>
        /// <param name="bias">The bias added to every output score.</param>
        internal LinearRegressionModelParameters(IHostEnvironment env, in VBuffer<float> weights, float bias)
            : base(env, RegistrationName, in weights, bias)
        {
        }
 
        private LinearRegressionModelParameters(IHostEnvironment env, ModelLoadContext ctx)
            : base(env, RegistrationName, ctx)
        {
        }
 
        internal static LinearRegressionModelParameters Create(IHostEnvironment env, ModelLoadContext ctx)
        {
            Contracts.CheckValue(env, nameof(env));
            env.CheckValue(ctx, nameof(ctx));
            ctx.CheckAtModel(GetVersionInfo());
            return new LinearRegressionModelParameters(env, ctx);
        }
 
        private protected override void SaveCore(ModelSaveContext ctx)
        {
            base.SaveCore(ctx);
            ctx.SetVersionInfo(GetVersionInfo());
        }
 
        private protected override void SaveSummary(TextWriter writer, RoleMappedSchema schema)
        {
            Host.CheckValue(writer, nameof(writer));
            Host.CheckValue(schema, nameof(schema));
 
            // REVIEW: Would be nice to have the settings!
            var weights = Weight;
            writer.WriteLine(LinearPredictorUtils.LinearModelAsText("Linear Regression Predictor", null, null,
                in weights, Bias, schema, null));
        }
 
        /// <summary>
        /// Combine a bunch of models into one by averaging parameters
        /// </summary>
        IParameterMixer<float> IParameterMixer<float>.CombineParameters(IList<IParameterMixer<float>> models)
        {
            VBuffer<float> weights;
            float bias;
            CombineParameters(models, out weights, out bias);
            return new LinearRegressionModelParameters(Host, in weights, bias);
        }
 
        ///<inheritdoc/>
        IList<KeyValuePair<string, object>> ICanGetSummaryInKeyValuePairs.GetSummaryInKeyValuePairs(RoleMappedSchema schema)
        {
            Host.CheckValue(schema, nameof(schema));
 
            var weights = Weight;
            List<KeyValuePair<string, object>> results = new List<KeyValuePair<string, object>>();
            LinearPredictorUtils.SaveLinearModelWeightsInKeyValuePairs(in weights, Bias, schema, results);
 
            return results;
        }
    }
 
    /// <summary>
    /// Model parameters for Poisson Regression.
    /// </summary>
    public sealed class PoissonRegressionModelParameters : RegressionModelParameters, IParameterMixer<float>, ISingleCanSaveOnnx
    {
        internal const string LoaderSignature = "PoissonRegressionExec";
        internal const string RegistrationName = "PoissonRegressionPredictor";
 
        private static VersionInfo GetVersionInfo()
        {
            return new VersionInfo(
                modelSignature: "POI RGRS",
                // verWrittenCur: 0x00010001, // Initial
                verWrittenCur: 0x00020001, // Fixed sparse serialization
                verReadableCur: 0x00020001,
                verWeCanReadBack: 0x00020001,
                loaderSignature: LoaderSignature,
                loaderAssemblyName: typeof(PoissonRegressionModelParameters).Assembly.FullName);
        }
 
        /// <summary>
        /// Constructs a new Poisson regression model parameters from trained model.
        /// </summary>
        /// <param name="env">The Host environment.</param>
        /// <param name="weights">The weights for the linear model. The i-th element of weights is the coefficient
        /// of the i-th feature. Note that this will take ownership of the <see cref="VBuffer{T}"/>.</param>
        /// <param name="bias">The bias added to every output score.</param>
        internal PoissonRegressionModelParameters(IHostEnvironment env, in VBuffer<float> weights, float bias)
            : base(env, RegistrationName, in weights, bias)
        {
        }
 
        private PoissonRegressionModelParameters(IHostEnvironment env, ModelLoadContext ctx)
            : base(env, RegistrationName, ctx)
        {
        }
 
        internal static PoissonRegressionModelParameters Create(IHostEnvironment env, ModelLoadContext ctx)
        {
            Contracts.CheckValue(env, nameof(env));
            env.CheckValue(ctx, nameof(ctx));
            ctx.CheckAtModel(GetVersionInfo());
            return new PoissonRegressionModelParameters(env, ctx);
        }
 
        bool ISingleCanSaveOnnx.SaveAsOnnx(OnnxContext ctx, string[] outputs, string featureColumn)
        {
            // Mapping score to prediction
            var linearRegressorOutput = ctx.AddIntermediateVariable(null, "LinearRegressorOutput", true);
            base.SaveAsOnnx(ctx, new[] { linearRegressorOutput }, featureColumn);
            var opType = "Exp";
            ctx.CreateNode(opType, new[] { linearRegressorOutput }, outputs, ctx.GetNodeName(opType), "");
            return true;
        }
 
        private protected override void SaveCore(ModelSaveContext ctx)
        {
            base.SaveCore(ctx);
            ctx.SetVersionInfo(GetVersionInfo());
        }
 
        private protected override float Score(in VBuffer<float> src)
        {
            return MathUtils.ExpSlow(base.Score(in src));
        }
 
        private protected override void SaveSummary(TextWriter writer, RoleMappedSchema schema)
        {
            Host.CheckValue(writer, nameof(writer));
            Host.CheckValue(schema, nameof(schema));
 
            // REVIEW: Would be nice to have the settings!
            var weights = Weight;
            writer.WriteLine(LinearPredictorUtils.LinearModelAsText("Poisson Regression Predictor", null, null,
                in weights, Bias, schema, null));
        }
 
        /// <summary>
        /// Combine a bunch of models into one by averaging parameters
        /// </summary>
        IParameterMixer<float> IParameterMixer<float>.CombineParameters(IList<IParameterMixer<float>> models)
        {
            VBuffer<float> weights;
            float bias;
            CombineParameters(models, out weights, out bias);
            return new PoissonRegressionModelParameters(Host, in weights, bias);
        }
    }
}