Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check failed: NDIMS == dims() (2 vs. 3) when using TFDF RandomForestModel and using TF Serving #170

Open
alexctslade opened this issue Mar 26, 2023 · 1 comment
Labels
bug Something isn't working

Comments

@alexctslade
Copy link

Summary
Sometimes features passed to TF Serving need to be wrapped in brackets to match the dimension, and the correct dimensions are required.
However, in some cases if the incorrect dimensions are passed TF Serving crashes, when it would be expected to handle the error
In particular:

  • Calling RandomForestModel with numeric features and brackets causes TF Serving to fail (core dumped, Check failed: NDIMS == dims() (2 vs. 3))
  • Similarly calling RandomForestModel with a categorical features and allowing the model to 1-hot encode, use of brackets causes TF Serving to fail (core dumped, Check failed: NDIMS == dims() (2 vs. 3))
  • However, note that sometimes brackets are necessary, e.g, when using a Tensorflow model

Detail on the cases below:
(1) When TF serving is used with a Tensorflow model with numerical and categorical features, using tensorflow.feature_column to dummify the categorical features

  • brackets can be used around the floats or not
  • but brackets must be used around the categorical feature or an error is returned

(2) When TF serving is used with a TFDF Random Forest Model with only numeric features

  • calling without brackets works
  • calling with brackets causes Tensorflow Serving to crash (core dumped) with a "Check failed: NDIMS == dims() (2 vs. 3)" error

(3) When TF serving is used with a TFDF Random Forest Model with numeric features and categorical features, and a preprocessing model

  • works with brackets or without on any of the floats or categorical

(4) When TF serving is used with a TFDF Random Forest Model with numeric features and categorical features,
and the RandomForestModel categorical_set_split_max_num_items and categorical_set_split_min_item_frequency are used to dummify the categorical field

  • without brackets: works
  • with brackets: causes Tensorflow Serving to crash (core dumped) with a "Check failed: NDIMS == dims() (2 vs. 3)" error

Related issue
tensorflow/tensorflow#9505

System details
Python 3.10
Tensorflow version 2.11.1
Tensorflow Decision Forest 1.2.0
tensorflow/serving latest

Code to recreate

import numpy as np
import pandas as pd
import tensorflow as tf
from sklearn.model_selection import train_test_split
from tensorflow.keras import backend, Input, layers, Model
from tensorflow import feature_column, random, string

import requests
import json

"""Make some fake data"""
rows = []
for _ in range(10000):
    y = np.random.uniform(0, 10000)
    x1 = y + np.random.normal(1000, scale=500)
    x2 = x1 + np.random.normal(1000, scale=500)
    tmp_x3 = y + np.random.normal(1000, scale=500)
    rows.append([x1, x2, tmp_x3, y])
df = pd.DataFrame(rows, columns=['x1', 'x2', 'tmp_x3', 'y'])
df['x3'] = 'cat' + pd.qcut(df['tmp_x3'], q=20, retbins=False, labels=False).astype('str')
train, valid = train_test_split(df, test_size=0.15)
train, test = train_test_split(train, test_size=0.15)
print(train.shape[0])
print(valid.shape[0])
print(test.shape[0])

"""(1) Model with numerical and cat features"""
feature_layer_input = {}
feature_columns = []
for feature in ['x1', 'x2']:
    feature_columns.append(feature_column.numeric_column(feature))
    feature_layer_input[feature] = Input(shape=(1,), name=feature)
for feature in ['x3']:
    feature_column_emb = feature_column.categorical_column_with_hash_bucket(
        feature, hash_bucket_size=10
    )
    feature_columns.append(feature_column.indicator_column(feature_column_emb))
    feature_layer_input[feature] = Input(shape=(1,), dtype=string, name=feature)

feature_layer = layers.DenseFeatures(feature_columns)(feature_layer_input)
hidden_layer = layers.Dense(units=80, activation='relu')(feature_layer)
output_layer = layers.Dense(units=1, activation='relu')(hidden_layer)
model = Model(feature_layer_input, output_layer)
from tensorflow.keras.losses import MeanSquaredError
model.compile(loss=MeanSquaredError(), optimizer='adam')
model.summary()

from beeswax.hexagon.real_time_models.model_common.model_utils import df_to_dataset
from sklearn.metrics import r2_score
features = ['x1', 'x2', 'x3']
train_ds = df_to_dataset(train[features], labels=train['y'])
valid_ds = df_to_dataset(valid[features], labels=valid['y'])
test_ds = df_to_dataset(test[features], labels=test['y'])

