视频链接:

不如语冰

https://space.bilibili.com/70431433?spm_id_from=333.1007.0.0

文章代码链接:

GitHub - zyf-ngu/Qmatter: 跟着问题学-AI大模型入门系列笔记和代码 · GitHub

tensor张量的创建与基本操作

元组和列表

定义一个普通列表,用中括号表示;

listvar = [111,3,13,True,3+4j,"abc"]print (listvar,type(listvar))

列表的特点:可获取,可修改,有序

定义一个普通元组

tuplevar = (False,3+4j,“aaa”,456)

元组特点:可获取、不可修改、有序

综上来看,列表和元组存储的数据类型都是多种多样的,最大的区别就是列表创建完成之后可以修改,而元组一旦创建之后不可修改(但是两个元组之间可以连接)。

后面我们将看到张量的很多操作,其参数是以元组或者列表传递的。

张量Tensor的定义

张量在形式上就是多维数组,例如标量就是0维张量,向量就是一维张量,矩阵就是二维张量,而三维张量就可以想象RGB图片,每个channel是一个二维的矩阵,共有三个channel,还可以考虑更多。

在进行张量的各种操作时,需要牢牢掌握张量的两个特性,

维度dimension:可以理解为一个坐标轴,从0开始计算。张量维度更加抽象:可以表示非空间概念(如批次大小、特征通道、时间步长等),在代码中,每一个中括号代表一个维度,深度学习中一般会有3-4个维度(batch_size,channel,width,height)

形状shape:对应维度的数值大小。

在张量里面我们描述某个维度的大小用形状,但是当我们特定描述张量里某个向量的时候,又会说向量的维度大小,要注意区分。

举个例子来说,张量a的形状为(1,2,3),则该张量有4维,第0,1,2,3维的形状大小分别为1,2,3,4.特别注意这两个特性,因为张量的所有操作都是针对这两个特性的。

下面重点探讨一下张量的维度和形状。

对于一维张量(从0开始计数,即第0维),可以理解为向量,对应形状为(s0),而当期形状长度为1的时候,可以理解为标量(也可以认为是0维张量);在nlp中对应一个单词的词向量d_model;

对于二维张量,可以理解为二维矩阵,对应形状为(s0,s1),在nlp中对应一个序列的词向量,[n_seq,d_model]。在图像处理中对应[width,height]

这里出现一个容易混淆的点,形状(3,)(元组(Tuple)只有 1 个元素时,必须在元素后加逗号,否则 Python 会将其识别为 “元素本身”,而非元组类型。)和形状(1,3)分别对应的是一维张量和二维张量,其中二维张量的第0维形状长度为1。

两者在内存中的存储完全一致,都是连续存储的3个元素

(3,)表示 同质元素的集合:如3个像素值、3个温度读数

(3,1) 表示 结构化数据:3个独立实体(行)

每个实体有1个特征(列)

对于三维张量,可以理解为二维张量并排成立体,对应形状为(s0,s1,s2),在nlp中对应[batch_size,n_seq,d_model],在图像处理中对应[channel,width,height],

对于四维张量,可以理解为一组三维张量立体,在nlp中可以对应为[batch_size,n_seq,n_head,d_model]

更高维的也可以认为是低维度的某种组织结构,事实上,三维及以上均可以认为是二维矩阵以某种嵌套结构组织得到的。以形状为 (2, 3, 2, 4)的四维张量为例,其表示该张量由 2 个大小为 (3, 2, 4) 的子张量组成,每个子张量又有 3 个大小为 (2, 4) 的二维矩阵。

让我们通过几个具体例子来理解这个概念:

示例1:将3D张量理解为2D张量的堆叠

假设我们有一个形状为 [2, 3, 4] 的3D张量(例如:2张图像,每张3×4像素):

# 创建一个3D张量

tensor_3d = torch.tensor([

    # 第一个2D张量(矩阵)

    [

        [1, 2, 3, 4],

        [5, 6, 7, 8],

        [9, 10, 11, 12]

    ],

    # 第二个2D张量(矩阵)

    [

        [13, 14, 15, 16],

        [17, 18, 19, 20],

        [21, 22, 23, 24]

    ]])

print("3D张量形状:", tensor_3d.shape)  # torch.Size([2, 3, 4])

这个3D张量可以理解为:2个3×4的矩阵沿着一个新的(批次)维度堆叠而成

示例2:将4D张量理解为2D张量的嵌套堆叠

在计算机视觉中,一个典型的4D张量形状是 [batch_size, channels, height, width]:

# 假设一个4D张量:2张RGB图像,每张3×4像素

tensor_4d = torch.randn(2, 3, 3, 4)

print("4D张量形状:", tensor_4d.shape)  # torch.Size([2, 3, 3, 4])

这个4D张量可以理解为:

最外层:2个样本(批次维度)

第二层:每个样本有3个通道(R、G、B)

最内层:每个通道是一个3×4的矩阵

换句话说,这是2×3=6个3×4矩阵,以特定的层次结构组织起来。

内存中的实际存储方式

理解这一点至关重要:无论张量有多少维,它在内存中总是以连续的一维数组形式存储

多维张量是通过"步幅"(strides)和"形状"(shape)信息来解释这一维数组的:

# 查看张量的内存布局信息

tensor = torch.randn(2, 3, 4)

print("形状:", tensor.shape)    # 如何解释数据

print("步幅:", tensor.stride()) # 如何访问数据

输出可能类似于:

形状: torch.Size([2, 3, 4])

步幅: (12, 4, 1)

这意味着:

沿维度0移动1步,需要在内存中移动12个元素

沿维度1移动1步,需要移动4个元素

沿维度2移动1步,需要移动1个元素

在调用张量api操作时往往有两种形式,一是t.api_name(arg),二是api_name(t,arg)

在代码中创建张量Tensor数据类型时,除了封装张量本身的数据data外,还会附加张量的一些性质和操作,例如数据的梯度(grad),创建tensor的函数(grad_fun,是求导的关键),是否为叶子节点(is_leaf),是否需要梯度(require_grad)。这部分主要涉及到梯度和损失函数,后面再用专题介绍。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