知识点回顾

零基础学 Python 机器学习:图像数据与显存
作为零基础学习者,咱们今天不讲复杂公式,只用 "大白话 + 小例子 + 可运行代码",把「图像数据与显存」的核心知识点拆明白。先记住一个核心比喻:机器学习训练就像开一家 "图像加工厂",显存就是工厂的 "工作台",我们要搞清楚工作台被哪些东西占了,以及怎么安排原料(数据)让工厂运转得又快又好。
知识点 1:图像数据的格式(灰度 / 彩色)
首先要明白:电脑里的图片不是 "画",而是一堆数字组成的格子(矩阵),就像你在方格本上涂色,每个格子的数字代表颜色 / 亮度。
1.1 灰度图像:黑白格子本
- 通俗解释:灰度图只有 "明暗" 没有颜色,就像黑白报纸。每个格子(像素)用一个数字表示亮度,范围是 0(纯黑)~255(纯白)。
- 形状规则 :比如一张 28×28 的手写数字灰度图,形状就是
(28, 28)------28 行、28 列的数字矩阵。 - 代码实操(能直接运行):
python
# 导入工具包(先安装:pip install numpy matplotlib)
import numpy as np
import matplotlib.pyplot as plt
# 1. 创建一张简单的灰度图:5×5的矩阵,中间亮、四周暗
gray_img = np.array([
[0, 0, 0, 0, 0],
[0, 100, 150, 100, 0],
[0, 150, 255, 150, 0],
[0, 100, 150, 100, 0],
[0, 0, 0, 0, 0]
])
# 2. 显示灰度图
plt.imshow(gray_img, cmap='gray') # cmap='gray'指定用灰度显示
plt.title('5×5灰度图(中间纯白255,四周黑0)')
plt.axis('off') # 关掉坐标轴
plt.show()
# 3. 查看灰度图的基本信息
print(f"灰度图形状:{gray_img.shape}") # (5,5) → 5行5列
print(f"灰度图数据类型:{gray_img.dtype}") # int64(数字类型)
print(f"单个像素值范围:{gray_img.min()} ~ {gray_img.max()}") # 0~255
- 运行结果:能看到一个中间亮、四周黑的 5×5 小方块,这就是最基础的灰度图像!
1.2 彩色图像:带 RGB 的彩色格子本

