引言
完整准确的临床文档是跟踪患者护理的重要工具。它使得护理计划能够在护理团队之间共享,有助于护理的连续性,并确保报销过程透明高效。
医生负责记录患者护理。传统的临床文档方法导致患者-医生体验不佳,与患者互动时间减少,工作与生活平衡下降。医生的大量时间花在电脑前处理管理任务。结果是,患者对整体体验满意度较低,而经过多年医学学习的医生无法充分发挥其专业能力,并因此精疲力竭。医生每提供一小时直接面对患者的临床时间,就会在门诊时间内额外花费近两小时进行 EHR 和案头工作。在办公室之外的时间,医生每晚还需要花费 额外的 1 到 2 小时个人时间 进行电脑和其他文书工作。
- 42% 的医生报告经历过职业倦怠。– Medscape
- 由于疫情,这个问题更加严重,目前 64% 的美国医生报告职业倦怠。- AAFP
- “太多繁琐的任务,例如记录和文书工作”是导致职业倦怠的主要因素,电脑化排名第四。 - Medscape
- 75% 的美国消费者希望他们的医疗体验更个性化,- Business Wire
- 如果沟通体验更个性化,61% 的患者会更频繁地去看医生。 – Business Wire
医生职业倦怠是 医疗错误、医疗事故诉讼、人员流动增加以及护理可及性下降的主要原因之一。职业倦怠导致医疗费用增加和整体患者满意度下降。职业倦怠每年给美国造成 46 亿美元的损失。
我们能做些什么来恢复医疗服务的信任、快乐和人性?很大一部分管理工作包括将患者数据输入电子健康记录 (EHR) 并创建临床文档。临床文档是根据 EHR 中已有的信息以及患者与医生交流的对话创建的。
本文将展示 Nuance Dragon Ambient eXperience (DAX),一种由人工智能驱动、语音启用、环境临床智能解决方案,如何在护理点自动准确高效地记录患者就诊信息,以及实现这一目标所使用的技术。
Nuance DAX 提升了护理质量和患者体验,提高了医生的效率和满意度,并改善了财务结果。它可用于办公室和远程医疗环境,适用于所有门诊专科,包括初级护理和急症护理。
自然语言处理
自然语言处理 (NLP) 是人工智能 (AI) 领域中最具挑战性的领域之一。它包含一系列算法,使计算机能够理解或生成人类使用的语言。这些算法可以处理和分析来自不同来源(声音或文本)的大量自然语言数据,以构建能够理解、分类甚至像人类一样生成自然语言的模型。与其他人工智能领域一样,NLP 取得了显著进展,这归功于深度学习 (DL) 的出现,它使得模型在某些任务上取得了与人类相当的结果。
这些先进的 NLP 技术正应用于医疗保健领域。在典型的患者-医生就诊过程中,医生通过问答构建患者现有疾病或症状发展的时间顺序描述。医生检查患者并做出临床决策以确立诊断并制定治疗计划。这种对话以及 EHR 中的数据为医生生成临床文档(即医疗报告)提供了所需信息。
两个主要的 NLP 组件在自动化创建临床文档方面发挥作用。第一个组件是自动语音识别 (ASR),用于将语音转换为文本。它获取就诊的音频记录并生成对话转录本(参见图 2)。第二个组件是自动文本摘要,用于从大型文本文档中生成摘要。该组件负责理解并捕捉转录对话中的细微之处和最重要方面,将其转化为叙事形式(参见图 3)、结构化形式或两者结合的最终报告。
我们将重点关注第二个组件,即自动文本摘要,这是一项艰巨的任务,存在许多挑战
- 其性能与多说话人的 ASR 质量相关(噪声输入)。
- 输入本质上是对话式的,包含非专业术语。
- 受保护健康信息 (PHI) 法规限制医疗数据访问。
- 一个输出句子所需的信息可能分散在多个对话轮次中。
- 输入和输出之间没有明确的句子对齐。
- 各种医学专科、就诊类型和 EHR 系统构成了一个广泛而复杂的输出空间。
- 医生进行就诊的方式各不相同,对医疗报告也有自己的偏好;没有统一的标准。
- 标准摘要度量可能与人类对质量的判断不同。
图 2:患者-医生对话转录本
图 3:AI 生成医疗报告摘录。HPI 代表现有疾病史 (History of present illness)。
使用 PyTorch 和 Fairseq 进行文本摘要
PyTorch 是一个由 Facebook 开发的开源机器学习框架,可帮助研究人员快速构建深度学习模型。 Fairseq 工具包构建在 PyTorch 之上,专注于序列生成任务,如神经机器翻译 (NMT) 或文本摘要。Fairseq 拥有一个活跃的社区,不断提供最先进模型的参考实现。它包含许多内置组件(模型架构、模块、损失函数和优化器),并且易于通过插件扩展。
文本摘要是自然语言处理中的一个重大挑战。我们需要能够生成文档简短版本同时保留关键点并避免无信息内容的能力模型。这些挑战可以通过不同的方法解决。1). 抽象文本摘要旨在训练能够生成叙事形式摘要的模型。2). 抽取方法,训练模型从输入文本中选择最重要的部分。3). 两者的结合,从输入中选择关键部分,然后以抽象方式进行摘要。因此,摘要可以通过单一的端到端网络实现,也可以作为抽取和抽象组件的管道实现。为此,Fairseq 提供了所有必要的工具来帮助我们成功。它提供了经典的 Transformer、不同类型的语言模型和预训练版本等端到端模型,使研究人员能够专注于最重要的事情——构建生成有价值报告的最先进模型。
然而,我们不仅仅是摘要转录的对话;我们正在生成高质量的医疗报告,这有许多需要考虑的事项。
- 医疗报告的每个部分在内容、结构、流畅度等方面都不同。
- 对话中提到的所有医学事实都应出现在报告中,例如,特定的治疗或剂量。
- 在医疗领域,词汇量很大,模型需要处理医学术语。
- 患者与医生对话通常比最终报告长得多。
所有这些挑战都需要我们的研究人员进行一系列广泛的实验。感谢 PyTorch 和 Fairseq 的灵活性,他们的生产力大大提高。此外,该生态系统提供了一条从构思、实现、实验到最终产品发布的便捷路径。使用多个 GPU 或 CPU 只需为工具提供一个额外的参数,并且由于紧密的 Python 集成,PyTorch 代码可以轻松进行调试。
在我们持续为开源社区做贡献的努力中,Nuance 开发了一些功能并将其推送到 Fairseq 的 GitHub 仓库。这些功能旨在克服一些挑战,例如,促进从输入到摘要复制尤其是罕见或未见过的词语,通过提高 Tensor Core 利用率来加快训练速度,并确保不同 Transformer 配置的 TorchScript 兼容性。接下来,我们将展示如何训练一个带指针生成器机制 (Transformer-PG) 的 Transformer 模型,该模型可以从输入中复制词语。
如何构建带指针生成器机制的 Transformer 模型
本分步指南假设用户已安装 PyTorch 和 Fairseq。
1. 创建词汇表并用源位置标记进行扩展
这些标记将允许模型指向输入序列中的任何词语。
vocab_size=<vocab_size>
position_markers=512
export LC_ALL=C
cat train.src train.tgt |
tr -s '[:space:]' '\n' |
sort |
uniq -c |
sort -k1,1bnr -k2 |
head -n "$((vocab_size - 4))" |
awk '{ print $2 " " $1 }' > dict.pg.txt
python3 -c "[print('<unk-{}> 0'.format(n)) for n in range($position_markers)]" >> dict.pg.txt
这将创建一个名为“dict.pg.txt”的文件,其中包含 <vocab_size> 个最常出现的词,后跟 512 个位置标记,命名从“<unk-0>”到“<unk-511>”。
如果我们有一个输入,比如
src = "Hello, I'm The Dogtor"
可能我们的模型在训练时词汇表中没有“Dogtor”这个词。因此,当我们把这个序列输入模型时,它应该被转换为
src = "Hello, I'm The <unk-3>"
现在,“<unk-3>”是我们词汇表的一部分,并且可以被模型预测(这就是指针生成器的作用)。在这种情况下,我们只需要对输出进行后处理,将“<unk-3>”替换为输入位置 3 的词语。
2. 预处理文本数据以将其未知词语替换为其位置标记
我们可以使用 https://github.com/pytorch/fairseq/tree/master/examples/pointer_generator 中的脚本。
# Considering we have our data in:
# train_src = /path/to/train.src
# train_tgt = /path/to/train.tgt
# valid_src = /path/to/valid.src
# valid_tgt = /path/to/valid.tgt
./preprocess.py --source /path/to/train.src \
--target /path/to/train.tgt \
--vocab <(cut -d' ' -f1 dict.pg.txt) \
--source-out /path/to/train.pg.src \
--target-out /path/to/train.pg.tgt
./preprocess.py --source /path/to/valid.src \
--target /path/to/valid.tgt \
--vocab <(cut -d' ' -f1 dict.pg.txt) \
--source-out /path/to/valid.pg.src \
--target-out /path/to/valid.pg.tgt
./preprocess.py --source /path/to/test.src \
--vocab <(cut -d' ' -f1 dict.pg.txt) \
--source-out /path/to/test.pg.src
3. 现在让我们对数据进行二值化,以便处理更快
fairseq-preprocess --task "translation" \
--source-lang "pg.src" \
--target-lang "pg.tgt" \
--trainpref /path/to/train \
--validpref /path/to/valid \
--srcdict dict.pg.txt \
--cpu \
--joined-dictionary \
--destdir <data_dir>
您可能会注意到任务类型是“translation”。这是因为没有“summarization”任务可用;我们可以将其理解为一种 NMT 任务,其中输入和输出语言共享,且输出(摘要)比输入短。
4. 现在我们可以训练模型了
fairseq-train <data_dir> \
--save-dir <model_dir> \
--task "translation" \
--source-lang "src" \
--target-lang "tgt" \
--arch "transformer_pointer_generator" \
--max-source-positions 512 \
--max-target-positions 128 \
--truncate-source \
--max-tokens 2048 \
--required-batch-size-multiple 1 \
--required-seq-len-multiple 8 \
--share-all-embeddings \
--dropout 0.1 \
--criterion "cross_entropy" \
--optimizer adam \
--adam-betas '(0.9, 0.98)' \
--adam-eps 1e-9 \
--update-freq 4 \
--lr 0.004 \
# Pointer Generator
--alignment-layer -1 \
--alignment-heads 1 \
--source-position-markers 512
此配置使用了 Nuance 回馈给 Fairseq 的功能
- 带指针生成器机制的 Transformer,以便于从输入复制词语。
- 序列长度填充为 8 的倍数,以便更好地利用张量核并减少训练时间。
5. 现在让我们看看如何使用我们新的医疗报告生成系统生成摘要
import torch
from examples.pointer_generator.pointer_generator_src.transformer_pg import TransformerPointerGeneratorModel
# Patient-Doctor conversation
input = "[doctor] Lisa Simpson, thirty six year old female, presents to the clinic today because " \
"she has severe right wrist pain"
# Load the model
model = TransformerPointerGeneratorModel.from_pretrained(data_name_or_path=<data_dir>,
model_name_or_path=<model_dir>,
checkpoint_file="checkpoint_best.pt")
result = model.translate([input], beam=2)
print(result[0])
Ms. <unk-2> is a 36-year-old female who presents to the clinic today for evaluation of her right wrist.
6. 或者,我们可以使用 fairseq-interactive 和后处理工具将位置未知标记替换为其在输入中的词语
fairseq-interactive <data_dir> \
--batch-size <batch_size> \
--task translation \
--source-lang src \
--target-lang tgt \
--path <model_dir>/checkpoint_last.pt \
--input /path/to/test.pg.src \
--buffer-size 20 \
--max-len-a 0 \
--max-len-b 128 \
--beam 2 \
--skip-invalid-size-inputs-valid-test | tee generate.out
grep "^H-" generate.out | cut -f 3- > generate.hyp
./postprocess.py \
--source <(awk 'NF<512' /path/to/test.pg.src) \
--target generate.hyp \
--target-out generate.hyp.processed
现在我们在“generate.hyp.processed”中得到了最终的报告集,其中“<unk-N>”被输入序列中的原始词语替换。
模型部署
PyTorch 在建模方面提供了极大的灵活性,并拥有丰富的周边生态系统。然而,尽管最近几篇文章表明 PyTorch 在研究和学术领域的使用可能接近超越 TensorFlow,但总体感觉 TensorFlow 似乎是用于生产部署的首选平台。到 2021 年,情况还是这样吗?希望在生产环境中提供其 PyTorch 模型的团队有几种选择。
在描述我们的历程之前,让我们先短暂地绕道一下,定义“模型”一词。
作为计算图的模型
几年前,机器学习工具包仍然普遍只支持特定类别的模型,这些模型的结构相当固定和僵化,只有少量自由度(例如支持向量机的核或神经网络的隐藏层数量)。受 Theano 中基础工作的启发,Microsoft 的 CNTK 或 Google 的 TensorFlow 等工具包率先推广了一种更灵活的模型视图,即具有相关参数的计算图,这些参数可以从数据中估计。这种视图模糊了流行模型类型(如 DNNs 或 SVMs)之间的界限,因为可以轻松地将每种模型的特征混合到您的图类型中。尽管如此,这样的图仍然必须在估计其参数之前预先定义,而且它是相当静态的。这使得将模型保存到独立的捆绑包中(例如 TensorFlow SavedModel)变得容易(这样的捆绑包仅包含图的结构以及估计参数的具体值)。然而,调试这样的模型可能很困难,因为构建图的 Python 代码中的语句在逻辑上与执行它的行是分开的。研究人员也渴望更简单的方式来表达动态行为,例如模型的前向传播的计算步骤有条件地依赖于其输入数据(或其先前的输出)。
最近,上述限制促成了以 PyTorch 和 TensorFlow 2 为首的第二次革命。计算图不再明确定义。相反,它将在 Python 代码对张量参数执行操作时隐式填充。推动这一发展的一项重要技术是自动微分。由于计算图在前向传播步骤执行时隐式构建,因此将跟踪所有必要的数据,以便稍后计算关于模型参数的梯度。这使得模型训练具有极大的灵活性,但也提出了一个重要问题。如果模型内部发生的计算仅仅通过我们的 Python 代码在执行具体数据时的步骤隐式定义,那么我们想要保存的模型是什么?答案——至少最初是——包含所有依赖项的 Python 代码,以及估计的参数。出于实际原因,这是不可取的。例如,负责模型部署的团队可能无法完全复制训练期间使用的 Python 代码依赖项,从而导致行为出现细微差异。解决方案通常包括结合两种技术:脚本化和追踪,即在 Python 代码中添加额外注释,以及在示例输入数据上执行代码,从而允许 PyTorch 定义和保存应在之后对新的、未见数据进行推理时执行的图。这要求创建模型代码的人员有一定的规范(这可能会使急切执行的原始灵活性有所丧失),但它会生成 TorchScript 格式的独立模型捆绑包。TensorFlow 2 中的解决方案非常相似。
提供我们的报告生成模型服务
我们在部署报告生成模型方面的历程反映了上述讨论。我们最初通过在定制 Docker 镜像中部署模型代码及其依赖项以及参数检查点,并通过 gRPC 服务接口提供模型服务。然而,我们很快注意到,复制模型团队在估计参数时使用的确切代码和环境容易出错。此外,这种方法使我们无法利用高性能模型服务框架,例如 NVIDIA 的 Triton,它使用 C++ 编写,并且需要无需 Python 解释器即可使用的独立模型。在这一阶段,我们面临着将 PyTorch 模型导出为 ONNX 或 TorchScript 格式的选择。ONNX 是一种用于表示机器学习模型的开放规范,其采用率正在不断提高。它由 Microsoft 开发的高性能运行时 (ONNX Runtime) 提供支持。虽然我们使用 ONNX Runtime 为我们的 TensorFlow BERT 模型实现了性能加速,但在当时,我们的某个 PyTorch 模型需要一些 ONNX 尚不支持的运算符。我们决定暂时研究 TorchScript,而不是使用自定义运算符来实现这些运算符。
日益成熟的生态系统
一切都顺利吗?不,这比我们预期的要坎坷。我们在直接提供 PyTorch 代码服务时遇到了 PyTorch 使用的 MKL 库中的内存泄漏问题。在尝试从多个线程加载多个模型时,我们遇到了死锁。我们将模型导出到 ONNX 和 TorchScript 格式时遇到了困难。模型无法在带有多个 GPU 的硬件上开箱即用,它们总是访问导出时所在的特定 GPU 设备。我们在 Triton 推理服务器中提供 TorchScript 模型服务时遇到了过多的内存使用,我们发现这是由于前向传播期间意外启用了自动微分。然而,生态系统正在不断改进,有一个乐于助人且充满活力的开源社区渴望与我们合作以缓解此类问题。
接下来何去何从?对于那些需要直接提供 PyTorch 代码服务的灵活性,而无需经历导出独立模型的额外步骤的用户,值得指出的是,TorchServe 项目现在提供了一种将代码与参数检查点捆绑到单个可服务存档中的方式,这大大降低了代码和参数分离的风险。然而,对我们来说,将模型导出到 TorchScript 已被证明是有益的。它在建模团队和部署团队之间提供了清晰的接口,并且 TorchScript 通过其即时编译引擎进一步降低了在 GPU 上提供模型服务时的延迟。
大规模扩展和未来
最后,高效部署到云端不仅仅是有效计算单个模型实例的响应。还需要灵活性来管理、版本控制和更新模型。必须通过负载均衡、横向扩展和纵向扩展等技术实现高层次的可伸缩性。如果涉及的模型很多,缩放至零 (scale-to-zero) 很快就会成为一个话题,因为为不响应任何请求的模型提供服务是不可接受的。在 Triton 等低级别推理服务器之上提供此类额外功能是编排框架的工作。在初步体验 KubeFlow 后,我们为此决定将注意力转向 Azure ML,它提供了类似的功能,但与 Azure 平台集成更深入,而我们的大部分技术栈都已经严重依赖于 Azure 平台。我们这部分的历程才刚刚开始。
结论
学术界早已认识到,我们是“站在巨人的肩膀上”。随着人工智能从一门科学学科成熟为一项技术,最初为其科学基础注入活力的合作精神已延续到软件工程领域。世界各地的开源爱好者加入科技公司,共同构建开放软件生态系统,为解决现代社会最紧迫的一些挑战提供了新的视角。在本文中,我们研究了 Nuance 的 Dragon Ambient eXperience,这是一款由人工智能驱动、语音启用的解决方案,可自动记录患者护理,从而减轻医疗服务提供者的管理负担。Nuance DAX 改善了患者与医生的体验,减轻了医生职业倦怠,并改善了财务成果。它将信任、快乐和人性重新带回医疗服务。Fairseq 和 PyTorch 已被证明是为这项人工智能技术提供动力的令人难以置信的平台,而 Nuance 也反过来贡献了其在该领域的一些创新。如需进一步阅读,我们邀请您阅读我们最近的 ACL 出版物 和 Nuance 的“What’s Next”博客。