对 PyTorch BERT 模型进行微调,并将其部署到 Amazon SageMaker 上的 Amazon Elastic Inference

2020 年 9 月 20 日

对 PyTorch BERT 模型进行微调,并将其部署到 Amazon SageMaker 上的 Amazon Elastic Inference

Original URL: https://aws.amazon.com/cn/blogs/machine-learning/fine-tuning-a-pytorch-bert-model-and-deploying-it-with-amazon-elastic-inference-on-amazon-sagemaker/


文本分类,是一种将不同文本内容划分到对应类别的技术,其拥有广泛的应用范围:电子邮件服务商通过文本分类检测垃圾邮件,营销机构借此对客户评论进行情感分析,论坛版主则借此检测不当发帖等等。


以往,数据科学家使用 tf-idf, word2vecbag-of-words (BOW)等方法,生成用于训练分类模型的特征。尽管这些技术在诸多自然语言处理(NLP)任务中获得了巨大成功,但在不同的上下文背景之下,其往往无法准确地捕捉单词含义。最近,随着基于 Transformers 的双向编码器表示(BERT)技术在结合实际上下文准确实现单词含义编码方面带来的突出表现,人们也希望借助 BERT 的力量在文本分类任务当中获得更理想的结果。


Amazon SageMaker 是一项全托管服务,能够为开发人员及数据科学家提供快速构建、训练并部署机器学习(ML)模型的能力。Amazon SageMaker 消除了 ML 流程中各个步骤带来的繁重工作,极大降低了高质量模型的开发门槛。Amazon SageMaker Python SDK 还提供开源 API 与容器,允许您更轻松地在 Amazon SageMaker 中使用多种不同 ML 与深度学习框架,实现模型的训练与部署作业。


我们的客户经常需要快速调优并轻松部署 NLP 模型。此外,客户也希望尽可能降低推理延迟与模型推理成本。Amazon Elastic Inference能够将 GPU 推理加速能力附加至 CPU 类型的终端端点当中,可以在不牺牲性能的前提下显著降低深度学习的推理成本。


本文将介绍如何使用 Amazon SageMaker 对 PyTorch BERT 模型进行微调,并将其部署在应用了 Elastic Inference 的 SageMaker 终端节点上。本文中使用的全部代码皆发布在GitHub repo之上。关于 BERT 微调的更多详细信息,请参阅PyTorch BERT调优教程


BERT 是什么?


BERT 最初发布于 2018 年 11 月,这是一种革命性的模型,能够主动屏蔽句子中的一个或者多个单词。BERT 将屏蔽过单词的句子作为输入,借此自我训练以预测被屏蔽的单词内容。此外,BERT 还能够应用于预测下一句的任务。


BERT 代表着一项重大突破,已经帮助业界研究人员及数据工程师在众多 NLP 任务中取得重大成果。BERT 提供的各个单词的表征能够切实与所处上下文(即句子中的其余部分)相匹配。关于 BERT 的更多详细信息,请参阅BERT:用于语言理解的深度双向Transformers预训练模型


BERT 调优


数据科学家在 NLP 项目当中面临的最大挑战之一,在于缺乏训练数据。大家往往只能获得几千条带有人工标记的文本数据,用于模型训练。但是,现代深度学习 NLP 任务又需要大量标记数据,而解决此难题的一大重要方法,就是使用迁移学习技术。


迁移学习是一种 ML 方法,旨在将预训练完成的模型(比如用于图像分类的预训练 ResNet 模型)重新用作另一不同、但具有相关性的问题。通过复用预训练模型中的参数,我们可以节约大量的训练时间与成本。


BERT 是基于 BookCorpus 与英文维基百科的数据进行训练,二者分别包含 8 亿以及 25 亿个单词[1]。从零开始训练 BERT 的成本极为高昂,但通过迁移学习,大家可以面对新的 场景用例时使用相关少量的训练数据对 BERT 进行快速微调,借此实现常见 NLP 任务(例如文本分类与问题解答)的高质量预测结果。


解决方案概述


在本文中,我们将分步介绍数据集、训练流程以及最终的模型部署环节。


我们使用 Amazon SageMaker notebook 实例用于代码运行。关于在 Amazon SageMaker 上使用 Jupyter notebooks 的更多详细信息,请参阅使用Amazon SageMaker notebook实例,或者Amazon SageMaker Studio入门指南


