全连接神经网络基础

什么是全连接层?

全连接神经网络(Fully Connected Neural Network,FCNN),或者更准确地说,由全连接层(Fully Connected Layer, FC Layer)组成的网络,是神经网络中最基础的一种结构单元。在一个标准的、没有循环或卷积等特殊连接的全连接层中,当前层中的每一个神经元都与前一层中的所有神经元以及后一层中的所有神经元连接。这意味着信息从前一层到当前层再到后一层时,是通过一个稠密的连接矩阵进行传递的。

简单来说,你可以想象每一层神经元就像一排灯泡,前一排的每个灯泡都连接着后一排的所有灯泡。通过这些连接,前一层的所有输出都会影响到后一层的每一个输入。

核心结构:神经元与连接

全连接层的核心构成要素包括:

  • 神经元(Neurons):每个神经元接收来自前一层所有神经元的输入信号,这些信号经过加权求和(weights)和偏置(bias)处理,然后通过一个非线性激活函数(activation function)产生输出。
  • 权重(Weights):连接不同层神经元之间的数值。每个连接都有一个对应的权重,表示该连接传递信号的重要性或强度。在一个全连接层中,如果前一层有 N 个神经元,当前层有 M 个神经元,那么它们之间的权重就构成一个 M × N 的矩阵。
  • 偏置(Biases):每个神经元除了接收加权输入外,还有一个偏置项。偏置可以看作是调整神经元激活阈值的常数,它使得激活函数可以在输入为零时不一定输出零,增加了模型的表达能力。每个神经元有一个偏置,所以一个全连接层有 M 个偏置。
  • 激活函数(Activation Function):应用于加权输入和偏置之和的非线性函数。它为网络引入了非线性能力,使得神经网络能够学习和表示复杂的、非线性的关系。常见的激活函数有ReLU、Sigmoid、Tanh等。

与其他网络的本质区别

全连接网络最显著的特点是它的“全连接”性。这与一些现代神经网络结构有本质区别:

  • 与卷积神经网络(CNN)的区别:CNN中的卷积层使用局部连接(每个神经元只连接输入的一个局部区域)和权重共享(同一个卷积核在整个输入上滑动,使用同一组权重)。这使得CNN特别擅长处理具有空间结构的数据(如图像),能有效地提取局部特征并减少参数数量。全连接层没有局部连接和权重共享的特性。
  • 与循环神经网络(RNN)的区别:RNN引入了循环连接,使得网络可以在时间序列数据上维持内部状态和记忆。全连接网络是前馈的,信息总是单向流动,没有内部循环或记忆单元来处理序列依赖关系。

  • 作为网络的一部分:虽然有这些区别,全连接层经常作为CNN或RNN的组成部分出现,例如在CNN的末端用于分类,或者在RNN的输出层之后进行最终的预测。

全连接层的工作原理

前向传播过程详解

全连接层的前向传播是整个网络信息流动的核心步骤。对于一个接收输入向量 x 的全连接层,其计算过程可以表示为简单的矩阵运算和非线性激活。

假设当前全连接层有 M 个神经元,前一层(输入层)有 N 个输出(即当前层的输入维度是 N)。

1. 加权求和:输入向量 x(维度 N × 1)与权重矩阵 W(维度 M × N)相乘。这相当于将前一层所有神经元的输出按连接权重加权求和,得到 M 个中间值。

z = Wx

2. 加入偏置:将偏置向量 b(维度 M × 1)加到加权求和结果 z 上。

a = z + b = Wx + b

此时 a 是一个维度为 M × 1 的向量,其每个元素对应当前层一个神经元的加权输入总和。

3. 通过激活函数:将向量 a 的每个元素通过激活函数 f

y = f(a) = f(Wx + b)

此时 y 是一个维度为 M × 1 的向量,代表当前层所有神经元的输出。这个输出 y 将作为下一层的输入。

整个前向传播过程可以简洁地概括为:输入经过一次仿射变换(线性变换 Wx 加上平移 b),然后通过一个非线性变换(激活函数 f)。

参与反向传播的梯度计算

神经网络的训练通常使用反向传播算法来更新权重和偏置,以最小化损失函数。全连接层在反向传播中扮演着接收上一层传来的误差信号,计算自身参数(权重 W 和偏置 b)的梯度,并将误差信号向前一层传递的角色。

