pytorch卷积可视化

Filter and Feature map Image by the author
筛选和特征图作者提供的图像

When dealing with image’s and image data, CNN are the go-to architectures. Convolutional neural networks have proved to provide many state-of-the-art solutions in deep learning and computer vision. Image recognition, object detection, self-driving cars would not be possible without CNN.

w ^ 母鸡交易与图像的图像数据,CNN是去到架构。 卷积神经网络已被证明可以提供深度学习和计算机视觉方面的许多最新解决方案。 没有CNN,图像识别,物体检测,自动驾驶汽车将无法实现。

But when it comes down to how CNN see’s and recognize the image the way they do, things can be trickier.

但是,当涉及到CNN如何看待并以其方式识别图像时,事情可能会变得更加棘手。

  • How a CNN decides whether a image is a cat or dog ?

    CNN如何确定图片是猫还是狗?

  • What makes a CNN more powerful than other models when it comes to image classification problems ?

    在图像分类问题上,什么使CNN比其他模型更强大?

  • How and what do they see in an image ?

    他们如何以及在图像中看到什么?

These were some of the questions i had back back when i first learned about CNN. The questions will grow as you deep dive into it.

这些是我第一次了解CNN时回想的一些问题。 当您深入研究时,问题将会越来越多。

Back then i heard about these terms filters and featuremaps, but dont know what they are and what they do. Later i know what they are but dont know what they look like but now, i know. When dealing with Deep Convolutional Networks filters and featuremaps are important. Filters are what makes the Featuremaps and that’s what the model see’s.

那时我听说过这些术语过滤器和功能图,但不知道它们是什么以及它们做什么。 后来我知道它们是什么,但不知道它们是什么样子,但是现在,我知道了。 在处理深度卷积网络时,过滤器和功能图很重要。 过滤器是构成Featuremap的要素,而这正是模型所看到的。

什么是CNN中的过滤器和FeatureMap? (What are Filters and FeatureMaps in CNN?)

Filters are set of weights which are learned using the backpropagation algorithm. If you do alot of practical deep learning coding, you may know them as kernels. Filter size can be of 3×3 or maybe 5×5 or maybe even 7×7.

˚Filters设置其使用的是BP算法了解到砝码。 如果您进行了大量实用的深度学习编码,则可能将它们称为内核。 过滤器尺寸可以是3×35×5甚至7×7

Filters in a CNN layer learn to detect abstract concepts like boundary of a face, edges of a buildings etc. By stacking more and more CNN layers on top of each other, we can get more abstract and in-depth information from a CNN.

CNN层中的过滤器学习检测抽象概念,例如人脸边界,建筑物边缘等。通过在彼此之上堆叠越来越多的CNN层,我们可以从CNN获得更多抽象和深入的信息。

7×7 and 3×3 filters
7×7和3×3滤镜

Feature Maps are the results we get after applying the filter through the pixel value of the image.This is what the model see’s in a image and the process is called convolution operation. The reason for visualising the feature maps is to gain deeper understandings about CNN.

˚Feature地图是结果通过image.This的像素值应用筛选后我们拿到的是什么模型中看到的一个图像中的过程被称为卷积运算 。 可视化特征图的原因是为了获得对CNN的更深入了解。

Feature map
功能图

选择型号 (Selecting the model)

We will use the ResNet-50 neural network model for visualizing filters and feature maps. Using a ResNet-50 model for visualizing filters and feature maps is not ideal. The reason is that the resnet models in general, are a bit complex. Traversing through the inner convolutional layers can become quite difficult. You will learn how to access the inner convolutional layers of a difficult architecture. In the future, you will feel much more comfortable working with similar or more complex architectures.

我们将使用ResNet-50神经网络模型来可视化过滤器和特征图。 使用ResNet-50模型来可视化过滤器和功能图不是理想的选择。 原因是resnet模型通常比较复杂。 遍历内部卷积层可能变得非常困难。 您将学习如何访问困难体系结构的内部卷积层。 将来,您将在使用类似或更复杂的体系结构时感到更加自在。

The image i used is a photo from pexels. Its a image i collected to train my face-detection classifier.

我使用的图像是来自像素像素的照片。 我收集来训练我的面部检测分类器的图像 。

pixels像素的图像

模型结构 (Model Structure)

At first glance, looking at the model structure can be intimidating, but it is really easy to get what we want. By knowing how to extract the layers of this model, you will be able to extract layers of more complex models. Below is the model structure.

乍一看,看模型结构可能会令人生畏,但真正容易获得我们想要的。 通过了解如何提取此模型的图层,您将能够提取更复杂的模型的图层。 下面是模型结构。

ResNet(  (conv1): Conv2d(3, 64, kernel_size=(7, 7), stride=(2, 2), padding=(3, 3), bias=False)  (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)  (relu): ReLU(inplace=True)  (maxpool): MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1, ceil_mode=False)  (layer1): Sequential(    (0): Bottleneck(      (conv1): Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)      (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (relu): ReLU(inplace=True)      (downsample): Sequential(        (0): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)        (1): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      )    )    (1): Bottleneck(      (conv1): Conv2d(256, 64, kernel_size=(1, 1), stride=(1, 1), bias=False)      (bn1): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (conv2): Conv2d(64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)      (bn2): BatchNorm2d(64, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (conv3): Conv2d(64, 256, kernel_size=(1, 1), stride=(1, 1), bias=False)      (bn3): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (relu): ReLU(inplace=True)...(2): Bottleneck(      (conv1): Conv2d(2048, 512, kernel_size=(1, 1), stride=(1, 1), bias=False)      (bn1): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (conv2): Conv2d(512, 512, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1), bias=False)      (bn2): BatchNorm2d(512, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (conv3): Conv2d(512, 2048, kernel_size=(1, 1), stride=(1, 1), bias=False)      (bn3): BatchNorm2d(2048, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)      (relu): ReLU(inplace=True)    )  )  (avgpool): AdaptiveAvgPool2d(output_size=(1, 1))  (fc): Linear(in_features=2048, out_features=1000, bias=True)

提取CNN层 (Extracting the CNN layers)

  • First, at line 4, we initialize a counter variable to keep track of the number of convolutional layers.

    首先,在第4行 ,我们初始化一个counter变量以跟踪卷积层的数量。

  • Starting from line 6, we are going through all the layers of the ResNet-50 model.

    第6行开始,我们将遍历ResNet-50模型的所有层。

  • Specifically, we are checking for convolutional layers at three levels of nesting具体来说,我们正在检查三个嵌套级别的卷积层
  • Line 7, checks if any of the direct children of the model is a convolutional layer.

    第7行 ,检查模型的任何直接子级是否为卷积层。

  • Then from line 10, we check whether any of the Bottleneck layer inside the Sequential blocks contain any convolutional layers.

    然后从第10行开始 ,检查Sequential块内的任何Bottleneck层是否包含任何卷积层。

  • If any of the above two conditions satisfy, then we append that child node and the weights to the conv_layers and model_weights respectively,

    如果以上两个条件中的任何一个都满足,则我们将该子节点和权重分别附加到conv_layersmodel_weights上,

The above code is simple and self-explanatory but it is limited to pre-existing models like other resnet model resnet-18, 34, 101, 152. For a custom model ,things will be different ,lets say there is a Sequential layer inside another Sequential layer and if there is a CNN layer it will be unchecked by the program. This is where the extractor.py module i wrote can be useful.

上面的代码很简单,不言自明,但仅限于像其他resnet模型resnet-18、34、101、152这样的现有模型。对于自定义模型,情况将有所不同,可以说内部有一个顺序层。另一个顺序层,如果有CNN层,程序将不对其进行检查。 这是我编写的extractor.py模块有用的地方。

提取器类 (Extractor class)

The Extractor class can find every CNN layer(except down-sample layers) including their weights in any resnet model and almost in any custom resnet and vgg model. Its not limited to CNN layers, it can find Linear layers and if the name of the Down-sampling layer is mentioned, it can find that too. It can also give some useful information like the number of CNN, Linear and Sequential layers in a model.

Extractor类可以在任何resnet模型以及几乎任何自定义的resnet和vgg模型中找到每个CNN层(向下采样层除外),包括它们的权重。 它不限于CNN层,它可以找到线性层,并且如果提到下采样层的名称,它也可以找到。 它还可以提供一些有用的信息,例如模型中的CNN,线性层和顺序层的数量。

如何使用 (How to use)

In the Extractor class the model parameter takes in a model and the DS_layer_name parameter is optional. The DS_layer_name parameter is to find the down-sampling layer, normally in resnet layer the name will be ‘downsample’ so it is kept as default.

在Extractor类中,模型参数接受模型,而DS_layer_name参数是可选的。 DS_layer_name参数用于查找下采样层,通常在resnet层中,名称为“ downsample”,因此将其保留为默认值。

extractor = Extractor(model = resnet, DS_layer_name = 'downsample')

The code extractor.activate() is to activate the program.

代码extractor.activate()用于激活程序。

You can get relevant details in a dictionary by calling extractor.info()

您可以通过调用extractor.info()获取字典中的相关详细信息。

{'Down-sample layers name': 'downsample', 'Total CNN Layers': 49, 'Total Sequential Layers': 4, 'Total Downsampling Layers': 4, 'Total Linear Layers': 1, 'Total number of Bottleneck and Basicblock': 16, 'Total Execution time': '0.00137 sec'}

访问权重和图层 (Accessing the weights and the layers)

extractor.CNN_layers -----> Gives all the CNN layers in a modelextractor.Linear_layers --> Gives all the Linear layers in a modelextractor.DS_layers ------> Gives all the Down-sample layers in a model if there are anyextractor.CNN_weights ----> Gives all the CNN layer's weights in a modelextractor.Linear_weights -> Gives all the Linear layer's weights in a model

Without any coding you can get CNN and Linear layers and their weights in almost every resnet model. Below is what the class methods looks like , there is more, do go through the entire script.

无需任何编码,您几乎可以在每个resnet模型中获得CNN和Linear图层及其权重。 下面是类方法的样子,还有更多,请仔细阅读整个脚本。

可视化 (Visualizing)

卷积层过滤器 (Convolutional Layer Filters)

Here we will visualize the convolutional layer filters. For simplicity, we will only visualize the filters of the first convolutional layer.

在这里,我们将可视化卷积层过滤器。 为了简单起见,我们将仅可视化第一卷积层的过滤器。

We are looping through the model weights of the first layer. For the first layer the filter size is 7×7 and there are 64 channels(hidden layers).

我们正在遍历第一层的模型权重。 对于第一层,过滤器大小为7×7,并且有64个通道(隐藏层)。

7×7 filter
7×7过滤器
7×7 filters from trained resnet-50 model
来自受过训练的resnet-50模型的7×7过滤器

The pixel values for each small boxes is between 0 to 255. 0 being complete black and 255 being white. The range can be different like between 0 to 1 or -1 to 1 with 0 as the mean.

每个小盒子的像素值在0到255之间。0为全黑,而255为白。 范围可以不同,例如0到1或-1到1,平均值为0。

要素图 (The Feature Maps)

Transformin g ^ (Transforming)

To visualize the feature maps, first the image need to be converted to a tensor image. Using the transforms from torchvision the image can be normalized and transformed to a tensor.

为了可视化特征图,首先需要将图像转换为张量图像。 使用来自火炬视觉的变换,可以将图像标准化并变换为张量。

The last line after the transforms means applying the transforms to the image. You can create a new variable and then apply it, but make sure to change the variable name. And the .unsqueeze(0) is to add an extra dimension to the tensor img. Adding the batch dimension is an important step. Now the size of the image, instead of being [3, 128, 128], is [1, 3, 128, 128], indicating that there is only one image in the batch.

变换后的最后一行表示将变换应用于图像。 您可以创建一个新变量,然后应用它,但请确保更改变量名称。 .unsqueeze(0)用于为张量img添加额外的尺寸。 添加批次尺寸是重要的步骤。 现在,图像的大小不是[3, 128, 128] ,而是[1, 3, 128, 128] ,指示批次中只有一个图像。

使输入图像通过每个卷积层 (Passing the Input Image Through Each Convolutional Layer)

The below code will pass the image through each convolutional layer.

以下代码将使图像通过每个卷积层。

We will first give the image as an input to the first convolutional layer. After that, we will use a for loop to pass the last layer’s outputs to the next layer, until we reach the last convolutional layer.

我们首先将图像作为第一卷积层的输入。 之后,我们将使用for循环将最后一层的输出传递到下一层,直到到达最后一个卷积层。

  • At line 1, we give the image as input to the first convolutional layer.

    第1行 ,我们将图像作为输入输入到第一卷积层。

  • Then we iterate from through the second till the last convolutional layer using a for loop.

    然后,我们使用for循环从第二个卷积层到最后一个卷积层进行迭代。

  • We give the last layer’s output as the input to the next convolutional layer (featuremaps[-1]).

    我们将最后一层的输出作为下一个卷积层( featuremaps[-1 ])的输入。

  • Also, we append each layer’s output to the featuremaps list.

    另外,我们将每个图层的输出附加到featuremaps列表。

可视化特征图 (Visualizing the Feature Maps)

This is the final step. We will write the code to visualize the feature maps. Notice that the final cnn layer have many feature maps, in the range of 512 to 2048. But we will only visualize 64 feature maps from each layer as any more than that will make the outputs really cluttered.

这是最后一步。 我们将编写代码以可视化要素地图。 请注意,最后的cnn图层具有许多要素图,范围在512到2048之间。但是,我们将仅可视化每个图层的64个要素图,因为这将使输出真正混乱。

  • Starting from line 2, we iterate through the featuremaps.

    第2行开始,我们遍历featuremaps

  • Then we get layers as featuremaps[x][0, :, :, :].detach() .

    然后,我们将layers作为featuremaps[x][0, :, :, :].detach()

  • Starting from line 5, we iterate through the filters in each layers. We break out of the loop if it is the 64th feature map.

    第5行开始,我们遍历每layers的过滤器。 如果它是第64个要素图,我们将跳出循环。

  • After that we plot the feature map, and save them if necessary.之后,我们绘制特征图,并在必要时保存它们。

结果 (Results)

Feature maps from the first convolutional layer of ResNet-50 model
来自ResNet-50模型的第一卷积层的特征图

You can see that different filters focus on different aspects while creating the feature map of an image.

您可以看到在创建图像的特征图时,不同的滤镜专注于不同的方面。

Some feature maps focus on the background of the image. Some others create an outline of the image. A few filters create feature maps where the background is dark but the image of the face is bright. This is due to the corresponding weights of the filters. It is very clear from the above image that in the deep layers, the neural network gets to see very detailed feature maps of the input image.

一些功能贴图集中在图像的背景上。 其他一些则创建图像的轮廓。 一些滤镜会创建要素图,其中背景较暗,但脸部图像较亮。 这是由于过滤器的相应重量。 从上面的图像很清楚,在较深的层中,神经网络可以看到输入图像的非常详细的特征图。

Let’s take a look at a few other feature maps.

让我们看一下其他一些功能图。

Feature maps from the 20th and 10th convolutional layer of ResNet-50 model
ResNet-50模型的第20和第10卷积层的特征图
Feature maps from the 40th and 30th convolutional layer of ResNet-50 model
ResNet-50模型的第40和第30卷积层的特征图

You can observe that as the image progresses through the layers the details from the images slowly disappears. They look like noise, but surely there is a pattern in those feature maps which human eyes cannot detect, but a neural network can.

您可以观察到,随着图像逐步穿过图层,图像中的细节逐渐消失。 它们看起来像噪声,但可以肯定的是,在这些特征图中,人眼无法检测到某种模式,但是神经网络可以检测到。

By the time the image reaches the last convolutional layer then it is impossible for a human being to tell what that is. These last layer outputs are really important for the fully connected neurons which basically form the classification layers in a convolutional neural network.

到图像到达最后一个卷积层时,人类就不可能知道那是什么。 这些最后一层的输出对于完全连接的神经元非常重要,这些神经元基本上形成了卷积神经网络中的分类层。

结论 (Conclusions)

A big thanks to @sovitrath5 author of machine learning blog DebuggerCafe for the content.

非常感谢机器学习博客DebuggerCafe的作者@ sovitrath5提供的内容。

翻译自: https://medium.com/swlh/visualizing-filters-and-feature-maps-in-convolutional-neural-networks-using-pytorch-110d4c1cfdeb

pytorch卷积可视化


http://www.taodudu.cc/news/show-863878.html

相关文章:

  • u-net语义分割_使用U-Net的语义分割
  • 地理空间数据
  • 嵌入式系统分类及其应用场景_词嵌入及其应用简介
  • hotelling变换_基于Hotelling-T²的偏最小二乘(PLS)中的变量选择
  • 命名实体识别 实体抽取_您的公司为什么要关心命名实体的识别
  • 机器学习 异常值检测_异常值是否会破坏您的机器学习预测? 寻找最佳解决方案
  • yolov3算法优点缺点_优点缺点
  • 主成分分析具体解释_主成分分析-现在用您自己的术语解释
  • netflix 数据科学家_数据科学和机器学习在Netflix中的应用
  • python画交互式地图_使用Python构建交互式地图-入门指南
  • 大疆 机器学习 实习生_我们的数据科学机器人实习生
  • ai人工智能的本质和未来_人工智能的未来在于模型压缩
  • tableau使用_使用Tableau探索墨尔本房地产市场
  • 谷歌云请更正这张卡片的信息_如何识别和更正Google Analytics(分析)报告中的(未设置)值
  • 科技情报研究所工资_我们所说的情报是什么?
  • 手语识别_使用深度学习进行手语识别
  • 数据科学的5种基本的面向业务的批判性思维技能
  • 大数据技术 学习之旅_数据-数据科学之旅的起点
  • 编写分段函数子函数_编写自己的函数
  • 打破学习的玻璃墙_打破Google背后的创新深度学习
  • 向量 矩阵 张量_张量,矩阵和向量有什么区别?
  • monk js_使用Monk AI进行手语分类
  • 辍学的名人_辍学效果如此出色的5个观点
  • 强化学习-动态规划_强化学习-第5部分
  • 查看-增强会话_会话式人工智能-关键技术和挑战-第2部分
  • 我从未看过荒原写作背景_您从未听说过的最佳数据科学认证
  • nlp算法文本向量化_NLP中的标记化算法概述
  • 数据科学与大数据排名思考题_排名前5位的数据科学课程
  • 《成为一名机器学习工程师》_如何在2020年成为机器学习工程师
  • 打开应用蜂窝移动数据就关闭_基于移动应用行为数据的客户流失预测

pytorch卷积可视化_使用Pytorch可视化卷积神经网络相关推荐

  1. pytorch 反卷积 可视化_手推反卷积

    先手推卷积热个身 在推导反卷积之前,先推导一下卷积. 假设输入为 ,卷积核为 ,输出大小的计算公式为 .当 时,输出为 . 将输入矩阵转成一个 的列阵,卷积核扩展为 的矩阵,即 则 , 所以 . 用p ...

  2. 基于plotly数据可视化_[Plotly + Datashader]可视化大型地理空间数据集

    基于plotly数据可视化 简介(我们将创建的内容): (Introduction (what we'll create):) Unlike the previous tutorials in thi ...

  3. pytorch保存准确率_初学Pytorch:MNIST数据集训练详解

    前言 本文讲述了如何使用Pytorch(一种深度学习框架)构建一个简单的卷积神经网络,并使用MNIST数据集(28*28手写数字图片集)进行训练和测试.针对过程中的每个步骤都尽可能的给出了详尽的解释. ...

  4. julia有 pytorch包吗_用 PyTorch 实现基于字符的循环神经网络 | Linux 中国

    导读:在过去的几周里,我花了很多时间用 PyTorch 实现了一个 char-rnn 的版本.我以前从未训练过神经网络,所以这可能是一个有趣的开始. 本文字数:7201,阅读时长大约: 9分钟 htt ...

  5. 人口密度可视化_使用GeoPandas可视化菲律宾的人口密度

    人口密度可视化 GeoVisualization /菲律宾. (GeoVisualization /Philippines.) Population density is a crucial conc ...

  6. c语言可视化_这些算法可视化网站助你轻松学算法

    前言 无疑,数据结构与算法学习最大的难点之一就是如何在脑中形象化其抽象的逻辑步骤.而图像在很多时候能够大大帮助我们理解其对应的抽象化的东西,而如果这个图像还是我们自己一点点画出来的,那么无疑这个印象是 ...

  7. myeclipse java可视化_使用MyEclipse可视化开发Hibernate实例

    使用MyEclipse可视化开发Hibernate实例 2.7节的例子源代码在配套光盘sourcecode/workspace目录的chapter02_first项目中. 这个实例主要演示如何使用My ...

  8. bert pytorch源码_【PyTorch】梯度爆炸、loss在反向传播变为nan

    点击上方"MLNLP",选择"星标"公众号 重磅干货,第一时间送达 作者丨CV路上一名研究僧 知乎专栏丨深度图像与视频增强 地址丨https://zhuanla ...

  9. pytorch 矩阵相乘_深入浅出PyTorch(算子篇)

    Tensor 自从张量(Tensor)计算这个概念出现后,神经网络的算法就可以看作是一系列的张量计算.所谓的张量,它原本是个数学概念,表示各种向量或者数值之间的关系.PyTorch的张量(torch. ...

最新文章

  1. 一款N-沟道耗尽型JFET晶体管 MPF102
  2. 中点坐标公式 矩形_二次函数中矩形的存在性问题
  3. [LeetCode] Maximum Subarray
  4. GC分析工具使用-gceacy分析堆栈
  5. pip list和pip freeze的区别(列出所有包,列出包的requirements格式)
  6. 最小公倍数与最大公约数
  7. java两个和三个_Java语言基础(day_03)
  8. kubunetes packages.cloud.google.com gpg:no valid OpenPGP data found
  9. MAC 下的SVN客户端 Versions、SmartSVN、Cornerstone
  10. git提交代码时账号或密码错误
  11. 模型的骨骼动画技术讲解
  12. Contrast Preserving Decolorization
  13. 计算机右键无法新建excel,电脑右键新建没有excel表格
  14. 网络虚拟(包括overlay、underlay介绍)
  15. git android pdk,Android源码下载,Syncing work tree: error
  16. gensim : AttributeError: The vocab attribute was removed from KeyedVector in Gensim 4.0.0.
  17. 基于战舰V3的LCD显示实验详解和剖析
  18. R语言中调用windows中的字体方法
  19. C语言 递归求n的阶乘和
  20. 有了LIGO,引力波不仅可以被探测,还可以被发射!

热门文章

  1. 查看centos当前版本
  2. 漫谈数据仓库之维度建模
  3. 一个月按多少天计算日工资合理
  4. Gigabit Ethernet复制数据会异常的缓慢
  5. 领地柜怎么砸_5㎡餐厅也配有餐边柜!布局、尺寸都给你们准备好,照着装准没错...
  6. Android闹钟动画,学习Android闹钟源代码(三)-AlarmClock类分析(part1)
  7. db2v9/9.5高级应用开发_macOS 10.15 全新音乐 app 将基于 iTunes 开发,原生 Mac 应用
  8. xdebug怎样在php中配置,教你在PHPStorm中配置Xdebug
  9. php 定义函数和访问,PHP-预定义函数访问数据库
  10. Codeforces Round #573 (Div. 2)(ABCD)