Enhancing Spark capabilities with MLflow

Hello, Habra people. As we have already mentioned, this month OTUS is launching two courses on machine learning, namely basic and advanced. In this regard, we continue to share useful material.

The goal of this article is to talk about our first experience with MLflow.

We will start the review MLflow with its tracking server and will log all iterations of the research. We will then share our experience of connecting Spark with MLflow using UDF.

Context

We at Alpha Health uses machine learning and artificial intelligence to empower people to take care of their health and well-being. Therefore, machine learning models are the foundation of the data processing products we develop, which is why we were attracted to MLflow — an open-source platform that encompasses all aspects of the machine learning lifecycle.

MLflow

The main objective of MLflow is to provide an additional layer over machine learning that allows data science specialists to work with virtually any machine learning library (h2o, keras, mleap, pytorch, sklearn and tensorflow), taking its performance to the next level.

MLflow provides three components:

  • Tracking – recording and querying experiments: code, data, configuration, and results. Monitoring the model development process is crucial.
  • Projects – A packaging format for deployment on any platform (e.g., SageMaker)
  • Models – a common format for submitting models to various deployment tools.

MLflow (as of the writing of this article in alpha version) is an open-source platform that enables managing the machine learning lifecycle, including experiments, reuse, and deployment.

Setting up MLflow

To use MLflow, you first need to set up the entire Python environment, for this we will use PyEnv (to install Python on Mac, check out here). This way we can create a virtual environment where we will install all the necessary libraries to launch.

```
pyenv install 3.7.0
pyenv global 3.7.0 # Use Python 3.7
mkvirtualenv mlflow # Create a Virtual Env with Python 3.7
workon mlflow
```

We will install the required libraries.

```
pip install mlflow==0.7.0 
            Cython==0.29  
            numpy==1.14.5 
            pandas==0.23.4 
            pyarrow==0.11.0
```

Note: We use PyArrow to run models like UDF. The versions of PyArrow and Numpy needed adjustment, as the latest versions conflicted with each other.

Launching Tracking UI

MLflow Tracking allows us to log and query experiments using Python and REST the API. Additionally, you can define where to store model artifacts (localhost, Amazon S3, Azure Blob Storage, Google Cloud Storage or SFTP server). Since we use AWS at Alpha Health, S3 will be used as the artifact storage.

# Running a Tracking Server
mlflow server 
    --file-store /tmp/mlflow/fileStore 
    --default-artifact-root s3://<bucket>/mlflow/artifacts/ 
    --host localhost
    --port 5000

MLflow recommends using persistent storage for files. A file storage is where the server will keep the metadata of runs and experiments. When starting the server, ensure it points to persistent storage. For this experiment, we will simply use /tmp.

Remember that if we want to use the mlflow server to run old experiments, they must be present in the file storage. However, even without this, we could use them in UDF, as we only need the path to the model.

Note: Keep in mind that both the Tracking UI and model client must have access to the artifact location. This means that regardless of the Tracking UI being located in an EC2 instance, when running MLflow locally, the machine must have direct access to S3 for writing model artifacts.

Enhancing Spark capabilities with MLflow
The Tracking UI stores artifacts in an S3 bucket

Running models

Once the Tracking server is up and running, you can start training models.

As an example, we will use the modified wine example from MLflow in Sklearn.

MLFLOW_TRACKING_URI=http://localhost:5000 python wine_quality.py 
  --alpha 0.9
  --l1_ratio 0.5
  --wine_file ./data/winequality-red.csv

As mentioned earlier, MLflow allows logging parameters, metrics, and model artifacts so that we can track their development over iterations. This feature is extremely useful as it enables us to reproduce the best model by referencing the Tracking server or understanding which code executed the necessary iteration using git commit logs.

with mlflow.start_run():

    ... model ...

    mlflow.log_param("source", wine_path)
    mlflow.log_param("alpha", alpha)
    mlflow.log_param("l1_ratio", l1_ratio)

    mlflow.log_metric("rmse", rmse)
    mlflow.log_metric("r2", r2)
    mlflow.log_metric("mae", mae)

    mlflow.set_tag('domain', 'wine')
    mlflow.set_tag('predict', 'quality')
    mlflow.sklearn.log_model(lr, "model")

Enhancing Spark capabilities with MLflow
Wine iterations

Backend for the model

The MLflow tracking server, launched with the command “mlflow server,” has a REST API for tracking runs and recording data in the local file system. You can specify the address of the tracking server using the environment variable 'MLFLOW_TRACKING_URI', and the MLflow tracking API will automatically connect to the tracking server at this address to create/retrieve information about runs, log metrics, etc.

Source: Docs// Running a tracking server

To serve the model, we need a running tracking server (see launch interface) and the model's Run ID.

Enhancing Spark capabilities with MLflow
Run ID

# Serve a sklearn model through 127.0.0.0:5005
MLFLOW_TRACKING_URI=http://0.0.0.0:5000 mlflow sklearn serve 
  --port 5005  
  --run_id 0f8691808e914d1087cf097a08730f17 
  --model-path model

