Skip to content

Commit 30dc6c7

Browse files
author
Michael Trinh
committed
feature: support JSON for input dataset and model output
1 parent cb0185c commit 30dc6c7

File tree

2 files changed

+78
-28
lines changed

2 files changed

+78
-28
lines changed

src/sagemaker/clarify.py

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
in (
5050
"text/csv",
5151
"application/jsonlines",
52+
"application/json",
5253
"application/sagemakercapturejson",
5354
"application/x-parquet",
5455
"application/x-image",
@@ -311,7 +312,7 @@ def __init__(
311312
s3_analysis_config_output_path: Optional[str] = None,
312313
label: Optional[str] = None,
313314
headers: Optional[List[str]] = None,
314-
features: Optional[List[str]] = None,
315+
features: Optional[str] = None,
315316
dataset_type: str = "text/csv",
316317
s3_compression_type: str = "None",
317318
joinsource: Optional[Union[str, int]] = None,
@@ -331,12 +332,18 @@ def __init__(
331332
If this field is None, then the ``s3_output_path`` will be used
332333
to store the ``analysis_config`` output.
333334
label (str): Target attribute of the model required by bias metrics. Specified as
334-
column name or index for CSV dataset or as JMESPath expression for JSONLines.
335+
column name or index for CSV dataset or a JMESPath expression for JSON/JSON Lines.
335336
*Required parameter* except for when the input dataset does not contain the label.
336-
features (List[str]): JMESPath expression to locate the feature columns for
337-
bias metrics if the dataset format is JSONLines.
337+
Note: For JSON, the JMESPath query must result in a list of labels for each
338+
sample. For JSON Lines, it must result in the label for each line.
339+
Only a single label per sample is supported at this time.
340+
features (str): JMESPath expression to locate the feature values
341+
if the dataset format is JSON/JSON Lines.
342+
Note: For JSON, the JMESPath query must result in a 2-D list (or a matrix) of
343+
feature values. For JSON Lines, it must result in a 1-D list of features for each
344+
line.
338345
dataset_type (str): Format of the dataset. Valid values are ``"text/csv"`` for CSV,
339-
``"application/jsonlines"`` for JSONLines, and
346+
``"application/jsonlines"`` for JSON Lines, ``"application/json"`` for JSON, and
340347
``"application/x-parquet"`` for Parquet.
341348
s3_compression_type (str): Valid options are "None" or ``"Gzip"``.
342349
joinsource (str or int): The name or index of the column in the dataset that
@@ -359,6 +366,7 @@ def __init__(
359366
360367
Clarify will not use the ``joinsource`` column and columns present in the facet
361368
dataset when calling model inference APIs.
369+
Note: this is only supported for ``"text/csv"`` dataset type.
362370
facet_headers (list[str]): List of column names in the facet dataset.
363371
predicted_label_dataset_uri (str): Dataset S3 prefix/object URI with predicted labels,
364372
which are used directly for analysis instead of making model inference API calls.
@@ -368,11 +376,16 @@ def __init__(
368376
* If the dataset and predicted label dataset are in multiple files (either one),
369377
then an index column, ``joinsource``, is required to join the two datasets.
370378
379+
Note: this is only supported for ``"text/csv"`` dataset type.
371380
predicted_label_headers (list[str]): List of column names in the predicted label dataset
372381
predicted_label (str or int): Predicted label of the target attribute of the model
373-
required for running bias analysis. Specified as column name or index for CSV data.
382+
required for running bias analysis. Specified as column name or index for CSV data,
383+
or a JMESPath expression for JSON/JSON Lines.
374384
Clarify uses the predicted labels directly instead of making model inference API
375385
calls.
386+
Note: For JSON, the JMESPath query must result in a list of predicted labels for
387+
each sample. For JSON Lines, it must result in the predicted label for each line.
388+
Only a single predicted label per sample is supported at this time.
376389
excluded_columns (list[int] or list[str]): A list of names or indices of the columns
377390
which are to be excluded from making model inference API calls.
378391
@@ -384,15 +397,20 @@ def __init__(
384397
if dataset_type not in [
385398
"text/csv",
386399
"application/jsonlines",
400+
"application/json",
387401
"application/x-parquet",
388402
"application/x-image",
389403
]:
390404
raise ValueError(
391405
f"Invalid dataset_type '{dataset_type}'."
392406
f" Please check the API documentation for the supported dataset types."
393407
)
394-
# parameters for analysis on datasets without facets are only supported for CSV datasets
395-
if dataset_type != "text/csv":
408+
if dataset_type not in [
409+
"text/csv",
410+
"application/jsonlines",
411+
"application/json",
412+
"application/x-parquet",
413+
]:
396414
if predicted_label:
397415
raise ValueError(
398416
f"The parameter 'predicted_label' is not supported"
@@ -405,6 +423,8 @@ def __init__(
405423
f" for dataset_type '{dataset_type}'."
406424
f" Please check the API documentation for the supported dataset types."
407425
)
426+
# parameters for analysis on datasets without facets are only supported for CSV datasets
427+
if dataset_type != "text/csv":
408428
if facet_dataset_uri or facet_headers:
409429
raise ValueError(
410430
f"The parameters 'facet_dataset_uri' and 'facet_headers'"
@@ -571,11 +591,13 @@ def __init__(
571591
Cannot be set when ``endpoint_name`` is set.
572592
Must be set with ``instance_count``, ``model_name``
573593
accept_type (str): The model output format to be used for getting inferences with the
574-
shadow endpoint. Valid values are ``"text/csv"`` for CSV and
575-
``"application/jsonlines"``. Default is the same as ``content_type``.
594+
shadow endpoint. Valid values are ``"text/csv"`` for CSV,
595+
``"application/jsonlines"`` for JSON Lines, and ``"application/json"`` for JSON.
596+
Default is the same as ``content_type``.
576597
content_type (str): The model input format to be used for getting inferences with the
577598
shadow endpoint. Valid values are ``"text/csv"`` for CSV and
578-
``"application/jsonlines"``. Default is the same as ``dataset_format``.
599+
``"application/jsonlines"`` for JSON Lines. Default is the same as
600+
``dataset_format``.
579601
content_template (str): A template string to be used to construct the model input from
580602
dataset instances. It is only used when ``model_content_type`` is
581603
``"application/jsonlines"``. The template should have one and only one placeholder,
@@ -641,7 +663,7 @@ def __init__(
641663
)
642664
self.predictor_config["endpoint_name_prefix"] = endpoint_name_prefix
643665
if accept_type is not None:
644-
if accept_type not in ["text/csv", "application/jsonlines"]:
666+
if accept_type not in ["text/csv", "application/jsonlines", "application/json"]:
645667
raise ValueError(
646668
f"Invalid accept_type {accept_type}."
647669
f" Please choose text/csv or application/jsonlines."

tests/unit/test_clarify.py

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,39 +42,54 @@ def test_uri():
4242
assert "306415355426.dkr.ecr.us-west-2.amazonaws.com/sagemaker-clarify-processing:1.0" == uri
4343

4444

45-
def test_data_config():
45+
@pytest.mark.parametrize(
46+
("dataset_type", "excluded_columns", "predicted_label"),
47+
[
48+
("text/csv", ["F4"], "Predicted Label"),
49+
("application/jsonlines", ["F4"], "Predicted Label"),
50+
("application/json", ["F4"], "Predicted Label"),
51+
("application/x-parquet", ["F4"], "Predicted Label"),
52+
],
53+
)
54+
def test_data_config(dataset_type, excluded_columns, predicted_label):
4655
# facets in input dataset
4756
s3_data_input_path = "s3://path/to/input.csv"
4857
s3_output_path = "s3://path/to/output"
4958
label_name = "Label"
50-
headers = [
51-
"Label",
52-
"F1",
53-
"F2",
54-
"F3",
55-
"F4",
56-
]
57-
dataset_type = "text/csv"
59+
headers = ["Label", "F1", "F2", "F3", "F4", "Predicted Label"]
5860
data_config = DataConfig(
5961
s3_data_input_path=s3_data_input_path,
6062
s3_output_path=s3_output_path,
6163
label=label_name,
6264
headers=headers,
6365
dataset_type=dataset_type,
66+
excluded_columns=excluded_columns,
67+
predicted_label=predicted_label,
6468
)
6569

6670
expected_config = {
67-
"dataset_type": "text/csv",
71+
"dataset_type": dataset_type,
6872
"headers": headers,
6973
"label": "Label",
7074
}
75+
if excluded_columns:
76+
expected_config["excluded_columns"] = excluded_columns
77+
if predicted_label:
78+
expected_config["predicted_label"] = predicted_label
7179

7280
assert expected_config == data_config.get_config()
7381
assert s3_data_input_path == data_config.s3_data_input_path
7482
assert s3_output_path == data_config.s3_output_path
7583
assert "None" == data_config.s3_compression_type
7684
assert "FullyReplicated" == data_config.s3_data_distribution_type
7785

86+
87+
def test_data_config_with_separate_facet_dataset():
88+
s3_data_input_path = "s3://path/to/input.csv"
89+
s3_output_path = "s3://path/to/output"
90+
label_name = "Label"
91+
headers = ["Label", "F1", "F2", "F3", "F4"]
92+
7893
# facets NOT in input dataset
7994
joinsource = 5
8095
facet_dataset_uri = "s3://path/to/facet.csv"
@@ -89,7 +104,7 @@ def test_data_config():
89104
s3_output_path=s3_output_path,
90105
label=label_name,
91106
headers=headers,
92-
dataset_type=dataset_type,
107+
dataset_type="text/csv",
93108
joinsource=joinsource,
94109
facet_dataset_uri=facet_dataset_uri,
95110
facet_headers=facet_headers,
@@ -126,7 +141,7 @@ def test_data_config():
126141
s3_output_path=s3_output_path,
127142
label=label_name,
128143
headers=headers,
129-
dataset_type=dataset_type,
144+
dataset_type="text/csv",
130145
joinsource=joinsource,
131146
excluded_columns=excluded_columns,
132147
)
@@ -158,7 +173,7 @@ def test_invalid_data_config():
158173
DataConfig(
159174
s3_data_input_path="s3://bucket/inputpath",
160175
s3_output_path="s3://bucket/outputpath",
161-
dataset_type="application/x-parquet",
176+
dataset_type="application/x-image",
162177
predicted_label="label",
163178
)
164179
error_msg = r"^The parameter 'excluded_columns' is not supported for dataset_type"
@@ -344,12 +359,25 @@ def test_facet_of_bias_config(facet_name, facet_values_or_threshold, expected_re
344359
assert bias_config.get_config() == expected_config
345360

346361

347-
def test_model_config():
362+
@pytest.mark.parametrize(
363+
("content_type", "accept_type"),
364+
[
365+
# All the combinations of content_type and accept_type should be acceptable
366+
("text/csv", "text/csv"),
367+
("application/jsonlines", "application/jsonlines"),
368+
("text/csv", "application/json"),
369+
("application/jsonlines", "application/json"),
370+
("application/jsonlines", "text/csv"),
371+
("image/jpeg", "text/csv"),
372+
("image/jpg", "text/csv"),
373+
("image/png", "text/csv"),
374+
("application/x-npy", "text/csv"),
375+
],
376+
)
377+
def test_valid_model_config(content_type, accept_type):
348378
model_name = "xgboost-model"
349379
instance_type = "ml.c5.xlarge"
350380
instance_count = 1
351-
accept_type = "text/csv"
352-
content_type = "application/jsonlines"
353381
custom_attributes = "c000b4f9-df62-4c85-a0bf-7c525f9104a4"
354382
target_model = "target_model_name"
355383
accelerator_type = "ml.eia1.medium"

0 commit comments

Comments
 (0)