面向医疗保健的-Keras-和-TensorFlow2-人工智能教程-四-

109 阅读58分钟

面向医疗保健的 Keras 和 TensorFlow2 人工智能教程(四)

原文:AI for Healthcare with Keras and Tensorflow 2.0

协议:CC BY-NC-SA 4.0

九、机器有所有的答案,除了生命的目的是什么

到目前为止,我们已经谈了这么多。向我们致敬。在过去的所有案例中,你都知道你在寻找什么,结果会是什么,无论是高危患者的再入院率,ICD-9 编码预测,还是肿瘤识别。但有时我们有的只是问题,我们不知道可能的答案。无论是医疗保健还是任何其他行业,研究、公司文档或任何公共信息中都嵌入了如此多的知识,有时我们会意识不到,因为浏览如此广泛的信息集会变得有些不知所措。所以,让我们让机器开始工作吧。

在本章中,我将简要回顾问答系统是如何构建的,然后您将使用新冠肺炎数据集为自己构建一个问答系统。我们都面临着疫情的情况,我们对目前药物的副作用以及共病如何影响治疗知之甚少。此外,如果没有像问答这样的技术,理解在如此短的时间内嵌入到数百万个文档中的关于 SARS 病毒家族的信息是不可能的。所以让我们直接进入它。

介绍

医疗保健行业的工作人员以及普通公众需要通过一个系统快速有效地访问生物医学信息,该系统能够理解复杂的生物医学概念,并能够找到最佳文档来支持特定的响应。

问答领域的研究,尤其是生物医学问答,受到了各种竞赛和会议的推动,如 **TREC ** **文本检索会议(TREC)**BioASQ。 BioASQ 组织生物医学语义索引和问答(QA)挑战。这些挑战包括与分层文本分类、机器学习、信息检索、来自文本和结构化数据的 QA、多文档摘要和许多其他领域相关的任务。

**在医疗保健的各个领域,如科学(CORD-19)、临床(emrQA)或消费者健康(MediQA、LiveQA-Med),通过独立研究和竞争发布了各种数据集。

尽管人们对此很感兴趣,但仍面临许多挑战:

  • 小型且不复杂的数据集:与 SQUAD v1 和 v2 数据集(通用领域)相比,大多数可用数据集的规模较小,通常不需要复杂的推理。

  • 本体和知识库未被利用 : NCBI 和 BioPortal 托管了一堆与医疗领域相关的本体和知识图,但通常一个独立的基于深度学习的解决方案无法利用它们。某些最近的论文正在出现,它们丰富了训练时的嵌入,或者使用现有的知识库对检索到的文档进行重新排序。

  • 缺乏可解释性:由于医疗保健领域的性质,有时对特定答案的解释可以帮助用户更好地理解推理,并相应地树立信心。

总的来说,问答系统大致有四种主要类型:

  • 开放/封闭领域:在检索器(信息检索)和阅读器/生成器(机器理解)框架中,来自知识源的大量段落被编码并存储在内存中。检索模型能够查询存储器以识别与问题嵌入具有最大内积的顶部相关段落。

  • 知识库:将查询转换为 RDF 三元组,并基于知识或本体(如 DbPedia 或语义图)回答问题。

  • 问题蕴涵:重用来自训练数据库中类似问题的答案来制定响应。

  • 视觉问答:从图像中回答问题。

你将在一个基于 IR 的 QA 系统上工作。这些系统从大量文档中查找并提取文本片段,并且是真实世界 QA 的最接近的实现,因为您首先决定从哪些文档中查找答案,然后查找答案。

