Docker提供了一种静态链接Linux核到应用程序的方式.
采用Docker容器可以调用GPUs,因此对于Tensorflow或者其它机器学习框架的部署是一种很好的工具.
利用Docker,不需要太多设置就可以重现机器学习项目,而不用再像下面这样:
# 6 hours of installing dependencies
python train.py
# > ERROR: libobscure.so cannot open shared object
只需进行类似于下面的操作,即可以执行 train.py 脚本,其集成了所有的依赖项,包括GPU支持:
dockrun tensorflow/tensorflow:0.12.1-gpu python train.py
> TRAINING SUCCESSFUL
此处, Docker是暂时的,且不会保存容器内的任何数据. 这里把Docker容器想象成一个1GB大小的 tensorflow.exe 应用程序,集成了需要编译的所有依赖项.
1. Docker的好处
开源软件往往有很多依赖项,造成难以重用,比如不同编译器的版本、丢失头文件、不正确的库路径等等,这些都导致需要浪费很多时间来设置依赖项,以运行软件.
2. Docker 的使用
对于机器学习项目,如果想要分享在GitHub上,项目的依赖项一般是一系列的Linux命令行,复制并粘贴到终端中安装.
Docker通过一个命令行来拉取正确的Docker镜像,可以取代后一部分的操作,运行项目程序. 通过静态的将项目所有依赖项集成进一个3GB的压缩镜像中,重用时只需拉取下载下来即可.
以基于Torch的 pix2pix 项目为例,直接在Ubuntu平台上时:
git clone https://github.com/phillipi/pix2pix.git
cd pix2pix
bash datasets/download_dataset.sh facades
# install dependencies for some time
...
# train
env \
DATA_ROOT=datasets/facades \
name=facades \
niter=200 \
save_latest_freq=400 \
which_direction=BtoA \
display=0 \
gpu=0 \
cudnn=0 \
th train.lua
如果依赖项较少时,该训练脚本是不错的,但实际上是有许多比较坑依赖项,如:
安装依赖项时,可能出现各种错误,比如:
luajit: symbol lookup error:
/root/torch/install/lib/lua/5.1/libTHNN.so: undefined symbol: TH_CONVERT_ACCREAL_TO_REAL
Docker则通过Docker Hub将项目依赖项打包做成二进制镜像形式.
3. 容器化 Dockerized
在Linux服务器上安装 [docker]() 和 [nvidia-docker](),然后Docker容器就可以访问GPUs,且基本上没有性能损失.
如果是Mac平台,可以安装 Docker for Mac,但是不能进行GPU运算,虽然少数Mac可能支持CUDA. 不过,仍可以以CPU模式进行测试,虽然速度很慢.
在Linux平台,下面的脚本可以在Ubuntu16.04安装 [docker](),可用于云服务商:
curl -fsSL https://affinelayer.com/docker/setup-docker.py | sudo python3
Docker安装成功后,可以以容器的方式运行 pix2pix 项目:
sudo docker run --rm --volume /:/host --workdir /host$PWD affinelayer/pix2pix <command>
具体过程如下:
git clone https://github.com/phillipi/pix2pix.git
cd pix2pix
bash datasets/download_dataset.sh facades
sudo docker run --rm --volume /:/host --workdir /host$PWD affinelayer/pix2pix \
env \
DATA_ROOT=datasets/facades \
name=facades \
niter=200 \
save_latest_freq=400 \
which_direction=BtoA \
display=0 \
gpu=0 \
cudnn=0 \
th train.lua
这里会下载作者编译的镜像(支持Torch+nvidia-docker),大概3GB的文件.
运行时会打印训练debug信息. 不过这里没有利用GPU. 基于GPU,能够有效提高pix2pix的训练速度.
4. GPU化
要利用GPU,仅需将 docker 替换为 nvidia-docker.
标准Docker中还未集成 nvidia-docker,因此需要另外安装. 这里提供一种基于Ubuntu16.04 LTS环境的安装脚本:
curl -fsSL https://affinelayer.com/docker/setup-nvidia-docker.py | sudo python3
这里大概花费5分钟完成安装,在 Microsoft Azure 和 AWS 测试过.
当 nvidia-docker 安装完成后,可以打印当前显卡的信息:
sudo nvidia-docker run --rm nvidia/cuda nvidia-smi
现在开启 pix2pix 的GPU训练模式:
sudo nvidia-docker run --rm --volume /:/host --workdir /host$PWD affinelayer/pix2pix \
env \
DATA_ROOT=datasets/facades \
name=facades \
niter=200 \
save_latest_freq=400 \
which_direction=BtoA \
display=0 \
th train.lua
使用了同一个 pix2pix Docker镜像,不过开启了GPU模式.
5. Protips 小贴士
对于基于Python的Tensorflow,可能用到一组命令行选项:
--env PYTHONUNBUFFERED=x # 即时打印所有的输出
--env CUDA_CACHE_PATH=/host/tmp/cuda-cache
#每次调用重新编译CUDA核,可避免每次启动Tensorflow的1分钟延迟
即:
sudo nvidia-docker run --rm --volume /:/host --workdir /host$PWD \
--env PYTHONUNBUFFERED=x \
--env CUDA_CACHE_PATH=/host/tmp/cuda-cache \
<image> \
<command>
若觉得命令行太长,可以定义一个别名(alias):
alias dockrun="sudo nvidia-docker run --rm --volume /:/host --workdir /host\$PWD --env PYTHONUNBUFFERED=x --env CUDA_CACHE_PATH=/host/tmp/cuda-cache"
利用别名 dockrun,运行 pix2pix-tensorflow:
git clone https://github.com/affinelayer/pix2pix-tensorflow.git
cd pix2pix-tensorflow
python tools/download-dataset.py facades
dockrun affinelayer/pix2pix-tensorflow python pix2pix.py \
--mode train \
--output_dir facades_train \
--max_epochs 200 \
--input_dir facades/train \
--which_direction BtoA
pix2pix-tensorflow 项目除了需要Tensorflow 0.12.1外,没有其它依赖项. 即便这样,在Github issue中还是有人遇到Tensorflow版本不对的问题.
6. Docker项目设置
使用Docker镜像来进行项目开发是很简单的.
只需在一个空路径中建立一个文件,命名为Dockerfile,内容类似于下面的形式:
FROM nvidia/cuda:8.0-cudnn5-devel # 父镜像
WORKDIR /root # 类似于 cd /root,进入到/root路径
# 在shell终端运行命令,以安装training dependencies
RUN apt-get update
RUN apt-get install -y --no-install-recommends git ca-certificates sudo
# torch 深度学习框架
RUN git clone https://github.com/torch/distro.git torch --recursive && \
cd torch && \
git checkout 49c5b4fd478cb2e7f87ba5853510d26bf28a3d83 && \
bash install-deps && \
bash install.sh -b
ENV PATH="/root/torch/install/bin/:${PATH}" # 指定torch环境变量
# 安装tool dependencies
# datasets/download_dataset.sh and models/download_model.sh
# wget
RUN apt-get install -y --no-install-recommends wget
# scripts/combine_A_and_B.py and scripts/edges/batch_hed.py
# opencv
RUN apt-get install -y --no-install-recommends python python-dev
RUN curl -O https://bootstrap.pypa.io/get-pip.py && python get-pip.py
RUN pip install numpy scipy
RUN curl -OL https://github.com/Itseez/opencv/archive/2.4.13.zip && \
unzip 2.4.13.zip && \
cd opencv-2.4.13 && \
mkdir release && \
cd release && \
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. && \
make && \
make install
# scripts/edges/batch_hed.py
# Caffe 深度学习框架
# based on https://github.com/BVLC/caffe/blob/master/docker/gpu/Dockerfile
# Caffe 官方镜像源
# 安装Caffe依赖项
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
cmake \
git \
wget \
libatlas-base-dev \
libboost-all-dev \
libgflags-dev \
libgoogle-glog-dev \
libhdf5-serial-dev \
libleveldb-dev \
liblmdb-dev \
libopencv-dev \
libprotobuf-dev \
libsnappy-dev \
protobuf-compiler \
python-dev \
python-numpy \
python-pip \
python-setuptools \
python-scipy && \
rm -rf /var/lib/apt/lists/*
ENV CAFFE_ROOT=/opt/caffe # 指定Caffe环境变量
RUN mkdir -p $CAFFE_ROOT && \
cd $CAFFE_ROOT && \
git clone --depth 1 https://github.com/s9xie/hed . && \
git checkout 9e74dd710773d8d8a469ad905c76f4a7fa08f945 && \
pip install --upgrade pip && \
cd python && for req in $(cat requirements.txt) pydot; do pip install $req; done && cd .. && \像不一致. 另外,如果 docker build指定了CPU,也会导致在另一台机器上不能运行.
# https://github.com/s9xie/hed/pull/23
sed -i "s|add_subdirectory(examples)||g" CMakeLists.txt && \
mkdir build && cd build && \
# /opt/caffe/include/caffe/util/cudnn.hpp(123):
# error: argument of type "int" is incompatible with parameter of
# type "cudnnNanPropagation_t" => -DUSE_CUDNN=OFF
# /usr/bin/ld: cannot find -lopencv_dep_cudart => -DCUDA_USE_STATIC_CUDA_RUNTIME=OFF
cmake -DUSE_CUDNN=OFF -DCUDA_USE_STATIC_CUDA_RUNTIME=OFF .. && \
make -j"$(nproc)"
ENV PYCAFFE_ROOT $CAFFE_ROOT/python # 指定Caffe的python API
ENV PYTHONPATH $PYCAFFE_ROOT:$PYTHONPATH
ENV PATH $CAFFE_ROOT/build/tools:$PYCAFFE_ROOT:$PATH # 指定Caffe的C++ API
RUN echo "$CAFFE_ROOT/build/lib" >> /etc/ld.so.conf.d/caffe.conf && ldconfig
RUN cd $CAFFE_ROOT && curl -O http://vcl.ucsd.edu/hed/hed_pretrained_bsds.caffemodel
# scripts/edges/PostprocessHED.m
RUN apt-get update && \
apt-get install -y --no-install-recommends octave liboctave-dev && \
octave --eval "pkg install -forge image" && \
echo "pkg load image;" >> /root/.octaverc
RUN curl -O https://pdollar.github.io/toolbox/archive/piotr_toolbox.zip && \
unzip piotr_toolbox.zip && \
octave --eval "addpath(genpath('/root/toolbox')); savepath;" && \
echo "#include <stdlib.h>" > wrappers.hpp && \
cat /root/toolbox/channels/private/wrappers.hpp >> wrappers.hpp && \
mv wrappers.hpp /root/toolbox/channels/private/wrappers.hpp && \
mkdir /root/mex && \
cd /root/toolbox/channels/private && \
mkoctfile --mex -DMATLAB_MEX_FILE -o /root/mex/convConst.mex convConst.cpp && \
mkoctfile --mex -DMATLAB_MEX_FILE -o /root/mex/gradientMex.mex gradientMex.cpp && \
mkoctfile --mex -DMATLAB_MEX_FILE -o /root/mex/imPadMex.mex imPadMex.cpp && \
mkoctfile --mex -DMATLAB_MEX_FILE -o /root/mex/imResampleMex.mex imResampleMex.cpp && \
mkoctfile --mex -DMATLAB_MEX_FILE -o /root/mex/rgbConvertMex.mex rgbConvertMex.cpp && \
octave --eval "addpath('/root/mex'); savepath;" && \
# gradient2 causes a segfault, use builtin gradient instead
echo "function [a, b] = gradient2(x)\n[a, b] = gradient(x, 1);\nend" > /root/mex/gradient2.m
RUN curl -O https://raw.githubusercontent.com/pdollar/edges/master/private/edgesNmsMex.cpp && \
octave --eval "mex edgesNmsMex.cpp" && \
mv edgesNmsMex.mex /root/mex/
RUN apt-get install -y --no-install-recommends python-imaging
基于该Dockerfile,编译:
mkdir docker-build
cd docker-build
curl -O https://affinelayer.com/docker/Dockerfile
sudo docker build --rm --no-cache --tag pix2pix .
花费一段时间完成后,即可新建Docker镜像,查看Docker镜像:
sudo docker images pix2pix
output:
REPOSITORY TAG IMAGE ID CREATED SIZE
pix2pix latest bf5bd6bb35f8 3 seconds ago 11.38 GB
设置Docker Hub账户,docker login登录,即可推送镜像:
sudo docker tag pix2pix <accountname>/pix2pix
sudo docker push <accountname>/pix2pix
这样即可使用该镜像来运行分享的软件和项目,很方便快捷.
也可以不用把Docker镜像推送到Docker Hub中,直接保存到本地:
# save image to disk, this took about 18 minutes 保存到磁盘
sudo docker save pix2pix | gzip > pix2pix.image.gz
# load image from disk, this took about 4 minutes 从磁盘加载
gunzip --stdout pix2pix.image.gz | sudo docker load
7. 可重现性 Reproducibility
Docker镜像便于复制,不必每次都利用Dockerfile重复创建镜像. 查看创建镜像历史:
sudo docker history --no-trunc pix2pix
采用Dockerfile重复创建镜像可能导致版本不一样. 例如,如果Dockerfile中有 git clone 或者 apt-get update 命令,在不同的时间编译相同的Dockerfile,就可能导致创建的镜像不一致. 另外,如果 docker build指定了CPU,也会导致在另一台机器上不能运行.
因此,Docker镜像的目的是为了便于复用和部署,如果需要直接从Dockerfile复用镜像,则需要仔细对待Dockerfile文件内容,否则可能出现问题.
如果从零开始编译Dockerfile,并添加 --network none,断开网络链接,则可重现性更好,但推荐直接从镜像进行复用.