To serve models using the MLflow serve feature, we need access to the Tracking UI to obtain information about the model simply by specifying --run_id.

Once the model is connected to the tracking server, we can obtain a new model endpoint.

# Query Tracking Server Endpoint
curl -X POST 
  http://127.0.0.1:5005/invocations 
  -H 'Content-Type: application/json' 
  -d '[
	{
		"fixed acidity": 3.42, 
		"volatile acidity": 1.66, 
		"citric acid": 0.48, 
		"residual sugar": 4.2, 
		"chloridessssss": 0.229, 
		"free sulfur dsioxide": 19, 
		"total sulfur dioxide": 25, 
		"density": 1.98, 
		"pH": 5.33, 
		"sulphates": 4.39, 
		"alcohol": 10.8
	}
]'

> {"predictions": [5.825055635303461]}

Running models from Spark

Although the tracking server is powerful enough to serve models in real-time, training them and using the serve feature (source: mlflow // docs // models # local), using Spark (batch or streaming) is an even more powerful solution due to its distributed nature.

Imagine you just completed training offline and then applied the output model to all your data. That's where Spark and MLflow excel.

Installing PySpark + Jupyter + Spark

Source: Get started with PySpark — Jupyter

To demonstrate how we apply MLflow models to Spark DataFrames, we need to set up collaboration between Jupyter notebooks and PySpark.

Start by installing the latest stable version Apache Spark:

cd ~/Downloads/
tar -xzf spark-2.4.3-bin-hadoop2.7.tgz
mv ~/Downloads/spark-2.4.3-bin-hadoop2.7 ~/
ln -s ~/spark-2.4.3-bin-hadoop2.7 ~/spark

Install PySpark and Jupyter in a virtual environment:

pip install pyspark jupyter

Set up environment variables:

export SPARK_HOME=~/spark
export PATH=$SPARK_HOME/bin:$PATH
export PYSPARK_DRIVER_PYTHON=jupyter
export PYSPARK_DRIVER_PYTHON_OPTS="notebook --notebook-dir=${HOME}/Projects/notebooks"

By defining notebook-dir, we can store our notebooks in the desired folder.

Launching Jupyter from PySpark

Since we’ve been able to set up Jupyter as the PySpark driver, we can now launch a Jupyter notebook in the context of PySpark.

(mlflow) afranzi:~$ pyspark
[I 19:05:01.572 NotebookApp] sparkmagic extension enabled!
[I 19:05:01.573 NotebookApp] Serving notebooks from local directory: /Users/afranzi/Projects/notebooks
[I 19:05:01.573 NotebookApp] The Jupyter Notebook is running at:
[I 19:05:01.573 NotebookApp] http://localhost:8888/?token=c06252daa6a12cfdd33c1d2e96c8d3b19d90e9f6fc171745
[I 19:05:01.573 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 19:05:01.574 NotebookApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=c06252daa6a12cfdd33c1d2e96c8d3b19d90e9f6fc171745

Enhancing Spark capabilities with MLflow

As mentioned above, MLflow provides the capability to log model artifacts to S3. Once we have the selected model in hand, we can import it as a UDF using the module mlflow.pyfunc.

import mlflow.pyfunc

model_path = 's3:///mlflow/artifacts/1/0f8691808e914d1087cf097a08730f17/artifacts/model'
wine_path = '/Users/afranzi/Projects/data/winequality-red.csv'
wine_udf = mlflow.pyfunc.spark_udf(spark, model_path)

df = spark.read.format("csv").option("header", "true").option('delimiter', ';').load(wine_path)
columns = [ "fixed acidity", "volatile acidity", "citric acid",
            "residual sugar", "chlorides", "free sulfur dioxide",
            "total sulfur dioxide", "density", "pH",
            "sulphates", "alcohol"
          ]
          
df.withColumn('prediction', wine_udf(*columns)).show(100, False)

Enhancing Spark capabilities with MLflow
PySpark – Wine Quality Prediction Output

Up until now, we have discussed how to use PySpark with MLflow to run wine quality predictions on the entire wine dataset. But what if we need to use MLflow Python modules from Scala Spark?

We tested this by sharing the Spark context between Scala and Python. That is, we registered the MLflow UDF in Python and used it from Scala (yes, perhaps not the best solution, but it's what we have).

Scala Spark + MLflow

For this example, we will add Toree Kernel to the existing Jupyter.

Installing Spark + Toree + Jupyter

pip install toree
jupyter toree install --spark_home=${SPARK_HOME} --sys-prefix
jupyter kernelspec list
```
```
Available kernels:
  apache_toree_scala    /Users/afranzi/.virtualenvs/mlflow/share/jupyter/kernels/apache_toree_scala
  python3               /Users/afranzi/.virtualenvs/mlflow/share/jupyter/kernels/python3
```

As can be seen from the attached notebook, UDF is used with both Spark and PySpark. We hope this section will be useful for those who love Scala and want to deploy machine learning models in production.

import org.apache.spark.sql.functions.col
import org.apache.spark.sql.types.StructType
import org.apache.spark.sql.{Column, DataFrame}
import scala.util.matching.Regex

val FirstAtRe: Regex = "^_".r
val AliasRe: Regex = "[\s_.:@]+".r

def getFieldAlias(field_name: String): String = {
    FirstAtRe.replaceAllIn(AliasRe.replaceAllIn(field_name, "_"), "")
}

def selectFieldsNormalized(columns: List[String])(df: DataFrame): DataFrame = {
    val fieldsToSelect: List[Column] = columns.map(field =>
        col(field).as(getFieldAlias(field))
    )
    df.select(fieldsToSelect: _*)
}

def normalizeSchema(df: DataFrame): DataFrame = {
    val schema = df.columns.toList
    df.transform(selectFieldsNormalized(schema))
}

FirstAtRe = ^_
AliasRe = [s_.:@]+

getFieldAlias: (field_name: String)String
selectFieldsNormalized: (columns: List[String])(df: org.apache.spark.sql.DataFrame)org.apache.spark.sql.DataFrame
normalizeSchema: (df: org.apache.spark.sql.DataFrame)org.apache.spark.sql.DataFrame
Out[1]:
[s_.:@]+
In [2]:
val winePath = "~\/Research\/mlflow-workshop\/examples\/wine_quality\/data\/winequality-red.csv"
val modelPath = "\/tmp\/mlflow\/artifactStore\/0\/96cba14c6e4b452e937eb5072467bf79\/artifacts\/model"

winePath = ~\/Research\/mlflow-workshop\/examples\/wine_quality\/data\/winequality-red.csv
modelPath = \/tmp\/mlflow\/artifactStore\/0\/96cba14c6e4b452e937eb5072467bf79\/artifacts\/model
Out[2]:
\/tmp\/mlflow\/artifactStore\/0\/96cba14c6e4b452e937eb5072467bf79\/artifacts\/model
In [3]:
val df = spark.read
              .format("csv")
              .option("header", "true")
              .option("delimiter", ";")
              .load(winePath)
              .transform(normalizeSchema)

df = [fixed_acidity: string, volatile_acidity: string ... 10 more fields]
Out[3]:
[fixed_acidity: string, volatile_acidity: string ... 10 more fields]
In [4]:
%%PySpark
import mlflow
from mlflow import pyfunc

model_path = "\/tmp\/mlflow\/artifactStore\/0\/96cba14c6e4b452e937eb5072467bf79\/artifacts\/model"
wine_quality_udf = mlflow.pyfunc.spark_udf(spark, model_path)

spark.udf.register("wineQuality", wine_quality_udf)
Out[4]:
<function spark_udf..predict at 0x1116a98c8>
In [6]:
df.createOrReplaceTempView("wines")
In [10]:
%%SQL
SELECT 
    quality,
    wineQuality(
        fixed_acidity,
        volatile_acidity,
        citric_acid,
        residual_sugar,
        chlorides,
        free_sulfur_dioxide,
        total_sulfur_dioxide,
        density,
        pH,
        sulphates,
        alcohol
    ) AS prediction
FROM wines
LIMIT 10
Out[10]:
+-------+------------------+
|quality|        prediction|
+-------+------------------+
|      5| 5.576883967129615|
|      5|  5.50664776916154|
|      5| 5.525504822954496|
|      6| 5.504311247097457|
|      5| 5.576883967129615|
|      5|5.5556903912725755|
|      5| 5.467882654744997|
|      7| 5.710602976324739|
|      7| 5.657319539336507|
|      5| 5.345098606538708|
+-------+------------------+

In [17]:
spark.catalog.listFunctions.filter('name like "%wineQuality%").show(20, false)

+-----------+--------+-----------+---------+-----------+
|name       |database|description|className|isTemporary|
+-----------+--------+-----------+---------+-----------+
|wineQuality|null    |null       |null     |true       |
+-----------+--------+-----------+---------+-----------+

Next Steps

Despite the fact that as of the time of writing this article, MLflow is in Alpha version, it looks quite promising. The very ability to run multiple machine learning frameworks and use them from a single endpoint elevates recommendation systems to a new level.

Moreover, MLflow bridges the gap between Data Engineers and Data Science specialists, establishing a common layer between them.

After this MLflow research, we are confident that we will move forward and use it for our Spark pipelines and in recommendation systems.

It would be nice to synchronize the file storage with the database instead of the file system. This way, we should have multiple endpoints that can use the same file storage. For example, utilizing several instances Presto and Athena with the same Glue metastore.

In conclusion, we would like to thank the MLFlow community for making our data work more interesting.

If you're experimenting with MLflow, feel free to write to us and share how you're using it, especially if you're utilizing it in production.

Learn more about the courses:
Machine Learning. Basic Course
Machine Learning. Advanced Course

Read more:

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster