JavaScript 深度学习(三)(1)https://developer.aliyun.com/article/1516961
7.2. 训练后的模型可视化
在之前的章节中,我们展示了可视化对数据的有用之处。在本节中,我们将展示如何在模型训练后可视化模型的各个方面,以获得有用的洞察力。为此,我们将主要关注以图像为输入的卷积神经网络(convnet),因为它们被广泛使用且产生有趣的可视化结果。
你可能听说过深度神经网络是“黑盒子”。不要让这个说法让你误以为在推理或训练神经网络时很难从内部获取任何信息。相反,查看 TensorFlow.js 中编写的模型的每个层在内部做了什么是相当容易的。此外,就卷积神经网络而言,它们学习的内部表示非常适合可视化,主要是因为它们是视觉概念的表示。自 2013 年以来,已经开发了各种各样的技术来可视化和解释这些表示。由于涵盖所有有趣的技术是不切实际的,我们将介绍三种最基本和最有用的技术:
³
这个说法实际上意味着,深度神经网络中发生的大量数学运算,即使可以访问,也比起某些其他类型的机器学习算法,如决策树和逻辑回归,更难以用 layperson 的术语描述。例如,对于决策树,你可以逐个沿着分支点走下去,并解释为什么选择了某个分支,通过用一句简单的句子如“因为因子 X 大于 0.35”来用语言化的方式解释原因。这个问题被称为模型可解释性,与我们在本节中涵盖的内容不同。
- 可视化 convnet 中间层(中间激活)的输出 —— 这有助于理解连续 convnet 层如何转换其输入,并且可以初步了解单个 convnet 滤波器学习的视觉特征。
- 通过找到最大化激活它们的输入图像来可视化 convnet 滤波器 —— 这有助于理解每个滤波器对哪种视觉模式或概念敏感。
- 可视化输入图像中类激活的热图 —— 这有助于理解输入图像的哪些部分在导致 convnet 生成最终分类结果时起着最重要的作用,这也可以有助于解释 convnet 如何达到其输出和“调试”不正确的输出。
我们将使用的代码来展示这些技术是来自 tfjs-examples 仓库的 visualize-convnet 示例。要运行示例,请使用以下命令:
git clone https://github.com/tensorflow/tfjs-examples.git cd tfjs-examples/visualize-convnet yarn && yarn visualize
yarn visualize
命令与您在先前示例中看到的yarn watch
命令不同。除了构建和启动网页之外,它还在浏览器外执行一些额外的步骤。首先,它安装一些所需的 Python 库,然后下载并转换 VGG16 模型(一个知名且广泛使用的深度卷积网络)为 TensorFlow.js 格式。VGG16 模型已经在大规模的 ImageNet 数据集上进行了预训练,并作为 Keras 应用程序提供。一旦模型转换完成,yarn visualize
在 tfjs-node 中对转换后的模型进行一系列分析。为什么这些步骤在 Node.js 中而不是浏览器中执行?因为 VGG16 是一个相对较大的卷积网络。^([4]) 因此,其中的一些步骤计算量很大,在 Node.js 中的资源限制较少的环境中运行得更快。如果您使用 tfjs-node-gpu 而不是默认的 tfjs-node,计算速度可以进一步加快(这需要具有所需驱动程序和库的 CUDA 启用 GPU;请参阅附录 A):
⁴
要了解 VGG16 有多大的概念,请意识到其总重量大小为 528 MB,而 MobileNet 的重量大小小于 10MB。
yarn visualize --gpu
一旦在 Node.js 中完成了计算密集的步骤,它们将生成一组图像文件在 dist/folder 中。作为最后一步,yarn visualize
将编译并启动一个 Web 服务器,用于一组静态 Web 文件,包括那些图像,除了在浏览器中打开索引页。
yarn visualize
命令包含一些额外可配置的标志。例如,默认情况下,它对感兴趣的每个卷积层执行八个过滤器的计算和可视化。您可以使用--filters
标志更改过滤器的数量:例如,yarn visualize --filters 32
。此外,yarn visualize
使用的默认输入图像是随源代码提供的 cat.jpg 图像。您可以使用--image
标志使用其他图像文件。^([5]) 现在让我们基于 cat.jpg 图像和 32 个过滤器查看可视化结果。
⁵
最常见的图像格式,包括 JPEG 和 PNG,都受支持。
7.2.1. 可视化卷积神经网络内部激活
在这里,我们计算并显示了给定输入图像的 VGG16 模型的各种卷积层生成的特征图。这些特征图被称为内部激活,因为它们不是模型的最终输出(模型的最终输出是一个长度为 1,000 的向量,表示 1,000 个 ImageNet 类别的概率分数)。相反,它们是模型计算的中间步骤。这些内部激活使我们能够了解输入是如何被网络学习的不同特征分解的。
回顾第四章,卷积层的输出具有 NHWC 形状[numExamples, height, width, channels]
。在这里,我们正在处理单个输入图像,因此numExamples
为 1。我们想要可视化每个卷积层输出的剩余三个维度:高度、宽度和通道。卷积层输出的高度和宽度由其滤波器大小、填充、步长以及图层输入的高度和宽度确定。一般来说,随着深入到卷积神经网络中,它们会变得越来越小。另一方面,随着深入,channels
的值通常会变得越来越大,因为卷积神经网络通过一系列层的表示转换逐渐提取越来越多的特征。卷积层的这些通道不能解释为不同的颜色分量。相反,它们是学习到的特征维度。这就是为什么我们的可视化将它们分成单独的面板并以灰度绘制的原因。图 7.8 展示了给定 cat.jpg 输入图像的 VGG16 的五个卷积层的激活。
图 7.8。VGG16 对 cat.jpg 图像执行推理的几个卷积层的内部激活。左侧显示原始输入图像,以及模型输出的前三个类别和它们关联的概率分数。可视化的五个层分别是命名为block1_conv1
、block2_conv1
、block3_conv2
、block4_conv2
和block5_conv3
的层。它们按照在 VGG16 模型中的深度从顶部到底部的顺序排序。也就是说,block1_conv1
是最靠近输入层的,而block5_conv1
是最靠近输出层的。请注意,出于可视化目的,所有内部激活图像都缩放到相同的大小,尽管由于连续的卷积和池化,后续层的激活具有较小的尺寸(较低的分辨率)。这可以从后续层中的粗略像素模式中看出。
在内部激活中你可能注意到的第一件事是随着网络的深入,它们与原始输入的差异越来越大。较早的层(例如block1_conv1
)似乎编码相对简单的视觉特征,例如边缘和颜色。例如,标记为“A”的箭头指向一个似乎响应黄色和粉色的内部激活。标记为“B”的箭头指向一个似乎与输入图像中某些方向的边缘有关的内部激活。
但是,后面的层(比如block4_conv2
和block5_conv3
)显示出越来越多地与输入图像中简单的像素级特征不相关的激活模式。例如,图 7.8 中标记为“C”的箭头指向block4_ conv2
中的一个滤波器,它似乎对猫的面部特征进行编码,包括耳朵、眼睛和鼻子。这是我们在第四章的图 4.6 中用示意图展示的逐渐特征提取的具体示例。但请注意,并非所有后续层中的滤波器都能用简单的方式用语言解释清楚。另一个有趣的观察是,激活图的“稀疏性”也随着层的深度增加而增加:在图 7.8 中显示的第一层中,所有滤波器都被输入图像激活(显示出非常量像素模式);然而,在最后一层中,一些滤波器变为空白(常量像素模式;例如,参见图 7.8 右面板的最后一行)。这意味着由那些空白滤波器编码的特征在这个特定的输入图像中是不存在的。
您刚刚目睹了深度卷积神经网络学习到的表示的一个重要的普遍特征:通过层提取的特征随着层的深度越来越抽象。深层的激活承载着越来越少关于输入细节的信息,越来越多关于目标的信息(在本例中是图像属于 1,000 个 ImageNet 类别中的哪一个)。因此,深度神经网络有效地充当着一个 信息蒸馏管道,原始数据进入并被重复地转换,以便过滤掉任务无关的方面,并逐渐放大和精炼对任务有用的方面。即使我们通过一个卷积神经网络的例子展示了这一点,但这个特征对其他深度神经网络(如 MLPs)也是成立的。
卷积神经网络发现有用的输入图像方面可能与人类视觉系统发现的有用方面不同。卷积神经网络的训练受到数据驱动,因此容易受到训练数据的偏见影响。例如,在本章末尾“进一步阅读和探索材料”部分列出的 Marco Ribeiro 和同事的论文指出了一个案例,在这个案例中,由于背景中有雪的存在,一张狗的图像被误分类为狼,这可能是因为训练图像中包含了狼在雪地背景下的实例,但没有包含类似背景下的狗的实例。
通过可视化深度卷积神经网络的内部激活模式,我们获得了这些有用的见解。下一小节描述了如何在 TensorFlow.js 中编写代码来提取这些内部激活。
深入了解如何提取内部激活
提取内部激活的步骤封装在 writeInternalActivationAndGetOutput()
函数中(清单 7.8)。它以已经构建或加载的 TensorFlow.js 模型对象和相关层的名称(layerNames
)作为输入。关键步骤是创建一个新的模型对象(compositeModel
),其中包括指定层的输出和原始模型的输出。 compositeModel
使用 tf.model()
API 构建,就像你在 第五章 的 Pac-Man 和简单物体检测示例中看到的一样。关于 compositeModel
的好处在于它的 predict()
方法返回所有层的激活,以及模型的最终预测(参见名为 outputs
的 const
)。清单 7.8 中的其余代码(来自 visualize-convnet/main.js)是关于将层的输出拆分为单独的滤波器并将它们写入磁盘文件的更加平凡的任务。
清单 7.8. 在 Node.js 中计算卷积神经网络的内部激活
async function writeInternalActivationAndGetOutput( model, layerNames, inputImage, numFilters, outputDir) { const layerName2FilePaths = {}; const layerOutputs = layerNames.map(layerName => model.getLayer(layerName).output); const compositeModel = tf.model( ***1*** { inputs: model.input, outputs: layerOutputs.concat(model.outputs[0]) }); const outputs = compositeModel.predict(inputImage); ***2*** for (let i = 0; i < outputs.length - 1; ++i) { const layerName = layerNames[i]; const activationTensors = ***3*** tf.split(outputs[i], outputs[i].shape[outputs[i].shape.length – 1], -1); const actualNumFilters = filters <= activationTensors.length ? numFilters : activationTensors.length; const filePaths = []; for (let j = 0; j < actualNumFilters; ++j) { const imageTensor = tf.tidy( ***4*** () => deprocessImage(tf.tile(activationTensors[j], [1, 1, 1, 3]))); const outputFilePath = path.join( outputDir, `${layerName}_${j + 1}.png`); filePaths.push(outputFilePath); await utils.writeImageTensorToFile(imageTensor, outputFilePath); } layerName2FilePaths[layerName] = filePaths; tf.dispose(activationTensors); } tf.dispose(outputs.slice(0, outputs.length - 1)); return {modelOutput: outputs[outputs.length - 1], layerName2FilePaths}; }
- 1 构建一个模型,返回所有期望的内部激活,以及原始模型的最终输出
- 2 输出是包含内部激活和最终输出的 tf.Tensor 数组。
- 3 将卷积层的激活按滤波器进行拆分
- 4 格式化激活张量并将其写入磁盘
7.2.2. 可视化卷积层对哪些内容敏感:最大激活图像
另一种说明卷积网络学习内容的方式是找到其各种内部层对哪些输入图像敏感。我们所说的对某个输入图像敏感是指在输入图像下,滤波器输出的最大激活(在其输出高度和宽度维度上取平均)。
我们找到最大激活图像的方式是通过一种将“正常”的神经网络训练过程颠倒过来的技巧。图 7.9 的面板 A 简要显示了当我们使用 tf.Model.fit()
训练神经网络时会发生什么。我们冻结输入数据,并允许模型的权重(例如所有可训练层的核和偏差)通过反向传播从损失函数更新。但是,我们完全可以交换输入和权重的角色:我们可以冻结权重,并允许输入通过反向传播进行更新。同时,我们调整损失函数,使其导致反向传播以一种方式来微调输入,该方式最大化了某个卷积滤波器的输出,当在其高度和宽度维度上平均时。该过程在图 7.9 的面板 B 中示意,被称为输入空间中的梯度上升,与 typica 模型训练的基于权重空间中的梯度下降相对应。实现输入空间中的梯度下降的代码将在下一小节中展示,并可以供感兴趣的读者研究。
⁶
这个图可以看作是图 2.9 的简化版本,我们在第二章中用它来介绍反向传播。
图 7.9. 示意图显示了通过输入空间中的梯度上升找到卷积滤波器的最大激活图像的基本思想(面板 B)以及与基于权重空间中的梯度下降的正常神经网络训练过程(面板 A)不同的地方。请注意,该图与先前显示的某些模型图有所不同,因为它将权重从模型中分离出来。这是为了突出两组可以通过反向传播更新的量:权重和输入。
图 7.10 展示了在 VGG16 模型的四个卷积层上执行梯度上升输入空间过程的结果(与我们用来展示内部激活的相同模型)。与先前的插图一样,图层的深度从图的顶部到底部逐渐增加。从这些最大激活输入图像中可以得到一些有趣的模式:
- 首先,这些是彩色图像,而不是前面部分的灰度内部激活图像。这是因为它们的格式是卷积网络的实际输入:由三个(RGB)通道组成的图像。因此,它们可以显示为彩色。
- 最浅的层(
block1_conv1
)对全局颜色值和带有特定方向的边缘等简单模式敏感。 - 中间深度层(如
block2_conv1
)对由不同边缘模式组合而成的简单纹理做出最大响应。 - 在较深层的滤波器开始响应更复杂的模式,这些模式在某种程度上与自然图像中的视觉特征(当然是来自 ImageNet 训练数据)相似,例如颗粒、孔洞、彩色条纹、羽毛、波纹等。
图 7.10. VGG16 深度卷积网络四个层的最大激活输入图像。这些图像是通过在输入空间中进行 80 次梯度上升计算得出来的。
一般来说,随着层级的加深,模式从像素级逐渐变得更加复杂和大规模。这反映了深度卷积网络逐层对特征进行提炼,组合出各种模式。在分析同一层的滤波器时,尽管它们具有类似的抽象级别,但在详细模式上存在相当大的变化。这突显了每一层以互补的方式提出了同一输入的多种表示,以捕获尽可能多的有用信息,从而解决网络训练的任务。
深入了解输入空间中的梯度上升
在可视化卷积网络的例子中,在 main.js 中的 inputGradientAscent()
函数中实现了输入空间中的梯度上升的核心逻辑,并且在 列表 7.9 中进行了展示。由于其耗时和占用内存,该代码运行在 Node.js 中。^([7]) 注意,尽管梯度上升在输入空间中的基本思想类似于基于权重空间的梯度下降的模型训练(参见 图 7.10),但我们不能直接重用 tf.Model.fit()
,因为该函数专门冻结输入并更新权重。相反,我们需要定义一个自定义函数,该函数计算给定输入图像的“损失”。这就是该行定义的函数
⁷
对于小于 VGG16 的卷积网络(如 MobileNet 和 MobileNetV2),可以在合理的时间内在 Web 浏览器中运行该算法。
const lossFunction = (input) => auxModel.apply(input, {training: true}).gather([filterIndex], 3);
这里,auxModel
是一个使用熟悉的tf.model()
函数创建的辅助模型对象。它具有与原始模型相同的输入,但输出给定卷积层的激活。我们调用辅助模型的apply()
方法,以获得层激活的值。apply()
类似于predict()
,因为它执行模型的前向路径。但是,apply()
提供了更细粒度的控制,例如将training
选项设置为true
,就像代码中前一行所做的那样。如果不将training
设置为true
,则不可能进行反向传播,因为默认情况下,前向传播会为内存效率而处置中间层激活。training
标志中的true
值使apply()
调用保留这些内部激活,从而启用反向传播。gather()
调用提取特定滤波器的激活。这是必要的,因为最大激活输入是根据每个过滤器逐个过滤器计算的,并且即使是相同层的过滤器之间的结果也会有所不同(请参见图 7.10 中的示例结果)。
一旦我们有了自定义损失函数,我们就将其传递给tf.grad()
,以便获得一个给出损失相对于输入的梯度的函数:
const gradFunction = tf.grad(lossFunction);
这里要注意的重要事情是,tf.grad()
不直接给出梯度值;相反,它会在调用时返回一个函数(在前一行中称为gradFunction
),该函数在调用时会返回梯度值。
一旦我们有了这个梯度函数,我们就在一个循环中调用它。在每次迭代中,我们使用它返回的梯度值来更新输入图像。这里的一个重要的不明显的技巧是在将梯度值加到输入图像之前对其进行归一化,这确保了每次迭代中的更新具有一致的大小:
const norm = tf.sqrt(tf.mean(tf.square(grads))).add(EPSILON); return grads.div(norm);
这个迭代更新输入图像的过程重复执行了 80 次,得到了我们在图 7.10 中展示的结果。
列表 7.9. 输入空间中的梯度上升(在 Node.js 中,来自 visualize-convnet/main.js)
function inputGradientAscent( model, layerName, filterIndex, iterations = 80) { return tf.tidy(() => { const imageH = model.inputs[0].shape[1]; const imageW = model.inputs[0].shape[2]; const imageDepth = model.inputs[0].shape[3]; const layerOutput = model.getLayer(layerName).output; const auxModel = tf.model({ ***1*** inputs: model.inputs, ***1*** outputs: layerOutput ***1*** }); const lossFunction = (input) => ***2*** auxModel.apply(input, {training: true}).gather([filterIndex], 3); ***2*** const gradFunction = tf.grad(lossFunction); ***3*** let image = tf.randomUniform([1, imageH, imageW, imageDepth], 0, 1) ***4*** .mul(20).add(128); ***4*** for (let i = 0; i < iterations; ++i) { const scaledGrads = tf.tidy(() => { const grads = gradFunction(image); const norm = tf.sqrt(tf.mean(tf.square(grads))).add(EPSILON); return grads.div(norm); ***5*** }); image = tf.clipByValue( image.add(scaledGrads), 0, 255); ***6*** } return deprocessImage(image); }); }
- 1 为原始模型创建一个辅助模型,其输入与原模型相同,但输出为感兴趣的卷积层
- 2 这个函数计算指定过滤器索引处的卷积层输出的值。
- 3 这个函数计算卷积滤波器输出相对于输入图像的梯度。
- 4 生成一个随机图像作为梯度上升的起始点
- 5 重要技巧:将梯度与梯度的大小(范数)相乘
- 6 执行一步梯度上升:沿着梯度方向更新图像
7.2.3. 卷积神经网络分类结果的视觉解释
我们将介绍的最后一个后训练卷积神经网络可视化技术是类激活映射(CAM)算法。CAM 旨在回答的问题是“输入图像的哪些部分对于导致卷积神经网络输出其顶部分类决策起到最重要的作用?”例如,当将 cat.jpg 图像传递给 VGG16 网络时,我们得到了一个“埃及猫”的顶级类别,概率分数为 0.89。但仅凭图像输入和分类输出,我们无法确定图像的哪些部分对于这个决定是重要的。肯定图像的某些部分(如猫的头部)必须比其他部分(例如白色背景)起到更重要的作用。但是否有一种客观的方法来量化任何输入图像的这一点?
答案是肯定的!有多种方法可以做到这一点,CAM 就是其中之一。^([8])给定一个输入图像和一个卷积神经网络的分类结果,CAM 会给出一个热图,为图像的不同部分分配重要性分数。图 7.11 展示了这样的 CAM 生成的热图叠加在三个输入图像上:一只猫,一只猫头鹰和两只大象。在猫的结果中,我们看到猫头的轮廓在热图中具有最高的值。我们可以事后观察到,这是因为轮廓揭示了动物头部的形状,这是猫的一个独特特征。猫头鹰图像的热图也符合我们的预期,因为它突出显示了动物的头部和翅膀。具有两只大象的图像的结果很有趣,因为该图像与其他两个图像不同,它包含了两只个体动物而不是一只。CAM 生成的热图为图像中的两只大象的头部区域分配了高重要性分数。热图明显倾向于聚焦于动物的鼻子和耳朵,这可能反映了长鼻子的长度和耳朵的大小对于区分非洲象(网络的顶级类别)和印度象(网络的第三类别)的重要性。
⁸
CAM 算法首次描述于 Bolei Zhou 等人的“为判别定位学习深度特征”,2016 年,
cnnlocalization.csail.mit.edu/
。另一个知名的方法是局部可解释的模型无关解释(LIME)。见mng.bz/yzpq
。
图 7.11。VGG16 深度卷积神经网络的三个输入图像的类激活映射(CAMs)。CAM 热图叠加在原始输入图像上。
CAM 算法的技术方面
CAM 算法虽然强大,但其背后的思想实际上并不复杂。简而言之,CAM 图中的每个像素显示了如果增加该像素值一单位量,获胜类别的概率分数将发生多大变化。下面稍微详细介绍了 CAM 中涉及的步骤:
- 找到卷积神经网络中最后一个(即最深的)卷积层。在 VGG16 中,这一层的名称为
block5_conv3
。 - 计算网络输出概率对于获胜类别相对于卷积层输出的梯度。
- 梯度的形状为
[1, h, w, numFilters]
,其中h
、w
和numFilters
分别是该层的输出高度、宽度和过滤器数量。然后,我们在示例、高度和宽度维度上对梯度进行平均,得到一个形状为[numFilters]
的张量。这是一个重要性分数的数组,每个卷积层的过滤器都有一个。 - 将重要性分数张量(形状为
[numFilters]
)与卷积层的实际输出值(形状为[1, h, w, numFilters]
)进行乘法运算,并使用广播(参见 附录 B,第 B.2.2 节)。这给我们一个新的张量,形状为[1, h, w, numFilters]
,是层输出的“重要性缩放”版本。 - 最后,平均重要性缩放的层输出沿最后一维(过滤器)进行,并挤压掉第一维(示例),从而得到一个形状为
[h, w]
的灰度图像。该图像中的值是图像中每个部分对于获胜分类结果的重要程度的度量。然而,该图像包含负值,并且比原始输入图像的尺寸要小(例如,在我们的 VGG16 示例中为 14 × 14,而原始输入图像为 224 × 224)。因此,我们将负值归零,并在覆盖输入图像之前对图像进行上采样。
详细代码位于 visualize-convnet/main.js 中名为 gradClassActivationMap()
的函数中。尽管该函数默认在 Node.js 中运行,但它所涉及的计算量明显少于前一节中我们看到的在输入空间中进行梯度上升的算法。因此,您应该能够在浏览器中使用相同的代码运行 CAM 算法,并且速度可接受。
在本章中,我们讨论了两个问题:在训练机器学习模型之前如何可视化数据,以及在训练完成后如何可视化模型。我们有意地跳过了其中一个重要步骤——也就是在模型训练过程中对模型进行可视化。这将成为下一章的重点。我们之所以单独提出训练过程,是因为它与欠拟合和过拟合的概念和现象有关,对于任何监督学习任务来说,这些概念和现象都是至关重要的,因此值得特别对待。通过可视化,我们可以更容易地发现和纠正欠拟合和过拟合问题。在下一章中,我们将重新讨论在本章第一部分介绍的 tfjs-vis 库,并了解到它不仅可以用于数据可视化,还可以显示模型训练的进展情况。
进一步阅读和探索材料
- Marco Tulio Ribeiro, Sameer Singh, and Carlos Guestrin,“为什么我应该相信你?解释任何分类器的预测”,2016 年,
arxiv.org/pdf/1602.04938.pdf
。 - TensorSpace (tensorspace.org) 使用动画 3D 图形在浏览器中可视化卷积神经网络的拓扑和内部激活。它构建在 TensorFlow.js、three.js 和 tween.js 之上。
- TensorFlow.js tSNE 库 (github.com/tensorflow/tfjs-tsne) 是基于 WebGL 的 t-distributed Stochastic Neighbor Embedding (tSNE) 算法的高效实现。它可以帮助您将高维数据集投影到 2D 空间中,同时保留数据中的重要结构。
练习
- 尝试使用
tfjs.vis.linechart()
的以下功能:
- 修改 列表 7.2 中的代码,看看当要绘制的两个系列具有不同的 x 坐标值集合时会发生什么。例如,尝试将第一个系列的 x 坐标值设置为 1、3、5 和 7,将第二个系列的 x 坐标值设置为 2、4、6 和 8。您可以从
codepen.io/tfjs-book/pen/BvzMZr
上分叉并修改 CodePen。 - 在示例 CodePen 中的线图中,所有的数据系列都是由没有重复 x 坐标值的数据点组成的。了解一下
linechart()
函数如何处理具有相同 x 坐标值的数据点。例如,在数据系列中,包括两个具有相同 x 值(例如-5 和 5)的数据点。
- 在 “visualize-convnet” 的例子中,使用
yarn visualize
命令的--image
标志来指定自己的输入图片。由于我们在第 7.2 节中仅使用了动物图片,请尝试探索其他类型的图片内容,例如人物、车辆、家居物品和自然风景。看看你能从内部激活和 CAM 中获得什么有用的见解。 - 在我们计算 VGG16 的 CAM 的示例中,我们计算了相对于最后一个卷积层输出的 胜利 类别的概率分数的梯度。如果我们计算 非胜利 类别(例如较低概率的类别)的梯度会怎样?我们应该期望生成的 CAM 图像 不 强调属于图像实际主题的关键部分。通过修改 visualize-convnet 示例的代码并重新运行确认这一点。具体来说,梯度将计算的类索引作为参数传递给
gradClassActivationMap()
函数在 visualize-convnet/cam.js 中。该函数在 visualize-convnet/main.js 中调用。
摘要
- 我们学习了 tfjs-vis 的基本用法,这是一个与 TensorFlow.js 紧密集成的可视化库。它可以用于在浏览器中呈现基本类型的图表。
- 数据可视化是机器学习不可或缺的一部分。对数据进行高效有效的呈现可以揭示模式并提供否则难以获得的见解,正如我们通过使用 Jena-weather-archive 数据所展示的那样。
- 丰富的模式和见解可以从训练好的神经网络中提取出来。我们展示了
- 可视化深度卷积网络的内部层激活。
- 计算哪些层对最大程度响应。
- 确定输入图像的哪些部分与 convnet 的分类决策最相关。这些帮助我们了解 convnet 学到了什么以及在推断过程中它是如何运作的。
JavaScript 深度学习(三)(3)https://developer.aliyun.com/article/1516963