☰
Fashion-MNIST服装分类实战:PyTorch与TensorFlow双框架教学闭环
2026/10/11 21:55:58 网站建设 项目流程

简介:本资源是一个面向深度学习初学者与课程设计实践者的服装图像智能分类项目,聚焦Fashion-MNIST数据集上的端到端建模与工程实现,解决图像识别中类别判别、特征提取与模型部署等核心问题,适用于人工智能课程设计、毕业设计及CV入门实战。压缩包共5个文件(984KB),含Python主程序(FashionClassification.py)实现CNN模型构建与训练、PDF版课程设计报告详述算法原理与实验过程、Word文档记录作者信息与设计思路、Markdown格式README说明运行环境与步骤、TXT文本提供基础使用提示,结构精炼、即开即用。目前已有123人学习下载,读者可直接复现完整训练流程,掌握数据预处理、PyTorch/TensorFlow模型搭建、准确率评估及结果可视化等关键技能,并通过报告与代码对照理解从理论到落地的全链路设计逻辑。

1. 为什么用 Fashion-MNIST 做服装分类,比直接上真实电商图更稳、更快、更适合课程设计?

你手头有一堆淘宝/拼多多风格的服装图:背景杂乱、模特姿态不一、光照忽明忽暗、还有水印和文字遮挡——直接扔进 ResNet 训练?大概率第一轮 val_acc 就卡在 65%,loss 曲线像心电图一样抖,调参三天后发现是数据在“玄学”干扰模型。而这个标题里的「服装图像智能分类与识别系统」,核心落点其实是:用 Fashion-MNIST 这个被千锤百炼过的灰度服装基准数据集,在 PyTorch/TensorFlow 框架下,跑通从数据加载、模型搭建、训练监控到推理部署的最小闭环。它不是工业级落地项目,而是帮你把「卷积怎么提取纹理」「BatchNorm 为什么能加速收敛」「CrossEntropyLoss 的 logits 输入逻辑」这些黑匣子,变成可打断、可打印、可改一行代码就看到效果变化的实体。适合课程设计、毕设开题、深度学习入门实战——不拼算力,不靠数据清洗玄学,只考你对框架 API 的理解深度和 debug 能力。压缩包里那份「详细课程设计报告」,本质是一份带批注的实验记录:每张 loss 曲线图都标了超参组合,每个准确率数字背后都有 dropout 率和学习率衰减策略的对照。这不是玩具,是能让你在答辩时被问「为什么用 Adam 不用 SGD」时,掏出训练日志截图当场解释的底气。

2. 用 PyTorch 在本地跑通 Fashion-MNIST 分类:从数据加载到模型验证的最小命令链

2.1 三行代码加载 Fashion-MNIST 并完成标准化预处理

Fashion-MNIST 是 28×28 灰度图,共 10 类(T-shirt、Trouser、Pullover 等),训练集 60,000 张,测试集 10,000 张。PyTorch 提供了开箱即用的torchvision.datasets.FashionMNIST,但直接加载会返回 PIL 图像,需转为 tensor 并归一化。关键不是「能不能加载」,而是「加载后张量形状是否符合后续 conv 层输入要求」:

import torch from torchvision import datasets, transforms # 定义预处理流水线:转 tensor + 归一化(均值0.1307,标准差0.3081是Fashion-MNIST官方统计值) transform = transforms.Compose([ transforms.ToTensor(), # PIL → [C, H, W],自动除以255缩放到[0,1] transforms.Normalize((0.1307,), (0.3081,)) # 单通道灰度图,均值/标准差为标量元组 ]) # 加载训练集和测试集,root指定数据存放路径,download=True自动下载 train_dataset = datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform) test_dataset = datasets.FashionMNIST(root='./data', train=False, download=True, transform=transform) # 验证张量形状:batch_size=1时,输入应为 [1, 1, 28, 28] sample_img, sample_label = train_dataset[0] print(f"Sample shape: {sample_img.shape}, Label: {sample_label}") # 输出: torch.Size([1, 28, 28]), 0

注意:transforms.Normalize的参数必须是元组,即使单通道也要写(0.1307,)而非0.1307,否则会报TypeError: img should be PIL Image。这是新手最常翻车的第一步——看似加载成功,实际sample_img是未归一化的[0,1]浮点 tensor,而 Normalize 期望的是[0,255]整型输入,导致归一化失效。我们这里用ToTensor()先完成缩放,再 Normalize,顺序不能颠倒。

