1. 为什么从零实现SimpleCNN是理解深度学习的必经之路
很多刚入门深度学习的同学,一上来就喜欢用PyTorch或者TensorFlow的现成模块,几行代码就能搭出一个看起来挺厉害的模型。我刚开始也是这么干的,但很快就发现一个问题:模型跑是跑起来了,但里面到底发生了什么,为什么卷积层能提取特征,为什么池化层能降维,心里完全没底。这就好比你会开车,但不知道发动机怎么工作,一旦车子抛锚,你就只能干瞪眼。
所以,我强烈建议每个想真正搞懂CNN的人,都亲手从零实现一遍。这里的“从零”不是让你去写CUDA代码,而是不依赖高级框架的封装,自己用基础的矩阵运算或者像PaddlePaddle、PyTorch这样的框架,但亲手定义每一个卷积核、计算每一次特征图的变化。这次我们拿Fashion-MNIST这个“时尚界的Hello World”数据集开刀,它比手写数字MNIST难一点,但又不像ImageNet那样需要巨大的计算资源,是绝佳的练手材料。
Fashion-MNIST里面有6万张28x28的灰度训练图,1万张测试图,共10类衣服鞋包。我们的目标就是建一个叫SimpleCNN的小型网络,把它分清楚。别小看这个任务,它能帮你把卷积、激活、池化、全连接这些概念,从抽象的公式变成实实在在的、能跑出结果的代码。我敢说,完整走完这一趟,你对CNN的理解会比只看十篇论文深刻得多。
2. 环境搭建与数据准备:万事开头要利索
工欲善其事,必先利其器。咱们先把环境搭好,把数据准备好。这里我用PaddlePaddle来演示,你用PyTorch或TensorFlow思路也完全一样。
2.1 安装依赖与数据加载
首先,确保你的Python环境(建议3.7以上)里装好了必要的库。除了深度学习框架,数据处理和可视化库也少不了。
# 安装PaddlePaddle(以CPU版本为例)
pip install paddlepaddle
# 安装其他辅助库
pip install numpy matplotlib
数据加载是第一步,也是容易踩坑的地方。Fashion-MNIST数据不大,很多框架都内置了,用起来很方便。但我们要的不只是下载,还要做好预处理,把它变成模型爱吃的样子。
import paddle
from paddle.vision import datasets, transforms
import paddle.io as io
import sys
import matplotlib.pyplot as plt
def get_dataloader_workers():
"""根据操作系统设置数据加载的进程数,Windows/Mac用单进程避免问题"""
if sys.platform.startswith(('win', 'darwin')):
return 0 # Windows和MacOS下,多进程容易出错,稳妥起见用0
else:
return 4 # Linux系统可以用多进程加速读取
def load_data_fashion_mnist(batch_size=256, resize=None):
"""
加载Fashion-MNIST数据集
参数:
batch_size: 每次训练喂给模型的图片数量
resize: 是否调整图片大小,默认28x28
返回:
train_iter, test_iter: 训练和测试的数据迭代器
"""
# 1. 定义数据变换管道
trans = [transforms.ToTensor()] # 核心:把PIL图片转成Tensor,并自动归一化像素值到[0,1]
if resize:
# 如果指定了resize,就在最前面插入一个调整大小的操作
trans.insert(0, transforms.Resize(resize))
# 用Compose把多个操作串起来
trans = transforms.Compose(trans)
# 2. 下载并加载数据集
# mode='train' 和 'test' 分别对应训练集和测试集
mnist_train = datasets.FashionMNIST(mode='train', transform=trans, download=True)
mnist_test = datasets.FashionMNIST(mode='test', transform=trans, download=True)
# 3. 构建DataLoader,这才是真正喂数据给模型的“勺子”
train_iter = io.DataLoader(mnist_train,
batch_size=batch_size,
shuffle=True, # 训练集一定要打乱顺序,防止模型学到数据顺序
return_list=True,
num_workers=get_dataloader_workers()) # 用几个进程读数据
test_iter = io.DataLoader(mnist_test,
batch_size=batch_size,
shuffle=False, # 测试集不用打乱
return_list=True,
num_workers=get_dataloader_workers())
return train_iter, test_iter, mnist_train, mnist_test
# 测试一下数据加载是否正常
if __name__ == "__main__":
train_iter, test_iter, train_set, test_set = load_data_fashion_mnist(batch_size=16)
print(f"训练集样本数: {len(train_set)}") # 应该输出 60000
print(f"测试集样本数: {len(test_set)}") # 应该输出 10000
# 取一个批次看看形状
for X, y in train_iter:
print(f"一个批次图片的形状: {X.shape}") # 应该是 [16, 1, 28, 28]
print(f"一个批次标签的形状: {y.shape}") # 应该是 [16]
break
这里有几个细节值得唠叨一下。ToTensor() 这个变换干了件大事:它不仅把图片从PIL格式变成张量,还悄悄把像素值从0-255的整数,除以255,转换成了0-1之间的浮点数。这种归一化操作对模型训练至关重要,能让梯度下降更平稳。shuffle=True 在训练时是必须的,想象一下如果数据是按类别排好序的,模型可能会“偷懒”只根据批次顺序来猜,而不是真正学习特征。
2.2 数据可视化:先看看我们“吃”的是什么
数据加载好了,别急着喂给模型。先拿出来看看长什么样,心里有个数。这就像做饭前先看看食材新不新鲜。
def get_fashion_mnist_labels(labels):
"""把数字标签(0-9)转换成我们能看懂的文字"""
text_labels = ['t-shirt(T恤)', 'trouser(裤子)', 'pullover(套衫)',
'dress(连衣裙)', 'coat(外套)', 'sandal(凉鞋)',


602

被折叠的 条评论
为什么被折叠?



