Bootstrap

深度学习基础(2024-10-30更新到tensor相关)

1. 名词解释

FFN

  • FFN : Feedforward Neural Network,前馈神经网络
  • 馈神经网络是一种基本的神经网络架构,也称为多层感知器(Multilayer Perceptron,MLP)
  • FFN 一般主要是包括多个全连接层(FC)的网络,其中,全连接层间可以包含 : 激活层、BN层、Dropout 层。

MLP 与 FFN 的区别

在机器学习和深度学习中,MLP(多层感知机)和 FFN(前馈神经网络)在很大程度上可以视为同义词,都指代了一个具有多个层的前馈神经网络结构。

  • MLP(多层感知机)更偏向于表达网络结构(多个全连接层)
  • FFN(前馈神经网络)更偏向于表达数据以前馈的方式流动

MLP 和 FFN 通常指的是只包含全连接层 和激活函数的神经网络结构。这两者都是基本的前馈神经网络类型,没有包含卷积层或其他复杂的结构。

Logit

“Logit” 通常指的是神经网络中最后一个隐藏层的输出,经过激活函数之前的值。比如:

  • 对于二分类问题,logit 是指网络输出的未经过 sigmoid 函数处理的值
  • 对于多分类问题,logit 是指网络输出的未经过 softmax 函数处理的值

NLL

NLL 是 Negative Log-Likelihood(负对数似然)的缩写。
在深度学习中,特别是在分类问题中,NLL 经常与交叉熵损失(Cross-Entropy Loss)等价使用。

Anchor Box 与 Anchor Point

  • Anchor box 通常表示 一个包含位置和大小信息的四元组 ( x , y , w , h ) (x, y, w, h) (x,y,w,h),而 Anchor point 通常表示 一个二元组 ( x , y ) (x, y) (x,y)。 其中, x x x y y y表示框的中心坐标, w w w h h h表示框的宽度和高度。
  • Anchor box 是目标检测中用于定义目标位置和大小的一种方式。而 Anchor point 主要用于在图像上生成 anchor box 的位置,生成的 anchor box 会在 anchor point 的周围不同尺寸和宽高比的情况下进行缩放,形成一系列不同形状的框。

parameter efficient

参数效率高,指的是网络在达到良好性能的同时所使用的参数数量较少。

Deep Supervision

Deep Supervision 是一种训练策略,旨在提高网络的梯度流动,并促使网络更快地收敛,并且有助于缓解梯度消失问题。Deep Supervision 的核心思想是在网络的不同层中引入额外的监督信号,而不仅仅在最后一层输出进行监督训练。具体来说:Deep Supervision 会使用网络的中间层输出,计算出一部分损失函数,然后和网络最后一层的损失函数一起,对网络的参数进行优化。

DP 与 DDP

DP : DataParallel,数据并行
DDP :Distributed Data Parallel,分布式数据并行

感受野(Receptive Field)

1、介绍

感受野(receptive field)是卷积神经网络输出特征图上的像素点在原始图像上所能看到的(映射的)区域的大小,它决定了该像素对输入图像的感知范围(获取信息的范围)。较小的感受野可以捕捉到更细节的特征,而较大的感受野可以捕捉到更全局的特征。
在这里插入图片描述
如果连续进行 2次卷积操作,卷积核大小都为 3x3,stride=1, padding=0, 如下图,layer3上的每一个像素点在 layer1上的感受野 为 5x5
在这里插入图片描述

2、感受野计算公式

感受野计公式 : F ( i ) = ( F ( i + 1 ) − 1 ) × S t r i d e + K s i z e F(i)=(F(i+1)-1)\times Stride + Ksize F(i)=F(i+1)1×Stride+Ksize F i n = ( F o u t − 1 ) × S t r i d e + K s i z e F_{in}=(F_{out}-1)\times Stride + Ksize Fin=Fout1×Stride+Ksize
其中:

  • F ( i ) F(i) F(i) :在第 i i i层的感受野
  • S t r i d e Stride Stride:第 i i i层步距
  • K s i z e Ksize Ksize:第 i i i层卷积或池化的 kernel size

3、计算举例

求 :layer3 上的每个像素在 layer1 上的感受野。
在这里插入图片描述
1)先来计算 layer3 上的一个像素( F ( 3 ) = 1 F(3)=1 F(3)=1)在 layer2 上的感受野 :
F ( 2 ) = ( F ( 3 ) − 1 ) × S t r i d e + K s i z e = ( 1 − 1 ) × 2 + 2 = 2 F(2) = (F(3)-1) \times Stride + Ksize = (1 -1) \times 2 + 2 = 2 F(2)=(F(3)1)×Stride+Ksize=(11)×2+2=2

2)计算 layer3 上的一个像素( F ( 3 ) = 1 ,    F ( 2 ) = 2 F(3)=1, \; F(2)=2 F(3)=1F(2)=2 )在 layer1 上的感受野 :
F ( 1 ) = ( F ( 2 ) − 1 ) × S t r i d e + K s i z e = ( 2 − 1 ) × 2 + 3 = 5 F(1)=(F(2)-1)\times Stride + Ksize =(2 -1)\times 2 + 3 = 5 F(1)=(F(2)1)×Stride+Ksize=(21)×2+3=5

如果仅计算 layer2 上的一个像素( F(2)=1 )在 layer1 上的感受野 :
F ( 1 ) = ( F ( 2 ) − 1 ) × S t r i d e + K s i z e = ( 1 − 1 ) × 2 + 3 = 3 F(1)=(F(2)-1)\times Stride + Ksize = (1 -1)\times 2 + 3 = 3 F(1)=F(2)1×Stride+Ksize=11×2+3=3

