可重复代码和模型方法
我们现在已经到了可以将模型/训练代码分发给他人使用的阶段。您可以用软盘分发或与他人共享代码,但这并不理想。是这样吗?也许很多年前,这是理想的做法,但现在不是了。 与他人共享代码和协作的首选方式是使用源代码管理系统。Git 是最流行的源代码管理系统之一。那么,假设你已经学会了 Git,并正确地格式化了代码,编写了适当的文档,还开源了你的项目。这就够了吗?不,还不够。因为你在自己的电脑上写的代码,在别人的电脑上可能会因为各种原因而无法运行。因此,如果您在发布代码时能复制自己的电脑,而其他人在安装您的软件或运行您的代码时也能复制您的电脑,那就再好不过了。为此,如今最流行的方法是使用 Docker 容器(Docker Containers)。要使用 Docker 容器,你需要安装 Docker。
让我们用下面的命令来安装 Docker。
sudo apt install docker.io
sudo systemctl start docker
sudo systemctl enable docker
sudo groupadd docker
sudo usermod -aG docker $USER
这些命令可以在 Ubuntu 18.04 上运行。Docker 最棒的地方在于它可以安装在任何机器上: Linux、Windows、OSX。因此,如果你一直在 Docker 容器中工作,哪台机器都没关系!
Docker 容器可以被视为小型虚拟机。你可以为你的代码创建一个容器,然后每个人都可以使用和访问它。让我们看看如何创建可用于训练模型的容器。我们将使用在自然语言处理一章中训练的 BERT 模型,并尝试将训练代码容器化。
首先,你需要一个包含 python 项目需求的文件。需求包含在名为 requirements.txt 的文件中。文件名是 thestandard。该文件包含项目中使用的所有 python 库。也就是可以通过 PyPI (pip) 下载的 python 库。用于 训练 BERT 模型以检测正/负情感,我们使用了 torch、transformers、tqdm、scikit-learn、pandas 和 numpy。 让我们把它们写入 requirements.txt 中。你可以只写名称,也可以包括版本。包含版本总是最好的,这也是你应该做的。包含版本后,可以确保其他人使用的版本与你的版本相同,而不是最新版本,因为最新版本可能会更改某些内容,如果是这样的话,模型的训练方式就不会与你的相同了。
下面的代码段显示了 requirements.txt。
现在,我们将创建一个名为 Dockerfile 的 Docker 文件。没有扩展名。Dockerfile 有几个元素。让我们来看看。
# Dockerfile
# First of all, we include where we are getting the image
# from. Image can be thought of as an operating system.
# You can do "FROM ubuntu:18.04"
# this will start from a clean ubuntu 18.04 image.
# All images are downloaded from dockerhub
# Here are we grabbing image from nvidia's repo
# they created a docker image using ubuntu 18.04
# and installed cuda 10.1 and cudnn7 in it. Thus, we don't have to
# install it. Makes our life easy.
FROM nvidia/cuda:10.1-cudnn7-runtime-ubuntu18.04
# this is the same apt-get command that you are used to
# except the fact that, we have -y argument. Its because
# when we build this container, we cannot press Y when asked for
RUN apt-get update && apt-get install -y \
git \
curl \
ca-certificates \
python3 \
python3-pip \
sudo \
&& rm -rf /var/lib/apt/lists/*
# We add a new user called "abhishek"
# this can be anything. Anything you want it
# to be. Usually, we don't use our own name,
# you can use "user" or "ubuntu"
RUN useradd -m abhishek
# make our user own its own home directory
RUN chown -R abhishek:abhishek /home/abhishek/
# copy all files from this direrctory to a
# directory called app inside the home of abhishek
# and abhishek owns it.
COPY --chown=abhishek *.* /home/abhishek/app/
# change to user abhishek
USER abhishek
RUN mkdir /home/abhishek/data/
# Now we install all the requirements
# after moving to the app directory
# PLEASE NOTE that ubuntu 18.04 image
# has python 3.6.9 and not python 3.7.6
# you can also install conda python here and use that
# however, to simplify it, I will be using python 3.6.9
# inside the docker container!!!!
RUN cd /home/abhishek/app/ && pip3 install -r requirements.txt
# install mkl. its needed for transformers
RUN pip3 install mkl
# when we log into the docker container,
# we will go inside this directory automatically
WORKDIR /home/abhishek/app
创建好 Docker 文件后,我们就需要构建它。构建 Docker 容器是一个非常简单的命令。
该命令根据提供的 Dockerfile 构建一个容器。Docker 容器的名称是 bert:train。输出结果如下:
❯ docker build -f Dockerfile -t bert:train .
Sending build context to Docker daemon 19.97kB
Step 1/7 : FROM nvidia/cuda:10.1-cudnn7-ubuntu18.04
---> 3b55548ae91f
Step 2/7 : RUN apt-get update && apt-get install -y git curl ca-
certificates python3 python3-pip sudo && rm -rf
/var/lib/apt/lists/*
.
.
.
.
Removing intermediate container 8f6975dd08ba
---> d1802ac9f1b4
Step 7/7 : WORKDIR /home/abhishek/app
---> Running in 257ff09502ed
Removing intermediate container 257ff09502ed
---> e5f6eb4cddd7
Successfully built e5f6eb4cddd7
Successfully tagged bert:train
请注意,我删除了输出中的许多行。现在,您可以使用以下命令登录容器。
你需要记住,一旦退出 shell,你在 shell 中所做的一切都将丢失。你还可以在 Docker 容器中使用。
输出情况:
Traceback (most recent call last):
File "train.py", line 2, in <module>
import config
File "/home/abhishek/app/config.py", line 28, in <module>
do_lower_case=True
File "/usr/local/lib/python3.6/dist-
packages/transformers/tokenization_utils.py", line 393, in
from_pretrained
return cls._from_pretrained(*inputs, **kwargs)
File "/usr/local/lib/python3.6/dist-
packages/transformers/tokenization_utils.py", line 496, in
_from_pretrained
list(cls.vocab_files_names.values()),
OSError: Model name '../input/bert_base_uncased/' was not found in
tokenizers model name list (bert-base-uncased, bert-large-uncased, bert-
base-cased, bert-large-cased, bert-base-multilingual-uncased, bert-base-
multilingual-cased, bert-base-chinese, bert-base-german-cased, bert-
large-uncased-whole-word-masking, bert-large-cased-whole-word-masking,
bert-large-uncased-whole-word-masking-finetuned-squad, bert-large-cased-
whole-word-masking-finetuned-squad, bert-base-cased-finetuned-mrpc, bert-
base-german-dbmdz-cased, bert-base-german-dbmdz-uncased, bert-base-
finnish-cased-v1, bert-base-finnish-uncased-v1, bert-base-dutch-cased).
We assumed '../input/bert_base_uncased/' was a path, a model identifier,
or url to a directory containing vocabulary files named ['vocab.txt'] but
couldn't find such vocabulary files at this path or url.
哎呀,出错了!
我为什么要把错误印在书上呢?
因为理解这个错误非常重要。这个错误说明代码无法找到目录".../input/bert_base_cased"。为什么会出现这种情况呢?我们可以在没有 Docker 的情况下进行训练,我们可以看到目录和所有文件都存在。出现这种情况是因为 Docker 就像一个虚拟机!它有自己的文件系统,本地机器上的文件不会共享给 Docker 容器。如果你想使用本地机器上的路径并对其进行修改,你需要在运行 Docker 时将其挂载到 Docker 容器上。当我们查看这个文件夹的路径时,我们知道它位于名为 input 的文件夹的上一级。让我们稍微修改一下 config.py 文件!
# config.py
import os
import transformers
# fetch home directory
# in our docker container, it is
# /home/abhishek
HOME_DIR = os.path.expanduser("~")
# this is the maximum number of tokens in the sentence
MAX_LEN = 512
# batch sizes is low because model is huge!
TRAIN_BATCH_SIZE = 8
VALID_BATCH_SIZE = 4
# let's train for a maximum of 10 epochs
EPOCHS = 10
# define path to BERT model files
# Now we assume that all the data is stored inside
# /home/abhishek/data
BERT_PATH = os.path.join(HOME_DIR, "data", "bert_base_uncased")
# this is where you want to save the model
MODEL_PATH = os.path.join(HOME_DIR, "data", "model.bin")
# training file
TRAINING_FILE = os.path.join(HOME_DIR, "data", "imdb.csv")
TOKENIZER = transformers.BertTokenizer.from_pretrained(BERT_PATH,do_lower_case=True )
现在,代码假定所有内容都在主目录下名为 data 的文件夹中。
请注意,如果 Python 脚本有任何改动,都意味着需要重建 Docker 容器!因此,我们重建容器,然后重新运行 Docker 命令,但这次要有所改变。不过,如果我们没有英伟达™(NVIDIA®)Docker 运行时,这也是行不通的。别担心,这只是一个 Docker 容器。你只需要做一次。要安装英伟达™(NVIDIA®)Docker 运行时,可以在 Ubuntu 18.04 中运行以下命令。
distribution=$(. /etc/os-release;echo $ID$VERSION_ID)
curl -s -L https://nvidia.github.io/nvidia-docker/gpgkey | sudo apt-key
add -
curl -s -L https://nvidia.github.io/nvidia-docker/$distribution/nvidia-
docker.list | sudo tee /etc/apt/sources.list.d/nvidia-docker.list
sudo apt-get update && sudo apt-get install -y nvidia-container-toolkit
sudo systemctl restart docker
现在,我们可以再次构建我们的容器,并开始训练过程:
docker run --gpus 1 -v
/home/abhishek/workspace/approaching_almost/input/:/home/abhishek/data/ -
ti bert:train python3 train.py
其中,-gpus 1 表示我们在 docker 容器中使用 1 个 GPU,-v 表示挂载卷。 因此,我们要将本地目录 /home/abhishek/workspace/approaching_almost/input/ 挂载到 docker 容器中的 /home/abhishek/data/。这一步要花点时间,但完成后,本地文件夹中就会有 model.bin。
这样,只需做一些简单的改动,你的训练代码就已经 "dockerized "了。现在,你可以在(几乎)任何你想要的系统上使用这些代码进行训练。
下一部分是将我们训练好的模型 "提供 "给最终用户。假设您想从接收到的推文流中提取情感信息。要完成这项任务,您必须创建一个 API,用于输入句子,然后返回带有情感概率的输出。使用 Python 构建 API 的最常见方法是使用 Flask,它是一个微型网络服务框架。
# api.py
import config
import flask
import time
import torch
import torch.nn as nn
from flask import Flask
from flask import request
from model import BERTBaseUncased
app = Flask(__name__)
MODEL = None
DEVICE = "cuda"
def sentence_prediction(sentence):
tokenizer = config.TOKENIZER
max_len = config.MAX_LEN
review = str(sentence)
review = " ".join(review.split())
inputs = tokenizer.encode_plus(
review,
None,
add_special_tokens=True,
max_length=max_len
)
ids = inputs["input_ids"]
mask = inputs["attention_mask"]
token_type_ids = inputs["token_type_ids"]
padding_length = max_len - len(ids)
ids = ids + ([0] * padding_length)
mask = mask + ([0] * padding_length)
token_type_ids = token_type_ids + ([0] * padding_length)
ids = torch.tensor(ids, dtype=torch.long).unsqueeze(0)
mask = torch.tensor(mask, dtype=torch.long).unsqueeze(0)
token_type_ids = torch.tensor(token_type_ids,
dtype=torch.long).unsqueeze(0)
ids = ids.to(DEVICE, dtype=torch.long)
token_type_ids = token_type_ids.to(DEVICE, dtype=torch.long)
mask = mask.to(DEVICE, dtype=torch.long)
outputs = MODEL(ids=ids, mask=mask, token_type_ids=token_type_ids)
outputs = torch.sigmoid(outputs).cpu().detach().numpy()
return outputs[0][0]
@app.route("/predict", methods=["GET"])
def predict():
sentence = request.args.get("sentence")
start_time = time.time()
positive_prediction = sentence_prediction(sentence)
negative_prediction = 1 - positive_prediction
response = {}
response["response"] = {
"positive": str(positive_prediction),
"negative": str(negative_prediction),
"sentence": str(sentence),
"time_taken": str(time.time() - start_time),
}
return flask.jsonify(response)
if __name__ == "__main__":
MODEL = BERTBaseUncased()
MODEL.load_state_dict(torch.load(
config.MODEL_PATH, map_location=torch.device(DEVICE)
))
MODEL.to(DEVICE)
MODEL.eval()
app.run(host="0.0.0.0")
然后运行 "python api.py "命令启动 API。API 将在端口 5000 的 localhost 上启动。cURL 请求及其响应示例如下。
❯ curl
$'http://192.168.86.48:5000/predict?sentence=this%20is%20the%20best%20boo
k%20ever'
{"response":{"negative":"0.0032927393913269043","positive":"0.99670726","
sentence":"this is the best book
ever","time_taken":"0.029126882553100586"}}
可以看到,我们得到的输入句子的正面情感概率很高。输入句子的正面情感概率很高。 您还可以访问 http://127.0.0.1:5000/predict?sentence=this%20book%20is%20too%20complicated%20for%20me。这将再次返回一个 JSON 文件。
{
response: {
negative: "0.8646619468927383",
positive: "0.13533805",
sentence: "this book is too complicated for me",
time_taken: "0.03852701187133789"
}
}
现在,我们创建了一个简单的应用程序接口,可以用来为少量用户提供服务。为什么是少量?因为这个 API 一次只服务一个请求。gunicorn 是 UNIX 上的 Python WSGI HTTP 服务器,让我们使用它的 CPU 来处理多个并行请求。Gunicorn 可以为 API 创建多个进程,因此我们可以同时为多个客户提供服务。您可以使用 "pip install gunicorn "安装 gunicorn。
为了将代码转换为与 gunicorn 兼容,我们需要移除 init main,并将其中的所有内容移至全局范围。此外,我们现在使用的是 CPU 而不是 GPU。修改后的代码如下。
# api.py
import config
import flask
import time
import torch
import torch.nn as nn
from flask import Flask
from flask import request
from model import BERTBaseUncased
app = Flask(__name__)
DEVICE = "cpu"
MODEL = BERTBaseUncased()
MODEL.load_state_dict(torch.load(config.MODEL_PATH, map_location=torch.device(DEVICE)))
MODEL.to(DEVICE)
MODEL.eval()
def sentence_prediction(sentence):
return outputs[0][0]
@app.route("/predict", methods=["GET"])
def predict():
return flask.jsonify(response)
我们使用以下命令运行这个应用程序接口。
这意味着我们在提供的 IP 地址和端口上使用 4 个 Worker 运行我们的 flask 应用程序。由于有 4 个 Worker,我们现在可以同时处理 4 个请求。请注意,现在我们的终端使用的是 CPU,因此不需要 GPU 机器,可以在任何标准服务器/虚拟机上运行。不过,我们还有一个问题:我们已经在本地机器上完成了所有工作,因此必须将其坞化。看看下面这个未注释的 Dockerfile,它可以用来部署这个应用程序接口。请注意用于培训的旧 Dockerfile 和这个 Dockerfile 之间的区别。区别不大。
# CPU Dockerfile
FROM ubuntu:18.04
RUN apt-get update && apt-get install -y \
git \
curl \
ca-certificates \
python3 \
python3-pip \
sudo \
&& rm -rf /var/lib/apt/lists/*
RUN useradd -m abhishek
RUN chown -R abhishek:abhishek /home/abhishek/
COPY --chown=abhishek *.* /home/abhishek/app/
USER abhishek
RUN mkdir /home/abhishek/data/
RUN cd /home/abhishek/app/ && pip3 install -r requirements.txt
RUN pip3 install mkl
WORKDIR /home/abhishek/app
让我们构建一个新的 Docker 容器。
当 Docker 容器构建完成后,我们就可以使用以下命令直接运行 API 了。
docker run -p 5000:5000 -v
/home/abhishek/workspace/approaching_almost/input/:/home/abhishek/data/ -
ti bert:api /home/abhishek/.local/bin/gunicorn api:app --bind
0.0.0.0:5000 --workers 4
请注意,我们将容器内的 5000 端口暴露给容器外的 5000 端口。如果使用 docker-compose,也可以很好地做到这一点。Dockercompose 是一个可以让你同时在不同或相同容器中运行不同服务的工具。你可以使用 "pip install docker-compose "安装 docker-compose,然后在构建容器后运行 "docker-compose up"。要使用 docker-compose,你需要一个 docker-compose.yml 文件。
# docker-compose.yml
# specify a version of the compose
version: '3.7'
# you can add multiple services
services:
# specify service name. we call our service: api
api:
# specify image name
image: bert:api
# the command that you would like to run inside the container
command: /home/abhishek/.local/bin/gunicorn api:app --bind
0.0.0.0:5000 --workers 4
# mount the volume
volumes:
-
/home/abhishek/workspace/approaching_almost/input/:/home/abhishek/data/
# this ensures that our ports from container will be
# exposed as it is
network_mode: host
现在,您只需使用上述命令即可重新运行 API,其运行方式与之前相同。恭喜你,现在,你也已经成功地将预测 API 进行了 Docker 化,可以随时随地部署了。在本章中,我们学习了 Docker、使用 flask 构建 API、使用 gunicorn 和 Docker 服务 API 以及 docker-compose。关于 docker 的知识远不止这些,但这应该是一个开始。其他内容可以在学习过程中逐渐掌握。 我们还跳过了许多工具,如 kubernetes、bean-stalk、sagemaker、heroku 和许多其他工具,这些工具如今被人们用来在生产中部署模型。"我要写什么?点击修改图 X 中的 docker 容器"?在书中描述这些是不可行的,也是不可取的,所以我将使用不同的媒介来赞美本书的这一部分。请记住,一旦你对应用程序进行了 Docker 化,使用这些技术/平台进行部署就变得易如反掌了。请务必记住,要让你的代码和模型对他人可用,并做好文档记录,这样任何人都可以使用你开发的东西,而无需多次询问你。这不仅能节省您的时间,还能节省他人的时间。好的、开源的、可重复使用的代码在您的作品集中也非常重要。