当前位置: 首页 > news >正文

【大模型微调系列-04】 神经网络基础与小项目实战

【大模型微调系列-04】 神经网络基础与小项目实战

💡 本章目标:通过构建一个能识别手写数字的AI模型,让你真正理解神经网络是如何"学习"的。2-3小时后,你将拥有第一个自己训练的AI模型!

4.1 理论讲解:神经网络的核心机制

4.1.1 激活函数:让神经网络"活"起来

想象一下,如果神经网络只是简单地把输入乘以权重再相加,那会发生什么?答案是:无论叠加多少层,最终都等价于一个简单的线性变换。这就像无论你用多少个直尺去画图,最终也只能画出直线,永远画不出曲线。

激活函数就是神经网络的"魔法开关",它把线性运算的结果进行非线性变换,让网络能够学习复杂的模式。

输入 x
线性变换
z = Wx + b
激活函数
a = f z
非线性输出
为什么必须要激活函数?

让我们用一个简单的数学例子来说明。假设有一个两层网络,没有激活函数:

第一层:y₁ = W₁x + b₁
第二层:y₂ = W₂y₁ + b₂
展开后:y₂ = W₂(W₁x + b₁) + b₂ = (W₂W₁)x + (W₂b₁ + b₂)

看到了吗?两层网络最终可以简化为 y = Wx + b 的形式,和单层网络没有区别!这就是为什么我们需要激活函数来打破这种线性叠加。

三种常用激活函数

让我们通过代码直观地看看这些激活函数长什么样:

import numpy as np
import matplotlib.pyplot as plt# 创建输入数据
x = np.linspace(-5, 5, 100)# 定义三种激活函数
def relu(x):"""ReLU: 过滤负值,保留正值"""return np.maximum(0, x)def sigmoid(x):"""Sigmoid: 压缩到0-1之间,像概率"""return 1 / (1 + np.exp(-x))def tanh(x):"""Tanh: 压缩到-1到1之间,中心化的sigmoid"""return np.tanh(x)# 绘制激活函数图像
plt.figure(figsize=(12, 4))plt.subplot(1, 3, 1)
plt.plot(x, relu(x), 'b-', linewidth=2)
plt.title('ReLU: 过滤器')
plt.xlabel('输入')
plt.ylabel('输出')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='k', linewidth=0.5)
plt.axvline(x=0, color='k', linewidth=0.5)plt.subplot(1, 3, 2)
plt.plot(x, sigmoid(x), 'r-', linewidth=2)
plt.title('Sigmoid: 概率转换器')
plt.xlabel('输入')
plt.ylabel('输出')
plt.grid(True, alpha=0.3)
plt.axhline(y=0.5, color='k', linewidth=0.5, linestyle='--')plt.subplot(1, 3, 3)
plt.plot(x, tanh(x), 'g-', linewidth=2)
plt.title('Tanh: 中心化压缩器')
plt.xlabel('输入')
plt.ylabel('输出')
plt.grid(True, alpha=0.3)
plt.axhline(y=0, color='k', linewidth=0.5)
plt.axvline(x=0, color='k', linewidth=0.5)plt.tight_layout()
plt.show()

💭 理解检查

  • ReLU为什么叫"整流"?因为它像电子元件中的整流器,只让正信号通过
  • Sigmoid为什么常用于二分类?因为它的输出恰好在0-1之间,可以解释为概率
  • Tanh相比Sigmoid的优势?输出以0为中心,有助于下一层的学习
XOR问题:激活函数的威力展示

XOR(异或)问题是一个经典例子,它无法用直线分割,但加入激活函数后就能轻松解决:

# XOR问题的数据
X = np.array([[0,0], [0,1], [1,0], [1,1]])
y = np.array([0, 1, 1, 0])  # XOR的输出# 没有激活函数:永远无法正确分类
# 有激活函数:能够学习非线性边界

📝 一句话总结:激活函数是神经网络的"非线性变换器",让网络能够学习曲线、圆形等复杂边界,而不仅仅是直线。

4.1.2 前向传播与反向传播:神经网络的学习机制

前向传播:数据的流水线

想象神经网络是一条生产流水线,原材料(输入数据)经过每个工作站(网络层)的加工,最终产出成品(预测结果)。

前向传播流程
第一层
W1*x+b1
输入
28*28图像
ReLU
激活
第二层
W2*a1+b2
ReLU
激活
输出层
W3*a2+b3
预测
数字0-9

每一层的计算都很简单:

  1. 线性变换z = W×x + b (权重矩阵乘以输入,加上偏置)
  2. 激活函数a = f(z) (引入非线性)
  3. 传递输出:这一层的输出成为下一层的输入
反向传播:智能的纠错机制

如果前向传播是"考试答题",那反向传播就是"批改试卷"。它从最终的错误开始,逐层往回找每个参数应该承担多少"责任"。

反向传播流程
输出层梯度
dL/dW3
计算误差
loss
第二层梯度
dL/dW2
第一层梯度
dL/dW1
更新所有参数
W = W - lr*梯度

核心概念解释

  • 梯度(Gradient):就是"改变的方向"。想象你在山上,梯度告诉你哪个方向下山最快
  • 链式法则(Chain Rule):错误的传递就像接力赛,每一层都要把"接力棒"(梯度)传给前一层
  • 参数更新W_new = W_old - 学习率 × 梯度

让我们用代码演示一个简化版的反向传播:

# 简化的反向传播示例
import torch# 创建一个简单的计算图
x = torch.tensor([1.0], requires_grad=True)
w = torch.tensor([2.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)# 前向传播
y = w * x + b  # y = 2*1 + 1 = 3
loss = (y - 5) ** 2  # 假设目标是5,loss = (3-5)² = 4# 反向传播(PyTorch自动完成)
loss.backward()print(f"x的梯度: {x.grad}")  # 告诉我们x对loss的影响
print(f"w的梯度: {w.grad}")  # 告诉我们w对loss的影响
print(f"b的梯度: {b.grad}")  # 告诉我们b对loss的影响# 参数更新(梯度下降)
learning_rate = 0.1
w_new = w - learning_rate * w.grad
print(f"更新后的w: {w_new}")

🎯 动手试试:修改上面代码中的目标值(从5改为其他数字),观察梯度如何变化。

📝 一句话总结:前向传播计算预测结果,反向传播计算如何调整参数,两者配合让网络不断学习进步。

4.1.3 训练与验证:如何让模型真正"学会"

数据集划分:考试系统的智慧

训练神经网络就像准备考试,我们需要合理安排学习和测试:

完整数据集
70,000张图片
训练集 60%
42,000张图片
用来学习
验证集 20%
14,000张图片
检验效果
测试集 20%
14,000张图片
最终考试
  • 训练集:相当于课本习题,模型通过它学习规律
  • 验证集:相当于模拟考试,用来调整学习策略(超参数)
  • 测试集:相当于期末考试,只在最后用一次,评估真实能力
过拟合:死记硬背的陷阱

过拟合就像学生死记硬背答案,考试题目稍微变化就不会做了。让我们看看过拟合的表现:

# 绘制训练过程中的loss曲线
epochs = np.arange(1, 21)# 模拟三种情况
# 正常情况
train_loss_normal = 1.0 / np.sqrt(epochs) + 0.1
val_loss_normal = 1.0 / np.sqrt(epochs) + 0.15# 欠拟合
train_loss_under = 0.8 - 0.01 * epochs + 0.5
val_loss_under = 0.8 - 0.01 * epochs + 0.52# 过拟合
train_loss_over = 1.0 / epochs
val_loss_over = 1.0 / epochs[:10].tolist() + (0.1 + 0.02 * epochs[10:]).tolist()plt.figure(figsize=(12, 4))# 绘制三种情况
for i, (train, val, title) in enumerate([(train_loss_under, val_loss_under, '欠拟合:还没学会'),(train_loss_normal, val_loss_normal, '正常:恰到好处'),(train_loss_over, val_loss_over, '过拟合:死记硬背')
], 1):plt.subplot(1, 3, i)plt.plot(epochs, train, 'b-', label='训练loss', linewidth=2)plt.plot(epochs, val, 'r--', label='验证loss', linewidth=2)plt.xlabel('训练轮数')plt.ylabel('Loss')plt.title(title)plt.legend()plt.grid(True, alpha=0.3)plt.tight_layout()
plt.show()

防止过拟合的技巧

  1. Early Stopping(早停):验证loss不再下降就停止训练
  2. Dropout(随机失活):训练时随机"关闭"一些神经元,防止过度依赖
  3. 数据增强:对图片旋转、缩放,制造更多训练样本

📝 一句话总结:合理划分数据集并监控验证指标,是让模型真正"理解"而非"死记"的关键。

4.1.4 模型保存与加载:AI的"存档系统"

训练好的模型就像游戏存档,需要妥善保存以便后续使用:

# 保存模型的两种方式# 方式1:保存整个模型(结构+参数)
torch.save(model, 'my_model.pth')
# 优点:加载简单
# 缺点:文件较大,依赖代码版本# 方式2:只保存参数(推荐)
torch.save(model.state_dict(), 'model_params.pth')
# 优点:文件小,跨版本兼容性好
# 缺点:加载时需要先定义模型结构# 保存训练状态(用于中断后继续训练)
checkpoint = {'epoch': epoch,'model_state_dict': model.state_dict(),'optimizer_state_dict': optimizer.state_dict(),'loss': loss,
}
torch.save(checkpoint, 'checkpoint.pth')

模型文件包含什么?

  • 网络每一层的权重矩阵和偏置
  • 批归一化层的统计信息(如果有)
  • 优化器的动量信息(如果保存checkpoint)

4.2 实操案例:构建你的第一个AI - MNIST手写数字识别

现在让我们动手实现一个完整的神经网络项目!MNIST手写数字识别是深度学习的"Hello World"。

4.2.1 项目概览与环境准备

# 导入必要的库
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np
from tqdm import tqdm  # 显示进度条# 检查是否有GPU可用
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(f'使用设备: {device}')# 设置随机种子,保证结果可复现
torch.manual_seed(42)
np.random.seed(42)

4.2.2 数据加载与探索

# 定义数据预处理
transform = transforms.Compose([transforms.ToTensor(),  # 将图片转为Tensortransforms.Normalize((0.1307,), (0.3081,))  # 标准化(MNIST的均值和标准差)
])# 下载并加载MNIST数据集
print("正在下载MNIST数据集...")
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform
)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transform
)# 创建数据加载器
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)print(f"训练集大小: {len(train_dataset)} 张图片")
print(f"测试集大小: {len(test_dataset)} 张图片")
print(f"每批次大小: {batch_size}")
print(f"训练批次数: {len(train_loader)}")# 可视化一些样本
def show_samples():"""展示一些MNIST样本"""fig, axes = plt.subplots(2, 5, figsize=(12, 6))axes = axes.ravel()# 获取一批数据images, labels = next(iter(train_loader))for i in range(10):# 反标准化用于显示img = images[i].squeeze()img = img * 0.3081 + 0.1307axes[i].imshow(img, cmap='gray')axes[i].set_title(f'标签: {labels[i].item()}')axes[i].axis('off')plt.suptitle('MNIST手写数字样本')plt.tight_layout()plt.show()show_samples()# 打印数据形状,帮助理解
sample_image, sample_label = train_dataset[0]
print(f"\n单张图片形状: {sample_image.shape}")  # torch.Size([1, 28, 28])
print(f"展平后的维度: {sample_image.flatten().shape}")  # torch.Size([784])

💡 理解要点

  • MNIST图片是28×28像素的灰度图
  • 展平后变成784维的向量,作为网络输入
  • 标签是0-9的数字,需要预测10个类别

4.2.3 定义神经网络模型