python
# 1. 创建一张3×3的彩色图:中间是红色,四周是绿色
color_img = np.zeros((3, 3, 3), dtype=np.uint8) # 先创建全黑的3×3×3矩阵
color_img[1, 1] = [255, 0, 0] # 中间像素设为纯红(R=255,G=0,B=0)
color_img[0, :] = [0, 255, 0] # 第一行设为纯绿
color_img[2, :] = [0, 255, 0] # 第三行设为纯绿
color_img[:, 0] = [0, 255, 0] # 第一列设为纯绿
color_img[:, 2] = [0, 255, 0] # 第三列设为纯绿
# 2. 显示彩色图
plt.imshow(color_img)
plt.title('3×3彩色图(中间红,四周绿)')
plt.axis('off')
plt.show()
# 3. 查看彩色图基本信息
print(f"彩色图形状:{color_img.shape}") # (3,3,3) → 3行3列,每个像素3个值
print(f"中间像素的RGB值:{color_img[1,1]}") # [255 0 0] → 纯红
- 运行结果:能看到一个 3×3 的小方块,中间红、四周绿,这就是彩色图像的本质 ------RGB 数字组合!
小结
| 图像类型 | 形状示例 | 像素值含义 | 核心特点 |
|---|---|---|---|
| 灰度图 | (28,28) | 单个数字(0~255,亮度) | 简单、占空间小 |
| 彩色图 | (32,32,3) | 三个数字(RGB,0~255) | 丰富、占空间是灰度图的 3 倍 |
知识点 2:模型的定义(图像加工厂的 "机器")
2.1 通俗理解
模型就是处理图像数据的 "加工厂机器":输入是图像的数字矩阵,经过机器的一系列计算,输出我们想要的结果(比如 "这张图是猫""这张图是数字 5")。
我们以 "识别手写数字(灰度图)" 为例,定义一个最简单的神经网络模型(全连接网络),核心结构:
- 输入层:把 28×28 的灰度图 "摊平" 成 1 行 784 列的数字(28×28=784),喂给机器;
- 隐藏层:128 个 "计算单元"(神经元),做核心运算;
- 输出层:10 个 "计算单元",对应 0~9 这 10 个数字,输出哪个数字的概率最高,就识别成哪个。
2.2 代码实操(用 PyTorch,最易上手的框架)
先安装 PyTorch:pip install torch
python
import torch
import torch.nn as nn # 神经网络核心包
# 定义手写数字识别模型(继承nn.Module,所有PyTorch模型的基础)
class SimpleDigitModel(nn.Module):
def __init__(self):
super().__init__() # 初始化父类
# 1. 输入层:把28×28的灰度图摊平成784维向量
self.flatten = nn.Flatten()
# 2. 隐藏层:784个输入 → 128个输出(简单的线性计算+激活函数)
self.hidden = nn.Linear(784, 128) # 线性层(y = ax + b)
self.relu = nn.ReLU() # 激活函数(让模型能学复杂规律)
# 3. 输出层:128个输入 → 10个输出(对应0~9)
self.output = nn.Linear(128, 10)
# 定义数据的"加工流程"(前向传播)
def forward(self, x):
x = self.flatten(x) # 摊平:(batch_size, 28,28) → (batch_size, 784)
x = self.hidden(x) # 隐藏层计算
x = self.relu(x) # 激活函数
x = self.output(x) # 输出层计算
return x
# 创建模型实例
model = SimpleDigitModel()
print("模型结构:")
print(model) # 打印模型的结构
# 测试模型:喂一张随机的28×28灰度图(模拟输入)
test_img = torch.randn(1, 28, 28) # 1张、28×28的随机灰度图(batch_size=1)
output = model(test_img) # 前向传播(加工数据)
print(f"\n输入图像形状:{test_img.shape}")
print(f"模型输出形状:{output.shape}") # (1,10) → 1张图,对应0~9的10个概率值
print(f"输出的10个数字概率(简化):{output.detach().numpy()[0][:5]}") # 前5个概率值

知识点 3:显存占用的 4 个地方(工作台被什么占了?)
显存(显卡的内存)就像加工厂的 "工作台",空间有限,训练时这 4 类东西会占工作台,我们逐个拆:
3.1 地方 1:模型参数 + 梯度参数(机器零件 + 调整笔记)

