还在为看不懂卷积神经网络头疼?别慌!这篇超详细入门文,从基础原理到核心操作,手把手带你搞懂CNN,零基础也能轻松get~先补个小知识:没了解过神经网络的同学,可先看我们这一篇文章→神经网络小白入门:从0看懂基础神经网络算法,公式+步骤超详细!

另外,我整理了卷积神经网络(CNN)算法经典论文+示例代码,感兴趣的dd~

原文、姿.料 这儿~

一、先搞懂:图像在电脑里长啥样?

想学好CNN,得先知道图像的“数字形态”:

  • 灰度图:是个二维矩阵,每个元素值在0-255之间,0代表最暗,255代表最亮,比如一张黑白照片,就是靠这样的矩阵存储。
    灰度图

  • RGB图:我们平时看的彩色图,是由红(R)、绿(G)、蓝(B)三个通道组成,相当于3个有序排列的二维矩阵,也能理解成三维张量,用(宽,高,深)来描述,这里的“深”就是通道数。
    RGB张量

二、为啥非得学CNN?传统神经网络不够用?

传统神经网络有个大bug——没法实现“平移不变性”:
比如同一种物体,放在图像左侧和右侧,传统神经网络可能会认成两种不同东西(因为输入神经元对应位置固定)。
而我们需要的是:不管物体在图像哪个位置,都能准确识别!这时候CNN就派上用场了,它靠卷积操作捕捉图像局部特征,不受物体位置影响,完美解决这个问题~
CNN可以准确识别特征

三、核心来了!什么是“卷积”?

1. 通俗理解

卷积就是用一个“可移动的小窗口”(叫数据窗口),和图像逐元素相乘再相加的操作。这个小窗口其实是卷积核(滤波器),里面是一组固定权重,就像“特征探测器”,帮网络提取图像特征(比如边缘、纹理)。
多通道卷积计算

2. 关键参数要记牢

  • 步长(stride):数据窗口每次滑动的距离,比如stride=2,就是每次移2个像素。
  • 卷积核个数:决定输出特征图的“深度(depth)”,有几个卷积核,输出深度就为几。
  • 零填充(zero-padding):在图像外围补几圈0,目的是让卷积核能覆盖图像边缘,还能调整输出特征图尺寸。

3. 必看公式:输出特征图尺寸怎么算?

当输入图像尺寸为 W i n × H i n × D i n W_{in} \times H_{in} \times D_{in} Win×Hin×Din(宽×高×深度),卷积核尺寸为 K × K K \times K K×K,步长为 s s s,填充数为 p p p时,输出特征图的宽和高计算公式为:
W o u t = W i n − K + 2 p s + 1 W_{out} = \frac{W_{in} - K + 2p}{s} + 1 Wout=sWinK+2p+1
H o u t = H i n − K + 2 p s + 1 H_{out} = \frac{H_{in} - K + 2p}{s} + 1 Hout=sHinK+2p+1
输出深度 D o u t D_{out} Dout = 卷积核个数

4. 举个例子更清楚

比如输入图像是4×4(无深度,简化为灰度图),卷积核3×3,stride=1:

  • 不填充(p=0):输出宽=(4-3+0)/1 +1=2,高=2,即输出2×2特征图,尺寸变小还丢边缘信息。
  • 填充p=1(外围补1圈0):输入变成6×6,输出宽=(6-3+2×1)/1 +1=6,高=6,尺寸和原图像一致,还能捕捉边缘特征~

四、CNN的“身体构造”是怎样的?

CNN原理图
CNN主要由5部分组成,层层递进提取特征:

  1. 输入层:接收原始图像数据,比如RGB图就是(宽,高,3)的张量。
  2. 卷积+激活层:先用卷积核做卷积操作,再通过激活函数(比如ReLU)引入“非线性”,让网络能学复杂特征(没有激活函数,再多卷积层也只能学线性特征)。
  3. 池化层:缩小特征图尺寸,减少计算量。常见两种:
    • 最大池化:取池化窗口内最大值(比如2×2窗口,取4个像素里最大的)。
    • 平均池化:取池化窗口内平均值。
  4. 多层堆叠:把“卷积层+激活层+池化层”反复堆叠,浅层学边缘、纹理,深层学更复杂的特征(比如物体的眼睛、鼻子)。
  5. 全连接+输出层:全连接层把前面提取的特征“汇总”,最后输出结果,比如分类任务输出每个类别的概率,回归任务输出具体数值。

五、最后看个直观效果:卷积后图像变啥样?

就像人眼先看轮廓再看细节,CNN通过卷积,能一步步“看到”图像的特征——比如第一层卷积可能提取出图像的边缘,后面的卷积层能提取出物体的局部结构,最后组合成完整的物体特征~
卷积前后图像对比

六、实战!用PyTorch跑通CNN:手写数字分类示例

原理讲完,直接上能跑的代码!下面用PyTorch实现一个简单CNN,完成MNIST手写数字分类(数据集自动下载,不用手动找图),每行都有注释,小白跟着复制粘贴就能运行~