class MLP(nn.Module):"""多层感知器(MLP)网络结构:784输入 → 128隐藏 → 64隐藏 → 10输出"""def __init__(self):super(MLP, self).__init__()# 定义网络层self.fc1 = nn.Linear(784, 128)  # 输入层到第一隐藏层self.fc2 = nn.Linear(128, 64)   # 第一隐藏层到第二隐藏层self.fc3 = nn.Linear(64, 10)    # 第二隐藏层到输出层# 定义激活函数self.relu = nn.ReLU()# Dropout层,防止过拟合self.dropout = nn.Dropout(0.2)  # 20%的神经元会被随机关闭def forward(self, x):"""前向传播过程"""# 将28×28的图片展平成784维向量x = x.view(-1, 784)  # -1表示自动计算批次大小# 第一层:线性变换 + 激活 + Dropoutx = self.fc1(x)       # [batch_size, 784] → [batch_size, 128]x = self.relu(x)      # ReLU激活x = self.dropout(x)   # Dropout# 第二层:线性变换 + 激活 + Dropoutx = self.fc2(x)       # [batch_size, 128] → [batch_size, 64]x = self.relu(x)      # ReLU激活x = self.dropout(x)   # Dropout# 输出层:线性变换(不需要激活函数)x = self.fc3(x)       # [batch_size, 64] → [batch_size, 10]return x# 创建模型实例
model = MLP().to(device)# 打印模型结构
print("模型结构:")
print(model)# 计算模型参数量
total_params = sum(p.numel() for p in model.parameters())
print(f"\n总参数量: {total_params:,}")
MLP网络结构
隐藏层1
128个神经元
ReLU激活
输入层
784个神经元
28*28图片
Dropout
20%
隐藏层2
64个神经元
ReLU激活
Dropout
20%
输出层
10个神经元
数字0-9

4.2.4 训练循环实现

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()  # 交叉熵损失,适合多分类
optimizer = optim.Adam(model.parameters(), lr=0.001)  # Adam优化器# 记录训练过程
train_losses = []
train_accuracies = []
val_losses = []
val_accuracies = []def train_epoch(model, loader, criterion, optimizer):"""训练一个epoch"""model.train()  # 设置为训练模式(启用Dropout)total_loss = 0correct = 0total = 0# 使用tqdm显示进度条progress_bar = tqdm(loader, desc='训练中')for batch_idx, (images, labels) in enumerate(progress_bar):# 将数据移到GPU(如果有)images, labels = images.to(device), labels.to(device)# ========= 5步训练流程 =========# 步骤1:清零梯度(必须的,否则梯度会累积)optimizer.zero_grad()# 步骤2:前向传播outputs = model(images)# 步骤3:计算损失loss = criterion(outputs, labels)# 步骤4:反向传播loss.backward()# 步骤5:更新参数optimizer.step()# ==============================# 统计指标total_loss += loss.item()_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()# 更新进度条显示if batch_idx % 10 == 0:progress_bar.set_postfix({'Loss': f'{loss.item():.4f}','Acc': f'{100.*correct/total:.2f}%'})avg_loss = total_loss / len(loader)accuracy = 100. * correct / totalreturn avg_loss, accuracydef validate(model, loader, criterion):"""验证模型性能"""model.eval()  # 设置为评估模式(关闭Dropout)total_loss = 0correct = 0total = 0# 不需要计算梯度,节省内存with torch.no_grad():for images, labels in tqdm(loader, desc='验证中'):images, labels = images.to(device), labels.to(device)outputs = model(images)loss = criterion(outputs, labels)total_loss += loss.item()_, predicted = torch.max(outputs.data, 1)total += labels.size(0)correct += (predicted == labels).sum().item()avg_loss = total_loss / len(loader)accuracy = 100. * correct / totalreturn avg_loss, accuracy# 开始训练
num_epochs = 10
best_val_acc = 0print("="*50)
print("开始训练...")
print("="*50)for epoch in range(num_epochs):print(f'\nEpoch [{epoch+1}/{num_epochs}]')# 训练train_loss, train_acc = train_epoch(model, train_loader, criterion, optimizer)train_losses.append(train_loss)train_accuracies.append(train_acc)# 验证val_loss, val_acc = validate(model, test_loader, criterion)val_losses.append(val_loss)val_accuracies.append(val_acc)print(f'训练 - Loss: {train_loss:.4f}, Accuracy: {train_acc:.2f}%')print(f'验证 - Loss: {val_loss:.4f}, Accuracy: {val_acc:.2f}%')# 保存最佳模型if val_acc > best_val_acc:best_val_acc = val_acctorch.save(model.state_dict(), 'best_model.pth')print(f'✅ 保存最佳模型 (验证准确率: {val_acc:.2f}%)')print(f'\n训练完成!最佳验证准确率: {best_val_acc:.2f}%')

