DAY 42 图像数据与显存

知识点回顾

零基础学 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()

零基础总结

今天咱们用 "加工厂" 的比喻,把核心知识点串起来了:

  1. 图像数据:灰度图是单数字矩阵,彩色图是 RGB 三数字矩阵;
  2. 模型:处理图像的 "机器",由参数组成,训练就是调整参数;
  3. 显存占用:工作台被 4 样东西占 ------ 参数 / 梯度、优化器、批量数据、中间状态;
  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))。

实操方法(新手无脑版)
  1. 先定初始值 :从行业通用的 "中间值" 开始试,比如 32(绝大多数入门显卡(2G/4G 显存)都能扛住);
  2. 逐步翻倍测试 :如果跑起来不报错,就试 64128256... 直到出现 "显存不足" 报错;
  3. 回退到安全值:报错后,把 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+)+ 大数据
新手微调建议
  1. 优先选 "中 batch":如果最大可用值是 128,就用 64/128;如果最大可用值是 64,就用 32/64;
  2. 小显存(2G/4G):用 16/32(比如最大可用是 32,就用 32,别贪大);
  3. 数据量少(比如 < 1 万张图):用小一点的 batch(16/32),避免模型 "学歪";
  4. 数据量多(比如 > 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 句话就行)

  1. 先测最大可用值:用第一步的代码,找到不爆显存的最大 batch size;
  2. 优先选中 batch:32/64/128 是新手的 "安全牌",兼顾速度和效果;
  3. 不行就调小 / 用梯度累积:显存不够调小,效果不好也调小,别硬撑。

选 batch size 没有 "绝对最优值",新手不用纠结 "差一点会不会影响效果"------ 先让模型跑起来,再慢慢微调,比死磕 "完美值" 更重要!

浙大疏锦行

相关推荐
InfiSight智睿视界5 小时前
AI驱动下的连锁餐饮巡店模式:从人工核验到智能闭环
人工智能·智能巡检系统
hopsky5 小时前
阿里云数据中台data+ai架构演进
人工智能·阿里云·架构
q_30238195565 小时前
双能突围!能源高效型模型压缩+碳足迹追踪,解锁数据中心与农业AI新价值
人工智能·python·深度学习·能源·课程设计·ai编程
byzh_rc5 小时前
[模式识别-从入门到入土] 高斯混合模型
人工智能·机器学习·支持向量机
赫凯5 小时前
【强化学习】第三章 马尔可夫决策过程
python·算法
Daily Mirror5 小时前
Day42 Dataset和Dataloader
python
智航GIS5 小时前
1.2 python及pycharm的安装
开发语言·python·pycharm
风途知识百科5 小时前
物联网虫情测报灯
人工智能·物联网
aaaa_a1335 小时前
李宏毅:AI AGENT
人工智能