什么是深度学习?
深度学习(Deep Learning)是机器学习的一个分支,它使用多层神经网络来学习数据的层次化表示。深度学习在计算机视觉、自然语言处理、语音识别等领域取得了突破性进展。
与传统机器学习方法不同,深度学习能够自动从原始数据中学习特征表示,无需人工特征工程。这使得深度学习在处理高维数据(如图像、语音、文本)时表现出色。
核心概念
神经网络的数学基础
1. 单个神经元
一个神经元接收多个输入,对它们进行加权求和,然后通过激活函数产生输出。数学表达式为:
其中:
- 是输入向量
- 是权重向量
- 是偏置项
- 是激活函数
- 是输出
2. 多层神经网络的矩阵表示
对于一个 层的神经网络,我们可以用矩阵运算来表示前向传播过程:
第 层的前向传播:
其中:
- 是第 层的激活值( 是样本数量)
- 是第 层的权重矩阵
- 是第 层的偏置向量
- 是第 层的线性组合
- 是第 层的激活函数
- 是输入数据
激活函数详解
激活函数为神经网络引入非线性,使其能够学习复杂的模式。
1. Sigmoid函数
定义:
导数:
特点:
- 输出范围:
- 适合二分类问题的输出层
- 缺点:容易出现梯度消失问题
2. Tanh函数
定义:
导数:
特点:
- 输出范围:
- 零中心化,收敛速度比Sigmoid快
- 仍然存在梯度消失问题
3. ReLU(修正线性单元)
定义:
导数:
特点:
- 计算简单,训练速度快
- 缓解梯度消失问题
- 缺点:可能出现”神经元死亡”(dying ReLU)
4. Leaky ReLU
定义:
特点:
- 解决了ReLU的”神经元死亡”问题
- 允许负值有小的梯度
反向传播算法:完整数学推导
反向传播是训练神经网络的核心算法,它利用链式法则计算损失函数对每个参数的梯度。
1. 损失函数
均方误差(MSE):
交叉熵损失(二分类):
交叉熵损失(多分类):
2. 反向传播的链式法则
对于第 层的参数,我们需要计算:
步骤1:计算输出层的误差
对于输出层 :
其中 表示元素级乘法(Hadamard积)。
步骤2:反向传播误差
对于隐藏层 ():
步骤3:计算梯度
步骤4:更新参数
其中 是学习率。
优化算法详解
1. 随机梯度下降(SGD)
标准SGD:
带动量的SGD(Momentum):
动量方法通过累积历史梯度来加速收敛并减少振荡。
其中 通常取 0.9, 是速度(velocity)。
2. RMSprop
RMSprop通过使用梯度平方的移动平均来自适应调整学习率。
其中 是一个很小的数(如 )用于防止除零。
3. Adam优化器
Adam(Adaptive Moment Estimation)结合了动量和RMSprop的优点。
其中:
- (一阶矩估计的指数衰减率)
- (二阶矩估计的指数衰减率)
- 和 是偏差修正后的估计
从零实现神经网络(NumPy)
下面我们使用NumPy从零实现一个简单的两层神经网络,用于二分类问题。
import numpy as npimport matplotlib.pyplot as plt
class NeuralNetwork: """ 两层神经网络实现 架构:输入层 -> 隐藏层 -> 输出层 """ def __init__(self, input_size, hidden_size, output_size, learning_rate=0.01): """ 初始化网络参数
参数: input_size: 输入特征维度 hidden_size: 隐藏层神经元数量 output_size: 输出维度 learning_rate: 学习率 """ # 使用He初始化权重 self.W1 = np.random.randn(input_size, hidden_size) * np.sqrt(2.0/input_size) self.b1 = np.zeros((1, hidden_size)) self.W2 = np.random.randn(hidden_size, output_size) * np.sqrt(2.0/hidden_size) self.b2 = np.zeros((1, output_size)) self.learning_rate = learning_rate
def sigmoid(self, z): """Sigmoid激活函数""" return 1 / (1 + np.exp(-np.clip(z, -500, 500))) # clip防止溢出
def sigmoid_derivative(self, z): """Sigmoid导数""" s = self.sigmoid(z) return s * (1 - s)
def relu(self, z): """ReLU激活函数""" return np.maximum(0, z)
def relu_derivative(self, z): """ReLU导数""" return (z > 0).astype(float)
def forward(self, X): """ 前向传播
参数: X: 输入数据,形状 (m, input_size)
返回: A2: 输出预测,形状 (m, output_size) """ # 第一层:输入 -> 隐藏层 self.Z1 = np.dot(X, self.W1) + self.b1 self.A1 = self.relu(self.Z1)
# 第二层:隐藏层 -> 输出层 self.Z2 = np.dot(self.A1, self.W2) + self.b2 self.A2 = self.sigmoid(self.Z2)
return self.A2
def compute_loss(self, y_true, y_pred): """ 计算二分类交叉熵损失
参数: y_true: 真实标签,形状 (m, 1) y_pred: 预测值,形状 (m, 1)
返回: loss: 损失值 """ m = y_true.shape[0] epsilon = 1e-8 # 防止log(0) loss = -np.mean(y_true * np.log(y_pred + epsilon) + (1 - y_true) * np.log(1 - y_pred + epsilon)) return loss
def backward(self, X, y_true): """ 反向传播
参数: X: 输入数据,形状 (m, input_size) y_true: 真实标签,形状 (m, output_size) """ m = X.shape[0]
# 计算输出层误差 dZ2 = self.A2 - y_true # 交叉熵+sigmoid的导数简化形式 dW2 = (1/m) * np.dot(self.A1.T, dZ2) db2 = (1/m) * np.sum(dZ2, axis=0, keepdims=True)
# 计算隐藏层误差 dA1 = np.dot(dZ2, self.W2.T) dZ1 = dA1 * self.relu_derivative(self.Z1) dW1 = (1/m) * np.dot(X.T, dZ1) db1 = (1/m) * np.sum(dZ1, axis=0, keepdims=True)
# 更新参数 self.W2 -= self.learning_rate * dW2 self.b2 -= self.learning_rate * db2 self.W1 -= self.learning_rate * dW1 self.b1 -= self.learning_rate * db1
def train(self, X, y, epochs=1000, verbose=True): """ 训练网络
参数: X: 训练数据,形状 (m, input_size) y: 标签,形状 (m, output_size) epochs: 训练轮数 verbose: 是否打印训练信息
返回: losses: 每个epoch的损失值列表 """ losses = []
for epoch in range(epochs): # 前向传播 y_pred = self.forward(X)
# 计算损失 loss = self.compute_loss(y, y_pred) losses.append(loss)
# 反向传播 self.backward(X, y)
# 打印训练信息 if verbose and (epoch % 100 == 0 or epoch == epochs - 1): accuracy = np.mean((y_pred > 0.5) == y) print(f"Epoch {epoch}/{epochs}, Loss: {loss:.4f}, Accuracy: {accuracy:.4f}")
return losses
def predict(self, X): """ 预测
参数: X: 输入数据,形状 (m, input_size)
返回: predictions: 预测类别,形状 (m, 1) """ y_pred = self.forward(X) return (y_pred > 0.5).astype(int)
# 示例:生成螺旋数据集并训练def generate_spiral_data(n_points=100, n_classes=2): """生成螺旋分类数据""" X = np.zeros((n_points * n_classes, 2)) y = np.zeros((n_points * n_classes, 1))
for class_num in range(n_classes): ix = range(n_points * class_num, n_points * (class_num + 1)) r = np.linspace(0.0, 1, n_points) t = np.linspace(class_num * 4, (class_num + 1) * 4, n_points) + \ np.random.randn(n_points) * 0.2 X[ix] = np.c_[r * np.sin(t), r * np.cos(t)] y[ix] = class_num
return X, y
# 生成数据X_train, y_train = generate_spiral_data(n_points=100, n_classes=2)
# 创建并训练网络print("训练两层神经网络(NumPy实现)")print("=" * 50)nn = NeuralNetwork(input_size=2, hidden_size=10, output_size=1, learning_rate=0.5)losses = nn.train(X_train, y_train, epochs=1000, verbose=True)
# 评估predictions = nn.predict(X_train)accuracy = np.mean(predictions == y_train)print(f"\n最终训练准确率: {accuracy:.4f}")输出示例:
训练两层神经网络(NumPy实现)==================================================Epoch 0/1000, Loss: 0.6932, Accuracy: 0.5000Epoch 100/1000, Loss: 0.3251, Accuracy: 0.8850Epoch 200/1000, Loss: 0.1823, Accuracy: 0.9400Epoch 300/1000, Loss: 0.1245, Accuracy: 0.9600...Epoch 999/1000, Loss: 0.0431, Accuracy: 0.9900
最终训练准确率: 0.9900PyTorch实战:MNIST手写数字识别
现在我们使用PyTorch实现一个完整的MNIST手写数字识别项目。
import torchimport torch.nn as nnimport torch.optim as optimfrom torch.utils.data import DataLoaderfrom torchvision import datasets, transformsimport torch.nn.functional as F
# 设置随机种子以保证结果可复现torch.manual_seed(42)
# 定义神经网络模型class MNISTNet(nn.Module): """ 用于MNIST数字识别的全连接神经网络 架构:784 -> 128 -> 64 -> 10 """ def __init__(self): super(MNISTNet, self).__init__() # 定义网络层 self.fc1 = nn.Linear(28 * 28, 128) # 输入层到第一隐藏层 self.fc2 = nn.Linear(128, 64) # 第一隐藏层到第二隐藏层 self.fc3 = nn.Linear(64, 10) # 第二隐藏层到输出层
# Dropout层用于防止过拟合 self.dropout = nn.Dropout(0.2)
def forward(self, x): """ 前向传播
参数: x: 输入张量,形状 (batch_size, 1, 28, 28)
返回: 输出张量,形状 (batch_size, 10) """ # 展平图像:(batch_size, 1, 28, 28) -> (batch_size, 784) x = x.view(-1, 28 * 28)
# 第一层:线性变换 + ReLU激活 x = F.relu(self.fc1(x)) x = self.dropout(x)
# 第二层:线性变换 + ReLU激活 x = F.relu(self.fc2(x)) x = self.dropout(x)
# 输出层:线性变换(不需要激活函数,因为我们使用CrossEntropyLoss) x = self.fc3(x)
return x
def train_epoch(model, device, train_loader, optimizer, criterion, epoch): """ 训练一个epoch
参数: model: 神经网络模型 device: 设备(CPU或GPU) train_loader: 训练数据加载器 optimizer: 优化器 criterion: 损失函数 epoch: 当前epoch编号 """ model.train() # 设置为训练模式 train_loss = 0 correct = 0 total = 0
for batch_idx, (data, target) in enumerate(train_loader): # 将数据移到设备上 data, target = data.to(device), target.to(device)
# 梯度清零 optimizer.zero_grad()
# 前向传播 output = model(data)
# 计算损失 loss = criterion(output, target)
# 反向传播 loss.backward()
# 更新参数 optimizer.step()
# 统计 train_loss += loss.item() _, predicted = output.max(1) total += target.size(0) correct += predicted.eq(target).sum().item()
# 打印训练信息 if batch_idx % 100 == 0: print(f'Epoch: {epoch} [{batch_idx * len(data)}/{len(train_loader.dataset)} ' f'({100. * batch_idx / len(train_loader):.0f}%)]\t' f'Loss: {loss.item():.6f}')
avg_loss = train_loss / len(train_loader) accuracy = 100. * correct / total print(f'\nTraining Set: Average loss: {avg_loss:.4f}, ' f'Accuracy: {correct}/{total} ({accuracy:.2f}%)\n')
return avg_loss, accuracy
def test(model, device, test_loader, criterion): """ 测试模型
参数: model: 神经网络模型 device: 设备(CPU或GPU) test_loader: 测试数据加载器 criterion: 损失函数
返回: 平均损失和准确率 """ model.eval() # 设置为评估模式 test_loss = 0 correct = 0
with torch.no_grad(): # 测试时不需要计算梯度 for data, target in test_loader: data, target = data.to(device), target.to(device) output = model(data) test_loss += criterion(output, target).item() pred = output.argmax(dim=1, keepdim=True) correct += pred.eq(target.view_as(pred)).sum().item()
test_loss /= len(test_loader) accuracy = 100. * correct / len(test_loader.dataset)
print(f'Test Set: Average loss: {test_loss:.4f}, ' f'Accuracy: {correct}/{len(test_loader.dataset)} ({accuracy:.2f}%)\n')
return test_loss, accuracy
# 主训练流程def main(): """主函数:完整的MNIST训练流程"""
# 超参数设置 batch_size = 64 learning_rate = 0.001 epochs = 10
# 设置设备 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"使用设备: {device}\n")
# 数据预处理 transform = transforms.Compose([ transforms.ToTensor(), # 转换为张量 transforms.Normalize((0.1307,), (0.3081,)) # 标准化(MNIST的均值和标准差) ])
# 加载MNIST数据集 print("加载MNIST数据集...") train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform) test_dataset = datasets.MNIST('./data', train=False, transform=transform)
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)}\n")
# 创建模型 model = MNISTNet().to(device) print("模型架构:") print(model) print()
# 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 学习率调度器(可选) scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)
# 训练和测试 train_losses = [] test_losses = [] train_accuracies = [] test_accuracies = []
print("开始训练...") print("=" * 70)
for epoch in range(1, epochs + 1): # 训练 train_loss, train_acc = train_epoch(model, device, train_loader, optimizer, criterion, epoch) train_losses.append(train_loss) train_accuracies.append(train_acc)
# 测试 test_loss, test_acc = test(model, device, test_loader, criterion) test_losses.append(test_loss) test_accuracies.append(test_acc)
# 更新学习率 scheduler.step()
print("-" * 70)
print("训练完成!") print(f"最终测试准确率: {test_accuracies[-1]:.2f}%")
# 保存模型 torch.save(model.state_dict(), 'mnist_model.pth') print("模型已保存到 mnist_model.pth")
return model, train_losses, test_losses, train_accuracies, test_accuracies
# 运行训练if __name__ == "__main__": model, train_losses, test_losses, train_accs, test_accs = main()预期输出:
使用设备: cuda
加载MNIST数据集...训练集大小: 60000测试集大小: 10000
模型架构:MNISTNet( (fc1): Linear(in_features=784, out_features=128, bias=True) (fc2): Linear(in_features=128, out_features=64, bias=True) (fc3): Linear(in_features=64, out_features=10, bias=True) (dropout): Dropout(p=0.2, inplace=False))
开始训练...======================================================================Epoch: 1 [0/60000 (0%)] Loss: 2.305841...Training Set: Average loss: 0.3421, Accuracy: 54231/60000 (90.39%)Test Set: Average loss: 0.1823, Accuracy: 9456/10000 (94.56%)...训练完成!最终测试准确率: 98.12%卷积神经网络(CNN)详解
卷积神经网络是专门为处理网格结构数据(如图像)设计的神经网络。
卷积层的数学原理
1. 二维卷积运算
对于输入 和卷积核 ,卷积运算定义为:
2. 多通道卷积
对于输入 和卷积核 :
3. 输出尺寸计算
给定:
- 输入尺寸:
- 卷积核大小:
- 步长(stride):
- 填充(padding):
输出尺寸为:
池化层
最大池化(Max Pooling)
平均池化(Average Pooling)
批归一化(Batch Normalization)
批归一化通过标准化每一层的输入来加速训练并提高稳定性。
对于一个mini-batch :
步骤1:计算均值和方差
步骤2:标准化
步骤3:缩放和平移
其中 和 是可学习的参数, 是防止除零的小常数。
PyTorch实战:CNN图像分类(CIFAR-10)
现在我们使用PyTorch实现一个完整的CNN模型来处理CIFAR-10数据集。
import torchimport torch.nn as nnimport torch.optim as optimfrom torch.utils.data import DataLoaderfrom torchvision import datasets, transformsimport torch.nn.functional as F
# 定义CNN模型class CIFAR10CNN(nn.Module): """ 用于CIFAR-10图像分类的卷积神经网络 架构:Conv -> Conv -> Pool -> Conv -> Conv -> Pool -> FC -> FC """ def __init__(self): super(CIFAR10CNN, self).__init__()
# 第一个卷积块 self.conv1 = nn.Conv2d(3, 32, kernel_size=3, padding=1) # 输入: 3通道(RGB), 输出: 32通道, 卷积核: 3x3 self.bn1 = nn.BatchNorm2d(32)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) self.bn2 = nn.BatchNorm2d(64)
# 第一个池化层: 32x32 -> 16x16 self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)
# 第二个卷积块 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1) self.bn3 = nn.BatchNorm2d(128)
self.conv4 = nn.Conv2d(128, 128, kernel_size=3, padding=1) self.bn4 = nn.BatchNorm2d(128)
# 第二个池化层: 16x16 -> 8x8 self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
# 全连接层 self.fc1 = nn.Linear(128 * 8 * 8, 512) self.dropout1 = nn.Dropout(0.5)
self.fc2 = nn.Linear(512, 10) # 10个类别
def forward(self, x): """ 前向传播
参数: x: 输入张量,形状 (batch_size, 3, 32, 32)
返回: 输出张量,形状 (batch_size, 10) """ # 第一个卷积块 x = self.conv1(x) x = self.bn1(x) x = F.relu(x)
x = self.conv2(x) x = self.bn2(x) x = F.relu(x)
x = self.pool1(x)
# 第二个卷积块 x = self.conv3(x) x = self.bn3(x) x = F.relu(x)
x = self.conv4(x) x = self.bn4(x) x = F.relu(x)
x = self.pool2(x)
# 展平 x = x.view(x.size(0), -1)
# 全连接层 x = self.fc1(x) x = F.relu(x) x = self.dropout1(x)
x = self.fc2(x)
return x
class ResidualBlock(nn.Module): """ 残差块实现 包含两个卷积层和一个跳跃连接 """ def __init__(self, in_channels, out_channels, stride=1): super(ResidualBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(out_channels)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(out_channels)
# 如果输入输出维度不同,需要调整残差连接 self.shortcut = nn.Sequential() if stride != 1 or in_channels != out_channels: self.shortcut = nn.Sequential( nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(out_channels) )
def forward(self, x): """ 前向传播 实现: H(x) = F(x) + x """ # 主路径 out = F.relu(self.bn1(self.conv1(x))) out = self.bn2(self.conv2(out))
# 残差连接 out += self.shortcut(x) out = F.relu(out)
return out
class SimpleResNet(nn.Module): """ 简化版ResNet用于CIFAR-10 """ def __init__(self, num_classes=10): super(SimpleResNet, self).__init__()
self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.bn1 = nn.BatchNorm2d(64)
# 残差块 self.layer1 = self._make_layer(64, 64, num_blocks=2, stride=1) self.layer2 = self._make_layer(64, 128, num_blocks=2, stride=2) self.layer3 = self._make_layer(128, 256, num_blocks=2, stride=2)
# 全局平均池化 self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))
# 全连接层 self.fc = nn.Linear(256, num_classes)
def _make_layer(self, in_channels, out_channels, num_blocks, stride): """创建残差块层""" layers = [] layers.append(ResidualBlock(in_channels, out_channels, stride)) for _ in range(1, num_blocks): layers.append(ResidualBlock(out_channels, out_channels, stride=1)) return nn.Sequential(*layers)
def forward(self, x): """前向传播""" out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out) out = self.layer2(out) out = self.layer3(out)
out = self.avg_pool(out) out = out.view(out.size(0), -1) out = self.fc(out)
return out
def train_cifar10(): """训练CIFAR-10分类器"""
# 超参数 batch_size = 128 learning_rate = 0.001 epochs = 50
# 设备 device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"使用设备: {device}\n")
# 数据增强和预处理 transform_train = transforms.Compose([ transforms.RandomCrop(32, padding=4), # 随机裁剪 transforms.RandomHorizontalFlip(), # 随机水平翻转 transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) # CIFAR-10的均值和标准差 ])
transform_test = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)) ])
# 加载数据集 print("加载CIFAR-10数据集...") train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train) test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test)
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2) test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False, num_workers=2)
# CIFAR-10类别 classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
print(f"训练集大小: {len(train_dataset)}") print(f"测试集大小: {len(test_dataset)}") print(f"类别: {classes}\n")
# 创建模型(可以选择CIFAR10CNN或SimpleResNet) # model = CIFAR10CNN().to(device) model = SimpleResNet(num_classes=10).to(device)
print("模型架构:") print(model) print(f"\n总参数量: {sum(p.numel() for p in model.parameters()):,}") print(f"可训练参数量: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}\n")
# 损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=5e-4)
# 学习率调度器 scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
# 训练循环 best_acc = 0.0
print("开始训练...") print("=" * 80)
for epoch in range(epochs): # 训练阶段 model.train() train_loss = 0 correct = 0 total = 0
for batch_idx, (inputs, targets) in enumerate(train_loader): inputs, targets = inputs.to(device), targets.to(device)
optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, targets) loss.backward() optimizer.step()
train_loss += loss.item() _, predicted = outputs.max(1) total += targets.size(0) correct += predicted.eq(targets).sum().item()
if batch_idx % 100 == 0: print(f'Epoch: {epoch+1}/{epochs} | Batch: {batch_idx}/{len(train_loader)} | ' f'Loss: {loss.item():.3f} | Acc: {100.*correct/total:.2f}%')
train_acc = 100. * correct / total avg_train_loss = train_loss / len(train_loader)
# 测试阶段 model.eval() test_loss = 0 correct = 0 total = 0
with torch.no_grad(): for inputs, targets in test_loader: inputs, targets = inputs.to(device), targets.to(device) outputs = model(inputs) loss = criterion(outputs, targets)
test_loss += loss.item() _, predicted = outputs.max(1) total += targets.size(0) correct += predicted.eq(targets).sum().item()
test_acc = 100. * correct / total avg_test_loss = test_loss / len(test_loader)
print(f'\nEpoch {epoch+1}/{epochs}:') print(f'Train Loss: {avg_train_loss:.3f} | Train Acc: {train_acc:.2f}%') print(f'Test Loss: {avg_test_loss:.3f} | Test Acc: {test_acc:.2f}%')
# 保存最佳模型 if test_acc > best_acc: print(f'保存模型... (准确率从 {best_acc:.2f}% 提升到 {test_acc:.2f}%)') best_acc = test_acc torch.save(model.state_dict(), 'best_cifar10_model.pth')
print('-' * 80)
# 更新学习率 scheduler.step()
print(f'\n训练完成! 最佳测试准确率: {best_acc:.2f}%')
return model
# 运行训练if __name__ == "__main__": model = train_cifar10()预期输出:
使用设备: cuda
加载CIFAR-10数据集...训练集大小: 50000测试集大小: 10000类别: ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
总参数量: 1,378,186可训练参数量: 1,378,186
开始训练...================================================================================Epoch: 1/50 | Batch: 0/391 | Loss: 2.305 | Acc: 10.16%...Epoch 1/50:Train Loss: 1.543 | Train Acc: 43.26%Test Loss: 1.234 | Test Acc: 55.42%保存模型... (准确率从 0.00% 提升到 55.42%)...Epoch 50/50:Train Loss: 0.156 | Train Acc: 94.63%Test Loss: 0.412 | Test Acc: 88.72%
训练完成! 最佳测试准确率: 89.15%正则化技术详解
正则化用于防止过拟合,提高模型的泛化能力。
1. L2正则化(权重衰减)
在损失函数中添加权重的L2范数:
对应的梯度更新:
2. L1正则化
添加权重的L1范数,促进稀疏性:
3. Dropout数学原理
Dropout在训练时随机丢弃神经元,丢弃概率为 。
训练时:
除以 是为了保持期望值不变。
测试时: 使用所有神经元,不需要缩放。
4. 早停法(Early Stopping)
监控验证集损失,当验证集损失不再下降时停止训练,防止过拟合。
高级优化技巧
学习率调度策略
1. 步长衰减(Step Decay)
其中 是步长, 是衰减因子(如0.1)。
2. 指数衰减(Exponential Decay)
3. 余弦退火(Cosine Annealing)
其中 是总的训练步数。
梯度裁剪(Gradient Clipping)
防止梯度爆炸,限制梯度的范数:
按值裁剪:
按范数裁剪:
主要应用领域
计算机视觉
深度学习在计算机视觉领域取得了革命性突破。
图像分类
使用CNN对图像进行分类。经典架构包括:
- AlexNet (2012):首次在ImageNet上使用深度CNN,top-5错误率15.3%
- VGGNet (2014):使用更深的网络(16-19层),证明深度的重要性
- ResNet (2015):引入残差连接,解决梯度消失问题,可训练152层
- EfficientNet (2019):通过复合缩放优化网络效率
目标检测
检测图像中的物体及其位置。主要方法:
- R-CNN系列:Region-based CNN
- YOLO:You Only Look Once,实时检测
- SSD:Single Shot Detector
- Faster R-CNN:使用RPN(Region Proposal Network)
图像分割
像素级别的分类任务:
- FCN:全卷积网络
- U-Net:医学图像分割的经典架构
- Mask R-CNN:实例分割
- DeepLab:使用空洞卷积
人脸识别
- FaceNet:使用三元组损失学习人脸嵌入
- DeepFace:Facebook的人脸识别系统
- ArcFace:改进的损失函数,提高识别准确率
自然语言处理
词嵌入(Word Embeddings)
将词语映射到连续向量空间:
Word2Vec的Skip-gram模型目标函数:
其中 是上下文窗口大小。
循环神经网络(RNN)
RNN能够处理序列数据,但存在梯度消失问题。
RNN的前向传播:
LSTM(长短期记忆网络)
LSTM通过门控机制解决长期依赖问题。
LSTM的数学表达:
其中 是sigmoid函数, 是元素级乘法。
Transformer架构
Transformer完全基于注意力机制,是当前NLP的主流架构。
自注意力机制(Self-Attention):
其中:
- (Query)、(Key)、(Value)分别由输入通过线性变换得到
- 是key的维度
- 除以 是为了防止点积过大
多头注意力(Multi-Head Attention):
预训练语言模型
- BERT:双向Transformer,使用掩码语言模型预训练
- GPT:生成式预训练,自回归语言模型
- T5:Text-to-Text Transfer Transformer
- GPT-4:大规模多模态模型
语音识别
- DeepSpeech:端到端语音识别
- WaveNet:生成原始音频波形
- Tacotron:文本转语音(TTS)
- Whisper:OpenAI的多语言语音识别模型
学习路径
1. 数学基础(2-3个月)
线性代数
- 向量和矩阵运算
- 特征值和特征向量
- 矩阵分解(SVD、LU分解等)
- 正交性和投影
微积分
- 导数和偏导数
- 链式法则(反向传播的基础)
- 梯度和Jacobian矩阵
- 泰勒展开
概率论与统计
- 概率分布(高斯分布、伯努利分布等)
- 期望和方差
- 贝叶斯定理
- 最大似然估计
2. 编程基础(1-2个月)
Python核心
# 必备Python技能import numpy as npimport pandas as pdimport matplotlib.pyplot as plt
# NumPy数组操作arr = np.array([[1, 2], [3, 4]])print(arr.shape) # (2, 2)print(arr.T) # 转置print(arr @ arr.T) # 矩阵乘法
# 广播机制a = np.array([1, 2, 3])b = np.array([[1], [2], [3]])print(a + b) # 广播
# Pandas数据处理df = pd.DataFrame({ 'A': [1, 2, 3], 'B': [4, 5, 6]})print(df.describe()) # 统计描述3. 机器学习基础(1-2个月)
监督学习
- 线性回归
- 逻辑回归
- 决策树和随机森林
- 支持向量机(SVM)
非监督学习
- K-means聚类
- PCA降维
- 自编码器
模型评估
- 交叉验证
- 混淆矩阵
- ROC曲线和AUC
- 精确率、召回率、F1分数
4. 深度学习框架(2-3个月)
PyTorch基础
import torchimport torch.nn as nn
# 张量操作x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)print(x.shape)print(x.device)
# 自动微分x = torch.tensor([2.0], requires_grad=True)y = x ** 2 + 3 * x + 1y.backward()print(x.grad) # dy/dx = 2x + 3 = 7
# 定义简单模型model = nn.Sequential( nn.Linear(10, 20), nn.ReLU(), nn.Linear(20, 1))
# 优化器optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 训练循环框架for epoch in range(100): # 前向传播 output = model(input_data) loss = criterion(output, target)
# 反向传播 optimizer.zero_grad() loss.backward() optimizer.step()4. 深度学习框架(2-3个月)
PyTorch基础
import torchimport torch.nn as nn
# 张量操作x = torch.tensor([[1, 2], [3, 4]], dtype=torch.float32)print(x.shape)print(x.device)
# 自动微分x = torch.tensor([2.0], requires_grad=True)y = x ** 2 + 3 * x + 1y.backward()print(x.grad) # dy/dx = 2x + 3 = 7
# 定义简单模型model = nn.Sequential( nn.Linear(10, 20), nn.ReLU(), nn.Linear(20, 1))
# 优化器optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 训练循环框架for epoch in range(100): # 前向传播 output = model(input_data) loss = criterion(output, target)
# 反向传播 optimizer.zero_grad() loss.backward() optimizer.step()迁移学习:利用预训练模型
迁移学习让我们可以利用在大规模数据集上预训练的模型,快速解决新问题。
使用预训练ResNet进行图像分类
import torchimport torch.nn as nnimport torchvision.models as modelsfrom torchvision import transforms
# 加载预训练的ResNet50模型model = models.resnet50(pretrained=True)
# 冻结所有层的参数for param in model.parameters(): param.requires_grad = False
# 替换最后的全连接层num_features = model.fc.in_featuresnum_classes = 10 # 你的数据集类别数model.fc = nn.Linear(num_features, num_classes)
# 只训练最后一层optimizer = torch.optim.Adam(model.fc.parameters(), lr=0.001)
# 或者,微调整个网络(学习率要小)for param in model.parameters(): param.requires_grad = True
optimizer = torch.optim.Adam([ {'params': model.fc.parameters(), 'lr': 0.001}, {'params': model.layer4.parameters(), 'lr': 0.0001}, {'params': model.layer3.parameters(), 'lr': 0.00001}])模型评估与可视化
混淆矩阵
from sklearn.metrics import confusion_matrix, classification_reportimport seaborn as snsimport matplotlib.pyplot as plt
def evaluate_model(model, test_loader, device, class_names): """评估模型并生成混淆矩阵""" model.eval() all_preds = [] all_labels = []
with torch.no_grad(): for inputs, labels in test_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) _, preds = outputs.max(1)
all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy())
# 计算混淆矩阵 cm = confusion_matrix(all_labels, all_preds)
# 绘制混淆矩阵 plt.figure(figsize=(10, 8)) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=class_names, yticklabels=class_names) plt.title('Confusion Matrix') plt.ylabel('True Label') plt.xlabel('Predicted Label') plt.tight_layout() plt.savefig('confusion_matrix.png', dpi=150)
# 打印分类报告 print(classification_report(all_labels, all_preds, target_names=class_names))
return cm
# 使用示例# cm = evaluate_model(model, test_loader, device,# ['class1', 'class2', 'class3'])特征可视化
def visualize_features(model, image, device, layer_name='layer4'): """可视化CNN的特征图""" model.eval()
# 注册钩子以获取中间层输出 features = [] def hook(module, input, output): features.append(output)
# 获取指定层 target_layer = dict(model.named_modules())[layer_name] handle = target_layer.register_forward_hook(hook)
# 前向传播 with torch.no_grad(): image = image.unsqueeze(0).to(device) _ = model(image)
# 移除钩子 handle.remove()
# 可视化特征图 feature_maps = features[0].squeeze(0).cpu() n_features = min(64, feature_maps.shape[0])
fig, axes = plt.subplots(8, 8, figsize=(12, 12)) for i, ax in enumerate(axes.flat): if i < n_features: ax.imshow(feature_maps[i], cmap='viridis') ax.axis('off') else: ax.axis('off')
plt.tight_layout() plt.savefig('feature_maps.png', dpi=150) print(f"特征图已保存到 feature_maps.png")
# 使用示例# visualize_features(model, test_image, device, layer_name='layer3')常见问题与解决方案
1. 过拟合
症状: 训练准确率高,验证准确率低
解决方案:
- 增加训练数据或使用数据增强
- 使用正则化(L2、Dropout)
- 减小模型复杂度
- 早停法(Early Stopping)
2. 欠拟合
症状: 训练和验证准确率都低
解决方案:
- 增加模型复杂度(更多层或更多神经元)
- 训练更长时间
- 减少正则化强度
- 检查数据质量
3. 梯度消失/爆炸
解决方案:
- 使用ReLU等激活函数
- 使用批归一化
- 使用残差连接
- 梯度裁剪(针对梯度爆炸)
- 合适的权重初始化
4. 训练速度慢
解决方案:
- 使用更大的batch size(如果GPU内存允许)
- 使用更快的优化器(如Adam)
- 混合精度训练
- 使用更快的数据加载(num_workers)
- 使用分布式训练
实战技巧总结
超参数调优
# 常用超参数范围hyperparameters = { 'learning_rate': [0.1, 0.01, 0.001, 0.0001], 'batch_size': [16, 32, 64, 128], 'optimizer': ['SGD', 'Adam', 'RMSprop'], 'weight_decay': [0, 1e-5, 1e-4, 1e-3], 'dropout': [0.0, 0.2, 0.5]}
# 网格搜索或随机搜索import itertools
def grid_search(train_loader, val_loader, param_grid): """简单的网格搜索实现""" best_acc = 0 best_params = None
# 生成所有参数组合 keys = param_grid.keys() values = param_grid.values()
for params in itertools.product(*values): param_dict = dict(zip(keys, params)) print(f"Testing: {param_dict}")
# 创建并训练模型 model = create_model() val_acc = train_and_validate(model, param_dict, train_loader, val_loader)
if val_acc > best_acc: best_acc = val_acc best_params = param_dict
return best_params, best_acc模型保存与加载
# 保存完整模型torch.save(model, 'complete_model.pth')
# 只保存参数(推荐)torch.save(model.state_dict(), 'model_weights.pth')
# 保存训练状态(用于断点续训)checkpoint = { 'epoch': epoch, 'model_state_dict': model.state_dict(), 'optimizer_state_dict': optimizer.state_dict(), 'loss': loss, 'best_acc': best_acc}torch.save(checkpoint, 'checkpoint.pth')
# 加载模型model = TheModelClass()model.load_state_dict(torch.load('model_weights.pth'))model.eval() # 设置为评估模式
# 加载检查点checkpoint = torch.load('checkpoint.pth')model.load_state_dict(checkpoint['model_state_dict'])optimizer.load_state_dict(checkpoint['optimizer_state_dict'])epoch = checkpoint['epoch']loss = checkpoint['loss']实践建议
- 从小项目开始:选择简单的数据集(如MNIST)进行练习
- 理解原理:不要只调用API,要理解背后的数学原理
- 动手实现:尝试从零实现简单的神经网络
- 阅读论文:关注顶会论文(CVPR、NeurIPS、ICML等)
- 参与竞赛:在Kaggle等平台上参与实际项目
推荐资源
在线课程
- 吴恩达的深度学习专项课程
- Fast.ai实用深度学习课程
- CS231n:卷积神经网络视觉识别
书籍推荐
- 《深度学习》(Goodfellow等著)
- 《动手学深度学习》
- 《Python深度学习》
社区资源
- GitHub开源项目
- Hugging Face模型库
- Papers with Code
总结
深度学习是一个快速发展的领域,需要持续学习和实践。从基础开始,循序渐进,注重理论与实践相结合,就能在这个领域取得进步。
记住:实践是学习深度学习最好的方式!