全连接层(Fully Connected Layer),也称为线性层(Linear Layer),是深度学习中最基础、最核心的组件之一。它之所以被称为"全连接",是因为输入层的每个神经元都与输出层的每个神经元相连,形成一个完全连接的网络结构。
全连接层的核心计算公式非常简单:
y = w * x + b
其中:
这个公式看起来简单,但它蕴含着强大的表达能力。通过调整w和b的值,全连接层可以实现从输入空间到输出空间的任意线性变换。
注意:在实际实现中,我们通常使用矩阵乘法而不是逐元素乘法。对于批量输入(多个样本同时处理),公式变为 Y = XW^T + b,其中X是输入矩阵,W^T表示权重矩阵的转置。
全连接层的一个重要功能是维度变换。让我们通过一个具体例子来理解:
假设我们有一个3维的输入,想要通过两个全连接层将其映射到2维输出:
第一层(隐含层1):
第二层(隐含层2):
这种维度变换的能力使得神经网络可以灵活地处理各种特征组合和表示学习任务。
理解全连接层的参数量对于模型设计和优化非常重要。参数量的计算公式为:
参数量 = (输入维度 + 1) × 输出维度
"+1"是因为每个输出神经元都有一个偏置项。在前面的例子中:
随着网络加深和维度增加,全连接层的参数量会急剧膨胀,这也是为什么在大型网络中需要谨慎使用全连接层的原因。
PyTorch提供了nn.Linear模块来实现全连接层,使用起来非常简单:
python复制import torch
import torch.nn as nn
class TorchModel(nn.Module):
def __init__(self, input_size, hidden_size1, hidden_size2):
super(TorchModel, self).__init__()
self.layer1 = nn.Linear(input_size, hidden_size1)
self.layer2 = nn.Linear(hidden_size1, hidden_size2)
def forward(self, x):
x = self.layer1(x)
y_pred = self.layer2(x)
return y_pred
这个实现有几个关键点:
为了更深入理解全连接层的运作原理,我们可以用NumPy手动实现:
python复制import numpy as np
class DiyModel:
def __init__(self, w1, b1, w2, b2):
self.w1 = w1
self.b1 = b1
self.w2 = w2
self.b2 = b2
def forward(self, x):
hidden = np.dot(x, self.w1.T) + self.b1
y_pred = np.dot(hidden, self.w2.T) + self.b2
return y_pred
手动实现的关键点:
我们可以用相同的输入和权重参数来验证两种实现是否一致:
python复制# 创建PyTorch模型
torch_model = TorchModel(3, 5, 2)
# 获取PyTorch模型的参数
w1 = torch_model.layer1.weight.detach().numpy()
b1 = torch_model.layer1.bias.detach().numpy()
w2 = torch_model.layer2.weight.detach().numpy()
b2 = torch_model.layer2.bias.detach().numpy()
# 创建手动模型
diy_model = DiyModel(w1, b1, w2, b2)
# 测试输入
x = np.array([[3.1, 1.3, 1.2], [2.1, 1.3, 13]])
# 比较结果
torch_result = torch_model(torch.FloatTensor(x)).detach().numpy()
diy_result = diy_model.forward(x)
print("PyTorch结果:\n", torch_result)
print("手动实现结果:\n", diy_result)
print("差异:", np.abs(torch_result - diy_result).max())
如果实现正确,两种方法的结果应该几乎完全相同(差异在浮点精度范围内)。
如果没有激活函数,无论有多少个全连接层叠加,整个网络仍然只能表示线性变换:
y = w3(w2(w1x + b1) + b2) + b3 = Wx + B
这大大限制了神经网络的表达能力。激活函数的引入使得神经网络可以拟合任意复杂的非线性函数。
公式:σ(x) = 1 / (1 + e^-x)
特点:
适用场景:
公式:tanh(x) = (e^x - e^-x) / (e^x + e^-x)
特点:
适用场景:
公式:ReLU(x) = max(0, x)
特点:
适用场景:
公式:GELU(x) = xΦ(x),其中Φ(x)是标准正态分布的累积分布函数
特点:
适用场景:
公式:softmax(x_i) = e^x_i / Σ_j e^x_j
特点:
适用场景:
隐藏层:
输出层:
注意事项:
python复制class TorchModelWithActivation(nn.Module):
def __init__(self, input_size, hidden_size1, hidden_size2):
super().__init__()
self.layer1 = nn.Linear(input_size, hidden_size1)
self.layer2 = nn.Linear(hidden_size1, hidden_size2)
self.activation = nn.ReLU() # 使用ReLU作为激活函数
def forward(self, x):
x = self.layer1(x)
x = self.activation(x) # 第一层后加激活函数
y_pred = self.layer2(x)
return y_pred
python复制class DiyModelWithActivation:
def __init__(self, w1, b1, w2, b2):
self.w1 = w1
self.b1 = b1
self.w2 = w2
self.b2 = b2
def relu(self, x):
return np.maximum(0, x)
def forward(self, x):
hidden = np.dot(x, self.w1.T) + self.b1
hidden = self.relu(hidden) # 应用ReLU激活
y_pred = np.dot(hidden, self.w2.T) + self.b2
return y_pred
我们可以比较使用和不使用激活函数时模型的输出差异:
python复制# 不带激活函数的模型
torch_model = TorchModel(3, 5, 2)
# 带ReLU激活的模型
torch_model_relu = TorchModelWithActivation(3, 5, 2)
x = torch.FloatTensor([[1.0, -1.0, 0.5]])
print("不带激活函数:", torch_model(x))
print("带ReLU激活:", torch_model_relu(x))
通过这个对比,可以直观地看到激活函数如何引入非线性,使模型能够学习更复杂的模式。
全连接层的表现很大程度上依赖于权重的初始值。常见的初始化方法:
Xavier/Glorot初始化:
He初始化:
在PyTorch中,nn.Linear默认使用Kaiming均匀初始化(He初始化的一种)。
深层全连接网络容易遇到梯度问题:
梯度消失:
梯度爆炸:
在大规模应用中,全连接层的一些替代方案:
检查维度:
验证前向传播:
监控激活值:
全连接层对批量处理非常高效。尽量使用批量输入而不是单个样本:
python复制# 不好:逐个样本处理
for x in dataset:
y = model(x[None, :]) # 添加批次维度
# 好:批量处理
loader = DataLoader(dataset, batch_size=32)
for batch in loader:
y = model(batch)
现代GPU支持混合精度计算,可以显著提升训练速度:
python复制scaler = torch.cuda.amp.GradScaler()
for x, y in loader:
with torch.cuda.amp.autocast():
y_pred = model(x)
loss = criterion(y_pred, y)
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
确保模型和数据在同一个设备上:
python复制device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
x = x.to(device)
对于大型全连接层,可以使用数据并行:
python复制if torch.cuda.device_count() > 1:
model = nn.DataParallel(model)
一个简单的全连接网络处理MNIST手写数字分类:
python复制class MNISTClassifier(nn.Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(28*28, 512)
self.fc2 = nn.Linear(512, 256)
self.fc3 = nn.Linear(256, 10)
self.dropout = nn.Dropout(0.2)
def forward(self, x):
x = x.view(-1, 28*28) # 展平图像
x = F.relu(self.fc1(x))
x = self.dropout(x)
x = F.relu(self.fc2(x))
x = self.dropout(x)
return F.log_softmax(self.fc3(x), dim=1)
用于房价预测的回归网络:
python复制class Regressor(nn.Module):
def __init__(self, input_size):
super().__init__()
self.fc1 = nn.Linear(input_size, 64)
self.fc2 = nn.Linear(64, 32)
self.fc3 = nn.Linear(32, 1)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
return self.fc3(x) # 线性输出,无激活函数
如何实现自定义权重初始化:
python复制def init_weights(m):
if isinstance(m, nn.Linear):
nn.init.xavier_uniform_(m.weight)
m.bias.data.fill_(0.01)
model = TorchModel(3, 5, 2)
model.apply(init_weights)
通过引入稀疏性减少参数量:
python复制class SparseLinear(nn.Module):
def __init__(self, input_dim, output_dim, sparsity=0.5):
super().__init__()
self.weight = nn.Parameter(torch.randn(output_dim, input_dim))
self.mask = torch.rand(output_dim, input_dim) > sparsity
self.weight.data *= self.mask.float()
def forward(self, x):
return F.linear(x, self.weight * self.mask, self.bias)
使用低秩分解减少计算量:
python复制class LowRankLinear(nn.Module):
def __init__(self, input_dim, output_dim, rank=32):
super().__init__()
self.U = nn.Linear(input_dim, rank, bias=False)
self.V = nn.Linear(rank, output_dim)
def forward(self, x):
return self.V(self.U(x))
动态调整神经元激活数量:
python复制class AdaptiveWidthLinear(nn.Module):
def __init__(self, input_dim, output_dim, max_width):
super().__init__()
self.weight = nn.Parameter(torch.randn(max_width, input_dim))
self.selector = nn.Parameter(torch.ones(1))
def forward(self, x):
active_units = int(self.selector.sigmoid() * self.weight.size(0))
active_weight = self.weight[:active_units]
return F.linear(x, active_weight)
可视化全连接层的权重可以帮助理解网络学到了什么:
python复制import matplotlib.pyplot as plt
# 获取第一层的权重
weights = model.layer1.weight.detach().cpu().numpy()
# 可视化
plt.figure(figsize=(10, 5))
plt.imshow(weights, cmap='coolwarm', aspect='auto')
plt.colorbar()
plt.xlabel("Input Features")
plt.ylabel("Hidden Units")
plt.title("Layer 1 Weight Visualization")
plt.show()
检查激活值的分布可以帮助诊断网络问题:
python复制def plot_activation_distribution(model, x):
activations = []
def hook(module, input, output):
activations.append(output.detach().cpu().numpy().ravel())
handle = model.layer1.register_forward_hook(hook)
_ = model(x)
handle.remove()
plt.hist(activations[0], bins=50)
plt.xlabel("Activation Value")
plt.ylabel("Frequency")
plt.title("Layer 1 Activation Distribution")
plt.show()
全连接层是深度学习的基础构建块,理解其工作原理对于构建有效的神经网络至关重要。在实际应用中,需要根据具体任务和数据特点选择合适的网络结构和激活函数。
进阶学习建议:
记住,在深度学习实践中,理论理解与动手实验同样重要。建议读者在理解本文内容的基础上,动手实现并修改这些代码示例,观察不同设置对模型性能的影响。