🔍 常见错误提示

  • 如果出现"CUDA out of memory",减小batch_size
  • 如果loss变成NaN,检查学习率是否过大
  • 如果准确率一直不提升,检查数据预处理是否正确

4.2.5 训练曲线可视化

def plot_training_history():"""绘制训练历史曲线"""fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 5))epochs_range = range(1, len(train_losses) + 1)# 绘制Loss曲线ax1.plot(epochs_range, train_losses, 'b-', label='训练Loss', linewidth=2)ax1.plot(epochs_range, val_losses, 'r-', label='验证Loss', linewidth=2)ax1.set_xlabel('Epoch')ax1.set_ylabel('Loss')ax1.set_title('Loss曲线')ax1.legend()ax1.grid(True, alpha=0.3)# 标注最低验证Lossmin_val_loss_epoch = np.argmin(val_losses) + 1ax1.plot(min_val_loss_epoch, val_losses[min_val_loss_epoch-1], 'ro', markersize=10)ax1.annotate(f'最低验证Loss\nEpoch {min_val_loss_epoch}', xy=(min_val_loss_epoch, val_losses[min_val_loss_epoch-1]),xytext=(min_val_loss_epoch+1, val_losses[min_val_loss_epoch-1]+0.05),arrowprops=dict(arrowstyle='->', color='red'))# 绘制准确率曲线ax2.plot(epochs_range, train_accuracies, 'b-', label='训练准确率', linewidth=2)ax2.plot(epochs_range, val_accuracies, 'r-', label='验证准确率', linewidth=2)ax2.set_xlabel('Epoch')ax2.set_ylabel('准确率 (%)')ax2.set_title('准确率曲线')ax2.legend()ax2.grid(True, alpha=0.3)# 标注最高验证准确率max_val_acc_epoch = np.argmax(val_accuracies) + 1ax2.plot(max_val_acc_epoch, val_accuracies[max_val_acc_epoch-1], 'go', markersize=10)ax2.annotate(f'最高准确率\n{val_accuracies[max_val_acc_epoch-1]:.2f}%', xy=(max_val_acc_epoch, val_accuracies[max_val_acc_epoch-1]),xytext=(max_val_acc_epoch-2, val_accuracies[max_val_acc_epoch-1]-3),arrowprops=dict(arrowstyle='->', color='green'))plt.suptitle('训练过程监控', fontsize=14)plt.tight_layout()plt.show()plot_training_history()

4.2.6 模型评估与错误分析

def evaluate_model():"""详细评估模型性能"""model.eval()# 收集所有预测结果all_predictions = []all_labels = []all_probs = []with torch.no_grad():for images, labels in test_loader:images, labels = images.to(device), labels.to(device)outputs = model(images)# 获取预测概率和类别probs = torch.softmax(outputs, dim=1)_, predicted = torch.max(outputs, 1)all_predictions.extend(predicted.cpu().numpy())all_labels.extend(labels.cpu().numpy())all_probs.extend(probs.cpu().numpy())# 计算混淆矩阵from sklearn.metrics import confusion_matriximport seaborn as snscm = confusion_matrix(all_labels, all_predictions)plt.figure(figsize=(10, 8))sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=range(10), yticklabels=range(10))plt.title('混淆矩阵')plt.ylabel('真实标签')plt.xlabel('预测标签')plt.show()# 找出预测错误的样本errors = []for i in range(len(all_predictions)):if all_predictions[i] != all_labels[i]:errors.append({'index': i,'true': all_labels[i],'pred': all_predictions[i],'confidence': max(all_probs[i]) * 100})print(f"错误样本数: {len(errors)} / {len(all_predictions)}")print(f"错误率: {len(errors)/len(all_predictions)*100:.2f}%")# 展示一些错误案例if errors:print("\n部分错误案例:")for error in errors[:5]:print(f"样本{error['index']}: 真实={error['true']}, "f"预测={error['pred']}, 置信度={error['confidence']:.1f}%")return errorserrors = evaluate_model()# 可视化错误案例
def show_error_cases(num_cases=6):"""展示预测错误的案例"""if not errors:print("没有错误案例!")returnfig, axes = plt.subplots(2, 3, figsize=(12, 8))axes = axes.ravel()# 随机选择一些错误案例error_indices = np.random.choice(len(errors), min(num_cases, len(errors)), replace=False)for idx, ax in enumerate(axes):if idx >= len(error_indices):ax.axis('off')continueerror = errors[error_indices[idx]]# 获取对应的图片test_data = test_dataset[error['index']][0]img = test_data.squeeze()img = img * 0.3081 + 0.1307  # 反标准化ax.imshow(img, cmap='gray')ax.set_title(f"真实: {error['true']}, 预测: {error['pred']}\n"f"置信度: {error['confidence']:.1f}%",color='red')ax.axis('off')plt.suptitle('预测错误案例分析', fontsize=14)plt.tight_layout()plt.show()show_error_cases()

