在这个博客中,我们将基于mlflow的官方案例来解读如何使用docker环境运行mlflow代码。我们使用的平台是Win10,但在运行过程中,我们发现在Win10平台上操作会出现bug,由于目前手头上正巧没有Linux主机,所以只能继续使用Win10平台,并在本文中详细解释bug的原因(docker: Error response from daemon: invalid mode
)以及如何更正。
我们先将代码clone下来:git clone https://gitee.com/yichaoyyds/mlflow-ex-docker-basic.git
。然年后进入文件夹mlflow-ex-docker-basic
。
根本上,MLFlow Projects
的作用就是让其他数据科学家更方便地运行MLFlow项目。在原本的MLFlow项目的基础上,我们需要增加一个MLProject
的yaml文件,在项目的根目录下。
一个MLProject
的例子如下:
name: docker-example
docker_env:
image: mlflow-docker-example
entry_points:
main:
parameters:
alpha: float
l1_ratio: {type: float, default: 0.1}
command: "python train.py --alpha {alpha} --l1-ratio {l1_ratio}"
需要注意的是,这里我们选择基于docker来搭环境,所以yaml文件里需要写docker_env: ...
,而如果我们选择基于conda来大环境,那么yaml文件里需要写conda_env: conda.yaml
。关于conda的案例参见之前的博客。另外,需要注意,image
的命名,在之后docker build的时候需要一致。
这个大家就很熟悉了,这里我们基于miniforge3
,然后安装了一些依赖,由于在大陆,直接运行pip可能会超时,所以我就加了镜像源。
FROM condaforge/miniforge3
RUN pip install mlflow>=1.0 -i https://pypi.tuna.tsinghua.edu.cn/simple\
&& pip install azure-storage-blob==12.3.0 -i https://pypi.tuna.tsinghua.edu.cn/simple\
&& pip install numpy==1.21.2 -i https://pypi.tuna.tsinghua.edu.cn/simple\
&& pip install scipy -i https://pypi.tuna.tsinghua.edu.cn/simple\
&& pip install pandas==1.3.3 -i https://pypi.tuna.tsinghua.edu.cn/simple\
&& pip install scikit-learn==0.24.2 -i https://pypi.tuna.tsinghua.edu.cn/simple\
&& pip install cloudpickle -i https://pypi.tuna.tsinghua.edu.cn/simple
接下来,我们创建镜像:
docker build -t mlflow-docker-example -f Dockerfile .
最后那个.
不要漏了,指的是当前路径下的Dockerfile文件。成功后,我们在terminal中输入docker image list
,应该就能看到新建的镜像了。需要注意镜像和容器的区别,当前,我们只是建了一个镜像,并没有“实例化”成容器,所以如果我们在terminal中输入docker container ls
,我们不会看到有相关容器正在运行。
如果你对docker的操作还不是很熟悉,那么可以参考我的docker每日一阅系列博客。
train.py
文件是我们需要运行的主文件。在这个文件中,我们通过sklearn
来训练一个模型,去预测Wine Quality。当然,这只是一个非常简单的例子,模型本身并没有太多可借鉴性,但我们可以从这个例子中学习如何将这个代码放到docker container上去运行,然后在本地查看结果。
代码的逻辑很简单,包括读取数据集并拆分训练和测试集:
wine_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "wine-quality.csv")
data = pd.read_csv(wine_path)
train, test = train_test_split(data)
train_x = train.drop(["quality"], axis=1)
test_x = test.drop(["quality"], axis=1)
train_y = train[["quality"]]
test_y = test[["quality"]]
然后新建一个mlflow run:with mlflow.start_run():
,并且训练以及验证模型:
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)
最后将这些结果的数据保存到mlflow的日志中:
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)
下一步就是需要在容器中运行train.py
代码,正常的操作是,只需要在terminal中输入:
mlflow run . -P alpha=0.5
注意我们当前位置(如果我们ls
一下,就能看到train.py
以及MLproject
),所以上面这条指令中,.
指的是当前路径下,系统会自己去找MLproject
这个文件。
在我们运行后(Win10平台下),我们“惊讶地”发现,运行不成功,terminal中的日志如下:
2022/03/09 10:34:54 INFO mlflow.projects.docker: === Building docker image docker-example:40a87f9 ===
2022/03/09 10:34:55 INFO mlflow.projects.docker: Temporary docker context file C:\XXX\Local\Temp\tmp3s4p1222 was not deleted.
2022/03/09 10:34:55 INFO mlflow.projects.utils: === Created directory C:\XXX\Local\Temp\tmpgk0laptb for downloading remote URIs passed to arguments of type 'path' ===
2022/03/09 10:34:55 INFO mlflow.projects.backend.local: === Running command 'docker run --rm -v D:\XXX\mlruns:/mlflow/tmp/mlruns -v D:\XXX\mlruns\0\5cd6ea07fe594a7995dc11cc6c79568e\artifacts:D:\XXX\mlruns\0\5cd6ea07fe594a7995dc11cc6c79568e\artifacts -e MLFLOW_RUN_ID=5cd6ea07fe594a7995dc11cc6c79568e -e MLFLOW_TRACKING_URI=file:///mlflow/tmp/mlruns -e MLFLOW_EXPERIMENT_ID=0 docker-example:40a87f9 python train.py --alpha 0.5
--l1-ratio 0.1' in run with ID '5cd6ea07fe594a7995dc11cc6c79568e' ===
docker: Error response from daemon: invalid mode: \XXX\mlruns\0\5cd6ea07fe594a7995dc11cc6c79568e\artifacts.
See 'docker run --help'.
2022/03/09 10:34:56 ERROR mlflow.cli: === Run (ID '5cd6ea07fe594a7995dc11cc6c79568e') failed ===
关于Docker Container的使用描述,mlflow的官网是这么写的:
When you run an MLflow project that specifies a Docker image, MLflow adds a new Docker layer that copies the project’s contents into the /mlflow/projects/code directory. This step produces a new image. MLflow then runs the new image and invokes the project entrypoint in the resulting container.
Environment variables, such as MLFLOW_TRACKING_URI, are propagated inside the Docker container during project execution. Additionally, runs and experiments created by the project are saved to the tracking server specified by your tracking URI. When running against a local tracking URI, MLflow mounts the host system’s tracking directory (e.g., a local mlruns directory) inside the container so that metrics, parameters, and artifacts logged during project execution are accessible afterwards.
我们综合上两端文字和日志,来分析一下其中的逻辑。
首先,我们基于dockerfile创建一个image(镜像):docker build -t mlflow-docker-example -f Dockerfile .
。这个没问题。
然后,当我们运行mlflow run
的时候,mlflow会基于这个镜像的基础上再加一层,build出一个新的镜像,目的是在这个新的镜像内,把项目的这些代码放到/mlflow/projects/code
这个路径下。我们看到,在terminal的返回日志中是这么记录的:2022/03/09 10:34:54 INFO mlflow.projects.docker: === Building docker image docker-example:40a87f9 ===
。虽然没有全部运行成功,但这个新的镜像是建好了,如果我们在terminal中输入docker image list
,会看到:
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-example 40a87f9 be0b149cae42 10 minutes ago 924MB
mlflow-docker-example latest f521cdac5b17 41 hours ago 924MB
而且,这个镜像因为后面执行了docker run
的操作也实例化了一个让我们进入这个容器。我们输入docker container ls -a
会看到:
CONTAINER ID IMAGE COMMAND CREATED STATUS
PORTS NAMES
2b721f33cdf1 docker-example:40a87f9 "tini -- /bin/bash" 38 seconds ago Up 37 seconds
suspicious_nash
我们进入这个容器:
docker run -it docker-example:40a87f9
ls
和pwd
的结果如下:
(base) root@2b721f33cdf1:/mlflow/projects/code# ls a87f9
Dockerfile Dockerfile.mlflow-autogenerated MLproject README.md README.rst mlruns train.py wine-quality.csv ine-quality.csv
(base) root@2b721f33cdf1:/mlflow/projects/code# pwd
/mlflow/projects/code
所以,确实如官方文档中所说,项目的代码都被放到/mlflow/projects/code
这个路径下,这个是mlflow自动执行的,在我们的Dockerfile中,并没有使用COPY以及WORKDIR的命令。
然后就是出错的一步了,在terminal中:
2022/03/09 10:34:55 INFO mlflow.projects.backend.local: === Running command 'docker run --rm -v D:\XXX\mlruns:/mlflow/tmp/mlruns -v D:\XXX\mlruns\0\5cd6ea07fe594a7995dc11cc6c79568e\artifacts:D:\XXX\mlruns\0\5cd6ea07fe594a7995dc11cc6c79568e\artifacts -e MLFLOW_RUN_ID=5cd6ea07fe594a7995dc11cc6c79568e -e MLFLOW_TRACKING_URI=file:///mlflow/tmp/mlruns -e MLFLOW_EXPERIMENT_ID=0 docker-example:40a87f9 python train.py --alpha 0.5
--l1-ratio 0.1' in run with ID '5cd6ea07fe594a7995dc11cc6c79568e' ===
docker: Error response from daemon: invalid mode: \XXX\mlruns\0\5cd6ea07fe594a7995dc11cc6c79568e\artifacts.
See 'docker run --help'.
2022/03/09 10:34:56 ERROR mlflow.cli: === Run (ID '5cd6ea07fe594a7995dc11cc6c79568e') failed ===
问题是处在路径的书写上,比如:D:\XXX\mlruns\
,这个路径的正确写法应该是/d/XXX/mlruns/
。我们重新写一下docker run
指令:
docker run --rm -v /d/XXX/mlruns:/mlflow/tmp/mlruns -v /d/XXX/mlruns/0/5cd6ea07fe594a7995dc11cc6c79568e/artifacts:/d/XXX/mlruns/0/5cd6ea07fe594a7995dc11cc6c79568e/artifacts -e MLFLOW_RUN_ID=5cd6ea07fe594a7995dc11cc6c79568e -e MLFLOW_TRACKING_URI=file:///mlflow/tmp/mlruns -e MLFLOW_EXPERIMENT_ID=0 docker-example:40a87f9 python train.py --alpha 0.5 --l1-ratio 0.1
就可以正常运行了。terminal中返回
Elasticnet model (alpha=0.500000, l1_ratio=0.100000):
RMSE: 0.7947931019036528
MAE: 0.6189130834228137
R2: 0.1841166871822183
这个问题应该在linux环境下不会出现,因为Windows环境下的路径书写规则与Linux不大一样而导致error。