python
# 1. 查看模型参数的数量和显存占用
total_params = 0
for name, param in model.named_parameters():
# param.shape:参数的形状;param.numel():参数的个数;param.dtype:参数类型(float32=4字节)
param_num = param.numel()
total_params += param_num
# 计算该参数占用的显存(字节):个数 × 每个参数的字节数(float32=4,float64=8)
param_mem = param_num * param.element_size()
print(f"参数名:{name},形状:{param.shape},个数:{param_num},显存:{param_mem}字节")
print(f"\n模型总参数个数:{total_params}")
print(f"模型参数总显存:{total_params * 4}字节 ≈ {total_params * 4 / 1024:.2f}KB")
# 2. 梯度参数的存在:训练时开启梯度计算
test_img.requires_grad = True # 开启梯度计算
output = model(test_img)
# 随便选一个参数,查看它的梯度(训练前是None,反向传播后才有值)
print(f"\n隐藏层权重的梯度(训练前):{model.hidden.weight.grad}") # None
- 运行结果 :能看到模型每个参数的个数和显存占用,比如隐藏层
hidden.weight的形状是 (128,784),个数是 128×784=100352,显存≈392KB。 - 关键说明:梯度和参数是一一对应的,有多少参数,就有多少梯度,所以梯度会额外占用和参数差不多的显存!
3.2 地方 2:优化器参数(工人的历史笔记)
- 通俗解释:优化器是 "调整机器零件的工人",比如 Adam 优化器,工人需要记录每个零件的 "调整历史"(比如上次调了多少、平均调整幅度),这些历史记录就是优化器参数,也要占工作台空间。
- 代码实操:
python
# 定义Adam优化器(最常用的优化器)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 查看优化器的参数状态
print("优化器参数状态:")
for param_group in optimizer.param_groups:
for key, value in param_group.items():
if key != 'params': # 跳过参数本身,看优化器的额外状态
print(f" {key}:{value}")
# 优化器的额外参数(动量、方差等)
print(f"\n优化器为每个参数保存的状态数:{len(optimizer.state[model.hidden.weight])}") # Adam保存2个状态(momentum, variance)
- 运行结果:能看到 Adam 优化器会为每个参数保存 "动量" 和 "方差" 两个额外状态,相当于每个参数多了 2 个数字,所以优化器参数的显存占用≈模型参数的 2 倍!
3.3 地方 3:数据批量所占显存(原料)
- 通俗解释:训练时不会一次喂 1 张图(效率太低),而是喂一批(batch)图,这批图就是 "原料",要堆在工作台上,batch 越大,原料占的空间越多。
- 代码实操:
python
# 计算不同batch size的图像显存占用
batch_sizes = [1, 32, 64, 128]
img_shape = (28, 28) # 单张灰度图形状
dtype = torch.float32 # 常用数据类型(4字节/像素)
for bs in batch_sizes:
# 创建bs张28×28的灰度图(batch_size, 28,28)
batch_imgs = torch.randn(bs, *img_shape, dtype=dtype)
# 计算显存占用:batch_size × 高度 × 宽度 × 每个像素字节数
mem = bs * img_shape[0] * img_shape[1] * dtype.itemsize
print(f"batch_size={bs},显存占用:{mem}字节 ≈ {mem/1024:.2f}KB")

3.4 地方 4:神经元输出中间状态(半成品)
- 通俗解释:模型加工数据时,每层会产生 "半成品"(比如隐藏层的 128 个输出值),这些半成品堆在工作台上,加工完如果不清理,就会占空间。
- 代码实操:
python
# 查看前向传播的中间状态显存
# 1. 先注册钩子,记录中间层输出
hidden_output = None
def hook_fn(module, input, output):
global hidden_output
hidden_output = output
# 给隐藏层注册钩子(捕获输出)
hook = model.hidden.register_forward_hook(hook_fn)
# 2. 前向传播,获取中间状态
batch_imgs = torch.randn(32, 28, 28) # batch_size=32
output = model(batch_imgs)
# 3. 计算中间状态的显存占用
print(f"隐藏层输出形状:{hidden_output.shape}") # (32, 128) → 32个样本,每个128个值
mem = hidden_output.numel() * hidden_output.element_size()
print(f"隐藏层中间状态显存占用:{mem}字节 ≈ {mem/1024:.2f}KB")
# 4. 移除钩子
hook.remove()
- 运行结果:隐藏层输出形状是 (32,128),显存≈16.0KB------ 这就是 "半成品" 占的空间,模型层数越多,中间状态占的显存越多!
显存占用小结(工作台占用清单)
| 占用地方 | 通俗类比 | 核心影响 |
|---|---|---|
| 模型参数 + 梯度 | 机器零件 + 调整笔记 | 模型越大(层数 / 神经元越多),占用越多 |
| 优化器参数 | 工人的历史笔记 | 优化器越复杂(如 Adam),占用越多 |
| 数据批量 | 加工原料 | batch_size 越大,占用越多 |
| 中间状态 | 加工半成品 | 模型层数越多,占用越多 |
知识点 4:batch size 和训练的关系(怎么安排原料?)
batch size(批量大小)就是每次喂给模型的图像数量,它直接影响显存、训练速度、训练效果,咱们用 "工厂进料" 的例子讲明白:
4.1 关系 1:batch size ↔ 显存占用(进料多少 ↔ 工作台空间)
- 规律:batch size 越大,批量数据 + 中间状态占用的显存越多,超过工作台容量就会 "显存不足(OOM)",程序崩溃。
- 例子:如果你的显卡显存只有 1GB,batch_size=128 时显存占满,再调到 256 就会报错 "out of memory"。
- 代码验证:
python
# 模拟不同batch size的显存占用(简化版)
def calc_mem(batch_size):
# 1. 批量数据显存
img_mem = batch_size * 28 * 28 * 4 # 灰度图,float32=4字节
# 2. 中间状态显存(隐藏层)
hidden_mem = batch_size * 128 * 4
total_mem = img_mem + hidden_mem
return total_mem / 1024 / 1024 # 转成MB
for bs in [32, 64, 128, 256]:
mem_mb = calc_mem(bs)
print(f"batch_size={bs},数据+中间状态显存:{mem_mb:.2f}MB")