model.fit(train_ds, epochs=10)

print(r2_score(valid['y'], model.predict(valid_ds)))
model.save('/home/username/model_assessment/test_model/1/')

"""
Now run TF Serving
docker run -p 8501:8501 --mount type=bind,source=/home/username/model_assessment/test_model,target=/models/test_model -e MODEL_NAME=test_model -t tensorflow/serving
"""

test_data = {"signature_name": "serving_default",
        "instances": [{"x1": [300],
            "x2": [220],
            "x3": ["cat11"]
        }]
}
headers = {"content-type": "application/json"}
response = requests.post('http://localhost:8501/v1/models/test_model:predict', data=json.dumps(test_data), headers=headers)
print(response.text)
# Success: '{\n    "predictions": [[250.152435]\n    ]\n}'

# Floats are fine without brackets
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": 300.0,
            "x2": 220.0,
            "x3": ["cat11"]
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

# But cat needs brackets
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": 300.0,
            "x2": 220.0,
            "x3": "cat11"
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model:predict', data=json.dumps(test_data), headers=headers)
print(response.text)


"""(2) Random Forest with TFDF - numerical features"""
import tensorflow_decision_forests as tfdf
from tensorflow_decision_forests.tensorflow import core_inference as tf_core
Task = tf_core.Task

rf = tfdf.keras.RandomForestModel(
    task=Task.REGRESSION,
    num_trees=100,
    max_depth=20,
    min_examples=5,
    num_candidate_attributes_ratio=0.45,
    split_axis="AXIS_ALIGNED", #"SPARSE_OBLIQUE",
    growing_strategy="LOCAL", #"BEST_FIRST_GLOBAL",
    )

features = ['x1', 'x2']
train_ds = df_to_dataset(train[features], labels=train['y'], batch_size=1000)
valid_ds = df_to_dataset(valid[features], labels=valid['y'], batch_size=1000)
test_ds = df_to_dataset(test[features], labels=test['y'], batch_size=1000)

rf.fit(train_ds)
print(r2_score(valid['y'], rf.predict(valid_ds)))

rf.save('/home/username/model_assessment/test_model_rf1/1/')

"""
Run model in TFServing
docker run -p 8501:8501 --mount type=bind,source=/home/username/model_assessment/test_model_rf1,target=/models/test_model_rf1 -e MODEL_NAME=test_model_rf1 -t tensorflow/serving
"""

# Without brackets, floats are fine
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": 300.0,
            "x2": 220.0,
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model_rf1:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

# With brackets, floats cause tf serving to fail
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": [300.0],
            "x2": [220.0],
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model_rf1:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

"""
Causes 
2023-03-21 10:31:24.921448: F external/org_tensorflow/tensorflow/core/framework/tensor_shape.cc:45] Check failed: NDIMS == dims() (2 vs. 3)Asking for tensor of 2 dimensions from a tensor of 3 dimensions
/usr/bin/tf_serving_entrypoint.sh: line 3:     7 Aborted              (core dumped) tensorflow_model_server --port=8500 --rest_api_port=8501 --model_name=${MODEL_NAME} --model_base_path=${MODEL_BASE_PATH}/${MODEL_NAME} "$@"
"""

"""(3) Now make a Random Forest with feature transformation on cat feature"""
feature_layer_input = {}
feature_columns = []
for feature in ['x1', 'x2']:
    feature_columns.append(feature_column.numeric_column(feature))
    feature_layer_input[feature] = Input(shape=(1,), name=feature)
for feature in ['x3']:
    feature_column_emb = feature_column.categorical_column_with_hash_bucket(
        feature, hash_bucket_size=10
    )
    feature_columns.append(feature_column.indicator_column(feature_column_emb))
    feature_layer_input[feature] = Input(shape=(1,), dtype=string, name=feature)

feature_layer = layers.DenseFeatures(feature_columns)(feature_layer_input)
feat_x3_model = Model(feature_layer_input, feature_layer)

rf = tfdf.keras.RandomForestModel(
    task=Task.REGRESSION,
    preprocessing=feat_x3_model,
    num_trees=100,
    max_depth=20,
    min_examples=5,
    num_candidate_attributes_ratio=0.45,
    split_axis="AXIS_ALIGNED", #"SPARSE_OBLIQUE",
    growing_strategy="LOCAL", #"BEST_FIRST_GLOBAL",
    )

