{ "cells": [ { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "58fab4bb-231e-48cf-8ed4-fc15a1b22845", "showTitle": false, "title": "" } }, "source": [ "

Databricks-ML-professional-S03a-Batch

" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "76c17cec-5d2d-49f0-93d1-5ded2421fda4", "showTitle": false, "title": "" } }, "source": [ "
\n", "
\n", "

This Notebook adds information related to the following requirements:


\n", "Batch:\n", "\n", "
\n", "

Download this notebook at format ipynb here.

\n", "
\n", "
" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "2d6aaf81-c559-44bd-bc70-25852c40193d", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "1. Describe batch deployment as the appropriate use case for the vast majority of\n", "deployment use cases
\n", "\n", "
" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "18e681ce-93ed-4c38-814e-6d851bb56281", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "2. Identify how batch deployment computes predictions and saves them somewhere\n", "for later use
\n", "

Refers to the process of making predictions in a batch deployment scenario and storing the results for future reference or utilization. Let's break down the key components:

\n", "
    \n", "
  1. Batch deployment: In the context of machine learning or data processing, batch deployment refers to a mode of operation where predictions or computations are performed on a set of data collected over a specific period or based on a predefined batch size.
  2. \n", "
  3. Computes Predictions: This indicates that the system is generating predictions or results based on the input data. In machine learning, this could involve running a trained model on a batch of input data to produce predictions.
  4. \n", "
  5. Save predictions Somewhere: After computing predictions, the results are not immediately discarded. Instead, they are stored or saved in a designated location. This storage could be in databases, files, or any other suitable data storage system.
  6. \n", "
  7. Later use: The predictions are saved with the intention of using them at a later time. This could be for various purposes such as analysis, reporting, or serving the predictions to end-users when needed.
  8. \n", "
\n", "

In practical terms, the process might involve running a batch job that takes a set of input data, applies a trained model to make predictions, and then stores these predictions in a database, file system, or another storage solution. This approach is common in scenarios where real-time processing is not critical, and predictions can be made on a periodic basis.

\n", "

For example, in a recommendation system, batch deployment might involve processing user interactions over a day and generating personalized recommendations overnight. The computed recommendations would then be saved for use the next day when users interact with the system.

" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "1dcba302-be4c-4076-a92a-4bf0b7b4d2c4", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "3. Identify live serving benefits of querying precomputed batch predictions
\n", "
    \n", "
  1. Reduced Latency: Precomputing predictions in batch mode allows the system to process and store results ahead of time. This can lead to lower latency during live serving since the predictions are readily available and don't require real-time computation.
  2. \n", "
  3. Scalability: Batch processing can be more efficient for large-scale computations. By precomputing predictions in batches, the system can scale more easily to handle varying workloads during live serving.
  4. \n", "
  5. Resource efficiency: Computing predictions in batch mode can be resource-efficient, especially for complex models or large datasets. It allows the system to optimize resource utilization during non-peak hours.
  6. \n", "
  7. Consistency: Precomputed batch predictions can offer consistency in results, as they are generated using the same model and data. This is in contrast to real-time predictions, which might be influenced by changes in the model or input data at the moment of serving.
  8. \n", "
  9. Offline analysis: Having precomputed predictions enables offline analysis of the results, allowing organizations to gain insights, perform audits, and conduct evaluations without affecting live serving.
  10. \n", "
" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "0dd6cebd-bdf8-4311-8d2d-aba1a06cb1e6", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "4. Identify less performant data storage as a solution for other use cases
\n", "

In the context of batch deployment for machine learning models, where predictions are generated in bulk, it's common to save these predictions for later use.

For live serving, high-performance databases are often preferred for quick retrieval. However, in certain scenarios like populating emails, where rapid access may not be crucial, less performant data storage options, such as a blob store, can be identified as suitable solutions.

These storage solutions may not offer the highest performance but are chosen strategically based on the specific needs of use cases like email population, balancing considerations of performance and efficiency.

" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "1fb137c6-a812-4fe9-b723-aa07ef9aa2f0", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "5. Load registered models with load_model
\n", "

Let's see two examples to illustrate this requirement:

\n", "\n", "

Then both models will be loaded using mlflow.pyfunc.load_model function and used the same way for prediction on test set.

" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "a7a110c9-0567-4cae-9512-c08076a669b9", "showTitle": false, "title": "" } }, "source": [ "Load some libraries" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "4d42b33e-8b38-464f-8132-c60c1789f9dc", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "application/vnd.databricks.v1+bamboolib_hint": "{\"pd.DataFrames\": [], \"version\": \"0.0.1\"}", "text/plain": [] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import pandas as pd\n", "import seaborn as sns\n", "import matplotlib.pyplot as plt\n", "#\n", "from pyspark.sql.functions import *\n", "#\n", "import mlflow\n", "import logging\n", "#\n", "from sklearn.ensemble import RandomForestRegressor\n", "from sklearn.metrics import mean_squared_error\n", "#\n", "from pyspark.ml.regression import LinearRegression\n", "from pyspark.ml.feature import VectorAssembler\n", "from pyspark.ml import Pipeline\n", "#\n", "from databricks import feature_store" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "3a17f196-3a17-4b6e-b0c7-dc89a780d430", "showTitle": false, "title": "" } }, "outputs": [], "source": [ "logging.getLogger(\"mlflow\").setLevel(logging.FATAL)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "493c1a72-5c5a-4d86-a3bf-1dad8f307290", "showTitle": false, "title": "" } }, "source": [ "

Load data into a pandas dataframe (for the sake of simplicity, let's only keep numerical columns):

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "90352c13-b715-457a-8308-09e6c6844b1a", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
caratdepthtablepricexyz
269852.0160.161.0170688.148.064.87
291970.3359.061.06944.494.562.67
323400.3062.156.07894.294.312.67
\n", "
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "arguments": {}, "data": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
caratdepthtablepricexyz
269852.0160.161.0170688.148.064.87
291970.3359.061.06944.494.562.67
323400.3062.156.07894.294.312.67
\n
", "datasetInfos": [], "metadata": {}, "removedWidgets": [], "textData": null, "type": "htmlSandbox" } }, "output_type": "display_data" } ], "source": [ "diamonds_df = sns.load_dataset('diamonds').drop(columns=['cut', 'clarity', 'color'], axis=1)\n", "diamonds_df.sample(3)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "05149010-aa7c-427b-ad6d-0af49c0d995d", "showTitle": false, "title": "" } }, "source": [ "

Let's drop duplicates and separate into train set (67%) and test set (33%):

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "54c32173-999f-4935-95cc-9d68b05bcf84", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of rows test set: 17731\n", "Number of rows train set: 36001\n", "Sum of count rows of train and test set: 53732\n", "Total number of rows of initial dataframe: 53732\n" ] } ], "source": [ "diamonds_sdf = spark.createDataFrame(diamonds_df).dropDuplicates()\n", "#\n", "# Spark Dataframes\n", "test_sdf = diamonds_sdf.orderBy(rand()).limit(int(33*diamonds_sdf.count()/100))\n", "train_sdf = diamonds_sdf.subtract(test_sdf)\n", "#\n", "# Pandas Dataframes\n", "test_df = test_sdf.toPandas()\n", "train_df = train_sdf.toPandas()\n", "#\n", "print(f\"Number of rows test set: {test_sdf.count()}\")\n", "print(f\"Number of rows train set: {train_sdf.count()}\")\n", "print(f\"Sum of count rows of train and test set: {train_sdf.count() + test_sdf.count()}\")\n", "print(f\"Total number of rows of initial dataframe: {diamonds_sdf.count()}\")" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "86c30e4b-65a0-4e75-a7e7-155a5be096a3", "showTitle": false, "title": "" } }, "source": [ "

Scikit-learn library:

\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "b9ba297d-f5d5-464c-8465-a83126c38c37", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Registered model 'scikit-learn_model' already exists. Creating a new version of this model...\n", "Created version '7' of model 'scikit-learn_model'.\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
predictions
06771.932454
110611.573560
2741.122107
31805.671346
41587.993408
\n", "
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "arguments": {}, "data": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
predictions
06771.932454
110611.573560
2741.122107
31805.671346
41587.993408
\n
", "datasetInfos": [], "metadata": {}, "removedWidgets": [], "textData": null, "type": "htmlSandbox" } }, "output_type": "display_data" } ], "source": [ "# Prepare features and target dataframes\n", "X = train_df.drop('price', axis=1)\n", "y = train_df['price']\n", "#\n", "# train model (is automatically logged to mlflow)\n", "rf = RandomForestRegressor(n_estimators=100, max_depth=5)\n", "rf.fit(X, y)\n", "#\n", "# get latest run_id programmaticaly\n", "latest_run_id = mlflow.search_runs().sort_values(by=\"end_time\", ascending=False).head(1)['run_id'][0]\n", "#\n", "# uri to latest run (by default, artifact_path is 'model')\n", "uri_scikit_learn = f\"runs:/{latest_run_id}/model\"\n", "#\n", "# register latest logged model\n", "mlflow.register_model(uri_scikit_learn, name=\"scikit-learn_model\")\n", "#\n", "# load latest registered model\n", "scikit_learn_model = mlflow.pyfunc.load_model(uri_scikit_learn)\n", "#\n", "# prediction of test set using loaded model\n", "pd.DataFrame(scikit_learn_model.predict(test_df.drop('price', axis=1)), columns=['predictions']).head(5)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "dcaf843c-eb33-4092-b312-137da9e35220", "showTitle": false, "title": "" } }, "source": [ "

MLlib library: There is an additional step which is to convert input to vector using VectorAssembler. Thus, we need a pipeline and we will log to MLflow the fitted pipeline.

\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "376bd9ee-157b-4964-8222-6824c01d8072", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Registered model 'mllib_model' already exists. Creating a new version of this model...\n", "Created version '4' of model 'mllib_model'.\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
predictions
06711.408932
19540.179628
2647.270082
32388.329445
41695.716464
\n", "
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "arguments": {}, "data": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
predictions
06711.408932
19540.179628
2647.270082
32388.329445
41695.716464
\n
", "datasetInfos": [], "metadata": {}, "removedWidgets": [], "textData": null, "type": "htmlSandbox" } }, "output_type": "display_data" } ], "source": [ "# set vector assembler parameters\n", "assembler_inputs = [c for c in train_sdf.columns if c not in ['price']]\n", "vec_assembler = VectorAssembler(inputCols=assembler_inputs, outputCol=\"features\")\n", "#\n", "# instantiate model\n", "mllib_rfr = LinearRegression(featuresCol=\"features\", labelCol='price')\n", "#\n", "# define pipeline stages\n", "stages = [vec_assembler, mllib_rfr]\n", "#\n", "# set pipeline\n", "pipeline = Pipeline(stages=stages)\n", "#\n", "# fit pipeline to train set\n", "model_mllib = pipeline.fit(train_sdf)\n", "#\n", "# get latest run_id programmaticaly\n", "latest_run_id = mlflow.search_runs().sort_values(by=\"end_time\", ascending=False).head(1)['run_id'][0]\n", "#\n", "# uri to latest run (by default, artifact_path is 'model')\n", "uri_mllib = f\"runs:/{latest_run_id}/model\"\n", "#\n", "# register latest logged model\n", "mlflow.register_model(uri_mllib, name=\"mllib_model\")\n", "#\n", "# load latest registered model\n", "mllib_model = mlflow.pyfunc.load_model(uri_mllib)\n", "#\n", "# Here predictions can be done using same input as for model trained using scikit learn library\n", "pd.DataFrame(mllib_model.predict(test_df.drop('price', axis=1)), columns=['predictions']).head(5)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "8847c4cc-4b9a-4789-8394-fab399d94983", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "6. Deploy a single-node model in parallel using spark_udf
\n", "

With the model trained using scikit-learn library, it is possible to load it and affect it to a Spark UDF function to make predictions:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "e309f1ac-9d80-4c0c-85ac-7df9f314710c", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
priceprediction
45806771.9324540939115
840810611.573559702078
1103741.122106786866
13321805.6713457303665
12931587.9934075468402
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 4580, 6771.9324540939115 ], [ 8408, 10611.573559702078 ], [ 1103, 741.122106786866 ], [ 1332, 1805.6713457303665 ], [ 1293, 1587.9934075468402 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "price", "type": "\"long\"" }, { "metadata": "{}", "name": "prediction", "type": "\"double\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "# load model into a spark udf\n", "predict_scikit_learn = mlflow.pyfunc.spark_udf(spark, uri_scikit_learn)\n", "#\n", "# make predictions on the spark test dataframe\n", "display(test_sdf.withColumn(\"prediction\", predict_scikit_learn(*[c for c in test_sdf.columns if c not in ['price']])).select(\"price\", \"prediction\").limit(5))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "ab3e9181-55f3-4134-bb15-6b9248d1d5b9", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "7. Identify z-ordering as a solution for reducing the amount of time to read predictions\n", "from a table
\n", "

Z-Ordering: colocates related information in the same set of files

\n", "

Z-Ordering is a form of multi-dimensional clustering that colocates related information in the same set of files. It reduces the amount of data that needs to be read. See more here.

\n", "

Here after is an example of use of Z-ordering.

\n", "

Let's first write a dataframe as a Delta table:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "43526c33-a6e1-43d7-9520-41f7ab1f7558", "showTitle": false, "title": "" } }, "outputs": [], "source": [ "(train_sdf.write\n", " .format(\"delta\")\n", " .mode(\"overwrite\")\n", " .option(\"overwriteSchema\", \"true\")\n", " .saveAsTable(\"train_set_diamonds\"))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "4a61070c-4099-4712-9c2a-8540c5afa9d9", "showTitle": false, "title": "" } }, "source": [ "

Let's get table location:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "d8164ea2-5abe-45de-9625-66297e69871f", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
col_namedata_typecomment
Locationdbfs:/user/hive/warehouse/train_set_diamonds
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ "Location", "dbfs:/user/hive/warehouse/train_set_diamonds", "" ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{\"comment\":\"name of the column\"}", "name": "col_name", "type": "\"string\"" }, { "metadata": "{\"comment\":\"data type of the column\"}", "name": "data_type", "type": "\"string\"" }, { "metadata": "{\"comment\":\"comment of the column\"}", "name": "comment", "type": "\"string\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "display(spark.sql(\"describe table extended train_set_diamonds\").filter(\"col_name in ('Location')\"))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "71cb898e-8da4-456d-acb8-a55f71ab6ad0", "showTitle": false, "title": "" } }, "source": [ "

Let's Z-order table by feature carat:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "67a61c82-7059-40c4-8a56-116d86d7339b", "showTitle": false, "title": "" } }, "outputs": [], "source": [ "delta_partitioned_path = \"dbfs:/user/hive/warehouse/train_set_diamonds\"\n", "#\n", "spark.sql(f\"OPTIMIZE delta.`{delta_partitioned_path}` ZORDER BY (carat)\");" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "cb2a00bd-a465-4ce9-8363-9ff1f189f5e2", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "8. Identify partitioning on a common column to speed up querying
\n", "

Partitioning: stores data associated with different categorical values in different directories

\n", "

Partition will create as many folders as there are distinct values in the specified column for partitioning. Thus, column with high cardinality are not recommanded as partition key.

Here after is an example of use of Partitioning.

\n", "

Let's first reload the original dataframe and save it as a managed Delta table:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "0ba3aed9-a669-4f36-bb12-818f98871a6a", "showTitle": false, "title": "" } }, "outputs": [], "source": [ "(spark.createDataFrame(sns.load_dataset('diamonds')).write\n", " .format(\"delta\")\n", " .mode(\"overwrite\")\n", " .option(\"overwriteSchema\", \"true\")\n", " .saveAsTable(\"diamonds_df_not_partitioned\"))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "860d0033-92f9-44d1-87f6-c37eccf3b835", "showTitle": false, "title": "" } }, "source": [ "

Let's have a look at the content of the delta table folder. We see that there are four parquet files an a folder _delta_log:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "e450e873-f459-4e17-9c4b-531b438f3d4a", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
col_namedata_typecomment
Locationdbfs:/user/hive/warehouse/diamonds_df_not_partitioned
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ "Location", "dbfs:/user/hive/warehouse/diamonds_df_not_partitioned", "" ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{\"comment\":\"name of the column\"}", "name": "col_name", "type": "\"string\"" }, { "metadata": "{\"comment\":\"data type of the column\"}", "name": "data_type", "type": "\"string\"" }, { "metadata": "{\"comment\":\"comment of the column\"}", "name": "comment", "type": "\"string\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "display(spark.sql(\"describe table extended diamonds_df_not_partitioned\").filter(\"col_name in ('Location')\"))" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "c435ce0b-f242-4d60-a2e4-60ab6649056a", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dbfs:/user/hive/warehouse/diamonds_df_not_partitioned/_delta_log/\n", "dbfs:/user/hive/warehouse/diamonds_df_not_partitioned/part-00000-ae36b44e-0c7d-4c9f-9a6b-303df7a6b41c-c000.snappy.parquet\n", "dbfs:/user/hive/warehouse/diamonds_df_not_partitioned/part-00001-88c54dec-c999-415e-a6b7-e18f6fcf912c-c000.snappy.parquet\n", "dbfs:/user/hive/warehouse/diamonds_df_not_partitioned/part-00002-05d4b875-93c6-49c3-a176-e25ac6c39cab-c000.snappy.parquet\n", "dbfs:/user/hive/warehouse/diamonds_df_not_partitioned/part-00003-739559b9-0ff4-443f-a6ef-502f1733bae4-c000.snappy.parquet\n" ] } ], "source": [ "for file in dbutils.fs.ls(\"dbfs:/user/hive/warehouse/diamonds_df_not_partitioned\"):\n", " print(file.path)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "3a110cb9-e2fd-44f3-9701-06dde864d68e", "showTitle": false, "title": "" } }, "source": [ "

We can identify the feature cut as a good candidate to be the partition key:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "3129c2fe-338c-4478-ac5e-9634f7fdd2be", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
cutcount
Ideal21551
Premium13791
Very Good12082
Good4906
Fair1610
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ "Ideal", 21551 ], [ "Premium", 13791 ], [ "Very Good", 12082 ], [ "Good", 4906 ], [ "Fair", 1610 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "cut", "type": "\"string\"" }, { "metadata": "{}", "name": "count", "type": "\"long\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "display(spark.table(\"diamonds_df_not_partitioned\").groupBy(\"cut\").count().orderBy(desc('count')))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "ac6e9f96-bf12-4291-b243-0d782ea17ea9", "showTitle": false, "title": "" } }, "source": [ "

Let's partition using cut as partition key:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "bdab33b6-54d0-41d7-bbab-78f1d6dc801f", "showTitle": false, "title": "" } }, "outputs": [], "source": [ "(spark.table(\"diamonds_df_not_partitioned\")\n", " .write.partitionBy(\"cut\")\n", " .format(\"delta\")\n", " .mode(\"overwrite\")\n", " .option(\"overwriteSchema\", \"true\")\n", " .saveAsTable(\"diamonds_df_partitioned\"))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "7fad799d-4524-40eb-a7c0-77ae6da2175d", "showTitle": false, "title": "" } }, "source": [ "

Now let's have a look at the content of the partitioned table. We see there as many folders as there are distinct values in column cut. This will speed-up requests.

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "d003f526-9e67-44f4-9500-fc80c99a39e0", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "dbfs:/user/hive/warehouse/diamonds_df_partitioned/_delta_log/\n", "dbfs:/user/hive/warehouse/diamonds_df_partitioned/cut=Fair/\n", "dbfs:/user/hive/warehouse/diamonds_df_partitioned/cut=Good/\n", "dbfs:/user/hive/warehouse/diamonds_df_partitioned/cut=Ideal/\n", "dbfs:/user/hive/warehouse/diamonds_df_partitioned/cut=Premium/\n", "dbfs:/user/hive/warehouse/diamonds_df_partitioned/cut=Very Good/\n" ] } ], "source": [ "for file in dbutils.fs.ls(\"dbfs:/user/hive/warehouse/diamonds_df_partitioned\"):\n", " print(file.path)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "088d26da-3f3c-400e-b86c-a9c5900e7c5c", "showTitle": false, "title": "" } }, "source": [ "\n", "
\n", "9. Describe the practical benefits of using the score_batch operation
\n", "

score_batch let's make predictions easily on a large amount of data at a time using features coming from feature store.

\n", "

Let's have a look at an example to illustrate this requirement:

\n", "
    \n", "
  1. Load dataset: The dataset used for the example is diamonds dataset from Seaborn library
  2. \n", "
  3. Create Feature table in Feature Store
  4. \n", "
  5. Push preprocessed features to Feature Store
  6. \n", "
  7. Create train and test sets
  8. \n", "
  9. Prepare and train models
  10. \n", "
  11. Log models to associate them to features in Features Store
  12. \n", "
  13. Score models on test set using score_batch
  14. \n", "
  15. Case of new data
  16. \n", "
" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "1e8c666e-4e83-481e-ba1c-9171de353f58", "showTitle": false, "title": "" } }, "source": [ "

1. Load dataset

\n", "\n", "Looks like there is a problem with having a column named x or X... let's rename." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "e3116320-dbb9-4aea-b8d4-b826acdfc39b", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
indexcaratcutcolorclaritydepthtablepricex_ryz
00.23IdealESI261.555.03263.953.982.43
10.21PremiumESI159.861.03263.893.842.31
20.23GoodEVS156.965.03274.054.072.31
30.29PremiumIVS262.458.03344.24.232.63
40.31GoodJSI263.358.03354.344.352.75
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 0, 0.23, "Ideal", "E", "SI2", 61.5, 55, 326, 3.95, 3.98, 2.43 ], [ 1, 0.21, "Premium", "E", "SI1", 59.8, 61, 326, 3.89, 3.84, 2.31 ], [ 2, 0.23, "Good", "E", "VS1", 56.9, 65, 327, 4.05, 4.07, 2.31 ], [ 3, 0.29, "Premium", "I", "VS2", 62.4, 58, 334, 4.2, 4.23, 2.63 ], [ 4, 0.31, "Good", "J", "SI2", 63.3, 58, 335, 4.34, 4.35, 2.75 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "index", "type": "\"long\"" }, { "metadata": "{}", "name": "carat", "type": "\"double\"" }, { "metadata": "{}", "name": "cut", "type": "\"string\"" }, { "metadata": "{}", "name": "color", "type": "\"string\"" }, { "metadata": "{}", "name": "clarity", "type": "\"string\"" }, { "metadata": "{}", "name": "depth", "type": "\"double\"" }, { "metadata": "{}", "name": "table", "type": "\"double\"" }, { "metadata": "{}", "name": "price", "type": "\"long\"" }, { "metadata": "{}", "name": "x_r", "type": "\"double\"" }, { "metadata": "{}", "name": "y", "type": "\"double\"" }, { "metadata": "{}", "name": "z", "type": "\"double\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "pd_diamonds = sns.load_dataset('diamonds').reset_index()\n", "#\n", "diamonds_full = spark.createDataFrame(pd_diamonds).withColumnRenamed('x', 'x_r')\n", "#\n", "display(diamonds_full.limit(5))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "91f71306-b829-4150-8562-2d7cc4728b48", "showTitle": false, "title": "" } }, "source": [ "

2. Create Feature table in Feature Store

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "92306fc4-ca06-46bb-9aa2-f49eb4c3e6d2", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "2023/11/23 17:32:06 WARNING databricks.feature_store._compute_client._compute_client: Deleting a feature table can lead to unexpected failures in upstream producers and downstream consumers (models, endpoints, and scheduled jobs).\n", "2023/11/23 17:32:09 INFO databricks.feature_store._compute_client._compute_client: Created feature table 'hive_metastore.default.diamonds_fs'.\n" ] } ], "source": [ "# create a feature store client\n", "fs = feature_store.FeatureStoreClient()\n", "#\n", "# fs.drop_table(\"default.diamonds_fs\")\n", "#\n", "# create feature table - as only the scema is provided in the command below, it will only create the table structure without populating it with data\n", "result = fs.create_table(name=\"diamonds_fs\", # required\n", " primary_keys=[\"index\"], # required\n", " schema=diamonds_full.drop(\"price\").schema, # need either dataframe schema\n", " #df=diamonds_full, # or dataframe itself\n", " description=\"seaborn diamonds dataset\");" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "4e64855c-5e9e-4ebc-bd3a-168ec46d9758", "showTitle": false, "title": "" } }, "source": [ "

3. Push preprocessed features to Feature table

\n", "(There's no preprocessing done there for the sake of simplicity. Ideally, features pushed to Feature Store should be processed and ready to be used for model training)" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "dd3f95e8-ebae-474a-8372-dd3043a63f28", "showTitle": false, "title": "" } }, "outputs": [], "source": [ "fs.write_table(name=\"diamonds_fs\",\n", " df=diamonds_full.drop('price'),\n", " mode='merge')" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "9215ba4b-6e84-451a-ba95-d3e012c12ba5", "showTitle": false, "title": "" } }, "source": [ "

After that, Feature table is available in Features menu as well the associated Delta table in Catalog menu.

\n", "" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "e96a564b-f4ac-423f-bfde-7a1ec12fa25b", "showTitle": false, "title": "" } }, "source": [ "

4. Create train and test sets

\n", "

Here, features are now available in Features Store. It is possible to load them from there to train a model. For this example, we will:

\n", "\n", "

What will help to make the difference between the train set and test set is the Primary key column: index.

\n", "

Moreover, later for scoring by using the test set, we will need the initial target values from initial price column. Thus, the columns needed for the train and test sets are: index and price.

\n", "" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "1317f3aa-58c4-4c96-9744-ce965b4bd720", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
priceindex
3378
3365
3260
3343
3366
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 337, 8 ], [ 336, 5 ], [ 326, 0 ], [ 334, 3 ], [ 336, 6 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "price", "type": "\"long\"" }, { "metadata": "{}", "name": "index", "type": "\"long\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "y_test = diamonds_full.select(\"price\", \"index\").orderBy(rand()).limit(int(33*diamonds_full.count()/100))\n", "y_train = diamonds_full.select(\"price\", \"index\").subtract(y_test)\n", "#\n", "display(y_train.limit(5))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "b207fd0e-c0f0-4dbe-a031-91d8c9744087", "showTitle": false, "title": "" } }, "source": [ "

Let's create the Feature Store training sets.

\n", "" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "609d2917-17b5-467c-8d2b-7a8fbd1beb36", "showTitle": false, "title": "" } }, "source": [ "Four features training set:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "46d75cc8-2c72-4e8e-be93-46b2052dd79f", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
x_ryzcaratprice
3.873.782.490.22337
3.943.962.480.24336
3.953.982.430.23326
4.24.232.630.29334
3.953.982.470.24336
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 3.87, 3.78, 2.49, 0.22, 337 ], [ 3.94, 3.96, 2.48, 0.24, 336 ], [ 3.95, 3.98, 2.43, 0.23, 326 ], [ 4.2, 4.23, 2.63, 0.29, 334 ], [ 3.95, 3.98, 2.47, 0.24, 336 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "x_r", "type": "\"double\"" }, { "metadata": "{}", "name": "y", "type": "\"double\"" }, { "metadata": "{}", "name": "z", "type": "\"double\"" }, { "metadata": "{}", "name": "carat", "type": "\"double\"" }, { "metadata": "{}", "name": "price", "type": "\"long\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "# With 4 features: x, y, z, carat\n", "feature_lookups_4_features = [feature_store.FeatureLookup(table_name=\"diamonds_fs\",\n", " feature_names=['x_r', 'y', 'z', 'carat'],\n", " lookup_key=\"index\")]\n", "#\n", "# create associated training set\n", "train_set_4_features = fs.create_training_set(y_train,\n", " feature_lookups_4_features,\n", " label=\"price\",\n", " exclude_columns=\"index\")\n", "#\n", "# load training set\n", "train_set_4 = train_set_4_features.load_df()\n", "#\n", "# display to check\n", "display(train_set_4.limit(5))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "81d2a88a-d33c-44cb-80a0-143e574891bc", "showTitle": false, "title": "" } }, "source": [ "All numerical features training set:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "7d56d779-8327-4bdd-b9e7-859c60d9d935", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
caratdepthtablex_ryzprice
0.2265.161.03.873.782.49337
0.2462.857.03.943.962.48336
0.2361.555.03.953.982.43326
0.2962.458.04.24.232.63334
0.2462.357.03.953.982.47336
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 0.22, 65.1, 61, 3.87, 3.78, 2.49, 337 ], [ 0.24, 62.8, 57, 3.94, 3.96, 2.48, 336 ], [ 0.23, 61.5, 55, 3.95, 3.98, 2.43, 326 ], [ 0.29, 62.4, 58, 4.2, 4.23, 2.63, 334 ], [ 0.24, 62.3, 57, 3.95, 3.98, 2.47, 336 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "carat", "type": "\"double\"" }, { "metadata": "{}", "name": "depth", "type": "\"double\"" }, { "metadata": "{}", "name": "table", "type": "\"double\"" }, { "metadata": "{}", "name": "x_r", "type": "\"double\"" }, { "metadata": "{}", "name": "y", "type": "\"double\"" }, { "metadata": "{}", "name": "z", "type": "\"double\"" }, { "metadata": "{}", "name": "price", "type": "\"long\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "# With all numerical features\n", "feature_lookups_all_features = [feature_store.FeatureLookup(table_name=\"diamonds_fs\",\n", " feature_names=[c for c in diamonds_full.columns if c not in ['index', 'cut', 'clarity', 'price', 'color']],\n", " lookup_key=\"index\")]\n", "#\n", "# create associated training set\n", "train_set_all_features = fs.create_training_set(y_train,\n", " feature_lookups_all_features,\n", " label=\"price\",\n", " exclude_columns=\"index\")\n", "#\n", "# load training set\n", "train_set_all = train_set_all_features.load_df()\n", "#\n", "# display to check\n", "display(train_set_all.limit(5))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "a8693996-799e-4cae-9e14-b6ba149a1c6f", "showTitle": false, "title": "" } }, "source": [ "

5. Prepare and train models

\n", "

Training of two scikit-learn models based on different features sets. Input dataframes need to be pandas dataframes/series.

" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "1ddce2d7-694d-42c1-ae9a-478d026a351f", "showTitle": false, "title": "" } }, "source": [ "Model trained using the four features dataset:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "703f0b5c-bfe3-4c2e-868d-272162750573", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Out[36]: RandomForestRegressor()" ] } ], "source": [ "X_train_4 = train_set_4.drop(\"price\").toPandas()\n", "y_train_4 = train_set_4.toPandas()[\"price\"]\n", "#\n", "rf_4_model = RandomForestRegressor()\n", "#\n", "rf_4_model.fit(X_train_4, y_train_4)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "0814a65e-5ff5-4c18-b53a-1fadff6ae268", "showTitle": false, "title": "" } }, "source": [ "Model trained using all numerical features dataset:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "82e4e873-31bd-4c18-874a-f88ce8c54c82", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Out[37]: RandomForestRegressor()" ] } ], "source": [ "X_train_all = train_set_all.drop(\"price\").toPandas()\n", "y_train_all = train_set_all.toPandas()[\"price\"]\n", "#\n", "rf_all_model = RandomForestRegressor()\n", "#\n", "rf_all_model.fit(X_train_all, y_train_all)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "838f0a82-3f68-4c36-b235-3ce2f873410a", "showTitle": false, "title": "" } }, "source": [ "

6. Log models to associate them to features in Feature Store

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "cdd03ad9-eab5-420a-a198-dd2c90f71ef3", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Registered model 'trained_with_4_features' already exists. Creating a new version of this model...\n", "Created version '7' of model 'trained_with_4_features'.\n" ] } ], "source": [ "model_name_4_features = \"trained_with_4_features\"\n", "#\n", "fs.log_model(rf_4_model,\n", " artifact_path=model_name_4_features, # parameter required\n", " flavor=mlflow.sklearn, # parameter required\n", " training_set=train_set_4_features, # either training_set or feature_spec_path parameters required\n", " registered_model_name=model_name_4_features); # not required. However model will not be linked to features in features store until model is registered" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "e44704fb-e0dd-451b-803f-70f7ba6a06c0", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Registered model 'trained_with_all_features' already exists. Creating a new version of this model...\n", "Created version '6' of model 'trained_with_all_features'.\n" ] } ], "source": [ "model_name_all_features = \"trained_with_all_features\"\n", "#\n", "fs.log_model(rf_all_model,\n", " artifact_path=model_name_all_features, # parameter required\n", " flavor=mlflow.sklearn, # parameter required\n", " training_set=train_set_all_features, # either training_set or feature_spec_path parameters required\n", " registered_model_name=model_name_all_features); # not required. However model will not be linked to features in features store until model is registered" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "61497a33-a9a4-4bf9-88b2-02e0f63f8bc3", "showTitle": false, "title": "" } }, "source": [ "

At this point we can see that models are associated with the features they were trained on in Features Store:

\n", "" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "b3a733f0-123a-4fe7-b497-70b72e58b9fc", "showTitle": false, "title": "" } }, "source": [ "

7. Score models on test set using score_batch

" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "b3f591e7-37f6-4ec6-a5d2-27f4db5323ec", "showTitle": false, "title": "" } }, "source": [ "

Model trained on four features:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "a3dac6a5-0caa-4252-8295-40383c5592aa", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "runs:/45a6dbcde3a1464a84a61353caf60365/trained_with_4_features\n" ] }, { "data": { "text/html": [ "
priceprediction
51466148.454523809524
9981054.7966666666666
722920.675
756911.7239999999999
43124765.28
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 5146, 6148.454523809524 ], [ 998, 1054.7966666666666 ], [ 722, 920.675 ], [ 756, 911.7239999999999 ], [ 4312, 4765.28 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "price", "type": "\"long\"" }, { "metadata": "{}", "name": "prediction", "type": "\"double\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "# latest run id for model named \"trained_with_4_features\"\n", "for val in mlflow.MlflowClient().get_registered_model(model_name_4_features):\n", " if val[0]=='latest_versions':\n", " run_id_4 = val[1][0].run_id\n", "#\n", "# uri to latest run\n", "uri_4_features = f\"runs:/{run_id_4}/{model_name_4_features}\"\n", "print(uri_4_features)\n", "#\n", "# predict on test set\n", "predictions_df_4_features = fs.score_batch(uri_4_features, y_test).select(\"price\", \"prediction\");\n", "display(predictions_df_4_features.orderBy(rand()).limit(5));" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "18a6eaba-d08c-477b-ae20-a4baa09390cd", "showTitle": false, "title": "" } }, "source": [ "

Score RMSE on test set for model trained with 4 features:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "8df34d1d-3064-4c18-b8b7-a4ff9fb793a0", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "RMSE for model trained on 4 features: 1448.8825377496491\n" ] } ], "source": [ "print(\"RMSE for model trained on 4 features:\",\n", " mean_squared_error(predictions_df_4_features.toPandas()['price'], predictions_df_4_features.toPandas()['prediction'], squared=False))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "10058824-419f-4ab9-b8d4-049a99b6db86", "showTitle": false, "title": "" } }, "source": [ "

Model trained on all numerical features:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "26c4c37f-55a2-463c-9eaa-a737070e550a", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "runs:/45a6dbcde3a1464a84a61353caf60365/trained_with_all_features\n" ] }, { "data": { "text/html": [ "
priceprediction
33523267.8175
22811654.9560000000001
36015770.05
90328038.75
61266958.44
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 3352, 3267.8175 ], [ 2281, 1654.9560000000001 ], [ 3601, 5770.05 ], [ 9032, 8038.75 ], [ 6126, 6958.44 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "price", "type": "\"long\"" }, { "metadata": "{}", "name": "prediction", "type": "\"double\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "# latest run id for model named \"trained_with_all_features\"\n", "for val in mlflow.MlflowClient().get_registered_model(model_name_all_features):\n", " if val[0]=='latest_versions':\n", " run_id_all = val[1][0].run_id\n", "#\n", "# uri to latest run\n", "uri_all_features = f\"runs:/{run_id_all}/{model_name_all_features}\"\n", "print(uri_all_features)\n", "#\n", "# predict on test set\n", "predictions_df_all_features = fs.score_batch(uri_all_features, y_test).select(\"price\", \"prediction\", \"carat\");\n", "display(predictions_df_all_features.select(\"price\", \"prediction\").orderBy(rand()).limit(5));" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "1553678a-e273-4b23-91ed-568c901ab53c", "showTitle": false, "title": "" } }, "source": [ "

Score RMSE on test set for model trained with all numerical features:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "97dab406-3bc9-4a07-9f51-02471704d3eb", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1401.7197923544627\n" ] } ], "source": [ "print(mean_squared_error(predictions_df_all_features.toPandas()['price'], predictions_df_all_features.toPandas()['prediction'], squared=False))" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "48be5619-0c6d-4915-b1b5-590396271f26", "showTitle": false, "title": "" } }, "source": [ "

Comparison of actual price and predicted price according to carat for model trained using all numerical features, on a sample of 1000 random entries:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "f53c8f7e-9a5b-424f-9ba9-283685d3ad54", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "image/png": "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zfZtIiIiIqIs5eK7LBH5j8l9mlpbgTVrgJ07AaWMn2vWZH9Q/MEPfoALLrgAS5cuRVVVFTZt2gQAWLduHXbs2IHNmzfjlVdeQSwWw2c/+1ksWLAAzz//PJ5//vmkj/vud78bL730Ev74xz/isssuw7//+7+n3KbXXnsNr732GhYvXgzAWFXgN7/5Df7rv/5r0u0++9nP4r3vfS/+9Kc/4Q9/+ANOO+00vPrqq/jud7+LX//619i8eTOKi4vR6vMnR9CWuqD8xVgjXRhrpAtjLThy9V3WK4w10iWIscal8NK0di0wdTT84KCxPRbL/HEfe+wx3HzzzQCAyy67DI899hjOPPNMPPvss7jhhhtQUmLsqhNPPDGtx92zZw8++clP4vXXX8fw8HBKy85997vfxYsvvojjjjsO3/nOd8af85JLLkFxcfG02z/33HN46KGHAADFxcWorKzEww8/jE2bNuHtb387AGMdSb+LVuhaco+IsUa6MNZIF8ZacOTqu6xXGGukSxBjjT33adq1K73tqTh8+DCee+45XHfddairq8N//Md/YMOGDVBKpfwY9srz9uXl/vZv/xY33XQT2tra8J3vfCelpeesOfe/+93vcPHFF49vP/7441Nuj1IKq1atwubNm7F582Zs2bIFt956a8r3z4X29nZfn5/Cg7FGujDWcotDlye0t7fz/QiIXHyX9RKPa6RLEGONyX2aamrS256KJ554AldeeSV27tyJHTt2YPfu3aivr8cLL7yAD3zgA/jOd76DkZERAMaJAMCo7tjf3z/+GPPnz8err76KsbExPPnkk+Pb+/r6UF1dDQB48MEHM29kEueffz7uuusuAEaNgL6+Ppx//vl44okncODAgfF279y5MyfPn6p0TpYQZYOxRrow1nIn34cu6/bqq8rT94MnCvyTi++yXuJxjXQJYqwxuU9TSwtQXj55W3m5sT1Tjz322KQecgD4q7/6Kzz22GO47rrrUFNTgxUrVuD000/Ho48+CgBYs2YNLrjggvGCerfffjsuvPBCvPOd78Qpp5wy/ji33norLrnkEpx55pk46aSTMm9kEnfccQeef/55LF++HGeeeSY6OjrQ1NSEf/3Xf8UHP/hBrFixAh/4wAfw+uuv5+T5U2Uf3UCUS4w10oWxljvJhi6H0a9/LZ69Hzxx4q9cfJf1Eo9rpEsQY02C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7ilIegDYe+IXmtvGKaV6lVLHzD/XAzhTU9sofGaMRyIvKKXi1pBDpdTTACIicpLPzaICZc5J/T6AVqXU/zjchMc28sRMscZjG3lNKfUGgOcBXDDlqvHjmoiUAKgE0Ku1cR5hck9B8jKAJSJSLyKlAC4D8JT9BlPmBX4Mxhwvolx4CsBVZmXpswH0KaVe97tRFDwicrI1N1BE3gHjs70gv5SQv8w4uhfAq0qp/3K5GY9tlLVUYo3HNvKCiMwVkRPM38tgFN7unHKzpwCsMn//awDPKaWmzssvCKyWT4GhlBoRkZsA/ARAMYD7lFJ/FpF/AbBRKfUUgM+KyMdgVGk9DOBq3xpMBU1EHgPwPgAnicgeAF+BUaQFSqm7ATwN4CMAtgMYBHCNPy2lQpdCrP01gM+IyAiAIQCXFeqXEvLduwBcCaDNnJ8KALcAqAF4bCNPpRJrPLaRF04B8KC5qlYRgA1KqR9NyQ/uBfCwiGyHkR9c5l9zsyP8HyEiIiIiIiIqbByWT0RERERERFTgmNwTERERERERFTgm90REREREREQFjsk9ERERERERUYFjck9ERERERERU4JjcExERkStzrenHRaRLRDaJyNMistSDx/2ciJR70UYiIiLiUnhERETkQkQEwG8APGiuOw0ROR1AVCn1Qgr3FaXUmMv1OwCsVEod8rbVRERE4cSeeyIiInJzLoCEldgDgFLqTwD+KCI/F5E/iEibiFwEACJSJyJbROQhAO0AFonIXSKyUUT+LCL/bN7uswAWAHheRJ7X/7KIiIiChz33RERE5MhMwuuVUn83ZXsJgHKlVFxETgLwEoAlAGoBvAbgnUqpl8zbnqiUOiwixQB+DuCzSqlX2HNPRETkrRK/G0BEREQFRwDcJiLvATAGoBrAfPO6nVZib7pURNbA+M5xCoAmAK/obCwREVEYMLknIiIiN38G8NcO22MA5gI4UymVMHvhZ5nXvWndSETqAfwDgLcrpY6IyAO22xEREZGHOOeeiIiI3DwH4Diz5x0AICIrYAy/P2Am9ueafzuJwkj2+0RkPoAP267rB1CRm2YTERGFD5N7IiIicqSMwjwXA3i/uRTenwH8G4CnAawUkTYAVwHodLn/nwD80bz+UQC/tl29DsCPWVCPiIjIGyyoR0RERERERFTg2HNPREREREREVOCY3BMREREREREVOCb3RERERERERAWOyT0RERERERFRgWNyT0RERERERFTgmNwTERERERERFTgm90REREREREQFjsk9ERERERERUYH7/wMu8LB84a39DAAAAABJRU5ErkJggg==\n" }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "arguments": {}, "data": "data:image/png;base64,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zfZtIiIiIqIs5eK7LBH5j8l9mlpbgTVrgJ07AaWMn2vWZH9Q/MEPfoALLrgAS5cuRVVVFTZt2gQAWLduHXbs2IHNmzfjlVdeQSwWw2c/+1ksWLAAzz//PJ5//vmkj/vud78bL730Ev74xz/isssuw7//+7+n3KbXXnsNr732GhYvXgzAWFXgN7/5Df7rv/5r0u0++9nP4r3vfS/+9Kc/4Q9/+ANOO+00vPrqq/jud7+LX//619i8eTOKi4vR6vMnR9CWuqD8xVgjXRhrpAtjLThy9V3WK4w10iWIscal8NK0di0wdTT84KCxPRbL/HEfe+wx3HzzzQCAyy67DI899hjOPPNMPPvss7jhhhtQUmLsqhNPPDGtx92zZw8++clP4vXXX8fw8HBKy85997vfxYsvvojjjjsO3/nOd8af85JLLkFxcfG02z/33HN46KGHAADFxcWorKzEww8/jE2bNuHtb387AGMdSb+LVuhaco+IsUa6MNZIF8ZacOTqu6xXGGukSxBjjT33adq1K73tqTh8+DCee+45XHfddairq8N//Md/YMOGDVBKpfwY9srz9uXl/vZv/xY33XQT2tra8J3vfCelpeesOfe/+93vcPHFF49vP/7441Nuj1IKq1atwubNm7F582Zs2bIFt956a8r3z4X29nZfn5/Cg7FGujDWcotDlye0t7fz/QiIXHyX9RKPa6RLEGONyX2aamrS256KJ554AldeeSV27tyJHTt2YPfu3aivr8cLL7yAD3zgA/jOd76DkZERAMaJAMCo7tjf3z/+GPPnz8err76KsbExPPnkk+Pb+/r6UF1dDQB48MEHM29kEueffz7uuusuAEaNgL6+Ppx//vl44okncODAgfF279y5MyfPn6p0TpYQZYOxRrow1nIn34cu6/bqq8rT94MnCvyTi++yXuJxjXQJYqwxuU9TSwtQXj55W3m5sT1Tjz322KQecgD4q7/6Kzz22GO47rrrUFNTgxUrVuD000/Ho48+CgBYs2YNLrjggvGCerfffjsuvPBCvPOd78Qpp5wy/ji33norLrnkEpx55pk46aSTMm9kEnfccQeef/55LF++HGeeeSY6OjrQ1NSEf/3Xf8UHP/hBrFixAh/4wAfw+uuv5+T5U2Uf3UCUS4w10oWxljvJhi6H0a9/LZ69Hzxx4q9cfJf1Eo9rpEsQY02C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7ilIegDYe+IXmtvGKaV6lVLHzD/XAzhTU9sofGaMRyIvKKXi1pBDpdTTACIicpLPzaICZc5J/T6AVqXU/zjchMc28sRMscZjG3lNKfUGgOcBXDDlqvHjmoiUAKgE0Ku1cR5hck9B8jKAJSJSLyKlAC4D8JT9BlPmBX4Mxhwvolx4CsBVZmXpswH0KaVe97tRFDwicrI1N1BE3gHjs70gv5SQv8w4uhfAq0qp/3K5GY9tlLVUYo3HNvKCiMwVkRPM38tgFN7unHKzpwCsMn//awDPKaWmzssvCKyWT4GhlBoRkZsA/ARAMYD7lFJ/FpF/AbBRKfUUgM+KyMdgVGk9DOBq3xpMBU1EHgPwPgAnicgeAF+BUaQFSqm7ATwN4CMAtgMYBHCNPy2lQpdCrP01gM+IyAiAIQCXFeqXEvLduwBcCaDNnJ8KALcAqAF4bCNPpRJrPLaRF04B8KC5qlYRgA1KqR9NyQ/uBfCwiGyHkR9c5l9zsyP8HyEiIiIiIiIqbByWT0RERERERFTgmNwTERERERERFTgm90REREREREQFjsk9ERERERERUYFjck9ERERERERU4JjcExERkStzrenHRaRLRDaJyNMistSDx/2ciJR70UYiIiLiUnhERETkQkQEwG8APGiuOw0ROR1AVCn1Qgr3FaXUmMv1OwCsVEod8rbVRERE4cSeeyIiInJzLoCEldgDgFLqTwD+KCI/F5E/iEibiFwEACJSJyJbROQhAO0AFonIXSKyUUT+LCL/bN7uswAWAHheRJ7X/7KIiIiChz33RERE5MhMwuuVUn83ZXsJgHKlVFxETgLwEoAlAGoBvAbgnUqpl8zbnqiUOiwixQB+DuCzSqlX2HNPRETkrRK/G0BEREQFRwDcJiLvATAGoBrAfPO6nVZib7pURNbA+M5xCoAmAK/obCwREVEYMLknIiIiN38G8NcO22MA5gI4UymVMHvhZ5nXvWndSETqAfwDgLcrpY6IyAO22xEREZGHOOeeiIiI3DwH4Diz5x0AICIrYAy/P2Am9ueafzuJwkj2+0RkPoAP267rB1CRm2YTERGFD5N7IiIicqSMwjwXA3i/uRTenwH8G4CnAawUkTYAVwHodLn/nwD80bz+UQC/tl29DsCPWVCPiIjIGyyoR0RERERERFTg2HNPREREREREVOCY3BMREREREREVOCb3RERERERERAWOyT0RERERERFRgWNyT0RERERERFTgmNwTERERERERFTgm90REREREREQFjsk9ERERERERUYH7/wMu8LB84a39DAAAAABJRU5ErkJggg==\n", "datasetInfos": [], "metadata": {}, "removedWidgets": [], "type": "image" } }, "output_type": "display_data" } ], "source": [ "plt.figure(figsize=(17, 6))\n", "#\n", "sample_predictions = predictions_df_all_features.orderBy(rand()).limit(1000)\n", "#\n", "plt.scatter(list(sample_predictions.toPandas()['carat']), list(sample_predictions.toPandas()['price']), label='Actual Price', color='blue', marker='o')\n", "plt.scatter(list(sample_predictions.toPandas()['carat']), list(sample_predictions.toPandas()['prediction']), label='Prediction', color='orange', marker='s')\n", "#\n", "# Adding labels and title\n", "plt.xlabel('Carat')\n", "plt.ylabel('Prices')\n", "plt.title('Actual Price vs Prediction')\n", "#\n", "# Adding grid for better readability\n", "plt.grid(True, linestyle='--', alpha=0.7)\n", "#\n", "# Adding legend\n", "plt.legend()\n", "#\n", "# Show plot\n", "plt.show();" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "e0bcb9d4-60d8-49f9-9af6-9d706b2badb2", "showTitle": false, "title": "" } }, "source": [ "

8. Case of new data

\n", "

How to predict newly arriving data if there is no information on it in Feature Store? Need to update first the Feature Store with new data:

" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "2b86ebd5-e014-43e9-882b-3e8dde08b4e0", "showTitle": false, "title": "" } }, "source": [ "

Let's create a new diamond data. Schema should match data in Feature Store:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "49b297af-6a07-448a-9342-e09d5847cc28", "showTitle": false, "title": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "root\n", " |-- index: long (nullable = true)\n", " |-- carat: double (nullable = true)\n", " |-- cut: string (nullable = true)\n", " |-- color: string (nullable = true)\n", " |-- clarity: string (nullable = true)\n", " |-- depth: double (nullable = true)\n", " |-- table: double (nullable = true)\n", " |-- price: long (nullable = true)\n", " |-- x_r: double (nullable = true)\n", " |-- y: double (nullable = true)\n", " |-- z: double (nullable = true)\n", "\n" ] } ], "source": [ "diamonds_full.printSchema()" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "f332ab5d-e77c-4880-bf10-1c0863f228a0", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
indexcaratcutcolorclaritydepthtablepricex_ryz
888877772.0GoodEVS140.064.03264.143.52.1
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 88887777, 2, "Good", "E", "VS1", 40, 64, 326, 4.14, 3.5, 2.1 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "index", "type": "\"long\"" }, { "metadata": "{}", "name": "carat", "type": "\"double\"" }, { "metadata": "{}", "name": "cut", "type": "\"string\"" }, { "metadata": "{}", "name": "color", "type": "\"string\"" }, { "metadata": "{}", "name": "clarity", "type": "\"string\"" }, { "metadata": "{}", "name": "depth", "type": "\"double\"" }, { "metadata": "{}", "name": "table", "type": "\"double\"" }, { "metadata": "{}", "name": "price", "type": "\"long\"" }, { "metadata": "{}", "name": "x_r", "type": "\"double\"" }, { "metadata": "{}", "name": "y", "type": "\"double\"" }, { "metadata": "{}", "name": "z", "type": "\"double\"" } ], "type": "table" } }, "output_type": "display_data" }, { "data": { "text/html": [ "
indexprice
888877774500
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 88887777, 4500 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "index", "type": "\"long\"" }, { "metadata": "{}", "name": "price", "type": "\"long\"" } ], "type": "table" } }, "output_type": "display_data" }, { "data": { "text/html": [ "
index
88887777
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 88887777 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "index", "type": "\"long\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "new_diamond = (diamonds_full.limit(1).withColumn('index', lit(88887777).cast('long'))\n", " .withColumn('carat', lit(2).cast('double'))\n", " .withColumn('cut', lit('Good').cast('string'))\n", " .withColumn('color', lit('E').cast('string'))\n", " .withColumn('clarity', lit('VS1').cast('string'))\n", " .withColumn('depth', lit(40).cast('double'))\n", " .withColumn('table', lit(64).cast('double'))\n", " .withColumn('x_r', lit(4.14).cast('double'))\n", " .withColumn('y', lit(3.5).cast('double'))\n", " .withColumn('z', lit(2.1).cast('double')))\n", "#\n", "new_diamond_with_price = spark.createDataFrame(pd.DataFrame({'index': [88887777], 'price': [4500]}))\n", "#\n", "new_diamond_without_price = spark.createDataFrame(pd.DataFrame({'index': [88887777]}))\n", "#\n", "diamond_unknown = spark.createDataFrame(pd.DataFrame({'index': [98989898]}))\n", "#\n", "display(new_diamond)\n", "display(new_diamond_with_price)\n", "display(new_diamond_without_price)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "69917373-02c9-46f1-adc7-73a283e1505b", "showTitle": false, "title": "" } }, "source": [ "

Now, update the Feature Store with the new diamond data:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "ad9f4763-9446-41a4-b963-e51c0d6fcb08", "showTitle": false, "title": "" } }, "outputs": [], "source": [ "fs.write_table(name=\"diamonds_fs\",\n", " df=new_diamond,\n", " mode='merge')" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "cdaea63c-b435-4f75-8cf0-aec1b2991b1b", "showTitle": false, "title": "" } }, "source": [ "

We verify that score_batch predicts either with/without the price of the new data, the only requirement is the primary key - in this particular case, column index - of the new diamond data in the Feature Store:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "88bed0bc-f4d4-4f22-95ca-83d4a56c4c84", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "
indexpricecaratdepthtablex_ryzprediction
8888777745002.040.064.04.143.52.19639.403333333334
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 88887777, 4500, 2, 40, 64, 4.14, 3.5, 2.1, 9639.403333333334 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "index", "type": "\"long\"" }, { "metadata": "{}", "name": "price", "type": "\"long\"" }, { "metadata": "{}", "name": "carat", "type": "\"double\"" }, { "metadata": "{}", "name": "depth", "type": "\"double\"" }, { "metadata": "{}", "name": "table", "type": "\"double\"" }, { "metadata": "{}", "name": "x_r", "type": "\"double\"" }, { "metadata": "{}", "name": "y", "type": "\"double\"" }, { "metadata": "{}", "name": "z", "type": "\"double\"" }, { "metadata": "{}", "name": "prediction", "type": "\"double\"" } ], "type": "table" } }, "output_type": "display_data" }, { "data": { "text/html": [ "
indexcaratdepthtablex_ryzprediction
888877772.040.064.04.143.52.19639.403333333334
" ] }, "metadata": { "application/vnd.databricks.v1+output": { "addedWidgets": {}, "aggData": [], "aggError": "", "aggOverflow": false, "aggSchema": [], "aggSeriesLimitReached": false, "aggType": "", "arguments": {}, "columnCustomDisplayInfos": {}, "data": [ [ 88887777, 2, 40, 64, 4.14, 3.5, 2.1, 9639.403333333334 ] ], "datasetInfos": [], "dbfsResultPath": null, "isJsonSchema": true, "metadata": {}, "overflow": false, "plotOptions": { "customPlotOptions": {}, "displayType": "table", "pivotAggregation": null, "pivotColumns": null, "xColumns": null, "yColumns": null }, "removedWidgets": [], "schema": [ { "metadata": "{}", "name": "index", "type": "\"long\"" }, { "metadata": "{}", "name": "carat", "type": "\"double\"" }, { "metadata": "{}", "name": "depth", "type": "\"double\"" }, { "metadata": "{}", "name": "table", "type": "\"double\"" }, { "metadata": "{}", "name": "x_r", "type": "\"double\"" }, { "metadata": "{}", "name": "y", "type": "\"double\"" }, { "metadata": "{}", "name": "z", "type": "\"double\"" }, { "metadata": "{}", "name": "prediction", "type": "\"double\"" } ], "type": "table" } }, "output_type": "display_data" } ], "source": [ "# predict with price\n", "predictions_new_diamond_with_price = fs.score_batch(uri_all_features, new_diamond_with_price)\n", "display(predictions_new_diamond_with_price)\n", "#\n", "# predict without price\n", "predictions_new_diamond_without_price = fs.score_batch(uri_all_features, new_diamond_without_price)\n", "display(predictions_new_diamond_without_price)" ] }, { "cell_type": "markdown", "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": {}, "inputWidgets": {}, "nuid": "88f5654a-c3c1-4b83-a268-d8d67bd94906", "showTitle": false, "title": "" } }, "source": [ "

And verify that if a primary key is not found in the Feature Store, it results in an error:

" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "application/vnd.databricks.v1+cell": { "cellMetadata": { "byteLimit": 2048000, "rowLimit": 10000 }, "inputWidgets": {}, "nuid": "fac28dd7-81a6-4ea8-bec1-b779aaa14d15", "showTitle": false, "title": "" } }, "outputs": [ { "data": { "text/html": [ "\n", "org.apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 431.0 failed 4 times, most recent failure: Lost task 0.3 in stage 431.0 (TID 1339) (10.139.64.4 executor driver): org.apache.spark.api.python.PythonException: 'ValueError: Input contains NaN, infinity or a value too large for dtype('float32').'. Full traceback below:\n", "Traceback (most recent call last):\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1025, in udf\n", " os.kill(scoring_server_proc.pid, signal.SIGTERM)\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 866, in _predict_row_batch\n", " result = predict_fn(pdf)\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1006, in batch_predict_fn\n", " return loaded_model.predict(pdf)\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 373, in predict\n", " return self._predict_fn(data)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 784, in predict\n", " X = self._validate_X_predict(X)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 422, in _validate_X_predict\n", " return self.estimators_[0]._validate_X_predict(X, check_input=True)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/tree/_classes.py\", line 407, in _validate_X_predict\n", " X = self._validate_data(X, dtype=DTYPE, accept_sparse=\"csr\",\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/base.py\", line 421, in _validate_data\n", " X = check_array(X, **check_params)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 63, in inner_f\n", " return f(*args, **kwargs)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 720, in check_array\n", " _assert_all_finite(array,\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 103, in _assert_all_finite\n", " raise ValueError(\n", "ValueError: Input contains NaN, infinity or a value too large for dtype('float32').\n", "\n", "\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.handlePythonException(PythonRunner.scala:694)\n", "\tat org.apache.spark.sql.execution.python.PythonArrowOutput$$anon$1.read(PythonArrowOutput.scala:110)\n", "\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.hasNext(PythonRunner.scala:647)\n", "\tat org.apache.spark.InterruptibleIterator.hasNext(InterruptibleIterator.scala:37)\n", "\tat scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:491)\n", "\tat scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)\n", "\tat org.apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIteratorForCodegenStage3.processNext(Unknown Source)\n", "\tat org.apache.spark.sql.execution.BufferedRowIterator.hasNext(BufferedRowIterator.java:43)\n", "\tat org.apache.spark.sql.execution.WholeStageCodegenExec$$anon$1.hasNext(WholeStageCodegenExec.scala:761)\n", "\tat org.apache.spark.sql.execution.collect.UnsafeRowBatchUtils$.encodeUnsafeRows(UnsafeRowBatchUtils.scala:80)\n", "\tat org.apache.spark.sql.execution.collect.Collector.$anonfun$processFunc$1(Collector.scala:186)\n", "\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$3(ResultTask.scala:75)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$1(ResultTask.scala:75)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:55)\n", "\tat org.apache.spark.scheduler.Task.doRunTask(Task.scala:174)\n", "\tat org.apache.spark.scheduler.Task.$anonfun$run$4(Task.scala:137)\n", "\tat com.databricks.unity.EmptyHandle$.runWithAndClose(UCSHandle.scala:125)\n", "\tat org.apache.spark.scheduler.Task.$anonfun$run$1(Task.scala:137)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.scheduler.Task.run(Task.scala:96)\n", "\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$13(Executor.scala:902)\n", "\tat org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1697)\n", "\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$4(Executor.scala:905)\n", "\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:760)\n", "\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n", "\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n", "\tat java.lang.Thread.run(Thread.java:750)\n", "\n", "Driver stacktrace:\n", "\tat org.apache.spark.scheduler.DAGScheduler.failJobAndIndependentStages(DAGScheduler.scala:3381)\n", "\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$abortStage$2(DAGScheduler.scala:3313)\n", "\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$abortStage$2$adapted(DAGScheduler.scala:3304)\n", "\tat scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)\n", "\tat scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)\n", "\tat scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)\n", "\tat org.apache.spark.scheduler.DAGScheduler.abortStage(DAGScheduler.scala:3304)\n", "\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$handleTaskSetFailed$1(DAGScheduler.scala:1428)\n", "\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$handleTaskSetFailed$1$adapted(DAGScheduler.scala:1428)\n", "\tat scala.Option.foreach(Option.scala:407)\n", "\tat org.apache.spark.scheduler.DAGScheduler.handleTaskSetFailed(DAGScheduler.scala:1428)\n", "\tat org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.doOnReceive(DAGScheduler.scala:3593)\n", "\tat org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:3531)\n", "\tat org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:3519)\n", "\tat org.apache.spark.util.EventLoop$$anon$1.run(EventLoop.scala:51)\n", "\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$runJob$1(DAGScheduler.scala:1177)\n", "\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n", "\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n", "\tat org.apache.spark.scheduler.DAGScheduler.runJob(DAGScheduler.scala:1165)\n", "\tat org.apache.spark.SparkContext.runJobInternal(SparkContext.scala:2746)\n", "\tat org.apache.spark.sql.execution.collect.Collector.$anonfun$runSparkJobs$1(Collector.scala:312)\n", "\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n", "\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n", "\tat org.apache.spark.sql.execution.collect.Collector.runSparkJobs(Collector.scala:271)\n", "\tat org.apache.spark.sql.execution.collect.Collector.collect(Collector.scala:322)\n", "\tat org.apache.spark.sql.execution.collect.Collector$.collect(Collector.scala:105)\n", "\tat org.apache.spark.sql.execution.collect.Collector$.collect(Collector.scala:112)\n", "\tat org.apache.spark.sql.execution.qrc.InternalRowFormat$.collect(cachedSparkResults.scala:115)\n", "\tat org.apache.spark.sql.execution.qrc.InternalRowFormat$.collect(cachedSparkResults.scala:104)\n", "\tat org.apache.spark.sql.execution.qrc.InternalRowFormat$.collect(cachedSparkResults.scala:88)\n", "\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.$anonfun$computeResult$1(ResultCacheManager.scala:527)\n", "\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n", "\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.collectResult$1(ResultCacheManager.scala:519)\n", "\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.$anonfun$computeResult$2(ResultCacheManager.scala:537)\n", "\tat org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.$anonfun$withFinalPlanUpdateLegacy$1(AdaptiveSparkPlanExec.scala:771)\n", "\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n", "\tat org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.withFinalPlanUpdateLegacy(AdaptiveSparkPlanExec.scala:769)\n", "\tat org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.withFinalPlanUpdate(AdaptiveSparkPlanExec.scala:764)\n", "\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.computeResult(ResultCacheManager.scala:537)\n", "\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.$anonfun$getOrComputeResultInternal$1(ResultCacheManager.scala:396)\n", "\tat scala.Option.getOrElse(Option.scala:189)\n", "\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.getOrComputeResultInternal(ResultCacheManager.scala:390)\n", "\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.getOrComputeResult(ResultCacheManager.scala:292)\n", "\tat org.apache.spark.sql.execution.SparkPlan.$anonfun$executeCollectResult$1(SparkPlan.scala:433)\n", "\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n", "\tat org.apache.spark.sql.execution.SparkPlan.executeCollectResult(SparkPlan.scala:430)\n", "\tat org.apache.spark.sql.Dataset.collectResult(Dataset.scala:3431)\n", "\tat org.apache.spark.sql.Dataset.$anonfun$collectResult$1(Dataset.scala:3422)\n", "\tat org.apache.spark.sql.Dataset.$anonfun$withAction$3(Dataset.scala:4297)\n", "\tat org.apache.spark.sql.execution.QueryExecution$.withInternalError(QueryExecution.scala:773)\n", "\tat org.apache.spark.sql.Dataset.$anonfun$withAction$2(Dataset.scala:4295)\n", "\tat org.apache.spark.sql.execution.SQLExecution$.$anonfun$withCustomExecutionEnv$8(SQLExecution.scala:249)\n", "\tat org.apache.spark.sql.execution.SQLExecution$.withSQLConfPropagated(SQLExecution.scala:399)\n", "\tat org.apache.spark.sql.execution.SQLExecution$.$anonfun$withCustomExecutionEnv$1(SQLExecution.scala:194)\n", "\tat org.apache.spark.sql.SparkSession.withActive(SparkSession.scala:985)\n", "\tat org.apache.spark.sql.execution.SQLExecution$.withCustomExecutionEnv(SQLExecution.scala:148)\n", "\tat org.apache.spark.sql.execution.SQLExecution$.withNewExecutionId(SQLExecution.scala:349)\n", "\tat org.apache.spark.sql.Dataset.withAction(Dataset.scala:4295)\n", "\tat org.apache.spark.sql.Dataset.collectResult(Dataset.scala:3421)\n", "\tat com.databricks.backend.daemon.driver.OutputAggregator$.withOutputAggregation0(OutputAggregator.scala:267)\n", "\tat com.databricks.backend.daemon.driver.OutputAggregator$.withOutputAggregation(OutputAggregator.scala:101)\n", "\tat com.databricks.backend.daemon.driver.PythonDriverLocalBase.generateTableResult(PythonDriverLocalBase.scala:723)\n", "\tat com.databricks.backend.daemon.driver.JupyterDriverLocal.computeListResultsItem(JupyterDriverLocal.scala:1424)\n", "\tat com.databricks.backend.daemon.driver.JupyterDriverLocal$JupyterEntryPoint.addCustomDisplayData(JupyterDriverLocal.scala:505)\n", "\tat sun.reflect.GeneratedMethodAccessor746.invoke(Unknown Source)\n", "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n", "\tat java.lang.reflect.Method.invoke(Method.java:498)\n", "\tat py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244)\n", "\tat py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:380)\n", "\tat py4j.Gateway.invoke(Gateway.java:306)\n", "\tat py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)\n", "\tat py4j.commands.CallCommand.execute(CallCommand.java:79)\n", "\tat py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:195)\n", "\tat py4j.ClientServerConnection.run(ClientServerConnection.java:115)\n", "\tat java.lang.Thread.run(Thread.java:750)\n", "Caused by: org.apache.spark.api.python.PythonException: 'ValueError: Input contains NaN, infinity or a value too large for dtype('float32').'. Full traceback below:\n", "Traceback (most recent call last):\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1025, in udf\n", " os.kill(scoring_server_proc.pid, signal.SIGTERM)\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 866, in _predict_row_batch\n", " result = predict_fn(pdf)\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1006, in batch_predict_fn\n", " return loaded_model.predict(pdf)\n", " File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 373, in predict\n", " return self._predict_fn(data)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 784, in predict\n", " X = self._validate_X_predict(X)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 422, in _validate_X_predict\n", " return self.estimators_[0]._validate_X_predict(X, check_input=True)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/tree/_classes.py\", line 407, in _validate_X_predict\n", " X = self._validate_data(X, dtype=DTYPE, accept_sparse=\"csr\",\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/base.py\", line 421, in _validate_data\n", " X = check_array(X, **check_params)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 63, in inner_f\n", " return f(*args, **kwargs)\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 720, in check_array\n", " _assert_all_finite(array,\n", " File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 103, in _assert_all_finite\n", " raise ValueError(\n", "ValueError: Input contains NaN, infinity or a value too large for dtype('float32').\n", "\n", "\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.handlePythonException(PythonRunner.scala:694)\n", "\tat org.apache.spark.sql.execution.python.PythonArrowOutput$$anon$1.read(PythonArrowOutput.scala:110)\n", "\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.hasNext(PythonRunner.scala:647)\n", "\tat org.apache.spark.InterruptibleIterator.hasNext(InterruptibleIterator.scala:37)\n", "\tat scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:491)\n", "\tat scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)\n", "\tat org.apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIteratorForCodegenStage3.processNext(Unknown Source)\n", "\tat org.apache.spark.sql.execution.BufferedRowIterator.hasNext(BufferedRowIterator.java:43)\n", "\tat org.apache.spark.sql.execution.WholeStageCodegenExec$$anon$1.hasNext(WholeStageCodegenExec.scala:761)\n", "\tat org.apache.spark.sql.execution.collect.UnsafeRowBatchUtils$.encodeUnsafeRows(UnsafeRowBatchUtils.scala:80)\n", "\tat org.apache.spark.sql.execution.collect.Collector.$anonfun$processFunc$1(Collector.scala:186)\n", "\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$3(ResultTask.scala:75)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$1(ResultTask.scala:75)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:55)\n", "\tat org.apache.spark.scheduler.Task.doRunTask(Task.scala:174)\n", "\tat org.apache.spark.scheduler.Task.$anonfun$run$4(Task.scala:137)\n", "\tat com.databricks.unity.EmptyHandle$.runWithAndClose(UCSHandle.scala:125)\n", "\tat org.apache.spark.scheduler.Task.$anonfun$run$1(Task.scala:137)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.scheduler.Task.run(Task.scala:96)\n", "\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$13(Executor.scala:902)\n", "\tat org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1697)\n", "\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$4(Executor.scala:905)\n", "\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n", "\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n", "\tat org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:760)\n", "\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n", "\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n", "\t... 1 more" ] }, "metadata": { "application/vnd.databricks.v1+output": { "arguments": {}, "data": "org.apache.spark.SparkException: Job aborted due to stage failure: Task 0 in stage 431.0 failed 4 times, most recent failure: Lost task 0.3 in stage 431.0 (TID 1339) (10.139.64.4 executor driver): org.apache.spark.api.python.PythonException: 'ValueError: Input contains NaN, infinity or a value too large for dtype('float32').'. Full traceback below:\nTraceback (most recent call last):\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1025, in udf\n os.kill(scoring_server_proc.pid, signal.SIGTERM)\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 866, in _predict_row_batch\n result = predict_fn(pdf)\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1006, in batch_predict_fn\n return loaded_model.predict(pdf)\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 373, in predict\n return self._predict_fn(data)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 784, in predict\n X = self._validate_X_predict(X)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 422, in _validate_X_predict\n return self.estimators_[0]._validate_X_predict(X, check_input=True)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/tree/_classes.py\", line 407, in _validate_X_predict\n X = self._validate_data(X, dtype=DTYPE, accept_sparse=\"csr\",\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/base.py\", line 421, in _validate_data\n X = check_array(X, **check_params)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 63, in inner_f\n return f(*args, **kwargs)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 720, in check_array\n _assert_all_finite(array,\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 103, in _assert_all_finite\n raise ValueError(\nValueError: Input contains NaN, infinity or a value too large for dtype('float32').\n\n\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.handlePythonException(PythonRunner.scala:694)\n\tat org.apache.spark.sql.execution.python.PythonArrowOutput$$anon$1.read(PythonArrowOutput.scala:110)\n\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.hasNext(PythonRunner.scala:647)\n\tat org.apache.spark.InterruptibleIterator.hasNext(InterruptibleIterator.scala:37)\n\tat scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:491)\n\tat scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)\n\tat org.apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIteratorForCodegenStage3.processNext(Unknown Source)\n\tat org.apache.spark.sql.execution.BufferedRowIterator.hasNext(BufferedRowIterator.java:43)\n\tat org.apache.spark.sql.execution.WholeStageCodegenExec$$anon$1.hasNext(WholeStageCodegenExec.scala:761)\n\tat org.apache.spark.sql.execution.collect.UnsafeRowBatchUtils$.encodeUnsafeRows(UnsafeRowBatchUtils.scala:80)\n\tat org.apache.spark.sql.execution.collect.Collector.$anonfun$processFunc$1(Collector.scala:186)\n\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$3(ResultTask.scala:75)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$1(ResultTask.scala:75)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:55)\n\tat org.apache.spark.scheduler.Task.doRunTask(Task.scala:174)\n\tat org.apache.spark.scheduler.Task.$anonfun$run$4(Task.scala:137)\n\tat com.databricks.unity.EmptyHandle$.runWithAndClose(UCSHandle.scala:125)\n\tat org.apache.spark.scheduler.Task.$anonfun$run$1(Task.scala:137)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.scheduler.Task.run(Task.scala:96)\n\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$13(Executor.scala:902)\n\tat org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1697)\n\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$4(Executor.scala:905)\n\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:760)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\tat java.lang.Thread.run(Thread.java:750)\n\nDriver stacktrace:\n\tat org.apache.spark.scheduler.DAGScheduler.failJobAndIndependentStages(DAGScheduler.scala:3381)\n\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$abortStage$2(DAGScheduler.scala:3313)\n\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$abortStage$2$adapted(DAGScheduler.scala:3304)\n\tat scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:62)\n\tat scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:55)\n\tat scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:49)\n\tat org.apache.spark.scheduler.DAGScheduler.abortStage(DAGScheduler.scala:3304)\n\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$handleTaskSetFailed$1(DAGScheduler.scala:1428)\n\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$handleTaskSetFailed$1$adapted(DAGScheduler.scala:1428)\n\tat scala.Option.foreach(Option.scala:407)\n\tat org.apache.spark.scheduler.DAGScheduler.handleTaskSetFailed(DAGScheduler.scala:1428)\n\tat org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.doOnReceive(DAGScheduler.scala:3593)\n\tat org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:3531)\n\tat org.apache.spark.scheduler.DAGSchedulerEventProcessLoop.onReceive(DAGScheduler.scala:3519)\n\tat org.apache.spark.util.EventLoop$$anon$1.run(EventLoop.scala:51)\n\tat org.apache.spark.scheduler.DAGScheduler.$anonfun$runJob$1(DAGScheduler.scala:1177)\n\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n\tat org.apache.spark.scheduler.DAGScheduler.runJob(DAGScheduler.scala:1165)\n\tat org.apache.spark.SparkContext.runJobInternal(SparkContext.scala:2746)\n\tat org.apache.spark.sql.execution.collect.Collector.$anonfun$runSparkJobs$1(Collector.scala:312)\n\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n\tat org.apache.spark.sql.execution.collect.Collector.runSparkJobs(Collector.scala:271)\n\tat org.apache.spark.sql.execution.collect.Collector.collect(Collector.scala:322)\n\tat org.apache.spark.sql.execution.collect.Collector$.collect(Collector.scala:105)\n\tat org.apache.spark.sql.execution.collect.Collector$.collect(Collector.scala:112)\n\tat org.apache.spark.sql.execution.qrc.InternalRowFormat$.collect(cachedSparkResults.scala:115)\n\tat org.apache.spark.sql.execution.qrc.InternalRowFormat$.collect(cachedSparkResults.scala:104)\n\tat org.apache.spark.sql.execution.qrc.InternalRowFormat$.collect(cachedSparkResults.scala:88)\n\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.$anonfun$computeResult$1(ResultCacheManager.scala:527)\n\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.collectResult$1(ResultCacheManager.scala:519)\n\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.$anonfun$computeResult$2(ResultCacheManager.scala:537)\n\tat org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.$anonfun$withFinalPlanUpdateLegacy$1(AdaptiveSparkPlanExec.scala:771)\n\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n\tat org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.withFinalPlanUpdateLegacy(AdaptiveSparkPlanExec.scala:769)\n\tat org.apache.spark.sql.execution.adaptive.AdaptiveSparkPlanExec.withFinalPlanUpdate(AdaptiveSparkPlanExec.scala:764)\n\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.computeResult(ResultCacheManager.scala:537)\n\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.$anonfun$getOrComputeResultInternal$1(ResultCacheManager.scala:396)\n\tat scala.Option.getOrElse(Option.scala:189)\n\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.getOrComputeResultInternal(ResultCacheManager.scala:390)\n\tat org.apache.spark.sql.execution.qrc.ResultCacheManager.getOrComputeResult(ResultCacheManager.scala:292)\n\tat org.apache.spark.sql.execution.SparkPlan.$anonfun$executeCollectResult$1(SparkPlan.scala:433)\n\tat com.databricks.spark.util.FrameProfiler$.record(FrameProfiler.scala:80)\n\tat org.apache.spark.sql.execution.SparkPlan.executeCollectResult(SparkPlan.scala:430)\n\tat org.apache.spark.sql.Dataset.collectResult(Dataset.scala:3431)\n\tat org.apache.spark.sql.Dataset.$anonfun$collectResult$1(Dataset.scala:3422)\n\tat org.apache.spark.sql.Dataset.$anonfun$withAction$3(Dataset.scala:4297)\n\tat org.apache.spark.sql.execution.QueryExecution$.withInternalError(QueryExecution.scala:773)\n\tat org.apache.spark.sql.Dataset.$anonfun$withAction$2(Dataset.scala:4295)\n\tat org.apache.spark.sql.execution.SQLExecution$.$anonfun$withCustomExecutionEnv$8(SQLExecution.scala:249)\n\tat org.apache.spark.sql.execution.SQLExecution$.withSQLConfPropagated(SQLExecution.scala:399)\n\tat org.apache.spark.sql.execution.SQLExecution$.$anonfun$withCustomExecutionEnv$1(SQLExecution.scala:194)\n\tat org.apache.spark.sql.SparkSession.withActive(SparkSession.scala:985)\n\tat org.apache.spark.sql.execution.SQLExecution$.withCustomExecutionEnv(SQLExecution.scala:148)\n\tat org.apache.spark.sql.execution.SQLExecution$.withNewExecutionId(SQLExecution.scala:349)\n\tat org.apache.spark.sql.Dataset.withAction(Dataset.scala:4295)\n\tat org.apache.spark.sql.Dataset.collectResult(Dataset.scala:3421)\n\tat com.databricks.backend.daemon.driver.OutputAggregator$.withOutputAggregation0(OutputAggregator.scala:267)\n\tat com.databricks.backend.daemon.driver.OutputAggregator$.withOutputAggregation(OutputAggregator.scala:101)\n\tat com.databricks.backend.daemon.driver.PythonDriverLocalBase.generateTableResult(PythonDriverLocalBase.scala:723)\n\tat com.databricks.backend.daemon.driver.JupyterDriverLocal.computeListResultsItem(JupyterDriverLocal.scala:1424)\n\tat com.databricks.backend.daemon.driver.JupyterDriverLocal$JupyterEntryPoint.addCustomDisplayData(JupyterDriverLocal.scala:505)\n\tat sun.reflect.GeneratedMethodAccessor746.invoke(Unknown Source)\n\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n\tat java.lang.reflect.Method.invoke(Method.java:498)\n\tat py4j.reflection.MethodInvoker.invoke(MethodInvoker.java:244)\n\tat py4j.reflection.ReflectionEngine.invoke(ReflectionEngine.java:380)\n\tat py4j.Gateway.invoke(Gateway.java:306)\n\tat py4j.commands.AbstractCommand.invokeMethod(AbstractCommand.java:132)\n\tat py4j.commands.CallCommand.execute(CallCommand.java:79)\n\tat py4j.ClientServerConnection.waitForCommands(ClientServerConnection.java:195)\n\tat py4j.ClientServerConnection.run(ClientServerConnection.java:115)\n\tat java.lang.Thread.run(Thread.java:750)\nCaused by: org.apache.spark.api.python.PythonException: 'ValueError: Input contains NaN, infinity or a value too large for dtype('float32').'. Full traceback below:\nTraceback (most recent call last):\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1025, in udf\n os.kill(scoring_server_proc.pid, signal.SIGTERM)\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 866, in _predict_row_batch\n result = predict_fn(pdf)\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 1006, in batch_predict_fn\n return loaded_model.predict(pdf)\n File \"/databricks/python/lib/python3.9/site-packages/mlflow/pyfunc/__init__.py\", line 373, in predict\n return self._predict_fn(data)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 784, in predict\n X = self._validate_X_predict(X)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/ensemble/_forest.py\", line 422, in _validate_X_predict\n return self.estimators_[0]._validate_X_predict(X, check_input=True)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/tree/_classes.py\", line 407, in _validate_X_predict\n X = self._validate_data(X, dtype=DTYPE, accept_sparse=\"csr\",\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/base.py\", line 421, in _validate_data\n X = check_array(X, **check_params)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 63, in inner_f\n return f(*args, **kwargs)\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 720, in check_array\n _assert_all_finite(array,\n File \"/databricks/python/lib/python3.9/site-packages/sklearn/utils/validation.py\", line 103, in _assert_all_finite\n raise ValueError(\nValueError: Input contains NaN, infinity or a value too large for dtype('float32').\n\n\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.handlePythonException(PythonRunner.scala:694)\n\tat org.apache.spark.sql.execution.python.PythonArrowOutput$$anon$1.read(PythonArrowOutput.scala:110)\n\tat org.apache.spark.api.python.BasePythonRunner$ReaderIterator.hasNext(PythonRunner.scala:647)\n\tat org.apache.spark.InterruptibleIterator.hasNext(InterruptibleIterator.scala:37)\n\tat scala.collection.Iterator$$anon$11.hasNext(Iterator.scala:491)\n\tat scala.collection.Iterator$$anon$10.hasNext(Iterator.scala:460)\n\tat org.apache.spark.sql.catalyst.expressions.GeneratedClass$GeneratedIteratorForCodegenStage3.processNext(Unknown Source)\n\tat org.apache.spark.sql.execution.BufferedRowIterator.hasNext(BufferedRowIterator.java:43)\n\tat org.apache.spark.sql.execution.WholeStageCodegenExec$$anon$1.hasNext(WholeStageCodegenExec.scala:761)\n\tat org.apache.spark.sql.execution.collect.UnsafeRowBatchUtils$.encodeUnsafeRows(UnsafeRowBatchUtils.scala:80)\n\tat org.apache.spark.sql.execution.collect.Collector.$anonfun$processFunc$1(Collector.scala:186)\n\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$3(ResultTask.scala:75)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.scheduler.ResultTask.$anonfun$runTask$1(ResultTask.scala:75)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:55)\n\tat org.apache.spark.scheduler.Task.doRunTask(Task.scala:174)\n\tat org.apache.spark.scheduler.Task.$anonfun$run$4(Task.scala:137)\n\tat com.databricks.unity.EmptyHandle$.runWithAndClose(UCSHandle.scala:125)\n\tat org.apache.spark.scheduler.Task.$anonfun$run$1(Task.scala:137)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.scheduler.Task.run(Task.scala:96)\n\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$13(Executor.scala:902)\n\tat org.apache.spark.util.Utils$.tryWithSafeFinally(Utils.scala:1697)\n\tat org.apache.spark.executor.Executor$TaskRunner.$anonfun$run$4(Executor.scala:905)\n\tat scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)\n\tat com.databricks.spark.util.ExecutorFrameProfiler$.record(ExecutorFrameProfiler.scala:110)\n\tat org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:760)\n\tat java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)\n\tat java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)\n\t... 1 more\n", "errorSummary": "PythonException: 'ValueError: Input contains NaN, infinity or a value too large for dtype('float32').'. Full traceback below:", "errorTraceType": "html", "metadata": {}, "type": "ipynbError" } }, "output_type": "display_data" } ], "source": [ "# predict unknown diamond\n", "predictions_unknown_diamond = fs.score_batch(uri_all_features, diamond_unknown)\n", "display(predictions_unknown_diamond)" ] } ], "metadata": { "application/vnd.databricks.v1+notebook": { "dashboards": [], "language": "python", "notebookMetadata": { "mostRecentlyExecutedCommandWithImplicitDF": { "commandId": 1612971859527038, "dataframes": [ "_sqldf" ] }, "pythonIndentUnit": 2 }, "notebookName": "Databricks-ML-professional-S03a-Batch", "widgets": {} }, "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 4 }