示例#1
0
文件: onnx.py 项目: iPieter/kiwi
def log_model(onnx_model,
              artifact_path,
              conda_env=None,
              registered_model_name=None,
              signature: ModelSignature = None,
              input_example: ModelInputExample = None):
    """
    Log an ONNX model as an MLflow artifact for the current run.

    :param onnx_model: ONNX model to be saved.
    :param artifact_path: Run-relative artifact path.
    :param conda_env: Either a dictionary representation of a Conda environment or the path to a
                      Conda environment yaml file. If provided, this decsribes the environment
                      this model should be run in. At minimum, it should specify the dependencies
                      contained in :func:`get_default_conda_env()`. If `None`, the default
                      :func:`get_default_conda_env()` environment is added to the model.
                      The following is an *example* dictionary representation of a Conda
                      environment::

                        {
                            'name': 'mlflow-env',
                            'channels': ['defaults'],
                            'dependencies': [
                                'python=3.6.0',
                                'onnx=1.4.1',
                                'onnxruntime=0.3.0'
                            ]
                        }
    :param registered_model_name: (Experimental) If given, create a model version under
                                  ``registered_model_name``, also creating a registered model if one
                                  with the given name does not exist.

    :param signature: (Experimental) :py:class:`ModelSignature <mlflow.models.ModelSignature>`
                      describes model input and output :py:class:`Schema <mlflow.types.Schema>`.
                      The model signature can be :py:func:`inferred <mlflow.models.infer_signature>`
                      from datasets with valid model input (e.g. the training dataset with target
                      column omitted) and valid model output (e.g. model predictions generated on
                      the training dataset), for example:

                      .. code-block:: python

                        from mlflow.models.signature import infer_signature
                        train = df.drop_column("target_label")
                        predictions = ... # compute model predictions
                        signature = infer_signature(train, predictions)
    :param input_example: (Experimental) Input example provides one or several instances of valid
                          model input. The example can be used as a hint of what data to feed the
                          model. The given example will be converted to a Pandas DataFrame and then
                          serialized to json using the Pandas split-oriented format. Bytes are
                          base64-encoded.


    """
    Model.log(artifact_path=artifact_path,
              flavor=kiwi.onnx,
              onnx_model=onnx_model,
              conda_env=conda_env,
              registered_model_name=registered_model_name,
              signature=signature,
              input_example=input_example)
示例#2
0
def test_mleap_module_model_save_with_absolute_path_and_valid_sample_input_produces_mleap_flavor(
        spark_model_iris, model_path):
    model_path = os.path.abspath(model_path)
    mlflow_model = Model()
    mleap.save_model(spark_model=spark_model_iris.model,
                     path=model_path,
                     sample_input=spark_model_iris.spark_df,
                     mlflow_model=mlflow_model)
    assert mleap.FLAVOR_NAME in mlflow_model.flavors

    config_path = os.path.join(model_path, "MLmodel")
    assert os.path.exists(config_path)
    config = Model.load(config_path)
    assert mleap.FLAVOR_NAME in config.flavors
示例#3
0
def test_mleap_module_model_save_with_relative_path_and_valid_sample_input_produces_mleap_flavor(
        spark_model_iris):
    with TempDir(chdr=True) as tmp:
        model_path = os.path.basename(tmp.path("model"))
        mlflow_model = Model()
        mleap.save_model(spark_model=spark_model_iris.model,
                         path=model_path,
                         sample_input=spark_model_iris.spark_df,
                         mlflow_model=mlflow_model)
        assert mleap.FLAVOR_NAME in mlflow_model.flavors

        config_path = os.path.join(model_path, "MLmodel")
        assert os.path.exists(config_path)
        config = Model.load(config_path)
        assert mleap.FLAVOR_NAME in config.flavors
示例#4
0
def test_load_pyfunc_succeeds_for_older_models_with_pyfunc_data_field(
        sklearn_knn_model, model_path):
    """
    This test verifies that scikit-learn models saved in older versions of MLflow are loaded
    successfully by ``mlflow.pyfunc.load_model``. These older models specify a pyfunc ``data``
    field referring directly to a serialized scikit-learn model file. In contrast, newer models
    omit the ``data`` field.
    """
    kiwi.sklearn.save_model(
        sk_model=sklearn_knn_model.model,
        path=model_path,
        serialization_format=kiwi.sklearn.SERIALIZATION_FORMAT_PICKLE)

    model_conf_path = os.path.join(model_path, "MLmodel")
    model_conf = Model.load(model_conf_path)
    pyfunc_conf = model_conf.flavors.get(pyfunc.FLAVOR_NAME)
    sklearn_conf = model_conf.flavors.get(kiwi.sklearn.FLAVOR_NAME)
    assert sklearn_conf is not None
    assert pyfunc_conf is not None
    pyfunc_conf[pyfunc.DATA] = sklearn_conf["pickled_model"]

    reloaded_knn_pyfunc = pyfunc.load_pyfunc(model_uri=model_path)

    np.testing.assert_array_equal(
        sklearn_knn_model.model.predict(sklearn_knn_model.inference_data),
        reloaded_knn_pyfunc.predict(sklearn_knn_model.inference_data))