2.2 搭建轻量 CNN 模型:兼顾课程设计可解释性与收敛速度

课程设计不需要 ResNet-50,一个 4 层卷积+2层全连接的结构足够:既能清晰展示特征图尺寸变化(28→24→20→16→12→8),又能在 CPU 上 10 分钟内跑完 10 个 epoch。重点在于让每一层输出尺寸可推导,方便调试时print(x.shape)定位维度错位:

import torch.nn as nn import torch.nn.functional as F class FashionCNN(nn.Module): def __init__(self, num_classes=10): super().__init__() # 第一层卷积:输入1通道,输出32通道,kernel=5,padding=2保证尺寸不变(28→28) self.conv1 = nn.Conv2d(1, 32, kernel_size=5, padding=2) # out: [32, 28, 28] self.bn1 = nn.BatchNorm2d(32) # 第二层卷积:32→64,kernel=5,stride=2实现下采样(28→14) self.conv2 = nn.Conv2d(32, 64, kernel_size=5, stride=2) # out: [64, 14, 14] self.bn2 = nn.BatchNorm2d(64) # 第三层卷积:64→128,kernel=3,padding=1(14→14),再接maxpool(14→7) self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1) # out: [128, 14, 14] self.pool3 = nn.MaxPool2d(2) # out: [128, 7, 7] # 全连接层:128*7*7=6272 → 512 → 10 self.fc1 = nn.Linear(128 * 7 * 7, 512) self.fc2 = nn.Linear(512, num_classes) self.dropout = nn.Dropout(0.5) # 防止过拟合,课程设计中 dropout=0.5 是经验值 def forward(self, x): x = F.relu(self.bn1(self.conv1(x))) # [1,1,28,28] → [1,32,28,28] x = F.relu(self.bn2(self.conv2(x))) # [1,32,28,28] → [1,64,14,14] x = F.relu(self.conv3(x)) # [1,64,14,14] → [1,128,14,14] x = self.pool3(x) # [1,128,14,14] → [1,128,7,7] x = x.view(x.size(0), -1) # flatten: [1,128,7,7] → [1,6272] x = F.relu(self.fc1(x)) # [1,6272] → [1,512] x = self.dropout(x) x = self.fc2(x) # [1,512] → [1,10] return x model = FashionCNN() print(model)

逻辑说明:forward中每一步的x.shape都可手动验算。例如conv2的stride=2是为了快速降维,避免全连接层参数爆炸;pool3放在conv3后而非中间,是因为课程设计强调「先提特征再压缩」的教学逻辑;view(x.size(0), -1)的-1表示自动计算剩余维度,等价于6272,但写-1更鲁棒——万一改了卷积参数,不用手动重算。

2.3 训练循环:带进度条、早停与模型保存的完整脚本

课程设计报告里「训练过程分析」章节,需要可复现的 loss/acc 曲线。以下脚本封装了tqdm进度条、torch.save模型快照、以及基于验证集准确率的早停(patience=3):

import torch.optim as optim from torch.utils.data import DataLoader from tqdm import tqdm # 数据加载器:batch_size=128是Fashion-MNIST的常用值,太大易OOM,太小收敛慢 train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True, num_workers=2) test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam比SGD收敛快,课程设计首选 def train_epoch(model, loader, criterion, optimizer, device='cpu'): model.train() running_loss = 0.0 for images, labels in tqdm(loader, desc="Training", leave=False): 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() return running_loss / len(loader) def validate(model, loader, device='cpu'): model.eval() correct = 0 total = 0 with torch.no_grad(): for images, labels in loader: images, labels = images.to(device), labels.to(device) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum().item() return 100 * correct / total # 主训练循环 device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model.to(device) best_acc = 0.0 patience_counter = 0 num_epochs = 20 for epoch in range(num_epochs): train_loss = train_epoch(model, train_loader, criterion, optimizer, device) val_acc = validate(model, test_loader, device) print(f"Epoch {epoch+1}/{num_epochs} | Train Loss: {train_loss:.4f} | Val Acc: {val_acc:.2f}%") # 早停:连续3轮val_acc不提升则停止 if val_acc > best_acc: best_acc = val_acc torch.save(model.state_dict(), 'best_fashion_cnn.pth') # 保存最优模型 patience_counter = 0 else: patience_counter += 1 if patience_counter >= 3: print(f"Early stopping at epoch {epoch+1}") break