第一步:准备环境

先安装必备库,打开命令行输入以下代码(安装过的可以跳过):

pip install torch torchvision matplotlib numpy
  • torch:PyTorch核心,用来搭神经网络
  • torchvision:提供现成数据集和图像处理工具
  • matplotlib:画图看数据和预测结果

第二步:完整代码+逐行注释

# 1. 导入需要的库
import torch  # PyTorch核心库
import torch.nn as nn  # 神经网络模块
import torch.optim as optim  # 优化器
from torchvision import datasets, transforms  # 数据集+图像预处理
from torch.utils.data import DataLoader  # 批量加载数据
import matplotlib.pyplot as plt  # 画图工具
import numpy as np  # 辅助数值计算

# ---------- 关键:强制指定matplotlib使用英文字体,解决显示乱码 ----------
plt.rcParams["font.family"] = ["Arial", "DejaVu Sans", "Helvetica"]
plt.rcParams["axes.unicode_minus"] = False  # 解决负号显示异常
# ---------------------------------------------------------------------

# 2. 图像预处理:把图片转成模型能读的格式
transform = transforms.Compose([
    transforms.ToTensor(),  # 把PIL图片转成Tensor(维度:[通道数, 高, 宽])
    transforms.Normalize((0.1307,), (0.3081,))  # 标准化
])

# 3. 加载MNIST手写数字数据集(自动下载)
train_dataset = datasets.MNIST(
    root='./data',
    train=True,
    download=True,
    transform=transform
)
test_dataset = datasets.MNIST(
    root='./data',
    train=False,
    download=True,
    transform=transform
)

# 4. 批量加载数据
train_loader = DataLoader(
    train_dataset,
    batch_size=64,
    shuffle=True
)
test_loader = DataLoader(
    test_dataset,
    batch_size=1000,
    shuffle=False
)

# 5. 搭建CNN模型
class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # 第一层:卷积+激活+池化
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1)
        self.relu1 = nn.ReLU()
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # 第二层:卷积+激活+池化
        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)
        self.relu2 = nn.ReLU()
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        
        # 全连接层
        self.fc1 = nn.Linear(in_features=32 * 7 * 7, out_features=128)
        self.relu3 = nn.ReLU()
        self.fc2 = nn.Linear(in_features=128, out_features=10)
    
    # 前向传播
    def forward(self, x):
        x = self.pool1(self.relu1(self.conv1(x)))
        x = self.pool2(self.relu2(self.conv2(x)))
        x = x.view(-1, 32 * 7 * 7)
        x = self.relu3(self.fc1(x))
        x = self.fc2(x)
        return x
    
    # 用于获取中间层特征图
    def get_features(self, x):
        # 获取第一层卷积后的特征图
        conv1_out = self.relu1(self.conv1(x))
        pool1_out = self.pool1(conv1_out)
        
        # 获取第二层卷积后的特征图
        conv2_out = self.relu2(self.conv2(pool1_out))
        pool2_out = self.pool2(conv2_out)
        
        return conv1_out, conv2_out

# 6. 初始化模型、损失函数、优化器
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = SimpleCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 7. 训练模型
def train_model(model, train_loader, criterion, optimizer, epochs=5):
    model.train()
    for epoch in range(epochs):
        total_loss = 0.0
        for i, (images, labels) in enumerate(train_loader):
            images = images.to(device)
            labels = labels.to(device)
            
            outputs = model(images)
            loss = criterion(outputs, labels)
            
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            
            total_loss += loss.item() * images.size(0)
        
        avg_loss = total_loss / len(train_loader.dataset)
        print(f'Training Epoch [{epoch+1}/{epochs}], Average Loss: {avg_loss:.4f}')

# 8. 测试模型
def test_model(model, test_loader):
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for images, labels in test_loader:
            images = images.to(device)
            labels = labels.to(device)
            
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
    accuracy = 100 * correct / total
    print(f'\nTest Set Accuracy: {accuracy:.2f}%')
    return accuracy

# 9. 可视化预测结果
def show_predictions(model, test_loader, num_samples=10):
    model.eval()
    with torch.no_grad():
        images, labels = next(iter(test_loader))
        images = images.to(device)
        labels = labels.to(device)
        outputs = model(images)
        _, predicted = torch.max(outputs, 1)
        
        plt.figure(figsize=(15, 6))
        for i in range(num_samples):
            plt.subplot(2, 5, i + 1)
            # 反标准化以正确显示图像
            img = images[i].cpu().numpy().squeeze(0)
            img = img * 0.3081 + 0.1307  # Apply denormalization
            plt.imshow(img, cmap='gray')
            
            # 根据预测是否正确设置标题颜色
            color = 'green' if labels[i].item() == predicted[i].item() else 'red'
            plt.title(f'True: {labels[i].item()}\nPred: {predicted[i].item()}', color=color)
            plt.axis('off')
        plt.tight_layout()
        plt.savefig('prediction_results.png')  # Save image
        print("Prediction results saved as 'prediction_results.png'")
        plt.show()