示例#5
0
def test_validate_deployment_flavor_validates_python_function_flavor_successfully(
        pretrained_model):
    model_config_path = os.path.join(
        _download_artifact_from_uri(pretrained_model.model_uri), "MLmodel")
    model_config = Model.load(model_config_path)
    mfs._validate_deployment_flavor(model_config=model_config,
                                    flavor=kiwi.pyfunc.FLAVOR_NAME)
示例#6
0
def test_load_model_with_differing_pytorch_version_logs_warning(sequential_model, model_path):
    kiwi.pytorch.save_model(pytorch_model=sequential_model, path=model_path)
    saver_pytorch_version = "1.0"
    model_config_path = os.path.join(model_path, "MLmodel")
    model_config = Model.load(model_config_path)
    model_config.flavors[kiwi.pytorch.FLAVOR_NAME]["pytorch_version"] = saver_pytorch_version
    model_config.save(model_config_path)

    log_messages = []

    def custom_warn(message_text, *args, **kwargs):
        log_messages.append(message_text % args % kwargs)

    loader_pytorch_version = "0.8.2"
    with mock.patch("mlflow.pytorch._logger.warning") as warn_mock,\
            mock.patch("torch.__version__") as torch_version_mock:
        torch_version_mock.__str__ = lambda *args, **kwargs: loader_pytorch_version
        warn_mock.side_effect = custom_warn
        kiwi.pytorch.load_model(model_uri=model_path)

    assert any([
        "does not match installed PyTorch version" in log_message and
        saver_pytorch_version in log_message and
        loader_pytorch_version in log_message
        for log_message in log_messages
    ])
示例#7
0
def _get_flavor_configuration_from_uri(model_uri, flavor_name):
    """
    Obtains the configuration for the specified flavor from the specified
    MLflow model uri. If the model does not contain the specified flavor,
    an exception will be thrown.

    :param model_uri: The path to the root directory of the MLflow model for which to load
                       the specified flavor configuration.
    :param flavor_name: The name of the flavor configuration to load.
    :return: The flavor configuration as a dictionary.
    """
    try:
        ml_model_file = _download_artifact_from_uri(
            artifact_uri=append_to_uri_path(model_uri, MLMODEL_FILE_NAME))
    except Exception as ex:
        raise MlflowException(
            "Failed to download an \"{model_file}\" model file from \"{model_uri}\": {ex}"
            .format(model_file=MLMODEL_FILE_NAME, model_uri=model_uri,
                    ex=ex), RESOURCE_DOES_NOT_EXIST)
    model_conf = Model.load(ml_model_file)
    if flavor_name not in model_conf.flavors:
        raise MlflowException(
            "Model does not have the \"{flavor_name}\" flavor".format(
                flavor_name=flavor_name), RESOURCE_DOES_NOT_EXIST)
    return model_conf.flavors[flavor_name]
示例#8
0
def test_signature_and_examples_are_saved_correctly(sklearn_knn_model,
                                                    iris_data):
    data = iris_data
    signature_ = infer_signature(*data)
    example_ = data[0][:3, ]
    for signature in (None, signature_):
        for example in (None, example_):
            with TempDir() as tmp:
                with open(tmp.path("skmodel"), "wb") as f:
                    pickle.dump(sklearn_knn_model, f)
                path = tmp.path("model")
                kiwi.pyfunc.save_model(
                    path=path,
                    data_path=tmp.path("skmodel"),
                    loader_module=os.path.basename(__file__)[:-3],
                    code_path=[__file__],
                    signature=signature,
                    input_example=example)
                mlflow_model = Model.load(path)
                assert signature == mlflow_model.signature
                if example is None:
                    assert mlflow_model.saved_input_example_info is None
                else:
                    assert all((_read_example(mlflow_model,
                                              path) == example).all())
