Machine Learning: 卷积神经网络CNN — 图像分类与商品识别指南

CNN给计算机装上了"眼睛"——卷积核就像视觉感受器,逐层从边缘到物体识别图像。

1. 你将学到


2. 一个电商运营的真实故事

(1) 痛点:新品上架人工分类太慢且不一致

Bob平台每天有1 thousand个新品上架,运营团队需要手动将商品图片归入正确的类目。人工分类速度慢(每张5秒)、不一致(同一商品不同人分到不同类)、且成本高(每月30 thousand USD人工费)。图片分类是电商运营的瓶颈。

(2) CNN的解法

CNN能自动从商品图片中学习视觉特征,实现自动分类——速度毫秒级、一致性强、零人工成本。

PYTHON
import torchvision.models as models

# Transfer learning: use pre-trained ResNet
model = models.resnet18(pretrained=True)
model.fc = nn.Linear(model.fc.in_features, num_classes)  # Replace last layer

(3) 收益:分类准确率95%,每月节省30千USD

Bob用CNN+迁移学习实现商品自动分类,准确率95%以上,处理速度从5秒/张降到0.01秒/张,每月节省30 thousand USD人工成本。


3. 卷积操作原理

(1) 卷积核与特征提取

100%
graph TB
    INPUT[Input Image<br/>H x W x C] --> CONV1[Conv Layer 1<br/>Edges & Lines]
    CONV1 --> POOL1[Pool Layer 1<br/>Reduce Spatial]
    POOL1 --> CONV2[Conv Layer 2<br/>Textures & Parts]
    CONV2 --> POOL2[Pool Layer 2]
    POOL2 --> CONV3[Conv Layer 3<br/>Objects & Shapes]
    CONV3 --> FLATTEN[Flatten]
    FLATTEN --> FC[Fully Connected]
    FC --> OUTPUT[Class Probabilities]

▶ 示例:手动实现卷积操作

PYTHON
import torch
import torch.nn as nn

# Input: 1 image, 1 channel, 5x5
input_img = torch.tensor([[
    [1, 2, 0, 1, 3],
    [0, 1, 2, 3, 1],
    [1, 3, 1, 0, 2],
    [2, 0, 3, 1, 1],
    [0, 1, 2, 0, 1],
]], dtype=torch.float32).unsqueeze(0)  # shape: (1, 1, 5, 5)

# Conv2d: 1 input channel, 1 output channel, 3x3 kernel
conv = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3, stride=1, padding=0)
print(f"Kernel weights:\n{conv.weight.data.squeeze()}")
print(f"Bias: {conv.bias.data.item():.4f}")

output = conv(input_img)
print(f"\nInput shape: {input_img.shape}")
print(f"Output shape: {output.shape}")
print(f"Output:\n{output.squeeze().detach()}")

输出:

TEXT 📖 仅展示
# 执行成功

(2) 卷积参数

参数 含义 效果
kernel_size 卷积核大小 3x3(常用)/5x5/7x7
stride 步长 1(默认)/2(下采样)
padding 填充 0(缩小)/1(保持尺寸)
in_channels 输入通道数 RGB=3, 灰度=1
out_channels 输出通道数 即卷积核数量

▶ 示例:不同卷积配置对比

PYTHON
import torch.nn as nn

x = torch.randn(1, 3, 32, 32)  # 1 batch, 3 channels, 32x32

configs = {
    "3x3, stride=1, pad=1": nn.Conv2d(3, 16, 3, stride=1, padding=1),
    "3x3, stride=2, pad=1": nn.Conv2d(3, 16, 3, stride=2, padding=1),
    "5x5, stride=1, pad=2": nn.Conv2d(3, 16, 5, stride=1, padding=2),
    "5x5, stride=2, pad=0": nn.Conv2d(3, 16, 5, stride=2, padding=0),
}

for name, layer in configs.items():
    out = layer(x)
    print(f"{name:25s}: {x.shape} → {out.shape}")

输出:

TEXT 📖 仅展示
# 执行成功

4. 池化层与CNN架构

(1) 池化操作

▶ 示例:MaxPool与AvgPool

PYTHON
import torch
import torch.nn as nn

x = torch.tensor([[
    [1, 3, 2, 4],
    [5, 6, 7, 8],
    [9, 2, 1, 3],
    [4, 5, 6, 7],
]], dtype=torch.float32).unsqueeze(0)  # (1, 1, 4, 4)

maxpool = nn.MaxPool2d(kernel_size=2, stride=2)
avgpool = nn.AvgPool2d(kernel_size=2, stride=2)

print(f"Input:\n{x.squeeze()}")
print(f"\nMaxPool (2x2):\n{maxpool(x).squeeze()}")
print(f"\nAvgPool (2x2):\n{avgpool(x).squeeze()}")

输出:

TEXT 📖 仅展示
# 执行成功

(2) 经典架构演进

架构 年份 创新 层数 参数量
LeNet 1998 首个CNN 5 60K
AlexNet 2012 ReLU+Dropout+GPU 8 60M
VGG 2014 小卷积核(3x3)堆叠 16-19 138M
ResNet 2015 残差连接(skip connection) 18-152 11-60M

▶ 示例:用PyTorch实现简单CNN

PYTHON
import torch
import torch.nn as nn

class SimpleCNN(nn.Module):
    def __init__(self, num_classes=5):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1),   # 3→32 channels, 32x32
            nn.ReLU(),
            nn.MaxPool2d(2, 2),               # 32→16x16
            nn.Conv2d(32, 64, 3, padding=1),  # 32→64 channels, 16x16
            nn.ReLU(),
            nn.MaxPool2d(2, 2),               # 16→8x8
            nn.Conv2d(64, 128, 3, padding=1), # 64→128 channels, 8x8
            nn.ReLU(),
            nn.MaxPool2d(2, 2),               # 8→4x4
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128 * 4 * 4, 256),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(256, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.classifier(x)
        return x

model = SimpleCNN(num_classes=5)
print(model)
print(f"Parameters: {sum(p.numel() for p in model.parameters()):,}")

输出:

TEXT 📖 仅展示
# 函数定义成功

5. 迁移学习

(1) 为什么迁移学习有效

ImageNet预训练模型已学到通用视觉特征(边缘→纹理→物体部件),只需替换最后一层即可适配新任务。

▶ 示例:ResNet18迁移学习

PYTHON
import torch
import torch.nn as nn
import torchvision.models as models
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import numpy as np

# Load pre-trained ResNet18
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)

# Freeze all layers (feature extractor mode)
for param in model.parameters():
    param.requires_grad = False

# Replace the last fully connected layer
num_classes = 5  # Electronics, Clothing, Food, Books, Home
model.fc = nn.Linear(model.fc.in_features, num_classes)

# Only the new fc layer has trainable parameters
trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
total = sum(p.numel() for p in model.parameters())
print(f"Trainable: {trainable:,} / Total: {total:,} ({trainable/total*100:.1f}%)")

# Data transforms for pre-trained model
transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])

输出:

TEXT 📖 仅展示
# 执行成功

(2) Fine-tuning策略

策略 冻结层数 训练数据量 训练时间 适用场景
Feature Extractor 全部(只训练fc) 少(<1k) 数据很少
Partial Fine-tune 后几层 中(1-10k) 数据适中
Full Fine-tune 多(>10k) 数据充足

▶ 示例:Fine-tune后几层

PYTHON
import torchvision.models as models
import torch.nn as nn

model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)

# Freeze early layers, unfreeze layer4
for name, param in model.named_parameters():
    if "layer4" in name or "fc" in name:
        param.requires_grad = True
    else:
        param.requires_grad = False

model.fc = nn.Linear(model.fc.in_features, 5)

trainable = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"Fine-tune trainable params: {trainable:,}")

输出:

TEXT 📖 仅展示
# 执行成功

6. SalesPredict商品图片分类

▶ 示例:完整迁移学习训练流程

