MLOps极致细节:5. MLFlow Projects 案例代码解读

MLOps极致细节:5. MLFlow Projects 案例代码解读

MLFlow Projects允许我们将代码及其依赖项打包为一个可以在其他平台上以可复制(reproducible)和可重用(reusable)的方式运行的项目。每个项目都包括自己的代码和一个MLproject文件,该文件定义了它的依赖项(例如Python环境),以及可以在项目中运行哪些命令以及它们采用的参数。

上一章节中,我们对MLflow Projects的概念以及结果进行了描述(参见MLOps极致细节:4. MLFlow Projects 案例介绍)。这里我们将对代码进行详细解读。



这个案例中,我们将使用scikit-learn来训练一个简单的线性回归模型,然后打包这个模型使之可重复使用,并且最终在HTTP服务器上重复使用。代码基于MLFlow官网改写。

我们引用wine quality数据集。在此数据集中,我们基于一些特征,如"fixed acidity",“volatile acidity”,"citric acid"等等,来对wine的"quality"进行预测。

预测的模型我们选择sklearn.linear_model.ElasticNet,详细的说明参见官网

现在,让我们一步步地进行解析。在学习MLFlow Projects的使用之前,我们先得把它放一边,把普通的代码跑通(即,第一种运行方式)。

1 相关参数的输入

首先,按照以往一般的运行代码的步骤,我们先进入虚拟环境,安装相关依赖。这个案例的依赖存放于requirements.txt文件中,我们通过conda activate mlFlowEx进入虚拟环境(mlFlowEx是我们创建的虚拟环境名称),然后pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple安装依赖。

此案例相关的参数存放于config.txt文件中。

[main]
alpha=0.5
l1ratio=0.5
mode=loop
experimentid=mlflowProjectExample11

对于mode这个参数,我们可以选择loop或者normalnormal指的是,我们仅将alpha以及l1_ratio这两个参数导入,而loop指的是,我们会写一个循环,尝试alpha以及l1_ratio的值从0到1,观察哪一个参数可以获得最优的预测结果。

在主函数main.py中,我们使用如下代码导入上述参数:

config = configparser.ConfigParser()                                    
config.read(cfg_file)
config.sections()

# Set properties of parameters inside config file.
mode = 'normal'                 # initialization
for key in config['main']:
    for key in config['main']:
        if key == 'alpha' :
            alpha_ = config.getfloat('main', key)    
        if key == 'l1ratio' :
            l1Ratio_ = config.getfloat('main', key)   
        if key == 'mode': 
            mode_ = config.get('main', key)
        if key == 'experimentid':
            experiment_ = config.get('main', key)

与此同时,我们可以支持在python main.py后面加参数,来对这些参数的导入:

alpha = float(sys.argv[1]) if len(sys.argv) > 1 else alpha_
l1Ratio = float(sys.argv[2]) if len(sys.argv) > 2 else l1Ratio_
mode = sys.argv[3] if len(sys.argv) > 3 else mode_
experimentname = sys.argv[4] if len(sys.argv) > 4 else experiment_

2 整体代码流程

整体的代码见main.py

整体的代码流程:当我们导入了程序需要的参数,并且导入训练模型所需要的数据后,我们新建一个experimentexperiment_id = mlflow.create_experiment(experimentname)。如果我们mode选择的是loop,那么系统将选择不同的alpha值以及l1_ratio值,并进行模型训练,以及训练出来的模型的保存。

在模型训练之前,我们需要新建run,我们在每一个loop开始的地方新建run,结束的地方关闭这个run。这么做的好处就是,我们可以将每一个训练出来的模型保存下来。代码如下:

for alpha in alphaList:
    for l1_ratio in l1RatioList:
        # Generate a new run in the beginning of each loop, and end this run at the end of each loop.
        with mlflow.start_run(run_name="ElasticnetModelTest{0}".format(step), experiment_id=experiment_id):
            lr = ElasticNet(alpha=alpha, l1_ratio=l1_ratio, random_state=42)
            lr.fit(train_x, train_y)
            predicted_qualities = lr.predict(test_x)
            (rmse, mae, r2) = eval_metrics(test_y, predicted_qualities)
            rmse = round(rmse, 2)
            mae = round(mae, 2)
            r2 = round(r2, 2)
            print("Test - alpha: {0}, l1_ratio: {1}. Result: RMSE: {2}, MAE: {3}, R2: {4}".format(\
                alpha, l1_ratio, rmse, mae, r2))
            metrics = {"alpha": alpha, "l1_ratio":l1_ratio, "rmse": rmse, "mae": mae, "r2": r2}
            mlflow.log_metrics(metrics)
            # Model registry does not work with file store
            tracking_url_type_store = urlparse(mlflow.get_tracking_uri()).scheme
            # Save this trained model under current run
            if tracking_url_type_store != "file":
                mlflow.sklearn.log_model(lr, "model", registered_model_name="ElasticnetWineModel")
            else:
                mlflow.sklearn.log_model(lr, "model")
            if r2 <= r2Final:
                r2Final = r2
                alphaFinal = alpha
                l1RatioFinal = l1_ratio
        step += 1
        mlflow.end_run()    # end this run at the end of each loop.

