动手学深度学习
本文最后更新于:2025年3月21日 凌晨
参考知名教程《动手学深度学习》,系统学一下~
杂七杂八
Linux的系统盘和数据盘
由于博主的电脑配置太辣鸡,所以选择在autodl租用显卡进行相关学习。公共资源需要靠抢,所以常常需要复制镜像,每次都随机抢不同的显卡……
- 镜像保存了Linux除了数据盘以外的所有数据;
- 数据盘的内容需要拷贝,只能在同一地区的显卡上拷贝数据。
- Linux数据盘和系统盘的查看
- 系统盘如同Windows的C盘。 使用
df -lh
查看,根路径下的都属于系统盘。 - 如果单独有数据盘,且数据盘没有分区和挂载,使用df -l命令是看不到的。可以使用
fdisk -l
,可以看到有哪些硬盘。
- 系统盘如同Windows的C盘。 使用
环境配置
配置一个环境来运行 Python、Jupyter Notebook、相关库以及运行本书所需的代码,以快速入门并获得动手学习经验。
安装Miniconda
Miniconda 是一个轻量级的 Conda 发行版,主要用于管理 Python 环境和软件包。简而言之,Miniconda 的作用是管理 Python 版本和依赖,比如你电脑中运行了多个项目,这些项目需要不同的 Python 版本和库,就可以用 Miniconda 创建独立的虚拟环境,避免相互干扰。
Linux 系统中安装和部署 Miniconda 的详细教程
检查pytorch是否安装成功、查看torch和cuda的版本
预备知识
数据操作
PyTorch:一个开源的深度学习框架,支持GPU加速计算和动态计算图
核心组件:
- torch.Tensor:支持梯度追踪的多维数组。
- torch.nn:神经网络层和损失函数。
- torch.optim:优化算法(如SGD、Adam)。
导入torch。请注意,虽然它被称为PyTorch,但是代码中使用torch而不是pytorch。
1 |
|
张量
张量表示一个由数值组成的数组,这个数组可能有多个维度。 具有一个轴的张量对应数学上的向量(vector); 具有两个轴的张量对应数学上的矩阵(matrix); 具有两个轴以上的张量没有特殊的数学名称。
- 张量中的每个值都称为张量的
元素
(element)。 - 除非额外指定,新的张量将存储在内存中,并采用基于CPU的计算。
- 使用
arange
创建一个行向量 x。这个行向量包含以0开始的前12个整数,它们默认创建为整数。也可指定创建类型为浮点数。- 可以通过张量的
shape
属性来访问张量(沿每个轴的长度
)的形状 。如果只想知道张量中元素的总数,即形状的所有元素乘积,可以检查它的大小(size)。 - 改变一个张量的形状而不改变元素数量和元素值,可以调用
reshape
函数。注意,通过改变张量的形状,张量的大小不会改变。- 例如,可以把张量x从形状为(12,)的行向量转换为形状为(3,4)的矩阵。 这个新的张量包含与转换前相同的值,但是它被看成一个3行4列的矩阵。 要重点说明一下,虽然张量的形状发生了改变,但其元素值并没有变。
- 通过-1来调用此自动计算出维度的功能。即我们可以用
x.reshape(-1,4)
或x.reshape(3,-1)
来取代x.reshape(3,4)
。
- 使用全0、全1、其他常量,或者从特定分布中随机采样的数字来初始化矩阵。
1
2
3
4
5
6
7
8
9x = torch.arange(12)
x.shape
x.numel()
X = x.reshape(3, 4)
torch.zeros((2, 3, 4)) # 创建一个形状为(2,3,4)的张量,其中所有元素都设置为0。
torch.ones((2, 3, 4))
torch.randn(3, 4) # 随机初始化参数的值,每个元素都从均值为0、标准差为1的标准高斯分布(正态分布)中随机采样。
torch.tensor([[2, 1, 4, 3], [1, 2, 3, 4], [4, 3, 2, 1]]) # 通过提供包含数值的Python列表(或嵌套列表),来为所需张量中的每个元素赋予确定值。
print(X) # 打印输出X的值
- 可以通过张量的
独热编码(One-Hot Encoding)
定义:用二进制向量表示离散类别。又称为一位有效编码,主要是采用N位状态寄存器来对N个状态进行编码,每个状态都由他独立的寄存器位,并且在任意时候只有一位有效。
例如:
- 类别1 → [1, 0, 0]
- 类别2 → [0, 1, 0]
- 类别3 → [0, 0, 1]
作用:将分类标签转换为模型可处理的数值形式。具体参考:
- 标签(Label):真实答案,此处表示样本属于第一个类别。
1
target = torch.tensor([1.0, 0.0, 0.0]) # 独热编码
交叉熵损失 (Cross-Entropy Loss)
衡量预测概率分布与真实分布的差异。
1 |
|
计算过程:
- target * torch.log(prob) → 仅保留真实类别对应的概率对数。
- -sum(…) → 取负数得到损失值(越小表示预测越准)。
反式传播
- 原理:通过链式法则
自动计算
损失对参数的梯度,只需调用backward()。
1
2loss.backward()
print("梯度:", x.grad) # 输出如 [-0.3410, 0.2424, 0.0986] - 步骤:
- 从loss开始,反向遍历计算图。
- 计算每个参与运算的张量的梯度。
- 结果:梯度存储在x.grad中。
梯度
- 意义:表示损失函数对x各元素的敏感度。
- 例如:输出 [-0.3410, 0.2424, 0.0986]
- 负梯度(如-0.3410)→ 增大x[0]可减少损失,概率越接近1,损失越小
- 正梯度(如0.2424)→ 减小x[1]可减少损失。
- 例如:输出 [-0.3410, 0.2424, 0.0986]
- 应用:通过优化器(如SGD)更新参数:x = x - 学习率 * x.grad
- 梯度下降的直观理解
将损失函数想象为一座山,目标是找到山谷最低点(最小损失):- 梯度:指向当前所在位置最陡的上坡方向。
- 负梯度方向:下山最快的方向。
- 学习率:决定每一步迈多大。
- 步子太小 → 下山慢。
- 步子太大 → 可能跨过山谷或摔倒(发散)。
优化器
随机梯度下降(SGD)的参数更新公式为:
$$\theta_{t+1} = \theta_t - \eta \cdot \nabla_\theta J(\theta_t)$$
其中,$\theta$ 是模型参数,$\eta$ 是学习率,$\nabla_\theta J(\theta_t)$是损失函数对参数的梯度。
1 |
|
- 学习率($\eta$)的作用
- 过小,如 $\eta = 0.001$ :更新步长小,收敛速度慢。
- 过大,如 $\eta = 1.0$ :可能跳过最优解,甚至发散。
- 合理选择:通常通过实验调整
优化器 | 适用场景 | 核心特点 | 常见应用领域 |
---|---|---|---|
SGD | 简单模型/凸优化问题 | 基础更新,收敛稳定但需手动调学习率 | 线性回归/简单分类任务 |
Momentum | 高维非凸优化场景 | 引入动量项加速收敛,减少参数更新振荡 | 计算机视觉/复杂网络结构 |
Adagrad | 稀疏数据特征处理 | 自适应调整学习率,对低频特征更敏感,内存消耗较大 | NLP词向量训练 |
RMSprop | 非平稳目标/循环网络 | 改进Adagrad的学习率衰减问题,采用指数移动平均 | RNN/LSTM训练 |
Adam | 通用深度学习任务(默认首选) | 结合动量+自适应学习率,收敛速度快,超参数鲁棒性好 | CNN/GAN/大多数深度学习模型 |
AdamW | 需要精细权重衰减的任务 | 解耦权重衰减项,解决Adam中权重衰减与梯度更新耦合的问题 | Transformer/BERT系列模型 |
运算符
按元素(elementwise)运算。
- 它们将标准标量运算符应用于数组的每个元素。 对于将
两个数组
作为输入的函数,按元素运算将二元运算符应用于两个数组中的每对位置对应的元素
。 我们可以基于任何从标量到标量的函数来创建按元素函数。1
2
3
4x = torch.tensor([1.0, 2, 4, 8])
y = torch.tensor([2, 2, 2, 2])
x + y, x - y, x * y, x / y, x ** y # **运算符是求幂(y次方)
torch.exp(x) # 计算输入张量 x 中每个元素的指数函数值(即自然常数 e 的对应元素次方)
torch.exp()函数的应用场景
Softmax
函数:用于将神经网络的输出转换为概率分布(所有值在0~1之间,且和为1):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24import torch
# 定义输入(假设是神经网络的原始输出)
x = torch.tensor([2.0, 1.0, 0.1]) # 原始分数
# 数值稳定的Softmax实现
def softmax(x):
# 1. 减去最大值,避免指数爆炸(数值稳定性技巧)
max_x = torch.max(x)
shifted_x = x - max_x
# 2. 计算指数
exp_x = torch.exp(shifted_x)
# 3. 计算概率分布
sum_exp = torch.sum(exp_x)
probabilities = exp_x / sum_exp
return probabilities
# 调用函数
prob = softmax(x)
print("Softmax结果:", prob) # 输出如 [0.6590, 0.2424, 0.0986],每个值表示对应类别的概率,总和为1。激活函数
:如高斯误差线性单元(GELU):- GELU是一种平滑的激活函数,常用于Transformer模型(如BERT)。GELU近似于用概率门控机制决定是否激活神经元,公式为(0.044715为经验系数):
$$\text{GELU}(x) = 0.5x \left( 1 + \tanh\left( \sqrt{\frac{2}{\pi}} \left( x + 0.044715x^3 \right) \right) \right)$$
- GELU vs ReLU:
- ReLU:当输入>0时输出原值,否则输出0(不光滑)。
- GELU:通过双曲正切函数(tanh)实现平滑过渡,更适合复杂模型。
1 |
|
- 如果 x 是需计算梯度的张量(requires_grad=True),torch.exp(x) 的梯度会自动计算,用于反向传播更新权重。
- 梯度是函数在某一点的导数,表示该点对输出的影响程度。
- 在训练神经网络时,梯度告诉我们应该如何调整参数(如 x)以减少误差。例如,如果梯度为7.3891,说明 x 增加1,y 会增加约7.3891。
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13import torch
# 创建一个需要计算梯度的张量
x = torch.tensor(2.0, requires_grad=True) # requires_grad=True表示跟踪梯度
# 定义计算过程
y = torch.exp(x) # 计算 y = e^x
# 反向传播计算梯度
y.backward() # 自动计算 dy/dx
# 查看梯度
print("梯度值:", x.grad) # 输出 tensor(7.3891),即 e^2 ≈ 7.3891
一些语法解释
- x - max_x这样的操作默认会对张量(矩阵)中的每个元素进行运算,但具体行为取决于max_x的维度。
- 假设x是一个矩阵,而max_x是通过torch.max(x)得到的全局最大值标量,此时x - max_x会对x中的每个元素都减去这个标量值。
- 如果max_x是通过指定维度(如dim=1)计算得到的向量,PyTorch会通过广播机制自动对齐维度,确保逐元素减法正确执行。
- 在按维度计算最大值时,
keepdim=True
会保留原始维度信息,确保广播正确。广播规则下,max_x会被扩展为与x同形状,最终每个元素减去对应行的最大值。1
2
3
4
5
6
7
8x = torch.tensor([[3.0, 5.0],
[2.0, 4.0]])
max_x = torch.max(x, dim=1, keepdim=True).values # max_x是按行(dim=1)计算的最大值,形状为(2, 1)。
print(max_x) # 输出 tensor([[5.0], [4.0]])
shifted_x = x - max_x
print(shifted_x)
- 在按维度计算最大值时,
综合示例 (结合Softmax、梯度与优化器)
1 |
|
注意:
- PyTorch默认会累积梯度(例如在RNN中可能需要)。在
每次迭代前必须手动清零梯度
,否则梯度会不断累加,导致更新错误。 - 参数更新:根据优化器规则(如SGD)和当前梯度更新参数。
1
optimizer.step()
- 若使用其他优化器(如Adam),只需修改定义:
- 不同优化器需调整学习率(如Adam常用 lr=0.001)。
1
2# 替换SGD为Adam
optimizer = torch.optim.Adam([x], lr=0.01)
- 不同优化器需调整学习率(如Adam常用 lr=0.001)。
f"..."
:格式化字符串,指在字符串中直接嵌入变量或表达式。x.detach()
:分离计算图- 作用:将张量 x 从当前计算图中分离,
返回一个不关联梯度的新张量
。 - 必要性:
- PyTorch张量若参与过梯度计算(如 requires_grad=True),直接操作可能引发错误。
- detach() 切断与计算图的联系,避免不必要的梯度追踪。
- 作用:将张量 x 从当前计算图中分离,
.numpy()
:转换为NumPy数组- 作用:将PyTorch张量转换为NumPy数组。
- 要求:
- 张量必须位于CPU上(若在GPU需先 .cpu())。
- 张量不能关联梯度(需先 .detach()
- 为何不直接打印x?
- PyTorch张量直接打印会显示梯度信息、设备位置(如GPU)等冗余内容,转换为NumPy数组后更简洁。
.round(4)
:数值四舍五入到指定小数位(此处保留4位)。
广播机制
当对两个形状不同的张量进行按元素操作(如加减乘除)时,PyTorch会自动触发
广播机制,尝试将张量扩展为兼容的形状以完成运算。
示例:
- 向量 + 标量
1
2
3
4
5
6
7
8import torch
a = torch.tensor([1, 2, 3]) # 形状 (3,)
b = torch.tensor(10) # 形状 () → 标量
# PyTorch自动将b广播为[10, 10, 10],然后相加
c = a + b
print(c) # 输出 tensor([11, 12, 13]) - 矩阵 + 向量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16a = torch.tensor([[1], [2], [3]]) # 形状 (3, 1)
b = torch.tensor([10, 20, 30]) # 形状 (3,)
# PyTorch将a扩展为 (3,3),b扩展为 (3,3):
# a → [[1, 1, 1],
# [2, 2, 2],
# [3, 3, 3]]
# b → [[10, 20, 30],
# [10, 20, 30],
# [10, 20, 30]]
c = a + b
print(c)
# 输出:
# tensor([[11, 21, 31],
# [12, 22, 32],
# [13, 23, 33]])
索引和切片
张量中的元素可以通过索引访问。第一个元素的索引是0,最后一个元素索引是-1。
基本语法:X[start:end]
- start:起始索引(包含该位置的元素)
- end:结束索引(不包含该位置的元素)
- 范围:
[start, end)
,即左闭右开区间。
1 |
|
指定索引赋值
- 指定单个元素
1
X[1, 2] = 9
- 为多个元素赋值相同的值
示例:[0:2, :]
访问第1行和第2行,其中“:”代表沿轴1(列)的所有元素。1
X[0:2, :] = 12
执行原地操作节省内存
使用切片表示法将操作的结果分配给先前分配的数组。
- 使用X[:] = X + Y或X += Y来减少操作的内存开销。
将张量转换为其他Python对象
转换为Python标量:
.item()
- 仅限单元素张量
转换为NumPy数组:
.detach().numpy()
- NumPy数组与原始张量共享内存,修改一方会影响另一方
转换为Python列表:
.tolist()
完整的深度学习项目框架
之前的示例代码仅展示了深度学习流程中的核心片段(前向传播、损失计算、反向传播与参数更新)。
一个完整的深度学习项目包括:
- 数据准备
- 数据加载:从文件(CSV、图像等)或数据库读取原始数据。
- 数据预处理:标准化、归一化、数据增强(图像旋转/翻转)。
- 数据划分:分为训练集、验证集、测试集。
- 模型定义
- 结构化模型类:使用 nn.Module 定义网络结构。
- 设备选择:CPU/GPU
- 训练流程
- 完整的训练循环:包含多个Epoch和批次训练。
- 验证与测试:评估模型泛化性能。
- 模型评估与优化
- 评估指标:除准确率外,可加入混淆矩阵、F1分数等。
- 超参数调优:使用网格搜索或自动化工具(如Optuna)。
- 学习率调度:动态调整学习率。
- 结果保存与部署
- 模型保存:保存训练好的模型参数。
- 结果可视化:绘制损失/精度曲线。
- 部署接口:构建预测API或导出为ONNX格式。
注释:
数据预处理
读取数据集
数据集的读取方式因数据类型(如图像、文本、结构化数据)和存储形式(如本地文件、数据库、云存储)而异。
关键:
- 使用
绝对路径
或统一管理路径变量。
示例:1
2import os
DATA_DIR = os.path.expanduser("~/datasets/mnist") # 跨平台兼容路径 - 数据校验:读取后检查数据量和维度是否合理。
示例:1
2print(f"加载图像数量: {len(dataset)}")
print(f"单张图像形状: {dataset[0][0].shape}") - 文本文件需指定正确编码(如utf-8、gbk)。
- 图像需统一通道顺序(如RGB vs BGR)。
结构化数据(CSV/Excel)
- 常见场景:表格数据(如房价预测、用户行为分析)。
- 核心工具:Pandas库。
第一步:读取CSV文件
1 |
|
注释:
- 在代码
data.iloc[:, :-1]
中,-1 是 Python 的负索引语法,表示从后向前计数。这里的 :-1 表示选取从第一列到倒数第二列的所有数据(即排除最后一列),目的是将最后一列作为标签(Label),其余列作为特征(Features)。:
:选取所有行。-1
:直接选取最后一列。
第二步:处理缺失值
1 |
|
注释:
策略1的使用场景:
- 缺失值占比小(如<5%),删除后不影响数据量。
- 缺失值随机分布,删除不会引入偏差。
策略2中的SimpleImputer的strategy可选值:
- “mean”(均值):是计算每个特征的均值
- “median”(中位数)
- “most_frequent”(众数)
- “constant”(固定值):需配合 fill_value 参数
策略2 的使用场景:
- 特征符合正态分布或接近对称分布(均值能较好代表中心趋势)。
- 缺失值较多或删除会导致数据不足的情况。
- 均值策略仅适用于数值型特征。
- 分类特征需改用众数——
strategy="most_frequent"
图像数据
- 常见场景:计算机视觉任务(分类、检测)。
- 核心工具:PIL/Pillow、OpenCV、TensorFlow/PyTorch工具。
从文件夹读取(按类别存储)
假设数据集结构如下:
1 |
|
示例代码:
1 |
|
注释:
batch_size=32
定义了每个批次(batch)中包含的样本数量。- 将整个数据集分成小批量,避免一次性加载全部数据导致内存不足,平衡内存占用与训练效率。
- batch_size的常用值为
32、64、128
。 - 每个批次计算一次梯度并更新参数(即
小批量梯度下降
)。
shuffle=True
设置:在每个训练轮次(epoch)开始时,随机打乱数据顺序。- 防止顺序偏差:避免模型因数据排列顺序而学到非泛化性模式(如总是先看到某一类样本)。
- 增强泛化性:打乱数据后,模型更均匀地学习不同特征,减少过拟合风险。
- 训练阶段:shuffle通常设为 True。
- 验证/测试阶段:通常设为 False(保持数据顺序一致)。
单张图像读取
1 |
|
文本处理
- 常见场景:自然语言处理(情感分析、机器翻译)。
- 核心工具:Python内置文件操作、NLTK、Hugging Face Datasets。
读取文本文件
1 |
|
使用Hugging Face数据集库
1 |
|
音频数据
- 常见场景:语音识别、声音分类。
- 核心工具:Librosa、TorchAudio。
1 |
|