4.2 关系 2:batch size ↔ 训练速度(进料多少 ↔ 加工效率)

4.3 关系 3:batch size ↔ 训练效果(进料多少 ↔ 产品质量)

4.4 代码演示(简化训练对比)
python
# 模拟不同batch size的训练Loss变化
import torch.optim as optim
import torch.nn.functional as F
# 模拟手写数字数据集(1000张28×28灰度图,标签0~9)
X = torch.randn(1000, 28, 28)
y = torch.randint(0, 10, (1000,))
# 定义训练函数
def train(batch_size):
model = SimpleDigitModel()
optimizer = optim.Adam(model.parameters(), lr=0.001)
losses = []
# 训练5轮
for epoch in range(5):
# 按batch_size分组
for i in range(0, len(X), batch_size):
batch_X = X[i:i+batch_size]
batch_y = y[i:i+batch_size]
# 前向传播
output = model(batch_X)
loss = F.cross_entropy(output, batch_y) # 计算损失
# 反向传播
optimizer.zero_grad()
loss.backward()
optimizer.step()
losses.append(loss.item())
return losses
# 训练不同batch size
loss_32 = train(32)
loss_128 = train(128)
# 绘制Loss曲线
plt.plot(loss_32[:50], label='batch_size=32')
plt.plot(loss_128[:50], label='batch_size=128')
plt.xlabel('训练步数')
plt.ylabel('Loss(损失,越小越好)')
plt.title('不同batch size的Loss变化')
plt.legend()
plt.show()

