一、全连接神经网络概述
全连接神经网络(Fully Connected Neural Network),也称为多层感知机(Multi-Layer Perceptron, MLP),是深度学习中最基础的神经网络结构之一。它由多个全连接层组成,每一层的神经元与下一层的所有神经元相连接。
1.1 神经网络基本结构
一个典型的全连接神经网络包含以下组成部分:
输入层:接收原始数据
隐藏层:进行特征提取和转换(可以有多层)
输出层:产生最终预测结果
权重(Weights):连接神经元之间的参数
偏置(Bias):每个神经元的附加参数
激活函数:引入非线性因素
1.2 为什么需要激活函数?
如果不使用激活函数,无论神经网络有多少层,输出都是输入的线性组合,这与单层神经网络等价。这是因为多层线性变换可以被简化为一个等效的单层线性变换。
具体来说:
- 假设一个两层神经网络,第一层的权重矩阵为W1,第二层为W2
- 没有激活函数时,输出y = W2(W1x) = (W2W1)x
- 这个结果等价于一个单层网络y = W'x,其中W'=W2W1
这种线性叠加会导致神经网络的表达能力被大大限制:
- 无法学习非线性关系(如XOR问题)
- 无法实现复杂的特征转换
- 无法逼近任意函数(根据通用近似定理)
常用的激活函数包括:
ReLU(Rectified Linear Unit)
- 公式:f(x) = max(0, x)
- 特点:
- 计算简单高效,只需判断阈值
- 有效缓解深度网络中的梯度消失问题
- 存在"死亡ReLU"现象(神经元可能永远不被激活)
- 应用场景:CNN、DNN等深层网络的隐藏层
- 示例:AlexNet、ResNet等经典网络均采用ReLU
Sigmoid
- 公式:f(x) = 1 / (1 + e^-x)
- 特点:
- 输出范围(0,1),可解释为概率
- 存在梯度消失问题(当输入绝对值较大时)
- 计算涉及指数运算,相对复杂
- 应用场景:
- 二分类问题的输出层
- 传统神经网络的隐藏层(现已较少使用)
- 示例:逻辑回归中的默认激活函数
Tanh(双曲正切函数)
- 公式:f(x) = (e^x - e^-x) / (e^x + e^-x)
- 特点:
- 输出范围(-1,1),以0为中心
- 梯度比sigmoid更陡峭
- 同样存在梯度消失问题
- 应用场景:RNN/LSTM等序列模型的隐藏层
- 示例:LSTM中用于控制记忆单元状态的更新
Softmax
- 公式:f(x_i) = e^x_i / Σ(e^x_j)
- 特点:
- 将输出转化为概率分布(总和为1)
- 放大最大值的概率,抑制较小值
- 通常配合交叉熵损失函数使用
- 应用场景:
- 多分类问题的输出层
- 注意力机制中的注意力权重计算
- 示例:图像分类网络(如VGG、Inception)的最后一层
其他常见激活函数
- Leaky ReLU:f(x) = max(αx, x)(α通常取0.01)
- ELU:f(x) = x (x>0), α(e^x-1) (x≤0)
- Swish:f(x) = x * sigmoid(βx)(Google提出的自门控激活函数)
注:在实际应用中,ReLU及其变种(如Leaky ReLU)是目前最常用的隐藏层激活函数,而输出层根据任务类型选择Sigmoid(二分类)或Softmax(多分类)。选择激活函数时需要权衡计算效率、梯度特性和网络深度等因素。
二、使用PyTorch构建全连接神经网络
PyTorch是一个开源的Python机器学习库,基于Torch,广泛应用于计算机视觉和自然语言处理等应用领域。下面我们将详细介绍如何使用PyTorch构建全连接神经网络。
2.1 环境准备
首先需要安装PyTorch:
# 使用pip安装PyTorch CPU版本
# pip install torch torchvision
# 如果有NVIDIA GPU,可以安装CUDA版本
# pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu113
2.2 导入必要的库
import torch
from torch import nn # 神经网络模块
from torch import optim # 优化器模块
from torch.nn import functional as F # 常用函数模块
import numpy as np
from sklearn.datasets import make_classification # 生成分类数据
from sklearn.model_selection import train_test_split # 数据集划分
from sklearn.preprocessing import StandardScaler # 数据标准化
2.3 数据准备
我们使用scikit-learn生成一个模拟的二分类数据集:
# 设置随机种子保证可复现性
torch.manual_seed(42)
np.random.seed(42)
# 生成模拟数据
# n_samples: 样本数量
# n_features: 特征数量
# n_classes: 类别数量
# n_informative: 有信息的特征数量
X, y = make_classification(n_samples=1000, n_features=10,
n_classes=2, n_informative=8,
random_state=42)
# 将数据转换为PyTorch张量
X = torch.from_numpy(X).float() # 转换为float32类型
y = torch.from_numpy(y).float().view(-1, 1) # 转换为float32并调整形状
# 数据标准化
scaler = StandardScaler()
X = torch.from_numpy(scaler.fit_transform(X.numpy())).float()
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)
# 转换为PyTorch的Dataset和DataLoader
from torch.utils.data import TensorDataset, DataLoader
train_dataset = TensorDataset(X_train, y_train)
test_dataset = TensorDataset(X_test, y_test)
# DataLoader参数详解:
# dataset: 数据集
# batch_size: 每批数据量
# shuffle: 是否打乱数据
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
2.4 定义模型结构
使用PyTorch的nn.Module
类定义我们的全连接神经网络:
class MLP(nn.Module):
def __init__(self, input_size):
"""
初始化MLP模型
参数:
input_size: 输入特征维度
"""
super(MLP, self).__init__()
# 第一个全连接层
# nn.Linear参数:
# in_features: 输入特征数
# out_features: 输出特征数(神经元数量)
# bias: 是否使用偏置项(默认为True)
self.fc1 = nn.Linear(input_size, 64)
# 第二个全连接层
self.fc2 = nn.Linear(64, 32)
# 输出层
self.fc3 = nn.Linear(32, 1)
# Dropout层,防止过拟合
# p: 丢弃概率
self.dropout = nn.Dropout(p=0.2)
def forward(self, x):
"""
前向传播
参数:
x: 输入数据
返回:
模型输出
"""
# 第一层: 线性变换 + ReLU激活 + Dropout
x = F.relu(self.fc1(x))
x = self.dropout(x)
# 第二层: 线性变换 + ReLU激活 + Dropout
x = F.relu(self.fc2(x))
x = self.dropout(x)
# 输出层: 线性变换 + Sigmoid激活(二分类问题)
x = torch.sigmoid(self.fc3(x))
return x
2.5 模型实例化与参数查看
# 实例化模型
input_size = X_train.shape[1] # 输入特征维度
model = MLP(input_size)
# 打印模型结构
print(model)
# 查看模型参数
for name, param in model.named_parameters():
print(f"Layer: {name} | Size: {param.size()} | Values : {param[:2]} \n")
2.6 定义损失函数和优化器
# 定义损失函数
# nn.BCELoss(): 二分类交叉熵损失函数
# 注意:使用BCELoss时,模型输出需要经过Sigmoid激活
criterion = nn.BCELoss()
# 定义优化器
# optim.SGD参数:
# params: 要优化的参数(通常为model.parameters())
# lr: 学习率(learning rate)
# momentum: 动量因子(0-1)
# weight_decay: L2正则化系数
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
# 也可以使用Adam优化器
# optimizer = optim.Adam(model.parameters(), lr=0.001, betas=(0.9, 0.999))
2.7 训练模型
# 训练参数
num_epochs = 100
# 记录训练过程中的损失和准确率
train_losses = []
test_losses = []
train_accuracies = []
test_accuracies = []
for epoch in range(num_epochs):
# 训练模式
model.train()
running_loss = 0.0
correct = 0
total = 0
for inputs, labels in train_loader:
# 梯度清零
optimizer.zero_grad()
# 前向传播
outputs = model(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播
loss.backward()
# 更新权重
optimizer.step()
# 统计信息
running_loss += loss.item()
predicted = (outputs > 0.5).float()
total += labels.size(0)
correct += (predicted == labels).sum().item()
# 计算训练集上的平均损失和准确率
train_loss = running_loss / len(train_loader)
train_accuracy = 100 * correct / total
train_losses.append(train_loss)
train_accuracies.append(train_accuracy)
# 测试模式
model.eval()
test_running_loss = 0.0
test_correct = 0
test_total = 0
with torch.no_grad(): # 不计算梯度
for inputs, labels in test_loader:
outputs = model(inputs)
loss = criterion(outputs, labels)
test_running_loss += loss.item()
predicted = (outputs > 0.5).float()
test_total += labels.size(0)
test_correct += (predicted == labels).sum().item()
# 计算测试集上的平均损失和准确率
test_loss = test_running_loss / len(test_loader)
test_accuracy = 100 * test_correct / test_total
test_losses.append(test_loss)
test_accuracies.append(test_accuracy)
# 打印训练信息
print(f'Epoch [{epoch+1}/{num_epochs}], '
f'Train Loss: {train_loss:.4f}, Train Acc: {train_accuracy:.2f}%, '
f'Test Loss: {test_loss:.4f}, Test Acc: {test_accuracy:.2f}%')
2.8 可视化训练过程
import matplotlib.pyplot as plt
# 绘制损失曲线
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.plot(train_losses, label='Train Loss')
plt.plot(test_losses, label='Test Loss')
plt.title('Training and Test Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
# 绘制准确率曲线
plt.subplot(1, 2, 2)
plt.plot(train_accuracies, label='Train Accuracy')
plt.plot(test_accuracies, label='Test Accuracy')
plt.title('Training and Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.tight_layout()
plt.show()
三、全连接神经网络的高级技巧
3.1 权重初始化
良好的权重初始化可以加速收敛并提高模型性能:
# 自定义权重初始化
def init_weights(m):
if isinstance(m, nn.Linear):
# Xavier/Glorot初始化
nn.init.xavier_uniform_(m.weight)
# 偏置初始化为0
nn.init.zeros_(m.bias)
# 应用初始化
model.apply(init_weights)
3.2 学习率调度
动态调整学习率可以提高训练效果:
# 定义学习率调度器
# optim.lr_scheduler.StepLR参数:
# optimizer: 优化器
# step_size: 多少epoch后调整学习率
# gamma: 学习率衰减因子
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
# 在训练循环中添加
# scheduler.step()
3.3 模型保存与加载
# 保存模型
torch.save(model.state_dict(), 'mlp_model.pth')
# 加载模型
loaded_model = MLP(input_size)
loaded_model.load_state_dict(torch.load('mlp_model.pth'))
loaded_model.eval()
完整示例:使用PyTorch创建并训练全连接神经网络
下面是一个完整的示例代码,展示了如何使用PyTorch创建、训练和评估一个全连接神经网络(MLP),包含详细注释和最佳实践。
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import matplotlib.pyplot as plt
import numpy as np
# 1. 设置随机种子保证可复现性
torch.manual_seed(42)
np.random.seed(42)
# 2. 数据准备
def prepare_data():
"""生成并准备训练数据"""
# 生成模拟数据集 (1000个样本,20个特征,2个类别)
X, y = make_classification(n_samples=1000, n_features=20, n_classes=2,
n_informative=15, random_state=42)
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 转换为PyTorch张量
X = torch.from_numpy(X).float()
y = torch.from_numpy(y).float().view(-1, 1) # 调整形状为(n_samples, 1)
# 划分训练集和测试集 (80%训练,20%测试)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)
# 创建DataLoader
train_dataset = TensorDataset(X_train, y_train)
test_dataset = TensorDataset(X_test, y_test)
train_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False)
return train_loader, test_loader, X_train.shape[1]
# 3. 定义模型
class MLP(nn.Module):
"""全连接神经网络模型"""
def __init__(self, input_size):
"""
初始化MLP
参数:
input_size (int): 输入特征维度
"""
super(MLP, self).__init__()
# 网络结构
self.fc1 = nn.Linear(input_size, 128) # 第一隐藏层
self.fc2 = nn.Linear(128, 64) # 第二隐藏层
self.fc3 = nn.Linear(64, 32) # 第三隐藏层
self.fc4 = nn.Linear(32, 1) # 输出层
# Dropout层 (防止过拟合)
self.dropout = nn.Dropout(p=0.3)
# 批归一化层 (加速训练)
self.bn1 = nn.BatchNorm1d(128)
self.bn2 = nn.BatchNorm1d(64)
self.bn3 = nn.BatchNorm1d(32)
def forward(self, x):
"""前向传播"""
x = F.relu(self.bn1(self.fc1(x)))
x = self.dropout(x)
x = F.relu(self.bn2(self.fc2(x)))
x = self.dropout(x)
x = F.relu(self.bn3(self.fc3(x)))
x = self.dropout(x)
x = torch.sigmoid(self.fc4(x)) # 二分类使用sigmoid
return x
def initialize_weights(self):
"""自定义权重初始化"""
for m in self.modules():
if isinstance(m, nn.Linear):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
# 4. 训练和评估函数
def train_model(model, train_loader, test_loader, num_epochs=100):
"""训练模型并记录指标"""
# 定义损失函数和优化器
criterion = nn.BCELoss() # 二分类交叉熵损失
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-4)
# 学习率调度器
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.1, patience=10, verbose=True)
# 记录指标
history = {
'train_loss': [],
'test_loss': [],
'train_acc': [],
'test_acc': []
}
for epoch in range(num_epochs):
# 训练阶段
model.train()
running_loss = 0.0
correct = 0
total = 0
for inputs, labels in train_loader:
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
predicted = (outputs > 0.5).float()
total += labels.size(0)
correct += (predicted == labels).sum().item()
# 计算训练集指标
train_loss = running_loss / len(train_loader)
train_acc = 100 * correct / total
history['train_loss'].append(train_loss)
history['train_acc'].append(train_acc)
# 评估阶段
model.eval()
test_loss = 0.0
test_correct = 0
test_total = 0
with torch.no_grad():
for inputs, labels in test_loader:
outputs = model(inputs)
loss = criterion(outputs, labels)
test_loss += loss.item()
predicted = (outputs > 0.5).float()
test_total += labels.size(0)
test_correct += (predicted == labels).sum().item()
# 计算测试集指标
test_loss /= len(test_loader)
test_acc = 100 * test_correct / test_total
history['test_loss'].append(test_loss)
history['test_acc'].append(test_acc)
# 更新学习率
scheduler.step(test_loss)
# 打印进度
print(f'Epoch [{epoch+1}/{num_epochs}] | '
f'Train Loss: {train_loss:.4f}, Acc: {train_acc:.2f}% | '
f'Test Loss: {test_loss:.4f}, Acc: {test_acc:.2f}%')
return history
def plot_history(history):
"""绘制训练曲线"""
plt.figure(figsize=(12, 5))
# 损失曲线
plt.subplot(1, 2, 1)
plt.plot(history['train_loss'], label='Train Loss')
plt.plot(history['test_loss'], label='Test Loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
# 准确率曲线
plt.subplot(1, 2, 2)
plt.plot(history['train_acc'], label='Train Accuracy')
plt.plot(history['test_acc'], label='Test Accuracy')
plt.title('Training and Validation Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.tight_layout()
plt.show()
# 5. 主程序
def main():
# 准备数据
train_loader, test_loader, input_size = prepare_data()
# 初始化模型
model = MLP(input_size)
model.initialize_weights() # 自定义权重初始化
# 打印模型结构
print(model)
# 训练模型
history = train_model(model, train_loader, test_loader, num_epochs=50)
# 绘制训练曲线
plot_history(history)
# 保存模型
torch.save(model.state_dict(), 'mlp_model.pth')
print("Model saved to mlp_model.pth")
if __name__ == '__main__':
main()
四、全连接神经网络的应用场景与局限性
4.1 适用场景
结构化数据:如表格数据、金融数据等
小型图像分类:如MNIST手写数字识别
简单回归问题:如房价预测
特征重要性分析:通过权重分析特征重要性
4.2 局限性
参数量大:全连接导致参数数量随网络规模快速增长
局部模式不敏感:对图像等具有局部结构的数据处理效率低
容易过拟合:特别是在深层网络中
梯度消失/爆炸:深层网络训练困难
五、总结
通过本教程,我们详细介绍了全连接神经网络的原理和PyTorch实现方法,包括:
数据准备与预处理
模型定义与参数初始化
损失函数与优化器选择
训练过程与评估方法
高级技巧与最佳实践
全连接神经网络虽然结构简单,但仍然是深度学习的重要基础。掌握MLP的原理和实现方法,有助于理解更复杂的神经网络结构。在实际应用中,可以根据具体问题调整网络结构、激活函数、优化器等超参数,以获得最佳性能。
希望本篇文章能帮助你快速入门全连接神经网络,并为后续学习更复杂的深度学习模型打下坚实基础!