知识点回顾:
- 彩色和灰度图片测试和训练的规范写法:封装在函数中
- 展平操作:除第一个维度 batchsize 外全部展平
- dropout 操作:训练阶段随机丢弃神经元,测试阶段 eval模式关闭 dropout
零基础学 Python 机器学习:训练 / 测试的规范写法
先跟你说个大前提:咱们今天学的内容就像 “做饭的固定流程”—— 不管做番茄炒蛋(灰度图)还是青椒肉丝(彩色图),都有 “备料→烹饪→装盘” 的规范步骤,封装成函数就不用每次都从头想;展平是把 “叠好的菜板” 摊开,dropout 是 “训练时故意藏起几个厨具,测试时全拿出来用”。全程用 PyTorch(最常用的机器学习框架)举例,代码每行都讲透,保证你能懂。
前置小概念(先吃透,不然后面懵)

知识点 1:彩色 / 灰度图的训练 & 测试(封装成函数)
为什么要封装函数?
就像你把 “早上刷牙→洗脸→吃早饭” 写成一个 “晨起流程” 清单,每次执行就行,不用每次都想步骤。机器学习里训练 / 测试的步骤固定,封装成函数:
- 代码整洁,不容易漏步骤;
- 想换数据集(灰度→彩色),改几行就行;
- 后续调参、复现结果更方便。
函数的核心结构(通用模板)
| 训练函数(train_fn) | 测试函数(test_fn) |
|---|---|
| 1. 模型切到训练模式(开启 dropout) | 1. 模型切到测试模式(关闭 dropout) |
| 2. 遍历数据批次 | 2. 遍历数据批次 |
| 3. 清空梯度(避免累计) | 3. 关闭梯度计算(节省内存) |
| 4. 图片输入模型→得到预测结果 | 4. 图片输入模型→得到预测结果 |
| 5. 计算损失(预测和真实值的差距) | 5. 计算损失 / 准确率 |
| 6. 反向传播(让模型学错在哪) | 6. 不更新参数(只评估) |
| 7. 更新模型参数 | 7. 返回测试损失 / 准确率 |
| 8. 返回训练损失 / 准确率 | - |
知识点 2:展平操作(除 batchsize 外全展平)
什么是展平?
把多维的图片 “摊成一维的线”。比如:
- 灰度图
(10,1,28,28):除了 batchsize=10,剩下的1×28×28=784,展平后变成(10,784); - 彩色图
(10,3,28,28):剩下的3×28×28=2352,展平后变成(10,2352)。
为什么要展平?
简单的神经网络(全连接网络)只能 “吃” 一维向量,就像你把叠好的床单(二维)摊开成一条直线(一维),才能塞进收纳袋(网络)里。
展平的写法(关键!)
PyTorch 里用torch.flatten(数据, start_dim=1):
start_dim=1:从第 1 个维度开始展平(第 0 维是 batchsize,必须保留);- 千万别写成
flatten()(默认从第 0 维展平,会把 batchsize 也拆了,比如(10,1,28,28)变成(7840,),完全错了!)。
举个栗子(展平实操)
import torch
# 模拟10张28×28的灰度图
gray_img = torch.randn(10, 1, 28, 28) # 形状:(10,1,28,28)
# 模拟10张28×28的彩色图
color_img = torch.randn(10, 3, 28, 28) # 形状:(10,3,28,28)
# 展平:从第1维开始
gray_flat = torch.flatten(gray_img, start_dim=1)
color_flat = torch.flatten(color_img, start_dim=1)
print("灰度图展平后形状:", gray_flat.shape) # 输出:torch.Size([10, 784])
print("彩色图展平后形状:", color_flat.shape) # 输出:torch.Size([10, 2352])
知识点 3:Dropout 操作(训练丢,测试关)
什么是 Dropout?
防止模型 “偷懒” 的技巧:训练时随机让一部分神经元 “闭嘴”(不参与计算),比如 100 个神经元随机丢 50 个,剩下的 50 个必须更努力学习,避免模型只依赖少数神经元(就像球队训练时故意不让主力上,让替补也练一练)。
测试时要把所有神经元都打开(主力全上),因为测试是要模型发挥全部能力,不是训练了。
Dropout 的核心规则
| 阶段 | 模型模式 | Dropout 状态 | 额外操作 |
|---|---|---|---|
| 训练 | model.train() | 开启(随机丢弃) | 计算梯度 |
| 测试 | model.eval() | 关闭(全部启用) | torch.no_grad ()(关闭梯度,省内存) |
举个栗子(Dropout 实操)
import torch
import torch.nn as nn
# 定义一个包含Dropout的简单模型
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.dropout = nn.Dropout(p=0.5) # 训练时丢弃50%的神经元
def forward(self, x):
return self.dropout(x)
# 初始化模型
model = SimpleModel()
# 模拟一个输入(1个样本,10个神经元)
x = torch.ones(1, 10) # 输入:[1,1,1,1,1,1,1,1,1,1]
# 训练模式:Dropout生效
model.train()
train_out = model(x)
print("训练模式输出(有神经元被丢弃):", train_out)
# 输出示例:[1., 0., 1., 0., 1., 1., 0., 1., 0., 1.](0就是被丢弃的)
# 测试模式:Dropout关闭
model.eval()
with torch.no_grad(): # 关闭梯度
test_out = model(x)
print("测试模式输出(所有神经元都在):", test_out)
# 输出:[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]
整合所有知识点:完整示例代码(灰度 + 彩色图通用)
咱们用经典数据集做例子:
- 灰度图:MNIST(手写数字,28×28 灰度)
- 彩色图:CIFAR10(日常物品,32×32 彩色)
步骤 1:导入必备库
# 导入PyTorch核心库
import torch
import torch.nn as nn
import torch.optim as optim
# 导入数据集和数据加载工具
from torchvision import datasets, transforms
from torch.utils.data import DataLoader
# 设备选择:有GPU用GPU,没有用CPU(新手不用管,复制就行)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
步骤 2:准备数据集(灰度 + 彩色)
# 数据预处理:把图片转成Tensor(PyTorch能处理的格式)
transform = transforms.Compose([
transforms.ToTensor(), # 转Tensor,维度变成(通道,高,宽)
transforms.Normalize((0.5,), (0.5,)) # 归一化(新手不用懂,复制就行)
])
# ========== 灰度图数据集(MNIST) ==========
train_gray_dataset = datasets.MNIST(
root="./data", # 数据保存路径
train=True, # 训练集
download=True, # 自动下载
transform=transform
)
test_gray_dataset = datasets.MNIST(
root="./data",
train=False, # 测试集
download=True,
transform=transform
)
# ========== 彩色图数据集(CIFAR10) ==========
train_color_dataset = datasets.CIFAR10(
root="./data",
train=True,
download=True,
transform=transform
)
test_color_dataset = datasets.CIFAR10(
root="./data",
train=False,
download=True,
transform=transform
)
# 数据加载器(按批次取数据)
batch_size = 64 # 一次处理64张图片
train_gray_loader = DataLoader(train_gray_dataset, batch_size=batch_size, shuffle=True)
test_gray_loader = DataLoader(test_gray_dataset, batch_size=batch_size, shuffle=False)
train_color_loader = DataLoader(train_color_dataset, batch_size=batch_size, shuffle=True)
test_color_loader = DataLoader(test_color_dataset, batch_size=batch_size, shuffle=False)
步骤 3:定义模型(包含展平 + Dropout)
class ImageModel(nn.Module):
def __init__(self, input_dim): # input_dim:展平后的维度(灰度=784,彩色=3072)
super().__init__()
self.fc1 = nn.Linear(input_dim, 128) # 全连接层1
self.dropout = nn.Dropout(0.5) # Dropout层(丢弃50%)
self.fc2 = nn.Linear(128, 10) # 全连接层2(10分类:MNIST是0-9,CIFAR10是10类物品)
def forward(self, x):
# 步骤1:展平(除batchsize外全展平)
x = torch.flatten(x, start_dim=1)
# 步骤2:过全连接层+激活函数
x = torch.relu(self.fc1(x))
# 步骤3:Dropout(训练生效,测试关闭)
x = self.dropout(x)
# 步骤4:输出结果
x = self.fc2(x)
return x
# 初始化模型:灰度图input_dim=28×28×1=784;彩色图input_dim=32×32×3=3072
gray_model = ImageModel(input_dim=784).to(device)
color_model = ImageModel(input_dim=3072).to(device)
步骤 4:定义训练 / 测试函数(核心!)
# 训练函数(封装)
def train_model(model, train_loader, optimizer, criterion, epoch):
model.train() # 切换到训练模式(开启Dropout)
total_loss = 0.0
correct = 0
total = 0
for batch_idx, (data, target) in enumerate(train_loader):
# 把数据放到GPU/CPU上
data, target = data.to(device), target.to(device)
# 1. 清空梯度(必须!不然梯度会累计)
optimizer.zero_grad()
# 2. 前向传播:输入图片→得到预测结果
output = model(data)
# 3. 计算损失(预测值和真实值的差距)
loss = criterion(output, target)
# 4. 反向传播:让模型知道错在哪
loss.backward()
# 5. 更新模型参数:模型学习改正
optimizer.step()
# 统计损失和准确率
total_loss += loss.item()
_, predicted = torch.max(output.data, 1) # 取预测概率最大的类别
total += target.size(0)
correct += (predicted == target).sum().item()
# 每100批次打印一次进度
if batch_idx % 100 == 0:
print(f'Epoch {epoch} | Batch {batch_idx} | Loss: {loss.item():.3f} | Acc: {100*correct/total:.2f}%')
# 返回本轮训练的平均损失和准确率
avg_loss = total_loss / len(train_loader)
avg_acc = 100 * correct / total
return avg_loss, avg_acc
# 测试函数(封装)
def test_model(model, test_loader, criterion):
model.eval() # 切换到测试模式(关闭Dropout)
total_loss = 0.0
correct = 0
total = 0
# 关闭梯度计算(测试不需要更新参数,省内存)
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
# 前向传播
output = model(data)
# 计算损失
loss = criterion(output, target)
# 统计
total_loss += loss.item()
_, predicted = torch.max(output.data, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
# 返回测试的平均损失和准确率
avg_loss = total_loss / len(test_loader)
avg_acc = 100 * correct / total
print(f'Test Loss: {avg_loss:.3f} | Test Acc: {avg_acc:.2f}%')
return avg_loss, avg_acc
步骤 5:运行训练和测试(灰度图为例,彩色图只需换加载器)
# 定义损失函数和优化器(通用)
criterion = nn.CrossEntropyLoss() # 分类任务的损失函数
optimizer = optim.SGD(gray_model.parameters(), lr=0.01, momentum=0.9) # 优化器
# 训练3轮(新手不用多,看效果就行)
epochs = 3
for epoch in range(1, epochs+1):
print(f"\n===== 训练轮数 {epoch} =====")
# 训练
train_loss, train_acc = train_model(gray_model, train_gray_loader, optimizer, criterion, epoch)
# 测试
test_loss, test_acc = test_model(gray_model, test_gray_loader, criterion)
# 如果要跑彩色图,只需要替换模型和加载器:
# optimizer_color = optim.SGD(color_model.parameters(), lr=0.01, momentum=0.9)
# for epoch in range(1, epochs+1):
# train_model(color_model, train_color_loader, optimizer_color, criterion, epoch)
# test_model(color_model, test_color_loader, criterion)
关键易错点总结(新手必看)
- 展平别写错维度:必须
torch.flatten(x, start_dim=1),别漏start_dim=1; - Dropout 别忘切模式:训练用
model.train(),测试用model.eval(),否则测试结果会错; - 测试别忘关梯度:
with torch.no_grad()一定要加,不然内存会爆; - 训练前清空梯度:
optimizer.zero_grad()必须在每次批次前执行,不然梯度会累计; - 彩色 / 灰度图只改输入维度:模型不用大改,只需要改
input_dim(灰度 784,彩色 3072)。
最终效果
运行代码后,你会看到:
- 训练时 Loss(损失)慢慢下降,Acc(准确率)慢慢上升;
- 测试时 Acc 能达到 90% 左右(MNIST 很简单);
- 如果换成彩色图(CIFAR10),准确率会低一些(因为更复杂),但流程完全一样。
现在你可以把代码复制到 PyCharm/Colab 里运行,改一改 batchsize、epochs,看看效果变化,慢慢就理解了。
用通俗易懂的语言解释一下什么是Dropout?
咱们用 “学霸复习考试” 的生活例子,把 Dropout 讲得明明白白,全程不用复杂术语:
先想个问题:为什么需要 Dropout?
假设你是个学霸,要备考数学(对应模型要 “学习识别图片”)。如果复习时,你只依赖 3 个 “王牌知识点”(比如只背公式 A、公式 B、公式 C),考试时题目稍微变一下(比如公式 A 换了个场景用),你就懵了 —— 这就是模型的 “过拟合”(死记硬背,不会灵活运用)。
Dropout 的核心作用就是:逼你不能只依赖少数 “王牌知识点”,必须让所有知识点都学会,这样考试时再怎么变题都不怕。
什么是 Dropout?(通俗版)
把模型里的每个 “神经元”(可以理解成 “知识点” 或 “学习小组成员”),想象成一个 “学霸的复习帮手”。
- 训练阶段(复习时):Dropout 会随机让一部分 “帮手放假”(比如 50% 的帮手不干活)。
比如你本来有 10 个复习帮手(对应 10 个神经元),Dropout 每次随机挑 5 个让他们休息,剩下的 5 个必须一起完成复习任务。
这样一来,你没法只靠那 3 个 “王牌帮手”(比如只靠公式 A、B、C 的帮手),必须让每个帮手都学扎实 —— 哪怕下次王牌帮手放假,其他帮手也能顶上去。
- 测试阶段(考试时):Dropout 让所有 “帮手都上班”(不放假了)。
考试是要拿满分的,不能再让帮手放假了,得让所有知识点都发挥作用,用全部实力答题。
再举个更具体的例子:
比如模型要识别 “猫” 的图片,训练时:
- 没有 Dropout:模型可能只依赖 “猫有尖耳朵”“猫有尾巴” 这两个特征(两个神经元),其他特征(比如猫的眼睛、毛发)都不学;
- 有 Dropout(p=0.5):训练时随机让 “尖耳朵” 或 “尾巴” 的神经元放假,模型被迫去学 “眼睛形状”“毛发纹理” 这些其他特征 —— 久而久之,模型记住了 “猫的所有关键特征”,哪怕遇到一只耳朵被挡住的猫(对应某特征没出现),也能认出来。
总结 Dropout 的本质:
就是训练时 “故意制造困难”,让模型不能 “偷懒只靠少数神经元”,必须让所有神经元都参与学习,从而变得更 “灵活”,避免考试时(测试时)因为 “某几个关键神经元没发挥作用” 而出错。
对应到代码里的逻辑:
model.train():告诉模型 “现在是复习时间,Dropout 开始随机让帮手放假”;model.eval():告诉模型 “现在是考试时间,所有帮手都回来上班,不许放假”;nn.Dropout(p=0.5):规定 “复习时每次让 50% 的帮手放假”(p 是放假比例)。
一句话记住:Dropout 就是 “训练时故意藏起一半工具,逼模型学会用所有工具;测试时把所有工具都拿出来,发挥全部实力”。
dropout 操作的代码示例
为了让你彻底吃透 Dropout 操作,我专门整理了 极简入门版 和 实战结合版 两个代码示例,所有代码可直接复制运行,每一行都加了通俗注释,重点对比「训练模式(Dropout 生效)」和「测试模式(Dropout 关闭)」的差异。
前置准备:导入必备库
所有示例都需要先运行这行代码(就像做饭先拿厨具):
import torch
import torch.nn as nn # 神经网络核心库,包含Dropout
示例 1:极简版 Dropout 演示(直观看效果)
这个示例只聚焦 Dropout 本身,不涉及复杂网络,能一眼看出训练 / 测试模式的区别:
# 1. 定义一个只有Dropout层的简单模块
# p=0.5 表示训练时随机丢弃50%的神经元
dropout_layer = nn.Dropout(p=0.5)
# 2. 模拟输入:1个样本(batchsize=1),包含10个神经元的输出(比如网络某一层的结果)
# 用全1的张量,方便观察哪些神经元被丢弃(丢弃后值为0)
input_data = torch.ones(1, 10)
print("原始输入:", input_data)
# 输出:tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
# ==================== 训练模式:Dropout生效 ====================
dropout_layer.train() # 切换到训练模式(必须!否则Dropout不生效)
train_output = dropout_layer(input_data)
print("\n训练模式输出(50%神经元被丢弃,值为0):", train_output)
# 示例输出(每次运行结果不同,因为随机丢弃):
# tensor([[2., 0., 2., 0., 2., 2., 0., 2., 0., 2.]])
# 🔔 注意:被保留的神经元值会乘以 1/(1-p)(这里是2),是PyTorch的默认补偿机制,不用管,只看0即可
# ==================== 测试模式:Dropout关闭 ====================
dropout_layer.eval() # 切换到测试模式(必须!关闭Dropout)
# with torch.no_grad(): 测试时关闭梯度(非必须,但能省内存,建议加)
with torch.no_grad():
test_output = dropout_layer(input_data)
print("\n测试模式输出(所有神经元保留,无0值):", test_output)
# 输出:tensor([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]])
关键说明:
nn.Dropout(p=0.5):p是丢弃概率,比如p=0.3就是丢弃 30% 的神经元;train():告诉 Dropout 层 “现在是训练阶段,要随机丢弃”;eval():告诉 Dropout 层 “现在是测试阶段,不要丢弃任何神经元”;torch.no_grad():测试时不需要计算梯度(梯度是训练时更新参数用的),加了能节省内存、加快速度。
示例 2:实战版 Dropout(结合神经网络 + 训练 / 测试流程)
这个示例模拟实际机器学习场景,把 Dropout 融入神经网络,完整展示训练和测试时的用法:
# 1. 定义一个包含Dropout的完整神经网络(手写数字识别为例)
class MNISTNet(nn.Module):
def __init__(self):
super().__init__()
# 第一层:全连接层(输入是展平后的28×28灰度图=784维)
self.fc1 = nn.Linear(784, 256)
# Dropout层(训练丢弃50%)
self.dropout = nn.Dropout(p=0.5)
# 第二层:全连接层(输出10类:0-9)
self.fc2 = nn.Linear(256, 10)
# 前向传播(数据走网络的路径)
def forward(self, x):
# 展平:把(批量数,1,28,28)变成(批量数,784)
x = torch.flatten(x, start_dim=1)
# 激活函数(让网络能学复杂规律)
x = torch.relu(self.fc1(x))
# Dropout(训练生效,测试关闭)
x = self.dropout(x)
# 输出最终结果
x = self.fc2(x)
return x
# 2. 初始化模型、损失函数、优化器(训练必备)
model = MNISTNet()
# 分类任务的损失函数
criterion = nn.CrossEntropyLoss()
# 优化器(更新模型参数)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
# 3. 模拟训练过程(1个批次为例)
print("===== 训练阶段 =====")
model.train() # 全局切换到训练模式(所有Dropout层生效)
# 模拟输入:批量数=8,1通道,28×28的灰度图
train_x = torch.randn(8, 1, 28, 28)
# 模拟真实标签(8个样本,标签是0-9的整数)
train_y = torch.tensor([0,1,2,3,4,5,6,7])
# 训练步骤(固定流程):
optimizer.zero_grad() # 清空梯度(避免累计)
train_pred = model(train_x) # 模型预测
loss = criterion(train_pred, train_y) # 计算损失
loss.backward() # 反向传播(算梯度)
optimizer.step() # 更新参数(学知识)
print(f"训练损失:{loss.item():.4f}")
# 4. 模拟测试过程
print("\n===== 测试阶段 =====")
model.eval() # 全局切换到测试模式(所有Dropout层关闭)
with torch.no_grad(): # 关闭梯度计算
# 模拟测试输入(批量数=4)
test_x = torch.randn(4, 1, 28, 28)
# 模拟测试标签
test_y = torch.tensor([0,1,2,3])
# 模型预测
test_pred = model(test_x)
# 计算测试损失(只评估,不更新参数)
test_loss = criterion(test_pred, test_y)
print(f"测试损失:{test_loss.item():.4f}")
# 验证Dropout是否真的关闭:对比两次测试的输出(如果Dropout开着,结果会随机;关着则完全一样)
with torch.no_grad():
test_pred1 = model(test_x)
test_pred2 = model(test_x)
print("\n两次测试输出是否完全一致?", torch.equal(test_pred1, test_pred2))
# 输出:True(因为Dropout关闭,没有随机丢弃,结果固定)
实战关键要点:
model.train()/model.eval():是 “全局开关”,会影响模型中所有的 Dropout 层(不用给每个 Dropout 单独设置);- 训练流程固定 4 步:
清空梯度→预测→算损失→反向传播+更新参数; - 测试流程固定:
切eval模式→关梯度→预测→算损失/准确率; - 测试时两次预测结果一致,证明 Dropout 确实关闭了(如果没切 eval,结果会随机)。
常见错误提醒(新手必避坑)
- 忘记切模式:训练时没写
model.train(),或测试时没写model.eval(),导致 Dropout 效果错误; - 测试时没关梯度:虽然不影响结果,但会浪费内存,尤其是大数据量时;
- Dropout 加错位置:不要加在输入层或输出层,一般加在中间的全连接层之间;
- p 值设置过大:比如
p=0.8(丢弃 80%),模型学不到足够特征,效果会变差,一般设 0.3-0.5。
你可以把这些代码复制到 Python 环境(比如 PyCharm、Jupyter)里运行,改一改p值、训练 / 测试模式,看看结果变化,就能彻底理解 Dropout 的用法了。
如何选择合适的Dropout概率p?
选择 Dropout 的概率p(训练时丢弃神经元的比例),核心原则是:既不让模型 “丢太多神经元学不到东西(欠拟合)”,也不让 “丢太少没起到防过拟合作用”。咱们用「学霸复习」的例子 +「实操步骤」讲透,新手能直接照做。
先明确:p的本质是什么?
p是训练时随机 “放假” 的神经元比例:
p=0:不丢弃任何神经元(相当于没用 Dropout);p=0.5:50% 神经元放假;p=1:所有神经元都放假(模型学不到任何东西)。
核心目标:让p刚好能 “逼模型学通用规律”,又不 “剥夺模型学习的能力”。
第一步:先记「通用经验值」(新手直接用,不用瞎试)
不同网络层的p有默认最优范围,这是行业总结的 “捷径”:
| 网络层类型 | 推荐p范围 | 原因(通俗解释) |
|---|---|---|
| 全连接层(FC) | 0.3 ~ 0.5 | 全连接层参数多、容易过拟合,丢 30%-50% 刚好 |
| 卷积层(CNN) | 0.1 ~ 0.3 | 卷积层学的是局部特征(比如猫的耳朵),丢多了学不到核心特征 |
| 循环层(RNN/LSTM) | 0.2 ~ 0.4 | 文本 / 时序数据依赖上下文,丢多了会破坏语义 |
| 输入层 / 输出层 | 0(不用) | 输入层丢了会破坏原始数据,输出层丢了会影响预测结果 |
新手首选:
- 做图片分类 / 简单任务(比如 MNIST 手写数字):全连接层直接设
p=0.5(最常用的默认值); - 做复杂任务(比如 CIFAR10 彩色图):全连接层
p=0.4,卷积层p=0.2。
第二步:根据「训练 / 测试表现」微调p(核心调参方法)
经验值只是起点,最终要靠 “训练集 + 验证集(或测试集)的表现” 判断p是否合适。
先看 3 种典型现象,对应调整策略:
| 现象(核心看 “训练准确率” 和 “测试准确率” 的差距) | 说明 | 调整p的方法 |
|---|---|---|
| 训练准确率很高(95%+),测试准确率很低(70%) | 过拟合(丢少了) | 适当增大p(比如从 0.5→0.6) |
| 训练准确率低(70%),测试准确率也低(68%) | 欠拟合(丢多了) | 减小p(比如从 0.5→0.3),甚至设p=0 |
| 训练 / 测试准确率都不错(差距 < 5%) | 刚好合适 | 保持当前p不动 |
生活化例子:
就像学霸复习时藏笔记:
- 藏太少(p=0.1):学霸还是能靠少数笔记记题(过拟合);
- 藏太多(p=0.8):学霸连核心知识点都看不到(欠拟合);
- 藏一半(p=0.5):学霸必须记住所有核心知识点,才能答题(刚好)。
实操调参步骤(示例):
比如你做 MNIST 手写数字识别,模型是全连接层:
- 先设
p=0.5,训练后:训练准确率 98%,测试准确率 90%(过拟合); - 调大
p到 0.6,训练后:训练准确率 96%,测试准确率 92%(差距缩小,合适); - 再试
p=0.7,训练后:训练准确率 90%,测试准确率 89%(欠拟合)→ 最终选p=0.6。
第三步:结合「数据 / 模型情况」调整p
p的选择还要适配你的数据量和模型复杂度,这是容易被忽略的点:
1. 数据量越少 → p可以稍大
- 比如只有 100 张训练图片(数据极少):全连接层
p=0.5~0.6,逼模型学通用规律; - 比如有 10 万张训练图片(数据充足):
p=0.2~0.3即可,甚至不用 Dropout(数据多本身就不容易过拟合)。
2. 模型越复杂 → p可以稍大
- 比如模型有 10 层全连接层(超复杂):
p=0.5~0.6; - 比如模型只有 2 层全连接层(简单):
p=0.3~0.4,甚至p=0。
3. 不同层可以设不同p(进阶技巧)
不用所有层都设一样的p:
- 靠近输入的层(比如第一层全连接):
p=0.3(少丢,保留原始特征); - 靠近输出的层(比如最后一层全连接):
p=0.5(多丢,防过拟合)。
第四步:避坑要点(新手别踩)
- 输出层绝对别加 Dropout:输出层是模型的 “最终答案”,丢神经元会直接导致预测结果出错;
- 别盲目追求 “大 p”:不是 p 越大防过拟合效果越好,超过 0.7 大概率会欠拟合;
- 测试时不用管 p:只要切换到
model.eval(),Dropout 会自动关闭,p 的值不影响测试; - 结合其他防过拟合方法时,p 要减小:比如同时用了 “权重衰减 + 数据增强”,p 可以从 0.5 降到 0.3(多重防护,避免过度限制模型)。
总结:选择p的极简流程(新手照做)
1. 先按层类型选经验值:全连接层 0.5,卷积层 0.2;
2. 训练后看训练 / 测试准确率差距:
- 过拟合→增大 p(每次 + 0.1);
- 欠拟合→减小 p(每次 - 0.1);
3. 结合数据量 / 模型复杂度微调:数据少 / 模型复杂→p 稍大,反之稍小;
4. 最终选 “测试准确率最高” 的 p(不是训练准确率)。
代码示例(不同 p 的对比测试)
import torch
import torch.nn as nn
# 定义不同p的模型
def build_model(p):
class Net(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 256)
self.dropout = nn.Dropout(p=p) # 不同p值
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = torch.flatten(x, 1)
x = torch.relu(self.fc1(x))
x = self.dropout(x)
return self.fc2(x)
return Net()
# 测试不同p的效果(伪代码)
p_list = [0.3, 0.5, 0.7]
best_p = 0
best_test_acc = 0
for p in p_list:
model = build_model(p)
# 训练模型(省略训练代码)
train_model(model)
# 测试模型
test_acc = test_model(model)
print(f"p={p},测试准确率:{test_acc}")
if test_acc > best_test_acc:
best_test_acc = test_acc
best_p = p
print(f"最优p值:{best_p},对应测试准确率:{best_test_acc}")
按这个流程,新手不用死记硬背,试 2-3 次就能找到合适的p,核心是 “看效果调参”,而不是凭感觉。
除了dropout,还有哪些防止过拟合的方法?
咱们还是用「学霸备考」的生活化例子,把除 Dropout 外最常用、最好理解的7 种防止过拟合方法讲透 —— 核心逻辑都是:不让模型 “死记硬背训练题(训练数据)”,而是 “理解解题思路(通用规律)”,这样遇到新题(测试数据)也能做对。
先复习下 “过拟合” 的本质:模型把训练数据里的 “无关细节” 也当成了规律(比如背题时记住了题目在卷子的第 3 行,而不是题目本身的解法),导致训练时分数很高,测试时分数暴跌。
方法 1:数据增强(Data Augmentation)—— 给训练题加 “花样”
通俗解释
就像学霸复习时,不仅看原题,还看 “换了字体的题、排版歪了的题、稍微改了数字的题”,逼自己理解核心解法,而不是记题目外观。
对应到机器学习:给训练图片 “做手脚”(但不改变核心特征),让模型见更多 “变种”,比如:
- 图片类:随机旋转(比如把猫的图片转 10 度)、随机裁剪(裁掉一点边缘)、随机翻转(左右翻)、调整亮度 / 对比度;
- 文本类:同义词替换(“开心” 换成 “高兴”)、随机插入 / 删除少量文字。
生活例子
学认 “猫”:不仅看正面、正脸的猫,还看侧着的猫、歪头的猫、光线暗的猫、只露半张脸的猫 —— 哪怕考试遇到一只趴着的猫,也能认出来。
实操示例(图片增强,PyTorch 代码)
from torchvision import transforms
# 定义增强规则:随机翻转+随机旋转+转Tensor
train_transform = transforms.Compose([
transforms.RandomHorizontalFlip(p=0.5), # 50%概率左右翻转
transforms.RandomRotation(10), # 随机旋转±10度
transforms.ToTensor(), # 转成模型能认的格式
])
# 测试时不增强(用原图考模型)
test_transform = transforms.Compose([
transforms.ToTensor(),
])
核心要点
- 只给训练数据做增强,测试数据必须用原图(不然相当于改了考试题,测不准);
- 增强的 “幅度” 要合理:比如猫的图片旋转 180 度就变成倒猫了,反而误导模型。
方法 2:早停(Early Stopping)—— 学到 “刚好会” 就停,别学傻了
通俗解释
学霸背单词:背到 “能默写出 80%” 就停,再背下去就开始记 “单词在笔记本的第 5 行” 这种无关细节,反而考试遇到陌生排版的单词就不会了。对应到机器学习:训练时盯着 “测试集分数”,如果测试分数连续好几轮不涨、甚至开始跌,就立刻停止训练 —— 哪怕训练集分数还在涨。
生活例子
玩游戏练操作:练到 “能稳定通关普通难度” 就停,再死磕 “无伤通关”,反而会养成 “只适应某一种怪的走位” 的坏习惯,换个难度就翻车。
实操逻辑(伪代码,一看就懂)
best_test_acc = 0 # 记录最好的测试准确率
patience = 5 # 连续5轮没进步就停
no_improve = 0 # 统计连续没进步的轮数
for epoch in range(100): # 计划训练100轮
# 训练模型(省略训练代码)
train_model()
# 测试模型
current_test_acc = test_model()
if current_test_acc > best_test_acc:
best_test_acc = current_test_acc
no_improve = 0 # 有进步,重置计数
save_model() # 保存最好的模型
else:
no_improve += 1
if no_improve >= patience:
print("测试分数不涨了,停止训练!")
break # 提前终止训练
核心要点
- 关键是 “看测试集表现”,不是训练集;
- 要保存 “测试分数最好时” 的模型,而不是最后一轮的模型。
方法 3:权重衰减(Weight Decay)—— 不让模型 “钻牛角尖”
通俗解释
学霸做题时,老师规定 “不许用太极端的解法”(比如不许用超纲的偏门公式),逼他用通用解法 —— 这样换个题目,通用解法依然能用。对应到机器学习:模型的 “参数(权重)” 相当于 “解题公式的系数”,权重衰减会惩罚 “太大的参数”,让参数都保持在较小的范围,避免模型依赖 “极端系数” 拟合训练数据的噪声。
生活例子
做菜时,不许放 “10 勺盐” 这种极端调料(不然只适合某一种食材),要求用 “1 勺盐” 这种温和的量 —— 这样做青菜、做豆腐都适用。
实操示例(PyTorch 代码)
import torch.optim as optim
# 优化器里加weight_decay就是权重衰减(L2正则的一种)
optimizer = optim.SGD(
model.parameters(),
lr=0.01, # 学习率
weight_decay=1e-4 # 权重衰减系数(越小越温和,常用1e-4~1e-5)
)
核心要点
- 权重衰减是L2 正则的简化写法,是最常用的正则化方式;
- 系数别设太大:比如设成 1,会把参数压得太小,模型学不到任何规律(欠拟合)。
方法 4:L1/L2 正则化 —— 让模型 “轻装上阵”
通俗解释
- L1 正则:逼模型 “少用解题公式”(只留核心公式),比如 10 个公式里只留 3 个最关键的,其他都清零 —— 相当于学霸只记核心知识点,不记杂七杂八的细节;
- L2 正则:就是上面的 “权重衰减”,逼模型 “解题公式的系数别太大”,比如系数都控制在 ±1 以内,避免极端解法。
生活例子
- L1:整理书包,只留笔、本子、尺子(核心工具),把贴纸、玩具都扔掉;
- L2:用尺子时,不许把刻度拉到最极限(比如只用到 0-10cm,不用 10-20cm 的极端刻度)。
实操示例(PyTorch 实现 L1 正则)
PyTorch 没有直接的 L1 参数,需要手动加损失:
# 计算普通损失
loss = criterion(output, target)
# 手动加L1正则损失:把所有参数的绝对值加起来,乘以系数
l1_lambda = 1e-5
l1_loss = 0
for param in model.parameters():
l1_loss += torch.sum(torch.abs(param)) # 所有参数的绝对值之和
total_loss = loss + l1_lambda * l1_loss # 总损失=普通损失+L1惩罚
方法 5:简化模型结构 —— 别用 “太复杂的脑子” 记细节
通俗解释
学霸用 “简单笔记本” 记知识点(只写核心),而不是 “厚本子记满所有细节”—— 复杂本子容易记无关信息,简单本子只能记关键规律。对应到机器学习:如果模型太复杂(比如有 100 层神经网络、10 万个参数),哪怕是简单的任务,也会 “硬记” 训练数据的细节;把模型改简单(比如减少层数、减少神经元数量),模型只能学核心规律。
生活例子
用计算器算加减法:普通计算器(简单模型)只会算核心规则,不会记 “上次算的是 2+3 还是 5+6”;超级计算机(复杂模型)反而会记住这些无关细节。
实操示例
# 复杂模型(容易过拟合)
class ComplexModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 1024) # 1024个神经元
self.fc2 = nn.Linear(1024, 512) # 512个神经元
self.fc3 = nn.Linear(512, 10)
# 简化模型(避免过拟合)
class SimpleModel(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 128) # 减少到128个神经元
self.fc2 = nn.Linear(128, 10) # 去掉多余的层
核心要点
- 模型复杂度要匹配任务难度:识别手写数字用简单模型就行,识别复杂场景用稍复杂的模型;
- 先从简单模型开始试,不够用再慢慢加复杂度。
方法 6:扩充数据集 —— 让模型见更多 “不同的题”
通俗解释
学霸刷 1000 道不同的数学题,比刷 100 道重复的题学得更透彻 —— 见的题型多了,自然能总结通用规律,而不是记某几道题的答案。对应到机器学习:训练数据越多,模型越难 “记完所有数据”,只能被迫学规律;如果数据少,模型很容易把所有数据都记下来(过拟合)。
生活例子
学认 “苹果”:看 100 个不同品种(红富士、青苹果、冰糖心)、不同大小、不同角度的苹果,比只看 10 个红富士苹果,更能认得出所有苹果。
实操思路
- 手动收集更多数据(比如多找图片、多爬取文本);
- 用 “公开数据集” 补充(比如 MNIST、CIFAR10);
- 用 “数据合成”(比如 AI 生成相似的图片 / 文本)。
方法 7:交叉验证(Cross Validation)—— 选 “真学会” 的模型,不是 “记题” 模型
通俗解释
学霸考完模拟考 1,又考模拟考 2、模拟考 3,不是只看一次模拟考的成绩 —— 如果每次模拟考都考得好,说明真学会了;如果只有一次考得好,可能是刚好记了那套题。对应到机器学习:把训练数据分成几份(比如 5 份),用 4 份训练、1 份验证,轮流换着验证,最后选 “平均验证分数最高” 的模型,避免选到 “刚好记了某一份数据” 的模型。
生活例子
面试时,面试官出 3 套不同的题,都答得好才录用 —— 如果只答好 1 套,可能是提前背了这道题。
实操示例(5 折交叉验证,伪代码)
from sklearn.model_selection import KFold
# 把数据分成5份
kf = KFold(n_splits=5, shuffle=True)
fold_acc = []
for fold, (train_idx, val_idx) in enumerate(kf.split(all_data)):
# 取当前折的训练/验证数据
train_data = all_data[train_idx]
val_data = all_data[val_idx]
# 训练模型、验证模型
train_model(train_data)
val_acc = test_model(val_data)
fold_acc.append(val_acc)
# 选平均准确率最高的模型
avg_acc = sum(fold_acc)/5
print(f"5折交叉验证平均准确率:{avg_acc}")
总结:防止过拟合的核心思路(一句话记)
所有方法都是为了:要么让模型 “学不到无关细节”(简化模型、权重衰减),要么让模型 “见更多样的内容”(数据增强、扩充数据集),要么 “及时止损”(早停),要么 “选对模型”(交叉验证)。
新手怎么选?
- 先试数据增强 + 早停(最通用,几乎所有任务都能用);
- 效果还不好,加权重衰减(代码改一行就行);
- 再不好,简化模型结构或扩充数据集;
- Dropout 可以和上面的方法叠加用(比如 Dropout + 数据增强,效果更好)。
权重衰减的原理是什么?
咱们还是用「生活化例子 + 极简数学」的方式讲透权重衰减(Weight Decay)的原理 —— 核心就一句话:通过 “惩罚模型中过大的参数(权重)”,逼模型用 “均衡、温和的规则” 学规律,而不是 “极端规则” 记细节。
先补两个前置小概念(不用记公式,懂意思就行):
- 权重(Weight):模型里的 “参数”,可以理解成「模型给每个特征的 “关注度打分”」。比如识别猫时,“尖耳朵” 特征的权重是 5,“尾巴” 特征的权重是 3,说明模型更关注尖耳朵;
- 损失函数:模型的 “错题本”,数值越小说明模型预测得越准。训练的目标就是让这个数值尽可能小。
第一步:先搞懂「为什么权重太大会导致过拟合?」
用「做菜」的例子最直观:假设你学做 “番茄炒蛋”(对应模型学识别猫),训练数据是 “妈妈做的番茄炒蛋”(对应训练图片):
- 妈妈做的番茄炒蛋:盐放 1 勺,糖放 0.5 勺(对应训练数据的 “正常特征”);
- 你记成:盐放 10 勺,糖放 0.1 勺(对应模型给 “盐味” 特征的权重过大,“甜味” 权重过小);
- 结果:你做的番茄炒蛋只适合妈妈的配方(训练数据),换个番茄品种(测试数据),放 10 勺盐就齁了(模型识别不出变异的猫)—— 这就是「权重过大导致过拟合」。
核心问题:权重太大,说明模型过度依赖某几个 “极端特征”,而不是 “通用特征”。比如模型给 “猫的尖耳朵” 权重 100,哪怕图片里只有一个尖耳朵的污点,模型也会认成猫;而正常的猫应该是 “耳朵 + 眼睛 + 尾巴” 等特征的综合判断。
第二步:权重衰减的核心原理 —— 给 “大权重” 加 “罚款”
权重衰减的本质是:在模型的 “错题本(损失函数)” 里,加一笔 “罚款”—— 权重越大,罚款越重。模型为了减少总损失(错题 + 罚款),会主动把权重压小,不敢过度依赖某几个特征。
1. 公式拆解(极简版,不用算,看逻辑)
原本的损失函数(只看预测对错):损失 = 模型预测值和真实值的差距(比如分类任务的交叉熵损失)
加了权重衰减后的总损失:总损失 = 预测差距损失 + 衰减系数 × 所有权重的平方和
关键细节:
- 「权重的平方和」:对大权重的惩罚是 “加倍的”!比如权重 = 10,平方 = 100;权重 = 1,平方 = 1——10 倍的权重,罚款差 100 倍,逼模型不敢把权重搞太大;
- 「衰减系数(比如 1e-4)」:控制罚款的 “力度”,系数越小,罚款越轻;系数越大,罚款越重(新手常用 1e-4~1e-5)。
2. 生活化例子(对应公式)
还是做菜的例子:
- 原本的损失:你做的菜和妈妈的菜的味道差距(比如差 5 分);
- 权重衰减的罚款:0.0001 ×(盐的用量 ² + 糖的用量 ²);
- 如果你放 10 勺盐:罚款 = 0.0001×(10²+0.1²)=0.010001,总损失 = 5+0.010001=5.010001;
- 如果你放 1 勺盐:罚款 = 0.0001×(1²+0.5²)=0.000125,总损失 = 5+0.000125=5.000125;
- 模型(你)会选总损失更小的方案 —— 主动把盐的用量从 10 勺降到 1 勺,权重(调料用量)自然变小。
3. 训练时的实际效果(梯度下降视角)
模型训练的核心是 “梯度下降”:每次让权重往 “减少损失” 的方向挪一小步。
加了权重衰减后,权重的更新公式会变成:新权重 = 旧权重 - 学习率×(预测误差的梯度 + 衰减系数×旧权重)
拆解一下:
- 没有权重衰减:权重只跟着 “预测误差” 走,可能越走越大;
- 有权重衰减:多了一项「- 学习率 × 衰减系数 × 旧权重」—— 相当于每次更新时,都把权重往 0 的方向 “拉一把”,不让它变大。
比如旧权重 = 10,学习率 = 0.01,衰减系数 = 1e-4:
- 这一项的影响是:-0.01×1e-4×10 = -0.00001;
- 新权重会比原来小一点点,久而久之,所有权重都会被控制在较小范围。
第三步:为什么是 “平方和”(L2 正则),而不是 “绝对值和”(L1)?
权重衰减本质是「L2 正则化」的简化写法,之所以用平方和而不是绝对值和:
- 平方和(L2):对 “超大权重” 惩罚狠,对 “小权重” 惩罚轻(温和)。比如权重 = 10 罚 100,权重 = 1 罚 1—— 既压大权重,又不把小权重直接清零,模型还能学特征;
- 绝对值和(L1):对所有权重惩罚一样狠(比如权重 = 10 罚 10,权重 = 1 罚 1),容易把很多小权重直接压到 0,模型学不到足够特征(欠拟合)。
用例子理解:
- L2(权重衰减):让模型 “雨露均沾”,每个特征都关注一点(比如耳朵权重 1,眼睛权重 0.8,尾巴权重 0.7);
- L1:让模型 “只挑几个特征”(比如只留耳朵权重 1,眼睛和尾巴权重 0)。
新手记住:权重衰减(L2)更通用,几乎所有场景都能用;L1 适合需要 “精简特征” 的场景(比如选最重要的特征)。
第四步:权重衰减的代码对应(看原理如何落地)
之前的代码里,权重衰减只需要在优化器里加一个参数,现在结合原理看:
import torch.optim as optim
# 权重衰减系数weight_decay=1e-4(罚款力度)
optimizer = optim.SGD(
model.parameters(), # 模型的所有权重
lr=0.01, # 学习率(每次挪步的大小)
weight_decay=1e-4 # 衰减系数(罚款力度)
)
关键参数怎么调?
- 系数太小(比如 1e-6):罚款太轻,权重还是会变大,防过拟合效果差;
- 系数太大(比如 1e-2):罚款太重,所有权重都被压到接近 0,模型学不到任何规律(欠拟合);
- 新手默认用 1e-4,效果不好再微调。
总结:权重衰减的原理(一句话记)
通过在损失函数中加入「权重平方和 × 衰减系数」的惩罚项,让模型训练时主动降低所有权重的大小,避免过度依赖某几个 “极端特征” 拟合训练数据的噪声,从而学会 “均衡关注所有核心特征” 的通用规律,防止过拟合。
核心对比(有无权重衰减)
| 无权重衰减 | 有权重衰减 |
|---|---|
| 权重可能极端大(比如 100) | 权重被控制在较小范围(比如 1) |
| 依赖少数特征,易过拟合 | 均衡关注多特征,泛化性强 |
| 只看 “预测对不对” | 既看 “预测对不对”,又看 “权重合不合理” |
1074

被折叠的 条评论
为什么被折叠?



