简介:面向深度学习初学者的时装图像分类代码包,包含源代码与Word说明文档,解决利用神经网络对时尚图片进行多类别分类的任务。资源围绕数据预处理、模型构建、训练与评估等完整流程展开,适合课程作业、毕业设计或神经网络入门实践。压缩包内共2个文件,其中1个py主程序实现从加载数据、归一化、标签编码到卷积神经网络训练、准确率评估的全部步骤;1个Word文档则系统说明实验原理、卷积层池化层与全连接层结构、超参数调整思路及操作流程。压缩包整体约559KB,小巧易用,便于快速下载与翻阅。已有297人学习使用。通过运行主程序,可以亲手复现完整图像分类模型;文档中对损失函数、优化器选择与调参方法也有讲解,能帮助读者深入理解神经网络在图像分类任务中的应用细节,是动手实践的实用参考。
1. 神经网络做Fashion数据分类:一套代码串起从数据到评估的完整链路
拿到「神经网络实现fashion数据分类代码.zip」这个标题的读者,多半不是来围观概念的,而是手头正卡在一个具体问题上:数据下载好了、环境装好了,但模型训练出来的准确率始终在85%附近打转,要么是loss降不下去,要么是训练集和验证集的指标对不上。这篇笔记就围绕Fashion MNIST这个10类服饰数据集,讲清楚从数据加载、模型搭建到训练评估的每一步,以及那些让新手反复翻车的隐藏细节。它适合两类人:刚跑通手写数字识别、想换一个更有挑战的数据集练手的初学者,以及需要一套可复用的图像分类代码模板来快速验证想法的开发者。
2. 先把数据吃透:Fashion MNIST的加载、归一化与标签映射
2.1 为什么要用Fashion MNIST而不是手写数字
MNIST手写数字识别已经被写到几乎每一个深度学习教程里,但它的问题在于太简单了:即使是随机初始化的简单网络,也能轻松跑到90%以上的准确率,练不出对数据分布的敏感度。Fashion MNIST就是冲着这个痛点来的——它同样有6万张训练图和1万张测试图,每张28×28像素的灰度图,但内容是T恤、裤子、连衣裙等10类服饰,类内差异大、类间相似度高。T恤和衬衫、凉鞋和运动鞋之间的边界比数字“4”和“9”模糊得多,这让模型必须真正学到纹理和轮廓特征,而不是靠简单的像素位置记忆。
从工程角度看,Fashion MNIST还有一个实际优势:它和MNIST的接口完全一致。torchvision里直接内置了数据集类,不需要自己写下载脚本,也不用处理复杂的目录结构。这意味着你的数据加载代码几乎可以零成本从MNIST迁移过来,真正的差异在于后面要讲的归一化方式和标签语义。对做工程的人来说,这种“接口不变、难度升级”的过渡是最舒服的。
2.2 数据加载与归一化:一行代码写错,准确率直接掉十几个点
先给出一段最基础的数据加载代码,它对应的是这套代码里通用的data_loader.py部分。常见做法是把数据加载逻辑独立成模块,方便训练和评估脚本复用。
import torch from torchvision import datasets, transforms from torch.utils.data import DataLoader # 定义数据预处理流程 transform = transforms.Compose([ transforms.ToTensor(), # 将PIL图像转为Tensor,像素值从0~255缩放到0~1 transforms.Normalize((0.2860,), (0.3530,)) # 用Fashion MNIST的全局均值和方法做标准化 ]) # 加载训练集 train_dataset = datasets.FashionMNIST( root='./data', train=True, download=True, transform=transform ) # 加载测试集,注意 train=False test_dataset = datasets.FashionMNIST( root='./data', train=False, download=True, transform=transform ) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=256, shuffle=False)这段代码里最容易被忽略的是Normalize那一步。ToTensor()已经把像素值从0~255压到了0~1区间,但模型训练时如果输入分布不在零附近,梯度更新会呈现锯齿状波动。Fashion MNIST的全局像素均值大约是0.2860、标准差约0.3530,这两个数是通过对整个训练集统计得到的,不要拍脑袋填0.5和0.5。如果你拿到的是别人整理的代码包,优先看这两个数字是否和数据集的真实分布匹配,这一处错了,后面所有结果都会偏离。
还有一个细节值得注意:shuffle参数在训练集和测试集上的设置不同。训练时每个epoch都要打乱样本顺序,防止模型学到样本的排列规律;测试时不需要打乱,因为你不会在测试集上更新梯度,保持固定顺序反而方便后续做混淆矩阵可视化时对齐标签。
2.3 标签映射与类别可视化:模型没做错,是你的标签看反了
Fashion MNIST的10个类别对应的标签是从0到9的整数,很多人在跑通训练后画出混淆矩阵,发现模型把“T恤”和“衬衫”混得一团糟,第一反应是模型不行,但实际上可能是标签映射表本身对应错了。官方定义的对应关系是:0为T恤/Top,1为裤子,2为套头衫,3为连衣裙,4为外套,5为凉鞋,6为衬衫,7为运动鞋,8为包,9为 ankle boot(短靴)。
这里有两个最容易记混的坑。第一,3号是连衣裙而不是裙子,4号是外套,这两类在视觉上确实和T恤有重叠区域,分类错误率高是正常现象,不代表代码有bug。第二,5号是凉鞋、7号是运动鞋,鞋类内部的区分度比服饰类更低。建议在训练前先打印一批样本和对应标签确认映射关系,一张图抵过十次猜疑,这个习惯能省下大量排查时间。
import matplotlib.pyplot as plt import numpy as np class_names = ['T恤', '裤子', '套头衫', '连衣裙', '外套', '凉鞋', '衬衫', '运动鞋', '包', '短靴'] # 取一个batch的数据 dataiter = iter(train_loader) images, labels = next(dataiter) # 画出前9张图及其标签 fig, axes = plt.subplots(3, 3, figsize=(6, 6)) for i in range(9): ax = axes[i // 3][i % 3] ax.imshow(images[i].squeeze(), cmap='gray') ax.set_title(class_names[labels[i].item()]) ax.axis('off') plt.tight_layout() plt.savefig('fashion_samples.png', dpi=150)可视化这一步不要省略,它不只是给你看的,更是给整个数据预处理流程做验证。如果图片显示的内容和标签明显不匹配,说明数据加载时索引错位了,这种问题靠看训练曲线是发现不了的。
3. 用前馈神经网络跑通最小分类流程:结构、训练与评估代码
3.1 网络结构设计:层数、隐藏层宽度与激活函数怎么选
在动手写代码之前,先明确前馈神经网络在这里扮演的角色。所谓前馈,指的是信号从输入层到输出层单向流动,没有反馈连接,每一层只把输出传给下一层。Fashion MNIST每张图是28×28的灰度图,把像素逐行展开就得到一个784维的向量,这个向量可以直接作为网络的输入。针对这类任务,通用做法是堆叠2到3个全连接层,在最后一层输出10维的类别得分。
层数和宽度的选择是新手最爱纠结的地方,但实际规律很直接。层数太少(只有输入和输出层),模型本质上是线性分类器,无法处理服饰类别之间的非线性边界;层数太多(超过4个隐藏层),在这个数据规模下收益很小,反而增加过拟合风险和训练时间。我一般会先用一个隐藏层128个神经元的结构做基线,然后逐步加宽或加深,观察验证集准确率的变化。激活函数选ReLU而不是sigmoid或tanh,原因是ReLU的梯度在正区间恒为1,能有效缓解深层网络里的梯度消失问题,训练收敛速度明显更快。
3.2 训练循环的五个关键参数:损失函数、优化器、学习率、batch size、epoch
这五个参数决定了训练的成败,但它们的调整逻辑是清晰的。损失函数用交叉熵,因为这是多分类任务的标配,PyTorch里的CrossEntropyLoss已经内置了softmax操作,所以网络最后一层不需要额外加softmax激活。优化器首选Adam,它对学习率的敏感度比SGD低很多,适合作为默认选择。学习率从0.001起步是一个稳定的经验值,如果loss震荡就降到0.0003,如果收敛太慢就升到0.003,但不要超过0.01。
batch size的设定影响训练的稳定性和显存占用。64和128是最常见的取值,太小会让梯度估计噪声过大,太大则每个epoch的更新次数变少,收敛变慢。epoch数则要根据验证集的指标来定,不要死磕一个固定值——常见做法是训练过程中保存验证准确率最高的模型,而不是跑完固定epoch后取最后一个。
3.3 完整的训练与评估代码:从零到90%准确率
import torch import torch.nn as nn import torch.optim as optim from tqdm import tqdm class FashionMLP(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Flatten(), # 将28x28的二维图像展平为784维向量 nn.Linear(784, 128), # 输入层到隐藏层 nn.ReLU(), nn.Linear(128, 64), # 隐藏层到第二个隐藏层 nn.ReLU(), nn.Linear(64, 10) # 输出层,10个类别 ) def forward(self, x): return self.net(x) model = FashionMLP() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model.to(device) num_epochs = 10 train_losses = [] val_accuracies = [] for epoch in range(num_epochs): model.train() running_loss = 0.0 for images, labels in tqdm(train_loader, desc=f'Epoch {epoch+1}/{num_epochs}'): images, labels = images.to(device), labels.to(device) optimizer.zero_grad() outputs = model(images) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() # 每个epoch结束后在测试集上评估一次 model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) total += labels.size(0) correct += (predicted == labels).sum().item() avg_loss = running_loss / len(train_loader) val_acc = 100 * correct / total train_losses.append(avg_loss) val_accuracies.append(val_acc) print(f'Epoch {epoch+1}: Loss={avg_loss:.4f}, Test Acc={val_acc:.2f}%')这段代码的逻辑分三段。训练阶段先调用model.train(),它告诉模型启用Dropout等训练特性;每次迭代先把梯度清零,否则PyTorch会默认累积梯度;反向传播后调用optimizer.step()更新权重。评估阶段要切换到model.eval(),并包裹在torch.no_grad()里,目的是关闭梯度计算——评估时不需要反向传播,不关掉会白白消耗内存。此处的准确率计算用torch.max取出每个样本得分最高的类别索引,与真实标签逐一对比。
跑完10个epoch,这个三层MLP在Fashion MNIST上的测试准确率通常落在88%~91%之间。如果低于88%,优先怀疑数据预处理有问题,而不是网络结构;如果超过91%,说明你的随机种子可能比较幸运,继续往下看卷积网络的方案会更稳定。
4. 从全连接到卷积:把准确率从90%推到93%以上的CNN实现
4.1 为什么CNN在图像分类上天然优于MLP
把784个像素展开成一维向量丢给全连接网络,本质上丢掉了像素之间的空间结构。相邻像素构成的边缘、纹理这些局部特征,在展开后的向量里被打散了,全连接层只能学习到全局的、位置敏感的模式。卷积神经网络通过卷积核在图像上滑动,天然保留了局部空间关系,而且权值共享让模型的参数量比全连接小一个数量级,不容易过拟合。
对于Fashion MNIST这种28×28的小图,不需要设计太深的网络。输入图像尺寸小,感受野很快就能覆盖全图,堆很多卷积层反而让训练变慢。一个卷积层加一个池化层交替的经典结构就足够,重点在于卷积核数量和全连接层的配合。卷积核数量从32开始,每经过一次池化翻倍,符合图像分辨率减半、通道数翻倍的经验法则。
4.2 一个能直接跑的CNN实现:结构、代码与参数
import torch.nn.functional as F class FashionCNN(nn.Module): def __init__(self): super().__init__() self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1) # 28x28 -> 28x28 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) # 14x14 -> 14x14 self.pool = nn.MaxPool2d(2, 2) # 分辨率减半 self.fc1 = nn.Linear(64 * 7 * 7, 128) self.fc2 = nn.Linear(128, 10) def forward(self, x): x = self.pool(F.relu(self.conv1(x))) # 输出 32x14x14 x = self.pool(F.relu(self.conv2(x))) # 输出 64x7x7 x = torch.flatten(x, 1) # 展平为 64*7*7=3136 维 x = F.relu(self.fc1(x)) x = self.fc2(x) return x model = FashionCNN() criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001)在训练循环不变的前提下,把上一节的FashionMLP()替换成FashionCNN()即可。训练过程会明显变慢,因为卷积层的计算量大于全连接层,但收益也直接体现在指标上:同样的10个epoch,CNN的测试准确率通常能达到92%~94%。padding=1这个参数值得专门说明——如果不加padding,3×3卷积会让28×28的图变成26×26,边缘像素只被卷积核覆盖一次,信息利用不充分;加上padding保持尺寸不变,让卷积层只负责提取特征、池化层负责降维,职责更清晰。
4.3 训练技巧:数据增强要不要上,学习率怎么调
Fashion MNIST这类商品图有一个特点:物体基本居中,背景干净,姿态变化小。随机裁剪、水平翻转这些常见数据增强手段引入的扰动,反而可能让模型学到与任务无关的偏移特征。我实测过在这套数据集上加随机旋转和裁剪,验证集准确率几乎没有变化甚至轻微下降。因此,如果你的目标是把模型跑通并拿到可靠的指标,跳过数据增强是合理的;但如果想检验模型的鲁棒性,可以尝试只加一个3到5度的随机旋转,观察验证集指标的变化。
学习率方面,CNN比MLP对学习率更敏感。0.001依然是稳妥的起点,但如果发现loss在前几个epoch没有下降,先检查是不是学习率太大导致梯度震荡,把learning rate降到0.0003再试,而不是去改网络结构。另一个值得做的尝试是余弦退火调度器,让学习率随训练进度逐渐衰减,往往能在不调其他参数的情况下把准确率再推高0.5到1个百分点。
from torch.optim.lr_scheduler import CosineAnnealingLR scheduler = CosineAnnealingLR(optimizer, T_max=10) # 在每个epoch训练结束后调用 scheduler.step()这段代码要放在epoch循环内部、评估完成之后。T_max设为与epoch数相同,表示在全部训练周期内完成一个完整的余弦周期。注意不要在训练中途加这个调度器,它会让后续学习率的计算基准错乱。
5. 避坑:Fashion MNIST分类最常见的五个翻车现场
5.1 现象:loss在0.7附近纹丝不动,准确率卡在80%以下
原因:最常见的是归一化参数用错,有人直接沿用了MNIST的均值和标准差,或者干脆跳过了Normalize层。Fashion MNIST的像素分布和手写数字不同,用错参数会让输入分布偏移,梯度更新方向混乱。解决:确认预处理流水线里Normalize用的确实是0.2860和0.3530,或者用训练集的真实统计值。
5.2 现象:训练集准确率达到95%,测试集只有85%
原因:这是标准的过拟合,尤其在MLP结构下更明显。全连接层的参数量大,模型把训练样本的特性当成了普适规律。解决:先检查是不是epoch数太多,把验证准确率最高的epoch对应的模型权重保存下来,不要用最后一个epoch的权重测试。如果10个epoch内就出现过拟合,说明模型宽度过大,把隐藏层从128降到64,或者加一个比例为0.5的Dropout层。
5.3 现象:训练时loss曲线剧烈震荡,忽高忽低
原因:学习率过大是最常见的原因,Adam虽然对学习率不敏感,但0.01以上仍然会让权重更新步长超过合理范围。解决:把学习率降到0.0003重新训练。如果仍然震荡,检查batch size是否过小——batch size为16时梯度噪声会明显大于64,优先增大到64或128。
5.4 现象:测试准确率看起来还行,但画出的混淆矩阵对角线很暗
原因:标签映射表用错了。用PyTorch内置的datasets.FashionMNIST时,标签是0到9的整数,但很多人写可视化代码时凭印象把0映射成了“衬衫”,把6映射成了“T恤”,画出的混淆矩阵自然不对。解决:始终用官方类别定义表,并且画完图后随机挑几个样本人工核对,不要只看准确率数字。
提示:混淆矩阵的对角线亮、非对角线暗,代表模型行为正常。类间混淆主要集中在T恤/衬衫/外套、凉鞋/运动鞋/短靴这两组,这是数据本身的特性,不是代码bug。
5.5 现象:换了一台机器重新训练,准确率对不上
原因:PyTorch的某些算子在不同CPU指令集和CUDA版本下存在浮点精度差异,加上数据加载顺序的随机性,结果不可能完全一致。解决:设置随机种子控制可复现性,在训练脚本开头加下面这段代码。
import random import numpy as np def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) set_seed(42)注意这里设置的是PyTorch的CPU和GPU随机种子,但PyTorch本身仍然存在非确定性算子,尤其在cuDNN加速下。完全可复现需要额外设置torch.backends.cudnn.deterministic = True,但会牺牲部分性能,只在调试阶段需要严格复现时才开启。
6. 验证模型没白训:混淆矩阵、单张推理与模型导出
模型训练完成只是第一步,真正能交付给下游使用的,是经过验证的模型权重和可用的推理代码。混淆矩阵是验证的起点,它比准确率这个单一数字信息量丰富得多。用sklearn可以快速生成。
from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay import matplotlib.pyplot as plt model.eval() all_preds = [] all_labels = [] with torch.no_grad(): for images, labels in test_loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs, 1) all_preds.extend(predicted.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) cm = confusion_matrix(all_labels, all_preds) disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=class_names) disp.plot(cmap='Blues', xticks_rotation=45) plt.tight_layout() plt.savefig('confusion_matrix.png', dpi=150)看混淆矩阵时,重点关注两个地方:每一行的非对角线元素是否集中在一两个类别上,如果是,说明这两个类别的特征确实接近,属于数据本身的挑战;如果分散在多列,说明模型学到的是噪声而不是特征,要回去检查训练过程。单张图片的推理代码同样需要单独准备,常见做法是写一个predict.py接收图片路径,返回类别名和置信度。
def predict_image(image_path, model, class_names): from PIL import Image image = Image.open(image_path).convert('L') # 转为灰度图 transform = transforms.Compose([ transforms.Resize((28, 28)), transforms.ToTensor(), transforms.Normalize((0.2860,), (0.3530,)) ]) tensor = transform(image).unsqueeze(0).to(device) with torch.no_grad(): outputs = model(tensor) probs = torch.softmax(outputs, dim=1) conf, pred = torch.max(probs, 1) return class_names[pred.item()], conf.item()这条推理路径里最容易被忽略的是Resize。训练时数据集的图像本来就是28×28,不需要resize,但实际使用中用户给的图片尺寸千差万别,不resize到28×28直接送进模型会报维度错误或者得到荒谬结果。模型导出用torch.save(model.state_dict(), 'fashion_cnn.pth')保存权重,加载时要先实例化模型再load_state_dict,不要直接torch.save(model),前者只存参数、兼容性更好,后者把整个模型结构序列化,换版本后容易反序列化失败。
我自己的习惯是训练完模型后永远先跑一遍单张推理再谈部署,这一步能拦截大多数“训练指标好但实际用不了”的问题。整个过程跑下来,你会对数据预处理、模型容量和验证闭环这三个环节的配合有完整的体感,而不是停留在调参的层面。希望这些经验对你有所帮助。
本文还有配套的精品资源,点击获取