示例#9
0
def test_load_model_succeeds_when_data_is_model_file_instead_of_directory(
        module_scoped_subclassed_model, model_path, data):
    """
    This test verifies that PyTorch models saved in older versions of MLflow are loaded successfully
    by ``mlflow.pytorch.load_model``. The ``data`` path associated with these older models is
    serialized PyTorch model file, as opposed to the current format: a directory containing a
    serialized model file and pickle module information.
    """
    artifact_path = "pytorch_model"
    with kiwi.start_run():
        kiwi.pytorch.log_model(
            artifact_path=artifact_path,
            pytorch_model=module_scoped_subclassed_model,
            conda_env=None)
        model_path = _download_artifact_from_uri("runs:/{run_id}/{artifact_path}".format(
            run_id=kiwi.active_run().info.run_id, artifact_path=artifact_path))

    model_conf_path = os.path.join(model_path, "MLmodel")
    model_conf = Model.load(model_conf_path)
    pyfunc_conf = model_conf.flavors.get(pyfunc.FLAVOR_NAME)
    assert pyfunc_conf is not None
    model_data_path = os.path.join(model_path, pyfunc_conf[pyfunc.DATA])
    assert os.path.exists(model_data_path)
    assert kiwi.pytorch._SERIALIZED_TORCH_MODEL_FILE_NAME in os.listdir(model_data_path)
    pyfunc_conf[pyfunc.DATA] = os.path.join(
        model_data_path, kiwi.pytorch._SERIALIZED_TORCH_MODEL_FILE_NAME)
    model_conf.save(model_conf_path)

    loaded_pyfunc = pyfunc.load_pyfunc(model_path)

    np.testing.assert_array_almost_equal(
        loaded_pyfunc.predict(data[0]),
        pd.DataFrame(_predict(model=module_scoped_subclassed_model, data=data)),
        decimal=4)
示例#10
0
def _get_flavor_configuration(model_path, flavor_name):
    """
    Obtains the configuration for the specified flavor from the specified
    MLflow model path. If the model does not contain the specified flavor,
    an exception will be thrown.

    :param model_path: The path to the root directory of the MLflow model for which to load
                       the specified flavor configuration.
    :param flavor_name: The name of the flavor configuration to load.
    :return: The flavor configuration as a dictionary.
    """
    model_configuration_path = os.path.join(model_path, MLMODEL_FILE_NAME)
    if not os.path.exists(model_configuration_path):
        raise MlflowException(
            "Could not find an \"{model_file}\" configuration file at \"{model_path}\""
            .format(model_file=MLMODEL_FILE_NAME,
                    model_path=model_path), RESOURCE_DOES_NOT_EXIST)

    model_conf = Model.load(model_configuration_path)
    if flavor_name not in model_conf.flavors:
        raise MlflowException(
            "Model does not have the \"{flavor_name}\" flavor".format(
                flavor_name=flavor_name), RESOURCE_DOES_NOT_EXIST)
    conf = model_conf.flavors[flavor_name]
    return conf
def test_load_model_with_differing_cloudpickle_version_at_micro_granularity_logs_warning(
        model_path):
    class TestModel(kiwi.pyfunc.PythonModel):
        def predict(self, context, model_input):
            return model_input

    kiwi.pyfunc.save_model(path=model_path, python_model=TestModel())
    saver_cloudpickle_version = "0.5.8"
    model_config_path = os.path.join(model_path, "MLmodel")
    model_config = Model.load(model_config_path)
    model_config.flavors[kiwi.pyfunc.FLAVOR_NAME][
        kiwi.pyfunc.model.
        CONFIG_KEY_CLOUDPICKLE_VERSION] = saver_cloudpickle_version
    model_config.save(model_config_path)

    log_messages = []

    def custom_warn(message_text, *args, **kwargs):
        log_messages.append(message_text % args % kwargs)

    loader_cloudpickle_version = "0.5.7"
    with mock.patch("mlflow.pyfunc._logger.warning") as warn_mock, \
            mock.patch("cloudpickle.__version__") as cloudpickle_version_mock:
        cloudpickle_version_mock.__str__ = lambda *args, **kwargs: loader_cloudpickle_version
        warn_mock.side_effect = custom_warn
        kiwi.pyfunc.load_pyfunc(model_uri=model_path)

    assert any([
        "differs from the version of CloudPickle that is currently running"
        in log_message and saver_cloudpickle_version in log_message
        and loader_cloudpickle_version in log_message
        for log_message in log_messages
    ])
