版權聲明:本文為博主原創文章,轉載注明出處即可。 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自行搜索。實驗室電腦有時候還是很奇葩的,需要耐心解決,積極的去利用一些新的技術解決難題應該是更應該考慮的事情。