反向传播的核心是链式法则。假设我们知道损失函数 L 对当前层输出 y 的梯度 ∂L/∂y。我们需要计算:

  • 损失函数对权重 W 的梯度 ∂L/∂W
  • 损失函数对偏置 b 的梯度 ∂L/∂b
  • 损失函数对当前层输入 x 的梯度 ∂L/∂x(用于传递给前一层)

根据链式法则和前向传播的公式 y = f(Wx + b),可以推导出:

1. 首先计算损失函数对激活函数输入 a = Wx + b 的梯度:

∂L/∂a = (∂L/∂y) * f'(a) (这里 f’ 是激活函数的导数,运算是逐元素的)

2. 然后计算损失函数对偏置 b 的梯度:

因为 a = Wx + b,所以 ∂a/∂b = 1

∂L/∂b = ∂L/∂a

这表明偏置的梯度就是损失函数对加权输入总和的梯度,这在直观上是合理的:如果我们想减小损失,就应该调整偏置来改变 a 的值。

3. 计算损失函数对权重 W 的梯度:

∂L/∂W = (∂L/∂a) * x^T (这里 x^T 是输入向量的转置)

这是一个外积操作,得到的梯度矩阵与权重矩阵 W 的维度相同。这说明权重的梯度与输入以及误差信号 ∂L/∂a 都相关。

4. 计算损失函数对输入 x 的梯度(用于传递给前一层):

∂L/∂x = W^T * (∂L/∂a) (这里 W^T 是权重矩阵的转置)

这个梯度将作为当前层向后一层传递的误差信号,用于前一层参数的更新。

通过这些梯度计算,优化器(如梯度下降)可以更新 Wb,使网络在训练数据上的损失函数值减小。

全连接层的角色与应用

为什么选择全连接层?(优点与局限性)

为什么在众多网络层类型中仍然广泛使用全连接层?

优点:

  1. 普适性:理论上,包含至少一个隐藏层的全连接网络(在有足够神经元和合适的激活函数的情况下)可以逼近任何连续函数(通用近似定理)。这使得它能够学习输入和输出之间的任意复杂映射关系。
  2. 特征组合:全连接层能够将前一层提取到的特征进行任意组合和变换。这在许多任务的末端非常有用,可以将前面层次学习到的局部或抽象特征整合成用于最终决策(如分类概率、回归值)的表示。
  3. 简单直观:数学原理相对简单,易于理解和实现。

局限性:

  1. 参数巨大:全连接层的参数数量(M × N + M)随着输入和输出维度的增加而平方级增长。这导致模型庞大,容易过拟合,需要大量训练数据,且计算成本高。
  2. 忽略结构信息:全连接层将输入视为一个扁平的向量,忽略了数据可能存在的空间结构(如图像的像素邻近关系)或序列结构(如文本的词语顺序)。直接将高维结构化数据(如大尺寸图像)展平输入全连接层通常不是一个好主意。

因此,全连接层通常不单独用于处理原始的高维结构化数据,而是作为更复杂的网络(如CNN、RNN)的组成部分,放在网络的后端处理前面层次已经提取和压缩过的特征。

它在网络中的典型位置

全连接层很少作为处理原始输入的第一个层(除非输入本身就是低维向量)。它更常出现在以下位置:

  • 网络的输出层:无论网络是用于分类还是回归,最后一层通常是一个全连接层。其输出神经元数量对应于任务的输出维度(如分类问题的类别数,回归问题的值的数量)。
  • 深层网络隐藏层:在某些多层感知机(MLP)中,中间的隐藏层全部由全连接层构成。
  • 特征提取网络(如CNN)的末端:在图像分类等任务中,CNN的前面层次(卷积层、池化层)负责提取图像特征。提取到的特征图在进入全连接层之前通常会被展平(flatten),然后一个或多个全连接层将这些展平的特征映射到最终的输出(如类别概率)。这部分全连接层被称为“分类头”或“密集层”。
  • 自编码器(Autoencoders)中:在自编码器的编码器和解码器部分,全连接层常被用来对数据进行编码和解码。

具体的应用场景举例

