Skip to content

Commit b5cc57c

Browse files
lu-wang-gtflite-support-robot
authored andcommitted
Add Sanity check to score calibration file
(1) Check if each row of the score calibration file has 3 or 4 elements (2) Check if scale (the first element) is non-negative. PiperOrigin-RevId: 399709641
1 parent b0c5182 commit b5cc57c

File tree

7 files changed

+81
-17
lines changed

7 files changed

+81
-17
lines changed

tensorflow_lite_support/metadata/metadata_schema.fbs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ enum AssociatedFileType : byte {
119119
// Contains sigmoid-based score calibration parameters, formatted as CSV.
120120
// Lines contain for each index of an output tensor the scale, slope, offset
121121
// and (optional) min_score parameters to be used for sigmoid fitting (in this
122-
// order and in `strtof`-compatible [1] format).
122+
// order and in `strtof`-compatible [1] format). Scale should be a
123+
// non-negative value.
123124
// A line may be left empty to default calibrated scores for this index to
124125
// default_score.
125126
// In summary, each line should thus contain 0, 3 or 4 comma-separated values.

tensorflow_lite_support/metadata/python/metadata_writers/metadata_info.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
# ==============================================================================
1515
"""Helper classes for common model metadata information."""
1616

17+
import csv
1718
import os
1819
from typing import Optional, List, Type
1920

@@ -273,11 +274,28 @@ def __init__(self,
273274
file_path: file_path of the score calibration file [1].
274275
[1]:
275276
https://github.com/tensorflow/tflite-support/blob/5e0cdf5460788c481f5cd18aab8728ec36cf9733/tensorflow_lite_support/metadata/metadata_schema.fbs#L122
277+
278+
Raises:
279+
ValueError: if the score_calibration file is malformed.
276280
"""
277281
self._score_transformation_type = score_transformation_type
278282
self._default_score = default_score
279283
self._file_path = file_path
280284

285+
# Sanity check the score calibration file.
286+
with open(self._file_path) as calibration_file:
287+
csv_reader = csv.reader(calibration_file, delimiter=",")
288+
for row in csv_reader:
289+
if row and len(row) != 3 and len(row) != 4:
290+
raise ValueError(
291+
"Expected empty lines or 3 or 4 parameters per line in score" +
292+
" calibration file, but got {}.".format(len(row)))
293+
294+
if row and float(row[0]) < 0:
295+
raise ValueError(
296+
"Expected scale to be a non-negative value, but got {}.".format(
297+
float(row[0])))
298+
281299
def create_metadata(self) -> _metadata_fb.ProcessUnitT:
282300
"""Creates the score calibration metadata based on the information.
283301

tensorflow_lite_support/metadata/python/tests/metadata_writers/BUILD

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ py_test(
4141
py_test(
4242
name = "metadata_info_test",
4343
srcs = ["metadata_info_test.py"],
44-
data = ["//tensorflow_lite_support/metadata/python/tests/testdata:test_files"],
44+
data = [
45+
"//tensorflow_lite_support/metadata/python/tests/testdata:test_files",
46+
"//tensorflow_lite_support/metadata/python/tests/testdata/image_classifier:test_files",
47+
],
4548
python_version = "PY3",
4649
srcs_version = "PY3",
4750
deps = [

tensorflow_lite_support/metadata/python/tests/metadata_writers/audio_classifier_test.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
_MULTIHEAD_MODEL = "../testdata/audio_classifier/two_heads.tflite"
2828
_YAMNET_LABEL_FILE = "../testdata/audio_classifier/yamnet_521_labels.txt"
2929
_LABEL_FILE = "../testdata/audio_classifier/labelmap.txt"
30-
_SCORE_CALIBRATION_FILE = "../testdata/audio_classifier/score_calibration.txt"
3130
_DEFAULT_SCORE_CALIBRATION_VALUE = 0.2
3231
_JSON_FOR_INFERENCE_DYNAMIC = "../testdata/audio_classifier/yamnet_tfhub.json"
3332
_JSON_FOR_INFERENCE_FIXED = "../testdata/audio_classifier/yamnet_wavin_quantized_mel_relu6.json"
@@ -46,7 +45,8 @@ def test_create_for_inference_should_succeed_dynamic_input_shape_model(self):
4645
_CHANNELS, [_LABEL_FILE],
4746
metadata_info.ScoreCalibrationMd(
4847
_metadata_fb.ScoreTransformationType.LOG,
49-
_DEFAULT_SCORE_CALIBRATION_VALUE, _SCORE_CALIBRATION_FILE))
48+
_DEFAULT_SCORE_CALIBRATION_VALUE,
49+
test_utils.create_calibration_file(self.get_temp_dir())))
5050

5151
metadata_json = writer.get_metadata_json()
5252
expected_json = test_utils.load_file(_JSON_FOR_INFERENCE_DYNAMIC, "r")
@@ -59,7 +59,8 @@ def test_create_for_inference_should_succeed_with_fixed_input_shape_model(
5959
[_YAMNET_LABEL_FILE],
6060
metadata_info.ScoreCalibrationMd(
6161
_metadata_fb.ScoreTransformationType.LOG,
62-
_DEFAULT_SCORE_CALIBRATION_VALUE, _SCORE_CALIBRATION_FILE))
62+
_DEFAULT_SCORE_CALIBRATION_VALUE,
63+
test_utils.create_calibration_file(self.get_temp_dir())))
6364