def test_signature_and_examples_are_saved_correctly(iris_data,
                                                    main_scoped_model_class):
    def test_predict(sk_model, model_input):
        return sk_model.predict(model_input) * 2

    data = iris_data
    signature_ = infer_signature(*data)
    example_ = data[0][:3, ]
    for signature in (None, signature_):
        for example in (None, example_):
            with TempDir() as tmp:
                path = tmp.path("model")
                kiwi.pyfunc.save_model(
                    path=path,
                    artifacts={},
                    python_model=main_scoped_model_class(test_predict),
                    signature=signature,
                    input_example=example)
                mlflow_model = Model.load(path)
                assert signature == mlflow_model.signature
                if example is None:
                    assert mlflow_model.saved_input_example_info is None
                else:
                    assert all((_read_example(mlflow_model,
                                              path) == example).all())
def test_model_log_load(sklearn_knn_model, main_scoped_model_class, iris_data):
    sklearn_artifact_path = "sk_model"
    with kiwi.start_run():
        kiwi.sklearn.log_model(sk_model=sklearn_knn_model,
                               artifact_path=sklearn_artifact_path)
        sklearn_model_uri = "runs:/{run_id}/{artifact_path}".format(
            run_id=kiwi.active_run().info.run_id,
            artifact_path=sklearn_artifact_path)

    def test_predict(sk_model, model_input):
        return sk_model.predict(model_input) * 2

    pyfunc_artifact_path = "pyfunc_model"
    with kiwi.start_run():
        kiwi.pyfunc.log_model(
            artifact_path=pyfunc_artifact_path,
            artifacts={
                "sk_model": sklearn_model_uri,
            },
            python_model=main_scoped_model_class(test_predict))
        pyfunc_model_uri = "runs:/{run_id}/{artifact_path}".format(
            run_id=kiwi.active_run().info.run_id,
            artifact_path=pyfunc_artifact_path)
        pyfunc_model_path = _download_artifact_from_uri(
            "runs:/{run_id}/{artifact_path}".format(
                run_id=kiwi.active_run().info.run_id,
                artifact_path=pyfunc_artifact_path))
        model_config = Model.load(os.path.join(pyfunc_model_path, "MLmodel"))

    loaded_pyfunc_model = kiwi.pyfunc.load_pyfunc(model_uri=pyfunc_model_uri)
    assert model_config.to_yaml() == loaded_pyfunc_model.metadata.to_yaml()
    np.testing.assert_array_equal(
        loaded_pyfunc_model.predict(iris_data[0]),
        test_predict(sk_model=sklearn_knn_model, model_input=iris_data[0]))
示例#14
0
def test_build_image_includes_default_metadata_in_azure_image_and_model_tags(
        sklearn_model):
    artifact_path = "model"
    with kiwi.start_run():
        kiwi.sklearn.log_model(sk_model=sklearn_model,
                               artifact_path=artifact_path)
        run_id = kiwi.active_run().info.run_id
    model_uri = "runs:///{run_id}/{artifact_path}".format(
        run_id=run_id, artifact_path=artifact_path)
    model_config = Model.load(
        os.path.join(_download_artifact_from_uri(artifact_uri=model_uri),
                     "MLmodel"))

    with AzureMLMocks() as aml_mocks:
        workspace = get_azure_workspace()
        kiwi.azureml.build_image(model_uri=model_uri, workspace=workspace)

        register_model_call_args = aml_mocks["register_model"].call_args_list
        assert len(register_model_call_args) == 1
        _, register_model_call_kwargs = register_model_call_args[0]
        called_tags = register_model_call_kwargs["tags"]
        assert called_tags["model_uri"] == model_uri
        assert called_tags["python_version"] ==\
            model_config.flavors[pyfunc.FLAVOR_NAME][pyfunc.PY_VERSION]

        create_image_call_args = aml_mocks["create_image"].call_args_list
        assert len(create_image_call_args) == 1
        _, create_image_call_kwargs = create_image_call_args[0]
        image_config = create_image_call_kwargs["image_config"]
        assert image_config.tags["model_uri"] == model_uri
        assert image_config.tags["python_version"] ==\
            model_config.flavors[pyfunc.FLAVOR_NAME][pyfunc.PY_VERSION]
