PyTorch 深度学习(GPT 重译)(六)(2)

简介: PyTorch 深度学习(GPT 重译)(六)

PyTorch 深度学习(GPT 重译)(六)(1)https://developer.aliyun.com/article/1485252

14.5.3 重复使用预先存在的权重:微调

一种快速获得结果的方法(通常也可以用更少的数据完成)是不从随机初始化开始,而是从在某个具有相关数据的任务上训练过的网络开始。这被称为迁移学习或者,当仅训练最后几层时,称为微调。从图 14.7 中突出显示的部分可以看出,在步骤 2c 中,我们将剪掉模型的最后一部分,并用新的东西替换它。

图 14.7 我们在本章中实施的端到端项目,重点是微调

回想一下第八章,我们可以将中间值解释为从图像中提取的特征–特征可以是模型检测到的边缘或角落,或者任何模式的指示。在深度学习之前,很常见使用手工制作的特征,类似于我们在卷积开始时简要尝试的内容。深度学习使网络从数据中提取对当前任务有用的特征,例如区分类别。现在,微调让我们混合使用古老的方法(将近十年前!)使用预先存在的特征和使用学习特征的新方法。我们将网络的一部分(通常是大部分)视为固定的特征提取器,只训练其上的相对较小的部分。

这通常效果非常好。像我们在第二章中看到的在 ImageNet 上训练的预训练网络对处理自然图像的许多任务非常有用–有时它们也对完全不同的输入效果惊人,从绘画或风格转移中的仿制品到音频频谱图。有些情况下,这种策略效果不佳。例如,在训练在 ImageNet 上的模型时,常见的数据增强策略之一是随机翻转图像–一个向右看的狗与向左看的狗属于同一类。因此,翻转图像之间的特征非常相似。但是如果我们现在尝试使用预训练模型进行一个左右有关的任务,我们可能会遇到准确性问题。如果我们想要识别交通标志,这里左转这里右转是完全不同的;但是基于 ImageNet 特征构建的网络可能会在这两个类之间产生许多错误的分配。

在我们的情况下,我们有一个在类似数据上训练过的网络:结节分类网络。让我们尝试使用它。

为了说明,我们在微调方法中保持非常基本。在图 14.8 中的模型架构中,两个特别感兴趣的部分被突出显示:最后的卷积块和head_linear模块。最简单的微调是剪掉head_linear部分–事实上,我们只是保留了随机初始化。在尝试了这个之后,我们还将探索一种重新训练head_linear和最后一个卷积块的变体。


图 14.8 章节 11 中的模型架构,突出显示了深度-1 和深度-2 的权重

我们需要做以下事情:

  • 加载我们希望从中开始的模型的权重,除了最后的线性层,我们希望保留初始化。
  • 对于我们不想训练的参数禁用梯度(除了以head开头的参数)。

当我们在超过head_linear上进行微调训练时,我们仍然只将head_linear重置为随机值,因为我们认为先前的特征提取层可能不太适合我们的问题,但我们期望它们是一个合理的起点。这很简单:我们在模型设置中添加一些加载代码。

列表 14.9 training.py:124,.initModel

d = torch.load(self.cli_args.finetune, map_location='cpu')
model_blocks = [
  n for n, subm in model.named_children()
  if len(list(subm.parameters())) > 0                            # ❶
]
finetune_blocks = model_blocks[-self.cli_args.finetune_depth:]   # ❷
model.load_state_dict(
  {
    k: v for k,v in d['model_state'].items()
    if k.split('.')[0] not in model_blocks[-1]                   # ❸
  },
  strict=False,                                                  # ❹
)
for n, p in model.named_parameters():
  if n.split('.')[0] not in finetune_blocks:                     # ❺
    p.requires_grad_(False)

❶ 过滤掉具有参数的顶层模块(而不是最终激活)

❷ 获取最后的 finetune_depth 块。默认值(如果进行微调)为 1。

❸ 过滤掉最后一个块(最后的线性部分)并且不加载它。从一个完全初始化的模型开始将使我们从(几乎)所有结节被标记为恶性的状态开始,因为在我们开始的分类器中,该输出表示“结节”。

❹ 通过 strict=False 参数,我们可以仅加载模块的一些权重(其中过滤的权重缺失)。

❺ 对于除 finetune_blocks 之外的所有部分,我们不希望梯度。

我们准备好了!我们可以通过运行以下命令来仅训练头部:

python3 -m p2ch14.training \
    --malignant \
    --dataset MalignantLunaDataset \
    --finetune data/part2/models/cls_2020-02-06_14.16.55_final-nodule-nonnodule.best.state \
    --epochs 40 \
    malben-finetune

让我们在验证集上运行我们的模型并获得 ROC 曲线,如图 14.9 所示。这比随机要好得多,但考虑到我们没有超越基线,我们需要看看是什么阻碍了我们。


图 14.9 我们重新训练最后一个线性层的微调模型的 ROC 曲线。不算太糟糕,但也不如基线那么好。

图 14.10 显示了我们训练的 TensorBoard 图表。观察验证损失,我们可以看到虽然 AUC 缓慢增加,损失减少,但即使训练损失似乎在一个相对较高的水平(比如 0.3)上趋于平稳,而不是朝向零。我们可以进行更长时间的训练来检查是否只是非常缓慢;但将这与第五章讨论的损失进展进行比较–特别是图 5.14–我们可以看到我们的损失值并没有像图中的 A 案那样完全平稳,但我们的损失停滞问题在质量上是相似的。当时,A 案表明我们的容量不足,因此我们应考虑以下三种可能的原因:

  • 通过在结节与非结节分类上训练网络获得的特征(最后一个卷积的输出)对恶性检测并不有用。
  • 头部的容量–我们唯一训练的部分–并不够大。
  • 整体网络的容量可能太小了。


