Skip to content

Commit 561471b

Browse files
lu-wang-gtflite-support-robot
authored andcommitted
Generalize the constraint for score_calibration in ImageClassifier
Major changes: (1) Use the default_score value from Metadata instead of always falling back to `kDefaultCalibratedScore` (-1), which was defined in ImageClassifier. (2) Removed the check for kMinCalibratedScore and kMaxCalibratedScore. First, it doesn't make sense to restrict the calibrated score to [0, 1]. The calibrated score is bounded by [0, scale]. Second, ComputeCalibratedScore returns either the default_score or the calibrated score derived from the sigmoid function. Therefore, it's redundant to check the value range once more in ImageClassifier. PiperOrigin-RevId: 398850436
1 parent f3e1530 commit 561471b

File tree

2 files changed

+34
-44
lines changed

2 files changed

+34
-44
lines changed

tensorflow_lite_support/cc/task/vision/image_classifier.cc

Lines changed: 12 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -46,26 +46,6 @@ using ::tflite::task::core::AssertAndReturnTypedTensor;
4646
using ::tflite::task::core::TaskAPIFactory;
4747
using ::tflite::task::core::TfLiteEngine;
4848

49-
// Default score value used as a fallback for classes that (1) have no score
50-
// calibration data or (2) have a very low confident uncalibrated score, i.e.
51-
// lower than the `min_uncalibrated_score` threshold.
52-
//
53-
// (1) This happens when the ScoreCalibration does not cover all the classes
54-
// listed in the label map. This can be used to enforce the blacklisting of
55-
// given classes so that they are never returned.
56-
//
57-
// (2) This is an optional threshold provided part of the calibration data. It
58-
// is used to mitigate false alarms on some classes.
59-
//
60-
// In both cases, a class that gets assigned a score of -1 is never returned as
61-
// it gets discarded by the `score_threshold` check (see post-processing logic).
62-
constexpr float kDefaultCalibratedScore = -1.0f;
63-
64-
// Calibrated scores should be in the [0, 1] range, otherwise an error is
65-
// returned at post-processing time.
66-
constexpr float kMinCalibratedScore = 0.0f;
67-
constexpr float kMaxCalibratedScore = 1.0f;
68-
6949
} // namespace
7050

7151
/* static */
@@ -378,12 +358,6 @@ absl::Status ImageClassifier::InitScoreCalibrations() {
378358
continue;
379359
}
380360

381-
// Use a specific default score instead of the one specified by default in
382-
// cc/task/vision/utils/score_calibration.h. See `kDefaultCalibratedScore`
383-
// documentation for more details.
384-
classification_heads_[i].calibration_params->default_score =
385-
kDefaultCalibratedScore;
386-
387361
score_calibrations_[i] = absl::make_unique<ScoreCalibration>();
388362
if (score_calibrations_[i] == nullptr) {
389363
return CreateStatusWithPayload(
@@ -453,23 +427,20 @@ StatusOr<ClassificationResult> ImageClassifier::Postprocess(
453427
for (auto& score_pair : score_pairs) {
454428
const std::string& class_name =
455429
head.label_map_items[score_pair.first].name;
430+
431+
// In ComputeCalibratedScore, score_pair.second is set to the
432+
// default_score value from metadata [1] if the category (1) has no
433+
// score calibration data or (2) has a very low confident uncalibrated
434+
// score, i.e. lower than the `min_uncalibrated_score` threshold.
435+
// Otherwise, score_pair.second is calculated based on the selected
436+
// score transformation function, and the value is guaranteed to be in
437+
// the range of [0, scale], where scale is a label-dependent sigmoid
438+
// parameter.
439+
//
440+
// [1]:
441+
// https://github.com/tensorflow/tflite-support/blob/af26cb6952ccdeee0e849df2b93dbe7e57f6bc48/tensorflow_lite_support/metadata/metadata_schema.fbs#L453
456442
score_pair.second = score_calibrations_[i]->ComputeCalibratedScore(
457443
class_name, score_pair.second);
458-
if (score_pair.second > kMaxCalibratedScore) {
459-
return CreateStatusWithPayload(
460-
StatusCode::kInternal,
461-
absl::StrFormat("calibrated score is too high: got %f, expected "
462-
"%f as maximum.",
463-
score_pair.second, kMaxCalibratedScore));
464-
}
465-
if (score_pair.second != kDefaultCalibratedScore &&
466-
score_pair.second < kMinCalibratedScore) {
467-
return CreateStatusWithPayload(
468-
StatusCode::kInternal,
469-
absl::StrFormat("calibrated score is too low: got %f, expected "
470-
"%f as minimum.",
471-
score_pair.second, kMinCalibratedScore));
472-
}
473444
}
474445
}
475446

tensorflow_lite_support/cc/task/vision/utils/score_calibration.cc

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ limitations under the License.
1414
==============================================================================*/
1515
#include "tensorflow_lite_support/cc/task/vision/utils/score_calibration.h"
1616

17+
#include <algorithm>
1718
#include <cmath>
1819
#include <memory>
1920
#include <utility>
@@ -95,6 +96,17 @@ StatusOr<Sigmoid> SigmoidFromLabelAndLine(absl::string_view label,
9596
TfLiteSupportStatus::kMetadataMalformedScoreCalibrationError);
9697
}
9798
}
99+
100+
// Verify if scale is a non-negative value.
101+
if (float_params[0] < 0) {
102+
return CreateStatusWithPayload(
103+
StatusCode::kInvalidArgument,
104+
absl::StrFormat(
105+
"Expected scale to be a non-negative value, but got %f.",
106+
float_params[0]),
107+
TfLiteSupportStatus::kMetadataMalformedScoreCalibrationError);
108+
}
109+
98110
Sigmoid sigmoid;
99111
sigmoid.label = std::string(label);
100112
sigmoid.scale = float_params[0];
@@ -160,13 +172,20 @@ float ScoreCalibration::ComputeCalibratedScore(const std::string& label,
160172

161173
// For numerical stability use 1 / (1+exp(-x)) when scale_shifted_score >= 0
162174
// and exp(x) / (1+exp(x)) when scale_shifted_score < 0.
175+
float calibrated_score;
163176
if (scale_shifted_score >= 0.0) {
164-
return sigmoid.value().scale /
165-
(1.0 + std::exp(static_cast<double>(-scale_shifted_score)));
177+
calibrated_score =
178+
sigmoid.value().scale /
179+
(1.0 + std::exp(static_cast<double>(-scale_shifted_score)));
166180
} else {
167181
float score_exp = std::exp(static_cast<double>(scale_shifted_score));
168-
return sigmoid.value().scale * score_exp / (1.0 + score_exp);
182+
calibrated_score = sigmoid.value().scale * score_exp / (1.0 + score_exp);
169183
}
184+
// Scale is non-negative (checked in SigmoidFromLabelAndLine),
185+
// thus calibrated_score should be in the range of [0, scale]. However, due to
186+
// numberical stability issue, it may fall out of the boundary. Cap the value
187+
// to [0, scale] instead.
188+
return std::max(std::min(calibrated_score, sigmoid.value().scale), 0.0f);
170189
}
171190

172191
absl::optional<Sigmoid> ScoreCalibration::FindSigmoidParameters(

0 commit comments

Comments
 (0)