示例#15
0
def run_local(model_uri, port=5000, image=DEFAULT_IMAGE_NAME, flavor=None):
    """
    Serve model locally in a SageMaker compatible Docker container.

    :param model_uri: The location, in URI format, of the MLflow model to serve locally,
                      for example:

                      - ``/Users/me/path/to/local/model``
                      - ``relative/path/to/local/model``
                      - ``s3://my_bucket/path/to/model``
                      - ``runs:/<mlflow_run_id>/run-relative/path/to/model``
                      - ``models:/<model_name>/<model_version>``
                      - ``models:/<model_name>/<stage>``

                      For more information about supported URI schemes, see
                      `Referencing Artifacts <https://www.mlflow.org/docs/latest/concepts.html#
                      artifact-locations>`_.

    :param port: Local port.
    :param image: Name of the Docker image to be used.
    :param flavor: The name of the flavor of the model to use for local serving. If ``None``,
                   a flavor is automatically selected from the model's available flavors. If the
                   specified flavor is not present or not supported for deployment, an exception
                   is thrown.
    """
    model_path = _download_artifact_from_uri(model_uri)
    model_config_path = os.path.join(model_path, MLMODEL_FILE_NAME)
    model_config = Model.load(model_config_path)

    if flavor is None:
        flavor = _get_preferred_deployment_flavor(model_config)
    else:
        _validate_deployment_flavor(model_config, flavor)
    print("Using the {selected_flavor} flavor for local serving!".format(
        selected_flavor=flavor))

    deployment_config = _get_deployment_config(flavor_name=flavor)

    _logger.info("launching docker image with path %s", model_path)
    cmd = [
        "docker", "run", "-v", "{}:/opt/ml/model/".format(model_path), "-p",
        "%d:8080" % port
    ]
    for key, value in deployment_config.items():
        cmd += ["-e", "{key}={value}".format(key=key, value=value)]
    cmd += ["--rm", image, "serve"]
    _logger.info('executing: %s', ' '.join(cmd))
    proc = Popen(cmd,
                 stdout=sys.stdout,
                 stderr=sys.stderr,
                 universal_newlines=True)

    def _sigterm_handler(*_):
        _logger.info("received termination signal => killing docker process")
        proc.send_signal(signal.SIGINT)

    import signal
    signal.signal(signal.SIGTERM, _sigterm_handler)
    proc.wait()
示例#16
0
def test_get_flavor_configuration_with_present_flavor_returns_expected_configuration(
        sklearn_knn_model, model_path):
    kiwi.sklearn.save_model(sk_model=sklearn_knn_model, path=model_path)

    sklearn_flavor_config = mlflow_model_utils._get_flavor_configuration(
        model_path=model_path, flavor_name=kiwi.sklearn.FLAVOR_NAME)
    model_config = Model.load(os.path.join(model_path, "MLmodel"))
    assert sklearn_flavor_config == model_config.flavors[
        kiwi.sklearn.FLAVOR_NAME]
示例#17
0
def test_model_save_load(sklearn_knn_model, iris_data, tmpdir, model_path):
    sk_model_path = os.path.join(str(tmpdir), "knn.pkl")
    with open(sk_model_path, "wb") as f:
        pickle.dump(sklearn_knn_model, f)

    model_config = Model(run_id="test", artifact_path="testtest")
    kiwi.pyfunc.save_model(path=model_path,
                           data_path=sk_model_path,
                           loader_module=os.path.basename(__file__)[:-3],
                           code_path=[__file__],
                           mlflow_model=model_config)

    reloaded_model_config = Model.load(os.path.join(model_path, "MLmodel"))
    assert model_config.__dict__ == reloaded_model_config.__dict__
    assert kiwi.pyfunc.FLAVOR_NAME in reloaded_model_config.flavors
    assert kiwi.pyfunc.PY_VERSION in reloaded_model_config.flavors[
        kiwi.pyfunc.FLAVOR_NAME]
    reloaded_model = kiwi.pyfunc.load_pyfunc(model_path)
    np.testing.assert_array_equal(sklearn_knn_model.predict(iris_data[0]),
                                  reloaded_model.predict(iris_data[0]))
示例#18
0
def test_get_preferred_deployment_flavor_obtains_valid_flavor_from_model(
        pretrained_model):
    model_config_path = os.path.join(
        _download_artifact_from_uri(pretrained_model.model_uri), "MLmodel")
    model_config = Model.load(model_config_path)

    selected_flavor = mfs._get_preferred_deployment_flavor(
        model_config=model_config)

    assert selected_flavor in mfs.SUPPORTED_DEPLOYMENT_FLAVORS
    assert selected_flavor in model_config.flavors
