使用GPT2、FastAPI和ReactJS训练与部署AI格言生成器
好的格言能让我们更坚强。格言真正鼓舞人心的并非其语调或满足感,而是分享者所反映的、真正有益于他人的生活经验。上面这段关于格言的话(格言套娃)并不是我写的,而是我训练的一个AI模型生成的。而且它说得比我可能表达的更好。格言对不同的人意味着不同的事物。有时它们激励我们,鼓舞我们。另一些时候,它们让我们思考生活、宗教,有时它们只是让我们发笑。那么,我们能否训练一个AI模型来生成更多格言,让我们思考、发笑
如何训练与部署自定义AI生成的格言:使用GPT2、FastAPI和ReactJS
问题
好的格言能让我们更坚强。格言真正鼓舞人心的并非其语调或满足感,而是分享者所反映的、真正有益于他人的生活经验。上面这段关于格言的话(格言套娃)并不是我写的,而是我训练的一个AI模型生成的。而且它说得比我可能表达的更好。格言对不同的人意味着不同的事物。有时它们激励我们,鼓舞我们。另一些时候,它们让我们思考生活、宗教,有时它们只是让我们发笑。
那么,我们能否训练一个AI模型来生成更多格言,让我们思考、发笑或受到启发?这就是我开始这段旅程的动机。
太长不看版
我已经在具有特定风格的格言上微调了GPT2模型,风格包括励志/鼓舞人心、有趣以及严肃/哲学,并部署在一个可即用的网站上:AI格言生成器。
模型
2020年6月3日,某中心发布了GPT3,这是一个在570GB互联网文本上训练的巨型语言模型。人们将这个多才多艺的模型应用于各种场景,从创建应用设计、网站,到实现近乎魔法的Excel函数。但存在一个问题——模型权重从未公开。我们访问它的唯一方式是通过付费的API。
所以,让我们时光倒流到2019年11月,那时GPT-2发布了。GPT-2虽然不如GPT-3强大,但在文本生成领域引发了一场革命。我记得当我阅读模型生成的演示文本时,惊讶得下巴都要掉下来了——其连贯性和语法结构近乎完美。我对模型的要求不是成为魔术师,而是能够生成结构完美的英语句子。为此,GPT2已经绰绰有余。
数据集
首先我需要一个数据集。从网上爬取格言是一个选择,但在那之前我想看看是否已经有人做过这件事。果然!Quotes-500k是一个包含近50万条格言的数据集,所有格言都是从网上爬取的,并附有诸如知识、爱情、友谊等标签。
现在我希望模型能够根据特定主题生成格言。由于我计划使用预训练模型,条件性文本生成并不容易实现。PPLM是某机构发布的一个模型,或者更确切地说,是一种使用预训练模型的方法,旨在实现这一目标(一篇非常有趣的论文,一定要看看),但最终我选择了另一条路。我决定训练三个模型,每个都针对特定类型的格言进行微调。
我考虑了三种类型——励志型、严肃型和有趣型。我使用Quotes500k数据集及其标签,根据标签将格言分离到这三个类别中。对于励志数据集,我使用了诸如爱情、生活、鼓舞人心、励志、人生教训、梦想等标签。对于严肃型,我选择了哲学、生活、上帝、时间、信仰、恐惧等标签。最后,对于有趣型,我只用了幽默和搞笑这类标签。
预处理
这里没什么特别的;只是基本的清理工作。例如:
- 转换为小写
- 替换缩写,如将"wasn’t"替换为完整形式"was not"。
- 移除HTML特殊实体(因为这是一个从网上抓取的语料库)
- 移除多余的空格
- 在单词和标点符号之间插入空格
- 拼写检查和纠正(使用pyenchant)
你可能会有疑问:那停用词呢?词形还原呢?那些步骤在哪里?删除停用词和词形还原并不是你在每个NLP任务中都必须执行的强制性步骤。这真的取决于任务。如果我们使用TF-IDF这类模型进行文本分类,那么,是的,做所有这些是有意义的。但对于使用上下文(如神经网络模型或N-Gram模型)的文本分类来说,必要性就小一些。如果你在进行文本生成或机器翻译,那么删除停用词和词形还原实际上可能会损害性能,因为我们正在从语料库中丢失有价值的上下文信息。
句首/句尾标记与最大长度
我还希望生成的格言不要太长。所以我查看了数据集,绘制了格言长度的频率分布图,并决定了一个适当的截断长度。在励志语料库中,我丢弃了所有超过100个单词的格言。
格言长度频率(励志型)
另一个使格言简短的重要方面是模型预测句尾标记的能力。因此,我们用句首标记和句尾标记来包装每条格言。
tokenizer = AutoTokenizer.from_pretrained('gpt2')
MAX_LEN = 100
train_m = ""
with open(train_path, "r", encoding='utf-8') as f:
for line in f.readlines():
if len(line.split())>MAX_LEN:
continue
train_m += (tokenizer.special_tokens_map['bos_token']+line.rstrip()+tokenizer.special_tokens_map['eos_token'])
with open(train_mod_path, "w", encoding='utf-8') as f:
f.write(train_m)
test_m = ""
with open(test_path, "r", encoding='utf-8') as f:
for line in f.readlines():
if len(line.split())>MAX_LEN:
continue
test_m += (tokenizer.special_tokens_map['bos_token']+line.rstrip()+tokenizer.special_tokens_map['eos_token'])
with open(test_mod_path, "w", encoding='utf-8') as f:
f.write(test_m)
训练
现在我们已经准备好并处理了数据集,让我们开始训练模型。来自某机构的Transformers库因其易于使用的API和惊人的预训练权重模型集合而成为显而易见的选择。得益于某机构,训练或微调模型是一件轻而易举的事。
我们首先加载GPT2的模型和分词器。
tokenizer = AutoTokenizer.from_pretrained('gpt2')
model = AutoModelWithLMHead.from_pretrained('gpt2')
就这样。在你编码时,巨大的预训练模型的全部力量就掌握在你手中。
现在,让我们定义一个加载数据集的函数。
def load_dataset(train_path,test_path,tokenizer):
train_dataset = TextDataset(
tokenizer=tokenizer,
file_path=train_path,
block_size=128)
test_dataset = TextDataset(
tokenizer=tokenizer,
file_path=test_path,
block_size=128)
data_collator = DataCollatorForLanguageModeling(
tokenizer=tokenizer, mlm=False,
)
return train_dataset,test_dataset,data_collator
train_dataset,test_dataset,data_collator = load_dataset(train_mod_path,test_mod_path,tokenizer)
现在我们有了数据集,让我们创建Trainer。这是处理所有训练过程的核心类。
from transformers import Trainer, TrainingArguments
training_args = TrainingArguments(
output_dir="./storage/gpt2-motivational_v6", #输出目录
overwrite_output_dir=True, #覆盖输出目录的内容
num_train_epochs=10, #训练轮数
per_gpu_train_batch_size=32, #训练批大小
per_gpu_eval_batch_size=64, #评估批大小
logging_steps = 500, #两次评估之间的更新步数
save_steps=500, #每#步后保存模型
warmup_steps=500,#学习率调度器的预热步数
)
trainer = Trainer(
model=model,
args=training_args,
data_collator=data_collator,
train_dataset=train_dataset,
eval_dataset=test_dataset,
prediction_loss_only=True,
)
到这个时候,我们已经拥有了开始训练所需的所有要素——模型、分词器和训练器。剩下要做的就是训练模型。训练完成后,我们只需要保存模型和分词器以备使用。
trainer.train()
trainer.save_model("./storage/gpt2-motivational_v6")
tokenizer.save_pretrained("./storage/gpt2-motivational_v6")
每个模型都在单个P5000 GPU上训练了约50轮,总共约12小时(不包括所有测试运行以及出现问题的运行)。
推理
我们已经训练并保存了模型。现在呢?为了进行推理,我们使用了某机构的另一个杰出特性——管道。这个很棒的特性让你只需几行代码就能将模型投入生产。
tokenizer = AutoTokenizer.from_pretrained("./storage/gpt2-motivational_v6")
model = AutoModelWithLMHead.from_pretrained("./storage/gpt2-motivational_v6")
gpt2_finetune = pipeline('text-generation', model=model, tokenizer=tokenizer)
# gen_kwargs有不同的选项,如max_length、beam_search选项、top-p、top-k等
gen_text = gpt2_finetune (seed, *gen_kwargs)
gen_kwargs配置文本生成。使用了k=50的top_k采样和p=0.95的top_p采样的混合方法。为了避免文本生成中的重复,使用了no_repeat_ngram_size = 3和repetition_penalty=1.2。
用户界面
现在我们已经训练好了核心模型,我们需要一种与它交互的方式。每次需要它生成内容时都写代码对用户不友好。所以,我们需要一个UI。我选择用ReactJS快速构建一个简单的UI。
虽然我无法将整个项目放到Github上,但我会将关键部分作为Github Gists发布。
出于UI的目的,我给三个模型起了代号——Inspiratobot(励志型)、Aristobot(严肃型)和FunnyBot(有趣型)。
UI的主要功能包括:
- 能够在三种不同风格之间进行选择
- 可以自己输入引语开头,也可以使用众多随机的起始种子之一(将自动填充)。如果不喜欢某个种子,点击随机按钮获取另一个种子。
- 在高级设置中,可以调整引语的最小和最大长度。也可以调整多样性(或采样中的温度参数)
- UI还使用了来自Unsplash的库存照片作为引语的背景,因为这是近来的潮流。它会从一系列适合当前生成引语风格的照片中进行选择。
- 还可以让你以1到5的等级为引语评分。
UI项目的文件夹结构如下:
|
+---public
| favicon.ico
| index.html
| logo.png
| logo.svg
| manifest.json
|
---src
App.js
customRatings.js # 心形评分组件
debug.log
index.js
quotes.css # 页面的CSS
quotes.js # 核心页面
theme.js
|
| .gitignore
| package-lock.json
| package.json
后端API服务器
为了托管和服务模型,我们需要一个服务器。为此,我选择了FastAPI,这是一个直截了当的Web框架,专门用于构建API。
由于它的极致简单性,我强烈推荐FastAPI。一个非常简单的API示例(来自文档)如下:
from typing import Optional
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
return {"item_id": item_id, "q": q}
不到10行代码就能为你的API搭建一个Web服务器,这听起来好得不像真的,但它确实如此。
不深入细节,API的关键部分是引语的生成,以下是相关代码。
def postprocess_gen_text(gen_text):
sentences = gen_text.split(".")
for i, sent in enumerate(sentences):
sent = ". ".join([s.strip() for s in sent.split(".")]).capitalize().strip()
sent = re.sub(r"\bi\b", "I", sent)
sent = re.sub(r"\bgod\b", "God", sent)
sent = re.sub(r"\bchrist\b", "Christ", sent)
sentences[i] = sent
return ". ".join(sentences).strip()
def generate_quote(model_name: ModelName, seed: str, gen_kwargs: dict):
global model
global tokenizer
global current_model_name
if model_name != current_model_name:
_load_model(model_name)
gpt2_finetune = pipeline(
"text-generation", model=model, tokenizer=tokenizer, device=-1,
)
gen_text = gpt2_finetune(seed, **gen_kwargs)[0]["generated_text"]
return postprocess_gen_text(gen_text)
项目的文件夹结构:
+---app
| | api.py
| | db_utils.py
| | enums.py
| | timer.py
| | init.py
| |
+---data
| funny_quotes.txt
| motivational_quotes.txt
| serious_quotes.txt
|
+---models
| +---gpt2-funny
| |
| +---gpt2-motivational
| |
| |---gpt2-serious
|
| app_config.yml
| logger.py
| logging_config.json
| main.py
| memory_profiler.log
| pipeline.log
| requirements.txt
部署
将应用程序容器化,启动一个EC2实例并进行托管,这本来会很容易。但我想要一个便宜(如果不是免费的话)的方案。另一个挑战是每个模型大约500MB,将它们加载到内存中会使RAM消耗徘徊在1GB左右。所以EC2实例成本会很高。除此之外,还需要在云端存储三个模型,这也需要存储成本。最重要的是,还需要一个数据库来存储用户给出的评分。
带着这些需求,我开始寻找产品/服务。为了让事情更简单,我将应用分成了两部分——一个后端API服务器和一个内部调用后端API的前端UI。
在搜索过程中,我偶然发现了一个开发者可以使用的免费服务的超棒列表。在评估了几个选项之后,我最终确定了以下选项,以使部署的总成本尽可能低:
- 后端API服务器 – Kintohub
- 前端托管 – Firebase(免费)
- 数据库 – MongoDB Atlas(免费)
KintoHub
“KintoHub是一个为全栈开发者设计的一体化部署平台”,主页上写道。如果成本不是问题,我本可以将整个应用程序部署在KintoHub上,因为这就是他们提供的服务——一个可以很好扩展的后端服务器、一个服务静态HTML文件的前端服务器以及一个数据库。在后台,他们通过一个非常易于使用的Web界面将应用程序容器化,并部署在选定配置的机器上。最棒的是,所有这些都可以通过Github完成。你将代码提交到Github仓库(公共或私有),并通过指向该仓库直接部署应用。
就必须要完成的最低限度设置而言,一个页面就足够了。
就是这样。当然还有更多可用设置,比如应用程序运行所需的内存等。虽然KintoHub有一个免费层,但我很快意识到,由于模型的内存消耗,我需要至少1.5GB才能使其运行而不崩溃。所以我转到了按需付费层,他们每月慷慨地赠送5美元信用额度。成本计算器显示应用程序托管将花费5.5美元,我可以接受(仍在等待月底看实际成本)。
Google Firebase
Firebase是很多东西的集合。它的主页上说它是一个完整的应用开发平台,事实也确实如此。但我们只对Firebase Hosting感兴趣,他们允许你免费托管单页网站。部署应用非常轻松。你需要做的就是:
- 安装firebase CLI
- 在你的React项目上运行
npm build - 从项目的根文件夹运行
firebase init,并将源路径从public指向build - 运行
firebase deploy
只需要一个非常简短的教程就能完成。
MongoDB Atlas
MongoDB Atlas是一个云数据库即服务,在云上提供MongoDB,好处是他们提供了一个具有512MB存储空间的免费层。对于我们的用例来说,这绰绰有余。
创建一个新的集群是直截了当的,一旦解决了访问问题,就可以使用像pymongo这样的Python包装器来实现我们的数据库连接。
成果
所有这些工作的成果是一个网站,你可以在其中与模型互动,生成格言并假装是自己的作品 😄。
虽然模型并不完美,但它仍然能产生一些真正让你思考的好格言。这不就是你对任何格言的期望吗?玩得开心。
免责声明
本项目中提及的公司名称仅用于技术背景描述,不构成任何形式的商业推广。相关技术实现和部署方案的选择基于作者的个人评估和项目需求。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)或者 我的个人博客 https://blog.qife122.com/
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)
更多推荐


所有评论(0)