diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/BinaryCrossentropy.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/BinaryCrossentropy.java new file mode 100644 index 00000000000..effdf990f71 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/BinaryCrossentropy.java @@ -0,0 +1,231 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +import static org.tensorflow.framework.utils.CastHelper.cast; + +/** + * Computes the cross-entropy loss between true labels and predicted labels. + * + * <p>Use this cross-entropy loss when there are only two label classes (assumed to be 0 and 1). For + * each example, there should be a single floating-point value per prediction. + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.6f, 0.4f}, {0.4f, 0.6f}}); + * BinaryCrossentropy bce = new BinaryCrossentropy(tf); + * Operand<TFloat32> result = bce.call(labels, predictions); + * // produces 0.815 + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {1.f, 0.f}); + * Operand<TFloat32> result = bce.call(labels, predictions, sampleWeight); + * // produces 0.458f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * BinaryCrossentropy bce = new BinaryCrossentropy(tf, Reduction.SUM); + * Operand<TFloat32> result = bce.call(labels, predictions); + * // produces 1.630f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * BinaryCrossentropy bce = new BinaryCrossentropy(tf, Reduction.NONE); + * Operand<TFloat32> result = bce.call(labels, predictions); + * // produces [0.916f, 0.714f] + * </pre> + */ +public class BinaryCrossentropy extends Loss { + public static final boolean FROM_LOGITS_DEFAULT = false; + public static final float LABEL_SMOOTHING_DEFAULT = 0.0f; + + private final boolean fromLogits; + private final float labelSmoothing; + + /** + * Creates a Binary Crossentropy Loss using {@link Class#getSimpleName()} as the loss name, {@link + * #FROM_LOGITS_DEFAULT} for fromLogits, {@link #LABEL_SMOOTHING_DEFAULT} for labelSmoothing and a + * Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public BinaryCrossentropy(Ops tf) { + this(tf, null, FROM_LOGITS_DEFAULT, LABEL_SMOOTHING_DEFAULT, REDUCTION_DEFAULT); + } + + /** + * Creates a Binary Crossentropy loss using {@link Class#getSimpleName()} as the loss name, {@link + * #FROM_LOGITS_DEFAULT} for fromLogits, and {@link #LABEL_SMOOTHING_DEFAULT} for labelSmoothing + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public BinaryCrossentropy(Ops tf, Reduction reduction) { + this(tf, null, FROM_LOGITS_DEFAULT, LABEL_SMOOTHING_DEFAULT, reduction); + } + + /** + * Creates a Binary Crossentropy loss using using {@link Class#getSimpleName()} as the loss name, + * labelSmoothing of {@link #LABEL_SMOOTHING_DEFAULT}, a reduction of {@link + * Loss#REDUCTION_DEFAULT}, + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + */ + public BinaryCrossentropy(Ops tf, boolean fromLogits) { + this(tf, null, fromLogits, LABEL_SMOOTHING_DEFAULT, REDUCTION_DEFAULT); + } + + /** + * Creates a Binary Crossentropy loss using labelSmoothing of {@link #LABEL_SMOOTHING_DEFAULT} a + * reduction of {@link Loss#REDUCTION_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param fromLogits Whether to interpret predictions as a tensor of logit values + */ + public BinaryCrossentropy(Ops tf, String name, boolean fromLogits) { + this(tf, name, fromLogits, LABEL_SMOOTHING_DEFAULT, REDUCTION_DEFAULT); + } + + /** + * Creates a Binary Crossentropy loss using using {@link Class#getSimpleName()} as the loss name, + * and a reduction of {@link Loss#REDUCTION_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing A number in the range, [0, 1]. When 0, no smoothing occurs. When > 0, + * compute the loss between the predicted labels and a smoothed version of the true labels, + * where the smoothing squeezes the labels towards 0.5. Larger values of labelSmoothing + * correspond to heavier smoothing. + */ + public BinaryCrossentropy(Ops tf, boolean fromLogits, float labelSmoothing) { + this(tf, null, fromLogits, labelSmoothing, REDUCTION_DEFAULT); + } + + /** + * Creates a Binary Crossentropy loss using a reduction of {@link Loss#REDUCTION_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing A number in the range, [0, 1]. When 0, no smoothing occurs. When > 0, + * compute the loss between the predicted labels and a smoothed version of the true labels, + * where the smoothing squeezes the labels towards 0.5. Larger values of labelSmoothing + * correspond to heavier smoothing. + */ + public BinaryCrossentropy(Ops tf, String name, boolean fromLogits, float labelSmoothing) { + this(tf, name, fromLogits, labelSmoothing, REDUCTION_DEFAULT); + } + + /** + * Creates a Binary Crossentropy loss + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing A number in the range, [0, 1]. When 0, no smoothing occurs. When > 0, + * compute the loss between the predicted labels and a smoothed version of the true labels, + * where the smoothing squeezes the labels towards 0.5. Larger values of labelSmoothing + * correspond to heavier smoothing. + * @param reduction Type of Reduction to apply to the loss. + */ + public BinaryCrossentropy(Ops tf, boolean fromLogits, float labelSmoothing, Reduction reduction) { + this(tf, null, fromLogits, labelSmoothing, reduction); + } + + /** + * Creates a Binary Crossentropy loss + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing A number in the range, [0, 1]. When 0, no smoothing occurs. When > 0, + * compute the loss between the predicted labels and a smoothed version of the true labels, + * where the smoothing squeezes the labels towards 0.5. Larger values of labelSmoothing + * correspond to heavier smoothing. + * @param reduction Type of Reduction to apply to the loss. + * @throws IllegalArgumentException if labelSmoothing is not in the inclusive range of 0. - 1. + */ + public BinaryCrossentropy( + Ops tf, String name, boolean fromLogits, float labelSmoothing, Reduction reduction) { + super(tf, name, reduction); + if (labelSmoothing < 0 || labelSmoothing > 1) + throw new IllegalArgumentException( + "labelSmoothing must be >= 0. and <= 1, found " + labelSmoothing); + this.fromLogits = fromLogits; + this.labelSmoothing = labelSmoothing; + } + + /** + * Generates an Operand that calculates the loss. + * + * <p>If run in Graph mode, the computation will throw {@link + * org.tensorflow.exceptions.TFInvalidArgumentException} if the predictions values are outside the + * range o [0. to 1.]. In Eager Mode, this call will throw {@link IllegalArgumentException}, if + * the predictions values are outside the range o [0. to 1.] + * + * @param labels the truth values or labels + * @param predictions the predictions, values must be in the range [0. to 1.] inclusive. + * @param sampleWeights Optional SampleWeights acts as a coefficient for the loss. If a scalar is + * provided, then the loss is simply scaled by the given value. If SampleWeights is a tensor + * of size [batch_size], then the total loss for each sample of the batch is rescaled by the + * corresponding element in the SampleWeights vector. If the shape of SampleWeights is + * [batch_size, d0, .. dN-1] (or can be broadcast to this shape), then each loss element of + * predictions is scaled by the corresponding value of SampleWeights. (Note on dN-1: all loss + * functions reduce by 1 dimension, usually axis=-1.) + * @param <T> The data type of the predictions, sampleWeights and loss. + * @param <U> The data type of the labels. + * @return the loss + * @throws IllegalArgumentException if the predictions are outside the range [0.-1.]. + */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> lPredictions; + if (!fromLogits) { + // add predictions range check for 0 - 1 + lPredictions = + LossesHelper.rangeCheck( + getTF(), + "predictions range check [0-1]", + predictions, + cast(getTF(), getTF().constant(0), predictions.asOutput().dataType()), + cast(getTF(), getTF().constant(1), predictions.asOutput().dataType())); + + } else { + lPredictions = predictions; + } + + Operand<T> losses = + Losses.binaryCrossentropy(getTF(), labels, lPredictions, fromLogits, labelSmoothing); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CategoricalCrossentropy.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CategoricalCrossentropy.java new file mode 100644 index 00000000000..7701ebfb806 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CategoricalCrossentropy.java @@ -0,0 +1,270 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +import static org.tensorflow.framework.utils.CastHelper.cast; + +/** + * Computes the crossentropy loss between the labels and predictions. + * + * <p>Use this crossentropy loss function when there are two or more label classes. We expect labels + * to be provided in a one_hot representation. If you want to provide labels as integers, please use + * {@link SparseCategoricalCrossentropy} loss. There should be <code># classes</code> floating point + * values per feature. + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0, 1, 0}, {0, 0, 1}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.05f, 0.95f, 0f}, {0.1f, 0.8f, 0.1f}}); + * CategoricalCrossentropy cce = new CategoricalCrossentropy(tf); + * Operand<TFloat32> result = cce.call(labels, predictions); + * // produces 1.177 + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.3f, 0.7f}); + * Operand<TFloat32> result = cce.call(labels, predictions, sampleWeight); + * // produces 0.814f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * CategoricalCrossentropy cce = new CategoricalCrossentropy(tf, Reduction.SUM); + * Operand<TFloat32> result = cce.call(labels, predictions); + * // produces 2.354f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * CategoricalCrossentropy cce = + * new CategoricalCrossentropy(tf, Reduction.NONE); + * Operand<TFloat32> result = cce.call(labels, predictions); + * // produces [0.0513f, 2.303f] + * </pre> + */ +public class CategoricalCrossentropy extends Loss { + public static final boolean FROM_LOGITS_DEFAULT = false; + public static final float LABEL_SMOOTHING_DEFAULT = 0.0f; + public static final int DEFAULT_AXIS = -1; + + private final boolean fromLogits; + private final float labelSmoothing; + private final int axis; + + /** + * Creates a categorical cross entropy Loss using {@link Class#getSimpleName()} as the loss name, + * {@link #FROM_LOGITS_DEFAULT} for fromLogits, {@link #LABEL_SMOOTHING_DEFAULT} for + * labelSmoothing, a Loss Reduction of {@link Loss#REDUCTION_DEFAULT}, and an axis of {@link + * #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + */ + public CategoricalCrossentropy(Ops tf) { + this(tf, null, FROM_LOGITS_DEFAULT, LABEL_SMOOTHING_DEFAULT, REDUCTION_DEFAULT, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss using {@link #FROM_LOGITS_DEFAULT} for fromLogits, + * {@link #LABEL_SMOOTHING_DEFAULT} for labelSmoothing, a Loss Reduction of {@link + * Loss#REDUCTION_DEFAULT}, and an axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param name the name of this loss + */ + public CategoricalCrossentropy(Ops tf, String name) { + this(tf, name, FROM_LOGITS_DEFAULT, LABEL_SMOOTHING_DEFAULT, REDUCTION_DEFAULT, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss using {@link Class#getSimpleName()} as the loss name, + * {@link #FROM_LOGITS_DEFAULT} for fromLogits, {@link #LABEL_SMOOTHING_DEFAULT} for + * labelSmoothing and an axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to loss. + */ + public CategoricalCrossentropy(Ops tf, Reduction reduction) { + this(tf, null, FROM_LOGITS_DEFAULT, LABEL_SMOOTHING_DEFAULT, reduction, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss {@link #FROM_LOGITS_DEFAULT} for fromLogits, {@link + * #LABEL_SMOOTHING_DEFAULT} for labelSmoothing, and an axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param name the name of this loss + * @param reduction Type of Reduction to apply to loss. + */ + public CategoricalCrossentropy(Ops tf, String name, Reduction reduction) { + this(tf, name, FROM_LOGITS_DEFAULT, LABEL_SMOOTHING_DEFAULT, reduction, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss using {@link Class#getSimpleName()} as the loss name, + * {@link #LABEL_SMOOTHING_DEFAULT} for labelSmoothing, a Loss Reduction of {@link + * Loss#REDUCTION_DEFAULT}, and an axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + */ + public CategoricalCrossentropy(Ops tf, boolean fromLogits) { + this(tf, null, fromLogits, LABEL_SMOOTHING_DEFAULT, REDUCTION_DEFAULT, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss using {@link #LABEL_SMOOTHING_DEFAULT} for + * labelSmoothing, a Loss Reduction of {@link Loss#REDUCTION_DEFAULT}, and a channel axis of + * {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param name the name of this loss + * @param fromLogits Whether to interpret predictions as a tensor of logit values + */ + public CategoricalCrossentropy(Ops tf, String name, boolean fromLogits) { + this(tf, name, fromLogits, LABEL_SMOOTHING_DEFAULT, REDUCTION_DEFAULT, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss using {@link Class#getSimpleName()} as the loss name, + * a Loss Reduction of {@link Loss#REDUCTION_DEFAULT}, and a channel axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing Float in <code>[0, 1]</code>. When <code>> 0</code>, label values are smoothed, meaning the + * confidence on label values are relaxed. e.g. <code>labelSmoothing=0.2<code> means that we will use a + * value of </code>0.1<code> for label </code>0<code> and </code>0.9<code> for label </code>1<code> + */ + public CategoricalCrossentropy(Ops tf, boolean fromLogits, float labelSmoothing) { + this(tf, null, fromLogits, labelSmoothing, REDUCTION_DEFAULT, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss using a Loss Reduction of {@link Loss#REDUCTION_DEFAULT}, + * and a channel axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param name the name of this loss + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing Float in <code>[0, 1]</code>. When <code>> 0</code>, label values are smoothed, meaning the + * confidence on label values are relaxed. e.g. <code>labelSmoothing=0.2<code> means that we will use a + * value of </code>0.1<code> for label </code>0<code> and </code>0.9<code> for label </code>1<code> + */ + public CategoricalCrossentropy(Ops tf, String name, boolean fromLogits, float labelSmoothing) { + this(tf, name, fromLogits, labelSmoothing, REDUCTION_DEFAULT, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss using {@link Class#getSimpleName()} as the loss name + * and a channel axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing Float in <code>[0, 1]</code>. When <code>> 0</code>, label values are smoothed, meaning the + * confidence on label values are relaxed. e.g. <code>x=0.2<code> means that we will use a + * value of </code>0.1<code> for label </code>0<code> and </code>0.9<code> for label </code>1<code> + * @param reduction Type of Reduction to apply to loss. + */ + public CategoricalCrossentropy( + Ops tf, boolean fromLogits, float labelSmoothing, Reduction reduction) { + this(tf, null, fromLogits, labelSmoothing, reduction, DEFAULT_AXIS); + } + + /** + * Creates a categorical cross entropy Loss + * + * @param tf the TensorFlow Ops + * @param name the name of this loss + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing Float in <code>[0, 1]</code>. When <code>> 0</code>, label values are smoothed, meaning the + * confidence on label values are relaxed. e.g. <code>labelSmoothing=0.2<code> means that we will use a + * value of </code>0.1<code> for label </code>0<code> and </code>0.9<code> for label </code>1<code> + * @param reduction Type of Reduction to apply to loss. + * @param axis The channels axis. <code>axis=-1</code> corresponds to data format `Channels Last' + * and <code>axis=1</code> corresponds to data format 'Channels First'. + * @throws IllegalArgumentException if labelSmoothing is not in the inclusive range of 0. - 1. + */ + public CategoricalCrossentropy( + Ops tf, + String name, + boolean fromLogits, + float labelSmoothing, + Reduction reduction, + int axis) { + super(tf, name, reduction); + if (labelSmoothing < 0 || labelSmoothing > 1) + throw new IllegalArgumentException( + "labelSmoothing must be >= 0. and <= 1, found " + labelSmoothing); + this.fromLogits = fromLogits; + this.labelSmoothing = labelSmoothing; + this.axis = axis; + } + + /** + * Generates an Operand that calculates the loss. + * + * <p>If run in Graph mode, the computation will throw {@link + * org.tensorflow.exceptions.TFInvalidArgumentException} if the predictions values are outside the + * range o [0. to 1.]. In Eager Mode, this call will throw {@link IllegalArgumentException}, if + * the predictions values are outside the range o [0. to 1.] + * + * @param labels the truth values or labels + * @param predictions the predictions, values must be in the range [0. to 1.] inclusive. + * @param sampleWeights Optional SampleWeights acts as a coefficient for the loss. If a scalar is + * provided, then the loss is simply scaled by the given value. If SampleWeights is a tensor + * of size [batch_size], then the total loss for each sample of the batch is rescaled by the + * corresponding element in the SampleWeights vector. If the shape of SampleWeights is + * [batch_size, d0, .. dN-1] (or can be broadcast to this shape), then each loss element of + * predictions is scaled by the corresponding value of SampleWeights. (Note on dN-1: all loss + * functions reduce by 1 dimension, usually axis=-1.) + * @param <T> The data type of the predictions, sampleWeights and loss. + * @param <U> The data type of the labels. + * @return the loss + * @throws IllegalArgumentException if the predictions are outside the range [0.-1.]. + */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> lPredictions; + if (!fromLogits) { + // add predictions range check for 0 - 1 + lPredictions = + LossesHelper.rangeCheck( + getTF(), + "predictions range check [0-1]", + predictions, + cast(getTF(), getTF().constant(0), predictions.asOutput().dataType()), + cast(getTF(), getTF().constant(1), predictions.asOutput().dataType())); + + } else { + lPredictions = predictions; + } + Operand<T> losses = + Losses.categoricalCrossentropy( + getTF(), labels, lPredictions, fromLogits, labelSmoothing, axis); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CategoricalHinge.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CategoricalHinge.java new file mode 100644 index 00000000000..f592c19f8bb --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CategoricalHinge.java @@ -0,0 +1,107 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the categorical hinge loss between labels and predictions. + * + * <p><code>loss = maximum(neg - pos + 1, 0)</code> where <code>neg=maximum((1-labels)*predictions) + * </code> and <code>pos=sum(labels*predictions)</code> + * + * <p><code>labels</code> values are expected to be 0 or 1.</p> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0, 1}, {0, 0}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.6f, 0.4f}, {0.4f, 0.6f}}); + * CategoricalHinge categoricalHinge = new CategoricalHinge(tf); + * Operand<TFloat32> result = categoricalHinge.call(labels, predictions); + * // produces 1.4 + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {1f, 0.f}); + * Operand<TFloat32> result = categoricalHinge.call(labels, predictions, sampleWeight); + * // produces 0.6f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * CategoricalHinge categoricalHinge = new CategoricalHinge(tf, Reduction.SUM); + * Operand<TFloat32> result = categoricalHinge.call(labels, predictions); + * // produces 2.8f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * CategoricalHinge categoricalHinge = + * new CategoricalHinge(tf, Reduction.NONE); + * Operand<TFloat32> result = categoricalHinge.call(labels, predictions); + * // produces [1.2f, 1.6f] + * </pre> + */ +public class CategoricalHinge extends Loss { + + /** + * Creates a Categorical Hinge Loss using {@link Class#getSimpleName()} as the loss name and a + * Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public CategoricalHinge(Ops tf) { + super(tf); + } + + /** + * Creates a Categorical Hinge Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public CategoricalHinge(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a Categorical Hinge + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public CategoricalHinge(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.categoricalHinge(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CosineSimilarity.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CosineSimilarity.java new file mode 100644 index 00000000000..137c7025c04 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/CosineSimilarity.java @@ -0,0 +1,179 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the cosine similarity between labels and predictions. + * + * <p>Note that it is a number between <code>-1</code> and <code>1</code>. When it is a negative number between <code>-1</code> and <code>0</code>, <code>0</code> + * indicates orthogonality and values closer to <code>-1</code>indicate greater similarity. The values closer to + * <code>1</code> indicate greater dissimilarity. This makes it usable as a loss function in a setting where you + * try to maximize the proximity between predictions and targets. If either <code>labels</code> or <code>predictions</code> is + * a zero vector, cosine similarity will be <code>0</code> regardless of the proximity between predictions and + * targets. + * + * <p><code>loss = -sum(l2Norm(labels) * l2Norm(predictions))</code> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {1.f, 1.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{1.f, 0.f}, {1.f, 1.f}}); + * CosineSimilarity cosineLoss = new CosineSimilarity(tf); + * Operand<TFloat32> result = cosineLoss.call(labels, predictions); + * // produces -0.5 + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.8f, 0.2f}); + * Operand<TFloat32> result = cosineLoss.call(labels, predictions, sampleWeight); + * // produces -0.0999f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * CosineSimilarity cosineLoss = new CosineSimilarity(tf, Reduction.SUM); + * Operand<TFloat32> result = cosineLoss.call(labels, predictions); + * // produces -0.999f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * CosineSimilarity cosineLoss = new CosineSimilarity(tf, Reduction.NONE); + * Operand<TFloat32> result = cosineLoss.call(labels, predictions); + * // produces [-0.f, -0.999f] + * </pre> + */ +public class CosineSimilarity extends Loss { + public static final int DEFAULT_AXIS = -1; + public static final Reduction DEFAULT_REDUCTION = Reduction.AUTO; + + private final int axis; + + /** + * Creates a Cosine Similarity Loss using {@link Class#getSimpleName()} as the loss name, an axis + * of {@link #DEFAULT_AXIS}, and a Loss Reduction of {@link #DEFAULT_REDUCTION} + * + * @param tf the TensorFlow Ops + */ + public CosineSimilarity(Ops tf) { + + this(tf, null, DEFAULT_AXIS, DEFAULT_REDUCTION); + } + + /** + * Creates a Cosine Similarity Loss using an axis of {@link #DEFAULT_AXIS}, and a Loss Reduction + * of {@link #DEFAULT_REDUCTION} + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + */ + public CosineSimilarity(Ops tf, String name) { + + this(tf, name, DEFAULT_AXIS, DEFAULT_REDUCTION); + } + + /** + * Creates a Cosine Similarity Loss using {@link Class#getSimpleName()} as the loss name, and a + * Loss Reduction of {@link #DEFAULT_REDUCTION} + * + * @param tf the TensorFlow Ops + * @param axis The dimension along which the cosine similarity is computed. + */ + public CosineSimilarity(Ops tf, int axis) { + + this(tf, null, axis, DEFAULT_REDUCTION); + } + + /** + * Creates a Cosine Similarity Loss using a Loss Reduction of {@link #DEFAULT_REDUCTION} + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param axis The dimension along which the cosine similarity is computed. + */ + public CosineSimilarity(Ops tf, String name, int axis) { + + this(tf, name, axis, DEFAULT_REDUCTION); + } + + /** + * Creates a Cosine Similarity Loss using {@link Class#getSimpleName()} as the loss name and an + * axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public CosineSimilarity(Ops tf, Reduction reduction) { + + this(tf, null, DEFAULT_AXIS, reduction); + } + + /** + * Creates a Cosine Similarity Loss using an axis of {@link #DEFAULT_AXIS} + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public CosineSimilarity(Ops tf, String name, Reduction reduction) { + + this(tf, name, DEFAULT_AXIS, reduction); + } + + /** + * Creates a Cosine Similarity Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param axis The dimension along which the cosine similarity is computed. + * @param reduction Type of Reduction to apply to the loss. + */ + public CosineSimilarity(Ops tf, int axis, Reduction reduction) { + + this(tf, null, axis, reduction); + } + + /** + * Creates a Cosine Similarity Loss + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param axis The dimension along which the cosine similarity is computed. + * @param reduction Type of Reduction to apply to the loss. + */ + public CosineSimilarity(Ops tf, String name, int axis, Reduction reduction) { + super(tf, name, reduction); + this.axis = axis; + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.cosineSimilarity(getTF(), labels, predictions, axis); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Hinge.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Hinge.java new file mode 100644 index 00000000000..5fdfd4c9b96 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Hinge.java @@ -0,0 +1,140 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; +import static org.tensorflow.framework.utils.CastHelper.cast; + +/** + * Computes the hinge loss between labels and predictions. + * + * <p><code>loss = maximum(1 - labels * predictions, 0)</code></p>. + * + * <p><code>labels/code> values are expected to be -1 or 1. + * If binary (0 or 1) labels are provided, they will be converted to -1 or 1.</p> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.6f, 0.4f}, {0.4f, 0.6f}}); + * Hinge hingeLoss = new Hinge(tf); + * Operand<TFloat32> result = hingeLoss.call(labels, predictions); + * // produces 1.3f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {1.f, 0.f}); + * Operand<TFloat32> result = hingeLoss.call(labels, predictions, sampleWeight); + * // produces 0.55f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * Hinge hingeLoss = new Hinge(tf, Reduction.SUM); + * Operand<TFloat32> result = hingeLoss.call(labels, predictions); + * // produces 2.6f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * Hinge hingeLoss = new Hinge(tf, Reduction.NONE); + * Operand<TFloat32> result = hingeLoss.call(labels, predictions); + * // produces [1.1f, 1.5f] + * </pre> + */ +public class Hinge extends Loss { + + /** + * Creates a Hinge Loss using {@link Class#getSimpleName()} as the loss name and a Loss Reduction + * of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public Hinge(Ops tf) { + this(tf, null, Reduction.AUTO); + } + + /** + * Creates a Hinge Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public Hinge(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a Hinge + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public Hinge(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** + * Generates an Operand that calculates the loss. + * + * <p>If run in Graph mode, the computation will throw {@link + * org.tensorflow.exceptions.TFInvalidArgumentException} if the label values are not in the set + * [-1., 0., 1.]. In Eager Mode, this call will throw {@link IllegalArgumentException}, if the + * label values are not in the set [-1., 0., 1.]. + * + * @param labels the truth values or labels, must be either -1, 0, or 1. Values are expected to be + * -1 or 1. If binary (0 or 1) labels are provided they will be converted to -1 or 1. + * @param predictions the predictions, values must be in the range [0. to 1.] inclusive. + * @param sampleWeights Optional sampleWeights acts as a coefficient for the loss. If a scalar is + * provided, then the loss is simply scaled by the given value. If sampleWeights is a tensor + * of size [batch_size], then the total loss for each sample of the batch is rescaled by the + * corresponding element in the SampleWeights vector. If the shape of SampleWeights is + * [batch_size, d0, .. dN-1] (or can be broadcast to this shape), then each loss element of + * predictions is scaled by the corresponding value of SampleWeights. (Note on dN-1: all loss + * functions reduce by 1 dimension, usually axis=-1.) + * @param <T> The data type of the predictions, sampleWeights and loss. + * @param <U> The data type of the labels. + * @return the loss + * @throws IllegalArgumentException if the predictions are outside the range [0.-1.]. + */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + @SuppressWarnings("unchecked") + Operand<T> tLabels = predictions.asOutput().dataType() == labels.asOutput().dataType() ? + (Operand<T>)labels : + cast(tf, labels, predictions.asOutput().dataType()); + tLabels = LossesHelper.valueCheck( + getTF(), + "labels value check [-1, 0, 1]", + tLabels, + cast(getTF(), getTF().constant(new int[] { -1, 0, 1}), + predictions.asOutput().dataType())); + + Operand<T> losses = Losses.hinge(getTF(), tLabels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Huber.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Huber.java new file mode 100644 index 00000000000..6d3e3f0c2ac --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Huber.java @@ -0,0 +1,138 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the Huber loss between labels and predictions. + * + * <p>For each value x in <code>error = labels - predictions</code>: + * + * <pre> + * loss = 0.5 * x^2 if |x| <= d + * loss = 0.5 * d^2 + d * (|x| - d) if |x| > d + * </pre> + * + * <p>where d is delta. + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.6f, 0.4f}, {0.4f, 0.6f}}); + * Huber huberLoss = new Huber(tf); + * Operand<TFloat32> result = huberLoss.call(labels, predictions); + * // produces 0.155 + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {1.f, 0.f}); + * Operand<TFloat32> result = huberLoss.call(labels, predictions, sampleWeight); + * // produces 0.09f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * Huber huberLoss = new Huber(tf, Reduction.SUM); + * Operand<TFloat32> result = huberLoss.call(labels, predictions); + * // produces 0.32f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * Huber huberLoss = new Huber(tf, Reduction.NONE); + * Operand<TFloat32> result = huberLoss.call(labels, predictions); + * // produces [0.18f, 0.13f] + * </pre> + * + * @see <a href="https://en.wikipedia.org/wiki/Huber_loss">Huber loss</a> + */ +public class Huber extends Loss { + public static final float DELTA_DEFAULT = 1.0f; + + private final float delta; + + /** + * Creates a Huber Loss using {@link Class#getSimpleName()} as the loss name, {@link + * #DELTA_DEFAULT} as the delta and a Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public Huber(Ops tf) { + this(tf, null, DELTA_DEFAULT, Reduction.AUTO); + } + + /** + * Creates a Huber Loss using {@link #DELTA_DEFAULT} as the delta and a Loss Reduction of {@link + * Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public Huber(Ops tf, String name) { + this(tf, name, DELTA_DEFAULT, Reduction.AUTO); + } + + /** + * Creates a Huber Loss using {@link Class#getSimpleName()} as the loss name and and {@link + * #DELTA_DEFAULT} as the delta + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public Huber(Ops tf, Reduction reduction) { + this(tf, null, DELTA_DEFAULT, reduction); + } + + /** + * Creates a Huber Loss using {@link #DELTA_DEFAULT} as the delta + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public Huber(Ops tf, String name, Reduction reduction) { + this(tf, name, DELTA_DEFAULT, reduction); + } + + /** + * Creates a Huber Loss + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param delta the point where the Huber loss function changes from quadratic to linear. + * @param reduction Type of Reduction to apply to the loss. + */ + public Huber(Ops tf, String name, float delta, Reduction reduction) { + super(tf, name, reduction); + this.delta = delta; + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.huber(getTF(), labels, predictions, delta); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/KLDivergence.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/KLDivergence.java new file mode 100644 index 00000000000..8cf3db8d518 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/KLDivergence.java @@ -0,0 +1,107 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes Kullback-Leibler divergence loss between labels and predictions. + * + * <p><code>loss = labels * log(labels / predictions)</code> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.6f, 0.4f}, {0.4f, 0.6f}}); + * KLDivergence kld = new KLDivergence(tf); + * Operand<TFloat32> result = kld.call(labels, predictions); + * // produces 0.458 + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.8f, 0.2f}); + * Operand<TFloat32> result = kld.call(labels, predictions, sampleWeight); + * // produces 0.366f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * KLDivergence kld = new KLDivergence(tf, Reduction.SUM); + * Operand<TFloat32> result = kld.call(labels, predictions); + * // produces 0.916f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * KLDivergence kld = new KLDivergence(tf, Reduction.NONE); + * Operand<TFloat32> result = kld.call(labels, predictions); + * // produces [0.916f, -3.08e-06f] + * </pre> + * + * @see <a href="https://en.wikipedia.org/wiki/Kullback?Leibler_divergence">Kullback?Leibler + * divergence</a> + */ +public class KLDivergence extends Loss { + + /** + * Creates a Kullback Leibler Divergence Loss using {@link Class#getSimpleName()} as the loss name + * and a Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public KLDivergence(Ops tf) { + super(tf); + } + + /** + * Creates a Kullback Leibler Divergence Loss Loss using {@link Class#getSimpleName()} as the loss + * name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public KLDivergence(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a Kullback Leibler Divergence Loss + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public KLDivergence(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.kullbackLeiblerDivergence(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/LogCosh.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/LogCosh.java new file mode 100644 index 00000000000..1669669a768 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/LogCosh.java @@ -0,0 +1,113 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes Computes the logarithm of the hyperbolic cosine of the prediction error. + * + * <p><code>logcosh = log((exp(x) + exp(-x))/2)</code>, where <code>x</code> is the error <code> + * predictions - labels</code>. + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{1.f, 1.f}, {0.f, 0.f}}); + * LogCosh logcosh = new LogCosh(tf); + * Operand<TFloat32> result = logcosh.call(labels, predictions); + * // produces 0.108 + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.8f, 0.2f}); + * Operand<TFloat32> result = logcosh.call(labels, predictions, sampleWeight); + * // produces 0.087f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * LogCosh logcosh = new LogCosh(tf, Reduction.SUM); + * Operand<TFloat32> result = logcosh.call(labels, predictions); + * // produces 0.217f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * LogCosh logcosh = new LogCosh(tf, Reduction.NONE); + * Operand<TFloat32> result = logcosh.call(labels, predictions); + * // produces [0.217f, 0f] + * </pre> + */ +public class LogCosh extends Loss { + + /** + * Creates a LogCosh Loss using {@link Class#getSimpleName()} as the loss name and a Loss + * Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public LogCosh(Ops tf) { + this(tf, null, Reduction.AUTO); + } + + /** + * Creates a LogCosh Loss using a Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public LogCosh(Ops tf, String name) { + this(tf, name, Reduction.AUTO); + } + + /** + * Creates a LogCosh Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public LogCosh(Ops tf, Reduction reduction) { + this(tf, null, reduction); + } + + /** + * Creates a LogCosh Loss + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public LogCosh(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.logCosh(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Loss.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Loss.java new file mode 100644 index 00000000000..ae33d5dfa37 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Loss.java @@ -0,0 +1,108 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +public abstract class Loss { + public static final Reduction REDUCTION_DEFAULT = Reduction.AUTO; + + protected final Ops tf; + protected final Reduction reduction; + + /** + * Creates a Loss using {@link Class#getSimpleName()} as the name and a Loss Reduction of {@link + * Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + protected Loss(Ops tf) { + this(tf, null, Reduction.AUTO); + } + + /** + * Creates a Loss using a Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + * @param name the name of this Loss, if null the name will be {@link Class#getSimpleName()}. + */ + protected Loss(Ops tf, String name) { + this(tf, name, Reduction.AUTO); + } + + /** + * Creates a Loss + * + * @param tf the TensorFlow Ops + * @param name the name of this loss, if null the name will be {@link Class#getSimpleName()}. + * @param reduction Type of Reduction to apply to the loss. + */ + protected Loss(Ops tf, String name, Reduction reduction) { + this.tf = name != null ? tf.withSubScope(name) : tf.withSubScope(getClass().getSimpleName()); + this.reduction = reduction; + } + + /** + * Calculates the loss + * + * @param labels the truth values or labels + * @param predictions the predictions + * @param <T> The data type of the predictions and loss. + * @param <U> The data type of the labels. + * @return the loss + */ + public <T extends TNumber, U extends TNumber> Operand<T> call(Operand<U> labels, Operand<T> predictions) { + return call(labels, predictions, null); + } + + /** + * Generates an Operand that calculates the loss. + * + * @param labels the truth values or labels + * @param predictions the predictions + * @param sampleWeights Optional sampleWeights acts as a coefficient for the loss. If a scalar is + * provided, then the loss is simply scaled by the given value. If SampleWeights is a tensor + * of size [batch_size], then the total loss for each sample of the batch is rescaled by the + * corresponding element in the SampleWeights vector. If the shape of SampleWeights is + * [batch_size, d0, .. dN-1] (or can be broadcast to this shape), then each loss element of + * predictions is scaled by the corresponding value of SampleWeights. (Note on dN-1: all loss + * functions reduce by 1 dimension, usually axis=-1.) + * @param <T> The data type of the predictions, sampleWeights and loss. + * @param <U> The data type of the labels. + * @return the loss + */ + public abstract <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights); + + /** + * Gets the TensorFlow Ops + * + * @return the TensorFlow Ops + */ + public Ops getTF() { + return tf; + } + + /** + * Gets the loss reduction + * + * @return the loss reduction + */ + public Reduction getReduction() { + return reduction; + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Losses.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Losses.java new file mode 100644 index 00000000000..7a633ede2bf --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Losses.java @@ -0,0 +1,704 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.DataType; +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossTuple; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.op.core.ReduceAll; +import org.tensorflow.op.core.ReduceMax; +import org.tensorflow.op.core.ReduceSum; +import org.tensorflow.op.math.Mean; +import org.tensorflow.op.math.Softplus; +import org.tensorflow.types.TBool; +import org.tensorflow.types.TInt64; +import org.tensorflow.types.family.TNumber; + +import static org.tensorflow.framework.utils.CastHelper.cast; + +/** Built-in loss functions. */ +public class Losses { + + /** Default Fuzz factor. */ + public static final float EPSILON = 1e-7f; + + /** + * Calculates the mean absolute error between labels and predictions. + * + * <p><code>loss = reduceMean(abs(labels - predictions))</code> + * + * @param tf The TensorFlow Ops + * @param labels the labels + * @param predictions the predictions + * @param <T> the data type of the predictions and result + * @param <U> the data type of the labels + * @return the mean absolute error + */ + public static <T extends TNumber, U extends TNumber> Operand<T> meanAbsoluteError( + Ops tf, Operand<U> labels, Operand<T> predictions) { + Operand<T> tLabels = cast(tf, labels, predictions.asOutput().dataType()); + LossTuple<T> ops = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = ops.getTarget(); + tLabels = ops.getLabels(); + return tf.math.mean( + tf.math.abs(tf.math.sub(tLabels, predictions)), tf.constant(-1), Mean.keepDims(false)); + } + + /** + * Computes the mean squared error between labels and predictions. + * + * <p><code>loss = reduceMean(square(labels - predictions))</code> + * + * @param tf The TensorFlow Ops + * @param labels the labels + * @param predictions the predictions + * @param <T> the data type of the predictions and result + * @param <U> the data type of the labels + * @return the mean squared error + */ + public static <T extends TNumber, U extends TNumber> Operand<T> meanSquaredError( + Ops tf, Operand<U> labels, Operand<T> predictions) { + Operand<T> tLabels = cast(tf, labels, predictions.asOutput().dataType()); + LossTuple<T> ops = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = ops.getTarget(); + tLabels = ops.getLabels(); + return tf.math.mean(tf.math.squaredDifference(predictions, tLabels), tf.constant(-1)); + } + + /** + * Calculates the mean absolute percentage error between labels and predictions. + * + * <p><code>loss = 100 * reduceMean(abs((labels - predictions) / labels))</code> + * + * @param tf The TensorFlow Ops + * @param labels the labels + * @param predictions the predictions + * @param <T> the data type of the predictions and result + * @param <U> the data type of the labels + * @return the mean absolute percentage error + */ + public static <T extends TNumber, U extends TNumber> Operand<T> meanAbsolutePercentageError( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> ops = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = ops.getTarget(); + tLabels = ops.getLabels(); + Operand<T> diff = + tf.math.abs( + tf.math.div( + tf.math.sub(tLabels, predictions), + tf.math.maximum(tf.math.abs(tLabels), cast(tf, tf.constant(EPSILON), dataType)))); + return tf.math.mul(cast(tf, tf.constant(100), dataType), tf.math.mean(diff, tf.constant(-1))); + } + + /** + * Calculates the mean squared logarithmic error between labels and predictions. + * + * <p><code>loss = reduceMean(square(log(labels + 1) - log(predictions + 1)))</code> + * + * @param tf The TensorFlow Ops + * @param labels the labels + * @param predictions the predictions + * @param <T> the data type of the predictions and result + * @param <U> the data type of the labels + * @return the mean squared logarithmic percentage error + */ + public static <T extends TNumber, U extends TNumber> Operand<T> meanSquaredLogarithmicError( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> ops = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = ops.getTarget(); + tLabels = ops.getLabels(); + + Operand<T> epsilonConst = cast(tf, tf.constant(EPSILON), dataType); + Operand<T> one = cast(tf, tf.constant(1), dataType); + + Operand<T> firstLog = tf.math.log(tf.math.add(tf.math.maximum(predictions, epsilonConst), one)); + Operand<T> secondLog = tf.math.log(tf.math.add(tf.math.maximum(tLabels, epsilonConst), one)); + + return tf.math.mean(tf.math.squaredDifference(firstLog, secondLog), tf.constant(-1)); + } + + /** + * Computes the binary crossentropy loss between labels and predictions. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing A number in the range [0, 1]. When 0, no smoothing occurs. When > 0, + * compute the loss between the predicted labels and a smoothed version of the true labels, + * where the smoothing squeezes the labels towards 0.5. Larger values of labelSmoothing + * correspond to heavier smoothing. + * @param <T> the data type of the predictions and labels + * @return the binary crossentropy loss. + */ + public static <T extends TNumber, U extends TNumber> Operand<T> binaryCrossentropy( + Ops tf, Operand<U> labels, Operand<T> predictions, boolean fromLogits, float labelSmoothing) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> ops = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = ops.getTarget(); + tLabels = ops.getLabels(); + + if (labelSmoothing != 0.0f) { + tLabels = smoothBinaryLabels(tf, tLabels, labelSmoothing); + } + Operand<T> bce = binaryCrossentropyHelper(tf, tLabels, predictions, fromLogits); + return tf.math.mean(bce, tf.constant(-1)); + } + + /** + * Computes the unreduced crossentropy loss between labels and predictions. + * + * @param tf the TensorFlow Ops + * @param target the target Operand + * @param output the output, either logits or a probability distribution + * @param fromLogits whether `output` is expected to be a logits tensor. By default, we consider + * that `output` encodes a probability distribution. + * @param <T> the data type of the Operands + * @return the binary crossentropy loss. + */ + private static <T extends TNumber> Operand<T> binaryCrossentropyHelper( + Ops tf, Operand<T> target, Operand<T> output, boolean fromLogits) { + if (fromLogits) return tf.nn.sigmoidCrossEntropyWithLogits(target, output); + + /* TODO - skip this loggic for now. It requires walking back the inputs which is not yet possible + if (!(output instanceof Variable) && (!tf.scope().env().isEager())) { + // TODO - this does not work + // TODO output = backtrackIdentity(output); + // TODO if (output.op().type().equals(Sigmoid.OP_NAME)) { + // TODO if (output.op().numInputess() != 1) + // TODO throw new IllegalArgumentException("output can only have 1 output"); + // TODO output = output.op().inout(0); + // TODO return tf.nn.sigmoidCrossEntropyWithLogits(target, output); + // TODO} + } + */ + + DataType<T> dataType = output.asOutput().dataType(); + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> epsilonConst = cast(tf, tf.constant(EPSILON), dataType); + Operand<T> oneMinusEpsilonConst = tf.math.sub(one, epsilonConst); + output = tf.clipByValue(output, epsilonConst, oneMinusEpsilonConst); + + // Compute cross entropy from probabilities. + Operand<T> bce = tf.math.mul(target, tf.math.log(tf.math.add(output, epsilonConst))); + bce = + tf.math.add( + bce, + tf.math.mul( + tf.math.sub(one, target), + tf.math.log(tf.math.add(tf.math.sub(one, output), epsilonConst)))); + return tf.math.neg(bce); + } + + /** + * Computes the categorical crossentropy loss between labels and predictions. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param labelSmoothing Float in <code>[0, 1]</code>. When <code>> 0</code>, label values are smoothed, meaning the + * confidence on label values are relaxed. e.g. <code>labelSmoothing=0.2<code> means that we will use a + * value of </code>0.1<code> for label </code>0<code> and </code>0.9<code> for label </code>1<code> + * @param axis the + * @param <T> the data type of the predictions and labels + * @return the categorical crossentropy loss. + */ + public static <T extends TNumber, U extends TNumber> Operand<T> categoricalCrossentropy( + Ops tf, + Operand<U> labels, + Operand<T> predictions, + boolean fromLogits, + float labelSmoothing, + int axis) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> ops = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = ops.getTarget(); + tLabels = ops.getLabels(); + + if (labelSmoothing != 0.0f) { + tLabels = smoothCategoricalLabels(tf, tLabels, labelSmoothing); + } + if (fromLogits) { + return tf.nn.softmaxCrossEntropyWithLogits(tLabels, predictions, -1); + } + /* TODO + if (!(predictions instanceof Variable) && (!tf.scope().env().isEager())) { + + // TODO output = backtrackIdentity(output); doesn't seem to work with Java version. + if (predictions.op().type().equals("Softmax")) { + if (predictions.op().numOutputs() != 1) + throw new IllegalArgumentException("output can only have 1 output"); + predictions = predictions.op().output(0); + return tf.nn.softmaxCrossEntropyWithLogits(tLabels, predictions, -1); + } + } + */ + + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> epsilonConst = cast(tf, tf.constant(EPSILON), dataType); + Operand<T> oneMinusEpsilonConst = tf.math.sub(one, epsilonConst); + predictions = + tf.math.div( + predictions, tf.reduceSum(predictions, tf.constant(axis), ReduceSum.keepDims(true))); + predictions = tf.clipByValue(predictions, epsilonConst, oneMinusEpsilonConst); + + // Compute cross entropy from probabilities. + Operand<T> cce = + tf.reduceSum( + tf.math.mul(tLabels, tf.math.log(predictions)), + tf.constant(axis), + ReduceSum.keepDims(false)); + return tf.math.neg(cce); + } + + /** + * Computes the categorical hinge loss between labels and predictions. + * + * @param tf the TensorFlow Ops + * @param labels true targets, values are expected to be 0 or 1. + * @param predictions the predictions + * @param <T> the data type of the predictions and labels + * @return the categorical hinge loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> categoricalHinge( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> zero = cast(tf, tf.constant(0), dataType); + + Operand<T> pos = + tf.reduceSum( + tf.math.mul(tLabels, predictions), tf.constant(-1), ReduceSum.keepDims(Boolean.FALSE)); + Operand<T> neg = + tf.reduceMax( + tf.math.mul(tf.math.sub(one, tLabels), predictions), + tf.constant(-1), + ReduceMax.keepDims(Boolean.FALSE)); + Operand<T> sub = tf.math.sub(neg, pos); + Operand<T> add = tf.math.add(sub, one); + return tf.math.maximum(zero, add); + } + + /** + * Computes the cosine similarity loss between labels and predictions. + * + * <p>Note that it is a number between <code>-1</code> and <code>1</code>, which is different from + * the mathematical definition of cosine similarity where <code>1</code> represents similar + * vectors, and <code>0</code> represents dissimilar vectors. In this function, the numbers are + * inverted in a range of <code>-1</code> to <code>1</code>. When it is a negative number between + * <code>-1</code> and <code>0</code>, <code>0</code> indicates orthogonality and values closer to + * <code>-1</code> indicate greater similarity. The values closer to <code>1</code> indicate + * greater dissimilarity. This makes it usable as a loss function in a setting where you try to + * maximize the proximity between predictions and targets. If either labels or predictions is a + * zero vector, cosine similarity will be <code>0</code> regardless of the proximity between + * predictions and targets. + * + * <p><code>loss = -sum(l2Norm(labels) * l2Norm(predictions))</code> + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param axis Axis along which to determine similarity. + * @param <T> the data type of the predictions and labels + * @return the cosine similarity loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> cosineSimilarity( + Ops tf, Operand<U> labels, Operand<T> predictions, int axis) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + + tLabels = l2Normalize(tf, tLabels, axis); + predictions = l2Normalize(tf, predictions, axis); + Operand<T> mathMul = tf.math.mul(tLabels, predictions); + Operand<T> sum = tf.reduceSum(mathMul, tf.constant(axis), ReduceSum.keepDims(Boolean.FALSE)); + return tf.math.neg(sum); + } + + /** + * Computes the hinge loss between labels and predictions + * + * <p><code>loss = reduceMean(maximum(1 - labels * predictions, 0))</code> + * + * @param tf the TensorFlow Ops + * @param labels true targets, values are expected to be -1 or 1. If binary (0 or 1) labels are + * provided, they will be converted to -1 or 1. + * @param predictions the predictions + * @param <T> the data type of the predictions and labels + * @return the hinge loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> hinge( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> zero = cast(tf, tf.constant(0), dataType); + + tLabels = maybeConvertLabels(tf, tLabels); + + return tf.math.mean( + tf.math.maximum(tf.math.sub(one, tf.math.mul(tLabels, predictions)), zero), + tf.constant(-1)); + } + + /** + * Computes the Huber loss between labels and predictions. + * + * <p>For each value x in error = labels - predictions: + * + * <pre> + * loss = 0.5 * x^2 if |x| <= d + * loss = 0.5 * d^2 + d * (|x| - d) if |x| > d + * </pre> + * + * <p>where d is delta. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param delta the point where the Huber loss function changes from quadratic to linear. + * @param <T> the data type of the predictions and labels + * @return the Huber loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> huber( + Ops tf, Operand<U> labels, Operand<T> predictions, float delta) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + + Operand<T> error = tf.math.sub(predictions, tLabels); + Operand<T> deltaConst = cast(tf, tf.constant(delta), dataType); + Operand<T> point5 = cast(tf, tf.constant(0.5), dataType); + Operand<T> absError = tf.math.abs(error); + Operand<T> quadratic = tf.math.minimum(absError, deltaConst); + Operand<T> linear = tf.math.sub(absError, quadratic); + Operand<T> q2Point5 = tf.math.mul(point5, tf.math.mul(quadratic, quadratic)); + Operand<T> deltaLinear = tf.math.mul(deltaConst, linear); + Operand<T> loss = tf.math.add(q2Point5, deltaLinear); + return tf.math.mean(loss, tf.constant(-1)); + } + + /** + * Computes the Kullback-Leibler divergence loss between labels and predictions. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param <T> the data type of the predictions and labels + * @return the Kullback-Leibler divergence loss + * @see <a href="https://en.wikipedia.org/wiki/Kullback?Leibler_divergence">Kullback?Leibler + * divergence</a> + */ + public static <T extends TNumber, U extends TNumber> Operand<T> kullbackLeiblerDivergence( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> epsilonConst = cast(tf, tf.constant(EPSILON), dataType); + + tLabels = tf.clipByValue(tLabels, epsilonConst, one); + predictions = tf.clipByValue(predictions, epsilonConst, one); + return tf.reduceSum( + tf.math.mul(tLabels, tf.math.log(tf.math.div(tLabels, predictions))), tf.constant(-1)); + } + + /** + * Computes the hyperbolic cosine loss between labels and predictions. + * + * <p><code>log(cosh(x))</code> is approximately equal to <code>(x ** 2) / 2</code> for small + * <code>x</code> and to <code>abs(x) - log(2)</code> for large <code>x</code>. This means that + * 'logCosh' works mostly like the mean squared error, but will not be so strongly affected by the + * occasional wildly incorrect prediction. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param <T> the data type of the predictions and labels + * @return the hyperbolic cosine divergence loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> logCosh( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + Operand<T> minusTwo = cast(tf, tf.constant(-2), dataType); + Operand<T> two = cast(tf, tf.constant(2), dataType); + + Operand<T> diff = tf.math.sub(predictions, tLabels); + Softplus<T> softplus = tf.math.softplus(tf.math.mul(minusTwo, diff)); + Operand<T> logcosh = tf.math.sub(tf.math.add(diff, softplus), tf.math.log(two)); + return tf.math.mean(logcosh, tf.constant(-1)); + } + + /** + * Computes the Poisson loss between labels and predictions. + * + * <p>The Poisson loss is the mean of the elements of the Tensor <code> + * predictions - labels * log(predictions)</code>. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param <T> the data type of the predictions and labels + * @return the Poisson loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> poisson( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + Operand<T> epsilonConst = cast(tf, tf.constant(EPSILON), dataType); + + return tf.math.mean( + tf.math.sub( + predictions, tf.math.mul(tLabels, tf.math.log(tf.math.add(predictions, epsilonConst)))), + tf.constant(-1)); + } + + /** + * Computes the sparse categorical crossentropy loss between labels and predictions. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param predictions the predictions + * @param fromLogits Whether predictions is expected to be logits. By default, it is assumed that + * predictions encodes a probability distribution. + * @param axis The dimension along which the entropy is computed. + * @param <T> the data type of the predictions and labels + * @return the sparse categorical crossentropy loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> sparseCategoricalCrossentropy( + Ops tf, Operand<U> labels, Operand<T> predictions, boolean fromLogits, int axis) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> epsilonConst = cast(tf, tf.constant(EPSILON), dataType); + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> oneMinusEpsilonConst = tf.math.sub(one, epsilonConst); + + /* TODO need ability to walk back inputs + if (!fromLogits && !(predictions instanceof Variable) && (!tf.scope().env().isEager())) { + // TODO output = backtrackIdentity(output); doesn't seem to work with Java version. + /* TODO + if (predictions.op().type().equals(Softmax.OP_NAME)) { + // When softmax activation function is used for output operation, we + // use logits from the softmax function directly to compute loss in order + // to prevent collapsing zero when training. + // TODO if( output.op().numOutputs() != 1) + // throw new IllegalArgumentException("output can only have 1 output"); + // TODO output = output.op.inputs[0] + fromLogits = true; + } + + } + */ + if (!fromLogits) { + + predictions = tf.clipByValue(predictions, epsilonConst, oneMinusEpsilonConst); + predictions = tf.math.log(predictions); + } + Shape predictionsShape = predictions.asOutput().shape(); + int predictionsRank = predictionsShape.numDimensions(); + axis %= predictionsRank; + if (axis < 0) { + axis += predictionsRank; + } + if (axis != predictionsRank - 1) { + int[] axisNew = moveAxisToEnd(axis, predictionsRank); + predictions = tf.linalg.transpose(predictions, tf.constant(axisNew)); + } + + Operand<TInt64> iLabels = cast(tf, labels, TInt64.DTYPE); + + // Try to adjust the shape so that rank of labels = rank of logits - 1. + Shape labelsShape = labels.asOutput().shape(); + int labelsRank = labelsShape.numDimensions(); + + boolean updateShape = labelsRank != predictionsRank - 1; + if (updateShape) { // TODO check to see if this is right + Shape newShape = labelsShape.take(labelsRank - 1); + iLabels = tf.reshape(iLabels, tf.constant(newShape)); // flatten one dimension + predictions = + tf.reshape( + predictions, + tf.constant( + new long[] {-1L, predictionsShape.size(predictionsShape.numDimensions() - 1)})); + } + + @SuppressWarnings("unchecked") + Operand<T> loss = tf.nn.sparseSoftmaxCrossEntropyWithLogits(iLabels, predictions); + if (updateShape && predictionsRank >= 3) { + Shape newShape = predictionsShape.take(predictionsShape.numDimensions() - 1); + loss = tf.reshape(loss, tf.constant(newShape)); + } + return loss; + } + + /** + * Computes the squared hinge loss between labels and predictions. + * + * <p><code>loss = reduceMean(square(maximum(1 - labels * predictions, 0)))</code> + * + * @param tf the TensorFlow Ops + * @param labels true targets, values are expected to be -1 or 1. If binary (0 or 1) labels are * + * provided, they will be converted to -1 or 1. + * @param predictions the predictions + * @param <T> the data type of the predictions and labels + * @return the squared hinge loss + */ + public static <T extends TNumber, U extends TNumber> Operand<T> squaredHinge( + Ops tf, Operand<U> labels, Operand<T> predictions) { + DataType<T> dataType = predictions.asOutput().dataType(); + Operand<T> tLabels = cast(tf, labels, dataType); + LossTuple<T> lossTuple = LossesHelper.squeezeOrExpandDimensions(tf, tLabels, predictions, null); + predictions = lossTuple.getTarget(); + tLabels = lossTuple.getLabels(); + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> zero = cast(tf, tf.constant(0), dataType); + + tLabels = maybeConvertLabels(tf, tLabels); + return tf.math.mean( + tf.math.square(tf.math.maximum(tf.math.sub(one, tf.math.mul(tLabels, predictions)), zero)), + tf.constant(-1)); + } + + /** + * Smooths binary labels + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param labelSmoothing A number in the range [0, 1]. When 0, no smoothing occurs. When > 0, + * compute the loss between the predicted labels and a smoothed version of the true labels, + * where the smoothing squeezes the labels towards 0.5. Larger values of labelSmoothing + * correspond to heavier smoothing. + * @param <T> the data type of the labels + * @return the smoothed binary labels + */ + private static <T extends TNumber> Operand<T> smoothBinaryLabels( + Ops tf, Operand<T> labels, float labelSmoothing) { + DataType<T> dataType = labels.asOutput().dataType(); + Operand<T> oneMinusSmoothing = cast(tf, tf.constant(1.f - labelSmoothing), dataType); + Operand<T> halfSmoothing = cast(tf, tf.constant(0.5F * labelSmoothing), dataType); + return tf.math.add(tf.math.mul(labels, oneMinusSmoothing), halfSmoothing); + } + + /** + * Smooths categorical labels + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param labelSmoothing Float in <code>[0, 1]</code>. When <code>> 0</code>, label values are smoothed, meaning the + * confidence on label values are relaxed. e.g. <code>labelSmoothing=0.2<code> means that we will use a + * value of </code>0.1<code> for label </code>0<code> and </code>0.9<code> for label </code>1<code> + * @param <T> the data type of the labels + * @return the smoothed categorical labels + */ + private static <T extends TNumber> Operand<T> smoothCategoricalLabels( + Ops tf, Operand<T> labels, float labelSmoothing) { + DataType<T> dataType = labels.asOutput().dataType(); + Operand<T> smoothing = cast(tf, tf.constant(labelSmoothing), dataType); + Shape labelsShape = labels.asOutput().shape(); + int numDims = labelsShape.numDimensions(); + Operand<T> numClasses = cast(tf, tf.constant(labelsShape.size(numDims - 1)), dataType); + Operand<T> oneMinusSmoothing = cast(tf, tf.constant(1.f - labelSmoothing), dataType); + return tf.math.add(tf.math.mul(labels, oneMinusSmoothing), tf.math.div(smoothing, numClasses)); + } + + // TODO this was tf.math.l2_normalize in TF Python + /** + * Normalizes along dimension axis using an L2 norm. + * + * @param tf The TensorFlow Ops + * @param x the input + * @param axis Dimension along which to normalize. + * @return the normalized values based on L2 norm + */ + public static <T extends TNumber> Operand<T> l2Normalize(Ops tf, Operand<T> x, int axis) { + Operand<T> squareSum = + tf.reduceSum(tf.math.square(x), tf.constant(axis), ReduceSum.keepDims(Boolean.TRUE)); + Operand<T> invNorm = + tf.math.rsqrt( + tf.math.maximum(squareSum, cast(tf, tf.constant(1e-12F), x.asOutput().dataType()))); + return tf.math.mul(x, invNorm); + } + + /** + * Converts binary labels into -1/1. + * + * @param tf the TensorFlow Ops + * @param labels true targets + * @param <T> the data type of the labels + * @return the labels, possibly converted into -1/1. + */ + private static <T extends TNumber> Operand<T> maybeConvertLabels(Ops tf, Operand<T> labels) { + DataType<T> dataType = labels.asOutput().dataType(); + + Operand<T> one = cast(tf, tf.constant(1), dataType); + Operand<T> zero = cast(tf, tf.constant(0), dataType); + Operand<T> two = cast(tf, tf.constant(2), dataType); + Operand<TBool> areZeros = tf.math.equal(labels, zero); + Operand<TBool> areOnes = tf.math.equal(labels, one); + Operand<TBool> isBinary = + tf.reduceAll( + tf.math.logicalOr(areZeros, areOnes), tf.constant(-1), ReduceAll.keepDims(true)); + Operand<T> convertBinaryLabels = tf.math.sub(tf.math.mul(two, labels), one); + return tf.select(isBinary, convertBinaryLabels, labels); + } + + /** + * Move the specified axis to end, to be used with transposes + * + * @param axis the axis to move + * @param outputRank the rank of the shape + * @return the new dimension array with the axis moved to the end. + */ + private static int[] moveAxisToEnd(int axis, int outputRank) { + int[] axisNew = new int[outputRank]; + for (int i = 0; i < axis; i++) { + axisNew[i] = i; + } + for (int i = axis + 1; i < outputRank; i++) { + axisNew[i - 1] = i; + } + axisNew[outputRank - 1] = axis; + return axisNew; + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanAbsoluteError.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanAbsoluteError.java new file mode 100644 index 00000000000..a2d5d5f8efc --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanAbsoluteError.java @@ -0,0 +1,103 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the mean of absolute difference between labels and predictions. + * + * <p><code>loss = abs(labels - predictions)</code> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{1.f, 1.f}, {1.f, 0.f}}); + * MeanAbsoluteError mae = new MeanAbsoluteError(tf); + * Operand<TFloat32> result = mae.call(labels, predictions); + * // produces 0.5f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.7f, 0.3f}); + * Operand<TFloat32> result = mae.call(labels, predictions, sampleWeight); + * // produces 0.25f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * MeanAbsoluteError mae = new MeanAbsoluteError(tf, Reduction.SUM); + * Operand<TFloat32> result = mae.call(labels, predictions); + * // produces 1.0f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * MeanAbsoluteError mae = new MeanAbsoluteError(tf, Reduction.NONE); + * Operand<TFloat32> result = mae.call(labels, predictions); + * // produces [0.5f, 0.5f] + * </pre> + */ +public class MeanAbsoluteError extends Loss { + + /** + * Creates a MeanAbsoluteError Loss using {@link Class#getSimpleName()} as the loss name and a + * Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public MeanAbsoluteError(Ops tf) { + super(tf); + } + + /** + * Creates a MeanAbsoluteError Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanAbsoluteError(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a MeanAbsoluteError + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanAbsoluteError(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.meanAbsoluteError(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanAbsolutePercentageError.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanAbsolutePercentageError.java new file mode 100644 index 00000000000..49133df610b --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanAbsolutePercentageError.java @@ -0,0 +1,103 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the mean absolute percentage error between labels and predictions. + * + * <p><code>loss = 100 * abs(labels - predictions) / labels</code> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{2.f, 1.f}, {2.f, 3.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{1.f, 1.f}, {1.f, 0.f}}); + * MeanAbsolutePercentageError mape = new MeanAbsolutePercentageError(tf); + * Operand<TFloat32> result = mape.call(labels, predictions); + * // produces 50f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.7f, 0.3f}); + * Operand<TFloat32> result = mape.call(labels, predictions, sampleWeight); + * // produces 20f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * MeanAbsolutePercentageError mape = new MeanAbsolutePercentageError(tf, Reduction.SUM); + * Operand<TFloat32> result = mape.call(labels, predictions); + * // produces 100.0f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * MeanAbsolutePercentageError mape = new MeanAbsolutePercentageError(tf, Reduction.NONE); + * Operand<TFloat32> result = mape.call(labels, predictions); + * // produces [25f, 75f] + * </pre> + */ +public class MeanAbsolutePercentageError extends Loss { + + /** + * Creates a MeanAbsolutePercentageError Loss using {@link Class#getSimpleName()} as the loss name + * and a Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public MeanAbsolutePercentageError(Ops tf) { + super(tf); + } + + /** + * Creates a MeanAbsolutePercentageError Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanAbsolutePercentageError(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a MeanAbsolutePercentageError + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanAbsolutePercentageError(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.meanAbsolutePercentageError(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanSquaredError.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanSquaredError.java new file mode 100644 index 00000000000..2a6c2be885e --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanSquaredError.java @@ -0,0 +1,103 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the mean of squares of errors between labels and predictions. + * + * <p><code>loss = loss = square(labels - predictions)</code> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{1.f, 1.f}, {1.f, 0.f}}); + * MeanSquaredError mse = new MeanSquaredError(tf); + * Operand<TFloat32> result = mse.call(labels, predictions); + * // produces 0.5f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.7f, 0.3f}); + * Operand<TFloat32> result = mse.call(labels, predictions, sampleWeight); + * // produces 0.25f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * MeanSquaredError mse = new MeanSquaredError(tf, Reduction.SUM); + * Operand<TFloat32> result = mse.call(labels, predictions); + * // produces 1.0f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * MeanSquaredError mse = new MeanSquaredError(tf, Reduction.NONE); + * Operand<TFloat32> result = mse.call(labels, predictions); + * // produces [0.5f, 0.5f] + * </pre> + */ +public class MeanSquaredError extends Loss { + + /** + * Creates a MeanSquaredError Loss using {@link Class#getSimpleName()} as the loss name and a Loss + * Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public MeanSquaredError(Ops tf) { + super(tf); + } + + /** + * Creates a MeanSquaredError Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanSquaredError(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a MeanSquaredError + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanSquaredError(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.meanSquaredError(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanSquaredLogarithmicError.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanSquaredLogarithmicError.java new file mode 100644 index 00000000000..2604e226b81 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/MeanSquaredLogarithmicError.java @@ -0,0 +1,103 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the mean squared logarithmic errors between labels and predictions. + * + * <p><code>loss = square(log(labels + 1.) - log(predictions + 1.))</code> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{1.f, 1.f}, {1.f, 0.f}}); + * MeanSquaredLogarithmicError msle = new MeanSquaredLogarithmicError(tf); + * Operand<TFloat32> result = msle.call(labels, predictions); + * // produces 0.240f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.7f, 0.3f}); + * Operand<TFloat32> result = msle.call(labels, predictions, sampleWeight); + * // produces 0.120f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * MeanSquaredLogarithmicError msle = new MeanSquaredLogarithmicError(tf, Reduction.SUM); + * Operand<TFloat32> result = msle.call(labels, predictions); + * // produces 0.480f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * MeanSquaredLogarithmicError msle = new MeanSquaredLogarithmicError(tf, Reduction.NONE); + * Operand<TFloat32> result = msle.call(labels, predictions); + * // produces [0.240f, 0.240f] + * </pre> + */ +public class MeanSquaredLogarithmicError extends Loss { + + /** + * Creates a MeanSquaredError Loss using {@link Class#getSimpleName()} as the loss name and a Loss + * Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public MeanSquaredLogarithmicError(Ops tf) { + super(tf); + } + + /** + * Creates a MeanSquaredError Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanSquaredLogarithmicError(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a MeanSquaredError + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public MeanSquaredLogarithmicError(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.meanSquaredLogarithmicError(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Poisson.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Poisson.java new file mode 100644 index 00000000000..c43be4f2821 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Poisson.java @@ -0,0 +1,112 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; + +/** + * Computes the Poisson loss between labels and predictions. + * + * <p><code>loss = predictions - labels * log(predictions)</code> + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0.f, 1.f}, {0.f, 0.f}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{1.f, 1.f}, {0.f, 0.f}}); + * Poisson poissonLoss = new Poisson(tf); + * Operand<TFloat32> result = poissonLoss.call(labels, predictions); + * // produces 0.5f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.8f, 0.2f}); + * Operand<TFloat32> result = poissonLoss.call(labels, predictions, sampleWeight); + * // produces 0.4f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * Poisson poissonLoss = new Poisson(tf, Reduction.SUM); + * Operand<TFloat32> result = poissonLoss.call(labels, predictions); + * // produces 0.999f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * Poisson poissonLoss = new Poisson(tf, Reduction.NONE); + * Operand<TFloat32> result = poissonLoss.call(labels, predictions); + * // produces [0.999f, 0f] + * </pre> + */ +public class Poisson extends Loss { + + /** + * Creates a Poisson Loss using {@link Class#getSimpleName()} as the loss name and a Loss + * Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public Poisson(Ops tf) { + this(tf, null, Reduction.AUTO); + } + + /** + * Creates a Poisson Loss using a Loss Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public Poisson(Ops tf, String name) { + this(tf, name, Reduction.AUTO); + } + + /** + * Creates a Poisson Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public Poisson(Ops tf, Reduction reduction) { + this(tf, null, reduction); + } + + /** + * Creates a Poisson Loss + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public Poisson(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** {@inheritDoc} */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> losses = Losses.poisson(getTF(), labels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Reduction.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Reduction.java new file mode 100644 index 00000000000..87ea43c6c3a --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/Reduction.java @@ -0,0 +1,45 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +/** + * Type of Loss Reduction + * + * <p>{@link #AUTO} indicates that the reduction option will be determined by the usage context. For + * almost all cases this defaults to {@link #SUM_OVER_BATCH_SIZE}. + * + * <p>{@link #NONE} Weighted losses with one dimension reduced (axis=-1, or axis specified by loss + * function). + * + * <p>{@link #SUM} Scalar sum of weighted losses. + * + * <p>{@link #SUM_OVER_BATCH_SIZE} Scalar <code>SUM</code> divided by number of elements in losses. + */ +public enum Reduction { + AUTO, + NONE, + SUM, + SUM_OVER_BATCH_SIZE; + + /** + * Get the Reduction based on name + * + * @param name the name of the reduction + * @return the Reduction + */ + public static Reduction ofName(String name) { + return Reduction.valueOf(name.toUpperCase()); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/SparseCategoricalCrossentropy.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/SparseCategoricalCrossentropy.java new file mode 100644 index 00000000000..5586a4da889 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/SparseCategoricalCrossentropy.java @@ -0,0 +1,218 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; +import static org.tensorflow.framework.utils.CastHelper.cast; + +/** + * Computes the crossentropy loss between labels and predictions. + * + * <p>Use this crossentropy loss function when there are two or more label classes. The labels are + * expected to be provided as integers. If you want to provide labels using <code>one-hot</code> + * representation, please use {@link CategoricalCrossentropy} loss. There should be <code># classes + * </code> floating point values per feature for <code>predictions</code> and a single floating + * point value per feature for <code>label</code>. + * + * <p>In the snippet below, there is a single floating point value per example for <code>labels + * </code> and <code># classes</code> floating pointing values per example for <code>predictions + * </code>. The shape of <code>labels</code> is <code>[batch_size]</code> and the shape of <code> + * predictions</code> is <code>[batch_size, num_classes]</code>. + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[] {1, 2}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.05f, 0.95f, 0f}, {0.1f, 0.8f, 0.1f}}); + * SparseCategoricalCrossentropy sparseCCE = new SparseCategoricalCrossentropy(tf); + * Operand<TFloat32> result = sparseCCE.call(labels, predictions); + * // produces 1.177f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {0.3f, 0.7f}); + * Operand<TFloat32> result = sparseCCE.call(labels, predictions, sampleWeight); + * // produces 0.814f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * SparseCategoricalCrossentropy sparseCCE = new SparseCategoricalCrossentropy(tf, Reduction.SUM); + * Operand<TFloat32> result = sparseCCE.call(labels, predictions); + * // produces 2.354f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * SparseCategoricalCrossentropy sparseCCE = new SparseCategoricalCrossentropy(tf, Reduction.NONE); + * Operand<TFloat32> result = sparseCCE.call(labels, predictions); + * // produces [0.0513f, 2.303f] + * </pre> + */ +public class SparseCategoricalCrossentropy extends Loss { + public static final boolean FROM_LOGITS_DEFAULT = false; + public static final int AXIS_DEFAULT = -1; + + private final boolean fromLogits; + private final int axis; + + /** + * Creates a SparseCategoricalCrossentropy loss using {@link Class#getSimpleName()} as the loss + * name, a Loss Reduction of {@link Loss#REDUCTION_DEFAULT}, and fromLogits={@link #FROM_LOGITS_DEFAULT}. + * + * @param tf the TensorFlow Ops + */ + public SparseCategoricalCrossentropy(Ops tf) { + this(tf, null, FROM_LOGITS_DEFAULT, REDUCTION_DEFAULT, AXIS_DEFAULT); + } + + /** + * Creates a SparseCategoricalCrossentropy loss using a Loss Reduction of {@link Loss#REDUCTION_DEFAULT}, + * and fromLogits={@link #FROM_LOGITS_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param name the name of this loss function + */ + public SparseCategoricalCrossentropy(Ops tf, String name) { + this(tf, name, FROM_LOGITS_DEFAULT, REDUCTION_DEFAULT, AXIS_DEFAULT); + } + + /** + * Creates a SparseCategoricalCrossentropy loss using {@link Class#getSimpleName()} as the loss + * name, with Reduction.AUTO and fromLogits={@link #FROM_LOGITS_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to loss. + */ + public SparseCategoricalCrossentropy(Ops tf, Reduction reduction) { + this(tf, null, FROM_LOGITS_DEFAULT, reduction, AXIS_DEFAULT); + } + + /** + * Creates a SparseCategoricalCrossentropy loss with Reduction.AUTO and fromLogits={@link + * #FROM_LOGITS_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param name the name of this loss function + * @param reduction Type of Reduction to apply to loss. + */ + public SparseCategoricalCrossentropy(Ops tf, String name, Reduction reduction) { + this(tf, name, FROM_LOGITS_DEFAULT, reduction, AXIS_DEFAULT); + } + + /** + * Creates a SparseCategoricalCrossentropy using a Loss Reduction of {@link Loss#REDUCTION_DEFAULT}, and + * fromLogits={@link #FROM_LOGITS_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param name the name of this loss function + * @param fromLogits Whether to interpret predictions as a tensor of logit values + */ + public SparseCategoricalCrossentropy(Ops tf, String name, boolean fromLogits) { + this(tf, name, fromLogits, REDUCTION_DEFAULT, AXIS_DEFAULT); + } + + /** + * Creates a SparseCategoricalCrossentropy loss using {@link Class#getSimpleName()} as the loss + * name, a Loss Reduction of {@link Loss#REDUCTION_DEFAULT} and fromLogits={@link #FROM_LOGITS_DEFAULT}. + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + */ + public SparseCategoricalCrossentropy(Ops tf, boolean fromLogits) { + this(tf, null, fromLogits, REDUCTION_DEFAULT, AXIS_DEFAULT); + } + + /** + * Creates a SparseCategoricalCrossentropy loss using {@link Class#getSimpleName()} as the loss + * name, + * + * @param tf the TensorFlow Ops + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param reduction Type of Reduction to apply to loss. + */ + public SparseCategoricalCrossentropy(Ops tf, boolean fromLogits, Reduction reduction) { + this(tf, null, fromLogits, reduction, AXIS_DEFAULT); + } + + /** + * Creates a SparseCategoricalCrossentropy + * + * @param tf the TensorFlow Ops + * @param name the name of this loss function + * @param fromLogits Whether to interpret predictions as a tensor of logit values + * @param reduction Type of Reduction to apply to loss. + * @param axis The channels axis. <code>axis=-1</code> corresponds to data format `Channels Last' + * and <code>axis=1</code> corresponds to data format 'Channels First'. + */ + public SparseCategoricalCrossentropy( + Ops tf, String name, boolean fromLogits, Reduction reduction, int axis) { + super(tf, name, reduction); + this.fromLogits = fromLogits; + this.axis = axis; + } + + /** + * Generates an Operand the calculates the loss. + * + * If run in Graph mode, the computation will throw {@link org.tensorflow.exceptions.TFInvalidArgumentException} + * if the predictions values are outside the range o [0. to 1.]. In Eager Mode, this call + * will throw {@link IllegalArgumentException}, if the predictions values are outside the range o [0. to 1.] + * + * @param labels the truth values or labels + * @param predictions the predictions, values must be in the range [0. to 1.] inclusive. + * @param sampleWeights Optional SampleWeights acts as a coefficient for the loss. If a scalar is + * provided, then the loss is simply scaled by the given value. If SampleWeights is a tensor + * of size [batch_size], then the total loss for each sample of the batch is rescaled by the + * corresponding element in the SampleWeights vector. If the shape of SampleWeights is + * [batch_size, d0, .. dN-1] (or can be broadcast to this shape), then each loss element of + * predictions is scaled by the corresponding value of SampleWeights. (Note on dN-1: all loss + * functions reduce by 1 dimension, usually axis=-1.) + * @param <T> The data type of the predictions, sampleWeights and loss. + * @param <U> The data type of the labels. + * @return the loss + * @throws IllegalArgumentException if the predictions are outside the range [0.-1.]. + */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + Operand<T> lPredictions; + if (!fromLogits) { + // add predictions range check for 0 - 1 + lPredictions = + LossesHelper.rangeCheck( + getTF(), + "predictions range check [0-1]", + predictions, + cast(getTF(), getTF().constant(0), predictions.asOutput().dataType()), + cast(getTF(), getTF().constant(1), predictions.asOutput().dataType())); + + } else { + lPredictions = predictions; + } + Operand<T> losses = + Losses.sparseCategoricalCrossentropy(getTF(), labels, lPredictions, fromLogits, axis); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/SquaredHinge.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/SquaredHinge.java new file mode 100644 index 00000000000..182ce592e55 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/SquaredHinge.java @@ -0,0 +1,140 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.impl.LossesHelper; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TNumber; +import static org.tensorflow.framework.utils.CastHelper.cast; + +/** + * Computes the squared hinge loss between labels and predictions. + * + * <p><code>loss = square(maximum(1 - labels * predictions, 0))</code> + * + * <p><code>labels</code> values are expected to be -1 or 1. If binary (0 or 1) labels are provided, they will be + * converted to -1 or 1. + * + * <p>Standalone usage: + * + * <pre> + * Operand<TFloat32> labels = + * tf.constant(new float[][] {{0., 1.}, {0., 0.}}); + * Operand<TFloat32> predictions = + * tf.constant(new float[][] {{0.6f, 0.4f}, {0.4f, 0.6f}}); + * SquaredHinge squaredHinge = new SquaredHinge(tf); + * Operand<TFloat32> result = squaredHinge.call(labels, predictions); + * // produces 1.86f + * </pre> + * + * <p>Calling with sample weight: + * + * <pre> + * Operand<TFloat32> sampleWeight = tf.constant(new float[] {1.f, 0.f}); + * Operand<TFloat32> result = squaredHinge.call(labels, predictions, + * sampleWeight); + * // produces 0.73f + * </pre> + * + * <p>Using <code>SUM</code> reduction type: + * + * <pre> + * SquaredHinge squaredHinge = new SquaredHinge(tf, Reduction.SUM); + * Operand<TFloat32> result = squaredHinge.call(labels, predictions); + * // produces 3.72f + * </pre> + * + * <p>Using <code>NONE</code> reduction type: + * + * <pre> + * SquaredHinge squaredHinge = new SquaredHinge(tf, Reduction.NONE); + * Operand<TFloat32> result = squaredHinge.call(labels, predictions); + * // produces [1.46f, 2.26f] + * </pre> + */ +public class SquaredHinge extends Loss { + + /** + * Creates a Squared Hinge Loss using {@link Class#getSimpleName()} as the loss name and a Loss + * Reduction of {@link Loss#REDUCTION_DEFAULT} + * + * @param tf the TensorFlow Ops + */ + public SquaredHinge(Ops tf) { + super(tf); + } + + /** + * Creates a Squared Hinge Loss using {@link Class#getSimpleName()} as the loss name + * + * @param tf the TensorFlow Ops + * @param reduction Type of Reduction to apply to the loss. + */ + public SquaredHinge(Ops tf, Reduction reduction) { + super(tf, null, reduction); + } + + /** + * Creates a Squared Hinge + * + * @param tf the TensorFlow Ops + * @param name the name of the loss + * @param reduction Type of Reduction to apply to the loss. + */ + public SquaredHinge(Ops tf, String name, Reduction reduction) { + super(tf, name, reduction); + } + + /** + * Generates an Operand that calculates the loss. + * + * <p>If run in Graph mode, the computation will throw {@link + * org.tensorflow.exceptions.TFInvalidArgumentException} if the label values are not in the set + * [-1., 0., 1.]. In Eager Mode, this call will throw {@link IllegalArgumentException}, if the + * label values are not in the set [-1., 0., 1.]. + * + * @param labels the truth values or labels, must be either -1, 0, or 1. Values are expected to be + * -1 or 1. If binary (0 or 1) labels are provided they will be converted to -1 or 1. + * @param predictions the predictions, values must be in the range [0. to 1.] inclusive. + * @param sampleWeights Optional SampleWeights acts as a coefficient for the loss. If a scalar is + * provided, then the loss is simply scaled by the given value. If SampleWeights is a tensor + * of size [batch_size], then the total loss for each sample of the batch is rescaled by the + * corresponding element in the SampleWeights vector. If the shape of SampleWeights is + * [batch_size, d0, .. dN-1] (or can be broadcast to this shape), then each loss element of + * predictions is scaled by the corresponding value of SampleWeights. (Note on dN-1: all loss + * functions reduce by 1 dimension, usually axis=-1.) + * @param <T> The data type of the predictions, sampleWeights and loss. + * @param <U> The data type of the labels. + * @return the loss + * @throws IllegalArgumentException if the predictions are outside the range [0.-1.]. + */ + @Override + public <T extends TNumber, U extends TNumber> Operand<T> call( + Operand<U> labels, Operand<T> predictions, Operand<T> sampleWeights) { + @SuppressWarnings("unchecked") + Operand<T> tLabels = predictions.asOutput().dataType() == labels.asOutput().dataType() ? + (Operand<T>)labels : + cast(tf, labels, predictions.asOutput().dataType()); + tLabels = LossesHelper.valueCheck( + getTF(), + "labels value check [-1, 0, 1]", + tLabels, + cast(getTF(), getTF().constant(new int[] { -1, 0, 1}), + predictions.asOutput().dataType())); + Operand<T> losses = Losses.squaredHinge(getTF(), tLabels, predictions); + return LossesHelper.computeWeightedLoss(getTF(), losses, getReduction(), sampleWeights); + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/impl/LossTuple.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/impl/LossTuple.java new file mode 100644 index 00000000000..2104937a979 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/impl/LossTuple.java @@ -0,0 +1,67 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses.impl; + +import org.tensorflow.Operand; +import org.tensorflow.types.family.TNumber; + +/** + * A helper class for loss methods to return labels, target, and sampleWeights + * + * @param <T> the data type of the LossTuple entries. + */ +public class LossTuple<T extends TNumber> { + private final Operand<T> labels; + private final Operand<T> target; + private final Operand<T> sampleWeights; + + /** + * Creates a LossTuple of Operands for labels, target, and sampleWeights + * + * @param labels the labels + * @param target the losses or target + */ + public LossTuple(Operand<T> labels, Operand<T> target) { + this(labels, target, null); + } + + /** + * Creates a LossTuple of Operands for labels, target, and sampleWeights + * + * @param labels the labels + * @param target the losses or target + * @param sampleWeights the sample weights + */ + public LossTuple(Operand<T> labels, Operand<T> target, Operand<T> sampleWeights) { + this.labels = labels; + this.target = target; + this.sampleWeights = sampleWeights; + } + + /** @return the labels */ + public Operand<T> getLabels() { + return labels; + } + + /** @return the target */ + public Operand<T> getTarget() { + return target; + } + + /** @return the sampleWeights */ + public Operand<T> getSampleWeights() { + return sampleWeights; + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/impl/LossesHelper.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/impl/LossesHelper.java new file mode 100644 index 00000000000..463296a1f50 --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/losses/impl/LossesHelper.java @@ -0,0 +1,417 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses.impl; + +import org.tensorflow.DataType; +import org.tensorflow.Operand; +import org.tensorflow.framework.losses.Reduction; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.op.core.AssertThat; +import org.tensorflow.op.core.ReduceSum; +import org.tensorflow.op.core.SetDiff1d; +import org.tensorflow.op.core.Squeeze; +import org.tensorflow.types.TBool; +import org.tensorflow.types.TInt32; +import org.tensorflow.types.family.TNumber; + +import java.util.Arrays; +import java.util.Collections; + +import static org.tensorflow.framework.utils.CastHelper.cast; + +/** + * These are helper methods for Losses and Metrics and will be module private when Java modularity is applied to + * TensorFlow Java. These methods should not be used outside of the losses and metrics packages. + */ +public class LossesHelper { + + /** + * Squeeze or expand last dimension if needed with a sampleWeights of one. + * + * <ol type="1"> + * <li>Squeezes last dim of <code>predictions</code> or <code>labels</code> if their rank + * differs by 1 (using {@link #removeSqueezableDimensions}). + * <li>Squeezes or expands last dim of <code>sampleWeight</code> if its rank differs by 1 from + * the new rank of <code>predictions</code>. If <code>sampleWeight</code> is scalar, it is + * kept scalar./li> + * </ol> + * + * @param tf the TensorFlow Ops + * @param predictions Predicted values, a <code>Operand</code> of arbitrary dimensions. + * @param labels Optional label <code>Operand</code> whose dimensions match <code>prediction + * </code>. + * @return LossTuple of <code>prediction</code>, <code>label</code>,<code>sampleWeight</code> will + * be null. Each of them possibly has the last dimension squeezed, <code>sampleWeight</code> + * could be extended by one dimension. If <code>sampleWeight</code> is null, (prediction, + * label) is returned. + */ + public static <T extends TNumber> LossTuple<T> squeezeOrExpandDimensions( + Ops tf, Operand<T> labels, Operand<T> predictions) { + return squeezeOrExpandDimensions(tf, labels, predictions, null); + } + + /** + * Squeeze or expand last dimension if needed. + * + * <ol type="1"> + * <li>Squeezes last dim of <code>predictions</code> or <code>labels</code> if their rank do not + * differ by 1. + * <li>Squeezes or expands last dim of <code>sampleWeight</code> if its rank differs by 1 from + * the new rank of <code>predictions</code>. If <code>sampleWeight</code> is scalar, it is + * kept scalar. + * </ol> + * + * @param tf the TensorFlow Ops + * @param predictions Predicted values, a <code>Operand</code> of arbitrary dimensions. + * @param labels Optional label <code>Operand</code> whose dimensions match <code>prediction + * </code>. + * @param sampleWeights Optional sample weight(s) <code>Operand</code> whose dimensions match<code> + * prediction</code>. + * @return LossTuple of <code>prediction<s/code>, <code>labels</code> and <code>sampleWeight</code>. + * Each of them possibly has the last dimension squeezed, <code>sampleWeight</code> could be + * extended by one dimension. If <code>sampleWeight</code> is null, only the possibly shape modified <code>predictions</code> and <code>labels</code> are + * returned. + */ + public static <T extends TNumber> LossTuple<T> squeezeOrExpandDimensions( + Ops tf, Operand<T> labels, Operand<T> predictions, Operand<T> sampleWeights) { + + Shape predictionsShape = predictions.asOutput().shape(); + long predictionsRank = predictionsShape.numDimensions(); + + // Default case when no modifications are made. + LossTuple<T> lossTuple = new LossTuple<>(labels, predictions, sampleWeights); + if (labels != null) { + Shape labelsShape = labels.asOutput().shape(); + long labelsRank = labelsShape.numDimensions(); + if (labelsRank != Shape.UNKNOWN_SIZE && predictionsRank != Shape.UNKNOWN_SIZE) { + // Use static rank for 'label' and 'prediction'. + if (predictionsRank - labelsRank != 1 || predictionsShape.size(-1) == 1) { + lossTuple = removeSqueezableDimensions(tf, labels, predictions); + } + } else { // use dynamic rank + lossTuple = removeSqueezableDimensions(tf, labels, predictions); + } + } + if (sampleWeights == null) { // nothing more to do. + return lossTuple; + } + Shape weightsShape = sampleWeights.asOutput().shape(); + long weightsRank = weightsShape.numDimensions(); + if (weightsRank == 0) { // scalar + return new LossTuple<>(lossTuple.getLabels(), lossTuple.getTarget(), sampleWeights); + } + + if (predictionsRank != Shape.UNKNOWN_SIZE && weightsRank != Shape.UNKNOWN_SIZE) { + + if (weightsRank - predictionsRank == 1) { + sampleWeights = tf.squeeze(sampleWeights); + } else if (predictionsRank - weightsRank == 1) { + sampleWeights = tf.expandDims(sampleWeights, tf.constant(-1L)); + } + return new LossTuple<>(lossTuple.getLabels(), lossTuple.getTarget(), sampleWeights); + } + // Use dynamic rank. + Operand<TInt32> weightsRankTensor = tf.rank(sampleWeights); + Operand<TInt32> rankDiff = tf.math.sub(weightsRankTensor, tf.rank(predictions)); + sampleWeights = + tf.select( + tf.math.equal(weightsRankTensor, tf.constant(0)), + sampleWeights, + maybeAdjustWeights(tf, sampleWeights, rankDiff)); + return new LossTuple<>(lossTuple.getLabels(), lossTuple.getTarget(), sampleWeights); + } + + /** + * Squeeze or expand the sampleWeight based on the rank difference + * + * <p>If the rank difference is +1, squeeze the last dimension of sampleWeight, If the rank + * difference is -1, expand the last dimension of sampleWeight. Otherwise, leave the shape of + * sampleWeight as is. + * + * @param tf the TensorFlow Ops + * @param sampleWeight the sample weights + * @param rankDiff the difference in rank + * @param <T> the data type for the Operands. + * @return the adjusted sampleWeight + */ + private static <T extends TNumber> Operand<T> maybeAdjustWeights( + Ops tf, Operand<T> sampleWeight, Operand<TInt32> rankDiff) { + return tf.select( + tf.math.equal(rankDiff, tf.constant(1)), + tf.squeeze(sampleWeight, Squeeze.axis(Collections.singletonList(-1L))), + maybeExpandWeights(tf, sampleWeight, rankDiff)); + } + + /** + * Expand the last dimension of sampleWeight. if the rank difference is -1. + * + * @param tf the TensorFlow Ops + * @param sampleWeight the sample weights + * @param rankDiff the difference in rank + * @param <T> the data type for the Operands. + * @return the adjusted sampleWeight + */ + private static <T extends TNumber> Operand<T> maybeExpandWeights( + Ops tf, Operand<T> sampleWeight, Operand<TInt32> rankDiff) { + return tf.select( + tf.math.equal(rankDiff, tf.constant(-1)), + tf.expandDims(sampleWeight, tf.constant(-1)), + sampleWeight); + } + + /** + * Squeeze last dim if ranks differ from expected by exactly 1. + * + * @param tf the TensorFlowOps + * @param labels Label values, a <code>Tensor</code> whose dimensions match <code>predictions + * </code>. + * @param predictions Predicted values, a <code>Tensor</code> of arbitrary dimensions. + * @return <code>labels</code> and <code>predictions</code>, possibly with last dim squeezed. + */ + public static <T extends TNumber> LossTuple<T> removeSqueezableDimensions( + Ops tf, Operand<T> labels, Operand<T> predictions) { + return removeSqueezableDimensions(tf, labels, predictions, 0); + } + + /** + * Squeeze last dim if ranks differ from expected by exactly 1. + * + * @param tf the TensorFlowOps + * @param labels Label values, a <code>Operand</code> whose dimensions match <code>predictions + * </code>. + * @param predictions Predicted values, a <code>Tensor</code> of arbitrary dimensions. + * @param expectedRankDiff Expected result of <code>rank(predictions) - rank(labels)</code>. + * @return <code>labels</code> and <code>predictions</code>, possibly with last dim squeezed. + */ + public static <T extends TNumber> LossTuple<T> removeSqueezableDimensions( + Ops tf, Operand<T> labels, Operand<T> predictions, int expectedRankDiff) { + + tf = tf.withSubScope("removeSqueezableDimensions"); + Shape predictionsShape = predictions.asOutput().shape(); + int predictionsRank = predictionsShape.numDimensions(); + Shape labelsShape = labels.asOutput().shape(); + int labelsRank = labelsShape.numDimensions(); + + if (predictionsRank != Shape.UNKNOWN_SIZE || labelsRank != Shape.UNKNOWN_SIZE) { + // Use static rank. + int rankDiff = predictionsRank - labelsRank; + if (rankDiff == expectedRankDiff + 1 && Shape.isCompatible(predictionsShape.size(-1), 1)) { + predictions = tf.squeeze(predictions); + } else if (rankDiff == expectedRankDiff - 1 && Shape.isCompatible(labelsShape.size(-1), 1)) { + labels = tf.squeeze(labels); + } + return new LossTuple<>(labels, predictions); + } + // Use dynamic rank. + + // TODO Operand<TInt32> rankDiff = tf.math.sub(tf.rank(predictions), tf.rank(labels)); + if (predictionsRank == Shape.UNKNOWN_SIZE && Shape.isCompatible(predictionsShape.size(-1), 1)) { + /* + * TODO, if we ever get a select that does lazy evaluation, but for now do the tf.squeeze + * predictions = tf.select( tf.math.equal(tf.constant(expectedRankDiff+1),rankDiff ), + * tf.squeeze(predictions, Squeeze.axis(Arrays.asList(-1L))), predictions ); * + */ + predictions = tf.squeeze(predictions, Squeeze.axis(Collections.singletonList(-1L))); + } + if (labelsRank == Shape.UNKNOWN_SIZE && Shape.isCompatible(labelsShape.size(-1), 1)) { + /* + * TODO, if we ever get a select that does lazy evaluation labels = tf.select( + * tf.math.equal(tf.constant(expectedRankDiff+1),rankDiff ), tf.squeeze(labels, + * Squeeze.axis(Arrays.asList(-1L))), predictions ); * + */ + labels = tf.squeeze(labels, Squeeze.axis(Collections.singletonList(-1L))); + } + return new LossTuple<>(labels, predictions); + } + + /** + * Computes the weighted loss + * + * @param tf the TensorFlow Ops + * @param loss the unweighted loss + * @param reduction the type of reduction + * @param sampleWeight the sample weight, if null then this defaults to one. + * @param <T> the data type of the loss + * @return the weighted loss + */ + public static <T extends TNumber> Operand<T> computeWeightedLoss( + Ops tf, Operand<T> loss, Reduction reduction, Operand<T> sampleWeight) { + DataType<T> dataType = loss.asOutput().dataType(); + if (sampleWeight == null) { + sampleWeight = cast(tf, tf.constant(1), dataType); + } + LossTuple<T> result = squeezeOrExpandDimensions(tf, null, loss, sampleWeight); + loss = result.getTarget(); + sampleWeight = result.getSampleWeights(); + + Operand<T> weightedLosses = tf.math.mul(loss, cast(tf, sampleWeight, dataType)); + loss = reduceWeightedLoss(tf, weightedLosses, reduction); + return cast(tf, loss, dataType); + } + + /** + * Reduces the weighted loss based on the reduction type + * + * @param tf the TensorFlow Ops + * @param weightedLoss the weighted loss + * @param reduction the type of reduction + * @param <T> the data type of the weighted loss + * @return the reduced weighted loss + */ + private static <T extends TNumber> Operand<T> reduceWeightedLoss( + Ops tf, Operand<T> weightedLoss, Reduction reduction) { + Operand<T> loss; + if (reduction == Reduction.NONE) { + loss = weightedLoss; + } else { + loss = + tf.reduceSum(weightedLoss, allAxes(tf, weightedLoss), ReduceSum.keepDims(Boolean.FALSE)); + if (reduction == Reduction.AUTO || reduction == Reduction.SUM_OVER_BATCH_SIZE) { + loss = safeMean(tf, loss, weightedLoss.asOutput().shape().size()); + } + } + return loss; + } + + /** + * Computes a safe mean of the losses. + * + * @param tf the TensorFlow Ops + * @param losses </code>Operand</code> whose elements contain individual loss measurements. + * @param numElements The number of measurable elements in <code>losses</code>. + * @param <T> the data type of the losses + * @return A scalar representing the mean of <code>losses</code>. If <code>numElements</code> is + * zero, then zero is returned. + */ + public static <T extends TNumber> Operand<T> safeMean( + Ops tf, Operand<T> losses, long numElements) { + Operand<T> totalLoss = tf.reduceSum(losses, allAxes(tf, losses)); + return tf.math.divNoNan( + totalLoss, cast(tf, tf.constant(numElements), losses.asOutput().dataType())); + } + + /** + * Gets a Constant integer array representing all the axes of the operand. + * + * @param tf the TensorFlow Ops + * @param op the TensorFlow Ops + * @param <T> the type of Operand + * @return a Constant that represents all the axes of the operand. + */ + public static <T extends TNumber> Operand<TInt32> allAxes(Ops tf, Operand<T> op) { + int rank = op.asOutput().shape().numDimensions(); + if (rank != Shape.UNKNOWN_SIZE) { + int[] axes = new int[rank]; + for (int i = 0; i < rank; i++) { + axes[i] = i; + } + return tf.constant(axes); + } else { + return tf.range(tf.constant(0), tf.rank(op), tf.constant(1)); + } + } + + /** + * Perform an inclusive range check on the values + * + * @param tf the TensorFlow Ops + * @param prefix A String prefix to include in the error message + * @param values the values to check + * @param minValue the minimum value + * @param maxValue the maximum value + * @param <T> the datatype for the values + * @return the values possibly with control dependencies if the TensorFlow Ops represents a Graph + * Session + * @throws IllegalArgumentException if the TensorFlow Ops represents an Eager Session + */ + public static <T extends TNumber> Operand<T> rangeCheck( + Ops tf, String prefix, Operand<T> values, Operand<T> minValue, Operand<T> maxValue) { + Operand<TInt32> allDims = allAxes(tf, values); + Operand<TBool> cond = + tf.math.logicalAnd( + tf.reduceAll(tf.math.greaterEqual(values, minValue), allDims), + tf.reduceAll(tf.math.lessEqual(values, maxValue), allDims)); + // Graph and Eager mode need to be handled differently, control dependencies are not allowed in + // Eager mode + if (tf.scope().env().isGraph()) { + AssertThat assertThat = + tf.assertThat( + cond, + Arrays.asList( + tf.constant(prefix), + tf.constant(": values out of range, "), + tf.constant("minimum = "), + minValue, + tf.constant(", maximum = "), + maxValue)); + Ops ltf = + tf.withSubScope("rangeCheck") + .withControlDependencies(Collections.singletonList(assertThat)); + return ltf.identity(values); + } else if (!cond.asOutput().data().getBoolean()) + throw new IllegalArgumentException(String.format("%s : values out of range", prefix)); + else return values; + } + + /** + * Checks to see if all the values are in the allowed values set. Running the operand in Graph + * mode will throw {@link org.tensorflow.exceptions.TFInvalidArgumentException}, if at least one + * value is not in the allowed values set. In Eager mode, this method will throw an {@link + * IllegalArgumentException} if at least one value is not in the allowed values set. + * + * @param tf The TensorFlow Ops + * @param prefix A String prefix to include in the error message + * @param values the values to check + * @param allowedValues the allowed values + * @param <T> the data type for values and allowed values + * @return the values possibly with control dependencies if the TensorFlow Ops represents a Graph + * Session + * @throws IllegalArgumentException if the Session is in Eager mode and at least one value is not + * in the allowed values set + */ + public static <T extends TNumber> Operand<T> valueCheck( + Ops tf, String prefix, Operand<T> values, Operand<T> allowedValues) { + Operand<T> flatValues = + tf.reshape(values, tf.constant(Shape.of(values.asOutput().shape().size()))); + SetDiff1d<T, TInt32> diff = tf.setDiff1d(flatValues, allowedValues, TInt32.DTYPE); + long diffSize = diff.out().asOutput().shape().size(); + + if (diffSize != Shape.UNKNOWN_SIZE) { + if (diffSize != 0) { // at least 1 value in the diff did not match the allowed values. + throw new IllegalArgumentException(String.format("%s : values not in value set,", prefix)); + } else return values; + } else { // use dynamic shape + Operand<TBool> cond = tf.math.equal(tf.shape.size(tf.shape(diff.out())), tf.constant(0)); + // Graph and Eager mode need to be handled differently, control dependencies are not allowed + // in Eager mode + if (tf.scope().env().isGraph()) { + AssertThat assertThat = + tf.assertThat( + cond, + Arrays.asList( + tf.constant(prefix), + tf.constant(": values not in value set, values = "), + values)); + Ops ltf = + tf.withSubScope("valueCheck") + .withControlDependencies(Collections.singletonList(assertThat)); + return ltf.identity(values); + } else if (!cond.asOutput().data().getBoolean()) + throw new IllegalArgumentException(String.format("%s : values not in value set", prefix)); + else return values; + } + } +} diff --git a/tensorflow-framework/src/main/java/org/tensorflow/framework/utils/CastHelper.java b/tensorflow-framework/src/main/java/org/tensorflow/framework/utils/CastHelper.java new file mode 100644 index 00000000000..aec75e6078a --- /dev/null +++ b/tensorflow-framework/src/main/java/org/tensorflow/framework/utils/CastHelper.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.tensorflow.framework.utils; + +import org.tensorflow.DataType; +import org.tensorflow.Operand; +import org.tensorflow.op.Ops; +import org.tensorflow.types.family.TType; + +/** A helper class for casting an Operand */ +public class CastHelper { + + /** + * Casts an operand to the desired type. + * + * @param tf The TensorFlow Ops + * @param value the value to be cast + * @param requiredType the required data type + * @param <T> the required data type + * @param <U> the original data type of the value + * @return the value cast to the required data type. + */ + @SuppressWarnings("unchecked") + public static <T extends TType, U extends TType> Operand<T> cast( + Ops tf, Operand<U> value, DataType<T> requiredType) { + return (value.asOutput().dataType() == requiredType) + ? (Operand<T>) value + : tf.dtypes.cast(value, requiredType); + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/BinaryCrossentropyTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/BinaryCrossentropyTest.java new file mode 100644 index 00000000000..d2128b80839 --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/BinaryCrossentropyTest.java @@ -0,0 +1,226 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class BinaryCrossentropyTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class BinaryCrossentropy. */ + @Test + public void testAllCorrectUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + BinaryCrossentropy instance = new BinaryCrossentropy(tf); + float[] trueArray = {1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + + Operand<TFloat32> loss = instance.call(yTrue, yTrue); + + float expected = 0.0f; + testSession.evaluate(expected, loss); + // Test with logits. + float[] logitsArray = { + 100.0f, -100.0f, -100.0f, + -100.0f, 100.0f, -100.0f, + -100.0f, -100.0f, 100.0f + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new BinaryCrossentropy(tf, true); + + loss = instance.call(yTrue, logits); + testSession.evaluate(expected, loss); + } + } + + @Test + public void testInvalidPredictionsRange() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Class<? extends Throwable> catchClass = + tfMode == TestSession.Mode.EAGER + ? IllegalArgumentException.class + : org.tensorflow.exceptions.TFInvalidArgumentException.class; + assertThrows( + catchClass, + () -> { + Ops tf = testSession.getTF(); + BinaryCrossentropy instance = new BinaryCrossentropy(tf); + float[] trueArray = {1f, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 1f}; + float[] predArray = {2f, 1f, -1f, 0f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 2))); + + Operand<TFloat32> loss = instance.call(yTrue, yPred); + testSession.run(loss); + }); + } + } + + /** Test of call method, of class BinaryCrossentropy. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + BinaryCrossentropy instance = new BinaryCrossentropy(tf); + float[] trueArray = {1f, 0f, 1f, 0f}; + float[] predArray = {1f, 1f, 1f, 0f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 3.83331f; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] trueArray1 = {1f, 0f, 1f, 0f, 1f, 1f}; + float[] logitsArray = { + 100.0f, -100.0f, 100.0f, + 100.0f, 100.0f, -100.0f + }; + Operand<TFloat32> yTrue1 = tf.reshape(tf.constant(trueArray1), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(2, 3))); + instance = new BinaryCrossentropy(tf, true); + loss = instance.call(yTrue1, logits); + expected = 33.33333f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class BinaryCrossentropy. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + BinaryCrossentropy instance = new BinaryCrossentropy(tf); + float[] trueArray = {1f, 0f, 1f, 0f}; + float[] predArray = {1f, 1f, 1f, 0f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 8.816612f; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] trueArray1 = {1f, 0f, 1f, 0f, 1f, 1f}; + float[] logitsArray = { + 100.0f, -100.0f, 100.0f, + 100.0f, 100.0f, -100.0f + }; + Operand<TFloat32> yTrue1 = tf.reshape(tf.constant(trueArray1), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(2, 3))); + instance = new BinaryCrossentropy(tf, true); + loss = instance.call(yTrue1, logits, sampleWeight); + expected = 76.66667f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + BinaryCrossentropy instance = new BinaryCrossentropy(tf); + float[] trueArray = {1f, 0f, 1f, 0f}; + float[] predArray = {1f, 1f, 1f, 0f}; + float[] sampleWeightArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleWeightArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 4.59997f; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] trueArray1 = {1f, 0f, 1f, 0f, 1f, 1f}; + float[] logitsArray = { + 100.0f, -100.0f, 100.0f, + 100.0f, 100.0f, -100.0f + }; + float[] sampleWeightArray1 = {4f, 3f}; + Operand<TFloat32> yTrue1 = tf.reshape(tf.constant(trueArray1), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight1 = tf.constant(sampleWeightArray1); + instance = new BinaryCrossentropy(tf, true); + loss = instance.call(yTrue1, logits, sampleWeight1); + expected = 100f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testNoReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + // Test with logits. + float[] trueArray = {1f, 0f, 1f, 0f, 1f, 1f}; + float[] logitsArray = { + 100.0f, -100.0f, 100.0f, + 100.0f, 100.0f, -100.0f + }; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(2, 3))); + BinaryCrossentropy instance = + new BinaryCrossentropy( + tf, true, BinaryCrossentropy.LABEL_SMOOTHING_DEFAULT, Reduction.NONE); + Operand<TFloat32> loss = instance.call(yTrue, logits); + Float[] expected = {0.f, 66.666664f}; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testLabelSmoothing() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float labelSmoothing = 0.1f; + float[] trueArray = {1f, 0f, 1f}; + float[] logitsArray = {100.0f, -100.0f, -100.0f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(1, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(1, 3))); + + BinaryCrossentropy instance = new BinaryCrossentropy(tf, true, labelSmoothing); + Operand<TFloat32> loss = instance.call(yTrue, logits); + float expected = (100.0f + 50.0f * labelSmoothing) / 3.0f; + testSession.evaluate(expected, loss); + } catch (Exception expected) { + + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CategoricalCrossentropyTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CategoricalCrossentropyTest.java new file mode 100644 index 00000000000..13b287de3cd --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CategoricalCrossentropyTest.java @@ -0,0 +1,263 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; +import org.tensorflow.types.TInt32; +import org.tensorflow.types.TInt64; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class CategoricalCrossentropyTest { + + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class CategoricalCrossentropy. */ + @Test + public void testAllCorrectUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + long[] trueArray = { + 1L, 0L, 0L, + 0L, 1L, 0L, + 0L, 0L, 1L + }; + float[] predArray = { + 1.F, 0.F, 0.F, + 0.F, 1.F, 0.F, + 0.F, 0.F, 1.F + }; + Operand<TInt64> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + CategoricalCrossentropy instance = new CategoricalCrossentropy(tf); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0F; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 10.F, 0.F, 0.F, + 0.F, 10.F, 0.F, + 0.F, 0.F, 10.F + }; + yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new CategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits); + testSession.setEpsilon(1e-3F); + testSession.evaluate(0.0F, loss); + } + } + + @Test + public void testInvalidPredictionsRange() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Class<? extends Throwable> catchClass = + tfMode == TestSession.Mode.EAGER + ? IllegalArgumentException.class + : org.tensorflow.exceptions.TFInvalidArgumentException.class; + assertThrows( + catchClass, + () -> { + Ops tf = testSession.getTF(); + CategoricalCrossentropy instance = new CategoricalCrossentropy(tf); + float[] trueArray = { + 1L, 0L, 0L, + 0L, 1L, 0L, + 0L, 0L, 1L + }; + float[] predArray = { + -1.F, 0.F, 0.F, + 0.F, 1.F, 0.F, + 0.F, 0.F, 1.F + }; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 2))); + + Operand<TFloat32> loss = instance.call(yTrue, yPred); + testSession.run(loss); + }); + } + } + + /** Test of call method, of class CategoricalCrossentropy. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalCrossentropy instance = new CategoricalCrossentropy(tf); + int[] trueArray = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + float[] predArray = { + .9F, .05F, .05F, + .5F, .89F, .6F, + .05F, .01F, .94F + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0.32396814F; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new CategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits); + expected = 0.0573755F; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class CategoricalCrossentropy. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + int[] trueArray = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + }; + float[] predArray = { + .9F, .05F, .05F, + .5F, .89F, .6F, + .05F, .01F, .94F + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3F); + + CategoricalCrossentropy instance = new CategoricalCrossentropy(tf); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = .7451267F; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new CategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits, sampleWeight); + expected = 0.13196386F; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSsampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalCrossentropy instance = new CategoricalCrossentropy(tf); + float[] sampeWeightArray = {1.2F, 3.4F, 5.6F}; + int[] trueArray = { + 1, 0, 0, + 0, 1, 0, + 0, 0, 1 + }; + float[] predArray = { + .9F, .05F, .05F, + .5F, .89F, .6F, + .05F, .01F, .94F + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampeWeightArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 1.0696F; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new CategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits, sampleWeight); + expected = 0.31829F; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testNoReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + // Test with logits. + int[] trueArray = {1, 0, 0, 0, 1, 0, 0, 0, 1}; + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + CategoricalCrossentropy instance = + new CategoricalCrossentropy(tf, true, 0.0F, Reduction.NONE); + Operand<TFloat32> loss = instance.call(yTrue, logits); + Float[] expected = {0.001822F, 0.000459F, 0.169846F}; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testLabelSmoothing() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float labelSmoothing = 0.1F; + int[] trueArray = {1, 0, 0}; + float[] logitsArray = {100.0F, -100.0F, -100.0F}; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(1, 3))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(1, 3))); + + CategoricalCrossentropy instance = new CategoricalCrossentropy(tf, true, labelSmoothing); + Operand<TFloat32> loss = instance.call(yTrue, logits); + float expected = 400.0F * labelSmoothing / 3.0F; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CategoricalHingeTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CategoricalHingeTest.java new file mode 100644 index 00000000000..b0d0442b3c7 --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CategoricalHingeTest.java @@ -0,0 +1,138 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; +import org.tensorflow.types.TInt32; + +public class CategoricalHingeTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class CategoricalHinge. */ + @Test + public void testReductionNone() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalHinge instance = new CategoricalHinge(tf, Reduction.NONE); + int[] trueArray = {1, 9, 2, -5}; + float[] predArray = {4f, 8f, 12f, 8f}; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + Float[] expected = {0.0f, 65.0f}; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class CategoricalHinge. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalHinge instance = new CategoricalHinge(tf); + int[] trueArray = {1, 9, 2, -5}; + float[] predArray = {4f, 8f, 12f, 8f}; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 32.5f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class CategoricalHinge. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalHinge instance = new CategoricalHinge(tf); + int[] trueArray = {1, 9, 2, -5, -2, 6}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 83.95f; + testSession.evaluate(expected, loss); + + Operand<TFloat32> loss2 = instance.call(yTrue, yPred, sampleWeight); + testSession.evaluate(loss, loss2); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalHinge instance = new CategoricalHinge(tf); + int[] trueArray = {1, 9, 2, -5, -2, 6}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] weightsNp = {1.2f, 3.4f}; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(weightsNp), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 124.1f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalHinge instance = new CategoricalHinge(tf); + int[] trueArray = {1, 9, 2, -5, -2, 6}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CategoricalHinge instance = new CategoricalHinge(tf); + int[] trueArray = {1, 9, 2, -5, -2, 6}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] weightsNp = {3, 6, 5, 0, 4, 2}; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(weightsNp), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 4.0f; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CosineSimilarityTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CosineSimilarityTest.java new file mode 100644 index 00000000000..8350d1403ed --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/CosineSimilarityTest.java @@ -0,0 +1,185 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class CosineSimilarityTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class CosineSimilarity. */ + @Test + public void testReductionNone() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + CosineSimilarity instance = new CosineSimilarity(tf, Reduction.NONE); + Shape shape = Shape.of(2, 3); + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(shape)); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(shape)); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + Float[] expected = {-0.720488f, 0.3460499f}; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class CosineSimilarity. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] expectedLoss = {0.720488f, -0.3460499f}; + CosineSimilarity instance = new CosineSimilarity(tf); + Shape shape = Shape.of(2, 3); + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(shape)); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(shape)); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = -mean(expectedLoss); + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class CosineSimilarity. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] expectedLoss = {0.720488f, -0.3460499f}; + CosineSimilarity instance = new CosineSimilarity(tf); + Shape shape = Shape.of(2, 3); + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(shape)); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(shape)); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = -mean(mul(expectedLoss, 2.3f)); + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] expectedLoss = {0.720488f, -0.3460499f}; + CosineSimilarity instance = new CosineSimilarity(tf); + float[] weightsArray = {1.2f, 3.4f}; + Shape shape = Shape.of(2, 3); + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(shape)); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(shape)); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(weightsArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = -mean(mul(expectedLoss, weightsArray)); + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + CosineSimilarity instance = new CosineSimilarity(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Shape shape = Shape.of(2, 3); + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(shape)); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(shape)); + Operand<TFloat32> sampleWeight = tf.constant(0f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + CosineSimilarity instance = new CosineSimilarity(tf); + Shape shape = Shape.of(2, 3, 1); + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(shape)); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(shape)); + float[] weightsArray = {3, 6, 5, 0, 4, 2}; + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(weightsArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = -2.0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testAxis() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] expectedLoss = {0.720488f, -0.3460499f}; + CosineSimilarity instance = new CosineSimilarity(tf, 1); + Shape shape = Shape.of(2, 3); + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(shape)); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(shape)); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = -mean(expectedLoss); + testSession.evaluate(expected, loss); + } + } + + private float mean(float[] v) { + float sum = 0; + for (float value : v) { + sum += value; + } + return sum / v.length; + } + + private float[] mul(float[] v, float scalar) { + float[] result = new float[v.length]; + for (int i = 0; i < v.length; i++) { + result[i] = v[i] * scalar; + } + return result; + } + + private float[] mul(float[] v, float[] b) { + float[] result = new float[v.length]; + for (int i = 0; i < v.length; i++) { + result[i] = v[i] * b[i]; + } + return result; + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/HingeTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/HingeTest.java new file mode 100644 index 00000000000..4770511207e --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/HingeTest.java @@ -0,0 +1,149 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class HingeTest { + + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class Hinge. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Hinge instance = new Hinge(tf); + float[] trueArray = {0f, 1f, 0f, 1f, 0f, 0f, 1f, 1f}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0.50625f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testInvalidLabelValue() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Class<? extends Throwable> catchClass = + tfMode == TestSession.Mode.EAGER + ? IllegalArgumentException.class + : org.tensorflow.exceptions.TFInvalidArgumentException.class; + assertThrows( + catchClass, + () -> { + Ops tf = testSession.getTF(); + Hinge instance = new Hinge(tf); + float[] trueArray = {2f, 1f, 0f, 1f, 0f, 0f, 1f, 1f}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + testSession.run(loss); + }); + } + } + + /** Test of call method, of class Hinge. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Hinge instance = new Hinge(tf); + float[] trueArray = {0f, 1f, 0f, 1f, 0f, 0f, 1f, 1f}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 1.164375f; + testSession.evaluate(expected, loss); + + // todo Verify we get the same output when the same input is given + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Hinge instance = new Hinge(tf); + float[] sampleArray = {1.2f, 3.4f}; + float[] trueArray = {0f, 1f, 0f, 1f, 0f, 0f, 1f, 1f}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 1.06125f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Hinge instance = new Hinge(tf); + float[] trueArray = {0f, 1f, 0f, 1f, 0f, 0f, 1f, 1f}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Hinge instance = new Hinge(tf, Reduction.AUTO); + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f, 1f, 3f}; + float[] trueArray = {0f, 1f, 0f, 1f, 0f, 0f, 1f, 1f}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = 2.0125f; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/HuberTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/HuberTest.java new file mode 100644 index 00000000000..d1751f223a1 --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/HuberTest.java @@ -0,0 +1,137 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class HuberTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + @Test + public void testAllCorrect() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {.9f, .2f, .2f, .8f, .4f, .6f}; + + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Huber instance = new Huber(tf); + Operand<TFloat32> loss = instance.call(yTrue, yTrue); + float expected = 0.0f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class Huber. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + float[] trueArray = {.9f, .2f, .2f, .8f, .4f, .6f}; + float[] predArray = {1.f, 0.f, 1.f, 1.f, 0.f, 0.f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Huber instance = new Huber(tf); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0.10416666666666669f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class Huber. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {.9f, .2f, .2f, .8f, .4f, .6f}; + float[] predArray = {1.f, 0.f, 1.f, 1.f, 0.f, 0.f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Huber instance = new Huber(tf); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0.23958333333333337f; + testSession.evaluate(expected, loss); + + // todo Verify we get the same output when the same input is given + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] sampleArray = {1.2f, 3.4f}; + float[] trueArray = {.9f, .2f, .2f, .8f, .4f, .6f}; + float[] predArray = {1.f, 0.f, 1.f, 1.f, 0.f, 0.f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Huber instance = new Huber(tf); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0.22766666666666668f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] trueArray = {.9f, .2f, .2f, .8f, .4f, .6f}; + float[] predArray = {1.f, 0.f, 1.f, 1.f, 0.f, 0.f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Huber instance = new Huber(tf); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + float[] trueArray = {.9f, .2f, .2f, .8f, .4f, .6f}; + float[] predArray = {1.f, 0.f, 1.f, 1.f, 0.f, 0.f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Huber instance = new Huber(tf); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = .4025f; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/KLDivergenceTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/KLDivergenceTest.java new file mode 100644 index 00000000000..d57b61b18dd --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/KLDivergenceTest.java @@ -0,0 +1,119 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class KLDivergenceTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class KLDivergence. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + KLDivergence instance = new KLDivergence(tf); + float[] predArray = {.4f, .9f, .12f, .36f, .3f, .4f}; + float[] trueArray = {.5f, .8f, .12f, .7f, .43f, .8f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0.5960738398643668f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class KLDivergence. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + KLDivergence instance = new KLDivergence(tf); + float[] predArray = {.4f, .9f, .12f, .36f, .3f, .4f}; + float[] trueArray = {.5f, .8f, .12f, .7f, .43f, .8f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 1.3709698316880434f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + KLDivergence instance = new KLDivergence(tf); + float[] predArray = {.4f, .9f, .12f, .36f, .3f, .4f}; + float[] trueArray = {.5f, .8f, .12f, .7f, .43f, .8f}; + float[] sampleArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 2.0075711736936492f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + KLDivergence instance = new KLDivergence(tf); + float[] predArray = {.4f, .9f, .12f, .36f, .3f, .4f}; + float[] trueArray = {.5f, .8f, .12f, .7f, .43f, .8f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + KLDivergence instance = new KLDivergence(tf, Reduction.AUTO); + float[] predArray = {.4f, .9f, .12f, .36f, .3f, .4f}; + float[] trueArray = {.5f, .8f, .12f, .7f, .43f, .8f}; + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = 0.2495994912084345f; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/LogCoshTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/LogCoshTest.java new file mode 100644 index 00000000000..c4347b3fccb --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/LogCoshTest.java @@ -0,0 +1,119 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class LogCoshTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class LogCosh. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + LogCosh instance = new LogCosh(tf); + float[] predArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 4.829245330860459f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class LogCosh. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + LogCosh instance = new LogCosh(tf); + float[] predArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 11.107264260979056f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + LogCosh instance = new LogCosh(tf); + float[] predArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 12.001114667519486f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + LogCosh instance = new LogCosh(tf); + float[] predArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + LogCosh instance = new LogCosh(tf, Reduction.AUTO); + float[] predArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = 11.653484271934046f; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanAbsoluteErrorTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanAbsoluteErrorTest.java new file mode 100644 index 00000000000..3498c6d53aa --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanAbsoluteErrorTest.java @@ -0,0 +1,194 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class MeanAbsoluteErrorTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class MeanAbsoluteError. */ + @Test + public void testAllCorrectUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf); + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yTrue); + float expected = 0.0f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanAbsoluteError. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 5.5f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanAbsoluteError. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 12.65f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 81.4f / 6f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf, Reduction.AUTO); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = 83f / 6f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testInvalidSampleWeight() { + for (TestSession.Mode tfMode : tfModes) + Assertions.assertThrows( + IllegalArgumentException.class, + () -> { + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 83f / 6f; + testSession.evaluate(expected, loss); + } + }); + } + + @Test + public void testNoReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf, Reduction.NONE); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + Float[] expected = {10.733333f, 14.566667f}; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSumReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsoluteError instance = new MeanAbsoluteError(tf, Reduction.SUM); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + Float[] expected = {25.29999f}; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanAbsolutePercentageErrorTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanAbsolutePercentageErrorTest.java new file mode 100644 index 00000000000..7816a8a288a --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanAbsolutePercentageErrorTest.java @@ -0,0 +1,167 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class MeanAbsolutePercentageErrorTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class MeanAbsolutePercentageError. */ + @Test + public void testAllCorrectUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf); + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yTrue); + float expected = 0.0f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanAbsolutePercentageError. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 211.85184f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanAbsolutePercentageError. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 487.25922f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 422.8889f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf, Reduction.AUTO); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 694.4445f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testNoReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf, Reduction.NONE); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + Float[] expected = {621.8518f, 352.66666f}; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSumReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanAbsolutePercentageError instance = new MeanAbsolutePercentageError(tf, Reduction.SUM); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 974.51843f; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanSquaredErrorTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanSquaredErrorTest.java new file mode 100644 index 00000000000..1a971f0492b --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanSquaredErrorTest.java @@ -0,0 +1,194 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class MeanSquaredErrorTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class MeanSquaredError. */ + @Test + public void testAllCorrectUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf); + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yTrue); + float expected = 0.0f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanSquaredError. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 49.5f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanSquaredError. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 113.85f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 127.96667f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf, Reduction.AUTO); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = 97.833336f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testInvalidSampleWeight() { + for (TestSession.Mode tfMode : tfModes) + Assertions.assertThrows( + IllegalArgumentException.class, + () -> { + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 173.25f; + testSession.evaluate(expected, loss); + } + }); + } + + @Test + public void testNoReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf, Reduction.NONE); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + Float[] expected = {84.333336f, 143.36665f}; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSumReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredError instance = new MeanSquaredError(tf, Reduction.SUM); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + Float[] expected = {227.69998f}; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanSquaredLogarithmicErrorTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanSquaredLogarithmicErrorTest.java new file mode 100644 index 00000000000..558f9c84659 --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/MeanSquaredLogarithmicErrorTest.java @@ -0,0 +1,194 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class MeanSquaredLogarithmicErrorTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class MeanSquaredLogarithmicError. */ + @Test + public void testAllCorrectUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf); + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yTrue); + float expected = 0.0f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanSquaredLogarithmicError. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 1.4370421f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class MeanSquaredLogarithmicError. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 3.3051968f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 3.7856376f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf, Reduction.AUTO); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = 2.647374f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testInvalidSampleWeight() { + for (TestSession.Mode tfMode : tfModes) + Assertions.assertThrows( + IllegalArgumentException.class, + () -> { + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 2))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 83f / 6f; + testSession.evaluate(expected, loss); + } + }); + } + + @Test + public void testNoReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf, Reduction.NONE); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + Float[] expected = {2.3006392f, 4.3097544f}; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSumReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + MeanSquaredLogarithmicError instance = new MeanSquaredLogarithmicError(tf, Reduction.SUM); + float[] trueArray = {1f, 9f, 2f, -5f, -2f, 6f}; + float[] predArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + Float[] expected = {6.6103935f}; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/PoissonTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/PoissonTest.java new file mode 100644 index 00000000000..55c59ca5ac6 --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/PoissonTest.java @@ -0,0 +1,119 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +public class PoissonTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class Poisson. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Poisson instance = new Poisson(tf); + float[] predArray = {1f, 9f, 2f, 5f, 2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = -3.306581945521002f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class Poisson. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Poisson instance = new Poisson(tf); + float[] predArray = {1f, 9f, 2f, 5f, 2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = -7.605138474698304f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Poisson instance = new Poisson(tf); + float[] predArray = {1f, 9f, 2f, 5f, 2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {1.2f, 3.4f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = -6.147338926788071f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Poisson instance = new Poisson(tf); + float[] predArray = {1f, 9f, 2f, 5f, 2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + Poisson instance = new Poisson(tf, Reduction.AUTO); + float[] predArray = {1f, 9f, 2f, 5f, 2f, 6f}; + float[] trueArray = {4f, 8f, 12f, 8f, 1f, 3f}; + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 3, 1))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = -12.263126013890561f; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/SparseCategoricalCrossentropyTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/SparseCategoricalCrossentropyTest.java new file mode 100644 index 00000000000..a6a0ff35c78 --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/SparseCategoricalCrossentropyTest.java @@ -0,0 +1,225 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; +import org.tensorflow.types.TInt32; +import org.tensorflow.types.TInt64; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SparseCategoricalCrossentropyTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class SparseCategoricalCrossentropy. */ + @Test + public void testAllCorrectUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + testSession.setEpsilon(1e-3f); + Ops tf = testSession.getTF(); + + int[] trueArray = {0, 1, 2}; + float[] predArray = { + 1.F, 0.F, 0.F, + 0.F, 1.F, 0.F, + 0.F, 0.F, 1.F + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + SparseCategoricalCrossentropy instance = new SparseCategoricalCrossentropy(tf); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0.0f; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 10.F, 0.F, 0.F, + 0.F, 10.F, 0.F, + 0.F, 0.F, 10.F + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new SparseCategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits); + testSession.evaluate(0.0f, loss); + } + } + + @Test + public void testInvalidPredictionsRange() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Class<? extends Throwable> catchClass = + tfMode == TestSession.Mode.EAGER + ? IllegalArgumentException.class + : org.tensorflow.exceptions.TFInvalidArgumentException.class; + assertThrows( + catchClass, + () -> { + Ops tf = testSession.getTF(); + SparseCategoricalCrossentropy instance = new SparseCategoricalCrossentropy(tf); + int[] trueArray = {0, 1, 2}; + float[] predArray = { + 1.9f, .05f, .05f, + .5f, .89f, .6f, + .05f, .01f, .94f + }; + Operand<TInt32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + testSession.run(loss); + }); + } + } + + /** Test of call method, of class SparseCategoricalCrossentropy. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + SparseCategoricalCrossentropy instance = new SparseCategoricalCrossentropy(tf); + int[] trueArray = {0, 1, 2}; + float[] predArray = { + .9f, .05f, .05f, + .5f, .89f, .6f, + .05f, .01f, .94f + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0.32396814f; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new SparseCategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits); + expected = 0.05737559f; + testSession.evaluate(expected, loss); + } + } + + /** Test of call method, of class SparseCategoricalCrossentropy. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + int[] trueArray = {0, 1, 2}; + float[] predArray = { + .9f, .05f, .05f, + .5f, .89f, .6f, + .05f, .01f, .94f + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + + SparseCategoricalCrossentropy instance = new SparseCategoricalCrossentropy(tf); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = .7451267f; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new SparseCategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits, sampleWeight); + expected = 0.13196386f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + SparseCategoricalCrossentropy instance = new SparseCategoricalCrossentropy(tf); + float[] sampleWeightArray = {1.2f, 3.4f, 5.6f}; + int[] trueArray = {0, 1, 2}; + float[] predArray = { + .9f, .05f, .05f, + .5f, .89f, .6f, + .05f, .01f, .94f + }; + Operand<TInt32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(3, 3))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleWeightArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 1.0696f; + testSession.evaluate(expected, loss); + + // Test with logits. + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + instance = new SparseCategoricalCrossentropy(tf, true); + loss = instance.call(yTrue, logits, sampleWeight); + expected = 0.31829f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testNoReduction() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + + // Test with logits. + long[] trueArray = {0L, 1L, 2L}; + float[] logitsArray = { + 8.F, 1.F, 1.F, + 0.F, 9.F, 1.F, + 2.F, 3.F, 5.F + }; + Operand<TInt64> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(3, 1))); + Operand<TFloat32> logits = + tf.reshape(tf.constant(logitsArray), tf.constant(Shape.of(3, 3))); + SparseCategoricalCrossentropy instance = + new SparseCategoricalCrossentropy(tf, true, Reduction.NONE); + Operand<TFloat32> loss = instance.call(yTrue, logits); + Float[] expected = {0.001822f, 0.000459f, 0.169846f}; + testSession.evaluate(expected, loss); + } + } +} diff --git a/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/SquaredHingeTest.java b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/SquaredHingeTest.java new file mode 100644 index 00000000000..57a012bbe9d --- /dev/null +++ b/tensorflow-framework/src/test/java/org/tensorflow/framework/losses/SquaredHingeTest.java @@ -0,0 +1,146 @@ +/* Copyright 2020 The TensorFlow Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=======================================================================*/ +package org.tensorflow.framework.losses; + +import org.junit.jupiter.api.Test; +import org.tensorflow.Operand; +import org.tensorflow.framework.utils.TestSession; +import org.tensorflow.ndarray.Shape; +import org.tensorflow.op.Ops; +import org.tensorflow.types.TFloat32; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class SquaredHingeTest { + private final TestSession.Mode[] tfModes = {TestSession.Mode.EAGER, TestSession.Mode.GRAPH}; + + /** Test of call method, of class SquaredHinge. */ + @Test + public void testUnweighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + SquaredHinge instance = new SquaredHinge(tf); + float[] trueArray = {0, 1, 0, 1, 0, 0, 1, 1}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + float expected = 0.364062f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testInvalidLabelValue() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Class<? extends Throwable> catchClass = + tfMode == TestSession.Mode.EAGER + ? IllegalArgumentException.class + : org.tensorflow.exceptions.TFInvalidArgumentException.class; + assertThrows( + catchClass, + () -> { + Ops tf = testSession.getTF(); + SquaredHinge instance = new SquaredHinge(tf); + float[] trueArray = {0, 2, 0, 1, 0, 0, 1, 1}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> loss = instance.call(yTrue, yPred); + testSession.run(loss); + }); + } + } + + /** Test of call method, of class SquaredHinge. */ + @Test + public void testScalarWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + SquaredHinge instance = new SquaredHinge(tf); + float[] trueArray = {0, 1, 0, 1, 0, 0, 1, 1}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> sampleWeight = tf.constant(2.3f); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0.8373437f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testSampleWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + SquaredHinge instance = new SquaredHinge(tf); + float[] sampleArray = {1.2f, 3.4f}; + float[] trueArray = {0, 1, 0, 1, 0, 0, 1, 1}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 1))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0.7043125f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testZeroWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + SquaredHinge instance = new SquaredHinge(tf); + float[] trueArray = {0, 1, 0, 1, 0, 0, 1, 1}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> yPred = tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> sampleWeight = tf.constant(0.F); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + float expected = 0f; + testSession.evaluate(expected, loss); + } + } + + @Test + public void testTimestepWeighted() { + for (TestSession.Mode tfMode : tfModes) + try (TestSession testSession = TestSession.createTestSession(tfMode)) { + Ops tf = testSession.getTF(); + SquaredHinge instance = new SquaredHinge(tf, Reduction.AUTO); + float[] trueArray = {0, 1, 0, 1, 0, 0, 1, 1}; + float[] predArray = {-0.3f, 0.2f, -0.1f, 1.6f, -0.25f, -1.f, 0.5f, 0.6f}; + Operand<TFloat32> yTrue = + tf.reshape(tf.constant(trueArray), tf.constant(Shape.of(2, 4, 1))); + Operand<TFloat32> yPred = + tf.reshape(tf.constant(predArray), tf.constant(Shape.of(2, 4, 1))); + float[] sampleArray = {3f, 6f, 5f, 0f, 4f, 2f, 1f, 3f}; + Operand<TFloat32> sampleWeight = + tf.reshape(tf.constant(sampleArray), tf.constant(Shape.of(2, 4))); + Operand<TFloat32> loss = instance.call(yTrue, yPred, sampleWeight); + + float expected = 1.54250000f; + testSession.evaluate(expected, loss); + } + } +}