示例#19
0
def test_schema_enforcement_no_col_names():
    class TestModel(object):
        @staticmethod
        def predict(pdf):
            return pdf

    m = Model()
    input_schema = Schema([
        ColSpec("double"),
        ColSpec("double"),
        ColSpec("double"),
    ])
    m.signature = ModelSignature(inputs=input_schema)
    pyfunc_model = PyFuncModel(model_meta=m, model_impl=TestModel())
    test_data = [[1.0, 2.0, 3.0]]

    # Can call with just a list
    assert pyfunc_model.predict(test_data).equals(pd.DataFrame(test_data))

    # Or can call with a DataFrame without column names
    assert pyfunc_model.predict(pd.DataFrame(test_data)).equals(
        pd.DataFrame(test_data))

    # Or with column names!
    pdf = pd.DataFrame(data=test_data, columns=["a", "b", "c"])
    assert pyfunc_model.predict(pdf).equals(pdf)

    # Must provide the right number of arguments
    with pytest.raises(MlflowException) as ex:
        pyfunc_model.predict([[1.0, 2.0]])
    assert "the provided input only has 2 columns." in str(ex)

    # Must provide the right types
    with pytest.raises(MlflowException) as ex:
        pyfunc_model.predict([[1, 2, 3]])
    assert "Can not safely convert int64 to float64" in str(ex)

    # Can only provide data frames or lists...
    with pytest.raises(MlflowException) as ex:
        pyfunc_model.predict(set([1, 2, 3]))
    assert "Expected input to be DataFrame or list. Found: set" in str(ex)
示例#20
0
def test_model_log_with_pyfunc_flavor(spacy_model_with_data):
    artifact_path = "model"
    with kiwi.start_run():
        kiwi.spacy.log_model(spacy_model=spacy_model_with_data.model,
                             artifact_path=artifact_path)
        model_path = _download_artifact_from_uri(
            "runs:/{run_id}/{artifact_path}".format(
                run_id=kiwi.active_run().info.run_id,
                artifact_path=artifact_path))

        loaded_model = Model.load(model_path)
        assert pyfunc.FLAVOR_NAME in loaded_model.flavors
示例#21
0
def test_deploy_throws_exception_if_model_does_not_contain_pyfunc_flavor(
        sklearn_model, model_path):
    kiwi.sklearn.save_model(sk_model=sklearn_model, path=model_path)
    model_config_path = os.path.join(model_path, "MLmodel")
    model_config = Model.load(model_config_path)
    del model_config.flavors[pyfunc.FLAVOR_NAME]
    model_config.save(model_config_path)

    with AzureMLMocks(), pytest.raises(MlflowException) as exc:
        workspace = get_azure_workspace()
        kiwi.azureml.deploy(model_uri=model_path, workspace=workspace)
        assert exc.error_code == INVALID_PARAMETER_VALUE
示例#22
0
def test_deploy_throws_exception_if_model_python_version_is_less_than_three(
        sklearn_model, model_path):
    kiwi.sklearn.save_model(sk_model=sklearn_model, path=model_path)
    model_config_path = os.path.join(model_path, "MLmodel")
    model_config = Model.load(model_config_path)
    model_config.flavors[pyfunc.FLAVOR_NAME][pyfunc.PY_VERSION] = "2.7.6"
    model_config.save(model_config_path)

    with AzureMLMocks(), pytest.raises(MlflowException) as exc:
        workspace = get_azure_workspace()
        kiwi.azureml.deploy(model_uri=model_path, workspace=workspace)
        assert exc.error_code == INVALID_PARAMETER_VALUE
