文章目录
硬件配置:
- Windows 11
- Intel®Core™i7-12700H
- NVIDIA GeForce RTX 3070 Ti Laptop GPU
软件环境:
- Pycharm Professional 2025.1
- Python 3.12.9
- Pytorch 2.6.0+cu124
1 N维数组
1.1 核心数据结构
高维数组
- 标量(0D):单个数值(如
1.0
),表示类别或阈值。 - 向量(1D):一列数值(如
[1.2, 3.4, 5.6]
),表示一个样本的特征。 - 矩阵(2D):行×列的表格(如
3×4
矩阵),表示多个样本的特征集合(每行一个样本)。
- 3D 数组:如 RGB 图片(宽×高×3通道)。
- 4D 数组:批量图片(批量大小×宽×高×通道),如 128 张图片组成一个 batch。
- 5D 数组:视频数据(批量×时间帧×宽×高×通道),本课程较少涉及。
- 标量(0D):单个数值(如
为什么用 N 维数组?
- 统一性:所有数据(图像、文本、音频)均可转换为 N 维数组。
- 高效计算:GPU/TPU 针对矩阵运算优化,加速训练。
1.2 创建数组的三大要素

- 形状(Shape)
- 指定维度大小,如
(3,4)
表示 3 行 4 列的矩阵。
- 指定维度大小,如
- 数据类型(dtype)
- 常用
float32
(节省内存)或int64
(类别标签)。
- 常用
- 初始化方式
- 全零:
torch.zeros(3,4)
- 全一:
torch.ones(3,4)
- 随机数:
- 正态分布:
torch.randn(3,4)
(均值 0,方差 1) - 均匀分布:
torch.rand(3,4)
(范围 [0, 1))
- 正态分布:
- 全零:
1.3 数组操作
访问元素
- 单元素:
x[1,2]
→ 第 2 行第 3 列(下标从 0 开始)。 - 整行/整列:
- 第 2 行:
x[1, :]
- 第 1 列:
x[:, 0]
- 第 2 行:
- 子区域切片:
- 行 1~2,所有列:
x[1:3, :]
(左闭右开,实际取第 1、2 行)。
- 行 1~2,所有列:
- 跳步访问:
- 每隔 2 行、每隔 1 列:
x[::2, ::1]
。
- 每隔 2 行、每隔 1 列:

2 实战:数据操作
张量是多维数组的泛化形式,是 PyTorch 和深度学习中的核心数据结构。
标量(0D)、向量(1D)、矩阵(2D)、3D及以上张量分别对应不同维度的数据。
PyTorch 张量 vs NumPy 数组
- 优势:
- GPU 加速计算
- 支持自动微分(Autograd)
- 相似性:操作接口与 NumPy 高度一致。
2.1 创建张量
首先导入
torch
。请注意,虽然它被称为 PyTorch,但是代码中使用torch
而不是pytorch
。import torch
张量表示一个由数值组成的数组,这个数组可能有多个维度。 具有一个轴的张量对应数学上的向量(vector); 具有两个轴的张量对应数学上的矩阵(matrix); 具有两个轴以上的张量没有特殊的数学名称。
使用
arange
创建一个行向量x
。这个行向量包含以 0 开始的前 12 个整数,它们默认创建为整数。x = torch.arange(12) # 生成0-11的整数向量 x
通过张量的
shape
属性来访问张量(沿每个轴的长度)的形状 。x.shape
如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。
因为这里在处理的是一个向量,所以它的
shape
与它的size
相同。x.numel()
要想改变一个张量的形状而不改变元素数量和元素值,可以调用
reshape
函数。例如,可以把张量
x
从形状为(12,)的行向量转换为形状为(3,4)的矩阵。 这个新的张量包含与转换前相同的值,但是它被看成一个 3 行 4 列的矩阵。注意,虽然张量的形状发生了改变,但其元素值并没有变。通过改变张量的形状,张量的大小不会改变。
X = x.reshape(3, 4) # 转换为3x4矩阵 X
如果我们的目标形状是(高度,宽度),在知道宽度后,高度会被自动计算得出,不必我们自己做除法。
在上面的例子中,为了获得一个3行的矩阵,我们手动指定了它有 3 行和 4 列。我们还可以通过
-1
来调用此自动计算出维度的功能。 即我们可以用x.reshape(-1,4)
或x.reshape(3,-1)
来取代x.reshape(3,4)
。使用全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。 我们可以创建一个形状为(2,3,4)的张量,其中所有元素都设置为0。代码如下:
torch.zeros((2,3,4)) # 全0张量 torch.ones((2,3,4)) # 全1张量 torch.randn(3,4) # 正态分布随机数,均值为0、标准差为1 torch.tensor([[2,1,4,3], [1,2,3,4]]) # 自定义数据
2.2 运算
对于任意具有相同形状的张量, 常见的标准算术运算符(
+
、-
、*
、/
和**
)都可以被升级为按元素运算。在下面的例子中,我们使用逗号来表示一个具有 5 个元素的元组,其中每个元素都是按元素操作的结果。
x = torch.tensor([1.0, 2, 4, 8]) y = torch.tensor([2, 2, 2, 2]) x + y, x - y, x * y, x / y, x ** y # **运算符是求幂运算
“按元素”方式可以应用更多的计算,包括像求幂这样的一元运算符。
torch.exp(x) # 指数运算
我们也可以把多个张量连结(concatenate)在一起, 把它们端对端地叠起来形成一个更大的张量。只需要提供张量列表,并给出沿哪个轴连结。
下面的例子分别演示了当我们沿行(轴-0,形状的第一个元素) 和按列(轴-1,形状的第二个元素)连结两个矩阵时,会发生什么情况。
X = torch.arange(12, dtype=torch.float32).reshape((3,4)) Y = torch.tensor([[2.0, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) torch.cat((X, Y), dim=0), torch.cat((X, Y), dim=1)
以
X == Y
为例: 对于每个位置,如果X
和Y
在该位置相等,则新张量中相应项的值为1。 这意味着逻辑语句X == Y
在该位置处为真,否则该位置为0。X == Y
对张量中的所有元素进行求和,会产生一个单元素张量。
X.sum()
2.3 广播机制
在某些情况下,即使形状不同,我们仍然可以通过调用广播机制(broadcasting mechanism)来执行按元素操作。 这种机制的工作方式如下:
- 通过适当复制元素来扩展一个或两个数组,以便在转换之后,两个张量具有相同的形状;
- 对生成的数组执行按元素操作。
在大多数情况下,我们将沿着数组中长度为 1 的轴进行广播,如下例子:
a = torch.arange(3).reshape((3, 1)) b = torch.arange(2).reshape((1, 2)) a, b
由于
a
和b
分别是 3×1 和 1×2 矩阵,如果让它们相加,它们的形状不匹配。此时两个矩阵会广播为一个更大的 3×2 矩阵,如下所示。矩阵a
将复制列, 矩阵b
将复制行,然后再按元素相加。a + b
广播可能掩盖形状错误,需通过
shape
属性验证结果维度。
2.4 索引和切片
就像在任何其他 Python 数组中一样,张量中的元素可以通过索引访问。 与任何 Python 数组一样:第一个元素的索引是 0,最后一个元素索引是 -1; 可以指定范围以包含第一个元素和最后一个之前的元素。
如下所示,用
[-1]
选择最后一个元素,用[1:3]
选择第二个和第三个元素:X[-1], X[1:3]
除读取外,我们还可以通过指定索引来将元素写入矩阵。
X[1, 2] = 9 X
如果我们想为多个元素赋值相同的值,我们只需要索引所有元素,然后为它们赋值。
X[0:2, :] = 12 # 批量赋值 X
2.5 节省内存
运行一些操作可能会导致为新结果分配内存。例如,如果我们用Y = X + Y
,我们将取消引用Y
指向的张量,而是指向新分配的内存处的张量。这可能是不可取的,原因有两个:
- 首先,我们不想总是不必要地分配内存。在机器学习中,我们可能有数百兆的参数,并且在一秒内多次更新所有参数。通常情况下,我们希望原地执行这些更新;
- 如果我们不原地更新,其他引用仍然会指向旧的内存位置,这样我们的某些代码可能会无意中引用旧的参数。
相关参考链接:2025-03-15 Python&深度学习2——Numpy库_python3.12.7版本对应numpy-CSDN博客(3.4 节)。
在下面的例子中,我们用 Python 的
id()
函数演示了这一点,它给我们提供了内存中引用对象的确切地址。 运行Y = Y + X
后,我们会发现id(Y)
指向另一个位置。 这是因为 Python 首先计算Y + X
,为结果分配新的内存,然后使Y
指向内存中的这个新位置。before = id(Y) Y = Y + X id(Y) == before
使用切片表示法将操作的结果分配给先前分配的数组,例如
Y[:] = <expression>
。首先创建一个新的矩阵
Z
,其形状与另一个Y
相同, 使用zeros_like
来分配一个全0的块。Z = torch.zeros_like(Y) print('id(Z):', id(Z)) Z[:] = X + Y print('id(Z):', id(Z))
如果在后续计算中没有重复使用
X
, 我们也可以使用X[:] = X + Y
或X += Y
来减少操作的内存开销。before = id(X) X += Y id(X) == before
2.6 与NumPy转换
将深度学习框架定义的张量转换为 NumPy 张量(ndarray
)很容易,反之也同样容易。
torch 张量和 numpy 数组将共享它们的底层内存,就地操作更改一个张量也会同时更改另一个张量。
A = X.numpy() B = torch.tensor(A) type(A), type(B)
要将大小为 1 的张量转换为 Python 标量,我们可以调用
item
函数或 Python 的内置函数。a = torch.tensor([3.5]) a, a.item(), float(a), int(a)
2.7 练习
运行本节中的代码。将本节中的条件语句
X == Y
更改为X < Y
或X > Y
,然后看看你可以得到什么样的张量。答:同样为布尔张量,每个元素值都是
X
与Y
中对应元素的比较。X < Y
用其他形状(例如三维张量)替换广播机制中按元素操作的两个张量。结果是否与预期相同?
相同,但这仅适用于一维行向量和一维列向量之间的加减。
a = torch.arange(3).reshape((3, 1)) b = torch.arange(3).reshape((1, 3)) a, b
a + b
3 实战:数据预处理
为了能用深度学习来解决现实世界的问题,我们常从预处理原始数据开始, 而不是从那些准备好的张量格式数据开始,通常使用pandas
软件包。
3.1 读取数据集
首先创建一个人工数据集,并存储在 CSV(逗号分隔值)文件 ../data/house_tiny.csv
中。 以其他格式存储的数据也可以通过类似的方式进行处理。
将数据集按行写入 CSV 文件中。
import os os.makedirs(os.path.join('..', 'data'), exist_ok=True) data_file = os.path.join('..', 'data', 'house_tiny.csv') with open(data_file, 'w') as f: f.write('NumRooms,Alley,Price\n') # 列名 f.write('NA,Pave,127500\n') # 每行表示一个数据样本 f.write('2,NA,106000\n') f.write('4,NA,178100\n') f.write('NA,NA,140000\n')
导入
pandas
包并调用read_csv
函数。该数据集有四行三列。其中每行描述了房间数量(“NumRooms”)、巷子类型(“Alley”)和房屋价格(“Price”)。# 如果没有安装pandas,只需取消对以下行的注释来安装pandas # !pip install pandas import pandas as pd data = pd.read_csv(data_file) print(data)
3.2 处理缺失值
“NaN”项代表缺失值。 为了处理缺失的数据,典型的方法包括插值法和删除法, 其中插值法用一个替代值弥补缺失值,而删除法则直接忽略缺失值。 在这里,我们将考虑插值法。
通过位置索引
iloc
,将data
分成inputs
和outputs
, 其中前者为data
的前两列,而后者为data
的最后一列。 对于inputs
中缺少的数值,我们用同一列的均值替换“NaN”项。inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2] # inputs = inputs.fillna(inputs.mean()) # 数值列用均值填充 inputs['NumRooms'] = inputs['NumRooms'].fillna(inputs['NumRooms'].mean()) inputs
对于
inputs
中的类别值或离散值,我们将“NaN”视为一个类别。 由于“巷子类型”(“Alley”)列只接受两种类型的类别值“Pave”和“NaN”,pandas
可以自动将此列转换为两列“Alley_Pave”和“Alley_nan”。巷子类型为“Pave”的行会将“Alley_Pave”的值设置为1,“Alley_nan”的值设置为0。 缺少巷子类型的行会将“Alley_Pave”和“Alley_nan”分别设置为0和1。
inputs = pd.get_dummies(inputs, dummy_na=True) print(inputs)
3.3 转换为张量
现在inputs
和outputs
中的所有条目都是数值类型,它们可以转换为张量格式。
当数据采用张量格式后,可以通过在 [2.1节](##2.1 创建张量)中引入的那些张量函数来进一步操作。
import torch X = torch.tensor(inputs.to_numpy(dtype=float)) y = torch.tensor(outputs.to_numpy(dtype=float)) X, y
3.4 练习
删除缺失值最多的列
# 计算每列的缺失值数量 missing_counts = data.isnull().sum() print("每列的缺失值数量:\n", missing_counts) # 找出缺失值最多的列名 column_to_drop = missing_counts.idxmax() print("\n缺失值最多的列:", column_to_drop) # 删除该列 data_dropped = data.drop(column_to_drop, axis=1) print("\n删除后的数据:\n", data_dropped)
将预处理后的数据集转换为张量格式。
# 1. 删除缺失值最多的列(前面已实现) data_dropped = data.drop(missing_counts.idxmax(), axis=1) # 2. 处理剩余缺失值(用均值填充数值列) data_dropped = data_dropped.fillna(data_dropped.mean()) # 3. 转换为张量 tensor_data = torch.tensor(data_dropped.values, dtype=torch.float32) print("\n张量格式的数据:\n", tensor_data)