PYTHON
import torch
import torch.nn as nn
import torchvision.models as models
from torch.utils.data import DataLoader, TensorDataset
from sklearn.model_selection import train_test_split
import numpy as np

# Simulate image data (in practice, load real images with ImageFolder)
rng = np.random.default_rng(42)
n = 2000
X = rng.standard_normal((n, 3, 224, 224)).astype(np.float32)  # 3-channel 224x224
y = rng.integers(0, 5, n)  # 5 product categories

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

train_ds = TensorDataset(torch.FloatTensor(X_train), torch.LongTensor(y_train))
test_ds = TensorDataset(torch.FloatTensor(X_test), torch.LongTensor(y_test))
train_loader = DataLoader(train_ds, batch_size=32, shuffle=True)
test_loader = DataLoader(test_ds, batch_size=64)

# Pre-trained ResNet18 with custom head
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
for param in model.parameters():
    param.requires_grad = False
model.fc = nn.Linear(model.fc.in_features, 5)

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.fc.parameters(), lr=0.001)

# Training
for epoch in range(10):
    model.train()
    correct, total = 0, 0
    for X_batch, y_batch in train_loader:
        y_pred = model(X_batch)
        loss = criterion(y_pred, y_batch)
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        correct += (y_pred.argmax(1) == y_batch).sum().item()
        total += y_batch.size(0)

    if epoch % 2 == 0:
        print(f"Epoch {epoch}: loss={loss.item():.4f}, acc={correct/total:.3f}")

# Evaluate
model.eval()
correct, total = 0, 0
with torch.no_grad():
    for X_batch, y_batch in test_loader:
        y_pred = model(X_batch)
        correct += (y_pred.argmax(1) == y_batch).sum().item()
        total += y_batch.size(0)

print(f"\nTest Accuracy: {correct/total:.3f}")

输出:

TEXT 📖 仅展示
# 执行成功

❓ 常见问题

Q CNN为什么比MLP更适合图像?
A 三个原因——1) 参数共享(同一卷积核扫全图,参数少);2) 局部连接(每个神经元只看局部区域);3) 平移不变性(物体出现在任何位置都能识别)。MLP把图像展平会丢失空间结构。
Q 迁移学习需要多少数据?
A Feature Extractor模式(只训练fc)只需几百张;Fine-tune后几层需1-10k张;从头训练需10k+张。数据越少,冻结越多层。
Q 3x3卷积核为什么最常用?
A 两个3x3卷积堆叠的感受野等于一个5x5,但参数更少(18 vs 25)且非线性更强(2次ReLU)。VGG证明小核堆叠比大核更好。
Q ResNet的残差连接是什么?
A y = F(x) + x,让网络学习残差(增量)而非完整映射。好处——解决深层网络梯度消失问题,允许训练100+层网络。
Q 图像预处理为什么要Normalize?
A 预训练模型用ImageNet的均值/标准差归一化。新数据必须用相同归一化,否则特征分布不匹配,迁移学习效果差。
Q 怎么处理类别不平衡?
A 三种方法——1) 加权损失函数(nn.CrossEntropyLoss(weight=class_weights));2) 过采样少数类;3) 数据增强(随机翻转/旋转/裁剪)。

📖 小节


📝 作业

  1. 基础题(难度⭐):用nn.Conv2d+nn.MaxPool2d构建一个2层CNN,输入3x32x32,输出16x8x8,打印每层形状。提示:Conv2d(3,8,3,pad=1)→Pool→Conv2d(8,16,3,pad=1)→Pool。
  2. 进阶题(难度⭐⭐):用迁移学习(ResNet18)对CIFAR-10分类(10类),冻结除fc外所有层,训练5个epoch。提示:torchvision.datasets.CIFAR10 + transforms。
  3. 挑战题(难度⭐⭐⭐):对比Feature Extractor vs Partial Fine-tune vs Full Fine-tune在CIFAR-10上的精度和训练时间,画出三种策略的accuracy vs epoch曲线。提示:分别冻结不同层数,记录每个epoch的test accuracy。

← 上一课:深度学习入门 | 下一课:模型评估与调优 →

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