简介
完整且准确的临床文档是跟踪患者护理的重要工具。它允许护理团队共享治疗方案,以辅助护理的连续性,并确保报销流程透明高效。
医生负责记录患者护理情况。传统的临床文档记录方式导致了较差的医患体验,减少了与患者互动的时间,并降低了工作与生活的平衡。医生花费大量时间在电脑前处理行政工作。结果,患者对整体体验不太满意,而接受多年医学培训的医生无法发挥其专业特长,且陷入职业倦怠。医生在诊疗期间每花一小时与患者直接交流,就会导致在电子健康记录(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)中最具挑战性的领域之一。它包含一组允许计算机理解或生成人类语言的算法。这些算法可以处理和分析来自不同来源(声音或文本)的海量自然语言数据,从而构建能够理解、分类甚至生成人类自然语言的模型。与其他 AI 领域一样,NLP 得益于深度学习(DL)的出现取得了显著进展,从而产生了在某些任务上达到人类水平的模型。
这些先进的 NLP 技术正被应用于医疗保健领域。在典型的医患就诊过程中,通过问答,医生会构建患者发病或症状发展的按时间顺序的描述。医生检查患者并做出临床决策,以确立诊断并制定治疗方案。这些对话以及 EHR 中的数据为医生生成临床文档(即医疗报告)提供了必要信息。
两个主要的 NLP 组件在自动化临床文档创建中发挥作用。第一个组件是自动语音识别(ASR),用于将语音转换为文本。它获取就诊的录音并生成对话转录(见图 2)。第二个组件是自动文本摘要,有助于从大型文本文档中生成摘要。该组件负责理解并捕获转录对话中的细微差别和最核心要素,最终形成叙述形式的报告(见图 3)、结构化报告或两者的组合。
我们将重点关注第二个组件——自动文本摘要,这是一项充满挑战的任务:
- 其性能取决于来自多位发言人的 ASR 质量(噪音输入)。
- 输入本质上是对话式的,并且包含通俗易懂的用语。
- 个人健康信息(PHI)法规限制了医疗数据的访问。
- 一个输出句子的信息可能分散在多个对话轮次中。
- 输入和输出之间没有明确的句子对齐。
- 各种医学专科、就诊类型和 EHR 系统构成了一个广泛且复杂的输出空间。
- 医生的问诊风格各异,对医疗报告有不同的偏好;没有统一标准。
- 标准的摘要评估指标可能与人类对质量的判断不同。

图 2:医患对话转录

