001/*
002 * Copyright (c) 2015-2020, Oracle and/or its affiliates. All rights reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.tribuo.classification.sgd.objectives;
018
019import com.oracle.labs.mlrg.olcut.config.Config;
020import com.oracle.labs.mlrg.olcut.provenance.ConfiguredObjectProvenance;
021import com.oracle.labs.mlrg.olcut.provenance.impl.ConfiguredObjectProvenanceImpl;
022import com.oracle.labs.mlrg.olcut.util.Pair;
023import org.tribuo.classification.sgd.LabelObjective;
024import org.tribuo.math.la.SGDVector;
025import org.tribuo.math.la.SparseVector;
026import org.tribuo.math.util.NoopNormalizer;
027import org.tribuo.math.util.VectorNormalizer;
028
029/**
030 * Hinge loss, scores the correct value margin and any incorrect predictions -margin.
031 * By default the margin is 1.0.
032 * <p>
033 * The Hinge loss does not generate a probabilistic model, and uses a {@link NoopNormalizer}.
034 */
035public class Hinge implements LabelObjective {
036
037    @Config(description="The classification margin.")
038    private double margin = 1.0;
039
040    /**
041     * Construct a hinge objective with the supplied margin.
042     * @param margin The margin to use.
043     */
044    public Hinge(double margin) {
045        this.margin = margin;
046    }
047
048    /**
049     * Construct a hinge objective with a margin of 1.0.
050     */
051    public Hinge() {
052        this(1.0);
053    }
054
055    @Deprecated
056    @Override
057    public Pair<Double, SGDVector> valueAndGradient(int truth, SGDVector prediction) {
058        return lossAndGradient(truth, prediction);
059    }
060
061    /**
062     * Returns a {@link Pair} of {@link Double} and {@link SGDVector} representing the loss
063     * and per label gradients respectively.
064     * @param truth The true label id.
065     * @param prediction The prediction for each label id.
066     * @return The loss and per label gradient.
067     */
068    @Override
069    public Pair<Double,SGDVector> lossAndGradient(Integer truth, SGDVector prediction) {
070        prediction.add(truth,-margin);
071        int predIndex = prediction.indexOfMax();
072
073        if (truth == predIndex) {
074            return new Pair<>(0.0, SparseVector.createSparseVector(prediction.size(),new int[0], new double[0]));
075        } else {
076            int[] indices = new int[2];
077            double[] values = new double[2];
078            if (truth < predIndex) {
079                indices[0] = truth;
080                values[0] = margin;
081                indices[1] = predIndex;
082                values[1] = -margin;
083            } else {
084                indices[0] = predIndex;
085                values[0] = -margin;
086                indices[1] = truth;
087                values[1] = margin;
088            }
089            SparseVector output = SparseVector.createSparseVector(prediction.size(),indices,values);
090            double loss = prediction.get(truth) - prediction.get(predIndex);
091            return new Pair<>(loss,output);
092        }
093    }
094
095    /**
096     * Returns a new {@link NoopNormalizer}.
097     * @return The vector normalizer.
098     */
099    @Override
100    public VectorNormalizer getNormalizer() {
101        return new NoopNormalizer();
102    }
103
104    /**
105     * Returns false.
106     * @return False.
107     */
108    @Override
109    public boolean isProbabilistic() {
110        return false;
111    }
112
113    @Override
114    public String toString() {
115        return "Hinge(margin="+margin+")";
116    }
117
118    @Override
119    public ConfiguredObjectProvenance getProvenance() {
120        return new ConfiguredObjectProvenanceImpl(this,"LabelObjective");
121    }
122}