2. tensor 相关

tensor 内部存储结构

1、数据区域和元数据

PyTorch 中的 tensor 内部结构通常包含了 数据区域(Storage) 和 元数据(Metadata) :

  • 数据区域 : 存储了 tensor 的实际数据,且数据被保存为连续的数组。比如: a = torch.tensor([[1, 2, 3], [4, 5, 6]]),它的数据在存储区的保存形式为 [1, 2, 3, 4, 5, 6]
  • 元数据 :包含了 tensor 的一些描述性信息,比如 : 尺寸(Size)、步长(Stride)、数据类型(Data Type) 等信息

占用内存的主要是 数据区域,且取决于 tensor 中元素的个数, 而元数据占用内存较少。
采用这种 【数据区域 + 元数据】 的数据存储方式,主要是因为深度学习的数据动辄成千上万,数据量巨大,所以采取这样的存储方式以节省内存
在这里插入图片描述


2、查看 tensor 的存储区数据: storage()

虽然 .storage() 方法即将被弃用,而是改用 .untyped_storage(),但为了笔记中展示方便,我们仍然使用 .storage() 方法。.untyped_storage() 方法的输出太长了,不方便截图放在笔记中。

a = torch.tensor([[1, 2, 3],
                  [4, 5, 6]])

print(a.storage())

在这里插入图片描述


3、查看 tensor 的步长: stride()

stride() : 在指定维度 (dim) 上,存储区中的数据元素,从一个元素跳到下一个元素所必须的步长

a = torch.randn(3, 2)
print(a.stride())  # (2, 1)

解读:
在这里插入图片描述
在第 0 维,想要从一个元素跳到下一个元素,比如从 a[0][0] 到 a[1][0] ,需要经过 2个元素,步长是 2
在第 1 维,想要从一个元素跳到下一个元素,比如从 a[0][0] 到 a[0][1], 需要经过 1个元素,步长是 1

4、查看 tensor 的偏移量:storage_offset()

表示 tensor 的第 0 个元素与真实存储区的第 0 个元素的偏移量

a = torch.tensor([1, 2, 3, 4, 5])
b = a[1:]   # tensor([2, 3, 4, 5])
c = a[3:]   # tensor([4, 5])
print(b.storage_offset())   # 1
print(c.storage_offset())   # 3
  • b 的第 0 个元素与 a 的第 0 个元素之间的偏移量是 1
  • c 的第 0 个元素与 a 的第 0 个元素之间的偏移量是 3

5、代码举例

  • 一般来说,一个 tensor 有着与之对应的 storage, storage 是在 data 之上封装的接口。

  • 不同 tensor 的元数据一般不同,但却可能使用相同的 storage。

  • data_ptr()

    • 返回的是张量数据 (storage 数据)存储的实际内存地址,确切来说是张量数据的起始内存地址。
    • data_ptr 中的 ptr 是 pointer(指针)的缩写,对应于 C 语言中的指针,因为 Python 的底层就是由 C 实现的
  • id(a)

    • 返回的是 a 在 Python 内存管理系统中的唯一标识符。虽然这个标识符通常与对象的内存地址有关,但它并不直接表示内存地址。

1)观察一

import torch

a = torch.arange(0, 6)
print('a = {}\n'.format(a))
print('tensor a 存储区的数据内容 :{}\n'.format(a.storage()))
print('tensor a 相对于存储区数据的偏移量 :{}\n'.format(a.storage_offset()))

print('*'*20, '\n')

b = a.view(2,3)
print('b = {}\n'.format(b))
print('tensor b 存储区的数据内容 :{}\n'.format(b.storage()))
print('tensor b 相对于存储区数据的偏移量 :{}\n'.format(b.storage_offset()))

在这里插入图片描述
2)观察二

import torch

a = torch.tensor([1, 2, 3, 4, 5, 6])
b = a.view(2, 3)

print(a.data_ptr())   # 140623757700864
print(b.data_ptr())   # 140623757700864

print(id(a))   # 4523755392
print(id(b))   # 4602540464

在这里插入图片描述

  • a.data_ptr()b.data_ptr() 一样,说明 tensor a 和 tensor b 共享相同的存储区,即,它们指向相同的底层数据存储对象。
  • id(a)id(b) 不一样,是因为虽然 a 和b 共享storage 数据,但是 它们 有不同的 size 或者 strides 、 storage_offset 等其他属性

3)观察三

import torch

a = torch.tensor([1, 2, 3, 4, 5, 6])
c = a[2:]

print(c.storage())

print('\n', '*'*20, '\n')

print('tensor a 首元素的内存地址 : {}'.format(a.data_ptr()))
print('tensor c 首元素的内存地址 : {}'.format(c.data_ptr()))
print(c.data_ptr() - a.data_ptr())

print('\n', '*'*20, '\n')

c[0] = -100
print(a)

在这里插入图片描述

  • data_ptr() 返回 tensor 首元素的内存地址
  • c 和 a 的首元素内存地址相差 16,每个元素占用 8 个字节(LongStorage), 也就是首元素相差两个元素
  • 改变 c 的首元素, a 对应位置的元素值也被改变

6、总结

  1. 由上可知,绝大多数操作并不修改 tensor 的数据,只是修改了 tensor 的元数据,比如修改 tensor 的 offset 、stride 和 size ,这种做法更节省内存,同时提升了处理速度。
  2. 有些操作会导致 tensor 不连续,这时需要调用 torch.contiguous 方法将其变成连续的数据,该方法会复制数据到新的内存,不再与原来的数据共享 storage。
;