全连接层单独或作为组件广泛应用于各种领域:

  • 图像分类:CNN提取特征后,全连接层进行最终分类判断。
  • 自然语言处理:在一些早期或特定的NLP模型中,或者作为更复杂模型(如Transformer的Feed-Forward Networks)的内部组件。
  • 推荐系统:用于学习用户和物品之间的复杂交互关系,进行评分预测或物品排序。

  • 预测问题:如房价预测、股票价格预测等回归任务,全连接网络可以直接学习输入特征与输出数值之间的关系。
  • 简单数据集分类/回归:对于特征已经提取好且维度不高的表格数据或其他结构化数据,纯全连接网络(多层感知机)是一个有效的模型。

构建与训练全连接网络

参数数量的计算与考量

了解全连接层的参数数量对于评估模型复杂度、计算需求和过拟合风险至关重要。

对于一个输入维度为 N,输出维度为 M 的全连接层,其参数数量计算如下:

权重参数:每个输入神经元到每个输出神经元之间都有一个连接,所以权重数量是 N × M

偏置参数:每个输出神经元有一个偏置,所以偏置数量是 M

总参数数: N × M + M

例如,一个从100个神经元连接到50个神经元的全连接层,其参数数量为 100 × 50 + 50 = 5000 + 50 = 5050

如果一个网络有多个全连接层,总参数数量是所有层参数数量之和。参数数量越多,模型的容量越大,理论上能学习更复杂的模式,但同时更容易在训练数据上表现得非常好而在未知数据上表现差(过拟合)。

如何确定网络结构(层数与神经元数)

确定全连接网络的层数和每层神经元的数量是构建网络时的一个重要决策,这通常被称为超参数调优,没有一个固定的公式或定律,很大程度上依赖于经验、实验和验证集上的表现。

以下是一些常用的思考方向:

  1. 从简到繁:可以从一个相对简单的结构开始,比如一个或两层隐藏层,每层神经元数量适中。如果在训练集上表现不佳(欠拟合),可以逐步增加层数或每层的神经元数量。
  2. 考虑任务复杂度:任务越复杂,可能需要更深或更宽的网络来学习更复杂的模式。
  3. 输入/输出维度:输入层和输出层的神经元数量由数据的特征维度和任务的输出维度决定。
  4. 数据量:数据量越大,越能支撑更复杂的模型(更多参数)。数据量小则应倾向于使用更简单的模型,并采用正则化技术。
  5. 计算资源:更深更宽的网络需要更多的计算内存和时间。
  6. 参考现有成功模型:对于类似的任务,可以参考在该领域表现良好的现有模型的结构。
  7. 使用验证集:将数据集划分为训练集、验证集和测试集。在训练集上训练模型,在验证集上评估不同结构的效果。根据验证集上的表现来调整网络结构。测试集只用于最终评估。

常用的隐藏层神经元数量经验法则(非严格):可以是输入和输出神经元数量的几何平均,或者介于两者之间,或者根据任务类别数、特征维度等因素来确定。但最可靠的方法是实验。

如何实现全连接层?(代码视角)

在现代深度学习框架(如TensorFlow、PyTorch、Keras)中实现全连接层非常便捷,通常只需要调用相应的API。框架底层已经封装了权重和偏置的初始化、前向传播的矩阵运算以及反向传播的梯度计算。

以PyTorch和TensorFlow为例:

PyTorch:

import torch
import torch.nn as nn

# 定义一个全连接层
# 输入特征维度 100,输出特征维度 50
fc_layer = nn.Linear(in_features=100, out_features=50)

# 假设有一个输入数据张量 (batch_size, in_features)
input_data = torch.randn(64, 100) # Batch size 64, input size 100

# 前向传播
output_data = fc_layer(input_data) # output_data shape (64, 50)

# 参数可以通过 fc_layer.weight 和 fc_layer.bias 访问
print(f"权重形状: {fc_layer.weight.shape}") # torch.Size([50, 100])
print(f"偏置形状: {fc_layer.bias.shape}")   # torch.Size([50])

TensorFlow/Keras:

import tensorflow as tf
from tensorflow import keras

# 定义一个全连接层 (在 Keras 中通常称为 Dense 层)
# 输入特征维度 100,输出特征维度 50
# Keras Dense 层通常不需要指定 input_shape,除非它是模型的第一层
# 这里为了演示,假设它是第一层或者前一层输出是100维
fc_layer = keras.layers.Dense(units=50, input_shape=(100,))

