Softmax
主要理论
Softmax 函数是一种将实数向量映射为概率分布的函数,常用于多分类问题的输出层。其核心思想是通过指数运算放大差异,再归一化得到概率。对于一个输入向量 z = [z₁, z₂, ..., zₙ],Softmax 的计算公式为:
- 性质:输出值的范围为 (0,1),且所有输出之和为 1,符合概率分布的特性。
- 梯度:在反向传播中,Softmax 的梯度计算与交叉熵损失结合时形式简洁,利于优化。
实现代码
Python 原生实现
python
import numpy as np
def softmax(z):
exp_z = np.exp(z - np.max(z)) # 防溢出优化
return exp_z / np.sum(exp_z, axis=0)
PyTorch 实现
python
import torch
import torch.nn as nn
# 直接调用内置函数
softmax_layer = nn.Softmax(dim=1) # dim 指定计算维度
output = softmax_layer(torch.randn(3, 5))
注意事项
-
数值稳定性 :原始公式可能因指数爆炸导致溢出,需通过减去最大值(
z - max(z))优化。 -
维度处理 :在多维张量中需明确指定计算维度(如
dim=1或axis=-1)。 -
与交叉熵结合 :深度学习框架中常将
LogSoftmax与NLLLoss结合使用,提升数值稳定性。
多层感知机
多层感知机(MLP)理论基础
多层感知机(Multilayer Perceptron, MLP)是一种前馈人工神经网络,由输入层、隐藏层(至少一层)和输出层组成。其核心理论基于以下关键点:
激活函数
MLP通过非线性激活函数引入非线性能力。常用激活函数包括:
反向传播算法
通过链式法则计算损失函数对权重的梯度,使用梯度下降法更新参数。损失函数如交叉熵(分类)或均方误差(回归)。
权重初始化
常用Xavier或He初始化方法,避免梯度消失/爆炸问题。
实现代码
以下是一个完整的MLP实现示例,包含训练和测试流程:
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
# 定义MLP模型
class MLP(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super(MLP, self).__init__()
self.layers = nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, output_dim)
)
def forward(self, x):
return self.layers(x)
# 参数设置
input_dim = 784 # 例如MNIST图像展平后
hidden_dim = 128
output_dim = 10
lr = 0.001
epochs = 10
batch_size = 64
# 模拟数据加载(实际使用时替换为真实数据)
x_train = torch.randn(1000, input_dim)
y_train = torch.randint(0, output_dim, (1000,))
train_dataset = TensorDataset(x_train, y_train)
train_loader = DataLoader(train_dataset, batch_size=batch_size)
# 初始化模型和优化器
model = MLP(input_dim, hidden_dim, output_dim)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)
# 训练循环
for epoch in range(epochs):
for batch_x, batch_y in train_loader:
optimizer.zero_grad()
outputs = model(batch_x)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}')
# 测试函数(示例)
def test(model, test_loader):
model.eval()
correct = 0
with torch.no_grad():
for x, y in test_loader:
outputs = model(x)
_, predicted = torch.max(outputs.data, 1)
correct += (predicted == y).sum().item()
accuracy = 100 * correct / len(test_loader.dataset)
print(f'Accuracy: {accuracy:.2f}%')
关键改进技巧
正则化方法
-
Dropout层:在训练时随机失活神经元
-
L2正则化:通过优化器的
weight_decay参数实现
批量归一化
在隐藏层后添加nn.BatchNorm1d层,加速收敛:
python
nn.Sequential(
nn.Linear(input_dim, hidden_dim),
nn.BatchNorm1d(hidden_dim),
nn.ReLU()
)
学习率调度
使用torch.optim.lr_scheduler动态调整学习率:
python
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
# 每个epoch后调用 scheduler.step()
深度学习计算
层(Layers)和块(Blocks)
在深度学习中,网络通常由多个层堆叠而成。每一层接收输入数据,进行变换(如线性变换、非线性激活),并产生输出。多个层可以组合成一个"块",块本身也可以被视为一个更大的层,这种模块化设计使得网络构建更灵活。
代码示例 (PyTorch): 定义一个简单的多层感知机 (MLP) 块
python
import torch.nn as nn
class MLPBlock(nn.Module):
def __init__(self, input_dim, hidden_dim, output_dim):
super().__init__()
self.linear1 = nn.Linear(input_dim, hidden_dim) # 全连接层1
self.relu = nn.ReLU() # 激活层
self.linear2 = nn.Linear(hidden_dim, output_dim) # 全连接层2
def forward(self, x):
x = self.linear1(x) # 线性变换
x = self.relu(x) # 非线性激活
x = self.linear2(x) # 线性变换
return x
# 使用块
model = MLPBlock(input_dim=784, hidden_dim=256, output_dim=10)
参数管理
深度学习模型的核心是其可学习的参数(如权重 w和偏置 b)。我们需要有效地访问、初始化和修改这些参数。
-
访问参数:
python# 获取模型中所有参数的迭代器 for name, param in model.named_parameters(): print(f"Parameter name: {name}, Shape: {param.shape}") # 访问特定层的权重 (例如第一个全连接层的权重) weight = model.linear1.weight.data -
初始化参数: 好的初始化对训练至关重要(例如 Xavier/Glorot 初始化)。
python# 对特定层应用初始化 def init_weights(m): if type(m) == nn.Linear: nn.init.xavier_uniform_(m.weight) # Xavier 初始化权重 nn.init.zeros_(m.bias) # 初始化偏置为0 model.apply(init_weights) # 将初始化函数应用到模型的每个子模块 -
绑定参数: 有时需要共享参数(如词嵌入层)。
python# 假设有两个线性层,希望共享权重 shared_layer = nn.Linear(20, 20) layer1 = shared_layer layer2 = shared_layer # layer1 和 layer2 现在共享相同的权重和偏置
参数初始化
初始化对网络能否有效学习有重大影响。常见初始化方法:
-
Xavier/Glorot 初始化: 适用于 tanh, sigmoid 等激活函数,旨在保持各层激活值和梯度的方差稳定。假设激活函数关于0对称且导数在0处为1。
-
python
PyTorch 实现:nn.init.xavier_uniform_(), nn.init.xavier_normal_()
-
He/Kaiming 初始化: 适用于 ReLU 及其变种,修正了 Xavier 假设的不足。
-
python
PyTorch 实现:nn.init.kaiming_uniform_(), nn.init.kaiming_normal_()
- 常数/固定值初始化: 如将所有偏置初始化为零
nn.init.zeros_(),或权重初始化为常数。
自定义层
当内置层不满足需求时,可以自定义层。需要继承 nn.Module 并定义 __init__ (初始化参数) 和 forward (定义前向传播逻辑) 方法。
代码示例 (PyTorch): 自定义一个带 'same' padding 的卷积层
python
class CustomConv2d(nn.Module):
def __init__(self, in_channels, out_channels, kernel_size, stride=1):
super().__init__()
# 计算所需的 padding 以实现 'same' 输出尺寸
padding = (kernel_size - 1) // 2 # 仅对奇数 kernel_size 有效
self.conv = nn.Conv2d(
in_channels, out_channels, kernel_size, stride=stride, padding=padding
)
def forward(self, x):
return self.conv(x)
# 使用自定义层
layer = CustomConv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1)
读写文件(模型保存与加载)
训练好的模型需要保存以便后续使用或部署。需要保存模型的结构和参数。
-
保存整个模型 (结构 + 状态字典):
pythontorch.save(model, 'model.pth') # 保存整个模型 -
仅保存状态字典 (推荐): 更轻量,兼容性更好。
pythontorch.save(model.state_dict(), 'model_state_dict.pth') # 仅保存参数 -
加载模型:
python# 加载整个模型 (需要模型类定义在作用域内) model = torch.load('model.pth') # 加载状态字典到现有模型实例 model.load_state_dict(torch.load('model_state_dict.pth')) model.eval() # 切换到评估模式(影响 dropout, batchnorm 等)
GPU 计算
利用 GPU 加速计算是深度学习的常态。核心是确保模型参数和输入数据都在 GPU 上。
-
检测 GPU 可用性:
pythondevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print(f"Using device: {device}") -
将模型和数据移至 GPU:
pythonmodel = model.to(device) # 将模型参数移至 GPU # 训练时,将每个 batch 的数据移至 GPU for X, y in train_loader: X, y = X.to(device), y.to(device) # ... 训练步骤 ... -
注意: 模型和输入数据必须在同一个设备上才能进行计算。
to(device)操作会记录梯度计算需求。