参数说明:num_workers=2可加速数据加载,但 Windows 下需放在if __name__ == '__main__':内;tqdm的leave=False避免训练轮次间刷屏;torch.max(outputs.data, 1)中的.data是为兼容旧版 PyTorch,新版可省略;best_fashion_cnn.pth是课程设计报告中「模型评估」章节的原始依据——答辩时可直接加载此文件做 demo。

3. TensorFlow/Keras 版本实现:Keras Functional API 构建可复现的分类流程

3.1 使用 tf.keras.datasets.fashion_mnist 加载并预处理数据

TensorFlow 用户习惯用tf.keras.datasets,其返回的是 numpy 数组,需手动转为 float32 并归一化。与 PyTorch 不同,Keras 默认输入为[N, H, W, C],因此需增加 channel 维度:

import tensorflow as tf from tensorflow import keras import numpy as np # 加载数据:返回 (x_train, y_train), (x_test, y_test) (x_train, y_train), (x_test, y_test) = keras.datasets.fashion_mnist.load_data() # 数据预处理:归一化 + 增加通道维度 x_train = x_train.astype('float32') / 255.0 # [60000, 28, 28] → [60000, 28, 28] x_test = x_test.astype('float32') / 255.0 # [10000, 28, 28] → [10000, 28, 28] x_train = np.expand_dims(x_train, axis=-1) # [60000, 28, 28] → [60000, 28, 28, 1] x_test = np.expand_dims(x_test, axis=-1) # [10000, 28, 28] → [10000, 28, 28, 1] # 标签 one-hot 编码(用于 categorical_crossentropy) y_train_cat = keras.utils.to_categorical(y_train, 10) y_test_cat = keras.utils.to_categorical(y_test, 10) print(f"x_train shape: {x_train.shape}, y_train shape: {y_train_cat.shape}") # 输出: x_train shape: (60000, 28, 28, 1), y_train shape: (60000, 10)

关键区别:PyTorch 的ToTensor()自动完成[H,W]→[C,H,W]和/255,而 Keras 需手动expand_dims和/255。若忘记expand_dims,模型会因输入维度不匹配报错expected conv2d_input to have 4 dimensions。这是跨框架迁移时最典型的维度陷阱。

3.2 用 Functional API 构建模型:显式定义输入输出,便于课程设计报告绘图

Functional API 比 Sequential 更清晰地展示数据流向,适合在课程设计报告中绘制模型结构图(如用keras.utils.plot_model):

# 定义输入层 inputs = keras.Input(shape=(28, 28, 1)) # 第一段:Conv → BN → ReLU → MaxPool x = keras.layers.Conv2D(32, (5, 5), padding='same', name='conv1')(inputs) x = keras.layers.BatchNormalization(name='bn1')(x) x = keras.layers.Activation('relu', name='relu1')(x) x = keras.layers.MaxPooling2D((2, 2), name='pool1')(x) # 28→14 # 第二段:Conv → BN → ReLU → MaxPool x = keras.layers.Conv2D(64, (5, 5), strides=(2, 2), name='conv2')(x) # 14→7 x = keras.layers.BatchNormalization(name='bn2')(x) x = keras.layers.Activation('relu', name='relu2')(x) x = keras.layers.MaxPooling2D((2, 2), name='pool2')(x) # 7→3(向下取整) # 全连接段:Flatten → Dense → Dropout → Dense x = keras.layers.Flatten(name='flatten')(x) x = keras.layers.Dense(512, activation='relu', name='fc1')(x) x = keras.layers.Dropout(0.5, name='dropout')(x) outputs = keras.layers.Dense(10, activation='softmax', name='output')(x) # 构建模型 model_keras = keras.Model(inputs=inputs, outputs=outputs) model_keras.compile( optimizer=keras.optimizers.Adam(learning_rate=0.001), loss='categorical_crossentropy', metrics=['accuracy'] ) # 打印模型结构(课程设计报告可截图此处) model_keras.summary()

逻辑说明:strides=(2,2)在第二层卷积中直接实现下采样,替代了单独的 MaxPool 层,减少参数量;padding='same'保证卷积后尺寸不变,教学时可对比valid的效果;name参数为每层命名,方便在plot_model中显示中文标签(如将'conv1'改为'第一层卷积')。

3.3 训练与回调:使用 ModelCheckpoint 和 ReduceLROnPlateau 实现自动化调优

Keras 的回调机制让课程设计中的「超参调优」变得可追溯。以下脚本自动保存最优权重、动态调整学习率,并生成训练历史供报告绘图:

# 定义回调函数 callbacks = [ keras.callbacks.ModelCheckpoint( filepath='best_fashion_keras.h5', monitor='val_accuracy', save_best_only=True, verbose=1 ), keras.callbacks.ReduceLROnPlateau( monitor='val_loss', factor=0.5, patience=2, min_lr=1e-7, verbose=1 ), keras.callbacks.EarlyStopping( monitor='val_accuracy', patience=3, restore_best_weights=True, verbose=1 ) ] # 开始训练 history = model_keras.fit( x_train, y_train_cat, batch_size=128, epochs=20, validation_data=(x_test, y_test_cat), callbacks=callbacks, verbose=1 ) # 保存训练历史(课程设计报告中 loss/acc 曲线的数据源) import pickle with open('training_history.pkl', 'wb') as f: pickle.dump(history.history, f)

参数说明:ReduceLROnPlateau的factor=0.5表示当val_loss2 轮不下降时,学习率减半;min_lr=1e-7是下限,防止学习率过小导致训练停滞;restore_best_weights=True确保早停后模型权重回滚到最优状态,避免答辩时加载的模型是早停前的次优版本。

4. 模型评估与可视化:混淆矩阵、错误样本分析与 Grad-CAM 热力图

4.1 绘制混淆矩阵:定位模型在哪类服装上持续犯错

混淆矩阵是课程设计报告「结果分析」章节的核心图表。它能直观暴露模型弱点,比如「T-shirt 和 Shirt」混淆严重,说明纹理特征提取不足:

import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix import numpy as np # 加载最优模型(PyTorch版) model.load_state_dict(torch.load('best_fashion_cnn.pth')) 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) _, preds = torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) # 计算混淆矩阵 cm = confusion_matrix(all_labels, all_preds) class_names = ['T-shirt', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'] plt.figure(figsize=(10, 8)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names) plt.title('Confusion Matrix') plt.ylabel('True Label') plt.xlabel('Predicted Label') plt.xticks(rotation=45) plt.yticks(rotation=0) plt.tight_layout() plt.savefig('confusion_matrix.png', dpi=300) plt.show()

技巧:annot=True, fmt='d'显示整数计数而非百分比,符合课程设计对「绝对错误数」的分析需求;cmap='Blues'避免红绿色盲争议;plt.tight_layout()防止中文标签被截断。这张图可直接插入报告,配合文字分析「Coat 与 Pullover 混淆率达 32%,因两者轮廓相似且袖口细节模糊」。

4.2 错误样本可视化:找出模型「看走眼」的典型图像

比准确率更有说服力的是:展示模型把「Sandal」错判成「Sneaker」的原始图像。这能体现你对数据分布的理解深度:

# 找出预测错误的样本索引 error_indices = np.where(np.array(all_preds) != np.array(all_labels))[0] print(f"Total errors: {len(error_indices)}") # 可视化前10个错误样本 fig, axes = plt.subplots(2, 5, figsize=(12, 6)) axes = axes.flatten() for i, idx in enumerate(error_indices[:10]): # 从test_dataset中获取原始图像(未归一化) img, true_label = test_dataset[idx] # 反归一化:img = img * std + mean img = img * 0.3081 + 0.1307 img = np.clip(img, 0, 1) # 限制在[0,1] pred_label = all_preds[idx] axes[i].imshow(img.squeeze(), cmap='gray') axes[i].set_title(f'True:{class_names[true_label]}\nPred:{class_names[pred_label]}', fontsize=9) axes[i].axis('off') plt.suptitle('Top 10 Misclassified Samples', y=1.02) plt.tight_layout() plt.savefig('misclassified_samples.png', dpi=300) plt.show()

血泪经验:img.squeeze()是因为 Fashion-MNIST 是单通道,imshow需要[H,W]而非[1,H,W];np.clip防止反归一化后像素值溢出[0,1]导致图像发白;fontsize=9确保双行标题在小图中清晰可读。这些细节决定答辩 PPT 的专业度。

4.3 Grad-CAM 热力图:可视化模型「关注点」,验证决策逻辑合理性

Grad-CAM 能回答「模型凭什么认为这是 Dress?」——它生成热力图叠加在原图上,显示模型决策依据的区域。这对课程设计中「模型可解释性」章节至关重要:

import cv2 class GradCAM: def __init__(self, model, target_layer): self.model = model self.target_layer = target_layer self.gradients = None self.features = None # 注册钩子 target_layer.register_forward_hook(self._save_features) target_layer.register_backward_hook(self._save_gradients) def _save_features(self, module, input, output): self.features = output def _save_gradients(self, module, grad_input, grad_output): self.gradients = grad_output[0] def __call__(self, input_img): self.model.eval() input_img = input_img.unsqueeze(0).to(device) # [1,1,28,28] # 前向传播 output = self.model(input_img) pred_class = output.argmax(dim=1).item() # 反向传播:计算目标类别的梯度 self.model.zero_grad() output[0, pred_class].backward() # 权重计算 pooled_gradients = torch.mean(self.gradients, dim=[0, 2, 3]) for i in range(self.features.shape[1]): self.features[:, i, :, :] *= pooled_gradients[i] # 全局平均池化得到热力图 heatmap = torch.mean(self.features, dim=1).squeeze() heatmap = np.maximum(heatmap.cpu().detach().numpy(), 0) heatmap /= np.max(heatmap) # 归一化到[0,1] return heatmap, pred_class # 使用最后一个卷积层(conv3) gradcam = GradCAM(model, model.conv3) # 对一个测试样本生成热力图 img, label = test_dataset[0] heatmap, pred = gradcam(img) # 反归一化原图 img_np = img * 0.3081 + 0.1307 img_np = np.clip(img_np, 0, 1).squeeze() # 调整热力图大小至28x28 heatmap_resized = cv2.resize(heatmap, (28, 28)) # 叠加热力图 plt.figure(figsize=(6, 3)) plt.subplot(1, 2, 1) plt.imshow(img_np, cmap='gray') plt.title(f'True: {class_names[label]}') plt.axis('off') plt.subplot(1, 2, 2) plt.imshow(img_np, cmap='gray') plt.imshow(heatmap_resized, cmap='jet', alpha=0.5) plt.title(f'Pred: {class_names[pred]}') plt.axis('off') plt.tight_layout() plt.savefig('gradcam_example.png', dpi=300) plt.show()

避坑提示:cv2.resize必须在heatmap归一化后进行,否则插值会破坏热力图语义;alpha=0.5控制透明度,确保底层图像纹理可见;np.maximum(..., 0)截断负值,因为 Grad-CAM 只关心正向激活区域。这张图能有力证明:模型不是靠「猜」,而是聚焦在服装的关键结构(如 Dress 的裙摆轮廓)。

5. 避坑指南:课程设计中最常踩的 5 个坑及血泪解决方案

5.1 现象:训练 loss 不下降,始终在 2.3 左右震荡

原因:CrossEntropyLoss的输入是 raw logits(未经过 softmax),而你误传了F.softmax(outputs)。softmax 会压缩 logits 范围,导致梯度消失。
解决:确认criterion(outputs, labels)中outputs是模型最后一层的直接输出(无 softmax)。PyTorch 的 CrossEntropyLoss 内部已包含 softmax,显式调用是重复操作。

5.2 现象:验证准确率远高于训练准确率(如 train_acc=75%, val_acc=92%)

原因:model.eval()未在验证阶段调用,导致 BatchNorm 和 Dropout 层仍处于训练模式,统计量未冻结。
解决:验证前必须加model.eval(),且验证结束后若需继续训练,要补model.train()。可在validate()函数开头强制model.eval(),结尾不恢复,因训练循环中train_epoch()会主动调用model.train()。

5.3 现象:RuntimeError: size mismatch, m1: [128 x 6272], m2: [6272 x 512]

原因:卷积层输出尺寸计算错误,view()时展平维度与全连接层输入不匹配。例如conv3输出[128, 14, 14],但pool3后应为[128, 7, 7],若pool3参数错写为kernel_size=3,则输出[128, 6, 6],128*6*6=4608 ≠ 6272。
解决:在forward中每层后加print(x.shape),或用torchsummary.summary(model, (1,28,28))查看各层输出尺寸。课程设计中建议手算一遍:((28+2*2-5)/1 +1)=28 → ((28-5)/2 +1)=14 → ((14+2*1-3)/1 +1)=14 → 14/2=7。

5.4 现象:ValueError: Input 0 of layer "conv2d" is incompatible with layer(Keras)

原因:x_train未增加通道维度,形状为[60000, 28, 28],但 Conv2D 要求[N, H, W, C]。
解决:务必执行x_train = np.expand_dims(x_train, axis=-1)。可在load_data()后立即检查print(x_train.shape),确认为(60000, 28, 28, 1)。