本文中的 notebook 与代码皆发布于 GitHub之上。您可以克隆 GitHub repo并打开Jupyter notebook文件


问题与数据集


在本文中,我们使用语言可接受性语料库(CoLA),这是一套对从已出版语言学文献中收集到的 10657 个英语句子进行符合语法与不符合语法标记的数据集。在我们的 notebook 中,将使用以下代码下载并解压这些数据:


Python


if not os.path.exists("./cola_public_1.1.zip"):    !curl -o ./cola_public_1.1.zip https://nyu-mll.github.io/CoLA/cola_public_1.1.zipif not os.path.exists("./cola_public/"):    !unzip cola_public_1.1.zip
复制代码


在训练数据中,我们只需要其中两列——句子本体及其标签:


Python


df = pd.read_csv(    "./cola_public/raw/in_domain_train.csv",    sep="\t",    header=None,    usecols=[1, 3],    names=["label", "sentence"],)sentences = df.sentence.valueslabels = df.label.values
复制代码


如果我们输出部分句子,即可看到该数据集如何根据句子语法的完整性进行句子标记。具体参见以下代码:


Python


print(sentences[20:25])print(labels[20:25])
["The professor talked us." "We yelled ourselves hoarse." "We yelled ourselves." "We yelled Harry hoarse." "Harry coughed himself into a fit."][0 1 0 0 1]
复制代码


接下来,我们对数据集进行拆分以进行训练与测试,而后将其上传至 Amazon S3 以供后续使用。SageMaker Python SDK 可帮助我们快速完成上传操作:


Python


from sagemaker.session import Sessionfrom sklearn.model_selection import train_test_split
train, test = train_test_split(df)train.to_csv("./cola_public/train.csv", index=False)test.to_csv("./cola_public/test.csv", index=False)
session = Session()inputs_train = session.upload_data("./cola_public/train.csv", key_prefix="sagemaker-bert/training/data")inputs_test = session.upload_data("./cola_public/test.csv", key_prefix="sagemaker-bert/testing/data")
复制代码


训练脚本


在本文中,我们使用 PyTorch-Transformers库。此库中包含用于 BERT 等多种 NLP 模型的 PyTorch 实现与预训练模型权重。详见以下代码:


Python


