如何用Docker成為更高效的數據科學家?


使用 Docker 容器來開發機器學習模型的好處有很多。近日,GitHub 的資深機器學習科學家 Hamel Husain 在 Towards Data Science 上發表了一篇入門級的 Docker 容器教程,文章從基本的概念談起,清楚明白地介紹了 Docker 容器的一些基本的操作方式和注意事項。機器之心對本文進行了編譯介紹。本文所涉及的所有相關代碼請訪問:https://github.com/hamelsmu/Docker_Tutorial

過去五年來,Docker 容器已然成了一個熱門辭彙,似乎我的所有軟體工程師朋友都在使用它們來開發應用。我想搞清楚這種技術可以如何讓我更有效率,但我發現我在網上找到的教程要麼過於注重細節(解釋了一些我作為數據科學家絕不會使用的功能),要麼就過於淺顯(沒有足夠的信息幫助我理解如何快速有效地使用 Docker)。

所以我寫了這篇快速入門,這樣你不必自己去網上篩選信息就能學習到快速上手 Docker 所需要的一切。

Docker 是什麼?

你可以把 Docker 看作是輕量級的虛擬機——包含你運行應用所需要的一切。Docker 容器可以獲取你的系統的狀態的快照,這樣其他人就可以使用這個快照快速重建你的計算環境。對於本教程而言,這就是你需要了解的一切。更多詳細介紹可參閱:https://goo.gl/YzUwbc

為什麼要使用 Docker?

1.重現性:作為專業的數據科學家,讓你的結果能夠重現是非常重要的。重現性不僅有助於同行評議,而且可以確保你創建的模型、應用或分析可以無障礙地運行,這能讓你交付的成果更穩健,更能經受時間的考驗。舉個例子,假如你用 Python 創建了一個模型,只是運行 pip freeze 並將結果得到的 requirements.txt 文件發送給你的同事是不夠的,因為其中只包含特定於 Python 的依賴條件——而實際上的依賴條件不只有 Python,還有操作系統、編譯器、驅動程序、配置文件以及你的代碼成功運行所需的其它數據。就算你只分享 Python 依賴條件也能成功,將所有東西都封裝到一個 Docker 容器中還是能減輕其他人重建你的環境的負擔,並讓他們能更輕鬆地訪問你的成果。

2.計算環境的可移植性:作為一位數據科學家,尤其是機器學習領域內的數據科學家,快速改變你的計算環境的能力能夠極大地影響你的生產力。數據科學的開始工作常常是原型設計、探索和研究——這些工作並不一定立即就需要特定的計算資源。這個工作往往是在筆記本電腦或個人計算機上完成的。但是在後面某個時候,你往往會需要不同的計算資源來顯著加速你的工作流程——比如使用更多 CPU 或強大的 GPU 來執行深度學習等任務。我看到很多數據科學家由於感受到了在遠程機器上重建他們的本地環境的困難,就將自己局限在了本地計算環境內。而 Docker 能讓你的環境(你的所有庫和文件等等)的移植非常簡單。在 Kaggle 競賽中,快速移植計算環境也是一個巨大的競爭優勢,因為你可以成本高效地利用 AWS 的寶貴計算資源。最後,創建 Docker 文件讓你能移植很多你喜歡的本地環境配置——比如 bash 別名或 vim 插件。

3.強化你的工程能力:熟練使用 Docker 讓你能將模型或分析部署成應用(比如用作提供預測的 REST API),從而讓其他人也能使用你的成果。此外,你在數據科學工作流程中可能需要與存在於 Docker 容器中的其它應用進行交互,比如資料庫。

Docker 術語

在我們繼續深入之前,熟悉一下 Docker 的術語會很有幫助:

·鏡像(image):是你想要創建的東西的藍圖。比如:Ubuntu+TensorFlow,帶有英偉達驅動程序和一個運行的 Jupyter 伺服器。

·容器(container):是你實現的運行的鏡像的實例化。你可以運行同一個鏡像的多個副本。分清鏡像和容器之間的差異非常重要,因為這是新入門者常常混淆的兩個概念。如果你不清楚鏡像和容器的差別,停下來再讀一次。

·Dockerfile:用於創建鏡像的配方。Dockerfile 包含特殊的 Docker 語法。官方文檔說:Dockerfile 是一個文本文檔,其中包含了用戶可以在命令行調用的用來組裝成鏡像的所有命令。

·commit:和 git 類似,Docker 容器提供了版本控制。通過 commit 發生的改變,你在任何時間都可以將你的 Docker 容器的狀態保存為一個新鏡像。

·DockerHub/Image Registry:人們可以發布公開(或私人)Docker 鏡像的地方,用於促進合作與共享。

·層(layer):對已有鏡像的修改,由 Dockerfile 中的一個指令表示。層按次序應用到基礎鏡像上,以創建出最終的鏡像。

本文將使用這些術語,如果你在閱讀時忘記了,一定要回來查看!這些術語很容易混淆,尤其是在鏡像和容器之間——所以你在閱讀時要保持警惕!

安裝 Docker

你可以免費下載安裝 Docker 社區版(Docker Community Edition),地址:https://www.docker.com/community-edition

創建你的第一個 Docker 鏡像

在創建 Docker 容器之前,創建一個將用於定義鏡像的 Dockerfile 會很有用。我們先慢慢解讀一下下面的 Dockerfile。你也可以在與本教程關聯的 GitHub 庫中找到這個文件:https://goo.gl/iE4Bdr

# reference: https://hub.docker.com/_/ubuntu/

FROM ubuntu:16.04

# Adds metadata to the image as a key value pair example LABEL version="1.0"

LABEL maintainer="Hamel Husain <www.github.com/hamelsmu>"

##Set environment variables

ENV.UTF-8 LC_ALL=C.UTF-8

RUN apt-get update --fix-missing && apt-get install -y wget bzip2 ca-certificates

build-essential

byobu

curl

git-core

htop

pkg-config

python3-dev

python-pip

python-setuptools

python-virtualenv

unzip

&&

apt-get clean &&

rm -rf /var/lib/apt/lists/*

RUN echo "export PATH=/opt/conda/bin:$PATH" > /etc/profile.d/conda.sh &&

wget --quiet https://repo.continuum.io/archive/Anaconda3-5.0.0.1-Linux-x86_64.sh -O ~/anaconda.sh &&

/bin/bash ~/anaconda.sh -b -p /opt/conda &&

rm ~/anaconda.sh

ENV PATH /opt/conda/bin:$PATH

RUN pip --no-cache-dir install --upgrade

multiprocessing

sklearn-pandas

# Open Ports for Jupyter

EXPOSE 7745

#Setup File System

RUN mkdir ds

ENV HOME=/ds

ENV SHELL=/bin/bash

VOLUME /ds

WORKDIR /ds

ADD run_jupyter.sh /ds/run_jupyter.sh

RUN chmod +x /ds/run_jupyter.sh

# Run the shell

CMD ["./run_jupyter.sh"]

FROM 語句

FROM ubuntu:16.04

FROM 語句包含了 Docker 最神奇的部分。這個語句指定了你想在上面進行創建的基礎鏡像。通過使用 FROM 指定一個基礎鏡像,Docker 將會在你的本地環境中尋找名為 ubuntu:16.04 的鏡像——如果它沒有找到,它就會搜索你指定的 Docker Registry,默認是 DockerHub:https://hub.docker.com/explore/。如果你需要經常在你的 Ubuntu 等操作系統上安裝程序,那麼這種分層機制就非常方便。你不必費心從頭開始安裝 Ubuntu,而是可以直接在官方的 Ubuntu 鏡像上開發!DockerHub 上託管著種類繁多的鏡像,包括那些不只是提供了一個操作系統的鏡像,比如如果你想要一個已經安裝了 Anaconda 的容器,你可以選擇在官方的 Anaconda Docker 鏡像上開發,地址:https://hub.docker.com/r/continuumio/anaconda3/。最重要的是,你也可以隨時發布你構建的鏡像,即使該鏡像是通過在其它鏡像上加層得到的!這有無盡的可能性。

在這個案例中,我們指定基礎鏡像為 ubuntu:16.04,它會搜索名叫 ubuntu 的 DockerHub 庫(https://hub.docker.com/_/ubuntu/)。鏡像名之後的部分 16.04 是指定了你想要安裝的基礎鏡像的版本的標籤(tag)。如果你檢索一下 Ubuntu DockerHub 庫,你會注意到不同版本的 Ubuntu 對應於不同的 tag:

2017 年 12 月的官方 Ubuntu DockerHub 庫截屏

比如,ubuntu:16.04、ubuntu:xenial-20171201、ubuntu:xenial 和 ubuntu:latest 全都是指 16.04 版的 Ubuntu,它們全都是同一個鏡像的別名。此外,這裡提供的鏈接指向了對應的 Dockerfile,可用於構建每個版本的鏡像。有時候你無法在 DockerHub 中找到 Dockerfile,因為維護者可以自己選擇是否將關於這些鏡像的創建方式的 Dockerfile 包含進來。我個人覺得閱讀一些 Dockerfile 有助於更好地理解 Dockerfile。(但不要急,讀完這篇教程再說!)

你需要特別注意一個標籤,即 :latest 標籤。這也是你在不為 FROM 語句指定標籤時默認 pull 的鏡像。比如說如果你的 FROM 語句是這樣:

FROM ubuntu

然後你就將 pull ubuntu:16.04 鏡像。為什麼?——仔細看上面,你可以看到 :latest 關聯的是 16.04.

關於 Docker 鏡像最後需要注意的一點:在從 DockerHub pull 隨機的 Docker 鏡像時要做出明智的判斷。有惡意的人創建的鏡像有可能會包含惡意軟體。

LABEL 語句

這個語句會為你的鏡像添加元數據,而且是完全可選的。我增加這個語句的目的是為了讓別人知道可以聯繫誰,同時也方便我搜索我的 Docker 容器,尤其是在一個伺服器上同時運行著很多容器時。

LABEL maintainer="Hamel Husain <youremail>"

ENV 語句

ENV.UTF-8 LC_ALL=C.UTF-8

這讓你可以修改環境變數,而且相當直接,相關情況請參閱:https://docs.docker.com/engine/reference/builder/

RUN 語句

這通常是最需要花功夫的地方,給出了你構建該 Docker 鏡像所想要完成的任務。你可以運行 apt-get 和 pip install 等任意的 shell 命令來安裝你需要的軟體包和依賴包。

RUN apt-get update --fix-missing && apt-get install -y wget bzip2

build-essential

ca-certificates

git-core

...

在這裡我安裝了一些我喜歡的實用工具,比如 curl、htop、byobu,然後安裝了 Anaconda,之後還安裝了一些基礎 Anaconda 中沒有的其它庫(你可以在完整的 Dockerfile 中查看其它 RUN 語句)。

RUN 語句後的命令與 Docker 沒什麼關係,只是一些你在安裝這些軟體包時需要運行的正常 Linux 命令,所以就算你不熟悉這些軟體包或 Linux 命令也不要擔心。另外,再給一個建議:當我最早開始學習 Docker 時,我查看了 GitHub 或 DockerHub 上的其它 Dockerfile,然後將我需要的部分複製粘貼到了我的 Dockerfile。

你可能注意到了 RUN 語句的格式。每個庫或軟體包都整齊地進行了縮進,而且為了可讀性還按字母進行了排序。這是 Dockerfile 的普遍慣例,所以我建議你也這樣做以便合作。

EXPOSE 語句

如果你想公開一個埠,這個語句會很有用——比如,如果你從該容器或某個網路服務內實施一個 Jupyter Notebook。Docker 的文檔相當好地解釋了 EXPOSE 語句:

EXPOSE 指令實際上並沒有發布該埠。它的功能是作為創建該鏡像的人和運行該容器的人之間的一類文檔,內容是關於打算髮布的埠。要實際發布該埠,就要在運行該容器時在 docker run 上使用 -p 標誌並且映射一個或多個埠,或者也可以使用 -P 標誌發布所有埠並將它們映射到高階埠。

VOLUME 語句

VOLUME /ds

這個語句讓你可以在 Docker 容器和主機計算機之間共享數據。VOLUME 語句讓你可以安裝外部安裝的卷。主機目錄只有在容器運行時才聲明(因為你可能在不同的計算機上運行該容器),而不會在定義鏡像時聲明*。目前你只指定了 Docker 容器內你想與主機容器共享的文件夾的名稱。

Docker 用戶指南解釋說:

主機目錄是在容器運行時聲明的:主機目錄(掛載點)本質上取決於主機。這是為了保證鏡像的可移植性,因為一個給定的主機目錄無法保證在所有主機上都可用。由於這個原因,你不能在 Dockerfile 中掛載主機目錄。VOLUME 指令不支持指定 host-dir 參數。你必須在創建或運行容器時指定掛載點。

此外,這些卷的目的是將數據保存到容器的文件系統之外,當你要操作大量數據而且不希望你的鏡像膨脹得很大時,這會很有用。當你保存一個 Docker 鏡像時,在這個 VOLUME 目錄中的任何數據都不會被保存為該鏡像的一部分,但是在這個容器目錄之外的數據會被保存。

WORKDIR 語句

WORKDIR /ds

這個語句設置了工作目錄,以便你在另一條命令中可以無需使用絕對路徑就能索引特定的文件。例如這個 Dockerfile 中的最後一條語句是:

CMD [「./run_jupyter.sh」]

該語句就默認假設工作目錄是 /ds

ADD 語句

ADD run_jupyter.sh /ds/run_jupyter.sh

這條命令讓你可以在 Docker 容器運行時將文件從主機計算機複製到該 Docker 容器。我使用這個命令來執行 bash 腳本以及將 .bachrc 文件等有用東西導入到容器中。

注意這裡的主機容器的路徑並沒有完全指定,因為其主機路徑是你在該容器運行時指定的背景路徑(context directory)的相對路徑(後面會討論)。

在我運行這個容器時,run_jupyter.sh 正好在背景路徑的根目錄內,所以在該源文件之前沒有路徑。

用戶指南中介紹說:

ADD <src>... <dest>

ADD 指令從 <src> 複製新文件、目錄或遠程文件 URL 並將它們添加到路徑 <dest> 的鏡像的文件系統中。

CMD 語句

Docker 容器的設計思想是這些容器是短暫的,能保證運行完你想運行的應用就行了。但在數據科學方面,我們往往希望保持這些容器一直運行,即使它們之中並沒有主動地運行著什麼。很多人都通過運行 bash shell 來實現這一點(除非你終止它,否則它就不會停止)。

CMD [「./run_jupyter.sh」]

在上面的命令中,我運行了一個實例化一個 Jupyter Notebook 伺服器的 shell 腳本。但是,如果你沒有什麼要運行的特定應用而只是想保持你的容器運行(而不退出),你可以直接運行 bash shell,只不過使用以下命令:

CMD ["/bin/bash"]

這種方法是有效的,因為除非你退出,否則 bash shell 就不會終止;因此該容器會一直保持運行。

用戶指南中介紹說:

在一個 Dockerfile 中只能有一個 CMD 指令。如果你列出了不止一個 CMD,那麼只有最後一個才有效。

CMD 的主要目的是為正在執行的容器提供默認配置。這些默認配置可能包含一個可執行文件,或者也可以省略可執行文件,在這種情況下你還必須指定一個 ENTRYPOINT 指令。

創建你的 Docker 鏡像

Dockerfile 中的信息可真夠多的。不要擔心,後面的內容就相對很簡單了。現在我們已經在 Dockerfile 中創建了我們的配方,是時候創造鏡像了。你可以通過以下命令完成:

GitHub 上也有:https://github.com/hamelsmu/Docker_Tutorial/blob/master/basic_tutorial/build_image.sh

這會創建一個 Docker 鏡像(而不是容器;如果你不記得這兩者之間的差異,請查閱文章前面的術語介紹),你可以在後面運行這個鏡像。

從你的 Docker 鏡像創建和運行容器

現在你已經準備好讓這一切工作起來了!我們可以通過執行以下命令來調出環境:

同樣 GitHub 也有:https://github.com/hamelsmu/Docker_Tutorial/blob/master/basic_tutorial/run_container.sh

運行完這個命令之後,你的容器就運行起來了!Jupyter 伺服器也運行起來了,因為在該 Dockerfile 最後有這個命令:

CMD [「./run_jupyter.sh」]

現在你應該可以通過其使用的埠訪問你的 Jupyter Notebook 了——在這個案例中可通過 http://localhost:7745/ 訪問,密碼是 tutorial。如果你是通過遠程的方式運行這個 Docker 容器,你還必須設置本地埠轉發,這樣你才能通過你的瀏覽器訪問你的 Jupyter 伺服器。埠轉發介紹:https://help.ubuntu.com/community/SSH/OpenSSH/PortForwarding

與你的容器交互

一旦容器設置完成並運行起來,下面這些命令就有用了:

·為容器附加一個新的終端會話。如果你需要安裝一些新軟體或使用 shell,這會很有用。

·將你的容器的狀態保存為新鏡像。即使你一開始就在 Dockerfile 中配置了你想安裝的所有庫,隨著時間的推移,你也可能還是需要對容器的狀態進行很大的調整——通過交互來增加更多庫和軟體包。將你的容器的狀態保存為鏡像是很有用的,你後面可以將其分享出去或在上面加層。你可以使用 docker commit CLI 命令將容器狀態保存為新鏡像:

docker commit <container_name> new_image_name:tag_name(可選的)

比如說,如果我想將名為 container1 的容器的狀態保存為名為 hamelsmu/tutorial:v2 的鏡像,我可以直接運行這個命令:

docker commit container_1 hamelsmu/tutorial:v2

你可能會疑惑鏡像名之前的 hamelsmu/ 是什麼——這只是為了讓之後將該容器推送到 DockerHub 的工作更輕鬆,因為 hamelsmu 是我的 DockerHub 用戶名(後面會再談這個問題)。如果你的工作要使用 Docker,那麼你的公司很可能有一個內部私有的 Docker 庫,你也可以將你的 Docker 推送到那裡。

·列出運行中的容器。當我忘記現在正在運行的容器的名稱時,我就常常使用這個命令:

docker ps -a -f status=running

如果你在使用該命令時沒有加上 status=running,那麼你就會看到你系統上的所有容器的列表(即使已經不再運行的容器也在)。這對查找舊容器而言很有用。

·列出你在本地保存的所有鏡像。

docker images

·將你的鏡像推送到 DockerHub(或其它地方)。如果你想與其他人分享你的工作或將鏡像保存到雲上,這個命令就會很有用。注意你在做這件事時可不要分享任何私人信息(DockerHub 上也有私有庫)。

首先創建一個 DockerHub 庫並給你的庫起一個適當的名稱,參考這裡:https://docs.docker.com/docker-hub/repos/。然後要運行 docker login 命令來連接到你在 DockerHub 或其它註冊位置的賬戶。比如,要推送一個鏡像到這個容器(https://hub.docker.com/r/hamelsmu/tutorial/),我首先必須將我的本地鏡像命令為 hamelsmu/tutorial(我可以選擇任意標籤名)。比如說,這個 CLI 命令就為:

docker push hamelsmu/tutorial:v2

將之前提到的 Docker 鏡像推送到這個庫,其標籤為 v2,參考:https://hub.docker.com/r/hamelsmu/tutorial/tags/。需要指出:如果你公開了你的鏡像,那麼其他人就可以直接在你的鏡像上加層,就像本教程中我們在 ubuntu 鏡像上加層一樣。對於想要重現或延展你的研究的其他人來說,這非常有用。

你已經掌握了

現在你知道如何操作 Docker 了,你可以執行以下任務:

·與同事和朋友共享可重現的研究。

·通過將你的代碼暫時遷移到所需的更大的計算環境中,無中斷地贏得 Kaggle 競賽。

·在你的筆記本電腦上的 Docker 容器內進行本地的原型開發,然後毫不費力地將同樣的計算過程無縫遷移到伺服器上,同時還能保留你喜歡的本地環境配置(你的別名、vim 插件、bash 腳本、自定義提示等)。

·使用 Nvidia-Docker 在 GPU 計算機上快速實例化運行 TensorFlow、PyTorch 或其它深度學習庫所需的所有依賴包。(如果你從頭開始做,這個過程將非常艱辛。)參閱後面的彩蛋。

·將你的模型作為應用發布,比如用作從 Docker 容器提供預測的 REST API。當你的應用 Docker 化了以後,就可以按照需要輕鬆地隨意複製。

進階閱讀

到這裡我們也只學到了 Docker 的一點皮毛,前面還有很多東西值得掌握。我很關注 Docker 領域,我認為數據科學家會常常遇到它,希望這篇文章能讓你有足夠的信心開始使用它。下面這些資源曾在我的 Docker 之旅中為我提供過幫助:

·有用的 Docker 命令:https://zaiste.net/posts/removing_docker_containers/

·更有用的 Docker 命令:https://www.digitalocean.com/community/tutorials/how-to-remove-docker-images-containers-and-volumes

·Dockerfile 參考:https://docs.docker.com/engine/reference/builder/

·如何創建和推送到 DockerHub 上的庫:https://docs.docker.com/docker-hub/repos/

彩蛋:Nvidia-Docker

我學習 Docker 最早的原因是要在單個 GPU 上做深度學習模型的原型開發,然後在我需要更多計算資源時再遷移到 AWS 上。我當時也在學習 Jeremy Howard 的出色的 Fast.AI 課程(http://www.fast.ai/),並且希望與其他人分享我的原型設計。

但是,要將英偉達 GPU 的驅動程序等所有依賴包都包含以來,你不能使用 Docker,而是要用 Nvidia-Docker(https://github.com/NVIDIA/nvidia-docker)。這比使用 vanilla Docker 要多花一些功夫,但只要你理解了 Docker,做起來就很簡單。

我將我的 Nvidia-Docker 設置放在這裡:https://github.com/hamelsmu/Docker_Tutorial/tree/master/gpu_tutorial,你可以用這個來進行練習。


謝謝回答這麼專業的問題。docker自2013年來一直火熱。無論是從 github 上的代碼活躍度,還是

Redhat

在RHEL6.5中集成對Docker的支持, 就連 Google 的 Compute Engine 也支持 docker 在其之上運行。這只是一款提供開源的應用容器引擎的軟體。如果想熟練運用。需要專業學習。但想成為科學家沒有捷徑。只有更努力學習。一分天才需九分努力。


推薦閱讀:

全球 TOP 互聯網公司及學術界人工智慧方向薪資、高薪的攬才計劃有哪些?
央行某副行長說移動支付太集中,有風險,要引進外資,你怎麼看?
物聯網、雲計算、大數據、人工智慧怎麼區分,又有何關係?
大數據逃離北上廣,大家怎麼看?
2017上半年離婚大數據出來了,你最想知道的內容有哪些?

TAG:大數據 | DevOps | Docker | 科學 |