Какова наилучшая практика для сопоставления сигнатуры модели TF2 keras с API классификации / прогнозирования / регрессии обслуживания TF в конвейере TFX? - PullRequest
1 голос
/ 29 мая 2020

Мы создаем автоматизированный конвейер TFX на основе Airflow и основали нашу модель на Учебнике Keras . Мы сохраняем модель keras следующим образом:

model.save(fn_args.serving_model_dir, save_format='tf',
           signatures=signatures,
           )

Этот signatures dict:

    signatures = {
    'serving_default':
        _get_serve_tf_examples_fn(model, tf_transform_output) \
            .get_concrete_function(
            tf.TensorSpec(
                shape=[None],
                dtype=tf.string,
                name='examples'
            )
        ),

    'prediction':
        get_request_url_fn(model, tf_transform_output) \
            .get_concrete_function(
            tf.TensorSpec(
                shape=[None],
                dtype=tf.string,
                name='prediction_examples'
            )
        ),
}

_get_serve_tf_examples_fn служит цели обеспечения компонента оценщика TFX дополнительными тензорами, тензоры не используются в модели для целей оценки модели. Это как в руководстве Keras TFX выше:

def _get_serve_tf_examples_fn(model, tf_transform_output):
    model.tft_layer = tf_transform_output.transform_features_layer()

    @tf.function
    def serve_tf_examples_fn(serialized_tf_examples):
        feature_spec = tf_transform_output.raw_feature_spec()
        feature_spec.pop(_LABEL_KEY)

        parsed_features = tf.io.parse_example(serialized_tf_examples, feature_spec)
        transformed_features = model.tft_layer(parsed_features)
        transformed_features.pop(_transformed_name(_LABEL_KEY))
        return model(transformed_features)

    return serve_tf_examples_fn

Вышеупомянутая модель 'interface' принимает TF.Examples, как это необходимо для компонента TFX Evaluator (TFMA).

Однако для обслуживания TF мы хотим иметь возможность отправлять 1 необработанную строку - просто URL - в REST API предсказателя обслуживания TF и ​​получать для нее прогнозируемую оценку. В настоящее время get_request_url_fn это:

def get_request_url_fn(model, tf_transform_output):
    model.tft_layer = tf_transform_output.transform_features_layer()

    @tf.function
    def serve_request_url_fn(request_url):
        feature_spec = tf_transform_output.raw_feature_spec()
        # Model requires just one of the features made available to other TFX components
        # Throw away the rest and leave just 'request_url'
        feature_spec = {'request_url': feature_spec['request_url']}

        parsed_features = tf.io.parse_example(request_url, feature_spec)
        transformed_features = model.tft_layer(parsed_features)
        transformed_features.pop(_transformed_name(_LABEL_KEY))
        return model(transformed_features)

    return serve_request_url_fn

Однако этот подход по-прежнему требует ввода в форме TF.Example. Это требует значительных накладных расходов со стороны клиента. А именно import tensorflow. Однако этот код работает:

url = f'http://{server}:8501/v1/models/wrcv3:predict'
headers = {"content-type": "application/json"}
url_request = b'index'
example = tf.train.Example(
            features=tf.train.Features(
              feature={"request_url": 
                          tf.train.Feature(bytes_list=tf.train.BytesList(value=[url_request]))
                      }
                )
            )
print(example)


data = {
  "signature_name":"prediction",
  "instances":[
    {
       "prediction_examples":{"b64": base64.b64encode(example.SerializeToString()).decode('utf-8')}
    }
  ]
}
data = json.dumps(data)
print(data)
json_response = requests.post(url, data=data, headers=headers)
print(json_response.content)
print(json_response.json)

Возвращается в результате:

features {
  feature {
    key: "request_url"
    value {
      bytes_list {
        value: "index"
      }
    }
  }
}

{"signature_name": "prediction", "instances": [{"prediction_examples": {"b64": "ChoKGAoLcmVxdWVzdF91cmwSCQoHCgVpbmRleA=="}}]}
b'{\n    "predictions": [[0.897708654]\n    ]\n}'
<bound method Response.json of <Response [200]>>

Когда мы отправляем строку в кодировке base64 вместо TF.Example, она явно терпит неудачу:

url = f'http://{server}:8501/v1/models/wrcv3:predict'
headers = {"content-type": "application/json"}
url_request = b'index.html'


data = {
  "signature_name":"prediction",
  "instances":[
    {
       "prediction_examples":{"b64": base64.b64encode(url_request).decode('UTF-8')}
    }
  ]
}
data = json.dumps(data)
print(data)
json_response = requests.post(url, data=data, headers=headers)
print(json_response.content)
print(json_response.json)

возвращается:

{"signature_name": "prediction", "instances": [{"prediction_examples": {"b64": "aW5kZXguaHRtbA=="}}]}
b'{ "error": "Could not parse example input, value: \\\'index.html\\\'\\n\\t [[{{node ParseExample/ParseExampleV2}}]]" }'
<bound method Response.json of <Response [400]>>

Вопрос: как должна выглядеть подпись / подпись, чтобы принимать необработанные строки? Если не нравится get_request_url_fn. Конечно, клиенту не нужно загружать TF только для того, чтобы сделать запрос ?

Сам веб-сайт TFX подробно описывает 3 protobufs для классификации / прогнозирования / регрессии здесь , но Это не интуитивно (для меня), как использовать эти 3 protobufs для создания нужного нам сопоставления.

Заранее глубокая благодарность.

1 Ответ

1 голос
/ 05 июня 2020

Согласно вашему коду входные данные функции serve_request_url_fn - это плотный тензор, но, возможно, вход вашего графа преобразования - это разреженный тензор.

Функция tf.io.parse_example знает, как десериализовать ваш tf.Example в Sparse Tensor, но если вы хотите отправить Tensor, не сериализуя его, вам следует вручную преобразовать его в Sparse Tensor и прекратите использование функции tf.io.parse.

Например:

@tf.function(input_signature=[tf.TensorSpec(shape=(None), dtype=tf.string, name='examples')])
def serve_request_url_fn(self, request_url):
    request_url_sp_tensor = tf.sparse.SparseTensor(
        indices=[[0, 0]],
        values=request_url,
        dense_shape=(1, 1)
    )
    parsed_features = {
        'request_url': request_url_sp_tensor,
    }
    transformed_features = self.model.tft_example_layer(parsed_features)
    transformed_features.pop(_transformed_name(_LABEL_KEY))
    return self.model(transformed_features)
...