6465
metadata_json = writer.get_metadata_json()
6566
expected_json = test_utils.load_file(_JSON_FOR_INFERENCE_FIXED, "r")
@@ -83,6 +84,11 @@ def test_create_from_metadata_info_by_default_succeeds_for_multihead(self):
8384
self.assertEqual(metadata_json, expected_json)
8485

8586
def test_create_from_metadata_info_succeeds_for_multihead(self):
87+
calibration_file1 = test_utils.create_calibration_file(
88+
self.get_temp_dir(), "score_cali_1.txt")
89+
calibration_file2 = test_utils.create_calibration_file(
90+
self.get_temp_dir(), "score_cali_2.txt")
91+
8692
general_md = metadata_info.GeneralMd(name="AudioClassifier")
8793
input_md = metadata_info.InputAudioTensorMd(
8894
name="audio_clip", sample_rate=_SAMPLE_RATE, channels=_CHANNELS)
@@ -97,7 +103,7 @@ def test_create_from_metadata_info_succeeds_for_multihead(self):
97103
],
98104
score_calibration_md=metadata_info.ScoreCalibrationMd(
99105
_metadata_fb.ScoreTransformationType.LOG,
100-
_DEFAULT_SCORE_CALIBRATION_VALUE, "score_cali_1.txt"),
106+
_DEFAULT_SCORE_CALIBRATION_VALUE, calibration_file1),
101107
tensor_name="Identity_1")
102108
output_head_md_2 = metadata_info.ClassificationTensorMd(
103109
name="head2",
@@ -107,7 +113,7 @@ def test_create_from_metadata_info_succeeds_for_multihead(self):
107113
],
108114
score_calibration_md=metadata_info.ScoreCalibrationMd(
109115
_metadata_fb.ScoreTransformationType.LOG,
110-
_DEFAULT_SCORE_CALIBRATION_VALUE, "score_cali_2.txt"),
116+
_DEFAULT_SCORE_CALIBRATION_VALUE, calibration_file2),
111117
tensor_name="Identity")
112118

113119
writer = (
@@ -139,7 +145,8 @@ def test_create_for_inference_fails_with_wrong_sample_rate(
139145
_CHANNELS, [_LABEL_FILE],
140146
metadata_info.ScoreCalibrationMd(
141147
_metadata_fb.ScoreTransformationType.LOG,
142-
_DEFAULT_SCORE_CALIBRATION_VALUE, _SCORE_CALIBRATION_FILE))
148+
_DEFAULT_SCORE_CALIBRATION_VALUE,
149+
test_utils.create_calibration_file(self.get_temp_dir())))
143150

