1、概述
初识深度学习
什么是深度学习?

深度学习是一种通过多层神经网络来模拟人脑工作方式的技术。通过这种多层网络,深度学习模型能够自动提取数据中的特征,并基于这些特征进行预测、分类、生成等任务
- 多层结构:深度学习模型通常包含多个隐藏层,能够处理复杂的非线性问题。
- 自动特征学习:深度学习能够自动从数据中学习特征,而不需要人工设计特征。
- 高效的表示学习:每一层都会从前一层提取更高层次的特征表示。
深度神经网络的本质:
通过多层的神经元层级结构,以逐层抽象和表示数据中的复杂模式来进行学习。与传统的浅层神经网络不同,深度神经网络拥有更多的隐藏层(深层结构),使得它能够捕捉和表示更复杂的数据关系和特征。其强大之处在于可以通过隐藏层神经元的非线性空间变换使得非线性数据变得线性可分


核心模型:
- 卷积神经网络(CNN):专门用于处理具有网格结构的数据(如图像)的深度神经网络。CNN通过模拟生物视觉神经系统的处理方式,在图像分类、人脸识别等任务中取得了显著成果
- 循环神经网络:能够通过历史信息推断当前信息。 处理序列数据的神经网络模型,广泛应用于自然语言处理、时间序列预测等领域。RNN的特点是网络中的节点可以有"记忆",能够处理并学习输入序列的上下文信息
- 注意力神经网络:用于提升模型性能的技术,尤其在处理序列数据时,通过给予输入不同部分不同的"注意力权重"来强化 模型对重要信息的关注。其核心思想是使模型能在每个时刻重点关注输入序列中与当前任务最相关的部分。 比如chatgpt
进阶:深度生成模型(图像生成,视频生成等等)

深度学习能干什么?
- 计算机视觉(文字识别,人脸识别,物体识别,增大图像分辨率)
- 语音技术(自动语音识别auto Speech Recognition,文本转语音Text to Speech)
- 自然语言处理(机器翻译 ,AI自动问答比如siri,文本生成比如chatgpt,知识图谱)
- 行业应用(自动驾驶识别路况,生物领域用于基因组学分析,医学用于分析医学影像,金融预测股市走势,网站APP推荐系统,农学用于作物识别,天文由于星系形态分类,地质领域用于地震预测)
- 多模态融合(虚拟主播)
- AIGC
技术栈
python
- tensorflow: Tensor(张量) + Flow(流) → 表示数据流图(Data Flow Graph)
- pytorch: 计算图是边运行边生成的
数据集

- MNIST , Flower102 , Twenty Newsgroups , Kaggle Data , GLUE Benchmark
jupyter notebook
IDE可以选择pycharm/vscode
线性代数
标量

向量

- 模长和范数

范数是"衡量向量或矩阵有多大"的数学工具。它像是"长度"的广义概念,用不同方式度量大小。
L1:绝对值和 → 稀疏
L2:平方和开根 → 平滑
L∞:取最大值 → 稳定约束
- 单位向量

- 向量的内积


- 向量的外积


矩阵

- 矩阵的转置

矩阵的转置就是把矩阵的行和列互换,第 i 行第 j 列的元素变成第 j 行第 i 列的元素
- 矩阵的乘法:


- 矩阵的内积

- 矩阵乘法的性质

张量
本质是多维数组,可以看作是向量和矩阵的推广
向量和矩阵的运算方法对张量同样适用

微积分
极限
表示某一点处函数值趋近于某一特定值的过程,一般记为:

极限是一种变化状态的描述,核心思想是无限靠近而永远不能到达
导数
导数是函数的局部性质,指一个函数在某一点附近的变化率

求导公式:

导数和极限

微分
当 Δx 很小时,曲线在局部看起来就像一条直线。我们可以用这条直线的"线性变化"来近似真实变化
导数是"变化率",、微分是"变化量"

偏导数
偏导数是指多元函数在某一点处关于某一变量的导数

梯度

链式法则
链式法则是用来计算复合函数导数的
所谓的链式法则,就是一层一层增加可以"相互抵消"的分子分母


概率
概率
概率是一种用来描述随机事件发生的可能性的数字度量
概率并不客观存在,是一种不确定性的度量
在深度学习中:
- 概率可以用来表示模型的准确率(错误率)
- 概率可以用来描述模型的不确定性
- 可以作为模型损失的度量
在统计学中:
- 概率研究的是一次事件的结果
- 统计研究的是总体数据的情况
- 概率是统计的基础,统计则根据观测的数据反向思考其数据生成过程
事件
事件相当于实验的结果
随机事件其实是指一次或多次随机实验的结果
事件的基本属性包括:可能性,确定性,兼容性
依赖事件指的是事件的发生受其他事件的影响
独立事件指的是事件的发生与其他事件无关
随机变量和概率分布
随机变量是概率统计中用来表示随机事件结果的变量
随机变量包括离散型随机变量和连续型随机变量
概率分布用来描述随机变量的分布情况
概率密度

联合概率和条件概率

贝叶斯定理
贝叶斯定理表明在已知条件概率的情况下,可以推导出联合概率。常用于根据已知信息推测位置信息,公式如下:

极大似然估计
MLE:利用已知的样本结果,反推最有可能导致这样结果的参数值,即找到参数的最大概率取值

环境搭建
- 安装Anaconda
安装参考链接:链接

conda实用命令:
- 查看版本:conda -v
- 更新conda包:conda update 包名
- 查看已安装环境:conda info -e
- 创建环境:conda create -n 名字 python=3.10
- 激活/切换环境:conda activate 环境名
- 退出环境:conda deactivate
- 列出环境下已经安装的包:conda list
- 装包:conda/pip install numpy
- 删除包:conda remove 包名
- 删除环境:conda remove -n 环境名 --all
- 安装CUDA:

- 安装pytorch
参考链接:链接

2、深度神经网络
神经网络原理
神经元模型
本质上就是一个线性模型

神经网络结构

图示为多层感知机(Multi-Layer Perception,MLP),是最基础的 前馈神经网络,它通过多层线性变换 + 非线性激活函数,学习从输入到输出的复杂映射关系。
层数不是越多越好,容易过拟合,在测试集上泛化能力不好
隐藏层的激活函数
激活函数是是神经网络从"线性模型"变成"非线性模型"的关键


输出层的softmax

c
输入层 X → (W1,b1) → 激活函数 → 隐藏层 H → (W2,b2) → 输出层 Y → Softmax → 概率输出
这张图展示了一个 用于4分类任务的多层感知机(MLP) ,由输入层(2维特征)、隐藏层(50个神经元)和输出层(4个类别)组成,最后用 Softmax 把输出转成概率,选出概率最大的那类作为预测结果
- 输入层 X(1×2)
- 表示输入的数据,有两个特征;
- 例如如果你要分类"苹果 vs 香蕉 vs 梨 vs 西瓜",
每个水果可能用两个特征表示(比如重量、颜色)。 - 输入向量的形状是:

- 第一层:线性变换(权重矩阵 W1 + 偏置 b1)

- 激活函数层(非线性层)

激活函数的作用:
让模型不再是简单的"线性映射",而是能"弯曲",学到复杂规律。
- 第二层:线性变换(W2 + b2)

- Softmax 层(输出层激活函数)

这表示:
- 第1类概率最高(0.9);
- 其他类概率较低;
- 所有概率加起来 = 1。
所以:Softmax 把输出层的结果"标准化"为概率。
- 最右边那一列(0.9 → 1)
那一列其实是真实标签(one-hot 编码) 。
比如分类任务中:
- 类别 1 →
[1, 0, 0, 0] - 类别 2 →
[0, 1, 0, 0]
你这张图右边的:
plain
0.9 → 1
0.06 → 0
0.02 → 0
0.02 → 0
表示:
模型预测第1类的概率是0.9,而真实答案确实是"类别1"。
损失函数

训练神经网络的目的是使得最后的损失尽可能减少
交叉熵损失(计算误差)------ Softmax 输出的是预测概率,交叉熵损失衡量"预测概率分布"和"真实标签分布"之间的差距
多层感知机(MLP)
- 为什么要引入非线性?
线性网络的局限:无法表示复杂的非线性数据
如何引入非线性呢------激活函数空间变换


- 多层感知机

隐藏层可以有多层,这里拿三层举例:

- 深度网络有什么好处
深度网络即加更多的隐藏层
- 能自动学习特征(不再依赖人工提取);
- 能捕捉复杂的非线性关系;
- 能分层理解世界(从边缘到语义);
- 是图像识别、语音识别、自然语言理解等 AI 技术的核心。
- 激活函数有哪些

(1)sigmoid函数:


线性空间的"非线性",在高维非线性空间却"线性可分"

(2)Tanh函数


但是输入值大的时候会梯度消失
映射到高维空间中,使得原本不能线性可分数据变得线性可分

(3)ReLU函数

导数始终为0或1,故不会梯度消失
高维空间非线性变换激活效果,实现线性可分

(4)softmax函数

前向传播和后向传播
前向传播

用损失函数来衡量预测的结果和真实的结果的差距,比如均方误差

通常我们希望MSE尽量的小,利用的就是反向传播算法和梯度下降算法
后向传播
根据最终输出误差,反向计算每一层的参数(权重和偏置)的梯度,以便更新参数、减小损失。




多层感知机代码实现
数据集-----MNIST数据集
10个数字的分类问题

pytorch搭建神经网络:
数据->网络结构->损失函数->优化器->训练->测试->保存
python
# 导包
import torch
from torchvision import datasets
from torchvision import transforms
import torch.nn as nn
import torch.optim as optim
python
# 1.加载数据集
train_data = datasets.MNIST(root="data/mnist",train=True,transform=transforms.ToTensor(),download=True)
test_data = datasets.MNIST(root="data/mnist",train=False,transform=transforms.ToTensor(),download=True)
| 参数名 | 作用 | 举例 |
|---|---|---|
root="data/mnist" |
指定数据存放的根目录。 | 数据会保存在项目的 data/mnist/ 文件夹下 |
train=True |
表示要加载 训练集(train set)。 | 如果写 train=False,就是加载测试集(test set) |
transform=transforms.ToTensor() |
定义数据预处理方式 。这里把图片从PIL格式转为Tensor,并自动把像素值从0255归一化到01。 | |
download=True |
如果本地没有数据集,就会自动从官网下载安装。 | 若已有,就不会重复下载 |

