1. 从零开始:为什么我建议你直接用CNN做图像识别
先亮个底。这篇文章讲的是用Python跑一个完整的CNN图像识别项目,从环境搭建到模型训练再到最后的准确率调优,全程有代码、有参数、有踩坑记录。如果你已经看过不少"三分钟上手深度学习"的标题党文章,被各种数学公式劝退过,那这篇应该能给你一个不同的观感:CNN没那么玄乎,图像识别也没那么遥远,动手跑通一次比看十遍理论都有用。
先说说什么场景需要这个东西。识别图片里的猫和狗,判断一张工业照片里有没有瑕疵,自动把快递单号从照片里抠出来,这些任务背后都可以用同一套技术栈解决,就是Python加上CNN。我自己最早接触这个方向是因为要给一批零件照片做缺陷分类,几百张图靠人工看眼睛都快瞎了,后来用CNN做了个简单的二分类,虽然初期准确率才85%,但已经比肉眼稳定太多。从那以后我就发现,图像识别不是算法工程师的专利,只要你愿意花几个晚上跑通一个完整流程,后面再遇到类似需求就有了底气。
这篇文章适合谁?适合那种懂点Python基础(会装库、会写for循环就行),但完全没碰过深度学习的朋友。我会尽量少讲数学,多讲直觉,因为我自己也是这么走过来的。CNN里那些卷积、池化的概念,本质上都是一些很朴素的图像处理思路,只不过被包装成了听起来很高档的术语。你不需要先修一门神经网络课再动手,边跑边学反而是效率最高的方式。
2. 环境准备:先把Python、CUDA和PyTorch这三件套伺候明白
2.1 为什么我推荐PyTorch而不是TensorFlow
新手问得最多的一个问题就是:到底学PyTorch还是TensorFlow?我的建议很直接:先学PyTorch。原因有三:第一,PyTorch的API设计更符合Python直觉,调试的时候报错信息能看懂;第二,现在的学术论文和开源项目大部分基于PyTorch,你照着GitHub上的代码学习几乎零门槛;第三,写代码时不用像TensorFlow早期版本那样先建静态图再执行,写起来就是普通的Python逻辑。TensorFlow当然也很强,尤其在工业部署场景有它的优势,但上手阶段PyTorch的容错率高得多。
版本选择上,别一味追新。以2024年中期的稳定生态为准,Python推荐3.10或3.11,PyTorch推荐2.x版本(目前2.3左右的版本很稳),CUDA建议11.8或12.x。有个特别实用的建议:先确认自己的显卡驱动支持哪个CUDA版本,再用对应的版本安装PyTorch。你可以打开命令行输入nvidia-smi看右上角的CUDA Version,那个数字是驱动支持的最高版本,往下兼容就行。如果没有NVIDIA显卡也别慌,PyTorch有纯CPU版本,跑小数据集完全够用,只是训练慢那么几分钟而已。
2.2 安装过程与常见翻车点
搭建环境的命令其实就三行核心:
conda create -n cnn_env python=3.10 conda activate cnn_env pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118第一行创建虚拟环境,第二行激活,第三行装PyTorch全家桶。这里有个细节:torchvision一定要装,因为后面加载数据集、做图像预处理都用得上它。装完之后可以验证一下:
python -c "import torch; print(torch.__version__, torch.cuda.is_available())"返回值如果是2.x.x True,说明GPU版本可用;如果显示False,也不是天塌了,后面用CPU跑就行。我见过最多的问题就是:装完PyTorch提示找不到CUDA动态库。这种情况99%是版本不匹配导致的,检查你装的cu118是否对应当前驱动支持的版本,或者干脆重装。
还需要装两个常用库:
pip install numpy matplotlib pillow opencv-pythonnumpy是数据处理的地基,matplotlib用来画训练曲线和查看图片结果,OpenCV主要用于图片读取和简单预处理。别小看这些配套库,后面几乎每一步都离不开它们。
2.3 Jupyter Notebook还是IDE?
这个没什么好纠结的。做实验阶段用Jupyter Notebook,写正式脚本用VS Code。Jupyter的交互特性非常适合探索性地调整模型,哪个参数变了,立刻重跑一个cell就能看到效果。VS Code则适合整理成一整套项目代码,比如训练脚本、预测脚本拆分开来。两者共用同一个虚拟环境,在VS Code里选择正确的解释器就行。
3. 数据集:模型就像小孩,喂什么学什么
3.1 这次实战选什么数据集
为了让你能完整体验流程,我建议用最经典的CIFAR-10数据集。它是10类小图片的集合(飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车),每张图32x32像素,总共6万张。为什么选它?因为它够小、够标准、足够有代表性。一张图才32x32,CPU也能快速训练,但分类难度又不会低到让你觉得无聊。这个数据集在PyTorch里自带下载接口,不用手动去网站找。
如果你做的是特定行业的识别任务(比如零件缺陷、农作物病害),往往需要自己整理数据集。核心原则就一条:每个类别至少要有几百张图,且图片背景要多样。我自己做零件缺陷分析的时候,刚开始只收集了每类50张,结果训练出来的模型换个拍摄角度就识别错了,后来补到每类300张,泛化能力立刻好了很多。
3.2 数据加载与数据增强
PyTorch加载数据用的是DataLoader,配合torchvision.transforms做预处理。数据增强是这里面的重要一环,但很多人忽略。
数据增强是什么?简单说,原始数据就那些,但我们可以在训练时对图片做随机翻转、裁剪、调亮度、加噪声,这样模型每次看到的"新图"都略有不同,相当于免费扩充了训练集。这个操作能显著提升模型的泛化能力,让模型不至于死记硬背。别小看这一步,我见过太多人训练集准确率99%,测试集只有80%,问题多半出在缺乏数据增强。
这里给出一个标准的预处理组合:
from torchvision import transforms transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), # 随机裁剪,模拟偏移 transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.ToTensor(), # 转张量并归一化到0-1 transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) # 标准化 ]) transform_test = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ])那三个均值方差是CIFAR-10官方统计好的RGB值,直接用就行。
加载训练数据:
from torchvision import datasets from torch.utils.data import DataLoader trainset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train) trainloader = DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2) testset = datasets.CIFAR10(root='./data', train=False, download=False, transform=transform_test) testloader = DataLoader(testset, batch_size=64, shuffle=False, num_workers=2)注意batch_size=64表示每次给模型64张图,shuffle=True表示每个epoch打乱顺序,这两个参数的合理选择能避免模型陷入不利的训练模式。
4. 理解CNN的"眼睛":卷积是怎么看图的
4.1 从全连接到卷积:一个生活化的类比
在CNN出现之前,神经网络处理图像的做法是把每个像素都当作输入特征,一张32x32的彩色图就是3072个数值,全部连接起来。听着没什么,但换成一寸照片大小的图,像素变成数百万级,参数数量会被撑爆。
CNN的思路完全不同。它不关心每一个像素位置,而是关心局部模式。把图像想象成一幅拼图,边缘、纹理、形状这些特征都是局部区域呈现的,不用看全局。这就好比你看一张人脸,不是先记住每一个像素点,而是先看眼睛鼻子嘴巴这些局部器官,再组合起来判断是谁。
卷积层做的就是这件事。一个卷积核(也叫滤波器)其实就是一个小矩阵,比如3x3,它在整张图上从左到右、从上到下滑动,每到一个位置就和那块的像素做加权求和,得到一个响应值。不同的卷积核提取不同的特征:有些对横线敏感,有些对竖线敏感,有些检测颜色变化。第一层卷积提取的是低级特征(线条、边缘),经过多层堆叠后,网络就能组合出高级特征(眼睛、轮胎)。
4.2 池化层:压缩信息的智慧
池化层简单粗暴,作用就是给特征图降维度。最常用的是最大池化(Max Pooling),在一个2x2区域内取最大值作为代表。为什么取最大值?因为它能保留这个区域内最明显的特征响应,同时把无关细节扔掉。就好比你在开会时让每个部门只派一个代表来发言,代表带来的一定是那个部门最强的声音。
还有Dropout层,它的作用是随机"关闭"一部分神经元,让网络不能过度依赖某个通道,从而抑制过拟合。训练时每个batch随机关掉一部分连接,测试时全部打开。这个机制我后面还会重点讲。
4.3 卷积核数量和感受野的直觉
搭建CNN时总听到"通道数64"、"通道数128"之类的说法。通道数就是卷积核的数量。初始层特征简单,用32个卷积核就够了;越往深层,特征越抽象,需要更多的卷积核来捕捉不同模式。感受野则是指某一层神经元看到的原始图像范围。卷积层堆叠得越多,感受野越大,网络就能看到更全局的信息。
一个常用的经验规律:图像尺寸每经过一次池化缩小一半,通道数扩大一倍。从32x32x3逐渐变成16x16x32再到8x8x64,这样既控制了计算量,又保持了信息丰富度。
5. 搭出第一个CNN:代码逐行走一遍
5.1 网络结构设计
清晰多了,直接给一个适合CIFAR-10的经典小模型:
import torch.nn as nn import torch.nn.functional as F class SimpleCNN(nn.Module): def __init__(self): super(SimpleCNN, self).__init__() self.conv1 = nn.Conv2d(3, 32, 3, padding=1) self.bn1 = nn.BatchNorm2d(32) self.conv2 = nn.Conv2d(32, 64, 3, padding=1) self.bn2 = nn.BatchNorm2d(64) self.pool = nn.MaxPool2d(2, 2) self.conv3 = nn.Conv2d(64, 128, 3, padding=1) self.bn3 = nn.BatchNorm2d(128) self.fc1 = nn.Linear(128 * 4 * 4, 256) self.fc2 = nn.Linear(256, 10) self.dropout = nn.Dropout(0.25) def forward(self, x): x = F.relu(self.bn1(self.conv1(x))) x = self.pool(x) x = F.relu(self.bn2(self.conv2(x))) x = self.pool(x) x = F.relu(self.bn3(self.conv3(x))) x = self.pool(x) x = x.view(x.size(0), -1) x = F.relu(self.fc1(x)) x = self.dropout(x) x = self.fc2(x) return x这段代码包含三层卷积、三层批归一化(BatchNorm)、三层池化以及两层全连接。结构图就是"卷积 -> 激活 -> 池化"循环三次,然后展平,接全连接层分类。
5.2 关键参数选择背后的为什么
注意到几个细节没有?为什么每个卷积层后面都加一个BatchNorm2d?因为批归一化能把每层的输出分布拉回到均值为0、方差为1的状态,使得网络在训练过程中更加稳定,梯度传播不容易消失,收敛速度明显加快。很多新手搭的网络训练起来损失曲线像心电图一样跳动,加一层BN就能缓解大半。
F.relu是激活函数,它的作用是引入非线性。如果不加激活函数,无论堆多少层,网络本质上还是一个线性变换,学不到复杂模式。ReLU函数就是负数置零、正数保留,简单粗暴但效果好。
x.view(x.size(0), -1)这行很多人看不懂,其实就是在打平。卷积和池化之后,输出是一个多维的特征图,比如64张图每张是4x4x128,view操作把它变成一个一维向量,好喂给全连接层。-1表示自动推导这个维度,配合x.size(0)(就是batch的大小)。
5.3 参数量估算:别做无头苍蝇
动手训练前,花十秒钟估算一下参数量是明智的。以这个网络为例:第一层卷积Conv2d(3, 32, 3)的参数是3*32*3*3 + 32 = 896(权重加偏置)。第三层卷积Conv2d(64, 128, 3)的参数约64*128*3*3 + 128 = 73856。全连接层Linear(128*4*4, 256)的参数是2048*256 + 256 = 524544。总参数在70万左右,对于现代设备来说很轻量。参数太多意味着需要更多数据才能充分训练,所以算参数量的意义在于:判断自己的数据量够不够。
CIFAR-10有五万张训练图,训这个七十几万的模型绰绰有余。如果你只有几千张自拍图,那就要减小模型规模或者大量增加数据增强。
6. 训练环节:损失函数、优化器与超参数调参实录
6.1 损失函数与优化器的选择
分类任务最常用的损失函数是交叉熵损失(CrossEntropyLoss)。你可以把它理解为:模型给正确类别分配的概率越低,损失值就越大。这个损失符合我们的直观:预测对了,惩罚小;预测错了,惩罚大。
优化器选Adam还是SGD?新手直接选Adam,它会自动为每个参数调整学习率,对初始化不敏感,默认参数就能跑出不错的效果。SGD要手动调学习率,配合动量(momentum)才能发挥威力,不是新手该碰的东西。等后面你理解了学习率和梯度下降的关系,再转SGD追求更好的泛化性也不迟。
import torch.optim as optim criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001)学习率lr=0.001是Adam的经典起点。如果你发现损失持续震荡不降,可以尝试降到0.0003。
6.2 完整训练循环
训练一个epoch的代码长这样:
def train_one_epoch(model, trainloader, optimizer, criterion, epoch): model.train() running_loss = 0.0 correct = 0 total = 0 for i, (images, labels) in enumerate(trainloader): outputs = model(images) # 前向传播 loss = criterion(outputs, labels) # 计算损失 optimizer.zero_grad() # 梯度清零 loss.backward() # 反向传播 optimizer.step() # 更新参数 running_loss += loss.item() _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() if i % 100 == 99: print(f'Epoch {epoch+1} Batch {i+1}: loss {running_loss/100:.3f}, acc {100*correct/total:.2f}%') running_loss = 0.0一个关键细节:optimizer.zero_grad()必须在loss.backward()之前执行。PyTorch在反向传播时是累加梯度,不清零的话,上一个batch的梯度会叠加到当前batch上,导致参数更新方向混乱。我第一次跑的时候忘了这行,结果损失死活降不下来,查了半天才发现是这个问题。
主循环:
num_epochs = 20 for epoch in range(num_epochs): train_one_epoch(model, trainloader, optimizer, criterion, epoch)GPU上跑大概几分钟一个epoch,CPU大约十几分钟,总共20个epoch也就一个多小时内完成。跑完记得在测试集上评估:
def evaluate(model, testloader): model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in testloader: outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() return 100 * correct / total print(f'Test Accuracy: {evaluate(model, testloader):.2f}%')注意评估时一定要开torch.no_grad(),这样就不会为推理过程中的中间张量保存梯度,省内存且提速;同时要把model切换为eval()模式,因为BN和Dropout在训练与推理时的行为不同。
6.3 训练结果解读
按上面的配置,跑20个epoch之后,CIFAR-10的测试集准确率通常能达到**75%到80%**之间,训练集准确率则会到95%以上。如果你看到模型在训练集上准确率已经99%,测试集只有70%,那说明过拟合了,后面我会专门讲怎么应对。
一个靠谱的调参思路不是盲目试,而是按顺序来:先确认代码能跑通、损失能下降;再逐步调学习率;然后增加epoch同时观察验证集准确率;最后根据过拟合程度增加正则化或数据增强。不要一上来就上大模型,那样只是把你的训练时间拉长,并不能提升最终效果。
7. 进阶与调优:把准确率再推高10个百分点
7.1 数据增强加码
前面用的数据增强只是基本款。想进一步提升,可以加入RandomRotation、ColorJitter等:
transform_train_advanced = transforms.Compose([ transforms.RandomCrop(32, padding=4), transforms.RandomHorizontalFlip(), transforms.RandomRotation(15), # 随机旋转15度 transforms.ColorJitter(brightness=0.2, contrast=0.2), # 调亮度对比度 transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ])注意增强要合理。把猫的照片旋转180度,猫还是猫,这是合理的增强;但如果是识别数字6和9,旋转180度语义就变了,这种增强会增加训练难度甚至误导模型。做数据增强前一定要问自己:这个变换在不改变分类结果的前提下是否合理。
7.2 学习率衰减
学习率固定为0.001时,训练后期往往会出现loss在某个平台期震荡的现象。这时候一个标准的做法是学习率衰减,让模型在后期用小步子精细调整。常见策略是每10个epoch把学习率除以10:
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)在每次epoch结束后调用scheduler.step()就行。
7.3 迁移学习:站在巨人的肩膀上
数据量不够或想要更高的精度,最快的方式是迁移学习。就是用别人在大数据集上预训练好的模型(比如ResNet18在ImageNet上训练过),把最后一层换成自己的分类层,只训练最后一层或者只以很小的学习率微调整个网络。这一招在自定义数据集上效果立竿见影,哪怕只有几千张图也能达到90%以上的准确率。
在PyTorch里用预训练模型很简单:
import torchvision.models as models model = models.resnet18(pretrained=True) num_ftrs = model.fc.in_features model.fc = nn.Linear(num_ftrs, 10) # 10是分类数,替换最后一层注意:因为CIFAR-10是32x32小图,ResNet默认输入是224x224,直接喂32x32会报尺寸错误。通用的解决办法是第一层之前加一个自适应池化把特征图缩到合适大小,或者自己修改输入尺寸。这个细节不解决可能就卡半小时。
我个人的经验是:能用预训练模型就用预训练模型,别自己从零硬训。你自己训练一个模型需要几天时间、大量数据和调参精力,而ResNet18这类模型是人家用成千上万张图几个月调出来的,白嫖它不香吗?唯一的缺点是模型文件大一些,推理速度略慢。
7.4 Batch Size和SGD:深水区的玩法
当你把准确率推到80%以上,瓶颈往往在学习率和优化器的配合上。此时可以试试SGD加动量:
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9, weight_decay=5e-4)SGD加动量要求的学习率通常比Adam大(0.01到0.1),配合余弦退火效果很好。这也是很多论文里的标准配置。不过你要做好学习率衰减和调试的心理准备,因为SGD对学习率敏感多了。
8. 常见问题速查:我踩过的坑你都别踩
跑这类项目时,每个环节都可能卡住。我把最常见的坑和对应的排查方式整理成表,方便你对着查。
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| 训练loss不降 | 学习率太大或太小 | 换成0.001起步,观察loss变化 |
| loss是nan | 学习率过大导致梯度爆炸 | 降低学习率到0.0001 |
| 模型在测试集上表现很差 | 过拟合 | 增加Dropout、数据增强、降低模型容量 |
| 训练集准确率比测试集高很多 | 过拟合的典型信号 | 增加正则化或简化网络 |
| CPU训练极慢 | batch_size太大 | 把batch_size从64降到16或8 |
| 显存不够(OOM) | 模型太大或batch太大 | 减小batch_size、减小图片尺寸、使用CPU |
| 验证集loss上升但训练集下降 | 过拟合正是在发生 | 早停或者加正则化 |
| 图片显示成纯色/乱码 | 忘记转Tensor或者归一化错误 | 检查ToTensor和Normalize的顺序与参数 |
| 运行报错找不到某模块 | 版本冲突 | 确认虚拟环境激活,重新pip install |
| 颜色通道顺序错乱 | OpenCV读图是BGR,PyTorch要求RGB | 用cv2.cvtColor转换或者直接用PIL读图 |
有一个非常容易被忽略的问题:在CPU上训练时第一次加载数据特别慢,这是因为PyTorch在初始化数据集时要做预处理。对策是设置num_workers=0减少多进程带来的开销,或者耐心等待。反之,在GPU上训练时num_workers=2能很好地分担数据加载压力,不要让GPU空等。
另外,关于环境变量的坑,在Windows上训练深度学习经常遇到系统PATH里的Python版本混乱导致装错库的问题。强烈建议用conda建独立虚拟环境,把环境激活后where python确认一下路径指向,指向错了训练多久都是白费。
9. 从Jupyter到项目工程化:部署与扩展思路
9.1 把模型保存下来
训练完的模型要保存:
torch.save(model.state_dict(), './cifar10_cnn.pth')加载时注意要新建一个结构相同的模型再用load_state_dict加载:
model = SimpleCNN() model.load_state_dict(torch.load('./cifar10_cnn.pth')) model.eval()关于保存整模型还是只保存权重,我建议只保存state_dict,因为它体积小、跨版本兼容性好。如果你用torch.save(model, ...)整个保存,换环境后经常遇到类定义路径不匹配的问题。
9.2 单张图片预测
部署时经常需要喂一张图进去出结果。核心步骤是读取图片、做和训练一样的预处理、放进模型推理:
from PIL import Image import torchvision.transforms.functional as TF def predict_single_image(model, image_path, device): img = Image.open(image_path).convert('RGB') img = TF.resize(img, (32, 32)) img = TF.to_tensor(img) img = TF.normalize(img, mean=(0.4914, 0.4822, 0.4465), std=(0.2023, 0.1994, 0.2010)) img = img.unsqueeze(0) # 增加batch维度 img = img.to(device) model.eval() with torch.no_grad(): outputs = model(img) _, predicted = torch.max(outputs, 1) return predicted.item()img.unsqueeze(0)这步很容易漏。模型训练时输入的维度是NCHW,也就是(batch、通道、高、宽),单张图只有CHW三个维度,必须补一个batch维度才能喂进去。
如果想让输出更直观,把数字标签映射回类别名:
classes = ['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'] print(f'预测结果: {classes[predicted.item()]}')9.3 拓展到自己的数据集
想在真实项目中用这套技术,核心就是准备一个像CIFAR-10一样的数据集目录:每个类别一个文件夹,文件夹里放对应图片。然后可以用torchvision.datasets.ImageFolder加载,几乎零改动就能复用上面的全部代码。
有一个重要提醒:真实场景的画面通常比32x32大得多。如果你的图片是几百像素的,直接用原始尺寸训会非常慢。正确的做法是设置resize为64x64或128x128,然后再调网络结构和池化层数量。经验规律是:模型输出的特征图尺寸最终要能变成不小于4x4,太小了全连接层就没有足够的信息了。
9.4 边缘设备可行吗
热词里出现了esp32s3cam图像识别,确实有人想把这套技术往嵌入式设备上搬。我的建议是现实一点:像上面这个SimpleCNN大约70万参数,编译量化后在ESP32-S3这种平台上勉强能跑,但推理速度很慢(几秒到十几秒)。更可行的路线是先训练一个大模型,然后蒸馏成一个小模型(比如MobileNetV3),再转成TFLite格式部署。这个方向很有趣,但不是一个晚上能搞定的,建议先把PC端流程跑通再想边缘迁移。
10. 关于GPU、CPU与算力的几句实在话
很多初学者焦虑:我的电脑没有GPU,能学深度学习吗?答案非常明确:必须是、肯定能。CIFAR-10数据集只有6万张32x32小图,一台2018年的老笔记本CPU也能在半小时内完成20轮训练。即使到了真实项目,只要数据量控制在几千张、图片resize到适当大小,CPU依然能完成训练,只是效率低一些而已。
如果要买卡,没有必要一上来就上4090。入门阶段用RTX 3060或者二手2080完全足够。我的实操经验是,当前这个模型在3060上训练一个epoch大约15秒,20个epoch五分钟就能跑完,而CPU可能要30分钟以上,差距确实明显,但不影响初学者在CPU上跑通流程。
还有一点值得说一下:跑深度学习确实对电脑的散热是一种考验。CPU长时间100%运转,风扇会狂转,这是正常的。如果笔记本过热自动关机,建议调小batch_size,降低负载,而不是去清灰把机器拆了(别问我为什么会说这个)。
11. 最后分享一点个人经验
从第一次跑通LeNet在MNIST上识别手写数字,到现在能快速搭建一个CNN原型,我最大的感受是:深度学习入门最难的不是理论,而是把无数个小细节串成一条龙。环境要对、数据格式要对、维度要匹配、训练和评估模式要切换、梯度要记得清零。这些细节每个单独拎出来都简单,但它们组合在一起,对于新手就像掉进了一个满是隐藏机关的房间。
我的建议是:一步一步来,不要贪多。第一次不追求准确率多高,只要能跑通流程,把代码从上到下运行一遍,就已经是一次巨大的胜利。然后在此基础上,尝试换一个优化器、调一个参数、加一层卷积,看看结果有什么变化。这种"做实验"的积累,比任何教程都更能帮你在脑子里建立起对模型的直觉。
如果你准备动手,今天就把环境搭好,明天用周六晚上的时间跑完第一轮训练。看到准确率徐徐上升的那一刻,你会觉得前面踩的所有坑都值了。这个项目既是入门的一次自我测试,也是后面所有图像识别场景的一块坚实跳板。祝第一次运行就一次通过。