model = BertForSequenceClassification.from_pretrained(    "bert-base-uncased",  # Use the 12-layer BERT model, with an uncased vocab.    num_labels=2,  # The number of output labels--2 for binary classification.    output_attentions=False,  # Whether the model returns attentions weights.    output_hidden_states=False,  # Whether the model returns all hidden-states.)
复制代码


根据 SageMaker PyTorch 镜像的规定,我们的训练脚本应将在训练过程中学习到的模型文件保存至文件路径model_dir。训练完成之后,Amazon SageMaker 将保存在model_dir中的模型文件上传至 Amazon S3 以进行下一步部署。脚本将使用以下代码保存训练得出的模型工件:


Python


model_2_save = model.module if hasattr(model, "module") else modelmodel_2_save.save_pretrained(save_directory=args.model_dir)
复制代码


我们将此脚本保存为 train_deploy.py文件,并将该文件放置在名为code/的目录当中。大家可以在该目录中查看完整的训练脚本。


由于 PyTorch-Transformer 本身并不包含在 Amazon SageMaker PyTorch 镜像当中,因此我们需要提供对应的requirements.txt文件,保证 Amazon SageMaker 能够安装该库以进行训练与推理。 requirements.txt文件属于文本文件,其中包含使用pip install进行安装的条目列表。您也可以指定需要安装的各条目的具体版本。要安装 PyTorch-Transformer,我们需要将以下行添加至 requirements.txt 文件当中。


Python


transformers==2.3.0
复制代码


您可以在 GitHub repo上查看完整文件,也可以通过 code/目录进行查看。关于requirements.txt文件的更多详细信息,请参阅Requirements文件


在 Amazon SageMaker 上执行训练


我们使用 Amazon SageMaker 对我们的自定义 PyTorch 代码执行模型训练与部署。Amazon SageMaker Python SDK 能够极大降低在 Amazon SageMaker 中运行 PyTorch 脚本的难度。接下来,我们可以使用 SageMaker Python SDK 对经过训练的模型加以部署,并运行实际预测。关于将 SDK 与 PyTorch 配合使用的更多详细信息,请参阅将PyTorch与SageMaker Python SDK配合使用


首先,我们使用PyTorch estimator 进行模型训练。在创建此 estimator 时,请注意明确指定以下内容:


  • entry_point – PyTorch脚本的名称

  • source_dir – 训练脚本与 requirements.txt文件的位置

  • framework_version : 我们希望使用的PyTorch版本


PyTorch estimator 支持多机分布式 PyTorch 训练。要使用此功能,我们只需将train_instance_count的值设定为大于 1 即可。我们的训练脚本仅支持面向 GPU 实例进行分布式训练。


在估计器创建完成之后,我们调用fit()以启动一项训练作业。接下来,我们使用之前上传训练数据时获得的 Amazon S3 URI,详见以下代码:


Python


from sagemaker.pytorch import PyTorch
estimator = PyTorch( entry_point="train_deploy.py", source_dir="code", role=role, framework_version="1.3.1", py_version="py3", train_instance_count=2, train_instance_type="ml.p3.2xlarge", hyperparameters={ "epochs": 1, "num_labels": 2, "backend": "gloo", })estimator.fit({"training": inputs_train, "testing": inputs_test})
复制代码


在训练开始之后,Amazon SageMaker 会显示训练进度(如以下代码所示),具体包括轮次、训练损失以及测试数据精度:


Python


2020-06-10 01:00:41 Starting - Starting the training job...2020-06-10 01:00:44 Starting - Launching requested ML instances......2020-06-10 01:02:04 Starting - Preparing the instances for training............2020-06-10 01:03:48 Downloading - Downloading input data...2020-06-10 01:04:15 Training - Downloading the training image..2020-06-10 01:05:03 Training - Training image download completed. Training in progress....Train Epoch: 1 [0/3207 (0%)] Loss: 0.626472Train Epoch: 1 [350/3207 (98%)] Loss: 0.241283Average training loss: 0.5248292144022736Test set: Accuracy: 0.782608695652174...
复制代码


我们可以监控训练进度,请保证在继续进行 notebook 中的后续部分之前,确认训练流程已经成功完成。


部署脚本


在模型训练完成之后,我们通过在 PyTorch estimator 上调用 deploy 将模型托管在 Amazon SageMaker 终端节点之上。该终端节点将运行一套 Amazon SageMaker PyTorch 模型服务器。我们需要对此服务器中的两项组件加以配置:模型加载与模型服务。这两个组件的实现通过推理脚本train_deploy.py完成,完整文件可通过 GitHub repo获取。


model_fn()函数用于加载已保存模型,并返回一个模型对象以供模型服务组件使用。SageMaker PyTorch 模型服务器通过调用model_fn加载我们的模型:


Python


def model_fn(model_dir):    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")    model = BertForSequenceClassification.from_pretrained(model_dir)    return model.to(device)
复制代码


input_fn() 对预测输入进行反序列化与数据转换。在本用例中,我们的请求正文将首先被序列化为 JSON 格式,而后发送至模型服务端点。接下来,我们首先在 input_fn()中对 JSON 格式的请求正文进行反序列化,然后根据 BERT 的要求将输入以 torch.tensor的形式返回:


Python


def input_fn(request_body, request_content_type):    if request_content_type == "application/json":        sentence = json.loads(request_body)            input_ids = []        encoded_sent = tokenizer.encode(sentence,add_special_tokens = True)        input_ids.append(encoded_sent)            # pad shorter sentences        input_ids_padded =[]        for i in input_ids:            while len(i) < MAX_LEN:                i.append(0)            input_ids_padded.append(i)        input_ids = input_ids_padded            # mask; 0: added, 1: otherwise        [int(token_id > 0) for token_id in sent] for sent in input_ids
# convert to PyTorch data types. train_inputs = torch.tensor(input_ids) train_masks = torch.tensor(attention_masks) # train_data = TensorDataset(train_inputs, train_masks) return train_inputs, train_masks
复制代码


predict_fn() 执行预测并返回结果。详见以下代码:


Python


def predict_fn(input_data, model):    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")    model.to(device)    model.eval()    input_id, input_mask = input_data    input_id.to(device)    input_mask.to(device)    with torch.no_grad():        return model(input_id, token_type_ids=None,attention_mask=input_mask)[0]
复制代码


预构建的 Amazon SageMaker PyTorch 镜像中的默认支持对预测结果进行序列化。


部署端点


要部署我们的端点,需要在 PyTorch estimator 对象上调用deploy(),并提供所需数量的实例与实例类型:


Python


predictor = estimator.deploy(initial_instance_count=1, instance_type="ml.m4.xlarge")
复制代码


接下来,我们通过配置让预测变量使用"application/json"作为内容类型,而后将请求发送至我们的端点:


Python


from sagemaker.predictor import json_deserializer, json_serializer
predictor.content_type = "application/json"predictor.accept = "application/json"predictor.serializer = json_serializerpredictor.deserializer = json_deserializer
复制代码


最后,我们使用预测变量对象以调用该端点:


Python


result = predictor.predict("Somebody just left - guess who.")print(np.argmax(result, axis=1))
[1]
复制代码


预测出的类别为 1,符合我们的预期,因为用于测试的句子确实拥有正确的语法表达。


使用 Elastic Inference 部署端点


要为推理任务选择正确的实例类型,我们需要在不同数量的 GPU、CPU 以及内存资源之间做出权衡。在独立 GPU 实例上针对其中某一种资源进行优化,往往会导致其他资源得不到充分利用。Elastic Inference 则能够为特定端点提供适量的 GPU 驱动型推理加速资源,解决了这一难题。自 2020 年 3 月起,用户已经可以在 Amazon SageMaker 与 Amazon EC2 上获得 Elastic Inference 对 PyTorch 的支持能力。


要使用 Elastic Inference,我们需要首先将训练完成的模型转换为 TorchScript。关于更多详细信息,请参阅使用Amazon Elastic Inference在Amazon SageMaker for PyTorch模型上降低ML推理成本


我们首先从 Amazon S3 处下载训练完成的模型文件。模型文件的位置为estimator.model_data。接下来,我们使用以下代码将模型转换为 TorchScript:


Python


model_torchScript = BertForSequenceClassification.from_pretrained("model/", torchscript=True)device = "cpu"for_jit_trace_input_ids = [0] * 64for_jit_trace_attention_masks = [0] * 64for_jit_trace_input = torch.tensor([for_jit_trace_input_ids])for_jit_trace_masks = torch.tensor([for_jit_trace_input_ids])
traced_model = torch.jit.trace( model_torchScript, [for_jit_trace_input.to(device), for_jit_trace_masks.to(device)])torch.jit.save(traced_model, "traced_bert.pt")
subprocess.call(["tar", "-czvf", "traced_bert.tar.gz", "traced_bert.pt"])
复制代码


要加载 TorchScript 模型并将其应用于实际预测,我们还需要对模型的加载与预测函数做出些许调整。我们需要创建一个新的脚本 deploy_ei.py,其内容与train_deploy.py脚本略有不同。


要加载模型,我们使用 torch.jit.load替代之前使用的 BertForSequenceClassification.from_pretrained调用:


Python


loaded_model = torch.jit.load(os.path.join(model_dir, "traced_bert.pt"))
复制代码


要进行预测,我们在最终 return 语句当中使用torch.jit.optimized_execution


Python


with torch.no_grad():    with torch.jit.optimized_execution(True, {"target_device": "eia:0"}):        return model(input_id,attention_mask=input_mask)[0]
复制代码


完整的 deploy_ei.py脚本可通过 GitHub repo获取。使用这套脚本,我们即可通过 Elastic Inference 进行模型部署:


Python


predictor = pytorch.deploy(    initial_instance_count=1,     instance_type="ml.m5.large",    accelerator_type="ml.eia2.xlarge")
复制代码


通过使用 accelerator_type="ml.eia2.xlarge"参数,我们即可将 Elastic Inference 加速器附加至终端节点当中。


资源清理


在实验完成之后,请及时删除期间创建的 Amazon SageMaker 端点以及 Amazon SageMaker notebook 实例,以避免产生不必要的费用。具体参见以下代码:


Python


predictor.delete_endpoint()
复制代码


总结


在本文中,我们使用 Amazon SageMaker 以 BERT 为起点,训练出一套能够标记句子语法完整性的模型。接下来,我们将模型分别部署在使用 Elastic Inference 与不使用 Elastic Inference 的 Amazon SageMaker 终端节点。您也可以使用这套解决方案对 BERT 做其他方向的微调,或者使用PyTorch-Transformers提供的其他预训练模型。关于将 PyTorch 与 Amazon SageMaker 配合使用的更多详细信息,请参阅将PyTorch与Amazon SageMaker配合使用


参考文献


[1] Yukun Zhu, Ryan Kiros, Rich Zemel, Ruslan Salakhutdinov, Raquel Urtasun, Antonio Torralba 以及 Sanja Fidler。2015 年。《书籍与电影的映射:在观看电影与阅读书籍中实现相似故事的视觉解释》,IEEE 国际计算机视觉会议论文集,第 19 至 27 页。


作者介绍


本篇作者



Qingwei Li


Amazon Web Services 机器学习专家。在超出研究补助预算却未能成功拿下预想中的诺贝尔奖之后,他开始转向运筹学领域。目前,他帮助金融服务与保险业客户在 AWS 上构建机器学习解决方案。在业余时间,他喜欢阅读和教学。


David Ping


AWS 首席解决方案架构师。他与我们的客户一道使用 AWS 构建云与机器学习解决方案。他住在纽约都会区,喜欢学习各类最新的机器学习技术。


Lauren Yu


Amazon SageMaker 软件开发工程师。她主要研究 SageMaker Python SDK,以及用于将 PyTorch、TensorFlow、MXNet 同 Amazon SageMaker 整合起来的工具包解决方案。业余时间,她喜欢在 Amazon 交响乐团与 Doppler Quartet 乐队中演奏中提琴。


本文转载自亚马逊 AWS 官方博客。


原文链接


对 PyTorch BERT 模型进行微调,并将其部署到 Amazon SageMaker 上的 Amazon Elastic Inference


2020 年 9 月 20 日 14:00991

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布
暂无评论
发现更多内容

寻找感动的养分

一直AC一直爽

感恩 随笔杂谈 感动

读《我的大学,我的苦难》有感

一直AC一直爽

随笔杂谈 读后感

Mysql错误:Ignoring query to other database解决方法

一直AC一直爽

MySQL

架构师第七周作业

傻傻的帅

性能测试和并发压力的关系

王锟

WebRTC框架下的实时视频关键路径

fumingwang

第七周学习总结

王锟

剪刀爱情

一直AC一直爽

电影

广义表的实现!

烫烫烫个喵啊

算法 广义表

MinIO 参数解析与限制

耳东

配置 Minio

企业架构框架之FEA

Winfield

企业架构

Apache下error.log文件太大的处理方法

一直AC一直爽

【一起学系列】之命令模式:封装一个简单Jedis

Kerwin

设计模式 命令模式 Jedis

爸爸,我想握住你的手

一直AC一直爽

随笔杂谈 父爱

HashiCorp官宣:禁止国内使用其旗下Consul等开源软件?

xcbeyond

Consul 条款

ARTS-WEEK7

一周思进

ARTS 打卡计划

MinIO 分布式集群搭建

耳东

Minio cluster Minio 集群

Elasticsearch源码解析:环境搭建

Jackey

elasticsearch

架构师训练营 -- 第七周作业

stardust20

手把手教你写数独计算器(1)

一直AC一直爽

c++ 算法 数独

如何写出完美的接口:接口规范定义、接口管理工具推荐

xcbeyond

接口规范 接口管理工具 swagger Easy Mock

总结:PHP值得注意的几个问题

一直AC一直爽

php

JVM系列之:String,数组和集合类的内存占用大小

程序那些事

Java JVM GC JOL

公开课 | 吉祥人寿从0到1的 Jira 落地实践

Atlassian速递

敏捷开发 研发管理 Jira

技术面试官应该怎么问?面试者应该怎么答?

xcbeyond

面试 自我介绍 面试经验

我向面试官讲解了单例模式,他对我竖起了大拇指

cxuan

设计模式 单例模式

手写实现Promise

GKNick

英特尔唐炯:把最好、最合适的产品带给最需要的消费者

飞天鱼2017

我有一个梦想

一直AC一直爽

随笔杂谈 梦想

ZK 从入门到放弃 入门篇

小隐乐乐

47 张图带你 MySQL 进阶!!!

cxuan

MySQL 后端

对 PyTorch BERT 模型进行微调,并将其部署到 Amazon SageMaker 上的 Amazon Elastic Inference-InfoQ