features = ['x1', 'x2', 'x3']
train_ds = df_to_dataset(train[features], labels=train['y'], batch_size=1000)
valid_ds = df_to_dataset(valid[features], labels=valid['y'], batch_size=1000)
test_ds = df_to_dataset(test[features], labels=test['y'], batch_size=1000)

rf.fit(train_ds)
print(r2_score(valid['y'], rf.predict(valid_ds)))
rf.save('/home/username/model_assessment/test_model_rf2/1/')

"""
Run in TF Serving
docker run -p 8501:8501 --mount type=bind,source=/home/username/model_assessment/test_model_rf2,target=/models/test_model_rf2 -e MODEL_NAME=test_model_rf2 -t tensorflow/serving
"""

# Without brackets. Fine
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": 300.0,
            "x2": 220.0,
            "x3": "cat11"
        },
            {"x1": 400.0,
            "x2": 320.0,
            "x3": "cat11"
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model_rf2:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

# With brackets. Fine
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": [300.0],
            "x2": [220.0],
            "x3": ["cat11"]
        }]
}
headers = {"content-type": "application/json"}
response = requests.post('http://localhost:8501/v1/models/test_model_rf2:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

# Brackets on floats, none on cat. Fine
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": [[300.0]],
            "x2": [[[[220.0]]]],
            "x3": "cat11"
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model_rf2:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

# Brackets on cat, none on floats. Fine
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": 300.0,
            "x2": 220.0,
            "x3": ["cat11"]
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model_rf2:predict', data=json.dumps(test_data), headers=headers)
print(response.text)


#(4) Build a model with no preprocessing model, categorical dummifying handled by the model
rf = tfdf.keras.RandomForestModel(
    task=Task.REGRESSION,
    num_trees=100,
    max_depth=20,
    min_examples=5,
    num_candidate_attributes_ratio=0.45,
    split_axis="AXIS_ALIGNED", #"SPARSE_OBLIQUE",
    growing_strategy="LOCAL", #"BEST_FIRST_GLOBAL",
    categorical_set_split_max_num_items=100,
    categorical_set_split_min_item_frequency=1,
    )

features = ['x1', 'x2', 'x3']
train_ds = df_to_dataset(train[features], labels=train['y'], batch_size=1000)
valid_ds = df_to_dataset(valid[features], labels=valid['y'], batch_size=1000)
test_ds = df_to_dataset(test[features], labels=test['y'], batch_size=1000)

rf.fit(train_ds)
print(r2_score(valid['y'], rf.predict(valid_ds)))

rf.save('/home/username/username/model_assessment/test_model_rf3/1/')

"""
Run in TF Serving
docker run -p 8501:8501 --mount type=bind,source=/home/username/username/model_assessment/test_model_rf3,target=/models/test_model_rf3 -e MODEL_NAME=test_model_rf3 -t tensorflow/serving
"""

# No brackets works
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": 300.0,
            "x2": 220.0,
            "x3": "cat11"
        },
        {"x1": 330.0,
            "x2": 230.0,
            "x3": "cat13"
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model_rf3:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

# Brackets on any (float or categorical) causes TF serving to fail
test_data = {"signature_name": "serving_default",
        "instances": [{"x1": [300.0],
            "x2": [220.0],
            "x3": ["cat11"]
        }]
}
response = requests.post('http://localhost:8501/v1/models/test_model_rf3:predict', data=json.dumps(test_data), headers=headers)
print(response.text)

"""
Causes
2023-03-21 10:39:01.342750: F external/org_tensorflow/tensorflow/core/framework/tensor_shape.cc:45] Check failed: NDIMS == dims() (2 vs. 3)Asking for tensor of 2 dimensions from a tensor of 3 dimensions
/usr/bin/tf_serving_entrypoint.sh: line 3:     7 Aborted                 (core dumped) tensorflow_model_server --port=8500 --rest_api_port=8501 --model_name=${MODEL_NAME} --model_base_path=${MODEL_BASE_PATH}/${MODEL_NAME} "$@"
"""
@rstz
Copy link
Collaborator

rstz commented Apr 3, 2023

Hi,
thank you for the detailed report, I will look into this and report back!

@rstz rstz added the bug Something isn't working label Apr 3, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants