docker配置深度學習環境
版權宣告:本文為博主原創文章,轉載註明出處即可。 https://blog.csdn.net/bskfnvjtlyzmv867/article/details/81017226
序
閱讀本篇文章可以幫你解決的問題是:提供一套解決方案,能夠在支援Docker的任何版本Ubuntu系統下,搭建出完美執行各種深度學習框架、各種版本、各種環境依賴(NAIVID顯示卡)深度學習工程的開發環境。不僅如此,還要像在本機一樣方便的修改程式碼執行計算。
搭建深度學習計算平臺,一般需要我們在本機上安裝一些必要的環境,安裝系統、顯示卡驅動、cuda、cudnn等。而隨著Docker的流行,往往能夠幫我們輕鬆的進行環境搭建、複製與隔離,所以官方也利用容器技術與深度學習相結合,因此也出現了以下方案。
容器方案比傳統方案帶來更多的隨意性,裝系統前不需要考慮Ubuntu哪一個版本符合不符合我們的程式碼執行要求,我們只需要安裝一個自己喜歡的(18.04完全可以),再在官網下載一下顯示卡驅動,或者軟體源附加驅動更新一下就行了,剩下都不需要我們繼續考慮。這些也非常的輕鬆,因為Nvidia對Ubuntu的支援越來越友好,我們只需要下載deb包,一行命令即可安裝成功。
系統 | 顯示卡驅動 | Cuda | Cudnn | |
---|---|---|---|---|
傳統方案 | 一種版本 | 必需 | 一種版本 | 必需 |
容器方案 |
各種版本 | 必需 | 非必需 | 非必需 |
安裝顯示卡驅動可以參照:https://blog.csdn.net/bskfnvjtlyzmv867/article/details/80102000
正式進入正文之前,確保你已經安裝好趁手的系統和顯示卡驅動。
I. 安裝Docker
關於Docker教程,詳見:Docker——入門實戰
安裝指定版本Docker CE
這裡的版本由第二部分的Nvidia Docker依賴決定,筆者在寫此文時需要的版本是18.03.1,如果在安裝Nvidia Docker時依賴的Docker CE版本已經變更,可以解除安裝重新安裝需要的版本。
sudo apt install curl
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
echo "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial edge" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update && sudo apt-get install -y docker-ce=18.03.1~ce-0~ubuntu1234
執行這個命令後,指令碼就會自動的將一切準備工作做好,並且把Docker CE 的Edge版本安裝在系統中。
啟動Docker CE
sudo systemctl enable docker
sudo systemctl start docker12
建立docker 使用者組
預設情況下,docker 命令會使用Unix socket 與Docker 引擎通訊。而只有root 使用者和docker 組的使用者才可以訪問Docker 引擎的Unix socket。出於安全考慮,一般Ubuntu系統上不會直接使用root 使用者。因此,更好地做法是將需要使用docker 的使用者加入docker使用者組。
# 建立docker組
sudo groupadd docker
# 將當前使用者加入docker組
sudo usermod -aG docker $USER1234
登出當前使用者,重新登入Ubuntu,輸入docker info,此時可以直接出現資訊。
配置國內映象加速
在/etc/docker/daemon.json 中寫入如下內容(如果檔案不存在請新建該檔案)
{
"registry-mirrors": [
"https://registry.docker-cn.com"
]
}12345
重新啟動服務
sudo systemctl daemon-reload
sudo systemctl restart docker12
II. 安裝Nvidia Docker2
Nvidia Docker2專案的主頁:https://github.com/NVIDIA/nvidia-docker
# If you have nvidia-docker 1.0 installed: we need to remove it and all existing GPU containers
ocker volume ls -q -f driver=nvidia-docker | xargs -r -I{} -n1 docker ps -q -a -f volume={} | xargs -r docker rm -f
sudo apt-get purge -y nvidia-docker
# Add the package repositories
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key add -
curl -s -L https://nvidia.github.io/nvidia-docker/ubuntu16.04/amd64/nvidia-docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
# Install nvidia-docker2 and reload the Docker daemon configuration
sudo apt-get update && sudo apt-get install -y nvidia-docker2
sudo pkill -SIGHUP dockerd
# Test nvidia-smi with the latest official CUDA image
docker run --runtime=nvidia --rm nvidia/cuda nvidia-smi1234567891011121314
III. 搭建環境
拉取映象
Nvidia官網在DockerHub中提供了關於深度學習的各個版本環境,點我…有Ubuntu14.04-18.04,Cuda6.5-9.2,Cudnn4-7,基本含蓋了我們所需要的各種版本的深度學習環境,我們直接拉取映象,在已有的映象基礎上配置我們的深度學習環境。
下載映象,這裡以ubuntu16.04、cuda8.0、cudnn5.1的版本為例,我們找到滿足版本要求的TAG為8.0-cudnn5-devel-ubuntu16.04。
# 拉取映象
docker pull nvidia/cuda:8.0-cudnn5-devel-ubuntu16.04
# 檢視映象
docker images -a1234
建立啟動容器
利用下載好的映象,建立一個互動式的容器。容器需要使用nvidia顯示卡,需要設定額外的引數。
docker run -it --name 自定義容器名 -v /home/你的使用者名稱/mnist/:/home/你的使用者名稱/mnist/ --runtime=nvidia -e NVIDIA_VISIBLE_DEVICE=0,1 nvidia/cuda:8.0-cudnn5-devel-ubuntu16.041
NVIDIA_VISIBLE_DEVICE引數指定對容器分配幾塊GPU資源;-v引數用於掛載本地目錄,冒號前為宿主機目錄,冒號後為容器目錄,兩個可以設定為一樣比較方便程式碼書寫。配置目錄掛載是為了方便本文下一部分測試Mnist服務。容器啟動完畢,此時,可以像正常本機配置的深度學習環境一樣,測試各個軟體的版本。
nvidia-smi
nvcc -V
# 檢視cudnn版本
cd /usr/lib/x86_64-linux-gnu/
ll |grep cudnn12345
IV. 構建環境
安裝環境
在上一部分我們搭建了深度學習計算的必要環境,包括CUDA和CUDNN。然而大多數深度學習環境都是需要執行Python編寫的深度學習程式碼的,甚至需要一些常用的深度學習框架,如TensorFlow、PyTorch等。在上一部分我們拉取的Nvidia官方提供的映象中並沒有包含Python執行環境以及任何的深度學習框架,需要我們自己安裝。
附上安裝環境的所有命令:
apt-get update
# 安裝Python2.7環境 3.+版本自行新增
apt-get install -y --no-install-recommends build-essential curl libfreetype6-dev libpng12-dev libzmq3-dev pkg-config python python-dev python-pip python-qt4 python-tk git vim
apt-get clean
## 安裝深度學習框架 自行新增
pip --no-cache-dir install setuptools
pip --no-cache-dir install tensorflow-gpu==1.2 opencv-python Pillow scikit-image matplotlib1234567
構建映象
安裝好環境後,其實已經可以開始執行我們的深度學習程式碼了。如果你想立刻測試自己的Docker深度學習環境搭建成功與否,可以直接開始下一部分的Mnist資料集測試。
如果此時,專案組另一位小夥伴也想跑深度學習,恰好需要和你一樣的環境依賴,我們完全可以“拷貝”一份配好的環境給他,他可以直接上手去使用。Docker的方便之處也體現在這,我們可以將自己定製的容器構建成映象,可以上傳到Docker Hub給別人下載,也可以生成壓縮包拷貝給別人。
利用commit命令,生成一個名為homography1.0的新映象。
# docker commit -a "作者資訊" -m "提交資訊" 之前啟動的容器名 自定義映象名
docker commit -a "wangguoping" -m "deep homography environment" tensorflow1.2 homography1.012
至於將映象提交Hub和拷貝就不是本文重點,也就不介紹了。
另外,這裡生成映象還有一個好處,就是第六部分結合PyCharm使用。PyCharm裡面配Docker選擇的是映象(IMAGE),而不是容器(Container),它會根據我們選擇的映象自己幫我們啟動一個容器,用來執行PyCharm裡面的程式碼。我一開始沒有搞清楚這個概念,也走了不少彎路。
V. 測試Mnist
進入上一部分掛載的目錄:
cd /home/test/mnist # test是我的使用者名稱
vim mnist.py # 建立mnist的tensorflow程式碼12
程式碼內容可以參考:Tensorflow——nn、cnn、rnn玩mnist
# coding=utf-8
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True)
train_img = mnist.train.images
train_lab = mnist.train.labels
test_img = mnist.test.images
test_lab = mnist.test.labels
dim_input = 784
dim_output = 10
x_data = tf.placeholder(tf.float32, [None, dim_input])
y_real = tf.placeholder(tf.float32, [None, dim_output])
stddev = 0.1
weights = {"w_conv1": tf.Variable(tf.random_normal([3, 3, 1, 64], stddev=stddev)),
"w_conv2": tf.Variable(tf.random_normal([3, 3, 64, 128], stddev=stddev)),
"w_fc1": tf.Variable(tf.random_normal([7 * 7 * 128, 1024], stddev=stddev)),
"w_fc2": tf.Variable(tf.random_normal([1024, dim_output], stddev=stddev))}
biases = {"b_conv1": tf.Variable(tf.zeros([64])),
"b_conv2": tf.Variable(tf.zeros([128])),
"b_fc1": tf.Variable(tf.zeros([1024])),
"b_fc2": tf.Variable(tf.zeros([dim_output]))}
def forward_prop(_input, _w, _b, keep_prob):
_input_r = tf.reshape(_input, shape=[-1, 28, 28, 1])
_conv1 = tf.nn.conv2d(_input_r, _w["w_conv1"], strides=[1, 1, 1, 1], padding="SAME")
_conv1 = tf.nn.relu(tf.nn.bias_add(_conv1, _b["b_conv1"]))
_pool1 = tf.nn.max_pool(_conv1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
# dropout
_pool_dr1 = tf.nn.dropout(_pool1, keep_prob=keep_prob)
_conv2 = tf.nn.conv2d(_pool_dr1, _w["w_conv2"], strides=[1, 1, 1, 1], padding="SAME")
_conv2 = tf.nn.relu(tf.nn.bias_add(_conv2, _b["b_conv2"]))
_pool2 = tf.nn.max_pool(_conv2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")
_pool_dr2 = tf.nn.dropout(_pool2, keep_prob=keep_prob)
flatten = tf.reshape(_pool_dr2, shape=[-1, _w["w_fc1"].get_shape().as_list()[0]])
_fc1 = tf.nn.relu(tf.add(tf.matmul(flatten, _w["w_fc1"]), _b["b_fc1"]))
_fc_dr1 = tf.nn.dropout(_fc1, keep_prob=keep_prob)
_out = tf.nn.relu(tf.add(tf.matmul(_fc_dr1, _w["w_fc2"]), _b["b_fc2"]))
return {"input_r": _input_r, "conv1": _conv1, "pool1": _pool1, "pool_dr1": _pool_dr1, "conv2": _conv2,
"pool2": _pool2, "pool_dr2": _pool_dr2, "flatten": flatten, "fc1": _fc1, "fc_dr1": _fc_dr1, "out": _out}
keep_prob = tf.placeholder(tf.float32)
y_pred = forward_prop(x_data, weights, biases, keep_prob)["out"]
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=y_pred, labels=y_real))
op = tf.train.GradientDescentOptimizer(learning_rate=0.001).minimize(loss)
correct = tf.equal(tf.arg_max(y_pred, 1), tf.arg_max(y_real, 1))
accuracy = tf.reduce_mean(tf.cast(correct, tf.float32))
training_epoch = 100
batch_size = 128
display_step = 2
init = tf.global_variables_initializer()
total_batch = mnist.train.num_examples // batch_size
print("have %d batchs,each batch size is:%d" % (total_batch, batch_size))
saver = tf.train.Saver(max_to_keep=2)
is_training = True
with tf.Session() as sess:
sess.run(init)
if is_training:
for epoch in range(training_epoch):
avg_loss = 0
for i_batch in range(total_batch):
batch_xs, batch_ys = mnist.train.next_batch(batch_size)
feed_dict = {x_data: batch_xs, y_real: batch_ys, keep_prob: 0.5}
sess.run(op, feed_dict=feed_dict)
avg_loss += sess.run(loss, feed_dict=feed_dict)
avg_loss = avg_loss / total_batch
if epoch % display_step == 0:
print("Epoch:%3d/%3d, loss:%.6f" % (epoch, training_epoch, avg_loss))
feed_dict = {x_data: batch_xs, y_real: batch_ys, keep_prob: 0.5}
train_accuracy = sess.run(accuracy, feed_dict=feed_dict)
print("train accuracy:%.6f" % train_accuracy)
saver.save(sess, "MNIST_model/model.ckpt-" + str(epoch))
else:
saver.restore(sess, tf.train.latest_checkpoint(checkpoint_dir="MNIST_model/"))
feed_dict = {x_data: mnist.test.images, y_real: mnist.test.labels, keep_prob: 1.0}
test_accuracy = sess.run(accuracy, feed_dict=feed_dict)
print("test accuracy:%.6f" % test_accuracy)
print("end!")
下載Mnist資料集,儲存在/home/test/mnist/MNIST_data目錄下。這裡需要我們修改目錄許可權,Docker共享目錄預設只讀。注意,這裡切換到宿主機的終端下進行操作,可能你會問為什麼不直接容器內下載,因為以後我們要跑的資料集不可能只是mnist大小,難道你要在docker裡下載幾十個G的資料集嗎。
sudo chmod -R a+rw /home/test/mnist
mkdir -p /home/test/mnist/MNIST_data
wget http://yann.lecun.com/exdb/mnist/train-images-idx3-ubyte.gz
wget http://yann.lecun.com/exdb/mnist/train-labels-idx1-ubyte.gz
wget http://yann.lecun.com/exdb/mnist/t10k-images-idx3-ubyte.gz
wget http://yann.lecun.com/exdb/mnist/t10k-labels-idx1-ubyte.gz123456
切換至Docker的終端下,執行mnist.py指令碼檔案,即可發現已經可以跑起來了。
python mnist.py1
此時會在/home/test/mnist下產生MNSIT_model資料夾,裡面儲存著訓練生成的模型。
VI. PyCharm+Docker
Mnist已經測試成功,基本的Docker+Deep Learning方案演示已經完成。然而,我一直不喜歡命令列修改程式碼,執行指令碼,檢視結果,我十分推崇PyCharm去除錯Python。比如,當Mnist訓練完畢,我需要修改第80行的is_training = False來測試我訓練出的模型,沒有PyCharm,我需要通過vim修改mnist.py,然後再輸入python來執行。可能你會覺得也不是很麻煩,如果需要修改模型,更換網路,甚至重構程式碼呢?
所以能用IDE儘量還是讓PyCharm來開發我們程式碼,我們只需要編碼,點選執行,剩下的其他操作我都不太願意去幹,一行命令都懶得敲,畢竟懶嘛!
PyCharm在2018的Profession版本之後都是提供Docker的功能的,可以利用容器中的Python直譯器為我們的程式碼提供執行條件。利用PyCharm,你可以像在使用本機的深度學習環境一樣,無需考慮因容器帶來的過多的繁瑣操作。官方關於Docker的使用文件參見:http://www.jetbrains.com/help/pycharm/run-debug-configuration-docker.html
在Settings的Build下有一個Docker選項,右側新增,PyCharm預設會給我們設定好選擇Unix Socket的方式與Docker守護程序進行通訊。出現Connection successful即可,點選OK。
新增成功後,PyCharm下方會出現docker的視窗,可以視覺化的檢視映象與容器的相關資訊。其中的homography1.0:latsest是我們上一步構建的映象。
下面新建一個Python的直譯器,類似於本地的Python建立虛擬環境。
按照圖示新建,點選OK,即可發現匯入容器的Python直譯器已經擁有了全部的第三方Python庫以及深度學習框架。
等待PyCharm匯入容器的直譯器成功,理論上我們便可以開始點選執行按鈕跑起我們的程式碼了。但實際還有兩個問題需要解決。
首先,PyCharm會根據我們的映象來啟動一個容器執行我們的程式碼,然而PyCharm並不知道我們是要執行深度學習程式,需要利用Nvidia Docker使用GPU資源。我們之前是通過配置–runtime=nvidia引數來啟動一個可以使用GPU的容器,那我們只要指定PyCharm啟動一個容器的時候也帶上這個引數就好。
另一個問題就是,我們現在跑的程式是讀取宿主機上某個目錄下的幾十個G的資料集,好像Docker也不知道資料在哪裡,畢竟我們沒有掛載。同樣,模型它也不會幫我們儲存,一旦程式執行結束,PyCharm啟動的容器銷燬,所有的結果都沒了,程式白跑了,所以我們也要指定-v引數告訴PyCharm掛載什麼目錄。
解決這兩個問題,在PyCharm的Run/Debug Configuration中,可以配置。
點選Docker Container settings右邊的按鈕,新增上面所說的兩個引數即可。
坑的是,你發現沒法加入–runtime引數。然而,還是找到了解決方案。把default-runtime”: “nvidia”新增到/etc/docker/daemon.json檔案中。這個檔案也是我們配置國內映象加速的檔案。
{
"registry-mirrors": [
"https://registry.docker-cn.com"
],
"default-runtime": "nvidia",
"runtimes": {
"nvidia": {
"path": "/usr/bin/nvidia-container-runtime",
"runtimeArgs": []
}
}
}123456789101112
修改完畢,需要重啟docker服務。
sudo systemctl daemon-reload
sudo systemctl restart docker12
好了,大功告成,點選執行,跑起來~~
VII. 結語
Docker還是有很多技巧的,短暫幾天也只學了個皮毛,用於深度學習也十分不錯。官方也有很多構建好的深度學習環境映象,包含了主流的深度學習框架,可以再Docker Hub自行搜尋。實驗室電腦有時候還是很奇葩的,需要耐心解決,積極的去利用一些新的技術解決難題應該是更應該考慮的事情。