chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:47:05 +08:00
commit 4f3b7da785
7394 changed files with 2005594 additions and 0 deletions
@@ -0,0 +1,405 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// Created by Yurii Shyrma on 20.11.2017.
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_absolute_difference_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(absolute_difference_loss, 3, 1, false, 0, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// input validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got "
"weights = %s and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, "
"but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
NDArray* diff = (*predictions) - (*labels);
NDArray* E = diff->transform(transform::Abs);
delete diff;
NDArray* EWeighted = (*E) * (*weightsBroad);
delete E;
switch (reductionMode) {
case 0: // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(EWeighted);
break;
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
auto* sumResult = EWeighted->reduceNumber(reduce::Sum);
output->assign(sumResult);
delete sumResult;
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * EWeighted->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
(*output) = 0.;
} else {
NDArray* sumE = EWeighted->reduceNumber(reduce::Sum);
NDArray* result = (*sumE) / (*sum);
output->assign(result);
delete result;
delete sumE;
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = EWeighted->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
(*output) = 0.;
} else {
NDArray* sumE = EWeighted->reduceNumber(reduce::Sum);
NDArray* result = (*sumE) / double(numOfNonZeroWeights);
output->assign(result);
delete result;
delete sumE;
}
break;
}
}
delete EWeighted;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(absolute_difference_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(absolute_difference_loss) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType * outShapeInfo = nullptr;
if (INT_ARG(0) != 0) { // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
} else { // in this case output has the same shape as labels and predictions
outShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(absolute_difference_loss_grad, 3, 3, false, 0, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
// inputs validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got "
"weights = %s and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, "
"but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
NDArray* E = (*predictions) - (*labels);
// dE_i/dp_i = sign(p_i - y_i)
E->applyTransform(transform::Sign, dLdp); // dE/dp
// dE_i/dy_i = -sign(p_i - y_i)
E->applyTransform(transform::Abs, E);
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
NDArray* dLdpWeighted = (*dLdp) * (*weightsBroad);
dLdp->assign(dLdpWeighted);
delete dLdpWeighted;
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
dLdw->assign(sumE);
delete sumE;
}
else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(E);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * E->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdw = 0.;
} else {
NDArray* dLdpWeighted = (*dLdp) * (*weightsBroad);
NDArray* dLdpResult = (*dLdpWeighted) / (*sum);
dLdp->assign(dLdpResult);
delete dLdpResult;
delete dLdpWeighted;
if (weights->isScalar()) {
*dLdw = 0.;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
gradTemp->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete gradTemp;
} else {
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(gradTemp);
delete gradTemp;
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdw = 0.;
} else {
auto numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
auto* result = (*sumE) / double(numOfNonZeroWeights);
dLdw->assign(result);
delete result;
delete sumE;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
NDArray* dLdwResult = (*dLdw) / (*numOfNonZeroWeightsScalar);
dLdw->assign(dLdwResult);
delete dLdwResult;
} else {
auto* result = (*E) / (*numOfNonZeroWeightsScalar);
dLdw->assign(result);
delete result;
}
NDArray* temp = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
delete temp;
delete numOfNonZeroWeightsScalar;
}
break;
}
}
NDArray neg = -*dLdp;
dLdl->assign(&neg);
delete E;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(absolute_difference_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(absolute_difference_loss_grad) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"ABSOLUTE_DIFFERENCE_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
auto dLdpShapeInfo = ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
auto dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
auto dLdlShapeInfo = ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(CONSTANT(dLdpShapeInfo), CONSTANT(dLdwShapeInfo), CONSTANT(dLdlShapeInfo));
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,475 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 22.11.2017
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_cosine_distance_loss)
#include <helpers/ShapeUtils.h>
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(cosine_distance_loss, 3, 1, false, 0, 2) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
int dim = INT_ARG(1); // axis along which sum will be made
if (dim < 0) dim += labels->rankOf();
// labels and predictions must have the same shapes
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"COSINE_DISTANCE_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// regard 4 possible reduction modes below
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"COSINE_DISTANCE_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but "
"got %i instead!",
reductionMode);
// input dimension can't be larger than labels/predictions/weights rank
REQUIRE_TRUE(dim < labels->rankOf(), 0,
"COSINE_DISTANCE_LOSS OP: input reduction dimension (got %i) must be < labels rank %i!", dim,
labels->rankOf());
if (!output->isScalar()) {
// weights array can be single scalar or has the same shape as output, and must be broadcastable to output shape
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == output->rankOf(), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: weights array should be scalar or have the same rank as output array, "
"but got %i and %i correspondingly!",
weights->rankOf(), output->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *output), 0,
"COSINE_DISTANCE_LOSS OP: shapes of weights and output arrays should be broadcastable, but got "
"weights = %s and output = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
}
std::vector<LongType> dims;
dims.push_back(dim);
NDArray* predLabels = (*predictions) * (*labels);
NDArray* dotProduct = predLabels->reduceAlongDimension(reduce::Sum, &dims, true);
delete predLabels;
NDArray* E = 1. - (*dotProduct);
delete dotProduct;
// perform weights broadcasting/tile to E if it is necessary
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(E))
weightsBroad = new NDArray(weights->tileToShape(E->shapeInfo()));
// multiply E on weights
NDArray* EWeighted = (*E) * (*weightsBroad);
switch (reductionMode) {
case 0: // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(EWeighted);
break;
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
auto* sumResult = EWeighted->reduceNumber(reduce::Sum);
output->assign(sumResult);
delete sumResult;
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * EWeighted->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*output = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* result = (*sumE) / (*sum);
output->assign(result);
delete result;
delete sumE;
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = EWeighted->lengthOf();
} else {
auto* countResult = EWeighted->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*output = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* result = (*sumE) / double(numOfNonZeroWeights);
output->assign(result);
delete result;
delete sumE;
}
break;
}
}
STORE_RESULT(*output);
delete EWeighted;
delete E;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(cosine_distance_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(cosine_distance_loss) {
// labels and predictions must have the same shapes
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
int dim = INT_ARG(1);
if (dim < 0) dim += labelsShapeInfo[0];
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"COSINE_DISTANCE_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// input dimension can't be larger than labels/predictions/weights rank
REQUIRE_TRUE(dim < labelsShapeInfo[0], 0,
"COSINE_DISTANCE_LOSS OP: input reduction dimension (got %i) must be < labels rank %i!", dim,
labelsShapeInfo[0]);
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
// evaluate output shapeInfo
LongType * outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the same shape as labels reduced by dim axis
std::vector<LongType> dimensions = {dim};
outShapeInfo = ShapeUtils::evalReduceShapeInfo(shape::order(predictionsShapeInfo), &dimensions, predictionsShapeInfo,
outType, true, false, block.getWorkspace());
// weights array can be single scalar or has the same rank as output, and must be broadcastable to output
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(outShapeInfo), 0,
"COSINE_DISTANCE_LOSS OP: weights array should be scalar or have the same rank as output array, but "
"got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(outShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, outShapeInfo), 0,
"COSINE_DISTANCE_LOSS OP: shapes of weights and output arrays should be broadcastable, but got weights = %s "
"and output = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(outShapeInfo).c_str());
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(cosine_distance_loss_grad, 3, 3, false, 0, 2) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
int dim = INT_ARG(1); // axis along which sum will be made
if (dim < 0) dim += labels->rankOf();
std::vector<LongType> dimensions = {dim};
// input validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"COSINE_DISTANCE_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"COSINE_DISTANCE_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, "
"but got %i instead!",
reductionMode);
auto lossShapeInfo = ShapeUtils::evalReduceShapeInfo(predictions->ordering(), &dimensions, predictions->shapeInfo(),
true, false, block.getWorkspace());
// weights array can be single scalar or has the same shape as loss, and must be broadcastable to loss shape
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == shape::rank(lossShapeInfo), 0,
"COSINE_DISTANCE_LOSS_GRAD OP: weights array should be scalar or have the same rank as loss array, but "
"got %i and %i correspondingly!",
weights->rankOf(), shape::rank(lossShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(weights->shapeInfo(), lossShapeInfo), 0,
"COSINE_DISTANCE_LOSS_GRAD OP: shapes of weights and loss arrays should be broadcastable, but got "
"weights = %s and loss = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(lossShapeInfo).c_str());
// input dimension can't be larger than labels/predictions/weights rank
REQUIRE_TRUE(dim < labels->rankOf(), 0,
"COSINE_DISTANCE_LOSS_GRAD OP: input reduction dimension (got %i) must be < labels rank %i!", dim,
labels->rankOf());
std::vector<LongType> dims;
dims.push_back(dim);
NDArray* predLabels = (*predictions) * (*labels);
NDArray* dotProduct = predLabels->reduceAlongDimension(reduce::Sum, &dims, true);
delete predLabels;
NDArray* E = 1. - (*dotProduct);
delete dotProduct;
// perform weights broadcasting/tile to E if it is necessary
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(E))
weightsBroad = new NDArray(weights->tileToShape(E->shapeInfo()));
NDArray negLabels = -(*labels);
NDArray negPreds = -(*predictions);
dLdp->assign(&negLabels);
dLdl->assign(&negPreds);
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
NDArray* dLdpWeighted = (*dLdp) * (*weightsBroad);
dLdp->assign(dLdpWeighted);
delete dLdpWeighted;
NDArray* dLdlWeighted = (*dLdl) * (*weightsBroad);
dLdl->assign(dLdlWeighted);
delete dLdlWeighted;
if (weights->isScalar() || weights->lengthOf() == 1) {
auto* sumE = E->reduceNumber(reduce::Sum);
dLdw->assign(sumE);
delete sumE;
} else {
if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(E);
}
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * E->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
NDArray* temp = (*weightsBroad) / (*sum);
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*temp);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete temp;
if (weights->isScalar() || weights->lengthOf() == 1) {
*dLdw = 0.;
} else {
if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
gradTemp->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete gradTemp;
} else {
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(gradTemp);
delete gradTemp;
}
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
NDArray* temp = (*weightsBroad) / numOfNonZeroWeights;
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*temp);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete temp;
if (weights->isScalar() || weights->lengthOf() == 1) {
auto* sumE = E->reduceNumber(reduce::Sum);
auto* result = (*sumE) / numOfNonZeroWeights;
dLdw->assign(result);
delete result;
delete sumE;
} else {
if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
NDArray* dLdwResult = (*dLdw) / numOfNonZeroWeights;
dLdw->assign(dLdwResult);
delete dLdwResult;
} else {
NDArray* result = (*E) / numOfNonZeroWeights;
dLdw->assign(result);
delete result;
}
}
}
break;
}
}
delete E;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(cosine_distance_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(cosine_distance_loss_grad) {
/// labels and predictions must have the same shapes
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
int dim = INT_ARG(1);
if (dim < 0) dim += labelsShapeInfo[0];
std::vector<LongType> dimensions = {dim};
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"COSINE_DISTANCE_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
auto lossShapeInfo = ShapeUtils::evalReduceShapeInfo(shape::order(predictionsShapeInfo), &dimensions,
predictionsShapeInfo, true, false, block.getWorkspace());
// weights array can be single scalar or has the same rank as loss, and must be broadcastable to loss
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(lossShapeInfo), 0,
"COSINE_DISTANCE_LOSS_GRAD OP: weights array should be scalar or have the same rank as loss array, but "
"got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(lossShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, lossShapeInfo),
0,
"COSINE_DISTANCE_LOSS_GRAD OP: shapes of weights and loss arrays should be broadcastable, but got "
"weights = %s and loss = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(lossShapeInfo).c_str());
// input dimension can't be larger than labels/predictions/weights rank
REQUIRE_TRUE(dim < labelsShapeInfo[0], 0,
"COSINE_DISTANCE_LOSS_GRAD OP: input reduction dimension (got %i) must be < labels rank %i!", dim,
labelsShapeInfo[0]);
auto outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
auto dLdpShapeInfo = ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
auto dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
auto dLdlShapeInfo = ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(CONSTANT(dLdpShapeInfo), CONSTANT(dLdwShapeInfo), CONSTANT(dLdlShapeInfo));
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,160 @@
/*******************************************************************************
* Copyright (c) 2021 Deeplearning4j Contributors
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://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.
*
* SPDX-License-Identifier: Apache-2.0
*******************************************************************************/
//
// @author AbdelRauf
//
#include <ops/declarable/CustomOperations.h>
#include <ops/declarable/helpers/ctc.h>
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_ctc_loss)
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(ctc_loss, 4, 1, false, 0, 1) {
auto targetLabels = INPUT_VARIABLE(0);
auto logitInput = INPUT_VARIABLE(1);
auto targetLabelLengths = INPUT_VARIABLE(2);
auto logitInputLengths = INPUT_VARIABLE(3);
auto outputLosses = OUTPUT_VARIABLE(0);
int blankIndex = INT_ARG(0);
REQUIRE_TRUE(
targetLabels->rankOf() == 2, 0,
"CtcLoss: target labels fails to meet rank requirement (batch_size, max_label_sequence_length): %i == 2 ",
targetLabels->rankOf());
REQUIRE_TRUE(logitInput->rankOf() == 3, 0,
"CtcLoss: logit Input fails to meet rank requirement (batch_size, frames, classes): %i == 3 ",
logitInput->rankOf());
REQUIRE_TRUE(targetLabelLengths->rankOf() == 1, 0,
"CtcLoss: target label length fails to meet rank requirement (batch_size): %i == 1 ",
targetLabelLengths->rankOf());
REQUIRE_TRUE(logitInputLengths->rankOf() == 1, 0,
"CtcLoss: logit Input lengths fails to meet rank requirement (batch_size): %i == 1 ",
logitInputLengths->rankOf());
auto batchSize0 = targetLabels->sizeAt(0);
auto batchSize1 = logitInput->sizeAt(0);
auto batchSize2 = targetLabelLengths->sizeAt(0);
auto batchSize3 = logitInputLengths->sizeAt(0);
auto batchSize4 = outputLosses->sizeAt(0);
bool check_batches = (batchSize0 == batchSize1) && (batchSize2 == batchSize3);
check_batches = check_batches && (batchSize0 == batchSize4) && (batchSize0 == batchSize2);
REQUIRE_TRUE(check_batches, 0, "CtcLoss: All batch sizes should be %i", batchSize0);
REQUIRE_TRUE(outputLosses->isSameShape(targetLabelLengths), 0,
"CtcLoss: wrong shape of output array, expected is %s but got %s instead !",
ShapeUtils::shapeAsString(targetLabelLengths).c_str(), ShapeUtils::shapeAsString(outputLosses).c_str());
auto emptyGradients = NDArrayFactory::empty<float>();
helpers::ctcLoss(block, *logitInput, *targetLabels, *logitInputLengths, *targetLabelLengths, *outputLosses,
*emptyGradients, blankIndex);
delete emptyGradients;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(ctc_loss) {
getOpDescriptor()
->setAllowedInputTypes(0,{ALL_INDICES})
->setAllowedInputTypes(1, {ALL_FLOATS})
->setAllowedInputTypes(2,{ALL_INDICES})
->setAllowedInputTypes(3,{ALL_INDICES})
->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(ctc_loss) {
auto yShapeInfo = inputShape->at(1);
auto zShapeInfo = inputShape->at(2);
auto dtype = ArrayOptions::dataType(yShapeInfo);
auto ret = SHAPELIST(ConstantShapeHelper::getInstance().castToDataType(zShapeInfo, dtype));
return ret;
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(ctc_loss_grad, 4, 1, false, 0, 1) {
auto targetLabels = INPUT_VARIABLE(0);
auto logitInput = INPUT_VARIABLE(1);
auto targetLabelLengths = INPUT_VARIABLE(2);
auto logitInputLengths = INPUT_VARIABLE(3);
auto outputGradients = OUTPUT_VARIABLE(0);
int blankIndex = INT_ARG(0);
REQUIRE_TRUE(
targetLabels->rankOf() == 2, 0,
"CtcLoss: target labels fails to meet rank requirement (batch_size, max_label_sequence_length): %i == 2 ",
targetLabels->rankOf());
REQUIRE_TRUE(logitInput->rankOf() == 3, 0,
"CtcLoss: logit Input fails to meet rank requirement (batch_size, frames, classes): %i == 3 ",
logitInput->rankOf());
REQUIRE_TRUE(targetLabelLengths->rankOf() == 1, 0,
"CtcLoss: target label length fails to meet rank requirement (batch_size): %i == 1 ",
targetLabelLengths->rankOf());
REQUIRE_TRUE(logitInputLengths->rankOf() == 1, 0,
"CtcLoss: logit Input lengths fails to meet rank requirement (batch_size): %i == 1 ",
logitInputLengths->rankOf());
auto batchSize0 = targetLabels->sizeAt(0);
auto batchSize1 = logitInput->sizeAt(0);
auto batchSize2 = targetLabelLengths->sizeAt(0);
auto batchSize3 = logitInputLengths->sizeAt(0);
auto batchSize4 = outputGradients->sizeAt(0);
bool check_batches = (batchSize0 == batchSize1) && (batchSize2 == batchSize3);
check_batches = check_batches && (batchSize0 == batchSize4) && (batchSize0 == batchSize2);
REQUIRE_TRUE(check_batches, 0, "CtcLoss Gradient: All batch sizes should be %i", batchSize0);
REQUIRE_TRUE(outputGradients->isSameShape(logitInput), 0,
"CtcLoss Gradient: wrong shape of output array, expected is %s but got %s instead !",
ShapeUtils::shapeAsString(logitInput).c_str(), ShapeUtils::shapeAsString(outputGradients).c_str());
auto emptyLoss = NDArrayFactory::empty<float>();
helpers::ctcLoss(block, *logitInput, *targetLabels, *logitInputLengths, *targetLabelLengths, *emptyLoss,
*outputGradients, blankIndex);
delete emptyLoss;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(ctc_loss_grad) {
getOpDescriptor()
->setAllowedInputTypes({ALL_INDICES})
->setAllowedInputTypes(1, {ALL_FLOATS})
->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(ctc_loss_grad) {
auto yShapeInfo = inputShape->at(1);
auto dtype = ArrayOptions::dataType(yShapeInfo);
auto ret = SHAPELIST(ConstantShapeHelper::getInstance().castToDataType(yShapeInfo, dtype));
return ret;
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,447 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 23.11.2017
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_hinge_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(hinge_loss, 3, 1, false, 0, 1) {
auto logits = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// input validation
REQUIRE_TRUE(labels->isSameShape(logits), 0,
"HINGE_LOSS OP: labels and logits arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"HINGE_LOSS OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"HINGE_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got "
"weights = %s and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(
reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"HINGE_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, "
"but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to logits if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(logits))
weightsBroad = new NDArray(weights->tileToShape(logits->shapeInfo()));
// We first need to convert binary labels to -1/1 labels (as floats)
NDArray* labelsScaled = (*labels) * 2.f;
NDArray* labelsTransformed = (*labelsScaled) - 1.f;
delete labelsScaled;
NDArray* logitsScaled = (*labelsTransformed) * (*logits);
delete labelsTransformed;
NDArray* E = 1.f - (*logitsScaled);
delete logitsScaled;
E->applyScalar(scalar::RELU, 0.0f, E);
// multiply E on weights
NDArray* EWeighted = (*E) * (*weightsBroad);
switch (reductionMode) {
case 0: { // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(EWeighted);
break;
}
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
auto* sumResult = EWeighted->reduceNumber(reduce::Sum);
output->assign(sumResult);
delete sumResult;
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * EWeighted->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*output = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* result = (*sumE) / (*sum);
output->assign(result);
delete result;
delete sumE;
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = EWeighted->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
(*output) = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* result = (*sumE) / numOfNonZeroWeights;
output->assign(result);
delete result;
delete sumE;
}
break;
}
}
delete EWeighted;
delete E;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(hinge_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(hinge_loss) {
auto logitsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(
shape::shapeEquals(labelsShapeInfo, logitsShapeInfo), 0,
"HINGE_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"HINGE_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i and %i "
"correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"HINGE_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and labels = "
"%s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
LongType *outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the same shape as labels and predictions
outShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(hinge_loss_grad, 3, 3, false, 0, 1) {
auto logits = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
// inputs validation
REQUIRE_TRUE(
labels->isSameShape(logits), 0,
"HINGE_LOSS_GRAD OP: labels and logits arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"HINGE_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"HINGE_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(
reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"HINGE_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(logits))
weightsBroad = new NDArray(weights->tileToShape(logits->shapeInfo()));
// We first need to convert binary labels to -1/1 labels (as floats)
NDArray* labelsScaled = (*labels) * 2.f;
NDArray* z = (*labelsScaled) - 1.f;
delete labelsScaled;
NDArray* logitsScaled = (*z) * (*logits);
NDArray* E = 1.f - (*logitsScaled);
delete logitsScaled;
E->applyScalar(scalar::RELU, 0.0f, E);
// turn E into gradient mask
NDArray gradientMask(E->shapeInfo(), block.getWorkspace());
E->applyTransform(transform::Sign, &gradientMask);
// For dLdp
NDArray negZ = -(*z);
NDArray* dLdpTemp = negZ * gradientMask;
dLdp->assign(dLdpTemp);
delete dLdpTemp;
// For dLdl
NDArray* logitsScaled2 = (*logits) * 2.f;
NDArray* dLdlTemp = (*logitsScaled2) * gradientMask;
delete logitsScaled2;
NDArray dLdlNeg = -(*dLdlTemp);
dLdl->assign(&dLdlNeg);
delete dLdlTemp;
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
NDArray* dLdpWeighted = (*dLdp) * (*weightsBroad);
dLdp->assign(dLdpWeighted);
delete dLdpWeighted;
NDArray* dLdlWeighted = (*dLdl) * (*weightsBroad);
dLdl->assign(dLdlWeighted);
delete dLdlWeighted;
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
dLdw->assign(sumE);
delete sumE;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(E);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * E->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
NDArray* weightsDivSum = (*weightsBroad) / (*sum);
NDArray* dLdpResult = (*dLdp) * (*weightsDivSum);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*weightsDivSum);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete weightsDivSum;
if (weights->isScalar()) {
*dLdw = 0.;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
gradTemp->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete gradTemp;
} else {
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(gradTemp);
delete gradTemp;
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
auto* numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
auto* result = (*sumE) / double(numOfNonZeroWeights);
dLdw->assign(result);
delete result;
delete sumE;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
NDArray* dLdwResult = (*dLdw) / (*numOfNonZeroWeightsScalar);
dLdw->assign(dLdwResult);
delete dLdwResult;
} else {
auto* result = (*E) / (*numOfNonZeroWeightsScalar);
dLdw->assign(result);
delete result;
}
NDArray* temp = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*temp);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete temp;
delete numOfNonZeroWeightsScalar;
}
break;
}
}
delete E;
delete z;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(hinge_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(hinge_loss_grad) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"HINGE_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"HINGE_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"HINGE_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType *dLdpShapeInfo =
ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
LongType *dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
LongType *dLdlShapeInfo =
ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(dLdpShapeInfo, dLdwShapeInfo, dLdlShapeInfo);
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,477 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 23.11.2017
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_huber_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(huber_loss, 3, 1, false, 1, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// FIXME: double?
double delta = T_ARG(0);
// input validation
REQUIRE_TRUE(
labels->isSameShape(predictions), 0,
"HUBER_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"HUBER_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i and %i "
"correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"HUBER_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(
reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"HUBER_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to predictions if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
NDArray* error = (*predictions) - (*labels);
error->applyTransform(transform::Abs, error);
NDArray quadratic(error->shapeInfo(), block.getWorkspace());
error->applyScalar(scalar::MinPairwise, delta, &quadratic);
NDArray* quadraticSquared = quadratic * quadratic;
NDArray* scaledQuadratic = (*quadraticSquared) * 0.5f;
delete quadraticSquared;
NDArray* errorMinusQuadratic = (*error) - quadratic;
NDArray* linearTerm = (*errorMinusQuadratic) * delta;
delete errorMinusQuadratic;
NDArray* E = (*scaledQuadratic) + (*linearTerm);
delete scaledQuadratic;
delete linearTerm;
// multiply E on weights
NDArray* EWeighted = (*E) * (*weightsBroad);
delete E;
switch (reductionMode) {
case 0: { // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(EWeighted);
break;
}
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
auto* sumResult = EWeighted->reduceNumber(reduce::Sum);
output->assign(sumResult);
delete sumResult;
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * EWeighted->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*output = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* result = (*sumE) / (*sum);
output->assign(result);
delete result;
delete sumE;
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = EWeighted->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
(*output) = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* result = (*sumE) / double(numOfNonZeroWeights);
output->assign(result);
delete result;
delete sumE;
}
break;
}
}
delete EWeighted;
delete error;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(huber_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(huber_loss) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(
shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"HUBER_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"HUBER_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i and %i "
"correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"HUBER_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and labels = "
"%s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType * outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the same shape as labels and predictions
outShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(huber_loss_grad, 3, 3, false, 1, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
auto delta = T_ARG(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
// inputs validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"HUBER_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"HUBER_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"HUBER_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(
reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"HUBER_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
NDArray* diff = (*predictions) - (*labels);
NDArray absDiff(*diff);
absDiff.applyTransform(transform::Abs, &absDiff);
NDArray quadratic(absDiff);
absDiff.applyScalar(scalar::MinPairwise, delta, &quadratic);
NDArray* quadraticSquared = quadratic * quadratic;
NDArray* scaledQuadratic = (*quadraticSquared) * 0.5f;
delete quadraticSquared;
NDArray* absDiffMinusQuadratic = absDiff - quadratic;
NDArray* linearTerm = (*absDiffMinusQuadratic) * delta;
delete absDiffMinusQuadratic;
NDArray* E = (*scaledQuadratic) + (*linearTerm);
delete scaledQuadratic;
delete linearTerm;
NDArray lteMask(diff->shapeInfo(), BOOL, true, block.launchContext());
absDiff.applyScalar(scalar::LessThanOrEqual, delta, &lteMask);
NDArray gtMask(diff->shapeInfo(), BOOL, true, block.launchContext());
absDiff.applyScalar(scalar::GreaterThan, delta, &gtMask);
NDArray signDiff(*diff);
diff->applyTransform(transform::Sign, &signDiff);
auto gtMaskFloat = gtMask.cast(diff->dataType());
auto lteMaskFloat = lteMask.cast(diff->dataType());
// For dLdp
NDArray* lteDiff = (*lteMaskFloat) * (*diff);
NDArray* gtSignScaled = (*gtMaskFloat) * delta;
NDArray* gtTerm = (*gtSignScaled) * signDiff;
delete gtSignScaled;
NDArray* dLdpTemp = (*lteDiff) + (*gtTerm);
delete lteDiff;
delete gtTerm;
dLdp->assign(dLdpTemp);
delete dLdpTemp;
// For dLdl
NDArray negLteMaskFloat = -(*lteMaskFloat);
NDArray* negLteDiff = negLteMaskFloat * (*diff);
NDArray* negGtSignScaled = (*gtMaskFloat) * delta;
NDArray* negGtTerm = (*negGtSignScaled) * signDiff;
delete negGtSignScaled;
NDArray negGtTermNeg = -(*negGtTerm);
delete negGtTerm;
NDArray* dLdlTemp = (*negLteDiff) + negGtTermNeg;
delete negLteDiff;
dLdl->assign(dLdlTemp);
delete dLdlTemp;
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
NDArray* dLdpWeighted = (*dLdp) * (*weightsBroad);
dLdp->assign(dLdpWeighted);
delete dLdpWeighted;
NDArray* dLdlWeighted = (*dLdl) * (*weightsBroad);
dLdl->assign(dLdlWeighted);
delete dLdlWeighted;
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
dLdw->assign(sumE);
delete sumE;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(E);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * E->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
NDArray* weightsDivSum = (*weightsBroad) / (*sum);
NDArray* dLdpResult = (*dLdp) * (*weightsDivSum);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*weightsDivSum);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete weightsDivSum;
if (weights->isScalar()) {
*dLdw = 0.;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
gradTemp->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete gradTemp;
} else {
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(gradTemp);
delete gradTemp;
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
auto numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
auto* result = (*sumE) / double(numOfNonZeroWeights);
dLdw->assign(result);
delete result;
delete sumE;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
NDArray* dLdwResult = (*dLdw) / (*numOfNonZeroWeightsScalar);
dLdw->assign(dLdwResult);
delete dLdwResult;
} else {
auto* result = (*E) / (*numOfNonZeroWeightsScalar);
dLdw->assign(result);
delete result;
}
NDArray* temp = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*temp);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete temp;
delete numOfNonZeroWeightsScalar;
}
break;
}
}
delete E;
delete diff;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(huber_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(huber_loss_grad) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"HUBER_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"HUBER_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"HUBER_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
auto dLdpShapeInfo = ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
auto dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
auto dLdlShapeInfo = ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(dLdpShapeInfo, dLdwShapeInfo, dLdlShapeInfo);
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,55 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// Created by GS <sgazeos@gmail.com> 31.01.2018
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_l2_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
CUSTOM_OP_IMPL(l2_loss, 1, 1, false, 0, 0) {
auto input = INPUT_VARIABLE(0);
auto output = OUTPUT_VARIABLE(0);
REQUIRE_TRUE(output->isScalar(), 0, "Rank output should be scalar");
// FIXME: output should be used directly here, to avoid sum
auto* result = input->reduceNumber(reduce::SquaredNorm);
NDArray* divided = (*result) / 2.;
output->assign(divided);
delete divided;
delete result;
return Status::OK;
}
DECLARE_SHAPE_FN(l2_loss) {
return SHAPELIST(ConstantShapeHelper::getInstance().scalarShapeInfo(ArrayOptions::dataType(inputShape->at(0))));
}
DECLARE_TYPES(l2_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,472 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 23.11.2017
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_log_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(log_loss, 3, 1, false, 1, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// FIXME: double?
double epsilon = T_ARG(0);
// input validation
REQUIRE_TRUE(
labels->isSameShape(predictions), 0,
"LOG_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"LOG_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i and %i "
"correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"LOG_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(
reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"LOG_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to predictions if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
// E = -labels * log(predictions + epsilon) - (1 - labels) * log(1 + epsilon - predictions)
// Break this into steps:
NDArray* predPlusEps = (*predictions) + epsilon;
NDArray* logPredPlusEps = predPlusEps->transform(transform::Log);
delete predPlusEps;
NDArray negLabels = -(*labels); // unary negation returns value
NDArray* term1 = negLabels * (*logPredPlusEps);
delete logPredPlusEps;
NDArray* oneMinusLabels = 1. - (*labels);
NDArray* onePlusEpsMinusPred = (1. + epsilon) - (*predictions);
NDArray* logOnePlusEpsMinusPred = onePlusEpsMinusPred->transform(transform::Log);
delete onePlusEpsMinusPred;
NDArray* term2 = (*oneMinusLabels) * (*logOnePlusEpsMinusPred);
delete oneMinusLabels;
delete logOnePlusEpsMinusPred;
NDArray* E_ptr = (*term1) - (*term2);
delete term1;
delete term2;
NDArray E = *E_ptr;
delete E_ptr;
// multiply E on weights
E *= *weightsBroad;
switch (reductionMode) {
case 0: { // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(&E);
break;
}
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
E.reduceNumber(reduce::Sum, output);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
double sum;
if (weights->isScalar()) {
sum = weights->e<double>(0) * E.lengthOf();
} else {
NDArray* sumPtr = weightsBroad->reduceNumber(reduce::Sum);
sum = sumPtr->e<double>(0);
delete sumPtr;
}
if (sum == 0.)
*output = 0.;
else {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / sum;
delete eSum;
output->assign(result);
delete result;
}
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countNonZero = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countNonZero->e<LongType>(0);
delete countNonZero;
}
if (numOfNonZeroWeights == 0)
(*output) = 0.;
else {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / double(numOfNonZeroWeights);
delete eSum;
output->assign(result);
delete result;
}
break;
}
}
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(log_loss) { getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS}); }
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(log_loss) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(
shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"LOG_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"LOG_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i and %i "
"correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"LOG_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and labels = %s "
"instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType* outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the same shape as labels and predictions
outShapeInfo = ConstantShapeHelper::getInstance()
.bufferForShapeInfo(outType, shape::order(labelsShapeInfo), shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))
->primary();
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(log_loss_grad, 3, 3, false, 1, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
// FIXME: double?
double epsilon = T_ARG(0);
// input validation
REQUIRE_TRUE(
labels->isSameShape(predictions), 0,
"LOG_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"LOG_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got %i and "
"%i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"LOG_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(
reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"LOG_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
NDArray* predictPlusEps_ptr = (*predictions) + epsilon;
NDArray predictPlusEps = *predictPlusEps_ptr;
delete predictPlusEps_ptr;
NDArray* oneMinusLabels_ptr = 1. - (*labels);
NDArray oneMinusLabels = *oneMinusLabels_ptr;
delete oneMinusLabels_ptr;
NDArray* onePlusEpsMinusPredict_ptr = (1. + epsilon) - (*predictions);
NDArray onePlusEpsMinusPredict = *onePlusEpsMinusPredict_ptr;
delete onePlusEpsMinusPredict_ptr;
// dE_i/dp_i = (1-y_i)/(1-p_i+eps) - y_i/(p_i+eps)
NDArray* oneMinusDiv = oneMinusLabels / onePlusEpsMinusPredict;
NDArray* labelsDiv = (*labels) / predictPlusEps;
NDArray* dEdp = (*oneMinusDiv) - (*labelsDiv);
delete oneMinusDiv;
delete labelsDiv;
dLdp->assign(dEdp);
delete dEdp;
// dE_i/dy_i = log((1+2eps)/(p_i+eps) - 1)
double onePlus2Eps = 1. + 2. * epsilon;
NDArray* ratio = onePlus2Eps / predictPlusEps;
NDArray* ratioMinus1 = (*ratio) - 1.;
delete ratio;
ratioMinus1->applyTransform(transform::Log, dLdl);
delete ratioMinus1;
// Compute E for gradient calculations
NDArray* logPredPlusEps = predictPlusEps.transform(transform::Log);
NDArray* logOnePlusEpsMinusPred = onePlusEpsMinusPredict.transform(transform::Log);
NDArray negLabels = -(*labels); // unary negation returns value
NDArray* term1 = negLabels * (*logPredPlusEps);
delete logPredPlusEps;
NDArray* term2 = oneMinusLabels * (*logOnePlusEpsMinusPred);
delete logOnePlusEpsMinusPred;
NDArray* E_ptr = (*term1) - (*term2);
delete term1;
delete term2;
NDArray E = *E_ptr;
delete E_ptr;
// process 3 possible reduction modes below
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
*dLdp *= *weightsBroad;
*dLdl *= *weightsBroad;
if (weights->isScalar()) {
NDArray* eSum = E.reduceNumber(reduce::Sum);
dLdw->assign(eSum);
delete eSum;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(&E);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
double sum;
if (weights->isScalar()) {
sum = weights->e<double>(0) * E.lengthOf();
} else {
NDArray* sumPtr = weightsBroad->reduceNumber(reduce::Sum);
sum = sumPtr->e<double>(0);
delete sumPtr;
}
if (sum == 0.) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
NDArray* weightsDivSum = (*weightsBroad) / sum;
NDArray temp = *weightsDivSum;
delete weightsDivSum;
*dLdp *= temp;
*dLdl *= temp;
if (weights->isScalar())
*dLdw = 0.;
else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
// Compute (E * sum - (E * weightsBroad).reduceNumber(Sum)) / (sum * sum)
NDArray* ETimesSum = E * sum;
NDArray* ETimesWeights = E * (*weightsBroad);
NDArray* ETimesWeightsSum = ETimesWeights->reduceNumber(reduce::Sum);
delete ETimesWeights;
NDArray* numerator = (*ETimesSum) - (*ETimesWeightsSum);
delete ETimesSum;
delete ETimesWeightsSum;
double sumSquared = sum * sum;
NDArray* result = (*numerator) / sumSquared;
delete numerator;
result->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete result;
} else {
// Compute (E * sum - (E * weightsBroad).reduceNumber(Sum)) / (sum * sum)
NDArray* ETimesSum = E * sum;
NDArray* ETimesWeights = E * (*weightsBroad);
NDArray* ETimesWeightsSum = ETimesWeights->reduceNumber(reduce::Sum);
delete ETimesWeights;
NDArray* numerator = (*ETimesSum) - (*ETimesWeightsSum);
delete ETimesSum;
delete ETimesWeightsSum;
double sumSquared = sum * sum;
NDArray* result = (*numerator) / sumSquared;
delete numerator;
dLdw->assign(result);
delete result;
}
}
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countNonZero = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countNonZero->e<LongType>(0);
delete countNonZero;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
auto* numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / numOfNonZeroWeights;
delete eSum;
dLdw->assign(result);
delete result;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
*dLdw /= *numOfNonZeroWeightsScalar;
} else {
NDArray* EDivNum = E / (*numOfNonZeroWeightsScalar);
dLdw->assign(EDivNum);
delete EDivNum;
NDArray* weightsDivNum = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
NDArray temp = *weightsDivNum;
delete weightsDivNum;
*dLdp *= temp;
*dLdl *= temp;
}
delete numOfNonZeroWeightsScalar;
}
break;
}
}
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(log_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(log_loss_grad) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(
shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"LOG_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"LOG_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got %i and "
"%i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"LOG_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and labels "
"= %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
auto dLdpShapeInfo = ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
auto dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
auto dLdlShapeInfo = ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(CONSTANT(dLdpShapeInfo), CONSTANT(dLdwShapeInfo), CONSTANT(dLdlShapeInfo));
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,426 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author raver119@gmail.com
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_log_poisson_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
CUSTOM_OP_IMPL(log_poisson_loss, 3, 1, true, 0, 1) {
auto log_predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
bool computeFullLoss = false;
if (block.numI() > 1) computeFullLoss = INT_ARG(1) != 0;
// inputs validation
REQUIRE_TRUE(labels->isSameShape(log_predictions), 0,
"LOG_POISSON_LOSS OP: labels and log_predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(log_predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"LOG_POISSON_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"LOG_POISSON_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"LOG_POISSON_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but got "
"%i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(log_predictions))
weightsBroad = new NDArray(weights->tileToShape(log_predictions->shapeInfo()));
NDArray E(labels->shapeInfo(), block.getWorkspace());
if (computeFullLoss)
labels->applyPairwiseTransform(pairwise::LogPoissonLossFull, log_predictions, &E);
else
labels->applyPairwiseTransform(pairwise::LogPoissonLoss, log_predictions, &E);
// multiply E on weights
E *= *weightsBroad;
switch (reductionMode) {
case 0: { // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(&E);
break;
}
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
E.reduceNumber(reduce::Sum, output);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum = nullptr;
if (weights->isScalar()) {
sum = (*weights) * E.lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*output = 0.;
} else {
NDArray* sumResult = E.reduceNumber(reduce::Sum);
NDArray* assign = (*sumResult) / (*sum);
delete sumResult;
output->assign(assign);
delete assign;
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0)
(*output) = 0.;
else {
NDArray* sumResult = E.reduceNumber(reduce::Sum);
NDArray* assign = (*sumResult) / double(numOfNonZeroWeights);
delete sumResult;
output->assign(assign);
delete assign;
}
break;
}
}
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(log_poisson_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(log_poisson_loss) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"LOG_POISSON_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"LOG_POISSON_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"LOG_POISSON_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType* outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the same shape as labels and predictions
outShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(log_poisson_loss_grad, 3, 3, false, 0, 1) {
auto log_predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
bool computeFullLoss = false;
if (block.numI() > 1) computeFullLoss = INT_ARG(1) != 0;
// inputs validation
REQUIRE_TRUE(labels->isSameShape(log_predictions), 0,
"LOG_POISSON_LOSS_GRAD OP: labels and log_predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(log_predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"LOG_POISSON_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"LOG_POISSON_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights "
"= %s and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"LOG_POISSON_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but "
"got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(log_predictions))
weightsBroad = new NDArray(weights->tileToShape(log_predictions->shapeInfo()));
NDArray E(labels->shapeInfo(), block.getWorkspace());
if (computeFullLoss) {
labels->applyPairwiseTransform(pairwise::LogPoissonLossFull, log_predictions, &E);
NDArray rDiv(labels->shapeInfo(), block.getWorkspace());
labels->applyScalar(scalar::ReverseDivide, 0.5f, &rDiv);
// For dLdl - first case
NDArray* logLabels = labels->transform(transform::Log);
NDArray negLogPredictions = -(*log_predictions); // unary negation returns value
NDArray* temp1 = rDiv + (*logLabels);
delete logLabels;
NDArray* dLdlTemp1 = (*temp1) + negLogPredictions;
delete temp1;
dLdl->assign(dLdlTemp1);
delete dLdlTemp1;
} else {
labels->applyPairwiseTransform(pairwise::LogPoissonLoss, log_predictions, &E);
// For dLdl - second case
NDArray dLdlTemp2 = -(*log_predictions); // unary negation returns value
dLdl->assign(&dLdlTemp2);
}
// For dLdp
NDArray* expResult = log_predictions->transform(transform::Exp);
NDArray* dLdpTemp = (*expResult) - (*labels);
delete expResult;
dLdp->assign(dLdpTemp);
delete dLdpTemp;
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
*dLdp *= *weightsBroad;
*dLdl *= *weightsBroad;
if (weights->isScalar()) {
NDArray* assign = E.reduceNumber(reduce::Sum);
dLdw->assign(assign);
delete assign;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(&E);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum = nullptr;
if (weights->isScalar()) {
sum = (*weights) * E.lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
NDArray* weightsBroadDivSum = (*weightsBroad) / (*sum);
*dLdp *= *weightsBroadDivSum;
*dLdl *= *weightsBroadDivSum;
delete weightsBroadDivSum;
if (weights->isScalar())
*dLdw = 0.;
else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
NDArray* eMulWeightsBroad = E * (*weightsBroad);
NDArray* sumReduced = eMulWeightsBroad->reduceNumber(reduce::Sum);
delete eMulWeightsBroad;
NDArray* eMulSum = E * (*sum);
NDArray* numerator = (*eMulSum) - (*sumReduced);
delete eMulSum;
delete sumReduced;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* result = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
result->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete result;
} else {
NDArray* eMulWeightsBroad = E * (*weightsBroad);
NDArray* sumReduced = eMulWeightsBroad->reduceNumber(reduce::Sum);
delete eMulWeightsBroad;
NDArray* eMulSum = E * (*sum);
NDArray* numerator = (*eMulSum) - (*sumReduced);
delete eMulSum;
delete sumReduced;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* assign = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(assign);
delete assign;
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
auto* numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
NDArray* sumResult = E.reduceNumber(reduce::Sum);
NDArray* assign = (*sumResult) / double(numOfNonZeroWeights);
delete sumResult;
dLdw->assign(assign);
delete assign;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
*dLdw /= *numOfNonZeroWeightsScalar;
} else {
NDArray* assign = E / (*numOfNonZeroWeightsScalar);
dLdw->assign(assign);
delete assign;
}
NDArray* temp = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
*dLdp *= *temp;
*dLdl *= *temp;
delete temp;
delete numOfNonZeroWeightsScalar;
}
break;
}
}
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(log_poisson_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(log_poisson_loss_grad) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"LOG_POISSON_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"LOG_POISSON_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"LOG_POISSON_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
auto dLdpShapeInfo = ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
auto dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
auto dLdlShapeInfo = ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(dLdpShapeInfo, dLdwShapeInfo, dLdlShapeInfo);
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,550 @@
#pragma clang diagnostic push
#pragma ide diagnostic ignored "cert-err58-cpp"
/*
* ******************************************************************************
* *
* *
* * This program and the accompanying materials are made available under the
* * terms of the Apache License, Version 2.0 which is available at
* * https://www.apache.org/licenses/LICENSE-2.0.
* *
* * See the NOTICE file distributed with this work for additional
* * information regarding copyright ownership.
* * 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.
* *
* * SPDX-License-Identifier: Apache-2.0
* *****************************************************************************
*/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 24.11.2017
// @author Paul Dubs
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_mean_pairwssqerr_loss)
#include <ops/declarable/CustomOperations.h>
#include <iostream>
#include <numeric>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(mean_pairwssqerr_loss, 3, 1, false, 0, 1) {
/*
* Implementation of mean pairwise squared error loss
*
* For context on where this loss function may be useful see:
*
* Wei, Z., Zhang, J., Shen, X., Lin, Z., Mech, R., Hoai, M. and Samaras, D., 2018.
* Good view hunting: learning photo composition from dense view pairs. In Proceedings of the IEEE Conference on
* Computer Vision and Pattern Recognition (pp. 5437-5446).
*
* The paper defines the loss function as:
*
* L(y,q) = 1/((n*(n-1))/2) * (sum_(i,j=1..n,i!=j)((y_i - y_j) - (q_i - q_j))^2)
*
* with y: predictions, q: labels, n: length of y and q
*
* As creating those pairs is computationally expensive, we implement a mathematically equivalent function:
*
* L(y,q) = 4/(n*(n-1)) * (n * sum (y_i - q_i)^2 - (sum y_i - q_i)^2)
*
* This equivalency can be derived as:
*
* sum_(i,j=1..n,i!=j)((y_i - y_j) - (q_i - q_j))^2 = sum_(i,j=1..n,i!=j)((y_i - q_i) - (y_j - q_j))^2
*
* To simplify the following equations we use
*
* sum_(i,j=1..n,i!=j)(d_i - d_j)^2 = sum_(i,j=1..n,i!=j)(d_i^2 + d_j^2 - 2*d_i*d_j)
*
* Due to the pairings each element will appear as both d_i and d_j exactly n-1 times. This allows us to split the
* sum:
*
* sum_(i,j=1..n,i!=j)(d_i^2 + d_j^2 - 2*d_i*d_j) = 2*(n-1)*sum d_i^2 - 2 * sum_(i,j=1..n,i!=j) d_i * d_j
* = 2*((n-1) * sum d_i^2 - sum_(i,j=1..n,i!=j) d_i * d_j)
*
* Now we use the following equivalency:
*
* (sum d_i)^2 = sum d_i^2 + sum_(i,j=1..n,i!=j) d_i * d_j
*
* This allows us to now use sum d_i^2 and (sum d_i)^2 as a quick way to calculate the sum:
*
* (n-1) * sum d_i^2 - sum_(i,j=1..n,i!=j) d_i * d_j = n * sum d_i^2 - (sum d_i)^2
*
* And by substituting it into the original definition we get:
*
* 1/((n*(n-1))/2) * 2*(n * sum d_i^2 - (sum d_i)^2)
*
* Which can be again simplified to
*
* 4/(n*(n-1)) * (n * sum d_i^2 - (sum d_i)^2)
*
* After substituting d_i back to (y_i - q_i) this results in the function that we actually implement.
*
*/
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// input validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"MEAN_PAIRWSSQERR_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"MEAN_PAIRWSSQERR_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but "
"got %i instead!",
reductionMode);
if (labels->rankOf() == 1) { // If labels and predictions are of rank 1, it means that all data entries are 0-tensor
// (scalar) so that the result of becomes always zero.
*output = 0.;
return Status::OK;
}
std::vector<LongType> zero;
zero.push_back(0);
std::vector<LongType> *reductionIdx = ShapeUtils::evalDimsToExclude(labels->rankOf(),1,zero.data());
auto n = double(labels->sizeAt(1));
// Compute diffs = predictions - labels
NDArray* diffs_ptr = (*predictions) - (*labels);
NDArray diffs = *diffs_ptr;
delete diffs_ptr;
// Compute sumOfSquares = sum(diffs^2)
NDArray* diffsSquared = diffs * diffs;
NDArray* sumOfSquares_ptr = diffsSquared->reduceAlongDimension(reduce::Sum, reductionIdx, true);
delete diffsSquared;
NDArray sumOfSquares = *sumOfSquares_ptr;
delete sumOfSquares_ptr;
// Compute squareOfSum = (sum(diffs))^2
NDArray* squareOfSum_ptr = diffs.reduceAlongDimension(reduce::Sum, reductionIdx, true);
NDArray squareOfSum = *squareOfSum_ptr;
delete squareOfSum_ptr;
squareOfSum.applyScalar(scalar::Pow, 2, &squareOfSum);
delete reductionIdx;
// Compute E = ((sumOfSquares * n) - squareOfSum) * (4 / (n * (n - 1)))
NDArray* sumOfSquaresTimesN = sumOfSquares * n;
NDArray* numerator = (*sumOfSquaresTimesN) - squareOfSum;
delete sumOfSquaresTimesN;
NDArray* E_ptr = (*numerator) * (4 / (n * (n - 1)));
delete numerator;
NDArray E = *E_ptr;
delete E_ptr;
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == E.rankOf(), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: weights array should be scalar or have the same rank as results array, "
"but got %i and %i correspondingly!",
weights->rankOf(), E.rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, E), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got "
"weights = %s and results = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(&E).c_str());
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(E)) weightsBroad = new NDArray(weights->tileToShape(E.shapeInfo()));
E *= *weightsBroad;
switch (reductionMode) {
case 0: // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(&E);
break;
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
E.reduceNumber(reduce::Sum, output);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sumPtr = nullptr;
if (weights->isScalar()) {
NDArray* weightTimesLen = (*weights) * E.lengthOf();
sumPtr = weightTimesLen;
} else {
sumPtr = weightsBroad->reduceNumber(reduce::Sum);
}
if (sumPtr->e<double>(0) == 0.) {
(*output) = 0.;
} else {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / (*sumPtr);
delete eSum;
output->assign(result);
delete result;
}
delete sumPtr;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countNonZero = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countNonZero->e<LongType>(0);
delete countNonZero;
}
if (numOfNonZeroWeights == 0)
(*output) = 0.;
else {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / double(numOfNonZeroWeights);
delete eSum;
output->assign(result);
delete result;
}
break;
}
}
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(mean_pairwssqerr_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(mean_pairwssqerr_loss) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"MEAN_PAIRWSSQERR_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType *outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the shape as labels and logits minus last dimension
std::vector<LongType> dimensions = {-1};
outShapeInfo = ShapeUtils::evalReduceShapeInfo(shape::order(predictionsShapeInfo), &dimensions, predictionsShapeInfo,
false, true, block.getWorkspace());
// weights array can be single scalar or has the same rank as output, and must be broadcastable to output
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(outShapeInfo), 0,
"MEAN_PAIRWSSQERR_LOSS OP: weights array should be scalar or have the same rank as output array, but "
"got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(outShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, outShapeInfo), 0,
"MEAN_PAIRWSSQERR_LOSS OP: shapes of weights and output arrays should be broadcastable, but got weights = %s "
"and output = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(outShapeInfo).c_str());
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(mean_pairwssqerr_loss_grad, 3, 3, false, 0, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
// inputs validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, "
"but got %i instead!",
reductionMode);
auto n = double(labels->sizeAt(1));
// Compute diffs = predictions - labels
NDArray* diffs_ptr = (*predictions) - (*labels);
NDArray diffs = *diffs_ptr;
delete diffs_ptr;
std::vector<LongType> dims2;
dims2.push_back(0);
std::vector<LongType> *reductionIdx = ShapeUtils::evalDimsToExclude(labels->rankOf(), 1,dims2.data());
// Compute sumOfSquares
NDArray* diffsSquared = diffs * diffs;
NDArray* sumOfSquares_ptr = diffsSquared->reduceAlongDimension(reduce::Sum, reductionIdx, true);
delete diffsSquared;
NDArray sumOfSquares = *sumOfSquares_ptr;
delete sumOfSquares_ptr;
// Compute squareOfSum
NDArray* squareOfSum_ptr = diffs.reduceAlongDimension(reduce::Sum, reductionIdx, true);
NDArray squareOfSum = *squareOfSum_ptr;
delete squareOfSum_ptr;
squareOfSum.applyScalar(scalar::Pow, 2, &squareOfSum);
// Compute E
NDArray* sumOfSquaresTimesN = sumOfSquares * n;
NDArray* eTerm1 = (*sumOfSquaresTimesN) - squareOfSum;
delete sumOfSquaresTimesN;
NDArray* E_ptr = (*eTerm1) * (4 / (n * (n - 1)));
delete eTerm1;
NDArray E = *E_ptr;
delete E_ptr;
// Compute gradients for predictions
NDArray* sumPred_ptr = predictions->reduceAlongDimension(reduce::Sum, reductionIdx, true);
NDArray sumPred = *sumPred_ptr;
delete sumPred_ptr;
NDArray* sumLabel_ptr = labels->reduceAlongDimension(reduce::Sum, reductionIdx, true);
NDArray sumLabel = *sumLabel_ptr;
delete sumLabel_ptr;
delete reductionIdx;
// Compute dLdp = ((diffs * n) - sumPred + sumLabel) * (8 / (n * (n - 1)))
NDArray* diffsTimesN = diffs * n;
NDArray* term1 = (*diffsTimesN) - sumPred;
delete diffsTimesN;
NDArray* term2 = (*term1) + sumLabel;
delete term1;
NDArray* dLdpTemp = (*term2) * (8 / (n * (n - 1)));
delete term2;
dLdp->assign(dLdpTemp);
delete dLdpTemp;
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == E.rankOf(), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: weights array should be scalar or have the same rank as results array, "
"but got %i and %i correspondingly!",
weights->rankOf(), E.rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, E), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got "
"weights = %s and results = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(&E).c_str());
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(E)) weightsBroad = new NDArray(weights->tileToShape(E.shapeInfo()));
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
*dLdp *= *weightsBroad;
if (weights->isScalar()) {
NDArray* eSum = E.reduceNumber(reduce::Sum);
dLdw->assign(eSum);
delete eSum;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(&E);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum = nullptr;
if (weights->isScalar()) {
NDArray* weightTimesLen = (*weights) * E.lengthOf();
sum = weightTimesLen;
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdw = 0.;
} else {
NDArray* weightsDivSum = (*weightsBroad) / (*sum);
*dLdp *= *weightsDivSum;
delete weightsDivSum;
if (weights->isScalar())
*dLdw = 0.;
else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
// Compute (E * sum - (E * weightsBroad).reduceNumber(Sum)) / (sum * sum)
NDArray* ETimesSum = E * (*sum);
NDArray* ETimesWeights = E * (*weightsBroad);
NDArray* ETimesWeightsSum = ETimesWeights->reduceNumber(reduce::Sum);
delete ETimesWeights;
NDArray* numerator = (*ETimesSum) - (*ETimesWeightsSum);
delete ETimesSum;
delete ETimesWeightsSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* result = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
result->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete result;
} else {
// Compute (E * sum - (E * weightsBroad).reduceNumber(Sum)) / (sum * sum)
NDArray* ETimesSum = E * (*sum);
NDArray* ETimesWeights = E * (*weightsBroad);
NDArray* ETimesWeightsSum = ETimesWeights->reduceNumber(reduce::Sum);
delete ETimesWeights;
NDArray* numerator = (*ETimesSum) - (*ETimesWeightsSum);
delete ETimesSum;
delete ETimesWeightsSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* result = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(result);
delete result;
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countNonZero = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countNonZero->e<LongType>(0);
delete countNonZero;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdw = 0.;
} else {
auto* numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / double(numOfNonZeroWeights);
delete eSum;
dLdw->assign(result);
delete result;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
*dLdw /= *numOfNonZeroWeightsScalar;
} else {
NDArray* eDivNum = E / (*numOfNonZeroWeightsScalar);
dLdw->assign(eDivNum);
delete eDivNum;
}
NDArray* weightsDivNum = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
NDArray temp = *weightsDivNum;
delete weightsDivNum;
*dLdp *= temp;
delete numOfNonZeroWeightsScalar;
}
break;
}
}
NDArray negDLdp = -(*dLdp); // unary negation returns value
dLdl->assign(&negDLdp);
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(mean_pairwssqerr_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(mean_pairwssqerr_loss_grad) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, "
"but got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"MEAN_PAIRWSSQERR_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = "
"%s and labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType *dLdpShapeInfo =
ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
LongType *dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
LongType *dLdlShapeInfo =
ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(dLdpShapeInfo, dLdwShapeInfo, dLdlShapeInfo);
}
} // namespace ops
} // namespace sd
#endif
#pragma clang diagnostic pop
@@ -0,0 +1,409 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 25.11.2017
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_mean_sqerr_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(mean_sqerr_loss, 3, 1, false, 0, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// inputs validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"MEAN_SQERR_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"MEAN_SQERR_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"MEAN_SQERR_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(
reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"MEAN_SQERR_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
NDArray E(labels->shapeInfo(), false, block.launchContext());
predictions->applyPairwiseTransform(pairwise::SquaredSubtract, labels, &E);
// multiply E on weights
NDArray* EWeighted = E * (*weightsBroad);
switch (reductionMode) {
case 0: // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(EWeighted);
break;
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
auto* sumResult = EWeighted->reduceNumber(reduce::Sum);
output->assign(sumResult);
delete sumResult;
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * EWeighted->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
(*output) = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* outAssign = (*sumE) / (*sum);
output->assign(outAssign);
delete outAssign;
delete sumE;
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = EWeighted->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
(*output) = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* outAssign = (*sumE) / double(numOfNonZeroWeights);
output->assign(outAssign);
delete outAssign;
delete sumE;
}
break;
}
}
STORE_RESULT(*output);
delete EWeighted;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(mean_sqerr_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(mean_sqerr_loss) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"MEAN_SQERR_LOSS OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"MEAN_SQERR_LOSS OP: weights array should be scalar or have the same rank as labels array, but got %i "
"and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"MEAN_SQERR_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
LongType * outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the same shape as labels and predictions
outShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(mean_sqerr_loss_grad, 3, 3, false, 0, 1) {
auto predictions = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dpredictions
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
// inputs validation
REQUIRE_TRUE(labels->isSameShape(predictions), 0,
"MEAN_SQERR_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(predictions).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"MEAN_SQERR_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got "
"%i and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"MEAN_SQERR_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights "
"= %s and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"MEAN_SQERR_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, but "
"got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(predictions))
weightsBroad = new NDArray(weights->tileToShape(predictions->shapeInfo()));
NDArray* diff = (*predictions) - (*labels);
// dE_i/dp_i = 2 * (p_i - y_i)
NDArray* dldpTemp = (*diff) * 2.;
dLdp->assign(dldpTemp);
delete dldpTemp;
// dE_i/dy_i = -2 * (p_i - y_i)
NDArray* E = (*diff) * (*diff);
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
NDArray* dLdpWeighted = (*dLdp) * (*weightsBroad);
dLdp->assign(dLdpWeighted);
delete dLdpWeighted;
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
dLdw->assign(sumE);
delete sumE;
}
else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
}
else {
dLdw->assign(E);
}
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * E->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdw = 0.;
} else {
NDArray* weightsDivSum = (*weightsBroad) / (*sum);
NDArray* dLdpResult = (*dLdp) * (*weightsDivSum);
dLdp->assign(dLdpResult);
delete dLdpResult;
delete weightsDivSum;
if (weights->isScalar()) {
*dLdw = 0.;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
gradTemp->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete gradTemp;
}
else {
NDArray* EWeighted = (*E) * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = (*E) * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* dLdwTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(dLdwTemp);
delete dLdwTemp;
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdw = 0.;
} else {
auto numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
auto* sumE = E->reduceNumber(reduce::Sum);
auto* dLdwTemp = (*sumE) / double(numOfNonZeroWeights);
dLdw->assign(dLdwTemp);
delete dLdwTemp;
delete sumE;
}
else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
NDArray* dLdwResult = (*dLdw) / (*numOfNonZeroWeightsScalar);
dLdw->assign(dLdwResult);
delete dLdwResult;
}
else {
auto* dLdwTemp = (*E) / numOfNonZeroWeights;
dLdw->assign(dLdwTemp);
delete dLdwTemp;
}
NDArray* temp = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
delete temp;
delete numOfNonZeroWeightsScalar;
}
break;
}
}
NDArray dldlAssign = -*dLdp;
dLdl->assign(&dldlAssign);
delete E;
delete diff;
if (weightsBroad != weights) delete weightsBroad;
return Status::OK;
}
DECLARE_TYPES(mean_sqerr_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
DECLARE_SHAPE_FN(mean_sqerr_loss_grad) {
auto predictionsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and predictions must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, predictionsShapeInfo), 0,
"MEAN_SQERR_LOSS_GRAD OP: labels and predictions arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(),
ShapeUtils::shapeAsString(predictionsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"MEAN_SQERR_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, but got "
"%i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"MEAN_SQERR_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s and "
"labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(predictionsShapeInfo));
auto dLdpShapeInfo = ShapeBuilders::copyShapeInfoAndType(predictionsShapeInfo, outType, false, block.getWorkspace());
auto dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
auto dLdlShapeInfo = ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(CONSTANT(dLdpShapeInfo), CONSTANT(dLdwShapeInfo), CONSTANT(dLdlShapeInfo));
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,441 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 25.11.2017
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_sigm_cross_entropy_loss)
#include <ops/declarable/CustomOperations.h>
#include <ops/declarable/helpers/legacy_helpers.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(sigm_cross_entropy_loss, 3, 1, false, 1, 1) {
auto logits = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
auto labelsSmoothing = T_ARG(0);
// input validation
REQUIRE_TRUE(labels->isSameShape(logits), 0,
"SIGM_CROSS_ENTROPY_LOSS OP: labels and logits arrays must have the same shapes, but got %s and %s "
"correspondingly!",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"SIGM_CROSS_ENTROPY_LOSS OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"SIGM_CROSS_ENTROPY_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got "
"weights = %s and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"SIGM_CROSS_ENTROPY_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, "
"but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(logits))
weightsBroad = new NDArray(weights->tileToShape(logits->shapeInfo()));
// If labelsSmoothing is nonzero, smooth the labels towards 1/2:
auto newLabels = labels;
if (labelsSmoothing != 0.) {
newLabels = new NDArray(*labels);
newLabels->applyScalar(scalar::SXELogitsSmoother, labelsSmoothing, newLabels);
}
NDArray E(labels, false, block.launchContext());
// logits - labels * logits + log(1 + exp(-logits)) -> take into account numerical stability at large logits
helpers::sigmCrossEntropy(block.launchContext(), logits, newLabels, &E);
// multiply E on weights
NDArray* EWeighted = E * (*weightsBroad);
switch (reductionMode) {
case 0: // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(EWeighted);
break;
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
auto* sumResult = EWeighted->reduceNumber(reduce::Sum);
output->assign(sumResult);
delete sumResult;
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * EWeighted->lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*output = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* outputTemp = (*sumE) / (*sum);
output->assign(outputTemp);
delete outputTemp;
delete sumE;
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = EWeighted->lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
(*output) = 0.;
} else {
auto* sumE = EWeighted->reduceNumber(reduce::Sum);
auto* outputTemp = (*sumE) / double(numOfNonZeroWeights);
output->assign(outputTemp);
delete outputTemp;
delete sumE;
}
break;
}
}
delete EWeighted;
if (weightsBroad != weights) delete weightsBroad;
if (newLabels != labels) delete newLabels;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(sigm_cross_entropy_loss) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(sigm_cross_entropy_loss) {
auto logitsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and logits must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, logitsShapeInfo), 0,
"SIGM_CROSS_ENTROPY_LOSS OP: labels and logits arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"SIGM_CROSS_ENTROPY_LOSS OP: weights array should be scalar or have the same rank as labels array, but "
"got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"SIGM_CROSS_ENTROPY_LOSS OP: shapes of weights and labels arrays should be broadcastable, but got weights = %s "
"and labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
LongType* outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the same shape as labels and logits
outShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(sigm_cross_entropy_loss_grad, 3, 3, false, 1, 1) {
auto logits = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dlogits
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
NDArray *labelsSmoothing = NDArrayFactory::create(logits->dataType(), T_ARG(0), block.launchContext());
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
// input validation
REQUIRE_TRUE(labels->isSameShape(logits), 0,
"SIGM_CROSS_ENTROPY_LOSS_GRAD OP: labels and logits arrays must have the same shapes, but got %s and %s "
"correspondingly!",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == labels->rankOf(), 0,
"SIGM_CROSS_ENTROPY_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, "
"but got %i and %i correspondingly!",
weights->rankOf(), labels->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *labels), 0,
"SIGM_CROSS_ENTROPY_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got "
"weights = %s and labels = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"SIGM_CROSS_ENTROPY_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, 2, "
"3, but got %i instead!",
reductionMode);
// perform weights broadcasting/tile to labels if needed
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(logits))
weightsBroad = new NDArray(weights->tileToShape(logits->shapeInfo()));
// If labelsSmoothing is nonzero, smooth the labels towards 1/2:
auto newLabels = labels;
if (labelsSmoothing->e<float>(0) != 0.f) {
newLabels = new NDArray(*labels);
newLabels->applyScalar(scalar::SXELogitsSmoother, labelsSmoothing->e<float>(0), newLabels);
}
NDArray E(labels, false, block.launchContext());
// logits - labels * logits + log(1 + exp(-logits)) -> take into account numerical stability at large logits
helpers::sigmCrossEntropy(block.launchContext(), logits, newLabels, &E);
// dLdp = 1 - labels - 1 / (1 + exp(logits))
helpers::sigmCrossEntropyGrad(block.launchContext(), logits, newLabels, dLdp);
// dLdl = -logits
*labelsSmoothing -= 1.f;
NDArray* dLdlTemp = (*logits) * (*labelsSmoothing);
dLdl->assign(dLdlTemp);
delete dLdlTemp;
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
NDArray* dLdpWeighted = (*dLdp) * (*weightsBroad);
dLdp->assign(dLdpWeighted);
delete dLdpWeighted;
NDArray* dLdlWeighted = (*dLdl) * (*weightsBroad);
dLdl->assign(dLdlWeighted);
delete dLdlWeighted;
if (weights->isScalar()) {
auto* sumE = E.reduceNumber(reduce::Sum);
dLdw->assign(sumE);
delete sumE;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else {
dLdw->assign(&E);
}
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum;
if (weights->isScalar()) {
sum = (*weights) * E.lengthOf();
} else {
sum = weightsBroad->reduceNumber(reduce::Sum);
}
if (sum->e<double>(0) == 0.) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
NDArray* temp = (*weightsBroad) / (*sum);
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*temp);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete temp;
if (weights->isScalar()) {
*dLdw = 0.;
} else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
NDArray* EWeighted = E * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = E * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* gradTemp = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
gradTemp->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete gradTemp;
} else {
NDArray* EWeighted = E * (*weightsBroad);
NDArray* EWeightedSum = EWeighted->reduceNumber(reduce::Sum);
delete EWeighted;
NDArray* ESum = E * (*sum);
NDArray* numerator = (*ESum) - (*EWeightedSum);
delete ESum;
delete EWeightedSum;
NDArray* sumSquared = (*sum) * (*sum);
NDArray* dLdwTemp1 = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(dLdwTemp1);
delete dLdwTemp1;
}
}
delete sum;
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
auto* countResult = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countResult->e<LongType>(0);
delete countResult;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
auto numOfNonZeroWeightsScalar =
NDArrayFactory::create(dLdw->dataType(), numOfNonZeroWeights, block.launchContext());
if (weights->isScalar()) {
auto* sumE = E.reduceNumber(reduce::Sum);
auto* dLdwTemp2 = (*sumE) / (*numOfNonZeroWeightsScalar);
dLdw->assign(dLdwTemp2);
delete dLdwTemp2;
delete sumE;
}
else if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
NDArray* dLdwResult = (*dLdw) / (*numOfNonZeroWeightsScalar);
dLdw->assign(dLdwResult);
delete dLdwResult;
} else {
auto* sumE = E.reduceNumber(reduce::Sum);
auto* dLdwTemp2 = (*sumE) / (*numOfNonZeroWeightsScalar);
dLdw->assign(dLdwTemp2);
delete dLdwTemp2;
delete sumE;
}
NDArray* temp = (*weightsBroad) / (*numOfNonZeroWeightsScalar);
NDArray* dLdpResult = (*dLdp) * (*temp);
dLdp->assign(dLdpResult);
delete dLdpResult;
NDArray* dLdlResult = (*dLdl) * (*temp);
dLdl->assign(dLdlResult);
delete dLdlResult;
delete temp;
delete numOfNonZeroWeightsScalar;
}
break;
}
}
delete labelsSmoothing;
if (weightsBroad != weights) delete weightsBroad;
if (newLabels != labels) delete newLabels;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(sigm_cross_entropy_loss_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(sigm_cross_entropy_loss_grad) {
auto logitsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and logits must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(labelsShapeInfo, logitsShapeInfo), 0,
"SIGM_CROSS_ENTROPY_LOSS_GRAD OP: labels and logits arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
// weights array can be single scalar or has the same rank as labels, and must be broadcastable to labels
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(labelsShapeInfo), 0,
"SIGM_CROSS_ENTROPY_LOSS_GRAD OP: weights array should be scalar or have the same rank as labels array, "
"but got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(labelsShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, labelsShapeInfo), 0,
"SIGM_CROSS_ENTROPY_LOSS_GRAD OP: shapes of weights and labels arrays should be broadcastable, but got weights = "
"%s and labels = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(labelsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
auto dLdpShapeInfo = ShapeBuilders::copyShapeInfoAndType(logitsShapeInfo, outType, false, block.getWorkspace());
auto dLdwShapeInfo = ShapeBuilders::copyShapeInfoAndType(weightsShapeInfo, outType, false, block.getWorkspace());
auto dLdlShapeInfo = ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(CONSTANT(dLdpShapeInfo), CONSTANT(dLdwShapeInfo), CONSTANT(dLdlShapeInfo));
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,571 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 25.11.2017.
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_softmax_cross_entropy_loss)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(softmax_cross_entropy_loss, 3, 1, false, 1, 1) {
auto logits = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto output = OUTPUT_VARIABLE(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
double labelsSmoothing = T_ARG(0);
// input validation
REQUIRE_TRUE(labels->isSameShape(logits), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: labels and logits arrays must have the same shapes, but got %s and %s "
"correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: reduction mode value is not acceptable, possible values are 0, 1, 2, 3, "
"but got %i instead!",
reductionMode);
// smoothing is possible for rank of logits/labels > 1
REQUIRE_TRUE(labels->rankOf() > 1 || (labels->rankOf() == 1 && labelsSmoothing == 0.), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: smoothing is not possible when rank of labels/ logits = 1 !");
if (!output->isScalar()) {
// weights array can be single scalar or has the same shape as output, and must be broadcastable to output shape
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == output->rankOf(), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: weights array should be scalar or have the same rank as output array, "
"but got %i and %i correspondingly!",
weights->rankOf(), output->rankOf());
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(*weights, *output), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: shapes of weights and output arrays should be broadcastable, but got "
"weights = %s and output = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(labels).c_str());
}
// If label_smoothing is nonzero, smooth the labels towards 1/num_classes: new_onehot_labels = onehot_labels * (1 -
// label_smoothing) + label_smoothing / num_classes num_classes = labels->sizeAt(1)
NDArray* cLabels = labels->cast(weights->dataType()); // cast() already returns NDArray*
NDArray* newLabels = cLabels;
if (labelsSmoothing != 0.) {
newLabels = new NDArray(cLabels);
NDArray* term1 = (1.f - labelsSmoothing) * (*cLabels);
NDArray* term2 = (*term1) + (labelsSmoothing / cLabels->sizeAt(1));
delete term1;
newLabels->assign(term2);
delete term2;
}
// main formula: result = - sum_i(lables_i * log(softmax_i)) - sum over last dimension
// softmax_i = exp(logits_i) / sum_j(exp(logits_j))
// so result = sum_i( lables_i * (log(sum_j(exp(logits_j))) - logits_i) )
// for numerical stability we use shifted logits (one can approve this using simple math):
// softmax_i = exp(logits_i - maxLogit) / sum_j(exp(logits_j - maxLogit))
// maxLogit is max among logits_i
std::vector<LongType> dimensions = {-1};
NDArray* maxLogits = logits->reduceAlongDimension(reduce::Max, &dimensions, true);
NDArray* shiftedLogits_ptr = (*logits) - (*maxLogits);
delete maxLogits;
NDArray shiftedLogits = *shiftedLogits_ptr;
delete shiftedLogits_ptr;
NDArray* expShifted = shiftedLogits.transform(transform::Exp);
NDArray* sumExp = expShifted->reduceAlongDimension(reduce::Sum, &dimensions, true);
delete expShifted;
NDArray* logSumExp_ptr = sumExp->transform(transform::Log);
delete sumExp;
NDArray logSumExp = *logSumExp_ptr;
delete logSumExp_ptr;
// E = (newLabels * (logSumExp - shiftedLogits)).reduceAlongDimension(Sum)
NDArray* diff = logSumExp - shiftedLogits;
NDArray* product = (*newLabels) * (*diff);
delete diff;
NDArray* E_ptr = product->reduceAlongDimension(reduce::Sum, &dimensions);
delete product;
NDArray E = *E_ptr;
delete E_ptr;
// perform weights broadcasting/tile to E if it is necessary
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(&E)) {
std::vector<LongType> weightsShape = {weights->lengthOf()};
if (E.rankOf() == 1 && weights->isVector() && weights->rankOf() > 1)
weightsBroad = weights->reshape(weights->ordering(), weightsShape);
else
weightsBroad = new NDArray(weights->tileToShape(E.shapeInfo()));
}
// multiply E on weights
E *= *weightsBroad;
switch (reductionMode) {
case 0: // 0 - "none", un-reduced weighted losses with the same shape as labels.
output->assign(&E);
break;
case 1: { // 1 - "weighted_sum", output is scalar and equal to sum of all elements of E array
E.reduceNumber(reduce::Sum, output);
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
double sum;
if (weights->isScalar())
sum = weights->e<double>(0) * E.lengthOf();
else {
NDArray* sumPtr = weightsBroad->reduceNumber(reduce::Sum);
sum = sumPtr->e<double>(0);
delete sumPtr;
}
if (sum == 0.)
*output = 0.;
else {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / sum;
delete eSum;
output->assign(result);
delete result;
}
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countNonZero = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countNonZero->e<LongType>(0);
delete countNonZero;
}
if (numOfNonZeroWeights == 0)
*output = 0.;
else {
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / double(numOfNonZeroWeights);
delete eSum;
output->assign(result);
delete result;
}
break;
}
}
if (weightsBroad != weights) delete weightsBroad;
if (newLabels != cLabels) delete newLabels;
delete cLabels;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(softmax_cross_entropy_loss) {
getOpDescriptor()
->setAllowedInputTypes(0, {ALL_FLOATS})
->setAllowedInputTypes(1, {ALL_FLOATS})
->setAllowedInputTypes(2, {ALL_FLOATS, ALL_INTS})
->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(softmax_cross_entropy_loss) {
auto logitsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
// labels and logits must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(logitsShapeInfo, labelsShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: labels and logits arrays must have the same shapes, but got %s and %s "
"correspondingly!",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
LongType* outShapeInfo = nullptr;
if (INT_ARG(0) != 0) // in this case output is scalar
outShapeInfo = ConstantShapeHelper::getInstance().scalarShapeInfo(outType);
else { // in this case output has the shape as labels and logits minus last dimension
std::vector<LongType> dimensions = {-1};
outShapeInfo = ShapeUtils::evalReduceShapeInfo(shape::order(logitsShapeInfo), &dimensions, logitsShapeInfo, false,
true, block.getWorkspace());
// weights array can be single scalar or has the same rank as output, and must be broadcastable to output
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(outShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: weights array should be scalar or have the same rank as output array, "
"but got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(outShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(
shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, outShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS OP: shapes of weights and output arrays should be broadcastable, but got weights = "
"%s and output = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(outShapeInfo).c_str());
}
return SHAPELIST(outShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(softmax_cross_entropy_loss_grad, 3, 3, false, 1, 1) {
auto logits = INPUT_VARIABLE(0);
auto weights = INPUT_VARIABLE(1);
auto labels = INPUT_VARIABLE(2);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dlogits
auto dLdw = OUTPUT_VARIABLE(1); // dL/dweights
auto dLdl = OUTPUT_VARIABLE(2); // dL/dlabels
auto labelsSmoothing = T_ARG(0);
int reductionMode =
INT_ARG(0); // 0 - "none"; 1 - "weighted_sum"; 2 - "weighted_mean"; 3 - "weighted_sum_by_nonzero_weights"
// take into account Alex's proposition to treat "none" the same as "weighted_sum" mode when calculating gradients
if (reductionMode == 0) reductionMode = 1;
std::vector<LongType> *dimensions = new std::vector<LongType>({-1});
// input validation
REQUIRE_TRUE(labels->isSameShape(logits), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: labels and logits arrays must have the same shapes, but got %s and "
"%s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
// only 4 possible reduction modes exist
REQUIRE_TRUE(reductionMode == 0 || reductionMode == 1 || reductionMode == 2 || reductionMode == 3, 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: reduction mode value is not acceptable, possible values are 0, 1, "
"2, 3, but got %i instead!",
reductionMode);
auto lossShapeInfo = ShapeUtils::evalReduceShapeInfo(logits->ordering(), dimensions, logits->shapeInfo(), false,
false, block.getWorkspace());
// weights array can be single scalar or has the same shape as loss, and must be broadcastable to loss shape
REQUIRE_TRUE(weights->isScalar() || weights->rankOf() == shape::rank(lossShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: weights array should be scalar or have the same rank as loss "
"array, but got %i and %i correspondingly!",
weights->rankOf(), shape::rank(lossShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(weights->isScalar() || ShapeUtils::areShapesBroadcastable(weights->shapeInfo(), lossShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: shapes of weights and loss arrays should be broadcastable, but got "
"weights = %s and loss = %s instead!",
ShapeUtils::shapeAsString(weights).c_str(), ShapeUtils::shapeAsString(lossShapeInfo).c_str());
// smoothing is possible for rank of logits/labels > 1
REQUIRE_TRUE(labels->rankOf() > 1 || (labels->rankOf() == 1 && labelsSmoothing == 0.), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: smoothing is not possible when rank of labels/ logits = 1 !");
// If label_smoothing is nonzero, smooth the labels towards 1/num_classes: new_onehot_labels = onehot_labels * (1 -
// label_smoothing) + label_smoothing / num_classes num_classes = labels->sizeAt(1)
NDArray* cLabels = labels->cast(weights->dataType()); // cast() already returns NDArray*
NDArray* newLabels = cLabels;
if (labelsSmoothing != 0.) {
newLabels = new NDArray(labels->shapeInfo(), dLdl->dataType(), false, block.launchContext());
NDArray* term1 = (1.f - labelsSmoothing) * (*cLabels);
NDArray* term2 = (*term1) + (labelsSmoothing / cLabels->sizeAt(1));
delete term1;
newLabels->assign(term2);
delete term2;
}
// Compute softmax
NDArray* maxLogits = logits->reduceAlongDimension(reduce::Max, dimensions, true);
NDArray* shiftedLogits_ptr = (*logits) - (*maxLogits);
delete maxLogits;
NDArray* expShifted = shiftedLogits_ptr->transform(transform::Exp);
delete shiftedLogits_ptr;
NDArray* sumExp = expShifted->reduceAlongDimension(reduce::Sum, dimensions, true);
NDArray* softmax_ptr = (*expShifted) / (*sumExp);
delete expShifted;
delete sumExp;
NDArray softmax = *softmax_ptr;
delete softmax_ptr;
// dEdp = softmax * sum_i(lables_i) - labels
NDArray* labelSum = newLabels->reduceAlongDimension(reduce::Sum, dimensions, true);
NDArray* softmaxTimesLabelSum = softmax * (*labelSum);
delete labelSum;
NDArray* dLdpTemp_ptr = (*softmaxTimesLabelSum) - (*newLabels);
delete softmaxTimesLabelSum;
dLdp->assign(dLdpTemp_ptr);
delete dLdpTemp_ptr;
// dEdl = -log(softmax)
NDArray* logSoftmax = softmax.transform(transform::Log);
NDArray negLogSoftmax = -(*logSoftmax); // unary negation returns value
delete logSoftmax;
NDArray* dLdlTemp_ptr = negLogSoftmax * (1.f - labelsSmoothing);
dLdl->assign(dLdlTemp_ptr);
delete dLdlTemp_ptr;
// Compute E for gradient calculations
NDArray* maxLogits2 = logits->reduceAlongDimension(reduce::Max, dimensions, true);
NDArray* shiftedLogits2_ptr = (*logits) - (*maxLogits2);
delete maxLogits2;
NDArray shiftedLogits = *shiftedLogits2_ptr;
delete shiftedLogits2_ptr;
NDArray* expShifted2 = shiftedLogits.transform(transform::Exp);
NDArray* sumExp2 = expShifted2->reduceAlongDimension(reduce::Sum, dimensions, true);
delete expShifted2;
NDArray* logSumExp_ptr = sumExp2->transform(transform::Log);
delete sumExp2;
NDArray logSumExp = *logSumExp_ptr;
delete logSumExp_ptr;
NDArray* diff = logSumExp - shiftedLogits;
NDArray* product = (*newLabels) * (*diff);
delete diff;
NDArray* E_ptr = product->reduceAlongDimension(reduce::Sum, dimensions);
delete product;
NDArray E = *E_ptr;
delete E_ptr;
// perform weights broadcasting/tile to E if it is necessary
auto weightsBroad = weights;
if (!weights->isScalar() && !weights->isSameShape(&E))
weightsBroad = new NDArray(weights->tileToShape(E.shapeInfo()));
auto excludeDims = ShapeUtils::evalDimsToExclude(dLdp->rankOf(), dimensions->size(), dimensions->data());
switch (reductionMode) {
case 1: { // 1 - "none" and "weighted_sum", output is scalar and equal to sum of all elements of E array
if (weights->isScalar() || weights->lengthOf() == 1) {
NDArray* eSum = E.reduceNumber(reduce::Sum);
dLdw->assign(eSum);
delete eSum;
*dLdp *= *weights;
*dLdl *= *weights;
} else {
dLdp->applyBroadcast(broadcast::Multiply, excludeDims, weightsBroad, dLdp);
dLdl->applyBroadcast(broadcast::Multiply, excludeDims, weightsBroad, dLdl);
if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
} else
dLdw->assign(&E);
}
break;
}
case 2: { // 2 - "weighted_mean", output is scalar and equal to sum of all elements of E array divided by sum of
// all elements of weightsBroad array
NDArray* sum_ptr = nullptr;
if (weights->isScalar())
sum_ptr = (*weights) * E.lengthOf();
else
sum_ptr = weightsBroad->reduceNumber(reduce::Sum);
NDArray sum = *sum_ptr;
delete sum_ptr;
if (sum.e<double>(0) == 0.) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
if (weights->isScalar() || weights->lengthOf() == 1) {
NDArray* temp_ptr = (*weights) / sum;
NDArray temp = *temp_ptr;
delete temp_ptr;
*dLdp *= temp;
*dLdl *= temp;
*dLdw = 0.;
} else {
NDArray* temp_ptr = (*weightsBroad) / sum;
NDArray temp = *temp_ptr;
delete temp_ptr;
dLdp->applyBroadcast(broadcast::Multiply, dimensions, &temp, dLdp);
dLdl->applyBroadcast(broadcast::Multiply, dimensions, &temp, dLdl);
if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
// Compute (E * sum - (E * weightsBroad).reduceNumber(Sum)) / (sum * sum)
NDArray* ETimesSum = E * sum;
NDArray* ETimesWeights = E * (*weightsBroad);
NDArray* ETimesWeightsSum = ETimesWeights->reduceNumber(reduce::Sum);
delete ETimesWeights;
NDArray* numerator = (*ETimesSum) - (*ETimesWeightsSum);
delete ETimesSum;
delete ETimesWeightsSum;
NDArray* sumSquared = sum * sum;
NDArray* result = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
result->reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
delete result;
} else {
// Compute (E * sum - (E * weightsBroad).reduceNumber(Sum)) / (sum * sum)
NDArray* ETimesSum = E * sum;
NDArray* ETimesWeights = E * (*weightsBroad);
NDArray* ETimesWeightsSum = ETimesWeights->reduceNumber(reduce::Sum);
delete ETimesWeights;
NDArray* numerator = (*ETimesSum) - (*ETimesWeightsSum);
delete ETimesSum;
delete ETimesWeightsSum;
NDArray* sumSquared = sum * sum;
NDArray* result = (*numerator) / (*sumSquared);
delete numerator;
delete sumSquared;
dLdw->assign(result);
delete result;
}
}
}
break;
}
case 3: { // 3 - "weighted_sum_by_nonzero_weights", output is scalar and equal to scalar sum of all elements of E
// array divided by number of non-zero weights
LongType numOfNonZeroWeights = 0;
if (weights->isScalar()) {
if (weights->e<double>(0) != 0.) numOfNonZeroWeights = E.lengthOf();
} else {
NDArray* countNonZero = weightsBroad->reduceNumber(reduce::CountNonZero);
numOfNonZeroWeights = countNonZero->e<LongType>(0);
delete countNonZero;
}
if (numOfNonZeroWeights == 0) {
*dLdp = 0.;
*dLdl = 0.;
*dLdw = 0.;
} else {
if (weights->isScalar() || weights->lengthOf() == 1) {
NDArray* temp_ptr = (*weights) / numOfNonZeroWeights;
NDArray temp = *temp_ptr;
delete temp_ptr;
*dLdp *= temp;
*dLdl *= temp;
NDArray* eSum = E.reduceNumber(reduce::Sum);
NDArray* result = (*eSum) / numOfNonZeroWeights;
delete eSum;
dLdw->assign(result);
delete result;
} else {
NDArray* temp_ptr = (*weightsBroad) / numOfNonZeroWeights;
NDArray temp = *temp_ptr;
delete temp_ptr;
dLdp->applyBroadcast(broadcast::Multiply, dimensions, &temp, dLdp);
dLdl->applyBroadcast(broadcast::Multiply, dimensions, &temp, dLdl);
if (weights != weightsBroad) {
std::vector<LongType> axesToReduceAlong =
ShapeUtils::evalBroadcastBackwardAxis(weights->shapeInfo(), weightsBroad->shapeInfo());
E.reduceAlongDimension(reduce::Sum, dLdw, &axesToReduceAlong, true);
*dLdw /= numOfNonZeroWeights;
} else {
NDArray* eDivNum = E / numOfNonZeroWeights;
dLdw->assign(eDivNum);
delete eDivNum;
}
}
}
break;
}
}
if (weightsBroad != weights) delete weightsBroad;
if (newLabels != cLabels) delete newLabels;
delete cLabels;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(softmax_cross_entropy_loss_grad) {
getOpDescriptor()
->setAllowedInputTypes(0, {ALL_FLOATS})
->setAllowedInputTypes(1, {ALL_FLOATS})
->setAllowedInputTypes(2, {ALL_FLOATS, ALL_INTS})
->setAllowedInputTypes(3, {ALL_FLOATS})
->setAllowedInputTypes(4, {ALL_FLOATS})
->setAllowedInputTypes(5, {ALL_FLOATS})
->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(softmax_cross_entropy_loss_grad) {
auto logitsShapeInfo = inputShape->at(0);
auto weightsShapeInfo = inputShape->at(1);
auto labelsShapeInfo = inputShape->at(2);
std::vector<LongType> dimensions = {-1};
// labels and logits must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(logitsShapeInfo, labelsShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: labels and logits arrays must have the same shapes, but got %s and "
"%s correspondingly!",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
auto lossShapeInfo = ShapeUtils::evalReduceShapeInfo(shape::order(logitsShapeInfo), &dimensions, logitsShapeInfo,
false, false, block.getWorkspace());
// weights array can be single scalar or has the same rank as loss, and must be broadcastable to loss
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || shape::rank(weightsShapeInfo) == shape::rank(lossShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: weights array should be scalar or have the same rank as loss "
"array, but got %i and %i correspondingly!",
shape::rank(weightsShapeInfo), shape::rank(lossShapeInfo));
// check whether broadcast operation is possible for weights array
REQUIRE_TRUE(shape::isScalar(weightsShapeInfo) || ShapeUtils::areShapesBroadcastable(weightsShapeInfo, lossShapeInfo),
0,
"SOFTMAX_CROSS_ENTROPY_LOSS_GRAD OP: shapes of weights and loss arrays should be broadcastable, but got "
"weights = %s and loss = %s instead!",
ShapeUtils::shapeAsString(weightsShapeInfo).c_str(), ShapeUtils::shapeAsString(lossShapeInfo).c_str());
auto outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
auto dLdpShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(logitsShapeInfo),
shape::rank(logitsShapeInfo),
shape::shapeOf(logitsShapeInfo))->primary();
auto dLdwShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(weightsShapeInfo),
shape::rank(weightsShapeInfo),
shape::shapeOf(weightsShapeInfo))->primary();
auto dLdlShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
return SHAPELIST(dLdpShapeInfo, dLdwShapeInfo, dLdlShapeInfo);
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,202 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 18.06.2018
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_softmax_cross_entropy_loss_with_logits)
#include <ops/declarable/CustomOperations.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(softmax_cross_entropy_loss_with_logits, 2, 1, false, 0, 0) {
auto logits = INPUT_VARIABLE(0);
auto labels = INPUT_VARIABLE(1);
auto output = OUTPUT_VARIABLE(0);
const int classesDim = block.getIArguments()->size() > 0 ? INT_ARG(0) : logits->rankOf() - 1;
// input validation
REQUIRE_TRUE(labels->isSameShape(logits), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS OP: labels and logits arrays must have the same shapes, but got "
"%s and %s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
REQUIRE_TRUE(classesDim < logits->rankOf(), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS OP: class dimension must be smaller than rank of logits, but "
"got %i and %i correspondingly !",
classesDim, logits->rankOf());
std::vector<LongType> dimension = {classesDim};
// Compute softmax log
NDArray* maxAlongDim_ptr = logits->reduceAlongDimension(reduce::Max, &dimension, true);
NDArray maxAlongDim = *maxAlongDim_ptr;
delete maxAlongDim_ptr;
NDArray* shiftedLogits_ptr = (*logits) - maxAlongDim;
NDArray* logExp_ptr = shiftedLogits_ptr->transform(transform::Exp);
delete shiftedLogits_ptr;
NDArray logExp = *logExp_ptr;
delete logExp_ptr;
NDArray* sumLogExp_ptr = logExp.reduceAlongDimension(reduce::Sum, &dimension, true);
NDArray sumLogExp = *sumLogExp_ptr;
delete sumLogExp_ptr;
NDArray* softmaxRatio_ptr = logExp / sumLogExp;
NDArray* logSoftMax_ptr = softmaxRatio_ptr->transform(transform::Log);
delete softmaxRatio_ptr;
NDArray logSoftMax = *logSoftMax_ptr;
delete logSoftMax_ptr;
// Compute cross entropy: -labels * log(softmax)
NDArray negLabels = -(*labels); // unary negation returns value
NDArray* product_ptr = negLabels * logSoftMax;
product_ptr->reduceAlongDimension(reduce::Sum, output, &dimension);
delete product_ptr;
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(softmax_cross_entropy_loss_with_logits) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(softmax_cross_entropy_loss_with_logits) {
auto logitsShapeInfo = inputShape->at(0);
auto labelsShapeInfo = inputShape->at(1);
const int classesDim = block.getIArguments()->size() > 0 ? INT_ARG(0) : -1;
std::vector<LongType> dimensions = {classesDim};
// labels and logits must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(logitsShapeInfo, labelsShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS OP: labels and logits arrays must have the same shapes, but got "
"%s and %s correspondingly!",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
auto outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
auto reducedShapeInfo = ShapeUtils::evalReduceShapeInfo(shape::order(labelsShapeInfo), &dimensions, labelsShapeInfo,
outType, false, false, block.getWorkspace());
return SHAPELIST(reducedShapeInfo);
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(softmax_cross_entropy_loss_with_logits_grad, 2, 2, false, 0, 0) {
auto logits = INPUT_VARIABLE(0);
auto labels = INPUT_VARIABLE(1);
auto output = OUTPUT_VARIABLE(0);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dlogits
auto dLdl = OUTPUT_VARIABLE(1); // dL/dlabels
const int classesDim = block.getIArguments()->size() > 0 ? INT_ARG(0) : logits->rankOf()-1;
// input validation
REQUIRE_TRUE(labels->isSameShape(logits), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS_GRAD OP: labels and logits arrays must have the same shapes, "
"but got %s and %s correspondingly !",
ShapeUtils::shapeAsString(labels).c_str(), ShapeUtils::shapeAsString(logits).c_str());
REQUIRE_TRUE(classesDim < logits->rankOf(), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS_GRAD OP: class dimension must be smaller than rank of logits, "
"but got %i and %i correspondingly !",
classesDim, logits->rankOf());
std::vector<LongType> dimension = {classesDim};
// Compute softmax
NDArray* maxAlongDim_ptr = logits->reduceAlongDimension(reduce::Max, &dimension, true);
NDArray maxAlongDim = *maxAlongDim_ptr;
delete maxAlongDim_ptr;
NDArray* shiftedLogits_ptr = (*logits) - maxAlongDim;
NDArray* softmax_ptr = shiftedLogits_ptr->transform(transform::Exp);
delete shiftedLogits_ptr;
NDArray softmax = *softmax_ptr;
delete softmax_ptr;
NDArray* sumSoftmax_ptr = softmax.reduceAlongDimension(reduce::Sum, &dimension, true);
NDArray sumSoftmax = *sumSoftmax_ptr;
delete sumSoftmax_ptr;
softmax /= sumSoftmax;
// dEdp = softmax * sum_i(labels_i) - labels
// note the eps is to account for exact 0s in the log calculation being nan
NDArray* labelsPlusEps_ptr = (*labels) + 1e-6;
NDArray labelsPlusEps = *labelsPlusEps_ptr;
delete labelsPlusEps_ptr;
NDArray* labelSum_ptr = labelsPlusEps.reduceAlongDimension(reduce::Sum, &dimension, true);
NDArray labelSum = *labelSum_ptr;
delete labelSum_ptr;
NDArray* softmaxTimesLabelSum_ptr = softmax * labelSum;
NDArray* dLdpTemp_ptr = (*softmaxTimesLabelSum_ptr) - labelsPlusEps;
delete softmaxTimesLabelSum_ptr;
dLdp->assign(dLdpTemp_ptr);
delete dLdpTemp_ptr;
// dEdl = -log(softmax)
softmax.applyTransform(transform::Log, dLdl);
dLdl->applyTransform(transform::Neg, dLdl);
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(softmax_cross_entropy_loss_with_logits_grad) {
getOpDescriptor()->setAllowedInputTypes(ANY)->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(softmax_cross_entropy_loss_with_logits_grad) {
auto logitsShapeInfo = inputShape->at(0);
auto labelsShapeInfo = inputShape->at(1);
// labels and logits must have the same shapes
REQUIRE_TRUE(shape::shapeEquals(logitsShapeInfo, labelsShapeInfo), 0,
"SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS_GRAD OP: labels and logits arrays must have the same shapes, "
"but got %s and %s correspondingly!",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
auto dLdpShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(logitsShapeInfo),
shape::rank(logitsShapeInfo),
shape::shapeOf(logitsShapeInfo))->primary();
auto dLdlShapeInfo = ConstantShapeHelper::getInstance().bufferForShapeInfo(outType, shape::order(labelsShapeInfo),
shape::rank(labelsShapeInfo),
shape::shapeOf(labelsShapeInfo))->primary();
return SHAPELIST(dLdpShapeInfo, dLdlShapeInfo);
}
} // namespace ops
} // namespace sd
#endif
@@ -0,0 +1,234 @@
/* ******************************************************************************
*
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
//
// @author Yurii Shyrma (iuriish@yahoo.com), created on 29.08.2018
//
#include <system/op_boilerplate.h>
#if NOT_EXCLUDED(OP_sparse_softmax_cross_entropy_loss_with_logits)
#include <ops/declarable/CustomOperations.h>
#include <ops/declarable/generic/helpers/ScatterHelper.h>
namespace sd {
namespace ops {
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(sparse_softmax_cross_entropy_loss_with_logits, 2, 1, false, 0, 0) {
auto labels = INPUT_VARIABLE(0);
auto logits = INPUT_VARIABLE(1);
auto output = OUTPUT_VARIABLE(0);
const int labelsRank = labels->rankOf();
const int logitsRank = logits->rankOf();
// input validation
REQUIRE_TRUE(labelsRank == logitsRank - 1, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS OP: input arrays should satisfy relation (labels_rank = "
"logits_rank - 1), but got labels_rank = %i and logits_rank = %i instead !",
labelsRank, logitsRank);
auto* labelsShapePtr = labels->getShapeAsVector();
std::vector<LongType> labelsShape = *labelsShapePtr;
delete labelsShapePtr;
auto* logitsShapePtr = logits->getShapeAsVector();
std::vector<LongType> logitsShape = *logitsShapePtr;
delete logitsShapePtr;
logitsShape.pop_back();
bool equalSoft = logitsShape == labelsShape;
REQUIRE_TRUE(
equalSoft, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS OP: wrong shape of labels array, its shape should be the same as "
"logits shape with last dimension excluded, however got labels_shape = %s and logits_shape = %s instead !",
ShapeUtils::shapeAsString(labelsShape).c_str(), ShapeUtils::shapeAsString(logitsShape).c_str());
std::vector<LongType> dimension = {-1};
// Compute log softmax: -log(exp(logits - max) / sum(exp(logits - max)))
NDArray* maxAlongDim_ptr = logits->reduceAlongDimension(reduce::Max, &dimension, true);
NDArray maxAlongDim = *maxAlongDim_ptr;
delete maxAlongDim_ptr;
NDArray* shiftedLogits_ptr = (*logits) - maxAlongDim;
NDArray* logitsExp_ptr = shiftedLogits_ptr->transform(transform::Exp, nullptr);
delete shiftedLogits_ptr;
NDArray logitsExp = *logitsExp_ptr;
delete logitsExp_ptr;
NDArray* sumLogitsExp_ptr = logitsExp.reduceAlongDimension(reduce::Sum, &dimension, true);
NDArray sumLogitsExp = *sumLogitsExp_ptr;
delete sumLogitsExp_ptr;
NDArray* softmaxRatio_ptr = logitsExp / sumLogitsExp;
NDArray* logSoftmax_ptr = softmaxRatio_ptr->transform(transform::Log);
delete softmaxRatio_ptr;
// Apply negation: -log(softmax)
NDArray negLogSoftmax = -(*logSoftmax_ptr); // unary negation returns value
delete logSoftmax_ptr;
NDArray logSoftMax = negLogSoftmax;
helpers::scatterForLoss(block.launchContext(), *labels, logSoftMax, *output, false);
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(sparse_softmax_cross_entropy_loss_with_logits) {
getOpDescriptor()
->setAllowedInputTypes(0, {ALL_INTS})
->setAllowedInputTypes(1, {ALL_FLOATS})
->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(sparse_softmax_cross_entropy_loss_with_logits) {
auto labelsShapeInfo = inputShape->at(0);
auto logitsShapeInfo = inputShape->at(1);
REQUIRE_TRUE(labelsShapeInfo[0] == logitsShapeInfo[0] - 1, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS OP: input arrays should satisfy relation (labels_rank = "
"logits_rank - 1), but got labels_rank = %i and logits_rank = %i instead !",
labelsShapeInfo[0], logitsShapeInfo[0]);
bool equalSoft = true;
for (int i = 1; i < labelsShapeInfo[0]; ++i)
if (labelsShapeInfo[i] != logitsShapeInfo[i]) {
equalSoft = false;
break;
}
REQUIRE_TRUE(
equalSoft, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS OP: wrong shape of labels array, its shape should be the same as "
"logits shape with last dimension excluded, however got labels_shape = %s and logits_shape = %s instead !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
auto outShapeInfo =
ShapeBuilders::copyShapeInfoAndType(labelsShapeInfo, logitsShapeInfo, false, block.getWorkspace());
return SHAPELIST(CONSTANT(outShapeInfo));
}
//////////////////////////////////////////////////////////////////////////
CUSTOM_OP_IMPL(sparse_softmax_cross_entropy_loss_with_logits_grad, 2, 1, false, 0, 0) {
auto labels = INPUT_VARIABLE(0);
auto logits = INPUT_VARIABLE(1);
auto dLdp = OUTPUT_VARIABLE(0); // dL/dlogits
const int labelsRank = labels->rankOf();
const int logitsRank = logits->rankOf();
// input validation
REQUIRE_TRUE(labelsRank == logitsRank - 1, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS_GRAD OP: input arrays should satisfy relation "
"(labels_rank = logits_rank - 1), but got labels_rank = %i and logits_rank = %i instead !",
labelsRank, logitsRank);
auto* labelsShapePtr = labels->getShapeAsVector();
std::vector<LongType> labelsShape = *labelsShapePtr;
delete labelsShapePtr;
auto* logitsShapePtr = logits->getShapeAsVector();
std::vector<LongType> logitsShape = *logitsShapePtr;
delete logitsShapePtr;
logitsShape.pop_back();
bool equalSoft = logitsShape == labelsShape;
REQUIRE_TRUE(equalSoft, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS_GRAD OP: wrong shape of labels array, its shape should "
"be the same as logits shape with last dimension excluded, however got labels_shape = %s and "
"logits_shape = %s instead !",
ShapeUtils::shapeAsString(labelsShape).c_str(), ShapeUtils::shapeAsString(logitsShape).c_str());
std::vector<LongType> dimension = {-1};
// Compute softmax
NDArray* maxAlongDim_ptr = logits->reduceAlongDimension(reduce::Max, &dimension, true);
NDArray maxAlongDim = *maxAlongDim_ptr;
delete maxAlongDim_ptr;
NDArray* shiftedLogits_ptr = (*logits) - maxAlongDim;
NDArray* softmax_ptr = shiftedLogits_ptr->transform(transform::Exp);
delete shiftedLogits_ptr;
NDArray softmax = *softmax_ptr;
delete softmax_ptr;
NDArray* sumSoftmax_ptr = softmax.reduceAlongDimension(reduce::Sum, &dimension, true);
NDArray sumSoftmax = *sumSoftmax_ptr;
delete sumSoftmax_ptr;
softmax /= sumSoftmax;
// dEdp = softmax - 1 (or 0)
dLdp->assign(&softmax);
// subtract unities at appropriate indexes of dLdp array
helpers::scatterForLoss(block.launchContext(), *labels, *dLdp,
*labels /*actually third array is unnecessary for gradient calculation*/, true);
return Status::OK;
}
//////////////////////////////////////////////////////////////////////////
DECLARE_TYPES(sparse_softmax_cross_entropy_loss_with_logits_grad) {
getOpDescriptor()
->setAllowedInputTypes(0, {ALL_INTS})
->setAllowedInputTypes(1, {ALL_FLOATS})
->setAllowedOutputTypes({ALL_FLOATS});
}
//////////////////////////////////////////////////////////////////////////
DECLARE_SHAPE_FN(sparse_softmax_cross_entropy_loss_with_logits_grad) {
auto labelsShapeInfo = inputShape->at(0);
auto logitsShapeInfo = inputShape->at(1);
REQUIRE_TRUE(labelsShapeInfo[0] == logitsShapeInfo[0] - 1, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS_GRAD OP: input arrays should satisfy relation "
"(labels_rank = logits_rank - 1), but got labels_rank = %i and logits_rank = %i instead !",
labelsShapeInfo[0], logitsShapeInfo[0]);
bool equalSoft = true;
for (int i = 1; i < labelsShapeInfo[0]; ++i)
if (labelsShapeInfo[i] != logitsShapeInfo[i]) {
equalSoft = false;
break;
}
REQUIRE_TRUE(equalSoft, 0,
"SPARSE_SOFTMAX_CROSS_ENTROPY_LOSS_WITH_LOGITS_GRAD OP: wrong shape of labels array, its shape should "
"be the same as logits shape with last dimension excluded, however got labels_shape = %s and "
"logits_shape = %s instead !",
ShapeUtils::shapeAsString(labelsShapeInfo).c_str(), ShapeUtils::shapeAsString(logitsShapeInfo).c_str());
DataType outType = DataTypeUtils::pickFloatingType(ArrayOptions::dataType(logitsShapeInfo));
LongType *dLdpShapeInfo =
ShapeBuilders::copyShapeInfoAndType(logitsShapeInfo, outType, false, block.getWorkspace());
return SHAPELIST(CONSTANT(dLdpShapeInfo));
}
} // namespace ops
} // namespace sd
#endif