示例#23
0
文件: __init__.py 项目: iPieter/kiwi
def _save_model_with_loader_module_and_data_path(path,
                                                 loader_module,
                                                 data_path=None,
                                                 code_paths=None,
                                                 conda_env=None,
                                                 mlflow_model=Model()):
    """
    Export model as a generic Python function model.
    :param path: The path to which to save the Python model.
    :param loader_module: The name of the Python module that is used to load the model
                          from ``data_path``. This module must define a method with the prototype
                          ``_load_pyfunc(data_path)``.
    :param data_path: Path to a file or directory containing model data.
    :param code_paths: A list of local filesystem paths to Python file dependencies (or directories
                      containing file dependencies). These files are *prepended* to the system
                      path before the model is loaded.
    :param conda_env: Either a dictionary representation of a Conda environment or the path to a
                      Conda environment yaml file. If provided, this decsribes the environment
                      this model should be run in.
    :return: Model configuration containing model info.
    """

    code = None
    data = None

    if data_path is not None:
        model_file = _copy_file_or_tree(src=data_path,
                                        dst=path,
                                        dst_dir="data")
        data = model_file

    if code_paths is not None:
        for code_path in code_paths:
            _copy_file_or_tree(src=code_path, dst=path, dst_dir="code")
        code = "code"

    conda_env_subpath = "mlflow_env.yml"
    if conda_env is None:
        conda_env = get_default_conda_env()
    elif not isinstance(conda_env, dict):
        with open(conda_env, "r") as f:
            conda_env = yaml.safe_load(f)
    with open(os.path.join(path, conda_env_subpath), "w") as f:
        yaml.safe_dump(conda_env, stream=f, default_flow_style=False)

    kiwi.pyfunc.add_to_model(mlflow_model,
                             loader_module=loader_module,
                             code=code,
                             data=data,
                             env=conda_env_subpath)
    mlflow_model.save(os.path.join(path, MLMODEL_FILE_NAME))
    return mlflow_model
示例#24
0
def test_log_model(mlflow_client, backend_store_uri):
    experiment_id = mlflow_client.create_experiment('Log models')
    with TempDir(chdr=True):
        kiwi.set_experiment("Log models")
        model_paths = ["model/path/{}".format(i) for i in range(3)]
        with kiwi.start_run(experiment_id=experiment_id) as run:
            for i, m in enumerate(model_paths):
                kiwi.pyfunc.log_model(m, loader_module="mlflow.pyfunc")
                kiwi.pyfunc.save_model(m,
                                       mlflow_model=Model(
                                           artifact_path=m,
                                           run_id=run.info.run_id),
                                       loader_module="mlflow.pyfunc")
                model = Model.load(os.path.join(m, "MLmodel"))
                run = kiwi.get_run(run.info.run_id)
                tag = run.data.tags["mlflow.log-model.history"]
                models = json.loads(tag)
                model.utc_time_created = models[i]["utc_time_created"]
                assert models[i] == model.to_dict()
                assert len(models) == i + 1
                for j in range(0, i + 1):
                    assert models[j]["artifact_path"] == model_paths[j]
示例#25
0
def test_deployment_of_model_with_no_supported_flavors_raises_exception(
        pretrained_model):
    logged_model_path = _download_artifact_from_uri(pretrained_model.model_uri)
    model_config_path = os.path.join(logged_model_path, "MLmodel")
    model_config = Model.load(model_config_path)
    del model_config.flavors[kiwi.pyfunc.FLAVOR_NAME]
    model_config.save(path=model_config_path)

    with pytest.raises(MlflowException) as exc:
        mfs.deploy(app_name="missing-flavor",
                   model_uri=logged_model_path,
                   flavor=None)

    assert exc.value.error_code == ErrorCode.Name(RESOURCE_DOES_NOT_EXIST)
示例#26
0
def test_model_with_no_deployable_flavors_fails_pollitely():
    from kiwi.models import Model
    with TempDir(chdr=True) as tmp:
        m = Model(artifact_path=None,
                  run_id=None,
                  utc_time_created="now",
                  flavors={
                      "some": {},
                      "useless": {},
                      "flavors": {}
                  })
        os.mkdir(tmp.path("model"))
        m.save(tmp.path("model", "MLmodel"))
        # The following should fail because there should be no suitable flavor
        p = subprocess.Popen(
            ["mlflow", "models", "predict", "-m",
             tmp.path("model")],
            stderr=subprocess.PIPE,
            cwd=tmp.path(""))
        _, stderr = p.communicate()
        stderr = stderr.decode("utf-8")
        print(stderr)
        assert p.wait() != 0
        assert "No suitable flavor backend was found for the model." in stderr
示例#27
0
文件: utils.py 项目: iPieter/kiwi
def _save_example(mlflow_model: Model, input_example: ModelInputExample, path: str):
    """
    Save example to a file on the given path and updates passed Model with example metadata.

    The metadata is a dictionary with the following fields:
      - 'artifact_path': example path relative to the model directory.
      - 'type': Type of example. Currently the only supported value is 'dataframe'
      - 'pandas_orient': Determines the json encoding for dataframe examples in terms of pandas
                         orient convention. Defaults to 'split'.
    :param mlflow_model: Model metadata that will get updated with the example metadata.
    :param path: Where to store the example file. Should be model the model directory.
    """
    example = _Example(input_example)
    example.save(path)
    mlflow_model.saved_input_example_info = example.info