图 14.10 最后一个线性层微调的 AUC(左)和损失(右)

如果仅对全连接部分进行微调训练不够,下一步尝试的是将最后一个卷积块包括在微调训练中。幸运的是,我们引入了一个参数,所以我们可以将block4部分包含在我们的训练中:

python3 -m p2ch14.training \
    --malignant \
    --dataset MalignantLunaDataset \
    --finetune data/part2/models/cls_2020-02-06_14.16.55_final-nodule-nonnodule.best.state \
    --finetune-depth 2 \      # ❶
    --epochs 10 \
    malben-finetune-twolayer

❶ 这个 CLI 参数是新的。

完成后,我们可以将我们的新最佳模型与基线进行比较。图 14.11 看起来更合理!我们几乎没有误报,就能标记出约 75%的恶性结节。这显然比直径基线的 65%要好。当我们试图超过 75%时,我们的模型性能会回到基线。当我们回到分类问题时,我们将希望在 ROC 曲线上选择一个平衡真阳性与假阳性的点。


图 14.11 我们修改后模型的 ROC 曲线。现在我们离基线非常接近。

我们大致与基线持平,我们会对此感到满意。在第 14.7 节中,我们暗示了许多可以探索以改善这些结果的方法,但这些内容没有包含在本书中。

从图 14.12 中观察损失曲线,我们可以看到我们的模型现在很早就开始过拟合;因此下一步将是进一步检查正则化方法。我们将留给您处理。

图 14.12 最后一个卷积块和全连接层微调的 AUC(左)和损失(右)