3 准备模型训练

在训练模型之前,我们需要做一些准备,这里只列举了一些最基本的准备,比如数据的导入,拆分(成训练集和验证集)。代码如下:

# Read the wine-quality csv file from the URL
csv_url = ("http://archive.ics.uci.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv")
try:
    data = pd.read_csv(csv_url, sep=";")
except Exception as e:
    logger.exception("Unable to download training & test CSV, check your internet connection. Error: %s", e)

# Split the data into training and test sets. (0.75, 0.25) split.
train, test = train_test_split(data)
# The predicted column is "quality" which is a scalar from [3, 9]
train_x = train.drop(["quality"], axis=1)
test_x = test.drop(["quality"], axis=1)
train_y = train[["quality"]]
test_y = test[["quality"]]

4 模型训练和验证

这里由于只是一个例子,模型的训练和验证我们就直接用sklearn的现有模块了。其核心代码就是如下几行:

lr = ElasticNet(alpha=alphaFinal, l1_ratio=l1RatioFinal, random_state=42)
lr.fit(train_x, train_y)
predicted_qualities = lr.predict(test_x)
(rmse, mae, r2) = eval_metrics(test_y, predicted_qualities)

5 MLFlow UI

当我们运行完代码后(这里我们先使用最原始的方式python main.py来运行代码),我们在terminal输入mlflow ui,然后进入网页http://127.0.0.1:5000

在这里插入图片描述

我们点击了对应的experiment之后,就能看到每一个循环,不同的alpha以及l1_ratio参数下对应的模型以及mse结果。

5.1 将代码打包进Conda环境

在之前的MLFlow Projects的介绍中,我们有提到,我们可以使用两种环境,conda以及docker container。这里我们先介绍conda环境下如何打包(即,第二第三种运行方式)。

我们在main.py相同目录下创建MLproject文件,键入:

name: tutorial
conda_env: conda.yaml
entry_points:
  main:
    parameters:
      alpha: {type: float, default: 0.5}
      l1_ratio: {type: float, default: 0.1}
      mode: {type: string, default: loop}
      experimentid: {type: string, default: mlflowProjectExample20}
    command: "python main.py {alpha} {l1_ratio} {mode} {experimentid}"

在新建conda.yaml,把相关的依赖写进去:

name: tutorial
channels:
  - conda-forge
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/pro
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
dependencies:
  - python=3.7
  - pip
  - scikit-learn
  - mlflow
  - pandas

这里需要注意,在一般的情况下,我们都会用pip进行依赖的下载,比如:pip3 install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple。由于在大陆,我们需要在pip install后面加上国内的镜像源。如果不加的话,很可能会因为超时而依赖加载失败。在conda.yaml的书写过程中,原版是这样的:

name: tutorial
channels:
  - conda-forge
dependencies:
  - python=3.7
  - pip
  - pip:
      - scikit-learn==0.23.2
      - mlflow>=1.0
      - pandas

但如果这样去运行,会出现超时。所以,最后我就把pip安装改成了conda安装。另外,我们需要对conda的环境添加清华源。详细的介绍参见此文

5.2 运行MLFlow Projects

按照第二种运行方式,克隆下来代码,并在本地terminal输入mlflow run mlflow-ex-project-basic。以下为一些运行之后的日志:

2022/03/02 16:27:25 INFO mlflow.utils.conda: === Creating conda environment mlflow-5cbba1deec6e564616d0ebee88bd7d510b35ef53 ===
Collecting package metadata (repodata.json): done
Solving environment: done

Downloading and Extracting Packages
click-8.0.4          | 146 KB    | ############################################################# | 100%  
pysocks-1.7.1        | 28 KB     | ############################################################# | 100%  
smmap-3.0.5          | 22 KB     | ############################################################# | 100%  
yaml-0.2.5           | 62 KB     | ############################################################# | 100%  
joblib-1.1.0         | 210 KB    | ############################################################# | 100%  
m2w64-gcc-libgfortra | 342 KB    | ############################################################# | 100%  
docker-py-5.0.3      | 189 KB    | ############################################################# | 100%  
pycparser-2.21       | 100 KB    | ############################################################# | 100%  
importlib-metadata-4 | 33 KB     | ############################################################# | 100%  
.....省略加载的依赖
appdirs-1.4.4        | 13 KB     | ############################################################# | 100%  
cloudpickle-2.0.0    | 24 KB     | ############################################################# | 100%  
Preparing transaction: done
Verifying transaction: done
Executing transaction: done
#
# To activate this environment, use
#
#     $ conda activate mlflow-5cbba1deec6e564616d0ebee88bd7d510b35ef53
#
# To deactivate an active environment, use
#
#     $ conda deactivate