除非你生活在岩石下,否则你一定用过谷歌。让我们在 Google 上搜索一个问题,它非常“简单”,是一个最近集成了 QA 功能的 IR 系统。见图 [9-1 。

img/502837_1_En_9_Fig1_HTML.jpg

图 9-1

谷歌搜索

如您所见,查询产生了

  • 链接列表,其中每个链接的相关段落突出显示了某些关键字

  • 给出实际答案的片段框

后端的 Google 使用多种技术来

  • 检索文档

  • 突出那些文档中的重要关键词/段落

  • 给出最终答案

  • 从给定的查询重构多个查询

  • 搜索历史

但是对于我们这些普通人来说,我们可以简单地理解 IR-QA 的工作方式,如图 9-2 所示。

  • 检索器充当搜索引擎,对相关文档进行排序和检索。

  • 理解通常是一个 seq-seq 模型,它试图从上下文中概率性地识别出哪个短语在给定的问题中最有可能被看到,是的,你猜对了:这通常使用问答数据集来完成。

img/502837_1_En_9_Fig2_HTML.jpg

图 9-2

IR-QA 系统流程图

获取数据

您将使用 CORD-19 数据集来构建您的问答模型。它包括超过 400,000 篇关于新冠肺炎、新型冠状病毒和相关冠状病毒的学术文章。您可以通过在 www.kaggle.com/allen-institute-for-ai/CORD-19-research-challenge .报名参加 CORD-19 研究挑战比赛,从 Kaggle 获得该数据集

CORD-19 数据集带有metadata.csv,这是一个记录 CORD-19 数据集中所有可用论文基本信息的文件。这是一个开始探索的好地方!

除了元数据,Kaggle 上的document_parses文件夹中还有取自 PubmedCentral 和 pdf(研究期刊-微软)的全文文章。这些是 JSON 文件,包含有关全文文章/PDF 的信息,如 SHA-ID、作者列表、摘要段落列表、全文、参考书目等。

对于这个案例研究,您将只使用metadata.csv,它包含一篇文章的标题和摘要信息。你可以很容易地扩展你在这里学到的原则,包括全文的段落。这是你应该尝试的事情。

所以现在,从 Kaggle 下载metadata.csv并把它放在工作目录的./Data文件夹中。

Note

注意metadata.readme的出现。这将跟踪数据经历的变化。这些数据由 Allen AI 和其他合作者共同维护。

加载元数据并查看存在的内容:

import os
import pandas as pd
    data_dir = "./Data/"
    metadata_path = os.path.join(data_dir,"metadata.csv")
    metadata_df = pd.read_csv(metadata_path, dtype={'Microsoft Academic Paper ID': str, 'pubmed_id': str})
    metadata_df = metadata_df.dropna(subset=['abstract', 'title']).reset_index(drop=True)
    metadata_df = metadata_df.drop_duplicates(['abstract', 'title']).reset_index(drop = True)

在这里加载元数据,确保全文 id 存储为字符串。删除缺少摘要和标题或在摘要和标题级别重复的行。这可能是因为主文档可以有多个信息源或引用多个文档(全文)。但是您可以忽略这些细节,继续进行分析。

保留我们在这个案例研究中关注的列。

#Subsetting Columns
    final_metadata = metadata_df[['abstract', 'title']]
    final_metadata["id"] = [str(i) for i in range(final_metadata.shape[0])]

对于那些计划整合全文以及摘要和标题的人,请访问 https://github.com/allenai/cord19#metadatacsv-overview 以了解不同栏目的含义。

因为您正在处理基于 transformer 的语言模型,所以您知道它们只捕获有限的上下文,最大长度固定为 512 个标记。这意味着如果摘要超过 512 个标记,它将不会被完全使用,剩余的标记将会丢失。此外,这 512 个标记不是从空白分割中得到的,而是转换器自己的内部标记化机制(BERT 架构的 wordpiece)和它使用的词汇。

为了处理这个问题,您将运行一个固定长度和步幅的窗口,并将摘要分成几个较小的块,以根据窗口长度来捕获上下文。图 9-3 显示了如何创建数据以便检索和理解。

因为您将使用针对 MedNLI 进行了微调的 Covid-BERT,所以您将加载它以决定标记化长度(在“检索机制”一节中讨论)。现在,你可以把它想象成一个用于从文本中编码信息的 BERT 模型。

img/502837_1_En_9_Fig3_HTML.jpg

图 9-3

在摘要上运行窗口以防止上下文的突然丢失

您可以从 https://huggingface.co/Darkrider/covidbert_mednli 下载一个预训练的模型,并在您的工作目录下创建一个名为pretrained_model的文件夹并保存在那里,或者您可以直接将字符串/Darkrider/covidbert_mednli传递给AutoTokenizer.from_pretrained().

然后,您可以使用拥抱脸的变形金刚包加载模型。

from nltk.tokenize import sent_tokenize
import numpy as np
from transformers import AutoTokenizer
    TOKENIZER = AutoTokenizer.from_pretrained('./pre_trained_model/training_nli_covidbert-mednli/0_Transformer')
    MAX_LEN = 300
    STRIDE = 1

接下来,编写一个将摘要分成不同段落的函数。

    def get_para_segments(text, stride, max_len, id_, title, tokenizer):
        """
        Get Running length window of certain length with a particular stride
        """
#     tokenizer = AutoTokenizer.from_pretrained('./pre_trained_model/training_nli_covidbert-mednli/0_Transformer')
    text_map = {i:sent for i, sent in enumerate(sent_tokenize(text))}
    text_lenmap = {i:len(input_id) for i,input_id in enumerate(tokenizer(list(text_map.values()))['input_ids'])}

    para = []
        i = 0
        if len(text_map) > 1:
        while i < len(text_map):
            for j in text_map.keys():
                if j > i:
                    new_para_sub_len = np.sum(list(text_lenmap.values())[i:j])
                        if j == (len(text_map) -1):
                            para.append("".join(list(text_map.values())[i:(j+1)]))
                            i = 999999 # some big value
                    if new_para_sub_len <= max_len:
                        continue
                    else:
                            para.append( "".join(list(text_map.values())[i:j]))
                        i = i+stride
    else:
            para.append(text_map[0])
    # at least 5 words should be there in the paragraph
    para = [paragraph for paragraph in para if len(paragraph.split()) > 5]
    return [[id_, str(id_) + "_" + str(i), title, paragraph] for i,paragraph in enumerate(para)]

上面的代码中主要发生了三件事:

  1. 您为摘要的每个句子提供一个 ID。这些句子是从 nltk 的句子标记化得到的。

  2. 您还可以使用相同的 ID 创建一个映射,并从 BERT 模型中获得标记化后的长度。

  3. 你继续迭代每个句子,直到你达到最大长度或者是可能的句子的结尾。

最后,在元数据数据帧块上调用该函数。但是在您这样做之前,请确保您在Data文件夹中创建了一个名为passage的文件夹。

Pickle 用于将 Python 对象序列化为字节流(1 和 0)。这使得将数据加载到您的工作环境变得容易。

from tqdm import tqdm
import pickle

    for i,df in enumerate(np.array_split(final_metadata, 10)):
    print(i)
        passage_list = [get_para_segments(row["abstract"],STRIDE, MAX_LEN,row["id"], row["title"], TOKENIZER) for i,row in tqdm(df.iterrows())]
        with open('./Data/passage/passage_'+str(i)+'.pkl', 'wb') as f:
        pickle.dump(passage_list, f)
        del passage_list

设计你的问答

如图 9-2 所示,一个 Q & A 系统中有多个组件。主要功能在检索器和阅读/理解模块之间划分。每个模块还包含多个部分,可以根据用例的复杂性和预期性能来删除或添加这些部分。让我们深入研究它们,看看是什么组成了一个检索器模块和一个读取模块。

检索器模块

检索器模块由三个主要部分组成:

  • 查询释义

  • 检索机制(核心)

  • 重新分级

查询释义

查询解释是在语义上询问相同的查询,但在语言上改变它的过程。比如,“服用柯华新有什么好处?”可以转述为“使用科瓦欣的优势是什么?”

可以有多种方式来解释查询。这一点在 NeurIPS 2016 年的一篇论文中得到了很好的体现,该论文题为“具有潜在单词包的释义生成”。傅等人提出,来自 WordNet 的词汇替换,如本体和 seq2seq 模型(生成模型),并不能完全捕捉句子的所有语言方面。这些语言方面可以是

  1. 形态学:对词根、前缀、后缀等词和词类的研究。(例如说-说-说)

  2. 同义词:与其他词相似的词(如 big-large、airplane-jet)

  3. 蕴涵:如果 A 句包含 B 句,那么 B 句不为真,A 句也不为真(例如,天空飞机,球场球拍)

  4. 转喻:Google、Quora 等搜索引擎。

作者使用源句子中的单词来预测他们的邻居,并使用目标句子中的单词作为目标弓。参见图 9-4 。

img/502837_1_En_9_Fig4_HTML.jpg

图 9-4

深度创成式 BOW 模型示例

论文中使用的数据集不能用于像生物医学这样的专业领域。像 MedSTS 这样的数据集给出了一对相似的生物医学句子,可以用来尝试论文中的观点。

郑等人的另一篇题为“BERT-QE:用于文档重新排序的上下文化查询扩展”的论文关注的不是实际创建新的查询,而是试图从要检索的段落内找到上下文证据。这减少了由于使用本体(不解决多义性和/或用法的语义)或其他基于语法正确性的方法而产生的虚假查询而导致的误报。

它分三个阶段进行:

img/502837_1_En_9_Fig5_HTML.jpg

图 9-5

伯特-QE 模型的第一和第二阶段

  • 阶段 1 :从 BM25(基于术语的匹配,在下一节中讨论)中获取前 n 个文档,并使用在 MSMARCO 和 ROBUST04 数据集上训练的微调 BERT 模型来找到相关性分数。这将选择给定查询的相关文档。

  • 阶段 2 :对于这些文档中的每一个,你现在选择组块,这些组块是从具有大小为 m 的滑动窗口的文档中提取的子短语,使得两个相邻的组块重叠多达 m/2 个单词。这将选择给定查询的相关块。见图 9-5 。

  • 阶段 3 :从阶段 2 中选择的组块与原始查询结合使用,以计算最终的重新排序。首先,使用选择的反馈块和查询相关性分数作为块和文档相关性的权重来评估文档的相关性。

rel\left(C,d\right)=\sum \limits_{c_i\in C}{\mathrm{softmax}}_{c_i\in C}\left( rel\left(q,{c}_i\right)\right)\cdot rel\;\left({c}_i,d\right)

使用𝞪作为超参数,您衡量(查询,文档)和(组块,文档)的相关性分数的重要性。

rel\left(q,C,d\right)=\left(1-\alpha \right)\cdot rel\left(q,d\right)+\alpha \cdot rel\;\left(C,d\right)

您将使用第二种方法,因为它是领域不可知的,并且您可以通过使用特定于 COVID 的语料库来捕获语义。您将使用来自 https://huggingface.co/deepset/covid_bert_base 的 deepset 的 Covid 微调模型。

检索机制

检索器生成一组候选段落。由于文档的数量可能非常大,特别是对于开放领域的问答系统,具有高效的检索机制是非常重要的。它可以是基于术语的或基于语义的

基于术语/短语

查询文本和上下文文本都由向量表示,其中每个维度表示词汇表中的一个单词。现在,由于每个上下文只包含可能术语的子集,它们的术语向量通常是稀疏的。

两个文本(例如一个文档和一个查询)之间的相似性可以通过这些向量之间的点积来计算,同时还可以使用像 TF-IDF 或 BM25 这样的技术来考虑术语的重要性。

BM25 有助于使术语频率饱和,并通过惩罚不包含与查询相关的术语的较大文档来考虑文档长度。如果你有兴趣了解更多,请前往 www.kmwllc.com/index.php/2020/03/20/understanding-tf-idf-and-bm25/ .

为了有效地进行大规模的基于术语的匹配,您需要创建内容的倒排索引。倒排索引是一种散列表,它将单词映射到它们所在的文档。所有主要的搜索引擎,如 Elasticsearch、Solr 和 Anserini,都使用倒排索引来获取给定单词集的文档。

创建倒排索引有三个主要步骤:

  1. 加载文档。

  2. 分析一下。

    • 删除“我”、“这个”、“我们”、“是”、“一个”等停用词。

    • 词干词根使单词规范化。

  3. 制作倒排索引。

    • 在一般搜索中,您会找到一个文档,然后找到其中的单词,但是在反向搜索中,您会直接查询术语,然后找到与它们相关的文档 id。

有关倒排索引如何工作的详细信息,请参阅

www.elastic.co/guide/en/elasticsearch/guide/current/inverted-index.html

基于语义的

几乎所有的搜索引擎都提供了传递索引词的同义词的能力,但是词汇表中的潜在术语可能非常大。我们的老朋友嵌入来了,它基本上试图量化你的文本在不同语义类别中的比例。

有各种方法来训练这些嵌入用于语义检索。我们来讨论其中的一些。在深入研究之前,您需要理解,这里可以使用任何类型的嵌入,但是您想要的嵌入是经过“相似性”任务训练的,例如端到端问答、句子相似性、自然语言推理(NLI)等。

  • 密集段落检索:使用两个独立的 BERT 网络对段落和查询进行编码,以考虑它们的不同性质,如长度、风格和语法,从而优化两种编码的点积,以更好地对查询-段落对进行排序。

  • NLI:自然语言推理是在给定一个“前提”的情况下,确定一个“假设”是真(蕴涵)、假(矛盾)还是未确定(中性)的任务

  • 句子相似度:给定一对句子,标记 1/0,表示句子是否相似,微调模型权重,减少目标和预测标签之间的二元交叉熵损失。

因为你最关心的是相似性任务的优化,这反过来意味着你需要采用最接近理解自然语言的方法,我将只详细讨论基于 NLI 的方法。

给定一对文本,通过预测三类来预测相似性:蕴涵(意思是相似)、矛盾(意思是完全不相似)和中性(意思是前提和假设完全独立)。随着第三种状态的加入,模型能够更好地理解句子。参见图 9-6 。

img/502837_1_En_9_Fig6_HTML.jpg

图 9-6

用 NLI 创造句子表征

Conneau 等人在题为“从自然语言推理数据的通用句子表示的监督学习”的论文中提出了使用 NLI 进行句子表示的想法。

您将使用一个预训练模型,该模型使用不同的 NLI 数据集,尤其是与生物医学领域相关的数据集。这些 MedNLI 数据集是从 PhysioNet 获得的,就是您第一个案例中访问 MIMIC 3 数据集的那个网站。好消息是,你不需要通过任何培训就可以访问这个新的数据集。参见图 9-7 。

img/502837_1_En_9_Fig7_HTML.jpg

图 9-7

physionet mednli 数据

因为您将在您的段落列表中使用大维度嵌入,所以建议对它们进行索引以便快速检索。为此,您将特别使用 FAISS。

FAISS 是一个带有 Python 绑定的 C++库,用于对数百万或数十亿个向量进行向量相似性匹配。更多详情可登陆 https://engineering.fb.com/2017/03/29/data-infrastructure/faiss-a-library-for-efficient-similarity-search/ .

重新分级

重新排名是获得最佳排名段落的最后一枚钉子,问答模型可以在这些段落上运行。

MS-MARCO 是段落重新排序时使用最广泛的数据集。为了重新排序,你需要对一组肯定和否定的段落进行查询。您可以自由选择任何比率的积极与消极的通道进行查询。这个比例也决定了你训练数据的大小。你可以在 https://github.com/microsoft/MSMARCO-Passage-Ranking#ranking-task .了解更多

您不能直接将 MS-MARCO 数据集用于您的领域,因为数据集中的大多数问题与医学无关,这会导致训练和评估数据之间的领域不匹配。

为了克服这一挑战,MacAvaney 等人在他们题为“SLEDGE-Z:新冠肺炎文献搜索的零射击基线”的论文中使用了 MedSyn,这是一种针对各种医学状况的外行人和专家术语的词典,以过滤医学问题。是的,你在这里的想法是正确的,你也可以用我们之前讨论过的 UML 本体来代替它,但是这个本体的美妙之处在于这些术语是更一般的人类对话术语,而不是基于科学文献的术语。

因此,您将使用针对排名任务进行微调的 CovidBert 变换器来对结果进行重新排名。

理解

有各种各样的机器理解/问题回答模型/技术利用了最先进的深度学习方法,如神经变分推理模型(VS-NET),具有自匹配注意力的 RNNs,甚至卷积网络。但是在本案例研究中,您将通过 BERT 模型利用 transformer 架构来了解什么是机器理解以及 BERT 是如何理解的。

在第四章中,你学到了很多关于 BERT 架构的知识。如果你还没有读过那一章,请浏览“理解语言建模是如何工作的”一节,以获得更多的了解。

在问答任务中,给你一个问题和一个包含问题答案的段落。目标是从段落中提取给定问题的答案。

用于问答的 BERT

为了准备在 BERT 上训练问答模型的输入,有五个主要步骤。您不必对每一步都进行编码,因为它们是通过使用外部库来处理的。

img/502837_1_En_9_Fig8_HTML.jpg

图 9-8

QnA 的 BERT 输入

  1. 当使用 BERT 进行问答任务时,您将输入的问题和段落表示为一个单独的压缩序列。

  2. [CLS]标记被添加到问题的开头。它在选择答案方面没有任何作用,但是浓缩了问题的上下文。

  3. [SEP]标记加在问题和文章的末尾。

  4. BERT 还使用片段嵌入来区分问题和包含答案的段落。BERT 创建了两个片段嵌入,一个用于问题,另一个用于段落,以区分问题和段落。然后,这些嵌入被添加到标记的一次性表示中(使用标记嵌入的 BERT 标记化),以在问题和段落之间进行分离。

  5. 还向每个标记添加位置嵌入,以指示其在序列中的位置。参见图 9-8 。

当我说模型必须从段落中提取答案时,它实际上必须返回包含答案的文本跨度。这是通过找到文本范围的开始和结束索引来完成的。

在微调过程中,您只需要引入一个起始向量 S 和一个结束向量 E。单词 I 作为答案范围的开始的概率被计算为 T i 和 S 之间的点积,随后是段落中所有单词的 Softmax。类似地,存在 E 向量来计算结束索引。

这里需要注意的一点是,S 和 E 都是 768 维向量,等于令牌嵌入的维数。对于一次迭代,相同的权重被应用于每个令牌嵌入。参见图 9-9 。

img/502837_1_En_9_Fig9_HTML.jpg

图 9-9

计算答案范围的开始索引

我希望这能非常简洁地回顾一下你是如何为问答培训设置 BERT 的。在本案例研究中,您将使用一个经过微调的 BERT 模型,而不是从头开始进行培训。

如果你想知道 BERT 是如何在问答中施展魔法的,请阅读题为“BERT 如何回答问题?对变压器表示的逐层分析”。它对三个主要参数进行了分析,这三个参数是可解释性、可移植性和模块化。

微调问答数据集

更常见的情况是,您不会仅仅为了问答任务而从头开始训练一个 transformer 模型。您将主要针对各种任务对其进行微调。它的工作原理就像你在前面章节中看到的迁移学习例子一样。

问答有多种形式。在本案例研究中,你将进行提取式问答,包括使用一篇文章作为理解的背景来回答问题,然后突出回答问题的那段文章。这包括微调预测通道中的开始位置和结束位置的模型。

这种任务的一个流行数据集是小队数据集。它由一组维基百科文章上的 10 万多个问题组成,每个问题的答案都是相应段落的文本片段。SQUAD 2.0 更进一步,将 100k 个问题与 50k+个看起来类似于可回答问题的不可回答问题结合在一起。

https://huggingface.co/graviraja/covidbert_squad 的阵容数据集中有一个微调过的伯特模型。你将使用这个模型来完成你的理解任务。

如果你想从头开始学习如何微调预训练模型,拥抱脸在 h ttps://huggingface.co/transformers/custom_datasets.html#question-answering-with-squad-2-0 提供了一个很好的教程。

最近发布了 COVID-QA ( https://github.com/deepset-ai/COVID-QA ),这是一个由 2019 个带注释的问题/答案对组成的问题回答数据集。它可以用来进一步微调你的理解模型。我把这个任务留给你去尝试。

最终设计和代码

根据您对 QnA 不同组成部分的理解,让我们快速列出设计 QnA 所需的步骤。参见图 9-10 。

img/502837_1_En_9_Fig10_HTML.jpg

图 9-10

问答系统设计

步骤 0:准备文档数据

首先加载在将摘要转换成段落后保存的 pickle 文件。

import glob
import pickle
import pandas as pd
all_metadata = []
    for i,files in enumerate(glob.glob("./Data/passage/passage_*.pkl")):
        with open(files, 'rb') as f:
        data_list = pickle.load(f)
        all_metadata.extend([data_pair for data in data_list \
                             for data_pair in data])

    all_metadata_df = pd.DataFrame(all_metadata, columns = ["id","passage_id","title","passage"])

第一步:伯特-QE 扩展

步骤 1.1:使用 BM-25 提取查询的前 k 个文档

由于有很多文档,我将从all_metadata_df中随机抽取 50,000 个段落来执行相关任务。如果您有大量的 RAM,您可以尝试使用全部或部分数据。

由于 BM-25 处理术语,您需要对这些术语的段落进行标记,以创建一个倒排索引,然后使用 BM-25 检索它们。在本练习中,您将使用 rank-bm25 软件包。

对于 tokenizers,您将使用一个基本的 spacy 包。请注意,spacy 发布了 3.0 版,它大量使用了转换器来提高准确性,但对于我们的目的(即标记化)来说效率不高。以前的空间流水线非常精确,因此您将使用 en_core_web_sm 空间包来实现这一目的。

from spacy.tokenizer import Tokenizer
from spacy.lang.en import English
nlp = English()
# Create a blank Tokenizer with just the English vocab
tokenizer = Tokenizer(nlp.vocab)

您将创建一个名为BM25RankedResults的类,它基本上是索引您的数据,而允许您查询并返回前 200 个文档。

from rank_bm25 import BM25Okapi
import numpy as np

    class BM25RankedResults:
        """
        BM25 Results from the abstract.

        Usage:

        bm25 = BM25RankedResults(metadata_df) # metadata_df is a pandas dataframe with 'title' and 'abstract' columns
        topbm25 = bm25.search("What is coronavirus", num=10) # Return `num` top-results
        """

        def __init__(self, corpus: pd.DataFrame):
        self.corpus = corpus
        self.columns = corpus.columns
        token_list = pd.Series([[str(token) for token in doc if str(token)] \
             for doc in tokenizer.pipe(corpus.passage,
                                       batch_size=5000)])
        self.index = token_list.to_frame()
            self.index.columns = ['terms']
        self.index.index = self.corpus.index
        self.bm25 = BM25Okapi(self.index.terms.tolist())

        self.bm25 = BM25Okapi(token_list)

        def search(self, query, num = 200):
            """
            Return top `num` results that better match the query
            """
        search_terms = query.split()
        doc_scores = self.bm25.get_scores(search_terms) # get scores

            ind = np.argsort(doc_scores)[::-1][:num] # sort results

        results = self.corpus.iloc[ind][self.columns] # Initialize results_df
            results['score'] = doc_scores[ind] # Insert 'score' column
            results = results[results.score > 0]
        return results.passage_id.tolist()

    passage_data = all_metadata_df.sample(50000)
bm25 = BM25RankedResults(passage_data) # Covid Search Engine

如果您想处理完整的数据并有足够的内存,您还可以加载一个预构建的 Lucene 索引,并使用 Pyserini (v 9.3.1)通过 BM-25 进行查询。它速度更快,可扩展性更强。

我已经为用于创建段落的配置构建了索引,该配置的步幅为 1,段落的最大长度为 300 个标记。你可以从 https://drive.google.com/file/d/1A824rH3iNg8tRjCYsH2aD50YQMNR6FVI/view 那里得到那些文件。

现在加载预构建的二进制文件并调用SimpleSearcher类。

from pyserini.search import SimpleSearcher
    bm25 = SimpleSearcher('./Data/indexes')

# Example
search_hits = bm25.search('what is coronavirus', k= 200)
    bm25_passage = [hit.docid for hit in search_hits]

我将对剩下的代码使用pyserini方法,因为它非常快,但是BM25RankedResults类在处理文本方面提供了更多的灵活性(清理、词汇化等)。)也可以使用。

步骤 1.2:前 200 个文档的相关性分数

如上一节所述,计算相关性分数有三个阶段。尽管作者在不同阶段试验了不同的变压器模型,但对于您的用例,您将使用来自 deepset 的 covidBert 模型,该模型可从 https://huggingface.co/deepset/covid_bert_base .获得

要加载这个模型,您将使用句子转换器库。这是一个优秀的库,可以快速帮助计算句子和段落的密集向量表示。它支持各种变压器网络,如伯特,罗伯塔,XLM 罗伯塔等。

如果您的笔记本电脑上安装了 cuda,您还可以将设备传递给模型来执行操作。对于 NVIDIA-GPU 卡可以通过device = 'cuda'

from sentence_transformers import SentenceTransformer,util
    covid_bert = SentenceTransformer("deepset/covid_bert_base", device = 'cuda')

因为伯特-QE 是基于找到前 n 个段落/组块,所以让我们写一个包装器函数来基于两个向量之间的余弦相似性获得前 k 个值。

首先,使用 covidBert 模型对列表中的所有文本进行编码。

然后,使用来自句子转换器实用程序文件的内置函数来计算余弦分数,然后根据余弦相似度返回前 k 个匹配。

如果您想直接使用余弦得分指标,可以使用一个标志变量。

    def get_top_k_vals(list1, list2, k = 100, model = covid_bert, return_cosine_mat = False):
    # Compute embedding for both lists
    embeddings1 = model.encode(list1, convert_to_tensor = True)
    embeddings2 = model.encode(list2, convert_to_tensor = True)

    # Compute cosine-similarity
    cosine_scores = util.pytorch_cos_sim(embeddings1, embeddings2)

    if return_cosine_mat:
        return cosine_scores.numpy()

    # Select top kd documents/passage
        _topkd = np.argsort(cosine_scores.numpy()[0])[::-1][:k]

        return _topkd, cosine_scores.numpy()[0][_topkd]

您还需要计算带有 top chunks 的查询的 Softmax,所以让我们定义一个可以处理 NumPy 数组的 Softmax 函数。

    def softmax(x):
        """Compute softmax values for each sets of scores in x."""
    e_x = np.exp(x - np.max(x))
        return e_x / e_x.sum(axis=0)

现在,您已经准备好编写主函数了。这些步骤如原始文件中所述。为了更好地理解,我将代码注释成不同的阶段。

from collections import OrderedDict
    def bert_qe(query, bm25_model, passage_id_map, bert_model = covid_bert,
                alpha = 0.4, document_size = 500, chunk_size = 8):
        """
        Re-ranks BM-25 document based on relevancy of query to chunks of a passage.
        """

        print("\tPhase 1")
    # Phase 1
    topbm25 = bm25_model.search(query, document_size)

    #doc index to passage map
    passage_index_map = OrderedDict({idx:passage_id_map[passages] if isinstance(passages,str) \
                               else passage_id_map[passages.docid] for idx,passages in enumerate(topbm25)})
    passageid_index_map = OrderedDict({idx:passages if isinstance(passages,str) \
                               else passages.docid for idx,passages in enumerate(topbm25)})

    _topdocidx, _topdocscores = get_top_k_vals([query],
                                       list(passage_index_map.values()),
                                       k = document_size, model = bert_model)
    # Store Top Contextually matching docs
    passage_scores = {idx:score for idx,score in zip(_topdocidx, _topdocscores)}

        print("\tPhase 2")
    # Phase 2
    # Create chunks of length "n" and stride them with a length of "n/2"
        _chunks = [[" ".join(phrase) for i, phrase in enumerate(nltk.ngrams(passage_index_map[idx].split(), chunk_size)) if i%(chunk_size/2)==0] for idx in _topdocidx]

    # Flatten the list
    all_chunks = list(chain.from_iterable(_chunks))

    # Get top chunks based on relevancy score with the query
    _topchunkidx, _topchunkscores = get_top_k_vals([query],
                                         all_chunks,
                                             k = int(len(all_chunks)/2), model = bert_model)

    top_chunks = np.array(all_chunks)[_topchunkidx]

    # Apply softmax over query and chunk relevancy score,
    # This acts as weights to chunk and document relevancy
    _topchunksoftmax = softmax(_topchunkscores)

    # Phase 3
        print("\tPhase 3")
    scores = get_top_k_vals(list(passage_index_map.values()),
                            list(top_chunks),
                            k = len(top_chunks),
                            model = bert_model,
                            return_cosine_mat = True)

    # Multiply the weights of chunk with query to relevancy of chunk with the document
    # and sum over all the top chunks (kc in the paper)
        docchunk_score = np.sum(np.multiply(_topchunksoftmax, np.array(scores)), axis = 1)

    # weighing importance of query relevance and query chunk-doc relevance

        final_score = alpha*_topdocscores + (1-alpha)*docchunk_score

    passage_score = dict(zip([passageid_index_map[idx] for idx in _topdocidx],final_score))

    return passage_score

步骤 2:语义段落检索

为了以相当快的速度实现语义检索,您将利用 Faiss。Faiss 是一个用于高效相似性搜索和密集向量聚类的库。它包含在任意大小的向量集中搜索的算法,甚至是不适合 RAM 的向量。

Faiss 只使用 32 位浮点矩阵。这意味着您必须在构建索引之前更改输入的数据类型。

这里,您将使用 IndexFlatIP 索引。这是一个执行最大内积搜索的简单索引。

有关该指数的完整列表,请访问

github.com/facebookresearch/faiss/wiki/Faiss-indexes

首先加载 covidbert-nli 模型,以获得所有段落的编码。这与您用于标记化的模型相同,用于从摘要中创建连续长度的段落。

# Instantiate the sentence-level covid-BERT NLI model
    from sentence_transformers import SentenceTransformer,util
    covid_nli = SentenceTransformer('./pre_trained_model/training_nli_covidbert-mednli', device = 'cuda')

# Convert abstracts to vectors
embeddings = covid_nli.encode(passage_data.passage.to_list(), show_progress_bar=True)

现在可以编写 Faiss 索引的代码了。请注意 Faiss 不支持字符串 id,因此需要为映射到整数值的passage_ids创建一个外部映射。

此外,要使用通道向量创建索引,您将

  • 将通道向量的数据类型更改为 float32。

  • 建立一个索引,并向它传递它将操作的向量的维数。

  • 将索引传递给IndexIDMap,这个对象使您能够为索引向量提供一个定制的 id 列表。

  • 将通道向量及其 ID 映射添加到索引中。

import faiss

# Building FAISS Index
    embeddings = np.array([embedding for embedding in embeddings]).astype("float32")

# Instantiate the index
    embedding_index = faiss.IndexFlatIP(embeddings.shape[1])

# Pass the passage index to IndexIDMap
embedding_index = faiss.IndexIDMap(embedding_index)

# Numerical map
passage_num_map = {int(i):x for i,x in enumerate(all_metadata_df.passage_id.values)}

# Add vectors and their IDs
embedding_index.add_with_ids(embeddings, np.array(list(passage_num_map.keys()), np.int64))

现在可以使用 Faiss 库保存这个索引。在下一章中,您可以使用这个索引来部署您的问答模型。

    faiss.write_index(index, "./Data/faiss_cord-19-passage.index")

您可以使用read_index命令加载 Faiss 索引。

    embedding_index = faiss.read_index("./Data/faiss_cord-19-passage.index")

步骤 3:在 Med-Marco 数据集上使用微调的 Covid BERT 模型进行段落重新排序

您处于检索器步骤的最后一步,其中您使用来自伯特-QE 和语义检索的段落,使用在 Med-Marco 数据集上的重新排序任务中训练的伯特模型进行重新排序。

您可以从 https://huggingface.co/Darkrider/covidbert_medmarco 下载预训练好的模型,并在您的工作目录下创建一个名为pretrained_model的文件夹并保存在那里。或者可以直接将字符串/Darkrider/covidbert_medmarco传递给CrossEncoder().

下载后,您可以将它放在您的pre_trained_model文件夹中。

from sentence_transformers.cross_encoder import CrossEncoder
    covid_marco = CrossEncoder("./pre_trained_model/training_medmarco_covidbert")

句子转换器提供了两个包装器函数来比较一对句子。第一种是双编码器,第二种是交叉编码器。

双编码器为给定的句子产生一个句子嵌入。您将句子 A 和 B 独立地传递给 a BERT,这导致了句子嵌入 u 和 v。然后可以使用余弦相似度来比较这些句子嵌入。相比之下,对于交叉编码器,您同时将两个句子传递到转换器网络。它产生一个介于 0 和 1 之间的输出值,表示输入句子对的相似度。

因为对于你的文章排序任务,你不关心单个的嵌入,而是关心两个句子有多相似,你将使用交叉编码器。参见图 9-11 。

img/502837_1_En_9_Fig11_HTML.jpg

图 9-11

句子对的双编码器与交叉编码器

如图 9-10 所示,你现在已经有了来自 BERT 查询扩展技术和语义向量匹配的重要段落。您可以在“检索器”一节中解释的 Med-Marco 数据集上使用微调的 BERT 模型对它们进行重新排序。

为此,您首先要编写一个包装函数来整理第 1 步和第 2 步的结果。这个函数基本上从步骤 1 和 2 中获取段落 id,并将它们传递给经过宏训练的模型(步骤 3)。

    def get_ranked_passages(query, bm25_model, bert_model, passage_id_map, faiss_index, bert_qe_alpha = 0.4):

        print("Step 1 : BERT-QE Expansion")
    #BERT-QE
    bertqe_dict = bert_qe(query, bm25_model = bm25_model, passage_id_map = passage_id_map,
            bert_model = bert_model, alpha = 0.4, document_size = 500, chunk_size = 8)

        print("Step 2 : Semantic Passage Retrieval")
    # Semantic Search
        _,indices = faiss_index.search(np.expand_dims(covid_nli.encode(query), axis = 0), k=500)
        semantic_passage_ids = [passage_num_map[idx] for idx in indices[0]]

    # passages to be re-ranked
    total_passage_ids = list(bertqe_dict.keys())+ semantic_passage_ids

    return list(set(total_passage_ids))

最后,根据查询,检索最终排序的文档。

# Some queries we want to search for in the document
    queries = ["What is Coronavirus"]

# Map of Passage id to Passage Text
passage_id_map = pd.Series(all_metadata_df.passage.values,index=all_metadata_df.passage_id).to_dict()

#Search in a loop for the individual queries
for i,query in enumerate(queries):
        print(f"Ranking Passages for {i+1} of {len(queries)} query/queries")
    passage_ids = get_ranked_passages(query, bm25_model = bm25, passage_id_map = passage_id_map,
                                      bert_model = covid_bert,
                                      faiss_index = embedding_index, 
                                          bert_qe_alpha = 0.4)

    #Concatenate the query and all passages and predict the scores for the pairs [query, passage]
    model_inputs = [[query, passage_id_map[passage_id]] for passage_id in passage_ids]

        print("Step 3 : Passage Re-ranking using Fine-Tuned Covid BERT ")
    scores = covid_marco.predict(model_inputs)

    #Sort the scores in decreasing order
        results = [{'input': inp, 'score': score} for inp, score in zip(passage_ids, scores)]
        results = sorted(results, key=lambda x: x['score'], reverse=True)

输出

    Ranking Passages for 1 of 1 query/queries
    Step 1 : BERT-QE Expansion
               Phase 1
               Phase 2
               Phase 3
    Step 2 : Semantic Passage Retrieval
    Step 3 : Passage Re-ranking using Fine-Tuned Covid BERT

注意,结果是一个包含passage_id和排名分数的字典。你现在终于准备好做理解了。您还可以根据分数对结果进行分组。让我们保持 0.3 的截止值

    final_results = {res_dict['input']:res_dict['score'] for res_dict in results if res_dict['score'] > 0.3}

len(final_results)

输出

107

您将看到如何通过使用智能检索技术,将您的文档搜索空间从 60 万个减少到大约 100 个文档/段落。

第四步:理解

拥抱脸为使用流水线的模型推断提供了一个简单的接口。这些对象抽象出了模型推理的大部分复杂代码。它们涵盖许多任务,例如

  • ConversationalPipeline

  • FeatureExtractionPipeline

  • FillMaskPipeline

  • QuestionAnsweringPipeline

  • SummarizationPipeline

  • TextClassificationPipeline

  • TextGenerationPipeline

  • TokenClassificationPipeline

  • TranslationPipeline

  • ZeroShotClassificationPipeline

  • Text2TextGenerationPipeline

  • TableQuestionAnsweringPipeline

好吧,这些任务中的每一项都值得单独写一章,但是在这个案例研究中,您只关心QuestionAnsweringPipeline

from transformers import pipeline
    comprehension_model = pipeline("question-answering", model='graviraja/covidbert_squad',tokenizer='graviraja/covidbert_squad', device=-1)

这些参数是

  • task(str):定义返回哪条流水线的任务。“问答”返回一个QuestionAnsweringPipeline.

  • model(str or PreTrainedModel(pytorch) or TFPreTrainedModel(Tensorflow)):流水线用来进行预测的模型。这可以是一个模型标识符(字符串),或者是一个继承自PreTrainedModel(py torch)或TFPreTrainedModel(tensor flow)的预训练模型的实际实例。

  • tokenizer (str or PreTrainedTokenizer):标记器,流水线将使用它来为模型编码数据。这可以是一个模型标识符,也可以是从PreTrainedTokenizer继承的一个实际的预训练标记器。如果没有提供,将加载给定模型的默认标记器(如果它是一个字符串)。

  • use_fast (bool):如果可能的话,是否使用快速标记器。快速记号赋予器是使用 Rust 实现的。

  • device是一个 kwarg 参数。您可以使用“-1”来启用 GPU。

要使用它,您需要传递问题和上下文。

# sample example
    comprehension_model(question="What is coronavirus", context=all_metadata_df.passage.tolist()[0])

输出

    {'score': 0.02539900690317154,
     'start': 529,
     'end': 547,
     'answer': 'community-acquired'}

现在,在 Kaggle 的 CORD-19 任务中,有几组问题被分成九个任务:

  1. 关于传播、潜伏期和环境稳定性,我们知道些什么?

  2. 我们对新冠肺炎风险因素了解多少?

  3. 我们对病毒遗传学、起源和进化了解多少?

  4. 我们对疫苗和疗法了解多少?

  5. 我们对非药物干预了解多少?

  6. 关于医疗保健已经发表了什么?

  7. 我们对诊断和监控了解多少?

  8. 关于信息共享和跨部门协作,已经发表了哪些内容?

  9. 关于伦理和社会科学的考虑已经发表了什么?

这些任务中的每一个都有一组在 COVID 文献搜索中经常被问到的问题,因此您也将包括它们。感谢@kaggle/dirktheeng整理这些问题。

以下是这些问题的一小部分:

covid_kaggle_questions = [
          {
                  "task": "What is known about transmission, incubation, and environmental stability?",
                  "questions": [
                      "Is the virus transmitted by aerosol, droplets, food, close contact, fecal matter, or water?",
                      "How long is the incubation period for the virus?",
               ...
              ]
          },
          {
                  "task": "What do we know about COVID-19 risk factors?",
                  "questions": [
                      "What risk factors contribute to the severity of 2019-nCoV?",
                      "How does hypertension affect patients?",
                      "How does heart disease affect patients?",
              ...
     }
   ]

让我们回过头来修改问题for循环,该循环对段落进行排序以便理解,并为每个任务的问题创建一个数据框架。

使用来自 Hugging Face 的问答流水线,您基本上可以得到一个这种形式的字典。

    {'score': 0.622232091629833, 'start': 34, 'end': 96, 'answer': 'COVID-19 happens in respiratory tract'}

因为您传递了一个列表passage_ids(也称为上下文),所以您得到了这个字典的列表。创建理解输出(comp_output变量)时,确保通过passage_idpassage_rank得分。您正在存储这些信息,以便在以后的部署中使用(第十章)。

最后,将所有任务中每个问题的所有答案存储在名为all_comprehension_df.的数据帧中

# Map of Passage id to Passage Text
passage_id_map = pd.Series(all_metadata_df.passage.values,index=all_metadata_df.passage_id).to_dict()

# Numerical map for semantic passage retrieval
passage_num_map = pd.Series(all_metadata_df.passage_id.values,index=pd.Series(range(len(all_metadata_df)))).to_dict()

# Map of Passage id to Paper Title
passage_id_title_map = pd.Series(all_metadata_df.title.values,index=all_metadata_df.passage_id).to_dict()

all_comprehension_df_list = []
#Search in a loop for the individual queries
for task_query_dict in covid_kaggle_questions:
        for i,query in enumerate(task_query_dict["questions"]):
            print(f"Ranking Passages for {i+1} of {len(task_query_dict['questions'])} query/queries")
            passage_ids = get_ranked_passages(query, bm25_model = bm25, passage_id_map = passage_id_map,bert_model = covid_bert, faiss_index = embedding_index, bert_qe_alpha = 0.4)

        #Concatenate the query and all passages and predict the scores for the pairs [query, passage]
        model_inputs = [[query, passage_id_map[passage_id]] for passage_id in passage_ids]

            print("Step 3 : Passage Re-ranking using Fine-Tuned Covid BERT ")
        scores = covid_marco.predict(model_inputs)

        #Sort the scores in decreasing order
            results = [{'input': inp, 'score': score} for inp, score in zip(passage_ids, scores)]
            results = sorted(results, key=lambda x: x['score'], reverse=True)

        # Filtering passages above a certain threshold
            final_results = {res_dict['input']:res_dict['score'] for res_dict in results if res_dict['score'] > 0.3}

            print("Step 4 : Comprehension ")
        # Comprehension
            comp_output = [[comprehension_model(question="What is coronavirus",
                                     context = passage_id_map[pass_id]),  pass_id, pass_score] \
                      for pass_id, pass_score in final_results.items() if len(passage_id_map[pass_id].split()) > 5]

        # Adding pass id and score to the comprehension
            [comp_output[i][0].update({'pass_id': comp_output[i][1],
                                 'pass_rank_score': comp_output[i][2]}) for i in range(len(comp_output))]

        # Converting list of dictionaries of ranked results to dataframe.
            comprehension_df = pd.DataFrame([comp_[0] for comp_ in comp_output])

        # adding query and the task
            comprehension_df["query"] = query
            comprehension_df["task"] = task_query_dict["task"]

        # Finally, using passage_id to replace with actual Paper Title and Context
            comprehension_df["title"] = [passage_id_title_map[pass_id] for pass_id in comprehension_df.pass_id]

        all_comprehension_df_list.append(comprehension_df)

    all_comprehension_df = pd.concat(all_comprehension_df_list, axis = 0)

现在你保存熊猫数据帧。

    all_comprehension_df.to_csv("all_question_comprehension.csv", index = None)

结论

你在这一章学到了很多。您从不同类型的问答系统开始,然后为一个封闭领域的问答系统构建了一个系统设计,该系统涉及在应用理解之前对正确的文档进行排序的多种方法。您还学习了新技术,例如用于内部产品搜索的 FAISS。这在问答之外还有应用,可以用在任何大规模生产环境中。

请随意回答不同的问题。尽管您将在下一章使用 CORD-19 任务中的问题,但您仍然可以传递自己的查询并更好地理解 COVID。**

十、你现在需要一个观众

今天,很大一部分 ML 研究和建模工作都被搁置在 Jupyter 笔记本或多个 Python 脚本中。数据科学家需要对其他 It 系统和企业架构有大量的了解,才能将东西投入生产并在真实系统上运行。行业趋势已经从“数据科学家”转变为“全栈数据科学家”

我们所有的现代 ML 应用程序代码只不过是带有复杂的数据管理设置过程的库。在这一章中,您将学习如何在 Docker 的帮助下将模型投入生产,Docker 可以再现您用来开发 ML 代码的环境,这将导致可再现的输出,从而提供可移植性。您还将使用 Heroku 部署带有实时 URL 的应用程序。

揭开网络的神秘面纱

如今,大多数企业应用程序都是 web 应用程序。下载一个.exe文件来运行最新软件的日子已经一去不复返了。如今,大多数软件都运行在云中。这导致了公司和消费者在规模、体验和成本方面的变化。我们正在将更大的计算能力放入更小的设备中,并通过互联网生活在一个“永远连接”的世界中。随着时代的变化,技术的变化是必然的。

现代软件系统遵循 CI/CD 方法(持续集成和持续部署)。持续集成的目的是将源代码与适当的测试集成在一起,而部署将获取这些代码并打包以供部署。人工智能要想成功,它需要成为这个系统的一部分。

数据科学家在遇到问题时,会从 Jupyter notebook/Python 脚本开始,创建一个解决问题的模型。一旦模型达到所需的精度,它将被存储为文件格式,如.h5.pkl.onnx,以便其他数据科学家或最终用户加载和使用。为了将其集成到传统上用 JS/C#/Java 或 C++编写的现代应用程序中,我们必须编写一个可以在其环境中调用这种模型的包装器,因为大多数数据流水线都是用这种语言编写的。这不仅仅是集成的问题,也是存储和提供运行这种模型的计算资源的问题,因为这种模型很可能需要 GPU。因此我们不能一直交换文件。我们需要像软件开发一样管理模型生命周期。

应用程序如何通信?

web 应用程序连接到 web 服务器,web 服务器只不过是一个远程计算机单元(类似于 CPU)。图 10-1 解释了 web 技术是如何从静态 HTML 发展到高级应用程序,如 Gmail、脸书等。讨论中忽略的一件重要事情是数据库技术的发展。虽然传统的应用程序是建立在 SQL 数据库上的,但是现在有了更先进的数据库技术,如 MongoDB、Cassandra、Neo4J 等。

img/502837_1_En_10_Fig1_HTML.jpg

图 10-1

网络技术的演变

一般来说,这些网站由公司 IT 部门维护的本地服务器提供支持,但是随着应用程序变得复杂且高度关联(与数据、人员和其他应用程序),很难按比例扩展服务器。这没有商业意义,也没有资源来维护这样一个高性能的系统。

云技术

然后是云技术。对于门外汉来说,云是一个随需应变的计算机系统,可供许多用户通过互联网使用。这种随需应变的系统有助于我们通过虚拟化获得所需的存储和处理能力(即通过软件将服务器划分为更小的虚拟机)。

随着云以非常低的成本提供企业级技术,许多服务开始涌现。这些技术的视图如图 10-2 所示。

img/502837_1_En_10_Fig2_HTML.jpg

图 10-2

各种基于云的服务。资料来源:redhat.com

这年头现场很少见。它可能用于一些只能通过公司内部网访问的内部软件/网站。

在 IaaS 中,只有基础架构是租用的(也就是说,将具有特定存储、RAM 和计算资源的机器委托给你)。想象一下买一个 CPU。现在你可以做任何事情:安装软件,制作应用程序,甚至建立一个网站。是的,你可以用你的电脑托管一个网站,但是你能保证正常运行时间和速度吗?

使用 PaaS 时,您只关心开发您的代码和数据脚本。您不关心需要多少个虚拟机来高效运行代码,也不关心为每个虚拟机分别提供操作系统、库版本等。

SaaS 一般都是基于网络的工具,如 Google Colab、脸书、LinkedIn 等。之所以这样称呼它们,是因为你不需要设置任何东西就可以使用它们。您所需要的只是一个与云通信的互联网连接。

码头工人和库柏工人

为什么是 Docker?

现代 web 应用程序包含许多依赖项。其中一些依赖于操作系统。其中一些依赖于所使用的不同库的版本。随着越来越多的库被独立开发,这种情况只会越来越多。你可以使用来自一个开发者的一个库和来自另一个开发者的另一个库,这就是 ML 的情况。

如果您必须集成在多台机器上测试的代码(在开发中),然后最终将它集成到一个临时服务器上,这可能会非常麻烦。为了管理这样的问题,一种新的开发模式正在兴起,叫做容器化应用。它基本上将代码、用于运行代码的库和操作系统级信息作为一个单独、隔离的单元。这个独立的单元可以在另一台机器上运行,而不用担心为运行应用程序代码而配置机器。Docker 是当今使用最广泛的容器技术,在 ML 社区中非常流行。

操作系统虚拟化

Docker 容器运行在主机操作系统之上,并为容器内运行的代码提供了一个标准化的环境。当开发环境的操作系统和测试操作系统相同时,Docker 是合适的。这些容器化的单元基本上解决了 ML 中的 DevOps 问题,因为你现在可以和代码一起获得所有具有正确版本的依赖库,甚至是操作系统(即开发者环境的精确副本)。

与通过虚拟机管理程序等应用程序创建虚拟机来实现的硬件虚拟化相比,使用 Docker 的这种操作系统虚拟化允许您实现高效的资源利用,因为您现在可以在 Docker 容器之间动态分配资源,尽管它们与虚拟机使用相同的服务器,虚拟机将资源分配给各自的单元。

忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈忽必烈

现在,想象一个成熟的应用程序,比如亚马逊,使用多个这样的容器图像。一个是允许搜索结果出现,一个是推荐新商品,一个是捕捉用户行为和与 web 应用程序的交互接触点。我们能否根据它们的使用情况来扩展它们?是的。对于编排独立的 Docker 容器,我们使用 Kubernetes。

对 Kubernetes 或 Docker 的详细介绍超出了本书的范围,但是网上有一些很好的资源,比如关于 https://mlinproduction.com/ 的文章。

部署 QnA 系统

我已经介绍了基础知识。现在,您可以部署问答设置并创建 web 应用程序了。

首先,您需要一个框架来处理您的部署和集成需求,例如前端和后端通信、客户端和服务器端伸缩等。为此,您将使用 Flask。让我们深入研究一下。

建立一个烧瓶结构

Flask 是一个基于 web 的微服务框架,允许您通过 API 公开任何业务逻辑/功能。虽然我不会涉及很多 Flask,但对于第一次使用 Flask 的人来说,这里有一些基础知识。

首先创建一个名为covidquest的文件夹。您将使用它作为应用程序的文件夹。

安装 Flask,这样你就可以通过 pip 频道下载最新的 Flask。

设置好之后,让我们创建 Flask 应用程序。

制作 Flask 应用程序需要两个基本要素,一个处理客户端(前端),另一个处理服务器端(后端)。

web 应用程序设置包含两个文件。因此,您将按如下方式创建这两个文件:

  • app.py :处理客户端通信并生成响应的 Python 脚本。

  • index.htm:你的 GUI 界面。它允许用户提交输入(也称为请求)进行计算,并呈现返回的结果,就像您在“应用程序如何通信”一节中学习的一样

您可以从 https://github.com/NeverInAsh/covidquest 克隆 app 文件。这将作为您的起点,但是让我们快速查看一下每个文件中的基本内容。

深入了解 app.py
from flask import Flask, render_template, request
import pandas as pd
import numpy as np
import sys

    app = Flask(__name__, template_folder='./templates/')

@app.before_first_request
    def at_startup():
    global answer_df, question_map, top_k_map

        answer_df = pd.read_csv("./all_question_comprehension.csv", index_col=None)
        question_map = {'1': 'Is the virus transmitted by aerosol, droplets,  food, close contact, fecal matter, or water?',
    ... skipped lines
                        '30': 'Can 2019-nCoV infect patients a second time?'}

        top_k_map = {'0': 5, '1': 10, '2': 20, '3': 30, '4': 50}

@app.route('/')
    def home():
        return render_template("index.html")

    def create_answer(text, start, end):
        output = [text[0:start],
              text[start:end],
              text[end:len(text)]]
    return output

@app.route('/top_k_results', methods=['GET', 'POST'])
    def top_k_results():
        question_select = "0"
        weight = "0.2"
        top_k = "0"
        if request.method == "POST":
            question_select = request.form.get('question_select', '')
            weight = request.form.get('weight', '')
            top_k = request.form.get('top_k', '')

    query = question_map[question_select]
    # Filtering answer dataframe for the query
        _df = answer_df[answer_df['query'].isin([query])]
        _df = _df.drop_duplicates(subset=['passage_id']).reset_index(drop=True)

        _df["final_score"] = np.float(
            weight)*_df["score"] + (1-np.float(weight))*_df["pass_rank_score"]

    _df = _df.sort_values(
            'final_score', ascending=False).reset_index(drop=True)

    # results-dictionary
        results = [{'passage': create_answer(row['passage'], row['start'], row['end']),
                    'title':row['title'],
                    'task':row['task']} for i, row in _df.head(top_k_map[top_k]).iterrows()]

        return render_template("index.html", question_select=question_select,
                           weight=weight, top_k=top_k, results=results)

    if __name__ == '__main__':
        port = int(os.environ.get("PORT", 5000))
        app.run('0.0.0.0', port)

您的app.py按以下方式组织:

img/502837_1_En_10_Fig3_HTML.jpg

图 10-3

用于获取用户输入的表单

  1. 首先,导入所有用于编写后端逻辑的相关库。

  2. 然后创建一个app对象,它是 Flask 对象的一个实例。使用它,您可以配置整个应用程序。例如,通过显式给出到templates文件夹的链接,确保 Flask 知道要呈现哪个网页。templates文件夹用于存储应用程序的所有 HTML 文件,而所有 CSS 和.js文件(用于前端/客户端的其他技术)存储在静态文件夹中。

  3. app 对象还帮助为端点/函数设置路由,这些端点/函数又调用 URL。(URL 是端点的地址。)这是使用装饰器@app.route(<url>, methods),完成的,它是一种用于通信的 HTTP 方法。

  4. 最常见的数据通信/传输方法是 GET 和 POST。GET 向服务器发送未加密的信息,而 POST 屏蔽这些信息并在请求体中传递数据。

  5. 您使用家庭端点作为您网站的登录页面。它只是呈现索引文件。

  6. 您还可以使用像@app.before_first_request这样的装饰器,它确保在服务器准备好进行通信之前,所有生成请求响应所需的文件/变量都已加载。

  7. app.route()用于将特定的 URL 与函数进行映射。例如,您正在使用 URL“/”映射网站的登录页面/主页。类似地,您用函数top_k_results.映射“/top_k_results”

  8. render_tempalte()用于呈现 HTML,它是用户界面的框架,供客户端交互。Flask 使用 Jinja 模板库来渲染模板。点击 https://jinja.palletsprojects.com/en/2.11.x/ 了解更多信息。

  9. 主代码逻辑存储在top_k_results()端点,从网站表单中收集数据(图 10-3 )。这个数据是

    1. 询问

    2. 理解分数在最终分数中的权重,是理解分数和中-宏观等级分数的线性加权和

    3. 针对所提问题显示的前 k 个结果

  10. 上面的数据是通过请求体中的 POST 方法返回的,并且都是字符串,所以您将它转换为 write 数据类型,并且还获得一个实际值,而不是 HTML 元素的值。

  11. 您返回一个render_template()函数来呈现与端点相关联的 HTML 或 URL。请注意,您传递了许多变量和render_template()。这有助于使用后端数据将逻辑嵌入到标记中。这是使用 Jinja 模板完成的(下面将详细讨论)。

  12. 最后,通过使用服务器监听请求的地址和端口号调用 Flask 应用程序来运行它。

了解 index.html

您的索引文件看起来像这样

    <form action="{{url_for('top_k_results',_anchor='resultsView')}}" method="post">
            <div class="container my-4">
              <p class="font-weight-bold">Questions</p>
              <select class="mdb-select md-form" id="question-select" name="question_select">
                <option value="" disabled selected>Choose your question</option>
                <option value='1' {% if question_select=='1' %} selected {% endif %}>Is the virus transmitted by aerosol,
                  droplets, food, close contact, fecal matter, or water?</option>
                <option value='2' {% if question_select=='2' %} selected {% endif %}>How long is the incubation period for
                  the virus?</option>
    ....
      <button type="submit" class="btn btn-primary btn-block btn-large">Get Top Results</button>
          </form>
        </div>

您使用一个表单从前端获取 post 请求。你看到的怪异模板{{}}叫做 Jinja 模板。它帮助创建 HTML、XML 和其他标记格式,这些格式通过 HTTP 响应返回给用户。

您可以使用从您与之交互的端点作为响应传递的任何变量。这很有帮助。在你的用例中,你事先不知道用户希望看到多少回答,所以这不是静态的。

看看复制一个你想要的结果数量的模板有多简单?

    <ul class="timeline">
              {% for result in results %}
              <li class="timeline-item bg-white rounded ml-3 p-4 shadow">
                <div class="timeline-arrow"></div>
                <h2 class="h5 mb-0">{{result.title}}</h2><span class="small text-gray"><i class="fa fa-clock-o mr-1"></i>{{result.task}}</span>
                <p class="text-small mt-2 font-weight-light">{{result.passage[0]}}<strong><span
                      style="color:orange">{{result.passage[1]}}</span></strong>{{result.passage[2]}}</p>
              </li>
              {% endfor %}
            </ul>

到现在为止,你应该对你的 Flask 应用程序及其结构有了很好的了解。在我结束这部分之前,我想让你看看你的 Flask 应用程序的目录树。

|   all_question_comprehension.csv
|   app.py
|
+---static
|   |   favicon-32x32.png
|   |
|   +---css
|   |       bootstrap.min.css
|   |       choices.min.css
|   |       font-awesome.min.css
|   |       index.css
|   |       jquery.mCustomScrollbar.min.css
|   |
|   \---js
|           bootstrap.bundle.min.js
|           choices.min.js
|           index.js
|           jquery-3.3.1.slim.min.js
|           jquery.mCustomScrollbar.concat.min.js
|
+---templates
|       index.html
|

要运行 Flask app,使用操作系统的命令行工具进入项目文件夹目录,输入flask run,如图 10-4 所示。

img/502837_1_En_10_Fig4_HTML.jpg

图 10-4

运行 flask 的 Windows 命令以在本地主机上启动应用程序

将你的申请归档

到目前为止,您已经构建了您的应用程序。现在可以在服务器上部署它了。虽然对于您的用例来说,由于您没有使用太多的库和包,因此没有必要对您的应用程序进行 dockerize,但是这是可以随着时间而改变的,因此可以缩短您的应用程序的生命周期。

此外,您在 Windows 上编码,但是大多数部署服务器是基于 Unix 的内核。如果代码利用 GPU,很有可能当您将这个应用程序投入使用时,会出现包问题和硬件资源使用问题。

因此,要创建一个独立的便携式机器,并保持真实的当前配置,您需要 Docker 平稳地完成将您的应用程序从笔记本电脑带到生产环境的旅程。

Note

要在您的系统上安装 Docker,请参考位于 https://docs.docker.com/desktop/ 的非常简单的指南。

创建 Docker 图像

为了创建 Docker 映像,即包含运行应用程序所需的所有配置和依赖信息的单个文件,您必须创建 Docker 文件。它包含所有的启动命令,这些命令在容器脱离后执行。容器是图像的运行实例。例如,房子的蓝图是图像,实际的房子是容器。就像您可以使用一个蓝图来创建许多房子一样,Docker 映像可以用来创建许多在单独的容器中运行的实例。

以下命令用于创建 Dockerfile 文件:

  • 复制

  • 工作目录

  • 揭露

  • 奔跑

  • CMD 或 ENTRYPOINT

基本图像和来自命令

每个 Docker 容器都是一个图像,在一堆只读层之上有一个读/写层。这意味着你从一个操作系统发行版开始,比如说 Linux Ubuntu,它是你的只读层,然后继续添加不同的层,比如 Anaconda,来设置你的 Python 环境和库,比如 Flask、pandas 和 NumPy,来运行你的应用程序。见图 10-5 。

img/502837_1_En_10_Fig5_HTML.jpg

图 10-5

Docker 容器是堆叠的图像

您可以使用 FROM 命令获取基础映像。这是构建 Dockerfile 文件的必要命令。对于您的应用程序,您将使用 continuum Anaconda 发行版。这张图片可以在 Docker hub 上找到,这是一个容器应用的集合: https://hub.docker.com/r/continuumio/anaconda3 .

复制并曝光

使用复制命令,你基本上传递你的文件和文件夹到 Docker 镜像。在您的情况下,这是包含您的 Flask 应用程序的covidquest文件夹。一旦复制完成,你就可以从 Docker 镜像中启动这个应用了。

EXPOSE 命令告诉 Docker OS 的网络为服务器打开一些端口来监听请求。

工作方向、运行和命令

WORKDIR 帮助您设置工作目录,在您的情况下,这个目录就是app.py文件所在的位置。这通常是您使用 COPY 命令将文件复制到的目录。

RUN 命令帮助您安装一组依赖项和库,以便在容器内运行应用程序。不是单独安装每个依赖项,而是使用一个包含所有特定版本所需文件的requirement.txt文件。

这不仅可以用于运行库安装,还可以用于运行任何其他命令行命令。显然,它随您选择的基础图像而变化。

docker 文件中的最后一个命令是 CMD,它是容器的启动命令。这就像你在你的本地。

Dockerfile

现在您已经掌握了这些知识,您终于可以使用这些命令来构建您的 Docker 映像了。

你首先复制你的covidquest文件夹,并将其重命名为covidquest_docker.在这个文件夹中,你创建你的 docker 文件.,它将是一个无扩展名的文件。您的目录现在看起来会像这样:

|   Dockerfile
|
\---covidquest
    |   all_question_comprehension.csv
    |   app.py
    |   requirements.txt
    |
    +---static
    |   |   favicon-32x32.png
    |   |
    |   +---css
    |   |       bootstrap.min.css
    |   |       choices.min.css
    |   |       font-awesome.min.css
    |   |       index.css
    |   |       jquery.mCustomScrollbar.min.css
    |   |
    |   \---js
    |           bootstrap.bundle.min.js
    |           choices.min.js
    |           index.js
    |           jquery-3.3.1.slim.min.js
    |           jquery.mCustomScrollbar.concat.min.js
    |
    \---templates
            index.html

将以下命令添加到 docker 文件中。您可以使用任何文本编辑器,但是要确保 docker 文件没有扩展名。

FROM continuumio/anaconda3
    MAINTAINER Anshik, https://www.linkedin.com/in/anshik-8b159173/
RUN mkdir /app
COPY ./covidquest /app
WORKDIR /app
EXPOSE 5000
RUN pip install -r requirements.txt
CMD flask run --host 0.0.0.0

还有一点需要注意的是,requirements.txt保存在app文件夹中,因为使用这个映像派生出来的多个容器将确切地知道哪些库用于构建这个应用程序逻辑。

建立码头形象

最后,使用以下命令构建 Docker 映像(参见图 10-6 ):

img/502837_1_En_10_Fig6_HTML.jpg

图 10-6

建立码头工人形象

docker build -t <docker_image_name> .

Note

t 标志用于给新创建的映像命名。

根据您的网络速度,此过程可能需要一些时间。图 10-7 显示图像是否已经创建。

img/502837_1_En_10_Fig7_HTML.jpg

图 10-7

Docker 图像列表

创建映像后,您可以使用以下命令运行容器。-p flag下面的命令用于向主机发布容器的端口。这里,您将 Docker 容器中的端口 5000 映射到主机上的端口 5000,这样您就可以在 localhost:5000 访问应用程序。见图 10-8 。

img/502837_1_En_10_Fig8_HTML.jpg

图 10-8

运行 Docker 容器

即使您按下 Ctrl + C 或 CMD + C,容器仍将在后台运行。

请注意,每个 Docker 容器都与一个 ID 相关联。您可以使用命令docker container ls找出有多少个容器正在运行,如图 10-9 所示。

img/502837_1_En_10_Fig9_HTML.jpg

图 10-9

列出码头集装箱

确保在使用后关闭容器(图 10-10 )。如果不这样做,它会抛出如下错误:

img/502837_1_En_10_Fig10_HTML.jpg

图 10-10

关掉容器

    (tfdeploy) C:\Users\bansa\Desktop\Book\Chapter 10\covidquest_docker>docker run -p 5000:5000 -d covidquest
    4778247c6c95a5a5093edd1279b03a1e41e243afb6ab84788752c9629fbaf69b
docker: Error response from daemon: driver failed programming external connectivity on endpoint funny_jemison (dc7d4acc7671b41c701558a8c4200406ec9f0474e360e8aea38b075cc1c2d5d0): Bind     for 0.0.0.0:5000 failed: port is already allocated.

当构建 Docker 映像covidquest并运行 Docker 容器时,会产生大量垃圾,比如

  • 停止的容器

  • 至少一个容器未使用的网络

  • 图像(参见图 10-7

  • 构建缓存

您可以通过运行命令删除所有这些不需要的文件并回收空间

docker system prune

用 Heroku 让它活起来

既然您已经将应用程序进行了 docker 化,那么您可以将它带到任何您想要的地方,并将它部署到一个实际的地址。但是在此之前,让我们先了解一下开发服务器。

到目前为止,您一直使用的是 Flask 自己的开发服务器。从某种意义上说,这个服务器非常有限,它不能很好地处理多个用户或多个请求。

当在生产环境中运行 web 应用程序时,您希望它能够处理多个用户和多个请求,这样就不会有明显的页面和静态文件加载时间。

为了使服务器更加“适合生产”,您可以使用 Gunicorn。Gunicorn 是一个用于 WSGI (Web 服务网关接口)应用程序的纯 Python HTTP 服务器。它允许您通过在 Heroku(也称为 dynos)委托的机器上运行多个 Python 进程来并发运行任何 Python 应用程序。

为了让您的应用程序在生产环境中运行,您需要进行某些更改。您需要更改 Docker 文件:

FROM continuumio/anaconda3
    MAINTAINER Anshik, https://www.linkedin.com/in/anshik-8b159173/
## make a local directory
RUN mkdir /app
COPY ./covidquest /app
# Not required by Heroku
# EXPOSE 5000
WORKDIR /app
RUN pip install -r requirements.txt
# CMD flask run --host 0.0.0.0
    CMD gunicorn app:app --bind 0.0.0.0:$PORT --reload

您还可以添加一个 Procfile。Procfile 是一种用于声明描述应用程序运行方式的进程类型的格式。流程类型声明其名称和命令行命令。这是一个原型,可以实例化为一个或多个运行的流程,如 Docker 容器。

它是一个无扩展文件,包含以下进程,基本上是一个 gunicorn 进程,告诉app.py文件它必须运行,因为它包含处理请求的函数/端点:

web: gunicorn app:app --log-file=-

您的covidquest_docker目录现在看起来像这样:

    |   Dockerfile
    |
    \---covidquest
        |   all_question_comprehension.csv
        |   app.py
        |   Procfile
        |   requirements.txt
        |
        +---static
        |   |   favicon-32x32.png
        |   |
        |   +---css
        |   |       bootstrap.min.css
        |   |       choices.min.css
        |   |       font-awesome.min.css
        |   |       index.css
        |   |       jquery.mCustomScrollbar.min.css
        |   |
        |   \---js
        |           bootstrap.bundle.min.js
        |           choices.min.js
        |           index.js
        |           jquery-3.3.1.slim.min.js
        |           jquery.mCustomScrollbar.concat.min.js
        |
        \---templates
                index.html

你终于准备好深入 Heroku 了。Heroku 是一个 PaaS 系统,它通过完全托管的数据服务来帮助构建数据驱动的应用。要了解更多关于 Heroku 的信息,请观看视频“Heroku 解释:冰山、伐木工人和公寓”

您将通过使用 Heroku CLI 来实现这一目的。Heroku 命令行界面(CLI)使得直接从终端创建和管理 Heroku 应用程序变得简单。这是使用 Heroku 的重要部分。您可以从 https://devcenter.heroku.com/articles/heroku-cli 开始按照 CLI 安装。

运行图 10-11 所示的命令,检查是否成功设置了 Heroku。

img/502837_1_En_10_Fig11_HTML.jpg

图 10-11

检查 Heroku 版本

接下来,你必须登录 Heroku。您可以通过在命令行输入命令heroku login来实现,该命令会将您重定向到浏览器进行登录。成功登录后(图 10-12 ,关闭选项卡,返回 CLI。

img/502837_1_En_10_Fig12_HTML.jpg

图 10-12

英雄库登录

现在,您可以使用命令heroku create <app-name>创建 Heroku 应用程序。这为 Heroku 接收你的源代码做好了准备。Heroku 不允许你取别人已经取过的名字。但在此之前,确保您移动到app目录(图 10-13 )。

img/502837_1_En_10_Fig13_HTML.jpg

图 10-13

创建 Heroku 应用程序

Heroku 在registry.heroku.com上运行一个容器注册表。在 CLI 中,您可以使用命令登录

heroku container:login

或者通过 Docker CLI

    docker login --username=<email-id> --password=$(heroku auth:token) registry.heroku.com

但是在您将应用程序推送到 Heroku 容器注册表之前,您需要告诉 Heroku CLI 您想要为哪个应用程序运行此命令。为此,您可以使用git init将文件夹转换成 Git 存储库。如果已经是 Git 回购,那就不用担心了。

之后,您为 repo 添加应用程序名称,并创建一个 git remote。Git 远程是位于其他服务器上的存储库版本。您可以通过将代码推送到与您的应用相关联的 Heroku 托管的特殊遥控器来部署您的应用。

heroku git:remote -a <your_app_name>

要构建一个映像并将其推送到容器注册表,请确保您的目录包含一个 Dockerfile 并运行命令heroku container: push web.参见图 10-14 。

img/502837_1_En_10_Fig14_HTML.jpg

图 10-14

用 Heroku 建立并推广码头工人形象

在您成功地将一个映像推送到容器注册中心之后,您可以创建一个新的版本。每当您部署代码、更改配置变量或修改应用程序的附加资源时,Heroku 都会创建一个新版本并重启您的应用程序。您可以通过使用

    heroku container:release web

最后,您可以使用以下命令打开您的应用程序。这将在浏览器中打开应用程序(图 10-15 )。

img/502837_1_En_10_Fig15_HTML.jpg

图 10-15

使用 URL 部署的应用程序

    heroku open

由于您正在使用免费层,该应用程序将在 30 分钟的空闲时间后关闭。为了让你的应用永远保持运行,你可以探索付费应用。

结论

这是一段漫长的旅程。如果你做到了这一步,你就是一个摇滚明星。我希望在这次超过七个案例研究的旅程中,您对当前医疗保健系统提供的机会以及为什么您需要应用高级人工智能和人工智能技能来规模化医疗保健感到好奇和兴奋。

您了解了不同的民族如何拥有不同的采用率(第三章),以及如何从 EHR 文本中提取 ICD-9 代码,以帮助处理数十亿美元的保险系统使用最新的语言理解模型“变形金刚”。然后,您探索了像 GCNs 这样的高级模型,这些模型不仅利用实体信息,还利用它们之间的联系,以便更好地从可用数据中学习。

在第六章中,你探讨了任何行业最大的痛点,尤其是医疗保健行业,因为获取模型的训练数据需要大量的专业知识。您了解了即将推出的一款充满动力的产品——浮潜,它让半监督学习变得可行。

第七章向您介绍了使用联邦学习训练 ML 模型的另一种方式。医疗保健在消费者(患者)、创造者(制药公司)和分销商(医生和政府机构)之间取得了恰当的平衡。由于有如此多的利益相关者涉及不平等的权力和资源,这就引出了一个问题,即我们如何保护个人隐私的权利,同时又能促进科学的发展。您了解了如何使用隐私保护机制来实现这一点。

第八章详细讨论了各种类型的医学图像数据及其各种格式。您还了解了如何处理两种不同且非常流行的图像结构,2d 和 3d,并分别解决了这些图像上的一些最重要的检测和分割任务。您还了解了如何使用迭代器优化数据流水线。

第九章带你来到我们将如何与计算机系统互动的未来。在之前的十年里,完成一项任务(比如买衣服)的点击次数已经大大减少了。随着 UI 和金融技术的进步,我们正在走向一个我们将只是与机器聊天的时代,QnA 是迈向这一时代的第一步。

最后,您部署了您构建的内容,因为如果世界看不到它,它将不会使任何人受益。

我希望你能继承从这本书中学到的知识,并且这些知识点燃了你拥抱、开发和部署你脑海中的下一个伟大的 ML 应用程序想法的火焰。