144151
self.assertEqual(
145152
"sample_rate should be positive, but got {}.".format(wrong_sample_rate),
@@ -164,7 +171,8 @@ def test_create_for_inference_fails_with_wrong_channels(self, wrong_channels):
164171
wrong_channels, [_LABEL_FILE],
165172
metadata_info.ScoreCalibrationMd(
166173
_metadata_fb.ScoreTransformationType.LOG,
167-
_DEFAULT_SCORE_CALIBRATION_VALUE, _SCORE_CALIBRATION_FILE))
174+
_DEFAULT_SCORE_CALIBRATION_VALUE,
175+
test_utils.create_calibration_file(self.get_temp_dir())))
168176

169177
self.assertEqual(
170178
"channels should be positive, but got {}.".format(wrong_channels),

tensorflow_lite_support/metadata/python/tests/metadata_writers/image_classifier_test.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ def test_create_for_inference_should_succeed(self, model_file, golden_json):
5454
[_LABEL_FILE],
5555
metadata_info.ScoreCalibrationMd(
5656
_metadata_fb.ScoreTransformationType.LOG,
57-
_DEFAULT_SCORE_CALIBRATION_VALUE, _SCORE_CALIBRATION_FILE))
57+
_DEFAULT_SCORE_CALIBRATION_VALUE,
58+
test_utils.get_resource_path(_SCORE_CALIBRATION_FILE)))
5859

5960
metadata_json = writer.get_metadata_json()
6061
expected_json = test_utils.load_file(golden_json, "r")

tensorflow_lite_support/metadata/python/tests/metadata_writers/metadata_info_test.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
"""Tests for metadata info classes."""
1616

1717
from absl.testing import parameterized
18-
1918
import tensorflow as tf
2019

2120
import flatbuffers
@@ -25,6 +24,9 @@
2524
from tensorflow_lite_support.metadata.python.metadata_writers import metadata_info
2625
from tensorflow_lite_support.metadata.python.tests.metadata_writers import test_utils
2726

27+
_SCORE_CALIBRATION_FILE = test_utils.get_resource_path(
28+
"../testdata/image_classifier/score_calibration.txt")
29+
2830

2931
class GeneralMdTest(tf.test.TestCase):
3032

@@ -280,7 +282,6 @@ class ClassificationTensorMdTest(tf.test.TestCase, parameterized.TestCase):
280282
_DESCRIPTION = "The classification result tensor."
281283
_LABEL_FILE_EN = "labels.txt"
282284
_LABEL_FILE_CN = "labels_cn.txt" # Locale label file in Chinese.
283-
_SCORE_CALIBRATION_FILE = "score_calibration.txt"
284285
_CALIBRATION_DEFAULT_SCORE = 0.2
285286
_EXPECTED_FLOAT_TENSOR_JSON = "../testdata/classification_tensor_float_meta.json"
286287
_EXPECTED_UINT8_TENSOR_JSON = "../testdata/classification_tensor_uint8_meta.json"
@@ -307,7 +308,7 @@ def test_create_metadata_should_succeed(self, tensor_type, golden_json):
307308
file_path=self._LABEL_FILE_CN, locale="cn")
308309
score_calibration_md = metadata_info.ScoreCalibrationMd(
309310
_metadata_fb.ScoreTransformationType.IDENTITY,
310-
self._CALIBRATION_DEFAULT_SCORE, self._SCORE_CALIBRATION_FILE)
311+
self._CALIBRATION_DEFAULT_SCORE, _SCORE_CALIBRATION_FILE)
311312