有更精细的微调方法。有些人主张逐渐解冻层,从顶部开始。其他人建议用通常的学习率训练后面的层,并为较低的层使用较小的学习率。PyTorch 本身支持使用不同的优化参数,如学习率、权重衰减和动量,通过将它们分开在几个参数组中,这些参数组只是那样:具有单独超参数的参数列表(pytorch.org/docs/stable/optim.html#per-parameter-options)。

14.5.4 TensorBoard 中的更多输出

当我们重新训练模型时,值得看一看我们可以添加到 TensorBoard 中的一些额外输出,以查看我们的表现如何。对于直方图,TensorBoard 有一个预制的记录功能。对于 ROC 曲线,它没有,因此我们有机会满足 Matplotlib 接口。

直方图

我们可以获取恶性的预测概率并制作一个直方图。实际上,我们制作了两个:一个是(根据地面实况)良性的,一个是恶性结节的。这些直方图让我们深入了解模型的输出,并让我们看到是否有完全错误的大集群输出概率。

注意 一般来说,塑造您显示的数据是从数据中获取高质量信息的重要部分。如果您有许多非常自信的正确分类,您可能希望排除最左边的箱子。将正确的内容显示在屏幕上通常需要一些仔细思考和实验的迭代。不要犹豫调整您显示的内容,但也要注意记住,如果您更改了特定指标的定义而没有更改名称,将很容易将苹果与橙子进行比较。除非您在命名方案或删除现在无效的数据运行时有纪律地更改。

我们首先在保存我们的数据的张量metrics_t中创建一些空间。回想一下,我们在某处定义了索引。

列表 14.10 training.py:31

METRICS_LABEL_NDX=0
METRICS_PRED_NDX=1
METRICS_PRED_P_NDX=2    # ❶
METRICS_LOSS_NDX=3
METRICS_SIZE = 4

❶ 我们的新指数,携带着预测概率(而不是经过阈值处理的预测)

一旦完成这一步,我们可以调用writer.add_histogram,传入一个标签、数据以及设置为我们呈现的训练样本数的global_step计数器;这类似于之前的标量调用。我们还传入bins设置为一个固定的尺度。

列表 14.11 training.py:496,.logMetrics

bins = np.linspace(0, 1)
writer.add_histogram(
  'label_neg',
  metrics_t[METRICS_PRED_P_NDX, negLabel_mask],
  self.totalTrainingSamples_count,
  bins=bins
)
writer.add_histogram(
  'label_pos',
  metrics_t[METRICS_PRED_P_NDX, posLabel_mask],
  self.totalTrainingSamples_count,
  bins=bins
)

现在我们可以看一看我们对良性样本的预测分布以及它在每个时期如何演变。我们想要检查图 14.13 中直方图的两个主要特征。正如我们所期望的,如果我们的网络正在学习任何东西,在良性样本和非结节的顶行中,左侧有一个山峰,表示网络非常确信它所看到的不是恶性的。同样,在恶性样本中右侧也有一个山峰。

但仔细观察,我们看到了仅微调一个层的容量问题。专注于左上角的直方图系列,我们看到左侧的质量有些分散,并且似乎没有减少太多。甚至在 1.0 附近有一个小峰值,而且相当多的概率质量分布在整个范围内。这反映了损失不愿意降到 0.3 以下。

图 14.13 TensorBoard 直方图显示仅微调头部

鉴于对训练损失的观察,我们不必再深入研究,但让我们假装一下。在右侧的验证结果中,似乎在顶部右侧图表中,远离“正确”一侧的概率质量对于非恶性样本比底部右侧图表中的恶性样本更大。因此,网络更经常将非恶性样本错误分类为恶性样本。这可能会让我们考虑重新平衡数据以展示更多的非恶性样本。但再次强调,这是当我们假装左侧的训练没有任何问题时。我们通常希望先修复训练!

为了比较,让我们看看我们深度为 2 的微调相同图表(图 14.14)。在训练方面(左侧两个图表),我们在正确答案处有非常尖锐的峰值,其他内容不多。这反映了训练效果很好。

图 14.14 TensorBoard 直方图显示,深度为 2 的微调

在验证方面,我们现在看到最明显的问题是底部右侧直方图中预测概率为 0 的小峰值。因此,我们的系统性问题是将恶性样本误分类为非恶性。这与我们之前看到的两层微调过拟合相反!可能最好查看一些这种类型的图像,看看发生了什么。

TensorBoard 中的 ROC 和其他曲线

正如前面提到的,TensorBoard 本身不支持绘制 ROC 曲线。但是,我们可以利用 Matplotlib 导出任何图形的功能。数据准备看起来就像第 14.5.2 节中的一样:我们使用了在直方图中绘制的数据来计算 TPR 和 FPR–分别是tprfpr。我们再次绘制我们的数据,但这次我们跟踪pyplot.figure并将其传递给SummaryWriter方法add_figure

列表 14.12 training.py:482,.logMetrics

fig = pyplot.figure()                                           # ❶
pyplot.plot(fpr, tpr)                                           # ❷
writer.add_figure('roc', fig, self.totalTrainingSamples_count)  # ❸

❶ 设置一个新的 Matplotlib 图。通常我们不需要它,因为 Matplotlib 会隐式完成,但在这里我们需要。

❷ 使用任意 pyplot 函数

❸ 将我们的图表添加到 TensorBoard

因为这是作为图像提供给 TensorBoard 的,所以它出现在该标题下。我们没有绘制比较曲线或其他任何内容,以免让您分心,但我们可以在这里使用任何 Matplotlib 工具。在图 14.15 中,我们再次看到深度为 2 的微调(左侧)过拟合,而仅对头部进行微调(右侧)则没有。

图 14.15 在 TensorBoard 中训练 ROC 曲线。滑块让我们浏览迭代。

14.6 当我们进行诊断时看到的情况

沿着图 14.16 中的步骤 3a、3b 和 3c,我们现在需要运行从左侧的步骤 3a 分割到右侧的步骤 3c 恶性模型的完整流程。好消息是,我们几乎所有的代码都已经就位!我们只需要将它们组合起来:现在是时候实际编写并运行我们的端到端诊断脚本了。

我们在第 14.3.3 节的代码中首次看到了处理恶性模型的线索。如果我们向nodule_analysis调用传递一个参数--malignancy-path,它将运行在此路径找到的恶性模型并输出信息。这适用于单个扫描和--run-validation变体。

图 14.16 我们在本章实施的端到端项目,重点是端到端检测

请注意,脚本可能需要一段时间才能完成;即使只有验证集中的 89 个 CT 花费了大约 25 分钟。⁷

让我们看看我们得到了什么:

Total
             | Complete Miss | Filtered Out | Pred. Benign | Pred. Malignant
 Non-Nodules |               |       164893 |         1593 |             563
      Benign |            12 |            3 |           70 |              17
   Malignant |             1 |            6 |            9 |              36

不算太糟糕!我们检测到大约 85%的结节,并正确标记了约 70%的恶性结节,从头到尾。⁸ 虽然我们有很多假阳性,但似乎每个真结节有 16 个假阳性减少了需要查看的内容(好吧,如果没有 30%的假阴性的话)。正如我们在第九章中已经警告过的那样,这还不到你可以为你的医疗人工智能初创公司筹集数百万资金的水平,⁹ 但这是一个相当合理的起点。总的来说,我们应该对我们得到的明显有意义的结果感到满意;当然,我们真正的目标一直是在学习深度学习的过程中。

接下来,我们可能会选择查看实际被错误分类的结节。请记住,对于我们手头的任务,即使标注数据集的放射科医生们在看法上也存在差异。我们可能会根据他们清晰地将结节识别为恶性的程度来分层我们的验证集。

14.6.1 训练、验证和测试集

我们必须提到一个警告。虽然我们没有明确地在验证集上训练我们的模型,尽管我们在本章的开头冒了这个风险,但我们确实选择了基于模型在验证集上的表现来使用的训练时期。这也是一种数据泄漏。事实上,我们应该预期我们的实际性能会略逊色于这个,因为最好的模型在我们的验证集上表现得很好,不太可能在每个其他未见过的数据集上表现得同样出色(至少平均而言)。

由于这个原因,实践者经常将数据分为组:

  • 一个训练集,就像我们在这里所做的一样
  • 一个验证集,用于确定模型演化的哪个时期被认为是“最佳”
  • 一个测试集,用于实际预测模型的性能(由验证集选择)在未见过的真实世界数据上

添加第三组将导致我们再次拉取我们的训练数据的另一个非常重要的部分,考虑到我们已经不得不为了对抗过拟合而努力。这也会使呈现变得更加复杂,所以我们故意将其排除在外。如果这是一个有资源获取更多数据并迫切需要构建在野外使用的最佳系统的项目,我们将不得不在这里做出不同的决定,并积极寻找更多数据用作独立的测试集。

总的来说,偏见潜入我们的模型的方式是微妙的。我们应该特别小心地控制信息泄漏的每一步,并尽可能使用独立数据验证其不存在。采取捷径的代价是在后期惨败,而这种情况发生的时间是最糟糕的:当我们接近生产时。

14.7 接下来呢?灵感(和数据)的额外来源

在这一点上,进一步的改进将很难衡量。我们的分类验证集包含 154 个结节,我们的结节分类模型通常至少有 150 个正确,大部分的变化来自每个时期的训练变化。即使我们对模型进行了显著改进,我们的验证集中也没有足够的准确性来确定这种改变是否肯定是改进!这在良性与恶性分类中也非常明显,验证损失经常曲折。如果我们将验证步幅从 10 减少到 5,我们的验证集的大小将翻倍,代价是我们训练数据的九分之一。如果我们想尝试其他改进,这可能是值得的。当然,我们还需要解决测试集的问题,这将减少我们已经有限的训练数据。

我们还希望仔细研究网络表现不如我们期望的情况,看看是否能够识别出任何模式。但除此之外,让我们简要谈谈一些通用的方法,我们可以改进我们的项目。在某种程度上,这一部分就像第八章中的第 8.5 节。我们将努力为您提供尝试的想法;如果您不详细了解每个想法也不要担心。

14.7.1 防止过拟合:更好的正则化

回顾第 2 部分我们所做的事情,在三个问题中–第十一章和第 14.5 节中的分类器,以及第十三章中的分割–我们都有过拟合模型。在第一种情况下,过拟合是灾难性的;我们通过在第十二章中平衡数据和增强来处理它。这种数据平衡以防止过拟合也是训练 U-Net 在结节和候选者周围的裁剪而不是完整切片的主要动机。对于剩余的过拟合,我们选择了退出,当过拟合开始影响我们的验证结果时提前停止训练。这意味着预防或减少过拟合将是改善我们结果的好方法。

这种模式–获得一个过拟合的模型,然后努力减少过拟合–实际上可以看作是一个配方。因此,当我们想要改进我们现在所取得的状态时,应该使用这种两步方法。

经典正则化和增强

您可能已经注意到,我们甚至没有使用第八章中的所有正则化技术。例如,辍学将是一个容易尝试的事情。

虽然我们已经进行了一些增强,但我们可以走得更远。我们没有尝试使用的一个相对强大的增强方法是弹性变形,其中我们将“数字皱褶”放入输入中。这比仅仅旋转和翻转产生了更多的变化,似乎也适用于我们的任务。

更抽象的增强

到目前为止,我们的增强受到几何启发–我们将输入转换为更或多或少看起来像我们可能看到的合理东西。事实证明,我们不必局限于这种类型的增强。

回顾第八章,从数学上讲,我们一直在使用的交叉熵损失是预测和将所有概率质量放在标签上的分布之间的差异度量,可以用标签的独热向量表示。如果我们的网络存在过度自信的问题,我们可以尝试的一个简单方法是不使用独热分布,而是在“错误”类别上放置一小部分概率质量。这被称为标签平滑

我们还可以同时处理输入和标签。一个非常通用且易于应用的增强技术被提出,名为mixup:作者建议随机插值输入和标签。有趣的是,在对损失进行线性假设(这由二元交叉熵满足)的情况下,这等效于仅使用从适当调整的分布中绘制的权重来操作输入。显然,在处理真实数据时,我们不希望出现混合输入,但似乎这种混合鼓励预测的稳定性并且非常有效。

超越单一最佳模型:集成

我们对过拟合问题的一个观点是,如果我们知道正确的参数,我们的模型可以按照我们想要的方式工作,但我们实际上并不知道这些参数。如果我们遵循这种直觉,我们可能会尝试提出几组参数(也就是几个模型),希望每个模型的弱点可以互相补偿。这种评估几个模型并组合输出的技术称为集成。简而言之,我们训练几个模型,然后为了预测,运行它们所有并平均预测。当每个单独模型过拟合时(或者我们在开始看到过拟合之前拍摄了模型的快照),似乎这些模型可能开始对不同的输入做出错误预测,而不总是首先过拟合相同的样本。

在集成中,我们通常使用完全独立的训练运行或者不同的模型结构。但如果我们想要简化,我们可以从单次训练运行中获取几个模型的快照–最好是在结束前不久或者在开始观察到过拟合之前。我们可以尝试构建这些快照的集成,但由于它们仍然相互接近,我们可以选择对它们进行平均。这就是随机权重平均的核心思想。我们在这样做时需要一些小心:例如,当我们的模型使用批量归一化时,我们可能需要调整统计数据,但即使没有这样做,我们也可能获得一些小的准确度提升。

概括我们要求网络学习的内容

我们还可以看看多任务学习,在这里我们要求模型学习除了我们将要评估的输出之外的额外输出,这已经被证明可以改善结果。我们可以尝试同时训练结节与非结节以及良性与恶性。实际上,恶性数据的数据源提供了我们可以用作额外任务的额外标签;请参见下一节。这个想法与我们之前看到的迁移学习概念密切相关,但在这里我们通常会同时训练两个任务,而不是先完成一个再尝试转移到下一个。

如果我们没有额外的任务,而是有一堆额外的未标记数据,我们可以研究半监督学习。最近提出的一个看起来非常有效的方法是无监督数据增强。在这里,我们像往常一样在数据上训练我们的模型。在未标记数据上,我们对未增强的样本进行预测。然后我们将该预测作为该样本的目标,并训练模型在增强样本上也预测该目标。换句话说,我们不知道预测是否正确,但我们要求网络无论增强与否都产生一致的输出。

当我们没有更多感兴趣的任务但又没有额外数据时,我们可能会考虑捏造数据。捏造数据有些困难(尽管有时人们会使用类似第二章中简要介绍的 GANs,取得一定成功),因此我们选择捏造任务。这时我们进入了自监督学习的领域;这些任务通常被称为借口任务。一个非常流行的借口任务系列是对一些输入进行某种形式的破坏。然后我们可以训练一个网络来重建原始数据(例如,使用类似 U-Net 结构)或者训练一个分类器来检测真实数据和破坏数据,同时共享模型的大部分部分(例如卷积层)。

这仍然取决于我们想出一种损坏输入的方法。如果我们没有这样的方法并且没有得到想要的结果,还有其他方法可以进行自监督学习。一个非常通用的任务是,如果模型学习的特征足够好,可以让模型区分数据集的不同样本。这被称为对比学习

为了使事情更具体,考虑以下情况:我们从当前图像中提取的特征以及另外 K 张图像的特征。这是我们的关键特征集。现在我们设置一个分类前提任务如下:给定当前图像的特征,即查询,它属于 K + 1 个关键特征中的哪一个?这乍一看可能很琐碎,但即使对于正确类别的查询特征和关键特征之间存在完美一致,训练这个任务也鼓励查询特征在分类器输出中被分配低概率时与 K 其他图像的特征最大程度地不同。当然,还有许多细节需要填充;我们建议(有些是任意的)查看动量对比。²⁰

14.7.2 优化的训练数据

我们可以通过几种方式改进我们的训练数据。我们之前提到恶性分类实际上是基于几位放射科医生更细致的分类。通过将我们丢弃的数据转化为“恶性或非恶性?”的二分法,一个简单的方法是使用这五类。然后,放射科医生的评估可以用作平滑标签:我们可以对每个评估进行独热编码,然后对给定结节的评估进行平均。因此,如果四位放射科医生观察一个结节,其中两位称其为“不确定”,一位将同一结节称为“中度可疑”,第四位将其标记为“高度可疑”,我们将根据模型输出和目标概率分布之间的交叉熵进行训练,给定向量0 0 0.5 0.25 0.25。这类似于我们之前提到的标签平滑,但以更智能、问题特定的方式。然而,我们必须找到一种新的评估这些模型的方法,因为我们失去了在二元分类中简单的准确性、ROC 和 AUC 的概念。

利用多个评估的另一种方法是训练多个模型而不是一个,每个模型都是根据单个放射科医生给出的注释进行训练的。在推断时,我们可以通过例如平均它们的输出概率来集成模型。

在之前提到的多任务方向上,我们可以再次回到 PyLIDC 提供的注释数据,其中为每个注释提供了其他分类(微妙性、内部结构、钙化、球形度、边缘定义性、分叶、刺状和纹理 (pylidc.github.io/annotation.html))。不过,首先我们可能需要更多地了解结节。

在分割中,我们可以尝试看看 PyLIDC 提供的掩模是否比我们自己生成的掩模效果更好。由于 LIDC 数据具有多位放射科医生的注释,可以将结节分组为“高一致性”和“低一致性”组。看看这是否对应于“易”和“难”分类的结节,即看看我们的分类器是否几乎完全正确地处理所有易处理的结节,只在那些对人类专家更模糊的结节上遇到困难。或者我们可以从另一方面解决问题,通过定义结节在我们的模型性能方面的检测难度:将其分为“易”(经过一两个训练周期后正确分类)、“中”(最终正确分类)和“难”(持续错误分类)三个桶。

除了现成的数据,一个可能有意义的事情是进一步按恶性类型对结节进行分区。让专业人士更详细地检查我们的训练数据,并为每个结节标记一个癌症类型,然后强制模型报告该类型,可能会导致更有效的训练。外包这项工作的成本对于业余项目来说是高昂的,但在商业环境中支付可能是合理的。

尤其困难的情况也可能会受到人类专家的有限重复审查,以检查错误。同样,这将需要预算,但对于认真的努力来说绝对是合理的。

14.7.3 比赛结果和研究论文

我们在第 2 部分的目标是呈现从问题到解决方案的自包含路径,我们做到了。但是寻找和分类肺结节的特定问题以前已经有人研究过;因此,如果您想深入了解,您也可以看看其他人做了什么。

Data Science Bowl 2017

尽管我们将第 2 部分的范围限定在 LUNA 数据集中的 CT 扫描上,但在 Data Science Bowl 2017(www.kaggle .com/c/data-science-bowl-2017)中也有大量信息可供参考,该比赛由 Kaggle(www.kaggle.com)主办。数据本身已不再可用,但有许多人描述了对他们有效和无效的方法。例如,一些 Data Science Bowl(DSB)的决赛选手报告说,来自 LIDC 的详细恶性程度(1…5)信息在训练过程中很有用。

您可以查看的两个亮点是这些:²¹

  • 第二名解决方案的撰写:Daniel Hammack 和 Julian de Wit mng.bz/Md48
  • 第九名解决方案的撰写:Team Deep Breath mng.bz/aRAX

注意 我们之前暗示的许多新技术对 DSB 参与者尚不可用。2017 年 DSB 和本书印刷之间的三年在深度学习领域是一个漫长的时间!

一个更合理的测试集的一个想法是使用 DSB 数据集而不是重复使用我们的验证集。不幸的是,DSB 停止分享原始数据,所以除非您碰巧有旧版本的副本,否则您需要另一个数据来源。

LUNA 论文

LUNA Grand Challenge 已经收集了一些结果(luna16.grand-challenge.org/Results),显示出相当大的潜力。虽然并非所有提供的论文都包含足够的细节来重现结果,但许多论文确实包含了足够的信息来改进我们的项目。您可以查阅一些论文,并尝试复制看起来有趣的方法。

14.8 结论

本章结束了第 2 部分,并实现了我们在第九章中承诺的承诺:我们现在有一个可以尝试从 CT 扫描中诊断肺癌的工作端到端系统。回顾我们的起点,我们已经走了很长的路,希望也学到了很多。我们使用公开可用的数据训练了一个能够做出有趣且困难的事情的模型。关键问题是,“这对现实世界有好处吗?”随之而来的问题是,“这准备好投入生产了吗?”生产的定义关键取决于预期用途,因此,如果我们想知道我们的算法是否可以取代专业放射科医师,那肯定不是这种情况。我们认为这可以代表未来支持放射科医师在临床例行工作中的工具的 0.1 版本:例如,通过提供对可能被忽视的事项的第二意见。

这样的工具需要通过监管机构(如美国食品药品监督管理局)的批准,以便在研究环境之外使用。我们肯定会缺少一个广泛的、经过精心策划的数据集来进一步训练,甚至更重要的是验证我们的工作。个别案例需要在研究协议的背景下由多位专家评估;而对于各种情况的适当表达,从常见病例到边缘情况,都是必不可少的。

所有这些情况,从纯研究用途到临床验证再到临床使用,都需要我们在一个适合扩展的环境中执行我们的模型。不用说,这带来了一系列挑战,无论是技术上还是流程上。我们将在第十五章讨论一些技术挑战。

14.8.1 幕后花絮

当我们结束第二部分的建模时,我们想拉开幕布,让你一窥在深度学习项目中工作的真相。从根本上说,这本书呈现了一种偏颇的看法:一系列经过策划的障碍和机会;一个经过精心呵护的花园小径,穿过深度学习的更广阔领域。我们认为这种半有机的挑战系列(尤其是第二部分)会使这本书更好,也希望会有更好的学习体验。然而,这并不意味着会有一个更真实的体验。

很可能,你的大部分实验都不会成功。并非每个想法都会成为一个发现,也不是每个改变都会是一个突破。深度学习是棘手的。深度学习是善变的。请记住,深度学习实际上是在推动人类知识的前沿;这是我们每天都在探索和拓展的领域,就在此刻。现在是从事这个领域的激动人心的时刻,但就像大多数野外工作一样,你的靴子上总会沾上一些泥巴。

符合透明度精神,这里有一些我们尝试过的、我们遇到困难的、不起作用的,或者至少不够好以至于不值得保留的事情:

  • 在分类网络中使用HardTanh而不是Softmax(这样更容易解释,但实际上效果并不好)。
  • 试图通过使分类网络更复杂(跳跃连接等)来解决HardTanh引起的问题。
  • 不良的权重初始化导致训练不稳定,特别是对于分割。
  • 对完整的 CT 切片进行分割训练。
  • 使用 SGD 进行分割的损失加权。这并没有起作用,需要使用 Adam 才能使其有用。
  • CT 扫描的真正三维分割。对我们来说不起作用,但 DeepMind 后来还是做到了。这是在我们转向裁剪到结节之前,我们的内存用完了,所以你可以根据当前的设置再试一次。
  • 误解 LUNA 数据中class列的含义,导致在撰写本书的过程中进行了一些重写。
  • 无意中留下一个“我想快速获得结果”的技巧,导致分割模块找到的候选结节中有 80%被丢弃,直到我们弄清楚问题所在(这花了整个周末!)。
  • 一系列不同的优化器、损失函数和模型架构。
  • 以各种方式平衡训练数据。

我们肯定还忘记了更多。很多事情在变得正确之前都出了错!请从我们的错误中学习。

我们可能还要补充一点,对于这篇文章中的许多内容,我们只是选择了一种方法;我们强调并不意味着其他方法不如(其中许多可能更好!)。此外,编码风格和项目设计在人们之间通常有很大的不同。在机器学习中,人们经常在 Jupyter 笔记本中进行大量编程。笔记本是一个快速尝试事物的好工具,但它们也有自己的注意事项:例如,如何跟踪你所做的事情。最后,与我们之前使用的prepcache缓存机制不同,我们可以有一个单独的预处理步骤,将数据写出为序列化张量。这些方法中的每一种似乎都是一种品味;即使在三位作者中,我们中的任何一位都会略有不同地做事情。尝试事物并找出哪种方法最适合你,同时在与同事合作时保持灵活性是很好的。

14.9 练习

  1. 为分类实现一个测试集,或者重用第十三章练习中的测试集。在训练时使用验证集选择最佳时期,但在最终项目评估时使用测试集。验证集上的性能与测试集上的性能如何相匹配?
  2. 你能训练一个能够在一次传递中进行三路分类,区分非结节、良性结节和恶性结节的单一模型吗?
  1. 什么类平衡分割对训练效果最好?
  2. 与我们在书中使用的两遍方法相比,这种单遍模型的表现如何?
  1. 我们在注释上训练了我们的分类器,但期望它在我们分割的输出上表现。使用分割模型构建一个非结节的列表,用于训练,而不是提供的非结节。
  1. 当在这个新集合上训练时,分类模型的性能是否有所提高?
  2. 你能描述哪种结节候选者在新训练的模型中看到了最大的变化吗?
  1. 我们使用的填充卷积导致图像边缘附近的上下文不足。计算 CT 扫描切片边缘附近分割像素的损失,与内部的损失相比。这两者之间是否有可测量的差异?
  2. 尝试使用重叠的 32×48×48 块在整个 CT 上运行分类器。这与分割方法相比如何?

14.10 总结

  • 训练集和验证(以及测试)集之间的明确分割至关重要。在这里,按病人分割要比其他方式更不容易出错。当您的管道中有几个模型时,这一点更为真实。
  • 从像素标记到结节的转换可以通过非常传统的图像处理实现。我们不想看不起经典,但重视这些工具,并在适当的地方使用它们。
  • 我们的诊断脚本同时执行分割和分类。这使我们能够诊断我们以前没有见过的 CT,尽管我们当前的Dataset实现未配置为接受来自 LUNA 以外来源的series_uid
  • 微调是在使用最少的训练数据的情况下拟合模型的好方法。确保预训练模型具有与您的任务相关的特征,并确保重新训练具有足够容量的网络的一部分。
  • TensorBoard 允许我们编写许多不同类型的图表,帮助我们确定发生了什么。但这并不是查看我们的模型在哪些数据上表现特别糟糕的替代品。
  • 成功的训练似乎在某个阶段涉及过拟合网络,然后我们对其进行正则化。我们可能也可以将其视为一种配方;我们可能应该更多地了解正则化。
  • 训练神经网络是尝试事物,看看出了什么问题,然后改进它。通常没有什么灵丹妙药。
  • Kaggle 是深度学习项目创意的绝佳来源。许多新数据集为表现最佳者提供现金奖励,而旧的比赛则有可用作进一步实验起点的示例。

¹ 你也可以使用 p2_run_everything 笔记本。

² 任何给定结节的大小显然是高度可变的。

³ 我们特意选择了这个系列,因为它有一个很好的结果混合。

⁴ 查看 PyLIDC 文档以获取完整详情:mng.bz/Qyv6

⁵ 请注意,在平衡数据集上的随机预测将导致 AUC 为 0.5,因此这为我们的分类器必须有多好提供了一个下限。

⁶ 你可以尝试使用受人尊敬的德国交通标志识别基准数据集,网址为 mng.bz/XPZ9

⁷ 大部分延迟来自于 SciPy 对连接组件的处理。在撰写本文时,我们还不知道有加速实现。

⁸ 请记住,我们之前的“几乎没有假阳性的 75%” ROC 数字是针对恶性分类的孤立情况。在我们甚至进入恶性分类器之前,我们已经过滤掉了七个恶性结节。

⁹ 如果是这样的话,我们会选择这样做而不是写这本书!

¹⁰ 至少有一位作者很愿意在本节涉及的主题上写一本完整的书。

¹¹ 另请参阅 Andrej Karparthy 的博客文章“A Recipe for Training Neural Networks”,网址为karpathy.github .io/2019/04/25/recipe以获取更详细的配方。

¹² 你可以在mng.bz/Md5Q找到一个配方(尽管是针对 TensorFlow 的)。

¹³ 你可以使用nn.KLDivLoss损失函数。

¹⁴Hongyi Zhang 等人,“mixup:超越经验风险最小化”,arxiv.org/abs/1710.09412

¹⁵ 请参阅 Ferenc Huszár 在mng.bz/aRJj/发布的文章;他还提供了 PyTorch 代码。

¹⁶ 我们可能会将其扩展为纯贝叶斯,但我们只会使用这一点直觉。

¹⁷Pavel Izmailov 和 Andrew Gordon Wilson 在mng.bz/gywe提供了一个 PyTorch 代码的介绍。

¹⁸ 请参阅 Sebastian Ruder,“深度神经网络中多任务学习概述”,arxiv.org/ abs/1706.05098;但这也是许多领域的关键思想。

¹⁹Q. Xie 等人,“无监督数据增强用于一致性训练”,arxiv.org/abs/ 1904.12848

²⁰K. He 等人,“动量对比用于无监督视觉表示学习”,arxiv.org/ abs/1911.05722

²¹ 感谢互联网档案馆将它们从重新设计中保存下来。

²²Stanislav Nikolov 等人,“用于放射治疗头颈解剖学临床适用分割的深度学习”,arxiv.org/pdf/1809.04430.pdf

²³ 哦,我们进行过的讨论!

第三部分:部署

*在第三部分中,我们将看看如何使我们的模型达到可以使用的程度。我们在前几部分中看到了如何构建模型:第一部分介绍了模型的构建和训练,第二部分从头到尾详细介绍了一个示例,所以辛苦的工作已经完成了。

但是在你真正能够使用模型之前,没有任何模型是有用的。因此,现在我们需要将模型投入使用,并将其应用于它们设计解决的任务。这部分在精神上更接近第一部分,因为它介绍了许多 PyTorch 组件。与以往一样,我们将专注于我们希望解决的应用和任务,而不仅仅是为了看 PyTorch 本身。

在第三部分的单一章节中,我们将了解 2020 年初的 PyTorch 部署情况。我们将了解并使用 PyTorch 即时编译器(JIT)将模型导出以供第三方应用程序使用,以及用于移动支持的 C++ API。

十五、部署到生产环境

本章涵盖内容

  • 部署 PyTorch 模型的选项
  • 使用 PyTorch JIT
  • 部署模型服务器和导出模型
  • 在 C++中运行导出和本地实现的模型
  • 在移动设备上运行模型

在本书的第一部分,我们学到了很多关于模型的知识;第二部分为我们提供了创建特定问题的好模型的详细路径。现在我们有了这些优秀的模型,我们需要将它们带到可以发挥作用的地方。在规模化执行深度学习模型推理的基础设施维护方面,从架构和成本的角度来看都具有影响力。虽然 PyTorch 最初是一个专注于研究的框架,但从 1.0 版本开始,添加了一组面向生产的功能,使 PyTorch 成为从研究到大规模生产的理想端到端平台。

部署到生产环境意味着会根据用例而有所不同:

  • 我们在第二部分开发的模型可能最自然的部署方式是建立一个网络服务,提供对我们模型的访问。我们将使用轻量级的 Python Web 框架来实现这一点:Flask ( flask.pocoo.org) 和 Sanic (sanicframework.org)。前者可以说是这些框架中最受欢迎的之一,后者在精神上类似,但利用了 Python 的新的异步操作支持 async/await 来提高效率。
  • 我们可以将我们的模型导出为一个标准化的格式,允许我们使用优化的模型处理器、专门的硬件或云服务进行部署。对于 PyTorch 模型,Open Neural Network Exchange (ONNX)格式起到了这样的作用。
  • 我们可能希望将我们的模型集成到更大的应用程序中。为此,如果我们不受 Python 的限制将会很方便。因此,我们将探讨使用 PyTorch 模型从 C++中使用的想法,这也是通往任何语言的一个过渡。
  • 最后,对于一些像我们在第二章中看到的图像斑马化这样的事情,可能很好地在移动设备上运行我们的模型。虽然你不太可能在手机上有一个 CT 模块,但其他医疗应用程序如自助皮肤检查可能更自然,用户可能更喜欢在设备上运行而不是将他们的皮肤发送到云服务。幸运的是,PyTorch 最近增加了移动支持,我们将探索这一点。

当我们学习如何实现这些用例时,我们将以第十四章的分类器作为我们提供服务的第一个示例,然后切换到斑马化模型处理其他部署的内容。

PyTorch 深度学习(GPT 重译)(六)(3)https://developer.aliyun.com/article/1485255

相关文章
|
3天前
|
机器学习/深度学习 PyTorch TensorFlow
深度学习:Pytorch 与 Tensorflow 的主要区别(2)
深度学习:Pytorch 与 Tensorflow 的主要区别(2)
8 0
|
6天前
|
机器学习/深度学习 PyTorch TensorFlow
Pytorch 与 Tensorflow:深度学习的主要区别(1)
Pytorch 与 Tensorflow:深度学习的主要区别(1)
18 2
|
6天前
|
机器学习/深度学习 PyTorch API
pytorch与深度学习
【5月更文挑战第3天】PyTorch,Facebook开源的深度学习框架,以其动态计算图和灵活API深受青睐。本文深入浅出地介绍PyTorch基础,包括动态计算图、张量和自动微分,通过代码示例演示简单线性回归和卷积神经网络的实现。此外,探讨了模型架构、自定义层、数据加载及预处理等进阶概念,并分享了实战技巧、问题解决方案和学习资源,助力读者快速掌握PyTorch。
34 5
|
6天前
|
机器学习/深度学习 PyTorch 算法框架/工具
【Python机器学习专栏】PyTorch在深度学习中的应用
【4月更文挑战第30天】PyTorch是流行的开源深度学习框架,基于动态计算图,易于使用且灵活。它支持张量操作、自动求导、优化器和神经网络模块,适合快速实验和模型训练。PyTorch的优势在于易用性、灵活性、社区支持和高性能(利用GPU加速)。通过Python示例展示了如何构建和训练神经网络。作为一个强大且不断发展的工具,PyTorch适用于各种深度学习任务。
|
6天前
|
机器学习/深度学习 自然语言处理 算法
PyTorch与NLP:自然语言处理的深度学习实战
随着人工智能技术的快速发展,自然语言处理(NLP)作为其中的重要分支,日益受到人们的关注。PyTorch作为一款强大的深度学习框架,为NLP研究者提供了强大的工具。本文将介绍如何使用PyTorch进行自然语言处理的深度学习实践,包括基础概念、模型搭建、数据处理和实际应用等方面。
|
6天前
|
机器学习/深度学习 并行计算 PyTorch
PyTorch与CUDA:加速深度学习训练
【4月更文挑战第18天】本文介绍了如何使用PyTorch与CUDA加速深度学习训练。CUDA是NVIDIA的并行计算平台,常用于加速深度学习中的矩阵运算。PyTorch与CUDA集成,允许开发者将模型和数据迁移到GPU,利用`.to(device)`方法加速计算。通过批处理、并行化策略及优化技巧,如混合精度训练,可进一步提升训练效率。监控GPU内存和使用调试工具确保训练稳定性。PyTorch与CUDA的结合对深度学习训练的加速作用显著。
|
6天前
|
前端开发 JavaScript 安全
JavaScript 权威指南第七版(GPT 重译)(七)(4)
JavaScript 权威指南第七版(GPT 重译)(七)
29 0
|
6天前
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(3)
JavaScript 权威指南第七版(GPT 重译)(七)
39 0
|
6天前
|
前端开发 JavaScript Unix
JavaScript 权威指南第七版(GPT 重译)(七)(2)
JavaScript 权威指南第七版(GPT 重译)(七)
43 0
|
6天前
|
前端开发 JavaScript 算法
JavaScript 权威指南第七版(GPT 重译)(七)(1)
JavaScript 权威指南第七版(GPT 重译)(七)
69 0