2022/03/02 16:31:57 INFO mlflow.projects.utils: === Created directory C:\XXX\Temp\tmpd5_ytmy5 for downloading remote URIs passed to arguments of type 'path' ===
2022/03/02 16:31:57 INFO mlflow.projects.backend.local: === Running command 'conda activate mlflow-5cbba1deec6e564616d0ebee88bd7d510b35ef53 && python main.py 0.42 0.1 loop mlflowProjectExample20' in run with ID '3f318366cd9b43ca9727b1254786473a' ===
Mode: loop
Test Elasticnet model with different alpha and l1_ratio values.
Test - alpha: 0.0, l1_ratio: 0.0. Result: RMSE: 0.65, MAE: 0.5, R2: 0.37
Test - alpha: 0.0, l1_ratio: 0.2. Result: RMSE: 0.65, MAE: 0.5, R2: 0.37
Test - alpha: 0.0, l1_ratio: 0.4. Result: RMSE: 0.65, MAE: 0.5, R2: 0.37
Test - alpha: 0.0, l1_ratio: 0.6000000000000001. Result: RMSE: 0.65, MAE: 0.5, R2: 0.37
Test - alpha: 0.0, l1_ratio: 0.8. Result: RMSE: 0.65, MAE: 0.5, R2: 0.37
Test - alpha: 0.2, l1_ratio: 0.0. Result: RMSE: 0.69, MAE: 0.55, R2: 0.28
Test - alpha: 0.2, l1_ratio: 0.2. Result: RMSE: 0.71, MAE: 0.57, R2: 0.24
Test - alpha: 0.2, l1_ratio: 0.4. Result: RMSE: 0.72, MAE: 0.57, R2: 0.22
Test - alpha: 0.2, l1_ratio: 0.6000000000000001. Result: RMSE: 0.72, MAE: 0.58, R2: 0.2
Test - alpha: 0.2, l1_ratio: 0.8. Result: RMSE: 0.73, MAE: 0.59, R2: 0.18
Test - alpha: 0.4, l1_ratio: 0.0. Result: RMSE: 0.7, MAE: 0.56, R2: 0.26
Test - alpha: 0.4, l1_ratio: 0.2. Result: RMSE: 0.72, MAE: 0.58, R2: 0.21
Test - alpha: 0.4, l1_ratio: 0.4. Result: RMSE: 0.74, MAE: 0.59, R2: 0.17
Test - alpha: 0.4, l1_ratio: 0.6000000000000001. Result: RMSE: 0.76, MAE: 0.61, R2: 0.13
Test - alpha: 0.6000000000000001, l1_ratio: 0.0. Result: RMSE: 0.71, MAE: 0.56, R2: 0.24
Test - alpha: 0.6000000000000001, l1_ratio: 0.2. Result: RMSE: 0.74, MAE: 0.59, R2: 0.17
Test - alpha: 0.6000000000000001, l1_ratio: 0.4. Result: RMSE: 0.76, MAE: 0.62, R2: 0.12
Test - alpha: 0.6000000000000001, l1_ratio: 0.6000000000000001. Result: RMSE: 0.79, MAE: 0.65, R2: 0.04  
Test - alpha: 0.6000000000000001, l1_ratio: 0.8. Result: RMSE: 0.8, MAE: 0.65, R2: 0.02
Test - alpha: 0.8, l1_ratio: 0.0. Result: RMSE: 0.71, MAE: 0.57, R2: 0.23
Test - alpha: 0.8, l1_ratio: 0.4. Result: RMSE: 0.78, MAE: 0.64, R2: 0.07
Test - alpha: 0.8, l1_ratio: 0.6000000000000001. Result: RMSE: 0.8, MAE: 0.65, R2: 0.02
Test - alpha: 0.8, l1_ratio: 0.8. Result: RMSE: 0.8, MAE: 0.66, R2: 0.02
Best Elasticnet model (alpha=0.800000, l1_ratio=0.800000):
 Best model - alpha: 0.8, l1_ratio: 0.8. Result: RMSE: 0.8026285560413718, MAE: 0.655679186024951, R2: 0.022402065369615642
2022/03/02 16:42:11 INFO mlflow.projects: === Run (ID '9bd071dd669747108a12271d65e641eb') succeeded ===

需要注意的是,首先conda会自己生成一个虚拟环境(mlflow-5cbba1deec6e564616d0ebee88bd7d510b35ef53),并且安装好对应的依赖。进入虚拟环境后(conda activate mlflow-5cbba1deec6e564616d0ebee88bd7d510b35ef53),再运行主代码(python main.py 0.42 0.1 loop mlflowProjectExample20),之后的日志是运行主程序出来的日志。

当我们运行mlflow run mlflow-projects之后,在本地当前路径下,也会自动生成一个mlruns的文件夹。我们可以在terminal中输入mlflow ui看到model的详细信息,以及一些可视化的结果。

另外一种打包运行的方式,就是可以直接把git上的项目下载下来,然后打包,比如:

mlflow run https://gitee.com/yichaoyyds/mlflow-ex-project-basic.git

其实道理和之前是一样的。这里就不赘述了(即,第三种运行方式)。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

破浪会有时

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值