예제 #1
0
        /// <summary>
        /// Finds the unidimensional criterion that maximizes accuracy for detecting the target category in a sample.
        /// </summary>
        /// <param name="targetCat">The target category.</param>
        /// <param name="cat">The category labels for each datum in the sample.</param>
        /// <param name="x">The univariate value for each datum in the sample.</param>
        /// <param name="idx">The index into x[.] and cat[.] which rank-orders the sample in terms of increasing x.</param>
        /// <param name="catWeight">The weight of each category on the accuracy.</param>
        /// <returns>The unidimensional criterion.</returns>
        public static UniCrit MaximumAccuracy(int targetCat, byte[] cat, float[] x, int[] idx, double[] catWeight)
        {
            UniCrit output = new UniCrit();

            output.MaximizeAccuracy(targetCat, cat, x, idx, catWeight);
            return(output);
        }
예제 #2
0
        public UniCrit Copy()
        {
            UniCrit output = new UniCrit();

            output.Accuracy    = this.Accuracy;
            output.CritIndex   = this.CritIndex;
            output.CritValue   = this.CritValue;
            output.TargetUpper = this.TargetUpper;
            return(output);
        }
예제 #3
0
        /// <summary>
        /// Trains the classifier by optimizing parameters based on the training data using specified solver options.
        /// This can only be called once.
        /// </summary>
        /// <param name="data">The training data.
        /// WARNING:  In order to save memory, this data is altered in palce (instead of copying a new object).</param>
        /// <param name="ops">Sets the member variable <see cref="Classifier.Options"/>.  If a value is not provided, default options are used.</param>
        /// <param name="ops">The pre-optimization analysis <see cref="Classifier.Analysis"/>.  If a value is not provided, default options are used.</param>
        public async Task <Trainer> Train(CategorizedData data, SolverOptions ops = null, PreOptimizationAnalysis analysis = null)
        {
            lock (this)
            {
                if (this.trainerIsRunning)
                {
                    throw new ApplicationException("The Train method can only be called once at a time.  You might consider creating multiple Trainer objects.");
                }
                this.trainerIsRunning = true;
            }
            try
            {
                if (data.Ncats != this.Classifier.Instance.Ncats)
                {
                    throw new ArgumentException("The number of categories in the classifier must be equal to the number of categories in the training set.");
                }

                //-----------------------
                //	Set the solver otions.
                //-----------------------
                if (ops == null)
                {
                    ops = new SolverOptions();
                }
                this.options = ops;

                //-----------------------
                //	Compute CatWeights.
                //-----------------------
                double cwTotal = 0.0;
                this.totalWeight = 0.0;                  // Total weight across all training data.
                if (this.options.WeightingRule == WeightingRule.EqualPriors)
                {
                    for (int iCat = 0; iCat < data.Ncats; iCat++)
                    {
                        double w = (double)data.Ntotal / (double)data.Neach[iCat] / (double)data.Ncats;
                        this.CatWeights[iCat] = w;
                        cwTotal          += w;
                        this.totalWeight += w * (double)data.Neach[iCat];
                    }
                }
                else if (this.options.WeightingRule == WeightingRule.ObservedPriors)
                {
                    for (int iCat = 0; iCat < data.Ncats; iCat++)
                    {
                        this.totalWeight     += (float)data.Neach[iCat];
                        this.CatWeights[iCat] = 1.0f;
                        cwTotal += 1.0f;
                    }
                }
                else
                {
                    throw new ApplicationException("Unhandled weighting rule.");
                }

                //	[iDatum] Used for identifying the category label for each datum.
                byte[] catVec = new byte[data.Ntotal];
                //	[iDatum] Used for storing the polynomial calculation for each datum.
                float[][] yVec = Static.NewArrays <float>(this.Classifier.Instance.Npoly, data.Ntotal);
                //	[iPoly][iDatum]  Used for sorting data rows.  Sort order is preserved for each polynomial.
                int[][] idxVec = Static.NewArrays <int>(this.Classifier.Instance.Npoly, data.Ntotal);

                //-----------------------
                //	Prepare category labels for each datum.
                //-----------------------
                int iDatum = 0;
                for (int iCat = 0; iCat < data.Ncats; iCat++)
                {
                    int nRows = data.X[iCat].Length;
                    for (int iRow = 0; iRow < nRows; iRow++)
                    {
                        catVec[iDatum++] = (byte)iCat;
                    }
                }

                if (analysis != null)
                {
                    this.Analysis = analysis;
                }
                else
                {
                    //-----------------------
                    //	Condition the data and perform a polynomial expansion.
                    //-----------------------
                    this.Analysis = new PreOptimizationAnalysis();
                    this.Analysis.ConditionMeasurer = SpatialConditionMeasurer.Measure(data);
                    this.Analysis.Conditioner       = this.Analysis.ConditionMeasurer.Conditioner();
                    this.Analysis.Conditioner.Condition(data);
                    data.Expand(this.Classifier.Instance.Coeffs);

                    this.Analysis.ParamScale = new float[this.Classifier.Instance.Coeffs.Ncoeffs];
                    this.Analysis.ParamInit  = Static.NewArrays <float>(this.Classifier.Instance.Ncats, this.Classifier.Instance.Coeffs.Ncoeffs);

                    //if (ops.InitializeParams)
                    this.Analysis.Crits = Static.NewArrays <UniCrit>(this.Classifier.Instance.Ncats, this.Classifier.Instance.Coeffs.Ncoeffs);

                    //-----------------------
                    //	Get the parameter scale.
                    //-----------------------
                    float   scale;
                    int     i15  = (int)(0.5 + 0.15 * (double)(data.Ntotal - 1));
                    int     i50  = (int)(0.5 + 0.50 * (double)(data.Ntotal - 1));
                    int     i85  = (int)(0.5 + 0.85 * (double)(data.Ntotal - 1));
                    float[] xVec = yVec[0];
                    for (int iCoeff = 0; iCoeff < this.Classifier.Instance.Coeffs.Ncoeffs; iCoeff++)
                    {
                        //	Quantiles determine the parameter scale.
                        Static.FillSeries(idxVec[0]);
                        iDatum = 0;
                        for (int iCat = 0; iCat < data.Ncats; iCat++)
                        {
                            int nSamp = data.Neach[iCat];
                            for (int iSamp = 0; iSamp < nSamp; iSamp++)
                            {
                                xVec[iDatum++] = data.X[iCat][iSamp][iCoeff];
                            }
                        }
                        Static.QuickSortIndex(idxVec[0], xVec, 0, xVec.Length - 1);
                        scale = xVec[idxVec[0][i85]] - xVec[idxVec[0][i15]];
                        this.Analysis.ParamScale[iCoeff] = (float)(1.0 / scale);

                        //if (ops.InitializeParams)
                        //{
                        //	Get univariate classification criteria to get a first-order clue about the saliency of each feature.
                        for (int iCat = 0; iCat < data.Ncats; iCat++)
                        {
                            this.Analysis.Crits[iCat][iCoeff] = UniCrit.MaximumAccuracy(iCat, catVec, xVec, idxVec[0], this.CatWeights);
                        }
                        //}
                    }

                    //-----------------------
                    //	Compute initial params.
                    //-----------------------
                    //	Compute the expected minimum value for the univariate 2-category classification accuracy.
                    double[] accMin = new double[data.Ncats];
                    for (int iCat = 0; iCat < data.Ncats; iCat++)
                    {
                        accMin[iCat] = (this.totalWeight - this.CatWeights[iCat] * (double)data.Neach[iCat]) / this.totalWeight;
                        if (accMin[iCat] < 0.5)
                        {
                            accMin[iCat] = 1.0 - accMin[iCat];
                        }
                    }

                    //	The magnitude of each parameter is a function of univariate classification accuracy for the corresponding spatial dimension.
                    double invNtotal = 1.0 / data.Ntotal;
                    for (int iCoeff = 0; iCoeff < this.Classifier.Instance.Coeffs.Ncoeffs; iCoeff++)
                    {
                        if (this.Classifier.Instance.Npoly == 1)
                        {
                            double tAcc = 0.5 *
                                          (
                                Math.Max(0.0, this.Analysis.Crits[0][iCoeff].Accuracy - accMin[0])
                                +
                                Math.Max(0.0, this.Analysis.Crits[1][iCoeff].Accuracy - accMin[1])
                                          );
                            tAcc /= (1.0 - 0.5 * (accMin[0] + accMin[1]) + invNtotal);
                            if (this.Analysis.Crits[0][iCoeff].TargetUpper)
                            {
                                this.Analysis.ParamInit[0][iCoeff] = (float)tAcc * this.Analysis.ParamScale[iCoeff];
                            }
                            else
                            {
                                this.Analysis.ParamInit[0][iCoeff] = -(float)tAcc * this.Analysis.ParamScale[iCoeff];
                            }
                        }
                        else
                        {
                            for (int iPoly = 0; iPoly < this.Classifier.Instance.Npoly; iPoly++)
                            {
                                double tAcc = Math.Max(0.0, this.Analysis.Crits[iPoly][iCoeff].Accuracy - accMin[iPoly]);
                                tAcc /= (1.0 - accMin[iPoly] + invNtotal);
                                if (this.Analysis.Crits[iPoly][iCoeff].TargetUpper)
                                {
                                    this.Analysis.ParamInit[iPoly][iCoeff] = (float)tAcc * this.Analysis.ParamScale[iCoeff];
                                }
                                else
                                {
                                    this.Analysis.ParamInit[iPoly][iCoeff] = -(float)tAcc * this.Analysis.ParamScale[iCoeff];
                                }
                            }
                        }
                    }
                }

                //-----------------------
                //	Set classifier parameters.
                //-----------------------
                if (ops.InitializeParams)
                {
                    Static.Copy <float>(this.Analysis.ParamInit, this.Classifier.Instance.Params);
                }
                else
                {
                    //	Inherit parameters passed in by the classifier.  We assume the classifier was already initialized with parameters.
                    Static.Copy <float>(this.Classifier.Instance.Params, this.Analysis.ParamInit);
                }

                //-----------------------
                //	Perform dual optimizations.
                //-----------------------
                if (this.Classifier.Instance.Npoly > 2 && this.options.InitializeParams)
                {
                    SolverOptions dualOps = this.options.Copy();
                    dualOps.WeightingRule = WeightingRule.EqualPriors;
                    Task <Trainer>[] dualTasks = new Task <Trainer> [this.Classifier.Instance.Npoly];
                    for (int iPoly = 0; iPoly < this.Classifier.Instance.Npoly; iPoly++)
                    {
                        Trainer t = new Trainer(this.Classifier.Instance.GetDual(iPoly));
                        dualTasks[iPoly] = t.Train(data.GetDual(iPoly), dualOps);
                    }
                    for (int iPoly = 0; iPoly < this.Classifier.Instance.Npoly; iPoly++)
                    {
                        Trainer t = await dualTasks[iPoly];
                        Array.Copy(t.Classifier.Instance.Params, this.Analysis.ParamInit[iPoly], this.Classifier.Instance.Coeffs.Ncoeffs);
                    }
                    Static.Copy <float>(this.Analysis.ParamInit, this.Classifier.Instance.Params);
                }

                //-----------------------
                //	Finish constructing the classifier for the first time.
                //-----------------------
                MonotonicRegressor regressor = new MonotonicRegressor();
                for (int iPoly = 0; iPoly < this.Classifier.Instance.Npoly; iPoly++)
                {
                    Static.FillSeries(idxVec[iPoly]);
                }

                for (int iPoly = 0; iPoly < this.Classifier.Instance.Npoly; iPoly++)
                {
                    //	Set the quantized probability limits.
                    double nPerQuantile = (double)Math.Min(data.Neach[iPoly], data.Ntotal - data.Neach[iPoly]) / (double)this.Classifier.Instance.Quant[iPoly].Nquantiles;
                    this.Classifier.Instance.Quant[iPoly].Pmin = 1.0 / nPerQuantile;
                    this.Classifier.Instance.Quant[iPoly].Pmax = 1.0 - this.Classifier.Instance.Quant[iPoly].Pmin;

                    //	Evaluate the polynomial expression for each datum.
                    iDatum = 0;
                    for (int iCat = 0; iCat < data.Ncats; iCat++)
                    {
                        int nSamp = data.Neach[iCat];
                        for (int iSamp = 0; iSamp < nSamp; iSamp++)
                        {
                            //	Evaluate the polynomial expression.
                            yVec[iPoly][iDatum++] = (float)this.Classifier.Instance.EvalPolyFromExpanded(iPoly, data.X[iCat][iSamp]);
                        }
                    }

                    //	Sort the output.  Indexes are preserved to speed up subsequent sorts.
                    Static.QuickSortIndex(idxVec[iPoly], yVec[iPoly], 0, data.Ntotal - 1);

                    //	Quantize the output.
                    this.Classifier.Instance.Quant[iPoly].Measure(idxVec[iPoly], yVec[iPoly], catVec, (byte)iPoly, this.CatWeights, totalWeight, regressor);
                }

                //-----------------------
                //	Measure the conditional entropy.
                //-----------------------
                float[]  y = new float[this.Classifier.Instance.Npoly];
                double[] p;
                double   fit = 0.0;
                byte     c;
                for (iDatum = 0; iDatum < data.Ntotal; iDatum++)
                {
                    for (int iPoly = 0; iPoly < this.Classifier.Instance.Npoly; iPoly++)
                    {
                        y[iPoly] = yVec[iPoly][iDatum];
                    }
                    p    = this.Classifier.Instance.ClassifyPolynomialOutputs(y);
                    c    = catVec[iDatum];
                    fit += Math.Log(p[c]) * this.CatWeights[c];
                }
                //	Change logarithm base and normalize by total weight.
                this.Classifier.Fit = -fit / totalWeight / Math.Log((double)data.Ncats);                        //	<-- The conditional entropy... we want to minimize it.

                //-----------------------
                //	Prepare optimization memory.
                //-----------------------

                //	Initialize an orthonormal if the parameter space is small enough.
                float[][][] ortho   = null;
                int         nParams = this.Classifier.Instance.Npoly * this.Classifier.Instance.Coeffs.Ncoeffs;
                if (nParams <= 100)
                {
                    ortho = this.randomDeviates();
                }
                int iOrtho  = 0;
                int ctOrtho = 0;
                int ctSteps = 0;

                //	Every `modOrtho` steps through the orthonormal basis, we try a gradient search.
                int modOrtho = Math.Min(10, nParams);

                //	For a trip through `modOrtho` bases, changes in entropy are partialled across the parameter space.
                float[][] dhOrtho = Static.NewArrays <float>(this.Classifier.Instance.Npoly, this.Classifier.Instance.Coeffs.Ncoeffs);

                //-----------------------
                //	Optimize.
                //-----------------------
                //	The attempted classifier.
                Classifier cTry = this.Classifier.Instance.Copy();
                //	The initial step size.
                float stepSize = this.Options.ParamDiffMax;
                //	The optimization mode.  We start by iterating through the orthogonal bases.
                OptimizationMode mode = OptimizationMode.Ortho;

                throw new NotImplementedException("TO DO");

                bool keepOptimizing = true;
                while (keepOptimizing)
                {
                    if (mode == OptimizationMode.Ortho)
                    {
                        if (iOrtho >= modOrtho)
                        {
                            iOrtho = 0;
                        }
                    }
                    else if (mode == OptimizationMode.Gradient)
                    {
                    }
                    else
                    {
                        throw new ApplicationException("Unhandled optimization mode.");
                    }
                }
            }
            finally
            {
                this.trainerIsRunning = false;
            }
        }