原文: Deep Learning with Docker

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 AzureAWS 测试过.

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,断开网络链接,则可重现性更好,但推荐直接从镜像进行复用.

Last modification:December 14th, 2018 at 05:23 pm