python
# 把MNIST数据集分成一批一批的小组(mini-batches)送进模型训练和测试
# 每次选择100张图片进行训练
batch_size = 100
train_loader = torch.utils.data.DataLoader(dataset=train_data,batch_size=batch_size,shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_data,batch_size=batch_size,shuffle=False)
| 参数名 | 含义 | 举例 |
|---|---|---|
dataset |
要加载的数据集对象(比如 train_data、test_data) |
从前面的 datasets.MNIST() 得到 |
batch_size |
每次送入网络的样本数量(mini-batch大小) | 这里设置为 100,即每次用100张图片训练一次 |
shuffle |
是否在每个epoch开始前打乱数据顺序 | 训练集用 True(防止模型记忆顺序),测试集用 False(保持固定顺序) |
python
# 2.定义 MLP 网络结构 继承nn.Module
class MLP(nn.Module):
# 初始化方法
# input_size输入数据的维度
# hidden_size 隐藏层的大小
# num_classes 输出分类的数量
def __init__(self, input_size, hidden_size, num_classes):
# 调用父类的初始化方法
super(MLP, self).__init__()
# 定义第1个全连接层
self.fc1 = nn.Linear(input_size, hidden_size)
# 定义激活函数
self.relu = nn.ReLU()
# 定义第2个全连接层
self.fc2 = nn.Linear(hidden_size, hidden_size)
# 定义第3个全连接层
self.fc3 = nn.Linear(hidden_size, num_classes)
# 定义forward函数,前向传播
# x 输入的数据
def forward(self, x):
# 第一层运算,输入 → 第1层
out = self.fc1(x)
# 将上一步结果送给激活函数
out = self.relu(out)
# 将上一步结果送给fc2
out = self.fc2(out)
# 同样将结果送给激活函数
out = self.relu(out)
# 将上一步结果传递给fc3
out = self.fc3(out)
# 返回结果
return out
# 定义参数
input_size = 28 * 28 # 输入大小,即每张图片的像素数
hidden_size = 512 # 隐藏层大小,每层512个神经元
num_classes = 10 # 输出大小(类别数),数字类别0~9
# 初始化MLP
model = MLP(input_size, hidden_size, num_classes)
| 层 | 输入维度 | 输出维度 | 激活函数 |
|---|---|---|---|
| 输入层 | 28×28 = 784 | 512 | ReLU |
| 隐藏层 | 512 | 512 | ReLU |
| 输出层 | 512 | 10 | 无(交叉熵里会自动用Softmax) |
python
# 3.定义损失函数loss
# 这个是交叉熵损失函数
criterion = nn.CrossEntropyLoss()
python
# 4.优化器optim,即负责更新参数的人
learning_rate = 0.001 # 学习率
# Adam 优化器
optimizer = optim.Adam(model.parameters(),lr=learning_rate)
| 参数名 | 含义 |
|---|---|
model.parameters() |
告诉优化器要更新哪些参数(就是你网络里所有可学习的权重和偏置) |
lr=learning_rate |
设置学习率(步长) |
python
# 5.训练
# 训练网络
num_epochs = 10 # 训练轮数
# 外层循环: 训练多少轮(epochs)
for epoch in range(num_epochs):
# 内层循环: 每一轮中遍历所有训练数据(mini-batches)
# 每次循环拿到一批图片 images 和对应的数字标签 labels
for i, (images, labels) in enumerate(train_loader):
# 将iamges转成向量,也就是展品图片
images = images.reshape(-1, 28 * 28)
# 将数据送到网络中,前向传播
outputs = model(images)
# 计算损失
loss = criterion(outputs, labels)
# 首先将梯度清零。每次更新前要清零,不然会梯度累积
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
# 打印训练状态
if (i + 1) % 100 == 0:
print(f'Epoch [{epoch+1}/{num_epochs}], Step [{i+1}/{len(train_loader)}], Loss: {loss.item():.4f}')
每次取100张图片 → 前向传播 → 反向传播 → 更新权重。
然后再取下一批,如此循环一轮叫做一个 epoch。
reshape就是因为MLP只接受一维输入, 而原始图片是 1×28×28 的二维灰度图。这一步的作用就是展平图片。
100, 1, 28, 28\] → \[100, 784

python
# 6.测试
# 在测试阶段不需要计算梯度
with torch.no_grad():
# correct 统计预测正确的图片数量
correct = 0
# total 统计测试图片总数(10000张)
total = 0
# 从 test_loader中循环读取测试数据
for images, labels in test_loader:
# 将images转成向量,即把 1×28×28 的图片展平为 784 维的向量
images = images.reshape(-1, 28 * 28)
# 将数据送给网络
outputs = model(images)
# 取出最大值对应的索引 即预测值
# torch.max 返回每行(即每张图片)得分最高的索引;
_, predicted = torch.max(outputs.data, 1)
# 累加label数
total += labels.size(0)
# 预测值与labels值比对 获取预测正确的数量
correct += (predicted == labels).sum().item()
# 打印最终的准确率
print(f'Accuracy of the network on the 10000 test images: {100 * correct / total} %')

python
# 7.保存模型
torch.save(model,"mnist_mlp_model.pkl")
回归问题
- 一元线性回归

目标是找到那条最好的直线,这就是最优化问题
经典问题:民主投票问题:

找最优直线就变成了找k和b使得函数值尽可能小,也就是

了解公式:

- 多元线性回归

计算过程:

- 多项式回归

线性回归代码实现

python
# 数据生成
import numpy as np
import torch
# 设置随机数种子,使得每次运行代码生成的数据相同
np.random.seed(42)
# 生成随机数据,w为2,b为1
x = np.random.rand(100, 1)
y = 1 + 2 * x + 0.1 * np.random.randn(100, 1)
# 将数据转换为 pytorch tensor
x_tensor = torch.from_numpy(x).float()
y_tensor = torch.from_numpy(y).float()
这里的tensor是张量的意思,是一种多维数组。在机器学习领域,tensor通常用来用来表示训练数据、模型参数、输入数据等
torch.from_numpy(x)
把 NumPy 数组 x 转换为 PyTorch 张量(Tensor)。这一步不会复制数据,底层内存共享。
.float()
把数据类型强制转换成 float32,因为神经网络中的计算(尤其是梯度)一般要求浮点型
python
# 设置超参数
learning_rate = 0.1 #学习率
num_epochs = 1000 #最大迭代次数
python
# 初始化参数,可以使用常数、随机数或预训练等
# 使用pytorch的randn函数来初始化参数w和zeros函数来初始化参数b
w = torch.randn(1, requires_grad = True)
b = torch.zeros(1, requires_grad = True)
randn函数会生成一个均值为0,标准差为1的随机张量,而zeros函数会生成一个全部元素都是0的张量
python
# 开始训练
# 迭代了最大迭代次数
for epoch in range(num_epochs):
# 计算预测值
y_pred = x_tensor * w + b
# 计算损失函数
loss = ((y_pred - y_tensor) ** 2).mean()
# 反向传播梯度
loss.backward()
# 更新参数w和b
with torch.no_grad():
w -= learning_rate * w.grad
b -= learning_rate * b.grad
# 清空梯度
w.grad.zero_()
b.grad.zero_()
# 输出训练后的参数,与数据生成时设置的常数基本一致
print('w:', w)
print('b:', b)

python
# 可视化
import matplotlib.pyplot as plt
# 'o' 表示用圆点绘制散点图。
plt.plot(x, y, 'o')
plt.plot(x_tensor.numpy(), y_pred.detach().numpy())
plt.show()
| 部分 | 解释 |
|---|---|
x_tensor |
训练时用的 PyTorch 张量 |
.numpy() |
转换为 NumPy 数组(因为 Matplotlib 不接受 Tensor) |
y_pred |
模型预测输出(Tensor) |
.detach() |
断开与计算图的联系(不再跟踪梯度) |
.numpy() |
转成 NumPy 数组用于绘图 |

完整代码:
python
import numpy as np
import torch
# 设置随机数种子,使得每次运行代码生成的数据相同
np.random.seed(42)
# 生成随机数据,w为2,b为1
x = np.random.rand(100, 1)
y = 1 + 2 * x + 0.1 * np.random.randn(100, 1)
# 将数据转换为 pytorch tensor
x_tensor = torch.from_numpy(x).float()
y_tensor = torch.from_numpy(y).float()
# 设置超参数
learning_rate = 0.1
num_epochs = 1000
# 初始化参数,可以使用常数、随机数或预训练等
w = torch.randn(1, requires_grad=True)
b = torch.zeros(1, requires_grad=True)
# 开始训练
for epoch in range(num_epochs):
# 计算预测值
y_pred = x_tensor * w + b
# 计算损失
loss = ((y_pred - y_tensor) ** 2).mean()
# 反向传播
loss.backward()
# 更新参数
with torch.no_grad():
w -= learning_rate * w.grad
b -= learning_rate * b.grad
# 清空梯度
w.grad.zero_()
b.grad.zero_()
# 输出训练后的参数,与数据生成时设置的常数基本一致
print('w:', w)
print('b:', b)

进阶:pytorch实现
python
# pytorch模型实现
import numpy as np
import torch
import torch.nn as nn
# 设置随机数种子,使得每次运行代码生成的数据相同
np.random.seed(42)
# 生成随机数据
x = np.random.rand(100, 1)
y = 1 + 2 * x + 0.1 * np.random.randn(100, 1)
# 将数据转换为 pytorch tensor
x_tensor = torch.from_numpy(x).float()
y_tensor = torch.from_numpy(y).float()
# 设置超参数
learning_rate = 0.1
num_epochs = 1000
# 定义输入数据的维度和输出数据的维度
input_dim = 1
output_dim = 1
# 定义模型,就是一个神经元
model = nn.Linear(input_dim, output_dim)
# 定义损失函数和优化器
criterion = nn.MSELoss()
optimizer = torch.optim.SGD(model.parameters(), lr = learning_rate)
# 开始训练
for epoch in range(num_epochs):
# 将输入数据喂给模型
y_pred = model(x_tensor)
# 计算损失
loss = criterion(y_pred, y_tensor)
# 清空梯度
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新参数
optimizer.step()
# 输出训练后的参数
print('w:', model.weight.data)
print('b:', model.bias.data)

分类问题
分类定义:
- 将输入数据集划分到一个或多个类别中的过程。
- 类别是事先确定的,并且标签的类别是已知的
- 输出是一个离散的类别标签,而不是连续的值
- 多分类问题的数学表示:


- 线性模型----softmax回归(多项式逻辑回归)
当输出类别不止两个时(K 类分类问题),我们希望模型输出 每个类别的概率

为什么不继续使用MSE?因为多项式逻辑回归输出值是一个概率而不是连续的实数值,通常使用对数似然函数或者交叉熵
- 损失函数----对数似然函数
对数运算是单调的,具有结合性(可将多个数的乘积转换为多个数和的形式),还有放缩性

- 损失函数----交叉熵损失函数

多分类问题代码实现

python
# 加载MINST数据集
import torch
import torchvision
transformation = torchvision.transforms.ToTensor()
train_dataset = torchvision.datasets.MNIST(root='data/mnist', train=True, download=True, transform=transformation)
test_dataset = torchvision.datasets.MNIST(root='data/mnist', train=False, download=True, transform=transformation)
python
# 数据加载器
batch_size = 64
train_dataloader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_dataloader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
import matplotlib.pyplot as plt
# 每次循环自动加载一个 mini-batch 的图像和标签
# images 是一个 Tensor(形状 [batch_size, 1, 28, 28])
# labels 是对应的标签 Tensor(形状 [batch_size])
for i, (images, labels) in enumerate(train_dataloader):
print(images.shape, labels.shape)
plt.imshow(images[0][0], cmap='gray')
plt.show()
# 打印图片对应的真实数字标签
print(labels[0])
if i > 10:
break
结果示例:
torch.Size([64, 1, 28, 28]) torch.Size([64])
- 一次取出64张图片;
- 每张图片是单通道(灰度),尺寸28×28;
- 标签是64个整数(0~9)
plt.imshow(images[0][0], cmap='gray')
images[0]:取出第一个样本(形状[1, 28, 28]);images[0][0]:去掉通道维度(取出[28, 28]的像素矩阵);cmap='gray':用灰度显示;












python
# 构建网络
import torch.nn as nn
# 继承自 nn.Module,这是所有 PyTorch 模型的基类
class Model(nn.Module):
# 初始化层结构
# 表示一个全连接层,y=wx+b
def __init__(self, input_size, output_size):
super().__init__()
# input_size = 784(28×28) → 输入层
# output_size = 10(数字类别) → 输出层
self.linear = nn.Linear(input_size, output_size)
# x 是输入(形状 [batch_size, 784])
def forward(self, x):
# self.linear(x) 执行矩阵乘法 𝑊𝑥+𝑏
logits = self.linear(x)
# 返回值 logits 是每个样本在10个类别上的"打分"
return logits
input_size = 28*28
output_size = 10
# 创建一个784 → 10 的线性模型
model = Model(input_size, output_size)
| 代码模块 | 含义 |
|---|---|
nn.Linear(784, 10) |
把28×28图片展平后映射到10个类别 |
forward() |
定义前向传播(计算logits) |
CrossEntropyLoss() |
自动包含Softmax + log计算 |
| 整个网络 | 就是Softmax回归,用于多分类 |
python
# 损失函数和优化器
# 多分类任务(Multi-class Classification) 的标准损失函数
# 其实是Softmax + 交叉熵(Cross-Entropy)
criterion = nn.CrossEntropyLoss()
# 随机梯度下降(Stochastic Gradient Descent, SGD) 来更新模型参数
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
python
# 模型评估
# 不进行训练,只在验证集 / 测试集上运行模型,计算预测准确率(accuracy)
def evaluate(model, data_loader):
# 将模型切换到 评估模式(evaluation mode)
model.eval()
correct = 0
total = 0
# no_grad说明下面的代码块中不需要计算梯度
with torch.no_grad():
# 从数据加载器中一批批取出图片和标签,x:输入数据,y:真实标签
for x, y in data_loader:
# 展平输入,会把 [batch_size, 1, 28, 28]变成 [batch_size, 784]
x = x.view(-1, input_size)
# logits 是模型的输出分数
logits = model(x)
_, predicted = torch.max(logits.data, 1)
total += y.size(0)
correct += (predicted == y).sum().item()
# 返回预测正确数/总样本数
return correct / total
_, predicted = torch.max(logits.data, 1)
torch.max(tensor, dim)返回两个值:
每行的最大值(这里不关心)
最大值所在的索引(即预测类别)predicted就是模型预测的类别编号(0~9)。
total += y.size(0)
correct += (predicted == y).sum().item()
y.size(0):当前批次的样本数;(predicted == y):比较预测是否正确;.sum():统计预测正确的数量;.item():把Tensor转成Python标量;- 最后累加到
correct和total中
python
# 模型训练
for epoch in range(10):
# 把模型切换为 训练模式
model.train()
# 每次循环从 train_dataloader 中取出一个 mini-batch(64张图片)
for images, labels in train_dataloader:
# 将图像和标签转换成张量
# 把二维图片展平为一维;[64, 1, 28, 28] → [64, 784]
images = images.view(-1, 28*28)
# 要求标签是整数类型
labels = labels.long()
# 前向传播
# 模型计算线性输出
outputs = model(images)
# 调用 nn.CrossEntropyLoss(),内部自动做 Softmax,然后计算交叉熵损失
loss = criterion(outputs, labels)
# 反向传播和优化
# 每次反向传播前都要清空梯度
optimizer.zero_grad()
loss.backward()
# 梯度更新参数
optimizer.step()
accuracy = evaluate(model, test_dataloader)
print(f'Epoch {epoch+1}: test accuracy = {accuracy:.2f}')

3、训练常见问题及对策
训练常见问题
- 模型架构设计

- 万能近似定理

- 宽度 or 深度

- 过拟合问题

- 欠拟合问题


过拟合欠拟合应对策略
- 问题的本质
数据和模型匹配问题:
- 数据复杂度
- 数据集大小的选择:数据集较小,很容易出现过拟合;数据集过大可能导致训练效率低
- 我们可以使用更多的数据训练,数据量不够就增加假数据,并添加到数据集,即数据增强。比如对训练数据进行变换,增强数据数量和多样性;有效解决过拟合问题,提高模型在新数据上的泛化能力
- 使用验证集:训练中评估模型性能,调整超参数

- 模型复杂度
- 奥卡姆剃刀法则:选择简单、合适的模型解决复杂的问题
- 训练策略
- K者交叉验证:将训练数据分成k份,对于每份数据作为验证集,剩余的k-1份数据作为训练集,进行训练和验证,计算k次验证的平均值
- 提前终止

过拟合和欠拟合示例
python
# 导入必要的库
import numpy as np
import torch
import torch.nn as nn
import matplotlib.pyplot as plt # 用于数据可视化
from torch.utils.data import DataLoader, TensorDataset # 用于构造数据加载器
from sklearn.model_selection import train_test_split # 用于划分数据集
python
# 1.数据生成
# 设置随机种子
np.random.seed(32)
# 生成满足 y = x^2 + 1 的数据
num_samples = 100 # 100个样本点
X = np.random.uniform(-5, 5, (num_samples, 1)) # 均匀分布
Y = X ** 2 + 1 + 5 * np.random.normal(0, 1, (num_samples, 1)) # 正态分布噪声
# 将 NumPy 变量转化为浮点型 PyTorch 变量
X = torch.from_numpy(X).float()
Y = torch.from_numpy(Y).float()
# 绘制数据散点图
plt.scatter(X, Y)
plt.show()

python
# 2.数据划分
# 将数据拆分为训练集和测试集
train_X, test_X, train_Y, test_Y = train_test_split(X, Y, test_size=0.3, random_state=0)
# 将数据封装成可迭代的数据加载器
train_dataloader = DataLoader(TensorDataset(train_X, train_Y), batch_size=32, shuffle=True)
test_dataloader = DataLoader(TensorDataset(test_X, test_Y), batch_size=32, shuffle=False)
TensorDataset(train_X, train_Y)
- 功能 :将两个张量(
train_X和train_Y)打包成一个数据集对象。 - 要求 :
train_X和train_Y的第一维长度必须相同(即样本数相等)。 - 结果 :每次取数据时,会同时返回一对
(x, y),对应一个样本及其标签
- DataLoader(...)
- 功能 :从
TensorDataset中按批次取数据,方便训练时迭代。 - 参数:
batch_size=32:每次训练取 32 个样本。shuffle=True:每个 epoch 开始前打乱数据顺序,提升泛化能力
python
# 3.模型定义
# 定义线性回归模型(欠拟合)
class LinearRegression(nn.Module):
def __init__(self):
super().__init__()
# 只有一层线性层,y=wx+b
self.linear = nn.Linear(1, 1) #输入维度为1,输出维度也设为1
def forward(self, x):
return self.linear(x)
# 定义多层感知机(正常)
class MLP(nn.Module):
def __init__(self):
super().__init__()
self.hidden = nn.Linear(1, 8) #隐藏层包含8个神经元,输入维度为1
self.output = nn.Linear(8, 1) #输出为1
def forward(self, x):
x = torch.relu(self.hidden(x))
return self.output(x)
# 定义更复杂的多层感知机(过拟合)
class MLPOverfitting(nn.Module):
def __init__(self):
super().__init__()
self.hidden1 = nn.Linear(1, 256) #第一个隐藏层256个神经元
self.hidden2 = nn.Linear(256, 256) #第二个隐藏层256个神经元
self.output = nn.Linear(256, 1)
def forward(self, x):
x = torch.relu(self.hidden1(x))
x = torch.relu(self.hidden2(x))
return self.output(x)
python
# 4.辅助函数
# 对传入的多个模型(如线性模型、MLP、复杂MLP),分别训练并计算每个 epoch
#的训练误差和测试误差,为后续绘图比较"欠拟合-正常-过拟合"提供数据
def plot_errors(models, num_epochs, train_dataloader, test_dataloader):
# 定义损失函数,这里用的均方误差
loss_fn = nn.MSELoss()
# 保存模型训练误差和测试误差数组
train_losses = []
test_losses = []
# 遍历每类模型
for model in models:
# 每个模型单独定义一个优化器
optimizer = torch.optim.SGD(model.parameters(), lr=0.005)
# 每类模型的训练和测试误差
train_losses_per_model = []
test_losses_per_model = []
# 迭代训练,每次 epoch 表示模型完整地看一遍训练数据
for epoch in range(num_epochs):
# 启用训练模式
model.train()
train_loss = 0
# 遍历训练集
for inputs, targets in train_dataloader:
# 预测、损失函数、反向传播
# 清空上次计算的梯度
optimizer.zero_grad()
# 前向传播,预测输出
outputs = model(inputs)
# 计算预测与真实标签的误差
loss = loss_fn(outputs, targets)
# 反向传播,计算梯度
loss.backward()
# 更新模型参数
optimizer.step()
# 记录loss
train_loss += loss.item()
# 计算loss并记录
train_loss /= len(train_dataloader)
train_losses_per_model.append(train_loss)
# 在测试数据上评估,测试模型不计算梯度
model.eval()
test_loss = 0
with torch.no_grad():
# 遍历测试集
for inputs, targets in test_dataloader:
# 预测、损失函数
outputs = model(inputs)
loss = loss_fn(outputs, targets)
# 记录loss
test_loss += loss.item()
# 计算loss并记录
test_loss /= len(test_dataloader)
test_losses_per_model.append(test_loss)
# 记录当前模型每轮的训练测试误差
train_losses.append(train_losses_per_model)
test_losses.append(test_losses_per_model)
return train_losses, test_losses
python
for 每个模型:
初始化优化器
for 每个epoch:
训练阶段(计算并更新梯度)
测试阶段(评估模型性能)
保存训练误差与测试误差
返回所有模型的误差曲线
python
# 5.获取训练和测试误差曲线数据
# 让三种模型分别训练 200 轮,并记录每一轮的训练误差和测试误差
num_epochs = 200
# 定义了一个模型列表
models = [LinearRegression(), MLP(), MLPOverfitting()]
train_losses, test_losses = plot_errors(models, num_epochs, train_dataloader, test_dataloader)
python
# 6.绘制训练和测试误差曲线
for i, model in enumerate(models):
# 新建一张大小为 8×4 英寸的图表
plt.figure(figsize=(8, 4))
plt.plot(range(num_epochs), train_losses[i], label=f"Train {model.__class__.__name__}")
plt.plot(range(num_epochs), test_losses[i], label=f"Test {model.__class__.__name__}")
plt.legend()
plt.ylim((0, 200))
plt.show()



正则化
定义:对学习算法的修改,目的是减少泛化误差,而不是训练误差

- 没有免费午餐定理
没有一种算法或者模型能够在所有的场景中都表现良好
也就是说:
- 如果我们不对任务或数据分布做任何假设,不存在"普遍更好的算法"
- 一个算法在某类任务上表现优秀,必然在另一类任务上表现较差
- 因此,"免费的午餐"不存在------想获得更好的性能,必须付出代价:引入先验知识或领域假设
正则化是一种权衡过拟合和欠拟合的手段

- L2正则化
通过给模型的损失函数添加一个模型参数的平方和的惩罚项实现正则化

这个也叫岭回归
空间解释:

L2正则化更倾向于小的非零权值,更适用于优化问题
- L1正则化
通过在损失函数中加入对模型参数权值矩阵中各元素绝对值之和的惩罚项,来限制模型参数的值

空间解释:

L1正则化更倾向于产生稀疏解,适于特征选择
- 范数惩罚
将L1和L2正则化扩展到一般情况:

- 权重衰减

Dropout
Dropout 是一种用于防止神经网络过拟合(overfitting) 的正则化方法
核心思想:在每次训练迭代中,随机"丢弃"一部分神经元(即让它们的输出暂时为 0),防止模型过度依赖某些特定节点
有什么用?
在深度神经网络中,如果参数太多、训练样本有限,模型可能:
- 记住训练集中的噪声;
- 对训练数据表现极好,但在测试集上泛化能力差。
这时 Dropout 就像是一种"集成学习"思想:每次训练时"禁用"不同子集的神经元,相当于训练了多个不同结构的网络,然后在测试时平均它们的效果
工作原理:
- 训练过程中随机"删除"(即将其权重设为0)一些神经元
- 只在训练期间,不用在测试期间
步骤:
- 指定一个保留的比例p(0.5或0.3)
- 每层每个神经元,以P的概率保留,以1-p的概率将权重设为0
- 训练中使用保留的神经元进行前向、反向传播
- 测试过程,将所有权重乘以p

在神经网络中的使用:

每个神经元生成一个随机生成的r,这是一个随机变量,这个要么0要么1

为什么能减少过拟合?
- 本质是Bagging集成学习,平均化作用
- 减少神经元之间复杂的关系
- 类似性别在生物进化中的角色

优点:可以有效地减少过拟合,简单方便,实用有效
缺点:降低训练效率,损失函数不够明确
Dropout代码实现
python
# 导入必要的库
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
# 随机数种子
torch.manual_seed(2333)
# 定义超参数
num_samples = 20 # 样本数
hidden_size = 200 # 隐藏层大小
num_epochs = 500 # 训练轮数
python
# 数据生成
# 生成训练集
x_train = torch.unsqueeze(torch.linspace(-1, 1, num_samples), 1)
y_train = x_train + 0.3 * torch.randn(num_samples, 1)
# 测试集
x_test = torch.unsqueeze(torch.linspace(-1, 1, num_samples), 1)
# torch.randn(num_samples, 1)生成服从 标准正态分布 N(0,1) 的噪声
y_test = x_test + 0.3 * torch.randn(num_samples, 1)
# 绘制训练集和测试集
# 'r'是red的意思
plt.scatter(x_train, y_train, c='r', alpha=0.5, label='train')
# 'b'是blue的意思
plt.scatter(x_test, y_test, c='b', alpha=0.5, label='test')
plt.legend(loc='upper left')
plt.ylim((-2, 2))
plt.show()
torch.linspace(-1, 1, num_samples)
生成一个从 -1 到 1 的等间距序列,共num_samples个点
相当于自变量 xxx。torch.unsqueeze(..., 1)
把一维张量(num_samples,)变成二维(num_samples, 1),
这样更方便神经网络输入(每一行一个样本)

python
# 模型定义
# 定义一个可能会过拟合的网络
net_overfitting = torch.nn.Sequential(
torch.nn.Linear(1, hidden_size),
torch.nn.ReLU(),
torch.nn.Linear(hidden_size, hidden_size),
torch.nn.ReLU(),
torch.nn.Linear(hidden_size, 1),
)
# 定义一个包含 Dropout 的网络
net_dropout = torch.nn.Sequential(
torch.nn.Linear(1, hidden_size),
torch.nn.Dropout(0.5), # p=0.5
torch.nn.ReLU(),
torch.nn.Linear(hidden_size, hidden_size),
torch.nn.Dropout(0.5), # p=0.5
torch.nn.ReLU(),
torch.nn.Linear(hidden_size, 1),
)
| 层级 | 类型 | 输入维度 | 输出维度 | 作用 |
|---|---|---|---|---|
| 1 | Linear(1, hidden_size) |
1 | hidden_size | 从一维特征映射到高维隐藏层 |
| 2 | ReLU() |
--- | --- | 激活函数,引入非线性 |
| 3 | Linear(hidden_size, hidden_size) |
hidden_size | hidden_size | 第二个隐藏层 |
| 4 | ReLU() |
--- | --- | 再次引入非线性 |
| 5 | Linear(hidden_size, 1) |
hidden_size | 1 | 输出层(预测值) |
python
# 模型训练
# 定义优化器和损失函数
optimizer_overfitting = torch.optim.Adam(net_overfitting.parameters(), lr=0.01)
optimizer_dropout = torch.optim.Adam(net_dropout.parameters(), lr=0.01)
# 损失函数
criterion = nn.MSELoss()
# 分别进行训练
for i in range(num_epochs):
# overfitting的网络:预测、损失函数、反向传播
pred_overfitting = net_overfitting(x_train)
loss_overfitting = criterion(pred_overfitting, y_train)
optimizer_overfitting.zero_grad()
loss_overfitting.backward()
optimizer_overfitting.step()
# 包含dropout的网络:预测、损失函数、反向传播
pred_dropout = net_dropout(x_train)
loss_dropout = criterion(pred_dropout, y_train)
optimizer_dropout.zero_grad()
loss_dropout.backward()
optimizer_dropout.step()
python
# 预测和可视化
# 在测试过程中不使用 Dropout
net_overfitting.eval()
net_dropout.eval()
# 预测
test_pred_overfitting = net_overfitting(x_test)
test_pred_dropout = net_dropout(x_test)
# 绘制拟合效果
plt.scatter(x_train, y_train, c='r', alpha=0.3, label='train')
plt.scatter(x_test, y_test, c='b', alpha=0.3, label='test')
plt.plot(x_test, test_pred_overfitting.data.numpy(), 'r-', lw=2, label='overfitting')
plt.plot(x_test, test_pred_dropout.data.numpy(), 'b--', lw=2, label='dropout')
plt.legend(loc='upper left')
plt.ylim((-2, 2))
plt.show()

梯度消失和梯度爆炸
梯度的重要性:
- 深度神经网络就是非线性多元函数
- 优化模型就是找到合适权重,最小化损失函数

为什么会导致梯度消失或者梯度爆炸?
反向传播的内在问题:

激活函数的导数小于1容易发生梯度消失

梯度爆炸:梯度可在更新中累积,变得非常大,导致网络不稳定。原因是深层网络和初始化权重的值过大
解决办法:
- 预训练加微调
- 梯度剪切
- ReLU激活函数
- Batchnorm
- 残差结构
- 正则
模型文件的读写
最好训练过程中定时保存模型训练结果
python
# 张量的保存
import torch
# 随机生成六个随机数
a = torch.rand(6)
a
输出:
tensor([0.4478, 0.6800, 0.5987, 0.1600, 0.3066, 0.0166])
# 保存
import os
os.makedirs("model", exist_ok=True) # 如果没有 model 文件夹就创建一个
torch.save(a, "model/tensor_a")
python
# 张量的加载
torch.load("model/tensor_a")
输出:
tensor([0.4478, 0.6800, 0.5987, 0.1600, 0.3066, 0.0166])
python
# 同样的方式保存和加载张量的列表
a = torch.rand(6)
b = torch.rand(6)
c = torch.rand(6)
[a,b,c]

python
torch.save([a,b,c],"model/tensor_abc")
torch.load("model/tensor_abc")

python
# 对于多个张量,pytorch支持有以字典的形式存储
tensor_dict= {'a':a,'b':b,'c':c}
tensor_dict

python
torch.save(tensor_dict,"model/tensor_dict_abc")
torch.load("model/tensor_dict_abc")

python
# 模型的加载与保存
from torchvision import datasets
from torchvision import transforms
import torch.nn as nn
import torch.optim as optim
# 定义 MLP 网络 继承nn.Module
class MLP(nn.Module):
# 初始化方法
# input_size输入数据的维度
# hidden_size 隐藏层的大小
# num_classes 输出分类的数量
def __init__(self, input_size, hidden_size, num_classes):
# 调用父类的初始化方法
super(MLP, self).__init__()
# 定义第1个全连接层
self.fc1 = nn.Linear(input_size, hidden_size)
# 定义激活函数
self.relu = nn.ReLU()
# 定义第2个全连接层
self.fc2 = nn.Linear(hidden_size, hidden_size)
# 定义第3个全连接层
self.fc3 = nn.Linear(hidden_size, num_classes)
# 定义forward函数
# x 输入的数据
def forward(self, x):
# 第一层运算
out = self.fc1(x)
# 将上一步结果送给激活函数
out = self.relu(out)
# 将上一步结果送给fc2
out = self.fc2(out)
# 同样将结果送给激活函数
out = self.relu(out)
# 将上一步结果传递给fc3
out = self.fc3(out)
# 返回结果
return out
# 定义参数
input_size = 28 * 28 # 输入大小
hidden_size = 512 # 隐藏层大小
num_classes = 10 # 输出大小(类别数)
# 初始化MLP
model = MLP(input_size, hidden_size, num_classes)
python
# 方式1
# 保存模型参数
torch.save(model.state_dict(),"model/mlp_state_dict.pth")
# 读取保存的模型参数
mlp_state_dict = torch.load("model/mlp_state_dict.pth")
# 新实例化一个MLP模型
model_load = MLP(input_size,hidden_size,num_classes)
# 调用load_state_dict方法 传入读取的参数
model_load.load_state_dict(mlp_state_dict)
输出:
<All keys matched successfully>
| 项目 | 内容 |
|---|---|
| 保存内容 | 仅保存模型参数(weights、bias) |
| 文件大小 | 小(只含数值) |
| 加载要求 | 需重新定义模型结构(MLP(...)) |
| 可跨文件使用 | 可以在别的脚本或项目中加载 |
| 官方推荐 | PyTorch 官方推荐使用这种方式 |
python
# 方式2
# 保存整个模型
torch.save(model,"model/mlp_model.pth")
# 加载整个模型
mlp_load = torch.load("model/mlp_model.pth")
| 项目 | 内容 |
|---|---|
| 保存内容 | 整个模型对象(结构 + 参数) |
| 文件大小 | 大(包含结构定义信息) |
| 加载要求 | 无需手动定义类(自动恢复) |
| 可跨文件使用 | 依赖原定义文件路径和类名 |
| 官方推荐 | 不推荐(仅在快速测试中用) |
python
# 方式3----checkpoint
# 保存参数
import torch
import torch.nn as nn
import torch.optim as optim
import os
# 路径(先确保目录存在)
PATH = "model/mlp_checkpoint.pth"
os.makedirs("model", exist_ok=True)
# 定义模型
input_size = 28 * 28
hidden_size = 512
num_classes = 10
model = MLP(input_size, hidden_size, num_classes)
# 定义优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 模拟:假设我们训练了 10 轮
epoch = 10
# 模拟:当前损失值(这里随便放一个)
loss = 0.25
# 保存模型训练快照
torch.save({
'epoch': epoch, # 当前训练轮数
'model_state_dict': model.state_dict(), # 模型参数
'optimizer_state_dict': optimizer.state_dict(), # 优化器状态
'loss': loss # 当前loss
}, PATH)
print(" 模型参数及训练状态已成功保存到:", PATH)
输出:
模型参数及训练状态已成功保存到: model/mlp_checkpoint.pth
它不仅保存模型参数,还保存:
- 当前训练轮数
epoch - 当前损失
loss - 优化器状态(
optimizer_state_dict) - (可选)学习率调度器、随机种子等训练状态
目的:
方便在训练中断后继续从上次的状态恢复,不丢进度
python
# 加载参数
model = MLP(28 * 28, 512, 10)
optimizer = optim.Adam(model.parameters(), lr=0.001)
checkpoint = torch.load("model/mlp_checkpoint.pth")
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
epoch = checkpoint['epoch']
loss = checkpoint['loss']
model.eval()