零基础总结
今天咱们用 "加工厂" 的比喻,把核心知识点串起来了:
- 图像数据:灰度图是单数字矩阵,彩色图是 RGB 三数字矩阵;
- 模型:处理图像的 "机器",由参数组成,训练就是调整参数;
- 显存占用:工作台被 4 样东西占 ------ 参数 / 梯度、优化器、批量数据、中间状态;
- batch size:进料数量,影响显存、速度、效果,新手先选 32/64/128 试手。
你可以把今天的代码逐行运行,改改参数(比如把 batch_size 改成 64,把图像形状改成 32×32),看看结果变化 ------零基础学编程,"动手改代码" 比死记硬背重要 100 倍!
如何选择合适的 batchsize?3 步搞定(显存 / 速度 / 效果全兼顾)
选 batch size 的核心原则:先保证 "不爆显存"(能跑起来),再追求 "训练快",最后微调 "效果好"。咱们还是用 "图像加工厂" 的比喻,结合新手能落地的实操方法,一步步讲清楚,全程避开复杂公式,只讲 "能直接用的方法"。
第一步:先找 "最大可用 batch size"(底线:不爆显存)
新手选 batch size 的第一步,是先找到你的显卡 "能扛住的最大 batch size"------ 就像加工厂先测工作台最多能堆多少原料,堆多了就会 "溢出"(程序报错:out of memory (OOM))。
实操方法(新手无脑版)
- 先定初始值 :从行业通用的 "中间值" 开始试,比如
32(绝大多数入门显卡(2G/4G 显存)都能扛住); - 逐步翻倍测试 :如果跑起来不报错,就试
64→128→256... 直到出现 "显存不足" 报错; - 回退到安全值:报错后,把 batch size 调回上一个不报错的数值,再减一点(比如试 256 报错,就用 128;试 128 报错,就用 64)。
代码实操(自动测试最大 batch size,新手直接复制运行)
python
import torch
import torch.nn as nn
from torch.utils.data import TensorDataset, DataLoader
# 1. 复用之前的手写数字模型
class SimpleDigitModel(nn.Module):
def __init__(self):
super().__init__()
self.flatten = nn.Flatten()
self.hidden = nn.Linear(784, 128)
self.relu = nn.ReLU()
self.output = nn.Linear(128, 10)
def forward(self, x):
x = self.flatten(x)
x = self.hidden(x)
x = self.relu(x)
x = self.output(x)
return x
# 2. 模拟数据集(和你的图像尺寸一致即可,这里是28×28灰度图)
def create_dummy_data(img_shape=(28,28), num_samples=1000):
X = torch.randn(num_samples, *img_shape) # 模拟图像
y = torch.randint(0,10,(num_samples,)) # 模拟标签
return TensorDataset(X, y)
# 3. 测试不同batch size是否爆显存
def find_max_batch_size(model, img_shape=(28,28)):
# 从大到小试,快速找到最大值(新手也可以从小到大)
test_batch_sizes = [256, 128, 64, 32, 16, 8]
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device) # 模型放到显卡(没有就用CPU)
dummy_data = create_dummy_data(img_shape)
for bs in test_batch_sizes:
try:
# 创建数据加载器
dataloader = DataLoader(dummy_data, batch_size=bs, shuffle=True)
# 模拟一次训练(前向+反向传播,最耗显存)
for batch_X, batch_y in dataloader:
batch_X = batch_X.to(device)
batch_y = batch_y.to(device)
# 前向传播
output = model(batch_X)
loss = nn.CrossEntropyLoss()(output, batch_y)
# 反向传播(计算梯度,最耗显存)
loss.backward()
# 只测第一个batch就够了,避免浪费时间
print(f"✅ batch_size={bs}:显存足够,能正常运行")
return bs # 找到第一个能用的最大batch size
except RuntimeError as e:
if "out of memory" in str(e):
print(f"❌ batch_size={bs}:显存不足,报错了")
continue
else:
raise e # 其他错误,不是显存问题
# 所有都报错,用最小的8
print(f"⚠️ 所有测试的batch size都爆显存,用最小的8")
return 8
# 运行测试
model = SimpleDigitModel()
max_bs = find_max_batch_size(model)
print(f"\n✅ 你的显卡能扛住的最大batch size是:{max_bs}")
关键说明
- 如果你的电脑没有显卡(只用 CPU):不用怕 "爆显存",batch size 可以选 64/128(CPU 速度慢,太大也没用);
- 如果是彩色图:把
img_shape改成(32,32,3)(比如),测试逻辑完全一样; - 报错关键词:看到
CUDA out of memory就是显存不够,直接调小 batch size。
第二步:在 "最大可用值" 基础上微调(兼顾速度 + 效果)
找到最大可用 batch size 后,不用直接用最大值,微调一下更兼顾 "训练速度" 和 "模型效果",新手按这个规则来:
| ##### batch size 特点 | 小 batch(比如 8/16) | 中 batch(32/64/128) | 大 batch(256/512) |
|---|---|---|---|
| 训练速度 | 慢(每次算的样本少) | 快(行业最优速度) | 极快(但提升有限) |
| 训练效果 | 波动大(容易过拟合) | 稳定(效果最好) | 效果差(学不到细节) |
| 适用场景 | 显存极少(1G/2G) | 新手首选(4G/8G 显存) | 大显存(16G+)+ 大数据 |
新手微调建议
- 优先选 "中 batch":如果最大可用值是 128,就用 64/128;如果最大可用值是 64,就用 32/64;
- 小显存(2G/4G):用 16/32(比如最大可用是 32,就用 32,别贪大);
- 数据量少(比如 < 1 万张图):用小一点的 batch(16/32),避免模型 "学歪";
- 数据量多(比如 > 10 万张图):用大一点的 batch(64/128),训练更快。
举个例子
- 测试后最大可用 batch size 是 128 → 优先用 64(效果更稳);
- 测试后最大可用 batch size 是 32 → 直接用 32(新手不用调小);
- 测试后最大可用 batch size 是 8 → 就用 8(没得选,先跑起来再说)。
第三步:特殊情况的小技巧(新手也能会)
如果遇到 "显存不够但想用好一点的 batch size",或者 "效果不好想调整",试试这 2 个简单技巧:
技巧 1:梯度累积(显存不够时 "变相增大 batch size")
- 通俗解释:工作台放不下 32 个原料,就分 4 次放 8 个,每次算完不调整机器,攒 4 次再一起调整 ------ 相当于用 8 的显存,实现 32 的效果;
- 代码实操(在训练循环里加几行):
python
# 梯度累积示例:batch_size=8,累积4次,等效于32
batch_size = 8
accumulation_steps = 4 # 累积次数
model = SimpleDigitModel().to(device)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
dataloader = DataLoader(create_dummy_data(), batch_size=batch_size)
for epoch in range(5):
model.train()
total_loss = 0
for i, (batch_X, batch_y) in enumerate(dataloader):
batch_X = batch_X.to(device)
batch_y = batch_y.to(device)
# 前向传播
output = model(batch_X)
loss = nn.CrossEntropyLoss()(output, batch_y)
# 梯度累积:把loss除以累积次数(避免梯度爆炸)
loss = loss / accumulation_steps
loss.backward()
# 每累积steps次,更新一次参数
if (i+1) % accumulation_steps == 0:
optimizer.step() # 调整模型参数
optimizer.zero_grad() # 清空梯度
total_loss += loss.item() * accumulation_steps
print(f"Epoch {epoch+1},Loss:{total_loss/len(dataloader):.4f}")
技巧 2:动态调整(训练中看效果微调)
训练过程中如果发现:
- Loss 曲线波动特别大(像过山车):把 batch size 调大一点(比如从 16→32);
- Loss 曲线降得很慢,甚至不降:把 batch size 调小一点(比如从 64→32);
- 训练快但测试准确率低:调小 batch size(比如从 128→64)。
新手速查表(直接抄作业)
| 显卡显存 | 图像类型 | 推荐 batch size |
|---|---|---|
| 2G | 灰度图(28×28) | 8/16 |
| 2G | 彩色图(32×32) | 8 |
| 4G | 灰度图(28×28) | 32/64 |
| 4G | 彩色图(32×32) | 16/32 |
| 8G | 灰度图(28×28) | 64/128 |
| 8G | 彩色图(32×32) | 32/64 |
| CPU | 任意 | 32/64 |
最终总结(新手记这 3 句话就行)
- 先测最大可用值:用第一步的代码,找到不爆显存的最大 batch size;
- 优先选中 batch:32/64/128 是新手的 "安全牌",兼顾速度和效果;
- 不行就调小 / 用梯度累积:显存不够调小,效果不好也调小,别硬撑。
选 batch size 没有 "绝对最优值",新手不用纠结 "差一点会不会影响效果"------ 先让模型跑起来,再慢慢微调,比死磕 "完美值" 更重要!