视频链接:
不如语冰
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)。这部分主要涉及到梯度和损失函数,后面再用专题介绍。