1、数据操作
深度学习里操作数据的基本单位是张量(tensor),可以理解成多维数组。PyTorch 的张量不仅能放在 GPU 上加速,还支持自动求导,比 NumPy 更适合训练模型。
1.1 创建张量与基本属性
python
import torch
# 用 arange 创建一个包含 0~11 的整数行向量
x = torch.arange(12)
x # 输出: tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
# 查看形状(每个轴的长度),这里是一个一维向量,长度为 12
x.shape # 输出: torch.Size([12])
# 查看元素总数
x.numel() # 输出: 12
1.2 改变形状
python
# 把向量 x 变成 3 行 4 列的矩阵,总元素数不变
X = x.reshape(3, 4)
X
# 输出:
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]])
# -1 可以让 PyTorch 自动推算那一维的大小
x.reshape(-1, 4) # 与 x.reshape(3,4) 效果相同
1.3 用常量或随机数初始化
python
# 形状为 (2, 3, 4) 的全 0 张量
torch.zeros((2, 3, 4)) # 输出一个 2x3x4 的 0. 张量
# 形状为 (2, 3, 4) 的全 1 张量
torch.ones((2, 3, 4)) # 输出一个 2x3x4 的 1. 张量
# 从标准正态分布(均值0,标准差1)中随机采样,形状 (3,4)
torch.randn(3, 4)
# 输出示例:
# tensor([[-0.0135, 0.0665, 0.0912, 0.3212],
# [ 1.4653, 0.1843, -1.6995, -0.3036],
# [ 1.7646, 1.0450, 0.2457, -0.7732]])
# 直接通过 Python 列表构造张量
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]])
# 输出:
# tensor([[2, 1, 4, 3],
# [1, 2, 3, 4],
# [4, 3, 2, 1]])
1.4 运算符
最常见的操作是按元素运算,即对两个张量中相同位置的元素逐一计算。
python
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
# 输出: (tensor([ 3., 4., 6., 10.]), # 加法
# tensor([-1., 0., 2., 6.]), # 减法
# tensor([ 2., 4., 8., 16.]), # 乘法
# tensor([0.5000, 1.0000, 2.0000, 4.0000]), # 除法
# tensor([ 1., 4., 16., 64.])) # 幂运算
# 一元运算符,如 exp
torch.exp(x)
# 输出: tensor([2.7183e+00, 7.3891e+00, 5.4598e+01, 2.9810e+03])
连结多个张量:
python
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]])
# 沿轴 0(行)堆叠,会变成 6×4 的矩阵
torch.cat((X, Y), dim=0)
# 输出:
# tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [ 2., 1., 4., 3.],
# [ 1., 2., 3., 4.],
# [ 4., 3., 2., 1.]])
# 沿轴 1(列)堆叠,会变成 3×8 的矩阵
torch.cat((X, Y), dim=1)
# 输出:
# tensor([[ 0., 1., 2., 3., 2., 1., 4., 3.],
# [ 4., 5., 6., 7., 1., 2., 3., 4.],
# [ 8., 9., 10., 11., 4., 3., 2., 1.]])
# 逻辑比较,结果是一个布尔型张量
X == Y
# tensor([[False, True, False, True],
# [False, False, False, False],
# [False, False, False, False]])
# 对所有元素求和,得到标量张量
X.sum() # 输出: tensor(66.)
1.5 广播机制
当两个张量形状不同但某些维度是 1 时,PyTorch 会自动复制扩展,使它们形状一致后再进行按元素运算。
python
a = torch.arange(3).reshape((3, 1)) # 形状 3×1
b = torch.arange(2).reshape((1, 2)) # 形状 1×2
a, b
# (tensor([[0],
# [1],
# [2]]),
# tensor([[0, 1]]))
a + b # a 被复制为 3×2,b 被复制为 3×2,然后相加
# tensor([[0, 1],
# [1, 2],
# [2, 3]])
2.1.6 索引与切片
和 Python 列表类似,支持用方括号访问和修改元素。
python
X = torch.arange(12, dtype=torch.float32).reshape(3, 4)
# 取最后一行
X[-1] # tensor([ 8., 9., 10., 11.])
# 取第 2 行、第 3 行(索引 1 和 2)
X[1:3]
# tensor([[ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.]])
# 写入单个元素
X[1, 2] = 9
# 将前两行全部赋值为 12
X[0:2, :] = 12
X
# tensor([[12., 12., 12., 12.],
# [12., 12., 12., 12.],
# [ 8., 9., 10., 11.]])
1.7 节省内存
类似 Y = Y + X 这样的操作会分配新内存。如果想原地更新 ,可以使用切片或 +=。
python
# 新内存分配的例子
before = id(Y)
Y = Y + X
id(Y) == before # False, 说明 Y 指向了新地址
# 原地操作:使用切片分配
Z = torch.zeros_like(Y)
Z[:] = X + Y # Z 的地址不变
# 或者用 +=
before = id(X)
X += Y
id(X) == before # True, 地址没有变
1.8 与 NumPy 互转
PyTorch 张量和 NumPy 数组共享底层内存,修改一个会直接影响另一个。
python
A = X.numpy() # 张量 -> numpy 数组
B = torch.tensor(A) # numpy 数组 -> 张量
type(A), type(B) # (numpy.ndarray, torch.Tensor)
# 大小为 1 的张量可以转换成 Python 标量
a = torch.tensor([3.5])
a.item() # 3.5
float(a) # 3.5
int(a) # 3
2 数据预处理
现实数据通常不是现成的张量,需要用 pandas 这样的库进行预处理。
2.1 读取 CSV 数据集
python
import os
import pandas as pd
# 创建一个小数据集,写入 CSV 文件(模拟真实数据)
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 读取
data = pd.read_csv(data_file)
print(data)
# 打印结果:
# NumRooms Alley Price
# 0 NaN Pave 127500
# 1 2.0 NaN 106000
# 2 4.0 NaN 178100
# 3 NaN NaN 140000
2.2 处理缺失值
缺失值用 NaN 表示。常见的处理方法是用同一列的均值填充数值列 ,用独热编码处理离散列(同时将缺失也当作一个类别)。
python
# 分开输入和输出列
inputs, outputs = data.iloc[:, 0:2], data.iloc[:, 2]
# 数值列的缺失值用该列均值填充
inputs = inputs.fillna(inputs.mean())
print(inputs)
# NumRooms Alley
# 0 3.0 Pave
# 1 2.0 NaN
# 2 4.0 NaN
# 3 3.0 NaN
# 离散列 Alley 包含 'Pave' 和 NaN 两种值,用 get_dummies 转成指示列
# 该行数值转换后表示的列数值为1 dummy_na=True表示nan会单独为一列
inputs = pd.get_dummies(inputs, dummy_na=True)
print(inputs)
# NumRooms Alley_Pave Alley_nan
# 0 3.0 1 0
# 1 2.0 0 1
# 2 4.0 0 1
# 3 3.0 0 1
2.3 转换为张量
处理干净之后,就可以把 inputs 和 outputs 转成 PyTorch 张量,继续用前面学到的张量运算了。
python
import torch
X = torch.tensor(inputs.to_numpy(dtype=float))
y = torch.tensor(outputs.to_numpy(dtype=float))
X, y
# (tensor([[3., 1., 0.],
# [2., 0., 1.],
# [4., 0., 1.],
# [3., 0., 1.]], dtype=torch.float64),
# tensor([127500., 106000., 178100., 140000.], dtype=torch.float64))
3 线性代数 内容总结
3.1 标量
标量是仅含单个数值的数学对象,在 PyTorch 中由单元素张量表示,支持基础算术运算。
python
import torch
# 定义两个标量张量(单元素张量)
x = torch.tensor(3.0) # x = tensor(3.)
y = torch.tensor(2.0) # y = tensor(2.)
# 加法:对应数值直接相加 3.0 + 2.0 = 5.0
x + y # tensor(5.)
# 乘法:3.0 * 2.0 = 6.0
x * y # tensor(6.)
# 除法:3.0 / 2.0 = 1.5
x / y # tensor(1.5000)
# 幂运算:3.0的2.0次方 = 9.0
x**y # tensor(9.)
3.2 向量
向量是一阶张量,由有序标量列表组成;可通过索引访问元素,len()获取长度,.shape获取张量形状。
python
# 生成从0开始、长度为4的一维整型向量
x = torch.arange(4)
# 结果:tensor([0, 1, 2, 3])
# 通过下标访问第4个元素(索引从0开始,索引3对应第4个值)
x[3] # tensor(3)
# 获取向量的元素个数(长度)
len(x) # 4
# 获取张量的形状:1个轴,轴长度为4
x.shape # torch.Size([4])
3.3 矩阵
矩阵是二阶张量,由 m 行 n 列标量组成;支持转置操作,对称矩阵满足「矩阵 = 自身转置」。
python
# 生成0~19的连续整数,重塑为5行4列的矩阵
A = torch.arange(20).reshape(5, 4)
# 结果:
# tensor([[ 0, 1, 2, 3],
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11],
# [12, 13, 14, 15],
# [16, 17, 18, 19]])
# 矩阵转置:行和列互换,原5×4变为4×5
A.T
# 结果:
# tensor([[ 0, 4, 8, 12, 16],
# [ 1, 5, 9, 13, 17],
# [ 2, 6, 10, 14, 18],
# [ 3, 7, 11, 15, 19]])
# 定义3阶对称矩阵(满足 a_ij = a_ji)
B = torch.tensor([[1, 2, 3], [2, 0, 4], [3, 4, 5]])
# 结果:
# tensor([[1, 2, 3],
# [2, 0, 4],
# [3, 4, 5]])
# 逐元素比较B和它的转置,验证对称性
B == B.T
# 结果:所有位置均相等,输出
# tensor([[True, True, True],
# [True, True, True],
# [True, True, True]])
3.4 张量
张量是 n 维数组的通用形式,标量是 0 阶张量、向量是 1 阶、矩阵是 2 阶,可扩展到更高维度(如图像的 3 阶张量)。
python
# 生成0~23的整数,重塑为 2个块 × 3行 × 4列 的三阶张量
X = torch.arange(24).reshape(2, 3, 4)
# 结果:
# tensor([[[ 0, 1, 2, 3], # 第0个块
# [ 4, 5, 6, 7],
# [ 8, 9, 10, 11]],
#
# [[12, 13, 14, 15], # 第1个块
# [16, 17, 18, 19],
# [20, 21, 22, 23]]])
3.5 张量算法的基本性质
同形状张量的二元运算为逐元素运算,结果形状不变;矩阵逐元素乘法称为 Hadamard 积。
标量与张量运算时,标量会广播到张量的每个元素,运算后形状不变。
python
# 创建float32类型的5×4矩阵,元素为0.~19.
A = torch.arange(20, dtype=torch.float32).reshape(5, 4)
B = A.clone() # 深拷贝:分配新内存,B与A数值相同但相互独立
# 逐元素加法:对应位置相加,每个元素变为原值的2倍
A + B
# 结果:
# tensor([[ 0., 2., 4., 6.],
# [ 8., 10., 12., 14.],
# [16., 18., 20., 22.],
# [24., 26., 28., 30.],
# [32., 34., 36., 38.]])
# Hadamard积(逐元素乘法):对应位置相乘
A * B
# 计算示例:第0行第0列 0.*0.=0.;第0行第1列 1.*1.=1.
# 结果:
# tensor([[ 0., 1., 4., 9.],
# [ 16., 25., 36., 49.],
# [ 64., 81., 100., 121.],
# [144., 169., 196., 225.],
# [256., 289., 324., 361.]])
# 标量与张量运算:广播机制
a = 2
X = torch.arange(24).reshape(2, 3, 4)
# 标量加法:每个元素都加2
a + X
# 结果:所有元素+2,形状仍为2×3×4
# tensor([[[ 2, 3, 4, 5],
# [ 6, 7, 8, 9],
# [10, 11, 12, 13]],
# [[14, 15, 16, 17],
# [18, 19, 20, 21],
# [22, 23, 24, 25]]])
# 标量乘法不改变张量形状
(a * X).shape # torch.Size([2, 3, 4])
3.6 降维
求和、求均值等操作会沿指定轴消除张量维度,称为降维;也可通过参数保持维度,或计算累积和。
python
# 定义测试向量
x = torch.arange(4, dtype=torch.float32) # tensor([0., 1., 2., 3.])
# 向量全局求和:所有元素相加 0+1+2+3 = 6
x.sum() # tensor(6.)
# A = tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
# 矩阵全局求和:所有元素相加
A.shape # 原形状 torch.Size([5, 4])
A.sum() # 0+1+...+19 = 190 → tensor(190.)
# 沿轴0(行方向)求和:消去行维度,对每一列的所有行元素求和
A_sum_axis0 = A.sum(axis=0)
# 计算过程:
# 第0列:0+4+8+12+16 = 40;第1列:1+5+9+13+17 = 45
# 第2列:2+6+10+14+18 = 50;第3列:3+7+11+15+19 = 55
A_sum_axis0 # tensor([40., 45., 50., 55.])
A_sum_axis0.shape # 消去轴0,形状变为 torch.Size([4])
# 沿轴1(列方向)求和:消去列维度,对每一行的所有列元素求和
A_sum_axis1 = A.sum(axis=1)
# 计算过程:
# 第0行:0+1+2+3 = 6;第1行:4+5+6+7 = 22
# 第2行:8+9+10+11 = 38;第3行:12+13+14+15 = 54;第4行:16+17+18+19 = 70
A_sum_axis1 # tensor([ 6., 22., 38., 54., 70.])
A_sum_axis1.shape # 消去轴1,形状变为 torch.Size([5])
# 同时沿两个轴求和,等价于全局求和
A.sum(axis=[0, 1]) # tensor(190.)
2. 平均值计算
python
# A = tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
# 全局平均值:总和 / 元素总数
A.mean() # 190 / 20 = 9.5 → tensor(9.5000)
A.sum() / A.numel() # numel()返回元素总个数,结果同上
# 沿轴0求平均值:每列的和 / 行数
A.mean(axis=0) # tensor([ 8., 9., 10., 11.])
A.sum(axis=0) / A.shape[0] # 40/5=8、45/5=9 ... 结果同上
3. 非降维求和(保持维度)
python
# A = tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
# 沿轴1求和,但保留被降维的轴(长度变为1)
sum_A = A.sum(axis=1, keepdims=True)
# 结果:5行1列的矩阵,而非长度为5的向量
# tensor([[ 6.],
# [22.],
# [38.],
# [54.],
# [70.]])
sum_A.shape # torch.Size([5, 1])
# 保留维度后可直接通过广播做归一化:每行元素除以该行总和(维度数量相同可以进行广播)
A / sum_A
# 计算示例:第0行 0/6=0, 1/6≈0.1667, 2/6≈0.3333, 3/6=0.5
# 结果:
# tensor([[0.0000, 0.1667, 0.3333, 0.5000],
# [0.1818, 0.2273, 0.2727, 0.3182],
# [0.2105, 0.2368, 0.2632, 0.2895],
# [0.2222, 0.2407, 0.2593, 0.2778],
# [0.2286, 0.2429, 0.2571, 0.2714]])
4. 累积求和(不降维)
python
# A = tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
# 沿轴0计算累积和,不降低维度
A.cumsum(axis=0)
# 计算过程:每个位置 = 上方所有元素(含自身)的累加和
# 第0行:保持原值 [0., 1., 2., 3.]
# 第1行:0+4=4, 1+5=6, 2+6=8, 3+7=10 → [4., 6., 8., 10.]
# 第2行:4+8=12, 6+9=15, 8+10=18, 10+11=21 → [12., 15., 18., 21.]
# 后续行以此类推
# 结果:
# tensor([[ 0., 1., 2., 3.],
# [ 4., 6., 8., 10.],
# [12., 15., 18., 21.],
# [24., 28., 32., 36.],
# [40., 45., 50., 55.]])
3.7 点积
两个同长向量的点积 = 对应位置元素相乘后求和,最终结果为一个标量;可表示加权和、向量夹角余弦等。
python
y = torch.ones(4, dtype = torch.float32) # 全1向量 tensor([1., 1., 1., 1.])
x = torch.arange(4, dtype=torch.float32) # tensor([0., 1., 2., 3.])
# 直接调用点积函数
torch.dot(x, y)
# 计算过程:0*1 + 1*1 + 2*1 + 3*1 = 6 → tensor(6.)
# 等价实现:逐元素相乘后求和
torch.sum(x * y) # 结果同样为 tensor(6.)
3.8 矩阵 - 向量积
矩阵Am×n与向量xn相乘,结果是长度为 m 的向量;每个元素等于矩阵对应行与向量的点积。
python
A.shape # 矩阵形状 torch.Size([5, 4])
x.shape # 向量长度 torch.Size([4])
# A = tensor([[ 0., 1., 2., 3.],
# [ 4., 5., 6., 7.],
# [ 8., 9., 10., 11.],
# [12., 13., 14., 15.],
# [16., 17., 18., 19.]])
# x = tensor([0., 1., 2., 3.])
# 矩阵-向量乘法
torch.mv(A, x)
# 计算过程(每行与向量做点积):
# 第0行:0*0 + 1*1 + 2*2 + 3*3 = 0+1+4+9 = 14
# 第1行:4*0 + 5*1 + 6*2 + 7*3 = 0+5+12+21 = 38
# 第2行:8*0 + 9*1 +10*2 +11*3 = 0+9+20+33 = 62
# 第3行:12*0 +13*1 +14*2 +15*3 = 0+13+28+45 = 86
# 第4行:16*0 +17*1 +18*2 +19*3 = 0+17+36+57 = 110
# 结果:tensor([ 14., 38., 62., 86., 110.])
3.9 矩阵 - 矩阵乘法
矩阵An×k与Bk×m相乘,得到n×m的结果矩阵;元素cij = A 的第 i 行与 B 的第 j 列的点积。
B = torch.ones(4, 3) # 4行3列全1矩阵
A.shape, B.shape # torch.Size([5, 4]), torch.Size([4, 3])
# 标准矩阵乘法
torch.mm(A, B)
# 计算过程:A的每行与B的每列做点积(B全为1,等价于对A的每行求和)
# 第0行结果:0+1+2+3 = 6 → [6., 6., 6.]
# 第1行结果:4+5+6+7 = 22 → [22., 22., 22.]
# 后续行以此类推
# 结果:
# tensor([[ 6., 6., 6.],
# [22., 22., 22.],
# [38., 38., 38.],
# [54., 54., 54.],
# [70., 70., 70.]])
注意:矩阵乘法要求「第一个矩阵的列数 = 第二个矩阵的行数」,且与逐元素 Hadamard 积是完全不同的运算。
3.10 范数
范数用于衡量向量 / 矩阵的「大小」,需满足非负性、齐次性、三角不等式;深度学习中常用于损失函数、正则项。
python
u = torch.tensor([3.0, -4.0])
# 1. L2范数(欧几里得范数):元素平方和的平方根
torch.norm(u)
# 计算过程:√(3² + (-4)²) = √(9+16) = √25 = 5 → tensor(5.)
# 2. L1范数:元素绝对值之和
torch.abs(u).sum()
# 计算过程:|3| + |-4| = 3+4 = 7 → tensor(7.)
# 3. Frobenius范数(矩阵的L2型范数):所有元素平方和的平方根
torch.norm(torch.ones((4, 9)))
# 计算过程:4×9共36个1,平方和为36 → √36 = 6 → tensor(6.)