312313
tesnor_md = metadata_info.ClassificationTensorMd(
313314
name=self._NAME,
@@ -397,16 +398,15 @@ def test_create_metadata_should_succeed(self):
397398
self.assertEqual(metadata_json, expected_json)
398399

399400

400-
class ScoreClalibrationMdTest(tf.test.TestCase):
401+
class ScoreCalibrationMdTest(tf.test.TestCase):
401402
_DEFAULT_VALUE = 0.2
402-
_SCORE_CALIBRATION_FILE = "score_calibration.txt"
403403
_EXPECTED_TENSOR_JSON = "../testdata/score_calibration_tensor_meta.json"
404404
_EXPECTED_MODEL_META_JSON = "../testdata/score_calibration_file_meta.json"
405405

406406
def test_create_metadata_should_succeed(self):
407407
score_calibration_md = metadata_info.ScoreCalibrationMd(
408408
_metadata_fb.ScoreTransformationType.LOG, self._DEFAULT_VALUE,
409-
self._SCORE_CALIBRATION_FILE)
409+
_SCORE_CALIBRATION_FILE)
410410
score_calibration_metadata = score_calibration_md.create_metadata()
411411

412412
metadata_json = _metadata.convert_to_json(
@@ -418,7 +418,7 @@ def test_create_metadata_should_succeed(self):
418418
def test_create_score_calibration_file_md_should_succeed(self):
419419
score_calibration_md = metadata_info.ScoreCalibrationMd(
420420
_metadata_fb.ScoreTransformationType.LOG, self._DEFAULT_VALUE,
421-
self._SCORE_CALIBRATION_FILE)
421+
_SCORE_CALIBRATION_FILE)
422422
score_calibration_file_md = (
423423
score_calibration_md.create_score_calibration_file_md())
424424
file_metadata = score_calibration_file_md.create_metadata()
@@ -435,6 +435,28 @@ def test_create_score_calibration_file_md_should_succeed(self):
435435
expected_json = test_utils.load_file(self._EXPECTED_MODEL_META_JSON, "r")
436436
self.assertEqual(metadata_json, expected_json)
437437

438+
def test_create_score_calibration_file_fails_with_less_colunms(self):
439+
malformed_calibration_file = test_utils.create_calibration_file(
440+
self.get_temp_dir(), content="1.0,0.2")
441+
442+
with self.assertRaisesRegex(
443+
ValueError,
444+
"Expected empty lines or 3 or 4 parameters per line in score" +
445+
" calibration file, but got 2."):
446+
metadata_info.ScoreCalibrationMd(_metadata_fb.ScoreTransformationType.LOG,
447+
self._DEFAULT_VALUE,
448+
malformed_calibration_file)
449+
450+
def test_create_score_calibration_file_fails_with_negative_scale(self):
451+
malformed_calibration_file = test_utils.create_calibration_file(
452+
self.get_temp_dir(), content="-1.0,0.2,0.1")
453+
454+
with self.assertRaisesRegex(
455+
ValueError, "Expected scale to be a non-negative value, but got -1.0."):
456+
metadata_info.ScoreCalibrationMd(_metadata_fb.ScoreTransformationType.LOG,
457+
self._DEFAULT_VALUE,
458+
malformed_calibration_file)
459+
438460

439461
def _create_dummy_model_metadata_with_tensor(
440462
tensor_metadata: _metadata_fb.TensorMetadataT) -> bytes:

tensorflow_lite_support/metadata/python/tests/metadata_writers/test_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,21 @@
1414
# ==============================================================================
1515
"""Test utils for MetadataWriter."""
1616

17+
import os
1718
from typing import Union
1819
from tensorflow.python.platform import resource_loader
1920

2021

22+
def create_calibration_file(file_dir: str,
23+
file_name: str = "score_calibration.txt",
24+
content: str = "1.0,2.0,3.0,4.0") -> str:
25+
"""Creates the calibration file."""
26+
calibration_file = os.path.join(file_dir, file_name)
27+
with open(calibration_file, mode="w") as file:
28+
file.write(content)
29+
return calibration_file
30+
31+
2132
def load_file(file_name: str, mode: str = "rb") -> Union[str, bytes]:
2233
"""Loads files from resources."""
2334
file_path = get_resource_path(file_name)

0 commit comments

Comments
 (0)