动手学深度学习笔记--数据操作、线性代数

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 转换为张量

处理干净之后,就可以把 inputsoutputs 转成 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.)
相关推荐
元岳数字人小元1 小时前
AI数字人交互系统:多场景落地应用价值解析
人工智能·人机交互·交互
pokemen邪11 小时前
PyTorch KernelAgent 源码解读 ---(6)--- Composer
人工智能·pytorch·composer
陈天伟教授1 小时前
图解人工智能(90)人工智能前沿-天文学家的助手
人工智能
Yang_jie_031 小时前
笔记:数据结构(栈是否使用底指针以及头指针的初始化值)
数据结构·笔记·算法
aaPIXa6222 小时前
C++采样引导优化SPGO——比PGO更智能的编译器决策新方案
java·c++·人工智能
2601_957190902 小时前
飞行影院安装施工指南:场地、动感系统与影片内容配套
大数据·前端·人工智能
ACP广源盛139246256732 小时前
IX9104 PCIe5.0 交换芯片@ACP#国产高端 AI PC 全搭配方案
大数据·人工智能·分布式·单片机·嵌入式硬件
her_heart2 小时前
把 ChatGPT 5.6 放进需求评审和测试设计之后,我反而减少了“一次成稿”的期待
网络·人工智能·网络协议·chatgpt·测试用例
YHHLAI2 小时前
Agent 智能体开发实战 · 第六课:MCP 协议 —— 让 Agent 跨进程调用工具
前端·人工智能
QiLinkOS2 小时前
第三视觉理解徐玉生与他的商业活动(41)
人工智能·dna双螺旋归因模型·专利竞争情报系统·技术专利·新能源汽车行业技术洞察报告