前言
提醒:
文章内容为方便作者自己后日复习与查阅而进行的书写与发布,其中引用内容都会使用链接表明出处(如有侵权问题,请及时联系)。
其中内容多为一次书写,缺少检查与订正,如有问题或其他拓展及意见建议,欢迎评论区讨论交流。
文章目录
- 前言
- 数据集
- 全连接神经网络
- 网络结构
- 数学表示
- 总结
- 数学公式总结
- 激活函数
- 训练过程(反向传播)
- 总结
- 项目实例
数据集
数据集可参考:MNIST数据集_CNN
本文章采用全连接神经网络进行分析。
全连接神经网络
全连接神经网络(Fully Connected Neural Network,简称 FCNN 或 DNN,即深度神经网络)是一种最基础的神经网络结构。在这种网络中,每一层的每个神经元都与上一层的每个神经元连接,因此称为“全连接”。
网络结构
全连接网络由多个层组成,常见的层包括输入层、隐藏层和输出层。每一层之间的连接是全连接的,即每一层的每个神经元都与上一层的所有神经元相连。
假设我们有一个包含 L L L 层的神经网络,输入层为 x ∈ R n x \in \mathbb{R}^{n} x∈Rn(输入数据是 n n n 维的向量),经过 L − 1 L-1 L−1 个隐藏层,最后输出层为 y ∈ R m y \in \mathbb{R}^{m} y∈Rm(输出是 m m m 维的向量)。
数学表示
假设神经网络的第 l l l 层有 N l N_l Nl 个神经元,层与层之间的连接通过权重矩阵和偏置向量表示。
-
输入层到隐藏层的计算:
每一层的神经元输出是由前一层神经元加权求和之后,通过一个激活函数进行非线性变换得到的。对于第 l l l 层神经网络(假设 l ≥ 1 l \geq 1 l≥1),其输入是上一层的输出 a [ l − 1 ] \mathbf{a}^{[l-1]} a[l−1],输出是该层的激活值 a [ l ] \mathbf{a}^{[l]} a[l]。
-
加权求和:第 l l l 层的加权求和公式为
z [ l ] = W [ l ] a [ l − 1 ] + b [ l ] , \mathbf{z}^{[l]} = \mathbf{W}^{[l]} \mathbf{a}^{[l-1]} + \mathbf{b}^{[l]}, z[l]=W[l]a[l−1]+b[l],
其中, W [ l ] ∈ R N l × N l − 1 \mathbf{W}^{[l]} \in \mathbb{R}^{N_l \times N_{l-1}} W[l]∈RNl×Nl−1 是该层的权重矩阵, b [ l ] ∈ R N l \mathbf{b}^{[l]} \in \mathbb{R}^{N_l} b[l]∈RNl 是偏置向量, a [ l − 1 ] ∈ R N l − 1 \mathbf{a}^{[l-1]} \in \mathbb{R}^{N_{l-1}} a[l−1]∈RNl−1 是上一层的激活值。 -
激活函数:加权和 z [ l ] \mathbf{z}^{[l]} z[l] 会通过激活函数(如 ReLU、Sigmoid 或 Tanh)得到当前层的激活值:
a [ l ] = f ( z [ l ] ) , \mathbf{a}^{[l]} = f(\mathbf{z}^{[l]}), a[l]=f(z[l]),
其中 f ( ⋅ ) f(\cdot) f(⋅) 表示激活函数。
-
-
输出层的计算:
最后,输出层的计算通常也是类似的,输出是最终的预测值 y \mathbf{y} y。假设输出层没有激活函数(或者使用 softmax 激活函数用于分类问题),那么我们有:
y = W [ L ] a [ L − 1 ] + b [ L ] , \mathbf{y} = \mathbf{W}^{[L]} \mathbf{a}^{[L-1]} + \mathbf{b}^{[L]}, y=W[L]a[L−1]+b[L],
其中 W [ L ] \mathbf{W}^{[L]} W[L] 是输出层的权重矩阵, b [ L ] \mathbf{b}^{[L]} b[L] 是输出层的偏置。
总结
因此,神经网络的前向传播过程可以概括为:
- 输入层到隐藏层:每一层的输入是上一层的输出,经过加权求和和激活函数得到输出。
- 最终输出层:输出层的计算与隐藏层类似,最终输出模型的预测结果。
数学公式总结
-
对于第 l l l 层:
z [ l ] = W [ l ] a [ l − 1 ] + b [ l ] , \mathbf{z}^{[l]} = \mathbf{W}^{[l]} \mathbf{a}^{[l-1]} + \mathbf{b}^{[l]}, z[l]=W[l]a[l−1]+b[l],
a [ l ] = f ( z [ l ] ) , \mathbf{a}^{[l]} = f(\mathbf{z}^{[l]}), a[l]=f(z[l]),
其中 f ( ⋅ ) f(\cdot) f(⋅) 是激活函数。 -
对于输出层:
y = W [ L ] a [ L − 1 ] + b [ L ] . \mathbf{y} = \mathbf{W}^{[L]} \mathbf{a}^{[L-1]} + \mathbf{b}^{[L]}. y=W[L]a[L−1]+b[L].
激活函数
在每一层的计算中,激活函数是引入非线性的关键。常用的激活函数包括:
- Sigmoid 函数: f ( x ) = 1 1 + e − x f(x) = \frac{1}{1 + e^{-x}} f(x)=1+e−x1,常用于二分类问题。
- Tanh 函数: f ( x ) = e x − e − x e x + e − x f(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} f(x)=ex+e−xex−e−x,输出范围是 ([-1, 1])。
- ReLU(Rectified Linear Unit): f ( x ) = max ( 0 , x ) f(x) = \max(0, x) f(x)=max(0,x),对大多数问题表现良好,尤其是在深度学习中。
- Softmax:用于多分类问题,输出的是概率分布。
训练过程(反向传播)
神经网络的训练主要依赖于 反向传播算法(Backpropagation)。通过计算损失函数的梯度,使用梯度下降法更新权重和偏置,使得网络的预测误差最小化。
-
损失函数:
常用的损失函数包括:- 均方误差(MSE):用于回归问题。
- 交叉熵损失:用于分类问题。
-
反向传播:
通过链式法则计算每一层的梯度,并更新每一层的权重和偏置。假设 L \mathcal{L} L 是损失函数,更新规则为:
W [ l ] ← W [ l ] − η ∂ L ∂ W [ l ] , \mathbf{W}^{[l]} \leftarrow \mathbf{W}^{[l]} - \eta \frac{\partial \mathcal{L}}{\partial \mathbf{W}^{[l]}}, W[l]←W[l]−η∂W[l]∂L,
b [ l ] ← b [ l ] − η ∂ L ∂ b [ l ] , \mathbf{b}^{[l]} \leftarrow \mathbf{b}^{[l]} - \eta \frac{\partial \mathcal{L}}{\partial \mathbf{b}^{[l]}}, b[l]←b[l]−η∂b[l]∂L,
其中, η \eta η 是学习率, ∂ L ∂ W [ l ] \frac{\partial \mathcal{L}}{\partial \mathbf{W}^{[l]}} ∂W[l]∂L 和 ∂ L ∂ b [ l ] \frac{\partial \mathcal{L}}{\partial \mathbf{b}^{[l]}} ∂b[l]∂L 分别是权重和偏置的梯度。
总结
全连接神经网络是一种简单但非常强大的模型,它可以通过多层非线性变换来学习复杂的映射关系。在实践中,通常使用多个隐藏层来构建深度神经网络,并结合适当的优化算法(如梯度下降、Adam等)进行训练。
项目实例
import torch
import torch.nn as nn
import torchvision
import torchvision.transforms as transforms# 设置设备为 GPU(如果可用),否则使用 CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# 设置网络的超参数
input_size = 784 # 输入层的大小(MNIST图片28x28,扁平化后是784维)
hidden_size = 500 # 隐藏层的神经元数量
num_classes = 10 # 输出类别数(MNIST有10个数字分类)
num_epochs = 5 # 训练的轮数
batch_size = 100 # 每个批次的数据量
learning_rate = 0.001 # 学习率# 下载并加载 MNIST 训练数据集
train_dataset = torchvision.datasets.MNIST(root='data', train=True, # 训练数据集download=True, # 如果数据集不存在就下载transform=transforms.ToTensor()) # 将图片转换为Tensor格式# 下载并加载 MNIST 测试数据集
test_dataset = torchvision.datasets.MNIST(root='data', train=False, # 测试数据集transform=transforms.ToTensor(), # 同样转换为Tensor格式download=True)# 使用DataLoader将数据集加载为可迭代的批次,自动打乱训练数据
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)# 测试数据集的DataLoader,不进行打乱
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)# 打印训练集和测试集的长度
print(len(train_dataset), len(test_dataset))# 打印训练集和测试集的第一个样本的图像和标签
print(train_dataset[0][0].shape, train_dataset[0][1])
print(test_dataset[0][0].shape, test_dataset[0][1])# 打印测试集的一个批次的图像和标签形状
for X, y in test_loader:print(f"Shape of X [N, C, H, W]: {X.shape}") # N:批次大小, C:通道数, H:高度, W:宽度print(f"Shape of y: {y.shape} {y.dtype}") # y: 标签的形状和类型break # 只查看一个批次
class NeuralNet(nn.Module):def __init__(self, input_size, hidden_size, num_classes):super(NeuralNet, self).__init__()# 第一个全连接层,输入784维,输出500维self.fc1 = nn.Linear(input_size, hidden_size)# 激活函数,使用Sigmoidself.sigmoid = nn.Sigmoid()# 第二个全连接层,输入500维,输出10维(对应10个类别)self.fc2 = nn.Linear(hidden_size, num_classes) # 定义前向传播过程def forward(self, x):out = self.fc1(x) # 输入经过第一层out = self.sigmoid(out) # 激活函数out = self.fc2(out) # 输入经过第二层return out
# 实例化神经网络模型并转移到GPU(如果可用)
model = NeuralNet(input_size, hidden_size, num_classes).to(device)# 使用均方误差(MSELoss)作为损失函数,这对于多类分类问题通常不太合适,但这里示范用作教学
criterion = nn.MSELoss()# 使用Adam优化器,学习率为0.001
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate) # 获取训练集的总步数
total_step = len(train_loader)
# 训练过程
for epoch in range(num_epochs): # 遍历每个epochfor i, (images, labels) in enumerate(train_loader): # 遍历每个batchimages = images.reshape(-1, 28*28).to(device) # 将每张28x28的图片展开成784维# 将标签转为one-hot编码形式,并转换为浮点类型(MSELoss要求标签是浮点数)labels = torch.nn.functional.one_hot(labels, num_classes=10).float().to(device)# 前向传播:将输入数据传入模型outputs = model(images)# 计算损失loss = criterion(outputs, labels)# 梯度清零,反向传播,更新参数optimizer.zero_grad()loss.backward()optimizer.step()# 每100个步骤打印一次当前损失if (i+1) % 100 == 0:print ('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}' .format(epoch+1, num_epochs, i+1, total_step, loss.item()))
# 在测试集上评估模型的性能
with torch.no_grad(): # 不计算梯度correct = 0total = 0# 遍历测试数据for images, labels in test_loader:images = images.reshape(-1, 28*28).to(device) # 扁平化图片labels = labels.to(device) # 获取标签# 前向传播:通过模型获得输出outputs = model(images)# 获取预测类别(根据最大输出值选择类别)predicted = torch.argmax(outputs.data, dim=1)total += labels.size(0) # 统计总数correct += (predicted == labels).sum().item() # 统计正确预测的个数# 打印测试集上的准确率print('Accuracy of the network on the 10000 test images: {} %'.format(100 * correct / total))
代码运行结果: