在人工智能图像识别领域,卷积神经网络(Convolutional Neural Network, CNN)扮演着核心角色。无论是移动设备的场景识别、医学影像的病灶检测,还是身份验证中的人脸识别,其底层核心技术均离不开CNN。与传统神经网络相比,CNN能够高效提取图像的空间特征,有效解决了图像像素维度高、模型参数易爆炸的技术痛点。
本文将从基础原理出发,系统拆解CNN的核心机制与关键技术术语,结合完整的PyTorch代码实例(花卉分类任务),清晰阐述CNN的工作流程,以及基于PyTorch框架实现简易CNN模型的具体步骤,为零基础学习者提供可落地的技术参考。
以本文后续实现的CNN花卉分类模型中采用的花卉图像为例,其尺寸为150×150×3(宽度150、高度150、3个RGB颜色通道),总计包含150×150×3=67500个像素点。若采用传统全连接神经网络,其输入层神经元数量需与像素点数量保持一致,即67500个输入神经元。
假设第一层隐藏层设置1000个神经元,该层的参数总量将达到67500×1000=67,500,000个。过量的参数不仅会导致模型过拟合(模型过度拟合训练数据,泛化能力下降),还会大幅提升计算复杂度,导致普通硬件设备难以承载。
CNN的核心优势在于通过局部感受野、权值共享与池化降维三大机制,在保证特征提取效果的前提下,大幅减少模型参数总量,有效解决了传统神经网络在图像识别中的上述痛点。
典型的CNN结构从输入到输出,依次由输入层、卷积层、激活层、池化层、全连接层构成,部分场景会引入批量归一化层(BN层)、Dropout层等正则化模块,以提升模型训练效率并抑制过拟合。结合本文实现的CNN花卉分类模型代码,对各关键层的功能与原理详细解析如下(后续代码解析将重点关联相关内容)。
本文所使用的完整可运行代码可从GitHub地址下载:https://github.com/sanmuny/machine-learning/blob/main/cnn.py,可直接克隆或下载代码文件进行本地实践。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from torchvision.io import read_image, ImageReadMode
import os
import argparse
import json
# 标签编码器:将类别名称转换为数字编码
class SimpleLabelEncoder:
def __init__(self):
self.label_to_idx = {}
self.idx_to_label = {}
def fit(self, labels):
unique_labels = sorted(list(set(labels)))
self.label_to_idx = {label: idx for idx, label in enumerate(unique_labels)}
self.idx_to_label = {idx: label for label, idx in self.label_to_idx.items()}
def transform(self, label):
return self.label_to_idx[label]
def inverse_transform(self, idx):
return self.idx_to_label[idx]
# 自定义数据集类:加载花卉图像与标签
class CustomImageDataset(Dataset):
def __init__(self, img_dir, transform=None, target_transform=None, encoder=None):
self.img_dir = img_dir
self.transform = transform
self.target_transform = target_transform
self.encoder = encoder
self.img_paths = []
self.labels = []
# 遍历数据集目录,获取图像路径和标签
for label_dir in os.listdir(img_dir):
label_path = os.path.join(img_dir, label_dir)
if os.path.isdir(label_path):
for img_name in os.listdir(label_path):
img_path = os.path.join(label_path, img_name)
self.img_paths.append(img_path)
self.labels.append(label_dir)
def __len__(self):
return len(self.img_paths)
def __getitem__(self, idx):
# 读取图像并转换为张量
img_tensor = read_image(self.img_paths[idx], mode=ImageReadMode.RGB)
label = self.labels[idx]
# 标签编码
if self.encoder:
label = self.encoder.transform(label)
# 图像变换
if self.transform:
img_tensor = self.transform(img_tensor)
if self.target_transform:
label = self.target_transform(label)
return img_tensor, label
# 训练一个epoch的函数
def train_epoch(model, dataloader, criterion, optimizer, device):
model.train()
total_loss = 0.0
total_correct = 0
total_samples = 0
for inputs, labels in dataloader:
inputs, labels = inputs.to(device), labels.to(device)
# 前向传播
outputs = model(inputs)
loss = criterion(outputs, labels)
# 反向传播与参数更新
optimizer.zero_grad()
loss.backward()
optimizer.step()
# 计算损失和准确率
total_loss += loss.item() * inputs.size(0)
_, predicted = torch.max(outputs, 1)
total_correct += (predicted == labels).sum().item()
total_samples += inputs.size(0)
avg_loss = total_loss / total_samples
accuracy = total_correct / total_samples
return avg_loss, accuracy
# 评估模型的函数
def evaluate(model, dataloader, criterion, device):
model.eval()
total_loss = 0.0
total_correct = 0
total_samples = 0
with torch.no_grad():
for inputs, labels in dataloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
loss = criterion(outputs, labels)
total_loss += loss.item() * inputs.size(0)
_, predicted = torch.max(outputs, 1)
total_correct += (predicted == labels).sum().item()
total_samples += inputs.size(0)
avg_loss = total_loss / total_samples
accuracy = total_correct / total_samples
return avg_loss, accuracy
# 保存模型为Hugging Face格式(便于部署)
def save_huggingface_format(model, save_dir, encoder):
os.makedirs(save_dir, exist_ok=True)
# 保存模型参数
torch.save(model.state_dict(), os.path.join(save_dir, "pytorch_model.bin"))
# 保存标签编码器
with open(os.path.join(save_dir, "label_encoder.json"), "w") as f:
json.dump({"label_to_idx": encoder.label_to_idx, "idx_to_label": encoder.idx_to_label}, f)
# 图像预测函数
def predict_image(image_path, model, encoder, device, img_size=150):
# 图像预处理:与训练集保持一致
transform = transforms.Compose([
transforms.Resize((img_size, img_size)),
transforms.ConvertImageDtype(torch.float),
transforms.Normalize(mean=[0.0, 0.0, 0.0], std=[1.0, 1.0, 1.0]) # 与训练时的归一化一致
])
# 读取并预处理图像
img_tensor = read_image(image_path, mode=ImageReadMode.RGB)
img_tensor = transform(img_tensor).unsqueeze(0).to(device) # 增加批次维度
# 模型预测
model.eval()
with torch.no_grad():
outputs = model(img_tensor)
probabilities = torch.softmax(outputs, dim=1)
_, predicted_idx = torch.max(outputs, 1)
predicted_label = encoder.inverse_transform(predicted_idx.item())
confidence = probabilities[0][predicted_idx.item()].item()
return predicted_label, confidence
# 优化后的CNN模型类
class OptimizedCNN(nn.Module):
def __init__(self, num_classes=4, img_size=150):
super(OptimizedCNN, self).__init__()
self.img_size = img_size
# 第1个卷积块:conv → bn → relu → pool
self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(32)
self.relu1 = nn.ReLU()
self.pool1 = nn.MaxPool2d(2)
# 第2个卷积块:conv → bn → relu → pool
self.conv2 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.relu2 = nn.ReLU()
self.pool2 = nn.MaxPool2d(2)
# 第3个卷积块:conv → bn → relu → pool
self.conv3 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, padding=1)
self.bn3 = nn.BatchNorm2d(64)
self.relu3 = nn.ReLU()
self.pool3 = nn.MaxPool2d(2)
# 第4个卷积块:conv → bn → relu → pool
self.conv4 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, padding=1)
self.bn4 = nn.BatchNorm2d(128)
self.relu4 = nn.ReLU()
self.pool4 = nn.MaxPool2d(2)
# 计算全连接层输入维度(经过4次池化,尺寸缩小16倍)
feature_h = img_size // 16
feature_w = img_size // 16
self.fc_input_dim = 128 * feature_h * feature_w
# 全连接层与Dropout
self.flatten = nn.Flatten()
self.fc1 = nn.Linear(self.fc_input_dim, 512)
self.relu_fc1 = nn.ReLU()
self.dropout = nn.Dropout(0.5)
self.fc2 = nn.Linear(512, num_classes)
def forward(self, x):
# 4个卷积块前向传播
x = self.pool1(self.relu1(self.bn1(self.conv1(x))))
x = self.pool2(self.relu2(self.bn2(self.conv2(x))))
x = self.pool3(self.relu3(self.bn3(self.conv3(x))))
x = self.pool4(self.relu4(self.bn4(self.conv4(x))))
# 拉平特征图,输入全连接层
x = self.flatten(x)
x = self.dropout(self.relu_fc1(self.fc1(x)))
x = self.fc2(x)
return x
# 主函数:整合训练、评估与预测流程
def main(args):
# 设置设备(GPU优先)
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {DEVICE}")
# 图像尺寸与批次大小
IMG_SIZE = 150
BATCH_SIZE = 32
# 1. 数据加载与预处理
# 标签编码器初始化与拟合
encoder = SimpleLabelEncoder()
# 遍历数据集获取所有标签并拟合编码器
all_labels = []
for label_dir in os.listdir(args.data_dir):
all_labels.append(label_dir)
encoder.fit(all_labels)
num_classes = len(encoder.label_to_idx)
# 数据变换(训练集增强,测试集仅归一化)
train_transforms = transforms.Compose([
transforms.Resize((IMG_SIZE, IMG_SIZE)),
transforms.RandomHorizontalFlip(), # 随机水平翻转
transforms.RandomRotation(15), # 随机旋转
transforms.ConvertImageDtype(torch.float),
transforms.Normalize(mean=[0.0, 0.0, 0.0], std=[1.0, 1.0, 1.0]) # 归一化到0-1
])
test_transforms = transforms.Compose([
transforms.Resize((IMG_SIZE, IMG_SIZE)),
transforms.ConvertImageDtype(torch.float),
transforms.Normalize(mean=[0.0, 0.0, 0.0], std=[1.0, 1.0, 1.0])
])
# 加载数据集并划分训练集、测试集(8:2)
full_dataset = CustomImageDataset(
img_dir=args.data_dir,
encoder=encoder
)
train_size = int(0.8 * len(full_dataset))
test_size = len(full_dataset) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(full_dataset, [train_size, test_size])
# 重新设置数据集的transform(因为split后transform会丢失)
train_dataset.dataset.transform = train_transforms
test_dataset.dataset.transform = test_transforms
# 创建数据加载器
train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_dataloader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)
# 2. 模型初始化
model = OptimizedCNN(num_classes=num_classes, img_size=IMG_SIZE).to(DEVICE)
criterion = nn.CrossEntropyLoss() # 交叉熵损失(多分类)
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam优化器
# 3. 训练模型(若为训练模式)
if args.mode == "train":
epochs = 100
best_test_acc = 0.0
print("Starting training...")
for epoch in range(1, epochs + 1):
train_loss, train_acc = train_epoch(model, train_dataloader, criterion, optimizer, DEVICE)
test_loss, test_acc = evaluate(model, test_dataloader, criterion, DEVICE)
# 打印训练日志
print(f"Epoch [{epoch}/{epochs}], Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f}, "
f"Test Loss: {test_loss:.4f}, Test Acc: {test_acc:.4f}")
# 保存最优模型
if test_acc > best_test_acc:
best_test_acc = test_acc
torch.save(model.state_dict(), args.model_path)
print(f"Best model saved (Test Acc: {best_test_acc:.4f})")
# 保存Hugging Face格式模型
save_huggingface_format(model, args.save_dir, encoder)
print(f"Training completed. Best Test Accuracy: {best_test_acc:.4f}")
# 4. 预测图像(若为推理模式)
elif args.mode == "infer":
# 加载训练好的模型
model.load_state_dict(torch.load(args.model_path, map_location=DEVICE))
# 加载标签编码器
with open(os.path.join(args.save_dir, "label_encoder.json"), "r") as f:
encoder_dict = json.load(f)
encoder.label_to_idx = encoder_dict["label_to_idx"]
encoder.idx_to_label = encoder_dict["idx_to_label"]
# 预测图像
predicted_label, confidence = predict_image(args.image_path, model, encoder, DEVICE, IMG_SIZE)
print(f"Prediction Result: {predicted_label}, Confidence: {confidence:.4f}")
if __name__ == "__main__":
# 解析命令行参数
parser = argparse.ArgumentParser(description="CNN Flower Classification")
parser.add_argument("--mode", type=str, choices=["train", "infer"], required=True, help="Mode: train or infer")
parser.add_argument("--data_dir", type=str, default="../flowers", help="Directory of flower dataset")
parser.add_argument("--model_path", type=str, default="cnn_flower_model.pth", help="Path to save/load model")
parser.add_argument("--save_dir", type=str, default="cnn_flower_model_hf", help="Directory to save Hugging Face format model")
parser.add_argument("--image_path", type=str, help="Path of image to infer (required in infer mode)")
args = parser.parse_args()
main(args)上述代码为本文实现的CNN花卉分类完整代码,涵盖标签编码、自定义数据集、模型定义、训练、评估、推理全流程,后续章节将结合该代码逐一部分解析,确保原理与实践紧密结合。
接收原始图像数据,将图像像素值转换为模型可处理的张量(Tensor)格式,为后续特征提取提供标准化输入。
其核心功能是将图像信息转换为模型可处理的数值形式,实现原始图像数据与模型输入的适配。
代码实现:图像数据的张量转换与归一化操作可通过如下代码实现(核心逻辑与原示例一致):X = torch.stack(X).float(); X /= 255.0。图像原始像素值范围为0-255,归一化后调整至0-1区间,可有效提升模型训练收敛速度,避免因数值尺度差异导致的训练不稳定问题。
通过卷积核(滤波器)对输入图像进行滑动卷积运算,计算局部像素区域的加权和,逐步提取图像的低级特征(边缘、纹理)、中级特征(目标局部轮廓)与高级特征(目标整体形态)。
卷积运算的核心逻辑是通过固定尺寸的卷积核,对图像局部区域进行特征采样与整合,不同卷积核可提取不同类型的图像特征,多层卷积堆叠可实现从低级特征到高级特征的逐步抽象。以3×3卷积核为例,其可有效捕捉图像的边缘特征,经过多层卷积运算后,可逐步整合形成花卉的花瓣轮廓、整体形态等高级特征。
关键参数(结合核心代码):CNN模型中通常会串联多个卷积层提取特征,以4个串联卷积层为例,核心定义如下,其中conv1的参数设置及含义解析如下:
self.conv1 = nn.Conv2d(in_channels=3, out_channels=32, kernel_size=3, padding=1)权值共享:作为CNN减少参数总量的核心机制,同一卷积层中的所有卷积核参数固定,在对整个图像进行滑动卷积时,始终使用同一组参数。以conv1为例,每个3×3卷积核的参数总量为3×3×3=27个,32个卷积核的总参数量为32×3×3×3=864个,远低于传统全连接层的参数规模。
对卷积层的输出特征图进行归一化处理,将特征值调整至均值接近0、方差接近1的标准化区间,从而加速模型训练收敛速度,避免梯度消失现象(训练过程中参数更新停滞)的发生。
卷积层输出的特征值尺度差异较大,易导致模型训练效率低下、收敛困难。BN层通过对批量数据的特征值进行标准化处理,统一特征值尺度,提升模型训练的稳定性与高效性。
代码实现:每个卷积层后均需串联一个BN层,以配合conv1的BN层为例,核心代码如下:self.bn1 = nn.BatchNorm2d(32),BN层的输入通道数与对应卷积层的输出通道数保持一致(此处为32),确保每个通道的特征均能得到标准化处理。
对卷积层与BN层的输出特征进行非线性转换,使模型能够拟合复杂的特征映射关系(如不同花卉类别的形态差异)。若缺少激活层,无论多少层卷积堆叠,其整体运算仍属于线性变换,无法处理复杂的图像分类任务。
卷积层输出的特征映射属于线性变换结果,而图像的特征关系(如花瓣弯曲程度、颜色渐变规律)具有较强的非线性,激活层通过引入非线性变换,使模型能够捕捉并拟合这些复杂的非线性特征,提升模型的特征表达能力。
代码实现:本文模型采用ReLU激活函数,核心代码如下:self.relu1 = nn.ReLU()(对应模型中4个卷积块的激活层,relu2、relu3、relu4定义方式一致)。该激活函数是CNN领域应用最广泛的激活函数之一,其核心作用是保留正特征值、抑制负特征值(将负特征值置为0),具有计算效率高、不易引发过拟合、可有效缓解梯度消失等优势。
补充说明:除ReLU激活函数外,Sigmoid、Tanh等激活函数也可用于CNN模型,但ReLU激活函数在图像分类任务中表现更优,因此本文模型优先选用该激活函数。
对激活层输出的特征图进行下采样处理,在保留核心特征的前提下,缩小特征图尺寸,减少模型参数总量与计算复杂度,同时抑制模型过拟合。
经过卷积与激活运算后,特征图仍保持较大尺寸,易导致模型参数过多、计算量过大。池化层通过固定尺寸的池化窗口对特征图进行采样压缩,常用的池化方式为最大池化(取池化窗口内的最大值),可在保留图像核心特征(如边缘、轮廓)的同时,实现特征图尺寸的减半压缩,有效降低模型复杂度。
代码实现:每个激活层后均需串联一个最大池化层,核心代码如下:self.pool1 = nn.MaxPool2d(2),其中参数2代表2×2的池化窗口,滑动步长默认与窗口尺寸一致(即2),确保池化窗口不重叠,实现高效的特征降维。
关键细节:4个卷积块(卷积层+BN层+激活层+池化层)串联处理后,图像尺寸从初始的150×150逐步缩小,经过4次池化(每次尺寸减半)后,最终尺寸可通过如下代码计算:feature_h = IMG_SIZE // 16,代入150计算得150//16=9,即最终特征图尺寸为9×9,为后续全连接层的输入做准备。
在模型训练过程中,随机丢弃部分神经元(将其输出置为0),避免模型过度依赖部分神经元的特征响应,从而有效抑制过拟合,提升模型泛化能力。
Dropout层的核心作用是通过随机丢弃神经元,打破模型对局部特征的过度依赖,迫使模型学习更具通用性的全局特征,确保模型在面对未见过的测试数据时,仍能保持较好的识别性能。
代码实现:Dropout层可通过如下代码定义,核心参数设置如下:self.dropout = nn.Dropout(0.5),其中参数0.5表示训练过程中,随机丢弃50%的神经元,以达到正则化效果。
将池化层输出的二维特征图拉伸为一维特征向量,通过全连接运算,将特征向量映射至具体的类别空间(本文实验选用4种花卉类别:雏菊、玫瑰、向日葵、郁金香),实现图像类别的最终判断。
全连接层的核心功能是对卷积、池化过程提取的高级特征进行整合与映射,建立高级特征与目标类别的对应关系,通过输出各类别的得分,完成图像分类决策。
代码实现:全连接层通常设置为两层以完成特征映射与分类,核心定义如下,具体参数设置及功能解析如下:
self.fc_input_dim = 128 * feature_h * feature_w # 128×9×9=10368
self.fc1 = nn.Linear(self.fc_input_dim, 512) # 从10368维压缩到512维
self.relu_fc1 = nn.ReLU()
self.dropout = nn.Dropout(0.5)
self.fc2 = nn.Linear(512, num_classes) # 从512维映射到4个类别其中,fc1层负责将拉伸后的一维特征向量(维度为128×9×9=10368)压缩至512维,减少参数总量;fc2层负责将512维特征向量映射至4个类别(num_classes=4),输出每个类别的得分,最终通过得分排序,确定图像的预测类别(得分最高的类别即为预测结果)。
结合上述各核心层的功能解析,依托本文实现的完整代码,系统梳理CNN完成花卉图像识别的全流程,该流程对应代码中的数据加载、模型训练、推理预测三大核心环节。
可以使用如下代码下载用于训练的数据集:
import kagglehub
# Download latest version
path = kagglehub.dataset_download("alxmamaev/flowers-recognition")
print("Path to dataset files:", path)CNN模型的训练与推理需依赖大量标注规范的图像数据,本文实验采用花卉数据集(包含daisy、rose、sunflower、tulip四类花卉),数据集存储于../flowers目录下。该环节的核心目标是将原始图像数据转换为模型可处理的标准化格式,具体实施步骤结合代码解析如下:
img_tensor = read_image(path, mode=ImageReadMode.RGB)),实现图像数据的数字化;resize_transform = transforms.Resize((img_size, img_size)),将所有图像统一调整至150×150尺寸,避免因图像尺寸不一致导致的模型训练报错;X /= 255.0,消除数值尺度差异;通过自定义标签编码器SimpleLabelEncoder,将花卉类别标签(如“rose”)转换为数字编码(如1),适配模型的数值计算需求;transforms.RandomHorizontalFlip(), transforms.RandomRotation(15)),增加训练数据的多样性,进一步抑制模型过拟合。代码实现:通过自定义CNN类OptimizedCNN实现上述结构,核心定义如下(与原示例逻辑一致),该模型的输入为150×150×3的图像张量,输出为4个花卉类别的得分向量。
模型的核心逻辑集中于forward方法,用于定义图像数据在模型中的前向传播路径,也是CNN实现特征提取与分类决策的核心流程,具体代码解析如下:
def forward(self, x):
# 4个卷积块:conv → bn → relu → pool
x = self.pool1(self.relu1(self.bn1(self.conv1(x))))
x = self.pool2(self.relu2(self.bn2(self.conv2(x))))
x = self.pool3(self.relu3(self.bn3(self.conv3(x))))
x = self.pool4(self.relu4(self.bn4(self.conv4(x))))
# 拉平特征图,输入全连接层
x = self.flatten(x)
x = self.dropout(self.relu_fc1(self.fc1(x)))
x = self.fc2(x)
return x前向传播流程简述:输入图像张量x依次经过4个卷积块的特征提取与降维,得到高级特征图;随后通过flatten操作将二维特征图拉伸为一维特征向量,经全连接层的特征映射与Dropout层的正则化处理后,输出4个类别的得分向量,完成一次前向传播。
模型训练的核心目标是通过迭代优化,调整模型参数(卷积核权重、全连接层参数),使模型的预测结果逐步逼近图像的真实类别,提升模型的分类准确率。本文实现的代码中,训练流程对应main函数的mode=’train’分支,具体步骤如下:
模型、损失函数与优化器初始化,核心代码如下:
model = OptimizedCNN(num_classes=num_classes).to(DEVICE) # 加载模型到指定设备(GPU/CPU)
criterion = nn.CrossEntropyLoss() # 交叉熵损失函数,适用于多分类任务
optimizer = optim.Adam(model.parameters(), lr=0.001) # Adam优化器,用于参数更新模型训练完成后,可通过训练好的模型对新的花卉图像进行分类预测,该流程对应推理模式,具体实现逻辑封装于predict_image函数,核心代码及步骤如下:
针对零基础学习者在运行CNN代码过程中易出现的问题,结合本文实现的CNN模型代码结构特点,整理3类高频问题的成因分析与优化建议,助力学习者高效避坑、提升模型性能。
成因分析:训练数据集样本量不足、数据增强策略单一,导致模型泛化能力较弱;或模型参数总量过多,易过度拟合训练数据特征。
优化建议:
成因分析:模型参数总量较大,计算复杂度较高;或未启用GPU加速,仅依赖CPU进行训练,计算效率低下。
优化建议:
成因分析:模型训练不充分,损失值未稳定收敛;待预测图像的预处理方式与训练集不一致,导致模型无法有效提取特征;或数据集类别分布不均衡,部分类别的样本量过少,导致模型偏向于样本量多的类别。
优化建议:
通过上述原理拆解与代码解析可以看出,CNN的核心机制是通过卷积运算实现图像特征的逐步提取,通过池化运算实现特征降维与过拟合抑制,通过全连接运算实现特征到类别的映射,其核心优势在于能够高效处理图像的空间特征,有效解决了传统神经网络在图像识别任务中的参数爆炸、过拟合等痛点。
除本文实现的花卉分类任务外,CNN在多个领域均有广泛应用,主要包括:
对于零基础学习者而言,本文提供的完整代码是入门CNN的优质实例,其涵盖了CNN模型训练与推理的全流程,结构清晰、注释完善。学习者可直接运行代码,结合本文的原理解析,通过调整模型参数(如卷积核数量、epoch数量、Dropout比例等),观察模型性能的变化,逐步掌握CNN的核心技术要点。
为便于学习者顺利实践,现将本文实现代码的运行步骤整理如下:
实践是掌握CNN技术的关键,建议学习者结合代码反复调试,深入理解各核心层的作用与参数调整对模型性能的影响,逐步提升自身的CNN应用能力。
我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=11gigmdvp71zv