# 或者作为Sequential模型的一部分,input_shape只在第一层指定
# model = keras.Sequential([
#     keras.layers.Dense(units=50, input_shape=(100,), activation='relu'),
#     keras.layers.Dense(units=10, activation='softmax') # 最后一层分类
# ])


# 假设有一个输入数据张量 (batch_size, input_shape)
input_data = tf.random.normal([64, 100]) # Batch size 64, input size 100

# 前向传播
output_data = fc_layer(input_data) # output_data shape (64, 50)

# 参数可以通过 fc_layer.get_weights() 访问 (返回权重和偏置的 NumPy 数组)
weights, biases = fc_layer.get_weights()
print(f"权重形状: {weights.shape}") # (100, 50) 注意 Keras 和 PyTorch 参数形状表示习惯不同
print(f"偏置形状: {biases.shape}")   # (50,)

可以看到,实现一个全连接层非常简单,主要是定义输入和输出维度以及选择合适的激活函数。框架会自动处理复杂的数学运算和参数管理。

训练过程中的关键点(优化器、损失函数)

训练一个包含全连接层的神经网络涉及到以下关键要素:

  • 数据准备:包括数据清洗、预处理(如标准化、归一化),以及将数据分成训练集、验证集和测试集。全连接层对输入数据的缩放比较敏感,标准化通常是必要的。
  • 损失函数(Loss Function):衡量模型预测输出与真实目标之间的差距。选择合适的损失函数取决于任务类型:

    • 回归任务:常用均方误差(Mean Squared Error, MSE)。
    • 分类任务:常用交叉熵(Cross-Entropy),特别是对于多分类任务。
  • 优化器(Optimizer):根据损失函数计算出的梯度来更新模型参数(权重和偏置),以最小化损失。常见的优化器包括:

    • 随机梯度下降(SGD)及其变种(Momentum, Nesterov)。
    • 自适应学习率方法(Adam, RMSprop, Adagrad)。Adam通常是一个很好的起点。

    优化器需要一个学习率(Learning Rate)超参数,控制参数更新的步长。学习率调度(Learning Rate Scheduling)可以帮助模型更好地收敛。

  • 批量大小(Batch Size):每次参数更新使用的数据样本数量。较大的批量通常能使梯度估计更稳定,但可能需要更多内存且容易陷入局部最优。较小的批量引入更多噪声,有正则化效果,可能跳出局部最优,但收敛路径可能更曲折。
  • 训练轮数(Epochs):整个训练数据集被模型完整遍历的次数。需要训练足够的轮数让模型收敛,但过多可能导致过拟合。

训练过程通常是迭代进行的:在前向传播中计算输出和损失,在反向传播中计算梯度,然后优化器使用梯度更新参数。这个过程重复多个epoch,直到模型在验证集上表现 satisfactory 或开始过拟合。

如何应对过拟合问题?

由于全连接层参数数量多,全连接网络,特别是深层或宽的全连接网络,很容易在训练数据上过拟合。以下是一些常用的应对策略:

  1. 正则化(Regularization):

    • L1/L2 正则化:在损失函数中加入权重参数的L1范数或L2范数惩罚项。L1倾向于产生稀疏权重(部分权重为零),L2倾向于使权重值更小。这限制了模型学习过于复杂的模式。
    • Dropout:在训练过程中,随机地“丢弃”(将其输出设置为零)一部分神经元。这使得网络不能依赖于任何单个神经元,强迫其他神经元学习更鲁棒的特征,相当于训练了多个不同的子网络并进行集成。在全连接层中使用Dropout非常常见。
  2. 增加数据量:更多样化的训练数据可以帮助模型学习到更普遍的模式,减少对训练集中特定噪声或特征组合的依赖。数据增强(Data Augmentation)是增加数据量的一种有效方法。
  3. 提前停止(Early Stopping):监控模型在验证集上的性能(如损失或准确率)。如果在连续多个epoch上验证集性能不再提升甚至开始下降,就停止训练。这可以防止模型在训练集上继续优化而损害泛化能力。
  4. 简化模型:减少网络的层数或每层的神经元数量,从而减少参数数量,降低模型容量。

在实践中,通常会结合使用这些策略来构建和训练具有良好泛化能力的全连接网络。


全连接神经网络