图 3:AI 生成的医疗报告摘录。HPI 代表现病史。
使用 PyTorch 和 Fairseq 进行文本摘要
PyTorch 是由 Facebook 开发的开源机器学习框架,旨在帮助研究人员构建深度学习模型原型。Fairseq 工具包基于 PyTorch 构建,专注于序列生成任务,例如神经机器翻译(NMT)或文本摘要。Fairseq 拥有一个活跃的社区,不断提供最先进模型的参考实现。它内置了许多组件(模型架构、模块、损失函数和优化器),并可通过插件轻松扩展。
文本摘要构成了 NLP 中的重大挑战。我们需要能够生成文档简短版本,同时保留关键点并避免无关内容。这些挑战可以通过不同的方法来解决: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”的文件,其中包含
如果我们有一个如下输入
src = "Hello, I'm The Dogtor"
可能会发生我们的模型在训练时词汇表中没有“Dogtor”这个词。因此,当我们把这个序列输入模型时,它应该被转换为
src = "Hello, I'm The <unk-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 的倍数,以更好地使用 Tensor Core 并缩短训练时间。
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”中的最终报告集,其中“
模型部署
PyTorch 在建模方面提供了极大的灵活性和丰富的周边生态系统。然而,尽管最近有几篇文章指出 PyTorch 在科研和学术界的使用可能接近甚至超过 TensorFlow,但人们似乎普遍认为 TensorFlow 仍然是生产部署的首选平台。2021 年情况依然如此吗?寻求将 PyTorch 模型投入生产的团队有几种选择。
在描述我们的历程之前,让我们简要地定义一下“模型”这个术语。
作为计算图的模型
几年前,机器学习工具包仅支持特定类别的模型是很常见的,这些模型具有相当固定和僵化的结构,且只有少数自由度(如支持向量机的核函数或神经网络的隐藏层数量)。受 Theano 基础工作的启发,像微软的 CNTK 或谷歌的 TensorFlow 等工具包率先推广了一种更灵活的模型视图:即作为具有相关参数的计算图,这些参数可以根据数据进行估计。这种观点模糊了流行模型类型(如 DNN 或 SVM)之间的界限,因为将每种模型的特征混合到你的图类型中变得很容易。尽管如此,这种图在估计参数之前必须预先定义,而且非常静态。这使得将模型保存到一个独立的包(如 TensorFlow SavedModel)变得很容易(这样的包只需包含图的结构以及估计参数的具体值)。然而,调试此类模型可能很困难,因为构建图的 Python 代码语句与执行它的行在逻辑上是分开的。研究人员也渴望有更简单的方式来表达动态行为,例如模型的正向传播计算步骤条件依赖于其输入数据(或其先前的输出)。
最近,上述限制导致了以 PyTorch 和 TensorFlow 2 为首的第二次革命。计算图不再被显式定义。相反,当 Python 代码在张量参数上执行操作时,它会被隐式填充。推动这一发展的核心技术是自动微分。由于计算图是在执行正向传播步骤时隐式构建的,因此所有必要的数据都将被跟踪,以便后续计算关于模型参数的梯度。这为训练模型提供了极大的灵活性,但也提出了一个重要问题:如果模型内部发生的计算仅通过我们 Python 代码在执行具体数据时的步骤来隐式定义,那么我们想要保存的模型究竟是什么?答案——最初——是包含所有依赖项的 Python 代码,连同估计出的参数。出于实际原因,这是不可取的。例如,存在一种危险,即负责模型部署的团队可能无法完全重现训练期间使用的 Python 代码依赖项,从而导致细微的行为差异。解决方案通常包括结合两种技术:脚本编写(Scripting)和追踪(Tracing),即在 Python 代码中进行额外注释,并对示例输入数据执行代码,从而允许 PyTorch 定义并保存应在随后对新数据进行推理时执行的图。这要求创建模型代码的人员具备一定的纪律(这可以说是抵消了即时执行的部分原有灵活性),但它最终会产生 TorchScript 格式的自包含模型包。TensorFlow 2 中的解决方案也非常相似。
服务我们的报告生成模型
我们在部署报告生成模型方面的历程反映了上述讨论。我们最初是通过在自定义 Docker 镜像中部署模型代码及其依赖项,连同参数检查点(checkpoint),并暴露 gRPC 服务接口来服务我们的模型的。然而,我们很快发现,在估计参数时复制建模团队使用的确切代码和环境非常容易出错。此外,这种方法使我们无法利用 NVIDIA Triton 等高性能模型服务框架,该框架是用 C++ 编写的,需要无需 Python 解释器即可使用的自包含模型。在这个阶段,我们在尝试将 PyTorch 模型导出为 ONNX 格式还是 TorchScript 格式之间面临选择。ONNX 是一种用于表示机器学习模型的开放规范,正日益得到采用。它由微软开发的高性能运行时(ONNX Runtime)提供支持。虽然我们能够使用 ONNX Runtime 为基于 TensorFlow BERT 的模型实现性能加速,但当时我们的一个 PyTorch 模型需要一些 ONNX 尚不支持的算子。我们决定暂时研究 TorchScript,而不是实现自定义算子。
成熟的生态系统
这是一帆风顺吗?不,这比我们预期的要艰难得多。我们遇到了 PyTorch 在直接服务代码时所使用的 MKL 库中似乎存在的内存泄漏。我们在尝试从多个线程加载多个模型时遇到了死锁。我们在将模型导出为 ONNX 和 TorchScript 格式时遇到了困难。模型在具有多个 GPU 的硬件上无法开箱即用,它们总是访问导出时所在的特定 GPU 设备。我们在使用 Triton 推理服务器服务 TorchScript 模型时遇到了内存使用过高的问题,后来发现这是因为在正向传播过程中意外启用了自动微分。然而,生态系统在不断改进,并且有一个乐于助人的活跃开源社区,渴望与我们合作减轻此类问题。
接下来怎么办?对于那些需要直接服务 PyTorch 代码的灵活性,而不经过导出自包含模型这一额外步骤的用户来说,值得指出的是,TorchServe 项目现在提供了一种将代码与参数检查点捆绑成单个可服务归档文件的方法,大大降低了代码和参数脱节的风险。然而,对我们而言,将模型导出为 TorchScript 已被证明是有益的。它在建模团队和部署团队之间提供了一个明确的接口,并且 TorchScript 通过其即时编译引擎进一步减少了在 GPU 上服务模型时的延迟。
大规模扩展与未来
最后,高效的云端部署不仅仅是高效计算单个模型实例的响应。在管理、版本控制和更新模型方面需要灵活性。必须通过负载均衡、水平扩展和垂直扩展等技术实现高水平的可扩展性。如果涉及许多模型,缩减至零(scale-to-zero)很快就会成为一个课题,因为为不响应任何请求的模型支付服务费用是不可接受的。在像 Triton 这样的底层推理服务器之上提供这种额外的功能是编排框架的工作。在获得了一些 KubeFlow 的初步经验后,我们决定将注意力转向 Azure ML,它提供了类似的功能,但与我们已经严重依赖的 Azure 平台集成得更深,这对我们庞大的技术栈至关重要。我们的这段旅程才刚刚开始。
结论
学术界早就认识到我们是“站在巨人的肩膀上”。随着人工智能从一门科学学科成熟为一种技术,最初推动其科学基础的同样协作精神也延伸到了软件工程领域。开源爱好者与全球科技公司携手合作,构建开放的软件生态系统,从而能够以新的视角解决现代社会中最紧迫的一些挑战。在本文中,我们介绍了 Nuance 的 Dragon Ambient eXperience,这是一种由人工智能驱动、支持语音的解决方案,可以自动记录患者护理过程,减少医护人员的行政负担。Nuance DAX 改善了医患体验,减少了医生职业倦怠,并改善了财务成果。它让医疗服务回归信任、快乐和人文关怀。Fairseq 和 PyTorch 已被证明是支持这项 AI 技术的绝佳平台,反过来,Nuance 也回馈了一些其在该领域的创新成果。如需深入阅读,我们邀请您查看我们最近发表的 ACL 出版物和 Nuance 的“What’s Next”博客。