5.5 现象:Grad-CAM 热力图全黑或全白

原因:self.gradients为 None,因未正确注册 backward hook,或output[0, pred_class].backward()前未调用model.zero_grad()清空历史梯度。
解决:检查钩子注册语法是否为target_layer.register_backward_hook(self._save_gradients);确认backward()前有self.model.zero_grad();打印self.gradients形状,应为[1, 128, 7, 7],若为None则钩子未触发。

6. 课程设计加分技巧:用 ONNX 导出模型并用 OpenCV DNN 模块部署推理

6.1 将 PyTorch 模型导出为 ONNX 格式:打通框架壁垒

课程设计报告若加入「跨平台部署」章节,能显著提升技术深度。ONNX 是通用中间表示,可被 OpenCV、TensorRT 等工具加载:

# 导出为 ONNX(需安装 onnx) dummy_input = torch.randn(1, 1, 28, 28).to(device) torch.onnx.export( model, dummy_input, "fashion_cnn.onnx", export_params=True, # 存储模型权重 opset_version=11, # ONNX opset 版本,11 兼容性好 do_constant_folding=True, # 优化常量 input_names=['input'], # 输入名 output_names=['output'], # 输出名 dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}} # 支持动态 batch ) print("Model exported to fashion_cnn.onnx")

参数说明:opset_version=11是 PyTorch 1.8+ 的推荐版本,避免高版本 opset 在旧环境无法加载;dynamic_axes允许推理时输入任意 batch size,对课程设计中的单图 demo 更友好;导出后可用onnx.checker.check_model(onnx.load("fashion_cnn.onnx"))验证文件有效性。

6.2 用 OpenCV DNN 模块加载 ONNX 并推理:零依赖部署

OpenCV 的cv2.dnn.readNetFromONNX可直接加载 ONNX 模型,无需 PyTorch/TensorFlow 环境,适合课程设计答辩现场演示:

import cv2 import numpy as np # 加载 ONNX 模型 net = cv2.dnn.readNetFromONNX("fashion_cnn.onnx") # 预处理单张图像(模拟 test_dataset[0]) img, label = test_dataset[0] img_np = img.numpy().squeeze() # [28,28] img_np = (img_np * 0.3081 + 0.1307) * 255 # 反归一化到[0,255] img_np = np.clip(img_np, 0, 255).astype(np.uint8) # OpenCV DNN 要求 BGR 格式,但灰度图可直接用 blob = cv2.dnn.blobFromImage( img_np, scalefactor=1.0/255.0, # 再次归一化(因OpenCV不自动做) size=(28, 28), mean=0, swapRB=False # 灰度图无需交换通道 ) # 推理 net.setInput(blob) preds = net.forward() predicted_class = np.argmax(preds) class_names = ['T-shirt', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'] print(f"OpenCV DNN Prediction: {class_names[predicted_class]} (True: {class_names[label]})")

关键点:blobFromImage的scalefactor=1.0/255.0是必须的,因为 ONNX 模型权重是在[0,1]归一化下训练的;size=(28,28)必须与训练尺寸一致;swapRB=False因灰度图无 RB 通道。这段代码可独立运行,证明你的模型已脱离训练框架,具备工程化潜力。

6.3 课程设计报告撰写技巧:用「问题-方法-证据」结构组织技术章节

我带过几届课程设计,发现高分报告的共性不是代码多,而是每个技术点都遵循「问题驱动」逻辑。例如在「模型选择」章节,不要写「我用了 CNN 因为它很火」,而要写:

问题:Fashion-MNIST 图像尺寸小(28×28),全连接网络参数量大(28×28×10=7840),易过拟合且无法提取局部特征。
方法:采用 3 层卷积结构,每层 kernel=5,通过 padding 保持尺寸,stride=2 实现下采样,最终全连接层输入降至 128×7×7=6272,参数量减少 19%。
证据:表1对比了 FCN 与 CNN 的 epoch-10 准确率(FCN: 82.3%, CNN: 89.7%),且 CNN 的 loss 曲线更平滑(见图3)。

这种写法让老师一眼看出你思考过,而不是复制粘贴。我在某高校课程设计评审中见过太多「代码堆砌型」报告,最后一页才出现一张没标注的 loss 图——而真正拿优的报告,每张图都有编号、标题、坐标轴标签,且文字分析直指模型行为本质。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询