赞
踩
接着上一篇博客,这一节开始介绍使用 Docker 部署 FastAPI,以及使用带 UI 的端到端应用程序。
到目前为止,我们已经创建了一个 AWS 实例,我们还创建了一个 FastAPI,它将图像的 base64 字符串表示形式作为输入并返回边界框和关联的类。 但是所有的 FastAPI 代码仍然驻留在我们的本地机器上。 我们如何将它放在ec2服务器上? 并在云端运行预测。
我们将按照 fastAPI 创建者本人的建议,使用 docker 部署我们的应用程序。 我将尝试解释 docker 是如何工作的。 下面的部分可能看起来令人生畏,但它只是一系列命令和步骤。 所以留在我身边。
我们可以从安装 docker 开始,使用:
- sudo apt-get update
- sudo apt install docker.io
然后我们使用以下命令启动 docker 服务:
sudo service docker start
- └── dockerfastapi
- ├── Dockerfile
- ├── app
- │ └── main.py
- └── requirements.txt
这里 dockerfastapi 是我们项目的主文件夹。 这是此文件夹中的不同文件:
requirements.txt:Docker 需要一个文件,告诉它运行我们的应用程序需要哪些库。 在这里,我列出了我在 Fastapi API 中使用的所有库。
- numpy
- opencv-python
- matplotlib
- torchvision
- torch
- fastapi
- pydantic
Dockerfile:第二个文件是Dockerfile。
- FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
-
- COPY ./app /app
- COPY requirements.txt .
- RUN pip --no-cache-dir install -r requirements.txt
dockerfile 可以被认为是类似于 sh 文件的东西,它包含创建可以在容器中运行的 docker 镜像的命令。 可以将 docker 镜像视为安装了 Python 和 Python 库等所有内容的环境。 容器是一个单元,它只是我们系统中使用 dockerimage 的一个孤立的盒子。 使用docker的好处是我们可以创建多个docker镜像,在多个容器中使用。 例如,一个图像可能包含 python36,而另一个图像可能包含 python37。 我们可以在单个 Linux 服务器中生成多个容器。
我们的 Dockerfile 包含一些东西:
FROM 命令:第一行 FROM 指定我们从 tiangolo(FastAPI 创建者)的 Docker 镜像开始。 根据他的网站:“这个图像包含一个“自动调整”机制,这样你就可以添加你的代码并自动获得相同的高性能。 而且没有做出牺牲”。 我们正在做的只是从一个为我们安装 python3.7 的映像开始,同时为 uvicorn 和 gunicorn ASGI 服务器添加了一些配置,并为 ASGI 服务器自动添加了一个 start.sh 文件。 对于喜欢冒险的人来说,特别是命令集 1 和命令集 2 通过一种菊花链命令来执行。
COPY 命令:我们也可以将 docker 镜像视为包含文件等的文件夹。 在这里,我们将之前创建的应用程序文件夹和 requirements.txt 文件复制到我们的 docker 镜像中。
运行命令:我们运行 pip install 命令来使用现在位于 docker 映像上的 requirements.txt 文件安装我们所有的 python 依赖项。
main.py:此文件包含我们之前创建的 fastapiapp.py 代码。 请记住仅保留文件 main.py 的名称。
我们已经获得了所需结构中的所有文件,但我们还没有使用任何 docker 命令。 我们首先需要使用 Dockerfile 构建一个包含所有依赖项的镜像。
我们可以简单地通过以下方式做到这一点:
sudo docker build -t myimage .
这会从 tiangolo 的镜像下载、复制和安装一些文件和库,并创建一个名为 myimage 的镜像。 这个 myimage 有 python37 和一些由 requirements.txt 文件指定的 python 包。
然后我们只需要启动一个运行这个图像的容器。 我们可以使用:
sudo docker run -d --name mycontainer -p 80:80 myimage
这将创建一个名为 mycontainer 的容器,它运行我们的 docker 镜像 myimage。 80:80 部分将我们的 docker 容器端口 80 连接到我们的 Linux 机器端口 80。
实际上就是这样。 此时,您应该能够在浏览器中打开以下 URL。
- # <IPV4 public IP>/docs
- URL: 18.237.28.174/docs
我们可以使用以下方式以编程方式检查我们的应用程序:
- payload = json.dumps({
- "base64str": base64str,
- "threshold": 0.5
- })
-
- response = requests.put("[http://18.237.28.174/predict](http://18.237.28.174/predict)",data = payload)
- data_dict = response.json()
- print(data_dict)
以上所有内容都很好,如果您按照确切的说明进行操作,就会开箱即用,但现实世界并非如此。 您肯定会在此过程中遇到一些错误,并且需要调试您的代码。 因此,为了帮助您解决这个问题,一些 docker 命令可能会派上用场:
当我们使用 sudo docker run 运行我们的容器时,我们没有得到很多信息,这在调试时是个大问题。 您可以使用以下命令查看实时日志。 如果您在此处看到错误,则需要更改代码并重新构建映像。
sudo docker logs -f mycontainer
启动和停止 Docker:有时,重新启动 Docker 可能会有所帮助。 在这种情况下,您可以使用:
- sudo service docker stop
- sudo service docker start
列出图像和容器:使用 docker,您最终会创建图像和容器,但您将无法在工作目录中看到它们。 您可以使用以下方式列出您的图像和容器:
- sudo docker container ls
- sudo docker image ls
删除未使用的 docker 镜像或容器:您可能需要删除一些镜像或容器,因为它们会占用大量系统空间。
- # the prune command removes the unused containers and images
- sudo docker system prune
-
- # delete a particular container
- sudo docker rm mycontainer
-
- # remove myimage
- sudo docker image rm myimage
-
- # remove all images
- sudo docker image prune — all
**Checking localhost:**Linux服务器没有浏览器,但我们仍然可以看到浏览器输出,虽然有点难看:
curl localhost
无需一次又一次地重新加载图像即可开发:对于开发而言,能够仅更改我们机器上的代码内容并实时测试它是很有用的,而不必每次都构建图像。 在这种情况下,在每次代码更改时自动运行带有实时自动重新加载的服务器也很有用。 在这里,我们使用 Linux 机器上的应用程序目录,并在开发过程中将默认目录 (/start.sh) 替换为开发备选目录 /start-reload.sh。 一切正常后,我们可以再次构建我们的镜像,在容器中运行它。
sudo docker run -d -p 80:80 -v $(pwd):/app myimage /start-reload.sh
我们在这里完成了 API 创建,但我们还可以使用我们的 FastAPI API 使用 Streamlit 创建基于 UI 的应用程序。 这不是您在生产环境中的做法(您可能让开发人员使用 React、node.js 或 javascript 制作应用程序),但主要是检查如何使用图像 API 的端到端流程。 我将在本地而不是 ec2 服务器上托管这个准系统 Streamlit 应用程序,它将从 ec2 上托管的 FastAPI API 获取边界框信息和类。
如果您需要了解更多关于 streamlit 的工作原理,您可以查看这篇文章。 此外,如果您还想将此 streamlit 应用程序部署到 ec2,这里又是一个教程。
这是 ec2 上带有 UI 和 FastAPI API 的整个应用程序的流程:
我们需要在 streamlit 应用程序中解决的最重要的问题是:
如何使用 Streamlit 从用户那里获取图像文件?
bytesObj = st.file_uploader(“Choose an image file”)
下一个问题是,我们从 streamlit 文件上传器得到的这个 bytesObj 是什么? 在 streamlit 中,我们将从 file_uploader 获取一个 bytesIO 对象,我们需要将其转换为 base64str 以用于我们的 FastAPI 应用程序输入。 这可以使用以下方法完成:
- def bytesioObj_to_base64str(bytesObj):
- return base64.b64encode(bytesObj.read()).decode("utf-8")
-
- base64str = bytesioObj_to_base64str(bytesObj)
url = st.text_input(‘Enter URL’)
然后我们可以使用请求模块和 base64 编码和 utf-8 解码从 URL 以 base64 字符串格式获取图像:
- def ImgURL_to_base64str(url):
- return base64.b64encode(requests.get(url).content).decode("utf-8")
-
- base64str = ImgURL_to_base64str(url)
这是我们 Streamlit 应用程序的完整代码。 您已经看到了这篇文章中的大部分代码。
- import streamlit as st
- import base64
- import io
- import requests,json
- from PIL import Image
- import cv2
- import numpy as np
- import matplotlib.pyplot as plt
- import requests
- import random
-
- # use file uploader object to recieve image
- # Remember that this bytes object can be used only once
- def bytesioObj_to_base64str(bytesObj):
- return base64.b64encode(bytesObj.read()).decode("utf-8")
-
- # Image conversion functions
-
- def base64str_to_PILImage(base64str):
- base64_img_bytes = base64str.encode('utf-8')
- base64bytes = base64.b64decode(base64_img_bytes)
- bytesObj = io.BytesIO(base64bytes)
- img = Image.open(bytesObj)
- return img
-
- def PILImage_to_cv2(img):
- return np.asarray(img)
-
- def ImgURL_to_base64str(url):
- return base64.b64encode(requests.get(url).content).decode("utf-8")
-
- def drawboundingbox(img, boxes,pred_cls, rect_th=2, text_size=1, text_th=2):
- img = PILImage_to_cv2(img)
- class_color_dict = {}
-
- #initialize some random colors for each class for better looking bounding boxes
- for cat in pred_cls:
- class_color_dict[cat] = [random.randint(0, 255) for _ in range(3)]
-
- for i in range(len(boxes)):
- cv2.rectangle(img, (int(boxes[i][0][0]), int(boxes[i][0][1])),
- (int(boxes[i][1][0]),int(boxes[i][1][1])),
- color=class_color_dict[pred_cls[i]], thickness=rect_th)
- cv2.putText(img,pred_cls[i], (int(boxes[i][0][0]), int(boxes[i][0][1])), cv2.FONT_HERSHEY_SIMPLEX, text_size, class_color_dict[pred_cls[i]],thickness=text_th)
- plt.figure(figsize=(20,30))
- plt.imshow(img)
- plt.xticks([])
- plt.yticks([])
- plt.show()
-
- st.markdown("<h1>Our Object Detector App using FastAPI</h1><br>", unsafe_allow_html=True)
-
- bytesObj = st.file_uploader("Choose an image file")
-
- st.markdown("<center><h2>or</h2></center>", unsafe_allow_html=True)
-
- url = st.text_input('Enter URL')
-
- if bytesObj or url:
- # In streamlit we will get a bytesIO object from the file_uploader
- # and we convert it to base64str for our FastAPI
- if bytesObj:
- base64str = bytesioObj_to_base64str(bytesObj)
-
- elif url:
- base64str = ImgURL_to_base64str(url)
-
- # We will also create the image in PIL Image format using this base64 str
- # Will use this image to show in matplotlib in streamlit
- img = base64str_to_PILImage(base64str)
-
- # Run FastAPI
- payload = json.dumps({
- "base64str": base64str,
- "threshold": 0.5
- })
-
- response = requests.put("http://18.237.28.174/predict",data = payload)
- data_dict = response.json()
-
-
- st.markdown("<center><h1>App Result</h1></center>", unsafe_allow_html=True)
- drawboundingbox(img, data_dict['boxes'], data_dict['classes'])
- st.pyplot()
- st.markdown("<center><h1>FastAPI Response</h1></center><br>", unsafe_allow_html=True)
- st.write(data_dict)

我们可以使用以下方法在本地运行这个 streamlit 应用程序:
streamlit run streamlitapp.py
我们可以看到我们的应用程序在 localhost:8501 上运行。 适用于用户上传的图像以及基于 URL 的图像。 这也是一些猫爱好者的猫图片。
我们在这里创建了一个完整的工作流程,以通过 ec2 上的 FastAPI 部署图像检测模型,并在 Streamlit 中利用这些结果。 我希望这可以帮助您解决在生产中部署模型的问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。