4.2.7 模型保存与加载

# 保存完整的训练状态
def save_checkpoint(model, optimizer, epoch, loss, accuracy, filename='checkpoint.pth'):"""保存训练检查点"""checkpoint = {'epoch': epoch,'model_state_dict': model.state_dict(),'optimizer_state_dict': optimizer.state_dict(),'loss': loss,'accuracy': accuracy,'model_architecture': str(model),}torch.save(checkpoint, filename)print(f"✅ 检查点已保存到 {filename}")# 保存当前模型
save_checkpoint(model, optimizer, num_epochs, val_losses[-1], val_accuracies[-1])# 加载模型进行推理
def load_model_for_inference():"""加载训练好的模型"""# 创建新的模型实例loaded_model = MLP().to(device)# 加载参数loaded_model.load_state_dict(torch.load('best_model.pth'))loaded_model.eval()print("✅ 模型加载成功!")return loaded_model# 测试加载的模型
loaded_model = load_model_for_inference()# 用加载的模型预测单张图片
def predict_single_image(model, image_tensor):"""预测单张图片"""model.eval()with torch.no_grad():image_tensor = image_tensor.unsqueeze(0).to(device)  # 添加batch维度output = model(image_tensor)probabilities = torch.softmax(output, dim=1)predicted_class = torch.argmax(output, dim=1)confidence = torch.max(probabilities) * 100return predicted_class.item(), confidence.item()# 测试预测功能
test_image, test_label = test_dataset[0]
pred_class, confidence = predict_single_image(loaded_model, test_image)
print(f"预测结果: {pred_class}, 置信度: {confidence:.2f}%, 真实标签: {test_label}")

4.2.8 进阶:CNN网络实现(选做)

class SimpleCNN(nn.Module):"""简单的卷积神经网络相比MLP的优势:参数共享、局部感知"""def __init__(self):super(SimpleCNN, self).__init__()# 卷积层self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)# 池化层self.pool = nn.MaxPool2d(2, 2)# 全连接层self.fc1 = nn.Linear(64 * 7 * 7, 128)self.fc2 = nn.Linear(128, 10)# 激活和Dropoutself.relu = nn.ReLU()self.dropout = nn.Dropout(0.25)def forward(self, x):# 第一个卷积块x = self.conv1(x)           # [batch, 1, 28, 28] → [batch, 32, 28, 28]x = self.relu(x)x = self.pool(x)            # [batch, 32, 28, 28] → [batch, 32, 14, 14]# 第二个卷积块x = self.conv2(x)           # [batch, 32, 14, 14] → [batch, 64, 14, 14]x = self.relu(x)x = self.pool(x)            # [batch, 64, 14, 14] → [batch, 64, 7, 7]# 展平并通过全连接层x = x.view(-1, 64 * 7 * 7)  # 展平x = self.fc1(x)x = self.relu(x)x = self.dropout(x)x = self.fc2(x)return x# 创建CNN模型
cnn_model = SimpleCNN().to(device)# 计算参数量对比
mlp_params = sum(p.numel() for p in model.parameters())
cnn_params = sum(p.numel() for p in cnn_model.parameters())print("模型参数量对比:")
print(f"MLP: {mlp_params:,} 参数")
print(f"CNN: {cnn_params:,} 参数")
print(f"CNN参数量是MLP的 {cnn_params/mlp_params*100:.1f}%")# 性能对比表格
comparison_data = {'模型': ['MLP', 'CNN'],'参数量': [mlp_params, cnn_params],'准确率': ['~98%', '~99%'],'训练时间/epoch': ['~30秒', '~45秒'],'优势': ['简单易懂', '更高准确率']
}import pandas as pd
comparison_df = pd.DataFrame(comparison_data)
print("\n性能对比:")
print(comparison_df.to_string(index=False))

