云中的基因组学-全-

云中的基因组学(全)

原文:zh.annas-archive.org/md5/96b87207eabbfbb3144bddeba563aaa9

译者:飞龙

协议:CC BY-NC-SA 4.0

第八章:高级主题

在前一章中,您已经概述了使用 Argo CD 配方实施 GitOps 工作流的概述。Argo CD 是一个著名且具有影响力的开源项目,既适用于简单的用例,也适用于更复杂的用例。在本章中,我们将讨论在您在 GitOps 旅程中前进时所需的主题,以及在多集群场景下管理安全性、自动化和高级部署模型的内容。

安全性是自动化和 DevOps 的关键方面。 DevSecOps 是一个新的定义,其中安全性是整个 IT 生命周期中共享的责任。此外,《DevSecOps 宣言》(https://www.devsecops.org)将安全性视为操作和减少摩擦的价值贡献代码的一部分。这与 GitOps 原则相一致,其中一切都是声明性的。

另一方面,这也引出了一个问题,即如何避免在 Git 中存储未加密的明文凭证。正如克里斯蒂安·埃尔南德斯的书《通往 GitOps 的路径》中所述,Argo CD 目前幸运地提供了两种模式来管理 GitOps 工作流中的安全性:

  • 在 Git 中存储加密的秘密,例如使用 Sealed Secret(见食谱 8.1)

  • 将秘密存储在外部服务或保险库中,然后只在 Git 中存储对这些秘密的引用(参见食谱 8.2)

本章随后介绍了高级部署技术,展示了如何使用 Argo CD 管理 Webhooks(参见食谱 8.3)和 ApplicationSets(参见食谱 8.4)。ApplicationSets 是 Argo CD 的一个组件,允许从单个 Kubernetes 资源管理多个应用程序、存储库或集群的部署。从本质上讲,这是一个模板化系统,可以在多个 Kubernetes 集群中部署和同步 GitOps 应用程序(参见食谱 8.5)。

最后但同样重要的是,本书以使用 Argo Rollouts 实现 Kubernetes 的渐进式交付结束(食谱 8.6),这对于使用蓝绿或金丝雀等先进部署技术部署应用程序非常有用。

8.1 加密敏感数据(密封密钥)

问题

您希望在 Git 中管理 Kubernetes Secrets 和加密对象。

解决方案

密封密钥 是 Bitnami 的一个开源项目,用于将 Kubernetes Secrets 加密为 SealedSecret Kubernetes 自定义资源,表示一个安全存储在 Git 中的加密对象。

密封密钥使用公钥加密技术,由两个主要组件组成:

  • 一个 Kubernetes 控制器,了解用于解密和加密加密秘密的私钥和公钥,并负责协调。该控制器还支持私钥的自动轮换和密钥过期管理,以强制重新加密秘密。

  • kubeseal 是开发人员用来在提交到 Git 存储库之前加密其秘密的命令行界面。

SealedSecret 对象仅由运行在目标 Kubernetes 集群中的 SealedSecret 控制器加密和解密。这个操作仅由这个组件独占,因此没有其他人可以解密该对象。kubeseal CLI 允许开发者将普通的 Kubernetes Secret 资源转换为 SealedSecret 资源定义,如 Figure 8-1 所示。

在您的 Kubernetes 集群中使用 Argo CD,您可以从 GitHub 项目的发布版本 中为您的操作系统安装 kubeseal CLI。在撰写本书时,我们使用的是版本 0.18.2。

小贴士

在 macOS 上,kubeseal 可以通过 Homebrew 安装如下:

[PRE0]

Sealed Secrets with GitOps

图 8-1. Sealed Secrets with GitOps

安装 CLI 后,您可以按如下方式安装控制器:

[PRE1]

您应该获得类似以下的输出:

[PRE2]

例如,让我们为部署在 第五章 中的 Pac-Man 游戏创建一个 Secret:

[PRE3]

您应该有以下输出:

[PRE4]

这里您可以看到 YAML 表示:

[PRE5]

[PRE6]

现在,您可以通过以下方式将 Secret 转换为 SealedSecret

[PRE7]

[PRE8]

1

这里找到由 Sealed Secrets 控制器加密的数据。

现在,您可以安全地将您的 SealedSecret 推送到 Kubernetes 部署清单存储库并创建 Argo CD 应用程序。这是来自 本书仓库的示例

[PRE9]

检查应用程序是否正在运行并且健康:

[PRE10]

您应该获得类似以下的输出:

[PRE11]

8.2 使用 ArgoCD 加密 Secrets(ArgoCD + HashiCorp Vault + External Secret)

问题

您希望避免在 Git 中存储凭据,并希望在外部服务或保险库中管理它们。

解决方案

在 Recipe 8.1 中,您看到了如何按照 GitOps 声明式方式管理 Git 中的加密数据,但如何避免即使加密凭据也与 GitOps 一起存储?

其中一种解决方案是 External Secrets,这是一个由 GoDaddy 最初创建的开源项目,旨在将密钥存储在不同供应商的外部服务或保险库中,然后仅在 Git 中存储对这些密钥的引用。

如今,External Secrets 支持诸如 AWS Secrets Manager、HashiCorp Vault、Google Secrets Manager、Azure Key Vault 等系统。其思想是为存储和管理密钥生命周期的外部 API 提供用户友好的抽象。

ExternalSecrets 深度上是一个 Kubernetes 控制器,它从包含指向外部密钥管理系统中密钥的参考的自定义资源中调解 Secrets 到集群。自定义资源 SecretStore 指定了包含机密数据的后端,并定义了模板,通过它可以将其转换为 Secret,如您在 Figure 8-2 中看到的那样。SecretStore 具有配置以连接到外部密钥管理系统。

因此,ExternalSecrets 对象可以安全地存储在 Git 中,因为它们不包含任何机密信息,只包含管理凭据的外部服务的引用。

使用 Argo CD 的外部 Secrets

图 8-2. 使用 Argo CD 的外部 Secrets

您可以按照以下步骤使用 Helm Chart 安装 External Secrets。在撰写本书时,我们使用的是版本 0.5.9:

[PRE12]

您应该会得到类似以下的输出:

[PRE13]

要开始使用 ExternalSecrets,您需要设置一个 SecretStore 或 ClusterSecretStore 资源(例如,通过创建一个 vault SecretStore)。

更多关于不同类型的 SecretStores 及如何配置它们的信息可以在我们的 GitHub 页面 找到。

提示

您还可以通过 OperatorHub.io 从 OLM 安装 External Secrets Operator。

例如,您可以使用支持的提供商之一,如 HashiCorp Vault,执行以下操作。

首先下载并安装适用于您操作系统的 HashiCorp Vault,并获取您的 Vault Token。然后按照以下步骤创建一个 Kubernetes Secret:

[PRE14]

然后创建一个 SecretStore 作为对这个外部系统的引用:

[PRE15]

1

运行您的 Vault 的主机名

2

包含 vault 令牌的 Kubernetes Secret 的名称

3

用于访问包含 vault 令牌内容的 Kubernetes Secret 中的值的关键:

[PRE16]

现在,您可以按照以下步骤在您的 Vault 中创建一个 Secret:

[PRE17]

然后从 ExternalSecret 中引用它,如下所示:

[PRE18]

[PRE19]

现在,您可以按照以下步骤使用 External Secrets 使用 Argo CD 部署 Pac-Man 游戏:

[PRE20]

8.3 自动触发应用程序部署(Argo CD Webhooks)

问题

您不想等待 Argo CD 同步,而是希望在 Git 中发生更改时立即部署应用程序。

解决方案

虽然 Argo CD 每三分钟轮询 Git 仓库以检测受监视的 Kubernetes manifests 的更改,但它也支持来自流行 Git 服务器(如 GitHub、GitLab 或 Bitbucket)的 webhook 通知的事件驱动方法。

在您的 Argo CD 安装中启用了 Argo CD Webhooks,并且可以在端点 /api/webhooks 处使用。

要使用 Minikube 测试 Argo CD 的 webhook,您可以使用 Helm 安装一个本地 Git 服务器,如 Gitea,一个用 Go 编写的开源轻量级服务器,如下所示:

[PRE21]

您应该会得到类似以下的输出:

[PRE22]

提示

使用默认凭据登录到 Gitea 服务器,您可以在 Helm Chart 的 values.yaml 文件中找到它们,或者通过覆盖定义新的凭据。您可以在这里找到 Helm Chart 的链接:here

Pac-Man manifests 仓库导入 Gitea。

配置 Argo 应用:

[PRE23]

要向 Gitea 添加 Webhook,请导航到右上角,然后单击“设置”。选择“Webhooks”选项卡,并按照图 8-3 中所示进行配置:

  • 负载 URL:http://localhost:9090/api/webhooks

  • 内容类型:application/json

Gitea Webhooks

图 8-3. Gitea Webhooks
提示

在这个示例中,您可以忽略密钥;但是,最佳实践是为您的 Webhooks 配置密钥。从文档中了解更多信息。

保存并推送更改到 Gitea 上的仓库。在您推送后,您将立即看到来自 Argo CD 的新同步。

8.4 部署到多个集群

问题

您想要将应用程序部署到不同的集群。

解决方案

Argo CD 支持 ApplicationSet 资源来“模板化” Argo CD Application 资源。它涵盖了不同的用例,但最重要的是:

  • 使用 Kubernetes 清单来针对多个 Kubernetes 集群。

  • 从一个或多个 Git 仓库部署多个应用程序。

由于 ApplicationSet 是一个包含运行时占位符的模板文件,我们需要用一些值来填充这些占位符。为此,ApplicationSet 具有 生成器 的概念。

生成器负责生成参数,这些参数最终将替换模板占位符,以生成有效的 Argo CD Application

创建以下 ApplicationSet

[PRE24]

1

定义一个生成器

2

设置参数的值

3

Application 资源定义为模板

4

cluster 占位符

5

url 占位符

通过运行以下命令应用先前的文件:

[PRE25]

当将此 ApplicationSet 应用于集群时,Argo CD 会生成并自动注册两个 Application 资源。第一个是:

[PRE26]

第二个:

[PRE27]

通过运行以下命令检查两个 Application 资源的创建:

[PRE28]

输出应类似于(已截断):

[PRE29]

通过删除 ApplicationSet 文件来删除这两个应用程序:

[PRE30]

讨论

我们已经看到了最简单的生成器,但截至本书编写时总共有八个生成器:

列表

通过一个固定的集群列表生成 Application 定义。(这是我们之前看到的那个)。

集群

类似于 List,但基于 Argo CD 中定义的集群列表。

Git

根据 Git 仓库中的 JSON/YAML 属性文件或仓库的目录布局生成 Application 定义。

SCM 提供者

通过组织内的仓库生成 Application 定义。

拉取请求

从开放式拉取请求生成 Application 定义。

集群决策资源

使用 鸭子类型 生成 Application 定义。

矩阵

结合两个独立生成器的值。

合并

合并两个或更多生成器的值。

在前面的示例中,我们从一个固定的元素列表中创建了Application对象。当可配置环境的数量较少时,这样做是合适的;例如,在示例中,两个集群分别指代两个 Git 文件夹(ch08/bgd-gen/stagingch08/bgd-gen/prod)。对于多个环境的情况(即多个文件夹),我们可以动态地使用Git生成器来为每个目录生成一个Application

让我们将前面的示例迁移到使用 Git 生成器。作为提醒,使用的 Git 目录布局是:

[PRE31]

创建类型为ApplicationSet的新文件,为配置的 Git 存储库中的每个目录生成一个Application

[PRE32]

1

配置 Git 存储库以读取布局

2

开始扫描目录的初始路径

3

Application定义

4

Git 存储库中与路径通配符(stagingprod)匹配的目录路径

5

目录路径(完整路径)

6

最右边的路径名

应用资源:

[PRE33]

由于有两个目录,Argo CD 创建了两个应用程序:

[PRE34]

此外,当您的应用程序由不同组件(服务、数据库、分布式缓存、电子邮件服务器等)组成,并且每个元素的部署文件放置在其他目录中时,此生成器也非常方便。或者,例如,一个包含所有要安装在集群中的操作器的存储库:

[PRE35]

Git 生成器可以创建具有在 JSON/YAML 文件中指定的参数的Application对象,而不是响应目录。

以下片段显示了一个示例 JSON 文件:

[PRE36]

这是用于响应这些文件的ApplicationSet的节选:

[PRE37]

1

查找放置在app所有子目录中的所有config.json文件

2

注入在config.json中设置的值

ApplicationSet将为匹配path表达式的文件夹中的每个config.json文件生成一个Application

参见

8.5 将拉取请求部署到集群

问题

部署应用程序预览时,希望在创建拉取请求时进行。

解决方案

使用pull request生成器自动发现存储库中的所有打开拉取请求,并创建一个Application对象。

让我们创建一个ApplicationSet来响应在配置的存储库上创建带有preview标签的 GitHub 拉取请求。

创建名为bgd-pr-application-set.yaml的新文件,其内容如下:

[PRE38]

1

GitHub 拉取请求生成器

2

组织/用户

3

存储库

4

选择目标 PR

5

轮询时间以检查是否有新的 PR(60 秒)

6

使用分支名称和编号设置名称

7

设置 Git SHA 号码

运行以下命令应用先前的文件:

[PRE39]

现在,如果您列出 Argo CD 应用程序,您会发现没有注册任何应用程序。原因是仓库中尚未带有preview标签的拉取请求:

[PRE40]

对仓库创建拉取请求并标记为preview

在 GitHub 中,拉取请求窗口应类似于图 8-4。

GitHub 中的拉取请求

图 8-4. GitHub 中的拉取请求

等待一分钟,直到ApplicationSet检测到更改并创建Application对象。

运行以下命令来检查是否已检测和注册更改:

[PRE41]

检查Application对拉取请求的注册:

[PRE42]

当拉取请求关闭时,Application对象会自动删除。

讨论

撰写本书时,支持以下拉取请求提供者:

  • GitHub

  • Bitbucket

  • Gitea

  • GitLab

ApplicationSet 控制器每隔requeueAfterSeconds时间间隔轮询以检测变更,同时还支持使用 Webhook 事件。

要进行配置,请参考 Recipe 8.3,但同时在 Git 提供商中也启用发送拉取请求事件。

8.6 使用高级部署技术

问题

您希望使用蓝绿部署或金丝雀发布等先进的部署技术来部署应用程序。

解决方案

使用 Argo Rollouts 项目来更新应用程序的部署。

Argo Rollouts 是一个 Kubernetes 控制器,提供先进的部署技术,如蓝绿部署、金丝雀发布、镜像、暗金丝雀、流量分析等,以供 Kubernetes 使用。它与许多 Kubernetes 项目集成,如 Ambassador、Istio、AWS 负载均衡器控制器、NGNI、SMI 或 Traefik 用于流量管理,以及 Prometheus、Datadog 和 New Relic 等项目用于执行分析以推动渐进式交付。

要将 Argo Rollouts 安装到集群中,请在终端窗口中运行以下命令:

[PRE43]

虽然不是强制性的,但我们建议您安装 Argo Rollouts Kubectl 插件以可视化部署过程。请按照说明安装它。一切就绪后,让我们部署 BGD 应用程序的初始版本。

Argo Rollouts 不使用标准 Kubernetes Deployment 文件,而是一个名为 Rollout 的特定新 Kubernetes 资源。它类似于Deployment对象,因此支持其所有选项,但它添加了一些字段以配置滚动更新。

让我们部署应用程序的第一个版本。当 Kubernetes 执行滚动更新时,我们将定义金丝雀发布过程,本例中的步骤如下:

  1. 将 20%的流量转发到新版本。

  2. 等待人类决定是否继续进行。

  3. 自动将 40%、60%、80%的流量转发到新版本,每次增加后等待 30 秒。

创建一个名为bgd-rollout.yaml的新文件,内容如下:

[PRE44]

1

金丝雀发布

2

要执行的步骤列表

3

设置金丝雀比例

4

发布被暂停

5

暂停 30 秒后继续发布

6

template部署定义

应用资源以部署应用程序。由于没有先前的部署,金丝雀部分被忽略:

[PRE45]

目前,根据replicas字段指定了五个 Pod:

[PRE46]

并使用 Argo Rollout Kubectl 插件:

[PRE47]

让我们部署一个新版本以触发金丝雀滚动更新。创建一个名为bgd-rollout-v2.yaml的新文件,内容与上一个文件完全相同,但将环境变量COLOR的值更改为green

[PRE48]

应用先前的资源并检查 Argo Rollouts 如何执行滚动更新。再次列出 Pod 以确保 20%的 Pod 是新的,而另外 80%是旧版本:

[PRE49]

1

新版本 Pod

并使用 Argo Rollout Kubectl 插件执行相同操作:

[PRE50]

请记住,滚动更新过程暂停,直到操作员执行手动步骤才能让进程继续。在终端窗口中运行以下命令:

[PRE51]

该发布被提升并继续以下步骤,即每 30 秒替换旧版本 Pod 为新版本:

[PRE52]

新版本逐步部署到集群完成滚动更新。

讨论

Kubernetes 不原生实现高级部署技术。因此,Argo Rollouts 使用已部署的 Pod 数量来实现金丝雀发布。

正如前面提到的,Argo Rollouts 与像Istio这样提供高级流量管理功能的 Kubernetes 产品集成。

使用 Istio,在基础设施级别正确执行流量分流,而不像第一个示例中玩弄副本数量。Argo Rollouts 与 Istio 集成以执行金丝雀发布,自动更新 Istio 的VirtualService对象。

假设您已了解 Istio 并且具有安装了 Istio 的 Kubernetes 集群,您可以通过将Rollout资源的trafficRouting设置为Istio来执行 Argo Rollouts 与 Istio 的集成。

首先,创建一个带有 Istio 配置的Rollout文件:

[PRE53]

1

金丝雀部分

2

引用指向新服务版本的 Kubernetes Service

3

指向旧服务版本的 Kubernetes 服务的引用

4

配置 Istio

5

更新权重的 VirtualService 的参考

6

VirtualService 的名称

7

VirtualService 中的路由名称

8

部署 Istio 边车容器

然后,我们创建两个 Kubernetes 服务,指向相同的部署,用于将流量重定向到旧版本或新版本。

下面的 Kubernetes 服务用于 stableService 字段:

[PRE54]

金丝雀版本与之相同,但名称不同。它是在 canaryService 字段中使用的服务:

[PRE55]

最后,创建 Istio 虚拟服务,由 Argo Rollouts 更新每个服务的金丝雀流量:

[PRE56]

1

稳定的 Kubernetes 服务

2

金丝雀 Kubernetes 服务

3

路由名称

应用这些资源后,我们将启动应用程序的第一个版本:

[PRE57]

Rollout 对象上发生任何更新时,将按照解决方案中描述的方式启动金丝雀发布。现在,Argo Rollouts 自动更新 bgd 虚拟服务 的权重,而不是调整 Pod 数量。

参见

第二章:引言

我于 1985 年从数学领域转入基因组学领域——比该领域正式诞生早了大约一年。术语基因组学于 1986 年首次被提出,同时也见证了在冷泉港实验室举行的首场关于启动人类基因组计划概念的公开辩论。

很难想象自那时以来发生了多大的变化。计算机几乎没有出现在生物医学中——白头研究所生物医学研究的初步设计,建立于上世纪 80 年代初,没有考虑到计算机的安排。大量数据被视为一种麻烦,而非资产——在一篇报道人类基因组计划辩论的《自然》杂志文章中,该杂志的生物学编辑写道:“如果现代生物学的技能和创造力已经被拉伸到解释已知重要性序列(例如 DMD 和 CGD 基因序列)的程度,那么更多序列能有什么可能用途?”

尽管有诸多疑虑,生物学家们最终决定继续前进——1990 年启动了人类基因组计划,这是他们的首个重大数据收集努力之一。其中一个重要动机是希望采用系统方法——而非猜测——来发现导致人类疾病的基因。1980 年,杰出的生物学家大卫·博茨坦提出了一种方法,通过追踪基因在家族中的遗传来定位罕见单基因疾病的位置,相对于人类基因组中的 DNA 变异遗传图谱。然而,要实现这一想法的全部力量,需要绘制——并最终测序——整个人类基因组。

人类基因组计划是一个非同寻常的合作项目,涵盖了六个国家和二十个机构,历时十三年,耗资 30 亿美元。当风尘落定时,世界上获得了一个单个人类基因组长达 30 亿核苷酸的 DNA 序列。

完成这一项目后,许多生物学家认为业务将恢复正常。但随后发生的事情更加引人注目。在接下来的 15 年里,生物学成为了信息科学——大规模数据的生成重塑了这一领域。例如:

  • 家族中的遗传图谱揭示了导致 5000 多种严重罕见单基因疾病的基因。

  • 在人群中进行新型遗传图谱的研究,发现了大约 10 万个特定基因区域与常见疾病和特征的稳健关联。

  • 对数千个肿瘤的遗传分析揭示了数百个新的基因突变推动了癌症的发生。

令人惊讶的是,测序一个人类基因组的成本降低了五百万倍——从 30 亿美元降至 600 美元——而且成本预计在未来几年可能会降至 100 美元以下。到目前为止,已经测序了一百多万个基因组。总体而言,各种类型的基因组数据大约每八个月翻一番。

没有强大的新计算方法和工具来处理生成的许多新类型数据,这一切都是不可能的。一个很好的例子是由 Broad 研究所的同事开发的基因组分析工具包,在本书中您将会详细了解更多相关信息。

今天,生命科学正处于新数据爆炸的中心。许多国家正在进行系统性的努力,将基因组和医疗数据收集到国家生物库中,这将使研究人员能够进一步探索普通和罕见疾病以及特征的遗传学。尤其重要的是确保世界各地的全部遗传多样性都在这些大规模努力中得到充分代表,而不仅仅是欧洲后裔。

由于近年来技术的惊人进步,我们现在不仅可以阅读 DNA 蓝图,还可以了解这些蓝图在个体细胞中作为 RNA 的读取方式。已经开发出方法来单细胞水平读取基因表达,初始分析 18 个细胞很快发展到分析超过 1800 万个细胞。这项工作促成了国际人类细胞图谱项目的诞生,涉及全球 60 多个国家。这些数据集开始使得可以使用计算方法,包括现代机器学习,系统地推断细胞底层的电路。

然而,随着生物应用的蓬勃发展,我们常常受制于我们访问和共享数据的系统性限制。世界上大多数生物医学数据传统上都存储在孤立的数据岛上,只能通过服务器访问,每个授权的研究人员或团体必须将其下载到自己机构的计算基础设施中。从纯技术角度来看,这是不可持续的。我们需要的是能够让研究人员在数据所在地操作数据的系统。我们还需要更透明的数据管理模型,以及有效的评估、执行和审计能够访问数据以及用途的方式。我们应该遵循以下四个原则:(1) 复制数据不应该是数据共享的默认模式;(2) 安全和审计应该内置并达到企业级;(3) 大规模分析应该对所有研究团体可访问;以及 (4) 计算资源应该是弹性的,可以根据需要扩展或缩减。

云计算已经成为这些挑战技术方面的领先解决方案。然而,在实践中,它也带来了需要创造性解决的新障碍。

在 Broad Institute,我们四年前开始向云端迁移,以应对不断增长的基因组数据潮流。我们首先通过将基因组数据处理操作从传统的本地系统转换为云端系统来积累经验,从基因序列平台生成数据的那一刻起就在云端运行。这一举措要求重新考虑整个流程的每一个方面,并从零开始构建全新的系统,以处理每天从测序机器中流出的几 TB 数据。但这只是个开始。一旦数据上传到云端,我们遇到了下一个障碍:当前状态下,云服务对于生命科学研究人员而言可能难以使用,除非他们接受过高级培训。因此,我们与合作伙伴合作开发了一个软件和分析平台 Terra。

随着生物医学研究中云端化的推广,其他类似平台也应运而生。今天,我们与许多其他团体合作,建立了一个互联的组件联邦数据生态系统,提供互补的服务和能力。我们期望这些平台将有助于促进开放协作,以整合涵盖多个领域和学科的数据、工具和专业知识。我们还希望降低个体研究人员参与基于云端生态系统的技术门槛,特别是那些可支配的 IT 资源较少的研究人员。

根据所有报告,基因组向云端的过渡仍处于早期阶段。在 Broad Institute,我们在通向云端的旅程中学到了许多艰难的经验,每天都在学习更多。在这样颠覆性变革的时代,团体之间分享彼此的经验至关重要。

这就是为什么我如此激动,因为不可比拟的 Geraldine Van der Auwera,长期以来在 Broad Institute 为研究社区发声的倡导者,以及 Brian O’Connor,在 UCSC 倡导软件和数据互操作性的积极倡导者,共同撰写了这本书。这本书捕捉了我们迄今为止所学到的精髓,并为新手提供了一个可访问的路径,加入基因组云生态系统。

Eric S. Lander,麻省理工学院和哈佛大学 Broad Institute 创始院长

第一章:引言

我们生活在一个巨大机遇的时代:技术进步使得我们可以生成关于一切的详细和全面的数据,从整个基因组的序列到单个细胞的基因表达模式。我们不仅可以生成这类数据,而且生成的量还非常庞大

在过去的十年中,全球测序数据量呈现惊人增长,这得益于短读取测序成本的大幅降低,这项技术在第二章中有详细介绍(图 1-1)。像长读取测序和单细胞转录组学这样的新技术近来迅速发展,承诺未来成本会继续大幅下降,并且更多地提供 ’组学实验设计的机会。

A) 数据集增长预测; B) Broad Institute 数据生产增长。

图 1-1. 记录的测序数据集增长至 2015 年及未来十年的预测增长(上图);Broad Institute 数据生产增长(下图)。^(1)

生物和生命科学中大数据的机遇与挑战

任何人,从个人实验室到大型机构,很快都能生成大量的数据。截至目前,被视为大规模项目的包括数十万个基因组的全基因组序列。在未来的十年中,我们预计会看到数百万基因组和转录组的测序项目,同时还会涌现出各种新的数据类型,如高级细胞成像和蛋白质组学。这些大量的数据和多样的新数据类型的存在,承诺将使研究人员更接近回答生物学中一些最困难——尽管看似简单的问题。例如,人体内有多少种细胞类型?什么遗传变异会导致疾病?癌症是如何产生的,我们能否更早预测?因为研究本质上是一项团队运动,我们希望广泛分享即将到来的大量数据,分享我们分析这些数据的算法,并与更广泛的世界分享我们的发现。

基础设施挑战

对研究人员而言,由于成本下降和可用的扩展实验设计的双重机会而产生了一系列挑战。身处技术前沿并非易事,每一项新技术都伴随其自身的复杂性。你如何准确地读取穿过纳米孔时飞快移动的单个碱基?你如何在不破坏它们的情况下在三维中成像活细胞?你如何比较一个实验室的单细胞表达数据与另一个实验室的数据,同时纠正由批次效应造成的差异?这些只是在开发或优化新实验设计时我们面临的技术挑战长列表中的几个例子。

但困难并不仅限于数据生成;如果说有什么的话,那只是一个开始。当实验完成并且你手头有了数据时,你将面临一个全新的复杂世界。事实上,在"组学"研究中最具挑战性的一个方面是决定如何在数据生成后处理数据。当你的成像研究每次实验产生一太字节的数据时,你会把这些图像存储在哪里以便让它们易于访问呢?当你的全基因组测序研究生成一组包含临床和表型数据以及序列数据的复杂混合物时,你如何组织这些数据使其在你自己的团队内及在发布时对更广泛的研究社区可见呢?当你需要更新你的方法论以使用分析软件的最新版本来处理超过 100,000 个样本时,你如何扩展你的分析呢?你如何确保你的分析技术能够在不同环境、不同平台和不同组织中正常工作呢?你如何确保你的方法能够被生命科学家复现,而这些科学家往往没有或几乎没有计算机方面的正规培训呢?

在本书中,我们向你展示如何利用公共云——通过互联网提供的按需计算服务来解决一些基础设施挑战。但首先,让我们谈谈为何我们认为云是一个特别吸引人的解决方案,并识别可能适用的一些限制。

这并不意味着列举出所有可用选项的详尽清单;就像实验设计的景观多样,使用云在研究中也有很多种方式。相反,本书专注于利用广泛使用的工具和方法,包括由基因组分析工具包(GATK)提供的最佳实践基因组工作流程(见图 1-2),这些工作流程使用工作流描述语言(WDL)实现,可以在研究计算中常用的任何平台类型上运行。我们将向你展示如何在Google Cloud Platform (GCP)上使用它们,首先通过 GCP 自己的服务,然后通过由 Broad Institute 和 Verily 运行在 GCP 顶部的Terra平台。

GATK 提供了一系列最佳实践工作流程,用于处理各种实验设计的序列数据。

图 1-2. GATK 提供了一系列最佳实践,用于处理各种实验设计的序列数据。

通过从这种端到端堆栈开始,您将获得基础技能,使您能够利用其他许多工作流语言、分析工具、平台和云中的选项。

朝向基于云的数据共享与分析生态系统

从 图 1-1 中,您可以看到数据已经以超过受尊敬的 摩尔定律 的速度增长,正如我们早些时候讨论的,新的生成大量数据的实验设计如雨后春笋般出现。这些数据的洪流在很多方面是推动科学计算迁移到云端的主要动力。然而,重要的是要理解,在当前形式下,公共云主要是一组低级基础设施组件,对于大多数目的而言,真正帮助研究人员管理其工作并回答他们正在调查的科学问题的是我们在这些组件之上构建的内容。这都是更大转变的一部分,利用云托管的数据、计算和可移植算法实现,以及使其更易于使用的平台、让平台互相通信的标准和一套使科学对每个人开放的概念原则。

云托管的数据和计算

生物学这个大数据时代的黎明面临的第一个巨大挑战在于如何使这些大型数据集对研究社区可用。传统方法如 图 1-3 所示,涉及中心化的存储库,感兴趣的研究人员必须从中央库下载数据副本,然后在其机构的本地计算环境中进行分析。然而,“把数据带给人们”的这种方法已经相当浪费(每个人都付费存储相同的数据副本),并且在我们预期的数据大规模增长(数据集数量和大小)面前无法扩展,这些数据以 PB(1,000 TB)为单位进行测量。

例如,在接下来的五年中,我们估计美国国家卫生研究院(NIH)和其他组织将托管超过 50 PB 的基因组数据,需要对研究社区开放访问。对于任何单个研究人员来说,花时间下载如此多的数据太多了,对于每个研究机构来说,本地托管其研究人员的数据也太多了。同样,分析基因组、成像和其他实验设计的计算要求非常显著。并非每个人都准备好具有数千个 CPU 的计算集群。

近年来显而易见的解决方案是颠覆现有模式,将“人们带到数据中来”。与其将数据存储在封闭的仓库中,我们将其托管在广泛可访问的存储库中,并直接连接到计算资源;因此,任何具有访问权限的人都可以在数据所在地进行分析,而无需转移任何数据,如图 1-3 所示。尽管这些要求(广泛访问和与存储共存的计算)可以通过多种技术解决方案实现,但最常用的是公共云基础设施。我们在第三章中详细介绍了这些内容,作为技术入门的一部分;现在,只需想象云就像任何机构的高性能计算(HPC)设施,只是通常规模更大、配置选项更加灵活,并且任何人都可以租用设备的时间。

云计算的流行选择包括 亚马逊网络服务(AWS),GCP 和 微软 Azure。每个平台都提供计算和存储的基础服务,以及更高级的服务;例如,GCP 上的 Pipelines API,在我们进行大规模分析时使用,详见第十章。

数据共享模型的反转。

图 1-3. 数据共享模型的反转。

与传统的 HPC 集群不同,你通常会根据环境编写脚本来进行分析,而图 1-3 中提出的模型则鼓励思考分析方法的可移植性。随着多个云竞争市场份额,每个云存储和提供对多个数据集的访问,研究人员希望能够将他们的算法应用于数据所在的任何地方。因此,在过去几年中,高度可移植的工作流程语言变得流行起来,包括我们在本书中使用并在第八章中进一步探讨的 WDL,通用工作流语言(CWL)和 Nextflow

生命科学研究平台

将研究数据迁移到云端的负面影响在于,它给研究计算的本来就不轻松的世界增加了一整套新的复杂性(或者可能是几套)。尽管一些研究人员可能已经接受过足够的培训或者个人喜好使他们能够有效地在工作中使用云服务,但这些人无疑是少数。生物医学研究社区的绝大多数成员通常并不足以处理公共云服务提供的“原始”服务,因此迫切需要开发适合研究人员需求、屏蔽操作细节的平台和界面,让这些研究人员能够专注于科学研究。

几个流行的平台提供了易于使用的网页界面,专注于为研究人员提供点击操作的方式来利用云存储和计算。例如,Terra(我们在 Chapter 11 中探讨,并贯穿整本书使用)、Seven BridgesDNAnexusDNAstack 都向研究人员提供这些先进的网络平台。

这些及类似平台可能具有不同的用户界面和不同的功能重点,但它们的核心是为用户提供一个工作空间环境。这是一个研究人员可以汇集他们的数据、元数据和分析工作流的地方,并在此过程中与他们的合作者分享。然后,工作空间的隐喻允许研究人员运行分析——例如,在 Terra 平台上,这可以是使用 WDL 进行批处理工作流,或者使用 Jupyter Notebook 进行交互式分析——而无需深入了解底层云服务的细节。我们在第 11、12 和 13 章中看到了这一过程。要点在于,这些平台使研究人员能够利用云的强大能力和规模,而无需处理底层的复杂性。

基础设施的标准化和重复使用

看起来研究人员可以使用多个云,多个团体在这些云平台上构建了平台,并解决了数据和计算在研究人员可以轻松访问的位置上共存的类似问题。而这背后的另一面是,我们需要这些独特的数据存储库和平台能够在不同组织之间实现互操作性。事实上,将数据和分析移至云端的一个重要希望之一是,它将打破过去使得跨多个数据集进行协作和应用分析变得困难的传统壁垒。想象一下,能够将 PB 级数据引入单一的交叉分析中,而无需担心文件的位置、如何传输它们以及如何存储它们。现在有个好消息:联邦数据分析的这一梦想已经成为现实,并且正在持续快速改善!

实现无论平台和云如何使用数据的这一愿景的关键在于标准。诸如全球基因组与健康联盟(GA4GH)等组织先驱性地协调了平台间通信的方式。这些标准涵盖从 CRAM、BAM 和 VCF 等文件格式(本书中将经常使用)到连接存储、计算、发现和用户身份之间的应用程序接口(API)。谈论 API 和文件格式可能听起来乏味或干燥,但现实是我们希望云平台支持共同的 API,以允许研究人员打破云平台之间的障碍并使用无论位置何在的数据。

除了标准之外,软件架构、视觉共享和组件重用是实现互操作性的另外关键驱动因素。在过去几年中,五家美国组织在 NIH 机构和项目的支持下共同开发云基础设施,并合作开发共享视野下的可互操作基础设施组件,形成了数据生物圈的共享愿景。这五个合作伙伴组织的技术领导者,分别是位于田纳西州纳什维尔的范德堡大学、加州大学圣克鲁斯分校(UCSC)、芝加哥大学、Broad 研究所以及 Alphabet 旗下的 Verily 公司,在 2017 年 10 月发表在Medium 的博客文章中明确了这一开放生态系统的共享愿景。数据生物圈强调四个关键支柱:社区驱动、基于标准、模块化和开源。除了我们建议您全文阅读的宣言外,这些合作伙伴还将这些原则整合到了各自正在构建和运营的组件和服务中。

综合来看,在 GA4GH 的基于社区的标准开发以及 Data Biosphere 中的系统架构愿景和软件组件共享推动了我们的共同前进。这些协作努力的结果是,今天,您可以登录 Broad Institute 的 Terra 平台,快速从由芝加哥大学、Broad Institute 等托管的多个存储库中导入数据到 Terra 的私人工作空间,从Dockstore 方法库导入工作流,并在 Google Cloud 上安全地执行分析,只需几次点击,如图 1-4 所示。

Data Biosphere 原则的实践:在 Terra 中跨多个数据集进行联合数据分析,使用从 Dockstore 导入并在 GCP 中执行的工作流。

图 1-4. Data Biosphere 原则的实践:在 Terra 中跨多个数据集进行联合数据分析,使用从 Dockstore 导入并在 GCP 中执行的工作流。

明确一点,Data Biosphere 生态系统的完整愿景还远未实现。仍然存在一些重要的障碍需要克服;有些纯粹是技术上的,但其他一些则根植于驱动个人、社区和组织的实践和激励因素。例如,在元数据中形式描述数据属性的标准化需求仍然突出,这影响跨数据集的可搜索性以及联合数据分析的可行性。具体而言,如果不同数据集中等效的数据文件在元数据中的标识方式不同,就很难对来自这些数据集的样本应用联合分析了(在一个数据集中是input_bam,在另一个是bam,在第三个是aligned_reads)。要解决这个问题,我们需要相关研究社区齐心协力制定共同标准。技术可以用来执行选择的约定,但首先需要有人(最好是几个人)站出来制定这些标准。

作为人驱动而不是技术驱动的另一个绊脚石例子,生物医学研究显然会受益于建立机制,可以在基础设施平台之间无缝运行联合分析;例如,从云到云(Google Cloud 和 AWS),从云到本地(Google Cloud 和您机构的本地 HPC 集群),以及任何您可以想象的多平台组合。在那个主题上存在一些技术复杂性,尤其是在身份管理和安全认证方面,但一个重要的障碍是,这个概念并不总是与商业云供应商和软件提供商的商业模式对齐。更普遍地说,许多组织需要参与开发和运营这样的生态系统,这带来了从法律领域(数据使用协议、运营授权和各国隐私法律)到技术领域(基础设施互操作性、数据协调)的一系列复杂性问题。

尽管如此,过去几年取得了显著进展,我们正朝着数据生物圈的愿景迈进。尽管在各种赠款项目上直接竞争,许多组和组织正在积极合作建设互操作云基础设施组件,这表明这一愿景有着充满活力的未来。共享目标是构建可以彼此交换数据和计算的平台——允许研究人员在所选择的环境中查找、混合和匹配数据——正在变为现实。Terra 作为一个平台处于这一趋势的前沿,并且是从 NCI国家人类基因组研究所(NHGRI)国家心脏、肺和血液研究所(NHLBI)人类细胞图谱计划Verily 的 Project Baseline 等项目中提供广泛的研究数据集访问的重要组成部分。这是可能的,因为这些项目正在采用 GA4GH API 和数据生物圈的常见架构原则,使它们与 Terra 和其他采纳这些标准和设计哲学的平台兼容。

成为 FAIR

到目前为止,在本章中,我们涵盖了很多内容,从生命科学数据的显著增长开始,以及这如何对数据下载的旧模式造成压力,并推动研究人员转向使用云来进行存储和计算的更好模式。我们还看了社区在标准化数据和计算在云上可访问方式方面的努力,以及数据生物圈的哲学如何塑造各平台共同努力,以便研究人员可以访问这些平台。

对于那些不希望重复造轮子并且有动机在尽可能多的地方重用 API、组件和架构设计的平台构建者来说,这种模型的好处是显而易见的。但是,从研究人员的角度来看,GA4GH 的这些标准和 Data Biosphere 的架构如何转化为他们研究中的改进呢?

总的来说,像 Terra 这样的平台中应用的这些标准和架构原则使研究人员能够使他们的研究更加 FAIR:易于发现、易于访问、可互操作、可重用。^(2) 我们在第十四章中会更详细地探讨这一点。但现在,思考到这些平台构建者描述的工作是为了使他们的系统、工具和数据更加 FAIR,对于研究人员同样如此。通过采用云技术、用像 WDL 这样的语言编写可移植的工作流,在 Terra 上运行分析,并在 Dockstore 上分享工作流,研究人员可以使自己的工作更加 FAIR。这使得其他研究人员可以找到和访问分析技术,进行互操作性,将分析在不同地方运行,并最终重用工具,作为开展新发现的一个基石。在全书中,我们从平台构建者和研究人员的角度回顾了 FAIR 原则。

总结和下一步

现在我们已经为您介绍了为何基因组学作为一门学科正在向云端转移的核心动机,让我们概述一下本书如何帮助您开始在这个新的世界中,正如在前言中所述。我们设计它作为一次旅程,引导您通过一系列技术主题的发展,最终解决上述基础设施挑战,向您展示如何在云端完成您的工作,并使其变得 FAIR。

请记住,有许多不同的方法可以解决这些挑战,使用不同的解决方案,我们仅关注一种特定的方法。然而,我们希望接下来的章节能为您在自己的工作中打下坚实的基础:

第二章 和 第三章

我们探索了生物学的基础和云计算技术。

第五章 到 第七章

我们深入探讨了 GATK 工具包以及当前适用于生殖系和体细胞突变发现的最佳实践流程。

第八章 和 第九章

我们描述了如何通过用 WDL 编写的工作流自动化您的分析并使其可移植。

第十章 和 第十一章

我们首先在 Google Cloud 上开始扩展分析,然后在 Terra 上进行。

第十二章

我们通过在 Terra 中使用 Jupyter 进行交互式分析,补充了基于工作流的分析。

第十三章 和 第十四章

我们向您展示如何在 Terra 中创建自己的工作空间,并将您学到的一切汇集起来,向您展示如何制作一个完全 FAIR 的论文。

本书结束时,我们希望您对基因组数据分析的当前最佳实践有深入了解,能够舒适地使用 WDL 表达您的分析过程,在 Terra 上能够进行工作流和交互式分析,并与合作者分享您的工作。

让我们开始吧!

^(1) Stephens ZD 等人,“大数据:天文学或基因组学?”PLoS 生物学 13(7): e1002195(2015)。https://doi.org/10.1371/journal.pbio.1002195

^(2) 科学数据管理和监护的 FAIR 指导原则 由 Mark D. Wilkinson 等人撰写,这一原则集的原始出版物。

第二章:基因组学概述:给这一领域的新手的入门指南

现在我们已经在第一章中为您提供了本书的广泛背景,是时候深入了解基因组学的更具体的科学背景了。在本章中,我们简要介绍了关键生物概念和相关实用信息,这些内容对于您完全理解本书中的练习是非常必要的。我们回顾了遗传学的基础知识,并逐步定义了基因组学的概念,这将框定本书的科学范围。在此框架确立之后,我们然后审视基因组学变异的主要类型,重点介绍它们在基因组中的表现形式。最后,我们讨论了在生成和处理用于基因组分析的高通量测序数据时涉及的关键过程、工具和文件格式。

根据您的背景,这可能是一个快速复习,或者它可能是您对许多这些主题的第一次介绍。最初的几个部分旨在面向在生物学、遗传学和相关生命科学方面没有接受过多正式培训的读者。如果您是生命科学的学生或专业人士,请随意浏览,直到遇到您尚不了解的内容为止。在后续章节中,我们逐步介绍更多基因组学特定的概念和工具,这些内容对所有但最经验丰富的从业者来说应该是有用的。

基因组学介绍

那么什么是基因组学呢?简短的回答,“它是基因组的科学”,如果您不知道基因组是什么以及为什么重要,这并没有真正帮助。因此,在我们深入讨论“如何进行基因组学”的详细内容之前,让我们退后一步,确保我们对基因组学的定义达成一致。首先,让我们复习涉及的基本概念,包括基因是什么以及它如何与 DNA 相关联。然后,我们逐步深入探讨特定定义范围内的关键数据类型和分析。请记住,这不会是所有可能被归为基因组学术语下的事物的详尽汇编,因为(a)本书并不意味着是一本科学教科书,(b)该领域仍在非常迅速地发展,有许多令人兴奋的新方法和技术正在积极开发中,因此在本书中尝试将它们编纂起来还为时过早。

基因作为遗传离散单元(某种程度上)

让我们从历史开始——基因。这是一个几乎普遍认可但不一定被很好理解的术语,因此值得探讨其起源并展开其演变(可以这么说)。

19 世纪关键思想家,如查尔斯·达尔文(《物种起源》的作者)和格里戈尔·孟德尔(“豌豆爱好者”),最早提到基因的概念。当时分子生物学还远未问世一百多年。他们需要一种方式来解释各种生物体中观察到的部分性状和组合性状的遗传模式,因此他们假设存在一种由父母传给后代的微观不可分割的粒子,并构成了物理特征的决定因素。这些假设的微粒最终在 20 世纪初由威廉·约翰森命名为基因

几十年后,罗莎琳德·富兰克林、弗朗西斯·克里克和詹姆斯·沃森阐明了现在著名的双螺旋DNA 结构:由称为脱氧核糖(DNA 中的 D)的分子的重复单元组成的两条互补链,这些链在实际信息内容中携带另一种称为核酸(DNA 中的 NA)的分子形式。DNA 中的四种核酸,更普遍地称为碱基,根据它们的首字母 A、C、G 和 T 分别命名为腺嘌呤胞嘧啶鸟嘌呤胸腺嘧啶。DNA 能够形成双螺旋的原因在于碱基的分子结构使得两条链可以配对:如果你把 A 碱基放在 T 碱基对面,它们的形状会吻合,就像磁铁一样吸引彼此。G 和 C 也是如此,只不过它们之间的吸引力比 A 和 T 更强,因为 G 和 C 形成三条键,而 A 和 T 只形成两条键。这种简单的生物化学互补性的含义巨大;遗憾的是,我们没有时间详细讨论所有这些,但我们留下了克里克和沃森在他们关于 DNA 结构的经典论文中的经典描述:

我们注意到我们假设的特异配对立即暗示了一种可能的遗传物质复制机制。

结合基因的概念和 DNA 的现实,我们发现我们所称之为基因的单元对应于我们在极长的 DNA 字符串中划定的相当短的区域。这些长串的 DNA 组成了我们的染色体,大多数人在高中生物课本中会认识到它们 X 形的波浪线。正如在图 2-1 中所示,这种 X 形状是染色体的一种压缩形式,其中 DNA 被紧密缠绕。

DNA、外显子、内含子、基因和染色体之间的关系。

图 2-1. 染色体(此处显示为两个姐妹染色单体,每个由一条名为脱氧核糖(DNA 中的 D)的分子的非常长的双链 DNA 组成),其中我们划分为由外显子和内含子组成的基因。

与许多其他生物一样,人类是二倍体的,这意味着你有两个来自每个生物父母的染色体副本:一个从每个生物父母那里继承。因此,你有每个存在于两条染色体上的基因的两个副本,这包括大部分的基因。在少数情况下,一些基因可能存在于一个染色体上而在另一个染色体上不存在,但我们不会在这里详细讨论。对于给定的基因,你的两个副本被称为等位基因,它们在序列上通常并非严格相同。如果你生育一个生物子代,你会向他们传递你的 23 对染色体中的每一对中的一条染色体,并且相应地,每个基因的两个等位基因之一。在变异发现的背景下,这个等位基因的概念将非常重要,它将适用于个体变异而非整体基因。

注意

虽然在流行媒体中,X 形状是染色体最常见的表现形式,但我们的染色体并不常常呈现那种形状。大部分时间它们是一种称为染色单体的杆状形式。当单个染色单体在细胞分裂事件之前复制时,两个相同的染色单体通过称为着丝粒的中心区域连接在一起,形成流行的 X 形状。要注意不要混淆姐妹染色单体(属于同一染色体)和你从每个生物父母那里继承的染色体复本。

如果我们更仔细地观察 DNA 区域的一个定义为基因的区段,我们会发现基因本身被分成称为外显子内含子的较小区域。当特定基因被调用时,外显子是将贡献其功能的部分,而内含子将被忽略。如果你很难区分它们,请试着记住显子是面的部分,而含子则是其部的片段。

所有这些都表明基因实际上并不像我们的前辈最初设想的那样是离散的粒子。如果说有什么,染色体更接近于离散的遗传单元,至少在物理意义上是这样,因为每个染色体对应于一条未断裂的 DNA 分子。实际上,由于你继承了一个染色体副本,很多由不同基因引起的特征会一起遗传,因为它携带的所有基因之间存在物理连接。然而,染色体遗传的现实比你想象的要复杂得多。在产生卵子和精子的细胞分裂之前,父母染色体副本会配对并在一个称为染色体重组的过程中交换片段,这个过程会重新组合物理上连接的特征组合,这些特征组合将传递给后代。注意:生物学就是这么复杂。

现在我们已经掌握了遗传继承的基本术语和机制,让我们来看看基因实际上是做什么的。

生物学的中心法则:DNA 转录为 RNA,再翻译为蛋白质

这个中心法则是我们理解基因是什么以及它们如何工作的下一个层次。这个想法是,基因中编码的“蓝图”信息以 DNA 的形式转录成一个紧密相关的中间形式,称为核糖核酸(RNA),我们的细胞随后将其用作将氨基酸串联成蛋白质的指南。 DNA 和 RNA 之间的主要区别在于 RNA 有一种不同的核苷酸(尿嘧啶而不是胸腺嘧啶,但它仍然与腺嘌呤结合),在生物化学上它不太稳定。你可以把 DNA 看作是主蓝图,RNA 是主蓝图的工作副本。这些工作副本是细胞将用来构建蛋白质的东西。这些蛋白质的功能范围从结构组分(例如肌纤维)到代谢剂(例如消化酶),最终作为我们身体的砖块和砖匠。

整个过程称为基因表达(参见图 2-2)。这个理论非常合理,解释了很多——但这并不是完整的画面。作为基因表达过程的描述,这本来是正确的,但几十年来,理论一直认为信息流是严格单向和全面的。是的,你猜对了:就像基因概念一样,这事实上是有些错误的。我们后来发现生命以多种方式违反了这个长期坚持的法则:例如通过仅限于 DNA 到 RNA 以及仅限于 RNA 的途径,这些途径以多种有趣和令人费解的方式修改基因表达。还存在通向蛋白质合成的途径,没有 RNA 模板的指导,称为非核糖体蛋白质合成,一些细菌利用它来产生扮演各种角色的小分子,如细胞间信号传导和微生物战争(即抗生素)。再次请记住,现实比卡通版本复杂得多,尽管卡通版本仍然有助于传达主要参与者之间最常见的关系:DNA、RNA、氨基酸和蛋白质。

生物学中心法则:DNA 导致 RNA;RNA 导致氨基酸;氨基酸导致蛋白质。

图 2-2. 生物学中心法则:DNA 导致 RNA;RNA 导致氨基酸;氨基酸导致蛋白质。

那么这种关系在实践中是如何运作的呢?如何从 DNA 序列到构成蛋白质的氨基酸?首先,DNA 到 RNA 的转录利用了一个巧妙的生化技巧:正如我们之前提到的,核苷酸彼此是互补的——A 粘附于 T/U,C 粘附于 G——因此,如果你有一个读作TACTTGATC的 DNA 字符串,细胞可以通过将适当(即粘性的)RNA 碱基置于每个 DNA 碱基的对面来生成“信使”RNA 字符串,从而产生AUGAACUAG。然后,遗传密码就开始发挥作用了:每组三个字母构成一个密码子,对应一个氨基酸,如图 2-3 所示。一种称为核糖体的细胞工具读取每个密码子,并将相应的氨基酸排列起来添加到链中,最终形成蛋白质。因此,我们的极简示例序列仅编码一个非常短的氨基酸链:仅甲硫氨酸(AUG),然后是天冬氨酸(AAC)。第三个密码子(UAG)只是简单地指示核糖体停止延长链。

遗传密码将信使 RNA 序列中的三字母密码子连接到特定的氨基酸。

图 2-3. 遗传密码将信使 RNA 序列中的三字母密码子连接到特定的氨基酸。

我们可以详细解释这一切是如何工作的,因为这确实非常有趣,所以我们鼓励你查阅更多资料以便了解更多。目前,我们想要强调的是这种机制对 DNA 序列的依赖性:改变 DNA 序列可能导致蛋白质的氨基酸序列发生变化,从而影响其功能。因此,让我们谈谈遗传变化。

DNA 突变的起源和后果

DNA 相当稳定,但有时会断裂(例如由于辐射或化学物质的作用),需要修复。负责修复这些断裂的酶偶尔会出错,并且会将错误的核酸(例如将 T 插入 A 的位置)并入其中。在细胞分裂期间复制 DNA 的酶也会犯错,有时这些错误会持续存在并传递给后代。除了我们在此不详细讨论的其他突变机制外,每代人从父母那里继承的 DNA 随着时间的推移会有所不同。绝大多数这些突变并不会产生任何功能效应,部分原因在于遗传密码中存在一定的冗余性:可以改变一些字母而不改变它们拼写的“单词”的含义。

然而,正如在图 2-4 中所示,一些突变确实会导致基因功能、基因表达或相关过程的改变。这些影响可能是积极的;例如,提高降解毒素的酶的效率,从而保护身体免受损害。其他则可能是消极的;例如,降低同一酶的效率。有些变化可能根据上下文具有双重影响;例如,一个众所周知的突变例子导致镰状细胞病,但同时对抗疟疾。最后,一些变化可能具有功能效果,但并不明显与直接的积极或消极结果相关,比如头发或眼睛颜色。

DNA 序列中的突变可以导致基因的蛋白质产物异常功能或完全失活。

图 2-4. DNA 序列中的突变可以导致基因的蛋白质产物异常功能或完全失活。

再次强调,这个主题远比我们在这里展示的要复杂得多;我们的目标主要是说明我们为什么如此关注确定人类基因组的确切序列。

基因组学作为基因组内和基因组间变异的清单

现在我们已经讲解了基础知识,我们可以退后一步,重新定义基因组学。我们可以说 DNA 和基因之间的关系可以粗略地总结为“基因由 DNA 组成,但你的 DNA 总体包括基因和非基因部分。”这种表达方式有些别扭,所以我们将使用术语基因组来指代“你的整体 DNA”。但是如果基因组意味着“包括(但不限于)你的基因在内的所有 DNA”,那么基因组学应该包括“基因组的科学”涵盖什么呢?

美国的NHGRI 网站宣称

遗传学指的是研究基因及特定特征或条件如何从一代传递到另一代的学科。基因组学描述了一个人所有基因(基因组)的研究。

这个定义的问题(除了相当随意地省略了“不是基因的所有 DNA”)是,一旦你开始在基因组中寻找某些特定内容,它就成为了遗传学问题。例如,在所有人类基因组中,哪些基因涉及到 2 型糖尿病的发展?这是基因组学还是遗传学?从技术上讲,你需要首先查看所有基因,以便将搜索范围缩小到对研究有意义的基因。因此,“一些基因”与“所有基因”之间的区别是一个相当模糊的区分,在我们的书中至少在上下文中并不完全有用。因此,我们更为狭义地定义基因组学作为一门任务是识别基因组的内容,无论是在个体、家庭还是人群层面。从这个角度来看,基因组学的目标是产生输入到下游遗传分析中的信息,最终会产生生物学的洞见。

这一定义包括识别哪些基因组内容是个体特有的,哪些是在个体和群体之间共享的,哪些是在个体和群体之间不同的过程。与此分支相关的几个学科包括转录组学,它是在基因组尺度上研究基因表达的学科。术语基因组学有时被用作这些学科的总称,尽管现在更普遍地使用更通用的术语组学。在机会出现时,我们不会详细讨论这些学科的细节。

注意

我们可以在个体意义上使用基因组(如“我的个人基因组”)和集体意义上使用基因组(如“人类基因组”)。

大规模基因组的挑战,按数字来看

人类基因组中大约有 30 亿个碱基对,或三个gigabases(GB),代表着一大批生物化学编码的信息内容。我们用来解读这些内容的技术会为一个基因组生成数亿条非常短的序列。具体来说,每个基因组大约有 4 千万条约 200 碱基的序列,这相当于每个基因组 100 GB 的文件。如果你做了计算,你会意识到这个数据集是冗余的,达到了 30 倍!这是有意的,因为数据充满了错误——有些是随机的,有些是系统性的——所以我们需要这种冗余来对我们的发现有足够的信心。在那些充满了冗余但错误的数据中,我们需要识别出四到五百万个小差异是真实的(相对于一个参考框架,我们将在下一节中讨论)。这些信息随后可以用于进行遗传分析,以确定哪些差异对特定的研究问题实际上很重要。哦,而且这只是一个人的情况;但我们可能想要将这应用到数百、数千、数万人或更多的群体中。(剧透警告:肯定更多。)

鉴于涉及的规模,我们将需要一些非常强大的程序来分析这些数据。但在我们开始处理计算方法之前,让我们深入探讨基因组变异的主题,更准确地定义我们将要寻找的内容。

基因组变异

基因组变异 可以采用多种形式,并且可以在生物体和种群的生命周期中的不同时间产生。在本节中,我们回顾描述和分类变异的方式。我们首先讨论用作变异目录共同框架的参考基因组的概念。然后,我们详细介绍基于涉及的物理变化定义的三个主要变异类别:短序列变异、拷贝数变异和结构变异。最后,我们讨论了生殖细胞变异和体细胞变异的区别,以及de novo突变在其中的位置。

作为共同框架的参考基因组

当涉及比较基因组之间的变异时,无论是在同一人体的细胞或组织之间,还是在个体、家庭或整个种群之间,几乎所有的分析都依赖于使用一个共同的基因组参考序列。

为什么?让我们看一个类似但更简单的问题。我们有三个现代句子,我们知道它们都演变自一个共同的祖先:

  1. 快棕色 fax 跳过了懒狗e

  2. _ 的狐狸跳过了它们的懒狗e

  3. 快棕色狐狸跳s过懒棕色狗。

我们希望以一种不偏向任何单一句子的方式列出它们的差异,并且对可能添加的新突变句子具有鲁棒性。因此,我们创建了一个综合的合成体,它最大程度地体现了它们的共同点,得出了这个结果:

快棕色狐狸跳过了懒狗 e。

我们可以将其作为一个共同的参考坐标系,用来绘制每个突变中不同的地方(即使不一定是唯一的):

  1. 第四个单词,o → a 替换;第九个单词,删除 e

  2. 第三个单词删除;第七个单词,添加 ir;第九个单词,添加 e

  3. 第五个单词 ed → s 替换;第八个单词后面的第三个单词重复出现

明显不是一个完美的方法,它给我们的并不是真正的祖先句子:我们怀疑狗原来的拼写不是这样的,我们也不确定原来的时态(jumps 还是 jumped)。但它使我们能够区分我们可以访问的人群中普遍观察到的内容与不同的内容。

我们可以在这个参考中引入尽可能多的句子,并且代表性越高,描述我们将来遇到的变异就越合适。

同样地,我们使用参考基因组来绘制个体的序列变异,相对于试图在基因组序列之间绘制差异。这使我们能够确定序列变异中哪些是普遍观察到的,哪些是特定样本、个体或群体独有的。

那么我们应该使用谁的基因组作为通用框架?在最简单的情况下,任何个体的基因组都可以用作参考基因组。然而,如果我们能够使用代表我们可能想要研究的最广泛个体群体的参考基因组,我们的分析将会产生更好的结果。

这导致了一个复杂的问题:为了使我们的参考基因组能够代表许多个体,我们需要评估每个基因组段的最常见观察到的序列,我们称之为单倍型。然后,我们可以使用这些信息来组成一个合成的混合序列。结果是一个在任何特定个体基因组中从未完全观察到的序列。

研究人员正试图通过使用更先进的基因组表示形式来解决这些限制,例如图形,其中节点代表不同的可能序列,它们之间的边允许您追溯给定的基因组。然而,为了我们在本书中展示的分析目的,我们使用了从科学界努力开始的高质量参考基因组,起源于人类基因组计划(HGP)。

理解普通参考的有效性受其开发过程中使用的原始样本多样性的限制是很重要的。当 HGP 在 2003 年宣布完成时,HGP 联盟发布的基因组序列实际上是基于极少数来自欧洲背景的人的合成参考。这限制了其作为非欧洲人群的参考的有效性,因为后来我们了解到,这些人群存在足够显著的差异,不能充分地映射回原始的欧洲中心参考。

自那时以来,已发布了连续的人类基因组参考“版本”,通常称为组装构建。除了通过纯技术进步提供更高质量外,这些对普通基因组参考的更新还通过包含来自历史上少有代表的人群的更多数据,增加了其代表性。值得强调的是,这些努力来补偿基因组研究参与者选择中的历史偏见是非常必要的。确实,研究数据中的代表性缺失会导致临床结果中的实际不平等,因为它影响了我们识别个体基因组序列中有意义变异的能力,特别是对于属于少数族裔的人群。

在人类中,迄今为止最显著的改进是在所谓的备用单倍型的表示中取得的,这些区域在不同人群中有时会有显著不同。最新的人类参考基因组构建,正式名称为GRCh38(用于基因组研究联盟的人类构建 38),但通常被昵称为Hg38(用于人类基因组构建 38),极大地扩展了代表备用单倍型的备用(ALT)连锁体的类型。这对我们检测和分析特定于携带备用单倍型人群的基因组变异具有显著影响。

作为最后的情节转折,请注意,所有当前的标准参考基因组序列都是单倍体,这意味着它们只代表每个染色体(或连锁体)的一份拷贝。最直接的后果是,在二倍体生物(如人类,它们有两份每个体染色体的拷贝,即任何不是性染色体 X 或 Y 的染色体)中,选择最常见的以杂合状态出现的位点的标准表示方式(表现为两种不同等位基因;例如 A/T)很大程度上是随意的。在多倍体生物中显然更糟,例如许多植物,包括小麦和草莓,它们有更多的染色体拷贝。虽然可以使用基于图的表示方法来表示参考基因组,以解决这个问题,但目前很少有基因组分析工具能够处理这种表示方式,而且在这个领域中,图基因组可能需要多年才能流行起来。

变异的物理分类

现在我们有了描述变异的框架,让我们来讨论我们将考虑的变异种类。我们基于它们所代表的物理变化,区分三大类变异,这些变异在图 2-5 中有所说明。短序列变异(单碱基变化和小插入或删除)与参考基因组相比是 DNA 序列的小变化;拷贝数变异是特定 DNA 片段数量的相对变化;而结构变异是相对于参考基因组某一 DNA 片段的位置或方向的变化。

通过对 DNA 进行物理变化分类的主要变异类型。

图 2-5. 通过对 DNA 物理变化分类的主要变异类型。

单核苷酸变异

早些时候,我们检查了混合句子:“快速棕色狐狸跳过了懒狗”。在三个句子的列表中,第一个句子使用了 fax 而不是 fox。在这里,我们可能会说在单词 fox 中发生了单个字母替换,将 o 替换为 a,因为“参考”说的是 fox

当我们将这个概念应用到 DNA 序列时——例如,当我们遇到一个 G 核苷酸而不是基于参考的 A 核苷酸时,我们称之为 单核苷酸 变异(见 图 2-6)。关于这些变异体的确切术语存在一些差异,足够合理的是,这种差异是一个单字母替换:根据上下文的不同,我们将它们称为 单核苷酸多态性(SNP;发音为“snip”),这在种群遗传学中最为合适;或者称为 单核苷酸变异(SNV;通常完整拼写,但有时发音为“sniv”),这在癌症基因组学中更为常见。

Single-nucleotide variants.

图 2-6. 单核苷酸变异。

SNV 是自然界中最常见的变异类型,因为导致它们的各种机制相当普遍,而它们的后果通常很小。生物功能往往对这些小变化具有鲁棒性,因为正如我们在概述中所看到的,遗传密码有多个密码子可以编码相同的氨基酸,所以单个碱基的变化并不总是导致蛋白质的变化。

插入和删除

在我们示例的第二句中,“The quick _ fox jumps over their lazy doge,” 我们看到了两种主要类型的小插入和删除:我们丢失了 brown 这个词,而 the 中的一个实例通过插入两个字母变成了 their。我们还有一个复杂的替换,jumpedjumps 取代,这很有趣,因为在没有额外信息的情况下,我们无法确定这种变化是在单个事件中发生的,即 ed 变为 s,还是 ed 先丢失,s 后来被添加上去。或者,jumpedjumps 的原始祖先可能是 jump,并且这两种形式后来通过独立的插入进化了!后一种情况似乎更有可能,因为它涉及更简单的事件链,但如果没有看到更多数据,我们无法确定。

应用于 DNA 序列,indels 是相对于参考序列的一个或多个碱基的插入或删除,如 图 2-7 所示。有趣的是,多项研究表明插入比删除更常见(至少在人类中)。尽管这一观察尚未完全解释,但可能与自然发生的移动基因元件有关,这是一种分子寄生虫,非常引人入胜,但可悲的是超出了本书的范围。

Indels can be insertions (left) or deletions (right).

图 2-7. Indels 可以是插入(左)或删除(右)。
注意

Indel 这个术语是通过结合 insertion 和 deletion 创造的混合词。

自然界中观察到的大多数插入缺失(indels)都比较短,长度小于 10 个碱基,但可以插入或删除的序列的最大长度没有限制。实际上,长于几百个碱基的插入缺失通常被分类为复制数或结构变异,我们将在接下来讨论。与单核苷酸变化相比,插入缺失更可能破坏生物功能,因为它们更可能引起相位移,即添加或删除碱基改变核糖体用于读取密码子的三个字母阅读框架。这可以完全改变插入缺失后序列的意义。它们还可能在编码区域插入时导致蛋白质折叠变化,例如。

复制数变异

通过复制数变异(图 2-8),我们进入了更复杂的领域,因为这个游戏不再仅仅是“发现差异”。此前的问题是“我在这个位置看到了什么?”现在问题转变为“我在任何给定区域看到了多少个副本?”例如,我们进化文本示例中的第三句,“The quick brown fox jumps over the lazy brown dog”中有两个单词brown的副本。

复制数变异的示例,由复制引起。

图 2-8. 复制数变异的示例。

在生物学背景下,如果你将每个单词想象成产生蛋白质的基因,这意味着我们可以从这个句子的 DNA 中产生比其他两个更多的“Brown”蛋白质。如果所讨论的蛋白质例如对抗危险化学品有保护作用,这可能是件好事。但如果细胞中的这种蛋白质数量影响另一种蛋白质的产生,这可能是件坏事:增加其中一种蛋白质可能会打破另一种蛋白质的平衡,引起有害的连锁反应,扰乱细胞过程。

另一个案例是等位基因比例的改变,即同一基因的两种形式之间的平衡。想象一下,你从父亲那里继承了一个缺陷基因的副本,该基因本应修复受损的 DNA,但没关系,因为你从母亲那里继承了一个功能性的副本。单一的功能性副本足以保护你,因为它产生足够的蛋白质来修复 DNA 的任何损伤。太好了。现在想象一下,在你的一个现在良性肿瘤中,有一部分经历了复制数事件:失去了来自你母亲的功能性副本,现在你只剩下来自你父亲的破损副本,这个副本无法产生 DNA 修复蛋白质。这些细胞不再能够修复任何 DNA 断裂(这可能是随机发生的),并开始积累突变。最终,肿瘤的这部分开始增殖,并变成癌细胞。不太好。

这些被称为剂量效应,展示了调节细胞代谢和健康的基因途径的生物学复杂性。DNA 拷贝数可以以其他方式影响生物功能,在所有情况下,效果的严重程度取决于许多因素。

举例来说,三体性症 是遗传疾病,患者某些染色体多了一份。最著名的是唐氏综合征,由于染色体 21 多了一份而引起。它与多种智力和身体障碍相关,但如果得到适当支持,携带这些变异的人可以过上健康而有成效的生活。影响性染色体(X 和 Y)的三体性症通常影响较少;例如,大多数三体性 X 染色体综合征的个体在医学上没有任何问题。相比之下,影响其他染色体的几种三体性症则非常不幸地与生命不相容,并且通常会导致流产或早期婴儿死亡。

结构变异

这一类变异包含了拷贝数变异和大片段插入/缺失,还包括各种复制中性结构改变,如易位和倒位,详见图 2-9。

结构变异示例。

图 2-9. 结构变异示例。

正如您从图 2-9 的例子中看到的那样,结构变异 可能相当复杂,因为它们涵盖了基因物质的一系列物理变换。一些变异可以跨越很大距离,甚至在某些情况下影响多个染色体。我们不会在本书中涵盖结构变异,因为这仍然是一个非常活跃的发展领域,相关方法尚未确定。

生殖细胞系变异与体细胞突变的区别

生殖细胞系变异与体细胞突变之间的基本生物学区别对于我们处理变异发现的方式有重大影响,具体取决于分析的特定目的。理解这一点可能有些棘手,因为它与遗传和胚胎发育的概念密切相关。让我们从生殖细胞系体细胞 这两个术语的定义开始。

生殖细胞系

生殖细胞系 这一术语结合了生物遗传的两个相关方面。细胞系 部分指的是种子的发芽概念,并不直接与微生物有关,尽管它是使我们称微生物为细菌 的同样概念。 部分指的是所有最终成为卵子或精子的细胞都来自一个单一的细胞系,这是胚胎发育早期分化的细胞群体,有一个共同的祖先。

实际上,术语 生殖系 常被用来指代个体的整个细胞群体作为生殖系。因此,你的生殖系变异原则上是你的生物父母的生殖系细胞中存在的变异,通常是产生你的卵细胞和/或精子在受精之前。因此,你的生殖系变异预计存在于你身体的每一个细胞中。在实践中,受精后不久产生的变异将很难或不可能与生殖系变异区分开来。

研究人员对生殖系变异感兴趣有多种原因:诊断具有遗传成分的疾病、预测发展疾病的风险、理解身体特征的起源、绘制人类种群的起源和演化——仅限于人类领域!生物种群内的生殖系变异也在农业、改良畜牧业、生态研究甚至微生物病原体的流行病学监测中极具兴趣。

体细胞

术语 体细胞 源自希腊词 soma,意为身体。它作为一个排除性术语,用来指代所有非生殖系的细胞,如图 2-10 所示。体细胞变异是在你一生中(包括在胚胎发育期间、出生前)个体细胞中的突变事件产生的,仅存在于你身体的部分细胞中。

这些突变可以由多种因素引起,从遗传易感性到环境暴露,例如辐射、香烟烟雾和其他有害化学物质。这些突变中有很多发生在你的身体中,但仅限于一个或少数几个细胞,并没有引起明显影响。在某些情况下,这些突变可以在受影响的细胞内引起连锁反应,损害它们调节生长的能力,并导致细胞肿瘤的形成。许多肿瘤幸运地是良性的;然而,有些会变成恶性并引发负面连锁反应,推动许多额外的体细胞突变的发生,并最终导致我们归类为癌症的代谢疾病。

生殖系变异与体细胞变异的比较

图 2-10. 人体所有细胞中均存在生殖系变异(左),而体细胞变异仅存在于部分细胞中(右)。

话虽如此,尽管癌症研究是体细胞变异分析的主要应用之一,体细胞变异的影响并不局限于肿瘤生长和癌症。它们还包括各种发育障碍,例如唐氏综合征,正如我们之前提到的,它是由染色体 21 号异常复制引起的;换句话说,是一种 CNV 形式。虽然唐氏综合征可以通过胚系遗传方式传播,但最常见的是由于胚胎发育早期发生的异常细胞分裂事件的结果。大多数情况下,这种情况发生得如此早,以至于几乎所有个体的细胞都受到影响。

这就是胚系变异和体细胞变异之间界线变得有些模糊的地方。正如我们刚刚讨论过的,你可以有一个孩子,出生时就带有在其发育早期形成的变异。就其起源而言,这是体细胞变异;然而,在分析你孩子的基因组时,它将表现为胚系变异,因为它存在于他们所有的细胞中。类似地,你可以在生活过程中的生殖细胞中获得体细胞突变;例如,如果在产生卵子或精子的细胞分裂过程中出现了问题。产生的变异明显是体细胞的起源,但如果你接下来生了一个从受影响细胞中发育出来的孩子,那么这个孩子将在其身体所有细胞中携带这些新变异。因此,就孩子而言,这些变异将是胚系变异;在这种情况下,由于孩子从你那里继承了它们,这一点就更加明显了。在这两种情况下,我们将这些变异称为拉丁语“de novo”意为“新发生”的de novo 变异,因为在孩子身上,它们似乎是从无中出现的。在本书中,我们将早期体细胞突变视为胚系突变。

高通量测序数据生成

现在我们知道我们在寻找什么了,让我们谈谈如何生成数据,以便我们能够识别和描述变异:高通量测序

我们用于序列化 DNA 的技术自从 Allan Maxam、Walter Gilbert 和 Fred Sanger 的开创性工作以来已经有了巨大的发展。许多墨水,无论是电子的还是其他形式的,都用于分类连续的浪潮或代际的测序技术。今天最广泛使用的技术是由 Illumina 推广的short read合成测序,它产生大量reads,即 DNA 序列字符串(最初大约 30 个碱基,现在可达大约 250 个碱基)。这通常被称为next-generation sequencing(NGS),与最初的Sanger 测序相对。然而,随着基于不同生化机制的新“第三代”技术的发展,生成更长 reads 的术语可以说已经过时了。

到目前为止,这些新技术并不直接替代 Illumina 短读取测序,在重测序应用中仍然占主导地位,但某些技术如 PacBio 和 Oxford Nanopore 已经在诸如de novo基因组组装等应用中得到了很好的确立。特别是 Nanopore 因其口袋大小版本 MinION 的可用性而越来越受欢迎,MinION 可以用于最小培训并通过 USB 端口连接到笔记本电脑。虽然 MinION 设备无法产生用于人类和其他大型基因组的常规分析所需的吞吐量,但它非常适合微生物检测等应用,对于这些应用来说,较小的产量已经足够。

在单细胞测序领域也发生了激动人心的进展,其中微流控设备用于分离和测序单个细胞的内容。这些通常侧重于 RNA,旨在识别例如组织间基因表达模式差异等生物现象。在这里,我们为简单起见专注于经典的 Illumina 式短读取 DNA 测序,但我们鼓励您关注该领域的最新发展。

从生物样本到大量读取数据

那么,我们如何使用短读取技术来测序某人的 DNA 呢?首先,我们需要收集生物材料(最常见的是血液或口腔拭子)并提取 DNA。然后,“准备 DNA 文库”,这涉及将整个 DNA 剪成相当小的片段,然后准备这些片段进行测序过程。在这个阶段,我们需要选择是测序整个基因组还是只测序子集。例如,当我们谈论整个基因组测序外显子测序时,不同的不是测序技术,而是文库制备:即 DNA 为测序准备的方式。我们在下一节中讨论主要类型的文库及其对分析的影响。现在,让我们假设我们可以检索出所有要测序的 DNA 片段;然后,我们添加用于跟踪的分子标签和适配器序列,这些序列将使片段在测序流动池中粘在正确的位置,如图 2-11 所示。

文库准备过程。

图 2-11. 大规模 DNA 文库制备过程(顶部);大规模 RNA 替代路径(底部)。

流动池是一种玻璃幻灯片,其内有液道通道,在适当的试剂通过时,测序化学反应就在其中发生循环。在此过程中,您可以在 Illumina 的在线文档中了解更多信息,测序仪使用激光和摄像机来检测由单个核苷酸结合反应发出的光。每个核苷酸对应于特定的波长,因此通过捕获每个反应周期产生的图像,机器可以从每个 DNA 片段的流动池上的读取中读取出碱基字符串,如图 2-12 所示。

Illumina 短读取测序概述。

图 2-12. Illumina 短读取测序概述。

通常情况下,反应被设置为从每个 DNA 片段的每一端产生一个读取,并且生成的数据仍保持为读取对。正如您稍后在本章中将看到的,相比于单个读取,每个片段的读取对为我们提供了可以在多个方面使用的信息。

高通量测序数据格式

Illumina 测序仪生成的数据最初存储在一种称为 Basecall(BCL)的格式中,但通常不直接与该格式交互。大多数测序仪配置为输出FASTQ,这是一种简单但体积庞大的文本文件格式,包含每个读取的名称(通常称为queryname)、其序列以及以 Phred 比例形式表示的碱基质量分数字符串,如图 2-13 所示。

FASTQ 和 Phred 比例。

图 2-13. FASTQ 和 Phred 比例。

Phred 比例是用于表达非常小的量(如概率和可能性)的对数比例尺,比原始的十进制数更直观;我们将遇到的大多数 Phred 比例值在 0 到 1000 之间,并四舍五入到最接近的整数。

当读取数据映射到参考基因组时,它被转换为序列比对映射(SAM),这是映射读取数据的首选格式,因为它可以包含映射和对齐信息,如图 2-14 所示。它还可以包含各种其他重要的元数据,分析程序可能需要在序列本身之上添加。与 FASTQ 类似,SAM 是一种结构化文本文件格式,因为它包含更多信息,所以这些文件确实可能会变得非常庞大。通常会遇到这两种格式的压缩形式;FASTQ 通常只是使用 gzip 实用程序进行压缩,而 SAM 可以使用专用实用程序压缩为二进制比对映射(BAM)或 CRAM,后者提供了额外的有损压缩程度。

SAM 格式的关键元素:文件头和读取记录结构。

图 2-14. SAM 格式的关键元素:文件头和读取记录结构。
注意

与 SAM 和 BAM 不同,CRAM 并不是一个真正的首字母缩写;它被命名为 SAM/BAM 命名模式的延伸,也是对“压缩(cram)”动词的一种玩笑,但“CR”并没有什么特别的含义。

在 SAM/BAM/CRAM 格式中,读取的局部对齐信息被编码在简洁特异性间隙对齐报告(CIGAR)字符串中。是的,某人确实希望它能够拼出“CIGAR”。简而言之,该系统使用单字母运算符表示一个或多个碱基与参考序列的对齐关系,并以数字前缀指定运算符描述的位置数,以便仅基于起始位置信息、序列和 CIGAR 字符串即可重建详细对齐信息。

例如,图 2-15 显示了一个读取序列,从一个软剪辑(忽略)碱基(1S)开始,然后是四个匹配位置(4M),一个缺失插入(1D),再两个匹配(2M),一个插入间隙(1I),最后是一个匹配(1M)。请注意,这仅仅是结构描述,并不意味着描述序列内容:在第一组匹配中(4M),第三个碱基与参考序列不匹配,因此它仅表示“在第四个位置上有一个碱基”。

CIGAR 字符串描述读取对齐的结构。

图 2-15. CIGAR 字符串描述读取对齐的结构。

让我们简要回顾一下图 2-15 中显示的软剪辑,我们将其称为“忽略”。软剪辑发生在读取序列的开头和/或结尾,当映射器无法满意地对齐一个或多个碱基时。映射器基本上放弃了,并使用软剪辑符号指示这些碱基不与参考序列对齐,并且在下游分析中应该忽略。正如您将在第五章中看到的那样,一些最终出现在软剪辑中的数据是可恢复的,并且可能很有用。

注意

在技术上,可以在 BAM 格式中存储读取数据,而无需包含映射信息,正如我们在第六章中所描述的那样。

长期存储大量序列数据是大型测序中心和许多资助机构关注的问题,促使各种团体提出了替代 FASTQ 和 SAM/BAM/CRAM 的格式。然而,到目前为止,没有一种能够在更广泛的社区中得到广泛采纳。

对于单个实验产生的读取数据量根据文库类型以及实验的目标覆盖度而异。覆盖度是一个衡量指标,总结了重叠给定参考基因组位置的读取数目。粗略地说,这反映了 DNA 文库中原始 DNA 副本的数目。如果这让你感到困惑,记住,文库制备过程涉及从生物样本中的许多细胞中分离的 DNA,并且(对于大多数文库制备技术)是随机进行碎片化的,因此对于基因组中的任何给定位置,我们预期可以获得来自多个重叠片段的读取对。

谈到文库,让我们更仔细地看看我们在这方面的选择。

DNA 文库类型:选择正确的实验设计

准备 DNA 文库的方法多种多样,就像分子生物学试剂公司一样多。一般而言,我们可以区分出三种主要方法,这些方法决定了供测序的基因组区域以及所得数据的主要技术特性:引物扩增全基因组准备靶向富集,用于生成基因组和外显子组,如图 2-16 所示。

实验设计比较。

图 2-16. 全基因组(顶部)和外显子(底部)的实验设计比较。

图 2-16 中显示的全基因组文库将导致在所有区域(外显子、内含子和基因间区)的测序覆盖,而外显子文库只导致在靶向区域(通常对应外显子)的覆盖。

引物准备

这类 DNA 准备技术依赖于经典的聚合酶链反应(PCR)扩增(使用特定设计的引物或限制酶位点),以产生引物,即大量复制的 DNA 片段,其起始和终止位置相同。这意味着供测序的基因组区域通常非常小,并且依赖于我们使用引物或限制酶预先确定的起始和终止点。

这种方法在小规模实验中相当经济,并且不需要参考基因组,因此在非模式生物的研究中很受欢迎。然而,它产生的数据具有巨大的 PCR 相关技术偏差。因此,在使用 GATK 和相关工具进行变异发现分析时,这种方法的适用性非常差;因此,我们在本书中不进一步介绍它。

全基因组准备

正如其名称所示,整个基因组准备的目标是对整个基因组进行测序。它涉及将 DNA 通过机械剪切过程处理,将其撕裂成大量小片段,然后基于其大小“过滤”片段,通常保留 200 到 800 碱基对之间的片段。因为 DNA 样品包含许多基因组的副本,且剪切过程是随机的,我们预计生成的片段将在整个基因组上冗余铺瓦。如果我们对每个片段进行一次测序,我们应该会看到基因组中的每个碱基多次出现。

在实践中,基因组的某些部分仍然很难对测序过程开放。在我们的细胞中,我们的 DNA 并不是简单地漂浮在周围,松散展开的;它的大部分都是紧紧卷起并与蛋白质复合物包装在一起。我们可以通过生化手段松弛它到一定程度,但有些区域仍然很难“解放”。此外,染色体的某些部分,如中心粒(中间部分)和端粒(末端部分),包含大片高度重复的 DNA,这些区域很难准确测序和组装。人类基因组参考序列实际上在这些区域是不完整的,需要专门的技术来探索它们。所有这些都是说,当我们谈论整个基因组测序时,我们指的是我们可以轻松访问的所有部分。尽管如此,即使存在这些限制,整个基因组测序覆盖的领域也是巨大的。

靶向富集:基因组子集和外显子

靶向富集方法试图找到一个两全其美的解决方案:它们允许我们集中精力研究我们关心的基因组子集,但效率比扩增子技术更高,并且在某种程度上不太容易出现技术问题。简而言之,第一步与整个基因组准备相同:将基因组剪切成片段,但随后的过滤不是基于大小,而是基于它们是否匹配我们关心的位置。这涉及使用诱饵序列,它们设计得非常像 PCR 引物,但固定在表面上以捕获感兴趣的目标,通常是基因组子集(用于基因组子集)或所有已注释外显子(用于外显子)。结果是,我们产生了可以告诉我们关于我们成功捕获的那些区域的数据块。

这种非常流行的方法经常被忽视的一个缺点是,用于生成面板和外显子数据的商业套件制造商使用不同的诱饵集,有时在靶向区域上存在重要差异,如在图 2-17 所示。这导致批次效应,并使得比较使用不同靶向区域的独立实验结果更加困难。

不同外显子准备套件可能在覆盖位置和数量上存在重要差异。

图 2-17. 不同的外显子准备试剂盒可能导致覆盖位置和数量的重要差异。

外显子测序有时被称为全外显子测序(WES),这是一个误导性的尝试,试图将其与全基因组测序(WGS)进行对比。这是误导的,因为对比已经体现在名称中:外显子的重点在于它是整个基因组的子集。称其为整个外显子测序是多余的,甚至可以说是不准确的。

全基因组与外显子

那么哪种方法更好呢?我们能说的最平衡的事情是,这些方法产生了定性和定量上不同的信息,并且每种方法都有特定的限制。外显子测序因为长期以来价格比基因组便宜且速度更快而非常受欢迎,对于许多常见应用如临床诊断来说,通常足以提供必要的信息。然而,这种方法存在显著的技术问题,包括参考偏倚和覆盖率分布不均匀,这些问题会在下游分析中引入许多混淆因素。

从纯技术角度来看,WGS 产生的数据优于定向数据集。它还使我们能够发现隐藏在不编码基因的广阔基因组领域中的生物学重要序列构型。由于成本的降低,全基因组测序现在正在变得流行,尽管其缺点是产生的数据量更大,更具挑战性。例如,标准人类外显子在 50X 覆盖率下测序需要大约 5GB 的 BAM 格式存储空间,而同一人的全基因组在 30X 覆盖率下测序则需要约 120GB。

如果你以前没有处理过外显子或整个基因组,当我们谈论数据量、文件大小和覆盖率分布时,它们之间的差异可能显得有些抽象。在基因组浏览器中查看一些文件可以帮助理解这在实践中意味着什么。如图 2-18 所示,整个基因组测序数据在基因组中分布几乎均匀,而外显子测序数据则集中在局部堆积。如果你查看基因组浏览器中的覆盖率轨迹,你会看到整个基因组的覆盖率图像像是远处的山脉,而外显子的图像则像是平坦海洋中的一座火山岛。这是一种简单而令人惊讶地有效的方法,可以用肉眼区分这两种数据类型。我们将在第 4 章中向您展示如何在集成基因组浏览器(IGV)中查看测序数据。

整个基因组序列(WGS,上)和外显子测序(下)在基因组浏览器中的视觉外观。

图 2-18. 整个基因组序列(WGS,上)和外显子序列(下)在基因组浏览器中的视觉外观。

本书中大多数练习使用全基因组测序数据,但我们也提供资源指导分析外显子数据,并在第十四章中进行外显子案例研究。

数据处理与分析

最后,我们进入计算部分!现在,你可能会认为一旦我们将序列数据转换为数字形式,真正的乐趣就开始了。然而,事情并非如此:测序仪产生的数据并不直接可用于变异发现分析:这是一大堆短读数,我们不知道它们来自原始基因组的哪里。此外,如果不加处理,偏倚和误差源可能会在下游分析中引起问题。因此,在进行基因组分析之前,我们需要应用几个预处理步骤。首先,我们将读取数据映射到参考基因组,然后应用几个数据清理操作来校正技术偏倚,使数据适合分析。接下来,我们终于能够应用我们的变异发现分析工作流程。在本节中,我们将介绍涉及基因组映射(也称为比对)和变异调用的核心概念,但将其余内容留待第六章。我们还简要触及可能影响变异发现分析的数据质量问题。

将读数映射到参考基因组。

理想情况下,我们希望重建我们正在研究的个体的整个基因组,以便与参考基因组进行比较,但我们没有完整的样本——它更像是一百万个碎片,正如图 2-19 所示。

实际上,标准 30X WGS 为 360 百万,标准 50X Exome 为 30 百万,根据Lander-Waterman 方程。这就像拼图一样复杂。顺便说一句,这些片段重叠 30 到 50 次(取决于目标深度)。

因此,映射步骤的目标是将每个读数与参考基因组的特定区段匹配。输出是一个 BAM 文件,其中每个读取记录中都编码了映射和比对信息,正如“高通量测序数据生成”中所述。

A)试图从数据中提取的信息与实际拥有的 B)有何不同。

图 2-19. 序列差异引入了映射挑战和歧义。

这通常被称为对齐,因为它为每个读取输出详细的对齐信息,但称之为映射可能更合适。基因组映射算法主要设计来解决将一个非常小的字母字符串(通常约 150 到 250 个字符)与一个极大的字母字符串(人类约 30 亿,不计算替代序列)进行最佳匹配的基本问题。除了其他巧妙的技巧外,这涉及使用惩罚间隙的评分系统。然而,在生成精细的逐字母对齐时存在缺点。映射器一次处理一个读取,因此不适合处理自然发生的序列特征,如大插入和删除,通常宁愿截断读取的末端而不引入大的插入/缺失。我们在第五章中介绍 GATK 及其旗舰全基因组短变异调用器HaplotypeCaller时会看到一个例子。在这一点上需要理解的重要一点是,我们通常相信映射器大部分时间会将读取放置在适当的位置,但我们并不能总是信任它产生的确切序列对齐。

如图 2-19 所述,另一个复杂性是一些基因组区域是祖先区域的副本,这些副本被复制了。这些复制的序列可能更或者少地发生了分歧,这取决于复制发生的时间以及在副本中产生变异事件的数量。这使得映射的工作变得更加困难,因为竞争性副本的存在会引入歧义,不确定特定读取应该属于哪个位置。值得庆幸的是,现在大多数短读取测序都是使用成对末端测序进行的,这通过为映射器提供额外信息来帮助解决歧义。根据文库制备的协议,我们知道可以预期 DNA 片段位于某个特定大小范围内,因此映射器可以根据一对读取之间的距离评估可能的映射位置的可能性,如图 2-20 所示。对于 PacBio 和 Oxford Nanopore 等长读取测序技术,这并不适用,因为读取的长度较长,通常通过完整读取重复区域的方式减少了歧义的机会。

成对末端测序有助于解决映射歧义。

图 2-20. 成对末端测序有助于解决映射歧义。
注意

在文库制备过程中产生的 DNA 片段有时被称为插入物,因为早期的制备技术涉及将每个片段插入称为质粒的小圆形 DNA 片段中。因此,相关的质量控制指标通常被称为插入物大小,尽管现在的文库制备不再涉及插入到质粒载体中。

正如刚才指出的那样,在我们通常进行变异调用之前,需要进行一些其他处理步骤来清理对齐的数据。我们稍后会详细介绍它们;现在,是时候终于面对这本书中几乎每一章都会跟随我们的难题了。

变异调用

术语变异调用经常被提及,并且有时用来指代整个变异发现过程。在这里,我们将其严格定义为在可用序列数据中寻找特定类型变异的步骤。这一步骤的输出是一个原始调用集或一组调用,其中每个调用都是潜在变异的个体记录,描述其位置和各种属性。这种的标准格式是 VCF,我们将在下一个侧栏中描述它。

我们用来识别变异的证据的性质因所寻找的变异类型而异。

对于短序列变异(SNV 和 indels),我们比较序列中短片段中的具体碱基。原则上,这相当简单:我们生成 reads 的堆叠,将它们与参考序列进行比较,并确定存在不匹配的位置。然后我们简单地计算支持每种等位基因的 reads 数量,以确定基因型:如果一半一半,样本是杂合的;如果全部是其中一种,样本是纯合的参考或纯合的变异。例如,IGV 中的截图图 2-22 显示了全基因组序列数据中可能的 10 碱基缺失(左侧),纯合变异 SNP(右中)和杂合变异 SNP(最右侧)。

IGV 中显示的可能的短变异的 reads 堆叠。

图 2-22. IGV 中 reads 的堆叠显示了几个可能的短变异。

对于拷贝数变异,我们看的是跨多个区域产生的序列 reads 的相对数量。同样,表面上这是相当简单的推理:如果基因组的一个区域在样本中是重复的,那么当我们对该样本进行测序时,会生成两倍于对该区域的对齐 reads 数目,如图 2-23 所示。

对于结构变异,我们既考虑碱基对分辨率的序列比较,也考虑序列的相对数量。

覆盖量的相对数量提供拷贝数建模的证据。

图 2-23. 覆盖量的相对量提供了复制数建模的证据。

在实践中,确保每种变异类型的过程确实更为复杂,远非我们刚刚概述的简单逻辑,主要是由于各种错误和不确定性的来源使情况变得复杂,我们很快会详述。这就是为什么我们将希望应用更健壮的统计方法,考虑到各种误差来源;一些通过经验推导的启发式方法,另一些则通过不需要事先知道数据中可能存在的错误类型的建模过程。

我们很少能够明确地说,“这个调用是正确的”或“这个调用是错误的”,因此我们用概率术语来表达结果。对于程序输出的每个变异调用,调用者将分配分数,这些分数表示我们对结果的信心有多大。在早期插入缺失的示例中,变异质量(QUAL)分数为 50 意味着“这个变异不是真实的概率为 0.001%”,第二个样本的基因型质量(GT)分数为 17 意味着“这个基因型分配错误的概率为 1.99%”。(我们在第五章中详细介绍了变异质量和基因型质量之间的区别。)

变异调用工具通常使用一些内部截断来确定何时发出变异调用或不发出依赖于这些分数。这避免了输出中充斥着明显不可能是真实的调用。然而,重要的是要理解,这些截断通常设计得非常宽容,以实现高度的敏感性。这是有利的,因为它最小化了错过真实变异的可能性,但这也意味着我们应该预期原始输出仍然包含大量假阳性。因此,我们需要在继续进行更深入的下游分析之前过滤原始的调用集。

过滤变异最终是一个分类问题:我们试图区分更可能是真实的变异和更可能是人为的变异。我们能够做出这种区分得越清晰,我们就有更好的机会在不损失太多真实变异的情况下消除大部分人为变异。因此,我们将根据每种过滤方法能够实现的有利折衷条件来评估它们,用灵敏度或召回率(我们能够识别的真实变异的百分比)和特异性或精确度(我们称之为变异的百分比实际上是真实的),如图 2-24 所示。

变异指标速查表。

图 2-24. 变异指标速查表。

有许多方法可以过滤变异,适当选择取决于我们关注的变异类别,还取决于我们对所研究生物体基因组变异的类型和质量有多少先验知识。例如,在人类中,我们有大量的变异目录已经被先前鉴定和验证,我们可以将其作为在新样本中过滤变异调用的基础,因此我们可以使用依赖于来自同一生物体训练数据可用性的建模方法。相反,如果我们正在研究一个首次被研究的非模式生物体,我们就没有这种奢侈,将需要借助其他方法。

数据质量和误差来源

整个过程中会出现很多问题(图 2-25),从初始样本收集一直到应用计算算法,每个阶段都可能出现多种误差和混杂因素。让我们回顾一下最常见的问题,这些问题对于理解我们将在基因组分析过程中应用的许多程序是很重要的。

变异发现中常见的误差来源。

图 2-25. 变异发现中的常见误差来源。

污染和样品交换

在样品收集和准备的最早阶段,我们容易受到几种污染的影响。最难预防的是外源性物质的污染,尤其是在脸颊拭子和唾液样本的情况下。想想你现在口中的情况。你上次刷牙是什么时候?你午餐吃了什么?你现在有点感冒的可能性吗,也许流鼻涕?如果我们试图根据唾液样本或脸颊拭子对你的基因组进行测序,你口中的任何东西都将与你自己的 DNA 一起被测序。至少,这意味着某些产生的序列数据将是无用的噪音;在最坏的情况下,它可能导致下游分析结果的错误。

还有交叉样本污染,人们不喜欢谈论这个问题,因为这意味着设备的质量或操作人员的技能出了问题。但现实是,样本之间的交叉污染经常发生,因此在分析中应予以关注。这可能发生在初始样本收集阶段,特别是在野外不理想条件下进行采样或在文库制备步骤中。在任一情况下,人 A 的一些遗传物质最终会出现在样本 B 的管子中。当污染水平较低时,这通常不会对生殖系变异分析造成问题,但正如在第七章中所述,对于体细胞变异分析可能会产生重大影响。同样在第七章中,我们讨论了如何在体细胞短变异管道中检测和处理交叉样本污染。

最后,样本交换通常归结为标签错误 —— 要么是标签误贴在了错误的管子上,要么是手写标签字迹模糊不清 —— 或者在处理过程中处理元数据时出现错误。为了检测处理过程中发生的任何样本交换,最佳实践建议是将入站样本通过指纹识别程序,并将处理后的数据与原始指纹进行比对,以检测任何交换。

生化、物理和软件工件

在样本制备过程中发生的各种物理和生化效应(例如剪切和氧化)可能会导致 DNA 损伤,最终在输出数据中表现为错误序列。涉及 PCR 扩增步骤的文库制备协议会引入类似的错误,因为我们的好朋友聚合酶非常高效,但容易将错误的碱基引入。当然,有时测序机器无法准确读取 DNA 反射的光信号,从而产生低置信度的 BCL 或直接错误。因此,我们得到的数据可能并不总是最高质量的。

某些序列上下文的生化特性会影响文库制备、流式池加载和测序过程的效率。这导致某些区域产生人为膨胀的覆盖率,而其他区域的覆盖率则不足。例如,正如图 2-26 所示,我们知道富含高 GC 含量的 DNA 区域(即含有大量 G 和 C 碱基的区域)通常显示的覆盖率较低于基因组的其他部分。高度重复的区域也与覆盖率异常相关联。

DNA 本身的某些生化特性导致某些区域存在偏差。

图 2-26。DNA 本身的某些生化特性导致某些区域存在偏差。

然后,在数据生成后进行处理时,我们遇到了其他错误源。例如,由于基因组的生物复杂性(剧透:它很混乱),可能会导致映射工件将读取数据放在错误的位置,从而支持看似变异但实际上并不存在的变异。为了提高性能而进行的算法快捷方式可能会导致小的近似变成错误。尽管我们很遗憾地说,软件缺陷是不可避免的。

最后的关键在于批处理效应——因为我们可以处理某种程度的错误,只要它在我们比较的所有样本中保持一致,但一旦我们开始比较以不同方式处理的样本的结果,我们就会陷入危险区域。如果我们不小心,我们可能会把生物影响归因于一个在我们的样本子集中偶然存在的人为效应,这是由于它们的处理方式不同。

功能等效流水线规范

在变异调用之前进行的数据预处理阶段可能是基因组分析中在有效替代实施方案方面最不一致的部分。有许多方法可以“正确地”完成这个过程,这取决于您如何处理后勤方面,无论您更关心成本还是运行时间等等。对于每个步骤,都有几种可供选择的替代软件工具(一些是开源的,一些是专有的)——特别是用于映射。对于新近成立的生物信息学开发者来说,编写新的映射算法几乎是一种成年礼。

然而,这种选择丰富性也有其阴暗面。不同的实施方式可能会在下游分析中引起微妙的批处理效应,这些分析比较那些由这些不同变体流水线产生的数据集时,有时会对科学结果产生重要影响。这就是为什么历史上 Broad 研究所团队总是选择重新处理来自外部来源的基因组数据预处理的原因。他们会系统地将任何经过对齐的读取数据恢复到未映射状态,并清除任何标签或可逆修改。然后,他们会将恢复的数据通过其内部预处理流水线处理。

那种策略,由于数据量的巨大增加,已经不再可持续,尤其是在诸如gnomAD这样的大型聚合项目中使用的数据量增加。为了解决这个问题,Broad 研究所与北美几个最大的基因组测序和分析中心(纽约基因组中心、华盛顿大学、密歇根大学、贝勒医学院)合作,制定了标准化流水线实施的标准。其理念是,通过符合规范的流水线处理的任何数据将是兼容的,这样就可以安全地对从多个符合规范的来源聚合的数据集进行分析,而不必担心批处理效应。

该联盟发布了功能等效规范,鼓励所有基因组中心以及任何在小规模生产数据的人采用该规范,以促进互操作性并消除联合研究中的批次效应。

总结和下一步计划

这就结束了我们的基因组学入门;你现在已经掌握了开始使用 GATK 和变异发现所需的所有科学知识。接下来,我们将在计算方面进行等效的介绍,通过技术入门带你了解运行涉及工具所需的关键概念和术语。

第三章:生命科学家的计算技术基础

在理想的世界里,当你追求研究时,你不需要太担心计算基础设施。事实上,在后面的章节中,我们会介绍一些专门设计的系统,以抽象掉计算基础设施的细枝末节,帮助你专注于科学研究。然而,在现实世界中,你会发现一定程度上无法避免某些术语和概念。投入一些精力学习它们将有助于你更高效地规划和执行工作,解决性能挑战,并以更少的努力实现更大的规模。在这一章中,我们回顾了构成最常见类型计算基础设施的基本组件,并讨论它们的优势和局限性如何指导我们在规模上高效完成工作的策略。我们还讨论了诸如并行计算和流水线处理等关键概念,这些概念在基因组学中是必不可少的,因为需要自动化和可重复性。最后,我们介绍了虚拟化技术,并提出了云基础设施的理由。

本章的前几节面向那些在信息学、编程或系统管理方面没有太多培训的读者。如果你是计算科学家或 IT 专业人员,请随意跳过,直到你遇到一些你还不知道的内容。最后两节,一起涵盖流水线处理、虚拟化和云计算,更专注于本书中我们解决的问题,应对不同背景读者都有益。

基础设施组件和性能瓶颈

别担心;我们不会让你详细列举计算机部件清单。相反,我们已经整理了一个关于你工作中最有可能遇到的组件、术语和概念的简短列表。针对每一个这些,我们总结了主要的性能挑战以及你可能需要考虑的使用策略。

让我们从科学计算中可能遇到的处理器类型的简要概述开始。

处理器硬件类型:CPU、GPU、TPU、FPGA、OMG

在其最简单的形式下,处理器 是计算机中执行计算的组件。有各种类型的处理器,其中最常见的是中央处理单元(CPU),它是一般用途计算机(包括笔记本电脑等个人电脑)的主要处理器。你笔记本电脑中的 CPU 可能有多个核心,可以更或多或少独立处理操作。

除了 CPU,你的个人电脑还有一个图形处理单元(GPU),用于处理屏幕上显示的图形信息。随着现代视频游戏的发展,GPU 开始受到关注,这些游戏需要极快的处理速度,以确保游戏动作的流畅视觉呈现。从本质上讲,GPU 解决方案将数学计算中涉及的矩阵和向量运算等特定类型的处理外包给了第二处理单元,后者专门处理某些类型的计算,这些计算对图形数据的应用非常高效。因此,GPU 也成为某些涉及大量矩阵或向量运算的科学计算应用的热门选择。

你应该了解的第三种处理器类型称为现场可编程门阵列(FPGA),尽管与*PU 命名惯例不同,但也是一种处理单元;然而,你不太可能在笔记本电脑中找到它。关于 FPGA 的有趣之处在于,与 GPU 不同,FPGA 并非为特定类型的应用而开发;相反,它们被开发为适应定制类型的计算。因此,“现场可编程”是它们名称的一部分。

在 GCP 上,你可能还会遇到一种称为张量处理单元(TPU)的处理器,这是谷歌为涉及张量数据的机器学习应用开发和品牌化的一种处理器。张量是一个数学概念,用于表示和操作与向量和矩阵相关的多层数据。考虑到向量是一个具有一维的张量,矩阵是一个具有两维的张量;更一般地,张量可以具有超过这些维度的任意数量的维度,因此它们在机器学习应用中非常受欢迎。TPU 属于一类称为特定应用集成电路(ASIC)的处理器,这些处理器专为专用用途而设计,而不是通用用途。

现在你已经了解了基本类型的处理器,让我们来谈谈它们在典型高性能计算设置中是如何组织的。

计算组织的层次:核心、节点、集群和云

当你超越个人电脑,进入高性能计算领域时,你会听到人们谈论核心、节点,以及集群或云,如图 3-1 所示。让我们来回顾一下它们的含义以及它们之间的关系。

计算组织的层次

图 3-1. 计算组织的层次。

低级别:核心

一个核心是机器或节点处理器单元内最小的不可分割处理单元,可以由一个或多个核心组成。如果你的笔记本电脑或台式电脑相对较新,它的 CPU 可能至少有两个核心,因此被称为双核。如果有四个核心,它就是四核,依此类推。高端消费者机器可以拥有更多核心;例如,最新的 Mac Pro 有一个十二核的 CPU(如果我们遵循拉丁术语应称为十二核),而专业级机器上的 CPU 可以拥有数十个或数百个核心,而 GPU 通常有数千个核心。与此同时,TPU 的核心数目与消费者 CPU 相似,而 FPGA 则完全打破了模式:它们的核心是根据它们的编程方式定义的,而不是根据它们的构建方式。

中层:节点/机器

一个节点实际上只是集群或云中的一台计算机。它类似于我们日常工作中主要与之交互的笔记本电脑或台式电脑,但没有我们通常与个人计算机关联的专用监视器和外设。一个节点有时也简单地称为机器

顶层:集群和云

集群和云都是机器/节点的集合。

一个集群是由节点部分网络连接在一起的 HPC 结构。如果你有权限访问一个集群,很可能是因为它属于你的机构,或者你的公司正在租用它。集群也可以称为服务器农场负载共享设施

一个与集群不同之处在于,在其休眠状态下,其节点不是显式地网络连接在一起的。相反,它是一组独立的机器,根据需要可以进行网络连接(或不连接)。我们将在本章的最后一节中更详细地讨论这一点,同时讨论虚拟化的概念,它为我们提供了虚拟机(VM)和容器化,它为我们提供了 Docker 容器。

现在,我们转向如何有效使用给定的计算基础设施的非常普遍的问题,这通常涉及识别和解决关键的计算瓶颈。与本章的其余部分一样,深入探讨这个话题超出了本书的范围,所以我们的目标只是让你熟悉关键的概念和术语。

解决性能瓶颈

你会偶尔发现一些计算操作似乎很慢,需要找出如何让它们更快(如果可能的话)。可供选择的解决方案取决于你面对的瓶颈的性质。

在非常高的层次上,以下是计算机通常需要执行的主要操作(不一定是线性顺序):

  1. 从永久存储中读取一些数据到内存中

  2. 让处理器执行指令,转换数据并生成结果

  3. 将结果写回永久存储

数据存储和 I/O 操作:硬盘与固态

第 1 步和第 3 步被称为I/O 操作(I/O 代表输入/输出)。您可能会听到一些人将某些软件程序描述为“I/O 绑定”,这意味着程序中花费最长时间的部分是读取和写入数据到相对较慢的存储介质。这通常适用于执行简单任务的程序,比如文件格式转换,其中您只是读取一些数据并以不同的形式写出,而不进行任何真正的计算(即,几乎没有涉及数学)。在这些情况下,您可以通过使用更快的存储驱动器来加快操作速度;例如,固态驱动器(SSD)而不是硬盘驱动器(HDD)。它们之间的关键区别在于 HDD 具有称为盘片的物理磁盘和通过磁性从盘片读取数据并将数据写入盘片的臂,就像一个微型高科技唱片机一样,而 SSD 没有移动部件。这使得 SSD 不太容易发生物理故障,并且在访问数据时速度要快得多。

如果您正在使用网络基础架构,其中存储驱动器不直接连接到计算节点,您还将受到数据在网络连接上传输速度的限制。这可能由硬件因素决定,例如用于连接网络部件的电缆的种类。尽管在处理小文件时您可能不会注意到差异,但在运行整个基因组时,您肯定会注意到差异;即使在具有非常快传输速度的网络上,传输整个基因组也会消耗一些明显的时间。

内存:缓存或崩溃

第二步是你的程序正在获取数据并应用某种转换或计算的地方,也就是有趣的部分。对于许多应用程序,计算需要在内存中保存大量信息。在这些情况下,如果您的计算机内存不足,它可能会诉诸于缓存,这是一种使用本地存储空间作为真实内存替代品的方式。这使您可以继续工作,但现在您的进程变得 I/O 绑定,因为它们需要在慢存储介质之间来回复制数据,这会让您回到第一个瓶颈。在极端情况下,程序可能会无限期地停滞、无法完成或崩溃。有时,开发人员可以重写程序,使其更加智能地处理同时需要查看的信息,但当无法做到这一点时,解决方案就是简单地增加更多内存。幸运的是,与人类的记忆不同,计算机内存只是硬件,而且相对便宜。

专用硬件和代码优化:权衡导航

有时,程序的性质要求处理器本身进行大量的重活。例如,在广泛使用的 GATK 工具HaplotypeCaller中,一个操作可以计算基因型可能性;我们需要使用称为PairHMM的隐藏马尔可夫模型(HMM)来计算每个候选等位基因的每个序列读数的可能性(如果这一切听起来像无意义的话,请不要担心——这只是一堆基因组特定的数学)。在基因组的某些区域,这导致我们在非常多的位点上每个位点做数百万次计算。我们从性能分析测试中得知,这个工具中 PairHMM 明显是最大的瓶颈。我们可以通过一些表面级的方法减少这个瓶颈;例如,通过使程序跳过一些预测它们在无信息情况下将不必要的计算。毕竟,计算某事物的最快方式是根本不计算它。

懒惰只能让我们走到这一步,因此,要达到下一个层次,我们需要考虑我们可以(或应该)为需要完成的工作使用的处理器类型。不仅因为某些处理器运行速度比其他处理器快,还因为可以以非常特定于特定类型和架构的处理器的方式编写程序指令。如果做得好,程序在这种环境中将更加高效,因此运行速度更快。这就是我们所说的代码优化,更具体地说是本地代码优化,因为它必须用处理器“本地”理解的低级语言编写,而无需经过额外的翻译层。

在像 CPU 这样的处理器类型内部,不同的制造商(例如 Intel 和 AMD)为其产品的不同世代(例如 Intel Skylake 和 Haswell)开发了不同的架构,这些不同的架构提供了优化软件的机会。例如,GATK 软件包包括了几个对应于特定 Intel 处理器架构优化的 PairHMM 算法的替代实现的代码模块。当程序在 Intel 处理器上运行时,它会自动激活最合适的版本,这提供了一些有用的速度增益。

然而,硬件优化的好处在于处理器类型之间最为明显;例如,如果将某些算法的实现方式与在 FPGA 上运行相比较而实现的情况。Illumina DRAGEN 工具包(最初由 Edico Genome 开发)包括了像HaplotypeCaller这样的工具的实现,这些工具经过了优化,可以在 FPGA 上运行,因此比原始的 Java 软件版本快得多。

硬件优化实现的缺点在于,按定义需要专门的硬件。对于依赖于共享机构计算系统并且无法获得其他硬件的许多研究实验室来说,这可能是一个大问题。相比之下,像 GATK 这样用 Java 编写的应用程序可以运行在广泛的硬件架构上,因为 Java 虚拟机(JVM)将应用程序代码(在 Java 世界中称为字节码)转换为适合该机器的指令。Java 字节码与实际在机器上执行的内容之间的这种关注分离(SoC)称为抽象层,对所有参与者来说都非常方便。开发人员无需担心我们的笔记本电脑上具体的处理器型号,我们也不需要担心他们编写代码时考虑的处理器类型。这还保证软件可以轻松部署在标准的现成硬件上,使其可以被世界上任何人使用。

有时候,你需要根据你最看重的因素(包括速度、可移植性和互操作性)在同一算法的不同实现之间进行选择。其他时候,你可以同时兼得。例如,Broad Institute 的 GATK 团队已与 Illumina 的 DRAGEN 团队展开合作,两个团队正在共同努力制定统一的 DRAGEN-GATK 流水线,这将作为免费开源版本(通过 Broad 提供)和经许可的硬件加速版本(通过 Illumina 提供)提供。合作的一个关键目标是使两种实现在功能上等效——这意味着你可以运行任何版本,并在被认为是微不足道的误差范围内获得相同的结果。这将极大地使研究社区受益,因为可以将任一流水线分析的样本组合到下游分析中,而不必担心批次效应,我们在前一章节中简要讨论过的问题。

并行计算

当你不能更快时,就采用并行。在计算的背景下,并行计算并行性是通过同时执行多个操作而不是顺序执行(即等待每个操作完成再开始下一个)来使程序尽快完成的一种方法。想象一下,你需要为 64 个人煮米饭,但你的电饭煲一次只能做够 4 个人吃的量。如果你需要依次煮所有批次的米饭,那将会花费整整一晚上。但如果你有八台电饭煲可以并行使用,你可以提前完成多达八倍的速度。

这是一个简单的想法,但它有一个关键要求:你必须能够将工作分解为可以独立执行的较小任务。将米饭分成部分很容易,因为米饭本身是一个离散单位的集合。但你并不总能做到这种分割:例如,一位孕妇需要九个月来孕育一个婴儿,但你不能让九个女人每人负责一个月来加快这个过程。

好消息是,大多数基因组分析更像是处理米饭而不是生婴儿——它们实际上由许多小独立操作序列组成,可以并行化。那么我们如何从煮米饭过渡到执行程序呢?

并行化简单分析

考虑到当您运行分析程序时,您只是告诉计算机执行一组指令。假设我们有一个文本文件,并且我们想计算其中的行数。执行此操作的指令可以简单如下:

打开文件;计算文件中的行数;告诉我们数量;关闭文件。

请注意,“告诉我们数量”可以意味着将其写入控制台或存储在某处以供以后使用——现在我们不要担心这个问题。

现在假设我们想知道每行的单词数。指令集如下:

打开文件;读取第一行;计算单词数;告诉我们数量;读取第二行;计算单词数;告诉我们数量;读取第三行;计算单词数;告诉我们数量。

一直这样做直到我们读取了所有行,最后我们可以关闭文件。这很简单直接,但是如果我们的文件有很多行,将会花费很长时间,而且可能不会充分利用我们可用的所有计算能力。因此,为了并行化这个程序并节省时间,我们只需将这组指令分割成独立的子集,就像这样:

  • 打开文件;索引行。

  • 读取第一行;计算单词数;告诉我们数量。

  • 读取第二行;计算单词数;告诉我们数量。

  • 读取第三行;计算单词数;告诉我们数量。

  • [对所有行重复。]

  • 收集最终结果并关闭文件。

在这里,“读取第 N 行”的步骤可以并行执行,因为它们都是独立的操作。

你会注意到我们增加了一个步骤:“索引行”。这是一点预备工作,使我们能够并行执行“读取第 N 行”的步骤(或按任意顺序执行),因为它告诉我们有多少行以及每一行在文件中的位置。这使整个过程更加高效。正如您将在以下章节中看到的,像 GATK 这样的工具需要主数据文件(参考基因组、读取数据和变体调用)的索引文件。原因在于已经完成索引步骤,以便我们可以让程序通过它们在基因组中的位置查找特定数据块。

总之,这是一个一般的原则:你将你的线性指令集转化为若干子指令集。通常有一个子集需要首先运行,一个需要最后运行,但所有中间的子集可以同时(并行)运行,或者按照你想要的任何顺序运行。

从核心到集群和云:多层次并行性

那么,我们如何从电饭锅到并行化执行基因组分析程序?总体而言,并行化计算操作的行动包括将我们想要完成的工作的子集发送到多个核心进行处理。我们可以通过将工作分配到单个多核机器的核心之间分割工作,或者如果我们可以访问集群或云,则可以将工作分发到其他机器。事实上,我们可以结合这两个想法,并将工作分派给多核机器,在这些机器的每个核心中进一步分割工作。回到煮饭的例子,这就好像不是自己做饭,而是雇用一个餐饮公司为您做饭。公司把工作分配给几个人,每个人都有自己的烹饪站,有多个电饭锅。现在,您可以在同样的时间内喂更多的人!而且您甚至不需要洗碗。

无论我们想要将工作分配到单台机器的多个核心上,还是跨多台机器分配工作,我们都需要一个系统来分割工作,分派执行任务,监控任务完成情况,然后编译结果。有几种系统可以做到这一点,大致分为两类:内部或外部于分析程序本身。在第一种情况下,并行化发生在我们运行的程序“内部”:我们运行该程序的命令行,而并行化无需我们额外的“包装”。我们称之为多线程。在第二种情况下,我们需要使用一个单独的程序来运行该程序的多个实例的命令行。外部并行化的一个例子是编写一个脚本,分别在基因组中的每条染色体上运行给定工具,然后通过额外的合并步骤组合结果。我们称之为散集-收集。在下一节介绍工作流管理系统时,我们将更详细地讨论这一点。在图 3-2 中,您可以看到我们如何在分析过程中使用多线程和散集并行性。

散集允许在不同的 CPU 核心上并行执行任务(在单台机器或多台机器上,具体取决于实现方式)。

图 3-2. 散集允许在不同的 CPU 核心上并行执行任务(在单台机器或多台机器上,具体取决于实现方式)。

并行性的权衡:速度、效率和成本

并行化是加速大量数据处理的一个很好的方法,但它有额外的开销。在这一点上不要过于技术化,我们只需说并行化的作业需要被管理,需要为它们保留内存,调节文件访问,收集结果等等。因此,平衡成本与收益非常重要,并避免将整体工作分解为过多的小作业。回到我们之前的例子,你不会想使用一千个只煮一粒米的小型电饭煲。它们会占用你台面上太多的空间,而且分发每一粒米并在煮好后收集它所需的时间将使并行化的任何好处都荡然无存。

更一般地说,虽然把并行视为加快速度的一种方式很诱人,但重要的是要记住,速度的印象完全取决于观察者的主观感受。实际上,每个数据片段上运行的计算并没有变得更快。我们只是同时运行更多计算,并且受到我们计算设置的并行能力(通常以节点或核心数来衡量)以及像 I/O 和网络速度这样的硬件限制的限制。更现实的是,把并行视为一种优化可用资源的方式,以便更快地完成任务,而不是让单个任务运行得更

这种区分在键盘前的人来看,这种区分可能显得很迂腐,因为经过的时间(通常称为挂钟时间;即“墙上时钟显示的时间”)较短。这不就是我们所理解的“更快”的方式吗?但是,从我们利用的资源的角度来看,如果我们将所有核心的计算时间相加,我们可能会发现整个作业花费的时间更多,因为有额外的开销。

这带来了另一个重要问题:利用这些资源的经济成本是多少?如果我们使用的是专用机器,这台机器只是坐在那里,有多个核心,而没有其他事情可做,那么并行化仍然绝对值得,即使有额外的开销。我们将希望在这台机器上对我们做的每一件事情进行大量并行化,以最大化效率。然而,当我们开始在云环境中工作,正如我们在第四章中所做的那样,并且我们需要按照使用的资源来付费时,我们将更仔细地考虑最小化挂钟时间和账单大小之间的权衡。

并行化用于并行化和自动化流水线

许多基因组分析涉及大量例行数据处理操作,需要并行化以提高效率,并自动化以减少人为错误。我们通过描述可由机器读取的工作流程来实现这一点,然后将其馈送到工作流程管理系统进行执行。我们将在第八章中详细讨论其实际运作,但首先让我们通过介绍基本概念、定义和关键软件组件来为此做好准备。在我们进行时,请记住,当前领域并不存在一种适合所有情况的解决方案,最终选择特定选项取决于您审查需求和可用选项。然而,我们可以确定一般原则来指导您的选择,并展示这些原则在实际中如何运作,使用由 GATK 开发团队推荐并在 Broad Institute 生产中使用的开源流水线解决方案进行演示。与本书的大部分内容一样,这里的目标不是指定使用特定软件,而是通过工作示例展示所有这些如何实际运作。

一个棘手的问题是,在生物信息学领域,我们有数十种脚本语言和工作流程管理系统可供选择——如果您看更广泛的领域,可能会有数百种。直接比较它们可能会很困难,因为它们往往是为特定类型的用户群体开发的,导致用户体验的模式非常不同。它们通常针对特定用例进行调优,有时还优化以在特定类别的基础设施上运行。我们经常看到一个解决方案对一个群体而言是首选的,但对另一个群体使用起来可能特别困难或令人沮丧。这些不同的解决方案通常也不具备互操作性,这意味着您不能将为一个工作流程管理系统编写的工作流脚本未经修改地运行在另一个系统上。这种缺乏标准化是人类已知的几乎每个研究领域的一个幽默和绝望的话题,正如在图 3-3 中所说明的那样。

XKCD 漫画,讨论标准的扩散。

图 3-3. XKCD 漫画,讨论标准的扩散(来源:https://xkcd.com/927)。

近年来,我们看到一些备受关注的倡议,如全球联盟 GA4GH 的出现,明确任务是制定共同标准并围绕一组具有互操作性作为核心价值的解决方案进行整合。例如,GA4GH 云工作组已经就其驱动项目的一小组工作流语言达成一致,包括 CWL、Nextflow 和 WDL,这些语言在本书中使用。同时,鉴于没有单一语言可能满足所有需求和偏好的认识,一些团体正在努力通过将对多种工作流语言的支持集成到其工作流管理系统中来增加互操作性。本书中使用的工作流管理系统 Cromwell 支持 WDL 和 CWL,未来还可以扩展支持其他语言。

工作流语言

原则上,我们可以用几乎任何编程语言编写我们的工作流;但实际上,有些编程语言比其他语言更适合描述工作流。就像自然语言一样,编程语言也展现出令人着迷的多样性,并且可以按照语法、执行方式以及支持的编程范式等方式进行分类。

从实际角度出发,我们首先要区分通用编程语言和领域特定语言(DSLs)。通用编程语言旨在可用于各种应用程序,而 DSLs 则是专门为特定领域或活动设计的,正如其名称所示。后者通常预装有特别制定的数据结构(即表示和操作数据的方式,“理解”底层信息的性质)和方便函数,作为快捷方式;例如,处理特定领域的文件格式,应用常见处理操作等。因此,如果您的需求与语言的预期范围非常匹配,尤其是在计算背景有限的情况下,DSL 可能是一个吸引人的选择,因为 DSL 通常使您能够在不必学习大量编程概念和语法的情况下完成工作。

另一方面,如果您的需求更加多样化,或者您习惯于使用通用语言的更广泛工具箱,那么您可能会感到领域特定语言的限制让您感到不适。在这种情况下,您可能更喜欢使用通用语言,尤其是那些丰富了领域特定库的通用语言,提供相关的数据结构和便利功能(例如,带有 Biopython 和相关库的 Python)。事实上,使用通用语言更有可能使您能够使用相同的语言编写数据处理任务本身,并管理操作流程,这正是许多人传统上进行此类工作的方式。然而,我们现在在这个领域看到的趋势是,一种向描述和内容分离的趋势,这体现为特别设计用于描述工作流程以及专门的工作流管理系统的 DSL 的增加采用。这种进化与推动互操作性和可移植性的努力密切相关。

流行的基因组学流水线语言

当我们看到那些位于生物信息学和基因组学交汇点的人群时,我们看到了各种背景、计算经验和需求的广泛范围。有些人来自软件工程背景,更喜欢功能齐全、高度结构化的语言,虽然强大但可访问性较差。有些人来自系统管理领域,认为一切问题都可以通过巧妙应用 Bash、sed 和 awk 来解决,这些在 Unix 世界中像胶带一样。在“生物”这一边,更多计算训练有素的人通常最喜欢分析师钟爱的 Python 和 R,这两者在逐渐取代老牌经典 Perl 和 MATLAB;一些人还倾向于领域特定语言。与此同时,湿实验室训练的研究人员可能在初次接触时对所有这些感到困惑。(作者注及免责声明:杰拉尔丁自认为最初感到困惑之一,曾接受传统微生物学训练,并最终在逃离湿实验室工作台时学习了 Perl 和 Python 的基础知识。剧透:这行得通了!)

根据最近的调查,基因组学空间中最受工作流程作者欢迎的语言包括 SnakeMake 和 Nextflow。这两者以其高度灵活性和易用性而闻名。同样,CWL 和 WDL 也因其专注于可移植性和计算再现性而受到青睐。在这两者中,CWL 更常被技术背景的人士偏爱,因为它具有高度的抽象和表现力。相反,WDL 一般认为更易于广泛受众接受。

最终,在选择工作流语言时,我们考虑了四个主要标准:语言支持的数据结构类型(即我们如何表示和传递信息),它如何使我们控制操作流程,它对目标读写者的可读性和可写性如何,以及它如何影响我们与他人协作的能力。无论我们选择什么,都不太可能满足每个人的需求。然而,如果我们把所有这些总结成一个建议,那就是:如果你希望你的工作流脚本在你的研究领域被广泛使用和理解,选择一种对新手开放和易于理解,同时又能够满足更高级需求的语言。当然,尽量选择一种能够在不同的工作流管理系统和计算平台上运行的语言,因为你永远不知道你或你的合作者下一步会进入什么环境。

工作流管理系统

许多工作流管理系统存在,但总体上它们遵循相同的基本模式。首先,工作流引擎读取并解释工作流脚本中的指令,将指令调用转换为与一系列输入相关联的可执行作业(包括数据和参数)。然后,它将每个带有其输入列表的作业发送给另一个程序,通常称为作业调度程序,负责在指定的计算环境上编排实际工作的执行。最后,在作业完成时,它检索生成的任何输出。大多数工作流管理系统都具有一些用于控制执行流程的内置逻辑;即它们调度作业的顺序以及确定它们如何处理错误并与计算基础设施通信。

增加分析的可移植性和互操作性的另一个重要进步是容器技术的采用,我们在本章的最后一节中详细介绍。现在,可以假设容器是一种机制,允许您封装特定任务的所有软件要求,从操作系统(OS)的最深层次到库导入、环境变量和辅助配置文件。

虚拟化与云

到目前为止,我们一直假设,无论您是在单台计算机上还是在集群上工作,您都在处理“真实”的物理机器,每台机器都设置有特定的操作系统和软件堆栈,如图 3-4 A 所示。不幸的是,与这种类型的系统交互有几个缺点,特别是在像机构集群这样的共享环境中。作为终端用户,您通常没有选择关于操作系统、环境和已安装的软件包。如果您需要使用一些当前不可用的东西,您可以请求管理员安装它,但他们可能会拒绝您的请求或您想要的软件包可能与现有软件不兼容。对于位于帮助台另一侧的系统管理员来说,跟踪用户需求、管理版本并处理兼容性问题可能是一件头疼的事情。这些系统需要努力进行更新和扩展。

这就是为什么大多数现代系统使用不同程度的虚拟化,这基本上是一种巧妙的抽象,通过虚拟机(VM)和容器使得在同一硬件上运行多个不同的软件配置成为可能,如图 3-4 B 和 C 所示。这些构造可以在许多情境下使用,包括在本地系统上选择性地(你甚至可以在笔记本电脑上使用容器!),但它们对于云基础设施来说绝对是必不可少的。

A) 安装在物理机上的软件堆栈;B) 提供客户访问个别 VM 的服务器;C) VM 和容器并排比较的表示。

图 3-4. A) 安装在物理机上的软件堆栈;B) 托管多个 VM 的系统;C) 托管多个容器的系统。

VM 和容器

虚拟机(VM)是一个基础设施级别的构造,包括其自己的操作系统。VM 位于运行在底层物理机实际操作系统上的虚拟化层之上。在最简单的情况下,VM 可以在单个物理机上运行,使得该物理机效果上成为多个共享底层资源的服务器。然而,最强大的系统利用多个物理机来支持 VM 层,它们之间有一个复杂的层来管理物理资源的分配。好消息是,对于终端用户来说,这并不会有任何区别——你只需要知道你可以与特定的 VM 隔离地交互,而不用担心它的底层环境。

容器在原理上类似于虚拟机,但它是一个应用级的构造,更轻量且更便携,意味着可以轻松部署到不同的站点,而虚拟机通常与特定位置的基础设施绑定。容器旨在捆绑运行特定程序或一组程序所需的所有软件。这使得在任何支持运行容器的基础设施上复制相同的分析变得更加容易,无论是从你的笔记本电脑到云平台,都不必去辛苦识别和安装所有涉及的软件依赖关系。你甚至可以在同一台机器上运行多个容器,因此如果需要运行具有不兼容系统要求的程序,可以轻松切换不同的环境。

如果你在想:“这两个听起来都很不错;我应该使用哪一个呢?”好消息是你可以同时结合使用,如图 3-5 所示。

表示一个系统有三个虚拟机,其中两个正在运行容器。

图 3-5. 一个有三个虚拟机的系统:左边的虚拟机正在运行两个容器,为 App #1 和 App #2 提供服务;中间的虚拟机正在运行一个容器,为 App #3 提供服务;右边直接为 App #4 提供服务(没有容器)。

有几个注册表可用于共享和获取容器,包括Docker HubQuay.ioGCR,谷歌在 GCP 中的通用容器注册表。在注册表中,容器被打包成一个镜像。请注意,这与图片无关;这里的镜像一词在软件特定的语境中使用,指的是一种特殊类型的文件。你知道有时候当你需要在电脑上安装新软件时,下载的文件被称为磁盘镜像吗?这是因为你下载的文件是以一种格式存在,你的操作系统会把它当作是你机器上的一个物理磁盘。这基本上是一样的道理。要使用一个容器,你首先告诉 Docker 程序从注册表(例如,Docker Hub,稍后详细介绍 Docker)下载或拉取一个容器镜像文件,然后告诉它初始化容器,概念上等同于启动一个虚拟机。容器运行后,你可以在其中运行安装在其系统上的任何软件。你还可以根据需要安装额外的软件包或进行额外的配置。Figure 3-6 说明了容器、镜像和注册表之间的关系。

容器、镜像和注册表之间的关系。

图 3-6. 注册表、镜像和容器之间的关系。

最广泛使用的容器系统品牌是由同名公司生产的 Docker。由于 Docker 的普及,人们通常会说“一个 docker”而不是“一个容器”,就像“xerox”在美国成为“复印机”的替代品一样,因为 Xerox 公司的主导地位。然而,小写ddocker也是您在本地安装以运行 Docker 容器的命令行程序。类似地,尽管将软件工具、包或分析打包到 Docker 容器中的行为应该被称为“containerizing”,但人们经常称之为“dockerizing”,比如,“我 dockerized 我的 Python 脚本”。Docker 化一个工具涉及编写一个称为 Dockerfile 的脚本,描述构建 Docker 镜像所需的所有安装和环境配置,如图 3-7 所示。

创建 Docker 容器的过程。

图 3-7. 创建 Docker 镜像的过程。

正如前面提到的,可以在各种环境中使用容器,包括本地机器、HPC 和云计算。一个重要的限制是,Docker 通常不允许在共享环境中使用,比如大多数机构的 HPC,因为它需要一个称为root的非常高级别的访问权限。在这种环境中,系统管理员会更倾向于使用Singularity,这是一个能达到相同效果的替代系统。幸运的是,可以在 Singularity 系统中运行 Docker 容器。

介绍云计算

最后,我们来到许多人都在等待的话题:云究竟是什么?令人惊讶的简单答案是,云计算就是一堆可以租用的计算机。在实践中,这意味着作为用户,您可以轻松启动一个虚拟机,并选择需要多少 RAM、存储空间和 CPU。您需要一个带有 1TB RAM 和 32 个 CPU 用于基因组装的虚拟机?没问题!大多数云中的虚拟机都运行某种形式的 Linux 作为操作系统,您可以在启动时选择,并且通常通过安全外壳(SSH)远程访问。

虽然一些虚拟机(VMs)包含免费存储空间,但这通常是暂时的,当您停止和启动虚拟机时会消失。相反,使用块存储(持久设备)来存储数据、脚本等内容在您的虚拟机上。您可以把它们想象成 USB 闪存驱动器,可以随时“插入”到您的虚拟机中。即使终止虚拟机,块存储上的文件也会安全保存。文件也可以存储在对象存储中——把它想象成更像是 Google Drive 或 Dropbox,多个虚拟机可以同时读写文件,但您通常不会把它们当作普通的文件系统使用。相反,它们更像是用于在虚拟机之间共享文件的 SSH 文件传输协议(SFTP)服务器,您可以通过实用工具在对象存储系统之间传输文件。云的最后一个基本组成部分是网络。通过虚拟网络服务,您可以控制谁可以访问您的虚拟机,严格锁定以确保只有您(或其他受信任的人)能够访问。

云不是松软的

当您想到云时,它们是松散、遥远和难以捉摸的,根本不是您可以触摸、感受和捕捉的实体。与其同名相比,今天大多数人直接(或间接)使用的云基础设施最终由真实的物理计算机组成,这些计算机装在巨大的数据中心中不停地闪烁。然而,与以往的计算模型不同(也符合其名称),云计算具有短暂的特性。就像云朵的出现和消失一样——突然出现、释放雨水,然后迅速消散——云计算对最终用户而言也是短暂的。云允许您作为研究人员、开发人员或分析师在需要时请求计算基础设施,在需要时使用它进行计算,然后在完成后释放所有资源。

这种方法很棒,因为它节省了时间和金钱,您可以一次性启动大量资源,完成工作后再关闭它们,节省连续运行硬件的成本。您不需要过多地考虑服务器放置在何处、它们的配置方式、硬件的健康状况、功耗或其他各种基础设施问题。这些都被从您身上“抽象化”出去,并且在您无需过多考虑的情况下得到照顾。相反,您应该关注的是您需要执行的计算工作、完成它所需的资源以及如何从时间和金钱的角度最有效地利用这些资源。

云基础设施和服务的演变

亚马逊于 2006 年推出了第一个广泛成功的商业公共云服务,但这个基本概念已经存在了很长时间。上世纪 60 年代,大型机通常是租用使用的,考虑到购买和运营的巨大成本,这是非常合理的做法。尽管个人电脑的发明,租用计算基础设施的概念在过去几十年中一再出现。在学术界和行业中,上世纪 90 年代和 2000 年代共享网格计算的概念是租用大型机时间的更现代等价物。团体联合建立了廉价但功能强大的基于 Linux 的高性能计算集群,通常是集中管理,并根据某种财务分割分配给多个团体。

当今的公共云在抽象级别上有所不同。因此,采用了蓬松、无定形的名称来反映这样一个事实:在云上运行大规模分析时,并不需要理解底层细节。在使用特定云服务时,你可能知道托管基础设施的大致地理位置(例如,AWS 的us-east-1位于北弗吉尼亚),但许多细节对你是隐藏的:你不知道有多少人正在使用你的虚拟机的基础硬件,数据中心真正的位置在哪里,网络如何设置等等。你所知道的是影响服务成本和作业执行时间的关键细节,比如有多少个 CPU 可用,虚拟机有多少 RAM,文件存储系统的正常运行时间保证,以及系统符合的法规。

现在有许多公共云提供商,即任何能够支付服务费用的人都可以使用的云服务。目前在西半球最主要的有 AWS、微软 Azure 和 GCP。每个提供商都提供类似的服务组合,从按小时(或分钟)租用的简单虚拟机,文件存储服务和网络服务到更专业的服务,比如谷歌的云 TPU 服务,用于加速机器学习操作。然而,重要的特点是这些资源作为服务提供:你根据需要使用,按小时、分钟、秒或 API 调用收费。

云的优缺点

许多人在讨论云计算时指出的一个主要优势是成本。建设数据中心时,固定成本是巨大的。您必须雇佣人员来安装和维护物理服务器,监视网络以防入侵,处理电力波动,备份,空调等等。说实话,这是一项繁重的工作!对于支持数百用户的数据中心,维护基础设施所需的成本可能是值得的。但许多研究人员、开发人员、分析师和其他人意识到,他们并不需要始终拥有数百台随时可用和运行的计算机,只等待任务。相反,使用云环境更合理,您可以在笔记本电脑上进行本地工作,无需大量资源,然后在分析完成时,可以扩展到数百甚至数千台机器。商业公共云允许您轻松地扩展您的容量,并在需要时进行大规模分析,而不是等待几周、几个月甚至几年才能完成专用本地集群的任务。同样,您无需为本地基础设施的维护支付所有时间,用于开发算法和本地完善分析。

最后,作为公共云用户,您对环境拥有完全控制权。需要特定版本的 Python 吗?是否有一个怪异的库,只有在安装了非常特定的工具链时才能编译?没问题!云允许您完全控制您的虚拟机,这是共享本地基础设施永远不允许的。即使在拥有此控制权时,遵循云供应商的最佳实践设置后,公共云解决方案由于在这些环境中专门用于安全服务的大量资源以及虚拟化所提供的用户隔离,通常比本地基础设施更安全。

尽管公共云平台令人惊叹、强大、灵活,而且在许多情况下,可以有效地节省大量资金,但也有一些需要注意的缺点。如果您希望始终处理您的测序组每月生成的固定数量的基因组,公共云可能不太吸引人,构建一个小型的本地计算环境以处理本地产生的这种非常可预测的工作负载会更有意义。当然,这是在假设您有能够充当管理员的 IT 专业人员的情况下。另一个考虑因素是专业知识。使用云计算需要一定水平的专业知识,一个毫无戒心的新手用户可能会意外使用具有弱密码的虚拟机,设置具有弱安全性的数据存储桶,以不安全的方式共享凭据,或在管理一群 Linux 虚拟机的过程中完全迷失方向。然而,即使存在这些潜在的缺陷,对于许多人来说,能够在商业云环境中灵活工作的好处通常超过了这些负面影响。

云服务的研究用例类别

之前章节描述的云基础组件实际上只是冰山一角。主要商业云平台上提供了许多更多的服务。事实上,有太多服务了,一些是通用的,一些是特定于某个云的,我们无法在这里详细描述。但让我们看看研究人员可能如何最常用到这些服务或云。表格 3-1 提供了一个总体概述。

表格 3-1. 云基础设施使用类型概述

使用类型 云环境 描述 优点 缺点
轻量级开发 Google Cloud Shell 使用一个简单启动的免费虚拟机编辑代码和脚本
  • 免费

  • 启动和登录极为简单

|

  • 虚拟机极为有限

|

中级分析和开发 单个虚拟机 启动单个虚拟机,登录,进行开发和分析工作
  • 可以控制启动的虚拟机上的资源

  • 虚拟机可以足够强大以执行实际分析

|

  • 启动虚拟机需要更多配置

  • 更大的虚拟机会增加成本

|

批量分析 通过批处理系统多个虚拟机 使用类似 AWS Batch 或 Google Cloud Pipelines API 这样的系统启动多个虚拟机并行分析数据
  • 允许并行、扩展的分析

  • 工作流管理系统如 Cromwell 支持这些操作而不需要太多努力

|

  • 成本和复杂性增加

|

框架分析 通过框架多个虚拟机 使用 Spark、Hadoop 或其他框架进行数据分析
  • 这些框架允许进行专业分析

|

  • 成本和复杂性增加

|

轻量级开发:Google Cloud Shell

云是软件开发的绝佳场所。尽管许多研究人员可能会想要使用自己的笔记本电脑或工作站进行开发,但使用云作为主要开发环境,特别是用于测试,确实有一些非常引人注目的理由。例如,在 GCP 上,您可以从 Google Cloud 控制台中使用 Google Cloud Shell 进行轻量级开发和测试。这是一个免费(是的,免费!)的虚拟机,拥有一个虚拟 CPU 核心和 5 GB 存储空间,您只需点击 Web 控制台中的终端图标即可使用。这是进行轻量级编码和测试的绝佳环境;只需记得将代码从您的免费实例中复制出来(例如使用 Git),因为每周的总运行时间有限制,而且如果您一段时间不使用该服务,您的 5 GB 存储空间可能会被清理掉。尽管如此,这仍然是快速开始使用云并执行轻量级任务的绝佳选择。您只需一个 Web 浏览器,而且 GCP 工具已预先安装和配置好。许多其他您可能想要使用的工具也已经安装好,包括 Git 和 Docker,以及像 Java 和 Python 等编程语言。您将有机会在下一章的早期尝试它。

中级开发和分析:单个虚拟机

尽管 Google Cloud Shell 非常适合许多用途,易于使用且免费,但有时您可能需要更多的计算能力,特别是如果您想测试您的代码或在更大的规模上进行分析,因此您会启动自己的专用虚拟机。这可能是最常用的选项之一,因为它结合了灵活性和简单性:您可以自定义您的虚拟机,确保具有足够的 CPU 核心、RAM 和本地存储来完成您的目标。与 Google Cloud Shell 不同,您必须为每小时或每分钟运行此虚拟机支付费用;但是,您可以完全控制虚拟机的性质。您可以将其用于软件或算法开发,测试您的分析方法,或者在多个虚拟机上同时运行小型虚拟机群来执行分析。然而,请记住,如果您手动启动这些虚拟机,它们将预先安装的工具较少,并准备好供您使用。这使得使用 Git 和 Docker 等实用工具非常有助于将您的分析任务从一个虚拟机转移到另一个虚拟机。您将有机会在第四章到第七章中广泛使用这些工具。

批处理分析:通过批处理服务使用多个虚拟机

对于大多数了解这一点的用户来说,这种方法确实是最合适的选择。尽管您可以使用笔记本电脑或 Google Cloud Shell 进行软件和脚本开发,并在适当大小的硬件上测试它们,甚至可以使用一个或多个虚拟机进行测试,但如果您的目标是扩展分析,最终您可能不希望手动管理虚拟机。想象一下同时运行 10,000 个基因组比对任务;您需要能够批量处理工作、自动为您提供虚拟机,并在工作完成后自动关闭虚拟机的系统。批处理系统专门设计用于此任务;例如,Google Cloud 提供了 Google Cloud Pipelines API,您可以使用它同时提交大量批处理作业。该服务将负责启动大量虚拟机来执行您的分析,然后在收集输出文件后自动清理它们。如果您需要对大量样本进行非交互式分析,这将非常方便。您将在第八章到第十一章中看到像 Cromwell 这样的工作流引擎是如何利用这些批处理服务的,这些服务负责处理启动批处理作业的所有细节。这使得您能够更容易地专注于正在执行的分析细节,而不是涉及的基础设施。

框架分析:通过框架服务使用多个虚拟机

许多研究人员最终采用的方法是交互式迭代分析。在基因组学中,您可以使用批处理系统进行大规模比对和变异调用,但在获取变异的 VCF 文件后,您可能选择迁移到 Spark 集群、RStudio、Jupyter Notebook 或任何大量分析环境中进行后续分析。在第十二章中,我们探讨了在 Terra 中的运作方式,您可以利用它轻松创建用于数据处理的自定义环境,使用 Jupyter 界面进行交互式分析,生成出版物的图表,并与他人分享结果。

结尾与下一步

在本章中,我们完成了引导性主题,为您提供了基因组学(第二章)和计算技术(本章)的背景。我们深入探讨了计算机硬件、并行计算和虚拟化的细节,并为您展示了使用工作流执行系统在云上扩展分析的强大能力。在第四章中,我们迈出了云环境的第一步,并向您展示了如何在 GCP 上运行自己的虚拟机。

第三章:生命科学家的计算技术基础

在理想的情况下,当您进行研究时,您不需要过多关注计算基础设施。事实上,在后面的章节中,我们会向您介绍专门设计的系统,以便摆脱计算基础设施的繁琐细节,帮助您专注于科学研究。然而,在现实世界中,您会发现某些术语和概念是不可避免的。投入一些精力学习它们将有助于您更有效地规划和执行工作,解决性能挑战,并以更少的努力实现更大规模。在本章中,我们回顾形成最常见类型计算基础设施的基本组件,并讨论它们的优势和局限性如何影响我们在大规模高效完成工作的策略。我们还讨论了诸如并行计算和管道化等关键概念,由于基因组学中自动化和可重现性的需求,这些概念至关重要。最后,我们介绍了虚拟化,并阐述了云基础设施的案例。

本章的前几节旨在针对那些在信息学、编程或系统管理方面没有太多培训经验(如果有的话)的读者。如果您是计算科学家或 IT 专业人士,请随意跳过,直到遇到您尚未了解的内容。最后两节涵盖管道化、虚拟化和云等内容,更专注于本书探讨的问题,无论背景如何,对所有读者都应该具有信息价值。

基本基础设施组件和性能瓶颈

别担心;我们不会让您详尽列举计算机零件清单。相反,我们为您整理了一个关于您在工作过程中最有可能遇到的组件、术语和概念的简短清单。关于每一个,我们总结了主要的性能挑战和您可能需要考虑的有效使用策略。

让我们首先简要概述一下您今天在科学计算中可能会遇到的处理器类型。

处理器硬件类型:CPU、GPU、TPU、FPGA、OMG

在最简单的情况下,处理器是您计算机中执行计算的一个组件。有各种类型的处理器,其中最常见的是作为一般用途计算机的主处理器的中央处理单元(CPU),包括笔记本电脑等个人计算机。您笔记本电脑中的 CPU 可能有多个核心,这些子单元可以相对独立地处理操作。

除了 CPU,你的个人计算机还配备了图形处理单元(GPU),用于处理显示屏上的图形信息。GPU 随着现代视频游戏的发展而引起关注,这些游戏要求非常快的处理速度,以确保平滑地渲染游戏动作的视觉效果。实质上,GPU 解决方案将数学计算(如矩阵和向量操作)中的特定类型处理从 CPU 外包到专门处理某些类型计算的辅助处理单元中,这些计算非常有效地应用于图形数据。因此,GPU 也成为某些涉及大量矩阵或向量运算的科学计算应用的热门选择。

你应该了解的第三种处理器称为现场可编程门阵列(FPGA),尽管与*PU 命名约定不同,但它也是一种处理单元;然而,你的笔记本电脑中不太可能找到它。关于 FPGA 的有趣之处在于,与 GPU 不同,FPGA 并非为特定类型的应用程序开发;相反,它们被开发为适应定制类型的计算。因此,“现场可编程”作为其名称的一部分。

在 GCP 上,你可能也会遇到一种称为张量处理单元(TPU)的处理器,这是谷歌专门为涉及张量数据的机器学习应用开发和品牌化的处理器。张量是一个数学概念,用于表示和操作与向量和矩阵相关的多层数据。考虑到向量是一个具有一维的张量,而矩阵是一个具有两维的张量;更一般地说,张量可以具有超出这些维度的任意数量的维度,因此它们在机器学习应用中非常流行。TPU 属于称为专用集成电路(ASIC)的处理器类别,这些处理器专门设计用于特定用途而非通用用途。

现在你已经掌握了基本的处理器类型,让我们讨论它们如何在典型的高性能计算设置中组织起来。

计算组织的层次:核心,节点,集群和云

当你超越个人计算机进入高性能计算领域时,你会听到人们谈论核心,节点以及集群或云,如图 3-1 所示。让我们回顾一下它们的含义及其彼此之间的关系。

计算组织的层次

图 3-1. 计算组织的层次。

低层次:核心

核心是机器或节点处理器单元内的最小不可分割处理单元,可以由一个或多个核心组成。如果您的笔记本电脑或台式机比较新,其 CPU 可能至少有两个核心,因此被称为双核。如果有四个核心,则为四核,依此类推。高端消费级机器可以拥有更多核心;例如,最新的 Mac Pro 拥有十二核 CPU(如果按照拉丁术语来说应该称为十二核)。但是,专业级机器的 CPU 可以拥有数十个或数百个核心,而 GPU 通常有数量级更高的核心,达到数千个。与此同时,TPU 的核心数量与消费级 CPU 类似,而 FPGA 则完全打破了这一模式:它们的核心是由编程方式定义的,而不是由建造方式定义的。

中层:节点/机器

一个节点实际上只是集群或云中的计算机。它类似于我们在日常工作中主要与之交互的笔记本电脑或台式电脑,但没有我们通常与个人计算机相关联的专用显示器和外围设备。节点有时也简称为机器

顶层:集群和云

一个集群和一个云都是一组机器/节点。

一个集群是由节点部分网络连接在一起的 HPC 结构。如果您可以访问集群,那么很可能它要么属于您的机构,要么是您的公司租用的时间。集群也可以称为服务器农场负载共享设施

一个与集群不同,因为在其休眠状态下,其节点不是显式地网络连接在一起的。相反,它是一组独立的机器,可以根据您的需求进行网络连接(或不连接)。我们在本章的最后一节详细介绍了这一点,还包括虚拟化的概念,它提供了虚拟机(VM)以及容器化,它提供了 Docker 容器。

现在,我们转向如何有效使用给定的计算基础设施的非常常见问题,这通常涉及识别和解决关键的计算瓶颈。与本章的其余部分一样,深入探讨这个主题将超出本书的范围,因此我们的目标仅仅是让您熟悉关键概念和术语。

处理性能瓶颈

您偶尔会发现某些计算操作似乎很慢,您需要找出如何使它们更快(如果可能的话)。可供您选择的解决方案将取决于您面临的瓶颈的性质。

在非常高的层次上,计算机通常必须执行以下主要操作(不一定是线性顺序):

  1. 从永久存储中将一些数据读入内存

  2. 使处理器执行指令,转换数据并产生结果

  3. 将结果写回永久存储

数据存储和 I/O 操作:硬盘对比固态

步骤 1 和 3 被称为I/O 操作(I/O 代表输入/输出)。你可能会听到一些人将某些软件程序描述为“I/O 受限”,这意味着程序中最耗时的部分是从相对缓慢的存储中读取和写入数据。这通常适用于简单的程序,比如文件格式转换,其中你只是读取一些数据并以不同的形式写出,没有进行实际的计算(即几乎没有涉及数学运算)。在这些情况下,你可以通过使用更快的存储驱动器来加快操作速度;例如固态硬盘(SSD)而不是硬盘驱动器(HDD)。它们之间的关键区别在于,HDD 有物理盘片旋转和通过磁性从盘片读取和写入数据的臂;就像一个微型高科技转盘一样,而 SSD 没有移动部件。这使得 SSD 不太容易发生物理故障,并且在访问数据时也快得多。

如果你在一个网络基础设施中工作,其中存储驱动器不直接连接到计算节点,你的速度也会受到网络连接传输数据速度的限制。这可能由硬件因素决定,例如连接网络部件所用的电缆的种类。尽管在处理小文件时你可能察觉不到差异,但在处理整个基因组时,你肯定会注意到;即使在传输速度非常快的网络上,传输整个基因组也会耗费一些显著的时间。

内存:缓存或崩溃

步骤 2 是你的程序获取数据并应用某种转换或计算的地方,也就是有趣的部分。对于许多应用程序来说,这种计算需要在内存中保存大量信息。在这些情况下,如果你的机器内存不足,它可能会采用缓存的方式,这是一种利用本地存储空间作为真实内存替代品的方法。这使得你可以继续工作,但现在你的进程变成了 I/O 受限,因为它们需要将数据来回复制到慢速存储器中,这又使你回到了第一个瓶颈。在极端情况下,程序可能会无限期地停顿、无法完成或崩溃。有时,开发人员可以重新编写程序,使其在并发查看所需信息时更加智能,但当无法做到这一点时,解决方案就是简单地增加更多内存。幸运的是,与人类的记忆不同,计算机内存只是硬件,而且相对便宜。

专门的硬件和代码优化:权衡取舍

有时,程序的性质要求处理器本身进行大量的重型工作。例如,在广泛使用的 GATK 工具HaplotypeCaller中,一个操作可以计算基因型似然度;我们需要使用名为PairHMM的隐马尔可夫模型(HMM)来计算每个候选等位基因给定每个序列读取的似然度(如果这听起来像胡言乱语,目前不用担心——这只是一堆基因组特定的数学)。在基因组的某些区域,这导致我们在非常多的位点上每个位点做数百万次计算。通过性能分析测试,我们知道 PairHMM 对该工具来说是迄今为止最大的瓶颈。我们可以在一些表面层次上减少这一瓶颈;例如,通过使程序跳过一些我们可以预测在无效信息的情况下将不需要的计算。毕竟,计算某事物最快的方法是根本不计算它。

然而,仅仅因为懒惰使我们只能走到这一步,所以为了达到更高的水平,我们需要考虑我们需要做的工作的处理器类型(或应该使用的处理器类型)。不仅因为某些处理器运行比其他处理器更快,而且因为可以以一种非常特定于特定类型和架构处理器的方式编写程序指令。如果做得好,程序在这种情况下将特别高效,因此运行得更快。这就是我们所谓的代码优化,更具体地说是本地代码优化,因为它必须用处理器能够“本地”理解的低级语言编写,而不是通过额外的翻译层。

在像 CPU 这样的处理器类型中,不同的制造商(例如 Intel 和 AMD)为其产品的不同世代(例如 Intel Skylake 和 Haswell)开发了不同的架构,这些不同的架构为优化软件提供了机会。例如,GATK 软件包包括几个代码模块,对应于 PairHMM 算法的替代实现,这些实现针对特定的 Intel 处理器架构进行了优化。程序在运行在 Intel 处理器上时会自动激活最合适的版本,从而提供一些有用的速度增益。

然而,硬件优化的好处在于跨处理器类型最为明显;例如,当你比较某些算法在实施为在 FPGA 上运行而不是在 CPU 上运行时的表现如何时。Illumina DRAGEN 工具包(最初由 Edico Genome 开发)包括了优化为在 FPGA 上运行的工具实现,例如HaplotypeCaller,结果比原始的 Java 软件版本快得多。

硬件优化实现的缺点在于,根据定义,它们需要专门的硬件。这对于依赖共享机构计算系统并且无法访问其他硬件的许多研究实验室可能是一个大问题。相比之下,用 Java 编写的应用程序,如 GATK,可以在各种硬件架构上运行,因为 Java 虚拟机(JVM)将应用程序代码(在 Java 世界中称为字节码)转换为适合该机器的指令。Java 字节码和实际在机器上执行的内容之间的关注分离(SoC)称为抽象层,对于所有相关方来说都非常方便。开发人员不需要担心我们的笔记本电脑上有什么样的处理器,我们也不需要担心他们编写代码时考虑了什么样的处理器。它还保证软件可以轻松部署在标准的现成硬件上,这使得任何人都可以使用。

有时,你需要根据对你最重要的因素选择相同算法的不同实现之间。包括你更看重速度还是可移植性和互操作性。有时,你将能够同时享受两全其美。例如,Broad 研究所的 GATK 团队与 Illumina 的 DRAGEN 团队展开合作,两个团队现在正在共同努力制作统一的 DRAGEN-GATK 流水线,这将作为免费开源版本(通过 Broad)和作为经许可的硬件加速版本(通过 Illumina)提供。合作的一个关键目标是使这两种实现在功能上等效——这意味着你可以运行任一版本并在被认为是微不足道的误差范围内获得相同的结果。这将极大地使研究社区受益,因为可以将任一流水线分析的样本结合到下游分析中,而无需担心我们在上一章中简要讨论的批次效应。

并行计算

当你无法更快地前进时,就并行处理。在计算领域中,并行计算,或者并行性,是一种通过同时执行多个操作而不是按顺序(即等待每个操作完成后再开始下一个)来使程序更快完成的方法。想象一下,你需要为 64 个人煮饭,但你的电饭锅一次只能做够 4 个人的份量。如果你需要按顺序煮所有批次的米饭,那将需要整夜的时间。但如果你有八个可以并行使用的电饭锅,你可以提前八倍完成。

这是一个简单的想法,但它有一个关键要求:你必须能够将工作分解为可以独立执行的较小任务。将米饭分成部分很容易,因为米饭本身是一组离散单位。但你并不总是能够做到这种分割:例如,一个孕妇需要九个月才能生出一个婴儿,但你不能让九个女人共同完成这项工作。

好消息是,大多数基因组分析更像是米饭而不是婴儿——它们基本上由许多可以并行化的小独立操作组成。那么我们如何从煮米饭转变为执行程序呢?

并行化一个简单的分析

考虑当你运行一个分析程序时,你只是告诉计算机执行一组指令。假设我们有一个文本文件,我们想要计算其中的行数。要做到这一点的指令集可能就像这样简单:

打开文件;计算其中的行数;告诉我们数字;关闭文件。

请注意,“告诉我们数字”可以意味着将其写入控制台或将其存储在某个地方以供以后使用——现在我们不用担心这个。

现在假设我们想知道每行的单词数。指令集将如下所示:

打开文件;读取第一行;计算单词数;告诉我们数字;读取第二行;计算单词数;告诉我们数字;读取第三行;计算单词数;告诉我们数字。

依此类推,直到我们读完所有行,最后我们可以关闭文件。这很简单,但如果我们的文件有很多行,将会花费很长时间,并且可能不会充分利用我们可用的所有计算能力。因此,为了并行化这个程序并节省时间,我们只需将这组指令分割成单独的子集,如下所示:

  • 打开文件;索引行。

  • 阅读第一行;计算单词数;告诉我们数字。

  • 阅读第二行;计算单词数;告诉我们数字。

  • 阅读第三行;计算单词数;告诉我们数字。

  • [对所有行重复。]

  • 收集最终结果并关闭文件。

在这里,“阅读第N行”步骤可以并行执行,因为它们都是独立操作。

你会注意到我们添加了一个步骤,“索引行”。这是一点点预备工作,让我们能够并行执行“阅读第N行”步骤(或以任何我们想要的顺序),因为它告诉我们有多少行,以及重要的是,在文件中如何找到每一行。这使整个过程更加高效。正如你将在接下来的章节中看到的,像 GATK 这样的工具需要主数据文件(参考基因组、读取数据和变异调用)的索引文件。原因是已经完成了索引步骤,这样我们就可以让程序通过它们在基因组中的位置查找特定数据块。

无论如何,这是一个通用原则:你将线性的一系列指令转化为几个指令子集。通常有一个必须首先运行的子集和一个必须最后运行的子集,但中间的所有子集可以同时运行(并行)或按照你想要的任何顺序运行。

从核心到集群和云:多层次的并行 ism

那么,我们如何从煮饭器转变为并行化执行基因组分析程序呢?总的来说,并行化计算操作的行为包括将我们想要完成的工作的子集发送到多个核心进行处理。我们可以通过将工作分配到单个多核机器的核心上来实现这一点,或者如果我们可以访问集群或云,则可以将工作分派到其他机器上。事实上,我们可以结合这两种思想,并将工作分派到多核机器,其中工作进一步分配到每台机器的核心中。回到煮饭的例子,这就好像你不是自己煮饭,而是雇了一个餐饮公司来为你做。公司将工作分配给几个人,每个人都有自己的烹饪站,配备多个煮饭器。现在,你可以在同样的时间内喂更多的人!而且你甚至不需要洗碗。

无论我们想要将工作分配到单台机器的多个核心上还是分配到多台机器上,我们都需要一个系统来分割工作,分派任务进行执行,监视任务完成情况,然后编译结果。有几种系统可以做到这一点,大致分为两类:内部或外部于分析程序本身。在第一种情况下,并行化发生在我们正在运行的程序“内部”:我们运行该程序的命令行,而并行化发生在我们的任何额外“包装”之外。我们称之为多线程。在第二种情况下,我们需要使用一个单独的程序来运行程序的多个实例的命令行。外部并行化的一个例子是编写一个脚本,分别在基因组中每个染色体的数据上运行给定工具,然后通过额外的合并步骤将结果组合起来。我们称之为散射-聚集。当我们介绍工作流管理系统时,我们将在下一节中更详细地介绍这一点。在图 3-2 中,您可以看到我们如何在分析过程中使用多线程和散射-聚集并行性。

散射-聚集允许在不同 CPU 核心上并行执行任务(在单台机器或多台机器上,取决于实现方式)。

图 3-2。散射-聚集允许在不同 CPU 核心上并行执行任务(在单台机器或多台机器上,取决于实现方式)。

并行性的权衡:速度、效率和成本

并行化是加快处理大量数据处理的好方法,但它具有开销成本。在这一点上不要深入技术细节,我们只需说并行化作业需要管理,需要为其分配内存,调节文件访问,收集结果等等。因此,平衡成本与收益是非常重要的,避免将整体工作分解成过多的小作业。回到我们之前的例子,你不会希望使用一千个每次只煮一粒米的小型电饭煲。它们会占用太多台面空间,并且在分发每粒米和在煮熟后收集它们时所需的时间将大大抵消最初并行化带来的任何好处。

更普遍地说,虽然将并行性视为加快速度的一种方式很诱人,但重要的是要记住速度印象完全是观察者的主观看法。实际上,每个数据片段上运行的计算速度并没有加快。我们只是同时运行更多的计算,并且我们受到计算设置(通常以节点或核心数量衡量)以及 I/O 和网络速度等硬件限制的限制。更现实的是,将并行性视为优化可用资源以便更早完成任务的一种方式,而不是使单个任务运行更

尽管从键盘前的人的角度来看,经过的时间(通常称为挂钟时间;即“墙上时钟显示的时间”)似乎更短,这种区分可能显得有些迂腐。而这不正是我们理解为什么速度更快的吗?然而,从我们所利用的资源的角度来看,如果我们把处理器在所有使用的核心上执行计算所花费的时间加起来,我们可能会发现整个任务比纯顺序执行花费的时间更多,这是因为开销成本的缘故。

这引出了另一个重要问题:利用这些资源的货币成本是多少?如果我们使用的是一个专用机器,它只是坐在那里有多个核心而没有其他任务可做,那么并行化仍然绝对值得,即使有开销。我们将希望在这台机器上对我们做的每件事情进行并行处理,以最大化效率。然而,当我们开始在云环境中工作,就像我们在第四章中所做的那样,并且我们需要根据使用的资源逐项支付费用时,我们将需要更仔细地权衡最小化挂钟时间和账单大小之间的权衡。

用于并行化和自动化的流水线技术

许多基因组分析涉及大量例行数据处理操作,需要并行化以提高效率并自动化以减少人为错误。我们通过用机器可读语言描述工作流程来实现这一点,然后可以将其馈送到工作流管理系统中进行执行。我们在第八章详细介绍了这在实践中的运作方式,但首先让我们通过介绍基本概念、定义和关键软件组件来铺垫舞台。在进行时,请记住,这个领域目前没有通用的解决方案,最终取决于您在选择特定选项之前审查您的需求和可用选项。然而,我们可以确定一般原则来指导您的选择,并且我们使用 GATK 开发团队推荐并在 Broad Institute 的生产中使用的开源管道化解决方案来演示这些原则。就像本书的大部分内容一样,这里的目标不是规定特定软件的使用,而是通过实际工作示例展示所有这些如何结合在一起。

生物信息学世界中一个棘手的方面是我们有数十种脚本语言和工作流管理系统可供选择——如果您考虑更广泛的领域可能会有数百种。直接比较它们可能会很困难,因为它们往往是为特定类型的受众开发的,导致用户体验的模态非常不同。它们通常针对特定的用例进行调整,有时优化以在特定类别的基础设施上运行。我们经常看到一个解决方案被一个群体偏爱,但对另一个群体来说使用起来特别困难或令人沮丧。这些各种解决方案通常也不可互操作,这意味着您不能将为一个工作流管理系统编写的工作流脚本未经修改地在另一个系统上运行。这种缺乏标准化是人类已知的几乎每个研究领域都具有幽默和绝望的主题,正如在图 3-3 中所示。

标准的泛滥

图 3-3. 标准的泛滥的 XKCD 漫画(来源:https://xkcd.com/927)。

近年来,我们看到一些备受关注的倡议,比如全球联盟 GA4GH,其明确任务是制定通用标准并团结力量,集中精力在以互操作性为核心价值的一小部分解决方案上。例如,GA4GH 云工作组已经统一了其驱动项目的少量工作流语言,包括 CWL、Nextflow 和 WDL,这些语言也被本书采用。同时,鉴于无法单一语言可能满足所有需求和偏好的认识,几个团体正在努力通过将多种工作流语言的支持集成到其工作流管理系统中来增强互操作性。我们在本书中使用的工作流管理系统 Cromwell 支持 WDL 和 CWL,并且未来还可以扩展支持其他语言。

工作流语言

原则上,我们可以用几乎任何编程语言编写我们的工作流程;但实际上,某些语言比其他语言更适合描述工作流程。就像自然语言一样,编程语言也展示出多样化,可以通过语法、执行方式以及支持的编程范式等多种方式进行分类。

从实际角度来看,我们首先要区分通用编程语言和领域特定语言(DSLs)。通用编程语言旨在适用于广泛的应用程序,而 DSLs 则专门为特定领域或活动而设计。后者通常预装了专门制定的数据结构(即表示和操作数据的方式,能够“理解”底层信息的性质)和方便函数,作为快捷方式;例如,处理特定领域的文件格式,应用常见处理操作等。因此,如果您的需求完全符合语言的预期范围,DSL 可能是一个有吸引力的选择,特别是如果您的计算背景有限,因为 DSL 通常使您能够完成工作而无需学习大量编程概念和语法。

另一方面,如果您的需求更加多样化,或者您习惯于拥有通用语言更丰富的工具箱,您可能会觉得领域特定语言限制了您的舒适度。在这种情况下,您可能更喜欢使用通用语言,特别是那些附带领域特定库(例如,Python 与 Biopython 及相关库)。事实上,使用通用语言更有可能使您能够同时用同一种语言编写数据处理任务和管理操作流程,这正是许多人传统上完成此类工作的方式。然而,我们现在在这个领域看到的是描述和内容分离的趋势,这体现为专门设计用于描述工作流的 DSL 及专门的工作流管理系统的增加采纳。这种演变与推动互操作性和可移植性紧密相关。

基因组学中流行的管道语言

当我们看向生物信息学和基因组学交汇处的人群时,我们看到了各种背景、计算经验和需求。一些人来自软件工程背景,喜欢功能丰富、高度结构化的语言,虽然强大但不易接触。另一些人来自系统管理,相信一切问题都可以通过巧妙运用 Bash、sed 和 awk 来解决,这些是 Unix 世界的胶带。在“生物”这一边,那些接受过计算训练的人更倾向于喜欢 Python 和 R 这样的分析师喜爱的语言,这些语言正在取代古老的 Perl 和 MATLAB;有些人也倾向于使用领域特定语言。与此同时,湿实验室训练过的研究人员可能对所有这些都感到困惑,至少在初次接触时是这样。(作者注和免责声明:杰拉尔丁认为自己是最初感到困惑的人之一,曾接受传统微生物学培训,并最终在拼命逃离湿实验室工作台时学会了 Perl 和 Python 的基础。剧透:她成功了!)

根据最近的民意调查,在基因组学领域最受工作流程作者欢迎的一些语言包括 SnakeMake 和 Nextflow。它们都因其高度灵活性和易用性而著名。同样,CWL 和 WDL 因其侧重于可移植性和计算再现性而受到青睐。在这两者中,CWL 更常被技术背景的人士青睐,他们喜欢其高抽象度和表现力。相比之下,WDL 一般被认为更易于广泛受众接受。

当选择工作流语言时,我们最终会考虑四个主要标准:语言支持的数据结构类型(即我们如何表示和传递信息),它如何使我们能够控制操作流程,对预期读写者而言它的可访问性,以及它如何影响我们与他人合作的能力。无论我们选择什么,都不太可能满足每个人的要求。然而,如果我们要将所有这些归结为一个建议,那就是:如果您希望您的工作流脚本在您的研究领域广泛使用和理解,请选择一种对新手来说既开放又易于访问,但又能够扩展到更高级别需求的语言。当然,尽量选择一种可以在不同工作流管理系统和计算平台上运行的语言,因为您或您的合作者下一个工作环境可能是什么,这是无法预测的。

工作流管理系统

许多工作流管理系统存在,但通常它们都遵循相同的基本模式。首先,工作流引擎读取并解释工作流脚本中的指令,将这些指令调用转换为可执行的作业,并与一系列输入(包括数据和参数)关联起来。然后,它将每个带有其输入列表的作业发送到另一个程序,通常称为作业调度器,负责在指定的计算环境上实际执行工作。最后,在作业完成时检索任何生成的输出。大多数工作流管理系统都具有一些内置逻辑,用于控制执行流程,即它们调度执行作业的顺序,并确定如何处理错误以及与计算基础设施进行通信的方式。

增加分析的可移植性和互操作性的另一个重要进展是采用容器技术,在本章的最后一节中详细介绍。暂时假设容器是一种机制,允许您封装特定任务的所有软件需求,从操作系统(OS)的最深层到库导入、环境变量和附属配置文件。

虚拟化和云计算

到目前为止,我们假设不论你是在使用单台计算机还是集群,你都在处理“真实”的物理机器,每台都设置有特定的操作系统和软件堆栈,如图 3-4 A 所示。不幸的是,在像机构集群这样的共享环境中,与这种类型的系统交互有几个缺点。作为最终用户,你通常无法选择操作系统、环境和安装的软件包。如果你需要使用一些不可用的内容,你可以要求管理员安装,但他们可能会拒绝你的请求,或者你想要的包可能与现有软件不兼容。对于帮助台背后的系统管理员来说,跟踪用户需求、管理版本和处理兼容性问题可能是一件令人头疼的事情。这些系统需要花费精力来更新和扩展。

因此,大多数现代系统使用不同程度的虚拟化,这基本上是一种巧妙的抽象技术,通过虚拟机(VMs)和容器,在同一硬件上运行多种不同的软件配置,如图 3-4 B 和 C 所示。这些构造可以在许多情境下使用,包括(可选地)在本地系统上(你甚至可以在笔记本电脑上使用容器!),但它们对于云基础设施是绝对必要的。

A) 安装在物理机器上的软件堆栈;B) 提供客户端访问单独 VM 的服务器;C) VM 和容器并排比较。

图 3-4. A) 安装在物理机器上的软件堆栈;B) 托管多个 VM 的系统;C) 托管多个容器的系统。

VM 和容器

VM 是一个基础架构级别的构造,包括自己的操作系统。VM 位于运行在底层物理机实际操作系统上的虚拟化层之上。在最简单的情况下,VM 可以在单台物理机上运行,其效果是将该物理机转换为共享底层资源的多台服务器。然而,最强大的系统利用多台物理机支持 VM 层,这些物理机之间有一个复杂的层,负责管理物理资源的分配。好消息是,对于最终用户来说,这并不会有任何区别——你只需知道你可以独立地与特定的 VM 进行交互,而不必担心它所依赖的环境。

容器在原理上类似于虚拟机,但它是一个应用程序级别的构造,要轻得多且更具移动性,这意味着可以轻松地部署到不同的站点,而虚拟机通常与特定位置的基础设施绑定。容器旨在捆绑运行特定程序或一组程序所需的所有软件。这使得可以在支持运行容器的任何基础设施上轻松重现相同的分析,从你的笔记本电脑到云平台,而无需去辛苦地识别和安装所有涉及的软件依赖关系。甚至可以在同一台机器上运行多个容器,因此如果需要运行具有不兼容系统要求的程序,可以轻松地在不同环境之间切换。

如果你在想,“这两者听起来都不错;我应该选择哪一个?” 这里有个好消息:你可以将两者结合使用,如 图 3-5 所示。

一个系统的表示,其中有三个虚拟机,其中两个正在运行容器。

图 3-5. 一个有三个虚拟机的系统:左侧的虚拟机运行两个容器,为应用程序 #1 和应用程序 #2 服务;中间的虚拟机运行一个容器,为应用程序 #3 服务;右侧直接为应用程序 #4 提供服务(没有容器)。

有几个注册表用于分享和获取容器,包括 Docker HubQuay.ioGCR,Google 在 GCP 中的通用容器注册表。在注册表中,容器被打包为一个 镜像。请注意,这与图片无关;这里的 镜像 是以软件特定方式使用的术语,指的是一种特殊类型的文件。你知道有时候在需要在计算机上安装新软件时,下载的文件称为 磁盘镜像 吗?那是因为你下载的文件是以你的操作系统会把它当作物理磁盘的格式来对待。这基本上就是同样的概念。要使用一个容器,首先要告诉 Docker 程序从注册表(比如 Docker Hub,稍后会详细介绍 Docker)下载或 拉取 一个容器镜像文件,然后告诉它初始化容器,这在概念上等同于启动一个虚拟机。在容器运行之后,你可以在其系统上运行任何安装的软件。你还可以根据需要安装额外的软件包或执行额外的配置。图 3-6 说明了容器、镜像和注册表之间的关系。

容器、镜像和注册表之间的关系。

图 3-6. 注册表、镜像和容器之间的关系。

容器系统中使用最广泛的品牌是由同名公司制造的Docker。由于 Docker 的普及性,人们通常会说“一个 docker”而不是“一个 container”,就像在美国,人们因为 Xerox 公司的主导地位而用“xerox”替代“复印机”一样。然而,小写的docker也是你在机器上安装以运行 Docker 容器的命令行程序。同样地,尽管将软件工具、包或分析打包成 Docker 容器的操作应该被称为“containerizing”,但人们通常称之为“dockerizing”,比如,“我将我的 Python 脚本 dockerized 了”。Dockerizing 一个工具涉及编写一个名为 Dockerfile 的脚本,描述构建 Docker 镜像所需的所有安装和环境配置,如图 3-7 所示。

创建 Docker 容器的过程。

图 3-7. 创建 Docker 镜像的过程。

如前所述,可以在各种环境中使用容器,包括本地机器、高性能计算(HPC)和云中。一个重要的限制是,像大多数机构的 HPC 这样的共享环境通常不允许使用特定的 Docker,因为它需要称为root的非常高级的访问权限。在这种情况下,系统管理员会更喜欢Singularity,这是一个可以达到相同效果的替代系统。幸运的是,在 Singularity 系统中可以运行 Docker 容器。

引入云计算

最后,我们来到了许多人一直在等待的话题:云计算究竟是什么?出人意料的简单答案是,云是一堆你可以租用的计算机。在实践中,这意味着作为用户,你可以轻松启动一个虚拟机,并选择需要多少 RAM、存储空间以及什么样的 CPU。你需要一台有 1 TB RAM 和 32 个 CPU 用于基因组装的虚拟机?没问题!大多数云中的虚拟机都在运行某种形式的 Linux 作为操作系统,你可以在启动时选择,并通常使用安全外壳(SSH)通过远程 Shell 访问。

尽管一些虚拟机包含免费存储空间,但这通常是暂时的,当你停止和启动虚拟机时会消失。相反,请使用块存储(一种持久设备)来存储数据、脚本等内容在你的虚拟机上。你可以把它们想象成可以随时“插入”虚拟机的 USB 闪存驱动器。即使终止虚拟机,块存储上的文件也会安全保存。文件也可以存储在对象存储中——把它想象成类似 Google Drive 或 Dropbox,文件可以被多个虚拟机同时读取和写入,但通常你不会将其用作普通文件系统。相反,它们更像是用于在虚拟机之间共享文件的 SSH 文件传输协议(SFTP)服务器,你可以通过实用程序与对象存储系统之间传输文件。云的最后一个基本组成部分是网络。通过虚拟网络服务,你可以控制谁能访问你的虚拟机,严密锁定以确保只有你(或你信任的其他人)能访问。

云并非松软

当你想到云时,它们是松软、遥远和难以捉摸的,根本不是可以触摸、感受和捕捉的真实事物。与其名字相反,如今大多数人直接(或间接)使用的云基础设施最终由真实的物理计算机组成,这些计算机装在巨大的数据中心中并不停地闪烁。然而,它与以往的计算模型不同之处在于它的短暂性。就像云朵时而出现、倾盆大雨后消散一样,云计算对于最终用户而言也是短暂的。云允许你作为研究人员、开发人员或分析人员在需要时请求计算基础设施,使用它进行计算,然后在完成后释放所有资源。

这种方法很棒,因为它节省了时间和金钱,你可以一次性启动大量资源,完成工作后再将其关闭,节省运行硬件的成本。你无需过多考虑服务器的机架位置、配置方式、硬件健康状况、功耗或其他基础设施问题。所有这些都已经为你抽象化并在你不需要过多考虑的情况下处理好了。你所专注的是需要执行的计算工作、需要使用的资源以及如何从时间和金钱的角度最有效地使用这些资源。

云基础设施和服务的演进

亚马逊在 2006 年推出了第一个广泛成功的商业公共云服务,但这个基本理念已经存在很长时间了。上世纪 60 年代的大型计算机通常是租用使用的,考虑到购买和运行这些设备的巨大成本,这是非常合理的。尽管个人计算机的发明,租用计算基础设施的概念在随后的几十年里一次又一次地出现。在学术界和工业界,上世纪 90 年代和 2000 年代共享网格计算的概念是租用主机时间的现代等价物。团体联合建立了廉价但功能强大的基于 Linux 的 HPC 集群,通常由中心管理并根据某种财务分配方法分配给多个团体使用。

当今的公共云在抽象级别上有所不同。因此,采用了“蓬松、无定形”的名称来反映这样一个事实:在云上运行大规模分析时,并不需要理解底层细节。在使用特定云时,您可能知道托管基础设施的大致地理位置(例如,AWS 的北弗吉尼亚区域us-east-1),但很多细节对您来说是隐藏的:您的虚拟机底层硬件有多少人在使用,数据中心真正的位置在哪里,网络如何设置等等。您了解的是影响服务成本和作业执行时间的关键细节,例如可用的 CPU 数量,虚拟机的 RAM 容量,文件存储系统的正常运行时间保证以及系统遵守的法规等。

现在有许多公共云提供商——任何能支付服务费的人都可以使用的云服务。目前在西半球最主导的是 AWS、微软 Azure 和 GCP。每家公司提供的服务组合类似,从可按小时(或分钟)租用的简单 VM,文件存储服务和网络服务到更专业的服务,例如谷歌的 Cloud TPU 服务,允许您执行加速的机器学习操作。然而,重要的特征是这些资源是作为服务提供的:您按需使用,按小时、分钟、秒或 API 调用计费。

云的利弊

当讨论云时,许多人指出的主要优势之一是成本。建设数据中心的固定成本是巨大的。您必须雇用人员来机架和维护物理服务器,监视入侵网络,处理电力波动,备份,空调等等。老实说,这是一项繁重的工作!对于支持数百用户的数据中心,维护基础设施所需的成本可能是值得的。但是许多研究人员、开发人员、分析师和其他人意识到,他们不必总是拥有数百台计算机随时待命,只等待任务。相反,使用云环境更有意义,您可以在笔记本电脑上进行本地工作,无需大量资源,然后,在您的分析准备好时,您可以扩展到数百甚至数千台机器。商业公共云允许您在需要时轻松扩展您的容量,并进行大规模分析,而不是等待几周、几个月甚至几年,以等待专用本地集群完成任务。同样,您不必为开发算法和在本地完善分析所花费的所有时间支付本地基础设施的维护费用。

最后,作为公共云用户,您可以完全控制您的环境。需要特定版本的 Python 吗?您有一个只有在安装了非常特定的工具链时才能编译的奇特库吗?没问题!云让您完全控制您的虚拟机(VMs),这是共享的本地基础设施所不能允许的。即使有这种控制权,如果按照云供应商的最佳实践进行设置,公共云解决方案由于在这些环境中专门用于安全服务的大量资源以及虚拟化带来的用户隔离,通常比本地基础设施更安全。

尽管公共云平台令人惊叹、强大、灵活,并且在许多情况下可以有效地节省大量费用,但也有一些需要注意的缺点。如果您希望每月始终处理由您测序组生成的固定数量的基因组,那么公共云可能就不那么吸引人了,而构建一个小型的本地计算环境对于这种非常可预测的本地数据工作负载更为合理。当然,这是在假设您有能够担任管理员角色的 IT 专业人员的情况下。另一个考虑因素是专业知识。使用云需求一定水平的专业知识,一个不经意的新手用户可能会意外地使用带有弱密码的虚拟机,设置具有弱安全性的数据存储桶,以不安全的方式共享凭据,或者在管理 Linux 虚拟机群时完全迷失。尽管如此,对于许多人来说,这些潜在的缺点通常被在商业云环境中灵活工作的好处所抵消。

云服务研究用例类别

前一节中描述的云的基本组件实际上只是冰山一角。主要商业云平台上提供了更多服务。事实上,服务种类繁多,有些是通用的,有些是特定于特定云的,我们无法在此详细描述。但是,让我们看看研究人员如何最常见地使用云服务。表 3-1 提供了总体概述。

表 3-1. 云基础设施使用类型概览

使用类型 云环境 描述 优点 缺点
轻量级开发 Google Cloud Shell 使用简单启动的免费虚拟机编辑代码和脚本
  • 免费

  • 启动和登录非常容易

|

  • 非常有限的虚拟机

|

中级分析和开发 单个虚拟机 启动单个虚拟机,登录,执行开发和分析工作
  • 可以控制启动的虚拟机上的资源

  • 虚拟机可能足够强大,能够进行逼真的分析

|

  • 启动虚拟机需要更多的配置

  • 更大的虚拟机会增加成本

|

批量分析 通过批处理系统使用多个虚拟机 使用像 AWS Batch 或 Google Cloud Pipelines API 这样的系统来并行启动多个虚拟机并分析数据
  • 允许并行扩展分析

  • 类似 Cromwell 的工作流管理系统支持这些操作,几乎不费吹灰之力

|

  • 成本和复杂性增加

|

框架分析 通过框架使用多个虚拟机 使用 Spark、Hadoop 或其他框架进行数据分析
  • 这些框架允许进行专业分析

|

  • 成本和复杂性增加

|

轻量级开发:Google Cloud Shell

云是软件开发的绝佳场所。即使许多研究人员希望使用自己的笔记本电脑或工作站进行开发,使用云作为主要开发环境,尤其是用于测试,有时也有非常引人注目的理由。例如,在 GCP 上,您可以从 Google Cloud 控制台使用 Google Cloud Shell 进行轻量级开发和测试,这是一个免费(是的,免费!)的虚拟机,具有一个虚拟 CPU 核心和 5 GB 存储空间,只需点击 Web 控制台中的终端图标即可使用。这是一个非常适合进行轻量级编码和测试的环境;只需记住从您的免费实例复制代码(例如使用 Git),因为每周的总运行时间有限制,如果一段时间不使用该服务,您的 5 GB 存储空间可能会被清空。但是,这是一个快速入门云计算并执行轻量级任务的绝佳选择。您只需要一个 Web 浏览器,GCP 工具已经预安装和配置好了。许多其他您可能希望使用的工具也已经安装,包括 Git 和 Docker,以及 Java 和 Python 等语言。在下一章中您将有机会早期尝试它。

中级开发和分析:单个 VM

尽管 Google Cloud Shell 在许多用途上表现出色,易于使用且免费,但有时您可能需要更强大的功能,特别是如果您想要在下一个规模上测试您的代码或分析,因此您会启动自己专用的 VM。这可能是最常用的选项之一,因为它提供了灵活性和简易性的结合:您可以定制您的 VM,确保有足够的 CPU 核心、RAM 和本地存储来完成您的目标。与 Google Cloud Shell 不同,您必须支付您运行此 VM 的每小时或每分钟费用;但是,您可以完全控制 VM 的性质。您可以将其用于软件或算法开发,测试分析方法,或者同时启动一小组这些 VM 来在多个 VM 上执行分析。但请记住,如果您手动启动这些 VM,它们上面预安装的工具较少,这些工具对您来说是准备好的。这使得像 Git 和 Docker 这样的实用程序对于将分析任务从一个 VM 移动到另一个 VM 非常有帮助。在第四章到第七章中,您将有机会广泛使用这些功能。

批量分析:通过批处理服务多个 VM

对于大多数了解它的用户来说,这种方法确实是最佳选择。虽然您可能会使用您的笔记本电脑或 Google Cloud Shell 进行软件和脚本开发,并在一个或多个 VM 上进行适当规模的硬件测试,但如果您的目标是扩展您的分析,则最终不希望手动管理 VM。想象一下同时运行 10,000 个基因组对齐任务;您需要能够批处理工作、自动为您提供 VM,并在工作完成后关闭 VM。批处理系统专为此任务设计;例如,Google Cloud 提供了 Google Cloud Pipelines API,您可以使用它同时提交大量作业。该服务将负责启动多个 VM 来执行您的分析,然后在收集输出文件后自动清理它们。如果您需要对大量样本进行非交互式分析,这将非常方便。在第八章到第十一章中,您将看到像 Cromwell 这样的工作流引擎是如何利用这些批处理服务的,它们负责处理启动批处理作业的所有细节。这使得您更容易专注于正在执行的分析细节,而不是所涉及的基础设施。

框架分析:通过框架服务多个 VM

许多研究人员将采用的最终方法涉及交互式迭代分析。在基因组学中,您可以使用批处理系统进行大规模比对和变异调用,但是在您获得变异的 VCF 文件后,您可能选择转移到 Spark 集群、RStudio、Jupyter Notebook 或任何大量分析环境中进行后续分析。在第十二章中,我们探讨了在 Terra 中的实现方式,您可以使用该平台轻松创建自定义环境进行数据处理,具有 Jupyter 界面进行交互式分析,生成用于出版的图表,并与他人分享结果。

总结与下一步

在本章中,我们完成了初级主题,为您介绍了基因组学(第二章)和计算技术(本章)。我们深入研究了计算机硬件、并行计算和虚拟化的细节,并为您展示了使用工作流执行系统在云上扩展分析能力的潜力。在第四章中,我们迈出了在云环境中的第一步,并向您展示如何在 GCP 上运行您自己的虚拟机。

第七章:GATK 体细胞变异发现的最佳实践

在本章中,我们将讨论体细胞变异的发现,主要是在癌症研究中的应用。这将引入新的挑战,相应地,也将带来新的实验和计算设计。我们从体细胞短变异开始,这些变异与生殖细胞的短变异仍然有很多共同点,但也有足够的新挑战来保持事情的趣味性。然后我们扩展视野,包括拷贝数变异,这需要与我们迄今为止采取的方法大不相同。

癌症基因组学中的挑战

在我们深入探讨之前,让我们明确一件事:与癌症相关的所有事情都更加困难。考虑在肿瘤甚至开始发展之前,在身体中正在发生的事情。我们体内的活细胞并非静态;它们具有新陈代谢活跃,吸收营养,进行工作,并排出废物。其中一些以各种速率分裂。酶正在解开 DNA 以转录它和/或复制并重新包装它。在单个细胞的尺度上,这些分子交易中极少有错,但因为这在数百万个细胞中随时都在发生,所以数目上积累了许多小错误,导致全面的突变。

大多数情况下,这些突变根本没有可察觉的影响。但是偶尔会出现一种突变,使受影响的细胞的代谢不稳定,导致其开始更快地分裂并产生肿瘤。但这还不是癌症;我们体内的大多数肿瘤仍然相对局限和无害。然而,有些肿瘤随后经历其他的不稳定性突变,引发一连串反应,导致癌症的发展,包括侵袭性生长,最坏的情况下还可能发生转移:向身体的其他部位扩散。推动这种进展的突变事件通常被称为驱动突变,与其他被称为乘客突变的突变形成对比。在癌症研究中,驱动突变通常是体细胞分析的主要焦点。

这里的困难在于随着肿瘤的进展,各种代谢功能开始崩溃,细胞失去修复受损 DNA 的能力。因此,突变事件的频率增加,变异在多种不同的细胞系中积累,产生高度异质的环境。当病理学家取样肿瘤组织时,他们得到的是多种细胞系的混合物,而不是健康组织中你所期望的主要是单克隆细胞的群体。

这对检测体细胞变异是一个巨大的问题,因为依赖于我们取样时肿瘤组织的内部状态,由驱动突变引起的变异可能仅占总基因组材料的一小部分。最直接的后果是,我们不能允许变异调用程序对我们预期发现的变异等位基因分数做出任何假设。这与生殖系情况形成了很大差异,其中变异等位基因分数应大致遵循有丝分裂体的多倍性。这就是为什么我们在体细胞与生殖系变异发现分析中使用不同的变异调用程序的原因,这些程序在底层使用不同的算法来模拟数据中的基因组变异。

此外,采样过程本身引入了混淆因素。根据病理学家的技能、肿瘤进展的阶段和生物组织类型的影响,我们可能会得到一种被健康“正常”细胞污染的肿瘤样本。相反,如果我们能够获得匹配的正常样本,即来自同一人的假定健康组织的样本—事实上,该样本可能受到低水平肿瘤细胞的污染,如图 7-1 所示。

A) 肿瘤进展导致异质性;B) 取样困难。

图 7-1. 肿瘤进展导致异质性(左);取样困难(右)。

所有这些对体细胞变异分析的实际影响是,我们的工作必然对污染源和误差非常敏感,因为我们试图检测的信号远低于数据中存在的噪音,特别是在晚期癌症的情况下尤为如此。

体细胞短变异(SNV 和 Indels)

让我们开始探索体细胞变异调用与短变异—SNV 和 indels。如果您已经阅读了第六章,这里涵盖的一些核心概念应该会让您感到耳熟能详,事实上,一些工具和底层算法也是相同的。与之前一样,我们将查看大量阅读材料及其确切序列,以识别替代和小插入及删除。

然而,我们实际遵循的基本实验设计却有很大不同。在生殖系变异发现中,我们主要关注的是在某些人中存在而在其他人中不存在的变异,这导致我们偏爱联合调用方法。在体细胞变异发现中,我们主要寻找的是来自同一人的样本之间的差异,因此我们将把焦点转移到另一种范式:肿瘤-正常 对分析。

在癌症背景下,识别体细胞短变异的最有用信息来源是将肿瘤与同一个体的健康组织样本进行比较,正如我们之前提到的,这称为匹配正常,通常简称为正常。这里的思想是,在正常样本中观察到的任何变异都是个体的生殖基因组的一部分,可以排除为背景,而在肿瘤样本中观察到的任何在正常样本中不存在的变异更可能是体细胞突变,正如图 7-2 所示。

不幸的是,一些混淆因素使情况变得更加复杂。例如,用福尔马林和石蜡保存的活检样本(FFPE,指福尔马林固定石蜡包埋),这是一种广泛使用的实验室技术,会在样本制备过程中发生生化改变,导致对肿瘤样本特定的假阳性变异调用模式。相反,正常组织中存在肿瘤细胞(无论是在取样过程中污染还是转移扩散过程中)可能会导致真正的体细胞变异被错误地认定为个体的生殖系变异。最后,正如我们之前讨论的,各种技术错误和污染源对所有高通量测序技术都会产生一定程度的影响,对体细胞变异调用的影响会更大一些。因此,我们需要应用一些强大的处理和过滤技术来减轻所有这些问题。(总体工作流程如图 7-3 所示。)

肿瘤-正常比较的基本概念。

图 7-2. 肿瘤-正常比较的基本概念。

在下一小节详细介绍肿瘤-正常工作流程之前,重要的是要注意,有时候不可能对样本或一组样本应用完整的肿瘤-正常对分析,通常是因为我们没有为它们准备匹配的正常样本。当这种情况发生时,特别是因为调查者未能从参与者或患者那里收集到匹配的正常样本时,情况就会变得非常令人沮丧。这些案例突显了在进行数据收集之前,研究人员确保绘制出完整的分析方案的重要性。另一方面,在某些情况下,从生物学角度来看,获取匹配的正常样本是不可能的;例如,血液源性癌症通常能够影响身体的每个组织,因此我们不能假设任何组织样本都能构成适当的正常样本。

无论缺乏匹配正常样本的原因是什么,我们在下一小节中介绍的工作流程可以在仅进行少量修改的情况下应用于肿瘤样本,这些修改在在线 GATK 文档 中有详细说明。然而,您应该意识到结果必然会更加嘈杂,因为您将依赖于种群资源来排除个体自身的基因组。

肿瘤-正常配对分析工作流程概述

这个工作流程,如 图 7-3 所示,旨在应用于肿瘤样本及其匹配的正常样本。我们可以同样应用于全基因组数据以及外显子组数据,但是肿瘤和正常样本必须是相同的数据类型,并且经过相同的预处理工作流程,就像我们为配对的基因组变异发现所描述的那样。

somatic short variant 发现的最佳实践。

图 7-3. somatic short variant 发现的最佳实践。

主要的变异调用步骤,涉及一个名为 Mutect2 的工具,应用于肿瘤和正常样本,同时添加了两个重要资源:一个正常样本汇总(PoN)和一个包含先前报告的所有基因组变异的基因组种群频率数据库。 PoN 是一个资源,允许我们消除常见的技术性伪迹;我们在下一小节专门解释它的工作原理以及如何创建一个,尽管出于实际原因您不会自己运行它。我们在 “过滤 Mutect2 调用” 中讨论基因组种群频率资源。过滤过程将考虑读取方向统计、跨样本污染估计、基因型背景和与 PoN 的一致性。最后,我们对变异调用集应用功能注释,以预测候选变异的影响。

创建 Mutect2 PoN

PoN 是一个以 VCF 格式呈现的调用集,旨在捕获常见的重复性人工产生的变异,因此如果在我们的肿瘤数据中遇到它们,我们可以将其过滤掉。我们通过在一组与我们的肿瘤样本使用相同测序和文库准备技术生产的正常样本上运行 somatic 调用器 Mutect2 来生成它。这个想法是,我们要求调用器展示它可能考虑为体细胞变异的一切,排除明显的生殖变异(除非另有说明)。因为这些是正常样本,而不是肿瘤样本,我们可以合理地假设以这种方式产生的任何变异调用要么是生殖变异,要么是人工产生的。对于更有可能是人工产生的那些,我们因此可以进一步假设,如果我们在多个正常样本中看到相同的人工产生的变异,那么它们可能是由涉及的技术过程引起的,因此我们可以期望在以相同方式处理的一些肿瘤样本中看到它们出现。如果我们看到它们,我们将希望降低它们是真实体细胞变异的可能性。

注意

制作 PoN 时使用的正常样本通常来自与我们分析肿瘤的人无关的人。它们可以是其他癌症研究参与者的匹配正常样本,也可以是从无关研究项目中收集的健康个体的生殖系样本,只要提供了适当的同意。参与者的背景遗传学或其来源人口并不重要;关键在于数据的技术特征。理想情况下,我们希望包括至少 40 个样本;在 Broad Institute 用于研究目的的大多数 PoN 由数百个样本组成。

我们不会在本章中运行 PoN 创建命令,因为运行时间太长。相反,我们将使用一些通用命令行演示用法。您可以在GATK 最佳实践文档中找到完整实现的链接。

如果我们要这样做,我们将首先在选定为 PoN 的正常样本上以肿瘤单模式运行 Mutect2。我们将在面板中的所有正常样本上分别执行此操作。以下命令(以及本节中的后续命令)仅为示例;稍后我们将让您在虚拟机上执行命令:

 gatk Mutect2 \
    -R reference.fasta \
    -I normal_1.bam \
    -O normal_1.vcf.gz \
    --max-mnp-distance 0

此命令将 somatic 变异调用模型应用于正常组织样本的 BAM 文件,并生成一个 VCF 文件,其中包含最有可能是人工产生的或生殖变异的调用。

注意

--max-mnp-distance 0 设置禁用了工具将相邻 SNV 合并为多核苷酸变体的能力,以最大化 PoN 中所有正常样本之间的可比性。

接下来,我们将使用 GenomicsDB 整合所有每个正常样本的 VCF 文件,您可能会从有关配对调用的常规流程中认出它。当时我们在整合 GVCFs,而在这里我们使用的是常规的 VCFs,但这并没有任何区别:GenomicsDB 工具能够轻松处理任何类型的 VCF:

gatk GenomicsDBImport \
    -R reference.fasta \
    -L intervals.interval_list \
    -V normal_1.vcf.gz \
    -V normal_2.vcf.gz \
    -V normal_3.vcf.gz \
    --genomicsdb-workspace-path pon_db

最后,我们将提取与 PoN 目标相关的调用。默认情况下,规则是保留在两个或更多样本中观察到的任何变异调用,但低于特定频率阈值的人群资源:

gatk CreateSomaticPanelOfNormals \
    -R reference.fasta \
    -V gendb://pon_db \
    --germline-resource af-only-gnomad.vcf.gz \
    -O pon.vcf.gz

您可能会认识到 gnomAD 是我们在第六章中使用的人群资源,在单样本过滤协议中。gnomAD 数据库是一个公共资源,包含来自超过 100,000 人的变异调用,并为每个人提供每个等位基因在数据库人群中的频率。我们在这里使用该信息来评估在面板中显示在多个正常样本中的变异调用,实际上可能是一种生殖细胞变异,而不是一个经常出现的人工产物。

这将生成我们将用作 PoN 的最终 VCF。它不包括任何样本基因型;仅保留位点级别的信息。我们提供一个示例 PoN,您可以像之前一样使用 zcatgrephead 来查看文件内部的结构(在 GATK 容器内 /home/book/data/somatic 目录中执行此命令):

# zcat resources/chr17_m2pon.vcf.gz | grep -v '##' | head -3
#CHROM  POS     ID      REF     ALT     QUAL    FILTER  INFO
chr6    29941027        .       G       A       .       .       .
chr6    29941061        .       G       C       .       .       .

注意,此示例 PoN 文件仅包含位于染色体 6、染色体 11 和染色体 17 内感兴趣区域的数据。

运行 Mutect2 对肿瘤-正常配对样本进行分析

现在您已经知道如何创建 PoN 了,让我们进入本节的主要内容,即使用我们从 1000 基因组计划的 40 个公共正常样本中为您创建的 PoN,在肿瘤-正常样本对上运行 Mutect2。就像在第六章中一样,我们将在我们值得信赖的 VM 上的 GATK 容器中完成这项工作,所以如果您的 VM 还没有运行,请启动它,并按照我们在第五章中所示的方式启动 GATK 容器。不过这次,我们的工作目录是 /home/book/data/somatic,我们需要创建另一个沙盒:

# cd /home/book/data/somatic
# mkdir sandbox

我们一起对肿瘤-正常样本对运行 Mutect2,指定在 BAM 文件中使用 -normal 参数编码的正常样本名称。我们提供 PoN 和 gnomAD 基因组资源,分别使用 -pon--germline-resource 参数。此外,我们指定间隔(使用 -L 和间隔列表文件),因为我们正在处理外显子数据:

# gatk Mutect2 \
    -R ref/Homo_sapiens_assembly38.fasta \
    -I bams/tumor.bam \
    -I bams/normal.bam \
    -normal HCC1143_normal \
    -L resources/chr17plus.interval_list \
    -pon resources/chr17_m2pon.vcf.gz \
    --germline-resource resources/chr17_af-only-gnomad_grch38.vcf.gz \
    -bamout sandbox/m2_tumor_normal.bam \
    -O sandbox/m2_somatic_calls.vcf.gz

这将运行几分钟,所以现在是伸展一下的好时机。大约五分钟后,您应该能看到来自Mutect2的完成消息:

15:07:19.715 INFO  ProgressMeter - Traversal complete. Processed 285005 total
regions in 5.0 minutes.

这个Mutect2命令生成一个包含可能变异调用的 VCF 文件以及一个 bamout 文件。你可能还记得我们在第五章中与HaplotypeCaller一起运行的练习中的 bamout 文件;这是同一件事。Mutect2是建立在与HaplotypeCaller大部分相同的代码上,并使用相同的基于图的重对齐技巧来提高对插入缺失的灵敏度。它们之间的主要区别在于基因分型模型。有关它们各自算法的更多信息,请参阅GATK 网站上的Haplotype​CallerMutect2文档。目前,只需记住Mutect2也会重新对齐读取,因此如果我们稍后想要查看读取数据作为变异审查过程的一部分,我们将需要 bamout。因为这是分析员在体细胞调用中非常频繁做的事情(相对而言,比对生殖细胞调用更频繁),所以我们只需系统地设置Mutect2命令来生成 bamout。

估计跨样本污染

正如我们之前概述的,鉴定体细胞突变受到低级噪音来源的影响更大,而不是生殖细胞情况。即使很小量的样本间污染(比研究人员愿意承认的情况要多)也会让呼叫者困惑,并导致大量的 FP。事实上,我们没有直接的方法来区分数据中出现的等位基因是否因为生物组织中存在真正的突变,还是因为我们的样本被来自其他人的 DNA 污染,而这个人的生殖细胞中具有这种等位基因。

我们无法直接对这种污染进行校正,但我们可以估计它对任何给定样本的影响程度。从这里开始,我们可以标记任何等于或小于污染率的等位分数观察到的突变调用。这并不意味着我们把这些调用彻底排除为假,但这确实意味着在我们进行下一个分析阶段时,我们会对这些调用持更加怀疑的态度。

要估计我们肿瘤样本中的污染率,我们首先在正常样本中识别同种异变位点,以选择在肿瘤中查看的位点,然后根据这些位点支持参考等位基因的读取比例来评估我们在肿瘤中可能有多少污染。这个想法是因为这些位点应该是同种异变的,任何参考序列的读数都必须来自于其他人通过污染事件。

为了选择我们在计算中使用的站点,我们查看了在常见变异目录中找到的双等位基因位点(这样有很大可能性它们是相关的),但是其备用等位基因频率相当低(因此不会有太多来自备用等位基因的噪声)。然后,对于该子集中的每个位点,我们进行了基于堆积的快速基因分型运行,以识别在该样本中为同种合子变体的位点。我们在肿瘤样本和匹配正常样本上都这样做:

# gatk GetPileupSummaries \
    -I bams/normal.bam \
    -V resources/chr17_small_exac_common_3_grch38.vcf.gz \
    -L resources/chr17_small_exac_common_3_grch38.vcf.gz \
    -O sandbox/normal_getpileupsummaries.table

# gatk GetPileupSummaries \
    -I bams/tumor.bam \
    -V resources/chr17_small_exac_common_3_grch38.vcf.gz \
    -L resources/chr17_small_exac_common_3_grch38.vcf.gz \
    -O sandbox/tumor_getpileupsummaries.table

这两个命令应该运行非常快。如果看到关于 GetPileupSummaries 工具处于 beta 评估阶段且尚未准备好投入生产的警告,可以忽略它;这是开发过程中的遗留问题,将在不久的将来移除。

注意

GATK 开发人员在开始工作时往往非常谨慎,并将他们的工具标记为 alpha 或 beta 版本,以确保研究人员不会误将实验性工具误认为已完全经过审核的工具。然而,有时候当工具经过完全审核后,他们会忘记移除或更新这些标签,因此警告会比必要的时间更长地存在。如果你对特定工具的状态感到怀疑,请毫不犹豫地在 社区支持论坛 中向支持团队提问。

每次运行都会生成一张表,总结每个被选中站点的等位基因计数以及在种群资源中注释的等位基因频率:

# head -5 sandbox/normal_getpileupsummaries.table
#<METADATA>SAMPLE=HCC1143_normal
contig  position        ref_count       alt_count       other_alt_count allele_frequency
chr6    29942512        7       4       0       0.063
chr6    29942517        12      4       0       0.062
chr6    29942525        13      7       0       0.063

# head -5 sandbox/tumor_getpileupsummaries.table
#<METADATA>SAMPLE=HCC1143_tumor
contig  position        ref_count       alt_count       other_alt_count allele_frequency
chr6    29942512        9       0       0       0.063
chr6    29942517        13      1       0       0.062
chr6    29942525        13      7       0       0.063

现在我们可以通过向计算工具提供这两张表来进行实际的估计:

# gatk CalculateContamination \
    -I sandbox/tumor_getpileupsummaries.table \
    -matched sandbox/normal_getpileupsummaries.table \
    -tumor-segmentation sandbox/segments.table \
    -O sandbox/pair_calculatecontamination.table 

该命令生成一张最终表格,告诉我们肿瘤调用集中交叉样品污染的可能率。查看文件内容后,我们发现肿瘤的污染估计率为 1.15%,误差为 0.19%:

$ cat sandbox/pair_calculatecontamination.table
sample  contamination   error
HCC1143_tumor   0.011485364960150258    0.0019180421331441303

这意味着每百个读取中可能有一个来自他人。这个百分比将有效地成为我们检测低频率体细胞事件的底线;对于任何在该等位频率或以下调用的潜在变异,我们将无法判断它们是真实存在还是由污染引起的。即使较大的等位频率也可能完全由污染引起。我们将向过滤工具提供这个表格,以便它能正确考虑污染估计。

过滤 Mutect2 调用

类似于HaplotypeCallerMutect2被设计为非常敏感,以捕获尽可能多的真阳性。因此,我们知道原始调用集将充斥各种类型的假阳性。为了过滤这些调用,我们将使用一个称为FilterMutectCalls的工具,该工具基于一个量过滤变异:变异是体细胞突变的概率。在内部,该工具考虑了多个因素,包括在变异调用步骤中提供的种群频率,污染估计和先前计算的读取方向统计。在计算了每个变异调用的概率后,工具然后确定了优化F 分数的阈值,这是整个调用集敏感性和精度的调和平均数。因此,我们只需运行这个命令即可对我们的调用集应用默认过滤:

# gatk FilterMutectCalls \
    -R ref/Homo_sapiens_assembly38.fasta \
    -V sandbox/m2_somatic_calls.vcf.gz \
    --contamination-table sandbox/pair_calculatecontamination.table \
    -O sandbox/m2_somatic_calls.filtered.vcf.gz \
    --stats sandbox/m2_somatic_calls.vcf.gz.stats \
    --tumor-segmentation sandbox/segments.table

此操作的主要输出是包含所有变异的 VCF 输出文件,并根据需要应用过滤器注释:

15:25:14.742 INFO  ProgressMeter - Traversal complete. Processed 333 total
variants in 0.0 minutes.
注意

如果您想调整平衡点,您可以调整灵敏度与精度的相对权重在调和平均数中。将-f-score-beta参数设置为大于其默认值1的值会增加灵敏度,而将其设置为更低则有利于更大的精度。

好了,现在是时候在 IGV 中查看我们产生的调用了。您可能已经注意到,我们现在正在使用 hg38 参考构建,因此在尝试加载任何文件之前,我们需要调整 IGV 中的参考设置。

要加载以下文件:肿瘤和正常的原始 BAM 文件,由Mutect2生成的 bamout 文件以及经过过滤的调用集。根据刚才运行的命令识别文件,然后按照第四章到第六章中的相同步骤将文件复制到您的 Google Cloud Storage 存储桶中,并使用 File > URL 路径在 IGV 中加载它们。

在加载文件后,将 IGV 的视图坐标设置为**chr17:7,666,402-7,689,550**,如图 7-4 所示,这些坐标围绕 TP53 基因座中心。我们关注这个基因是因为它被称为肿瘤抑制因子;它被 DNA 损伤激活,并产生一种名为 p53 的蛋白质,可以修复损伤或触发杀死细胞。对这一基因的任何突变可能会干扰这个非常重要的功能,从而促使未受控制的肿瘤发展。

要详细查看这个调用,请放大输出 BAM 跟踪中的体细胞调用,使用坐标 **chr17:7,673,333-7,675,077**。悬停或点击变异跟踪中的灰色调用以查看注释,并尝试滚动数据以评估每个样本的覆盖量。在序列跟踪中,我们看到肿瘤中的 C 到 T 变异以红色突出显示,但正常样本中没有(图 7-4)。

在 IGV 中放大 TP53。

图 7-4. 在 IGV 中放大 TP53。

您如何看待这个体细胞变异调用?

使用 Funcotator 注释预测的功能影响

筛选体细胞突变调用可能比其生殖等价物更具挑战性。因此,用预测的功能影响和其重要性注释调用可能特别有帮助。例如,蛋白质编码区域中间的终止密码子可能比内含子中间的突变更为显著。了解这些关于您调用的信息可以帮助您确定哪些值得深入调查。

要评估功能影响,我们需要知道基因组中编码蛋白质序列的区域,以及与基因表达重要的元素对应的区域。例如,像 GENCODE 这样的转录注释资源在标准化的 Gene Transfer Format(GTF)中捕获了这些信息。GATK 团队提供了一组资源,其中包括编译的功能注释资源或数据源,适用于许多常见的功能注释需求,以及一个名为 Funcotator 的工具,可以使用这些数据源的信息为您的调用集进行注释。您也可以创建自己的定制数据源。此外,虽然 Funcotator 默认输出 VCF 格式的结果,但您也可以使其输出突变注释格式(MAF),这在癌症基因组学社区中被广泛使用。详细信息请参阅 Funcotator 文档

现在,让我们把 Funcotator 应用到我们筛选过的 Mutect2 调用集中:

# gatk Funcotator \
    --data-sources-path resources/funcotator_dataSources_GATK_Workshop_20181205/ \
    --ref-version hg38 \
    -R ref/Homo_sapiens_assembly38.fasta \
    -V sandbox/m2_somatic_calls.filtered.vcf.gz \
    -O sandbox/m2_somatic_calls.funcotated.vcf.gz \
    --output-file-format VCF

这个过程非常快速,生成的 VCF 文件已经根据 GENCODE 的功能数据进行了精心注释。这里是这些注释在其自然环境中的初步展示:

# zcat sandbox/m2_somatic_calls.funcotated.vcf.gz | grep -v '##' | head -3
#CHROM  POS     ID      REF     ALT     QUAL    FILTER  INFO    FORMAT  HCC1143_normal
HCC1143_tumor
chr17   1677390 .       A       T       .       weak_evidence
CONTQ=19;DP=23;ECNT=1;FUNCOTATION=[PRPF8|hg38|chr17|1677390|1677390|INTRON||SNP|A|A|T|
g.chr17:1677390A>T|ENST00000572621.5|-|||c.e13-
175T>A|||0.4463840399002494|CTGCCTCTCAAGGCCCCAGAA|PRPF8_ENST00000304992.10_INTRON];
GERMQ=32;MBQ=33,31;MFRL=301,311;MMQ=60,60;MPOS=22;NALOD=1.06;NLOD=3.01;POPAF=6.00;SEQQ
=1;STRANDQ=17;TLOD=4.22 GT:AD:AF:DP:F1R2:F2R1:SB       0/0:10,0:0.081:10:6,0:4,0:6,4,0,
0	0/1:9,2:0.229:11:5,1:3,1:6,3,2,0
chr17   2394409 .       G       T       .       PASS
CONTQ=93;DP=106;ECNT=1;FUNCOTATION=[MNT|hg38|chr17|2394409|2394409|INTRON||SNP|G|G|T|
g.chr17:2394409G>T|ENST00000575394.1|-|||c.e1-
6231C>A|||0.6209476309226932|TTCCTGACCAGCGCCGCCACC|MNT_ENST00000174618.4_INTRON];
GERMQ=93;MBQ=32,31;MFRL=148,177;MMQ=60,60;MPOS=14;NALOD=1.56;NLOD=10.52;POPAF=6.00;SEQQ
=93;STRANDQ=93;TLOD=30.05  GT:AD:AF:DP:F1R2:F2R1:SB       0/0:35,0:0.027:35:15,0:19,0:4,
31,0,0	0/1:53,13:0.206:66:23,5:29,8:13,40,4,9

现在让我们专门查看我们之前在 IGV 中查看的 TP53 突变的注释,位置在 chr17:7674220:

# zcat sandbox/m2_somatic_calls.funcotated.vcf.gz | grep 7674220
chr17   7674220 .       C       T       .       PASS
CONTQ=93;DP=134;ECNT=1;FUNCOTATION=[TP53|hg38|chr17|7674220|7674220|MISSENSE||SNP|C|C|T|
g.chr17:7674220C>T|ENST00000269305.8|-|7|933|c.743G>A|c.(742-
744)cGg>cAg|p.R248Q|0.5660847880299252|GATGGGCCTCCGGTTCATGCC|TP53_ENST00000445888.6_MISSENSE_
p.R248Q/TP53_ENST
00000420246.6_MISSENSE_p.R248Q/TP53_ENST00000622645.4_MISSENSE_p.R209Q/
TP53_ENST00000610292.4_MISSENSE_p.R209Q/TP53_ENST00000455263.6_MISSENSE_p.R248Q/
TP53_ENST00000610538.4_MISSENSE_p.R209Q/TP53_ENST00000620739.4_MISSENSE_p.R209Q/
TP53_ENST00000619485.4_MISSENSE_p.R209Q/TP53_ENST00000510385.5_MISSENSE_p.R116Q/
TP53_ENST00000618944.4_MISSENSE_p.R89Q/TP53_ENST000005
04290.5_MISSENSE_p.R116Q/TP53_ENST00000610623.4_MISSENSE_p.R89Q/
TP53_ENST00000504937.5_MISSENSE_p.R116Q/TP53_ENST00000619186.4_MISSENSE_p.R89Q/
TP53_ENST00000359597.8_MISSENSE_p.R248Q/TP53_ENST00000413465.6_MISSENSE_p.R248Q/
TP53_ENST00000615910.4_MISSENSE_p.R237Q/TP53_ENST00000617185.4_MISSENSE_p.R248Q];
GERMQ=93;MBQ=31,32;MFRL=146,140;MMQ=60,60;MPOS=21;NALOD=1.73;NLOD=15.33;POPA
F=6.00;SEQQ=93;STRANDQ=93;TLOD=264.54      GT:AD:AF:DP:F1R2:F2R1:SB
0/0:51,0:0.018:51:22,0:29,0:36,15,0,0   0/1:0,76:0.987:76:0,38:0,38:0,0,52,24

这个单核苷酸变异是一个 C-to-T 替换,预测会导致一个 错义突变,将精氨酸变为谷氨酰胺。错义突变会改变从基因产生的蛋白质,这可能在功能上是中性的,但也可能具有有害效应。如果我们看一下文件的其余部分,在我们的 124 个突变记录中,有 21 个被注释为 MISSENSE;其中有 10 个通过了所有的过滤器。这些信息应该有助于我们在分析的下一阶段确定要调查的内容。

至此,我们完成了肿瘤-正常对分析工作流程,用于发现体细胞 SNVs 和 indels!接下来,我们将详细讨论我们用于调查体细胞复制数变化的流程。

注意

如果您在继续下一节之前休息,请考虑停止您的虚拟机以避免在您离开享受生活时支付费用。

基因组中的体细胞复制数改变

到目前为止,我们一直在专注于小变异、单核苷酸变体(SNVs)和插入/缺失变异(indels),这些变异是基于相对于参考序列特定核苷酸的存在或缺失进行特征化的。转向复制数涉及到一个重大的心理(和方法论上的!)转变,因为现在我们将要观察样本中感兴趣的相对序列量,相对于其自身进行测量(参见 Figure 7-5 和 Figure 7-6)。换句话说,我们实际上不会测量生物材料中物理存在的复制数。我们测量的是复制比,这是一种代用品,因为它是复制数变化(CNAs)的结果。作为这一过程的一部分,我们将主要使用参考基因组作为一个坐标系统,用来定义显示 CNAs 的片段。

如 Figure 7-5 所示,复制数 是每个位点(即基因或其他有意义的 DNA 片段)的绝对整数值复制数。复制比是每个位点的复制数与平均倍性的相对实数比率。

复制数和复制比之间的区别。

图 7-5. 复制数和复制比之间的区别。

Figure 7-6 显示了正常细胞系和癌细胞系之间基因组组成的显著对比。在这张图中,正常细胞系(事实上,大多数健康的人类细胞)任何给定染色体的复制数为 2,其复制比为 1。与此同时,在癌细胞系中,染色体之间存在大量变异;例如,染色体 7 的复制数看起来是 5,而染色体 12 的复制数是 3,我们无法准确猜测它们的复制比,因为这些改变非常广泛,我们对该基因组的平均倍性没有一个良好的了解。

光谱核型染色技术(spectral karyotyping)用彩色标记每对染色体,显示被放大或缺失的各种染色体片段(左右面板中的颜色不必匹配)。

图 7-6. 光谱核型染色技术用彩色标记每对染色体,显示被放大或缺失的各种染色体片段(左右面板中的颜色不必匹配)。

好消息是,与短变异分析相比,拷贝数分析对我们之前列举的一些挑战不太敏感,因为我们对确切的序列不那么关心。然而,它对影响序列覆盖均匀性的技术偏倚更为敏感。这两点对分析设计的影响是,拥有匹配的正常样本可用性不那么重要,而拥有一个由具有紧密匹配技术配置文件的正常样本组成的 PoN 更为重要。然而,请注意,拷贝数 PoN 与用于短变异分析的 PoN 完全不同。我们将在下一节详细介绍具体内容。

肿瘤单样本分析工作流程概述

肿瘤体细胞拷贝数分析的标准工作流程涉及四个主要步骤(图 7-7)。首先,我们收集用于 PoN 的案例样本和任何正常样本的比例覆盖统计数据。暂时将案例样本置于一旁,我们创建一个拷贝数 PoN,捕捉到整个外显子或基因组中观察到的覆盖率系统偏移的量。然后,我们利用这些信息来对我们收集的案例样本的覆盖数据进行归一化,从而消除技术噪声并揭示数据中实际的拷贝比差异。最后,我们运行一个分割算法,识别序列的连续片段,这些片段呈现相同的拷贝比,并为那些拷贝比显著偏离均值的片段发出 CNA 调用。可选地,我们可以生成图形来在几个阶段可视化数据,这有助于解释。

肿瘤体细胞拷贝数变异发现的最佳实践工作流程。

图 7-7. 肿瘤体细胞拷贝数变异发现的最佳实践工作流程。

再次强调,你将在你的虚拟机中的 GATK 容器内完成这些工作,所以确保所有设备都打开,并且你在 /home/book/data/somatic 目录下。本节中的练习使用与第一节相同的参考基因组和一些相同的资源文件,我们将使用相同的沙盒来写输出结果。

收集覆盖计数

我们计算一系列间隔的读深度或覆盖度。在这里,我们使用外显子捕获靶向区域,因为我们正在分析外显子,但如果我们分析整个基因组,我们将切换到使用任意长度的区间。对于外显子,我们使用与捕获靶向区域对应的间隔列表,并首先添加一些填充物。

# gatk PreprocessIntervals \
    -R ref/Homo_sapiens_assembly38.fasta \
    -L resources/targets_chr17.interval_list \
    -O sandbox/targets_chr17.preprocessed.interval_list \
    --padding 250 \
    --bin-length 0 \
    --interval-merging-rule OVERLAPPING_ONLY 

在此命令中,--interval-merging-rule 参数控制 GATK 引擎是否应合并相邻的间隔以进行处理。在某些情况下,合并它们是有意义的,但在这种情况下,我们更喜欢将它们保持分开,以获得更细粒度的分辨率,并且我们仅合并实际重叠的间隔。

注意

如果我们想为整个基因组数据生成一个区间列表,我们将运行相同的工具,但不使用目标文件,并将--bin-length参数设置为我们想要使用的 bin 的长度(默认设置为 100 个碱基)。

当我们有了分析就绪的区间列表时,我们可以运行读取计数收集工具,该工具通过计算每个区间内的起始读取数来运行。默认情况下,该工具以HDF5 格式写入数据,后续工具更高效地处理,但这里我们将输出格式更改为制表符分隔值(TSV),因为这样更容易查看:

# gatk CollectReadCounts \
    -I bams/tumor.bam \
    -L sandbox/targets_chr17.preprocessed.interval_list \
    -R ref/Homo_sapiens_assembly38.fasta \
    -O sandbox/tumor.counts.tsv \
    --format TSV \
    -imr OVERLAPPING_ONLY

输出文件包括一个头部,重复列出了序列字典,然后是每个目标的计数表:

# head -5 sandbox/tumor.counts.tsv
@HD     VN:1.6
@SQ     SN:chr1 LN:248956422
@SQ     SN:chr2 LN:242193529
@SQ     SN:chr3 LN:198295559
@SQ     SN:chr4 LN:190214555

令人讨厌的是,hg38 参考构建的序列字典非常长,因为存在所有的ALT contigs(在第二章的基因组入门中解释)。因此,要查看我们的文件,最好使用tail而不是head

# tail -5 sandbox/tumor.counts.tsv
chr17   83051485        83052048        1
chr17   83079564        83080237        0
chr17   83084686        83085575        1010
chr17   83092915        83093478        118
chr17   83094004        83094827        484

啊,这样更有帮助,不是吗?第一列是染色体或 contig,第二列和第三列是我们查看的目标区域的起始和结束,第四列是重叠目标的读取计数。图 7-8 提供了这种可视化的示例。

每个基因组目标或区段中的读取计数形成估算分段复制比率的基础。

图 7-8. 每个基因组目标或区段中的读取计数形成估算分段复制比率的基础,每个点表示单个目标或区段的值。

图中的每个点代表我们收集覆盖度的区间之一,按其在基因组上的位置排列。背景中的灰白条纹代表交替的染色体,虚线表示它们的着丝点位置。请注意,由于从一个到另一个的覆盖量存在巨大变异,点形成波浪状的模糊。此时,无法确信地识别任何 CNAs。

创建一个体细胞 CNA PoN

正如我们在第二章中讨论的那样,许多技术变异影响了测序过程,其中一些是相当系统化的——某些区域将会产生比其他区域更多的序列数据,主要是因为化学因素。我们可以通过使用一种称为奇异值分解的算法来建立正常变异看起来像什么的基线,这在概念上类似于主成分分析,并旨在从以相同方式测序和处理的正常面板中捕获系统噪声。CNA PoN 中的最小样本数应至少为 10 个正常样本,以确保工具正常运行,但最佳实践建议尽可能使用 40 个或更多的正常样本。

创建 PoN 是一项艰巨的工作,因此我们为您提供了一个,该 PoN 是从 1000 基因组计划的 40 个样本中制作的。这 40 个样本中的每一个都是通过像之前描述的那样单独运行CollectReadCounts工具,并且将默认输出格式设置为 HDF5 来生成的(因此在命令行中显示了.hdf5扩展名,在下一个代码段中显示)。然后通过对所有读数文件运行以下命令来创建面板。明确地说,我们只是以示例的形式展示这一点,因此不要尝试运行它;它不适用于我们为本书提供的数据。

gatk CreateReadCountPanelOfNormals \
    -I file1_clean.counts.hdf5 \
    … 
    -I file40_clean.counts.hdf5 \
    -O cnaponC.pon.hdf5

此命令生成的 PoN 与我们之前在本章节中为短变异部分创建的 PoN 完全不同。对于短变异流水线,PoN 只是带有注释的变异调用 VCF 文件的一种类型。然而,在这种情况下,通过聚合 40 个正常样本的读数数据生成的文件对于肉眼来说没有意义。

应用去噪

有了我们手头的样本读数和准备好的 PoN,我们可以运行此工作流程中最重要的一步:去噪病例样本读数。

# gatk DenoiseReadCounts \
    -I cna_inputs/hcc1143_T_clean.counts.hdf5 \
    --count-panel-of-normals cna_inputs/cnaponC.pon.hdf5 \
    --standardized-copy-ratios sandbox/hcc1143_T_clean.standardizedCR.tsv \
    --denoised-copy-ratios sandbox/hcc1143_T_clean.denoisedCR.tsv

尽管我们只运行了一个命令,但工具在内部执行了两次连续的数据转换。首先,它通过对 PoN 中记录的中位数计数进行标准化,这涉及到一个以 2 为底的对数转换,并将分布归一化到中心值为一,从而生成拷贝比率。然后,它利用 PoN 的主成分对这些标准化的拷贝比率应用去噪算法。

注意

您可以通过调整--number-of-eigensamples参数来调节去噪工具的侵略程度,这会影响结果的分辨率;也就是说,生成的片段平滑度如何。使用较大的数字将产生更高程度的去噪,但可能会降低分析的灵敏度。

再次说明,输出文件实际上不太适合人类阅读,因此让我们绘制它们的内容,以了解此时数据显示的内容:

# gatk PlotDenoisedCopyRatios \
    --sequence-dictionary ref/Homo_sapiens_assembly38.dict \
    --standardized-copy-ratios sandbox/hcc1143_T_clean.standardizedCR.tsv \
    --denoised-copy-ratios sandbox/hcc1143_T_clean.denoisedCR.tsv \
    --minimum-contig-length 46709983 \
    --output sandbox/cna_plots \
    --output-prefix hcc1143_T_clean

最终的图包括第一个内部步骤产生的标准化拷贝比率和最终的去噪拷贝比率(图 7-9)。

您可以使用gsutil将输出的图表从您的 VM 复制到 Google 存储桶,然后从 GCP 控制台查看(或下载)它们。如果您需要关于此的复习,请参阅第四章。以下操作是从您的 VM 执行的(该 VM 已安装并配置了gsutil工具,并且在第四章中运行了gatk命令而不是您运行gatk命令的 Docker 容器),并将上传到您在此示例中指定的存储桶(my-bucket):

$ export BUCKET="gs://my-bucket"
$ gsutil -m cp -R sandbox/cna_plots $BUCKET/somatic-sandbox/

拷贝数变异分析图表,显示第一步去噪后的标准化拷贝比(顶部)和第二轮完全去噪后的完全去噪拷贝比(底部)。

图 7-9。在第一轮去噪之后显示标准化拷贝比的拷贝数变异分析图(顶部)和在第二轮完全去噪之后显示的完全去噪拷贝比(底部)。

在这些图像的书籍版本中,可能很难清楚地看到,但如果您放大您自己生成的图表,您应该能够看到第二轮图像中的数值范围比第一轮更紧凑,这显示了从一步到下一步的改进。无论如何,我们可以认为这些与在图 7-8 中显示的原始读数相比,分辨率更高。我们已经能够辨认出一些可能的拷贝数变异。

执行分割并调用 CNAs

现在,我们已经拥有了精心去噪的拷贝比,我们唯一需要做的就是确定哪些区域确实显示了 CNAs 的证据。为此,我们将分组具有相似拷贝比的相邻区间成为片段。然后,我们应该能够确定哪些片段的整体拷贝比支持存在与其余部分的扩增或删除。

我们使用ModelSegments工具完成此操作,该工具使用高斯核二元分割算法执行多维核分割。

# gatk ModelSegments \
    --denoised-copy-ratios sandbox/hcc1143_T_clean.denoisedCR.tsv \
    --output sandbox  \
    --output-prefix hcc1143_T_clean

就前面的图表而言,查看它们的最简单方法是将它们转移到您的 Google 存储桶中,并从 GCP 控制台打开(或下载)它们。此命令生成多个文件,因此我们提供的输出名称仅是我们要给将包含这些文件的新目录的名称。再次强调,输出结果并不特别用户友好,因此我们将制作一些图表来可视化结果。我们向绘图工具提供了经去噪的拷贝比(来自DenoiseReadCounts)、片段(来自ModelSegments)和参考序列字典:

# gatk PlotModeledSegments \
    --denoised-copy-ratios sandbox/hcc1143_T_clean.denoisedCR.tsv \
    --segments sandbox/hcc1143_T_clean.modelFinal.seg \
    --sequence-dictionary ref/Homo_sapiens_assembly38.dict \
    --minimum-contig-length 46709983 \
    --output sandbox/cna_plots \
    --output-prefix hcc1143_T_clean

图 7-10 显示由PlotModelSegments工具识别的片段。代表交替片段上的靶或箱柱的点着色为蓝色和橙色,而片段中位数则用黑色绘制。大多数片段的拷贝比约为 1,构成基线。在此背景下,我们可以观察到多个不同大小和不同拷贝比的扩增和缺失。您可能会注意到,大多数这些不符合您对于给定片段有四个拷贝而不是两个的预期数字。这是因为某些拷贝数变异发生在亚克隆群体中,这意味着只有部分取样组织受到影响。因此,效果被稀释,您可能会看到拷贝比为 1.8 而不是 2。

基于去噪拷贝比建模的片段图表。

图 7-10. 基于去噪复制比例建模的分段图。

更一般地说,这个样本中似乎发生了很多事情;事实上,我们可以查看输出目录中的分割文件,看到我们为这个样本预测了 235 个分段。这在常规肿瘤样本中并不典型,尽管有些可能相当混乱;这里发生的情况是,我们正在查看从细胞系中取样的样本。癌细胞系对于教学和测试软件很方便,因为数据可以很容易地公开获取,但从生物学上讲,它们往往积累了非常高的变异数量。因此,这代表了一个极端情况。值得一提的是,您在现实世界中遇到的大多数样本可能会比这个更干净(更容易解释)。所以您有一些期待的事情!

话虽如此,即使在这样一个混乱的样本中,软件仍然可以帮助我们更清晰地了解情况。我们可以通过运行工作流程中的最后一个工具来获得最终的 CNA 调用,即确定哪些分段我们可以认为具有扩增或缺失的显著证据,具体操作如下:

# gatk CallCopyRatioSegments \
    -I sandbox/hcc1143_T_clean.cr.seg \
    -O sandbox/hcc1143_T_clean.called.seg

这个工具向由ModelSegments生成的分段复制比例.cr.seg文件添加了一列,标记了扩增(+)、缺失(-)和中性分段(0):

# tail -5 sandbox/hcc1143_T_clean.called.seg
chrX    118974529       139746109       864     0.475183        +
chrX    139749773       139965748       28      -0.925385       -
chrX    140503468       153058699       277     -0.366860       -
chrX    153182138       153580550       47      0.658197        +
chrX    153588113       156010661       544     0.075279        0

就是这样;这展示了体细胞拷贝数工作流的输出。如果我们看完整的进展,您会看到我们从原始覆盖计数一直到识别出特定的缺失或扩增区域,如图 7-11 所示(使用不同的数据和仅一部分染色体)。

从原始数据到结果的完整进展。

图 7-11. 从原始数据到结果的完整进展。

在图 7-11 中计算和处理的复制比例计数如前面的图所示,向右旋转 90°。最左边的面板显示了每个目标或基因组区段计算的原始复制比例计数。第二和第三个面板分别显示了标准化和去噪后的计数。第四个面板显示了分割和调用结果,包括一个整体染色体扩增(AMP)和一个缺失(DEL)。最右边的面板显示了使用下一节中概述的程序计算的次等位基因分数。

具有讽刺意味的是,这些结果并不包括实际的拷贝数,只包括每个分段的边界、其复制比例和最终调用。要获得拷贝数,您需要进行一些超出本章范围的额外工作。有关更多信息和下一步,请参阅文档

附加分析选项

我们刚刚介绍了使用 GATK 工具执行拷贝数分析的标准工作流程。这包括收集覆盖计数,使用 PoN 进行去噪,执行分割以对肿瘤案例样本建模并最终调用拷贝数改变片段。然而,你可以应用两个额外选项以进一步提高结果的质量。

肿瘤-正常对分析

Tumor-only workflow 可以为体细胞 CNAs 的发现提供不错且可用的结果。但是,如果你有配对的正常样本,你可以获得额外的好处!首先,你可以按照之前描述的流程在正常样本上运行工作流程,这有助于理解你正在处理的基线。在许多情况下,有配对的正常样本还可以帮助去噪过程。

欢迎自行运行这些命令。在最终的分割结果中,请注意染色体 2 和染色体 6 上可能的 CNAs。这个配对的正常样本本身来自细胞系,尽管是从健康组织而不是肿瘤中衍生出来的。即使在正常组织被作为细胞系传播时,也不罕见看到一些背景改变发生。

注意

这结束了本章的练习,所以再次,记得在进入下一章之前暂停你的 VM。

等位复制比分析

到目前为止,我们仅使用读深度作为 CNAs 的证据,但我们也可以包括等位复制比:在具有杂合 SNV 的位点上,不同等位基因的复制比。这可以揭示我们案例样本的杂合性的有趣信息,这可能对生物学有重要影响,尤其是在存在等位基因丧失时。

通过观察案例样本中常见变异群体中的等位计数(即每个位点不同等位基因的读数),我们可以检测段的杂合性失衡。对于二倍体生物,我们预期任何杂合 SNV 表现为两种不同等位基因以相等比例存在。假设我们有 A/T 变异体;我们预期在序列读数中看到大约 50%的 A 和 50%的 T(加上一些技术变异)。在工作流程的第一步中计数读数时,我们可以实时检测这些位点。然后,我们可以将这些位点用作标记;如果我们在肿瘤中观察到这些位点并发现它们的等位比例显著偏斜,这就为我们提供了关于影响这些位点所在片段的 CNA 程度的额外信息。

如果你对探索这种额外的分析模式感兴趣,请查看 GATK 网站上的somatic copy-number documentation

总结和下一步

那么,在配对的正常工作流程和这两种体细胞分析工作流程之间,我们已经运行了大量的基因组学命令。那么,这是否意味着我们准备好进行全面的基因组分析了?

Hah, no. 现在你可以逐个运行各个工具——这在工作的不同阶段特别有用,尤其是在初始测试/评估以及故障排除时,但你也知道变异发现的最佳实践包括多个任务,涉及各种工具。相信我们,你不想为需要处理的每个样本或样本集手动运行每个任务。你会希望将这些任务串联成脚本,自动化大部分工作,以减少单调的枯燥工作以及降低人为错误的几率。所以在第八章,我们向你展示如何使用 WDL 编写工作流脚本,该脚本自动调用整个分析管道中的每个步骤。然后,我们使用一个名为 Cromwell 的工作流管理系统来运行它们,这个系统同样适用于你的笔记本电脑、集群或云平台。

第四章:云中的第一步

在前两章中,我们带您了解了基因组学和计算技术的基础知识。无论您是更多地从其中一方面接触到这个领域,还是可能来自其他领域,我们的目标是确保您在两个领域都有足够的基础知识。如果是这样,欢迎!请继续保持。

我们意识到,前两章可能感觉非常被动,因为没有涉及实际操作的练习。所以好消息来了:你终于可以开始动手了。本章的重点是让你熟悉并适应本书中使用的 GCP 服务。首先,我们将带你创建一个 GCP 账号,并在 Google Cloud Shell 中运行简单命令。接下来,我们将向你展示如何在云中设置自己的虚拟机,运行 Docker,并设置你将在第五章中用于运行 GATK 分析的环境。最后,我们将向你展示如何配置 IGV 以访问 Google Cloud 存储中的数据。完成这些设置后,你将准备好进行实际的基因组学工作。

设置您的 Google Cloud 账号和第一个项目

您可以通过访问https://cloud.google.com并按照提示操作来注册 GCP 账号。我们在这里故意保持详细内容较少,因为账号设置界面可能会发生变化。但从高层次来看,您的目标是建立一个新的 Google Cloud 账号,设置一个计费账号,接受免费试用信用(如果您符合条件),并创建一个新项目,该项目与您的计费账号关联。

如果您还没有任何形式的 Google 身份,您可以使用常规电子邮件账号创建一个,不必使用 Gmail 账号。请注意,如果您的机构使用 G Suite,即使域名不是gmail.com,您的工作电子邮件也可能已与 Google 身份关联。

在注册后,请前往GCP 控制台,该控制台提供了一个基于 Web 的图形界面,用于管理云资源。您可以通过纯命令行界面访问控制台提供的大部分功能。在本书中,我们会展示如何通过 Web 界面和命令行执行一些操作,具体取决于我们认为哪种方式更方便或更典型。

创建一个项目

让我们从创建你的第一个项目开始,这对于组织你的工作、设置计费并访问 GCP 服务是必要的。在控制台中,转到“管理资源”页面,然后在页面顶部选择创建项目。如图 4-1 所示,你需要给你的项目取一个名字,在整个 GCP 中必须是唯一的。如果你的 Google 身份关联了一个组织(如果你有一个机构/工作 G Suite 账户,通常是这种情况),你也可以选择一个组织,但如果你刚刚创建了你的账户,这可能暂时不适用于你。选择了一个组织意味着新项目将默认与该组织关联,这允许对项目进行集中管理。在这些说明中,我们假设你是第一次设置你的账户,目前没有与之关联的现有组织。

创建一个新项目。

图 4-1. 创建一个新项目。

检查您的计费账户并激活免费信用

如果您按照前一节中概述的注册流程并激活了您的免费试用,系统将在整体账户创建过程中为您设置计费信息。您可以在控制台的计费部分中查看您的计费信息,您也可以随时从侧边栏菜单中访问。

如果您有资格参加免费信用计划,计费概览页面上的一个面板将总结您剩余的信用额度和天数。请注意,如果您的页面显示一个蓝色的升级按钮,如图 4-2 所示,您的试用尚未开始,您需要激活它以利用该计划。您还可能在浏览器窗口顶部看到一个“免费试用状态”横幅,带有一个蓝色的激活按钮。GCP 的某个人正在努力不让您放弃免费的钱,所以点击其中任何一个按钮开始流程并获得您的免费信用。

在计费控制台中总结免费试用信用额度的面板。

图 4-2. 在计费控制台中总结免费试用信用额度的面板。

更一般地说,计费概览页面提供了迄今为止花费了多少钱(或信用额度)以及一些基本的预测摘要。话虽如此,重要的是要了解系统不会实时显示您的成本:在您使用可计费资源和成本更新到您的计费页面之间存在一些滞后时间。

许多转向云端的人报告说,跟踪他们的支出是整个过程中最困难的部分之一。这也是最令他们焦虑的部分,因为如果不小心,很容易在云中迅速花费大量资金。GCP 提供的一个特别有用的功能是“预算与警报”设置,如图 4-3 所示。这使您可以设置电子邮件警报,通知您(或者是您帐户上的计费管理员)当您超过某些支出阈值时。清楚地说,这不会停止任何正在运行的东西,也不会阻止您启动可能使您超出阈值的任何新工作,但至少它会让您知道您的情况。

预算和警报阈值管理

图 4-3. 预算和警报阈值管理。

要访问计费通知功能,请在 GCP 控制台的主菜单上选择“计费”,选择您刚创建的计费账户,然后查找“预算和警报”选项。选择后,您将能够使用图 4-3 中显示的“创建预算”表单来设置新的预算。如果您希望在接近预算金额时得到警告,可以为不同百分比的预算设置多个触发器。但正如我们刚才提到的,要记住它仍然只是一个通知服务,并不会阻止您产生额外的费用。

在 Google Cloud Shell 中运行基本命令

现在您已经建立了您的帐户,设置了计费,并创建了您的项目,下一步是登录到您的第一个虚拟机。在这里的练习中,我们使用 Google Cloud Shell,它不需要任何配置即可开始使用,并且完全免费,尽管它带有一些重要的限制,我们稍后会讨论。

登录到 Cloud Shell 虚拟机

要使用 SSH 协议创建到 Cloud Shell VM 的安全连接,请点击控制台右上角的终端图标:

打开 Cloud Shell 的终端图标。

这会在控制台底部启动一个新面板;如果需要,您也可以将终端弹出到独立窗口。这样,您就可以访问装有适度资源的基于 Debian 的 Linux VM,包括挂载在$HOME上的 5 GB 免费存储(挂载在持久性磁盘上)。一些基本软件包已预安装并准备就绪,包括 Google Cloud SDK(也称为gcloud),它提供了丰富的基于命令行的工具集,用于与 GCP 服务进行交互。我们将在几分钟内使用它来尝试一些基本的数据管理命令。与此同时,可以随意探索这个 Debian VM,四处看看,了解安装了哪些工具。

注意

请注意,每周使用配额限制您在 Cloud Shell 上运行的时间;截至目前为止,每周为 50 小时。此外,如果您不定期使用(截至目前为止为 120 天),提供免费存储空间的磁盘内容可能会被删除。

第一次登录 Cloud Shell 时,它会提示您使用上述gcloud实用程序指定项目 ID:

Welcome to Cloud Shell! Type "help" to get started.
To set your Cloud Platform project in this session use “gcloud config set project
[PROJECT_ID]”

您可以在控制台的主页上找到您的项目 ID,如图 4-4 所示。

GCP 控制台中项目 ID 的位置。

图 4-4. GCP 控制台中项目 ID 的位置。

当您拥有项目 ID 时,在 Cloud Shell 中运行以下命令,将此处显示的项目 ID 替换为您自己的项目 ID:

genomics_book@cloudshell:~$ gcloud config set project ferrous-layout-260200
Updated property [core/project].
genomics_book@cloudshell:~ (ferrous-layout-260200)$

请注意,您的命令提示现在包含您的项目 ID。它非常长,因此在以后,我们将仅显示提示中的最后一个字符—在本例中为美元符号($)—当我们演示运行命令时。例如,如果我们使用ls命令列出工作目录的内容,它将如下所示:

$ ls
README-cloudshell.txt

嘿,这里已经有东西了:一个README文件,正如名称所示,真的希望您阅读它。您可以通过运行cat命令来阅读它:

$ cat README-cloudshell.txt

这将显示一个欢迎消息,总结了一些使用说明和获取帮助的建议。有了这个,您已经准备好使用 Cloud Shell 开始与基本的 GCP 服务进行交互了。让我们开始吧!

使用 gsutil 访问和管理文件

现在我们可以访问这个非常简单启动且免费(虽然相当有限)的虚拟机,让我们使用它来查看是否可以访问本书提供的示例数据包。数据包存储在 Google Cloud Storage(GCS)中,这是一种对象存储(即用于存储文件)的形式,其中存储单元称为存储桶。您可以通过 GCP 控制台的存储浏览器部分通过网络查看 GCS 存储桶的内容并执行基本管理任务,但界面相当有限。更强大的方法是使用gcloud工具,gsutil(Google Storage Utilities)从命令行访问。您可以通过其 GCS 路径访问存储桶,该路径只是其名称前缀为gs://

例如,本书的公共存储桶路径为gs://genomics-in-the-cloud。您可以在云 shell 中输入以下命令来列出存储桶的内容:

$ gsutil ls gs://genomics-in-the-cloud
gs://genomics-in-the-cloud/hello.txt
gs://genomics-in-the-cloud/v1/

应该有一个名为hello.txt的文件。让我们使用 Unix 命令catgsutil版本,它允许我们读取文本文件的内容,以查看这个hello.txt文件包含什么:

$ gsutil cat gs://genomics-in-the-cloud/hello.txt
HELLO, DEAR READER!

您还可以尝试将文件复制到您的存储磁盘:

$ gsutil cp gs://genomics-in-the-cloud/hello.txt .
Copying gs://genomics-in-the-cloud/hello.txt...
/ [1 files][   20.0 B/   20.0 B]
Operation completed over 1 objects/20.0 B.

如果您再次使用ls列出工作目录的内容,您现在应该有一个hello.txt文件的本地副本:

$ ls
hello.txt README-cloudshell.txt

在我们使用gsutil的同时,如何做一些以后有用的事情呢:创建您自己的存储桶,以便您可以在 GCS 中存储输出。您需要替换这里显示的命令中的my-bucket,因为存储桶名称必须在整个 GCS 中是唯一的:

$ gsutil mb gs://my-bucket

如果您没有更改存储桶名称,或者尝试的名称已经被其他人使用,可能会收到以下错误消息:

Creating gs://my-bucket/...
ServiceException: 409 Bucket my-bucket already exists.

如果是这种情况,请尝试另一个更有可能唯一的名称。当您在输出中看到Creating *name*...并且在gsutil未有更多投诉后,您将知道它已成功。完成后,您将创建一个环境变量,该变量将作为存储桶名称的别名。这样,您可以节省一些输入,而且在后续命令中可以复制粘贴,而无需每次替换存储桶名称:

$ export BUCKET="gs://my-bucket"

您可以对新变量运行echo命令,以验证存储桶名称已正确存储:

$ echo $BUCKET
gs://my-bucket

现在,让我们帮助您熟悉使用gsutil。首先,将hello.txt文件复制到您的新存储桶中。您可以直接从原始存储桶中进行操作:

$ gsutil cp gs://genomics-in-the-cloud/hello.txt $BUCKET/

或者,您可以从本地副本中进行操作;例如,如果您进行了要保存的修改:

$ gsutil cp hello.txt $BUCKET/

最后,作为基本文件管理的另一个示例,您可以决定文件应该位于存储桶中自己的目录中:

$ gsutil cp $BUCKET/hello.txt $BUCKET/my-directory/

正如您所看到的,gsutil命令设置得尽可能与其原始 Unix 对应命令相似。因此,例如,您还可以使用-r来使cpmv命令递归地应用于目录。对于大文件传输,您可以使用一些云规范优化来加快过程,比如gsutil -m选项,该选项并行传输文件。方便的是,系统通常会在终端输出中告知您何时可以利用这些优化,因此您无需在开始之前去记忆文档。

GCP 控制台存储浏览器。

图 4-5. GCP 控制台存储浏览器。

这将打开一个相当简单的配置表单。在这里做的最重要的事情是选择一个好的名称,因为您选择的名称必须在整个 Google Cloud 中是唯一的—所以要有创意!如果您选择了一个已经在使用的名称,系统将在您单击配置表单中的“继续”时告知您,如在图 4-6 中演示的那样。

给您的存储桶命名。

图 4-6. 给您的存储桶命名。

当您有一个唯一的名称时,系统会扩展菜单选项,让您可以继续进行下一步。这些选项允许您自定义存储位置和桶的访问控制,但目前您可以接受默认设置并单击“创建”。这将带您返回到桶列表,此时列表应包含您新创建的桶。您可以单击其名称查看其内容,但当然它仍然是空的,所以视图不会特别令人兴奋,正如 图 4-7 所示。

该界面提供了一些基本的管理选项,如删除桶和文件,以及上传文件和文件夹。请注意,您甚至可以将本地计算机中的文件和文件夹拖放到桶内容窗口中,这非常容易(试试看),但在基因组学工作中,您不太可能经常这样做。在实际环境中,您更有可能使用 gsutil 命令行实用程序。使用命令行路径的一个优势是可以将这些命令保存为脚本,用于溯源和在需要时重现您的步骤。

查看桶内容。

图 4-7. 查看桶内容。

拉取 Docker 镜像并启动容器

云 Shell 是一份不断奉送的礼物:第三章 中介绍的 Docker 应用程序已预装,因此您可以立即开始使用!我们将使用一个简单的 Ubuntu 容器来演示基本的 Docker 功能。尽管 GATK 提供了一个 Docker 镜像,并且在接下来的几章中我们会大量使用它,但我们在这里不会使用它,因为它相当庞大,启动需要一些时间。由于免费的 Cloud Shell 分配了较少的 CPU 和内存资源,我们实际上无法在其中运行任何现实分析任务。

注意

在这种情况下学习如何使用 Docker 容器的第一件事是… 避开在线 Docker 文档!真的。不是因为它不好,而是因为这些文档主要是为想在云中运行 Web 应用程序的人编写的。如果这是您想要做的事情,那么恭喜您,但您正在读错书。我们在这里提供的是量身定制的指导,将教您如何使用 Docker 在容器中运行研究软件。

正如刚才所提到的,我们将使用一个非常通用的例子:一个包含 Ubuntu Linux 操作系统的镜像。这是一个官方镜像,作为公共容器镜像库 Docker Hub 的核心库的一部分提供,所以我们只需要声明它的名称。稍后您会看到,由社区贡献的镜像是以贡献者的用户名或组织名称作为前缀的。在您的 Cloud Shell 终端中(无论您的工作目录在哪里),运行以下命令从 Docker Hub 官方(认证)镜像库中检索 Ubuntu 镜像:

$ docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
7413c47ba209: Pull complete
0fe7e7cbb2e8: Pull complete
1d425c982345: Pull complete
344da5c95cec: Pull complete
Digest: sha256:d91842ef309155b85a9e5c59566719308fab816b40d376809c39cf1cf4de3c6a
Status: Downloaded newer image for ubuntu:latest
docker.io/library/ubuntu:latest

pull命令获取镜像并保存到您的虚拟机。容器镜像的版本由其tag(可以是镜像创建者想要分配的任何内容)和其sha256哈希值(基于镜像内容)表示。默认情况下,系统会给出可用的最新版本,因为我们没有指定特定的标签;在后续练习中,您将看到如何通过标签请求特定版本。请注意,容器镜像通常由几个模块化的slices组成,这些模块化的部分会分别被拉取。它们被组织得这样,下次您拉取镜像的版本时,系统将跳过任何与您已有版本相比未更改的部分。

现在让我们启动容器。有三种主要选项可以运行它,但棘手的是通常只有一种正确的方式正如其作者所意图的,如果文档没有指定(这种情况太常见了),很难知道那是什么。感到困惑了吗?让我们通过具体案例来澄清一下,并且你会明白为什么我们要让你经历这一瞬间的挫败和神秘感——这是为了在今后避免潜在的痛苦。

第一个选项

只需运行它!

$ docker run ubuntu

结果

稍作停顿,然后您的命令提示符又回来了。没有输出。发生了什么?实际上 Docker 启动了容器,但在这些条件下,容器没有配置执行任何操作,所以基本上是耸了耸肩然后再次关闭了。

第二个选项

附加命令运行:

$ docker run ubuntu echo "Hello World!"
Hello World!

结果

它回显了Hello World!,正如请求的那样,然后再次关闭了。好的,现在我们知道可以向容器传递命令,如果它是在其中某个地方被识别的命令,它将被执行。然后,当所有命令都已完成时,容器将关闭。有点懒,但还算合理。

第三个选项

使用-it选项进行交互式运行:

$ docker run -it ubuntu /bin/bash
root@d84c079d0623:/#

结果

啊哈!一个新的命令提示符(在本例中是 Bash)!但是带有不同的 shell 符号:#而不是$。这意味着容器正在运行,而且你正在其中。现在,您可以运行任何在 Ubuntu 系统上通常使用的命令,包括安装新的软件包。尝试运行一些 Unix 命令,如lsls -la来探索并查看容器的功能。本书的后面部分,特别是第十二章,我们将深入探讨其中的一些含义,包括如何定制并重新分发您的图像,以便以可重现的方式分享您自己的分析。

当您完成探索时,在命令提示符处输入**exit**(或按 Ctrl+D)来终止 shell。因为这是容器运行的主要进程,终止它将导致容器关闭并返回到 Cloud Shell 本身。清楚地说,这将关闭容器以及当前正在运行的任何命令

如果你感兴趣:是的,可以在不关闭容器的情况下离开容器;这称为分离。要这样做,按 Ctrl+P+Q,而不是使用exit命令。这样,你随时可以跳回容器中——只要你能识别它。默认情况下,Docker 为您的容器分配一个全局唯一标识符(UUID)以及一个随机的易读名称(通常听起来有点傻)。您可以运行docker ps列出当前运行的容器,或者docker ps -a列出已创建的容器。这将显示一个按其容器 ID 索引的容器列表,应该看起来像这样:

$ docker ps -a
CONTAINER ID	IMAGE	COMMAND                 CREATED     STATUS			
PORTS		NAMES
c2b4f8a0c7a6	ubuntu	"/bin/bash"             5 minutes ago	 Up 5 minutes	
vigorous_rosalind
9336068da866	ubuntu	"echo ’Hello World!’"	8 minutes ago	Exited (0) 8 minutes ago
objective_curie

我们展示了最后两次 Docker 调用对应的两个条目,每个都有一个唯一标识符,即CONTAINER ID。我们看到 ID 为c2b4f8a0c7a6的当前正在运行的容器被命名为vigorous_rosalind,状态为Up 5 minutes。您可以看出另一个容器objective_curie不在运行,因为其状态为Exited (0) 8 minutes ago。我们在这里看到的名称是随机分配的(我们发誓!这有多大概率?),所以它们显然不是特别有意义的。如果同时运行多个容器,这可能会变得有点混乱,因此您需要一种更好的方法来识别它们。好消息是,您可以在初始命令的docker run之后立即添加--name=*meaningful_name*来为容器指定有意义的名称,将*meaningful_name*替换为您想要给容器的名称。

要进入容器,只需运行docker attach *c2b4f8a0c7a6*(替换为你的容器 ID),然后按 Enter,你将回到控制台(你的键盘可能标有 Return 而不是 Enter)。如果你想要能够在容器内部工作的同时在 Cloud Shell 中运行命令,可以打开第二个命令选项卡。请注意,你可以在单个 VM 上同时运行多个容器,这是容器系统的一大优势之一,但它们将竞争 VM 的 CPU 和内存资源,在 Cloud Shell 中这些资源相当有限。本章后面,我们将向你展示如何启动具备更强大能力的 VM。

挂载卷以从容器内部访问文件系统

完成了前面的练习后,现在你可以检索并运行任何公共存储库中共享的容器映像实例。许多常用的生物信息学工具,包括 GATK,在 Docker 容器中预先安装。其理念在于,知道如何在 Docker 容器外使用它们意味着你不需要担心正确的操作系统或软件环境。然而,我们仍然需要向你展示一个诀窍,以便真正使其对你有用:如何通过挂载卷从容器内部访问您机器的文件系统。

上次的最后一部分是什么意思?默认情况下,当你在容器内部时,无法访问容器外部文件系统中的任何数据。容器就像是一个封闭的盒子。虽然有办法在容器和你的文件系统之间来回复制东西,但那样很快就变得很繁琐。因此,我们将选择更简单的方法,即在容器外部建立一个链接,使其看起来就像是在容器内部。换句话说,我们要在容器的墙壁上打个洞,如图 4-8 所示。

将 Google Cloud Shell 中的卷或目录挂载到 Docker 容器中。

图 4-8. 将 Google Cloud Shell VM 中的目录挂载到 Docker 容器中:本章中使用的 Ubuntu 容器(左);在第五章介绍的 GATK 容器(右)。

例如,让我们在 Cloud Shell VM 的主目录中创建一个名为book的新目录,并将之前的hello.txt文件放入其中:

$ mkdir book
$ mv hello.txt book/
$ ls book
hello.txt

因此,这一次,让我们运行该命令通过使用-v参数(其中v表示卷),这使我们能够指定文件系统位置和容器内的挂载点来启动我们的 Ubuntu 容器:

$ docker run -v ~/book:/home/book -it ubuntu /bin/bash

命令中的-v ~/book_data:/home/book部分将您指定的位置链接到 Docker 容器内的路径/home/book目录。路径中的/home是容器中已存在的目录,而book部分可以是您选择的任何名称。现在,您文件系统中book目录中的所有内容都可以从 Docker 容器的/home/book目录访问:

# ls home/book
hello.txt

在这里,我们使用与实际位置相同的挂载点名称,因为这样更直观,但如果您希望,也可以使用不同的名称。请注意,如果您将挂载点命名为容器中该路径已存在的目录或文件的名称,则会“压制”现有路径,这意味着在挂载卷的时间内该路径将不可访问。

还有一些其他有用的 Docker 技巧需要了解,但目前这已经足够演示您将在第五章中使用的核心 Docker 功能的基本功能了。我们会在遇到更复杂的选项时详细讨论更多细节。

设置您自己的自定义 VM

现在,您已成功运行了一些基本的文件管理命令,并掌握了与 Docker 容器交互的技巧,是时候转向更大更好的事物了。Google Cloud Shell 环境非常适合快速开始一些轻量级编码和执行任务,但为 Cloud Shell 分配的虚拟机性能确实较低,当需要在第五章中运行真正的 GATK 分析时肯定不够用。

在本节中,我们将向您展示如何使用 Google 的 Compute Engine 服务在云中设置自己的 VM(有时称为实例),该服务允许您选择、配置和运行任何所需大小的 VM。

创建和配置您的 VM 实例

首先,进入计算引擎,或者通过左侧的侧边栏菜单访问页面,如图 4-9 所示。

显示 VM 实例菜单项的计算引擎菜单。

图 4-9. 显示 VM 实例菜单项的计算引擎菜单。

点击此菜单中的 VM 实例链接,进入运行图像的概述页面。如果这是一个新帐户,您将没有任何正在运行的实例。请注意顶部有一个“创建实例”的选项。点击它,让我们一起完成只使用您所需资源创建新 VM 的过程。

接下来,在顶部菜单栏中点击“创建实例”,如图 4-10 所示。这将弹出一个配置表单,如图 4-11 所示。

创建一个实例

图 4-10. 创建一个 VM 实例。

VM 实例配置面板。

图 4-11. VM 实例配置面板。

按照接下来的小节中的逐步说明配置 VM。这里有大量的选项,如果您对术语不熟悉,整个过程可能会非常混乱,因此我们列出了配置表单中的最简单路径,可以让您在本书的前几章节中运行所有命令练习。请确保除非您真正知道自己在做什么,否则使用完全相同的设置。

为您的 VM 命名

为您的 VM 命名;例如 genomics-book,如图 4-12 所示。这必须在您的项目中是唯一的,但与 GCP 不同,它不需要在全局范围内唯一。一些人喜欢使用他们的用户名,以便其他有权访问项目的人员可以立即识别创建资源的人。

给您的 VM 实例命名。

图 4-12. 给您的 VM 实例命名。

选择一个区域(重要!)和区域(不那么重要)

云的物理位置有所不同。与大多数商业云供应商一样,GCP 在世界各地维护数据中心,并为您提供选择使用哪一个的选项。区域是最高级别的地理区分,其名称相对描述准确(比如 us-west2,指的是洛杉矶的一个设施)。每个区域进一步分为两个或更多由单个字母指定的区域(abc 等),对应具有自己物理基础设施(电力、网络等)的独立数据中心,尽管在某些情况下它们可能共享同一建筑物。

区域和区域的这种系统在限制像停电这样的局部问题方面发挥了重要作用,所有主要的云供应商都使用某种形式的这种策略。有关更多信息,请参阅 这篇有趣的博客文章,作者 Kyle Galbraith 讨论了云区域和区域(在他的情况下,AWS)如何在僵尸末日事件中发挥重要作用。

注意

选择特定区域和区域用于您的项目,对于处理关于人主体数据存储位置的监管限制越来越有帮助,因为它允许您指定符合规定的位置用于所有存储和计算资源。然而,一些世界上的地区目前云服务覆盖不足或者不同云供应商之间的覆盖方式不同,因此在选择供应商时您可能需要考虑可用的数据中心位置。

要为项目选择区域,您可以查阅完整的Google Cloud 可用区域和区域列表,并基于地理接近性做出决定。或者,您可以使用在线工具来衡量每个数据中心的网络响应时间,例如http://www.gcping.com。例如,如果我们从马萨诸塞州西部的桑德兰小镇运行这个测试(结果见表 4-1),我们发现从位于弗吉尼亚北部的us-east4区域(698 公里远)获取响应需要 38 毫秒,而从蒙特利尔的northamerica-northeast1区域(441 公里远)获取响应需要 41 毫秒。这表明,地理接近性并不直接与网络区域接近性相关。更为显著的例子是,我们发现与位于伦敦的europe-west2区域(5,353 公里远)相比,与位于洛杉矶的us-west2区域(4,697 公里远)相比,我们与前者的响应时间为 102 毫秒,而与后者的响应时间为 180 毫秒。

表 4-1. 桑德兰,马萨诸塞州的地理距离和响应时间

区域 位置 距离(公里) 响应时间(毫秒)
us-east4 美国弗吉尼亚州北部 698 38
northamerica-northeast1 蒙特利尔 441 41
europe-west2 伦敦 5,353 102
us-west2 洛杉矶 4,697 180

这使我们回到了我们的 VM 配置。对于区域,我们将使用us-east4(弗吉尼亚北部),因为它是我们中最少旅行的人(杰拉尔丁)最近的一个,而对于区域,我们随意选择了us-east4-a。您需要确保根据前面的讨论选择您的区域,这对您自身的利益(它将更快)以及避免在 GATK 软件的所有 60,000 注册用户同时开始进行这些练习的不太可能事件中占据弗吉尼亚州的数据中心都很重要,尽管这是测试云计算所标榜的“弹性”的一种方式。

选择机器类型

在这里,您可以配置即将启动的虚拟机的资源。您可以控制 RAM 和 CPU。对于某些实例类型(在自定义选项下可用),甚至可以选择带有 GPU 的 VM,用于加速某些程序。关键是,您在此选择的内容将决定 VM 运行时间的每秒计费金额;机器越大越强大,成本就越高。页面右侧应显示在更改机器类型时,每小时和每月成本的变化情况。还请注意,您将按 VM 在线时间计费,而不是您实际使用它的时间。我们稍后会介绍限制成本的策略,但请记住这一点!

在这里选择 n1-standard-2;这是一台非常基础的机器,几乎不会花费太多,如图 4-13 所示。

选择机器类型。

图 4-13. 选择机器类型。

指定一个容器?(不需要)

我们不会填写这部分。如果您希望使用您预先选择或生成的自定义容器映像设置非常具体的设置,那么这将非常有用。实际上,我们本可以为您预配置一个容器并跳过接下来的一堆设置。但这样您就没有机会学习如何为自己做这些事情了,对吧?所以,现在,让我们跳过这个选项。

自定义启动磁盘

就像机器类型一样,这是另一个非常有用的设置。您可以在这里定义两件事情:您想要使用的操作系统及其版本以及您想要的磁盘空间量。前者在您需要使用特定类型和版本的操作系统时尤为重要。当然,后者在您不希望在分析进行到一半时磁盘空间用尽时尤为重要。

默认情况下,系统提供了一种特定版本的 Linux 操作系统,并配备了仅有 10 GB 的磁盘空间,如图 4-14 所示。我们需要更大的硬盘。

选择不同的启动磁盘。

图 4-14. 选择启动磁盘大小和镜像。

要访问此设置菜单,请单击“更改”。这将打开一个新屏幕,显示预定义选项菜单。您还可以创建自己的自定义镜像,甚至在 Google Cloud Marketplace 中查找更多镜像。

对于我们目前的需求,我们更喜欢 Ubuntu 18.04 LTS,这是 Ubuntu 的最新长期支持版本。它可能不像 Ubuntu 19.04 那样前沿,但是 LTS(长期支持)版本保证了在发布后的五年内对其进行安全漏洞和软件包更新的维护。这个 Ubuntu 镜像已经包含了我们需要的大部分内容,包括各种标准 Linux 工具和我们将会非常依赖的 GCP SDK 命令行工具。

在操作系统菜单中选择 Ubuntu,然后在版本菜单中选择 Ubuntu 18.04 LTS,如图 4-15 所示。

选择基础镜像。

图 4-15. 选择基础镜像。

在表单底部,您可以更改启动磁盘大小以提供更多空间。如图 4-16 所示,选择 100 GB 而不是默认的 10 GB(我们即将处理的数据可能会占用大量空间)。您可以根据数据集的大小和需求进一步增加此大小。虽然在 VM 启动后无法轻松调整它,但您可以在启动后向正在运行的实例添加块存储卷的选项,将其视为插入 USB 驱动器的云等效。因此,如果您的磁盘空间不足,您不会完全陷入困境。

设置启动磁盘大小。

图 4-16. 设置启动磁盘大小。

当您完成所有这些操作后,请单击“选择”; 这将关闭屏幕并将您返回到实例创建表单,其中“启动磁盘”部分应与图 4-17 中的截图匹配。

更新的启动磁盘选择。

图 4-17. 更新的启动磁盘选择。

在表单底部,单击“创建”。这会将您返回到列出 Compute Engine VM 实例的页面,包括您新创建的 VM 实例。在创建和启动实例时,您可能会看到其名称前面的旋转图标,然后当实例正在运行并且准备就绪时,会出现一个带有复选标记的绿色圆圈,如图 4-18 所示。

查看 VM 状态。

图 4-18. 查看 VM 状态。

voilà,您的 VM 已准备就绪。

登录到您的 VM 使用 SSH

在 VM 运行后,您可以通过多种方式访问它,您可以在 GCP 文档中了解详情。我们将向您展示最简单的方法,即使用 Google Cloud 控制台和内置的 SSH 终端。这种方式几乎无懈可击:一旦在 Google Cloud 控制台中看到绿色复选标记,您只需点击 SSH 选项即可打开下拉菜单,如图 4-19 所示。选择“在浏览器窗口中打开”,几秒钟后,您应该会看到一个打开到此 VM 的 SSH 终端。

SSH 连接 VM 的选项

图 4-19. SSH 连接 VM 的选项。

这将打开一个新窗口,其中包含一个终端,允许您从 VM 实例内运行命令,如图 4-20 所示。建立连接可能需要一分钟的时间。

VM 实例终端。

图 4-20. VM 实例终端。

随意四处浏览并了解您全新的 VM;在接下来的几章中,您将花费大量时间与它亲密接触(但这是一种好方式)。

检查您的身份验证

您可能迫不及待地想要运行一些有趣的东西,但让我们首先确保您的帐户凭据已正确设置,以便您可以使用 GCP 命令行工具,这些工具已预先安装在我们选择的镜像上。在 SSH 终端中,运行以下命令:

$ gcloud init
Welcome! This command will take you through the configuration of gcloud.
Your current configuration has been set to: [default]
You can skip diagnostics next time by using the following flag:
 gcloud init --skip-diagnostics
Network diagnostic detects and fixes local network connection issues.
Checking network connection...done.                                                                                                                                   
Reachability Check passed.
Network diagnostic passed (1/1 checks passed).
Choose the account you would like to use to perform operations for
this configuration:
[1] XXXXXXXXXXX-compute@developer.gserviceaccount.com
[2] Log in with a new account
Please enter your numeric choice:

[1] 开头的那一行告诉您,默认情况下,GCP 会让您以服务帐户身份登录:域为 @developer.gserviceaccount.com。这对于在您的虚拟机内运行工具是可以的,但如果您想要能够管理资源,包括将文件复制到 GCS 存储桶中,您需要以具有相关权限的帐户进行操作。可以授予此服务帐户您在这些练习中所需的各种权限,但这将使我们深入到 GCP 帐户管理的内部,这不是我们目前想要的—我们希望尽快让您开始进行基因组学工作!因此,让我们使用在本章开头创建项目时使用的原始帐户,因为该帐户已经作为项目所有者拥有这些权限。

要使用该帐户登录,请在提示处按 2。这将触发与程序的一些交互;GCP 将警告您,在虚拟机上使用您的个人凭据是一种安全风险,因为如果您让其他人访问虚拟机,他们将能够使用您的凭据:

You are running on a Google Compute Engine virtual machine.
It is recommended that you use service accounts for authentication.
You can run:
 $ gcloud config set account `ACCOUNT`
to switch accounts if necessary.
Your credentials may be visible to others with access to this
virtual machine. Are you sure you want to authenticate with
your personal account?
Do you want to continue (Y/n)?

解决方案:不要共享对您个人虚拟机的访问权限。^(1)

如果您输入 Y 以确认,程序将会给您一个链接:

Go to the following link in your browser:
   https://accounts.google.com/o/oauth2/auth?redirect_uri=<...>
Enter verification code:

当您点击链接或将其复制粘贴到浏览器中时,您将看到一个 Google 登录页面。使用您在 GCP 中使用的相同帐户登录以获取您的身份验证代码,然后将其复制粘贴回您的终端窗口。gcloud 实用程序将确认您的登录身份,并要求您从您可以访问的项目列表中选择要使用的项目 ID。它还将提供设置您首选的计算和存储区域的选项,这应该与您在创建虚拟机时设置的相匹配。如果您在项目 ID 列表中看到的不是您期望的内容,您可以随时再次检查 GCP 控制台中的资源管理页面

将书籍材料复制到您的虚拟机

在接下来的几章中,您将在您的虚拟机上运行真实的 GATK 命令和工作流程,因此您需要获取示例数据、源代码和一些软件包。我们将大部分内容打包在一个地方:一个名为 genomics-in-the-cloud 的云存储桶中。唯一分开的部分是源代码,我们在 GitHub 上提供。

首先,您将使用 gsutil 将数据包从存储桶复制到您的虚拟机中,gsutil 是我们在本章 Cloud Shell 部分中已经使用过的 GCP 存储实用程序。在您的虚拟机终端窗口中,创建一个名为 **book** 的新目录,然后运行 gsutil 命令将书籍数据包复制到与您的虚拟机关联的存储空间中:

$ mkdir ~/book
$ gsutil -m cp -r gs://genomics-in-the-cloud/v1/* ~/book/

这将复制大约 10 GB 的数据到您的 VM 存储中,因此即使使用了启用并行下载的-m标志,可能需要几分钟时间。正如您稍后将看到的,即使没有先复制它们,也可以直接在云存储中的文件上运行一些分析命令,但我们希望在开始时尽可能保持简单。

现在,前往GitHub 上的公共代码库检索源代码。我们将代码放在那里是因为它是一个非常流行的用于在版本控制下共享代码的平台,我们承诺为书中使用的代码提供长期维护。要在您的 VM 上获取副本,请首先使用cd命令移动到新创建的book目录,然后使用git clone命令复制存储库的内容:

$ cd ~/book
$ git clone https://github.com/broadinstitute/genomics-in-the-cloud.git code

这将创建一个目录(~book/code),其中包含我们在整本书中使用的所有示例代码。不仅如此,它将设置为一个活跃的 Git 仓库,因此你可以通过在代码目录中运行git pull命令来获取最新的更改,如下所示:

$ cd ~/book/code
$ git pull

现在,你应该有了最新和最好的书本代码。要了解自原始出版以来的变化,请查看代码目录中的README文本文件。

在您的 VM 上安装 Docker

您将在 VM 上使用 Docker 工作,因此让我们确保您可以运行它。如果您在终端中简单地运行docker命令,您将收到一个错误消息,因为 Docker 未预安装在 VM 上:

$ docker
Command 'docker' not found, but can be installed with:
snap install docker     # version 18.09.9, or
apt  install docker.io
See 'snap info docker' for additional versions.

错误消息友好地指出了如何使用预安装的snap软件包来修复问题,但我们实际上将使用一个稍微不同的方法来安装 Docker:我们将从 Docker 网站下载并运行一个脚本,这将大大自动化安装过程。这样,如果你需要在没有内置软件包管理器选项的地方安装 Docker,你就会知道该怎么做。

运行以下命令在 VM 上安装 Docker:

$ curl -sSL https://get.docker.com/ | sh 
# Executing docker install script, commit: f45d7c11389849ff46a6b4d94e0dd1ffebca
32c1 + sudo -E sh -c apt-get update -qq >/dev/null
...
Client: Docker Engine - Community
Version:           19.03.5
...
If you would like to use Docker as a non-root user, you should now consider
adding your user to the "docker" group with something like:
 sudo usermod -aG docker genomics_book
Remember that you will have to log out and back in for this to take effect!
WARNING: Adding a user to the "docker" group will grant the ability to run
        containers which can be used to obtain root privileges on the
        docker host.
        Refer to https://docs.docker.com/engine/security/security/#docker-daemon-
        attack-surface for more information.

这可能需要一些时间才能完成,所以让我们花点时间更详细地检查命令。首先,我们使用一个方便的小工具叫做curl(缩写为Client URL)从我们提供的 Docker 网站 URL 下载安装脚本,带有一些命令参数(-sSL),这些参数指示程序跟随任何重定向链接并将输出保存为文件。然后,我们使用管道字符(|)将该输出文件传递给第二个命令sh,意思是“运行我们刚刚给你的那个脚本”。输出的第一行让你知道它正在做什么:“执行 docker 安装脚本”(出于简洁起见,我们省略了前面的输出部分)。

当完成时,脚本会提示您运行接下来的示例中的usermod命令,以授予您无需每次使用sudo运行 Docker 命令的能力。调用sudo docker可能导致输出文件由 root 所有,使得以后难以管理或访问它们,因此执行此步骤非常重要:

$ sudo usermod -aG docker $USER

这不会产生任何输出;我们将在一分钟内测试它是否正常工作。但是,首先,您需要注销 VM,然后再次登录。这样做将使系统重新评估您的 Unix 组成员资格,这对刚刚做出的更改生效是必要的。只需在命令提示符处键入**exit**(或按 Ctrl+D):

$ exit

这将关闭到您的 VM 的终端窗口。返回 GCP 控制台,在计算引擎实例列表中找到您的 VM,然后单击 SSH 重新登录。这可能感觉需要经过很多步骤,但请坚持下去;我们即将进入精彩部分。

设置 GATK 容器镜像

当您回到 VM 时,请通过拉取 GATK 容器来测试您的 Docker 安装,我们将在接下来的章节中使用它:

$ docker pull us.gcr.io/broad-gatk/gatk:4.1.3.0
4.1.3.0: Pulling from us.gcr.io/broad-gatk/gatk
ae79f2514705: Pull complete
5ad56d5fc149: Pull complete
170e558760e8: Pull complete
395460e233f5: Pull complete
6f01dc62e444: Pull complete
b48fdadebab0: Pull complete
16fb14f5f7c9: Pull complete
Digest: sha256:e37193b61536cf21a2e1bcbdb71eac3d50dcb4917f4d7362b09f8d07e7c2ae50
Status: Downloaded newer image for us.gcr.io/broad-gatk/gatk:4.1.3.0
us.gcr.io/broad-gatk/gatk:4.1.3.0

作为提醒,容器名称后的最后一部分是版本标签,您可以更改以获取与我们在此处指定的不同版本。请注意,如果更改版本,某些命令可能不再起作用。我们无法保证所有代码示例都将与未来兼容,特别是对于仍在积极开发中的新工具。如前所述,有关更新的材料,请参见本书的 GitHub 存储库

GATK 容器镜像相当庞大,因此下载可能需要一些时间。好消息是,下次需要拉取 GATK 镜像(例如,获取另一个版本)时,Docker 只会拉取已更新的组件,因此速度会更快。

注意

在这里,我们从 Google 容器存储库(GCR)中拉取 GATK 镜像,因为 GCR 与我们正在运行的 VM 位于同一网络上,因此比从 Docker Hub 拉取要快。但是,如果您在不同平台上工作,您可能会发现从 Docker Hub 上的 GATK 存储库拉取镜像更快。要这样做,请将镜像路径中的us.gcr.io/broad-gatk部分更改为**broadinstitute**

现在,记得之前在本章中遵循的指令来启动一个挂载文件夹的容器吗?您将再次使用它来使book目录对 GATK 容器可访问:

$ docker run -v ~/book:/home/book -it us.gcr.io/broad-gatk/gatk:4.1.3.0 /bin/bash

您现在应该能够从容器内部浏览您在 VM 中设置的book目录。它将位于/home/book下。最后,为了双重检查 GATK 本身是否按预期工作,请尝试在运行中的容器内部的命令行中运行gatk命令。如果一切正常,您应该会看到一些文本输出,概述了基本的 GATK 命令行语法和一些配置选项:

# gatk
Usage template for all tools (uses --spark-runner LOCAL when used with a Spark tool)
   gatk AnyTool toolArgs
Usage template for Spark tools (will NOT work on non-Spark tools)
   gatk SparkTool toolArgs  [ -- --spark-runner <LOCAL | SPARK | GCS> sparkArgs ]
Getting help
   gatk --list       Print the list of available tools
   gatk Tool --help  Print help on a particular tool
Configuration File Specification
    --gatk-config-file                PATH/TO/GATK/PROPERTIES/FILE
gatk forwards commands to GATK and adds some sugar for submitting spark jobs
  --spark-runner <target>    controls how spark tools are run
    valid targets are:
    LOCAL:      run using the in-memory spark runner
    SPARK:      run using spark-submit on an existing cluster
                --spark-master must be specified
                --spark-submit-command may be specified to control the Spark submit command
                arguments to spark-submit may optionally be specified after --
    GCS:        run using Google cloud dataproc
                commands after the -- will be passed to dataproc
                --cluster <your-cluster> must be specified after the --
                spark properties and some common spark-submit parameters will be translated
                to dataproc equivalents
  --dry-run     may be specified to output the generated command line without running it
  --java-options 'OPTION1[ OPTION2=Y ... ]''   optional - pass the given string of options to 
                 the java JVM at runtime. 
                Java options MUST be passed inside a single string with space-separated values

我们将在 第五章 中详细讨论这一切的含义;目前,您已经设置好了在接下来的三章中运行 GATK 工具所需的环境。

停止您的 VM…以防止它花费您的钱

您刚刚设置完毕的 VM 将在整本书中派上用场;在接下来的几章中,您将回到这个 VM 进行许多练习。但是,只要它在运行,就会花费您积分或实际货币。处理这个问题的最简单方法是停止它:在您不活跃使用时将其暂停。

您可以根据需要重新启动它;只需一两分钟即可使其恢复运行状态,并且将保留所有环境设置、先前运行的历史记录以及本地存储中的任何数据。请注意,即使 VM 未运行,您也将为该存储空间支付一小笔费用,并且您不会因 VM 本身而被收费。在我们看来,这是非常值得的,因为您可以在任意时间回到您的 VM 并继续您离开的工作。

要停止您的 VM,在 GCP 控制台中,转到 VM 实例管理页面,如前所示。找到您的实例,单击右侧的垂直三点符号以打开控件菜单,然后选择停止,如 图 4-21 所示。该过程可能需要几分钟才能完成,但您可以安全地离开该页面。要稍后重新启动您的实例,只需按照相同的步骤,但在控件菜单中单击启动。

停止、启动或删除您的 VM 实例。

图 4-21. 停止、启动或删除您的 VM 实例。

或者,您可以完全删除您的 VM,但请记住,删除 VM 将同时删除所有本地存储的数据,因此请确保您首先将您关心的任何内容保存到存储桶中。

配置 IGV 以从 GCS 存储桶中读取数据

在进入下一章之前,还有一个小步骤:我们将安装和配置一个名为集成基因组浏览器(IGV)的基因组浏览器,它可以直接与 GCP 中的文件一起使用。这将使您能够查看序列数据和变异调用,而无需将文件复制到本地计算机。

首先,如果您尚未在本地计算机上安装 IGV 程序,请从网站上获取并按照安装说明进行安装。如果您已经有一份副本,请考虑更新到最新版本;我们使用的是 2.7.2 版本(macOS 版本)。打开应用程序后,从顶部菜单栏中选择“查看” > “首选项”,如 图 4-22 所示。

选择“首选项”菜单项。

图 4-22. 选择“首选项”菜单项。

这将打开首选项面板,如 Figure 4-23 所示。

在首选项面板中,选择“启用 Google 访问”复选框,点击保存,然后退出 IGV 并重新打开以强制刷新顶部菜单栏。现在,你应该能看到一个之前不存在的 Google 菜单项;点击它,选择登录,如 Figure 4-24 所示,使用你的 Google 帐户凭据设置 IGV。

IGV 首选项面板。

Figure 4-23. IGV 首选项面板。

选择 Google 登录菜单项。

Figure 4-24. 选择 Google 登录菜单项。

这将在你的网络浏览器中带你到 Google 登录页面;按照提示允许 IGV 访问你的 Google 帐户的相关权限。完成后,你应该会看到一个简单显示“OK”的网页。现在切换回 IGV 并测试它是否工作。从顶部菜单中点击文件 > 从 URL 加载,如 Figure 4-25 所示,确保不要误选其他选项(它们看起来相似,所以很容易搞错)。同时确保 IGV 窗口左上角的参考下拉菜单设置为“Human hg19”。

注:

如果你对人类参考的区别感到困惑,请参考关于 hg19 和 GRCh38 的笔记 “作为公共框架的参考基因组”。

“从 URL 加载”菜单项。

Figure 4-25. “从 URL 加载”菜单项。

最后,在弹出的对话窗口中输入书籍数据包中提供的样本 BAM 文件之一的 GCS 文件路径(例如 mother.bam,如 Figure 4-26 所示),然后点击确定。记住,你可以使用 gsutil 从你的虚拟机或 Cloud Shell 获取桶中的文件列表,或者你可以使用 Google Cloud 控制台存储浏览器 浏览桶的内容。如果你使用浏览器界面获取文件路径,你需要通过去掉 URL 中桶名前的第一部分来组成 GCS 文件路径;例如,移除 https://console.cloud.google.com/storage/browser 并将其替换为 **gs://**。对于 BAM 的伴随索引文件,路径和文件名相同,但以 .bai 结尾。^(2)

“从 URL 加载”对话框。

Figure 4-26. “从 URL 加载”对话框。

这将使数据作为新数据轨迹在 IGV 中可用,但默认情况下主视图中不会加载任何内容。要检查是否可以查看数据,请在搜索窗口中输入基因组坐标**20:9,999,830-10,000,170**,然后点击 Go。这些坐标将带您到第 20 条人类染色体上第 1 千万个 DNA 碱基的左侧边界附近±170 的位置,如图 4-27 所示,您将看到我们在此样本文件中提供的序列数据片段的左侧边缘。我们将在第五章详细解释如何解读 IGV 的视觉输出,当我们用它来研究一个真实(小)分析的结果时。

IGV 查看位于 GCS 存储桶中的 BAM 文件。

图 4-27。IGV 查看位于 GCS 存储桶中的 BAM 文件。

IGV 一次只检索小片段的数据,因此除非您的互联网连接特别慢,否则传输速度应该非常快。但请注意,像所有商业云提供商一样,GCP 将对从云中传输数据收取出口费用。好消息是,这是一笔很小的费用,与您传输的数据量成比例。因此,在 IGV 中查看数据片段的成本微乎其微——大约是几分钱的级别——这绝对比将整个文件传输到离线浏览器中要划算得多!

您可以使用相同的操作方式查看其他数据文件的内容,如 VCF 文件,只要这些文件存储在 GCP 存储桶中。不幸的是,这意味着这对于存储在 VM 本地存储中的文件不起作用,因此每当您想要检查这些文件之一时,您需要先将其复制到存储桶中。您将很快熟悉gsutil工具。

当您打开 IGV 时,请注意最后一件事:点击 IGV 窗口工具栏中的小黄色信息气泡,它控制详细查看器的行为,如图 4-28 所示。不妨将设置从“悬停显示详细信息”改为“点击显示详细信息”。无论您选择哪种操作,都将触发一个小对话框的出现,详细显示您点击或悬停的数据部分的所有映射信息,例如序列读取时,将显示所有映射信息以及完整序列和碱基质量。您可以立即尝试加载的数据来验证。如您所见,详细显示功能本身非常方便,但在您初次接触界面时,“悬停”版本的此行为可能会有些压倒性,因此我们建议切换到“点击”模式。

更改详细查看器的行为,从“悬停”改为“点击”。

图 4-28。更改详细查看器的行为,从“悬停”改为“点击”。

总结和下一步

在本章中,我们向您展示了如何开始使用 GCP 资源,从创建帐户,使用超基本的 Cloud Shell,然后升级到您自己的自定义 VM。您学会了如何管理 GCS 中的文件,运行 Docker 容器,并管理您的 VM。最后,您检索了书籍数据和源代码,完成了设置您的自定义 VM 以与 GATK 容器一起工作,并设置了 IGV 以查看存储在存储桶中的数据。在第五章,我们将帮助您开始使用 GATK 自身,很快,您将在云中的示例数据上运行真正的基因组学工具。

^(1) 请记住,如果您为 GCP 项目中的其他用户创建帐户,他们也可以通过 SSH 连接到您的 VM。在共享项目中进一步限制对您的 VM 访问是可能的,但这超出了我们在此处简单介绍的范围。

^(2) 例如,https://console.cloud.google.com/storage/browser/genomics-in-the-cloud/v1/data/germline/bams/mother.bam 变成了 gs://genomics-in-the-cloud/v1/data/germline/bams/mother.bam

第四章:云中的第一步

在前两章中,我们带您了解了基因组学和计算技术的基本知识。我们的目标是确保您在这两个领域有足够的基础,无论您是更多地从其中一方面接触这些内容,还是从完全不同的领域来的;如果是后者,欢迎您!请继续保持耐心。

我们意识到,前两章可能感觉很被动,因为没有涉及实际操作。所以这里有个好消息:你终于可以开始实际操作了。本章旨在帮助你熟悉和适应我们在本书中使用的 GCP 服务。首先,我们将引导你创建一个 GCP 账号并在 Google Cloud Shell 中运行简单命令。之后,我们将向你展示如何在云中设置自己的虚拟机,运行 Docker,并配置环境,以便在第五章中运行 GATK 分析。最后,我们将向你展示如何配置 IGV 以访问 Google Cloud Storage 中的数据。完成这些设置后,你就可以开始进行真正的基因组学工作了。

设置您的 Google Cloud 账户和第一个项目

您可以通过访问https://cloud.google.com并按照提示操作来注册 GCP 账号。我们在这里故意不详细说明,因为账户设置界面可能会发生变化。但总体而言,您的目标是建立一个新的 Google Cloud 账户,设置一个计费账户,接受免费试用额度(如果符合条件),并创建一个新项目,将其与计费账户关联。

如果你还没有任何形式的 Google 身份,你可以使用常规电子邮件账号创建一个;你不需要使用 Gmail 账号。请记住,如果你的机构使用 G Suite,即使域名不是gmail.com,你的工作邮件可能已经与 Google 身份关联。

注册完成后,请访问GCP 控制台,该控制台提供了一个基于 Web 的图形界面,用于管理云资源。您可以通过纯命令行界面访问控制台提供的大多数功能。在本书中,我们将向您展示如何通过 Web 界面和命令行执行某些操作,具体取决于我们认为哪种方式更为方便和/或典型。

创建一个项目

让我们开始创建你的第一个项目,这是组织你的工作、设置结算并获取 Google Cloud 平台服务所必需的。在控制台中,进入“管理资源”页面,然后在页面顶部选择“创建项目”。如图 4-1 所示,你需要为你的项目命名,这个名字必须在整个 Google Cloud 平台内是唯一的。如果你的 Google 身份关联了一个组织(如果你有一个机构或工作 G Suite 账户通常都会有这种情况),你还可以选择一个组织,但如果你刚刚创建了你的账户,这可能目前对你不适用。选择一个组织意味着新项目将默认关联到该组织,这允许对项目进行集中管理。根据本说明的目的,我们假设你是第一次设置你的账户,并且目前没有预先链接的组织与之关联。

创建一个新项目。

图 4-1. 创建一个新项目。

检查你的结算账户并激活免费积分

如果你按照前一节中概述的注册流程并激活了你的免费试用,系统将在整个账户创建过程中为你设置了结算信息。你可以在控制台的计费部分查看你的结算信息,在侧边栏菜单中随时访问。

如果你有资格参加免费积分计划,计费概述页面的一个面板将总结你剩余的积分和可以使用它们的天数。请注意,如果你的页面显示一个蓝色的“升级”按钮,如图 4-2,说明你的试用尚未开始,你需要激活它以利用该计划。你还可能在浏览器窗口顶部看到一个“免费试用状态”横幅,上面有一个蓝色的“激活”按钮。Google Cloud 的某位同事非常努力,不想让你放弃免费的机会,所以点击其中任何一个按钮开始这个过程,领取你的免费积分。

在计费控制台中总结免费试用积分可用性的面板。

图 4-2. 在计费控制台中总结免费试用积分可用性的面板。

总体来说,计费概述页面提供了到目前为止你花费了多少钱(或积分)的总结,以及一些基本的预测信息。但需要明确的是,系统不会实时显示你的费用:在你使用可计费资源和费用更新到你的计费页面之间会有一些延迟。

许多转向云端的人报告说,跟踪他们的支出是这个过程中最困难的部分之一。这也是他们最焦虑的部分,因为如果不小心的话,在云端很容易很快地花掉大笔钱。GCP 提供的一个特性,我们认为在这方面特别有用的是“预算和警报”设置,如图 4-3 所示。这允许您设置电子邮件警报,当您超过某些支出阈值时,将通知您(或者是您账户上的计费管理员)。明确地说,这不会阻止任何东西运行,也不会阻止您启动任何可能使您超过阈值的新工作,但至少它会让您知道您的情况。

预算和警报阈值管理

图 4-3. 预算和警报阈值管理。

要访问计费通知功能,在 GCP 控制台的主菜单上,选择计费,选择您刚刚创建的计费账户,然后查找预算和警报选项。选择后,您将能够使用图 4-3 中显示的创建预算表单来设置新的预算。如果您希望在接近预算金额时收到警告,您可以创建多个预算并为不同百分比的预算设置多个触发器。但正如我们刚才提到的,要记住它仍然只是一个通知服务,不会阻止您产生额外的费用。

在 Google Cloud Shell 中运行基本命令

现在您已经建立了您的账户,设置了计费方式,并创建了您的项目,下一步是登录到您的第一个虚拟机。在我们这里的练习中,我们使用的是 Google Cloud Shell,它不需要任何配置即可开始使用,而且完全免费,尽管它有一些重要的限制,我们稍后会讨论。

登录到 Cloud Shell 虚拟机

要使用 SSH 协议创建到 Cloud Shell 虚拟机的安全连接,在控制台右上角,点击终端图标:

打开 Cloud Shell 的终端图标。

这将在控制台底部启动一个新面板;如果您愿意,您也可以将终端弹出到自己的窗口中。这将为您提供对您自己的基于 Debian 的 Linux 虚拟机的 shell 访问,该虚拟机配备了适度的资源,包括 5 GB 的免费存储(挂载在$HOME)在持久磁盘上。一些基本软件包已经预装并准备就绪,包括 Google Cloud SDK(又名gcloud),它提供了一套丰富的基于命令行的工具,用于与 GCP 服务进行交互。我们将在几分钟内使用它来尝试一些基本的数据管理命令。与此同时,随意探索这个 Debian 虚拟机,四处看看,看看安装了什么工具。

注意

请注意,每周使用配额限制您可以运行 Cloud Shell 的时间;截至本文撰写时,为每周 50 小时。此外,如果您不定期使用(截至本文撰写时为 120 天),则提供免费存储的磁盘内容可能会被删除。

当您首次登录 Cloud Shell 时,它会提示您使用上述gcloud实用程序指定项目 ID:

$ java -jar program.jar [program arguments]

您可以在控制台首页找到您的项目 ID,如图 4-4 所示。

在 GCP 控制台中查找项目 ID 的位置。

图 4-4. 在 GCP 控制台中查找项目 ID 的位置。

当您获得项目 ID 后,在 Cloud Shell 中运行以下命令,将此处显示的项目 ID 替换为您自己的项目 ID:

$ gatk ToolName [tool arguments]

注意,您的命令提示现在包含您的项目 ID。它非常长,所以在接下来的内容中,我们只会显示提示符的最后一个字符——在本例中是美元符号($)——当演示运行命令时。例如,如果我们使用ls命令列出工作目录的内容,看起来会像这样:

$ java -Xmx4G -XX:+PrintGCDetails -jar program.jar [program arguments]

嘿,这里已经有东西了:一个README文件,正如名称所示,真的希望您阅读它。您可以通过运行cat命令来阅读它:

$ gatk --java-options "-Xmx4G -XX:+PrintGCDetails" ToolName [tool arguments]

这将显示一个欢迎消息,总结了一些使用说明和获取帮助的建议。有了这些,您就可以使用 Cloud Shell 开始与基本的 GCP 服务进行交互了。让我们开始吧!

使用 gsutil 访问和管理文件

现在我们可以访问这个非常简单启动且免费(虽然相当有限)的 VM,让我们看看能否访问本书提供的示例数据包。数据包存储在 Google Cloud Storage(GCS)中,这是一种对象存储(即用于存储文件的形式),其存储单位称为存储桶。您可以通过 GCP 控制台的存储浏览器部分查看 GCS 存储桶的内容并执行基本的管理任务,但接口相当有限。更强大的方法是使用命令行工具gcloudgsutil(Google 存储实用工具)。您可以通过其 GCS 路径访问存储桶,即它们的名称前缀为gs://

例如,本书的公共存储桶路径是gs://genomics-in-the-cloud。您可以在 Cloud Shell 中键入以下命令来列出存储桶的内容:

gatk MySparkTool \
    -R data/reference.fasta \
    -I data/sample1.bam \
    -O data/variants.vcf \
    -- \
    --spark-master 'local[4]'

应该有一个名为hello.txt的文件。让我们使用 Unix 命令catgsutil版本来查看这个hello.txt文件包含什么内容:

    --spark-runner SPARK --spark-master spark://23.195.26.187:7077
    ```

您还可以尝试将文件复制到您的存储磁盘:

--spark-runner GCS --cluster my_cluster
```

如果再次使用ls列出工作目录的内容,您现在应该有hello.txt文件的本地副本:

$ docker run -v ~/book:/home/book -it us.gcr.io/broad-gatk/gatk:4.1.3.0 /bin/bash

当我们使用gsutil时,我们做些稍后会有用的事情:创建您自己的存储桶,以便您可以在 GCS 中存储输出。您需要在此处显示的命令中替换my-bucket,因为存储桶名称必须在整个 GCS 中是唯一的:

(gatk) root@ce442edab970:/gatk#

如果您没有更改存储桶名称,或者尝试使用已被其他人使用的名称,则可能会收到以下错误消息:

# ls
GATKConfig.EXAMPLE.properties   gatk-package-4.1.3.0-spark.jar  gatkdoc
README.md                       gatk-spark.jar                  gatkenv.rc
gatk                            gatk.jar                        install_R_packages.R
gatk-completion.sh              gatkPythonPackageArchive.zip    run_unit_tests.sh
gatk-package-4.1.3.0-local.jar  gatkcondaenv.yml                scripts

如果是这种情况,只需尝试其他更有可能是唯一的内容。当您在输出中看到Creating *name*...并且然后从gsutil没有进一步的投诉时,您就知道它起作用了。完成后,您将创建一个环境变量,该变量将作为存储桶名称的别名。这样,您就可以节省一些输入,并且可以复制和粘贴后续命令,而无需每次都替换存储桶名称:

# gatk
Usage template for all tools (uses --spark-runner LOCAL when used with a Spark tool)
   gatk AnyTool toolArgs
Usage template for Spark tools (will NOT work on non-Spark tools)
   gatk SparkTool toolArgs  [ -- --spark-runner <LOCAL | SPARK | GCS> sparkArgs ]
Getting help
   gatk --list       Print the list of available tools
   gatk Tool --help  Print help on a particular tool
Configuration File Specification
    --gatk-config-file             PATH/TO/GATK/PROPERTIES/FILE

gatk forwards commands to GATK and adds some sugar for submitting spark jobs

  --spark-runner <target>    controls how spark tools are run
    valid targets are:
    LOCAL:      run using the in-memory spark runner
    SPARK:      run using spark-submit on an existing cluster
                --spark-master must be specified
                --spark-submit-command may be specified to control the Spark submit command
                arguments to spark-submit may optionally be specified after --
    GCS:        run using Google cloud dataproc
                commands after the -- will be passed to dataproc
                --cluster <your-cluster> must be specified after the --
                spark properties and some common spark-submit parameters will be translated
                to dataproc equivalents
  --dry-run      may be specified to output the generated command line without running it
  --java-options 'OPTION1[ OPTION2=Y ... ]''   optional - pass the given string of options to 
                 the java JVM at runtime.
                Java options MUST be passed inside a single string with space-separated values

您可以在新变量上运行echo命令,以验证您的存储桶名称已正确存储:

# gatk HaplotypeCaller --help

现在,让我们熟悉如何使用gsutil。首先,将hello.txt文件复制到您的新存储桶中。您可以直接从原始存储桶中复制:

# cd /home/book/data/germline
# mkdir sandbox

或者,您可以从本地副本执行它;例如,如果您进行了要保存的修改:

# gatk HaplotypeCaller \
    -R ref/ref.fasta \
    -I bams/mother.bam \
    -O sandbox/mother_variants.vcf

最后,作为基本文件管理的另一个例子,您可以决定文件应该放在自己的目录中:

Using GATK jar /gatk/gatk-package-4.1.3.0-local.jar
Running:
   java -Dsamjdk.use_async_io_read_samtools=false -Dsamjdk.use_async_io_wri
te_samtools=true -Dsamjdk.use_async_io_wri
te_tribble=false -Dsamjdk.compression_level=2 -jar /gatk/gatk-package-4.1.3.0-
local.jar HaplotypeCaller -R ref/ref.fas
ta -I bams/mother.bam -O sandbox/mother_variants.vcf
09:47:17.371 INFO  NativeLibraryLoader - Loading libgkl_compression.so from
jar:file:/gatk/gatk-package-4.1.3.0-local.
jar!/com/intel/gkl/native/libgkl_compression.so
09:47:17.719 INFO  HaplotypeCaller - -----------------------------------------
-------------------
09:47:17.721 INFO  HaplotypeCaller - The Genome Analysis Toolkit (GATK) v4.1.3.0
09:47:17.721 INFO  HaplotypeCaller - For support and documentation go to
https://software.broadinstitute.org/gatk/
09:47:17.722 INFO  HaplotypeCaller - Executing as root@3f30387dc651 on Linux
v5.0.0-1011-gcp amd64
09:47:17.723 INFO  HaplotypeCaller - Java runtime: OpenJDK 64-Bit Server VM
v1.8.0_191-8u191-b12-0ubuntu0.16.04.1-b12
09:47:17.724 INFO  HaplotypeCaller - Start Date/Time: August 20, 2019 9:47:17 AM
UTC

正如您所见,gsutil命令设置得尽可能与其原始 Unix 版本相似。因此,例如,您也可以使用-r来使cpmv命令递归应用于目录。对于大文件传输,您可以使用一些云规格优化来加快进程,例如gsutil -m选项,该选项并行化文件传输。方便的是,系统通常会在终端输出中通知您何时可以利用这些优化,因此您无需在开始之前去记忆文档。

GCP 控制台存储浏览器。

图 4-5. GCP 控制台存储浏览器。

这将打开一个相当简单的配置表单。在这里要做的最重要的事情是选择一个好的名称,因为您选择的名称必须在 Google Cloud 的所有内容中都是唯一的——所以要有创意!如果选择的名称已经被使用,系统将在您单击配置表单中的继续时通知您,如图 4-6 所示。

命名您的存储桶。

图 4-6. 命名您的存储桶。

当你有一个唯一的名称时,系统会允许你通过展开菜单选项来进行下一步。这些选项允许你自定义存储位置和访问控制,但目前你可以只接受默认设置并点击创建。这样做会带你回到存储桶列表,这时应该包括你新创建的存储桶。你可以点击它的名称来查看其内容——当然它还是空的,所以视图不会特别令人兴奋,正如图 4-7 所示。

界面提供了一些基本的管理选项,比如删除存储桶和文件,以及上传文件和文件夹。请注意,你甚至可以将本地计算机中的文件和文件夹拖放到存储桶内容窗口中,这非常简单(试试看),但在进行基因组学工作过程中你不会经常这样做。在现实世界中,你更可能使用gsutil命令行实用程序。使用命令行路径的一个优点是你可以将这些命令保存为脚本,用于溯源和在需要时重现你的步骤。

查看存储桶内容。

图 4-7. 查看存储桶内容。

拉取 Docker 镜像并启动容器

Cloud Shell 是一份持续给予的礼物:我们在第三章中介绍的 Docker 应用程序已预装,所以你可以立即开始使用!我们将使用一个简单的 Ubuntu 容器来说明基本的 Docker 功能。虽然有一个用于 GATK 的 Docker 镜像,并且在接下来的几章中我们会大量使用它,但我们在这里不会使用它,因为它相当庞大,所以启动时间稍长。在免费 Cloud Shell 中由于分配给此免费 VM 的 CPU 和内存资源较少,我们实际上无法使用它来运行任何实际的分析。

注意

在这种情况下学习如何使用 Docker 容器的第一步是…好吧,避免在线的 Docker 文档!认真的。不是因为它不好,而是因为这些文档大多数是为那些想要在云中运行 Web 应用程序的人编写的。如果这是想要做的事情,那么你可以自由选择,但你正在阅读错误的书。我们在这里提供的是量身定制的指导,将教会你如何使用 Docker 来运行研究软件在容器中。

正如刚才提到的,我们将使用一个非常通用的例子:一个包含 Ubuntu Linux 操作系统的镜像。这是一个官方镜像,作为公共容器镜像仓库 Docker Hub 的核心库的一部分提供,所以我们只需要声明其名称。稍后您将看到,由社区贡献的镜像以贡献者的用户名或组织名作为前缀。在 Cloud Shell 终端中(无论您的工作目录在哪里),运行以下命令从 Docker Hub 官方(认证)镜像库中检索 Ubuntu 镜像:

09:47:18.347 INFO  ProgressMeter - Starting traversal
09:47:18.348 INFO  ProgressMeter -        Current Locus  Elapsed Minutes
Regions Processed   Regions/Minute
09:47:22.483 WARN  InbreedingCoeff - InbreedingCoeff will not be calculated; at
least 10 samples must have called geno
types
09:47:28.371 INFO  ProgressMeter -          20:10028825              0.2
33520         200658.5
09:47:38.417 INFO  ProgressMeter -          20:10124905              0.3
34020         101709.1
09:47:48.556 INFO  ProgressMeter -          20:15857445              0.5
53290         105846.1
09:47:58.718 INFO  ProgressMeter -          20:16035369              0.7
54230          80599.5
09:48:08.718 INFO  ProgressMeter -          20:21474713              0.8
72480          86337.1
09:48:18.718 INFO  ProgressMeter -          20:55416713              1.0
185620         184482.4

pull 命令获取镜像并将其保存到您的虚拟机中。容器镜像的版本由其tag(可以是镜像创建者想要分配的任何内容)和其sha256哈希(基于镜像内容)指示。默认情况下,系统提供了可用的最新版本,因为我们没有指定特定的标签;在后面的练习中,您将看到如何通过标签请求特定版本。请注意,容器镜像通常由几个模块化的slices组成,可以分开拉取。它们被组织起来,以便下次拉取镜像版本时,系统会跳过与您已经拥有的版本相比未更改的任何切片。

现在让我们启动容器。有三种主要的运行选项,但棘手的是通常只有一种正确的方式如其作者所愿,如果文档没有明确指定(这种情况非常常见),那么很难知道哪种方式是正确的。感到困惑了吗?让我们逐步分析具体情况,你会明白为什么我们要让你经历这一短暂的沮丧和神秘感——这是为了在未来避免潜在的痛苦。

第一选项

只需运行它!

09:48:20.714 INFO  ProgressMeter - Traversal complete. Processed 210982 total
regions in 1.0 minutes.
09:48:20.738 INFO  VectorLoglessPairHMM - Time spent in setup for JNI call: 
0.045453468000000004
09:48:20.739 INFO  PairHMM - Total compute time in PairHMM 
computeLogLikelihoods(): 6.333675601
09:48:20.739 INFO  SmithWatermanAligner - Total compute time in java 
Smith-Waterman: 6.18 sec
09:48:20.739 INFO  HaplotypeCaller - Shutting down engine
[August 20, 2019 9:48:20 AM UTC]
org.broadinstitute.hellbender.tools.walkers.haplotypecaller.HaplotypeCaller done.
Elapsed time: 1.06 minutes.
Runtime.totalMemory()=717225984

结果

稍作停顿,然后您的命令提示符会回来。没有输出。发生了什么?实际上,Docker 确实启动了容器,但在这些条件下,容器没有配置执行任何操作,所以它基本上耸了耸肩又关闭了。

第二选项

运行附加命令:

# gatk ValidateSamFile \
    -R ref/ref.fasta \
    -I bams/mother.bam \
    -O sandbox/mother_validation.txt

结果

它像请求的那样回显Hello World!,然后再次关闭。好的,现在我们知道我们可以向容器传递命令,如果是容器中的某个东西识别的命令,它将被执行。然后,当所有命令都已完成时,容器将关闭。有点懒惰,但合理。

第三选项

使用-it选项以交互方式运行它:

# gatk HaplotypeCaller \
    -R ref/ref.fasta \
    -I bams/mother.bam \
    -O sandbox/mother_variants.200k.vcf \
    -L 20:10,000,000-10,200,000

结果

啊哈!一个新的命令提示符(在这种情况下是 Bash)!但是有一个不同的 shell 符号:#,而不是$。这意味着容器正在运行并且您在其中。现在,您可以运行任何您在 Ubuntu 系统上通常使用的命令,包括安装新的软件包。尝试运行一些 Unix 命令,如lsls -la来浏览并查看容器的功能。在本书的后面,特别是在第十二章,我们将详细讨论这些内容,包括如何打包和重新分发您定制的镜像,以便以可重现的方式分享您自己的分析。

当您完成浏览后,在命令提示符处键入**exit**(或按 Ctrl+D)以终止 shell。因为这是容器运行的主进程,终止它将导致容器关闭并返回到 Cloud Shell 本身。明确一点,这将关闭容器及当前运行的任何命令

如果您好奇的话:是的,确实可以在不关闭容器的情况下离开它;这称为分离。为此,请按 Ctrl+P+Q,而不是使用exit命令。随时可以重新进入容器——前提是您能识别它。默认情况下,Docker 为您的容器分配一个通用唯一标识符(UUID)以及一个随机的人类可读名称(听起来有点傻)。您可以运行docker ps来列出当前运行的容器,或者docker ps -a来列出已创建的容器。这将显示一个由容器 ID 索引的容器列表,看起来应该像这样:

$ cd ~/book/data/germline/sandbox

我们展示了最后两次 Docker 调用对应的两个条目,每个都有一个唯一标识符,即CONTAINER ID。我们看到当前正在运行的容器 ID 为c2b4f8a0c7a6,其名称为vigorous_rosalind,状态为Up 5 minutes。您可以看出另一个名为objective_curie的容器不在运行,因为其状态为Exited (0) 8 minutes ago。这里看到的名称是随机分配的(我们发誓!几率有多大?),因此它们确实不是非常有意义的。如果您同时运行多个容器,这可能会有些令人困惑,因此您需要一种更好的方法来识别它们。好消息是,您可以在初始命令中的docker run之后立即添加--name=*meaningful_name*,用您想要给容器起的名称替换*meaningful_name*,这样可以给它们起一个有意义的名称。

要进入容器,只需运行 docker attach *c2b4f8a0c7a6*(替换您的容器 ID),然后按 Enter 键,您将发现自己回到了控制台(您的键盘可能标有 Return 而不是 Enter)。如果您希望能够在容器内部工作的同时在 Cloud Shell 中打开第二个命令选项卡,则可以这样做。请注意,您可以在单个 VM 上同时运行多个容器,这是容器系统的一个伟大优势之一,但它们将竞争 Cloud Shell VM 的 CPU 和内存资源,这在 Cloud Shell 中相对较少。本章后面我们将向您展示如何启动具有更强大功能的 VM。

挂载卷以访问容器内部的文件系统

完成了前面的练习后,您现在可以检索和运行任何公共存储库中共享的容器镜像的实例。许多常用的生物信息学工具,包括 GATK,在 Docker 容器中预安装。这样做的想法是,知道如何在 Docker 容器外部使用它们意味着您不需要担心拥有正确的操作系统或软件环境。然而,我们仍然需要向您展示一个技巧,以确保这对您真正有效:如何通过 挂载卷 从容器内部访问您计算机的文件系统。

最后一部分是什么意思?默认情况下,当您在容器内部时,无法访问容器外部文件系统上的任何数据。容器是一个封闭的盒子。有办法在容器和您的文件系统之间复制数据,但这会变得非常繁琐。因此,我们将选择更简单的路径,即在容器外的目录之间建立一个链接,使其看起来像是在容器内部。换句话说,我们将在容器墙壁上开一个洞,如 图 4-8 所示。

将卷或目录从您的 Google Cloud Shell 挂载到 Docker 容器中。

图 4-8. 从您的 Google Cloud Shell VM 挂载目录到 Docker 容器中:本章中使用的 Ubuntu 容器(左侧);在 第五章 中介绍的 GATK 容器(右侧)。

例如,让我们在 Cloud Shell VM 的主目录中创建一个名为 book 的新目录,并将之前的 hello.txt 文件放入其中:

$ export BUCKET="gs://my-bucket"

所以这一次,让我们运行命令来启动我们的 Ubuntu 容器,使用 -v 参数(其中 v 表示卷),这允许我们指定文件系统位置和容器内的挂载点:

$ echo $BUCKET
gs://my-bucket

命令中的 -v ~/book_data:/home/book 部分将您指定的位置链接到 Docker 容器中 /home/book 目录的路径。路径中的 /home 是容器中已经存在的目录,而 book 部分可以是您选择的任何名称。现在,您文件系统中 book 目录中的所有内容都可以从 Docker 容器的 /home/book 目录访问:

$ gsutil cp mother_variants.200k.vcf* $BUCKET/germline-sandbox/
Copying file://mother_variants.200k.vcf [Content-Type=text/vcard]...
Copying file://mother_variants.200k.vcf.idx [Content-Type=application/octet-
stream]...
- [2 files][101.1 KiB/101.1 KiB]                                               
Operation completed over 2 objects/101.1 KiB.

在这里,我们将挂载点的名称与实际位置相同,因为这样更直观,但如果您愿意,您也可以使用不同的名称。请注意,如果您将挂载点命名为容器中已存在路径的目录或文件名称,则将“压缩”该现有路径,这意味着只要挂载点挂载,该路径将不可访问。

还有一些其他 Docker 技巧是值得知道的,但现在这已经足够演示您将在 第五章 中使用的核心 Docker 功能。

设置您自己的自定义 VM

现在,您已经成功运行了一些基本的文件管理命令,并且熟悉了与 Docker 容器的交互方式,是时候转向更大更好的事情了。Google Cloud Shell 环境非常适合快速开始一些轻量级的编码和执行任务,但为 Cloud Shell 分配的 VM 实例性能确实有限,当运行真正的 GATK 分析时将不足以胜任 第五章 中的任务。

在本节中,我们将向您展示如何使用 Google 的 Compute Engine 服务在云中设置您自己的 VM(有时称为 实例),该服务允许您选择、配置和运行任何大小的 VM。

创建和配置您的 VM 实例

首先,转到 Compute Engine 或通过左侧边栏菜单访问该页面,如 图 4-9 所示。

计算引擎菜单显示 VM 实例菜单项。

图 4-9. 计算引擎菜单显示 VM 实例菜单项。

点击此菜单中的 VM 实例链接,以查看运行镜像的概述。如果这是一个新账户,您将不会有任何运行中的实例。请注意顶部有一个“创建实例”的选项。点击它,让我们一起完成创建新 VM 的过程,仅使用您需要的资源。

接下来,在顶部菜单栏中点击“创建实例”,如 图 4-10 所示。这将打开一个配置表单,如 图 4-11 所示。

创建一个实例

图 4-10. 创建一个 VM 实例。

VM 实例配置面板。

图 4-11. VM 实例配置面板。

按照后续子节中的逐步说明配置虚拟机。有大量的选项,如果你对术语不熟悉,这个过程可能会非常令人困惑,因此我们列出了配置表单中的最简单路径,这样您就可以在本书前几章中运行所有命令练习。请确保除非您真的知道自己在做什么,否则使用与此处完全相同的设置。

给你的虚拟机命名

为您的虚拟机取一个名字,例如genomics-book,如图 4-12 所示。这个名字必须在您的项目中是唯一的,但与存储桶名字不同,它不需要在整个 GCP 中是唯一的。有些人喜欢使用他们的用户名,这样有权限访问项目的其他人可以立即识别出是谁创建了这个资源。

为你的虚拟机实例命名。

图 4-12. 为你的虚拟机实例命名。

选择一个区域(很重要!)和一个区域(不那么重要)

云有不同的物理位置。像大多数商业云服务提供商一样,GCP 在世界许多地方都有数据中心,并为您提供选择使用哪一个的选项。区域是最高级别的地理区分,其名称相当描述性(如us-west2,指的是洛杉矶的设施)。每个区域进一步分为两个或多个由单个字母(abc 等)指定的区域,这些区域对应具有自己物理基础设施的独立数据中心(电力、网络等),尽管在某些情况下它们可能共享同一建筑物。

区域和区域的系统在限制像停电这样的局部问题方面起着重要作用,所有主要的云提供商都使用这种策略的某个版本。有关此主题的更多信息,请参阅 Kyle Galbraith 关于云区域和区域(在他的情况下是 AWS)如何在僵尸启示录中发挥重要作用的这篇有趣的博客文章

注意

选择特定区域和区域为您的项目越来越有助于处理人类数据存储的监管限制,因为它允许您指定符合条件的位置来存储所有存储和计算资源。然而,世界上一些地区尚未得到云服务的良好覆盖,或者不同云服务提供商的覆盖范围不同,因此在选择提供商时可能需要考虑可用的数据中心位置。

要为您的项目选择一个区域,您可以查看可用的 Google Cloud 区域和区域的完整列表,并根据地理邻近性做出决定。或者,您可以使用在线实用程序,根据网络响应时间,测量您与每个数据中心的实际接近程度,例如http://www.gcping.com。例如,如果我们从马萨诸塞州西部的小镇桑德兰运行此测试(在表 4-1 中显示结果),我们发现从位于北弗吉尼亚州北部的us-east4区域(距离 698 公里)获得响应需要 38 毫秒,而从位于蒙特利尔的northamerica-northeast1区域(距离 441 公里)获得响应需要 41 毫秒。这表明地理接近性与网络区域接近性并不直接相关。更引人注目的是,我们发现与位于伦敦的欧洲西部 2区域(距离 5,353 公里)相比,与位于洛杉矶的美国西部 2区域(距离 4,697 公里)相比,我们相当“接近”,其响应时间为 102 毫秒和 180 毫秒。

表 4-1。来自马萨诸塞州桑德兰的地理距离和响应时间

区域 位置 距离(公里) 响应时间(毫秒)
美国东部 4 美国弗吉尼亚州北部 698 38
北美东北部 1 蒙特利尔 441 41
欧洲西部 2 伦敦 5,353 102
美国西部 2 洛杉矶 4,697 180

这将使我们回到 VM 配置。对于区域,我们将使用us-east4(北弗吉尼亚州),因为它最靠近我们中最少旅行的人(杰拉尔丁),而对于区域,我们只是随机选择us-east4-a。您需要确保根据前面的讨论选择您的区域,这对您有利(速度更快),并且在所有 60,000 注册用户同时开始通过这些练习工作的不太可能事件中,避免破坏弗吉尼亚州的一个数据中心—尽管这是测试云的宏伟“弹性”的一种方式。

选择机器类型

在这里,您可以配置即将启动的虚拟机的资源。您可以控制 RAM 和 CPU。对于某些实例类型(在“自定义”下可用),甚至可以选择具有 GPU 的 VM,用于加速某些程序。问题在于,您在此选择的内容将决定 VM 正常运行每秒钟的计费金额;机器越大越强,成本就越高。页面的右侧应显示更改机器类型时的每小时和每月成本变化。还要注意,您的计费是基于 VM 在线时间的,而不是实际使用时间。我们稍后会介绍限制成本的策略,但请牢记这一点!

在这里选择n1-standard-2;这是一台相当基础的机器,几乎不会有太多成本,如图 4-13 所示。

选择机器类型。

图 4-13. 选择机器类型。

指定一个容器?(不需要)

我们不打算填写这一部分。如果您想使用预先选择或自动生成的自定义容器镜像设置非常具体的设置,这会很有用。实际上,我们本可以为您预配置一个容器并跳过接下来的一堆设置。但这样的话,您就没有机会学习如何自己做这些事情了,对吧?所以,目前,让我们跳过这个选项吧。

自定义引导磁盘

像机器类型一样,这是另一个非常有用的设置。您可以在这里定义两件事情:您想要使用的操作系统及其版本,以及您想要的磁盘空间量。如果需要使用特定类型和版本的操作系统,前者尤为重要。当然,如果您不希望在分析过程中磁盘空间不足,后者也很重要。

默认情况下,系统提供了特定类型的 Linux 操作系统,伴随着仅有的 10 GB 磁盘空间,如图 4-14 所示。我们需要更大的磁盘空间。

选择不同的引导磁盘。

图 4-14. 选择引导磁盘大小和镜像。

要访问此设置菜单,请单击“更改”。这将打开一个新屏幕,显示预定义选项菜单。您还可以制作自己的自定义镜像,或者在Google Cloud Marketplace中找到更多镜像。

对于我们当前的目的,我们更喜欢 Ubuntu 18.04 LTS,这是 Ubuntu 的最新长期支持版本,截至本文撰写时。虽然可能没有 Ubuntu 19.04 那么前沿,但 LTS(长期支持)版本保证了其在发布后五年内会进行安全漏洞和软件包更新维护。这个 Ubuntu 镜像已经包含了我们所需的大部分内容,已准备就绪并安装好,包括各种标准的 Linux 工具和我们将非常依赖的 GCP SDK 命令行工具。

在操作系统菜单中选择 Ubuntu,然后在版本菜单中选择 Ubuntu 18.04 LTS,如图 4-15 所示。

选择基础镜像。

图 4-15. 选择基础镜像。

在表单底部,您可以更改启动盘大小(Boot disk Size),以便为自己提供更多空间。如图 4-16 所示,选择将默认的 10 GB 更改为 100 GB(我们将要处理的数据可能会占用大量空间)。根据您的数据集大小和需求,您可以进一步增加此值。虽然在启动虚拟机后您无法轻松调整它,但您可以选择在实例运行后添加块存储卷——这就像在云中插入 USB 驱动器一样。因此,如果您的磁盘空间不足,您不会陷入困境。

设置启动盘大小

图 4-16. 设置启动盘大小。

完成所有操作后,点击“选择”;这将关闭屏幕,并将您返回到实例创建表单,其中“启动盘”部分应与图 4-17 中的截图匹配。

更新后的启动盘选择

图 4-17. 更新后的启动盘选择。

在表单底部,点击“创建”。这将返回到列出 Compute Engine VM 实例的页面,包括您新创建的 VM 实例。在实例创建和启动时,您可能会看到其名称前有一个旋转图标,然后在它运行并准备好使用时会出现一个绿色圆圈和复选标记,如图 4-18 所示。

查看 VM 状态

图 4-18. 查看 VM 状态。

好了,您的 VM 已准备就绪。

登录您的 VM 使用 SSH

在运行后,有几种方式可以访问 VM,您可以在 GCP 文档中了解。我们将向您展示最简单的方法,使用 Google Cloud 控制台和内置的 SSH 终端。它几乎无懈可击:一旦在 Google Cloud 控制台中看到绿色复选标记,您只需点击 SSH 选项即可打开下拉菜单,如图 4-19 所示。选择“在浏览器窗口中打开”,几秒钟后,您应该会看到一个 SSH 终端打开到该 VM。

SSH 进入 VM 的选项

图 4-19. SSH 进入 VM 的选项。

这将打开一个新窗口,其中包含一个终端,允许您从 VM 实例内运行命令,如图 4-20 所示。建立连接可能需要一分钟时间。

VM 实例终端

图 4-20. VM 实例终端。

随意浏览并了解您全新的 VM;在接下来的几章中,您将会花费大量时间与它一起(但是,这是一种好方法)。

检查您的身份验证

您可能迫不及待地想要运行一些有趣的东西,但让我们首先确保您的帐户凭据已正确设置,以便您可以使用预安装在我们选择的镜像上的 GCP 命令行工具。在 SSH 终端中,运行以下命令:

# gatk HaplotypeCaller \
    -R ref/ref.fasta \
    -I bams/mother.bam \
    -O sandbox/mother_variants.snippet.debug.vcf \
    -bamout sandbox/mother_variants.snippet.debug.bam \
    -L 20:10,002,000-10,003,000

[1]开头的那行显示,默认情况下,GCP 让您使用一个服务帐户登录:域为@developer.gserviceaccount.com。对于在您的 VM 内运行工具来说,这没问题,但如果您想管理资源,包括将文件复制到 GCS 存储桶中,您需要以具有相关权限的帐户进行操作。可以授予此服务帐户所有您在这些练习中需要的各种权限,但这将使我们深入 GCP 帐户管理的内部,这不是我们目前想要的。因此,让我们使用在本章开始时创建项目时使用的原始帐户,因为它已经作为项目所有者具有这些权限。

要使用该帐户登录,请在提示时按 2。这将触发与程序的一些交互;GCP 将警告您在 VM 上使用个人凭据存在安全风险,因为如果您将 VM 访问权限授予他人,他们将能够使用您的凭据:

# zcat vcfs/motherSNP.vcf.gz | grep -v '##' | head -3
#CHROM  POS     ID      REF     ALT     QUAL    FILTER  INFO    FORMAT  NA12878
20      61098   .       C       T       465.13  .
AC=1;AF=0.500;AN=2;BaseQRankSum=0.516;ClippingRankSum=0.00;DP=44;ExcessHet=3.0103;FS=0.000;
MQ=59.48;MQRankSum=0.803;QD=10.57;ReadPosRankSum=1.54;SOR=0.603
GT:AD:DP:GQ:PL0/1:28,16:44:99:496,0,938
20      61795   .       G       T       2034.16 .       AC=1;AF=0.500;AN=2;
BaseQRankSum=-6.330e-01;ClippingRankSum=0.00;DP=60;ExcessHet=3.9794;FS=0.000;MQ=59.81;
MQRankSum=0.00;QD=17.09;ReadPosRankSum=1.23;SOR=0.723        
GT:AD:DP:GQ:PL 0/1:30,30:60:99:1003,0,1027

解决方案:不要共享您的个人 VM 访问权限。^(1)

如果您输入 Y 以确认,程序将提供一个链接:

AC=1;AF=0.500;AN=2;BaseQRankSum=0.516;ClippingRankSum=0.00;DP=44;ExcessHet=
3.0103;FS=0.000;MQ=59.48;MQRankSum=0.803;QD=10.57;ReadPosRankSum=1.54;SOR=0.603 

当您单击链接或将其复制并粘贴到浏览器中时,将显示 Google 登录页面。使用您用于 GCP 的相同帐户登录以获取您的身份验证代码,然后将其复制并粘贴回您的终端窗口。gcloud实用程序将确认您的登录身份,并要求您从您可以访问的项目列表中选择要使用的项目 ID。它还将提供设置您首选的计算和存储区域的选项,这应与您创建 VM 时设置的一致。如果在项目 ID 列表中看不到您期望的内容,您可以随时再次检查GCP 控制台中的资源管理页面

将书籍材料复制到您的 VM

在接下来的几章中,您将在 VM 上运行真实的 GATK 命令和工作流程,因此需要检索示例数据、源代码和一些软件包。我们将大部分内容打包在一个地方:名为genomics-in-the-cloud的 Cloud Storage 存储桶中。唯一分开的部分是源代码,我们在 GitHub 上提供。

首先,您需要使用gsutil将数据包从存储桶复制到您的 VM 中。gsutil是 GCP 存储实用程序,在本章的 Cloud Shell 部分中我们已经使用过。在您的 VM 终端窗口中,创建一个名为**book**的新目录,然后运行gsutil命令将书籍数据包复制到与您的 VM 关联的存储空间中:

# zcat vcfs/motherSNP.giab.vcf.gz | grep -v '##' | head -3
#CHROM  POS     ID      REF     ALT     QUAL    FILTER  INFO    FORMAT  INTEGRATION
20      61098   rs6078030       C       T       50      PASS
callable=CS_CGnormal_callable,CS_HiSeqPE300xfreebayes_callable;callsetnames=CGnormal,
HiSeqPE300xfreebayes,HiSeqPE300xGATK;callsets=3;datasetnames=CGnormal,HiSeqPE300x;
datasets=2;datasetsmissingcall=10XChromium,IonExome,SolidPE50x50bp,SolidSE75bp;filt=
CS_HiSeqPE300xGATK_filt,CS_10XGATKhaplo_filt,CS_SolidPE50x50GATKHC_filt;platformnames=
CG,Illumina;platforms=2GT:PS:DP:ADALL:AD:GQ    0/1:.:542:132,101:30,25:604
20      61795   rs4814683       G       T       50      PASS
callable=CS_HiSeqPE300xGATK_callable,CS_CGnormal_callable,CS_HiSeqPE300xfreebayes_
callable;callsetnames=HiSeqPE300xGATK,CGnormal,HiSeqPE300xfreebayes,10XGATKhaplo,
SolidPE50x50GATKHC,SolidSE75GATKHC;callsets=6;datasetnames=HiSeqPE300x,CGnormal,
10XChromium,SolidPE50x50bp,SolidSE75bp;datasets
=5;datasetsmissingcall=IonExome;platformnames=Illumina,CG,10X,Solid;platforms=4
GT:PS:DP:ADALL:AD:GQ    0/1:.:769:172,169:218,205:1337

这将向您的虚拟机存储复制约 10 GB 的数据,因此即使使用启用并行下载的-m标志,可能也需要几分钟时间。正如您将在稍后看到的,即使不先复制文件,也可以直接在云存储中运行一些分析命令,但我们希望在开始时尽可能简化事务。

现在,请获取来自GitHub 上公共代码库的源代码。我们将代码放在那里,因为它是一个非常流行的在版本控制下共享代码的平台,并且我们致力于为书中使用的代码提供长期维护。要在您的虚拟机上获取副本,请首先使用cd命令进入新创建的book目录,然后使用git clone命令复制仓库的内容:

# gatk VariantFiltration \
    -R ref/ref.fasta \
    -V vcfs/motherSNP.vcf.gz \
    --filter-expression "QD < 2.0" \
    --filter-name "QD2" \
    -O sandbox/motherSNP.QD2.vcf.gz

这将创建一个目录(~book/code),其中包含我们在整本书中使用的所有示例代码。不仅如此,它将被设置为一个活跃的 Git 仓库,因此您可以在代码目录中运行git pull命令来获取最新的更改,如下所示:

# zcat sandbox/motherSNP.QD2.vcf.gz | grep -v '##' | head -3
#CHROM  POS     ID      REF     ALT     QUAL    FILTER  INFO    FORMAT  NA12878
20      61098   .       C       T       465.13  PASS
AC=1;AF=0.500;AN=2;BaseQRankSum=0.516;ClippingRankSum=0.00;DP=44;
ExcessHet=3.0103;FS=0.000;MQ=59.48;MQRankSum=0.803;QD=10.57;
ReadPosRankSum=1.54;SOR=0.603GT:AD:DP:GQ:PL  0/1:28,16:44:99:496,0,938
20      61795   .       G       T       2034.16 PASS   
AC=1;AF=0.500;AN=2;BaseQRankSum=-6.330e-01;ClippingRankSum=0.00;DP=60;
ExcessHet=3.9794;FS=0.000;MQ=59.81;MQRankSum=0.00;QD=17.09;
ReadPosRankSum=1.23;SOR=0.723        GT:AD:DP:GQ:PL	0/1:30,30:60:99:1003,0,1027

现在,您应该拥有书籍代码的最新版本。要查看自原出版以来发生了什么变化,请查看代码目录中的README文本文件。

在您的虚拟机上安装 Docker

您将在虚拟机上使用 Docker,因此让我们确保您可以运行它。如果您只在终端中运行命令docker,您会收到错误消息,因为虚拟机未预安装 Docker:

# gatk VariantFiltration \
    -R ref/ref.fasta \
    -V vcfs/motherSNP.vcf.gz \
    --filter-expression "QD < 2.0 || DP > 100.0" \
    --filter-name "lowQD_highDP" \
    -O sandbox/motherSNP.QD2.DP100.vcf.gz

错误消息友好地指出了如何通过预安装的snap包解决这种情况,但我们实际上将使用一种稍微不同的方式安装 Docker:我们将从 Docker 网站下载并运行一个脚本,这将在很大程度上自动化安装过程。这样,如果您发现需要在没有内置包管理器选项的地方安装 Docker,您就会知道该怎么做。

在虚拟机上运行以下命令以安装 Docker:

[PRE30]

这可能需要一点时间才能完成,因此让我们花点时间更详细地检查命令。首先,我们使用一个便捷的小工具叫做curl(简称为Client URL)从我们提供的 Docker 网站 URL 下载安装脚本,使用几个命令参数(-sSL)指示程序跟随任何重定向链接并将输出保存为文件。然后,我们使用管道字符(|)将该输出文件传递给第二个命令sh,这意味着“运行我们刚刚给你的那个脚本”。输出的第一行告诉您它正在做什么:“执行 docker 安装脚本”(为了简洁起见,我们省略了前面输出的部分)。

当完成后,脚本将提示您在随后的示例中运行usermod命令,以便允许自己每次不使用sudo来运行 Docker 命令时,都具备这个能力。调用sudo docker可能会导致输出文件由 root 所有,使得稍后难以管理或访问它们,因此执行此步骤非常重要:

[PRE31]

这不会产生任何输出;我们将在一分钟内测试它是否正常工作。但是首先,您需要注销 VM 然后重新登录。这样做将使系统重新评估您的 Unix 组成员资格,这对于您刚刚进行的更改生效是必要的。只需在命令提示符下键入**exit**(或按 Ctrl+D):

[PRE32]

这将关闭连接到您的 VM 的终端窗口。返回 GCP 控制台,找到您的 VM 在 Compute Engine 实例列表中,然后单击 SSH 再次登录。可能感觉需要经历很多步骤,但坚持住;我们即将进入重要部分。

设置 GATK 容器镜像

当您回到 VM 后,请通过拉取 GATK 容器来测试您的 Docker 安装,这将在接下来的章节中使用:

[PRE33]

作为提醒,容器名称后面的最后一部分是版本标签,您可以更改它以获取不同于我们在此处指定的版本。请注意,如果更改版本,某些命令可能不再起作用。我们无法保证所有代码示例都能够与未来兼容,尤其是对于仍在积极开发中的新工具。如前所述,有关更新的资料,请参阅本书的 GitHub 存储库

GATK 容器镜像相当大,因此下载可能需要一些时间。好消息是,下次需要拉取 GATK 镜像(例如获取另一个版本时),Docker 将仅拉取已更新的组件,因此速度会更快。

注意

我们正在从 Google 容器库(GCR)中拉取 GATK 镜像,因为 GCR 与我们正在运行的 VM 在同一网络上,所以比从 Docker Hub 拉取更快。然而,如果您在不同的平台上工作,可能会发现从 Docker Hub 的 GATK 存储库拉取镜像更快。为此,请将镜像路径中的us.gcr.io/broad-gatk部分更改为**broadinstitute**

现在,请记住您在本章前面遵循的指令来启动一个带挂载文件夹的容器?您将再次使用它,以使book目录对 GATK 容器可访问:

[PRE34]

您现在应该能够从容器内部浏览到您在 VM 中设置的book目录。它将位于/home/book下。最后,为了再次确认 GATK 本身是否按预期工作,请尝试在运行中的容器中从命令行运行gatk命令。如果一切正常,您应该会看到一些文本输出,概述基本的 GATK 命令行语法和一些配置选项:

[PRE35]

我们会在第五章中详细讨论这一切的含义;现在,你已经设置好了用于在接下来三章中运行 GATK 工具的环境。

暂停你的虚拟机…以防止它消耗你的资金。

你刚刚设置好的虚拟机将在本书中非常方便;在接下来的几章中,你将会回到这个虚拟机进行许多练习。然而,只要它保持运行,它就会花费你信用点或实际货币。处理这个问题最简单的方法就是将其停止:在你不主动使用它时暂停它。

你可以随时重新启动它;只需一两分钟就能重新启动它,并且它将保留所有环境设置、之前运行过的历史记录以及本地存储的任何数据。请注意,即使虚拟机没有运行且你不支付虚拟机本身的费用,你仍会因为存储而支付一小笔费用。在我们看来,为了能够随时回到虚拟机并继续你的工作,这是非常值得的便利。

要停止你的虚拟机,在 GCP 控制台中,转到虚拟机实例管理页面,如前所示。找到你的实例并点击右侧的垂直三点符号以打开控件菜单,然后选择停止,如图 4-21 所示。该过程可能需要几分钟来完成,但你可以安全地离开该页面。要稍后重新启动你的实例,只需按照同样的步骤操作,但在控件菜单中点击启动。

停止、启动或删除你的虚拟机实例。

图 4-21. 停止、启动或删除你的虚拟机实例。

或者,你可以完全删除你的虚拟机,但请记住,删除虚拟机也会删除所有本地存储的数据,所以确保你首先将任何重要的东西保存到存储桶中。

配置 IGV 以从 GCS 存储桶读取数据

在你进入下一章之前,还有一个小步骤:我们将安装并配置一个名为 Integrated Genome Viewer (IGV) 的基因组浏览器,它可以直接与 GCP 中的文件一起使用。这将允许你查看序列数据和变体调用,而无需将文件复制到本地机器上。

首先,在你的本地机器上,如果尚未安装,请从IGV 网站获取程序并按照安装说明进行安装。如果你已经有一个副本,请考虑更新到最新版本;我们使用的是 2.7.2 版本(macOS 版本)。一旦你打开应用程序,选择顶部菜单栏中的 View > Preferences,如图 4-22 所示。

选择偏好设置菜单项。

图 4-22. 选择偏好设置菜单项。

在首选项面板中,选择“启用 Google 访问”复选框,点击保存,然后退出并重新打开 IGV 以强制刷新顶部菜单栏。现在您应该会看到一个之前不存在的 Google 菜单项;点击它并选择登录,如图 4-24 所示,使用您的 Google 账户凭据设置 IGV。

图 4-25. “从 URL 加载”菜单项。

注意

图 4-24. 选择 Google 登录菜单项。

选择 Google 登录菜单项。

IGV 首选项面板。

这将在您的网络浏览器中打开一个谷歌登录页面;按照提示允许 IGV 访问您谷歌账户上的相关权限。完成后,您应该会看到一个简单显示“OK”的网页。现在我们切换回到 IGV 并测试其功能是否正常。从顶部菜单选择文件 > 从 URL 加载,如图 4-25 所示,确保不要错误地选择其他选项(它们看起来很相似,很容易弄错)。同时确保 IGV 窗口左上角的参考下拉菜单已设置为“Human hg19”。

图 4-26. “从 URL 加载”对话框。

如果您对人类参考之间的区别感到困惑,请参阅关于 hg19 和 GRCh38 的“参考基因组作为共同框架”的注释。

“从 URL 加载”菜单项。

“从 URL 加载”对话框。

最后,在弹出的对话框中输入我们提供的书籍数据包中一个样本 BAM 文件的 GCS 文件路径(例如mother.bam,如图 4-26 所示),然后点击“OK”。记住,您可以使用 gsutil 在您的 VM 或云 Shell 中获取存储桶中的文件列表,或者使用Google Cloud 控制台存储浏览器浏览存储桶的内容。如果您使用浏览器界面获取文件路径,则需要通过删除 URL 的第一部分(即https://console.cloud.google.com/storage/browser)并用**gs://**替换它来组成 GCS 文件路径。对于 BAM 的伴随索引文件,文件名和路径相同,但以 .bai 结尾。^(2)

图 4-23. IGV 首选项面板。

这将打开首选项面板,如图 4-23 所示。

这将使数据作为新数据轨道在 IGV 中对您可用,但默认情况下主查看器中不会加载任何内容。要检查您是否可以查看数据,请在搜索窗口中输入基因组坐标 **20:9,999,830-10,000,170**,然后点击 Go。这些坐标将带您到第 20 条人类染色体上第 10 百万个 DNA 基地的左侧边缘 ±170,如图 4-27 所示,您将看到我们在此示例文件中提供的序列数据切片。我们在 第五章 中详细解释如何解读 IGV 的视觉输出,当我们用它来调查真实(小)分析的结果时。

位于 GCS 存储桶中的 BAM 文件的 IGV 视图。

图 4-27. 位于 GCS 存储桶中的 BAM 文件的 IGV 视图。

IGV 每次仅检索小片段的数据,因此传输速度应该非常快,除非您的互联网连接特别慢。然而,请记住,像所有商业云提供商一样,GCP 会对从云中传输数据收取出口费用。但好消息是,这是一个小费用,与您传输的数据量成比例。因此,在 IGV 中查看数据切片的成本微不足道——大约是几分钱的数量级,这绝对比为离线浏览整个文件而支付的费用要低得多!

您可以使用相同的操作集查看其他数据文件的内容,例如 VCF 文件,只要文件存储在 GCP 存储桶中。不幸的是,这意味着这不适用于存储在 VM 的本地存储中的文件,因此每当您想要检查其中一个文件时,您都需要先将其复制到存储桶中。您将很快与 gsutil 变得非常熟悉。

哦,当您打开 IGV 时,请注意最后一件事:点击 IGV 窗口工具栏中的小黄色信息气泡,该气泡控制详细查看器的行为,如图 4-28 所示。为自己做一个好事,并将设置从悬停显示详细信息更改为点击显示详细信息。您选择的任何操作都将触发一个小对话框的出现,该对话框会为您点击或悬停在数据的任何部分提供详细信息;例如,对于序列读取,它将为您提供所有映射信息以及完整的序列和碱基质量。您现在可以尝试使用刚刚加载的数据。正如您将看到的,详细显示功能本身非常方便,但当您初次接触界面时,“悬停”版本的此行为可能有些压倒性;因此,我们建议切换到“点击”版本。

将详细查看器的行为从“悬停”更改为“点击”。

图 4-28. 将详细查看器的行为从“悬停”更改为“点击”。

总结和下一步计划

在本章中,我们向您展示了如何开始使用 GCP 资源,从创建帐户、使用超级基础的 Cloud Shell,然后逐步过渡到自己的自定义 VM。您学会了如何管理 GCS 中的文件,运行 Docker 容器,并管理您的 VM。最后,您获取了书籍数据和源代码,完成了设置自定义 VM 以与 GATK 容器配合工作,并设置了 IGV 以查看存储在存储桶中的数据。在第五章中,我们将带您开始使用 GATK 本身,在您意识到之前,您将在云中的示例数据上运行真正的基因组学工具。

^(1) 请记住,如果您为 GCP 项目中的其他用户创建帐户,他们也将能够通过 SSH 访问您的 VM。在共享项目中进一步限制对 VM 的访问是可能的,但这超出了我们在此简单介绍的范围。

^(2) 例如,https://console.cloud.google.com/storage/browser/genomics-in-the-cloud/v1/data/germline/bams/mother.bam 变成 gs://genomics-in-the-cloud/v1/data/germline/bams/mother.bam

第八章:使用工作流自动化分析执行

到目前为止,我们一直在终端手动运行单个命令。然而,大多数基因组学工作实际上—即从原始数据到我们将进一步用于下游分析以最终产生生物学见解的浓缩信息的次生分析—涉及按照相同的顺序在所有数据上运行相同的命令。再次看看第 6 和 7 章描述的 GATK 最佳实践工作流程;想象一下为每个样本手动完成所有这些工作会有多么乏味。该领域的新手通常发现,逐步运行测试数据中涉及的命令有助于了解关键步骤、其要求及其特点—这就是为什么我们在第 6 和 7 章中向您详细介绍了这些内容—但归根结底,您希望尽可能自动化所有这些过程。自动化不仅减少了您需要做的繁琐手动工作量,还提高了分析的吞吐量,并减少了人为错误的机会。

在本章中,我们讨论了从单个命令或一次性脚本转向可重复使用的工作流的转变。我们向您展示了如何使用工作流管理系统(Cromwell)和语言 WDL,这两者主要因其可移植性而被选择。我们将引导您编写第一个示例工作流,执行它,并解释 Cromwell 的输出。然后,我们将使用几个更加现实的工作流再做同样的事情,运行 GATK 并使用散集-聚集并行处理。

介绍 WDL 和 Cromwell

您可能还记得,我们在技术概述中引入了工作流的概念(第 3 章)。当时我们指出,有许多工具选项可用于编写和运行工作流,具有各种功能、优势和劣势。我们不能告诉您整体上最佳的选择,因为很大程度上取决于您的特定背景和需求。出于本书的目的,我们选择了 Cromwell 和 WDL 的组合,因为它最适合我们面向的广泛受众和需求范围。

在此之前我们已经简要介绍了 WDL,但考虑到这已经是半本书之前的事情,也经历了许多变故,让我们重新概述一下主要内容。正如刚才提到的,它的全名是Workflow Description Language,但通常简称为 WDL(发音为“widdle”)。它是一种 DSL,通常用于基因组工作流程,在 Broad Institute 最初开发,后来演变为由名为OpenWDL的公共团体推动的社区驱动开源项目。作为工作流语言,它被设计成非常适合生物信息学新手,这些人没有软件编程的正式培训,同时最大限度地提高在不同系统间的可移植性。

我们将用来实际运行WDL 编写的工作流的工作流管理系统是 Cromwell,这是一款在 Broad Institute 开发的开源应用程序。Cromwell 设计为能够在几乎任何现代 Java 支持的计算环境中运行,包括像 Slurm 和 SGE 这样的流行 HPC 系统,商业云平台如 GCP 和 AWS,以及运行某种 Linux 版本的任何笔记本电脑或台式电脑。这种可移植性是 Cromwell 的核心特性,旨在促进在多个机构运行相同工作流,以最大化科学研究中的协作和计算重现性。Cromwell 另一个面向可移植性的主要特性是,它支持(但不需要)使用容器来提供您想要在给定工作流的每个组件任务中运行的代码。您将有机会在本章的练习中尝试两种运行方式。

最后,在合作和互操作性精神的继续中,Cromwell 还设计支持多种工作流语言。目前,它支持 WDL 以及 CWL,另一种设计用于可移植性和计算可重现性的流行工作流语言。

Cromwell 提供两种操作模式:一次性运行模式和服务器模式。一次性运行模式是运行 Cromwell 的简单方法,涉及单个命令行:您提供一个工作流和一个列出工作流输入的文件,它将开始运行,执行工作流,并在工作流完成时关闭。这对于间断运行工作流非常方便,几乎没有麻烦,这也是我们在本章练习中使用的方式。服务器模式的操作涉及设置一个持久运行的服务器,并通过 REST API(一种编程接口)向该服务器提交工作流执行请求。

启动 Cromwell 服务器非常容易。一旦运行,它提供的功能在单次运行模式下是不可用的,其中一些我们在第十一章中介绍。然而,要安全地管理其运行,需要具备专门的技能,这是大多数个人研究人员或没有专门支持人员的小团体所不具备的。在第十一章中,我们向您介绍 Terra,这是由 Broad Institute 操作的托管系统,通过 GUI 和 API 提供对持久 Cromwell 服务器的访问。这将使您有机会在服务器模式下尝试 Cromwell,而无需自己管理服务器。

注意

我们不会在本书中涵盖 Cromwell 服务器管理内容,如果您有兴趣了解更多信息,请参阅Cromwell 文档

无论您将其作为一次性任务还是在服务器模式下运行,Cromwell 都具有旨在提高效率和可伸缩性的有趣功能,但没有人愿意在这本书中读取一长串功能清单,因此让我们转向练习,并在适当的时候提到这些关键功能。

安装和设置 Cromwell

在本章中,我们将检查和执行一些用 WDL 编写的工作流程,以熟悉语言的基本结构,并了解 Cromwell 如何管理输入和输出、日志等。为了与前几章保持连贯性,我们在之前使用的 GCP Compute Engine VM 上运行 Cromwell,即在第四章和第五章中使用过的环境,不再从 GATK 容器内运行任何内容。相反,我们直接在 VM 环境中安装和运行 Cromwell。

您需要运行几个安装命令,因为 Cromwell 需要 Java,而我们使用的虚拟机上没有预安装。为此,请通过 SSH 再次登录到您的虚拟机,就像在前几章中所做的那样。请记住,您始终可以在 GCP 控制台中找到您的 VM 实例列表,直接访问Compute Engine,或者在控制台左侧的 GCP 服务菜单中点击 Compute Engine,如果您忘记了 URL。

在您的虚拟机中,在提示符下键入 **java -version**。您应该会得到以下输出:

$ java -version
Command ’java’ not found, but can be installed with:
apt install openjdk-11-jre-headless  # version 11.0.3+7-1ubuntu2~19.04.1, or
apt install default-jre              # version 2:1.11-71
apt install openjdk-8-jre-headless   # version 8u212-b03-0ubuntu1.19.04.2
apt install openjdk-12-jre-headless  # version 12.0.1+12-1
apt install openjdk-13-jre-headless  # version 13~13-0ubunt1
Ask your administrator to install one of them.

Cromwell 需要 Java 版本 8,因此让我们安装 openjdk-8-jre-headless 选项,这是一个轻量级环境,足以满足我们的需求:

$ sudo apt install openjdk-8-jre-headless
Reading package lists... Done
Building dependency tree      
Reading state information... Done
[...]
done.

这将触发安装过程,应该可以顺利完成。您可能会看到一些通知,但只要看到最终的 done 输出,您就应该没问题。您可以再次运行 Java 版本检查以确保安装成功:

$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-8u222-b10-1ubuntu1~19.04.1-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)

安装了 Java 后,让我们设置 Cromwell 本身,它附带一个称为 Womtool 的伴侣实用程序,用于语法验证和创建输入文件。它们都作为编译后的 .jar 文件分发,并且我们已经在书籍捆绑包中包含了一份副本,因此你不需要做任何复杂的操作,只需指向它们所在的位置即可。为了使我们的命令尽可能简洁,让我们设置一个指向它们位置的环境变量。我们称之为 BIN 用来表示程序的编译形式:

$ export BIN=~/book/bin

让我们检查一下是否可以运行 Cromwell,通过请求它的 help 输出,这将呈现出你可以给它的三个命令的摘要:serversubmit 是我们之前讨论的服务器模式的一部分,而 run 是我们很快将使用的一次性模式:

$ java -jar $BIN/cromwell-48.jar --help
cromwell 48
Usage: java -jar /path/to/cromwell.jar [server|run|submit] [options] <args>...
 --help                   Cromwell - Workflow Execution Engine
 --version               
Command: server
Starts a web server on port 8000\.  See the web server documentation for more 
details about the API endpoints.
Command: run [options] workflow-source
Run the workflow and print out the outputs in JSON format.
 workflow-source          Workflow source file or workflow url.
 --workflow-root <value>  Workflow root.
 -i, --inputs <value>     Workflow inputs file.
 -o, --options <value>    Workflow options file.
 -t, --type <value>       Workflow type.
 -v, --type-version <value>
                          Workflow type version.
 -l, --labels <value>     Workflow labels file.
 -p, --imports <value>    A directory or zipfile to search for workflow imports.
 -m, --metadata-output <value>
                          An optional directory path to output metadata.
Command: submit [options] workflow-source
Submit the workflow to a Cromwell server.
 workflow-source          Workflow source file or workflow url.
 --workflow-root <value>  Workflow root.
 -i, --inputs <value>     Workflow inputs file.
 -o, --options <value>    Workflow options file.
 -t, --type <value>       Workflow type.
 -v, --type-version <value>
                          Workflow type version.
 -l, --labels <value>     Workflow labels file.
 -p, --imports <value>    A directory or zipfile to search for workflow imports.
 -h, --host <value>       Cromwell server URL.

对于 Womtool,也值得做同样的事情,以了解各种可用的实用命令:

$ java -jar $BIN/womtool-48.jar --help
Womtool 48
Usage: java -jar Womtool.jar
[validate|inputs|parse|highlight|graph|upgrade|womgraph] [options]
workflow-source
 workflow-source          Path to workflow file.
 -i, --inputs <value>     Workflow inputs file.
 -h, --highlight-mode <value>
                          Highlighting mode, one of 'html', 'console'
(used only with 'highlight' command)
 -o, --optional-inputs <value>
                          If set, optional inputs are also included in the
inputs set. Default is 'true' (used onl
y with the inputs command)
 --help                  
 --version               
Command: validate
Validate a workflow source file. If inputs are provided then 'validate'
also checks that the inputs file is a valid set of inputs for the
workflow.
Command: inputs
Generate and output a new inputs JSON for this workflow.
Command: parse
(Deprecated; WDL draft 2 only) Print out the Hermes parser’s abstract
syntax tree for the source file.
Command: highlight
(Deprecated; WDL draft 2 only) Print out the Hermes parser’s abstract
syntax tree for the source file. Requires at least one of 'html' or 'console'
Command: graph
Generate and output a graph visualization of the workflow in .dot format
Command: upgrade
Automatically upgrade the WDL to version 1.0 and output the result.
Command: womgraph
(Advanced) Generate and output a graph visualization of Cromwell’s
internal Workflow Object Model structure for this workflow in .dot format

在列出的功能中,你将有机会在本章中使用 inputsvalidategraph

现在让我们检查一下,你是否已经拥有本章所提供的所有工作流文件。如果你按照第四章中的设置说明操作,你应该有一个从 GitHub 克隆的代码目录。在 ~/book/code 下,你会看到一个名为 workflows 的目录,其中包含本章中你将使用的所有代码和相关文件(除了来自存储桶的数据)。你将从主目录运行命令(而不是像在早期章节中那样进入子目录),因此为了在各种命令中保持路径简短,让我们设置一个环境变量,指向工作流文件的位置:

$ export WF=~/book/code/workflows

最后,让我们谈谈文本编辑器。在接下来的练习中,除了一个例外,你只需查看和运行我们提供的预写脚本,因此,你可以下载或克隆这些文件到你的笔记本电脑,并在你喜欢的文本编辑器中打开它们来进行查看。在一个例外情况下,我们建议你修改一个 WDL 文件以破坏它,以便查看 Cromwell 的错误消息和处理行为,因此你确实需要编辑这个文件。我们会展示如何使用一个称为 nano 的内置 shell 文本编辑器来完成这个操作,这被认为是对不习惯使用命令行文本编辑器的人们最为可访问的选择。当然,如果你更喜欢使用 viemacs 等其他 shell 编辑器,你可以自行根据我们提供的命令进行调整。

无论你决定使用什么样的文本编辑器,请确保不要使用像 Microsoft Word 或 Google Docs 这样的文字处理器。这些应用程序可能会引入隐藏字符,因此不适合编辑代码文件。有了这些都明了,让我们扎紧安全带,开始处理你的第一个 WDL 工作流。

你的第一个 WDL:Hello World

我们从 WDL 脚本中最简单可行的工作示例开始:典型的HelloWorld。如果你对此不熟悉,这在编程语言的文档中是一个常见的表达方式;简而言之,这个想法是提供一个最小代码量的介绍性示例,能够产生HelloWorld!这个短语。实际上,我们将运行三个基本的 WDL 工作流程来展示这种功能级别,首先是绝对最小的示例,然后添加足够的代码来展示核心功能,这些功能在技术上不是必需的,但在实际使用中是需要的。

通过极简示例学习基本的 WDL 语法

让我们通过将hello-world.wdl工作流文件加载到nano编辑器中来查看最简单的示例:

$ nano $WF/hello-world/hello-world.wdl

正如前面提到的,nano是一个基本的编辑器。你可以使用键盘上的箭头键在文件中移动。要退出编辑器,请按 Ctrl+X。

这就是 WDL 中最简化的 Hello World 的样子:

version 1.0

workflow HelloWorld {
  call WriteGreeting
}

task WriteGreeting {
  command {
     echo "Hello World"
  }
  output {
     File output_greeting = stdout()
  }
}

首先,让我们忽略除了那一行中有HelloWorld短语的引号中的内容以外的一切。你认出那行命令了吗?没错,这是一个简单的echo命令;你现在可以在你的终端中运行这一行:

$ echo "Hello World"
Hello World

所以这就是我们的脚本核心中执行所需操作的命令,其他所有内容都是为了使其能够通过我们的工作流管理系统以脚本形式运行。

现在让我们详细解释这个包装。在最高级别上,我们有两个不同的段落或代码块:一个以workflow HelloWorld开头,另一个以task WriteGreeting开头,在每种情况下的大括号之间有几行代码(WDL 的原始设计者非常喜欢大括号;你会看到更多这样的情况)。我们可以这样总结它们:

workflow HelloWorld {...}

task WriteGreeting {...}

这使得我们的脚本结构变得非常清晰:workflow块中我们调用希望工作流程执行的动作,而task块中我们定义动作细节。这里我们只有一个任务,这在实际情况中并不典型,因为大多数工作流包含两个或更多任务;我们将在本节进一步讨论包含多个任务的工作流。

让我们更仔细地看一下在WriteGreeting任务中如何定义行动(即命令):

task WriteGreeting {
  command {
     echo "Hello World"
  }
  output {
     File output_greeting = stdout()
  }
}

在第一行,我们声明这是一个名为WriteGreeting的任务。在最外层的花括号内部,我们可以将代码的结构分解为另外两个代码块:command {...}output {...}command块非常简单:它包含echo "Hello World"命令。所以这是相当容易理解的,对吧?一般来说,你可以在这里放置几乎任何你在终端 shell 中运行的内容,包括管道命令、多行命令,甚至像 Python 或 R 这样的“外来”代码块,只要你用heredoc 语法进行包装。我们在第九章中提供了这种语法的示例。

与此同时,output块可能稍显不明显。这里的目标是定义我们计划运行的command块的输出。我们声明我们期望的输出将是一个File,我们选择称为output_greeting(此名称可以是任何你想要的,除了 WDL 规范中定义的保留关键字之一)。然后,在稍微棘手的部分中,我们声明输出内容本身将是发到stdout的任何内容。如果你对命令行术语不是很熟悉,stdoutstandard out的缩写,指的是输出到终端窗口的文本输出,也就是运行命令时在终端看到的内容。默认情况下,此内容也会保存到执行目录中的文本文件中(稍后我们会查看),因此这里我们表明我们指定该文本文件作为我们命令的输出。在基因组工作流程中这样做并不是一个非常现实的事情(尽管你可能会感到惊讶…我们见过更奇怪的事情),但这就是 Hello World 的典型案例!

总之,这就是我们的task块的解释。现在,让我们来看看workflow块:

workflow HelloWorld {
  call WriteGreeting
}

好吧,这很简单。首先,我们声明我们的工作流程名称为HelloWorld,然后在花括号内部,我们使用call语句调用WriteGreeting任务。这意味着当我们通过 Cromwell 实际运行工作流程时,它将尝试执行WriteGreeting任务。让我们试试吧。

在您的 Google 虚拟机上使用 Cromwell 运行一个简单的 WDL

退出nano编辑器,按下 Ctrl+X 键返回到虚拟机的 shell 界面。你将使用位于~/book/bin目录下的 Cromwell .jar文件来启动hello-world.wdl工作流程,在本章的设置部分,我们将其别名为$BIN。这个命令是一个简单的 Java 命令:

$ java -jar $BIN/cromwell-48.jar run $WF/hello-world/hello-world.wdl

此命令调用 Java 来运行 Cromwell,使用其一次性(运行)工作流执行模式,这与本章前面介绍的持续server模式形成对比。因此,它只会启动,运行我们提供给run命令的工作流,并在完成后关闭。目前为止,没有其他内容涉及,因为我们的工作流完全是自包含的;接下来我们将讨论如何将工作流参数化以接受输入文件。

继续运行该命令。如果您已正确设置一切,您现在应该看到 Cromwell 开始向终端输出大量信息。我们在这里展示了输出的最相关部分,但我们省略了一些不相关的块(由[...]表示):

[...]  
[2018-09-08 10:40:34,69] [info] SingleWorkflowRunnerActor: Workflow submitted
b6d224b0-ccee-468f-83fa-ab2ce7e62ab7  
[...]  
Call-to-Backend assignments: HelloWorld.WriteGreeting -> Local
[2018-09-08 10:40:37,15] [info] WorkflowExecutionActor-b6d224b0-ccee-468f-83fa-
ab2ce7e62ab7 [b6d224b0]: Starting HelloWorld.WriteGreeting
[2018-09-08 10:40:38,08] [info] BackgroundConfigAsyncJobExecutionActor
[b6d224b0HelloWorld.WriteGreeting:NA:1]: echo "Hello World"
[2018-09-08 10:40:38,14] [info] BackgroundConfigAsyncJobExecutionActor 
[...]  
[2018-09-08 10:40:40,24] [info] WorkflowExecutionActor-b6d224b0-ccee-468f-83fa-
ab2ce7e62ab7 [b6d224b0]: Workflow HelloWorld complete. Final Outputs:
{  
  "HelloWorld.WriteGreeting.output_greeting": "/home/username/cromwell-
executions/HelloWorld/b6d224b0-ccee-468f-83fa-ab2ce7e62ab7/call-
WriteGreeting/execution/stdout"  
}  
[2018-09-08 10:40:40,28] [info] WorkflowManagerActor WorkflowActor-b6d224b0-ccee-
468f-83fa-ab2ce7e62ab7 is in a terminal state: WorkflowSucceededState
[2018-09-08 10:40:45,96] [info] SingleWorkflowRunnerActor workflow finished with
status ’Succeeded’.
[...]  
[2018-09-08 10:40:48,85] [info] Shutdown finished.

正如您所看到的,Cromwell 的标准输出有点……嗯,冗长。Cromwell 主要设计用于作为一组相互连接的服务的一部分使用,我们在第十一章中讨论了这一点,在那里有一个专门的界面用于监视进度和输出。单次运行模式更常用于故障排除,因此开发团队选择使本地执行模式非常健谈,以帮助调试。这起初可能会让人感到有点不知所措,但别担心:我们在这里将向您展示如何解读这一切——或至少我们关心的部分。

解释 Cromwell 日志输出的重要部分

首先,让我们检查我们的工作流的输出是否符合预期。在终端输出中找到以下一组行:

WorkflowExecutionActor-b6d224b0-ccee-468f-83fa-ab2ce7e62ab7 [b6d224b0]: Workflow
HelloWorld complete. Final Outputs:
{  
  "HelloWorld.WriteGreeting.output_greeting": "/home/username/cromwell-
executions/HelloWorld/b6d224b0-ccee-468f-83fa-ab2ce7e62ab7/call-
WriteGreeting/execution/stdout"
}  

还没有详细讨论,但我们可以看到,这以 JSON 格式提供了生成的输出文件列表;在这种情况下,只有一个文件捕获了我们的一个echo "Hello World"命令的stdout。Cromwell 为我们提供了完全限定的路径,这意味着它包括了工作目录上面的目录结构,这真的很方便,因为它允许我们在任何需要的命令中快速复制和粘贴使用。您现在可以这样做,查看输出文件的内容,并验证它是否包含我们期望的内容:

注意

请记住,在我们展示的命令中,您需要替换用户名和执行目录哈希。查找您输出中相应的行可能比定制我们的命令更容易。

$ cat ~/cromwell-executions/HelloWorld/b6d224b0-ccee-468f-83fa-
ab2ce7e62ab7/call-WriteGreeting/execution/stdout
Hello World

就是这样!所以我们知道它起作用了。

现在让我们花几分钟时间逐步查看 Cromwell 在所有这些日志输出中提供给我们的信息,以识别最相关的要点:

SingleWorkflowRunnerActor: Workflow submitted b6d224b0-ccee-468f-83fa-ab2ce7e62ab7

= 我正在查看这个工作流并为其分配一个独特的标识符。

Cromwell 为每个工作流运行的每个运行分配一个随机生成的唯一标识符,并在该标识符内创建一个目录,所有中间文件和最终文件都将写入其中。稍后我们将详细讨论输出目录结构的细节。现在,你真正需要知道的是,这旨在确保你永远不会覆盖相同工作流之前运行的结果,也不会在具有相同名称的不同工作流之间发生冲突:

Call-to-Backend assignments: HelloWorld.WriteGreeting -> Local 

= 我计划将其发送到本地机器进行执行(而不是远程服务器)。

默认情况下,Cromwell 直接在您的本地机器上运行工作流;例如,您的笔记本电脑。正如我们之前提到的,您可以配置它将作业发送到远程服务器或云服务;这在 Cromwell 的术语中称为后端分配(不要与换尿布搞混):

Starting HelloWorld.WriteGreeting

= 我现在执行WriteGreeting任务调用HelloWorld工作流。

Cromwell 将工作流中的每个任务调用视为单独的作业进行执行,并会相应地为每个作业提供更新。如果工作流涉及多个任务调用,Cromwell 会将它们组织在队列中,并在适当时发送每个任务进行执行。我们稍后会讨论一些相关的工作原理。关于状态报告方面,可以想象,一旦我们开始运行更复杂的工作流,通过标准输出获取这些报告就显得不太实际。这就是提供界面以解析和组织所有这些信息的前端软件真正派上用场的地方;你将有机会在第十一章中体验到这一点:

[b6d224b0HelloWorld.WriteGreeting:NA:1]: echo "Hello World"

= 这是我在此调用中实际运行的命令。

在我们的最小化 Hello World 示例中并未包含任何变量,因此从这个特定调用中并不明显,但 Cromwell 在此输出的是将要执行的真实命令。在接下来的参数化示例中,你可以看到,如果在脚本中包含变量,日志输出将显示命令的形式,其中变量已由我们提供的输入值替换。这个完全解释的命令也会输出到执行目录以备记录:

[b6d224b0]: Workflow HelloWorld complete. Final Outputs:
{
"HelloWorld.WriteGreeting.output_greeting": "/home/username/cromwell-
executions/HelloWorld/b6d224b0-ccee-468f-83fa-ab2ce7e62ab7/call-
WriteGreeting/execution/stdout"
}

= 我已经完成了这个工作流的运行。这是你想要的输出文件的完整路径。

正如前面所述,以 JSON 格式提供了生成的所有输出文件的完全命名空间列表。命名空间

HelloWorld.WriteGreeting.output_greeting 

告诉我们,我们正在查看HelloWorld工作流中属于WriteGreeting任务调用输出的output_greeting

输出文件的完全限定路径显示整个目录结构;让我们展开来看看每个部分对应的内容:

~                                       (working directory)
cromwell-executions/                    (Cromwell master directory)
 HelloWorld                             (name of our workflow)
  b6d224b0-ccee-468f-83fa-ab2ce7e62ab7  (unique identifier of the run)
   call-WriteGreeting                   (name of our task call)
    execution                           (directory of execution files)

这个结构中的重要部分是工作流/标识符/调用的嵌套。正如你将在下一个练习中看到的那样,任何以相同名称运行的工作流都将添加到HelloWorld工作流目录下,一个新的带有另一个唯一标识符的目录中。

SingleWorkflowRunnerActor workflow finished with status 'Succeeded'.

= 哟,一切都运行正常!

[2018-09-08 10:40:48,85] [info] Shutdown finished.

= 我在这里完成了。再见。

到此时,这就是你需要关心的全部内容,完成了你的第一个 Cromwell 工作流执行。做得好!

添加变量并通过 JSON 提供输入

好的,但是运行一个完全独立的 WDL 是不现实的,所以让我们看看如何添加变量以引入一些可以从运行到运行改变的外部输入。在nano编辑器中,打开代码目录中的hello-world-var.wdl

$ nano $WF/hello-world/hello-world-var.wdl

有什么不同?workflow块完全相同,但是WriteGreeting task块中现在有更多内容:

task WriteGreeting {

  input {
      String greeting
  }

  command {
     echo "${greeting}"
  }

  output {
     File output_greeting = stdout()
  }
}

Hello World输入到echo命令的${greeting}已被${greeting}替换,并且我们现在在command块之前有一个新的input块,其中包含String greeting这行。这行声明了名为greeting的变量,并说明其值应为String类型;换句话说,是一个字母数字序列。这意味着我们已经对将要回显到终端的问候语进行了参数化;我们将能够指示 Cromwell 在每次运行时将什么插入命令中。

这引出了下一个问题:我们如何向 Cromwell 提供该值?我们绝对不希望直接在命令行上给出它,因为虽然这个特定案例很简单,但在将来,我们可能需要运行期望包含数十个值的工作流,其中许多比一个简单的String更复杂。

Cromwell 期望您以JavaScript 对象表示法(JSON)文本格式提供输入。JSON 具有键值对结构,允许我们为每个变量分配一个值。您可以在我们提供的$WF/hello-world/hello-world.inputs.json文件中看到一个例子:

{
 "HelloWorld.WriteGreeting.greeting": "Hello Variable World"
}

在这个简单的inputs JSON 文件中,我们通过其完全限定名称定义了我们HelloWorld工作流中的greeting变量,该名称包括工作流本身的名称(HelloWorld),然后是任务的名称(WriteGreeting),因为我们在任务级别声明了变量,然后是变量本身的名称。

要将inputs JSON 文件提供给 Cromwell,只需通过使用-i参数(简写为--input)添加到您的 Cromwell 命令中,如下所示:

$ java -jar $BIN/cromwell-48.jar run $WF/hello-world/hello-world-var.wdl \
-i $WF/hello-world/hello-world.inputs.json

以与您之前相同的方式查找输出;您应该看到由工作流输出的文件中的消息与 JSON 文件中的文本匹配。

Cromwell 在所有级别强制使用完全限定名称,这使得声明全局变量成为不可能。虽然这可能感觉像一种繁琐的约束,但比起另一种选择要安全得多,因为这意味着您可以在工作流的不同部分中使用相同名称的变量而不会造成冲突。在简单的工作流中,很容易跟踪变量并防止此类问题,但在具有数十个以上变量的更复杂的工作流中,这可能变得非常困难。特别是当您使用导入和子工作流来促进代码重用时,我们在第九章中详细介绍(哦,剧透)。请注意,您可以在工作流级别声明变量(并在inputs JSON 文件中使用输入命名语法WorkflowName.variable),但必须明确地将其传递给任何您想使用它的任务调用。稍后在本章中,您将看到此操作的示例。

添加另一个任务,使其成为一个适当的工作流程

现实世界的工作流通常有多个任务,并且其中一些任务依赖于其他任务的输出。在nano编辑器中,打开hello-world-again.wdl

$ nano $WF/hello-world/hello-world-again.wdl

这是我们第三次尝试编写一个 Hello World 示例,展示了两个任务链接成一个正确的工作流:

version 1.0

workflow HelloWorldAgain {

  call WriteGreeting

  call ReadItBackToMe {
     input:
        written_greeting = WriteGreeting.output_greeting
  }

  output {
     File outfile = ReadItBackToMe.repeated_greeting
  }
}

task WriteGreeting {

  input { 
     String greeting
  }

  command {
     echo "${greeting}"
  }
  output {
     File output_greeting = stdout()
  }
}

task ReadItBackToMe {

  input {
     String = read_string(written_greeting)
  }

  command {
     echo "${original_greeting} to you too"
  }
  output {
     File repeated_greeting = stdout()
  }
}

您可以看到workflow块现在有更多内容;它具有指向新任务ReadItBackToMe的附加调用语句,并且该调用语句附有一些代码,放在花括号中,我们将其称为input块:

  call ReadItBackToMe {
     input:
        written_greeting = WriteGreeting.output_greeting
  }

input块允许我们从工作流级别传递值到特定任务调用。在这种情况下,我们引用WriteGreeting任务的输出,并将其分配给名为written_greeting的变量,以便在ReadItBackToMe调用中使用。

让我们来看看这个新任务定义:

task ReadItBackToMe {

  input {
    File written_greeting
  }

  String greeting = read_string(written_greeting)

  command {
     echo "${greeting} to you too"
  }
  output {
     File repeated_greeting = stdout()
  }

read_string()部分是 WDL 标准库中的一个函数,用于读取文本文件的内容并以单个字符串的形式返回。因此,此任务旨在将文件的内容读入String变量中,然后使用该变量来组成新的问候语并将其回显到stdout

有鉴于此,附加到ReadItBackToMe调用语句的额外代码显得非常合理。我们正在调用ReadItBackToMe任务,并指定我们用于编写新问候语的输入文件应该是调用Write​Greeting任务的输出。

最后,让我们看看在这个工作流程的新版本中我们尚未检查的最后一块代码:

  output {
     File outfile = ReadItBackToMe.repeated_greeting
  }

这个工作流在工作流级别定义了一个output块,除了各个任务级别的output块。当工作流是独立运行时,这个工作流级别的输出定义是完全可选的;这更多是一种惯例而不是功能。通过定义工作流级别的输出,我们传达了我们关心工作流产生的哪些输出。话虽如此,你可以利用这个输出定义进行功能性目的;例如,当工作流将被用作嵌套子工作流时,我们需要将其输出传递给进一步的调用。你将在第九章中看到这一点。现在,尝试使用与之前工作流相同的输入 JSON 运行此工作流,然后查看执行目录,看看任务目录之间的关系以及输出位于何处。

你的第一个 GATK 工作流:Hello HaplotypeCaller

现在你已经牢固掌握了基本的 WDL 语法,让我们转向更现实的一组示例:实际的 GATK 流水线!我们从一个非常简单的工作流开始,以逐渐建立你对语言的熟悉度。我们希望一个工作流在单个样本 BAM 文件上线性(无并行化)运行 GATK HaplotypeCaller,以 GVCF 模式运行,如图 8-1 所示。

一个运行 HaplotypeCaller 的假设工作流的概念图。

图 8-1. 一个运行 HaplotypeCaller 的假设工作流。

工作流应该接受通常所需的文件——基因组参考、输入读取和要分析的区间文件(在 GATK 看来技术上是可选的,但在这里我们通过 WDL 将其设为必需)——并输出一个根据输入文件命名的 GVCF 文件。

探索 WDL

为了说明所有这些,我们组合了一个通过单个任务HaplotypeCallerGVCF实现这些要求的 WDL 工作流。现在在nano编辑器中打开它:

$ nano $WF/hello-hc/hello-haplotypecaller.wdl

让我们逐步浏览脚本的主要部分,回顾我们的 HelloWorld 示例的结构:

version 1.0

workflow HelloHaplotypeCaller {

    call HaplotypeCallerGVCF
}

task HaplotypeCallerGVCF {

 input {
  		String docker_image
        String java_opt

        File ref_fasta
        File ref_index
        File ref_dict
        File input_bam
        File input_bam_index
        File intervals
    }

    String gvcf_name = basename(input_bam, ".bam") + ".g.vcf"

    command {
        gatk --java-options ${java_opt} HaplotypeCaller \
            -R ${ref_fasta} \
            -I ${input_bam} \
            -O ${gvcf_name} \
            -L ${intervals} \
            -ERC GVCF
    }

    output {
        File output_gvcf = "${gvcf_name}"
    }

    runtime {
        docker: docker_image
    }

}

为了清晰起见,将任务折叠起来,你会看到这确实是一个单任务工作流,只有一个单独的调用,workflow块中没有其他内容:

workflow HelloHaplotypeCaller {

    call HaplotypeCallerGVCF
}

task HaplotypeCallerGVCF { … }

所以让我们更详细地看一下HaplotypeCallerGVCF任务,从command块开始,因为最终我们将从中获取关于任务实际操作的大部分信息:

command {
        gatk --java-options ${java_opt} HaplotypeCaller \
            -R ${ref_fasta} \
            -I ${input_bam} \
            -O ${gvcf_name} \
            -L ${intervals} \
            -ERC GVCF
    }

我们看到一个经典的 GATK 命令,调用HaplotypeCaller以 GVCF 模式运行。它使用占位符变量来表示预期的输入文件以及输出文件。它还包括一个占位符变量,用于传递 Java 选项,例如内存堆大小,如第五章所述。到目前为止,这相当简单明了。

所有这些变量都应该在某个地方定义,所以让我们寻找它们。通常,在 command 块之前,在任务描述的开头进行这样的操作。这就是我们在那里看到的内容:

input {
        String docker_image
        String java_opt

        File ref_fasta
        File ref_index
        File ref_dict
        File input_bam
        File input_bam_index
        File intervals
    }

    String gvcf_name = basename(input_bam, ".bam") + ".g.vcf"

现在先忽略 String docker_image 行,这显示我们声明了所有输入文件的变量以及 Java 选项,但是我们没有为它们赋值。因此,任务在运行时将期望接收这些变量的值。不仅如此,它还将期望接收我们通常认为理所当然的所有辅助文件的值:refIndexrefDictinputBamIndex,它们指的是索引和序列字典。我们没有在命令本身中包含这些文件,因为 GATK 会自动检测它们的存在(只要它们的名称符合主文件的格式约定),但我们确实需要通知 Cromwell 它们的存在,以便在运行时执行时使它们可用。

不过,有一个例外;对于输出文件,我们看到了这一行:

String gvcf_name = basename(input_bam, ".bam") + ".g.vcf"

basename(input_bam, ".bam") 函数是 WDL 标准库中的一个便利函数,允许我们根据输入文件的名称创建输出文件的名称。 basename() 函数接受输入文件的完整路径,剥离文件名前面的路径部分,并可选择剥离文件名末尾的特定字符串。在这种情况下,我们剥离了期望的 .bam 扩展名,然后使用行中的 + ".g.vcf" 部分添加新的扩展名,以便于输出文件。

谈到输出文件,现在让我们跳到任务级别的 output 块:

output {
    File output_gvcf = "${gvcf_name}"
}

这也很直接;我们说明了命令将生成一个我们关心的输出文件,为了在工作流中处理它,给它命名,并提供相应的占位符变量,以便 Cromwell 在命令运行完成后能够识别正确的文件。

从技术上讲,如果你计划在具有所需程序的本地安装系统上运行此流程,这就是工作流和任务定义中所需的全部内容。然而,在这一章节中,你是在 VM 中工作,但在 GATK 容器外运行,因此 GATK 并不直接对你的工作流可用。幸运的是,Cromwell 能够利用 Docker 容器,因此我们只需要在工作流中添加一个 runtime 块来指定一个容器映像:

runtime {
    docker: docker_image
}

这就是为什么在我们的任务变量中有 String docker_image 行的原因:我们还使用了一个容器映像的占位符变量。在下一步中填写输入 JSON 时,我们将指定 us.gcr.io/broad-gatk/gatk:4.1.3.0 映像。然后,在启动工作流时,Cromwell 将从我们指定的映像中启动一个新的容器,并在其中运行 GATK。

在技术上,我们可以在此处硬编码映像名称,使用双引号(例如,docker: "us.gcr.io/broad-gatk/gatk:4.1.3.0"),但我们不建议这样做,除非您确实希望将特定脚本固定到特定版本,这显著降低了灵活性。有些人使用latest标签使其工作流始终运行最新可用版本的程序,但我们认为这是一个不良实践,弊大于利,因为您永远不知道最新版本可能会发生什么变化并破坏您的工作流程。

生成输入的 JSON

好了,我们已经过了工作流中的所有代码;现在我们需要确定在运行它时如何向工作流提供输入。在“Your First WDL: Hello World”中,我们运行了一个非常简单的工作流,首先没有任何变量输入,然后有一个输入。在单一输入情况下,我们创建了一个指定该输入的 JSON 文件。现在我们有八个输入需要指定。我们可以像以前一样继续操作——创建一个 JSON 文件并写入HaplotypeCallerGVCF任务期望的每个输入的名称,但有一种更简单的方法:我们将使用Womtool inputs命令创建一个模板 JSON。

首先,因为我们将在本章中首次写入我们关心的文件,请让我们创建一个sandbox目录来保持我们的输出有序:

$ mkdir ~/sandbox-8

现在,您可以运行Womtool命令来生成inputs JSON 模板文件:

$ java -jar $BIN/womtool-48.jar \
    inputs $WF/hello-hc/hello-haplotypecaller.wdl \
    > ~/sandbox-8/hello-haplotypecaller.inputs.json

因为在此命令的最后一行指定了输出文件(实际上是可选的),所以命令会将其输出写入该文件。如果一切顺利,您在终端上不应该看到任何输出。让我们看看我们刚刚创建的文件的内容:

$ cat ~/sandbox-8/hello-haplotypecaller.inputs.json

{
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.input_bam_index": "File",
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.input_bam": "File",
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.ref_fasta": "File",
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.ref_index": "File",
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.ref_dict": "File",
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.intervals": "File",
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.docker_image": "String",
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.java_opt": "String"
}

这里是:所有HaplotypeCallerGVCF任务期望的输入都以适当的方式列出,其中包含占位符值指示它们的类型,以 JSON 格式显示(尽管在您的情况下可能顺序不同)。现在我们只需填写这些值;这些是前六个相关文件的路径以及最后两个的运行时参数(容器镜像和 Java 选项)。在懒惰的精神下,我们提供了一个填充版本,使用我们在第五章中使用的片段数据,但是如果愿意,您也可以从在第四章下载的数据包中的其他输入中填写inputs JSON。这是预填充 JSON 的样子(路径相对于主目录):

$ cat $WF/hello-hc/hello-haplotypecaller.inputs.json
{
"HelloHaplotypeCaller.HaplotypeCallerGVCF.input_bam_index":
"book/data/germline/bams/mother.bai",
"HelloHaplotypeCaller.HaplotypeCallerGVCF.input_bam":
"book/data/germline/bams/mother.bam",
"HelloHaplotypeCaller.HaplotypeCallerGVCF.ref_fasta":
"book/data/germline/ref/ref.fasta",
"HelloHaplotypeCaller.HaplotypeCallerGVCF.ref_index":
"book/data/germline/ref/ref.fasta.fai",
"HelloHaplotypeCaller.HaplotypeCallerGVCF.ref_dict":
"book/data/germline/ref/ref.dict",
"HelloHaplotypeCaller.HaplotypeCallerGVCF.intervals":
"book/data/germline/intervals/snippet-intervals-min.list",
"HelloHaplotypeCaller.HaplotypeCallerGVCF.docker_image": "us.gcr.io/broad-
gatk/gatk:4.1.3.0",
"HelloHaplotypeCaller.HaplotypeCallerGVCF.java_opt": "-Xmx8G"
}

注意,所有的值都用双引号显示,但这是一个技术性的问题,因为这些值都是String类型。对于其他类型,如数字、布尔值和数组,你不应该使用双引号。当然,对于字符串数组,你应该在字符串周围使用双引号,但不要在数组本身周围使用双引号,["like","this"]

运行工作流程

要运行工作流程,我们将使用与之前相同的命令行语法。确保在你的主目录中执行此操作,以便inputs JSON 文件中的相对路径与数据文件的位置匹配:

$ java -jar $BIN/cromwell-48.jar \
    run $WF/hello-hc/hello-haplotypecaller.wdl \
    -i $WF/hello-hc/hello-haplotypecaller.inputs.json

正如之前提到的,Cromwell 的输出非常冗长。在这个练习中,你要找的是终端输出中类似于以下示例的行:

[2019-08-14 06:27:14,15] [info] BackgroundConfigAsyncJobExecutionActor
[9a6a9c97HelloHaplotypeCaller.HaplotypeCallerGVCF:NA:1]: Status change from
WaitingForReturnCode to Done
[2019-08-14 06:27:15,46] [info] WorkflowExecutionActor-9a6a9c97-7453-455c-8cd8-
be8af8cb6f7c [9a6a9c97]: Workflow HelloHaplotypeCaller complete. Final Outputs:
{
 "HelloHaplotypeCaller.HaplotypeCallerGVCF.output_gvcf": "/home/username/cromwell-
executions/HelloHaplotypeCaller/9a6a9c97-7453-455c-8cd8-be8af8cb6f7c/call-
HaplotypeCallerGVCF/execution/mother.g.vcf"
}
[2019-08-14 06:27:15,51] [info] WorkflowManagerActor WorkflowActor-9a6a9c97-7453-
455c-8cd8-be8af8cb6f7c is in a terminal state: WorkflowSucceededState
[2019-08-14 06:27:21,31] [info] SingleWorkflowRunnerActor workflow 
status ’Succeeded’.
{
 "outputs": {
   "HelloHaplotypeCaller.HaplotypeCallerGVCF.output_gvcf":
"/home/username/cromwell-executions/HelloHaplotypeCaller/9a6a9c97-7453-455c-8cd8-
be8af8cb6f7c/call-HaplotypeCallerGVCF/execution/mother.g.vcf"
workflow HelloHaplotypeCaller {
 },
 "id": "9a6a9c97-7453-455c-8cd8-be8af8cb6f7c"
}

这里最令人兴奋的片段是Status change from WaitingForReturnCode to Donefinished with status 'Succeeded',它们一起意味着你的工作流已经运行完成,并且所有运行的命令都表示它们成功了。

输出的另一个令人兴奋的部分是输出路径。在下一节中,我们会稍微讨论一下为什么它们会被列出两次;现在,让我们高兴地看到 Cromwell 精确告诉我们在哪里找到我们的输出文件,这样我们就可以轻松地查看它。当然,这个特定工作流的输出是一个 GVCF 文件,所以阅读起来并不是很愉快,但重要的是文件在那里,其内容正如你所期望的那样。

我们用head实用程序来做这个目的;请记住,你需要将文件路径中显示的执行目录哈希(例如9a6a9c97-7453-455c-8cd8-be8af8cb6f7c)替换为你的输出中显示的哈希:

$ head ~/cromwell-executions/HelloHaplotypeCaller/9a6a9c97-7453-455c
-8cd8-be8af8cb6f7c/call-HaplotypeCallerGVCF/execution/mother.g.vcf
##fileformat=VCFv4.2
##ALT=<ID=NON_REF,Description="Represents any possible alternative allele at this
location">
##FILTER=<ID=LowQual,Description="Low quality">
##FORMAT=<ID=AD,Number=R,Type=Integer,Description="Allelic depths for the ref and
alt alleles in the order listed">
##FORMAT=<ID=DP,Number=1,Type=Integer,Description="Approximate read depth (reads
with MQ=255 or with bad mates are filtered)">
##FORMAT=<ID=GQ,Number=1,Type=Integer,Description="Genotype Quality">
##FORMAT=<ID=GT,Number=1,Type=String,Description="Genotype">
##FORMAT=<ID=MIN_DP,Number=1,Type=Integer,Description="Minimum DP observed within
the GVCF block">
##FORMAT=<ID=PGT,Number=1,Type=String,Description="Physical phasing haplotype
information, describing how the alternate alleles are phased in relation to one
another">
##FORMAT=<ID=PID,Number=1,Type=String,Description="Physical phasing ID information,
where each unique ID within a given sample (but not across samples) connects
records
within a phasing group">

这次运行应该很快完成,因为我们使用的间隔列表仅跨越一个短区域。在这里花费的大部分时间是 Cromwell 启动和启动容器,而 GATK 的HaplotypeCaller本身仅运行了极短的时间。与 Hello World 示例一样,你可能会觉得为这样一个小工作量做这么多工作有点浪费,你是对的;这就像用火箭筒打苍蝇。对于玩具示例来说,启动 Cromwell 的开销使分析本身相形见绌。当我们进行适当的全面分析时,像 Cromwell 这样的工作流管理系统才能真正展示其价值,你将在第十章和第十一章中体验到这一点。

中断工作流以测试语法验证和错误消息

希望到目前为止一切都按预期进行,这样您就知道成功的样子。但实际上,事情偶尔会出错,所以现在让我们看看失败的情况。具体来说,我们将看看 Cromwell 如何处理两种常见的脚本错误类型,即 WDL 语法和 command 块语法错误,通过在 WDL 中引入一些错误。首先,让我们复制工作流文件,以便可以自由玩耍:

$ cp $WF/hello-hc/hello-haplotypecaller.wdl ~/sandbox-8/hc-break1.wdl

现在,在您喜欢的文本编辑器中,打开新文件并在 WDL 语法中引入一个错误。例如,您可以弄乱一个变量名、一个保留关键字,或删除一个大括号来干扰块结构。在这里,我们会有点残忍,删除 basename() 函数调用中的第二个括号。这种小错误通常是致命的,但很容易被忽视。让我们看看运行时会发生什么:

$ java -jar $BIN/cromwell-48.jar \
    run ~/sandbox-8/hc-break1.wdl \
    -i $WF/hello-hc/hello-haplotypecaller.inputs.json

如预期的那样,工作流执行失败,并且 Cromwell 提供了一些详细的错误消息:

[2019-08-14 07:30:49,55] [error] WorkflowManagerActor Workflow 0891bf2c-4539-498c-
a082-bab457150baf failed (during MaterializingWorkflowDescriptorState):
cromwell.engine.workflow.lifecycle.materialization.MaterializeWorkflowDescriptorAct
or$$anon$1: Workflow input processing failed:
ERROR: Unexpected symbol (line 20, col 2) when parsing ’_gen18’.

Expected rparen, got command .
       command {
^
$e = :string

[stack trace]
[2019-08-14 07:30:49,57] [info] WorkflowManagerActor WorkflowActor-0891bf2c-4539-
498c-a082-bab457150baf is in a terminal state: WorkflowFailedState

其中有许多我们不关心的内容,比如堆栈跟踪,这里我们没有展示。真正重要的部分是 Workflow input processing failed: ERROR: Unexpected symbol。这是一个明显的暗示,表明您有语法问题。Cromwell 尝试提供更具体的信息,指出语法错误可能位于哪里;在这种情况下,它相当准确——它期望但未找到第 20 行的右括号 (rparen),但请注意,有时情况并不那么明显。

当您正在积极开发新的工作流时,您可能不想每次需要测试某些新代码的语法时都通过 Cromwell 启动工作流。好消息是:您可以通过使用 Womtool 的 validate 命令来节省时间。这实际上是 Cromwell 在幕后运行的内容,这是一种非常轻量级的测试语法的方式。现在尝试在您的破碎工作流上运行它:

$ java -jar $BIN/womtool-48.jar \
    validate ~/sandbox-8/hc-break1.wdl 

ERROR: Unexpected symbol (line 20, col 2) when parsing '_gen18'.
Expected rparen, got command .
       command {
^
$e = :string

看到了吧?您在更短的时间内获得了 Cromwell 输出的重要部分,甚至不需要为工作流提供有效的输入。作为额外的练习,尝试引入其他 WDL 语法错误;例如,尝试删除变量声明,仅更改变量名称中的一个出现,以及拼写保留关键字如 workflowcommandoutputs。这将帮助您识别验证错误并解释它们如何被 Womtool 报告。总之,我们强烈建议在任何新的或更新的 WDL(是的,它也适用于 CWL)上系统地使用 Womtool validate

话虽如此,重要的是要理解,Womtool 的 WDL 语法验证只能帮助你到这一步:它无法处理你可能在运行的工具命令语法上出错之外的任何其他错误;例如,如果你搞乱了你想要运行的工具的命令语法。要查看在这种情况下会发生什么,请在你的沙盒中再复制一份原始工作流(称为 hc-break2.wdl),并打开它以在这次 GATK 命令中引入错误;例如,通过弄乱工具的名称,将其更改为 HaploCaller

       command {
               gatk --java-options ${java_opt} HaploCaller \
                       -R ${refFasta} \
                       -I ${inputBam} \
                       -O ${gvcfName} \
                       -L ${intervals} \
                       -ERC GVCF
       }

如果你运行 Womtool validate,你会看到这个工作流程通过了验证;Womtool 愉快地报告说 Success! 然而,如果你真正通过 Cromwell 运行它,这个工作流程肯定会失败:

$ java -jar $BIN/cromwell-48.jar \
    run ~/sandbox-8/hc-break2.wdl \
    -i $WF/hello-hc/hello-haplotypecaller.inputs.json

滚动输出以查找显示失败消息的行:

[2019-08-14 07:09:52,12] [error] WorkflowManagerActor Workflow dd77316f-7c18-4eb1
-aa86-e307113c1668 failed (during ExecutingWorkflowState): Job
HelloHaplotypeCaller.HaplotypeCallerGVCF:NA:1 exited with return code 2 which has
not been declared as a valid return code. See ’continueOnReturnCode’ runtime
attribute for more details.
Check the content of stderr for potential additional information:
/home/username/cromwell-executions/HelloHaplotypeCaller/dd77316f-7c18-4eb1-aa86-
e307113c1668/call-HaplotypeCallerGVCF/execution/stderr.
[First 300 bytes]:Picked up _JAVA_OPTIONS: -Djava.io.tmpdir=/cromwell-
executions/HelloHaplotypeCaller/dd77316f-7c18-4eb1-aa86-e307113c1668/call-
HaplotypeCallerGVCF/tmp
.e6f08f65
USAGE:  <program name> [-h]
Available Programs:

日志行中显示 Job HelloHaplotypeCaller.HaplotypeCallerGVCF:NA:1 exited with return code 2 意味着 HaplotypeCallerGVCF 是失败的任务,具体来说,它正在运行的命令报告了一个 2 的退出代码。这通常表示你尝试运行的工具在某些方面出了问题——可能是语法问题、未满足的输入要求、格式错误、内存不足等等。

错误消息继续指出,你可以通过查看命令生成的标准错误 (stderr) 输出来了解更多信息,并且它贴心地包含了完整的文件路径,这样你可以轻松地查看其中内容。它还包括 stderr 日志的前几行,这有时足够了,如果工具的错误输出非常简短的话。由 GATK 产生的 stderr 输出属于更详细的类型。因此,在这里我们需要检查完整的 stderr 来找出问题所在。再次强调,请确保用你的输出中显示的用户名和执行目录哈希替换这里显示的(dd77316f-7c18-4eb1-aa86-e307113c1668)。

$ cat /home/username/cromwell-executions/HelloHaplotypeCaller/dd77316f-7c18-4eb1
-aa86-e307113c1668/call-HaplotypeCallerGVCF/execution/stderr
(...)
***********************************************************************
A USER ERROR has occurred: 'HaploCaller' is not a valid command.
Did you mean this?
       HaplotypeCaller
***********************************************************************
(...)

啊,看这个!我们把工具的名称写错了;谁知道呢?顺便说一句,感谢 GATK 建议更正;顺便说一句,这在 GATK4 中是新功能。

注意

所有命令行工具在完成运行时都会输出一个 返回码 作为报告它们状态的简洁方式。根据工具的不同,返回码可能更或少有意义。传统上,返回码为 0 表示成功,而其他任何值表示失败。在某些情况下,非零返回码可能意味着运行成功;例如,Picard 工具 ValidateSamFile 在检查的文件中发现格式验证错误时报告非零代码。

还有其他可能出错的事情我们在这里没有涵盖,比如如果你为输入文件指定了错误的路径,或者如果你忘记在输入 JSON 中的字符串输入上加双引号。再次建议你故意制造这些错误来实验,因为这会帮助你更快速地诊断问题。

引入 Scatter-Gather 并行性

在本章中,我们将再介绍一个工作流示例,以补充你对 WDL 和 Cromwell 的第一次接触,因为我们真的希望你能尝试并行处理的强大之处,如果你之前没有经历过的话。所以现在我们将看一个工作流,它通过scatter()函数并行处理HaplotypeCaller任务的操作,然后在随后的步骤中合并并行作业的输出,如图 8-2 所示。

并行执行 HaplotypeCaller 的工作流的概念图。

图 8-2. 并行执行 HaplotypeCaller 的工作流。

这个工作流将让你接触到散集-聚集并行处理的魔力,这是基因组学工作流的重要组成部分,特别是在云端。它还将让你有机会更详细地了解如何根据输入和输出将多个任务串联在一起的机制,比我们之前讨论的更详细。

探索 WDL

这是一个完整的 WDL,它将HaplotypeCallerGVCF的操作并行化处理在子区间上。在nano编辑器中,像往常一样打开它:

$ nano $WF/scatter-hc/scatter-haplotypecaller.wdl

让我们逐步走过脚本的主要部分,指出与这个工作流的线性实现相比有哪些变化:

version 1.0

workflow ScatterHaplotypeCallerGVCF {

 input {
        File input_bam
        File input_bam_index
        File intervals_list
    }

    String output_basename = basename(input_bam, ".bam") 

    Array[String] calling_intervals = read_lines(intervals_list)

    scatter(interval in calling_intervals) {
        call HaplotypeCallerGVCF { 
            input: 
                input_bam = input_bam,
                input_bam_index = input_bam_index,
                intervals = interval, 
                gvcf_name = output_basename + ".scatter.g.vcf"
        }
    }
    call MergeVCFs { 
        input: 
            vcfs = HaplotypeCallerGVCF.output_gvcf, 
            merged_vcf_name = output_basename + ".merged.g.vcf"
    }

    output {
        File output_gvcf = MergeVCFs.mergedGVCF
    }
}

task HaplotypeCallerGVCF {

 input {
        String docker_image
        String java_opt

        File ref_fasta
        File ref_index
        File ref_dict
        File input_bam
        File input_bam_index
        String intervals
        String gvcf_name
    }

    command {
        gatk --java-options ${java_opt} HaplotypeCaller \
            -R ${ref_fasta} \
            -I ${input_bam} \
            -O ${gvcf_name} \
            -L ${intervals} \
            -ERC GVCF 
    }

    output {
        File output_gvcf = "${gvcf_name}"
    }

 runtime {
        docker: docker_image
    }
}

task MergeVCFs {

    input {
        String docker_image
  String java_opt

        Array[File] vcfs
        String merged_vcf_name
    }

    command {
        gatk --java-options ${java_opt} MergeVcfs \
            -I ${sep=’ -I’ vcfs} \
            -O ${merged_vcf_name}
    }

    output {
        File merged_vcf = "${merged_vcf_name}"
    }

 runtime {
        docker: docker_image
    }
}

最明显的区别是现在workflow块中发生了更多的事情。这个动作的核心是这个子集(为了可读性将两个call块合并):

scatter(intervals in calling_intervals) {
        call HaplotypeCallerGVCF { ... }
    }
    call MergeVCFs { ... }

以前,我们只是简单地调用了HaplotypeCallerGVCF任务,就是这样。现在,你会看到对HaplotypeCallerGVCF的调用被隶属于一个更高级别的操作,在这行下面,它打开了一个scatter块:

scatter(intervals in calling_intervals) { 

对于这么短的一行,它做了很多工作:这就是我们如何在指定的区间上并行执行HaplotypeCaller的过程,这些区间在括号中指定。calling_intervals变量指的是一个区间列表,HaplotypeCallerGVCF任务将在提供的文件中的每个区间上作为一个独立的调用运行。如果你熟悉脚本构造,这可能看起来很像一个for 循环,实际上它确实类似,因为它的目的是对列表中的每个元素应用相同的操作。然而,scatter 专门设计用于允许每个操作的独立执行,而 for 循环导致线性执行,其中每个操作只能在前一个操作(如果有的话)完成后才能运行。

现在你已经了解了scatter()指令的作用,让我们来看看HaplotypeCallerGVCF任务本身,从它是如何被调用的开始:

call HaplotypeCallerGVCF { 
            input: 
                input_bam = input_bam,
                input_bam_index = input_bam_index,
                intervals = intervals, 
                gvcf_name = output_basename + ".scatter.g.vcf"
        }

在工作流的先前版本中,对任务的调用只是这样:call关键字,然后是任务名称。在这个版本中,语句包括一个input块,指定了任务所需的一些变量的值:输入 BAM 文件及其索引,间隔文件,以及 GVCF 输出文件的名称。说到这一点,你会注意到我们用于生成输出名称的basename函数调用现在发生在这个input块内,而不是任务内部。如果你比较了本版本工作流中HaplotypeCallerGVCF任务的定义和之前版本的定义,那就是唯一的区别。让我们在几分钟后再次记住这一点。

现在让我们谈谈跟在包含HaplotypeCallerGVCF调用的scatter块后面的内容:对名为MergeVCFs的新任务的调用。明确一点,这个调用语句是scatter块之外的,因此它只会运行一次:

call MergeVCFs { ... }

你可能已经根据其名称对这项任务有了一个相当好的想法,但我们假装这并不那么明显,按照逻辑路径来解密工作流程的结构。预览MergeVCFs任务定义,我们看到它运行的命令是一个调用名为MergeVcfs的工具(实际上是捆绑到 GATK4 中的 Picard 工具)。作为输入,它接受一个或多个 VCF 文件(其中 GVCF 是一个子类型),并输出一个合并后的单个文件:

command {
        gatk --java-options ${java_opt} MergeVcfs \
            -I ${sep=' -I' vcfs} \
            -O ${vcf_name}
    }

我们可以从命令参数和变量名推断出这一点,并通过查阅 GATK 网站上的MergeVcfs工具文档来确认。

注意

这里要注意 WDL 语法的一个新点:-I ${sep=' -I' vcfs}的表达式是我们处理需要将列表中的项目放入命令行的相同参数的任意长度列表的方式。给定文件列表[FileA, FileB, FileC],前述代码将生成命令行的以下部分:-I FileA -I FileB -I FileC

MergeVCFs任务定义还告诉我们,此任务期望一个文件列表(在技术上表示为Array[*File*])作为其主要输入。让我们看看任务在workflow块中是如何调用的:

call MergeVCFs { 
            input: 
            vcfs = HaplotypeCallerGVCF.output_gvcf, 
            merged_vcf_name = output_basename + ".merged.g.vcf"
    }

如果你还记得HelloWorld​Again工作流中的ReadItBackToMe任务,这可能会让你觉得很熟悉。就像我们当时所做的那样,我们通过引用HaplotypeCallerGVCF任务的输出来分配vcfs输入变量。不同之处在于,在那种情况下,我们将单个文件输出传递给单个文件输入。相反,在这里,我们引用了一个位于scatter块内的任务调用的输出。这到底是什么样子?

很好的问题。根据定义,scatter块内部对任务的每个单独调用生成其自己的单独输出。scatter结构的巧妙之处在于,这些单独的输出自动收集到一个列表(技术上是一个数组),名称为任务指定的输出变量。因此,在这里,虽然HaplotypeCallerGVCF任务的单个调用的输出值,即HaplotypeCallerGVCF.output_gvcf,是单个 GVCF 文件,但在workflow块中引用的HaplotypeCallerGVCF.output_gvcf的值是scatter块内生成的 GVCF 文件列表。当我们将该引用传递给下一个任务调用时,我们实际上是提供了作为数组的全部文件列表。

让我们通过指出一些更多的细节来完成这次探索。首先,您可能注意到我们在工作流级别的output块中明确声明了MergeVCFs调用的输出作为工作流的最终输出。这在技术上并不是必需的,但这是一种良好的实践。其次,BAM 文件、其索引和间隔文件的变量声明都被提升到了工作流的级别。对于 BAM 文件及其索引,这一举措允许我们在两个任务调用的input块中生成输出文件的名称,这样做除了其他优势外,如果我们想将任务定义放入公共库中,还可以给我们更多的灵活性。对于这样的情况,我们希望任务尽可能通用,并将诸如文件命名约定之类的细节留给工作流实现。至于间隔文件,我们需要在工作流级别上可用它,以便实现scatter块。

最后,现在您应该能够生成inputs JSON,根据之前的练习填写它,并使用与以前相同的设置运行工作流。我们提供了一个预填充的 JSON,如果您不想填写文件路径,可以使用它;只需确保使用.local.inputs.json版本,而不是我们在第十章中使用的.gcs.inputs.json版本。

这里是使用预填充本地输入 JSON 的 Cromwell 命令。请确保在您的主目录中执行此命令,以便inputs JSON 文件中的相对路径与数据文件的位置匹配。

$ java -jar $BIN/cromwell-48.jar \
       run $WF/scatter-hc/scatter-haplotypecaller.wdl \
       -i $WF/scatter-hc/scatter-haplotypecaller.local.inputs.json

当您运行此命令时,您可能会注意到散布的作业在虚拟机上并行运行。这很好,因为它意味着作业将更快完成。但是,如果您尝试在整个基因组上运行五百个散布调用会发生什么呢?将所有这些并行运行将会引起问题;即使不会直接崩溃,它也会将您的虚拟机推向磁盘交换内存的边缘。好消息是有几种解决方案。首先,您可以通过“local”后端控制允许的并行性级别,如 在线文档 中所述。或者,您可以使用另一种专为优雅处理此类情况而设计的后端。在 第 10 章 中,我们将向您展示如何使用 Google 后端自动将并行作业发送到多个虚拟机。

生成可视化的图形图表

作为此练习的结尾,让我们学习应用另一个 Womtool 实用程序:graph 命令。到目前为止,我们所看到的工作流在步骤数量和总体管道方面都相当简单。在下一章节(以及现实世界中),您将会遇到更复杂的工作流,单凭代码可能很难建立起一个心理模型。这时,能够生成工作流的可视化图形将非常有帮助。Womtool graph 命令允许您生成一个 .dot 格式的图形文件,并使用通用的图形可视化工具进行查看:

$ java -jar $BIN/womtool-48.jar \
       graph $WF/scatter-hc/scatter-haplotypecaller.wdl \
       > ~/sandbox-8/scatter-haplotypecaller.dot

.dot 文件是一个纯文本文件,因此您可以在终端中查看它;例如:

$ cat ~/sandbox-8/scatter-haplotypecaller.dot
digraph ScatterHaplotypeCallerGVCF {
 #rankdir=LR;
 compound=true;
 # Links
 CALL_HaplotypeCallerGVCF -> CALL_MergeVCFs
 SCATTER_0_VARIABLE_interval -> CALL_HaplotypeCallerGVCF
 # Nodes
 CALL_MergeVCFs [label="call MergeVCFs"]
 subgraph cluster_0 {
   style="filled,solid";
   fillcolor=white;
   CALL_HaplotypeCallerGVCF [label="call HaplotypeCallerGVCF"]
   SCATTER_0_VARIABLE_interval [shape="hexagon" label="scatter over String as interval"]
 }
}

但是这并不直观,所以让我们将其加载到图形查看器中。有许多可用的选项,包括非常流行的开源软件包 Graphviz。您可以在本地安装该软件包,也可以通过其 许多在线实现 之一使用它。要这样做,只需将 .dot 文件的内容复制到可视化应用程序的文本窗口中,它将为您生成图形图表,如 图 8-3 所示。

在在线 Graphviz 应用程序中可视化工作流图。

图 8-3. 在线 Graphviz 应用程序中可视化工作流图。

当我们像这样可视化工作流图时,通常首先查看表示工作流中任务调用的椭圆形。在这里,您可以看到我们工作流中的两个任务调用确实存在,HaplotypeCallerGVCFMergeVCFs。连接椭圆形的箭头方向表示执行流程,因此在 图 8-3 中,您可以看到 HaplotypeCallerGVCF 的输出将成为 MergeVCFs 的输入。

有趣的是,对HaplotypeCallerGVCF的调用显示为一个框,这意味着它受到一个修改函数的控制。修改函数以六边形表示,在这里您可以看到六边形标记为“在字符串作为间隔上分散”。这一切都说得通,因为我们刚刚讨论过在这个工作流中,HaplotypeCaller任务的执行是在一组间隔上分散的。

在本例中,工作流程相对简单,因此图形可视化并没有告诉我们任何我们之前不知道的内容,但是当我们在下一章中处理更复杂的工作流时,图形可视化将成为我们工具箱中至关重要的一部分。

注意

这就结束了本章的练习,所以不要忘记停止您的虚拟机;否则,您将仅仅为了它闲逛而支付费用。

总结和下一步

在本章中,我们讨论了如何将单个命令串联成一个简单的 Hello World 工作流,并在我们在第四章中设置的单个 VM 上使用 Cromwell 的一次性运行模式执行它。我们介绍了解释 Cromwell 终端输出和查找输出文件的基础知识。我们在原始的Hello​World工作流上进行了迭代,添加了变量和一个额外的任务。然后,我们继续研究了运行真实 GATK 命令并使用分散聚集并行性的更现实的工作流,尽管规模仍然相对较小。在此过程中,我们使用了一些关键的实用程序,如生成 JSON 模板、验证 WDL 语法、测试错误处理以及生成图形可视化。

然而,我们只是浅尝 WDL 作为工作流语言的能力,所以现在是时候转向一些更复杂的工作流了。在第九章,我们将研究两个神秘的工作流,并试图反向工程它们的功能,这将为您提供锻炼侦探技能的机会,同时学习一些在真实基因组分析工作流中使用的有用模式。

第八章:使用工作流自动化执行分析

到目前为止,我们一直在终端手动运行单个命令。然而,绝大多数基因组学工作——即次级分析,在此过程中我们从原始数据到精炼信息,再将其馈送到下游分析以最终产生生物洞察力——都涉及按照相同的顺序在所有数据上运行相同的命令。再次看看《GATK 最佳实践》工作流,它们在第六章和第七章中有所描述;想象一下,为每个样本手动完成这一切会是多么繁琐。这个领域的新手通常发现,逐步在测试数据上运行涉及的命令,以了解关键步骤、其要求及其怪癖,是有益的——这就是为什么我们在第六章和第七章中详细介绍了这一切的原因——但归根结底,你将希望尽可能地自动化所有这些。自动化不仅减少了您需要进行的繁琐手工工作量,还增加了分析的吞吐量,并减少了人为错误的机会。

在本章中,我们涵盖了从单个命令或一次性脚本到可重复工作流的转变。我们向您展示如何使用工作流管理系统(Cromwell)和语言 WDL,我们主要因其可移植性而选择。我们将引导您编写您的第一个示例工作流,执行它,并解释 Cromwell 的输出。然后,我们用几个更实际的工作流重复这个过程,这些工作流运行 GATK 并使用分散汇总并行性。

引入 WDL 和 Cromwell

您可能还记得,我们在技术入门指南(第三章)中介绍了工作流的概念。正如我们当时指出的,有许多工具选项可用于编写和运行工作流,具有各种特性、优势和劣势。我们无法告诉您整体最佳选项,因为这一决定在很大程度上取决于您的具体背景和需求。对于本书的目的,我们选择了一个选项,即 Cromwell 和 WDL 的组合,这对我们所针对的广泛受众和需求范围最为合适。

我们在那时已经简要介绍了 WDL,但让我们回顾一下主要要点,因为那已经是半本书之前的事情,沧海桑田。正如刚才提到的,它的全称是Workflow Description Language,但通常简称为 WDL(发音为“widdle”)。这是一种常用于基因组学工作流的 DSL,最初由 Broad Institute 开发,后来演变为一个社区驱动的开源项目,由一个名为 OpenWDL 的公共团体支持。作为一种工作流语言,它旨在非常适合没有软件编程正规培训的生物信息学新手,同时最大限度地提高在不同系统间的可移植性。

我们将用于实际运行用 WDL 编写的工作流的工作流管理系统是 Cromwell,这是一个在 Broad Institute 开发的开源应用程序。Cromwell 设计为可在几乎任何现代支持 Java 的计算环境中运行,包括像 Slurm 和 SGE 这样的流行 HPC 系统,商业云平台如 GCP 和 AWS,以及运行在某种 Linux 版本的任何笔记本电脑或台式电脑。这种可移植性是 Cromwell 的核心特性,旨在促进在多个机构运行相同工作流,以最大程度地促进科学合作和研究中的计算再现性。Cromwell 的另一个主要面向可移植性的特性是,它支持(但不要求)使用容器来提供你想要在给定工作流的每个组件任务中运行的代码。在本章的练习中,你将有机会尝试两种运行方式。

最后,延续合作和互操作性精神,Cromwell 还设计支持多种工作流语言。目前,它支持 WDL 以及 CWL,另一种流行的工作流语言,旨在实现可移植性和计算再现性。

Cromwell 提供两种运行模式:一次性运行模式和服务器模式。一次性运行模式是运行 Cromwell 的简单方式,涉及一个命令行:你给它一个工作流和一个列出工作流输入的文件,它将开始运行,执行工作流,最终在工作流完成时关闭。这对于周期性地运行工作流非常方便,几乎没有麻烦,这也是我们在本章的练习中使用的方式。服务器模式的运行方式涉及设置一个持久性服务器,该服务器始终运行,并通过 REST API(一种编程接口)向该服务器提交工作流执行请求。

启动 Cromwell 服务器非常简单。在它运行后,它提供了在单次运行模式中不可用的功能,其中一些我们在第十一章中介绍。然而,安全地管理其运行需要一个专门的技能集,大多数没有专职支持人员的个人研究人员或小团体是不具备的。在第十一章中,我们向您介绍 Terra,这是由 Broad Institute 运营的管理系统,通过 GUI 以及 API 提供对持久性 Cromwell 服务器的访问。这将为您提供在服务器模式下尝试 Cromwell 的机会,而无需自己管理服务器。

注意

我们不会在本书中涵盖 Cromwell 服务器管理,因此如果您有兴趣了解更多信息,请查看Cromwell 文档

无论您将其作为一次性任务还是服务器模式运行,Cromwell 都具有旨在提升效率和可扩展性的有趣功能,但是在本书中没有人想要阅读功能的详细清单,所以让我们继续进行练习,并在相关的地方提出这些关键功能。

安装和设置 Cromwell

在本章中,我们将检查并执行一些使用 WDL 编写的工作流,以熟悉语言的基本结构,并了解 Cromwell 如何管理输入和输出、日志等。为了与前几章保持连贯性,我们在之前的章节中(第四章和第五章)使用了 GCP Compute Engine VM 上运行 Cromwell。然而,我们不再从 GATK 容器内运行任何内容。相反,我们直接在 VM 环境中安装和运行 Cromwell。

由于 Cromwell 需要 Java,而我们使用的 VM 上没有预安装 Java,因此您需要运行一些安装命令。为此,请通过 SSH 重新登录到您的 VM,就像您在之前的章节中所做的那样。请记住,您始终可以通过直接转到Compute Engine在 GCP 控制台中找到您的 VM 实例列表,或者在控制台左侧的 GCP 服务菜单中点击 Compute Engine,如果您忘记了 URL。

在您的 VM 中,在提示符处键入**java -version**。您应该会得到以下输出:

$ export CASE1=~/book/code/workflows/mystery-1

Cromwell 需要 Java 版本 8,因此让我们安装openjdk-8-jre-headless选项,这是一个轻量级环境,足以满足我们的需求:

$ mkdir ~/sandbox-9

这将触发安装过程,应该会无错误地完成运行。您可能会看到一些通知,但只要看到最终的done输出,您就应该没问题了。您可以再次运行 Java 版本检查,以确保安装成功:

$ cat ~/sandbox-9/haplotypecaller-gvcf-gatk4.dot
$ java -jar $BIN/womtool-48.jar graph $CASE1/haplotypecaller-gvcf-gatk4.wdl \
    > ~/sandbox-9/haplotypecaller-gvcf-gatk4.dot

安装了 Java 后,让我们设置 Cromwell 本身,它附带一个称为Womtool的伴随实用程序,用于语法验证和创建输入文件。它们都作为编译好的.jar文件分发,并且我们已经在书籍捆绑包中包含了一个副本,因此您不需要做任何复杂的操作,只需指向它们所在的位置即可。为了尽可能缩短我们的命令,让我们设置一个环境变量来指向它们的位置。我们称之为BIN,代表二进制,这是一个经常用来指代程序的编译形式的术语:

digraph HaplotypeCallerGvcf_GATK4 {
  #rankdir=LR;
  compound=true;
  # Links
  CALL_HaplotypeCaller -> CALL_MergeGVCFs
  SCATTER_1_VARIABLE_interval_file -> CALL_HaplotypeCaller
  CALL_CramToBamTask -> CALL_HaplotypeCaller
  # Nodes
  subgraph cluster_0 {
    style="filled,dashed";
    fillcolor=white;
    CALL_CramToBamTask [label="call CramToBamTask"]
    CONDITIONAL_0_EXPRESSION [shape="hexagon" label="if (is_cram)" style="dashed" ]
  }
  CALL_MergeGVCFs [label="call MergeGVCFs"]
  subgraph cluster_1 {
    style="filled,solid";
    fillcolor=white;
    CALL_HaplotypeCaller [label="call HaplotypeCaller"]
    SCATTER_1_VARIABLE_interval_file [shape="hexagon" label="scatter over File as interval_file"]
  }
}

让我们检查一下能否通过请求其help输出来运行 Cromwell,该输出显示了您可以给它的三个命令的摘要:serversubmit是我们之前讨论过的服务器模式的一部分,而run是我们不久将要使用的一次性模式:

    CramToBamTask
    HaplotypeCaller
    MergeGVCFs
    ```

对于`Womtool`,也值得做同样的事情,以了解各种可用的实用命令:

call CramToBamTask {          line 68
call HaplotypeCaller {        line 84 
call MergeGVCFs {             line 100
```

在刚列出的函数中,您将有机会在本章中使用inputsvalidategraph

现在让我们检查您是否拥有本章提供的所有工作流文件。如果您按照第四章中的设置说明操作,您应该有一个从 GitHub 克隆下来的代码目录。在~/book/code下,您将看到一个名为workflows的目录,其中包含本章中将要使用的所有代码和相关文件(除了来自存储桶的数据)。您将从主目录运行命令(而不是像之前章节那样进入子目录),因此为了在各种命令中保持路径尽可能简短,让我们设置一个环境变量来指向工作流文件的位置:

        if ( is cram ) {
          call CramToBamTask {
            input:
              input_cram = input_bam,
              ...
            }
        }

最后,让我们谈谈文本编辑器。在接下来的练习中,除了一个例外,您只需查看和运行我们提供的预写脚本,因此,对于查看,您只需将文件下载或克隆到您的笔记本电脑上,并在您喜欢的文本编辑器中打开它们。在一个例外情况下,我们建议您修改一个 WDL 以打破它,以查看 Cromwell 的错误消息和处理行为,因此您需要实际编辑该文件。我们将向您展示如何使用其中一个 shell 内置的文本编辑器,称为nano,这被认为是对于不习惯命令行文本编辑器的人来说最易于接触的之一。当然,您也可以选择使用另一个 shell 编辑器,如viemacs,如果选择这样做,您将需要根据自己的喜好调整我们提供的命令。

无论您决定使用什么文本编辑器,只要确保不要使用像 Microsoft Word 或 Google Docs 这样的文字处理器。这些应用程序可能会引入隐藏字符,因此不适合用于编辑代码文件。弄清楚这一切后,让我们做好准备,着手处理您的第一个 WDL 工作流。

您的第一个 WDL:Hello World

我们从最简单的可行的 WDL 脚本示例开始:典型的HelloWorld。如果你对此不熟悉,这在编程语言的文档中是一个常见的概念;简而言之,这个想法是提供一个包含最少量代码但能产生HelloWorld!短语的入门示例。我们将运行三个基本的 WDL 工作流来演示这个功能级别,从绝对最小的示例开始,然后仅添加足够的代码来展示核心功能,尽管技术上不是必需但在实际使用中需要的部分。

通过最简示例学习基本的 WDL 语法

通过将 hello-world.wdl 工作流文件加载到 nano 编辑器中,我们来看一下最简单的示例:

          scatter (interval_file in
          scattered_calling_intercals
          ) {
            ...
            call HaplotypeCaller {}
              input:
                input_am = 
          select_first([CramToBamTask.output_bam,
          input_bam]),
                ...
              }
          }

正如前面提到的,nano是一个基本的编辑器。你可以使用键盘上的箭头键在文件中移动。要退出编辑器,请按 Ctrl+X。

这是 WDL 的极简 Hello World 的样子:

          call MergeGVCFs {
            input:
              input_vcfs = 
          HaplotypeCalle.output_vcf,
              ...
          }

首先,我们先忽略除了有HelloWorld短语的那一行之外的所有内容,那行短语在引号内。你能认出那行命令吗?没错,它是一个简单的echo命令;你可以立即在你的终端上运行那行:

if ( is_cram ) {
    call CramToBamTask {
          input:
            input_cram = input_bam,
            ...
    }
  }

所以这是我们脚本核心的执行所需操作的命令,其余都是为了使其通过我们的工作流管理系统以脚本形式运行而包装的。

现在我们来分解这个包装。在最高层,我们只有两个不同的代码块:一个以workflow HelloWorld开头的块,另一个以task WriteGreeting开头的块,在每种情况下,大括号中有几行代码(WDL 的原始设计者真的很喜欢大括号;你会看到更多的)。我们可以像这样总结它们:

#is the input a cram file?
Boolean is_cram = sub(basename(input_bam), ".*\\.", "") == "cram"

这使得我们的脚本结构变得非常清晰:有两个部分组成,workflow块,用于调用我们希望工作流执行的操作,以及task块,用于定义操作的详细信息。这里我们只有一个任务,这在实际上并不典型,因为大多数工作流由两个或更多任务组成;我们在本节中进一步介绍多任务的工作流。

让我们更仔细地看一下动作——也就是命令——在WriteGreeting任务中是如何定义的:

Boolean is_cram      

在第一行中,我们声明这是一个名为WriteGreeting的任务。在最外层的大括号内,我们可以将代码结构分解为另外两个代码块:command {...}output {...}command块非常简单:它包含echo "Hello World"命令。所以这就很明了了,对吧?一般来说,你可以把几乎任何你想在终端运行的东西放在这里,包括管道、多行命令,甚至是像 Python 或 R 这样的“外来”代码块,只要你用heredoc 语法包裹起来。我们在第九章提供了类似的示例。

与此同时,output块可能显得不那么明显。这里的目标是定义我们计划运行的command块的输出。我们声明我们期望输出将是一个File,我们选择称之为output_greeting(此名称可以是任何您想要的,除了 WDL 规范中定义的保留关键字之一)。然后,在稍微复杂的部分,我们声明输出内容本身将是发送到stdout的任何内容。如果您对命令行术语不太熟悉,stdout是标准输出的缩写,指的是在终端窗口中显示的文本输出,这意味着当您运行命令时在终端中看到的内容。默认情况下,此内容也会保存到执行目录中的文本文件中(我们稍后会检查),因此这里我们说的是我们指定该文本文件作为我们命令的输出。在基因组工作流中做这件事可能并不是一件非常现实的事情(尽管您可能会感到惊讶……我们见过更奇怪的事情),但这就是 Hello World 的本质!

无论如何,我们的task块已经解释完毕。现在,让我们看一下workflow块:

sub(basename(input_bam), ".*\\.", "") == "cram"

好了,这很简单。首先,我们声明我们的工作流叫做HelloWorld,然后,在大括号内,我们使用call语句调用WriteGreeting任务。这意味着当我们通过 Cromwell 实际运行工作流时,它将尝试执行WriteGreeting任务。让我们试试吧。

在您的 Google 虚拟机上使用 Cromwell 运行一个简单的 WDL

退出nano编辑器,按 Ctrl+X 返回虚拟机的 shell 界面。你将使用位于~/book/bin目录下的 Cromwell .jar文件启动hello-world.wdl工作流,这在本章的设置部分我们将其别名为$BIN。命令非常简单,是普通的 Java 命令:

sub(basename(input_bam), ".*\\.", "")

此命令调用 Java 来使用其一次性(run)工作流执行模式运行 Cromwell,我们在本章早些时候将其与持久的server模式进行了对比。因此,它只会启动,运行我们提供给run命令作为输入的工作流,并在完成后关闭。目前,没有其他涉及,因为我们的工作流完全是自包含的;接下来我们会介绍如何对工作流进行参数化,以接受输入文件。

继续运行该命令。如果您已经正确设置了一切,您现在应该看到 Cromwell 开始向终端输出大量内容。我们在这里展示了输出的最相关部分,但省略了一些不符合我们立即目的的块(用[...]表示):

File input_bam = "gs://my-bucket/sample.bam"

正如您所见,Cromwell 的标准输出有点……嗯,啰嗦。Cromwell 主要设计用于作为一组相互连接的服务的一部分使用,我们在第十一章中讨论了在常规使用过程中监视进度和输出的专用界面。单次运行模式更常用于故障排除,因此开发团队选择使本地执行模式非常冗长,以帮助调试。起初可能感觉有点压倒性,但别担心:我们在这里向您展示如何解读所有内容——或者至少是我们关心的部分。

解释 Cromwell 日志输出的重要部分

首先,让我们检查我们的工作流输出是否符合预期。在终端输出中找到以下一组行:

sub("string to modify", "substring or pattern we want to replace", "replacement")

在此之前,我们看到这提供了一个以 JSON 格式列出的输出文件列表;在这种情况下,只有一个文件捕获了我们的一个echo "Hello World"命令的stdout。Cromwell 为我们提供了完全限定路径,这意味着它包括工作目录上面的目录结构,这非常方便,因为它允许我们在任何命令中快速复制和粘贴使用。您可以立即执行此操作,查看输出文件的内容,并验证其是否包含我们预期的内容:

请记住,在我们展示的命令中,您需要替换用户名和执行目录哈希。在您的输出中查找相应行可能比定制我们的命令更容易。

    basename(input_bam)     "sample.bam"
    ```

就是这样!所以我们知道它起作用了。

现在让我们花几分钟时间浏览 Cromwell 在所有这些日志输出中为我们提供的信息,以识别最相关的要点:

".*\\."            "sample."+"bam"
```

= 我正在查看此工作流并为其分配此唯一标识符。

Cromwell 为每次工作流运行生成一个随机生成的唯一标识符,并创建一个带有该标识符的目录,在该目录中将写入所有中间和最终文件。我们稍后详细讨论输出目录结构的细节。现在,您真正需要知道的是,这是设计用来确保您永远不会覆盖同一工作流之前运行的结果,或者在具有相同名称的不同工作流之间发生冲突:

    ""                ""+"bam"
    ```

> = *我计划将其发送到本地机器执行(而不是远程服务器)。*

默认情况下,Cromwell 直接在您的本地机器上运行工作流;例如,您的笔记本电脑。正如我们之前提到的,您可以配置它将作业发送到远程服务器或云服务;这在 Cromwell 术语中称为*后端分配*(不要与换尿布混淆):

"bam"
```

= 我现在正在执行HelloWorld工作流中的WriteGreeting任务调用。

Cromwell 将工作流中的每个任务调用视为一个单独的执行作业,并将根据需要为每个作业提供单独的更新。如果工作流涉及多个任务调用,Cromwell 将对它们进行排队,并在适当时发送每个任务进行执行。稍后我们将讨论其工作原理的一些方面。关于状态报告方面,您可以想象,一旦我们开始运行更复杂的工作流,通过标准输出获取这些报告是相当不实际的。这就是提供界面来解析和组织所有这些信息的前端软件真正派上用场的地方;在第十一章中,您将有机会体验到这一点:

String sample_basename = if is_cram then basename(input_bam, ".cram") else
basename(input_bam, ".bam")

= 这是我当前调用的实际命令。

从这个特定的调用中并不明显,因为我们在最小的 Hello World 示例中没有包含任何变量,但是 Cromwell 在这里输出的是实际将要执行的命令。在接下来的参数化示例中,你可以看到,如果我们在脚本中包含一个变量,日志输出将显示命令的形式,其中变量已被我们提供的输入值替换。这完全解释的命令也会输出到执行目录以供记录:

if is_cram then basename(input_bam, ".cram") else basename(input_bam, ".bam")

= 我已经完成了这个工作流。这是你想要的输出文件的完整路径。

正如前面所述,这以 JSON 格式提供了所有生成的输出文件的完整命名空间的列表。命名空间

scatter (...) {
  ...
  call HaplotypeCaller {
      input:
        input_bam = select_first([CramToBamTask.output_bam, input_bam]),
        ...
   }
}

告诉我们,我们正在查看由HelloWorld工作流中的WriteGreeting任务调用输出的output_greeting

输出文件的完全限定路径显示了整个目录结构;让我们展开来看看每个部分对应什么:

String? gatk_docker_override
String gatk_docker = select_first([gatk_docker_override, 
                                  "us.gcr.io/broad-gatk/gatk:4.1.0.0"])

这个结构中的重要部分是 workflow/identifier/calls 的嵌套。正如你将在下一个练习中看到的那样,具有相同名称的工作流的任何运行都将添加到HelloWorld工作流目录下,具有另一个唯一标识符的新目录。

$ export CASE2=~/book/code/workflows/mystery-2

= 嘿,一切都正常工作!

$ java -jar $BIN/womtool-48.jar graph $CASE2/WholeGenomeGermlineSingleSample.wdl  \
    > ~/sandbox-9/WholeGenomeGermlineSingleSample.dot

= 我这里都完成了。再见。

现在你只需关注这一点,即完成你的第一个 Cromwell 工作流执行。干得漂亮!

添加一个变量并通过 JSON 提供输入

好的,但是运行一个完全自包含的 WDL 是不现实的,所以让我们看看如何添加变量以引入一些可以从运行到运行变化的外部输入。在nano编辑器中,继续打开代码目录中的hello-world-var.wdl

$ cat ~/sandbox-9/WholeGenomeGermlineSingleSample.dot
digraph WholeGenomeGermlineSingleSample {
 #rankdir=LR;
 compound=true;
 # Links
 CALL_UnmappedBamToAlignedBam -> CALL_BamToCram
 CALL_UnmappedBamToAlignedBam -> CALL_CollectRawWgsMetrics
 CALL_UnmappedBamToAlignedBam -> CALL_CollectWgsMetrics
 CALL_UnmappedBamToAlignedBam -> CALL_AggregatedBamQC
 CALL_UnmappedBamToAlignedBam -> CALL_BamToGvcf
 CALL_AggregatedBamQC -> CALL_BamToCram
 # Nodes
 CALL_AggregatedBamQC [label="call AggregatedBamQC";shape="oval";peripheries=2]
 CALL_BamToGvcf [label="call BamToGvcf";shape="oval";peripheries=2]
 CALL_UnmappedBamToAlignedBam [label="call
UnmappedBamToAlignedBam";shape="oval";peripheries=2]
 CALL_BamToCram [label="call BamToCram";shape="oval";peripheries=2]
 CALL_CollectRawWgsMetrics [label="call CollectRawWgsMetrics"]
 CALL_CollectWgsMetrics [label="call CollectWgsMetrics"]
}

有什么不同?workflow块完全相同,但现在WriteGreeting task块中发生了更多的事情:

        call ToBam.UnmappedGamToAlignedBam {
          input:
            sample_and_unmapped_bams =
        sample_and_unmapped_bams,
            ...
        }

echo命令的Hello World输入已被${greeting}替换,现在在command块之前有一个新的input块,其中包含String greeting的行。这行声明了名为greeting的变量,并声明其值应为String类型;换句话说,是一个字母数字序列。这意味着我们已经参数化了要回显到终端的问候语;我们将能够指示 Cromwell 在每次运行时插入到命令中的内容。

这带来了下一个问题:我们如何向 Cromwell 提供这个值?我们绝对不想直接在命令行上给出它,因为虽然这个特定情况很简单,但在未来我们可能需要运行预期需要多达数十个值的工作流,其中许多值比简单的String更复杂。

Cromwell 期望您以JavaScript 对象表示法(JSON)文本格式提供输入。JSON 具有键值对结构,允许我们为每个变量分配一个值。您可以在我们提供的$WF/hello-world/hello-world.inputs.json文件中看到一个示例:

          call AggregatedQC.AggregatedBamQC {
            input:
              base_recalibrated_bam = 
          UnmappedBamToAlignedBam.output_bam,
              ...
          }

在这个简单的inputs JSON 文件中,我们通过其完全限定名定义了来自我们HelloWorld工作流的greeting变量,该名包括工作流本身的名称(HelloWorld),然后是任务(WriteGreeting)的名称,因为我们在任务级别声明了变量,然后是变量本身的名称。

要将inputs JSON 文件提供给 Cromwell,只需通过使用-i参数(--input的缩写)添加到您的 Cromwell 命令中,如下所示:

          call ToCram.BamToCram as BamToCram {
            input:
              input_bam = 
          UnmappedBamToAlignedBam.output_bam,
              ...
          }

以与之前相同的方式查找输出;您应该看到由工作流输出的消息与 JSON 文件中的文本匹配。

Cromwell 在所有级别都强制使用全限定名称,这使得不可能声明全局变量。虽然这可能感觉像一种累赘的限制,但比起允许变量在工作流的不同部分中重复命名而言,这更安全。在简单的工作流中,足以轻松跟踪变量并防止此类问题,但在有数十个以上变量的更复杂的工作流中,这可能会变得相当困难。特别是当您使用导入和子工作流来促进代码重用时,我们在第九章中介绍了这一点(哦,剧透)。请注意,您可以在工作流级别声明变量(并在inputs JSON 文件中使用输入命名语法WorkflowName.variable),但您需要显式地将其传递给任何想要在其中使用它的任务调用。稍后在本章中将会看到这种操作的示例。

添加另一个任务以使其成为适当的工作流

现实世界的工作流通常不止一个任务,并且它们的某些任务依赖于其他任务的输出。在nano编辑器中,打开hello-world-again.wdl

          #QC the sample WGS metrics (stringent thresholds)
          call QC.CollectWgsMetrics as 
          CollectWgsMetrics{
            input:
              input_bam =
          UnmappedBamToAlignedBam.output_bam,
          ...
        }

这是我们尝试的第三次迭代的 Hello World 示例,展示了两个任务如何链接成一个正确的工作流:

          #QC the sample WGS metrics (common thresholds)
          call QC.CollectRawWgsMetrics as 
          CollectWgsMetrics{
            input:
              input_bam =
          UnmappedBamToAlignedBam.output_bam,
          ...
        }

您可以看到workflow块现在有更多的内容;它有一个指向新任务ReadItBackToMe的额外调用语句,并且该调用语句附有花括号中的代码,我们将其称为input块:

          call ToGvcf.VariantCalling as BamToGvcf {
            input:
              ...
              input_bam = 
          UnmappedBamToAlignedBam.output_bam,
              ...
          }

input块允许我们从工作流级别传递值到特定任务调用。在这种情况下,我们引用了WriteGreeting任务的输出,并将其分配给名为written_greeting的变量,以在ReadItBackToMe调用中使用。

让我们来看看那个新的任务定义:

call ToBam.UnmappedBamToAlignedBam

read_string()部分是 WDL 标准库中的一个函数,用于读取文本文件的内容并将其以单个字符串的形式返回。因此,此任务旨在将文件内容读入String变量中,然后使用该变量来组成新的问候语并将其回显到stdout

鉴于此,附加到ReadItBackToMe调用语句的额外代码完全合理。我们正在调用ReadItBackToMe任务,并指定我们用于撰写新问候语的输入文件应该是调用WriteGreeting任务的输出。

最后,让我们看一看这个新版本工作流中我们还没有检查过的最后一段代码:

import "tasks/UnmappedBamToAlignedBam.wdl" as ToBam

除了各个任务级别的output块外,此工作流还在工作流级别定义了一个output块。当工作流打算单独运行时,此工作流级别的输出定义完全是可选的;这更多地是一种惯例而不是功能性的问题。通过定义工作流级别的输出,我们可以明确表达工作流生成的输出中我们关心哪些。话虽如此,你可以在功能上使用这个输出定义;例如,当工作流将用作嵌套子工作流并且我们需要将其输出传递给进一步的调用时。你将在第九章看到它的实际应用。现在,尝试使用与前一个工作流相同的输入 JSON 运行此工作流,然后查看执行目录,了解任务目录之间的关系以及输出的位置。

您的第一个 GATK 工作流:Hello HaplotypeCaller

现在您已经牢固掌握了基本的 WDL 语法,让我们转向一个更现实的例子集:真实的 GATK 流水线!我们从一个非常简单的工作流开始,逐步建立您对语言的熟悉度。我们希望运行 GATK HaplotypeCaller的工作流程是线性的(无并行化),以 GVCF 模式处理单个样本的 BAM 文件,如图 8-1 中所示。

概念图,展示运行 HaplotypeCaller 的假设工作流程。

图 8-1. 运行 HaplotypeCaller 的假设工作流程。

这个工作流应该接受通常要求的文件——基因组参考、输入读取和要分析的区间文件(在技术上 GATK 认为是可选的,但在这里我们通过 WDL 使其成为必需的)——并输出一个以输入文件命名的 GVCF 文件。

探索 WDL

为了说明这一切,我们通过一个单独的任务HaplotypeCallerGVCF编写了一个 WDL 工作流来满足这些要求。现在在nano编辑器中打开它:

call ToBam.UnmappedBamToAlignedBam

让我们详细讨论脚本的主要部分,回想一下我们的 HelloWorld 示例的结构:

# WORKFLOW DEFINITION
workflow UnmappedBamToAlignedBam {

为了清晰起见,折叠任务,您可以看到这确实是一个单任务工作流,只有一个单独的调用,并且在workflow块中没有其他操作:

$ java -jar $BIN/womtool-48.jar graph $CASE2/tasks/VariantCalling.wdl \
    > ~/sandbox-9/VariantCalling.dot

因此,让我们更详细地查看HaplotypeCallerGVCF任务,从command块开始,因为最终我们将从中获取关于任务实际操作的大部分信息:

call Utils.ScatterIntervalList as ScatterIntervalList 

我们看到一个典型的 GATK 命令,调用HaplotypeCaller以 GVCF 模式运行。它使用占位符变量来表示预期的输入文件以及输出文件。它还包括一个占位符变量,用于传递 Java 选项,如在第五章中描述的内存堆大小。到目前为止,这相当简单明了。

所有这些变量都应该在某处定义,所以让我们找找它们。按照惯例,我们在任务描述的开头,在 command 块之前这样做。这是我们在那里看到的内容:

import "tasks/Utilities.wdl" as Utils

现在暂时忽略 String docker_image 行,这显示我们声明了所有输入文件的变量以及 Java 选项,但我们没有为它们分配值。因此,任务将期望在运行时接收所有这些变量的值。不仅如此,它还将期望为我们经常视为理所当然的所有附属文件提供值:refIndexrefDictinputBamIndex,它们是索引和序列字典。我们没有将这些文件包含在命令本身中,因为 GATK 自动检测它们的存在(只要它们的名称符合其主文件的格式约定),但我们需要通知 Cromwell 它们的存在,以便它在运行时可用于执行。

不过,有一个例外;对于输出文件,我们看到了这一行:

version 1.0

## Copyright Broad Institute, 2018
##
## This WDL defines utility tasks used for processing of sequencing data.
##
...

# Generate sets of intervals for scatter-gathering over chromosomes
task CreateSequenceGroupingTSV {
  input {
    File ref_dict
...

basename(input_bam, ".bam") 是 WDL 标准库中的便捷函数,允许我们根据输入文件的名称创建输出文件的名称。basename() 函数接受输入文件的完整路径,去除文件名前面的路径部分,并可选地去除文件名末尾的指定字符串。在这种情况下,我们去除了预期的 .bam 扩展名,然后使用行中的 + ".g.vcf" 部分添加新的扩展名,以适用于输出文件。

说到输出文件,现在让我们跳到任务级别的 output 块:

task ScatterIntervalList {
  input {
    File interval_list
    Int scatter_count
    Int break_bands_at_multiples_of
  }

  command <<<
...

这也很直接:我们声明该命令将生成一个我们关心的输出文件,并为其命名,以便在工作流程内处理,并提供相应的占位变量,以便 Cromwell 在命令完成后可以识别正确的文件。

从技术上讲,在工作流程和任务定义中如果您计划在具有程序本地安装的系统上运行,则这就是您所需的全部内容。然而,在本章中,您是在虚拟机中工作,但在 GATK 容器外部工作,因此 GATK 不能直接用于您的工作流程。幸运的是,Cromwell 能够利用 Docker 容器,所以我们只需在工作流程中添加一个 runtime 块来指定容器镜像:

import "tasks/VariantCalling.wdl" as ToGvcf

这就是为什么我们在任务变量中有那一行 String docker_image:我们还使用一个容器镜像的占位变量。在下一步填写输入 JSON 时,我们将指定 us.gcr.io/broad-gatk/gatk:4.1.3.0 镜像。然后,当我们启动工作流程时,Cromwell 将从我们指定的镜像中启动一个新的容器,并在其中运行 GATK。

从技术上讲,我们可以在这里硬编码图像名称,使用双引号(例如,docker: "us.gcr.io/broad-gatk/gatk:4.1.3.0"),但除非您真的想将特定脚本固定到特定版本,显著降低了灵活性,否则我们不建议这样做。有些人使用latest标签使其工作流始终与程序的最新可用版本一起运行,但我们认为这是一个坏习惯,弊大于利,因为您永远不知道最新版本可能会改变什么并且打破您的工作流。

生成输入 JSON

好了,我们已经浏览了工作流中的所有代码;现在我们需要确定在运行它时如何提供工作流的输入。在“您的第一个 WDL:Hello World”中,我们运行了一个非常简单的工作流,首先没有任何变量输入,然后只有一个变量输入。在单输入情况下,我们创建了一个指定该输入的 JSON 文件。现在我们有八个需要指定的输入。我们可以像以前一样继续——创建一个 JSON 文件并写入HaplotypeCallerGVCF任务所需的每个输入的名称,但有一个更简单的方法:我们将使用Womtool inputs命令创建一个模板 JSON。

首先,因为我们将首次写入我们关心的文件,让我们创建一个sandbox目录来组织我们的输出:

call ToGvcf.VariantCalling as BamToGvcf {
    input:
      ...
      input_bam = UnmappedBamToAlignedBam.output_bam,

现在,您可以运行生成inputs JSON 模板文件的Womtool命令:

call Utils.ScatterIntervalList as ScatterIntervalList 

由于在此命令的最后一行指定了输出文件(实际上是可选的),该命令将其输出写入该文件。如果一切顺利,您在终端上不应该看到任何输出。让我们看看刚刚创建的文件的内容:

[PRE45]

[PRE46]

所以你看:HaplotypeCallerGVCF任务预期的所有输入都以适当的方式列出,并且用占位符值表示其类型,以 JSON 格式(尽管在您的情况下可能顺序不同)。现在我们只需填写这些值;这些是前六个相关文件的路径和最后两个的运行时参数(容器映像和 Java 选项)。出于懒惰精神,我们提供了一个填写好的版本,其中使用了我们在第五章中使用的片段数据,但如果您愿意,您也可以通过填写inputs JSON 来处理数据包中第四章下载的其他输入。这就是预填充 JSON 的样子(路径相对于主目录):

[PRE47]

[PRE48]

请注意,所有的值都显示在双引号中,但这是一个小小的瑕疵,因为这些值都是 String 类型。对于其他类型,如数字、布尔值和数组,您不应该使用双引号。当然,对于字符串数组,您应该在字符串周围使用双引号,而不是在数组本身周围,["like","this"]

运行工作流

要运行工作流,我们将使用与前面相同的命令行语法。确保在您的主目录中执行此操作,以便 inputs JSON 文件中的相对路径与数据文件的位置匹配:

[PRE49]

正如前面提到的,Cromwell 输出非常详细。在这个练习中,您需要查找终端输出中类似以下行的内容:

[PRE50]

这里最令人兴奋的片段是 状态从 WaitingForReturnCode 变为 Done完成状态为'Succeeded',这意味着您的工作流已经运行完成,并且所有运行的命令都表示它们运行成功。

输出的另一个令人兴奋的部分是输出的路径。在接下来的部分中,我们会稍微谈一谈为什么它们会列出两次;现在,让我们高兴地注意到 Cromwell 精确告诉我们在哪里找到我们的输出文件。当然,这个特定工作流的输出是一个 GVCF 文件,所以阅读起来并不是很愉快,但关键是文件在那里,并且它的内容是您期望看到的。

我们使用 head 实用程序来完成这个目的;请记住,您需要将执行目录哈希在以下示例中显示的文件路径中(9a6a9c97-7453-455c-8cd8-be8af8cb6f7c)替换为输出中显示的哈希值:

[PRE51]

这次运行应该很快完成,因为我们使用的间隔列表仅涵盖一个短区域。这里大部分时间花费在 Cromwell 启动和容器的启动上,而 GATK HaplotypeCaller 本身只运行了极短的时间。就像 Hello World 示例一样,您可能会觉得这对于如此小的工作量来说是一种过度劳动,您是对的;这就像用火箭筒打苍蝇一样。对于玩具示例,让 Cromwell 运行起来的开销远远超过了分析本身。当我们进行适当的全面分析时,像 Cromwell 这样的工作流管理系统真正显示其价值,这一点您将在第十章和第十一章中体验到。

打破工作流以测试语法验证和错误消息

希望到目前为止一切都如预期般顺利,这样你就知道成功是什么样子。但实际情况是,事情偶尔会出错,所以现在让我们看看失败的样子。具体来说,我们将看看 Cromwell 如何处理两种常见的脚本错误,即 WDL 语法和command块语法错误,通过在 WDL 中引入一些错误。首先,让我们复制工作流文件,以便可以自由玩耍:

[PRE52]

现在,在你喜欢的文本编辑器中,打开新文件并在 WDL 语法中引入一个错误。例如,你可以弄乱一个变量名、一个保留关键字,或者删除basename()函数调用中的第二个括号。这种小错误是致命的,但很容易被忽视。让我们看看当我们运行这个时会发生什么:

[PRE53]

正如预期的那样,工作流执行失败,Cromwell 为我们提供了一些冗长的错误消息:

[PRE54]

这里有很多我们不关心的内容,比如我们这里没有展示的堆栈跟踪。真正重要的部分是Workflow input processing failed: ERROR: Unexpected symbol。这是一个明显的暗示,你可能有语法问题。Cromwell 会试图给出更具体的指示,指出语法错误可能出现的位置;在这种情况下,它非常准确——它预期但没有找到第 20 行的右括号(rparen表示右括号)。但要注意,有时候情况并不那么明显。

当你正在积极开发一个新的工作流时,你可能不想每次需要测试一些新代码的语法时都要通过 Cromwell 启动工作流。好消息是:你可以通过使用Womtool的验证命令来节省时间。这实际上是 Cromwell 在幕后运行的命令,它是一个非常轻量级的方式来测试你的语法。现在就在你的出错工作流上试试吧:

[PRE55]

看到了吗?你在更短的时间内获得了 Cromwell 输出的重要部分——甚至不需要提供工作流的有效输入。作为额外的练习,尝试引入其他 WDL 语法错误;例如,尝试删除变量声明,仅在其出现中更改变量名称,并拼错workflowcommandoutputs等保留关键字。这将帮助你识别验证错误并解释Womtool如何报告它们。总的来说,我们强烈建议在任何新的或更新的 WDL 上系统地使用Womtool validate(是的,它也适用于 CWL)。

话虽如此,重要的是要理解,Womtool 的 WDL 语法验证只能帮助到这一步:它不能处理其他范围之外的任何其他错误,例如,如果您搞乱了要运行的工具的命令语法。为了看看在这种情况下会发生什么,您可以在沙盒中再次复制原始工作流(将其命名为 hc-break2.wdl),然后打开它,这次在 GATK 命令中引入一个错误,例如,将工具名称弄错为 HaploCaller

[PRE56]

如果您运行 Womtool validate,您会看到此工作流程通过验证顺利进行;Womtool 欢快地报告 Success! 但是如果您实际通过 Cromwell 运行它,工作流程肯定会失败:

[PRE57]

浏览输出以找到显示失败消息的行:

[PRE58]

记录中写道 Job HelloHaplotypeCaller.HaplotypeCallerGVCF:NA:1 exited with return code 2 表明 HaplotypeCallerGVCF 是失败的任务,具体来说,它运行的命令报告了退出码 2。这通常表示您尝试运行的工具在某些地方出现问题——可能是语法问题、未满足的输入要求、格式错误、内存不足等。

错误消息继续指出,您可以通过查看命令生成的标准错误 (stderr) 输出来了解更多情况,并且它还包含了完整的文件路径,以便您轻松地查看其中的内容。它还包括 stderr 日志的前几行,以便方便查看,如果工具的错误输出非常简短,这有时就足够了。由 GATK 生成的 stderr 输出较为冗长。因此,我们需要检查完整的 stderr 来找出问题所在。同样,请确保用您输出中显示的用户名和执行目录哈希(dd77316f-7c18-4eb1-aa86-e307113c1668)替换这里显示的内容:

[PRE59]

啊,看这个!我们错误地写了工具的名称;谁知道呢?顺便说一句,赞美 GATK 建议的更正;这在 GATK4 中是新功能。

注意

所有命令行工具在完成运行时都会输出一个 返回码 作为简洁的状态报告方式。根据工具的不同,返回码可能更或少有意义。通常情况下,返回码为 0 表示成功,其他任何返回码表示失败。在某些情况下,非零返回码可能表示运行成功;例如,Picard 工具 ValidateSamFile 在运行成功但发现文件中存在格式验证错误时会报告非零返回码。

这里没有涵盖的其他问题可能会出错,比如如果您的输入文件路径错误,或者在输入 JSON 中忘记将字符串输入用双引号括起来。我们建议您故意制造这些错误来进行实验,因为这将帮助您更快地学会诊断问题。

介绍分散-聚集并行性

在本章中,我们将再次介绍一个工作流示例,以完成你对 WDL 和 Cromwell 的首次接触,因为我们真的希望你能尝试一下并行化的强大之处,如果你之前没有这样的经验的话。现在我们将看一个工作流,通过scatter()函数来并行化我们之前通过HaplotypeCaller任务运行的操作,并在随后的步骤中合并并行作业的输出,如图 8-2 所示。

并行执行 HaplotypeCaller 的工作流的概念图。

图 8-2. 并行执行 HaplotypeCaller 的工作流的概念图。

这个工作流将让你体验到 scatter-gather 并行处理的魔力,这在基因组工作流中尤其重要,特别是在云端。它还将让你有机会更详细地挖掘基于输入和输出将多个任务串在一起的机制,比我们之前涵盖的更详细一些。

探索 WDL

这里是一个完整的 WDL,它将HaplotypeCallerGVCF的操作并行化处理到子集间隔中。在nano编辑器中,像往常一样打开它:

[PRE60]

让我们逐步了解脚本的主要部分,指出与线性实现此工作流有何不同的地方:

[PRE61]

最明显的区别是现在workflow块中发生了很多变化。行动的核心是这个子集(为了可读性,合并了两个call块):

[PRE62]

以前,我们只是简单地调用了HaplotypeCallerGVCF任务,就这样。现在,你可以看到对HaplotypeCallerGVCF的调用被隶属于一个更高级的操作,就在这行下面,打开了一个scatter块:

[PRE63]

对于这样一个简短的行,它做了很多工作:这是我们如何在子集间隔上并行执行HaplotypeCaller的过程,这些间隔在括号中指定。calling_intervals变量引用一个间隔列表,而HaplotypeCallerGVCF任务将作为单独的调用运行在提供的文件中的每个间隔上。如果你熟悉脚本构造,这可能看起来很像一个for 循环,事实上,它确实类似于这种构造,其目的是对列表中的每个元素应用相同的操作。然而,scatter 是专门设计用来允许每个操作的独立执行,而 for 循环导致线性执行,其中每个操作只能在前一个(如果有的话)完成执行后才能运行。

现在你理解了scatter()指令的作用,让我们深入了解HaplotypeCallerGVCF任务本身,从它如何被调用开始:

[PRE64]

在工作流的先前版本中,对任务的调用只是关键字call后跟任务名称。在这个版本中,该语句包括一个input块,指定了任务所需的一些变量的值:输入 BAM 文件及其索引,间隔文件,以及 GVCF 输出文件的名称。说到这一点,你会注意到我们用于生成输出名称的basename函数调用现在发生在这个input块中,而不是在任务内部。如果你比较了这个版本和之前版本的HaplotypeCallerGVCF任务定义,这是唯一的区别。让我们心里记下这一点,因为几分钟后会再次提到。

现在让我们谈谈包含HaplotypeCallerGVCF调用的scatter块后面的内容:对一个名为MergeVCFs的新任务的调用。清楚地说,这个调用语句在scatter块之外,因此它只会运行一次。

[PRE65]

根据任务名称,你可能已经对此任务的用途有了相当好的了解,但让我们假装不是那么明显,并按照逻辑路径解读工作流的结构。预览MergeVCFs任务定义,我们看到它运行的命令是一个调用 GATK 命令,调用一个名为MergeVcfs的工具(实际上是捆绑在 GATK4 中的 Picard 工具)。作为输入,它接受一个或多个 VCF 文件(其中 GVCF 是一个子类型),并输出单个合并文件:

[PRE66]

我们可以通过命令参数和变量名称推断出这一点,并通过查阅 GATK 网站上的MergeVcfs工具文档进行确认。

注意

在此需要注意的 WDL 语法新点:-I ${sep=' -I' vcfs}的形式是我们处理需要将任意长度的项目列表放入命令行,并为每个项目使用相同参数的方法。给定文件列表[FileA, FileB, FileC],前面的代码将生成命令行的以下部分:-I FileA -I FileB -I FileC

MergeVCFs任务定义还告诉我们,该任务期望一个文件列表(技术上表示为Array[*File*])作为其主要输入。让我们看看任务在workflow块中的调用方式:

[PRE67]

如果你还记得HelloWorld​Again工作流中的ReadItBackToMe任务,这可能会让你感到熟悉。与之前一样,我们通过引用HaplotypeCallerGVCF任务的输出来为vcfs输入变量赋值。不同之处在于,在那种情况下,我们将单个文件输出传递给单个文件输入。相比之下,这里我们引用了位于scatter块内的任务调用的输出。这究竟是什么样子呢?

优秀的问题。根据定义,在scatter块内的每个单独调用任务的生成的输出都是单独的。scatter结构的巧妙之处在于,这些单独的输出会自动收集到一个列表(技术上是一个数组)中,列表的名称是任务指定的输出变量的名称。因此,在这里,虽然HaplotypeCallerGVCF任务的单个调用的输出值,即HaplotypeCallerGVCF.output_gvcf,是一个单个的 GVCF 文件,但在workflow块中引用的HaplotypeCallerGVCF.output_gvcf的值是在scatter块中生成的 GVCF 文件的列表。当我们将该引用传递给下一个任务调用时,我们实际上是将完整的文件列表作为数组提供。

让我们通过指出一些更多细节来完成这次探索。首先,您可能会注意到我们在 workflow 级别的output块中明确声明了MergeVCFs调用的输出作为工作流的最终输出。这在技术上并不是必需的,但是这是一个好的做法。其次,BAM 文件、其索引和间隔文件的变量声明都被提升到了 workflow 级别。对于 BAM 文件及其索引,这一举措使我们能够在两个任务调用的input块中生成输出文件的名称,这在其他方面带来了更多的灵活性,如果我们想要将我们的任务定义放入一个公共库中。对于这样的东西,我们希望任务尽可能地通用,并将诸如文件命名约定之类的细节留给工作流实现。至于间隔文件,我们需要在 workflow 级别可用它,以便实现scatter块。

最后,现在你应该能够生成inputs JSON 了,根据前面的练习填写它,并使用与以前相同的设置运行工作流。如果你想节省填写文件路径的麻烦,我们提供了一个预填充的 JSON;只需确保使用.local.inputs.json版本而不是.gcs.inputs.json,我们在第十章中使用它。

这是使用预填充本地输入 JSON 的 Cromwell 命令。确保在您的主目录中执行此命令,以使inputs JSON 文件中的相对路径与数据文件的位置匹配:

[PRE68]

当您运行此命令时,您可能会注意到散点作业在虚拟机上并行运行。 这很好,因为这意味着作业会更快完成。 不过,如果您尝试在整个基因组上运行五百个散点调用会发生什么呢? 并行运行这些调用将导致问题;如果不是直接崩溃,至少会使您的虚拟机陷入困境,因为它会疯狂地将 RAM 交换到磁盘。 好消息是,针对这种情况有几种解决方案。 首先,您可以通过 “local” 后端控制 Cromwell 允许的并行性级别,如 在线文档 中所述。 或者,您可以使用设计用于优雅处理这种情况的不同后端。 在 第十章 中,我们将向您展示如何使用 Google 后端自动将并行作业发送到多个虚拟机。

生成可视化的图形图表

作为这个练习的收尾,让我们学习如何应用另一个 Womtool 实用程序: graph 命令。 到目前为止,我们所看到的工作流非常简单,步骤和整体管道都很简单。 在下一章节(以及现实世界中),您将会遇到更复杂的工作流程,仅依靠代码可能难以建立起心理模型。 这时候,能够生成工作流程可视化图形将会非常有帮助。 Womtool graph 命令允许您生成 .dot 格式的图形文件,并使用通用图形可视化工具进行可视化:

[PRE69]

.dot 文件是一个纯文本文件,因此您可以在终端中查看它;例如:

[PRE70]

不过,这样看起来并不直观,所以让我们将其加载到图形查看器中。 有许多可用选项,包括非常流行的开源软件包 Graphviz。 您可以在本地安装该软件包,或者通过其 许多在线实现 之一使用它。 要这样做,只需将 .dot 文件的内容复制到可视化应用程序的文本窗口中,它将为您生成图形图表,如 Figure 8-3 所示。

在在线 Graphviz 应用程序中可视化工作流图。

图 8-3. 在在线 Graphviz 应用程序中可视化工作流图。

当我们像这样可视化工作流图时,通常首先查看表示工作流中任务调用的椭圆形。 在这里,您可以看到我们工作流中的两个任务调用确实存在,分别是 HaplotypeCallerGVCFMergeVCFs。 连接椭圆形的箭头方向表示执行流程,因此在 Figure 8-3 中,您可以看到 HaplotypeCallerGVCF 的输出将成为 MergeVCFs 的输入。

有趣的是,对HaplotypeCallerGVCF的调用显示为带有框,这意味着它受到修改函数的控制。修改函数表示为六边形,在这里你可以看到六边形标记为“在字符串作为间隔上进行散列”。这一切都很合理,因为我们刚刚讨论过,在这个工作流中,HaplotypeCaller任务的执行是在一系列间隔上进行散列的。

在这种情况下,工作流足够简单,以至于图形可视化并没有告诉我们任何我们不知道的东西,但是当我们在下一章中处理更复杂的工作流时,图形可视化将成为我们工具箱中至关重要的一部分。

注意

这结束了本章的练习,所以不要忘记停止您的虚拟机;否则,您将支付仅仅让它空闲地思考存在的无意义。

结尾和下一步

在本章中,我们看了如何将单个命令串成一个简单的 Hello World 工作流,并在我们在第四章中设置的单个虚拟机上使用 Cromwell 的一次性运行模式执行它。我们介绍了解释 Cromwell 终端输出和查找输出文件的基础知识。我们在原始的Hello​World工作流上进行了迭代,添加了变量和额外的任务。然后,我们继续检查运行真实 GATK 命令并使用散集-聚集并行性的更实际的工作流,尽管规模仍然相对较小。在这一过程中,我们还练习了生成 JSON 模板、验证 WDL 语法、测试错误处理和生成图形可视化的关键实用程序。

然而,我们只是浅尝了 WDL 作为工作流语言的能力,现在是时候转向一些更复杂的工作流了。在第九章中,我们将研究两个神秘的工作流,并试图逆向工程它们的功能,这将为您提供磨练侦探技能的机会,同时学习一些在真实基因组分析工作流中使用的有用模式。

第十章:使用管道 API 在规模上运行单一工作流程

在第八章中,我们首次开始运行工作流程,使用了 GCP 中的自定义虚拟机设置。然而,那种单一机器的设置并不能利用云的最大优势:即按需提供看似无限数量的机器!因此,在本章中,我们使用了 GCP 提供的名为基因组管道 API(PAPI)的服务,它作为 GCP 计算引擎实例的作业调度程序,正好能做到这一点。

首先,我们尝试简单地更改我们虚拟机上的 Cromwell 配置,以将作业执行提交到 PAPI 而不是本地机器。接着,我们尝试一个名为WDL_Runner的工具,它包装了 Cromwell 并管理向 PAPI 的提交,这使得“启动并忘记”WDL 执行变得更加容易。这两个选项我们在本章的前半部分进行探讨,将为我们打开门户,使我们能够在第九章中的单一 VM 设置上无法运行的全面 GATK 流水线。在此过程中,我们还讨论了诸如运行时、成本、可移植性以及在云中运行工作流的整体效率等重要考虑因素。

介绍 GCP 基因组管道 API 服务

基因组管道 API 是由 GCP 运营的服务,使得可以轻松地将作业调度到 GCP 计算引擎上,而无需直接管理 VM。尽管其名称中带有“基因组”二字,但基因组管道 API 并不限于基因组研究,因此可以用于许多不同的工作负载和用例。通常,我们简称为管道 API 或 PAPI。可以按照Google Cloud 文档中描述的方式使用 PAPI 直接执行特定的分析命令,但在本章中,我们的目标是在 Cromwell 的工作流执行上使用 PAPI,如图 10-1 所示。

Cromwell + PAPI 操作概述。

图 10-1。Cromwell + PAPI 操作概述。

正如在第八章中一样,我们提供了一个 WDL 描述我们的工作流程给 Cromwell 引擎,然后 Cromwell 解释工作流并生成需要执行的各个作业。新的是,我们不再让 Cromwell 将作业交给运行在其自身所在机器上的本地执行器,而是要将其指向 PAPI,如图 10-1 所示。

对于 Cromwell 发送到 PAPI 的每个作业,该服务将在 Google Compute Engine 上创建一个具有指定运行时属性(CPU、RAM 和存储)的虚拟机,设置在 WDL 中指定的 Docker 容器,将输入文件复制到虚拟机的本地磁盘,运行命令,将任何输出和日志复制到最终位置(通常是 GCS 存储桶),最后删除虚拟机并释放任何相关的计算资源。这使得快速调度一批自定义虚拟机、执行工作流任务,并在完成后获取结果变得非常简便,而无需担心管理计算资源,因为这一切都已为您处理。

注意

当我们完成本书时,Google Cloud 将推出一个更新版本的服务,以新名称“Life Sciences API”发布。一旦我们有机会尝试它,我们将撰写关于新服务以及如何调整书中练习以使用它的博客文章。与此同时,我们预计基因组管道 API 将在可预见的未来继续可用。

在您的 Google Cloud 项目中启用基因组 API 和相关 API

要运行本章的练习,您需要在您的 Google Cloud 项目中启用三个 API:基因组 API云存储 JSON API计算引擎 API。您可以使用我们提供的直接链接,或者您可以转到 Google Cloud 控制台的 APIs & Services 部分,并单击“+ 启用 API 和服务”按钮。单击该按钮将带您到 API Library,在那里您可以通过名称搜索每个 API。如果您在搜索结果中看到具有类似名称的 API 时感到困惑,只需检查图 10-2 中显示的标志和描述即可。

三个必需 API 的标志和描述:基因组 API、云存储 JSON API 和计算引擎 API。

图 10-2. 三个必需 API 的标志和描述:基因组 API、云存储 JSON API 和计算引擎 API。

在每个 API 的页面上,您会看到一个蓝色按钮,上面写着“Enable”或“Manage”。后者表示该 API 已在您的项目中启用,您无需采取任何措施。如果看到“Enable”按钮,请单击以启用 API。您可能会看到一条消息,指出“要使用此 API,您可能需要凭据。单击‘创建凭据’以开始。”但您可以忽略它——GCP 和 Cromwell 将处理身份验证,而无需您提供额外的凭据。

注意

要使用计算引擎 API,即使使用免费信用额度,您也必须在计费部分设置支付方式。如果您按照 第四章 中的说明操作,您应该已经准备好了;但如果您直接跳到这里,请返回并按照该章节中的计费设置操作。

直接将 Cromwell 作业发送到 PAPI

在第八章中,我们在没有配置文件的情况下运行了 Cromwell,因此默认将所有作业发送到机器的本地执行器。现在,我们将提供一个配置文件,使 Cromwell 指向 PAPI 进行执行。

有趣的事实是,您可以配置 Cromwell 以从任何地方运行通过 PAPI 提交的作业,无论您是在笔记本电脑、本地服务器还是不同云平台的虚拟机上运行 Cromwell。在这里,我们展示了如何在您的 VM 上使用 GCP 执行此操作,前提是您已经适当地设置了(已安装 Cromwell 并通过 GCP 进行了身份验证),但基本的操作步骤和要求在其他任何地方也是相同的。

配置 Cromwell 以与 PAPI 通信

Cromwell 文档中有一个关于使用 GCP 的部分,其中包括一个用于在 PAPI 上运行的模板配置文件,我们已将其复制到书籍捆绑包中供您方便使用。如果您按照第四章中提供的说明操作,您可以在 VM 上的~/book/code/config/google.conf找到它。如果您在文本编辑器中打开google.conf文件,它看起来像这样:

$ cat ~/book/code/config/google.conf

# This is an example configuration file that directs Cromwell to execute
# workflow tasks via the Google Pipelines API backend and allows it to retrieve 
# input files from GCS buckets. It is intended only as a relatively simple example 
# and leaves out many options that are useful or important for production-scale
# work. See https://cromwell.readthedocs.io/en/stable/backends/Google/ for more
# complete documentation. 

engine {
  filesystems {
    gcs {
      auth = "application-default"
      project = "<google-billing-project-id>"
    }
  }
}

backend {
  default = PAPIv2

  providers {
    PAPIv2 {
      actor-factory = "cromwell.backend.google.pipelines.v2alpha1.PipelinesApi
      LifecycleActorFactory"
      config {
        # Google project
        project = "<google-project-id>"

        # Base bucket for workflow executions
        root = "gs://<google-bucket-name>/cromwell-execution"

        # Polling for completion backs-off gradually for slower-running jobs.
        # This is the maximum polling interval (in seconds):
        maximum-polling-interval = 600

        # Optional Dockerhub Credentials. Can be used to access private docker images.
        dockerhub {
          # account = ""
          # token = ""
        }

        # Number of workers to assign to PAPI requests
        request-workers = 3

        genomics {
          # A reference to an auth defined in the `google` stanza at the top. This auth is used
          # to create
          # Pipelines and manipulate auth JSONs.
          auth = "application-default"

          # Endpoint for APIs, no reason to change this unless directed by Google.
          endpoint-url = "https://genomics.googleapis.com/"

          # Pipelines v2 only: specify the number of times localization and delocalization 
          # operations should be attempted
          # There is no logic to determine if the error was transient or not, everything 
          # is retried upon failure
          # Defaults to 3
          localization-attempts = 3

        }

        filesystems {
          gcs {
            auth = "application-default"
            project = "<google-billing-project-id>"
            }
          }
        }

        default-runtime-attributes {
          cpu: 1
          failOnStderr: false
          continueOnReturnCode: 0
          memory: "2048 MB"
          bootDiskSizeGb: 10
          # Allowed to be a String, or a list of Strings
          disks: "local-disk 10 SSD"
          noAddress: false
          preemptible: 0
          zones: ["us-east4-a", "us-east4-b"]
        }
      }
    }
  }
}

我们知道如果您不习惯处理这种事情,这些信息可能有点多;好消息是,您几乎可以忽略其中的所有内容,我们将会引导您了解您确实需要关心的部分。在我们开始之前,请注意,您需要编辑并将修改后的文件保存为my-google.conf,存放在本章的沙盒目录~/sandbox-10中。现在就去创建那个目录吧。我们建议您使用与早期章节编辑文本文件相同的步骤:

$ mkdir ~/sandbox-10
$ cp ~/book/code/config/google.conf ~/sandbox-10/my-google.conf

现在设置环境变量以指向您的沙盒和相关目录:

$ export CONF=~/sandbox-10
$ export BIN=~/book/bin
$ export WF=~/book/code/workflows

在第四章中,我们还定义了一个BUCKET环境变量。如果您在新终端中工作,请确保重新定义此变量,因为我们稍后在本章中会用到它。在下面的命令中,用您在第四章中使用的值替换*my-bucket*

$ export BUCKET="gs://my-bucket"

现在我们的环境已经设置好,我们可以开始工作了。首先,让我们确定这个配置文件之所以指向 GCP 和 PAPI 而不是其他后端的原因。现在打开您的配置文件副本吧:

$ nano ~/sandbox-10/my-google.conf

您可以看到文件中有许多关于 Google 的引用,但关键设置发生在这里:

backend {
  default = PAPIv2

  providers {
    PAPIv2 {

这里,PAPIv2指的是当前 PAPI 的名称和版本。

在文件的这一部分滚动深入,您会找到两个设置,项目 ID 和输出存储桶,这两个设置非常重要,因为您必须修改它们才能使此配置文件为您工作:

config {
        // Google project
        project = "<google-project-id>"

        // Base bucket for workflow executions
        root = "gs://<google-bucket-name>/cromwell-execution"

您需要替换*google-project-id*以指定您正在使用的 Google 项目,并替换*google-bucket-name*以指定 Cromwell 用于存储执行日志和输出的 GCS 位置。例如,对于我们目前使用的测试项目,填写如下所示:

config {
        // Google project
        project = "ferrous-layout-260200"

        // Base bucket for workflow executions
        root = "gs://my-bucket/cromwell-execution"

最后,在访问设置为requester-pays模式的 GCS 存储桶中的数据时,您还需要提供项目 ID,如第四章中讨论的那样。此配置文件中有两处出现:一处在文件开头,一处在文件末尾:

gcs {
      auth = "application-default"
      project = "<google-billing-project-id>"
    }

具体来说,您需要替换*google-billing-project-id*以指定用于此目的的计费项目。此设置允许您使用与计算不同的计费项目,例如,如果您使用不同的计费账户来支付不同类型的费用。在这里,我们只是使用与前面定义的相同项目:

gcs {
      auth = "application-default"
      project = "ferrous-layout-260200"
    }

请确保在文件中编辑此设置的两处出现。

编辑完文件后,请务必将其保存为本章节的沙盒目录中的my-google.conf,即~/sandbox-10。如果您愿意,可以为其指定不同的名称和/或放置在不同的位置,但然后您需要相应地在下一部分的命令行中编辑名称和路径。

通过 PAPI 运行散点型杂合体分析器(Scattered HaplotypeCaller)

在您可以实际运行 Cromwell run 命令以测试此配置之前,您还需要完成一步操作:生成一个凭据文件,Cromwell 将提供给 PAPI 用于身份验证。要执行此操作,请运行以下gcloud命令:

$ gcloud auth application-default login

这类似于您在第四章中运行的gcloud init命令,用于设置您的 VM,直至包括关于安全协议的简短讲座。按照提示通过浏览器登录并复制访问代码以完成该过程。凭据文件,系统称之为应用程序默认凭据(ADC),将在gcloud实用程序可以访问的标准位置中创建。您无需采取任何其他措施使其正常工作。

我们提供了一个输入的 JSON 文件,其中填充了测试文件的路径(参见$WF/scatter-hc/scatter-haplotypecaller.gcs.inputs.json)。您可以检查其内容;您会看到这些文件与您本地使用的文件相同,但这次我们将它们的位置指向了 GCS 中的 Cromwell(在书籍存储桶中):

$ cat $WF/scatter-hc/scatter-haplotypecaller.gcs.inputs.json

有了这个文件,现在是时候运行以下 Cromwell 命令了:

$ java -Dconfig.file=$CONF/my-google.conf -jar $BIN/cromwell-48.jar \
    run $WF/scatter-hc/scatter-haplotypecaller.wdl \
    -i $WF/scatter-hc/scatter-haplotypecaller.gcs.inputs.json

此命令调用了我们在 第八章 中最后运行的相同工作流,scatter-haplotypecaller.wdl,但这次我们添加了 -Dconfig.file 参数来指定配置文件,这会导致 Cromwell 将工作分发给 PAPI 而不是本地机器。PAPI 然后会编排在 Compute Engine 上创建新的 VM 来执行工作。它还会负责拉取任何输入文件,并将日志和任何输出保存到你在配置文件中指定的存储桶中。最后,它会在每个 VM 完成工作后将其删除。

在 Google Compute Engine 上监视工作流执行

这听起来很棒,但是当你运行那个命令时,你怎么知道实际上发生了什么?像往常一样,Cromwell 会向终端输出大量信息,这些信息相当难以解析,所以让我们一起走过几种方法,来识别正在运行的内容。

首先,在日志输出的早期部分,你应该能看到 Cromwell 正确识别需要使用 PAPI。这并不是非常明显,但你会看到几行类似提到 PAPI 的内容:

[2019-12-14 18:37:49,48] [info] PAPI request worker batch interval is 
33333 milliseconds

往下滚动一点,你会找到一行引用 Call-to-Backend assignments 的内容,列出了工作流程中的任务调用:

[2019-12-14 18:37:52,56] [info] MaterializeWorkflowDescriptorActor [68271de1]:
Call-to-Backend assignments: ScatterHaplotypeCallerGVCF.HaplotypeCallerG
VCF -> PAPIv2, ScatterHaplotypeCallerGVCF.MergeVCFs -> PAPIv2

-> PAPIv2 部分表示每个任务都已被分发到 PAPI(版本 2)以进行执行。在此之后,超出通常从 WDL 和 inputs JSON 文件生成的命令详细列表,你会看到许多与 PipelinesApiAsyncBackendJobExecutionActor 相关的引用,这是 PAPI 系统中处理你的工作流执行的另一个组件。

当然,这只告诉你 Cromwell 正在将工作分发给 PAPI,并且 PAPI 在响应中做了一些事情,但你怎么知道它在做什么?最直接的方法是转到 Compute Engine 控制台,它列出了在你的项目下运行的 VM 实例。至少,你应该看到正在使用来完成书中练习的 VM,你应该能够识别其名称。如果你在它们运行时抓住它们,可能会看到最多四个显然是机器生成名称的 VM 实例(google-pipelines-worker-xxxxx…),如 Figure 10-4 所示。这些是 PAPI 根据你的 Cromwell 命令创建的 VM。

活动 VM 实例列表

图 10-4. 活动 VM 实例列表

你可能会问,为什么 PAPI 创建了多个虚拟机?作为提醒,这个工作流将调用变体分为四个单独的作业,这些作业将在不同的基因组间隔上运行。当 Cromwell 并行启动了这四个作业时,PAPI 为每个作业创建了一个单独的虚拟机。唯一一个稍后排队的工作是将四个并行作业的输出收集到一个单一任务中的合并任务。这是因为合并任务必须等待所有其他任务完成,以满足其输入依赖关系。在这种情况下,PAPI 将再次尽责完成工作并将最终输出整理到您的存储桶中。

不想过分细说,但正是云计算在这种分析中的巨大优势之一。只要你能将工作分割成独立的片段,你就可以比在本地服务器或集群上通常能承受的更大程度地并行执行。即使云计算实际上并非无限(抱歉,牙仙也不存在),但它确实具有非常令人印象深刻的能力,可以容纳大量的作业提交。例如,Broad Institute 经常在 Cromwell 上同时跨数千个节点运行工作流程。

注意

PAPI 生成的虚拟机的区域可能与您自己创建的 VM 的区域不同,就像我们在这里的情况一样。这是因为我们没有在配置文件中明确指定区域,因此 PAPI 使用了默认值。有几种方法可以控制工作将在哪个区域完成,包括在工作流程级别或甚至在工作流程内的任务级别上,正如Cromwell 文档中描述的那样

您还可以在项目的主页仪表板中查看计算引擎活动的概述,如图 10-5 所示。尽管该仪表板只提供了汇总视图,但它允许您查看过去的活动,而不仅仅是当前正在运行的内容。

计算引擎活动概述

图 10-5. 计算引擎活动概述。

最后,您可以检查配置中指定的存储桶中的执行目录。您可以通过导航到Cloud Storage 控制台中的存储桶,或者使用gsutil,如我们之前描述的那样来执行此操作。花几分钟时间探索该目录,并查看输出的结构。当您在 VM 的本地系统上运行相同的工作流程时,它应该与第八章中的情况相同。但是,请记住,存储桶中执行目录的内容并不代表 VM 实时发生的情况。同步过程会更新执行日志,并在可用时复制任何输出,该过程的间隔开始非常短,然后逐渐增加,以避免长时间运行的作业过度负担系统。这些间隔的最大长度是可定制的;您可以在我们之前使用的配置文件中查看相关代码:

// Polling for completion backs-off gradually for slower-running jobs.
// This is the maximum polling interval (in seconds): maximum-polling-interval = 600

时间间隔以秒计,因此您可以看到,默认情况下,存储桶更新之间的最大时间为 10 分钟。

工作流程可能需要最多 10 分钟才能完成。在那时,您将看到通常的finished with status 'Succeeded'消息,随后是以 JSON 格式列出的最终输出列表:

[INFO] … SingleWorkflowRunnerActor workflow finished with status 'Succeeded'.
 "outputs": {
 "ScatterHaplotypeCallerGVCF.output_gvcf": "gs://genomics-book-test-99/cromwell-
execution/ScatterHaplotypeCallerGVCF/68271de1-4220-4818-bfaa-5694551cbe81/call-
MergeVCFs/mother.merged.g.vcf"
 },

您应该看到,这次最终输出位于配置文件中指定的存储桶中。输出的路径结构与我们为本地执行描述的结构相同。

现在,请花点时间思考一下您刚刚取得的成就:使用大部分预配置的配置文件和仅一个命令行,您启动了一个过程,涉及整理复杂的计算资源以并行执行真实(尽管简单)的基因组工作流程。您可以使用完全相同的过程来运行全面的工作流程,如我们在第九章中解析的整个基因组分析流水线,或者您可以在我们展示几种替代选项时保持耐心。具体来说,在本章后面,我们向您展示了两种方法,这些方法涉及将 Cromwell 包装在附加层中,从而增加此系统的易用性和可扩展性。

但是,在我们跳入 Cromwell 支持的附加组件的葱郁生态系统之前,我们将稍微偏离主题,讨论在云上运行工作流程涉及的权衡和机会。

理解和优化工作流效率

你有没有注意到通过 PAPI 运行scatter-haplotypecaller.wdl工作流需要多长时间?大约 10 分钟,对吧?还记得在第八章中在您的 VM 上运行只需要多长时间吗?大约两分钟?所以让我们明确一下:在多台机器上并行运行相同的工作流比在单台机器上运行相同的作业要慢五倍。这听起来……有点可怕?

好消息是,这主要是我们运行的作业规模非常小的副产品。我们使用的一组间隔只覆盖基因组的一个小区域,而HaplotypeCaller本身在这样短的间隔上运行的时间非常短。当您在本地的 VM 上运行工作流时,实际上没有太多工作要做:GATK 容器映像和文件已经存在,所以实际上需要发生的只是 Cromwell 读取 WDL 并启动 GATK 命令,正如我们之前提到的,这些命令运行得很快。相比之下,当您告诉 Cromwell 将工作分派给 PAPI 时,您启动了涉及创建 VM、检索容器映像、从 GCS 本地化文件等庞大机制。所有这些都是作为更长运行时间形式出现的开销。因此,对于短任务来说,“真正工作”的总运行时间实际上被在幕后设置的时间所抹去。然而,这种设置时间大致是恒定的,因此对于运行时间更长的任务(例如,如果您在更大的基因组间隔上运行此工作流),设置时间最终只是谷歌云中的一滴水。

有了这个例子,让我们深入了解一些您需要牢记的考虑因素,无论您是计划开发自己的工作流还是仅仅在云上使用别人的工作流。

操作的粒度

我们这里运行的HaplotypeCaller工作流程旨在运行时间长得多的间隔,覆盖大量数据,因此通过 PAPI 执行它在处理大规模数据集时是完全合理的。但是,如果您的工作流的一部分涉及简单的文件格式转换和索引操作呢?那么,将几个操作合并为工作流中的单个任务可能是一个机会。我们已经在我们第二个工作流中看到了一个例子,在第九章中我们检查了CramToBamTask command块中的一个例子:

 command {
    set -e
    set -o pipefail

    ${samtools_path} view -h -T ${ref_fasta} ${input_cram} |
    ${samtools_path} view -b -o ${sample_name}.bam -
    ${samtools_path} index -b ${sample_name}.bam
    mv ${sample_name}.bam.bai ${sample_name}.bai
  }

工作流的作者本可以将这个command块的子集分离成单独的 WDL 任务,以最大化模块化。例如,拥有独立的 BAM 索引任务可能会在其他地方发挥作用且可重用。但是,他们正确地认识到,这样做会增加通过 PAPI 运行时的开销,因此他们在模块化和效率之间做了一些权衡。

时间与金钱的平衡

理解这些折衷方案的重要性很关键,不仅影响到运行管道所需的时间,还会影响到其成本。如果你大部分时间都在本地系统上工作,那些计算资源已经是预付费的,并且你主要受到分配的配额限制,可能不太习惯考虑这些问题。然而,在云上,几乎完全是按需付费,所以如果你预算有限,考虑问题是非常值得的。

例如,当将工作流程移至云端时,还有一个需要考虑的折衷方案:在执行变异调用工作流程时应该如何广泛地并行化?你可以简单地按染色体并行化,但对于整个基因组样本来说,这仍然会产生大量数据,因此每个处理过程需要很长时间,而且你无法充分利用云端并行处理的惊人能力。此外,至少在人类中,各染色体的长度差异巨大;例如,染色体 1 的长度大约是染色体 22 的五倍,所以后者会更快完成,并且其结果将在等待其他染色体处理完毕时保持不变。

更有效的方法是将染色体本身切割为不确定性区域的子区间,其中参考序列具有N个碱基的区域,意味着这些区域的内容是未知的,因此在这些地方中断处理是可以接受的。然后,你可以平衡区间的大小,使大多数区间的处理时间大致相同。但这仍然给你在设计列表时平均长度的区间留下了很大的自由度。你将序列切分得越细,就越早能够获得结果,因为你可以(在合理范围内)并行运行所有这些区间。然而,每个单独区间的运行时间越短,你就会越感受到涉及将工作分发到单独的虚拟机所带来的开销,更重要的是成本方面的影响。

还不确定吗?假设你定义了三百个需要每个分析三小时的区间(这些数字是虚构的,但应该接近现实情况)。对于每个区间,在实际分析开始之前的设置阶段,你将支付最多 10 分钟的虚拟机额外时间费用(哦是的,你要为那段时间付费)。那就是 300 × 10 分钟;换句话说,50 小时的虚拟机成本用于额外费用。假设你使用的是每小时大约 0.03 美元的基本机器,那就是 1.50 美元,尽管这并非世界末日。现在假设你将你的区间粒度提高 10 倍,生成 3000 个较短的区间,每个区间分析需要 18 分钟。你将支付 3000 × 10 分钟,或者 500 小时的虚拟机额外费用。现在你每个样本在额外费用上花费 15 美元。在大量样本中,这可能在你的预算中留下明显的痕迹。但我们不评判;问题是速度对是否值得这些钱。

这里的要点是,云计算为你提供了很多自由空间来找到你的幸福点。

当 Broad Institute 最初将其整个基因组分析流程从本地集群迁移到 GCP 时,每个全基因组样本(30X 覆盖率)的运行成本约为 45 美元。通过工作流优化和与 GCP 工程师的合作,该研究院团队将成本降低到每个全基因组样本 5 美元。这些显著的节省转化为更低的生产成本,最终意味着每研究美元可以获得更多科学成果。

诚然,云管道确实需要大约 23 小时才能完成单个样本的运行,这比现在可用于在专用硬件上更快运行 GATK 的主流加速解决方案要长得多,例如 Illumina 的 DRAGEN 解决方案。不过,作为背景,典型情况下湿实验室侧的样本准备和测序过程通常需要大约两天时间,所以除非你从事提供紧急诊断服务的业务,分析的额外一天通常不会成为关注点。

问题在于:处理时间并不占用你本可以用于其他事情的计算资源。如果你需要更多的虚拟机,只需请求 PAPI 召唤它们。由于运营团队可以同时启动的工作流没有实际上限,当大批订单到来时,几乎不会出现积压的风险,除非是规模达到gnomAD的程度,其中包含超过 10 万个外显子和 8 万个全基因组样本。那么,就需要一些提前计划和一封友好的 GCP 账户经理的礼貌邮件,这样他们可以告诉他们的运营团队做好准备。(我们想象这封邮件会这样写:“做好准备,gnomAD 即将到来。”)

注意

不幸的是,由于发现影响全球计算机处理器的两个主要安全漏洞SpectreMeltdown,Broad 的基于云的全基因组分析管道的成本每个样本增加到约 8 美元。使机器安全使用所需的安全补丁增加了额外开销,导致更长的运行时间,这似乎将在云供应商更换受影响硬件之前始终无法避免。然而,随着时间的推移,云上存储和计算的成本继续下降,因此我们预计 8 美元的价格会随时间而降低。

建议的节省成本优化策略

如果你想了解 Broad Institute 团队开发的工作流程实施细节以实现大幅节省成本,无需再看其他,直接参考第九章即可。我们所讨论的优化流水线,就是该章节中我们分析的第二个工作流程(包含子工作流和任务库)。

不要深入细节,以下是优化在 Google Cloud 上便宜运行 WDL 工作流的三种最有效策略的总结。您可以在我们在第九章中查看的 GermlineVariantDiscovery.wdl 任务库中看到这些策略的应用。

资源分配的动态调整

对于给定任务的虚拟机请求的存储量越多,成本就会越高。您可以通过仅请求最低限度的磁盘存储量来降低成本,但如何在不手动检查每个需要处理的样本的文件输入大小的情况下处理文件输入大小的变化?好消息是:有一些 WDL 函数允许您在运行时(但在请求虚拟机之前)评估输入文件的大小。然后,您可以应用一些算术(基于对任务产生的文件大小的合理假设)来计算应分配多少磁盘。例如,以下代码测量参考基因组文件的总大小,然后根据需要考虑输入 BAM 文件的一部分加上参考文件,再加上一些填充以处理输出:

Float ref_size = size(ref_fasta, "GB") +
size(ref_fasta_index, "GB") + size(ref_dict, "GB")
Int disk_size = ceil(((size(input_bam, "GB") + 30) / hc_scatter) + ref_size) + 20

查看此博文以详细讨论这一方法。

文件流传输到 GATK4 工具

这是减少任务 VM 所需磁盘空间请求的另一种好方法。通常,Cromwell 和 PAPI 将所有输入文件本地化到 VM 中作为任务执行的先决条件。然而,GATK(从版本 4.0 开始)能够直接从 GCS 流式传输数据,因此,如果你在基因组间隔上运行 GATK 工具,可以指示 Cromwell 不本地化文件,而是让 GATK 处理,它将仅检索间隔中指定的数据子集。例如,如果你在一个 300 Gb BAM 文件上并行运行 HaplotypeCaller,但将其操作分解为许多数量级较小的间隔,这就尤为有用。在前面的代码示例中,这就是为什么输入 BAM 文件的大小会被分割为 scatter 的宽度(即间隔的数量)。要在你的 WDL 中指示输入文件可以进行流式传输,只需为相关的输入变量在任务定义中添加以下内容:

parameter_meta {
    input_bam: {
      localization_optional: true
    }
  }

请注意,目前此功能仅适用于 GCS 中的文件,并不适用于捆绑在 GATK 中的 Picard 工具。

预留 VM 实例

这涉及使用称为预留的一类 VM,其使用成本比正常定价便宜得多(当前为定价的 20%)。这些折扣 VM 具有与正常 VM 相同的所有规格,但这里有个问题:Google 可以随时从你手中收回它们。其理念是这是一个备用 VM 的池子,你可以正常使用,只要资源没有短缺。如果某人突然请求与你使用相同类型的大量机器,而可用资源不足,则你正在使用的部分或所有 VM 将重新分配给他们,并且你的作业将被中止。

为了避免最常见的两个问题,不,没有(内置的)方法来保存中断前所做的任何进度;是的,你会被收取使用时间的费用。光明的一面是,PAPI 会通知 Cromwell 你的作业已被抢占,并且 Cromwell 将尝试在新的 VM 上自动重新启动它们。具体来说,Cromwell 将再次尝试使用预留 VM,最多尝试自定义数量次数(在大多数 Broad Institute 工作流中,默认为三次尝试);然后,它将退回到使用常规(全价)VM。要控制对于给定任务你愿意允许的预留尝试次数,只需在 runtime 块中设置 preemptible: 属性。要禁用预留使用,将其设置为 0 或完全删除该行。在下面的示例中,使用 preemptible_tries 变量设置了预留尝试次数,以便根据需要轻松自定义,以及 Docker 容器和磁盘大小:

 runtime {
    docker: gatk_docker
    preemptible: preemptible_tries
    memory: "6.5 GiB"
    cpu: "2"
    disks: "local-disk " + disk_size + " HDD"
  }

结论呢?使用抢先式虚拟机(preemptible VMs)通常对短时间运行的作业非常值得,因为它们在完成之前被抢占的机会通常非常小,但随着作业时间的增加,胜算会变得越来越不利。

平台特定的优化与可移植性

所有这些优化都非常具体于在云上运行——对于其中一些优化来说,不是所有的云都适用——因此现在我们需要讨论它们对可移植性的影响。正如我们在第八章介绍工作流系统时讨论的那样,Cromwell 和 WDL 的开发关键目标之一就是可移植性:即你可以将相同的工作流在任何地方运行并获得相同的结果,而不需要处理大量软件依赖或硬件要求。计算可移植性的好处包括更容易与其他团队合作,以及让全球任何研究人员在你发表后能够重现和建立在你工作基础上的研究。

不幸的是,优化工作流以在特定平台上高效运行的过程可能会降低其可移植性。例如,较早版本的 WDL 没有包含 localization_optional: true 惯用法,因此为了使用 GATK 的文件流功能,工作流作者必须通过将这些输入变量声明为 String 类型而不是 File 类型来欺骗 Cromwell 以避免本地化文件。问题在哪里?结果是,生成的工作流不能在本地系统上运行,除非修改相关的变量声明。即时的可移植性失败。

引入 localization_optional: true 是一大进步,因为它作为工作流管理系统的一种“提示”功能。基本上,它的意思是,“嘿,如果你处于支持流式处理的情况下,你可以不必本地化这个文件,但如果不支持,请务必进行本地化。” 因此,你可以在不同地方运行相同的工作流,享受你可以的优化,并确保工作流在其他地方也能正常工作。可移植性胜利!

我们向你展示的其他优化一直都是一种无害的“提示”状态,意味着它们的存在不会影响可移植性。除了在第八章中讨论的 docker 属性外,Cromwell 会忽略 runtime 块中指定的在当前环境下不适用的任何属性,因此,即使只是在笔记本上运行工作流,你也可以安全地指定 preemptibledisk 等属性。

另一些云提供商(如 Microsoft Azure 和 AWS)有自己的类似功能的等价物,如预留 VM 实例(在 AWS 上,最接近的等价物称为 Spot Instances),但它们的行为不完全相同。因此,不可避免地会出现这样的问题:当在 AWS 上运行时,Cromwell 是否应该使用 preemptible 属性来控制 Spot Instances,或者提供一个单独的属性来实现这一目的。如果是后者,这会到哪一步?每个运行时属性都应该存在多种云平台的版本吗?

噢,不,我们 没有对此有答案。我们将会在这里加热一些爆米花并看开发人员之间的战斗。要跟踪给定 Cromwell 后端当前能做什么和不能做什么,最好的方法是查看在线的 后端文档。现在,让我们回到讨论在 GCP 上运行 Cromwell 最便捷和可扩展的方法。

使用 WDL Runner 封装 Cromwell 和 PAPI 执行

提醒:请确保你可以从几乎任何地方通过 Cromwell 向 PAPI 提交工作流,只要你能运行 Cromwell 并连接到互联网即可。正如之前提到的,我们要求你使用 GCP VM 是因为它最大程度上减少了设置所需的步骤(运行大型工作流!),并减少了因为一些问题无法立即工作的风险。但是你绝对可以从你的笔记本电脑或者如果你感觉有点狡猾的话,从 AWS 或 Azure 的 VM 中完成同样的事情。然而,使用笔记本电脑的缺点是,在你的工作流运行期间,你需要确保笔记本保持开启(电源连接并保持连接)。对于短期运行的工作流,这可能没问题;但是对于像全基因组分析这样的大规模工作,你真的希望能够启动一个工作流然后在晚上整理好你的设备而不中断执行。

进入 WDL Runner。这个开源工具包作为 Cromwell 和 PAPI 的轻量级封装。通过你的一个单一命令行,WDL Runner 在 GCP 中创建一个 VM,设置一个包含配置好使用 PAPI 的 Cromwell 的容器,并通过 PAPI 提交你的工作流进行执行,如图 Figure 10-6 所示。这些听起来是不是有些耳熟?除了一些细节之外,这基本上就是你在前面章节中所做的:创建一个 VM 实例,安装 Cromwell,配置它与 PAPI 通信,并运行一个 Cromwell 命令来启动工作流。

WDL Runner 操作概览

图 10-6. WDL Runner 操作概览。

所以基本上,你可以将WDL Runner视为一种方式来外包所有这些工作,并实现相同的结果,而无需手动管理虚拟机。但还有更多:当工作流程完成时,WDL Runner会将执行日志传输到你选择的位置并删除虚拟机,因此如果你不立即关闭它,它不会继续花费你的资金。最后的点睛之笔是,WDL Runner还会将最终输出复制到你选择的位置,因此你无需深入到Cromwell的(令人讨厌的深层)执行目录中找到你关心的结果。哦,还有另一个点睛之笔:WDL Runner还配备了监控工具,允许你获取工作流程的状态摘要。

当然,你可以从任何地方运行WDL Runner,但我们建议你从虚拟机中运行,因为它就在那里,而且包含了你需要的一切。当然,你也可以从你的笔记本电脑上尝试。

设置WDL Runner

好消息:不需要进行设置!我们已经在书籍捆绑包中包含了WDL Runner代码的副本,可以在GitHub 上获取WDL Runner的主要要求是 Python 2.7、Java 8 和gcloud,这些在你的虚拟机上已经都有了。

为了使路径管理更容易,我们再次使用环境变量。其中两个变量是你应该在本章前面已经定义的变量,$WF指向workflows目录,$BUCKET指向你的 Google 存储桶。此外,你还要创建一个$WR_CONF变量,指向config目录,以及一个$WR_PIPE变量,指向WDL_Runner流水线配置文件的位置,即wdl_pipeline.yaml,如下所示:

$ export WR_CONF=~/book/code/config
$ export WR_PIPE=~/book/wdl_runner/wdl_runner

顺便说一句,这不是错误;事实上,有两个嵌套的目录都被称为wdl_runner;更低级的一个指的是特定于wdl_runner的代码,而另一个指的是整个包。如果你查看后者,你会发现除了另一个名为wdl_runner的目录外,还有一个名为monitoring_tools的目录。我们来猜猜看,你有三次猜测的机会,前两次不算。

使用WDL Runner运行分散的HaplotypeCaller工作流

这将相当直接。需要注意的是,使用WDL Runner启动工作流的命令行会比直接使用Cromwell启动要长一些。这是因为WDL Runner为你提供了更多选项,比如在不需要配置文件的情况下选择存储输出位置等。下面是示例:

$ gcloud alpha genomics pipelines run \
      --pipeline-file $WR_PIPE/wdl_pipeline.yaml \
      --regions us-east4 \
      --inputs-from-file WDL=$WF/scatter-hc/scatter-haplotypecaller.wdl,\
 WORKFLOW_INPUTS=$WF/scatter-hc/scatter-haplotypecaller.gcs.inputs.json,\
 WORKFLOW_OPTIONS=$WR_CONF/empty.options.json \
      --env-vars WORKSPACE=$BUCKET/wdl_runner/test/work,\
 OUTPUTS=$BUCKET/wdl_runner/test/output \
      --logging $BUCKET/wdl_runner/test/logging

但别急着运行它!让我们简要地走一遍,这样你就知道哪些部分需要定制,哪些部分需要保持不变。

第一行告诉您这个命令主要调用了 gcloud,这在情况上有点有趣的转折。之前,我们运行 Cromwell 并指示它与 Google Cloud 事务通信。现在,我们调用 Google Cloud 事务来运行 Cromwell 并让它与 Google Cloud 事务通信。您可能不会完全惊讶地了解到,WDL Runner 最初是由 GCP 团队编写的。

--pipeline-file参数接受指向一种配置文件的路径,该配置文件预先包含了 WDL Runner 代码;如果您希望从不同的工作目录运行 WDL Runner,需要修改路径。

--regions参数,不出所料,控制着您希望 VM 位于的地区。有趣的是:WDL Runner 似乎需要这个参数,但它与您在最初通过 gcloud 认证时设置的默认 gcloud 设置相冲突。具体来说,该命令失败,并返回以下错误消息:

ERROR: (gcloud.alpha.genomics.pipelines.run) INVALID_ARGUMENT: Error: validating
pipeline: zones and regions cannot be specified together

要解决这个问题,您需要通过运行以下 gcloud 命令取消设置默认区域:

$ gcloud config set compute/zone ""

--inputs-from-file参数列出了工作流 WDL 文件、inputs JSON 文件和选项文件。选项文件允许您指定将应用于整个工作流的运行时配置设置。我们不需要指定任何内容,但由于 WDL Runner 要求我们提供一个,我们包含了一个实际上并不包含任何设置的存根文件。顺便说一句,请确保在指定文件的键值对之间(甚至逗号周围)绝对没有空白,否则命令将因为关于未识别参数的奇怪错误而失败。是的,我们尽力体验所有可能的失败,这样您就不必亲自经历了。

--env-vars参数列出了您希望存储执行目录和最终输出的位置;只要是以gs://开头的有效 GCS 路径,这些位置可以是任何您想要的东西。在这里,我们使用了 $BUCKET 环境变量,这是我们在第四章中最初设置的,并且我们使用了一个我们认为方便管理输出和日志的目录结构。请随意规划自己的路径,正如您所愿。--logging参数和前一个参数的功能相同,但用于执行日志。

当您对命令的外观满意时,请运行它,然后坐下来享受软件工作的景象。(是的,说这话确实有点像在招惹命运,是吧。)如果一切顺利,您应该会得到一个简单的单行回复,看起来像这样:

Running [projects/ferrous-layout-260200/operations/8124090577921753814].

operations/后面的数字(有时候还会有字母)是一个唯一标识符,即操作 ID,服务将使用它来跟踪您的提交状态。一定要把这个 ID 存放在某个地方,因为它对接下来的监控步骤至关重要。

监控 WDL Runner 执行

现在我们准备监视我们的工作流程提交的状态。还记得叫做 monitoring_tools 的不那么神秘的目录吗?是的,我们现在要用它。具体来说,我们将使用位于该目录中的名为 monitor_wdl_pipeline.sh 的 shell 脚本。这个脚本本身是相当典型的系统管理员工作,这意味着如果您不熟悉这种类型的代码,要解密它实在是很痛苦。然而,出乎意料的是,它非常容易使用:您只需给脚本上一步记录的操作 ID,它就会告诉您工作流程的执行情况。

然而,在您跳入之前,我们建议您打开一个新的 SSH 窗口到您的 VM,以便您可以让脚本继续运行。它被设计为持续运行,定期轮询 gcloud 服务,直到收到工作流程执行完成的消息(无论成功与否)。作为提醒,您可以从 Compute Engine 控制台打开任意数量的 SSH 窗口到您的 VM。

当您在新的 SSH 窗口中时,请进入第一个 wdl_runner 目录,并运行以下命令,替换您从上一步保存的操作 ID:

$ cd ~/book/wdl_runner
$ bash monitoring_tools/monitor_wdl_pipeline.sh 7973899330424684165
Logging:
Workspace:
Outputs:
2019-12-15 09:21:19: operation not complete
No operations logs found.
There are 1 output files
Sleeping 60 seconds

这是您在启动 WDL Runner 命令后立即启动监视脚本时会看到的早期输出。随着时间的推移,脚本将生成新的更新,这些更新会变得越来越详细。

如果您对监视脚本输出的内容不满意,您可以像之前那样转到 Compute Engine 控制台,查看正在处理您的工作流程的虚拟机列表,如 Figure 10-7 所示。这次,您将看到多达五个新的带有机器生成名称的 VM(除了您手动创建的 VM)。

活动虚拟机实例列表(WDL Runner 提交)

Figure 10-7. 活动虚拟机实例列表(WDL Runner 提交)。

但是等等,这次为什么会有五个 VM 而不是四个?如果您感到困惑,请快速回顾一下 Figure 10-6,以刷新您对这个系统如何工作的记忆。这应该提醒您,这种情况下第五个轮子相当有用:它是控制工作的具有 Cromwell 的 VM 实例。

回到监视脚本,您最终应该看到一个以 operation complete 开头的更新:

2019-12-15 09:42:47: operation complete

下面会有大量的输出内容,那么您如何知道是否成功呢?您需要留意接下来的几行内容。要么您会看到类似这样的内容,然后您会松一口气:

Completed operation status information
 done: true
 metadata:
   events:
   - description: Worker released

或者您会看到在 done: true 后立即出现了可怕的 error: 行,然后您深呼吸,再深入查看错误消息的详细信息:

Completed operation status information
 done: true
 error:
   code: 9
   message: ’Execution failed: while running "WDL_Runner": unexpected exit status 1
was not ignored’

我们不会掩饰这一点:调试 Cromwell 和 PAPI 错误可能会很艰难。如果你的工作流提交出现问题,你可以在 monitor_wdl_pipeline.sh 脚本的输出以及保存到存储桶中的文件中找到错误信息,我们稍后会详细查看这些内容。

最后,让我们来看看存储桶中由 WDL Runner 产生的输出,如 Figure 10-8 所示。

WDL Runner 提交的输出。

图 10-8. WDL Runner 提交的输出。

logging 输出是一个文本文件,其中包含 Cromwell 的日志输出。如果你窥视其中,你会发现它与你直接运行 Cromwell 时看到的日志输出不同。这是因为这个 Cromwell 实例是在服务器模式下运行的!这很激动人心:你运行了一个 Cromwell 服务器,甚至都不知道自己在做什么。虽然你并没有真正利用服务器功能——但这是个开始。不过,Cromwell 服务器日志对于普通人来说实在太难读了,所以我们甚至不打算在这里试图去读它们。

work 目录只是 Cromwell 执行目录的一个副本,因此它具有你现在已经习惯的兔子洞式的结构和内容。

最后,output 目录是 WDL Runner 中我们非常喜欢的一个特性:该目录包含根据 WDL 中标识的文件作为工作流的最终输出的副本。这意味着如果你对结果满意,可以直接删除工作目录,无需费力去查找你关心的输出内容。当你的工作流产生大量大型中间输出时,这可以节省大量时间——或者因为不需要支付这些中间输出的存储费用而节省金钱。

总之,我们发现 WDL Runner 在快速测试工作流时非常方便,开销很小,而且仍然能够“点火并忘记”;也就是说,启动工作流,关闭笔记本电脑,然后走开(只要记录这些操作 ID!)。不过,它有一个不足之处,就是它已经有一段时间没有更新,已经与 WDL 的功能脱节;例如,它既不支持子工作流,也不支持任务导入,因为它缺少一种打包超过一个 WDL 文件的方法。此外,如果你发现自己经常启动许多工作流,你需要非常有纪律性地跟踪这些操作 ID。

在这里,我们展示了如何使用 WDL Runner 作为一个项目封装 Cromwell 的示例,而不是一个完全成熟的解决方案,并且在下一章中,我们将转向我们更喜欢的系统。然而,如果你喜欢 wdl_runner 的概念,请不要犹豫在项目的 GitHub 页面 上表达你的声音,以推动进一步的开发。

注意

现在几点了?现在是时候再次关闭您的 VM,这次尤为重要,因为在本书的剩余练习中我们将不再使用它。如果您不打算回到以前的教程,请随时删除您的 VM。

总结和下一步

在本章中,我们向您展示了如何通过在 GCP 基因组学 PAPI 服务中运行工作流来利用云计算的扩展能力,首先直接从 Cromwell 命令行,然后是一个名为 WDL Runner 的轻量包装器。然而,尽管这两种方法使您能够以可扩展的方式在 GCP 中运行任意复杂度的单个工作流,但每次运行新工作流时启动和关闭一个新的 Cromwell 实例所涉及的开销非常低效,如果您计划运行大量工作流,将不利于扩展。

对于真正可扩展的工作,我们希望使用一个合适的 Cromwell 服务器,可以根据需求接收和执行任意数量的工作流,并能利用复杂功能如调用缓存。调用缓存允许 Cromwell 从失败点恢复或重新执行中断的工作流。调用缓存需要连接到数据库,并不受 WDL Runner 支持。然而,设置和管理服务器和数据库,更不用说在云环境中安全操作它们,比大多数人所能处理的技术支持负担更复杂。

因此,在下一章中,我们将向您展示一条不必承担维护负担即可享受 Cromwell 服务器强大功能的替代路径。具体来说,我们将介绍 Terra,这是一个由 Broad Institute 与 Verily 合作在 GCP 上构建和运行的生物医学研究可扩展平台。Terra 包括一个 Cromwell 服务器,您将首次用它来完整运行 GATK 最佳实践工作流。

第十一章:在 Terra 中方便地运行多个工作流

在第十章中,我们给您带来了 Cromwell 加上 Pipelines API 组合强大能力的令人期待的第一印象。您学会了如何将单个工作流分派到 PAPI,无论是通过 Cromwell 直接操作还是通过 WDL Runner 包装器间接操作。这两种方法都使您能够快速调度任意数量的云计算资源,而无需直接管理它们,这可能是您从本书中获得的最重要的教训。然而,正如我们讨论过的那样,这两种方法都存在限制,这些限制可能会阻止您实现云提供的真正强大的可扩展性。

在本章中,我们将向您展示如何在Terra中使用全功能的 Cromwell 服务器,这是由 Broad Institute 运营的基于云的平台。我们将从介绍平台开始,并引导您了解在 Terra 中运行工作流的基础知识。在此过程中,您将有机会尝试调用缓存功能,该功能允许 Cromwell 服务器从故障点恢复失败或中断的工作流。有了这些经验,您将毕业于在整个基因组数据集上运行完整的 GATK 最佳实践流水线。

开始使用 Terra

你离体验充满乐趣的全功能 Cromwell 服务器仅有几步之遥,多亏了Terra,这是由 Broad Institute 与 Verily 合作运营的生物医学研究可扩展平台。Terra 旨在为研究人员提供用户友好而灵活的环境,用于在 GCP 中进行安全和可扩展的分析。在幕后,Terra 包括一个持久的 Cromwell 服务器,配置以将工作流分派到 PAPI,并配备有专用的调用缓存数据库。在表面上,它提供了一个点-and-click 界面,用于运行和监控工作流,以及为那些喜欢通过 API 与系统进行编程交互的用户。Terra 还提供了丰富的功能,用于访问和管理数据,通过 Jupyter Notebook 进行交互式分析,并通过强大的权限控制安全地进行协作。图 11-1 总结了 Terra 当前的主要功能。

Terra 平台概览。

图 11-1. Terra 平台概览。

在本节中,我们关注通过 Web 界面进行的工作流配置、执行和监控功能,但在本章后面,我们还将深入探讨在 Jupyter 中进行的一些交互式分析。

注意

我们撰写本书时,Terra 平台正在积极开发中,因此您可能会在此处的截图和实际 Web 界面中遇到差异,以及新的功能和行为。关于任何重大变更,我们将在本书的博客中提供更新的说明和指导。

创建账户

您可以免费注册Terra 账户。在浏览器窗口左上角,点击三条线符号以展开侧边菜单,并显示出使用 Google 登录按钮,如图 11-2 所示。

展开的侧边菜单显示登录按钮。

图 11-2. 展开的侧边菜单显示登录按钮。
注意

Terra 中的菜单符号和导航模式通常与 GCP 控制台相似。每当看到相同的符号时,您可以安全地假设它们具有相同的功能。

使用您迄今为止使用的同一帐户——您用来设置 Google Cloud 平台帐户的帐户,并提交所示的注册表单,参见图 11-3。联系电子邮件地址可以与您登录时使用的电子邮件地址不同;例如,如果您使用个人账户进行工作但希望将电子邮件通知发送到工作账户。来自 Terra 的电子邮件通知主要包括有关新功能发布和服务状态(例如计划维护或事件警报)的通知。它们相对不频繁,如果您不希望收到这些通知,您可以选择退出。您在此处指定的联系电子邮件地址还将被 Terra 帮助台票务系统使用,如果您提出问题、报告错误或建议功能,系统将通过电子邮件与您联系。

新用户注册表单。

图 11-3. 新用户注册表单。

您需要接受 Terra 服务条款。我们强烈建议您按照建议,使用两步验证来保护您的 Google 关联账户。请记住,Terra 的主要设计目的是支持人类患者数据分析,并包括承载由美国政府资助的数据的仓库,因此平台的安全性问题非常重要。

创建计费项目

完成注册过程后,您应该会进入 Terra 门户的登陆页面。页面顶部可能会显示一个广告免费积分的横幅,如图 11-4 所示。截至撰写本文时,您可以获得价值 300 美元的免费积分来试用平台(此外,您可能已经直接从 GCP 获得了积分),详细信息请参阅Terra 用户指南。Terra 是基于 GCP 构建的,当您在 Terra 上进行需要付费的工作时,GCP 将直接向您的账户计费。Broad Institute 不会为使用平台收取任何额外费用。

您可能看到的广告免费积分横幅。

图 11-4. 您可能看到的广告免费积分横幅。

我们建议您利用免费信用额度的机会,不仅仅是为了信用额度本身,而且因为系统将自动设置一个计费项目,您可以立即在 Terra 中使用。这是开始使用 Terra 的最快方式;只需点击“开始试用”,接受服务条款(不要用免费信用挖比特币!),然后您就可以开始了。如果您查看计费页面,您应该会看到一个新的计费项目,其名称遵循这种模式:fccredits-*化学元素*-*颜色*-*数字*

如果您没有看到横幅或者不想立即激活免费试用,您需要访问计费页面(可从您登录的侧边菜单访问),设置一个计费账户。您可以通过点击蓝色加号符号并按照弹出的说明进行操作,连接到迄今为止使用的计费账户。请查看附带的侧边栏,详细了解连接现有计费账户以在 Terra 中使用的流程。

在设置好计费项目之后,您只需一个工作空间即可运行一些工作流程。在第十三章中,我们将向您展示如何从头开始创建自己的工作空间,但目前,您只需克隆我们为您设置的具备所有基本要素的工作空间即可。

克隆预配置工作空间

您可以在图书馆展示中找到基因组云-v1 工作空间(可从左侧可展开菜单访问),或直接访问此链接。着陆页或仪表板会提供有关其目的和内容的信息。如果您愿意,可以花一分钟阅读摘要描述,然后我们就开始工作。

此工作空间是只读的,因此您需要做的第一件事是创建一个属于您的克隆副本,因此您可以进行操作。为此,在浏览器右上角点击带有三个点的圆形符号以展开操作菜单。选择克隆,如图 11-8 所示,以打开工作空间克隆表单。

克隆预配置工作空间。A) 可用操作列表;B) 克隆表单。

图 11-8. 克隆预配置工作空间。A) 可用操作列表;B) 克隆表单。

选择您的计费项目;这将确定 Google Cloud Platform 在您进行产生费用的工作时要计费的账户。您可以更改工作空间名称或保留原样;工作空间名称必须在计费项目内是唯一的,但不需要在 Terra 的所有空间中唯一。目前忽略授权域位;这是一个您当前不需要的可选白名单选项。

当您单击克隆工作空间按钮时,系统会自动将您带到新的工作空间。好好看看周围吧;光及之处皆为您所有。有很多东西,对吧?实际上,你知道吗,不要花太多时间四处看。让我们紧紧地专注于我们的目标,那就是通过 Terra 的 Cromwell 服务器使您能够运行工作流。

在 Terra 中使用 Cromwell 服务器运行工作流

好了,现在开始表演时间。前往工作空间的工作流部分,您会看到可用工作流配置列表,如图 11-9 所示(#list_of_available_workflow_configuratio)。

可用工作流配置列表。

图 11-9. 可用工作流配置列表。

这些是同一个工作流的两个配置,您可能会从它们的名称的前半部分认出它们,scatter-hc. 是的,这就是您最喜欢的并行化HaplotypeCaller工作流。如果您好奇,这两个配置之间的关键区别在于一个设计用于单个输入样本的运行,而另一个可以运行任意数量的输入样本。听起来很令人兴奋,对吧?没错。但首先让我们专注于简单的情况。

在单个样本上运行工作流

在列出工作流配置的页面上,单击名为scatter-hc.filepaths的配置,以打开配置页面。图 11-10 所示(#viewing_the_workflow_information_summar)显示了摘要信息,包括一行简要说明和短描述。

查看工作流信息摘要。

图 11-10. 查看工作流信息摘要。

此外,摘要链接到工作流的源。如果您点击该链接,将跳转到 Broad Institute 的方法库,这是一个工作流的内部存储库。Terra 还可以从Dockstore导入工作流,正如您将在第十三章中看到的那样,但在本练习中,我们选择使用内部存储库,这在初次接触时更加方便。

回到scatter-hc.filepaths配置页面,查看 SCRIPT 选项卡,显示实际的工作流代码。毫无疑问,这正是我们一直在使用的同一工作流。

如图 11-11 所示(#viewing_the_workflow_scriptdot),代码显示使用语法高亮:根据 WDL 语言的语法对代码进行着色。在此窗口中无法编辑代码,但上述方法库包含一个代码编辑器(也带有语法高亮),可以在不太麻烦的情况下方便地进行小调整。

查看工作流脚本。

图 11-11. 查看工作流脚本。

现在您确定要运行哪个工作流程了,但是输入应该放在哪里呢?当您从命令行运行此工作流程时,您会将一个 JSON 文件的输入与 WDL 文件一起交给 Cromwell。您可以在此处的输入选项卡中看到功能上等效的部分,如图 11-12 所示。对于每个输入变量,您可以在最左侧列中看到任务名称,然后是在工作流脚本中定义的变量名称及其类型。最后,最右侧列包含我们为每个变量提供的属性或值。

查看工作流输入。

图 11-12. 查看工作流输入。

我们已为您预填写了配置表单,因此您无需编辑任何内容,但请花点时间查看如何指定输入值。特别是对于文件变量:我们已提供了文件在 GCS 中位置的完整路径。这实际上是输入 JSON 文件内容的真实转录。事实上,正如您将在第十二章中详细了解的那样,为了设置这一切,我们只需上传 JSON 文件即可填充表单内容。

那么,您准备好点击那个大蓝色的“运行分析”按钮了吗?去吧;您应得的。一个小窗口将弹出,询问是否确认,如图 11-13 所示。按下蓝色的“启动”按钮,然后坐下来,Terra 会处理您的工作流程提交。

工作流启动对话框。

图 11-13. 工作流启动对话框。

在幕后,系统向内置的 Cromwell 服务器发送了一个信息包,其中包含工作流代码和输入。像往常一样,Cromwell 解析工作流并开始将单个作业分派给 PAPI,在 Google Compute Engine(GCE)上执行,如图 11-14 所示。

Terra 中工作流提交的概览。

图 11-14. Terra 中工作流提交的概览。

与此同时,Terra 将带您进入工作空间的作业历史部分,您可以在那里监控执行并查看任何过去提交的状态——当然,前提是您有机会运行一些作业。说到这一点,当您刚刚提交的工作流程进展更进一步时,这里会有更多内容可供查看,因此让我们继续规划,稍后在本章节中再回顾作业历史。

到了这一步,假设一切顺利,您实际上只是通过 PAPI 在单个样本上重新运行了分散的HaplotypeCaller工作流程的先前成就。这很好,但我们的目的不是要能够同时在多个样本上运行工作流吗?是的,确实是。这就是第二个配置发挥作用的地方。

在数据表中运行多个样本的工作流程

让我们来看看另一个配置——名为scatter-hc.data-table的配置。作为提醒,您需要导航到工作流面板,然后点击该配置。您会看到与上一个配置大致相同的内容(并且源链接完全相同),但是如果您仔细观察,会发现一个重要的区别。如图 11-15 所示,该配置被设置为“使用数据表定义的输入运行工作流程”,而.filepaths配置被设置为“使用文件路径定义的输入运行工作流程”,并指定book_sample表作为数据来源,如图 11-10 所示。

第二个工作流程设置为在数据表中的行上运行。

图 11-15. 第二个工作流程设置为在数据表中的行上运行。

现在仔细看看输入选项卡上如何定义的输入。您有看到任何不熟悉的内容吗?对于大多数变量,直接的值(如 Docker 地址和文件路径)被看起来更像是变量的东西替换了:要么是workspace.*,要么是this.*,如图 11-16 所示。

工作流输入配置引用数据表。

图 11-16. 工作流输入配置引用数据表。

这确实是这些值的确切内容,它们是对某处定义的值的引用。具体而言,它们指向存储在工作空间数据部分元数据表中的值。让我们现在过去看看,看看是否能解释这一切是如何运作的。

在数据部分,您将找到一个数据资源菜单,应该看起来类似于图 11-17。在该菜单中,您应该会看到一个名为book_sample和另一个名为Workspace Data的表。我们将从Workspace Data表开始,假设这是最容易理解的表。

在 DATA 选项卡上查看数据表菜单。

图 11-17. 在 DATA 选项卡上查看数据表菜单。

点击 Workspace Data 查看该表的内容,如图 11-18 所示。正如您所见,这是一个相当简单的键值对列表;例如,键gatk_docker与值us.gcr.io/broad-gatk/gatk:4.1.3.0相关联。与此同时,在幕后,这个Workspace Data表被称为workspace。因此,我们可以在工作流输入表单中使用表达式workspace.gatk_docker,如图 11-16 所示,以引用存储在workspace表的gatk_docker键下的值us.gcr.io/broad-gatk/gatk:4.1.3.0

Workspace Data 表。

图 11-18. Workspace Data 表。

因此,workspace键本质上充当了你可以在同一工作区内多个配置中使用的全局变量。如果你想要轻松更新所有配置中 GATK Docker 镜像版本的话,这非常方便:只需更新Workspace Data表中键的值,就万事大吉了。

同样的好处也适用于资源文件,这些文件通常在项目中的多个工作流中使用,比如参考基因组文件或区间列表。考虑到长长的gs://文件路径可能有多麻烦,只需定义一次然后在其他地方简单地用短指针引用它们,这真是一大福音。蓝色并带下划线显示的文件名是 GCS 中位置的完整文件路径,尽管系统只显示文件名。

注意

Workspace Data表中定义的键不需要与 WDL 中的变量名称匹配。我们倾向于在工作流中统一变量名称和键,以便更容易理解它们之间的关系,但这并不是系统的要求。如果你愿意,你可以设置一个名为my_docker的键,并将其作为workspace.my_docker提供给工作流配置中的gatk_docker变量。

现在让我们看看另一张表,book_sample。正如图 11-19 所示,这是一张更为正式的表,具有多行和多列。每一行都是一个不同的样本,由book_sample_id列中独特的(但非常易读的)键标识。每个样本在input_bam列下有一个文件路径,在input_bam_index列下有另一个文件路径。如果你查看这些路径,你可能会认出它们是我们在第 6 章中使用的家庭三体联合调用练习中使用的样本文件,那时候你还在 VM 中运行单个命令行。

“book_samples”表。

图 11-19. book_sample表。

那么,我们如何将这个表的内容插入输入表单中呢?嗯,你刚学过workspace.*something*的语法,我们在图 11-16 中遇到过;现在是时候扩展这个课程,以阐明this.*something*的语法了。简而言之,这是相同的概念,只是现在指针是this而不是workspace,它将指向从book_sample表中取出并提交给工作流系统进行处理的任何数据行,而不是指向Workspace Data表。

过于抽象了?让我们举个具体的例子来说明。想象一下,你从book_sample表中选择了一行。这实际上产生了一个键值对列表,就像Workspace Data表一样:

book_sample_id		->    mother
input_bam      		->    gs://path/to/mother.bam
input_bam_index		->    gs://path/to/mother.bai

您因此可以根据列名引用每个值,这些列名已通过隔离该行的过程简化为一个简单的键。这就是this.*input_bam*语法的作用。它指示系统:对于您运行工作流程的每个样本行,使用该行的*input_bam*值作为*input_bam*变量的输入,以及使用此语法的每个变量。 (再次强调,名称匹配并不是必需的要求,尽管我们有意为之以保持一致性。)

好了,解释有点长,但我们知道这个系统确实让 Terra 的新手们感到困惑,所以希望这篇文章是值得的。这个功能本身肯定是值得的,因为它可以让您只需点击一下按钮就可以在任意数量的样本上启动工作流程。如果这还不够,数据表系统还有另一个好处:当您的工作流程连接到数据表作为输入时,您可以直接从数据表本身启动流程。

您想尝试一下吗?请使用复选框选择两个样本,如图 11-20 所示。

在数据子集上直接启动分析。

图 11-20. 直接在数据子集上启动分析。

选择样本后,单击位于所选行计数旁边的三个竖点的圆形符号。单击“打开方式…”以打开选项菜单,并选择工作流程,如图 11-21 所示。对于不适合您的数据选择的选项将显示为灰色。选择工作流程后,您将看到一个可用工作流程配置的列表。务必选择设置为使用数据表的工作流程;系统不会自动过滤掉配置为直接文件路径的工作流程。

指定在所选数据上运行的工作流程。

图 11-21. 指定在所选数据上运行的工作流程。

在选择工作流程配置后,您将返回到工作流程部分。查看配置摘要时,您会发现它现在已设置为在您选择的数据子集上运行,如图 11-22 所示。

配置更新后的数据选择。

图 11-22. 配置更新后的数据选择。

第一次提交此工作流程执行请求时,系统会保存您选择的样本列表,并将该列表存储为新数据表sample_set中的一行。之后,每次您以这种方式对样本运行工作流程时,系统都会向sample_set表中添加行。请注意,您也可以自行创建样本集;在第 13 章中会看到一个示例。

注意

手动选择行主要用于测试目的;这种方法不太可扩展。为了更具可扩展性,您可以选择在表中的所有行上运行工作流,或者您可以提前创建集合。要访问这些选项,只需点击表选择菜单旁边的选择数据链接。说到这个:是的,您可以在工作区中拥有多个数据表,尽管您不能同时在多个表上启动工作流。

最后,只需点击运行分析按钮,然后确认启动即可。但是这次,当您进入作业历史部分时,请留下来,这样我们就可以看看您的第一个工作流提交的情况如何。

监控工作流执行

当您在启动工作流后被带到作业历史页面时,您看到的是您刚刚创建的提交的摘要页面。如果您在此期间离开然后自己回到作业历史页面,您将看到一个提交列表,如图 11-23 所示。如果是这样,您需要点击其中一个以打开它,以便按照接下来的说明进行操作。

作业历史中的提交列表。

图 11-23. 作业历史中的提交列表。

提交摘要页面,如图 11-24 所示,包括返回工作流配置的链接,有关启动工作流的数据信息,以及列出单独工作流执行的表格。

表中的每一行对应工作流的一个单独运行。如果您使用数据表方法启动工作流,则数据实体列显示对应数据表中行的标识符以及表的名称。如果您像在第一个练习中那样直接在文件路径上运行工作流,则该列为空白。工作流 ID是系统分配的唯一跟踪号,链接到 GCS 中执行日志的位置;如果您点击它,将带您到 GCP 控制台。大多数情况下,您会忽略该链接,而是使用最左侧列中的查看链接,查看有关工作流运行的详细状态信息。

工作流提交摘要页面。

图 11-24. 工作流提交摘要页面。

继续点击一行中的查看链接以打开该工作流运行的详细信息。工作流运行详细信息页面包含大量信息,让我们一次性逐个查看。

注意

截至目前,工作流运行详细信息会在一个名为作业管理器的新页面上打开,因为它使用的服务尚未完全集成到主 Terra 门户。您可能需要使用您的 Google 凭据登录以访问工作流运行详细信息页面。

首先,看一下总体状态摘要窗格,如图 11-25 所示。如果一切顺利,您将看到您的工作流从运行状态变为成功状态。

工作流在 A) 运行状态和 B) 成功状态下的情况。

图 11-25. 工作流在 A) 运行状态和 B) 成功状态下的情况。

如果您的工作流运行失败,页面将显示额外的细节,包括任何错误消息和日志链接,如图 11-26 所示。

工作流处于失败状态,显示错误摘要和失败消息。

图 11-26. 工作流处于失败状态,显示错误摘要和失败消息。

当一切正常时,摘要窗格下方的面板,如图 11-27 所示,是您监视工作流逐步执行所需的一切资源。随着工作流的进展,任务列表将被更新,并且将填充有诸如每个任务的日志、执行目录、输入和输出的有用资源链接。需要明确的是,在任务实际开始工作之前,不会列出任务,因此当您在工作流正在运行时检查其状态时,看到的只是任务的一个子集时,请不要惊慌。

任务列表和相关资源。

图 11-27. 任务列表和相关资源。

您可能注意到在图 11-27 中,HaplotypeCallerGVCF任务与MergeVCFs任务相比有所不同:其名称被下划线标记,并且侧面有一个看起来像一叠纸的符号。这就是散乱任务的表示方式。如图 11-28 所示,您可以将鼠标悬停在堆叠符号上,查看该步骤生成的所有shards的状态摘要;即散乱步骤生成的各个任务。

查看散乱任务的 shard 状态。

图 11-28. 查看散乱任务的 shard 状态。

图 11-27 中显示的面板有多个选项卡,请花点时间探索一下。我们在此面板中最喜欢的选项卡是时间图,如图 11-29 所示,它展示了每个任务运行所花费的时间。这不仅包括在用户操作阶段的时间——运行分析命令(例如,HaplotypeCaller),还包括在 VM 设置、拉取容器映像、本地化文件等方面所花费的时间。

时间图显示了每个任务调用阶段的运行时细分。

图 11-29. 时间图显示了每个任务调用阶段的运行时细分。

如果您还记得在第 10 章中关于工作流效率的讨论,每个阶段都会带来一定的开销。时序图是检查这些开销的好资源。它可以帮助您识别工作流中需要解决的任何瓶颈;例如,如果您的基因组区间列表不平衡或者某个命令花费的时间远远超过预期。

想看看它如何运作?试着将鼠标悬停在着色段上,您将看到工具提示出现,指示您正在查看的阶段及其所花费的时间。例如,在图 11-29 中,我们看到右侧的黄色块,显示HaplotypeCaller仅用了 21 秒来运行我们用于测试的非常短的时间间隔。相比之下,拉取容器映像花了 101 秒!

最后,您可能会注意到弹出窗口中任务的名称包括尝试 1。这是因为工作流配置为使用可抢占实例,我们在第 10 章中讨论过优化。如果其中一个任务被抢占并重新启动,则每次运行尝试将在图表中显示为单独的线,如图 11-30 所示(这来自我们分开运行的无关工作流)。

显示了被抢占调用的时间图(绿色条,位于从顶部第 2、12 和 13 行)。

图 11-30. 显示了被抢占调用的时间图(绿色条,位于从顶部第 2、12 和 13 行)。

表示被抢占作业的条形图显示为单一的实色,因为操作的不同阶段没有报告。因此,您通常可以清楚地看到是否存在一系列串行的抢占模式;例如,在图 11-30 中,一个作业开始然后被抢占(从顶部第 12 行),重新启动然后再次被抢占(从顶部第 13 行),然后再次成功启动。从这个例子中可以看出,即使是最短的被抢占作业也运行了近四个小时,如果您遇到连续多次作业抢占的情况,而这些作业构成管道中的瓶颈,总运行时间可能会显著增加。这突显了仔细评估使用可抢占实例可以获得的成本节约是否值得冒管道运行时间明显延长的风险的重要性。我们的经验法则是,如果我们的大规模数据处理结果将于下周交付,那就使用可抢占实例。但如果我们正在为两天后的会议展示拼命地完成演示,那就关闭它们并承担额外的成本。

在数据表中查找工作流输出

正如我们之前指出的,在图 11-27 中显示的工作流详细信息包括指向输出文件位置的指针。这是定位特定工作流运行输出的相当不错的方式,但在处理数百甚至数十万个样本时,这种方式并不适用。(是的,有些人已经达到了这个程度——这不是一个令人兴奋的时代吗?)

如果你在这种规模下工作,使用数据表会有一个更好的方法。下面是它:通过一个微小的配置技巧,你可以让系统自动将工作流输出添加到数据表中。当然,在我们要求你运行的工作流配置中启用了这个功能,所以让我们去看一下数据表,显示在图 11-31 中,看看它是否起作用了。

显示新生成的"output_gvcf"列的数据表。

图 11-31. 显示新生成的 output_gvcf 列的数据表。

看!正如你在图 11-31 中所见,book_sample表中出现了一个名为output_gvcf的新列,所有我们运行工作流的样本现在都有与工作流运行产生的 GVCF 文件对应的文件路径。

那么,新列的名称从何而来?让我们返回到scatter-hc.data-table工作流配置,看看我们之前忽略的输出选项卡。如你在图 11-32 中所见,我们指定了输出名称,并设置将其附加到行数据,使用this.语法,我们之前已经描述过。你可以选择任意名称(例如,你可以改用*this.my_gvcf*),或者你可以点击“使用默认”以自动使用在工作流脚本中指定的变量名称,就像我们在这里所做的那样。

工作流输出配置面板。

图 11-32. 工作流输出配置面板。

请记住,这个选项仅在你使用数据表定义工作流输入时才可用。无论哪种情况,文件写入位置都是相同的。系统默认将工作流产生的所有输出存储在一个与工作空间紧密关联的存储桶中。创建工作空间时会自动创建这个存储桶,它与工作空间具有相同的所有权权限,因此如果你与某人分享工作空间(与克隆相同的菜单),他们也能访问工作空间中的数据。然而,重要的限制是你无法在 Terra 之外修改存储桶的权限。

注意

如果删除工作空间,其存储桶及其所有内容也会被删除。此外,当你克隆一个工作空间时,数据不会被复制,元数据表中的路径仍然指向数据的原始位置。这样做的好处是,你可以避免为相同数据的多个副本支付存储费用,否则你将会生成多个副本。但是,如果你认为通过克隆一个工作空间来保存数据的副本,那么,这是不可能的。

最后,你可能已经注意到在数据页面中,在左侧菜单中有一个"Files"链接,在其他地方也可以看到图 11-31 等。如果你返回到那一页并点击"Files"链接,它将打开一个类似文件系统的界面,你可以在不离开 Terra 的情况下浏览存储桶的内容,正如图 11-33 所示。

文件浏览器界面显示工作空间存储桶中的工作流输出。

图 11-33. 文件浏览器界面显示工作空间存储桶中的工作流输出。

你甚至可以通过将它们从桌面拖放到文件浏览器中,向你的存储桶添加文件。话虽如此,如果你更喜欢,你总是可以通过 GCS 控制台访问存储桶,并通过gsutil与其内容交互。

再次运行相同的工作流来展示调用缓存功能

你以为我们已经完成了吗?还没有,我们不可能让你继续前进,而不去体验 Cromwell 的调用缓存功能。我们已经多次讲解了它的用途——让你避免运行重复的作业,并从上次失败、中断或修改的位置恢复工作流程——现在让我们看看它的实际效果。继续使用刚学到的启动流程,在一个你已经运行过的样本上再次运行工作流。当你进入作业历史页面时,当"View"链接变为可用时,点击到工作流监控详细信息。这可能需要几分钟,所以随意去拿杯茶、咖啡或其他选择的饮料——只是回来时不要把它洒在你的笔记本上。记得刷新页面;工作流状态不会自动刷新。

当你在工作流程详细页面时,导航到时序图并悬停在其中一条线的最宽条上。你会看到类似于图 11-34 的内容,报告了一个名为CallCacheReading的阶段大约运行了 10 秒钟。

显示 CallCacheReading 阶段运行时间的时序图。

图 11-34. 显示 CallCacheReading 阶段运行时间的时序图。

如果你将鼠标悬停在其他条上,你会发现没有与拉取容器镜像或本地化输入相关的任何阶段。这表明,当调用缓存对一个任务生效时,系统甚至不需要设置虚拟机。事实上,Cromwell 甚至不会将有关该任务的任何内容发送到 PAPI。

那么实际上会发生什么呢?好问题;让我们谈谈调用缓存在实际中是如何工作的。首先,你应该知道,每当 Cromwell 服务器成功运行一个任务调用时,它会在其数据库中存储该调用的详细记录。这包括任务的名称和所有的输入值:文件、参数、容器镜像版本等等,以及一个指向输出文件的链接。然后,下次你发送一个工作流程来运行时,它会在发送任务执行之前检查每个任务是否与其过去调用的数据库中的记录完全匹配。如果找到任何完美匹配,它将跳过该任务的执行,并简单地输出上次成功执行时链接到的输出文件的副本。图 11-35 展示了这一过程。

Cromwell 的调用缓存机制概览。

图 11-35. Cromwell 的调用缓存机制概览。

在这个例子中,工作流中的所有任务调用都匹配了调用缓存;换句话说,调用缓存(是的,你可以把它当做动词用),因此除了 Cromwell 服务器本身进行的几次文件复制操作外,实际上没有运行任何内容。如果你稍微修改MergeVCFs调用,然后再次运行,HaplotypeCaller调用会调用缓存,但MergeVCFs不会,因此 Cromwell 将发送该调用到 PAPI 执行。你可以尝试通过修改MergeVCFs使用的容器镜像版本(尝试 4.1.4.1)然后查看时序图。

注意

你可以通过在工作流配置中取消选中复选框来禁用调用缓存,但是为什么你会想这么做呢?调用缓存太棒了。调用缓存...ka-ching!(因为它可以省钱。)#父亲笑话。

好了,是时候休息一下了。你感觉怎么样?此刻感觉有点头晕是正常的;毕竟,你刚刚获得了超能力。说真的,你现在可以在尽可能多的样本上运行真正复杂的基因组学工作流程。这可不是小事。然而,证据就像人们所说的那样,得看结果,所以拿起勺子,让我们迎接一些大的挑战吧。

在全规模下运行真正的 GATK 最佳实践管道

你还记得第九章中的神秘工作流程#2 吗?它最终证明是 Broad Institute 的整个基因组分析流程?虽然它可能不是世界上最大的基因组学流水线(尽管它已经足够大了),它也不是世界上最快的流水线(因为它需要廉价)。但它可能只是在基因组学历史上处理了最多人类整个基因组样本的流水线(至今为止)。如果你想学习如何做一些相当标准的事情,在未来某个时候可能会有用,那么选择这个流水线是个合理的选择。

在本章的最后一节,我们将向你展示在哪里找到它,测试它,并在全尺寸人类整个基因组样本上运行它。

好消息是,这将非常简单。我们之前已经提到过,GATK 团队在GitHub 仓库中提供了其最佳实践工作流程,但这还不是全部;它还为所有这些工作流程提供了完全加载的 Terra 工作空间。这些最佳实践工作空间包含了填充有适当示例数据样本(通常是小型和全尺寸的样本)的数据表,以及已经连接到示例数据运行的工作流配置。你所需做的就是克隆工作空间。然后,你可以按照本章学到的步骤直接运行工作流程。(我们会在第十三章中展示如何从其他来源引入数据。)

查找和克隆 GATK 生发性短变异发现最佳实践工作空间

早些时候在本章中,你已经访问过Terra 图书馆展示,找到了我们为本书创建的教程工作空间。让我们回到那里,但这次你将寻找 GATK 团队发布的真正最佳实践工作空间。截至本文撰写时,特色的生发性短变异工作空间被称为Whole-Genome-Analysis-Pipeline,如图 11-36 所示。这个名称可能会随着团队计划根据工具的扩展范围调整如何命名和打包这些资源而发生变化。如果你在找到合适的工作空间时遇到问题,请查看 GATK 网站的最佳实践部分,其中包含了 Terra 工作空间的相关资源列表。还请务必查看我们书籍的博客,我们将随时间提供更新。

Whole-Genome-Analysis-Pipeline 工作空间的摘要信息。

图 11-36. Whole-Genome-Analysis-Pipeline 工作空间的摘要信息

当找到工作空间后,按照本章前文的描述进行克隆。您会注意到仪表板提供了关于工作流程、示例数据以及如何有效使用这些资源的大量信息。它甚至包括运行工作流程在不同样本上所需的时间和成本摘要。说到这一点,让我们去看看里面有什么。

检查预加载数据

在克隆的工作空间中,导航到数据部分。同样,这可能会改变,但截至本文写作时,该工作空间包含两个主要数据表以及工作空间数据表。后者列出了全基因组参考文件的全尺寸版本以及整个基因组流水线中使用的其他资源。请注意,这些资源都基于 hg38 构建(更正式称为 GRCh38),因此与我们早期使用的教程工作空间中的数据不兼容。

主要数据表称为 参与者样本,如 图 11-37 所示。

表格列表和示例表的详细视图。

图 11-37. 表格列表和示例表的详细视图。

样本 表应该对您来说很熟悉,因为它与本章早期遇到的 book_sample 表是相同类型的表,只是有几列额外的内容。正如您在 图 11-36 中看到的,那里列出了两个样本:NA12878 和 NAA12878_small。它们都来自同一个研究参与者,被称为 NA12878。前者是全尺寸的全基因组测序数据集,而后者是该数据集的降采样版本。对于每个样本,我们有一列未映射的 BAM 文件(这将是工作流程的主要输入),以及其他文件,文档解释这些文件是工作流程已经在这个工作空间中运行时产生的输出。

另一方面,参与者 表可能是新的对您来说。它列出了研究参与者,尽管在这种情况下,只有一个人。如果您仔细查看示例表,可能会注意到其中的一个列是 参与者,它是一个指向参与者表中条目(该表中的 参与者 ID)的标识符。参与者表的目的是为您的数据提供更高级别的组织,这在您有多个研究参与者并且每个参与者可以有多个相关样本(对应不同数据类型、不同测定方法或两者)时非常有用。有了这个设置,您可以使用数据表系统来做诸如在属于参与者子集的所有样本上运行工作流的事情,例如。

作为另一个例子,Mutect2体细胞分析管道(在第七章中详细描述)期望看到每位患者的肿瘤样本和配对的正常样本,这些样本正式描述为样本对。如果您查看对应的 GATK 最佳实践工作区(这个,截至撰写时),您会看到它有四个数据表将数据组织成参与者、样本、样本对和样本集合(用于在 PoN 中使用的正常样本,如第七章中描述)。该工作区中的数据表符合由 TCGA 癌症研究计划定义的数据模型:癌症基因组图谱。

更一般地说,您可以使用任意数量的表格来组织您的数据,并描述数据实体之间的关系,如参与者、样本等在结构化数据模型中的组织。在第十三章中,我们讨论了在 Terra 中构建您的数据模型的选项,并有效地使用它来节省时间和精力。

选择数据并配置完整规模的工作流程

正如我们之前所描述的,您可以直接进入工作区的工作流程页面,或者您可以从数据页面开始,通过选择样本表中的一个或两个样本来启动流程。(我们建议选择两者,这样您可以亲自体验管道测试和完整运行之间的运行时差异。)如果您选择按照之前的相同步骤操作,请点击“打开方式…”选择工作流选项,并选择预配置在此工作空间中的一个工作流程。除非自书籍出版以来有很多变化,否则这应该与我们在第九章中详细检查的工作流程相同或几乎相同。但是,您会注意到在工作流程页面上,您只能查看主要的 WDL 脚本,而不能查看包含子工作流和任务库的任何额外 WDL 文件。这是当前界面的一个限制,将在未来的工作中解决。

当我们在第九章中解剖它时,我们并没有详细查看此工作流的输入,因为当时我们专注于理解其内部管道。现在通过 Terra 界面查看它们,在工作流输入配置表单的上下文中,我们发现它只有四个必填输入,比我们迄今为止处理的更简单的工作流少。然而,这在很大程度上是对现实的一种扭曲;事实上,这四个输入中的两个表示使用结构变量捆绑在一起的更多输入,我们在第九章中遇到过这种情况。这两个结构体代表你在基因组工作流中经常遇到的两种典型输入类别。一个是参考数据的捆绑,包括基因组参考序列、相关索引文件和用于验证的已知变异资源。另一个是包含你要处理的实际数据文件的文件组。

大多数此工作流的可选输入都是任务级运行时参数和条件检查,这些都是通过它们的布尔类型轻松识别的。你可能还记得,在我们在第九章中研究的第一个工作流中,我们找到了许多定义设置如默认运行时资源分配的条件语句。这里的可选输入设置是为了在配置工作流时可以覆盖这些默认设置;例如,如果你有理由认为默认设置对你的用例不合适。然而,如果你对运行时资源分配一无所知,你可以将这些字段留空,并相信预设值足够好。

还值得快速查看“输出”选项卡,在那里你会想起这个工作流生成了大量输出,其中大部分是与质量控制相关的指标和报告。这正是我们非常感激的地方,因为将输出文件的路径写入数据表格,使得查找输出比在每个文件的 Cromwell 执行目录中查找要容易得多。值得一提的是,你最关心的输出可能是output_bamoutput_cram和最重要的output_vcf,后者是你希望在下游分析中使用的变异调用集。

尽管如此,所有这些内容应该大部分都是预填充并且准备就绪,但在“输出”选项卡上,点击“使用默认值”设置输出映射到样本表格。

启动全面工作流和监控执行

言归正传,让我们运行这个东西!如前所述,点击“运行分析”,然后点击“启动”提交工作流程执行。您可以按照我们之前描述的相同步骤来监视管道的进展,但预计这将花费更长的时间!正如之前提到的,仪表板摘要包括工作流程在两个示例数据集上运行的典型运行时间,因此请确保将其用作衡量何时检查进度的指南。您还可以浏览原始工作区的作业历史记录(您从中克隆副本的那个),其中包括过去的执行,因此您可以在那里查看时序图。

您可能注意到的一个新特点是工作流程详细信息页面的列表视图会折叠子工作流程,并以下划线字体显示它们的名称,如图 11-38 所示。这类似于散列任务的表示,但缺少侧边的小堆栈图标。

主工作流程中任务调用的列表视图。

Figure 11-38. 主工作流程中任务调用的列表视图。

请查看此级别提供的时序图:您基本上会得到工作流程组成部分的高级摘要,如图 11-39 所示。如果您将鼠标悬停在各条线上,您将看到左侧最长的片段对应于UnmappedBamToAlignedBam,这是处理数据的子工作流程,接收原始未映射数据并输出准备好进行分析的 BAM 文件。接下来的三条线由主要的质量控制子工作流程AggregatedBamQC和两个未捆绑到子工作流程中的度量收集任务组成。下面是BamToGvcf变异调用子工作流程,它生成每个样本管道的最终输出,变异的 GVCF 文件。您会注意到这四个段落都在第一个段落完成时同时开始,因为它们彼此之间是独立的。这就是并行处理的实际应用!最后一个段落是BamToCram工作流程,它为归档目的生成已处理测序数据的 CRAM 文件版本。该段的时间安排可能看起来有些奇怪,直到您意识到它在AggregatedBamQC工作流程完成后立即开始。

主工作流程的时序图,显示子工作流程(实心红色条)和未捆绑到子工作流程中的单独任务(多色条)。

Figure 11-39. 主工作流程的时序图,显示子工作流程(实心红色条)和未捆绑到子工作流程中的单独任务(多色条)。

现在返回到列表视图,点击其中一个子工作流,例如 BamToGvcf 变异调用子工作流。您将看到它会像独立工作流一样在自己的页面上打开,具有自己的任务列表、时间图等,如 图 11-40 所示。

BamToGvcf 子工作流的工作流详情页面。

图 11-40. BamToGvcf 子工作流的工作流详情页面。

理论上,您可以无限嵌套 WDL 子工作流,只要不创建循环。但在实践中,我们尚未看到超过三级嵌套(包括任务库)的工作流。无论如何,您可以通过点击右上角的向上箭头导航回到父工作流水平。

最后,您可以检查输出是否按预期生成,无论是在作业历史视图中还是在样本数据表中,正如我们在本章前面描述的那样。

下载输出数据的选项——或者不下载

无论您是通过作业历史页面浏览工作流的输出,还是通过数据页面上的数据表或文件浏览器浏览,您都可以通过单击其名称或路径来下载任何文件。这将弹出一个小窗口,其中包括文件的预览(如果它是可读文本格式)、文件大小以及下载费用的估算,这是由于出站费用。在 图 11-41 中,我们展示了两种文件的下载窗口示例:作为流水线输入的未映射 BAM 文件列表,这是一个纯文本文件,因此可以预览;以及作为流水线的最终数据输出的 GVCF,它原本也是纯文本,但这里经过 gzip 压缩以减少存储空间,因此无法预览。对于其他压缩文件格式如 BAM 和 CRAM 文件,也同样无法预览。

文件下载窗口显示 A) 未映射的 BAM 文件列表和 B) 最终的 GVCF 输出。

图 11-41. 文件下载窗口显示 A) 未映射的 BAM 文件列表和 B) 最终的 GVCF 输出。

下载窗口提供三种下载感兴趣文件的方式。您可以转到 GCS 控制台,选择多个文件并通过浏览器下载它们。您也可以直接点击蓝色的下载按钮下载该文件,浏览器也会自动下载它。或者,您可以复制终端下载命令,其中包括文件在 GCS 中完整路径,使用命令行工具 gsutil 下载文件。

直接下载选项适用于您想快速查看的单个文件,特别是如果它们很小的话。例如,图 11-41 中的 1.9 KB 文本文件预计检索成本不到一分钱。如果您需要检索多个中小型文件,比如图 11-41 中的 185 MB GVCF 文件(只需两分钱?听起来合理),通常最好使用 GCP 控制台或更好地使用gsutil。但是,如果您发现自己需要检索大文件(多个千兆字节,多个美元),也许值得停下来重新考虑,是否真的不能在没有本地文件副本的情况下完成所需操作。例如,您计划使用 IGV 查看 BAM 和 VCF 文件来检查某些变体调用吗?请记住,您可以通过将 IGV 连接到 GCS 来实现这一点。或者,您是否想要运行另一种需要工作流程的分析,或者需要交互过程?啊,这很公平……但也许您可以继续在云端完成这项工作,而不是回到本地环境。如果您能够从原始数据一直到制作论文中将出现的图形,都能在云端完成,那岂不是太神奇了?

这不是一个空想。这已经是今天的现实——但是您需要的不仅仅是比虚拟机更好的东西,也不仅仅是不同于工作流系统的东西。您将需要一个集成环境,在云端进行交互式分析,而 Terra 也提供了这一点。

工作流程生成的文件名使用普通的.vcf扩展名,而不是.g.vcf,后者是可选的,但推荐用于标识文件是 GVCF 而不是常规 VCF 文件。这突显了一个事实,即您很少能够仅凭文件名和扩展名就确切知道文件包含的内容及其生成方式。工作空间数据模型等数据管理框架可以通过帮助我们跟踪数据片段之间的关系来缓解这类问题。

总结与下一步

在过去的四章中,包括本章,我们一直在为工作流而奋斗。我们从第八章开始小规模进行,运行在单个虚拟机上的 WDL 脚本。然后,在第九章中,我们解析了真实的基因组学工作流程,以了解它们的功能和如何配置。这使我们更好地理解了这些工作流的一些要求,这些工作流被设计用来利用各种云功能,包括并行性和突发能力。

但是这让我们意识到,即使加大资源配置,我们也不能继续在单个虚拟机上运行工作流程,因此我们转而将作业分派到 PAPI 以在第十章中执行。我们尝试了两种方法:直接使用 Cromwell 和通过 WDL Runner 包装器进行,这展示了 PAPI 的强大之处,但也展示了一次只能启动单个工作流程的局限性。

最后,在本章中我们转向 Terra,以使用其内置的全功能 Cromwell 服务器。我们能够利用 Cromwell 服务器的一些最酷的功能,比如著名的调用缓存机制和时间图,而不需要为服务器维护担忧。在此过程中,我们还遇到了使用 Terra 的意外好处,主要归功于数据管理与工作流执行的集成。通过学会在数据表中的行上启动工作流程,我们现在能够轻松处理任意数量的数据。而且我们知道如何让输出自动添加到表中,所以不需要费力去查找它们。

在解释所有这些内容时,我们可能暗示应用 GATK 最佳实践到您自己的数据中现在应该只是运行我们演示的工作流程的问题。然而,在进行真正的基因组研究过程中,您通常需要执行各种不方便包装为工作流程的外围任务。无论是用于测试命令、评估结果质量还是解决错误,您都需要以更即时的方式与数据进行交互。如果您的工作职责超出了我们狭义定义的基因组学(即变异发现),包括开发和测试以回答特定科学或临床问题的假设,那情况将更是如此。

在第十二章中,我们使用了一个流行的交互式分析环境,它也对可重复科学有奇效,称为 Jupyter。我们仍然在 Terra 平台内工作,该平台为在云中提供 Jupyter 提供了可扩展的系统。

第十二章:Jupyter Notebook 中的交互分析

通过本章,我们完成了在云中工作的体验。我们在第四章从 shell 中运行单个命令开始,通过第五章到第七章,逐步熟练使用 GATK 工具。然后,在第八章中,您了解了脚本化工作流,并逐步发现了更好地运行它们的方法。

然而,我们现在回到一个不可避免的事实,即基因组学中并非所有事情都可以(或者应该)作为脚本化工作流来完成。有时,您只想直接与数据交互,也许生成几个图表,并根据图表的情况确定下一步该怎么做。您可能处于项目的早期探索阶段,卡在中途需要排查一些失败的样本,或者转向深入挖掘一组人的遗传学。无论如何,您需要能够快速尝试想法,跟踪每个尝试产生的结果,并与他人分享您的工作。

在本章中,我们向您展示如何在 Terra 中使用 Jupyter Notebook 实现这些目标。我们从简要介绍 Jupyter 开始,以防您对概念和工具不熟悉。我们花费了更多时间描述 Jupyter 在 Terra 中的工作方式,重点介绍更适合于 Terra 和云环境的功能和行为。然后,在本章的实际操作部分,我们将通过一个示例笔记本指导您进行三种类型的交互式分析,直接与前几章涵盖的主题和练习相关联。

在 Terra 中介绍 Jupyter

如果您按照预定的顺序阅读本书,我们在第十一章介绍了 Terra,主要是让您能够使用其内置的 Cromwell 服务器以高效且规模化地运行工作流。您学会了克隆工作空间、读取工作流配置,并在预设数据集的一部分或全部上启动工作流。然而,Terra 不仅适用于运行工作流,还包括用于进行交互式分析的工具,包括一个 Jupyter 服务。在本章中,我们将向您展示如何在 Terra 中使用 Jupyter 与数据交互,并实时进行分析。您仍然会在同一个工作空间中工作,但这一次您会进入“Notebooks”选项卡,而不是“Workflows”选项卡。

在本介绍的第一部分中,我们旨在提供足够的背景和基础知识,以便如果您以前从未听说过 Jupyter,也能完成接下来的练习。如果您已经熟悉 Jupyter,请随意跳过此部分。在第二部分中,我们具体讨论了 Jupyter 在 Terra 中的工作原理,主要集中在与典型本地安装相比的不同之处。我们强烈建议您阅读它,即使您对 Jupyter 有一般的了解,因为它将帮助您更好地理解练习的关键点。

Jupyter Notebooks 概述

简而言之,Jupyter 是一个应用程序,可以创建一种特殊类型的文档,将静态内容(如文本和图片)与可执行代码甚至交互式元素结合在一起。例如,本章中您将使用的教程笔记本有纯文本部分简要解释正在进行的内容,还包括代码单元格,其中包含完全功能的工具命令(您可以执行以在真实数据上运行 GATK)。它还包括一个集成的 IGV 模块,允许您查看命令的结果。要运行代码单元格的内容,您只需点击单元格,然后在键盘上按 Shift+Enter,或者在菜单栏上点击运行图标。命令运行时,命令的输出日志直接出现在代码单元格的下方,如图 12-1 所示。当您将运行了代码单元格的笔记本副本发送给合作者时,他们可以在文档中查看嵌入的结果。

一个 Jupyter Notebook 中文档文本、代码单元格和执行输出的截图。

图 12-1. Jupyter 笔记本中的文档文本、代码单元格和执行输出的截图。

基本思想是将分析方法和发现结合在一个地方,以任何人都可以轻松分发的形式呈现。在某种程度上,这是传统科学论文的逻辑演进,但更好,因为它大大缩短了阅读分析方法和实际重现分析之间的路径。难以估量这一概念有多强大以及它对计算科学中发现的可重复性和再利用性所产生的巨大影响。

那么在这样一个交互式笔记本中,你可以运行什么样的代码呢?最初的概念是在名为IPython的环境下专门运行 Python 代码。Jupyter 项目源于 IPython,旨在将这一概念扩展到其他语言,首先是 Julia、Python 和 R,这些也体现在 Jupyter 的命名中(因此,Jupyter 的拼写中有py,而不是罗马神话中的 Jupiter,即雷神)。现在 Jupyter 还有支持其他流行语言kernels,例如 Ruby。在本章的练习中,我们使用一个 Python kernel,它支持包含 R 代码,并且通过称为Python 魔术方法的一组精彩特性,几乎可以运行你能在 Shell 环境中运行的任何内容。在我们看来,这就像同时拥有多个世界的最好一面。

注意

在通用计算中,kernel是操作系统核心的程序。在 Jupyter 环境中,kernel是解释代码单元的程序,将这些指令传递给笔记本运行的实际操作系统,并检索结果以在笔记本中显示。

Jupyter 笔记本由一个服务器应用程序支持,其需求非常简单,几乎可以在任何类型的计算基础设施上运行,包括您的笔记本电脑。此外,Jupyter 笔记本使研究人员能够封装重现其描述的分析所使用的软件环境所需的信息,使其成为分发可重现代码的绝佳工具。它还是一种越来越受欢迎的教学工具,希望通过本章的练习,这一点将变得显而易见。

Jupyter 的日益流行催生了一个丰富的附加工具和服务生态系统。例如,GCP 运营一个名为Colaboratory的服务,提供免费访问云端笔记本,并提供针对机器学习应用的教程材料。与此同时,Google Cloud AI 平台提供的付费服务提供预配置的虚拟机,用于运行与其他 Google Cloud 服务集成的笔记本。另一个例子是Binder,一个开源社区驱动的项目,可以接受 GitHub 存储库中的任何 Jupyter 笔记本,并在交互式环境中打开它们。这些免费服务通常在它们提供的环境的计算能力方面存在限制,但对于分享工作代码、教程等来说仍然非常方便。

尽管如此,对于计算科学中的需求和偏好,单一工具实际上不可能满足全方位的需求和偏好,我们也意识到 Jupyter 笔记本确实存在一些局限性,这些局限性限制了它在特定受众中的吸引力。例如,有着高级编程经验的人通常批评缺乏像语法高亮和代码检查这样大多数现代编程工具上标准的开发功能。此外,习惯于像RStudio这样的探索性分析界面的数据科学家们倾向于认为主要的 Jupyter 界面过于基础,缺乏辅助功能。JupyterLab项目旨在通过提供更丰富的界面来解决这一局限性,这一界面概念更接近于 RStudio。鉴于我们近年来在数据科学领域看到的经济投资激增,我们预计工具选择将随着时间的推移而不断改善,我们期待看到下一代界面会是什么样子。

与此同时,我们选择使用 Jupyter 是因为它对新手友好,并且支持可移植性和再现性,目前尚无与之相匹敌的支持。

Jupyter Notebooks 在 Terra 中的工作原理

在深入细节之前,您应该知道 Terra 使用标准的 Jupyter 服务器实现,因此您将要使用的界面和核心功能基本上与其他设置中看到的一样。因此,您可以利用互联网上丰富的文档和教程来学习如何使用各种菜单选项、小部件等,这里我们不会详细介绍。

与 typcial 本地安装相比,Jupyter 笔记本在 Terra 中工作的一个真正不同之处是计算环境的设置方式。现在让我们详细讨论一下,因为这对您在多个方面的重要影响,例如,您定制环境的灵活性,以及访问数据和保存分析结果的方式。

概述

在 Terra 中,笔记本文档存储在您工作空间的存储桶中。当您首次打开笔记本时,Terra 会请求在 GCP 中创建一个 VM,启动一个带有 Jupyter 服务器的容器,并在该容器环境中加载您的笔记本,如图 12-2 所示。

An overview of the Jupyter service in Terra.

图 12-2. 在 Terra 中 Jupyter 服务的概述。

从那时起,在笔记本中运行的任何代码都将在该 VM 上的容器中执行。甚至可以在 VM 上运行安装软件包或加载库的命令,以即时自定义环境。从概念上讲,这与通过 GCP 控制台设置的 VM 类似,后续在每一章中使用,只是它最初是由 Terra 创建的,并且为您提供了 Jupyter 界面,而不是裸露的 shell 终端。

可以打开一个终端界面到笔记本,这使您能够执行动作,如列出文件和安装软件包,而无需将这些动作放入代码单元中。然而,我们建议谨慎使用此功能,主要是因为这与 Jupyter 的基本目的相悖,即在分析过程中捕获每一个有意义的操作。在这种情况下,列出目录内容可能并不具有意义,但安装软件包或导入数据则可能起到关键作用。在笔记本记录中省略此类操作可能导致工作的可重复性中断。

成本模型也是一样的;GCP 会根据 VM 运行的时间收费,即使 VM 没有在执行任何操作。好消息是,Terra 有自动功能来检测不活动状态;稍后详细讨论。基本费率取决于您选择使用的 VM 配置;默认情况下,Terra 提供基本配置以满足常见的性能需求,但您可以根据特定需求调整配置。我们稍后回到这个话题。

由 VM 加上容器及其包含的所有软件组成的整体计算环境称为笔记本运行时。它严格个人使用;即使您与他人共享工作区,其他人也无法访问它。在本介绍的最后部分,当您更好地理解它的工作原理时,我们会讨论分享和协作。

访问数据

您的笔记本运行时配备了本地存储空间。尽管存在云计算的细微差别,它基本上就像一个带有文件系统的硬盘。您可以通过笔记本中的代码单元与文件系统交互,通过 Jupyter 内置的图形文件浏览器,或通过上述支持经典命令如lscd的终端界面来操作。这使您可以在笔记本中使用常规文件路径运行命令,就像在您的笔记本电脑上一样。此外,有几种方法可以在不先将数据复制到本地文件系统的情况下运行命令。我们不会详细列出从笔记本访问数据的所有选项,因为那样会很无聊。相反,这里是我们最常用的几种方式的列表:

  • 通过笔记本的图形文件浏览器,将桌面上的文件上传到笔记本的本地存储。

  • 使用gsutil cp将数据从 GCS 存储桶复制到笔记本的本地存储。

  • 运行支持流式处理(如 GATK4)的工具直接在 GCS 中的文件上。

  • 通过使用编程接口(API)从工作区数据选项卡上的数据表中导入表格数据。

  • 从 Google 的 BigQuery 数据存储服务导入表格数据。

我们将在下一节实践中向你展示如何使用第二和第三个选项,并将为学习第四和第五个选项提供附加资源的指引。

保存、停止和重新启动

在你使用笔记本时,系统会定期将更改保存回工作区存储桶中原始的笔记本文档。然而,除了笔记本本身外,Terra 不会自动将笔记本的本地存储中的任何文件保存回工作区。出于我们即将讨论的原因,你不能依赖笔记本运行时进行永久存储,因此你必须采取措施保存任何关心的输出文件的副本,最好保存到工作区存储桶中。我们将在“设置沙盒并将输出文件保存到工作区存储桶”中展示一个简单的方法来做到这一点。

当你完成工作并关闭笔记本时,Terra 指示 GCP 停止笔记本的运行时,但保存其状态,包括 Jupyter 容器的状态,以及你可能通过安装软件包等方式进行的任何修改,以及其本地存储分区上存在的任何文件。这样,你可以随时以最小的努力恢复工作:当你重新打开笔记本时,Terra 会重新启动虚拟机,并将笔记本的运行时恢复到其保存的状态。在 GCP 端,重新启动过程最多可能需要两分钟,但在此期间,Terra 会根据文档的最新保存状态为你提供笔记本内容的只读视图。最后,正如前一段提到的,Terra 能够检测到你不再活跃地使用笔记本(根据虚拟机空闲时间),并会自动保存笔记本并停止虚拟机,以限制你的成本。

自定义你的笔记本计算环境

你可能希望通过两种主要方式定制你的笔记本运行时:修改虚拟机资源分配(例如 CPU 数量、内存等)和/或修改容器中预安装的软件。

正如之前简要提到的,你可以轻松修改分配给笔记本运行时的虚拟机资源;例如,如果你需要比默认配置中包含的更多的 CPU 或内存,你可以相应地调整笔记本运行时的配置。甚至,如果你计划使用支持 Spark 的工具,你可以请求一个 Spark 集群而不是单个虚拟机。方便的是,Terra 包含一个笔记本运行时配置面板,比起等价的 GCP 接口要简单得多,你可能还记得来自第四章的内容。但真正酷的是,你可以随时进行这些操作,即使你已经开始在笔记本中工作。你只需拉起配置面板,指定你想要的内容,让系统使用新的规格重新生成你的笔记本运行时。因此,你无需费力地预测你将需要的资源类型。你可以从最小设置开始工作,然后在遇到限制时再逐步调整。

然而——这是一个重要的警告——新的运行时 将是一个空白,因为再生过程会使用原始容器映像进行新虚拟机的配置,并且使用空存储分区。如果你的笔记本工作主要包括运行时间较短的命令,不涉及太多计算成本,这并不是一个大问题:Jupyter 菜单中包含重新运行所有代码单元(或者到某一点之前的所有代码单元)的选项,因此你可以简单地再现先前的状态。然而,如果你的工作涉及到大量计算,重新运行可能并不轻松,这时你可能需要更好的策略。例如,你可以明确将到目前为止生成的输出保存到工作空间存储桶中,然后设置笔记本以便在下一部分工作中使用这些已保存的输出作为输入。

第二个定制点是在生成笔记本运行时时修改容器中的软件,这有点复杂,但值得花几分钟来讨论。首先,为什么要这样做呢?假设您的分析需要一些不包括在默认笔记本运行时配置中的软件包。您可以开始笔记本并进行一些安装步骤,但如果有多个需要相同配置命令的笔记本,这可能会成为一个维护头痛。如果将其中一些软件安装步骤移出笔记本并放入环境配置中,将会更容易得多。在 Terra 中,您可以通过两种方式实现这一点:您可以指定一个设置脚本,Terra 将在创建或重新生成笔记本运行时时在容器中运行它,或者您可以为运行时服务提供自定义容器镜像。或者您实际上可以结合两者:指定一个自定义容器和一个启动脚本来修改其设置。图 12-3 阐明了这些选项之间的区别。

自定义笔记本运行时安装软件的选项。

图 12-3. 自定义笔记本运行时安装软件的选项。

在即将进行的练习中,我们将向您展示如何使用启动脚本选项,因为这是功能与易用性的良好折衷:制作您自己的脚本并不困难,您可以轻松地为其他在 Terra 中使用相同内核的用户分发该脚本。自定义容器镜像选项在技术上更强大且更具可移植性,但更为复杂。如果您有兴趣了解其工作原理,请查看Terra 上关于自定义容器的文档

现在您知道您可以定制笔记本运行时的强大功能后,我们需要解决一个关键问题,这是我们一直小心避免提到的:您可以使用多少个笔记本运行时?您是否为所有内容使用相同的运行时,每个工作区一个,每个笔记本一个,或者您可以像在 GCP 控制台中创建 VM 实例那样随意创建新的运行时?坦率地说,答案可能会随着平台进一步成熟以及产品开发团队收集更多研究者实际需求的数据而发生变化(所以请随时提供您的意见!)。

截至目前,Terra 笔记本服务为特定计费项目内的所有工作空间提供单一笔记本运行时。明确地说,这意味着如果你在与第一个笔记本相同的计费项目中的任何工作空间中打开或创建另一个笔记本,新的笔记本将在相同的运行时环境中打开。如果你计划在两个笔记本中使用的数据、资源和软件有很大的重叠,这可能非常方便。然而,如果你正在处理配置需求完全不同的不同项目,这可能会带来主要的复杂性。在这种情况下,你可能需要考虑在不同的计费项目下开发具有不兼容要求的笔记本,因为这将为每个项目提供完全独立的笔记本运行时环境。

分享和协作

到此为止,你已经完成了这个介绍的最后一节,现在是时候谈谈如何与他人友好相处了。正如我们之前提到的,笔记本运行时是个人的:只有你能访问那台机器、容器和 Jupyter 服务器。如果你与合作者分享工作空间,他们打开相同的笔记本时,它将在他们自己的运行时环境中打开。因此,他们在笔记本中所做的任何工作都不会影响你的运行时环境的状态。然而,系统会自动保存他们对共享文档所做的任何更改,因此与合作者明确设置期望非常重要,他们是否可以修改笔记本,或者他们是否应该在单独的副本中工作。

此外,请注意,当有人正在使用笔记本文档时,Terra 将会锁定它,以避免多人同时进行冲突更改。在这种情况下,你的合作者可以在只读预览模式下打开笔记本,或者他们可以在特殊的游乐场模式下打开笔记本,在这种模式下,他们可以在自己的运行时环境中进行更改和运行代码,但不会保存任何更改到原始文件中,如图 12-4 所示。这可能不完全符合你可以基于 Google Docs 设想的理想协作体验,但考虑到所涉及的限制,这提供了一个合理的折衷方案。

在 Terra 中分享和协作笔记本的选项。

图 12-4. 在共享工作空间中,当两个人同时打开笔记本时,可以防止覆盖。

如果你在实践中对这一切如何运作感到困惑,不用担心!首先,这完全可以接受;如果有什么不清楚的地方,那是我们的错,而不是你的错。其次,好消息是:你已经到达了理论壁垒的尽头,现在是时候在真实的笔记本中进行实际练习了。

在 Terra 中开始使用 Jupyter

我们将使用预写的笔记本进行工作,因此您大多数时候只需运行单元格,尽管在可能的情况下,我们尝试包含一些额外的提示,以加强学习。在第十三章中,我们讨论如何从头开始创建您自己的笔记本或从外部来源导入现有笔记本。

要开始,请返回到您通过克隆原始书籍工作区创建的工作区第十一章。如果您手头没有 URL,请在您的工作区中找到它;或者如果您已删除它,则可以按照第十一章开头的相关说明再次克隆原始工作区。找到您的工作区后,打开它并转到笔记本选项卡。在那里,您将看到列出两个笔记本,如图 12-5 所示。这实际上是同一个笔记本的两个副本:一个从未运行过,另一个我们已经运行了所有内容,以便您可以看到预期的输出。

笔记本选项卡显示两个笔记本副本:一个已执行,另一个没有任何之前的结果。

图 12-5. 笔记本选项卡显示两个笔记本副本:一个已执行,另一个没有任何之前的结果。

我们建议您仅以预览模式打开第二个,以便将其内容保留为参考,以防在另一个中遇到令人惊讶的内容,您将使用它来完成即将进行的练习。

然而,在您打开任何内容之前,我们将指导您自定义运行时配置。如果您已经开始打开其中一个笔记本,请不要惊慌;您仍然可以重新配置笔记本运行时。我们只是想节省您一点时间,因为启动新的运行时需要几分钟时间,并且我们知道,我们将会需要比默认配置提供的更多内容。

检查和自定义笔记本运行时配置

正如前面所述,默认情况下,Terra 为您创建的运行时环境设置了基本资源分配和一组标准软件包。只要您在适当的计费项目下的工作空间中,您可以随时查看此配置,而无需打开笔记本。要这样做,请查找笔记本运行时状态小部件,截至撰写本文时,几乎所有工作空间页面的右上角都显示了该小部件,如图 12-6 所示。

笔记本运行时状态小部件。

图 12-6. 笔记本运行时状态小部件。
注意

我们从 Terra 产品开发团队听说过有关笔记本运行时状态小部件显示可能会在不久的将来发生变化的传言,如果情况如此,您将需要查找它,或者如果失败,可以在书的存储库中的GitHub上查阅相关文档。

单击右侧的齿轮图标可打开运行时配置页面。如果在当前计费项目下未对运行时进行任何自定义,则表单应显示所有默认设置,如图 12-7 所示。

默认笔记本运行时配置设置。

图 12-7. 默认笔记本运行时配置设置。

您可以看到一个小列表,选择默认环境,其特征是被认为是最重要的软件包。要获取安装在每个环境上的完整详细信息,请选择您感兴趣的环境,然后点击“此环境中安装了什么?”以打开详细视图。如图图 12-8 所示,该详细视图进一步分为诸如 Python、R 和工具等类别。选择 Python 或 R 将显示相应语言的运行时环境中包含的完整软件包列表。选择工具将显示运行时环境中还包含的命令行可执行工具列表。

实际上,如果选择工具,您会发现这个默认设置实际上包括 GATK,这对我们这些在基因组学领域工作的人来说是一个很好的选择,考虑到 Terra 不仅仅面向基因组学的更广泛的受众。也就是说,出于本书的目的,我们正在使用与默认配置版本不同的版本,因此我们需要进行定制。此外,我们还将要使用一个 Python 库(剧透警报),它可以在笔记本中嵌入 IGV 浏览器窗口(这太酷了)。我们可以在笔记本内部安装这两者,但正如介绍中所述,我们更喜欢使用启动脚本,在笔记本运行时创建过程中安装它们。如果您感兴趣,我们在附带的侧边栏中提供了有关所讨论脚本的更详细的内容,但如果您不感兴趣,或者您急于开始使用笔记本本身,则可以跳过它。

默认运行时环境中安装的软件包的详细视图。

图 12-8. 默认运行时环境中安装的软件包的详细视图。

那么我们应该在哪里指定启动脚本?你可能会想要在笔记本运行时自定义页面的环境菜单中查找,甚至选择自定义环境选项,这个概念上看似正确,但实际上是错误的。那里是你用来指定替换内置 Docker 镜像的地方。相反,你需要向下看一点,到计算能力部分(在图 12-7 中显示),这里允许你修改虚拟机资源分配。该部分包括一个菜单,允许你从三个预设配置中选择,标记为提供中等、增加或高级计算能力,或者在自定义标签下提供你自己的配置。

注意

坦白地说,如果这部分界面在不久的将来也有所发展,我们不会感到惊讶,因为将软件定制选项与硬件分配分组在一起并不是很合理。更不用说那些预设配置的命名了,它们就像星巴克杯子的大小名称一样不太有帮助:一旦你习惯了它们,它们看起来有点合理,但第一次踏进星巴克,你只是为了价格告诉你哪个更大而感到开心。

随意选择每个预设配置,查看它们各自的资源分配方式。当你准备好继续时,选择自定义选项以打开可编辑的配置窗口。你会看到一个之前不可用的名为“启动脚本”的新字段出现(图 12-9)。你最终可以在那里的 URI 文本框中输入启动脚本的路径(对应统一资源标识符(uniform resource identifier)的一种,是 URL 的近亲)。我们在书的存储桶中包含了脚本的副本,所以你可以使用这个路径:

gs://genomics-in-the-cloud/v1/scripts/install_GATK_4130_with_igv.sh

其余的资源分配将会按默认值(中等)进行,包括 4 个 CPU、15 GB 内存(RAM)和 50 GB 硬盘空间。图 12-9 展示了这个配置的外观。

作为提醒,这个启动脚本会在运行时环境中安装 GATK 版本 4.1.3.0,以及一个集成模块,使得你可以在笔记本内部使用 IGV 查看基因组数据。

如果选择自定义配置,则“计算能力”部分允许您指定一个启动脚本。

图 12-9. 计算能力部分允许你在选择自定义配置时指定一个启动脚本。

当你完成时点击“创建”按钮(如果之前已创建过运行时,则标记为“替换”),Terra 开始根据你的设置创建新的运行时环境。你可以去拿一杯喜欢的饮料或继续下一组指令,按你的喜好选择。在任何情况下,Terra 与 GCP 通信以提供你闪亮新的运行时环境时,你可能需要等待几分钟。

打开编辑模式的笔记本并检查内核

为了查看笔记本,你不需要等待运行时准备就绪,所以请打开教程笔记本的未运行副本。无论你的运行时是否准备就绪,Terra 最初会以预览模式打开笔记本,即笔记本是只读的。正如图 12-10 所示,预览面板顶部的菜单提供两个选项,以交互方式打开笔记本:编辑,正常工作笔记本;或者游乐场模式,无需保存任何内容进行实验,正如我们在介绍中简要讨论的那样。

笔记本预览页面上的菜单显示主要选项:预览、编辑和游乐场模式。

图 12-10. 笔记本预览页面上显示的菜单,包括主要选项:预览、编辑和游乐场模式。

点击“编辑”并等待运行时准备就绪。当转换为编辑模式时,你会注意到标准的 Jupyter 菜单栏的出现,如图 12-11 所示。一个更微妙但同样重要的标志是菜单栏右侧的小白色框,显示“编辑模式”标签。如果你错误地打开了游乐场模式,Jupyter 菜单栏也会显示,但是白色的“编辑模式”标签将被橙色的“游乐场模式(未保存编辑)”标签替代。

标准的 Jupyter 菜单栏。

图 12-11. 标准的 Jupyter 菜单栏。

在菜单栏区域的更右侧,是标识活动内核的 Python 3 标签以及 Python 标志。你可能还记得笔记本的内核是解释笔记本中代码并启动每个运行单元执行的计算引擎。对于这个特定的笔记本,我们决定使用 Python 3 内核,以便使用 Python 魔术方法执行终端命令(包括像gsutil和 GATK 这样的程序)——这确实是一个技术术语,我们发誓。你很快就会看到它的神奇之处。

如果你想知道,是的,你可以在笔记本运行时使用 Kernel 菜单切换内核,但我们真的不建议这样做。原则上,这可能听起来很有用——例如,在子集单元格中运行不同语言的代码——但实际上却是危险的,如果不小心会造成混乱。如果你需要在同一个笔记本中使用 Python 和 R 代码,我们强烈建议使用 Python 3 内核和魔术方法命令,正如我们在这个笔记本中展示的那样。

现在是时候运行一些真实的代码单元格了!

运行 Hello World 单元格

让我们运行一些简单的示例,这样你就能感受一下在 Jupyter 笔记本中工作的感觉,如果你以前没有这样做过的话。我们将运行三种类型的命令来演示运行 Python 代码、R 代码以及 Python 环境下的命令行工具的语法。

Python Hello World

这是经典的 Python Hello World 示例,使用print()函数并给它传递字符串Hello World。你可能还记得在第八章中我们在 WDL 中使用echo "Hello World"命令的情况。要运行该单元格,点击灰色区域中的任意位置选择单元格,然后按键盘上的 Shift+Enter,或者在页面顶部的工具栏上使用“Run”菜单运行该单元格:

In [1]  print("Hello World!")
Out [1] Hello World!

如果你想要给问候语增加一些灵活性,可以使用变量。双击单元格编辑并修改代码,如下所示,然后再次运行它:

In [2]  greeting = "Hello World!"
        print(greeting)
Out [2] Hello World!

顺便说一句,这显示了当你有一个包含多行内容的单元格时,运行它会执行单元格中的所有代码。有时,将多个命令分组到一个单元格中是有意义的,因为你总是希望运行它们所有。但是,你也可以选择将它们分成多个单元格。现在试着做一下这个。你可以通过点击页面顶部工具栏上的“+”图标或者转到“Insert”菜单向笔记本添加新单元格。前者会自动在当前活动单元格下方创建新单元格,而“Insert”菜单则可以明确选择将其添加到当前活动单元格的上方或下方。

In [3]  greeting = "Hello World!"
In [4]  print(greeting)
Out [4] Hello World!

这使你能够将变量的赋值与任务执行分开。在这个简单的例子中,这并没有太大的区别,但是当你执行更复杂的操作时,选择将哪些命令分组以及哪些命令分开变得更加重要。

现在你已经掌握了基本的操作技巧,让我们看看如何在这个 Python 笔记本中运行一些 R 代码。

使用 Python 魔术方法运行 R Hello World

在我们可以深入进行 Hello World 练习之前,这需要一点设置。还记得我们用来自定义笔记本运行时的启动脚本吗?在该脚本中的一个步骤是安装rpy2包,该包处理 Python 笔记本中的 R 代码解释,是我们在本章前面提到的魔术方法功能之一。要激活它,您首先需要导入rpy包并激活相应的笔记本扩展:

In [5]  import rpy2
        %load_ext rpy2.ipython

这可能需要几秒钟,此期间服务器在单元格左侧的括号中显示一个星号(*)以指示正在处理中。加载包后,您有两种调用魔术方法的方式:使用%R用于单行代码或%%R用于整个单元格。

这是同样基本的 Hello World 示例,但这次是在 R 中运行的,使用单行魔术方法调用:

In [6]  %R print ("Hello World!")
Out [6] [1] "Hello World"

是的,这与我们在 Python 示例中运行的代码相同,因为 R 也有一个print()函数。您可以通过输出显示它是 R 版本,因为输出显示为带有问候语作为单个字符串元素的数组,而 Python 版本只返回问候语的文本字符串本身。

话虽如此,将其修改为使用变量赋值会使其更加明显,说明它是 R 代码而不是 Python 代码。这次,使用%%R将魔术方法应用于整个单元格:

In [7]  %%R
        greeting <- "Hello World!"
        print(greeting)
Out [7] [1] "Hello World"

就是这样:您正在 Python 笔记本中运行 R 代码。当我们进行“严肃”的练习时,这将非常方便,因为我们需要使用 Python 来嵌入 IGV 浏览器(很快就会介绍!),但我们也希望稍后使用现有的 R 脚本进行绘图。现在我们可以同时享受最好的两个世界。只需记住,当您开始在自己的笔记本中使用它时,您需要包含导入rpy2并激活扩展的单元格。

这将我们带到我们笔记本中要运行的第三种类型的命令:像lsgsutil和 GATK 这样的命令行工具。

使用 Python 魔术方法的命令行工具 Hello World

对于这个例子,没有什么需要加载的;Hello World 案例可以直接使用。我们将使用在 WDL Hello World 示例中使用过的经典echo命令。只需在命令前面加上一个感叹号,然后运行该单元格:

In [8]  ! echo "Hello World!"

Out [8] Hello World!

您可以以这种方式使用所有经典的 Shell 命令;例如,如果您想列出工作目录的内容,请在单元格中键入**! ls**并运行它。类似地,您可以以此方式运行笔记本运行环境中安装的任何命令行工具。Terra 中所有预设环境都包括gsutil包,因此您可以在任何 Terra 笔记本中使用这些工具。我们将在下一节中详细介绍具体示例。稍后,我们还会运行命令以运行我们的启动脚本安装的 GATK,使用相同的基本语法。

到目前为止,我们一直专注于代码单元格,但请记住,所有描述性文本单元格也是可编辑的。当然,可以双击一些单元格,看看它们的外观如何变化以显示它们处于编辑模式。尝试进行一些编辑,然后完成后,“运行”单元格(就像运行代码单元格一样),退出文本编辑模式。描述性文本单元格使用一种简单的格式标记语言称为Markdown,因此您可以设置标题级别,制作项目列表等。有关使用 Markdown 的更多信息,请参阅 Jupyter 项目文档中这个有用的页面。创建笔记本中的新单元格时,默认情况下会将它们设置为代码单元格,但可以通过 Cell 菜单选择 Cell Type > Markdown 将它们切换为 Markdown。

现在您对基础知识有了牢固的掌握,是时候做一些更具体于云环境和我们感兴趣的基因组学主题的工作了。

使用 gsutil 与 Google Cloud Storage 桶交互

在笔记本中,我们想要处理的数据大多数时间存储在 GCS 桶中,因此您首先需要学习如何访问这些数据。好消息是:您可以使用gsutil命令来执行我们在第四章及以后展示的所有相同操作。例如,使用gsutil ls列出书桶的内容:

In [9]  ! gsutil ls gs://genomics-in-the-cloud/

同样,您可以使用gsutil cp本地化文件;也就是说,将它们从桶复制到笔记本运行时的本地存储空间。例如,使用以下命令将文件从书桶复制到笔记本运行时的sandbox目录:

In [10]  ! gsutil cp gs://genomics-in-the-cloud/hello.txt .

然后,您可以运行cat来读取本地化文件的内容。您可以在 Python 中编写这个命令,因为这是一个 Python 笔记本,但是很难超越cat的简洁性!

In [11]  ! cat hello.txt

正如你所看到的,这些基本上是我们在第四章中使用的相同命令,只是我们在前面加了!来告诉 Python 解释器将该行作为终端命令执行,而不是作为 Python 代码读取。

设置一个变量指向书桶中的生殖系列数据

接下来的几个练习将使用我们在书的数据捆绑包中提供的生殖系列数据。由于示例数据的路径相当长,让我们设置一个变量以更简洁的形式将其存储在笔记本中,就像我们在第五章中使用环境变量一样:

In [12]  GERM_DATA = "gs://genomics-in-the-cloud/v1/data/germline"

这是一个 Python 变量,所以当您在 shell 命令中使用它时,您需要用大括号括起来。例如,使用gsutil命令列出该目录的内容如下:

In [13]  ! gsutil ls {GERM_DATA}

想一想我们在这个变量周围使用花括号而不是像您可能根据我们迄今使用过的 Bash 环境变量中那样调用它为$GERM_DATA的做法。这里需要记住的关键点是,我们将存储桶快捷方式设置为 Python 变量,而不是 shell 环境变量。在笔记本中可以使用 shell 环境变量,但这是另一个时间的话题。

您还可以基于这个变量来组合路径以列出子目录;例如,获取 BAM 文件列表或对特定文件执行操作。对于以下每个命令,请尝试推断其功能,然后在笔记本中运行它,并将结果与您的预期进行评估。

In [14]  ! gsutil ls {GERM_DATA}/bams

In [15]  ! gsutil cp {GERM_DATA}/bams/mother.ba* .

In [16]  ! ls .

本章后面,我们还向您展示了如何在一些 Python 代码的上下文中使用相同的GERM_DATA变量。

设置一个沙盒并将输出文件保存到工作空间存储桶。

当您在笔记本中运行生成输出文件的命令时,默认情况下这些文件将保存在笔记本的本地存储空间中。然而,与您的笔记本关联的本地存储是临时的,因此您需要将任何关心的输出复制到一个 GCS 存储桶中。您可以使用任何您有写入权限的存储桶来执行此操作,但我们建议使用工作空间专用的存储桶。

为了简化将输出保存到存储桶的过程,我们喜欢做两件事:创建一个沙盒来存放我们将要生成的输出文件,并设置一个指向工作空间存储桶的变量。

让我们从sandbox目录开始;创建一个新目录,并将一些文件移动到那里以进行演示。这里我们仅包含命令,但不详细说明其目的或结果。我们在笔记本中提供了更多细节。在查看完整描述并在笔记本中运行命令之前,请尝试推断每个命令的功能:

In [17]  ! mkdir -p sandbox/

In [18]  ! mv mother.ba* sandbox/

In [19]  ! ls sandbox

当您的沙盒准备好后,让我们来处理工作空间存储桶。工作空间存储桶名称是一长串由字母和数字组成的机器生成序列,使用起来有些烦人。幸运的是,我们可以通过程序导入它(而不是手动在工作空间仪表板中查找),因为 Terra 将其作为系统变量提供给笔记本。为了更加简便,我们将按以下方式从该系统变量创建 Python 变量:

In [20]  import os
         WS_BUCKET = os.environ['WORKSPACE_BUCKET']

In [21]  print(WS_BUCKET)
Out [21] ’gs://fc-46207b9c-d593-4e7a-9057-7aca3bb5c9a7’

这里,我们使用 Python 命令在 Python 层级设置变量。import os命令允许我们在 Python 代码中与操作系统交互,而os.environ['WORKSPACE_BUCKET']调用则使用这一功能访问环境变量'WORKSPACE_BUCKET'的值,这个变量最初是由 Terra 笔记本服务为您设置的。

从这里开始,您可以将工作空间存储桶称为WS_BUCKET。例如,您可以使用gsutil ls列出其内容:

In [22]  ! gsutil ls -r {WS_BUCKET}

请注意,我们用花括号括起了WS_BUCKET变量,就像我们在前一节中对 germline 数据变量所做的那样。

设置完成后,每当你想把输出保存到工作空间存储桶时,只需在sandbox目录上运行相同的gsutil cp命令:

In [23]  ! gsutil -m cp -r sandbox {WS_BUCKET}

In [24]  ! gsutil ls {WS_BUCKET}/sandbox

这将整个sandbox目录从笔记本的本地存储复制到工作空间存储桶。请记住,如果你生成了许多大文件,这种方法可能不会很好扩展;在这种情况下,你可能需要考虑将 sandbox 分成单独的子目录来管理。承认手动同步文件到存储桶并不理想;我们期待看到这些工具的体验随着技术的发展而改进。至少笔记本文件本身是自动保存的,正如我们之前讨论的那样。

在嵌入的 IGV 窗口中可视化基因组数据

现在我们的笔记本已经设置好并准备就绪,让我们试试一个酷炫的技巧:在笔记本的上下文中使用 IGV 来可视化基因组数据。在之前的章节中,我们让你使用桌面版 IGV,并设置它从 GCS 获取数据。这一次,我们要做一些不同的事情:我们将使用一个称为IGV.js的特殊 IGV 包,在笔记本中嵌入 IGV 浏览器。当你希望将数据可视化包含在学生教程中或在报告中向合作者传达结果时,这是非常方便的,这样他们就不需要使用单独的程序。

注意

IGV.js包有一些限制,我们稍后将进一步描述。简而言之,当它工作时非常酷,所以我们确实认为在 Jupyter 笔记本中展示如何使用它是值得的,但它并不总是无缝运行。如果你在使用过程中遇到任何困难,我们建议你回到使用桌面版 IGV,如前所述。

在这个练习中,我们将加载两个 BAM 文件:WGS 母样本,这已经是本书大部分时间的测试文件,以及同一人的外显子样本进行比较。你可能还记得在第二章(一辈子前),我们曾触及几种文库设计策略的区别,包括 WGS 和外显子测序。特别是,我们讨论了它们的覆盖率图的形状有很大不同:WGS 倾向于看起来像远处的山脉,而外显子样本则更像是散布在海洋中的一系列火山岛屿。这是一个让你亲自看到的机会,本质上是复制图 2-18 的交互形式,同时尝试IGV.js的集成。

设置嵌入式 IGV 浏览器

好消息是,在这里您不需要做太多事情。还记得您作为运行时环境定制的一部分运行的启动脚本吗?这包括安装运行 IGV.js 所需的所有先决条件的说明,系统在创建您的定制运行时环境时执行了这些说明。因此,我们只需执行一次导入来激活 igv Python 包。之后,只需按照以下代码单元格中显示的方式创建需要显示的任意数量的 IGV 浏览器即可。以下所有内容都是 Python 代码,遵循 IGV 团队在 GitHub 上 IGV-Jupyter 仓库 中记录的指南:

In [25]  import igv

In [26]  IGV_Explore = igv.Browser(
             {"genome": "hg19",
             "locus": "chr20:10,025,584-10,036,143"
             }
         )

In [27]  IGV_Explore.show()

我们为浏览器起的名称(这里是IGV_Explore)完全是任意的。您可以提供其他参数来初始化浏览器,但唯一必需的是基因组参考;其他所有内容都是可选的。话虽如此,通常我们会为浏览器指定一些坐标(或感兴趣的基因名称),以便立即进行放大。

当您运行该单元格时,应该会看到一个嵌入的 IGV 浏览器,其中包括参考基因组和 RefSeq 基因轨道,但没有实际的数据轨道,如 Figure 12-12 所示。

新创建的 IGV 浏览器。

图 12-12. 新创建的 IGV 浏览器。

接下来,让我们添加我们想要比较的两个样本 BAM 文件。

将数据添加到 IGV 浏览器中

对于我们想要加载的每一个轨道,我们需要提供相同的元数据:轨道的名称、文件在 GCS 中的路径、格式以及相应索引文件的路径。我们使用 load_track() 函数向 IGV 提供这些信息:

In [28]  IGV_Explore.load_track(
             {
                 "name": "Mother WGS",
                 "url": GERM_DATA + "/bams/mother.bam",
                 "indexURL": GERM_DATA + "/bams/mother.bai",
                 "format": "bam"
             })

In [29]  IGV_Explore.load_track(
             {
                 "name": "Mother Exome",
                 "url": GERM_DATA + "/bams/motherNEX.bam",
                 "indexURL": GERM_DATA + "/bams/motherNEX.bai",
                 "format": "bam"
             })

因为这都是 Python 代码,我们可以简单地通过引用其名称 GERM_DATA 来使用我们为生殖细胞数据设置的 Python 变量。

注意

请注意一下 Python 语法,它明确使用 + 运算符来连接变量和子目录字符串,以便组成指向数据文件所在位置的完整地址。这与我们之前在 gsutil 命令中使用的 shell 语法 "{GERM_DATA}/bams" 形成对比,后者是一种更为隐式的指令。如果您对 Python 不太熟悉,知道这体现了 Python 编程的一个基本原则:显式优于隐式。

当您运行两个单元格,并且它们每一个都以 OK 作为结果返回后,请向上滚动到浏览器,您应该会看到旋转符号,表示数据正在加载。当旋转器消失并显示数据时,在您的 IGV 浏览器中应该会有两个数据轨道:母体样本的全基因组测序和外显子版本,如 Figure 12-13 所示。

IGV 浏览器显示的两个序列数据轨道。

图 12-13. IGV 浏览器显示的两个序列数据轨道。

尝试缩放和拖动序列左右以查看视图,并对比这两个样本中数据分布的经典“山脉对火山岛屿”的覆盖率差异。从此开始,您可以直接通过视觉判断任何测序样本的文库设计类型。

注意

在嵌入式 IGV 浏览器中加载 VCF 和 BAM 文件而不指定索引文件是可行的技术操作。为此,请省略 "indexURL" 行,并将其替换为 "indexed": False。但请注意,这样做会导致 IGV 加载数据的时间大大延长。数据加载可能需要几分钟,并且可能会看到一个弹出窗口提示页面无响应。如果出现此情况,请关闭警报并再等待一分钟。如果加载时间远远超过此时间,您可能需要回到使用 IGV 的桌面版本。

希望您同意,这是一种不错的方式,可以在分析日志或报告中包含基因组数据的视图,尽管它存在一些限制。一个限制是在初始加载数据时可能遇到的延迟;另一个是与 IGV 桌面版本相比,不是所有的显示定制选项都可用。如果没有适当的指导,身份验证可能是一个主要障碍:如果您想访问私有存储桶中的数据(包括您的工作空间存储桶!),您需要跳过涉及访问凭据的额外步骤。您可能还记得,在第四章中,您必须在 IGV 桌面版本中启用 Google 登录选项,以便查看来自私有存储桶的文件。在这里,我们将做类似的事情,只是不是通过点点点的过程,而是通过几行代码来完成。

设置访问令牌以查看私有数据

在前面的例子中,我们从完全公开的存储桶中读取数据,因此不需要进行任何身份验证。但是,您最终可能需要查看私有存储桶中的文件。为此,您需要设置一个访问令牌,IGV 可以使用该令牌来访问您私有存储桶中的数据。

首先,让我们使用 gcloud auth 生成访问令牌并将其保存到文件中:

In [30]  ! gcloud auth print-access-token > token.txt

只要此文件仅保存在笔记本的本地存储中,它就是安全的,因为您的运行环境严格个人化,其他人无法访问,即使您与他们分享工作空间或笔记本。但不要将此文件保存到您的工作空间存储桶中!保存到存储桶将使任何与您分享工作空间的人都能看到该文件。

接下来,将令牌文件的内容读入 Python 变量中。由于令牌只包含一行文本,我们可以使用 readline() 函数,该函数将文件的第一行读入字符串中:

In [31]  token_file = open("token.txt","r") 
         token = token_file.readline()

此时,您已经将 token 变量存储并准备好在需要加载存储在私有存储桶中的文件时与 IGV 一起使用。

例如,回想一下前一节中我们让你复制mother.bam文件及其索引到你的工作空间存储桶。即使你拥有那个存储桶,运行在你笔记本中的 IGV 进程也不知道你有权访问它。你必须明确地通过在调用load_track()函数时提供刚刚设置的令牌来指示它,如下所示:

In [32]  IGV_Explore.load_track(
             {
                "name": "Workspace bucket copy of Mother WGS",
                "url": WS_BUCKET + "/sandbox/mother.bam",
                "indexURL": WS_BUCKET + "/sandbox/mother.bai",
                "format": "bam",
                "oauthToken": token
             })

如你所见,我们复制了之前用于加载 BAM 文件的相同代码,但这次我们提供了工作空间存储桶中文件的路径,并添加了先前生成的令牌。如果你好奇不提供令牌会发生什么,请随意尝试删除该行(以及前一行末尾的逗号)。请注意,也可以访问由 Terra 之外的管理私有存储桶中的数据。正如我们将在第十三章中看到的那样,这需要将存储桶访问权限授予您的代理组服务帐户。

如果你有多个私有文件要加载到 IGV 浏览器中,你需要在每个轨迹定义中包含令牌。IGV 文档指出可以设置全局 IGV 配置变量,igv.setGoogleOauth​Token​(accessToken),适用于所有轨迹,但截至撰写本文时,在我们的笔记本中这并不起作用。

运行 GATK 命令以学习、测试或排除故障

虽然我们可以在笔记本中从可视化测序数据,但是我们已经能够通过桌面版的 IGV 实现了相当的结果。笔记本概念真正酷的地方在于,我们可以运行分析命令,然后在同一环境中可视化输出结果。

你可能会问,你可以运行什么样的分析呢?好吧,几乎任何你想运行的东西。正如我们在让你运行gsutil命令时所看到的,你不仅限于在 Python 笔记本中运行 Python 代码。你几乎可以运行任何你可以安装并在 shell 环境中运行的东西。

注意

在这里,我们依赖于你在本章练习开始时用来初始化笔记本运行环境的启动脚本。该脚本包括下载 GATK 包并使其可用于命令行调用的说明,这些说明在为您创建环境时已执行,因此您无需自行操作。

在本节中,我们将向您展示如何在笔记本中运行 GATK 命令并在 IGV 中可视化结果。我们发现,与我们在较早的章节中采用的“分屏”方法相比,这提供了一种更加集成和无缝的体验,之前我们在虚拟机中运行 GATK 命令,然后在桌面版 IGV 中可视化结果。我们让您经历了这一切,因为我们是虐待狂,而且这也为您提供了建立基础技能的机会。通过这个过程,您对云计算的基础组件有了一定的熟悉度,这应该有助于您在运行工作流程或在笔记本中工作时理解幕后发生的事情。也许通过这个经历,您对基于笔记本的方法有了更深的理解,即使只是用于教学、测试和故障排除等目的。

为此,我们将重新访问您在第五章中之前完成的练习,这样您可以将注意力集中在如何进行工作,而不是分析的含义是什么。

运行基本的 GATK 命令:HaplotypeCaller

让我们从在整本书中都在使用的相同样本上运行HaplotypeCaller工具开始。您应该能够认出这个命令,我们几乎是直接从第五章中复制的:

In [33]  ! gatk HaplotypeCaller \
             -R {GERM_DATA}/ref/ref.fasta \
             -I {GERM_DATA}/bams/mother.bam \
             -O sandbox/mother_variants.200k.vcf.gz \
             -L 20:10,000,000-10,200,000

与我们在第五章中运行它的方式相比,这个命令有什么不同?现在您应该能够认出!在 GATK 命令之前的作用是绕过 Python 解释器并将其作为 shell 命令运行的信号。由于我们使用的是大括号而不是 $,因此对文件路径变量的引用也略有不同,正如前面提到的。我们还将输出的 VCF 文件以压缩的gzip形式写入,这是下一步中 IGV 的要求。

另一个区别是我们使用 Python 变量来存储输入文件路径的公共部分,gs://genomics-in-the-cloud/v1/data/germline。这一次,我们使用 GCS 中文件的路径而不是指向本地副本。我们之所以能够这样做,是因为正如我们之前多次提到的,GATK 工具能够直接从 GCS 流式传输大多数类型的文件输入。在实践中,每当 GATK 命令行解析器识别到符合条件的输入文件路径以 gs:// 开头时,就会发生这种行为。这很棒,因为它允许我们避免将相关文件本地化到笔记本的本地存储中。顺便说一句,这也适用于直接将输出文件写入 GCS,尽管我们在这里没有演示。

在 第五章 到 第七章 中,我们要求您定位完整的数据包并使用本地文件输入运行所有 GATK 命令。我们本可以让您使用存储桶路径运行大多数命令,并依赖 GATK 的数据流功能;在您的虚拟机上完全可以正常工作。但是,我们觉得在这么早就介绍这些方面会过于复杂化云端的第一次体验。相反,我们选择让您使用本地化文件,希望随后的体验能够提供足够的熟悉感让您感觉更舒适。我们现在提出这个问题,以防您选择回到虚拟机环境工作,这样您就知道您仍然可以利用流式传输功能。而且,正如您可能还记得在 第十章 中对优化讨论中所提到的那样,在 WDL 工作流的背景下也是适用的。

当您运行该命令时,您应该看到日志输出被写入单元格下的笔记本。这在将分析的所有信息保持在一个地方方面非常好,这是 Jupyter 概念的主要优势之一。不过,如果您运行的工具输出特别冗长(如 GATK 有时可能会出现的情况),您可能会在笔记本的中间得到几页几页的日志。这时,在 Markdown 单元格中使用清晰的节标题来划分分析的不同部分特别有帮助,尤其是结合自动生成目录和侧边导航菜单的笔记本小部件时。

HaplotypeCaller 运行完成后,让我们列出 sandbox 内容以确认命令是否正常工作,并且期望创建变异调用的 VCF:

In [34]  ! ls sandbox/

是的,这就是它,还有它的索引文件。让我们在 IGV 中查看它。

加载数据(BAM 和 VCF)到 IGV

假设我们想要在笔记本中用 IGV 打开输出的 VCF,主要是为了进行视觉检查并将其与 BAM 文件进行比较。我们可以使用之前创建的 IGV 浏览器查看不同的 BAM 文件,但由于这是一个具有不同目的的单独练习——而且我们懒得向上滚动几页——我们将创建一个新的浏览器。

第一部分代码与之前使用的基本相同,只是我们为浏览器对象使用了不同的名称:

In [35]  IGV_InspectCalls = igv.Browser(
             {"genome": "hg19",
             "locus": "chr20:10,002,294-10,002,623"
             }
          )

          IGV_InspectCalls.show()

这将在单元格下创建一个新的浏览器,放大感兴趣的区间,但没有任何数据。因此,让我们从笔记本本地存储空间的 sandbox 目录中加载用 HaplotypeCaller 命令生成的 VCF 文件中的变异数据:

In [36]  IGV_InspectCalls.load_track(
             {
                "name": "Mother variants",
                "url": "files/sandbox/mother_variants.200k.vcf.gz",
                "indexURL": "files/sandbox/mother_variants.200k.vcf.gz.tbi",
                "format": "vcf"
             })

这是我们之前用来加载 BAM 文件的相同代码,只是这次我们将跟踪的format属性更改为vcf而不是bam,文件路径(urlindexURL)指向本地文件而不是指向 GCS 中的位置。

注意

注意那些文件路径:你应该注意到它们并不完全是基于笔记本本地存储空间目录结构所期望的文件路径。你看到了吗?files/部分并不指向一个真实的目录!这是我们为了 IGV 的利益而添加的前缀,正如IGV-Jupyter 项目文档中所述。

或者,你可以运行gsutil cp命令将沙盒复制到工作空间存储桶中,然后使用工作空间存储桶副本的路径加载 VCF 跟踪。但是,如果你这样做,请不要忘记包括如前一节所述的访问令牌。

最后,让我们加载原始配子数据包中的 BAM 文件及其索引。这些文件位于公共存储桶中,因此不需要指定访问令牌(但如果包括也没有什么坏处):

In [37]  IGV_InspectCalls.load_track(
             {
                "name": "Mother WGS",
                "url": GERM_DATA + "/bams/mother.bam",
                "indexURL": GERM_DATA + "/bams/mother.bai",
                "format": "bam"
             })

结果视图显示,与您在第五章中生成的内容基本相同,只是桌面版本与IGV.js版本的视觉渲染之间有一些外观上的差异。

IGV.js 渲染的测序数据(“Mother WGS”跟踪)和由 HaplotypeCaller 生成的输出变异(“Mother variants”跟踪)

图 12-14. IGV.js 渲染的测序数据(“Mother WGS”跟踪)和由 HaplotypeCaller 生成的输出变异(“Mother variants”跟踪)。

点击查看者中的数据元素(例如,阅读或变异)以获取更多详细信息,就像我们在第五章中所做的那样。视觉显示略有不同,但基本功能相同,只是不能切换为“悬停显示”详细信息。

这里不明显的一个区别是嵌入的 IGV 窗口在组织跟踪时与桌面版 IGV 的方式有些不同。在桌面版中,变异跟踪始终自动显示在序列数据跟踪之上,而不管它们加载的顺序如何。你可以先加载一个 BAM 文件,然后加载一个 VCF 文件,但变异跟踪始终会显示在顶部。相反,在嵌入的 IGV 窗口中,跟踪显示的顺序与它们添加的顺序一致。因此,如果您首先加载 BAM 文件,那么它将显示在顶部,即使随后加载 VCF 文件也是如此。

希望这能让您了解如何在笔记本中使用嵌入的 IGV。让我们从原始的第五章课程中再进行一个练习,以练习使用这个工具并涵盖一些更小的选项。

在内嵌的 IGV 浏览器中排除问题的可疑变异调用

您可能还记得,在第五章中,我们更仔细地查看了这一区域的变异轨道中出现的三个 T 碱基的同型变异插入。乍一看,我们对HaplotypeCaller的决定持怀疑态度,因为这个调用似乎并未得到测序数据的支持。您还记得我们进行调查的第一步是什么吗?没错,我们打开了软剪接的显示,这些被映射器标记为“不可用”的序列数据片段通常默认情况下是隐藏的。现在让我们在笔记本中的 IGV 窗口中执行此操作。

正如您在 Figure 12-15 中所看到的,您可以通过单击感兴趣的轨道右侧的齿轮图标来显示轨道查看选项。现在为 Mother WGS 序列数据轨道执行此操作,并选择“显示软剪接”;然后,在菜单的右上角,单击 X 关闭它。

“Mother WGS”序列数据轨道的显示选项菜单。

Figure 12-15. “Mother WGS”序列数据轨道的显示选项菜单。

您应该看到整个区域因大量不匹配而明亮地闪烁,如 Figure 12-16 所示。

显示软剪接。

Figure 12-16. 显示软剪接。

您现在知道该做什么了,对吧?可疑的插入缺失调用,大量软剪接…是的,是时候生成一个 bamout 来看看HaplotypeCaller在进行这个调用时是怎么想的了:

In [38]  ! gatk HaplotypeCaller \
             -R {GERM_DATA}/ref/ref.fasta \
             -I {GERM_DATA}/bams/mother.bam \
             -O sandbox/motherHCdebug.vcf.gz \
             -bamout sandbox/motherHCdebug.bam \
             -L 20:10,002,000-10,003,000

这应该运行得非常快,并产生我们感兴趣的关键输出,即显示HaplotypeCaller如何重新排列读取数据的 BAM 文件,正如在第五章中所解释的。让我们将该文件添加到我们的 IGV 浏览器中:

In [39]  IGV_InspectCalls.load_track(
             {
                "name": "Mother HC bamout",
                "url": "files/sandbox/motherHCdebug.bam",
                "indexURL": "files/sandbox/motherHCdebug.bai",
                "height": 500,
                "format": "bam"
             })

同样,这应该产生一个视图,虽然不完全相同,但与我们在第五章中遇到的相似。与之前一样,我们可以得出结论,考虑到重新排列的数据,HaplotypeCaller对插入缺失的调用是合理的。

顺便说一句,您可能注意到,在这个调用中,我们指定了轨道的高度为"height": 500。例如,当我们试图以减少滚动的方式展示数据的特定视图时,这可能非常有用。请随意尝试设置不同轨道的高度。

您认为这种运行和检查 GATK 命令的方法如何?我们可以通过这种方式继续在这本书中镜像所有我们在第五章到第七章中涵盖的材料,并且事实上,在公共 Terra 工作区中有几个这样的 GATK 教程笔记本,GATK 团队在其流行的国际研讨会系列中使用。我们鼓励您进一步研究这些内容。

然而,出于本书和特别是本章的目的,我们希望专注于涵盖 Jupyter 笔记本与基因组数据交互的最有用的方面。我们还有一些要向您展示的内容,所以我们需要继续前进。

下一个逻辑步骤是绘制变异数据。有许多变异数据的方面可能需要视觉探索,但我们无法覆盖所有——实际上,我们只能真正涵盖其中一个。因此,让我们来解决可视化变异上下文注释值分布的主题,这对于理解变异过滤方法很有帮助,正如我们在第五章中讨论的那样。

可视化变异上下文注释数据

您可能还记得,在第五章中,我们描述了使用从 GiaB 真实数据集中派生的注释(callsets)来理解变异上下文注释分布如何帮助我们理解变异调用质量的方法。我们使用了一种视觉方法来进行评估,其中涉及以几种方式(密度图和散点图)绘制变异上下文注释值。如果这并没有让您想起什么,或者如果您对细节感到困惑,请花几分钟时间再次阅读该部分,以刷新您的记忆。当时,我们专注于概念并仅以一般术语概述了过程,因此在这里,我们将利用这个机会向您展示如何应用关键步骤来复现图 5-8 到图 5-11 中显示的图表。

使用 VariantsToTable 导出感兴趣的注释

我们从以前用 GiaB 真实数据集为 Mother WGS 样本调用的 SNP 的 VCF 文件开始,已经对其进行了注释。要查看如何执行完整过程的教程,包括子集和注释步骤,请参阅这个GATK 教程工作空间

直接操作 VCF 文件格式相当痛苦,因此在这个练习中,我们将简化操作,将我们关心的信息从 VCF 文件导出到一个制表符分隔的表格中,以便在 R 中更容易解析。为此,我们在注释过的输入 VCF 文件上运行 GATK 工具VariantsToTable,并提供我们感兴趣的注释列表。我们使用-F参数用于 INFO(站点级)注释和-GF用于 FORMAT(样本级)注释,其中F代表字段,GF代表基因型字段:

In [40]  ! gatk VariantsToTable \
		 	-V {GERM_DATA}/vcfs/motherSNP.giab.vcf.gz \
		 	-F CHROM -F POS -F QUAL \
		 	-F BaseQRankSum -F MQRankSum -F ReadPosRankSum \
		 	-F DP -F FS -F MQ -F QD -F SOR \
		 	-F giab.callsets \
		 	-GF GQ \
		 	-O sandbox/motherSNP.giab.txt

VariantsToTable命令应该非常快速地生成输出文件motherSNP.giab.txt。这是一个纯文本文件,所以我们可以使用cat查看它的片段:

In [41]  ! cat sandbox/motherSNP.giab.txt | head -n300

正如您所看到的,该工具生成了一个表格,其中每一行表示来自 VCF 的变异记录,每一列表示我们在导出命令中指定的注释。无论请求的注释是否存在(例如,同源位点没有RankSum注释,因为该注释仅对杂合位点计算),值都被替换为NA。有了这个纯文本表格,我们可以轻松地将完整的变异调用集及其注释值加载到 R DataFrame 中。

要将表格内容加载到 R DataFrame 中,我们调用readr库并使用其read_delim函数将motherSNP.giab.txt表格加载到motherSNP.giab DataFrame 对象中。请注意,R 命令前面有%%R符号,正如我们之前学到的,它指示笔记本内核应该解释本单元格中的所有代码为 R 代码:

In [42]  %%R
         library(readr)
         motherSNP.giab <- read_delim("sandbox/motherSNP.giab.txt","\t", 
                       escape_double = FALSE, 
                       col_types = cols(giab.callsets = col_character()), 
                       trim_ws = TRUE)

当 DataFrame 准备好时,您可以使用您喜欢的 R 函数来操纵它。正如情况所发生的那样,我们已经为您准备了一些方便的绘图函数。

加载 R 脚本以使绘图函数可用

我们将利用由 GATK 支持团队提供的现有 R 脚本。该脚本可在书籍存储库和存储桶中找到,定义了三个绘制函数,利用名为ggplot2的出色 R 库来可视化变异注释值的分布。

要在笔记本中使这些函数可用,我们可以简单地将 R 脚本内容复制到代码单元格中并运行它。但是,由于这是一个我们可能想要在多个笔记本中运行的脚本,并且我们不想维护单独的副本,让我们使用更智能的方法来导入代码。您将把 R 脚本复制到笔记本的本地存储中,然后使用 R 中的source()函数将 R 脚本代码加载到笔记本中:

In [43]  ! gsutil cp gs://genomics-in-the-cloud/v1/scripts/plotting.R .
         %R source("plotting.R") 

这将输出大约一页的日志,这里我们不展示。日志输出显示为红色背景,有点令人担忧,但是除非下一步失败,否则不要担心。如果遇到问题,请检查您的输出是否与笔记本的预运行副本中显示的内容不同(该副本还包含自助练习的解决方案)。如果一切正常,您现在将安装并加载了几个新的 R 包,并且绘图函数将可用。

让我们试试吧,首先是密度图。

制作 QUAL 的密度图使用 makeDensityPlot

makeDensityPlot 函数接受一个 DataFrame 和一个感兴趣的注释,生成一个密度图,这基本上是直方图的平滑版本,表示该注释的值的分布。这是我们如何使用它来复现 第 5-8 和 第 5-9 图的方法。在以下每个单元格中,第一行创建图表,然后调用其名称显示它:

In [44]  %%R
         QUAL_density = makeDensityPlot(motherSNP.giab, "QUAL")
         QUAL_density

图 12-17 中显示的 QUAL 分布在右侧有一个非常长的尾部,因此让我们通过使用可选的 xmax 参数将 x 轴限制在合理的最大值上来进行放大,结果呈现在 图 12-18 中:

In [45]  %%R
         QUAL_density_zoom = makeDensityPlot(motherSNP.giab, "QUAL", xmax=10000)
         QUAL_density_zoom

QUAL 分布。

图 12-17. QUAL 分布。

QUAL 密度图。

图 12-18. QUAL 密度图。

我们还可以指定一个注释来将数据组织成子集,并让函数为每个数据子集生成一个单独的密度曲线。在这里,我们使用了 giab.callsets 注释,它指的是在 GiaB 真值集中叫做同一变异的呼叫集数量。数字越高,我们对变异调用的信任度就越高:

In [46]  %%R
         QUAL_density_split = makeDensityPlot(motherSNP.giab, "QUAL", xmax=10000,
         split="giab.callsets")
         QUAL_density_split

图 12-19 显示了结果。

GiaB 从呼叫集的 QUAL 密度图。

图 12-19. GiaB 从呼叫集的 QUAL 密度图。

在你完成这个工作之后,尝试为其他注释生成相同类型的图。例如,我们使用类似的命令生成了 第 5-10 图,用于 QualByDepth (QD) 注释。

现在让我们尝试制作散点图。每个人都喜欢一个好的散点图,对吧?

制作 QUAL 对 DP 的散点图

makeScatterPlot 函数接受一个 DataFrame 和两个感兴趣的注释,生成两个注释的 2D 散点图,其中每个数据点是一个单独的变异调用。这是我们如何使用它来复现 第 5-8 图,同时 第 12-20 图显示结果的方法:

In [47]  %%R
         QUAL_DP_scatterplot = makeScatterPlot(motherSNP.giab, "QUAL", "DP")
         QUAL_DP_scatterplot

散点图 QUAL 对 DP。

图 12-20. QUAL 对 DP 的散点图。

这个函数接受与 makeDensityPlot 相同的 xmax 参数,用于限制 x 轴上的值范围,并且还有一个新的 ymax 参数来限制 y 轴上的值。随时尝试使用这些参数来放大数据的子集。

你还可以使用相同的 split 参数将数据分割成子集,根据它们所属的子集对数据点进行着色。现在根据你在之前练习中学到的内容尝试这样做,然后尝试应用相同的原则来绘制其他注释。

最后,在我们的最后一个绘图练习中,我们将同时结合散点图和密度图绘制。

绘制由边缘密度图包围的散点图

makeScatterPlotWithMarginalDensity函数接受一个 DataFrame 和两个注释,结合其他两个函数生成横向和纵向由注释对应的密度图包围的散点图。以下是我们如何使用它来复制第五章中的图 5-11,结果显示在图 12-21 中:

In [48]  %%R
         QUAL_DP_comboplot = makeScatterPlotWithMarginalDensity(motherSNP.giab,
         "QUAL",
         "DP", split="giab.callsets", xmax=10000, ymax=100, ptSize=0.5, 
         ptAlpha=0.05)
         QUAL_DP_comboplot

一张散点图和密度图。

图 12-21. 一张散点图和密度图。

与以往一样,我们将giab.callsets指定为要根据信任程度将变体数据点分组的变量。我们还设置了可选参数(xmaxymax)以限制轴以显示数值的子集,并调整数据的显示以优化可读性(ptSizeptAlpha)。

试着将其应用于其他注释对。请注意,某些注释可能具有负值,因此要注意,绘图函数还接受xminymin参数以限制显示负值的范围。

要清楚的是,在笔记本中操作和绘制变体数据的方法有很多种。实际上,这种特定的方法在处理较大数据集时不会很有效,因为它涉及直接将可能非常庞大的表格读入内存。我们之所以选择它作为本教程的一部分,是因为它对新手来说很容易理解,并且我们的主要目标是让您对可能性有所了解,并熟悉涉及的基本机制。但是,对于大规模工作,您可能会希望使用更健壮的方法。我们建议查看Hail,这是一个基于 Python 的、专注于遗传学的工具包,具有极好的可扩展性,并包括一套变体质量控制功能等其他功能。像一些较新的 GATK 工具一样,Hail 能够使用 Spark 并行化分析,并已用于进行像英国生物银行这样的大数据集的全基因组分析研究(GWAS)。Terra 库中有一些功能 Hail 的工作区,包括一些教程笔记本和一个完整的 GWAS 示例

总结与下一步

在本章中,你学会了如何在 Terra 中使用 Jupyter 与你的数据交互。你首先学习了在云端使用笔记本的基本机制,设置你的计算环境,打开示例笔记本并运行代码单元。有了这些基础,你进行了三种交互式分析:在嵌入的 IGV 浏览器中可视化基因组数据,运行和排除 GATK 命令以及在 R 中绘制变异上下文注释数据。

这绝不是在这个环境中你可以做的所有事情的详尽目录;如果说有什么,我们只是勉强触及了可能性的表面。然而,你现在已经掌握了足够的技术和工具的基础,可以开始将自己的分析适应于 Terra 框架中运行。在第十三章,我们向你展示了如何从组成元素(数据、工具和来自不同来源的代码)组装你自己的工作空间。

^(1) 从这一点开始,我们不再展示单元格的输出。你可以将输出与 Terra 工作空间中包含的笔记本副本进行比对。此外,我们还在书的GitHub 存储库提供了预运行笔记本的 html 版本。

第十三章:在 Terra 中组装你自己的工作空间

在第十一章和第十二章中,你学习了如何在 Terra 中使用预先准备好的工作空间来运行工作流和交互式笔记本。现在,是时候学习如何自己创建工作空间,以便在 Terra 框架内构建自己的分析了。这个领域提供了很多选项和多种有效的方法,所以我们不打算提供一个适用于所有情况的路径,而是将会介绍三种场景。

在第一个场景中,我们重新创建了书籍教程工作空间,从基础组件开始演示如何从头开始组装工作空间的关键机制。在第二和第三个场景中,我们将向你展示如何利用现有的工作空间来最小化在启动新项目时需要做的工作量。在一个情况下,我们解释了如何向已经为特定分析设置好的现有工作空间添加数据,例如官方 GATK 最佳实践工作空间。在另一个情况下,我们演示了如何围绕从 Terra 数据库导出的数据构建分析。然而,在我们深入探讨这三种场景之前,让我们先探讨一下我们在所有三种情况下都应用的数据管理策略。

在工作空间内外管理数据

将工作迁移到云端最重要的一个方面之一是设计一种可持续长期使用的数据管理策略,特别是如果你希望处理作为多个项目输入的大型数据集。这是一个足够复杂的主题,以至于完整讨论超出了本书的范围,整本书都可以单独讨论这个主题。然而,花一些时间讨论一下在 Terra 环境中特别适用的几个关键考虑因素是值得的,这些因素会影响我们如何决定数据存放的位置。

工作空间存储桶作为数据存储库

正如你在第十一章中学到的,每个 Terra 工作空间都创建有专用的 GCS 存储桶。你可以在工作空间存储任何你想要的数据,以及你在该工作空间中运行的所有笔记本、工作流的日志和输出。然而,并没有义务将输入数据存储在工作空间的存储桶中。只要你能提供文件的相关指针并且能够授权给你的 Terra 帐户访问这些数据(稍后会详细讨论这一点),你可以计算存储在 GCS 任何位置的数据。

有几个重要的原因需要注意。首先,如果您打算将同一数据用作多个项目的输入,您不希望必须在每个工作空间中维护数据的副本,因为每个存储桶都会产生存储费用。相反,您可以将数据放在一个位置,并从需要使用它的任何地方指向该位置。其次,请注意,工作空间存储桶随着您的工作空间而生存和消亡:如果删除工作空间,则存储桶及其内容也将被删除。与此相关的是,当您克隆一个工作空间时,克隆仅从其父项继承笔记本目录的存储桶内容。克隆将继承父项的数据表的副本,并保留到父项存储桶中数据的链接,但不包括文件本身。这被称为对工作空间内容进行浅复制。如果随后删除父工作空间,则克隆的数据表中的链接将断开,您将无法再对受影响的数据进行分析。

由于这样做,我们通常建议将数据集存储在专门的工作空间中,与分析工作空间分开,并且更严格的权限限制了可以修改或删除它们的人数。或者,您也可以将数据存储在您在 Terra 之外管理的存储桶中,就像我们为本书提供的示例数据所做的那样。将数据存储在 Terra 之外的优势在于,您(或者拥有存储桶计费账户的人)完全控制它。这样可以自由设置像每个文件的详细权限或者完全公开内容这样的事情,而这些在 Terra 工作空间桶目前出于安全原因是不可能的。此外,您还可以选择有意义的名称来命名您自己创建的存储桶,而不是 Terra 分配的长而抽象的名称,这些名称对人类来说不够友好。不过,如果您决定采用这种方式,您将需要启用 Terra 访问您在 Terra 之外自行管理的任何私有数据,如下一节所述。

访问您在 Terra 之外管理的私有数据

到目前为止,我们已经使用的数据要么位于公共存储桶中,要么位于由 Terra 管理的工作空间自己的存储桶中。然而,您最终可能需要访问位于由 Terra 不管理的私有存储桶中的数据。在那时,您将遇到一个意外的复杂性:即使您拥有该私有存储桶,您也需要经过额外的身份验证步骤,涉及到 GCP 控制台和代理组帐户。以下是您需要了解和执行以克服此障碍的步骤。

每当您通过 Terra 服务发出指令以执行 GCP 中的操作时,Terra 系统实际上并不使用您的个人账户。相反,它使用 Terra 为您创建的服务账户。事实上,您可以通过 Terra 为您有权访问的每个计费项目获得多个服务账户。所有这些服务账户都汇总到一个代理组中,Terra 使用它来管理您对各种资源的凭据。这在安全性和便利性方面都有益。

大多数情况下,您不需要了解这些内容,因为您在 Terra 内创建或管理的任何资源(如工作区及其存储桶或笔记本电脑)都会自动与您的代理组账户共享。此外,每当有人在 Terra 中与您共享资源时,相同的情况也会发生:您的代理组账户会自动包含在其中,因此您可以无缝地使用这些资源。然而,当您开始连接不受 Terra 管理的资源时,控制访问权限的 Google 系统不会自动知道您的代理组账户被允许作为您的代理。因此,您需要在 Google 权限系统中标识您的代理组账户,并指定它应该被允许执行的操作。

幸运的是,如果您知道该怎么做,这并不太困难,我们即将为您介绍最常见情况的过程:访问一个不受 Terra 控制的 GCS 存储桶。对于其他资源,该过程基本相同。

首先,您需要查找您的代理组账户标识符。有几种方法可以做到这一点。最简单的方法是在您的Terra 用户资料中查找,如图 13-1 所示。

用户资料中显示的代理组标识符。

图 13-1. 用户资料中显示的代理组标识符。

有了这个信息,您可以前往 GCS 控制台并查找您的外部存储桶;也就是说,您最初为第四章中的练习创建的存储桶。转到存储桶详情页面,并找到权限面板,其中列出了所有被授权以某种方式访问该存储桶的账户,如图 13-2 所示。

显示具有访问权限的存储桶的权限面板。

图 13-2. 显示具有访问权限的存储桶的权限面板。

点击“添加成员”按钮打开相关页面,并在“新成员”字段中添加您的宠物服务帐户标识符。在“选择角色”下拉菜单中,如有需要,请滚动选择左列中的存储服务,然后进一步选择右列中的存储对象管理员角色,如图 13-3 所示。点击保存以确认。

向新成员授予对存储桶的访问权限。

图 13-3. 向新成员授予对存储桶的访问权限。

在完成这些操作之后,您将能够从笔记本和工作流中访问外部 GCP 资源,例如不受 Terra 管理的存储桶。对于您没有管理权限的任何存储桶,您需要请求管理员代表您执行此过程。

访问 Terra 数据库中的数据

正如您可能从第一章的上下文设置讨论中回想起的那样,我们最初介绍的平台 Terra 包括一个数据库,通过该数据库可以连接到由各种组织在 GCP 上托管的数据集。其中一些数据集仅在 Terra 工作空间中提供,例如您将在本章第二和第三个场景中使用的 1000 Genomes 高覆盖数据集。您将有机会尝试几种利用这种类型数据存储库的方式。

其他数据集托管在独立的存储库中,您可以通过称为数据浏览器的专用界面访问这些存储库。数据浏览器使您能够基于元数据属性选择数据子集,然后将其导出到常规工作空间。我们不会详细介绍这些内容,但建议您探索该库,并尝试从ENCODENeMo存储库检索数据。这两个存储库都是完全公开的,并且具有非常不同的界面,因此它们非常适合进行导航练习。

Terra 数据库中的大多数数据集仅限于授权研究人员访问,因为它们包含受保护的健康信息,并且访问模式取决于项目主机组织。例如,像TCGA这样的数据集通过 dbGAP 凭据进行介入,如果您已经获得授权,您可以在 Terra 用户配置文件中链接这些凭据以自动访问。其他数据集则是在 Terra 之外进行管理,并需要与项目维护者进行交互。如果您对库中的任何数据集感兴趣,点击转到其项目页面,通常可以找到访问要求的描述。

鉴于当前库中包含的数据存储库的异质性,要有效地使用这些资源还不是件轻松的事,特别是如果您打算交叉分析多个数据集的话——这是不幸的,因为这是迁移到云的一个关键吸引点之一。这是一个正在持续发展的领域,许多组织正在积极合作,以提高这些资源的互操作性和可用性水平。我们已经看到早期采用者成功地利用这些资源进行研究,并且我们对即将到来的改进持乐观态度,这将使更广泛的调查者更容易做到这一点。

然而,现在让我们把我们的抱负降低几个档次,专注于确保基础设施到位。回到工作!

从基础组件重新创建教程工作空间

在本章中,我们最紧迫的目标是为您提供知识和技能,让您能够自己组装一个完整而基础的工作空间。我们选择通过让您重新创建第十一章和第十二章的教程工作空间来实现这一点,因为您在之前的章节中已经广泛使用它,并且它以相当简单的形式包含了完整工作空间的所有基本元素。在引导您完成这个过程时,我们将专注于用于引入各种组件(数据、代码等)的最常用机制,并且为了避免让您感到不知所措,我们有意选择不讨论平台上的每一个可用选项。

你准备好开始了吗?因为在本节课程中我们将让你多次检查模型工作空间,我们建议您打开两个单独的浏览器窗口(或标签页):一个用于模型工作空间,另一个用于您即将创建的工作空间。

创建一个新的工作空间

在您的第二个浏览器标签页或窗口中,导航至列出您可以访问的工作空间的页面,可以通过折叠导航菜单中选择您的工作空间,或从 Terra 登录页面选择查看工作空间。您应该在页面左上角的工作空间标题旁看到一个加号标志。现在点击它以创建一个新的空白工作空间。

注意

默认情况下,此页面仅列出您自己创建的或共享给您的“我的工作空间”标签下的私人工作空间,不包括对所有人公开的工作空间。您可以通过选择其他工作空间类别标签之一来查看公共工作空间。

在弹出的工作区创建对话框中,为你的新工作区命名并选择一个计费项目,就像在之前章节克隆工作区时所做的那样。因为这是一个全新的工作区,名称和描述字段最初将是空白的。你的工作区名称必须在计费项目内是唯一的,但除此之外,在命名上有很大的自由度,包括在名称中使用空格,正如你在图 13-4 中所看到的示例。

“创建新工作区”对话框。

图 13-4. 创建新工作区对话框。

描述字段将显示在你的新工作区的仪表板中。为了将来参考,输入一些有用的信息是个好习惯,即使是可选的。你以后还有机会编辑它,所以不要为细节烦恼。

授权域字段允许你限制对工作区及其所有内容的访问权限,仅限于你单独定义的特定用户组。这是一个用于保护私人信息的有用功能,但我们不打算在这里演示其用法,所以请将此字段留空。如果你想了解更多关于此功能的信息,请参阅Terra 用户指南中的文章

点击创建按钮;然后你将被引导到全新工作区的仪表板页面。随意点击各个选项卡查看内容,但你很快会注意到,除了你提供的描述(如果提供了)和指向 Terra 为你的工作区创建的专用 GCS 存储桶的链接之外,那里没有什么可看的。你可能还会注意到小部件显示工作区每月估计成本,这对应于存储存储桶内容的成本。该成本估算不包括你可能在工作区中执行的分析产生的费用。现在,成本估算为零,因为里面什么都没有。耶?

并不是我们希望你花钱,但这个空白的工作区需要一些内容,所以让我们想办法如何加载它。因为这应该是从第十一章教程的重新创建,我们将按照相同的操作顺序进行。因此,我们的第一站将是工作流面板,设置HaplotypeCaller工作流。

将工作流添加到方法存储库并将其导入工作区

正如您可能记得的那样,在第十一章中使用的工作流程与您在之前章节中使用的HaplotypeCaller工作流程相同。出于教程目的,我们已经将工作流程存储在 Terra 的内部方法库中,因此您实际上可以直接查找并导入它到您的工作空间。然而,我们希望您能够导入您自己的私有工作流程,因此我们将让您存储自己的HaplotypeCaller WDL 的副本,并将其导入您的工作空间中。

在空白工作区中,导航到工作流面板,点击标有“查找工作流”的大框。这将打开一个对话框,列出一些示例工作流以及两个工作流程库:Dockstore 和 Broad Methods Repository。点击后者以访问方法库。

您可能需要重新登录您的 Google 账户并接受一组条款和条件。找到“创建新方法”按钮(可能被重命名为“创建新工作流”),以打开工作流创建页面。按照图 13-5 中显示的信息提供信息,替换您自己的命名空间,它可以是您的用户名或另一个对您来说可能是唯一的标识符。您可以从书中的GitHub 仓库获取原始 WDL 文件;打开文本文件并将内容复制到 WDL 文本框中,或使用“从文件加载”链接将其上传到仓库。默认情况下,可选的文档字段将使用 WDL 文件顶部的注释文本块填充。简介框允许您添加工作流的一行摘要,而快照注释框则旨在总结上传相同工作流的新版本时发生的更改。您可以将后者留空。

广泛方法库中的创建新方法页面。

图 13-5. 广泛方法库中的创建新方法页面。

当您点击上传按钮时,系统将使用Womtool在后台验证您的 WDL 语法。假设一切正常,它将在您提供的命名空间下创建一个新的工作流条目,并向您显示摘要页面,如图 13-6 所示。

新创建工作流程的摘要页面。

图 13-6. 新创建工作流程的摘要页面。

请稍等片刻,点击“权限”按钮,打开一个对话框,允许你与特定人分享你的工作流,或者完全公开它。随意选择你喜欢的任何一种方式;只需记住,如果你将你的工作空间与他人分享,只有在你这样做的情况下,他们才能查看和运行你的工作流。话虽如此,如果有需要,你随后可以回到这个页面来执行这些操作。我们现在提到这一点是因为我们经常看到人们在他们的合作者忘记分享工作流和工作空间后遇到错误。

顺便说一句,工作流摘要页面也是你在方法库中找到另一个工作流时会看到的内容,无论是通过搜索还是通过合作者分享的链接。因此,以下说明将适用于无论你是不是第一次创建工作流。

使用 JSON 文件快速创建配置

当你完成工作流程的分享(或不分享;我们不评判),请点击“导出到工作空间”按钮。如果弹出对话框提示你选择方法配置,请点击“使用空白配置”按钮。在下一个对话框中,提示你选择目标工作空间时,在下拉菜单中选择你的空白工作空间,然后点击“导出到工作空间”。最后,可能会出现最后一个对话框,询问你是否要“立即转到编辑页面”。点击“是”,我们发誓这是最后一个对话框了。你现在应该回到 Terra 的工作流页面,面前是你全新的工作流配置页面。

配置工作流程将会非常简单。首先,因为我们遵循第 11 章的流程(在 ch11.xhtml#running_many_workflows_conveniently_in),请确保选择“使用文件路径定义的输入运行工作流”选项(位于工作流文档摘要下方),这允许你使用文件路径设置工作流配置,而不是使用数据表。然后,转到输入页面,这个页面应该会显示一些小橙色感叹号,表示有一些输入是缺失的。事实上,所有输入都缺失,因为你使用了空白配置,而工作流本身并未指定任何默认值。

注意

如果你有一个格式错误或者变量类型错误的输入(比如一个数字而不是文件),橙色感叹号也会显示出来。尝试将鼠标悬停在其中一个上面以查看错误消息;在 Terra 中,这些符号通常在你悬停时显示更多信息,有时会解决你的问题。

因此,您需要在输入页面上插入适当的文件路径和参数值。您可以在原始工作区中单独查找每个输入值并手动输入,但还有一种不那么繁琐的方法。你能猜到是什么吗?没错,让 JSON inputs 文件来完成所有工作。呃,我们指的是 JSON 输入文件,当然。(是的,这是个差劲的笑话,但如果它能帮助您记住您可以上传一个 JSON 文件作为输入,那么我们现在感到的羞愧也是值得的。)

因为我们之前通过 Cromwell 直接运行了相同的工作流,见第十章,在本书桶中有一个 JSON 文件,指定了所有必要的输入。您只需获取文件的本地副本,然后在输入页面(在“搜索输入”框的右侧)使用“拖拽或点击上传 json”选项将其包含的输入定义添加到您的配置中。这应该填充页面上的所有字段。点击保存按钮,并确认页面上没有任何橙色感叹号。

到此为止,您的工作流应该已完全配置并准备好运行。随时点击“运行分析”按钮进行测试,并按照您之前在第十一章中进行监视执行的其余步骤。

通过 Broad Methods Repository 的方式成功将 WDL 工作流导入到 Terra,并使用直接文件路径配置选项配置为在单个样本上运行。这很棒,因为这意味着您现在可以将世界上任何找到的 WDL 工作流在 Terra 中测试,假设您有正确的测试数据和一个示例 JSON 文件可用。

但是,当您成功测试工作流并希望同时启动多个样本时会发生什么?正如您在第十一章中学到的那样,这就是数据表发挥作用的地方。您刚刚重现了该章节中的第一个练习,该练习展示了您可以使用直接输入配置表单中的文件路径来运行工作流。但是,要在多个样本上启动工作流,您需要设置一个数据表,在工作区的数据页面上列出样本及其相应的文件路径。然后,您可以像该章节中的第二个练习那样配置工作流以在数据表中的行上运行。在接下来的部分中,我们将向您展示如何设置您的数据表。

添加数据表

请记住,我们的教程工作空间使用了一个位于 GCS 中的公共桶中的示例数据集,我们在 Terra 外部进行管理。数据页面上的样本表列出了每个样本,通过在各自列中的文件路径来标识,其中 BAM 文件及其关联的索引文件各自在一列中。

所以问题是,你如何在我们的空白工作空间中重新创建样本数据表?Terra 希望你做什么?简而言之,你需要创建一个以制表符分隔值的纯文本格式的加载文件。一篇Terra 文档文章详细介绍了这一点,但基本上,它是一个你保存为 TSV 格式的电子表格。这相当简单,除了一个小技巧,通常会让初次尝试的人摔倒:文件需要有包含列名的头部,并且第一列必须是每行的唯一标识符。这里有一个专业建议:与其试图从头开始创建加载文件,不如从包含数据表的任何工作空间下载一个现成的文件,并以此作为起点。在这里,我们会非常懒惰,从原始教程工作空间下载表格 TSV 文件,查看它并讨论一些关键点,然后将其不加修改地上传到我们的空白工作空间。

在你打开原始教程工作空间的浏览器标签或窗口中,导航至数据页面,并点击蓝色的“下载所有行”按钮。这应该会在你的浏览器中触发下载,由于这是一个非常小的文本文件,传输应该会立即完成。找到下载的文件并在你偏爱的电子表格编辑器中打开;我们使用 Google Sheets。

注意

在查看文件内容时,你也可以使用纯文本编辑器,但我们建议在修改 TSV 加载表时使用电子表格编辑器,因为这样可以减少搞乱制表符分隔格式的风险。

正如你在图 13-7 中所看到的那样,这个文件包含了原始工作空间中样本数据表的原始内容,因此,例如,与超链接father.bam不同,我们在存储桶中看到了 BAM 文件的完整路径。此外,该表具有包含列名的头行,这些列名显示在工作空间中,但有一个例外:文件中第一列的名称前面有entity:前缀,在工作空间中则不是这样。这个小细节标志着本节的最重要的要点:包含每行唯一标识符的列必须是加载文件中列的第一列,并且它的名称必须以_id后缀结尾。当你将加载文件上传到 Terra 时,系统将根据该列的名称(去掉entity:前缀和_id后缀)派生出数据表的名称本身。如果你不包括按照这种模式命名的列,Terra 将拒绝你的表。清楚地说,你给加载文件的名称不会影响表的名称,因为表将在 Terra 中创建。

从 Google Sheets 查看的教程工作空间中的样本数据表。

图 13-7. 从教程工作空间查看的样本数据表。

让我们在实践中尝试一下。在您的空白工作空间中,导航到数据页面,单击数据菜单上 TABLES 旁边的“+”图标,以打开 TSV 加载文件导入对话框框,如图 13-8 所示。您可以拖放文件或单击链接打开文件浏览器;使用您喜欢的任何方法来上传我们刚才查看的 TSV 文件。

TSV 加载文件导入 A)按钮和 B)对话框。

图 13-8. TSV 加载文件导入 A)按钮和 B)对话框。

如果上传成功完成,您现在应该可以在数据菜单中看到示例表。单击其名称以查看其内容,并将其与原始内容进行比较:如果您没有进行任何更改,则应完全相同。

随意尝试使用此示例加载文件来测试 TSV 导入功能的行为。例如,尝试修改第一列的名称并观察结果。您还可以尝试添加更多列,包括纯文本、数字、文件路径和元素列表(使用数组格式,用方括号和逗号分隔元素)。还可以尝试修改行并创建更多行,无论是在同一个文件中添加行还是创建具有不同行的新文件。只需记住,如果您正在电子表格编辑器中编辑加载文件(正如我们推荐的那样),您需要确保将文件保存为 TSV 格式。

注:

许多电子表格编辑器(包括 Microsoft Excel)将此格式称为制表符分隔文本,并将文件保存为.txt扩展名而不是.tsv,这完全没问题;只要内容格式正确,Terra 不会关心扩展名。

如果由于此类实验而导致您的数据页面变得有些混乱,请不要担心:您可以删除行甚至整个表格;只需选择相关复选框(使用页眉中的复选框来选择整个表格),然后单击窗口右下角出现的垃圾桶图标。唯一的限制是目前无法从表格中删除列,因此如果您想要去除不需要的列,则需要删除整个表格并重新进行上传程序。出于这个原因,在您真正的项目中工作时,特别是如果您计划随时间添加数据,将您认为是“良好”状态的表格的版本副本存储起来可能是个好主意。在 Terra 中还没有内置此功能,因此这是一个手动的过程,需要将 TSV 下载并存储在某个地方(例如工作空间存储桶)以及在其他位置外部存储,如果您想要额外谨慎的话。

在您完成与数据表的乐趣后,请转移到工作空间数据表中的资源数据。

填写工作空间资源数据表

正如你可能记得的,这个表格的目的是保存我们可能想要在多个工作流配置中使用的变量,比如基因组参考序列文件或者 GATK Docker 容器。这样一来,你只需要配置一次,然后在任何需要它的配置中引用该变量即可。你不仅不需要再次查找文件路径,而且如果你决定更新文件的版本或位置,只需在一个地方进行更新即可。话虽如此,你可能并不总是希望更新传播到每个资源文件或参数设置的每次使用。在设置自己的分析时,请务必仔细考虑如何管理共享资源和“默认”参数。

在实践中,这个数据表的结构非常简单,因为它只是键值对,而且它提供了一个类似表单的界面来添加和修改元素。你可以简单地使用该界面将表格的内容从原始工作区复制过来,如果你不介意手动操作的话。或者,你可以通过点击“下载所有行”链接以 TSV 格式下载原始内容,并在上传 TSV 链接中在你的工作区中不经修改地上传。你甚至可以将文件从本地文件系统拖到表格区域。随意尝试并选择最适合你的方法。有了这些,你已经完成了数据的设置,现在是时候回到工作流程了。

创建使用数据表的工作流配置

在你不再那么空白的工作区中,返回到工作流页面并查看列在那里的配置,但不要打开它。点击具有三个垂直堆叠圆点的圆圈,你现在可能已经认出它是 Terra 用来为特定资产(无论是工作空间、工作流还是笔记本)提供操作选项的符号。在打开的动作菜单中选择“复制工作流”,然后给复制命名,以表明它将使用数据表,就像我们在教程工作区中所做的那样。

注意

目前还不能简单地在原地重命名工作流配置。如果你想要给你的工作流配置一个与教程工作区中一样明确的“文件路径”版本的名称,你需要创建另一个副本,并删除原始版本。

打开要修改以使用数据表的配置副本,并在这次将配置选项切换到“处理多个工作流程”。在下拉菜单中,选择sample表,并选择是选择数据子集还是采用默认行为,即在表中的所有项目上运行工作流程。现在是这项练习更繁琐的部分,即将输入页面上的输入分配连接到数据表的特定列或工作空间资源数据表中的变量。不幸的是,这次你没有可用的预填充 JSON 作为快捷方式,但值得一提的是,输入页面具有辅助自动完成功能,可以显著加快这一过程。对于每个需要连接的变量(不包括我们只是留下硬编码的几个运行时参数,你可以在原始工作区中查找),在相关文本框中开始输入**workspace****this**。这将显示一个上下文菜单,列出相关表的所有选项:this指向您在配置下拉菜单中选择的任何表,而workspace始终指向工作空间资源数据,其中列出了参考基因组序列、Docker 镜像等。

在输入页面上使用辅助自动完成功能的另一种方法是在相应的输入文本框中开始输入输入变量名称的一部分。如果相关数据表列或工作空间资源变量与输入变量名称相同,正如我们教程工作区的情况一样,这通常会显示出一系列匹配值。例如,在一个输入字段中输入**ref**将仅显示工作空间参考序列、其索引文件和字典文件。当您处理大量变量时,这种方法可能会更快,但不能保证对每个配置都有效,因为它依赖于名称是否足够匹配,这并非总是情况。有些人只是想看着世界燃烧。

继续填写输入页面,如果在任何时候遇到困难,请参考原始工作区。完成后,请确保单击保存按钮,并确认没有像之前那样留下任何感叹号。

你觉得,这个工作流程准备好启动了吗?什么。意思是,你还有一个配置任务没有完成,这在“文件路径”环节中不适用。跳转到输出页面,这需要你指定你想要对工作流程输出做什么。明确一下,这不会改变文件的写入位置;这由内置的 Cromwell 服务器为你确定,就像你在第十一章中学到的那样。这将确定输出将被添加到数据表中的列名。你可以点击“使用默认值”链接来简单地使用输出变量名,或者你可以指定不同的东西——要么是一个已经存在的列,要么是系统将用来创建一个新列的新名称。在我们的工作区中,我们选择使用默认值。

好了,现在你完成了。继续启动工作流程来尝试一下,然后坐下来欣喜地感受一下,你刚刚在很大程度上提升了自己的水平。有效使用数据表通常被认为是使用 Terra 最具挑战性的方面之一,而你已经掌握了。你还不是专家——当你掌握了使用多个链接数据表的技巧,比如参与者和样本集,你就会成为专家——但你已经走了大部分路。我们将在下一个场景中介绍这一点,在那里你将从 Terra 数据库导入数据到一个 GATK 最佳实践工作区中。

在我们开始之前,我们仍然需要处理教程工作区中使用的 Jupyter Notebook 部分,这部分在第十二章中使用过。但不用担心,这部分将会非常简短——如果你选择了简单的路线的话。

添加笔记本并检查运行环境

在你日益装备精良的工作区中,导航到笔记本页面,并猜测接下来应该是什么步骤。从技术上讲,你在这里有选择。你可以创建一个空白的新笔记本,并通过键入所有单元格来重新创建原始笔记本。要做到这一点,点击标记为“创建新笔记本”的框,然后在打开的对话框中,给它一个名称,并选择 Python 3 作为语言。然后,你可以再次通过第十二章工作,自己键入命令,而不是像上次那样只运行预先准备好的版本中的单元格。或者,你可以选择简单的路线,只需上传原始笔记本的副本,就像其他所有内容一样,它在 GitHub 存储库中都可以找到。具体来说,清除的副本和之前运行的笔记本的副本在这里。获取文件的本地副本,并在笔记本页面上使用“拖放或点击以添加 ipynb 文件”选项将其上传到你的工作区。

无论您选择哪条路,如果您在与您用于通过第十二章进行工作的同一计费项目下创建了工作区,则无需再次定制运行时环境,因为您在计费项目内的所有工作区中运行时环境都是相同的。方便,不是吗?也许是,但这也有点懒惰;如果您真的要从头开始构建此工作区,您将需要返回并重新按照第十二章中的说明定制运行时环境。所以,享受懒惰的机会,但请记住,在不同的情境下,您可能仍然需要关注运行时环境。

当您在工作区中使用笔记本完成所有教程工作的功能方面时,请退后一步,检查您是否成功重新创建了所有功能方面的教程工作区。一切都正常吗?做得好!您现在已经了解如何从单个组件中组装自己的工作区的所有基本机制。

记录您的工作区并分享它

您还没有做到与原始教程工作区匹配(或超越)的唯一事情是填写仪表板中工作区描述的内容,除了您创建工作区时放入的短暂占位符之外。如果您还有一点精力,我们鼓励您在记忆尚新的时候立即完成这项工作。只需点击看起来像是仪表板页面上铅笔的编辑符号,它会打开一个包含图形工具栏和分屏预览模式的 Markdown 编辑器。

我们通常不喜欢说任何事情是不言自明的,因为这种修饰语只会让您在处理有问题的事物时感觉更糟,但这个就是最接近应得到那个标签的东西。试一试吧,点击所有按钮,看看会发生什么。在云端处理工作区描述是唯一完全免费的事情之一,在私有克隆中,它完全是无害的,所以请享受。这也是一个很好的机会,可以在您通过处理 Terra 章节的过程中写下一些关于您学到的内容的笔记,也许还有关于您未来可能会遇到的问题的警告。

最后,考虑与朋友或同事分享您的工作空间。要这样做,请单击与在第十一章中克隆教程工作空间时使用的相同的圆圈点符号。这将打开一个小对话框,在其中您可以添加他们的电子邮件地址并设置您想要给予他们的权限。点击“添加用户”,然后务必点击右下角的“保存”。如果您的接收方尚未拥有 Terra 账户,他们将收到一封邀请他们加入的电子邮件,以及指向您工作空间的链接。如果他们只想查看工作空间内容,他们无需拥有自己的计费项目。

从 GATK 最佳实践工作空间开始

正如您刚刚体验到的那样,从其基本组件设置新工作空间需要相当大的努力。这并不一定困难 —— 您可能在日常工作中执行更复杂的操作 —— 但需要遵循许多小步骤,因此在您多次进行操作之前,您可能需要在整个过程中参考这些说明或您自己的笔记。

好消息是有各种机会可以采取捷径。例如,如果您只想运行由 GATK 支持团队提供的特色工作空间中的官方 GATK 最佳实践工作流程,您可以通过直接从相关工作空间开始跳过整个过程的一个部分。这些工作空间已经包含示例数据和资源数据以及工作流程本身,完全配置并准备运行。您真正需要做的只是克隆感兴趣的工作空间,并将要在数据表上运行工作流程的数据添加到其中。

在本节中,我们将详细讲解这种场景,以便您能够了解实际操作中所需的内容、潜在的复杂性以及自定义分析的选项。这种场景的经验通常适用于超出 GATK 工作空间的范围,适用于任何您可以访问已经配置了基本分析组件的工作空间的情况。我们将其视为“只需添加水”的场景,这是工具开发者希望研究人员可以适当地和尽可能少地使用其工具的一个有吸引力的模型。鉴于这样的工作空间构成了最终方法补充,它也是提高已发表论文的计算可再现性的有前景的模型。我们将在第十四章中详细讨论这一机制和其影响。现在,让我们开始处理 GATK 最佳实践工作空间。

复制一个 GATK 最佳实践工作空间

我们将使用Germline-SNPs-Indels-GATK4-hg38 工作空间,展示了作为三个独立工作流实现的 GATK 最佳实践短变异发现。第一个工作流程名为1_Processing_for_Variant_Discovery,接受原始测序数据并输出单个样本的分析 BAM 文件。第二个工作流程2_HaplotypeCaller_GVCF,接受该 BAM 文件并运行变异调用以生成单个样本的 GVCF 文件。最后,第三个工作流程3_Joint_Discovery,接受多个 GVCF 文件并应用联合调用以生成感兴趣的队列的多样本集合。

现在导航到该工作空间并像以前其他工作空间一样克隆它。当你在克隆版中时,快速查看一下内容以熟悉它的内容。您将在仪表板中找到详细描述,在数据页面上有三个数据表和一组预定义资源,并且在工作流页面上提到的三个完全配置的工作流程。

检查 GATK 工作空间数据表以了解数据结构的方式

让我们更仔细地看看数据页面上的三个数据表。一个表格名为参与者。它包含一个显示单列名称参与者 ID的标题行,以及一个由我们亲爱的 NA12878 填充的单行,或者我们在本书中称之为母亲

参与者 ID
NA12878

这将她确定为研究的参与者;换句话说,这是最初采集生物样本的实际个人,以生成我们最终将分析的数据。

参与者 ID属性是参与者的唯一标识符,并用于索引表。如果要向表格添加属性(例如一些表型信息,如参与者的身高、体重和健康状况),它们将作为列添加到标识符的右侧。

第二个表称为样本,包含多个列以及标题下面的两个样本行。这个表格比前一个更复杂,主要是因为除了运行工作流所需的最小输入外,它还包含了工作流先前运行的输出。这是一个不包含这些输出的表的最简版本:

样本 ID 未映射的流式细胞束bams列表 参与者 ID
NA12878 NA12878.ubams.list NA12878
NA12878_small NA12878_24RG_small.txt NA12878

在这个表格中,你会看到最左边的列是sample_id,每个样本的唯一标识符,用作该表格的索引值。快速跳到表格的另一端,最右边的列是participant_id。你能猜到这里发生了什么吗?没错,这是我们用来指示哪个样本属于哪个参与者的方式。sample 表格在回指到 participant 表格。这可能看起来无聊,但实际上非常重要,因为通过建立这种关系,我们使系统能够跟踪这些引用。通过两个表格之间类似的关系,我们将能够执行像配置工作流一样的任务,以便联合分析样本,而不是在每个样本上单独启动工作流。如果你觉得这很混乱,现在不用担心;我们将在后面更详细地讨论这个问题。现在只需记住,这些表格彼此连接着。

中间列 flowcell_unmapped_bams_list 指向从生物样本生成的测序数据。具体而言,这些数据以未映射 BAM 文件列表的形式提供,其中包含来自不同流式细胞流道的同一样本的测序数据。工作流程的数据处理部分期望提供这些数据子集的分离 BAM 文件,以便它可以分别处理这些数据的前几个步骤,然后将这些子集合并到一个包含该样本所有数据的单个文件中。

对于给定的样本,未映射的 BAM 文件列表将是 Processing_for_Variant_Discovery 工作流的主要输入。你可以通过查看工作流配置来验证这一点,其中输入变量 flowcell_unmapped_bams 设置为 this.flowcell_unmapped_bams_list。回想一下我们在 第十一章 中对数据表启动工作流的探索,语法读出为“对于表中的每一行,将 flowcell_unmapped_bams_list 列中的值传递给这个变量。”该工作流的主要输出将添加到工作区中完整表格的 analysis_ready_bam 列下。这一列反过来将作为 HaplotypeCaller_GVCF 工作流的输入,后者将其内容输出到 gvcf 列中。

两个样本行对应于从参与者 NA12878 派生的原始全基因组数据集的两个版本。在第一行中,NA12878 样本是全尺度数据集,而第二行中的 NA12878_small 样本是经过降采样的版本,这意味着它仅包含原始数据的一个子集。降采样版本的目的是更快速、更便宜地进行测试。

最后,第三个表称为sample_set,这个名字可能听起来很陌生,取决于你在第十一章中的注意程度。你还记得当你在数据表的行上运行工作流时发生了什么吗?系统会自动在新的sample_set表中创建一行,列出你启动工作流时使用的样本。在这个工作空间中,你可以看到一个名为one_sample的样本集,samples列下标有一个标签为1 entity的链接,如下所示:

sample_set_id samples
one_sample 1 entity

如果你点击它,会打开一个窗口,显示引用了 NA12878 样本的列表。这是表之间连接的另一个示例,我们将在很短的时间内具体利用它。

注意

系统并未显示这个样本集是如何创建的,但根据我们对工作流的了解,我们可以推断在此工作空间中运行第一或第二工作流程将复制样本集的创建过程。请注意,也可以手动创建样本集;稍后我们将展示如何操作。

这里有趣的地方是,这个样本集中的one_sample行与其相关联的输出文件,这在第十一章运行工作流时并非如此。你能猜到这可能意味着什么吗?我们给你一个提示:output_vcf列包含变异调用的 VCF。明白了吗?是的,这是在样本集上运行Joint_Discovery工作流的结果:它以样本集中samples列引用的样本列表作为输入,并将其 VCF 输出附加到样本集中。这是一个有些人为的示例,因为样本集仅包含一个样本,所以稍后在本节中我们将在更实际的样本集上再次运行以带给你更有意义的体验。

现在我们已经这样分析了数据表,我们可以总结数据集的结构及其组成实体之间的关系。结果如图 13-9 所示,我们称之为数据模型:参与者 NA12878 有两个关联样本,NA12878 和 NA12878_small,并且一个样本集引用了 NA12878 样本。

数据模型的视觉表示——示例数据集的结构。

图 13-9. 数据模型——示例数据集的结构。

请注意,只有在一个参与者只有一个样本的情况下,样本标识符与参与者标识符相同才会发生。在现实的研究背景中,一个参与者可能会有多个相关样本,因此你预期会有不同的标识符。此外,你预期这两个样本数据集可能源自不同的生物样本或者是使用不同的分析方法生成,而我们知道在这种情况下,它们来自同一个样本。再次强调,这是由于示例数据的某种人为特性。在此场景的下一步中,我们将会查看一些符合更现实期望的数据。

了解 1000 基因组高覆盖数据集

正如我们之前讨论的,数据库提供了对由各种组织托管在几种数据库中的数据集的访问。因此,根据数据库的不同,检索数据的协议也有所不同。在这个练习中,我们从一个仅包含来自第三阶段 1000 基因组计划的 2,504 个样本的全基因组数据集的仓库中检索数据,这些数据最近由纽约基因组中心作为 AnVIL 项目 的一部分重新测序。这个特定的仓库只是一个包含数据表的 公共工作空间,在这个规模下作为数据仓库的一种简单而有效的形式。

现在前往 1000 基因组数据工作空间,可以通过点击上面的直接链接或浏览数据库来实现。如果选择后者,请注意,如 图 13-10 所示,还有另一个 1000 基因组项目数据仓库,但它与我们所需的大不相同:那一个包含了完整 3,500 名研究参与者的低覆盖序列数据,而我们需要的是来自项目第三阶段的 2,504 名参与者的新高覆盖序列数据。

Terra 数据库包含来自 1000 基因组计划的两个不同的数据仓库。

图 13-10。Terra 数据库包含来自 1000 基因组计划的两个数据库。

当你在工作区时,转到数据页面查看数据表。你会看到与我们在 GATK 工作区中描述的相同的三个表:样本参与者样本集。正如预期的那样,参与者表列出了数据集中的 2,504 名参与者。同样,样本表列出了对应序列数据文件位置的路径,这些文件以 CRAM 格式提供,以及通过运行HaplotypeCaller流程生成的 GVCF 文件。此外,两者都包含了许多在 GATK 工作区中不存在的元数据字段,包括关于数据生成阶段(仪器类型、文库制备协议等)和研究参与者的原始人口信息。所有这些元数据的存在是一个很大的指标,表明这是一个更真实的数据集,与 GATK 工作区中的玩具示例数据相比。与此同时,样本集表包含了与样本集名称1000G-high-coverage-2019-all相关的所有 2,504 个样本的列表。

我们可以总结这个数据集的数据模型如下:每个参与者都有一个关联的样本,并且有一个参考所有样本的样本集,如图 13-11 所示。

1000 基因组高覆盖数据集的数据模型的可视化表示。

图 13-11. 1000 基因组高覆盖数据集的数据模型。

在这一点上,我们有一个坦白要做:我们花在两个工作区的数据表上的所有时间并不仅仅是数据旅游。这一切都有一个非常具体的目的,回答一个你可能已经看到或者可能没有看到的未明说的问题:它们的数据模型是否兼容?

而且好消息是是的,它们似乎是兼容的,这意味着你可以将它们的数据表合并而不会引起冲突,你应该能够对来自两个数据集的样本进行联合数据分析。诚然,这里的联合程度并不真正有意义,因为只有一个人的基因组信息在 GATK 工作区中,但这个基本原则同样适用于你可能想要合并到单个工作区中的其他数据集。

现在我们满意地认为我们应该能够在我们的工作区中使用数据,让我们弄清楚如何实际执行信息的传输。

从 1000 基因组工作区复制数据表

有两种主要方法可以做到这一点:一种是点对点的方法,另一种是基于加载文件的方法。让我们从点对点的方法开始,因为它将一些在使用加载文件时涉及的复杂性抽象化。

在 1000 基因组高覆盖工作区的数据页面上,点击样本表,并通过左侧选择其对应复选框选择几个样本。在表格上方找到并点击三点符号以打开操作菜单(就像你在第十一章中为工作流配置数据输入时所做的那样),然后选择“导出到工作区”,如图 13-12 所示。

复制数据到工作区对话框。

图 13-12。复制数据到工作区对话框。

这应该会打开一个对话框,其中列出了样本列表和工作区选择菜单。选择你的 GATK 工作区的克隆,然后点击复制。请记住,尽管按钮似乎暗示我们要复制文件,但我们只想复制数据表中的元数据,其中包括文件在 GCS 中的位置路径。所以,请放心,你不会突然间面临大额数据存储费用的问题。

你应该看到一个确认消息,询问你是否要留在当前位置(1000 基因组工作区)还是转到你复制数据的位置(GATK 工作区)。选择后者选项,这样我们可以查看转移产生的结果。如果一切按预期工作,你应该会在样本表中看到你选择的样本列表。然而,在参与者表中,你不会看到对应的参与者列表,因为系统不会自动复制所选数据引用的其他表中的数据。唯一的例外是集合,例如我们一直在使用的样本集。如果你选择一个集合并复制它,系统也会复制集合的内容。如果你还想复制参与者,你需要作为一个单独的步骤进行,要么通过选择复选框选择感兴趣的行,要么通过使用加载文件定义参与者集合。

如果你稍微尝试一下“复制数据到工作区”的功能,你很快就会意识到,这种方法在处理大型数据集时相当有限。为什么呢?因为当你尝试选择表中的所有行时,即使使用表格左上角的复选框,系统实际上只会选择显示在页面上的项目。由于允许的最大数量为一百个项目,所以使用集合方法是你通过点和点击方法一次复制超过一百个样本的唯一选择。

现在让我们尝试基于加载文件的方法,如果你正在处理大型数据集或需要在复制数据之前进行某些调整,则这种方法具有一些优势。

使用 TSV 加载文件从 1000 基因组工作区导入数据

从技术上讲,你之前已经做过这个——这就是我们让你在本章早些时候复制原始书籍教程工作空间中的数据表来重新创建自己的工作空间的方式。你选择了表格,点击了“下载所有行”按钮,获取了下载的文件,然后上传到了新的工作空间。完成。所有我们的样本都属于你。

然而,这一次有点不同:涉及到几个数据表,并且其中一些表彼此有引用。因此,有一个优先顺序:你必须从没有引用其他表的表开始,因为 Terra 不能处理对尚未定义的事物的引用。例如,你不能在上传样本集之前先上传样本,因为样本集引用了这些样本。这是一个令人恼火的限制,你可以想象系统简单地为任何它不认识的引用创建一个替代对象。然而,我们需要按照当前状态来操作系统,因此底线是:顺序很重要;接受这个事实吧。

在实际操作中,为了进行这个练习,你需要从 1000 基因组工作空间下载三个数据表的 TSV 文件,如之前所述(以任意顺序),然后按照正确的顺序上传到你的 GATK 工作空间克隆中。你会问正确的顺序是什么?这里有个提示:查看数据模型在图 13-11 中的可视化表示,并根据箭头的方向做出决策。你认为呢?没错,你首先上传参与者,然后是样本,最后是样本集。现在就去做吧。

哦,等等,你在执行这个过程中遇到了一些意外的事情吗?确实:样本集有两个加载文件:一个定义表本身并命名它包含的样本集,标识为entity:sample_set_id,另一个列出每个样本集包含的成员,标识为membership:sample_set_id。查看它们的内容以了解它们之间的关系。它们确实有关联;因为成员 TSV 引用了实体 TSV 中定义的样本集的名称,所以你需要先上传实体列表,然后再上传成员列表。

注意

处理加载文件和优先顺序规则是在 Terra 中设置数据中比较尴尬和枯燥的部分之一。我们期待未来的发展将通过提供某种向导式功能来平滑这些尖锐的边缘。

好消息是,在完成这一步之后,你应该能在你的 GATK 工作区中看到来自 1000 基因组数据集的 2504 个样本以及它们对应的参与者。然而,出现了一个问题。你能看出来吗?仔细看一下样本表中的列。当然,大部分列在两个原始表格之间是不同的,因为 GATK 工作区版本主要包含展示在工作区中的工作流输出文件,而 1000 基因组工作区版本主要包含有关数据来源的元数据。然而,它们本应该有一个共同点:GVCF 文件。你能找到它们吗?是的,它们确实都有 GVCF 文件,但它们位于不同的列中。

在原始的 GATK 工作区中,包含 GVCF 文件的列名全小写为gvcf,而在 1000 基因组工作区中,它是混合大小写的gVCF。顺便说一句,包含各自索引文件的列也有微小的差异,gvcf_indexgVCF_TBI,其中 TBI 是由一个叫 Tabix 的实用程序生成的索引格式的扩展名。

唉,真遗憾。我们本来想给你个惊喜,说:“看,现在你可以对你的 NA12878 样本进行联合调用分析,结合所有的 1000 基因组第三阶段数据。这不酷吗?”但如果 GVCF 文件在不同的列中,这个操作就行不通了(悲伤的表情)。

振作点,这是可以解决的。你还有用来上传 1000 基因组数据表的 TSV 文件,对吧?只需打开sample TSV 文件,并将两个列重命名为与 GATK 工作区中对应名称相匹配的名称。其他都不需要改变,只是这两个列名。然后,上传 TSV 文件,看看会发生什么:现在来自 1000 基因组样本的 GVCF 文件及其索引文件也会显示在正确的列中。危机解除!旧的列名不匹配的列仍然存在,但你可以忽略它们。事实上,你可以直接隐藏它们,以减少视觉混乱。只需点击表格右上角的齿轮图标,打开表格显示菜单。你可以取消列名前面的复选框以隐藏它们,甚至按照你的喜好重新排序它们。

注意

无可否认地,你不能简单地在原地编辑列名或删除不需要的列有点烦人。导入对话框也有些受限制,我们希望能够在确认导入操作之前预览数据并获得行和列的摘要计数。我们期待随着 Terra 的成熟,界面的这些方面会有所改进。

幸运的是,我们成功地通过编辑仅两个列名来克服了这个微小的命名不匹配问题。然而,尽管看起来微不足道,这个障碍表面上只是一个很小的问题,但它实际上是一个更大问题的症结所在:在数据集的结构化和属性命名方面缺乏足够的标准化。几乎任何试图将来自不同来源的数据集进行联合的尝试,都可能迅速变成一种挫败感的练习,因为您会发现自己在处理冲突的模式和命名约定。虽然目前没有普遍适用的解决方案,但当您遇到这类问题时,将问题简化到需要协调解决的最小组件集可能会有所帮助。例如,尝试定义每个数据集的核心数据模型;也就是说,确定关键数据部分及其之间的关系。从这里开始,您可以评估使数据集兼容所需程度所需的工作量。

在我们的案例中,现在我们已经拥有所需的一切:所有样本都在样本表中定义,并在gvcf列中列出了一个 GVCF 文件,以及在gvcf_index列中列出了相应的索引文件。其他任何内容对我们接下来想要做的事情都不相关,即对工作空间中的所有样本应用联合调用。

在联合数据集上运行联合调用分析

要完成这个场景,我们将运行预先配置在这个工作空间中的3_Joint_Discovery工作流程,该工作流程应用了 GATK 最佳实践,用于对一组样本进行联合调用生殖系短变异,详见第六章。我们将在一部分样本上运行以进行测试,但我们将提供指引,以便您尝试在所有样本上运行它。无论如何,该工作流程将生成一个包含我们选择包括的样本的多样本 VCF 文件,其中包含变异调用信息。

正如我们之前承认的那样,将这称为联合数据集有点夸张,因为实际上我们只是将一个样本添加到 1000 基因组第 3 阶段数据中。然而,我们讨论的原则如果您现在想要将更多样本添加到此工作空间中,同样适用。例如,您可能希望使用 1000 基因组数据来填充您对小队的分析,以最大化您从联合调用中获得的好处,正如 GATK 文档建议的那样。

工作流已经配置好,现在让我们来看看它的预期。转到工作流页面,点击3_Joint_Discovery工作流以查看配置详细信息。首先,我们要看看工作流配置在哪个表上运行。数据选择下拉菜单显示为 Sample Set,这意味着我们需要提供来自sample_set表的一个样本集。该表目前包含两个样本集;一个仅列出 NA12878 样本,另一个列出我们导入工作区的完整 1000 基因组第三阶段队列。我们需要一个包含来自 1000 基因组队列的一些样本以及来自 GATK 工作区的 NA12878 样本的样本集,因此现在让我们创建一个。

注意

我们将使用 25 个样本,因为这是工作流预定义配置所能容纳的数据量;一旦我们在这个规模上完成测试,我们将提供扩展的指导。

要快速创建一个测试样本集,请在步骤 1 下设置数据表选择器为sample_set,并在步骤 2 下点击选择数据。在打开的对话框中,选择“从所选样本创建新集合”选项。您可以选择单个样本的复选框,或者选择列顶部的复选框以选择默认显示的所有 25 个样本。当您选择了样本后,您可以使用标记为“所选样本将保存为新集合命名”的框指定样本集的名称,或者保留默认生成的名称不变。按“确定”确认并返回工作流配置页面。请注意,此时您实际上还没有创建新的样本集;您只是设置系统在按“启动”按钮时创建它。

但是不要立即按按钮!我们想向您展示另一种创建样本集的方式,不仅因为我们有点刻薄(我们确实有),还因为这样可以为您处理更复杂的情况提供装备。

在处理比单个屏幕能显示的更多样本时,创建集合的界面可能会太受限制,因此我们还将向您展示如何使用手动 TSV 方法进行此操作。这将是一个两步过程,类似于您之前所做的:使用entity加载文件创建一个新样本集,然后使用membership加载文件提供其成员列表。第一个文件非常简单,因为它只有一个列标题,entity:sample_set_id,以及下一行中新样本集的名称,如下所示:

entity:sample_set_id
federated-dataset

您可以将此保存为 TSV 文件,并像之前一样上传到您的工作区,或者您可以利用图 13-13 中说明的备选选项,将两行复制并粘贴到文本字段中。令人讨厌的是,无法直接在此文本框中输入文本或编辑粘贴的内容,但这确实减少了导入过程中的几次点击。

TSV 格式数据表内容的直接文本导入。

图 13-13. TSV 格式数据表内容的直接文本导入。

上传该内容将在sample_set表中创建一个新行,但新的样本集尚无样本与之关联。为了解决这个问题,我们需要制作一个包含我们要运行的样本列表的membership TSV 文件。我们喜欢从现有数据表开始,因为这样可以减少出错的可能性,特别是那些棘手的标题行。为此,请下载与样本集表对应的 TSV 文件,并在电子表格编辑器中打开sample_set_membership.tsv文件。如图 13-14 所示,您应该看到第二列中显示的 1000 基因组样本的完整列表,每行的第一列显示原始 1000 基因组样本集的名称。如果您滚动到文件的末尾,您还将看到来自 GATK 工作区的 NA12878 样本,该样本被分配到one_sample集。

成员加载文件*sample_set_membership.tsv*的起始和结束行。

图 13-14. 成员加载文件sample_set_membership.tsv的起始和结束行。

我们将从中提取一部分,并将其转换为成员列表,将我们选择的样本与我们新创建的联合数据集样本集相关联。首先,删除属于 1000 基因组队列的样本中除 24 个之外的所有样本,以便列表中总共剩下 25 个样本。不管保留哪些样本,然后,使用您编辑器的查找和替换或重命名功能(通常在编辑菜单中找到)来更改第一列中的样本集名称为联合数据集,如图 13-15 所示。确保还替换 NA12878 样本的样本集名称。然后,保存并像之前一样上传文件。

更新的成员加载文件示例 _set_membership.tsv 分配 25 个样本到联合数据集样本集。

图 13-15. 更新的成员加载文件sample_set_membership.tsv将 25 个样本分配给联合数据集样本集。

上传了这个成员列表后,sample_set 表现在应该将这 25 个样本列为联合数据集样本集的成员,如图 13-16 所示。

显示三个样本集的*sample_set*表。

图 13-16. sample_set 表现在显示的三个样本集。

顺便说一句,这表明您可以通过仅上传要更新或扩展的表中部分内容的 TSV 内容来添加或修改行。您不需要每次都重现整个表。我们觉得这在不能在图形界面中创建样本集时弥补了一些不足。

如果您想知道为什么我们只包含 GATK 工作区中全尺寸的NA12878样本而不是降采样的NA12878_small样本,那是因为它们来自同一个原始人,因此使用两者将是多余的。由于降采样样本的数据量较少,它生成的有意义结果的可能性较低,因此我们排除了它。

现在,我们有了一个样本集,列出了我们要在分析中使用的所有样本,我们可以继续下一步。在sample_set表中联邦数据集行的左侧选择复选框,然后点击“打开方式”按钮,选择工作流,最后选择3_Joint_Discovery以启动工作流提交。正如我们之前展示的那样,您可以选择从数据页面中的选择数据开始此过程,或者您也可以从工作流页面开始,取决于您的喜好。按照我们刚才描述的方式进行操作的优势在于现在您的工作流已经设置为在正确的样本集上运行;否则,当您到达配置页面时,您将需要使用“选择数据”菜单来完成此操作。

现在,我们只需检查配置的其余部分,从输入开始。输入字段很多,我们不会全部查看;相反,让我们专注于主要的文件输入。根据您在第六章中学到的关于联合调用的知识,您应该对您正在寻找的输入变量有一个概念:它应该是指向 GVCF 列表的一种输入变量。确实,如果您稍微向下滚动,您会找到名为input_gvcfs的工作流变量,如图 13-17 所示。该变量的类型Array[File],这意味着工作流期望我们提供一个文件列表作为输入,因此一切都符合。您还会看到为索引文件列表设置了相应的变量。

图 13-17. *input_gvcfs*和*input_gvcfs_indices*变量的输入配置详细信息。

图 13-17. input_gvcfsinput_gvcfs_indices变量的输入配置详细信息。

现在,如果您查看输入配置面板右侧列出的数值,您会看到this.samples.gvcf。这似乎既熟悉又新奇。熟悉是因为它看起来是我们之前见过和使用的this.something语法的一种变体,允许我们说:“对于工作流在其上运行的表的每一行,请将something列的内容提供给此输入变量。”在我们以前启动HaplotypeCaller工作流并行执行多个样本时,我们曾使用过这种语法,这种语法使得我们可以轻松地将input_bam变量连接到每个工作流调用的输入 BAM 文件,而无需显式指定任何文件。

然而,这也有点新鲜,因为它包含了一个额外的元素,形成了this.other.something语法。你能猜到那里发生了什么吗?这真的很酷;这是建立在前面章节中提到的表之间连接的回报。这种语法基本上是在说,“对于每个样本集,查找其samples列中的列表,然后转到sample表,并收集对应行中所有 GVCF 文件。”你可以使用.other.元素引用另一个表中的任何行列表,查询它们的特定字段,并返回相应元素的列表。这就是我们基于样本集中链接的样本列表生成输入 GVCF 文件列表的方法。作为奖励,列表生成的顺序是一致的,所以我们可以将this.samples.gvcfthis.samples.gvcf_index分配给两个不同的变量,并放心这些 GVCF 文件列表及其索引文件列表将按照相同的样本顺序排列。

这是拥有良好设计的数据模型的关键好处之一;你可以利用不同级别数据实体之间的关系。甚至可以将它们级联多层深;理论上你可以无限次数地进行.other.查找。有关你可以沿着这些线进行的更多示例,请查看用于体细胞最佳实践工作空间中使用的数据模型,其中每个参与者都有一个肿瘤样本和一个正常样本。在该数据模型中,另一个表列出了肿瘤-正常配对,另一个表列出了配对集。

随意浏览其余的输入配置页面。当你觉得你已经对输入如何连接有了很好的掌握时,可以前往输出页面进行最后的检查,然后再启动工作流。输出都按照this.output_模式设置,你应该能看到一行写着,“输出将写入到 Tables / sample_set”,这告诉你最终的 VCF 和相关输出文件将附加到样本集。这是有道理的,因为根据联合调用分析的定义,结果适用于所有包含的样本。

最后,继续启动工作流。你可以像之前的练习一样监控执行并探索结果。在 25 个样本上,我们的工作流测试运行花费了$10,并且大约耗时 10 小时(包括几次抢占)才能完成运行。迄今为止运行时间最长的步骤是ImportGVCFs,每个基因组的并行化段大约运行了三个小时,并且其中一半时间用于文件本地化阶段。这明显表明,该步骤非常需要优化以流式传输数据。另一个长时间运行的并行化任务是GenotypeGVCFs,每个并行化段大约需要一小时。最后,变异重校准步骤,这不是并行化的,大约需要两个小时。

建议再次运行此流程,使用几种不同的队列大小,并比较不同运行的时间图,以了解这个工作流程在样本数量不同的情况下时间和成本的缩放情况。您需要按照之前的指示创建新的样本集,但是可以保留您感兴趣的样本数量。确保为每个样本集起一个不同的名称,并执行实体 TSV 步骤将其添加到表中;否则,它将只更新您最初创建的样本集。如果您选择在超过我们迄今为止测试的 25 个样本的更多样本上运行工作流程,则根据工作区仪表板中3_Joint_Discovery工作流程输入要求中的说明,需要进行一些配置更改。简而言之,您需要为更多的样本 GVCF 分配更多的磁盘空间,使用磁盘覆盖输入变量。

事实是,这个联合发现工作流程的这个版本存在关键的扩展效率问题,包括我们刚才指出的不使用流式处理,并且不自动处理资源分配缩放。我们之所以使用它进行这个练习,是因为它方便获取,并且得到 GATK 支持团队的全面支持。坦率地说,这是观察效率低下后果的绝佳方式,这些问题看似个别不大,但在成千上万的样本规模下将导致重大困难。然而,我们不建议尝试在完整队列上运行它——事实上,在您至少进行了一些初步规模测试以评估工作流程时间和成本随样本数量增加而增加之前,我们不建议超过几百个样本。

如果您需要一个更好地适应开箱即用的解决方案,一个此工作流程的替代版本据说能够更好地扩展,但截至本文撰写时,尚未通过 GATK 支持团队的发布流程,因此它没有得到官方支持。此外,它的输入要求不允许您直接在工作区的数据表上运行它,因为它期望一个样本映射文件,列出样本名称和 GVCF 文件,如此示例(但有多行,每行一个样本)。要克服这个障碍,您可以基于工作区中的样本表创建这样一个文件,并将其添加为样本集的属性;或者如果您感到有冒险精神,甚至可以编写一个简短的 WDL 任务,在主工作流程之前运行。我们将这留给读者作为一种谚语式的练习。

这个场景最初旨在演示在运行 GATK 最佳实践时可以采用的一些快捷方式,但在过程中,你还掌握了一些额外的知识点:从不同来源组合数据时需要注意的事项,如何设置分析结果的联合数据集,以及一个精心制作的数据模型如何帮助你整合不同层次的数据。最后,你在多个全基因组样本的群体中进行了复杂的分析,毫不费力地完成了。做得好!

对于本章的最后一个场景,我们将颠倒操作顺序,看看这如何影响我们的工作空间构建过程。

围绕数据集构建工作空间

到目前为止,在我们的场景中,我们主要采取了一种以工具为先的方法,主要是因为我们的主要焦点是教你如何在 GCP 和 Terra 提供的云计算框架中使用一定范围的工具。因此,我们的指导模式是先设置工具,然后引入数据。但是,我们意识到实际上,很多人会采取数据为先的方法:先引入数据,然后再考虑如何应用各种工具。

在这最后一个场景中,我们将演示如何应用于与前一个场景中使用的相同的 1000 基因组数据集相同的示例。这一次,我们不再克隆 GATK 工作空间并从库中的 1000 基因组数据中提取数据,而是将克隆 1000 基因组工作空间,并从公共工具仓库 Dockstore 中导入一个 GATK 工作流。

克隆 1000 基因组数据工作空间

返回到数据库中的 1000 基因组高覆盖率数据工作空间,并像之前一样克隆它,指定一个名称和一个计费项目。正如我们在本章开头讨论的那样,克隆一个工作空间将浅层复制其内容,这意味着存储桶中的数据文件不会被复制到克隆版本中。克隆的数据表只是简单地指向原始文件的位置;通过查看克隆和原始文件的文件位置,你可以确信这一点。这相当于我们在上一个场景中执行的复制操作的结果,首先通过界面中的数据复制选项,然后通过重新使用原始工作空间的 TSV 加载文件。

注意

在你的克隆版本中,可以随意替换仪表板中的一些或全部描述,以链接到原始内容,以便为你自己的笔记留出空间。

从 Dockstore 导入工作流

现在问题是,我们如何引入一些工作流来分析这些数据?像往常一样,在 Terra 中,您有几个选择,具体取决于您想要实现的目标。首先,您可以像以前一样简单地使用内部方法库——无论是放入您自己的工作流还是浏览公共部分,看看是否能找到您喜欢的工作流。然而,内部方法库仅由在 Terra 中进行工作的人使用,因此可能缺少许多由更广泛的生物医学社区中的其他人开发的有趣工作流。

另一个选择是简单地从另一个工作空间复制工作流;例如,您可以通过使用工作空间操作菜单(带有堆叠点的圆形图标)中的“复制到另一个工作空间”选项,从 GATK 最佳实践工作空间中复制 3_Joint_Discovery 工作流(原始版本或您在先前场景中使用的克隆版本)。然而,这再次限制您只能使用已经引入 Terra 的工作流。在实践中,您可能会发现一些有趣的工作流在 Terra 之外,因此我们将向您展示如何从 Dockstore 导入工作流,这是一个越来越受欢迎的针对生物医学社区的注册表,支持使用 WDL、CWL 和 Nextflow 编写的工作流。

在您的 1000 Genomes 工作空间克隆中,导航到工作流页面,并点击“查找工作流”按钮,就像您以前做过的那样。这一次,不要选择内部方法库,而是选择 Dockstore 选项。这将带您进入 Dockstore 网站,您将能够浏览一系列 WDL 工作流。在左侧菜单中,您应该看到一个写着“输入搜索词”的搜索框。输入 **joint discovery**,并在右侧面板查看结果列表,其中包括几个 GATK 工作流,如 图 13-18 所示。

在 Dockstore 中搜索“joint discovery”的搜索结果。

图 13-18. 在 Dockstore 中搜索“joint discovery”的搜索结果。

搜索结果中列出的一些工作流是相同工作流的不同版本,由于历史原因分别注册。不幸的是,搜索结果列表中总结的信息并未提供一种轻松区分它们的方法,因此有时您可能需要单击详细描述以了解它们的区别。

找到名为gatk-workflows/gatk4-germline-snps-indels/joint-discovery-gatk4的工作流,并点击进入其详细信息页面。这个工作流的名称和描述应该会有一点熟悉的感觉,因为这实际上是你在之前场景中使用的3_Joint_Discovery工作流的源头。这段代码本身位于 GitHub 上,位于 gatk-workflows 组织下的一个仓库中,由 GATK 支持团队用于发布官方的 GATK 工作流。如果你在工作流详细信息页面点击“Versions”选项卡,你可以看到来自 GitHub 仓库的代码开发分支和发布版本列表。应该有一个版本被注册工作流的人标记为default,这相当于他们在说:“除非你知道更好的选择,否则你应该使用这个版本。”

有时看看这些内容是有趣的,以了解你感兴趣的工作流是否正在进行活跃的开发;例如,在本文写作时,我们看到默认版本在 2019 年 5 月设置为 1.1.1,但最近的工作是在 2020 年 1 月的开发分支中进行的。也许这些变更在你阅读本文时已经发布,并且一个新的默认版本已经设置。

你可以自行探索其他各种选项卡,但我们想指出几个我们认为尤其有用的,除了我们已经审查过的那些。首先是“Files”选项卡,其中引用了描述符文件下的 WDL 源文件,以及测试参数文件下的 JSON 文件。现在这些可能有点无聊,但以后会派上用场。我们非常喜欢的另一个选项卡是“DAG”选项卡,它代表有向无环图,指的是工作流图。这为你提供了一个与你在第九章中使用Womtool生成的交互式工作流可视化类似的展示,尽管显示略有不同,正如你可以在图 13-19 中看到的那样。默认情况下,这只显示工作流中的任务,这是一个非常好的方式,让你以高层次查看工作流的连接方式,而无需查看 WDL 代码。还有一个选项可以切换到由一家名为 EPAM 的公司制作的不同可视化工具,它提供了一个包括输入和输出的更精细的视图。这对于深入研究非常酷,但细节可能会有点令人不知所措。

在 Dockstore 的 DAG 选项卡中提供的 Joint Discovery 工作流的可视化。

图 13-19. 在 Dockstore 的 DAG 选项卡中提供的 Joint Discovery 工作流的可视化。

当您完成在 Dockstore 界面中探索工作流详细信息时,请查找右侧菜单中标记为“启动方式”的框,其中提供了几个运行工作流的选项,包括 Terra。点击 Terra 按钮返回 Terra,并将工作流带在口袋里。在导入屏幕上询问您要将其放置在哪个工作区时,请选择 1000 个基因组工作区的克隆。您将会进入您工作区中新导入工作流的配置页面。

配置工作流以使用数据表

输入页面将完全空白,所以我们建议首先上传在 Dockstore 的文件选项卡中列出的示例 JSON 输入文件。是的,这就是为什么我们提到了那个无聊的选项卡。不幸的是,目前没有办法让 Terra 随着工作流本身导入该文件(这显然是一个功能请求的好时机),所以你需要下载副本,然后使用“上传 JSON”选项将其上传到工作流配置中。上传完成后,保存配置并检查输入页面上是否没有剩余的错误指示(橙色感叹号)。确保选择直接在文件路径上运行工作流的选项,然后尝试运行导入的工作流,确保一切都可以立即使用。

假设测试运行成功完成,您需要重新配置输入,以指向数据表而不是直接文件路径。这可能是整个过程中最困难的部分,因为您必须弄清楚哪些输入需要连接到数据表,哪些输入可以移动到工作空间资源表,以及哪些输入只需留在配置中。确定第一组需要的一个好方法是查看工作流文档,了解其输入需求的描述。您在现场找到的工作流中并不总是有这样的描述,但当有这样的描述时,它通常对您应该考虑作为工作流主要输入是非常启发性的。对于这个特定的工作流,由于我们刚刚在本章的大部分时间里对它进行了详细研究,所以您应该知道应该连接什么,所以现在去做吧。如果遇到困难,请记住您可以查看原始的 GATK 工作空间,以了解您的目标模型。

注意

关于移动到工作空间资源表中的适当内容,这实际上只是判断和偏好的问题,因为在 Terra 看来并没有真正的技术差异。您可以简单地决定将所有内容保留在工作流中,直到导入另一个工作流并意识到它们利用相同的资源,并计划在那时将相关资源移动到工作空间数据表中。

当您设置好配置后,您将需要按照我们在前一个场景中给出的相同说明来定义一个样本集。事实上,在这一点上,剩下的工作几乎与前一个场景的最后两节完全相同,所以我们不觉得有必要再次步步指导;我们相信你可以自己完成。祝你好运!

最后,如果你感觉有点冤枉,因为我们让你导入与上一节相同的工作流程,这里有一个建议,可以让你在一个对你全新的工作流程上展示一下你的新技能。有时候,如果算法或支持资源发生了足够的变化(例如,如果有一个新的参考基因组构建),你可能需要重新执行 GVCF 调用步骤,但是提供的 1000 个基因组高覆盖数据集中的测序数据文件都是 CRAM 格式,当使用流式处理时,GATK 团队尚不推荐直接使用该格式进行数据访问。因此,您需要找到一个 CRAM 转换为 BAM 的工作流程作为预处理步骤运行,或者一个以内置 CRAM 转换为 BAM 任务开始的 GATK Haplotype-Caller工作流程。如果你在第九章中认真听讲的话,这应该会让你有所耳闻。

因此,请继续尝试找到适合使用 1000 个基因组数据的工作流程或工作流组合,使用我们在过去几章中提供给您的各种方法和资源。如果你真的遇到困难,请通过在GitHub 仓库中发布问题来告诉我们,我们将帮助您解决问题。如果很多人报告在进行伸展任务时遇到困难,我们会考虑在博客中发布逐步解决方案。

结束语和下一步计划

在本章中,您学习了如何在 Terra 中从各种来源(包括 Terra 内部和外部的数据和代码组件)组装自己的工作空间。我们非常强调如何考虑数据集的结构,以便使您能够充分利用 Terra 中链接数据表的强大功能,并通常结合来自不同来源的数据。您练习了从数据库导入数据并运行联合分析,包括来自 1000 基因组计划的数据,为大规模分析做好了准备。您还学会了从 Dockstore 导入工作流程,这是一个可以连接到除了 Terra 之外多个其他平台的工具和工作流程存储库。到此时,您已经具备了从云端公共或私有可用工具和数据集中组装和执行自己可扩展分析所需的全部基础。在第十四章中,即最后一章,我们将通过一个工作空间示例演示从已发表的论文复制端到端分析,以展示目前在生物医学研究中实现最佳计算可重现性的能力、障碍和展望。

第十四章:制作一个完全可复制的论文

在整本书中,您已经学习如何使用一系列单独的工具和组件执行特定任务。现在,您已经掌握了完成工作所需的一切——用小片段。在这最后一章中,我们将向您展示一个案例研究,演示如何将所有组件整合到一个端到端分析中,并额外展示确保全面计算再现性的方法。

该案例研究中的挑战是复现一项研究,其中研究人员确定了某个基因对特定先天性心脏病风险的贡献。原始研究是在受控数据访问下进行的,因此挑战的第一部分是生成可以替代原始数据的合成数据集。然后,我们必须重新创建数据处理和分析,包括变异发现、效果预测、优先级排序和基于论文提供信息的聚类。最后,我们必须将这些方法应用于合成数据集,并评估我们是否能成功复制原始结果。在本章的过程中,我们从面对的挑战中得出了一些教训,这些教训应该指导您努力使自己的工作在计算上可复制。

案例研究概述

我们最初构思这个案例研究,是为了作为我们提议在会议上进行的一对研讨会的基础,首先是从 2018 年 10 月开始的美国人类遗传学会(ASHG)年会。我们研讨会提案的基本前提是,我们将评估在人类医学遗传学中广泛应用的基因组方法的计算再现性障碍,并教会观众如何克服这些障碍。

我们最初对可能影响基因组研究的作者和读者的一些关键挑战有一些期望,并且我们知道大多数这些挑战已经存在解决方案。因此,我们的主要目标是突出这些已知挑战的实际发生情况,并演示如何根据开放科学运动中专家推荐的方法和原则实际克服这些挑战。然后,我们将开发教育材料,最终目标是推广一套研究人员在发布自己的工作时应用的良好实践。

通过我们不久将描述的一系列情况,我们选择了一项关于一种先天性心脏病的遗传风险因素的研究,并与其主要作者之一,Matthieu J. Miossec 博士合作,以复制文中核心的计算分析(可以这么说)。在项目的过程中,我们验证了一些我们的假设,但也遇到了我们没有预料到的障碍。因此,我们学到了比我们预期的要多得多的东西,这并不是我们能想象到的最糟糕的结果。

在本章的第一部分,我们通过讨论指导我们决策的原则来为舞台设定背景,然后我们开始描述我们试图复现的研究。我们讨论了最初识别的挑战,并描述了我们应用的逻辑来解决它们。最后,我们为你提供了我们实施计划的概述,作为我们深入探讨各个项目阶段细节的序幕。^(1)

计算复现性与 FAIR 框架

在我们深入分析试图复现的分析具体内容之前,值得重申我们所说的复现性是什么意思,并确保我们将其与复制区分开来。我们看到这些术语有不同的用法,有时甚至可以互换,而且目前并没有明确的共识认为哪种用法是最正确的。因此,让我们在本书的范围内对其进行定义。如果你在不同的上下文中遇到这些术语,请确保了解作者的意图。

我们定义复现性,侧重于分析过程和结果的可重复性。当我们说我们正在复现分析时,我们试图验证如果我们通过相同的处理输入相同的输入,我们会得到与我们(或其他人)第一次得到的相同结果。这是我们在扩展他人技术工作之前通常需要做的事情,因为我们通常需要确保我们正确运行他们的分析。因此,这是科学进步的绝对重要加速器。对于培训目的来说也是至关重要的,因为当我们给学习者一个练习时,我们通常需要确保他们严格按照说明可以得到预期的结果——除非目的是展示非确定性过程。希望你在本书中找到的练习是可以复现的!

另一方面,复制则完全是关于确认实验结果所得的见解(向传奇的反对者卡尔·波普致歉)。要复制研究的发现,我们通常希望采用不太可能受到相同弱点或人为因素影响的不同方法,以避免简单地陷入同样的陷阱。理想情况下,我们还希望检查独立收集的数据,以避免确认从那个阶段起源的任何偏见。如果结果仍然导致我们得出相同的结论,我们可以说我们复制了原始工作的发现。

这种差异,如图 14-1 所示,归结为管道真相。在一个案例中,我们试图验证“事情是否按预期运行”,而在另一个案例中,“是的,这就是自然的这一小部分运作方式”。

分析的可重现性与研究结果的可复制性。

图 14-1. 分析的可重现性与研究结果的可复制性。

鉴于我们在早期章节已经提到了可重现性的核心概念,这些定义不应该听起来太离奇。例如,在第八章中,当我们介绍工作流程时,我们指出它们作为一种将复杂分析操作编码为系统化、可自动化方式的重要价值。当我们探索 Jupyter Notebook 作为一种更灵活、交互式的打包分析代码方法时,我们也触及了类似的主题。我们确信,当这两种工具在本章后期出现时,你不会感到惊讶。

然而,可重现性只是开放科学万花筒中的一个方面。当我们着手处理这个案例研究时,我们做出了有意义的决定,通过 FAIR 框架来审视它。正如我们在第一章中简要提到的那样,FAIR是一个缩写,代表着可发现性、可访问性、互操作性和可重复使用性。它指的是一种评估研究资产和服务开放性的框架,基于这四个特征。其基本思想很简单:所有四个支柱都是必须满足的要求,才能将一个资产视为开放(如开放科学)。例如,要认为分析是开放的,仅仅代码在技术上可重用并且与其他计算工具互操作是不够的;其他研究人员还应该能够找到这段代码并获取一个完整的工作副本。FAIR 原则的原始发布强调了将其应用于科学数据管理目的的必要性,但也明确指出它们可以应用于各种其他类型的资产或数字研究对象,例如代码和工具。

虽然案例研究的大部分内容都集中在可重现性上,我们认为这与 FAIR 框架中的可重用性基本上是同义词,但在我们的各种实施决策中也会反映出其他三个 FAIR 特征。当我们回顾我们选择遵循的方法论时,我们会再次回到这一点,在本章结束时,我们会讨论这项工作的最终结果。现在是时候看一看我们选择在这个项目中展示的原始研究了。

原始研究和案例研究的历史

最初的研究由 Donna J. Page 博士、Matthieu J. Miossec 博士等撰写,旨在通过分析来自多个研究中心的 829 例病例和 1252 例对照的外显子组测序数据,识别与非综合征性Fallot 四联症相关的遗传组分。我们将在稍后更详细地介绍分析,但现在先概述一下,他们首先应用了基于 GATK 最佳实践的变异发现方法,在全样本集(包括病例和对照)中调用变异体,并使用功能效应预测识别可能的有害变异体。最后,他们进行了变异负荷分析,以识别在病例样本中与对照相比更频繁受有害变异体影响的基因。

通过这项分析,作者们发现了 49 个在NOTCH1基因内的有害变异体,这些变异体似乎与 Fallot 四联症先天性心脏病相关。此前其他人在患有先天性心脏病,包括 Fallot 四联症的家庭中已经发现了NOTCH1变异体,因此这并非完全意料之外的结果。然而,这项工作是首次将 Fallot 四联症的变异分析扩展到近千例病例样本的研究,并显示NOTCH1是 Fallot 四联症风险的重要贡献者。截至目前,这仍然是我们所知道的最大的非综合征性 Fallot 四联症外显子研究。

我们与 Miossec 博士讨论了我们正在为拉丁美洲几个学会的联合会议 ISCB-LA SOIBIO EMBnet 开发的基因组分析研讨会,该会议定于 2018 年 11 月在智利举行,此时手稿仍处于预印本阶段。同时,我们的团队还承诺为即将于 2018 年 10 月举行的 ASHG 2018 研讨会开发关于计算再现性的案例研究,正如前文提到的。巧合的是,Miossec 博士的预印本非常适合这两个目的,因为它典型地符合我们预期观众的需求:(1)变异发现和关联方法的经典应用案例,(2)在足够大以引起一些扩展挑战但又足够小以在大多数研究团体能力范围内的参与者队列中进行,(3)使用当时最常见的外显子组测序数据。鉴于该研究本身具有“正好合适”的特点,以及在我们最初讨论 ISCB-LA SOIBIO EMBnet 研讨会开发项目时 Miossec 博士的合作态度,我们便与他商议以他的预印本为基础开发案例研究。接下来的几个月里,我们共同努力开发了案例研究,并最终分别在 ASHG 2018 会议和 ISCB-LA SOIBIO EMBnet 2018 年会上进行了展示。

注意

明确一点,选择这项研究并不旨在展示 GATK 最佳实践;从技术上讲,作者的原始实现与经典最佳实践有所偏离。此外,这并不意味着对研究的生物学有效性进行认可。在本案例研究中,我们仅关注分析的计算再现性问题。

评估可获得信息和关键挑战

在我们甚至选择缺血性心脏病的四联症研究之前,我们已经根据我们在科学出版方面的经验回顾了我们预计会面临的关键挑战。如图 14-2 所示,科学论文作者和读者之间存在着信息和手段的根本不对称。作为作者,您通常可以完全访问原始数据集,而在人类基因组数据的情况下,这些数据几乎总是受到数据访问和数据使用限制的约束。您拥有您的计算环境,可能通过各种硬件和软件组件进行定制,并且您有工具或代码,可以在该环境中应用于数据以生成结果。

作者与读者之间信息可获得性的典型不对称性。

图 14-2. 作者与读者之间信息可获得性的典型不对称性。

另一方面,当您是读者时,通常首先看到的是预印本或已发表的论文,这本身就是作者原始结果的高度加工、筛选并在某种程度上审查过的视角。从论文中,您必须向后追溯以找到应用的方法描述,这些描述通常是不完整的,就像可怕的“我们按照在线文档中描述的 GATK 最佳实践进行变异调用。”(没有版本、特定工具的指示。)如果幸运的话,在补充材料中埋藏的未格式化的 PDF 中可能包含到 GitHub 仓库中代码或脚本的链接,这些可能或可能没有文档化。如果非常幸运,您甚至可能会找到提供软件环境需求的 Docker 容器映像的参考,或者至少是用于生成此类映像的 Dockerfile。最后,您可能会发现数据受到无法克服的访问限制。您可能能够根据可用信息、代码和论文中的软件资源来拼凑分析过程,公平地说,这有时可能会被呈现得相当完善,但是没有原始数据,您无法知道结果是否符合预期。因此,当您将其应用于自己的数据时,很难知道可以信任其产生的结果有多少。

现在我们描绘了这样一个悲观的图景,我们的情况如何?嗯,在 Tetralogy of Fallot 论文的那个时候,预印本完全符合标准。Figure 14-3 总结了其中包含的方法论信息。

Tetralogy of Fallot 论文原始预印本中提供的信息总结。

图 14-3。总结了 Tetralogy of Fallot 论文原始预印本中提供的信息。

在数据处理早期阶段使用的方法描述(从比对到变异调用)引用了另一家机构操作的第三方流程,这显然过于模糊,无法确保准确复制工作。但积极的一面是,它确实提到了关键工具及其版本,如来自 GATK 3.2 的HaplotypeCaller,我们对这些工具有足够的了解,可以自行了解其要求。对于分析的后续部分,方法描述更为详细,并指向 Bash 脚本和 Perl 代码,但这些仍然无法完全独立实现。

注意

剧透警告:我们极大地受益于我们的领头作者具有详细的分析方法知识;没有 Dr. Miossec 的直接帮助,我们是不可能成功的。

然而,我们遇到的困难完全可预见:我们很早就知道数据是受限访问的,并且事实证明,我们在任何时候都无法访问任何数据,甚至是为了开发目的。但不要泄气;这并不意味着项目已经失败。我们在几分钟内就能找到解决方案。

设计可重复实施方案

在确定了这些初始条件后,我们开始思考如何在实践中解决这个项目。我们希望使用一种方法论,不仅能够自己复制分析,还能将结果实施打包供他人复制。为此,我们遵循了贾斯汀·基茨等人在《可重复研究的实践》(加州大学出版社,2018 年)中提供的指导。你可以从他们的书中学到很多内容,这里我们无法一一涵盖,建议你去查阅。我们迅速实施的一个收获是根据以下问题分解整体工作:

  1. 什么是输入数据,我们如何使其对他人可访问?

  2. 对数据需要应用哪些(a)处理和(b)分析方法,并且如何使它们可移植?

  3. 如何打包我们生成的所有资源并分享给他人使用?

这是一个简单而有效的框架,提醒我们我们的优先事项并指导我们的决策。在我们达成一致意见后,处理和分析方法之间的区别尤其有用。处理 将指所有前期数据转换、清理、格式更改等操作,通常都是相同的,通常是批量自动运行的。这大致对应于通常描述的次级分析。与此同时,分析 则指更改多、依赖于上下文的活动,如建模和聚类,通常是交互式地、基于具体案例进行的。这对应于三级分析。在变异调用和效应预测方面存在一些灰色地带,但我们最终决定变异调用应属于处理类别,而变异效应预测则属于分析类别。

虽然这些区别在某种程度上是任意的,但对我们来说很重要,因为我们已经决定它们将指导我们关于我们将多么密切地遵循原始研究的努力。显然,我们希望尽可能接近原来的情况,但那时我们已经知道一些信息是不完整的。显然,我们需要做出妥协来在追求完美和我们能够投入项目的努力量之间达到平衡,这显然是无限的。所以我们做了一些假设。我们假设(1)基本数据处理如对齐和变异调用应该对适应具有相当的鲁棒性,只要它以相同的方式应用于所有数据,避免批次效应,并且(2)研究核心的效应预测和变异负荷分析更关键,需要精确复制。

谨记这些指导原则,我们概述了以下实施策略:

  1. 数据输入: 为了处理被封锁的数据,我们决定生成合成外显子数据,模拟原始数据集的属性,直到感兴趣的变体。合成数据将完全开放,因此满足了 FAIR 框架对可访问性可重用性的要求。

  2. 数据处理与分析

    1. 处理: 由于合成数据已经完全对齐,正如我们马上要讨论的那样,我们只需要在这个阶段实现变异调用。根据我们对原始流程的了解,我们决定使用一套组合现有和新的用 WDL 编写的工作流。WDL 的开放和便携性将使处理从 FAIR 的角度看起来是可访问的可互操作的可重用的

    2. 分析: 在 Miossec 博士的帮助下,我们决定分两部分重新实施他的原始脚本:作为 WDL 工作流的变体效果预测,以及作为 Jupyter 笔记本中的 R 代码的变体负荷分析。与 WDL 工作流一样,使用 Jupyter 笔记本将使分析从 FAIR 的角度变得可访问可互操作可重用

  3. 分享: 我们计划在 Terra 工作空间中完成所有工作,然后与社区共享,以便完全访问数据、WDL 工作流和 Jupyter 笔记本。在 Terra 中轻松共享将有助于我们通过使我们的案例研究从 FAIR 的角度变得可找到可访问

做这个案例研究的原始工作的最后一个亮点是在 Terra 中完成:我们知道完成后,可以轻松克隆开发工作空间,创建一个公共工作空间,其中包含所有工作流、笔记本和数据引用。因此,我们只需做最少的工作,即可将我们的私人开发环境转变为完全可复制的方法补充。

在接下来的两个部分中,我们将解释我们如何在实践中实现所有这些。在每个阶段,我们都会指向工作空间中开发的具体代码和数据元素,以便您能够清楚地了解我们在谈论什么。本章节不要求您运行任何内容,但请随时克隆工作空间并尝试各种工作流和笔记本。

现在,您准备好深入了解细节了吗?做好准备,系好安全带,因为我们将从事最具挑战性的部分开始:创建合成数据集。

生成合成数据集作为私人数据的替代品

原始分析涉及 829 个病例和 1,252 个对照,全部使用外显子测序生成,不幸的是,所有这些数据都受到我们无法轻易克服的访问限制。事实上,即使我们能够访问数据,我们肯定也无法将其与我们计划开发的教育资源一同分发。虽然仍然有用将数据用于测试,但无论如何,我们都将需要借助合成数据来完全实现我们的目标。

但等等……你问,这到底意味着什么?一般来说,这可能意味着很多事情,因此让我们在这里澄清一下,我们具体指的是合成序列数据:这是完全由计算机程序生成的读取序列组成的数据集。我们的想法是创建这样一个合成数据集,模仿原始数据,正如 图 14-4 所示,包括特定比例的感兴趣变体的存在,以便我们可以替换它并仍然实现我们复制分析的目标,尽管我们无法访问原始数据。

用合成数据集替换不能分发的真实数据集,以模仿原始数据的特征。

图 14-4. 用合成数据集替换不能分发的真实数据集,以模仿原始数据的特征。

让我们来谈谈实际操作中的工作原理。

总体方法论

使用合成基因组数据的概念并不新鲜,人们已经有一段时间在使用合成数据。例如,ICGC-TCGA DREAM 突变挑战是一系列重复的竞赛,组织者在其中提供了经过工程化的合成数据,这些数据已知带有特定变异,参与者的挑战是开发能够高度准确和具体识别这些突变的分析方法。

多程序可以生成合成序列数据,用于此类目的;实际上,有几个程序部分开发出来就是为了支持这些挑战。基本机制是基于参考基因组的序列模拟读取,并将其输出为标准的 FASTQ 或 BAM 文件。这些工具通常接受一个 VCF 变异调用文件作为辅助输入,这会修改数据模拟算法,使得生成的序列数据支持输入 VCF 中包含的变异。还有一些程序可以将变异引入(或spike in)已有的序列数据中。你向工具提供一组变异,然后工具修改部分读取以支持所需的变异调用。

实际上,在早期的头脑风暴阶段,我们曾考虑简单地编辑来自 1000 基因组计划的真实样本,使其携带感兴趣的变异,并且让数据集的其余部分充当对照。这将使我们避免生成真实的合成数据。然而,我们的初步测试显示,来自低覆盖数据集的外显子数据不具备足够好的质量来满足我们的目的。当时,尚未推出在第十三章中介绍的新高覆盖数据集,而且因为该数据是使用 WGS 生成的,而不是外显子测序,因此即使有了也不适合我们使用。

因此,我们决定创建一组合成外显子,但是基于 1000 基因组第三阶段变异调用(每个项目参与者一个)来构建,以便它们具有逼真的变异模式。这本质上相当于根据先前确定的变异重构真实人群的测序数据。然后,我们会对一部分被分配为病例的合成外显子进行突变,使用 Miossec 博士等在法洛氏四联症研究中发现的变异,这些变异在论文中有详细列出。由于 1000 基因组数据集中的参与者比法洛氏四联症队列要多,这使得我们有足够的余地生成我们需要的样本。图 14-5 展示了这个过程的关键步骤。

理论上,这种方法似乎相当直接,但正如我们迅速了解到的那样,实施这一策略(特别是在我们所需的规模上)的现实并不简单。我们最终使用的软件包非常有效,而且在大多数情况下都有很好的文档支持,但是它们并不是开箱即用的。在我们的观察中,这些工具主要被精通工具开发人员用于小规模测试和基准测试,这让我们想知道高门槛努力的多少是由于目标受众是专家,而不是由于它们在普通用户中普及率有限的原因。无论如何,我们没有看到生物医学研究人员单独使用它们来提供我们在项目中克服的困难的可重复研究补充。正如我们后来讨论的那样,这进一步激励我们考虑如何利用我们所取得的结果,使其更容易让其他人采纳提供合成数据作为研究伴侣的模式。

在下一节中,我们详细介绍了如何实施这部分工作的细节,偶尔停下来围绕特定的挑战提供额外的描述,我们认为这些描述可以为其他人提供有价值的见解,或者至少为保持轻松气氛提供一丝幽默感。

生成适当合成数据的实施概述。

图 14-5. 生成适当合成数据的实施概述。

从 1000 基因组参与者中检索变异数据

正如我们之前提到的,我们决定基于来自 1000 基因组计划参与者的 VCF 来进行合成数据模拟步骤。我们选择了这个数据集,因为它是最大的可用基因组学数据集,完全公开,并且在 GCS 中可以免费获取。然而,方便只是到此为止。从一开始,我们就不得不跳过一些障碍,首先是因为当时 1000 基因组变异调用是以多样本 VCF 的形式提供的,其中包含来自所有项目参与者的变异调用,按染色体分割。而我们需要的是相反的:一个包含每个项目参与者所有染色体数据的单样本 VCF。

因此,我们首先实现的是一个 WDL 工作流,根据参与者的标识符,使用 GATK 的SelectVariants从每个染色体文件中提取参与者的变异调用,然后使用 Picard 的MergeGvcfs将数据合并为该参与者的单个 VCF。听起来足够简单,对吧?是的,但这是生物信息学,所以当然事情并不那么简单。在尝试处理原始多样本 VCF 时,我们遇到了由于文件头格式不正确而导致的奇怪错误,因此我们不得不添加一个调用 Picard 的FixVcfHeader来修补它们。然后,在SelectVariants步骤之后,我们遇到了排序错误,因此我们不得不添加一个调用 Picard 的SortVcf来解决这些问题。我们将所有三个命令压缩到一个 WDL 步骤中,以避免进行大量来回文件复制。虽然不太美观,但它起作用了。这就成为了工作流1_Collect-1000G-participants,您可以在案例研究工作空间中看到它的运行情况。在这个工作流中的所有任务中,我们使用了标准的 GATK4 容器映像,其中也包含了 Picard 工具。

为了在我们的工作空间中运行这个流程,我们设置了来自 1000 基因组计划的最少元数据的参与者表,包括参与者标识符和他们最初所属的队列,以防后来有用(实际上并没有)。然后,我们配置了收集器工作流,以每行参与者表中的行运行,并将结果的 VCF 输出到同一行下的sampleVcf列中。

注意

在这个工作空间中,我们不需要区分参与者和样本,因此我们决定使用一个非常简单和扁平的数据结构,只使用其中之一。当时,参与者表是强制性的,所以我们别无选择,只能使用它。如果我们今天要重新构建这个流程,我们可能会转而使用样本,以使我们的表格更直接地与其他工作空间中的内容兼容。

由于工作流程存在一些主要的低效性,因此运行时间较长(平均为 4.5 小时,100 个文件集合并行运行时为 12 小时,其中包括一些抢先停止),但它确实产生了我们所寻找的结果:每个参与者的完整个体 VCF。有了这些,我们现在可以继续下一步:生成合成数据!

创建基于真实人物的虚假外显子

我们希望创建合成外显子数据,以代替原始的受控访问数据集,因此我们在这里的第一个任务是选择一个具有适当功能的工具包,因为正如我们之前提到的,有很多选择,并且它们展示了相当广泛的功能。在调查了现有的选项之后,我们选择了由 Zachary Stephens 等人开发的NEAT-genReads 工具包,这是一个开源软件包。它具有很多我们实际上不需要的功能,但似乎广受好评,并且具有我们寻找的核心功能:根据现有变异调用的输入 VCF(在我们的情况下是 1000 个基因组参与者的 VCF)生成整个基因组或外显子数据。

它是如何工作的?简而言之,该程序基于参考基因组序列模拟合成读取,并在输入 VCF 中存在变异的位置上修改某些读取中的碱基,比例与 VCF 中的基因型匹配。

图 14-6 描绘了这一过程。此外,该程序能够通过随机或基于错误模型修改读取中的其他碱基,以模拟测序错误和其他人工效应。默认情况下,该程序输出包含未对齐的合成序列数据的 FASTQ 文件,一个包含同一数据的 BAM 文件(与参考基因组对齐),以及作为真实数据集的 VCF 文件,其中包含程序引入的变异。有关此功能如何工作及其他选项的更多信息,请参阅前面引用的 GitHub 仓库以及描述 PLoS ONE 工具包的原始出版物

NEAT-GenReads 根据参考基因组和变异列表创建模拟读取数据。

图 14-6. NEAT-genReads 根据参考基因组和变异列表创建模拟读取数据。

除了这个核心功能外,NEAT-genReads 还接受各种参数,用于控制模拟数据的不同方面。例如,您可以使用间隔文件指定应覆盖或不覆盖的区域,并指定它们的目标覆盖度。您还可以控制读取长度、片段长度等,以及用于错误和自发突变的突变率。我们利用所有这些功能,根据预印本中可用的信息尽可能地调整生成的合成数据,使其尽量接近原始研究队列。

为了实现这一点,我们开发了 WDL 工作流2_Generate-synthetic-reads,它接受参考基因组和变异位点文件(VCF)用于生成序列读取,并生成 BAM 文件以及真实的 VCF 文件。同样,你可以在工作空间中看到它的运行过程。

我们很乐意说,第二个工作流比第一个顺利得多,但事实并非如此。我们遇到了更多问题,这些问题似乎要么是由于数据格式不正确,要么是工具无法处理 VCF 格式规范允许的元素。例如,NEAT-genReads 不能容忍在rsID字段中具有多个标识符的变异记录,因此我们不得不应用 awk 命令来清理 VCF 文件,从每个记录中删除rsID字段的内容。另一个有趣的问题是,NEAT-genReads 在真实的 VCF 文件中发出了不完整的 VCF 头部,因此我们再次使用 Picard 的FixVcfHeader来解决。它有时还会发出格式不正确的读取名称,因此为了解决这个问题,我们采用了 awk 和 samtools 的不寻常组合。最后,我们还必须向 NEAT-genReads 生成的 BAM 文件中添加readgroup信息,这显然并未考虑到该元数据的重要性(GATK 回应:亵渎!)。

你头晕了吗?我们当然是。不过,如果你对更详细的细节不熟悉,也不要担心。最终,重点是,在你深入进行自己的分析之后,你很可能会遇到这样的事情,即使是在像 NEAT-genReads 这样的强大工具中(是的,我们真的很喜欢它,无论它有什么问题)。更糟糕的是,这种事情往往被忽略在方法描述中。想象一下,如果我们简单地写道,“我们使用 NEAT-genReads 和参数xyz来生成合成数据”,并隐瞒了工作流代码。如果你尝试在没有我们的工作流(其中包括有关问题的评论)的情况下重现这项工作,你将不得不自己重新发现和排除所有这些问题。

与此同时,这里的另一个小复杂之处在于我们找不到任何关于 NEAT-genReads 的 Docker 容器镜像,因此我们不得不自己创建一个。这时我们惊恐地意识到,我们已经写到了这本书的第十三章多一点,而我们甚至还没有向你展示如何创建你自己的 Docker 镜像。糟糕?嗯,记住,如今大多数常用工具都可以通过像BioContainers这样的项目提供的 Docker 镜像来使用,这为社区提供了极好的服务——它们甚至接受请求。根据你的职业,很可能你可以完全依赖云来完成大部分甚至所有的工作,而无需担心创建 Docker 镜像。然而,我们不能真的让你在没有至少展示基础知识的情况下完成这本书,因此如果你确实需要打包自己的工具,你会知道该期待什么。让我们利用这个 NEAT-genReads 的揭示来在附带的侧栏中做到这一点。

我们使用侧边栏中显示的 Dockerfile 创建容器镜像,然后将其发布到 Docker Hub 的公共仓库。

为了在我们的 Terra 工作空间中运行工作流,我们配置它在参与者表的各行上运行,就像第一个工作流一样。我们将由上一个工作流的输出填充的 sampleVcf 列连接起来,作为这个第二个工作流的输入。最后,我们配置了两个结果输出,BAM 文件和 VCF 文件,写入表中的新列 synthExomeBamoriginalCallsVcf,以及它们的索引文件。

就像我们的第一个工作流一样,这个也因为我们不得不添加的清理任务而存在一些低效率。它运行的时间大约与第一个相同,尽管在这段时间内它做了更多的工作:为我们运行的每个参与者产生了一个完整的外显子 BAM 文件,覆盖率和相关指标模拟了预印本中报告的法洛四联症队列外显子。我们在 IGV 中打开了一些输出的 BAM 文件,惊讶地发现我们无法轻易区分具有类似覆盖属性的合成外显子和真实外显子。

这将我们带到生成合成数据集过程的最后一步:添加感兴趣的变体。

改变假外显子

进入这个过程时,我们已经熟悉了由 Adam Ewing 开发的一个名为 BAMSurgeon 的开源工具,我们的一些同事之前已成功使用它为变异调用开发工作创建测试用例。简而言之,BAMSurgeon 旨在修改现有 BAM 文件中的读取记录,引入变体等位基因,如 图 14-7 所示。该工具接受列出感兴趣变体的位置、等位基因和等位基因分数的 BED 文件(一个制表符分隔的文件格式),并相应地修改相应比例的读取。因为我们有在法洛四联症队列中确定的感兴趣变体列表,包括它们的等位基因和位置,我们认为我们应该能够将它们引入到我们的一部分合成外显子中,以重新创建案例样本的相关特征。

BAMSurgeon 在读取数据中引入变异。

图 14-7. BAMSurgeon 在读取数据中引入变异。

我们将其实现为 WDL 工作流 3_Mutate-reads-with-BAMSurgeon,它接受参考基因组、一个 BAM 文件和一个列出要引入的变体的 BED 文件,并生成突变的 BAM 文件。在第一次迭代中,我们仅限于添加 SNPs,因为 BAMSurgeon 对不同类型的变体使用不同的命令。在后续工作中,社区的贡献者添加了一些逻辑到工作流中,以处理插入缺失和拷贝数变体,使用相关的 BAMSurgeon 命令。

我们在这个问题上遇到了多少麻烦?几乎没有任何麻烦,如果你能相信的话。我们唯一真正遇到的问题是当我们最初在 1000 基因组低覆盖数据上进行测试时,因为 BAMSurgeon 拒绝在序列覆盖度太低的位置添加变异。然而,当我们转而在由 NEAT-genReads 生成的可爱、丰富数据上运行(我们已经设定了 50 倍的目标覆盖度),它完美地运行起来了。

与以往一样,在我们的 Terra 工作空间中,我们配置了工作流以在participant表的各行上运行。然后,我们只需确定如何标记病例与对照以及如何设置哪些病例将被突变成哪种变异体。

我们意识到,在这一部分,我们过于简化了实验设计。我们最初的计划只是在一部分外显子中引入论文中列出的变异体作为病例样本,而其余的则保持不变作为对照样本。不幸的是,这种推理存在两个缺陷。

首先,并非所有病例样本在原始研究中都产生了感兴趣的变异体,因此变异体少于病例样本。此外,该列表中的许多变异体位于基因中,其变异负荷结果不太令人信服,因此我们早早决定仅专注于NOTCH1变异体用于本案研究。因为NOTCH1是论文本身的主要焦点,我们认为重现NOTCH1结果足以作为概念验证的证据。然而,我们确实认为我们应该保持与论文中相同的病例和对照比例。因此,许多标记为病例样本的合成外显子实际上不会接收到感兴趣的变异体。

第二,将一些数据与其余数据经历不同的处理步骤会使我们暴露于批次效应。如果不同处理中的某些因素导致影响分析的人工效应,该怎么办?我们需要消除这种偏倚的来源,并确保所有数据都以相同的方式处理。

为了解决这些缺陷,我们决定在任何不接收NOTCH1变异的外显子中引入一个中性变异体,无论其被标记为病例样本还是对照样本。为此,我们设计了一个我们预测不会产生任何影响的同义突变。

在 Terra 工作空间中,我们向participant表添加了一个role列,以便我们可以根据他们将接收的突变标记参与者为neutralcase。我们使用术语neutral而不是control,因为我们想要给自己灵活性,可以将具有中性突变的参与者作为真实的对照样本或作为不具有NOTCH1突变的病例样本。我们通过随机选择过程挑选出那些将成为NOTCH1病例样本的参与者,并在参与者表中为它们分配了case角色,将所有其他参与者标记为中性。

最后,我们为论文中报告的每一个NOTCH1变异创建了 BED 文件,以及我们设计的中性变异体。我们随机分配了个体NOTCH1变异体给标记为case的参与者,并将neutral变异体分配给其他所有参与者。对于每个参与者,我们在参与者表的新列mutation中链接了相应的 BED 文件。我们配置了 BAMSurgeon 工作流,以此字段(this.mutation)作为每个参与者外显子 BAM 文件中引入的变异体的输入。

尽管我们需要做适当的准备来合理分配变异体并不是一件简单的事情,但工作流程是直接而且运行速度很快(平均 0.5 小时,对于一组 100 个文件并行运行为 2.5 小时,再次包括一些抢先处理)。按计划,它为我们运行的所有参与者生成了变异的外显子 BAM 文件。我们在 IGV 中检查了几个输出的 BAM 文件,以确认确实引入了所需的变异,并且满意地看到该过程按预期运行。

生成确定性数据集

成功地将现有数据和工具整合到上述工作流程中后,我们现在有能力生成一个能够充分替代原始 Fallot 四联症队列的合成数据集。然而,我们不得不做出一个妥协,即将创建的外显子数量限制为 500 个,而不是我们最初计划的 2081 个,以模拟原始队列。原因?原因很俗气,让人感到痛心:我们在会议前简直没有时间了。有时候,你只能凭借手头现有的东西继续前行。

因此,我们生成了 500 个变异合成外显子,进一步在工作空间中组织为两个参与者集,富有想象力地命名为 A100 和 B500。A100 集是包含八个NOTCH1案例的 100 名参与者子集。B500 集是所有参与者的超集,我们为其创建了合成外显子,并包含了论文中报告的所有 49 个NOTCH1案例。您可能注意到,这些组成与总体 Fallot 四联症队列中NOTCH1案例的比例不完全匹配,但两组之间的比例相似。稍后,我们将讨论这如何影响最终结果的解释。

此时,我们已经准备好进入下一部分,尝试复制研究方法学本身。

重新创建数据处理和分析方法学

如果你现在已经忘记了原始研究,我们不会责怪你,所以让我们进行一个快速复习。如图 14-8 所示,Miossec 博士等人从 2081 名研究参与者的外显子测序数据开始,几乎均匀分为两组:病例,即患有先天性心脏病四联症(具体来说是一种称为非综合征性的类型),和对照组(未受该疾病影响的人)。他们使用基于 GATK 最佳实践的管道对外显子数据进行了相当标准的处理,主要包括与参考基因组的比对和变异调用,这些你在第六章已经是专家了。然后,他们应用了一个定制的分析,涉及预测变异的影响,以便专注于有害变异,然后尝试识别具有比随机预期更高变异负载的基因。

研究的两个阶段总结:处理和分析。

图 14-8. 研究的两个阶段总结:处理和分析。

关于这些程序的含义和目的,我们稍后会详细讨论。目前,我们想重点关注的概念是研究的这两个阶段之间的区别。在处理阶段,这可能是高度标准化和自动化的,我们主要试图清理数据并从我们面对的大量数据中提取我们认为有用的信息。这会在整个测序领域产生变异调用,即一个人的基因组与一个任意参考之间的差异长列表。这个列表本身并不提供任何真正的见解;它仍然只是数据。这就是分析阶段的作用所在:我们将开始对数据提出具体问题,并且如果一切顺利,产生有关我们正在调查的生物系统特定方面的见解。

我们强调这一区别,因为这是我们关于我们认为需要多接近复制原始研究中使用的确切方法的决定的关键因素。让我们深入探讨,你会明白我们的意思。

比对和变异发现

一开始,我们就遇到了一个重要的偏差。原始研究从 FASTQ 格式的外显子测序数据开始,必须在发生任何其他事情之前进行映射。然而,当我们生成合成外显子数据集时,我们产生了包含已经映射并准备好的测序数据的 BAM 文件。我们本可以将数据恢复到未映射状态,并从头开始运行管道,但我们选择不这样做。为什么?时间、成本和便利性,并非按照这个顺序。我们做出了有意的决定采取捷径,因为我们估计这对我们的结果影响将是最小的。根据我们的经验,只要满足一些关键要求并且数据质量合理,数据处理管道的具体实施方式并不像应用于整个数据集时一致性那样重要。但是,我们确保使用了相同的参考基因组,当然。

注意

请注意前提条件;我们并不是说什么都可以。不幸的是,关于这个话题的全面讨论超出了本书的范围,但如果您想讨论,请在 Twitter 上联系我们。

因此,我们直接跳到处理的变异发现部分。原始研究使用了在蒙特利尔加拿大计算基因组学中心开发和运行的GenPipes DNAseq管道。根据我们在预印本和GenPipes DNAseq在线文档中找到的信息,该管道遵循了 GATK 关于种系短变异发现的最佳实践。这包括使用 GATK HaplotypeCaller对每个样本进行单样本变异调用,生成每个样本的 GVCF,然后联合调用所有样本。我们注意到一个偏差,即根据预印本,变异调用集过滤步骤是通过硬过滤而不是使用 VQSR 进行的。

因此,我们重新利用了手头上已有的一些 GATK 工作流,并根据我们手头可用的材料描述定制了它们的处理。这产生了两个 WDL 工作流 4_Call-single-sample-GVCF-GATK(包括您最喜欢的HaplotypeCaller)和 5_Joint-call-and-hard-filter-GATK4(包括GenotypeGVCFs 及其友好的特性),您可以在 Terra 工作区中找到它们。我们配置了第一个工作流以运行表中每个参与者的合成外显子 BAM 文件,并将 GVCF 输出到相应行。相比之下,我们配置了第二个工作流以样本集水平运行,接收来自集合中所有参与者的 GVCF,并为集合生成经过筛选的多样本 VCF。

在这里,我们允许自己再做一个显著的偏离:我们没有像原始研究那样使用 GATK 3.2 版本,而是使用了 GATK 4.0.9.0 版本,以便利用 GATK4 版本相比较老版本的速度和可伸缩性方面的显著改进。我们预期,考虑到我们合成数据的高质量,HaplotypeCaller在这些版本之间的输出几乎没有差异。我们只期望在低质量数据和基因组的低置信区域才会出现重要的差异。再次强调,这里有一些注意事项我们没有深入讨论,我们一直在整个数据集中一致地应用相同的方法。

总而言之,我们在处理阶段迅速完成了工作,部分得益于对原始方法的两次偏离。自那时以来,我们考虑过是否回头重新从头处理数据,采用更精确的原始方法重新实现,以衡量这将产生多大的差异。这并没有让我们失眠,但我们很想知道结果是否支持我们的判断。如果你把它当作一项课后作业来完成,别犹豫,告诉我们我们错得多厉害。

现在,是时候进入分析的真正有趣的部分了,我们将发现是否能够重现原始研究的主要结果。

变异效应预测、优先级和变异负载分析

让我们花几分钟回顾一下问题陈述,并审视这部分的实验设计,考虑到到目前为止,我们大部分时间都是略过细节的。首先,我们手头有什么,我们想要实现什么目标?我们从一个队列级别的 VCF 开始,这是每个人变异的长清单,我们希望识别与发育性心脏病变形四联症相关的基因。明确一点,我们不是试图找到与疾病相关的特定变异;我们正在寻找与疾病相关的基因,理解不同患者可能携带位于同一基因内的不同变异。使这一点困难的是变异在所有基因中自然发生,如果你足够多地观察足够的样本中的足够多变异,你很容易找到毫无意义的伪关联。

解决这个问题的第一步是缩小变异列表,尽可能多地消除它们,根据它们的普遍性,它们是否存在于对照样本中,以及它们可能具有的生物学影响类型。确实,通话集中绝大多数变异都是普通的、无聊的,和/或不太可能有任何生物学影响。我们希望优先考虑;例如,集中关注仅在病例样本中发现的罕见变异,这些变异可能对基因功能具有有害影响。

有了这个大大减少的变异列表,我们可以进行变异负荷分析,如图 14-9 所示。这涉及查找那些看起来比你期望的更频繁发生变异的基因,考虑到它们的大小。

比较多个样本中基因的变异负荷。

图 14-9. 比较多个样本中基因的变异负荷。

原研究使用了一个名为SnpEff的工具来预测变异效应,然后使用 GEMINI 中的vt来根据它们的预测效应、在人群数据库中的频率以及仅在病例样本中存在的情况对变异进行优先排序。我们将其实现为一个两步骤的 WDL 工作流,6_ 预测变异效应-GEMINI,该工作流对两个工具使用相同的版本和命令,以及相同的人群资源。在 Terra 中,我们配置了工作流以在参与者集合级别运行,并将优先排序的变异列表输出到表中的同一行。对于这一步,我们必须为每个参与者添加一个家系文件,指定它应该被视为病例样本还是对照样本。对于给定的参与者集合,我们选择了一半的参与者,所有携带中性突变的参与者作为对照样本。然后,我们将集合中的其余参与者,包括所有携带NOTCH1突变的参与者,分配为病例样本。

最后,Miossec 博士帮助我们将用于聚类分析的原始 Perl 脚本改写为 R 语言,放入了一个Jupyter 笔记本,你也可以在工作区中找到。该笔记本设置为导入 GEMINI 生成的输出文件。然后通过一系列分析步骤,最终进行了一个聚类测试,寻找病例样本中罕见有害变异的过量。该分析有每个步骤的解释文档,所以如果你想了解更多关于这个分析,请务必查看。

在工作流和笔记本中,我们非常小心地逐字复制原始分析,因为我们认为这些部分对最终结果的影响最大。

新实现的分析性能

那么最关键的问题是…它起作用了吗?我们能够从大海捞针中找到NOTCH1吗?简短的答案是,大部分是;长答案则更有趣。

正如我们之前提到的,我们最终只能在我们拥有的时间内生成了 500 个合成外显子,因此我们定义了一个包含 8 个NOTCH1案例的 100 参与者组,以及一个包含所有 49 个NOTCH1案例的 500 参与者组。尽管NOTCH1案例的比例高于原始数据集中的比例,但在那时对我们来说重要的是,我们的两个参与者组在大致上是成比例的。因为我们不能按照最初的计划在全尺度上测试该方法,所以我们至少能够评估结果如何相对于数据集大小按比例变化,这从早期就是一个感兴趣的点。

我们在两个参与者组上运行了完整的处理和分析工作流程,然后将两者的结果加载到笔记本中,在那里您可以看到对两组重复进行的最终聚类分析。在两种情况下,NOTCH1都被提出作为候选基因,但置信水平却有所不同,如图 14-10 所示。在 100 参与者组中,NOTCH1仅在候选基因表中排名第二,未能超越背景噪声。相比之下,在 500 参与者组中,NOTCH1作为毫无争议的头号候选者出现,并以显著的优势领先其他参与者。

图 14-10. A) 100 参与者组和 B) 500 参与者组的聚类测试排名。

图 14-10. A) 100 参与者组和 B) 500 参与者组的聚类测试排名。

我们认为,尽管在忠实度方面做出了一些妥协,但我们能够生成符合预期的结果,因此这被视为分析的成功再现。

我们特别鼓舞人心地看到,这种方法可以用来测试统计能力的扩展,这取决于数据集的大小。我们可以轻松想象设置额外的实验,以测试观察到的扩展到更细的粒度和更大的尺度。我们还希望测试在NOTCH1案例相对于整体队列大小的比例变化如何影响结果,从实际观察到的比例开始。

机会多,时间少。顺便说一句,时间已经不早了;我们几乎到了最后...

FAIR 原则的漫漫长路

鉴于我们刚刚描述的内容,现在你应该能够在 Terra 中复制法洛氏四联症的分析,并在适用时重复使用数据和/或方法。在大部分情况下,你也应该能够在 Terra 之外做到这一点,因为大多数组件可以直接在其他平台上使用。你可以下载数据以在不同平台上使用,并且 Docker 镜像和 WDL 工作流几乎可以在任何地方运行。由于笔记本环境与用于工作流的 Docker 镜像不同,可能需要更多的努力来重新使用笔记本。你可以访问有关计算环境的所有必要信息,但仍然需要独立设置一个等效的环境。我们希望随着时间的推移,这方面的情况会进一步改善;例如,想象一下如果有一种选择可以生成一个 Dockerfile,以重新创建特定笔记本的软件环境。我们还希望看到一种标准化的方法来获取 Terra 工作空间的数字对象标识符(DOI),以便在包含伴随工作空间作为补充材料的出版物中使用。这与其他人为了更容易地进行归档、检索和重复使用研究工件而进行的重要工作密切相关。然而,当制定标准时,通常情况下会变得更加复杂。

那么,为了在你自己的工作中实现计算再现性和 FAIR 性,你可以做些什么呢?在本案例研究过程中,我们指出了几个通常在您直接控制之下的关键因素。我们在它们出现的背景下讨论了许多因素,但我们认为提供一个我们认为最重要的准则总结可能会有所帮助:

开源

使用开源工具和工作流的重要性难以言表。使用开源工具在两个方面有助于保证您的工作的再现性:它确保所有人都可以访问,并增加了方法的透明度。如果由于任何原因某人无法运行完全相同的代码,他们仍然有机会阅读代码并重新实现算法。

版本控制

尽可能系统地跟踪您使用的工具、工作流和笔记本的版本是至关重要的,因为从一个版本到下一个版本的变化可能会对工具的工作方式和产生的结果产生重大影响。如果您使用其他人的工具,请记录您使用的版本,并在发布分析时包含这些信息。如果您开发自己的工具和工作流,请确保使用像 GitHub 这样的版本控制系统。在 Terra 中,工作流通过 GitHub 和 Dockstore 或通过内部方法库系统地进行版本控制。目前在 Terra 中,笔记本并没有进行版本控制,因此我们建议定期下载您的笔记本,并将其提交到像 GitHub 这样的版本控制系统中。

自动化和可移植性

当涉及到开发你将在大量数据上多次运行的分析部分时,请选择一种能够尽可能自动化并减少对特定环境依赖的方法。例如,选择像 WDL 或 CWL 这样的流水线语言,其他人可以轻松运行,而不是编写繁琐的 Bash、Python 或 R 脚本,即使你提供了代码,其他人也可能难以运行。

内置文档

对于涉及大量数据交互和判断步骤如何进行的分析,考虑提供一个 Jupyter 笔记本,重现你的分析,并在每个步骤解释发生的情况。即使你更喜欢在日常工作中提供更灵活性的环境,比如 RStudio,将成品打包为 Jupyter 笔记本也会极大地增强其可重复性和可理解性。想象一下你上次帮助新的实验室成员或同学快速掌握新的分析方法的情况;想象一下能否给他们一个逐步说明该怎么做的笔记本,而不是一堆松散的脚本和一个或许不是最新的README文档。

开放数据

最后,房间里的大象通常会是数据。你可能无法分享你所处理的原始数据,尤其是当涉及到受保护的人类数据时,有许多合理的理由。然而,你可以考虑使用足以展示你分析工作方式的开放获取数据。当这不是一个选择时,就像我们刚刚描述的案例研究一样,考虑是否可能使用合成数据。

这最后一点是否意味着你应该重复我们生成合成数据集的步骤?希望不是。你可以使用我们生成的免费可访问的合成外显子组数据,或者使用我们展示的工作流来创建你自己的数据。我们的工作流在目前的形式下确实不够高效,并且会受益于一些优化,以使其运行成本更低、更具可扩展性,但它们有效。而且,嘿,它们是开源的,所以请随意玩耍并提出一些改进建议!

在更广阔的视角下,我们相信这里存在一个机会,可以开发一个社区资源,以减少在这个领域中任何人需要做的重复工作量。考虑到测序数据生成的标准化程度(至少对于短读取技术来说),应该能够识别出最常用的数据类型,并生成一系列通用的合成数据集。这些数据集可以存储在云端,并与诸如 BAMSurgeon 之类的工具结合使用,研究人员可以用来重现他人的工作,或使自己的工作更容易可重现,与我们在本章中描述的思路类似。

最终结论

好了,我们来到了最后一章的结尾。感觉怎么样?在云端,你走过了很多路程,可以这么说。你从一个微不足道的云 Shell 开始,然后迅速转向一个更强大的虚拟机,在那里你用 GATK 运行了真正的基因组分析;首先是手动逐步,然后通过简单的 WDL 工作流和 Cromwell。你运用内在的侦探精神解密神秘的工作流程,学习如何系统地解决新工作流程的方法。接下来,你升级到使用管道 API,提升了你的工作流水平,并体验了在广阔空间中启动并行工作流的自由。然后,你跃升到 Terra,在那里你逐步揭示了工作流、笔记本、数据管理的各个层面,最终通过我们完全可重现的论文案例研究找到了自己的立足点。

根据你在本书中学到的知识,你现在可以开始在自己的工作中使用这些工具了。利用大规模托管在云端的数据集和大量的 Docker 化工具,你无需费心去安装它们。开发你自己的分析,规模化地执行它们,并与世界分享——或者只与你的实验室同事分享。无论你继续使用我们在这里为这些目的使用的具体工具,还是使用日益发展的基于云的生命科学生态系统中的其他工具,你可以放心,类似的原则和范例仍然适用。

请务必定期查看书籍的 GitHub 仓库配套博客,以及围绕 GATK、WDL、Docker 和 Terra 的最新发展。

玩得开心,并保持可重现性!

^(1) 这个案例研究最初由 Broad Institute 的数据科学平台支持和教育团队开发。因为当时这项工作由我们其中一位(杰拉尔丁)领导,所以我们保留了第一人称复数的描述,但我们要明确其他团队成员和社区成员直接贡献了此处呈现的材料,无论是对原始研讨会的开发和交付,还是后来的材料改进。

^(2) Page 等最初于 2018 年 4 月将他们的手稿作为生物预印本在 bioRxiv 上与研究社区共享,并最终于 2018 年 11 月在同行评议的期刊Circulation Research上发表了这篇文章:“Whole Exome Sequencing Reveals the Major Genetic Contributors to Nonsyndromic Tetralogy of Fallot”,https://doi.org/10.1161/CIRCRESAHA.118.313250

posted @ 2025-11-24 09:16  绝不原创的飞龙  阅读(5)  评论(0)    收藏  举报