# 10. 可视化卷积核
def visualize_kernels(model):
    # 获取第一层卷积核
    kernels = model.conv1.weight.data.cpu().numpy()
    
    # 标准化卷积核以更好地显示
    kernels = (kernels - kernels.min()) / (kernels.max() - kernels.min())
    
    plt.figure(figsize=(8, 8))
    for i in range(kernels.shape[0]):  # 16 kernels
        plt.subplot(4, 4, i + 1)
        plt.imshow(kernels[i, 0], cmap='gray')  # Display each kernel
        plt.title(f'Kernel {i+1}')
        plt.axis('off')
    plt.tight_layout()
    plt.savefig('conv1_kernels.png')  # Save image
    print("Convolution kernels visualization saved as 'conv1_kernels.png'")
    plt.show()

# 11. 可视化特征图
def visualize_feature_maps(model, test_loader, img_index=0):
    model.eval()
    with torch.no_grad():
        # 获取一张测试图像
        images, _ = next(iter(test_loader))
        img = images[img_index:img_index + 1].to(device)  # Take a single image
        
        # 获取特征图
        conv1_features, conv2_features = model.get_features(img)
        
        # 显示原始图像
        plt.figure(figsize=(16, 12))
        plt.subplot(3, 6, 1)
        original_img = img.cpu().numpy().squeeze()
        original_img = original_img * 0.3081 + 0.1307  # Denormalize
        plt.imshow(original_img, cmap='gray')
        plt.title('Original Image')
        plt.axis('off')
        
        # 显示第一层卷积后的特征图
        conv1_features = conv1_features.cpu().numpy().squeeze()
        for i in range(min(15, conv1_features.shape[0])):  # Show first 15 feature maps
            plt.subplot(3, 6, i + 2)
            feature_map = conv1_features[i]
            feature_map = (feature_map - feature_map.min()) / (feature_map.max() - feature_map.min())
            plt.imshow(feature_map, cmap='gray')
            plt.title(f'1st Layer Feature Map {i+1}')
            plt.axis('off')
        
        plt.tight_layout()
        plt.savefig('conv1_feature_maps.png')
        print("First layer convolution feature maps saved as 'conv1_feature_maps.png'")
        plt.show()
        
        # 显示第二层卷积后的特征图
        plt.figure(figsize=(16, 12))
        plt.subplot(3, 6, 1)
        plt.imshow(original_img, cmap='gray')
        plt.title('Original Image')
        plt.axis('off')
        
        conv2_features = conv2_features.cpu().numpy().squeeze()
        for i in range(min(15, conv2_features.shape[0])):  # Show first 15 feature maps
            plt.subplot(3, 6, i + 2)
            feature_map = conv2_features[i]
            feature_map = (feature_map - feature_map.min()) / (feature_map.max() - feature_map.min())
            plt.imshow(feature_map, cmap='gray')
            plt.title(f'2nd Layer Feature Map {i+1}')
            plt.axis('off')
        
        plt.tight_layout()
        plt.savefig('conv2_feature_maps.png')
        print("Second layer convolution feature maps saved as 'conv2_feature_maps.png'")
        plt.show()

# 12. 主函数:运行整个流程
if __name__ == '__main__':
    print(f'Using device: {device}')
    train_model(model, train_loader, criterion, optimizer, epochs=5)
    test_model(model, test_loader)
    
    # 生成并保存各种可视化图像
    show_predictions(model, test_loader)
    visualize_kernels(model)
    visualize_feature_maps(model, test_loader, img_index=0)  # Visualize feature maps of the 0th test image

运行结果
准确率
conv1_feature_maps
conv1_kernels
conv2_feature_maps

第三步:运行说明&常见问题

  1. 怎么运行
    把代码复制到PyCharm、VS Code或Jupyter Notebook,点击运行即可。第一次运行会自动下载MNIST数据集,之后会直接用本地数据。

  2. 预期结果

    • 训练时:每轮的“平均损失”会逐渐降低(从0.3左右降到0.05以下)。
    • 测试后:准确率能达到98%以上(比如98.5%),证明模型能准确识别手写数字。
    • 最后会弹出10张手写数字图,每张图标注“真实数字”和“预测数字”,大部分都会预测正确。
  3. 小白常见问题

    • Q:CPU运行太慢?
      A:如果电脑有NVIDIA显卡,先装CUDA(参考PyTorch官网的安装指南),代码会自动用GPU加速。
    • Q:准确率不够高?
      A:把epochs=5改成10(多训练几轮),或把lr=0.001改成0.0005(调小学习率)。
    • Q:某行代码看不懂?
      A:重点看注释!比如conv1的参数注释,和前面讲的“卷积核个数、步长、填充”完全对应,结合原理再看代码,很快就能理解。

跟着跑一遍代码,再回头看前面的CNN原理,是不是感觉“纸上的知识落地了”?动手实践才是理解深度学习的最好方式~

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