4.3 本章小结与练习

🎯 学习成果检查清单

完成本章学习后,你应该能够:

  • 解释为什么神经网络需要激活函数
  • 描述前向传播和反向传播的基本流程
  • 独立完成MNIST手写数字识别项目
  • 绘制并解读训练曲线
  • 保存和加载训练好的模型
  • 识别并处理过拟合问题

💡 关键概念回顾

  1. 激活函数:引入非线性,让网络能学习复杂模式
  2. 前向传播:数据从输入层流向输出层的计算过程
  3. 反向传播:根据误差调整网络参数的过程
  4. 梯度下降:沿着梯度的反方向更新参数
  5. 过拟合:模型在训练集上表现好但泛化能力差

🚀 进阶练习

  1. 调参实验

    • 改变隐藏层大小(如256、512),观察效果
    • 调整学习率(0.01、0.001、0.0001),找出最优值
    • 修改Dropout率,观察对过拟合的影响
  2. 数据增强

    • 对MNIST图片进行随机旋转(-15°到15°)
    • 添加随机噪声,提高模型鲁棒性
  3. 新数据集挑战

    • 尝试Fashion-MNIST(服装分类)
    • 挑战CIFAR-10(彩色图片分类)

📚 推荐资源

  • PyTorch官方教程:https://pytorch.org/tutorials/
  • 可视化神经网络:http://playground.tensorflow.org/
  • MNIST数据集详情:http://yann.lecun.com/exdb/mnist/

🎉 恭喜你!

你已经成功训练了第一个神经网络!这是深度学习之旅的重要里程碑。下一章,我们将深入了解Qwen大模型的架构,看看这些基础知识如何应用到数十亿参数的大模型中。

http://www.dtcms.com/a/334507.html

相关文章:

  • windows环境下使用vscode以及相关插件搭建c/c++的编译,调试环境
  • GIMP:功能强大的跨平台图像处理软件
  • 嵌入式硬件篇---电容本质
  • leetcodehot100 矩阵置零
  • Jenkins安装部署(Win11)和常见配置镜像加速
  • B3837 [GESP202303 二级] 画三角形
  • csrf攻击
  • 11、软件需求工程
  • AMD Ryzen AI Max+ 395四机并联:大语言模型集群推理深度测试
  • 智能二维码刷卡人脸识别梯控控制器硬件规格书​
  • 【C++】高效资源管理四剑客:RVO、NRVO、std::move、RAII 深度解析
  • 【3D重建技术】如何基于遥感图像和DEM等数据进行城市级高精度三维重建?
  • 【Vibe Coding 工程之 StockAnalyzerPro 记录】- EP3.Phase 2股票列表管理功能
  • Font shape `TU/ptm/m/n‘ undefined(Font) using `TU/lmr/m/n‘ instead
  • UE5多人MOBA+GAS 45、制作冲刺技能
  • Business Magic
  • [创业之路-550]:公司半年度经营分析会 - 解决方案汇总
  • 【Java web】Servlet 详解
  • Linux -- 文件【下】
  • MATLAB R2010b系统环境(二)MATLAB环境的准备
  • 基于Transformer的机器翻译——模型篇
  • 力扣面试150(57/100)
  • 罗技MX Anywhere 2S鼠标修复记录
  • RocketMq面试集合
  • Redis--day6--黑马点评--商户查询缓存
  • 极简工具箱:安卓工具箱合集
  • redis的key过期删除策略和内存淘汰机制
  • Python爬虫实战:研究pygalmesh,构建Thingiverse平台三维网格数据处理系统
  • 记录Linux的指令学习
  • ktg-mes 改造成 Saas 系统