示例#28
0
def test_model_log():
    with TempDir(chdr=True) as tmp:
        experiment_id = kiwi.create_experiment("test")
        sig = ModelSignature(inputs=Schema([ColSpec("integer", "x"), ColSpec("integer", "y")]),
                             outputs=Schema([ColSpec(name=None, type="double")]))
        input_example = {"x": 1, "y": 2}
        with kiwi.start_run(experiment_id=experiment_id) as r:
            Model.log("some/path", TestFlavor,
                      signature=sig,
                      input_example=input_example)

        local_path = _download_artifact_from_uri("runs:/{}/some/path".format(r.info.run_id),
                                                 output_path=tmp.path(""))
        loaded_model = Model.load(os.path.join(local_path, "MLmodel"))
        assert loaded_model.run_id == r.info.run_id
        assert loaded_model.artifact_path == "some/path"
        assert loaded_model.flavors == {
            "flavor1": {"a": 1, "b": 2},
            "flavor2": {"x": 1, "y": 2},
        }
        assert loaded_model.signature == sig
        path = os.path.join(local_path, loaded_model.saved_input_example_info["artifact_path"])
        x = _dataframe_from_json(path)
        assert x.to_dict(orient="records")[0] == input_example
示例#29
0
文件: __init__.py 项目: iPieter/kiwi
def _install_pyfunc_deps(model_path=None, install_mlflow=False):
    """
    Creates a conda env for serving the model at the specified path and installs almost all serving
    dependencies into the environment - MLflow is not installed as it's not available via conda.
    """
    # If model is a pyfunc model, create its conda env (even if it also has mleap flavor)
    has_env = False
    if model_path:
        model_config_path = os.path.join(model_path, MLMODEL_FILE_NAME)
        model = Model.load(model_config_path)
        # NOTE: this differs from _serve cause we always activate the env even if you're serving
        # an mleap model
        if pyfunc.FLAVOR_NAME not in model.flavors:
            return
        conf = model.flavors[pyfunc.FLAVOR_NAME]
        if pyfunc.ENV in conf:
            print("creating and activating custom environment")
            env = conf[pyfunc.ENV]
            env_path_dst = os.path.join("/opt/mlflow/", env)
            env_path_dst_dir = os.path.dirname(env_path_dst)
            if not os.path.exists(env_path_dst_dir):
                os.makedirs(env_path_dst_dir)
            shutil.copyfile(os.path.join(MODEL_PATH, env), env_path_dst)
            conda_create_model_env = "conda env create -n custom_env -f {}".format(
                env_path_dst)
            if Popen(["bash", "-c", conda_create_model_env]).wait() != 0:
                raise Exception("Failed to create model environment.")
            has_env = True
    activate_cmd = ["source /miniconda/bin/activate custom_env"
                    ] if has_env else []
    # NB: install gunicorn[gevent] from pip rather than from conda because gunicorn is already
    # dependency of mlflow on pip and we expect mlflow to be part of the environment.
    install_server_deps = ["pip install gunicorn[gevent]"]
    if Popen(["bash", "-c", " && ".join(activate_cmd + install_server_deps)
              ]).wait() != 0:
        raise Exception(
            "Failed to install serving dependencies into the model environment."
        )
    if has_env and install_mlflow:
        install_mlflow_cmd = [
            "pip install /opt/mlflow/." if _container_includes_mlflow_source()
            else "pip install mlflow=={}".format(MLFLOW_VERSION)
        ]
        if Popen([
                "bash", "-c", " && ".join(activate_cmd + install_mlflow_cmd)
        ]).wait() != 0:
            raise Exception(
                "Failed to install mlflow into the model environment.")
示例#30
0
文件: __init__.py 项目: iPieter/kiwi
def _load_pyfunc_conf_with_model(model_path):
    """
    Loads the `python_function` flavor configuration for the specified model or throws an exception
    if the model does not contain the `python_function` flavor.

    :param model_path: The absolute path to the model.
    :return: The model's `python_function` flavor configuration and the model.
    """
    model_path = os.path.abspath(model_path)
    model = Model.load(os.path.join(model_path, MLMODEL_FILE_NAME))
    if pyfunc.FLAVOR_NAME not in model.flavors:
        raise MlflowException(
            message=("The specified model does not contain the `python_function` flavor. This "
                     " flavor is required for model deployment required for model deployment."),
            error_code=INVALID_PARAMETER_VALUE)
    return model.flavors[pyfunc.FLAVOR_NAME], model