如何使用 Kubernetes 輕鬆部署深度學習模型
這簡直太容易了,連你的老闆都能做到!
本文展示瞭如何用 Keras 構建深度學習模型的簡單示例,將其作為一個用 Flask 實現的 REST API,並使用 Docker 和 Kubernetes 進行部署。本文給出的並不是一個魯棒性很好的能夠用於生產的示例,它只是為那些聽說過 Kubernetes 但沒有動手嘗試過的人編寫的快速上手指南。
為此,我在這個過程的每個步驟中都使用了 Google Cloud。這樣做的原因很簡單——我並不想在我的 Windows 10 家用筆記本上安裝 Docker 和 Kubernetes。而谷歌雲能很好地支援這二者的工作。此外,你可以準確地按照我接下來所使用的規範流程進行操作,這可以幫助你更容易地復現我的步驟。而且,你也不用擔心實踐這篇文章的成本。谷歌為新賬戶提供了幾百美金的免費額度,而實現本文示例所需的費用只不過是九牛一毛。
為什麼要將 Kubernetes 用於機器學習和資料科學?
Kubernetes 及其從屬的流行概念「雲原生」(cloud-native)正席捲全球。別擔心——你有所懷疑是對的。我們都見證過鋪天蓋地的「人工智慧」、「大資料」、「雲端計算」等術語的技術泡沫。Kubernetes 是否也會發生相同的情況,還有待觀察。
但是,如今許多對資料科學一知半解的人會誤導我們,所以我對轉而使用 Kubernetes 的原因並不感興趣也不理解。我的動機很簡單,我希望部署、擴充套件、管理一個能夠提供預測能力的 REST API。在下文中,你會看到 Kubernetes 會使這一切非常容易。
讓我們開始吧!
大綱
1. 使用 Google Cloud 建立你的環境。
2. 使用 Keras、Flask 和 Docker 提供深度學習模型介面。
3. 使用 Kubernetes 部署上述模型。
4. 享受你所掌握的新知識吧!
步驟 1:使用 Google Cloud 建立你的環境
我在谷歌計算引擎上使用一個小型虛擬機器來構建、部署、docker 化深度學習模型。你並不一定非要這麼做。我曾試過在我的 Windows 10 筆記本上安裝最新版本的 Docker CE(Community Edition),但是失敗了。因此我決定直接使用免費的 Google Cloud 額度,這比弄清如何安裝 Docker 能更好地利用我的時間。你可以選擇是否要這樣做。
要想啟動一臺 Google Cloud 虛擬機器,你可以開啟螢幕左側的工具欄。選擇 Compute Engine。接著,選擇「Create Instance」。如下圖所示,我已經擁有了一個正在工作的虛擬機器例項。
下一步,你需要選擇你想要使用的計算規模。預設的(最便宜的)機器設定也可以很好地工作,但是考慮到我們最多隻需要使用這個虛擬機器大約 1 小時,我選擇了記憶體為 15GB 的 4vCPU 配置。
接下來,我將選擇要使用的作業系統和磁碟空間。選擇「Boot Disk」來編輯預設值。這裡我選擇了 Centos 7 作為作業系統,並將磁碟的大小從 10GB 增加到了 100GB。實際上,並不一定要像我一樣選擇 Centos 作業系統。但是,我建議將磁碟大小增加到 10GB 以上,因為我們建立的每個 Docker 容器的大小都大約為 1GB。
建立虛擬機器的最後一步是設定防火牆允許使用 HTTP/S。誠然,我並不知道是否需要這個步驟。在部署 Kubernetes 之前,我將展示如何編輯防火牆設定以在虛擬機器上測試我們的 API。因此,僅僅檢視這些對話方塊是不夠的,我們還有更多的工作要做。以前我並沒有檢視這些對話方塊,現在我重新試試按照這個教程去做。
我並不確定是否需要此步驟。
現在單擊「Creat」按鈕。很好,困難的部分基本上已經完成了。
步驟 2:使用 Keras 構建深度學習模型
現在,讓我們使用 SSH 連線到虛擬機器,並開始構建模型。最簡單的方法是單擊下圖所示的虛擬機器旁邊的 SSH 圖示。這個操作會在你的瀏覽器中開啟一個終端。
1. 解除安裝已有的 Docker 版本
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine
請注意,如果你使用的作業系統不是 Centos 7,指令可能不同。
2. 安裝最新版 Docker
sudo yum install -y yum-utils device-mapper-persistent-data lvm2 sudo yum-config-manager — add-repo https://download.docker.com/linux/centos/docker-ce.repo sudo yum install docker-ce
3. 啟動 Docker 並執行測試指令碼
sudo systemctl start docker sudo docker run hello-world
如果你看到下圖所示的返回結果,你就完成了 Docker 的部署。
Hello from Docker! This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.
4. 建立我們的深度學習模型
我們將複製一段 Adrian Rosebrock 寫的指令碼。Adrian 寫了一篇很棒的教程,關於如何利用 Keras 構建深度學習模型並使用 Flask 部署它。教程參見:https://blog.keras.io/building-a-simple-keras-deep-learning-rest-api.html
我們需要對 Adrian 的指令碼進行兩處關鍵的修改,才能使其執行。如果你不關心 Docker 和TensorFlow 的技術細節,請跳過下面兩段。
我們要修改的第一個地方與 Docker 有關。在本地執行應用程式時,預設的 flask behavior 會在本地主機(127.0.0...)上提供應用程式服務。在 Docker 容器內執行時,這可能會產生一些問題。解決的方法很簡單。當呼叫 app.run() 時,使用 app.run(host='0.0.0.0') 將 URL 設定為 0.0.0.0。這樣,我們的應用就可以在本地主機和外部 IP 上同時使用了。
下一個問題涉及TensorFlow。當我執行 Adrian 的原始指令碼時,我無法成功呼叫模型。於是,我閱讀了下面這個 Github issue(https://github.com/tensorflow/tensorflow/issues/14356),並對程式碼進行了修改。
global graph graph = tf.get_default_graph() ... with graph.as_default(): preds = model.predict(image)
說實話,我也不知道為什麼這樣做就行得通。但它確實做到了。所以就這樣執行吧。
首先,建立一個名為 keras-app 的新資料夾,並將當前的路徑移動到該資料夾中。
mkdir keras-app cd keras-app
現在我們建立一個名為 app.py 的檔案。你可以自己選擇要使用的編輯器。我在這裡選用 vim,輸入下面的指令建立並開啟 app.py:
vim app.py
開啟檔案後,敲擊鍵盤上的「i」鍵,進入插入模式。現在你可以把下面的程式碼貼上進去:
# USAGE # Start the server: # python app.py # Submit a request via cURL: # curl -X POST -F [email protected] 'http://localhost:5000/predict' # import the necessary packages from keras.applications import ResNet50 from keras.preprocessing.image import img_to_array from keras.applications import imagenet_utils from PIL import Image import numpy as np import flask import io import tensorflow as tf # initialize our Flask application and the Keras model app = flask.Flask(__name__) model = None def load_model(): # load the pre-trained Keras model (here we are using a model # pre-trained on <mark data-type="technologies" data-id="f3400606-ef61-441c-8bc3-dd5663313fb9">ImageNet</mark> and provided by Keras, but you can # substitute in your own networks just as easily) global model model = ResNet50(weights="imagenet") global graph graph = tf.get_default_graph() def prepare_image(image, target): # if the image mode is not RGB, convert it if image.mode != "RGB": image = image.convert("RGB") # resize the input image and preprocess it image = image.resize(target) image = img_to_array(image) image = np.expand_dims(image, axis=0) image = imagenet_utils.preprocess_input(image) # return the processed image return image @app.route("/predict", methods=["POST"]) def predict(): # initialize the data dictionary that will be returned from the # view data = {"success": False} # ensure an image was properly uploaded to our endpoint if flask.request.method == "POST": if flask.request.files.get("image"): # read the image in PIL format image = flask.request.files["image"].read() image = Image.open(io.BytesIO(image)) # preprocess the image and prepare it for classification image = prepare_image(image, target=(224, 224)) # classify the input image and then initialize the list # of predictions to return to the client with graph.as_default(): preds = model.predict(image) results = imagenet_utils.decode_predictions(preds) data["predictions"] = [] # loop over the results and add them to the list of # returned predictions for (imagenetID, label, prob) in results[0]: r = {"label": label, "probability": float(prob)} data["predictions"].append(r) # indicate that the request was a success data["success"] = True # return the data dictionary as a JSON response return flask.jsonify(data) # if this is the main thread of execution first load the model and # then start the server if __name__ == "__main__": print(("* Loading Keras model and Flask starting server..." "please wait until server has fully started")) load_model() app.run(host='0.0.0.0')
當你複製以上程式碼後,敲擊「Esc」鍵退出插入模式。
然後輸入 :x,儲存並關閉檔案。
5. 建立一個 requirements.txt 檔案
現在回到正題。我們將在 Docker 容器中執行這段程式碼。為了做到這一點,我們首先要建立一個 requirements.txt 檔案。這個檔案將包含程式碼需要執行的程式包(如 keras、flask 等)。這樣一來,無論我們將 Docker 容器裝載在哪裡,底層的伺服器都能夠安裝程式碼所需的依賴。
keras tensorflow flask gevent pillow requests
6. 建立 Dockerfile
很好!現在讓我們建立 Dockerfile。這是 Docker 將要讀取的檔案,用它來構建和執行我們的專案。
FROM python:3.6 WORKDIR /app COPY requirements.txt /app RUN pip install -r ./requirements.txt COPY app.py /app CMD ["python", "app.py"]~
現在,我們正引導 Docker 下載一個 Python 3 的基礎映象。然後,要求 Docker 使用 Python 程式包管理器 pip 安裝 requirements.txt 檔案中詳細指定的包。
接著,我們讓 Docker 通過 python app.py 指令執行我們的指令碼。
7. 建立 Docker 容器
目前一切進展順利,現在讓我們構建並測試我們的應用程式。
為了構建我們的 Docker 容器,我們需要執行如下指令:
sudo docker build -t keras-app:latest .
該指令將引導 Docker 為我們當前工作空間的資料夾 keras-app 中的程式碼構建一個容器。
這個指令需要一到兩分鐘才能執行完成。在此過程中,Docker 會下載一個 python 3.6 的映象並且安裝 requirements.txt 中列出的包。
8. 執行 Docker 容器
現在,讓我們執行 Docker 容器來測試我們的應用程式。
sudo docker run -d -p 5000:5000 keras-app
注:通過上面的指令中的數字 5000:5000,我們告訴 Docker 讓埠 5000 處於外部可用狀態,並把我們的本地應用程式指向該埠(它也在埠 5000 上本地執行)。
你可以通過執行 sudo docker ps -a 檢視 Docker 容器的狀態。你應該看到如下圖所示的結果:
[gustafcavanaugh@instance-3 ~]$ sudo docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES d82f65802166 keras-app "python app.py" About an hour ago Up About an hour 0.0.0.0:5000->5000/tcp nervous_northcutt
9. 測試模型
我們的模型能夠成功執行後,是時候測試一下它的效能了。該模型將狗的圖片作為輸入,並返回狗的品種。在 Adrian 的 repo 中,他提供了一個示例圖片,我們在這裡也將使用它。
在終端中執行:
curl -X POST -F [email protected] 'http://localhost:5000/predict'
確保你當前的資料夾中有狗狗的圖片「dog.jpg」(或提供正確的檔案路徑),你會看到下面的執行結果:
{"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true}
我們可以看到,模型正確地將狗狗分類為小獵犬。太棒了!你已經成功地用 Keras 運行了一個預訓練好的深度學習模型,並且使用 Flask 部署其服務、用 Docker 將其封裝了起來。至此,我們已經完成了困難的部分。現在讓我們用 Kubernetes 部署該容器。
步驟 3:用 Kubernetes 部署我們的模型
1. 建立一個 Docker Hub 賬戶(如果你沒有的話)
我們要做的第一件事是將模型上傳到 Docker Hub 上。如果你還沒有 Docker 賬戶,請建立一個,別擔心,這是免費的。我們這樣做的原因是,我們不會將容器物理移動到 Kubernetes 叢集上,而是引導 Kubernetes 在集中託管伺服器(即 Docker Hub)上安裝我們的容器。
2. 登入 Docker Hub 賬戶
建立好 Docker Hub 賬戶後,你可以通過 sudo docker login 指令從命令列登入。你需要提供使用者名稱、密碼,就像你登入網站一樣。
如果你看到下面的資訊:
Login Succeeded
那麼你就成功登入了。現在讓我們進行下一步。
3. 對容器命名
在上傳容器之前,我們需要為容器打標籤。你可以將此步驟看做為容器命名。
首先,執行 sudo docker images,並定位 keras-app 容器的映象 id。
輸出應該如下所示:
REPOSITORY TAG IMAGE ID CREATED SIZE keras-app latest ddb507b8a017 About an hour ago 1.61GB
現在,我們可以為 keras-app 打標籤了。請務必遵循我的格式,並將映象 id 和 docker hub id 的值替換為你自己指定的值。
#Format sudo docker tag <your image id> <your docker hub id>/<app name> #My Exact Command - Make Sure To Use Your Inputs sudo docker tag ddb507b8a017 gcav66/keras-app
4. 將容器 push 到 Docker Hub 上
現在可以 push 我們的容器了。在 shell 中執行以下行:
#Format sudo docker push <your docker hub name>/<app-name> #My exact command sudo docker push gcav66/keras-app
現在,如果你返回到 Docker Hub 網站,你就可以看到你的 keras-app repo 了。很好,接下來,我們將進入最後一步。
5. 建立一個 Kubernetes 叢集
在 Google Cloud 的主頁上選擇 Kubernetes Engine:
接著建立一個新的 Kubernetes 叢集:
接下來,我們將自定義該叢集中節點的規模。我選擇了記憶體為 15GB、4vCPU 的配置。你可以在更小的叢集上進行嘗試。請記住,預設設定包含 3 個節點,所以整個叢集會擁有 3 倍於你所選擇的資源(即,在本例中為 45GB 記憶體)。我在這裡偷個懶,選擇了更大的規模,這樣我們的 Kubernetes 叢集不會執行太長時間。
接著,只需點選 Creat。等上一兩分鐘,你的叢集就能運轉了。
現在讓我們連線到叢集。點選 Run in Cloud Shell,就可以為 Kubernetes 叢集提供控制檯。請注意,這是虛擬機器中的一個單獨 shell 環境,你在這裡可以建立並測試 Docker 容器。我們可以在虛擬機器上安裝 Kubernetes,谷歌的 Kubernetes 服務會自動為我們完成這個步驟。
現在,在 Kubernetes 上執行我們的 docker 容器。請注意,映象標籤僅指向我們在 Docker Hub 上託管的 docker 映象。此外,我們通過——port 指定我們想在埠 5000 上執行應用。
kubectl run keras-app --image=gcav66/keras-app --port 5000
在 Kubernetes 中,容器都在 pod(容器集合)中執行。我們可以輸入 kubectl get pods 來驗證我們的 pod 是否正在執行。如果你看到下面的結果,你就完成了配置。
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get pods NAME READY STATUS RESTARTS AGE keras-app-79568b5f57-5qxqk 1/1 Running 0 1m
此時,我們的 pod 正在執行,我們需要將我們的 pod 暴露給 80 埠從而與外界相連。這意味著任何訪問我們部署的 IP 地址的人都可以訪問我們的 API。這也意味著我們不必在 URL 後面指定一個麻煩的埠號(與 :5000 說再見!)。
kubectl expose deployment keras-app --type=LoadBalancer --port 80 --target-port 5000
就要完成了!現在,執行 kubectl get service 來確定我們的部署(以及我們呼叫 API 所需的 URL)的狀態。同樣地,如果命令的輸出結果和下圖所示的結果類似,你就完成了這一步!
gustafcavanaugh@cloudshell:~ (basic-web-app-test)$ kubectl get service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE keras-app LoadBalancer 10.11.250.71 35.225.226.94 80:30271/TCP 4m kubernetes ClusterIP 10.11.240.1 <none> 443/TCP 18m
現在是關鍵時刻了!請獲取 keras 應用程式的 cluster-ip。開啟本地終端(或者存有狗狗照片的地方),執行 curl -X POST -F [email protected] 'http://<your service IP>/predict' 指令呼叫 API。
享受你的實驗結果吧!
如下所示,API 正確地為該圖返回了「小獵犬」的標籤。
$ curl -X POST -F [email protected] 'http://35.225.226.94/predict' {"predictions":[{"label":"beagle","probability":0.987775444984436},{"label":"pot","probability":0.0020967808086425066},{"label":"Cardigan","probability":0.001351703773252666},{"label":"Walker_hound","probability":0.0012711131712421775},{"label":"Brittany_spaniel","probability":0.0010085132671520114}],"success":true}
步驟 4:封裝
在本教程中,我們使用 Keras 和 Flask 實現了一個深度學習模型,並將其部署為 REST API。然後我們把這個應用程式放在 Docker 容器中,將該容器上傳至 Docker Hub,並且使用 Kubernetes 對其進行部署。
只需要兩個指令,Kubernetes 就部署好了我們的應用程式並向外部提供服務。你應該為此而感到自豪。
現在,我們可以對這個專案做出很多改進。首先,我們應該將執行 flask 應用程式的 python web 伺服器從本地 python 伺服器替換為 gunicorn 這樣的生產級伺服器。我們還應該探索 Kubernetes 的擴充套件和管理特性,這是本文中幾乎沒有涉及到的。最後,我們可以嘗試從頭開始建立一個 kuberenetes 環境。
原文連結:https://medium.com/analytics-vidhya/deploy-your-first-deep-learning-model-on-kubernetes-with-python-keras-flask-and-docker-575dc07d9e76