简介
(来自ChatGPT的介绍,如有更正建议请指出)
径向基函数神经网络(Radial Basis Function Neural Network, RBFNN)是一种特殊的前馈神经网络,其结构和特点与其他常见的神经网络有所不同,主要表现在以下几个方面:
- 网络结构
- 三层结构:RBF神经网络通常由三层组成:输入层、隐藏层和输出层。与常规的多层感知器(MLP)相比,其隐藏层的激活函数采用径向基函数(通常是高斯函数)。
- 隐层节点:每个隐藏层节点表示一个中心点,输入到该节点的距离通过径向基函数进行转换,因此隐层节点的作用类似于一种局部响应单位。
- 径向基函数
- 局部特性:径向基函数的输出是基于输入数据与某个中心的距离,输出在离中心较近的地方较大,离中心较远时趋近于零。常见的径向基函数包括高斯函数、多项式、逆多二次函数等。
- 非线性映射:由于径向基函数可以对输入进行非线性映射,RBFNN具备了处理复杂非线性问题的能力。
- 训练过程
- 两阶段训练:RBFNN的训练分为两个阶段。首先是通过无监督学习(如K均值聚类)确定隐藏层节点的中心,然后在第二阶段使用线性优化算法(如最小二乘法)来训练输出层的权重。这种方式相比传统的反向传播算法收敛速度更快。
- 少量参数优化:因为RBF神经网络的输出层通常是线性权重,因此需要优化的参数数量较少,训练速度快。
- 应用场景
- 函数逼近与插值:RBFNN特别适合用于函数逼近和插值问题,因为它对输入空间进行局部化响应,能够较好地拟合复杂的函数。
- 分类和回归:RBFNN可以用来解决分类和回归问题,尤其适合那些具有明显聚类特性的任务。
- 特点总结
- 良好的逼近能力:RBFNN具有良好的逼近非线性函数的能力,并且在理论上可以逼近任意连续函数。
- 快速训练:由于采用两阶段训练方法,RBF神经网络的训练速度较快。
- 鲁棒性差:RBFNN对噪声数据的敏感度较高,容易出现过拟合现象。
- 需要确定中心和宽度参数:选择合适的中心和宽度参数对网络性能有很大影响,选择不当会导致模型性能较差。
RBF神经网络的这些特点使其在一定的应用场景中表现突出,尤其是在需要快速训练和局部响应的情况下效果尤为显著。
案例实操
案例概述
平面上有一些点,将这些点映射到马鞍面的对应点上。
输入为平面点空间坐标(x,y,z),输出为马鞍面点空间坐标(x,y,z)
训练,预测一些点的对应坐标
函数主体
# 使用 matplotlib 的交互模式
%matplotlib widget
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
# RBF Layer
class RBFLayer(nn.Module):
def __init__(self, in_features, out_features, centers, gamma):
super(RBFLayer, self).__init__()
self.in_features = in_features
self.out_features = out_features
# 将中心点设置为可训练参数
self.centers = nn.Parameter(torch.tensor(centers, dtype=torch.float32), requires_grad=True)
# 将 gamma 设置为可训练参数
self.gamma = nn.Parameter(torch.tensor(gamma, dtype=torch.float32), requires_grad=True)
def forward(self, points):
# 计算输入到每个中心的欧式距离
points = points.unsqueeze(1).repeat(1, self.out_features, 1)
centers = self.centers.unsqueeze(0).repeat(points.size(0), 1, 1)
distance = torch.norm(points - centers, dim=2)
# 应用 RBF 函数 (高斯函数),使用可训练的 gamma
return torch.exp(-self.gamma * distance ** 2)
# RBFNN Model
class RBFNN(nn.Module):
def __init__(self, in_dim, out_dim, num_cen):
super(RBFNN, self).__init__()
self.num_centers = num_cen
# 使用 KMeans 聚类确定初始中心点
kmeans = KMeans(n_clusters=num_cen, random_state=42)
centers = kmeans.fit(X_train).cluster_centers_
# RBF 层,初始 gamma 值设为 1.0
self.rbf_layer = RBFLayer(in_dim, num_cen, centers, gamma=1.0)
# 输出层,线性回归
self.linear = nn.Linear(num_cen, out_dim)
def forward(self, x_in):
# 通过 RBF 层和线性层
rbf_out = self.rbf_layer(x_in)
return self.linear(rbf_out)
参数更新
可见,RBFNN中更新的参数不光有最后的线性连接层的权重,还有RBFLayer中手动设置的中心与gamma(宽度参数)的更新。
此处gamma对应高斯系数为
1
/
σ
2
1/σ^2
1/σ2,取值越大,方差越小,高斯函数越瘦高;反之越宽。
计算每个输入到中心点距离
以下细节值得注意:
计算输入到每个中心的欧式距离
points = points.unsqueeze(1).repeat(1, self.out_features, 1)
centers = self.centers.unsqueeze(0).repeat(points.size(0), 1, 1)
distance = torch.norm(points - centers, dim=2)
1. points = points.unsqueeze(1).repeat(1, self.out_features, 1)
- 含义:
- points 是输入的批量样本,维度应该是 (batch_size, in_features),即 batch_size 个样本,每个样本有 in_features 个特征。比如(500,3)
- unsqueeze(1) 在 points 的第 1 维增加一个维度,变为 (batch_size, 1, in_features),为后续操作准备。比如(500,1,3)
- repeat(1, self.out_features, 1) 将 points 的形状沿第 1 维扩展为 self.out_features 个副本,最终变为 (batch_size, self.out_features, in_features)。比如(500,50,3)
- 目的:
- 这一步的目的是将每个输入点在空间中与所有的 RBF 中心一一对应起来,即每个输入样本需要与 self.out_features(RBF中心数量)个中心点计算距离。
- 通过 repeat,将每个输入点复制 self.out_features 次,以便后续计算它与所有中心的距离。
2. centers = self.centers.unsqueeze(0).repeat(points.size(0), 1, 1)
- 含义:
- self.centers 是 RBF 网络中的中心点,维度是 (out_features, in_features),表示 self.out_features 个 RBF 中心,每个中心有 in_features 个特征。比如(50,3)
- unsqueeze(0) 在第 0 维增加一个维度,变为 (1, out_features, in_features)。比如(1,50,3)
- repeat(points.size(0), 1, 1) 复制 centers,使其与 points 的批次大小相同,最终变为 (batch_size, out_features, in_features)。比如(500,50,3)
- 目的:
- 通过这一步,我们将所有的 RBF 中心点扩展到与输入数据的批次大小相同,以便将每个输入点与每个 RBF 中心进行对应。
- 这确保了对于每个输入样本,我们都有相同的 self.out_features 个中心进行距离计算。
3. distance = torch.norm(points - centers, dim=2)
- 含义:
- points - centers 计算每个输入点与所有 RBF 中心之间的向量差。由于 points 和 centers 的维度都是 (batch_size, out_features, in_features),这一步的结果也是 (batch_size, out_features, in_features),比如(500,50,3)。表示每个输入点与所有 RBF 中心的特征差异。
- torch.norm(…, dim=2) 默认的范数是 L2 范数(也称为欧式距离),即 p=2。计算这些向量差的欧式距离(L2 范数),即每个输入点与所有中心的距离。dim=2 表示沿着特征维度(in_features)进行范数计算。
- 结果是一个维度为 (batch_size, out_features) 的张量,表示每个输入样本与所有 RBF 中心之间的距离。
- 目的:
- 通过计算欧式距离,衡量输入点与每个 RBF 中心的距离。这个距离将作为 RBF 核函数(高斯函数)的输入,用于进一步计算 RBF 输出。
对于dim取值的对比
import torch
point = torch.tensor([[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]],
[[7.0, 8.0, 9.0], [10.0, 11.0, 12.0]]])
center = torch.tensor([[[0, 0, 0], [0, 0, 0]],
[[0, 0, 0], [0, 0, 0]]])
print((point - center).shape)
print(torch.norm(point - center, dim=0))
print(torch.norm(point - center, dim=1))
print(torch.norm(point - center, dim=2))
torch.Size([2, 2, 3])
tensor([[ 7.0711, 8.2462, 9.4868],
[10.7703, 12.0830, 13.4164]])
tensor([[ 4.1231, 5.3852, 6.7082],
[12.2066, 13.6015, 15.0000]])
tensor([[ 3.7417, 8.7750],
[13.9284, 19.1050]])
可见,当dim=2时,才是计算两组坐标点差值向量的长度(两点欧氏距离)
训练
# 生成示例数据,平面包络体 -> 马鞍面包络体
n_samples = 500
X_train = np.random.uniform(-1, 1, (n_samples, 2)) # 平面 (x, y)
# 计算马鞍面
y_train_saddle = X_train[:, 0] ** 2 - X_train[:, 1] ** 2
# 将 y_train_saddle 重新形状为列向量
y_train_saddle = y_train_saddle.reshape(-1, 1)
# 水平堆叠 X_train 的列和 y_train_saddle
y_train = np.hstack([X_train, y_train_saddle])
X_train = np.hstack([X_train, np.zeros((n_samples, 1))]) # 平面 z = 0
# 转换为 PyTorch tensor
X_train_tensor = torch.FloatTensor(X_train)
y_train_tensor = torch.FloatTensor(y_train)
# 初始化模型
input_dim = 3 # 输入为平面上的三维坐标
output_dim = 3 # 输出为曲面上的三维坐标
num_centers = 50 # RBF 隐藏层节点
model = RBFNN(in_dim=input_dim, out_dim=output_dim, num_cen=num_centers)
criterion = nn.MSELoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 训练模型
epochs = 1000
for epoch in range(epochs):
model.train()
# 前向传播
outputs = model(X_train_tensor)
loss = criterion(outputs, y_train_tensor)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch + 1) % 100 == 0:
print(f'Epoch [{epoch + 1}/{epochs}], Loss: {loss.item():.4f}, Gamma: {model.rbf_layer.gamma.item():.4f}')
# 打印模型的中心点和 gamma 参数
print("Trained Centers: ", model.rbf_layer.centers)
print("Trained Gamma: ", model.rbf_layer.gamma)
结果显示:
Epoch [100/1000], Loss: 0.0540, Gamma: 1.0377
Epoch [200/1000], Loss: 0.0229, Gamma: 1.0969
Epoch [300/1000], Loss: 0.0140, Gamma: 1.0868
Epoch [400/1000], Loss: 0.0106, Gamma: 1.0102
Epoch [500/1000], Loss: 0.0084, Gamma: 0.9405
Epoch [600/1000], Loss: 0.0070, Gamma: 0.8858
Epoch [700/1000], Loss: 0.0059, Gamma: 0.8423
Epoch [800/1000], Loss: 0.0051, Gamma: 0.8069
Epoch [900/1000], Loss: 0.0044, Gamma: 0.7775
Epoch [1000/1000], Loss: 0.0039, Gamma: 0.7526
Trained Centers: Parameter containing:
tensor([[-5.5090e-01, 1.0923e+00, 0.0000e+00],
...
[ 1.1742e+00, -2.9787e-01, 0.0000e+00]], requires_grad=True)
Trained Gamma: Parameter containing:
tensor(0.7526, requires_grad=True)
预测
# 预测新点
num_points = 5
x_new = np.linspace(-1, 1, num_points)
y_new = np.linspace(-1, 1, num_points)
# 生成网格数据并展开为二维点
X_new, Y_new = np.meshgrid(x_new, y_new)
X_new = X_new.flatten()
Y_new = Y_new.flatten()
# 创建新点数组(z 坐标初始化为 0)
new_points = np.column_stack([X_new, Y_new, np.zeros_like(X_new)])
# 转换为 PyTorch tensor
new_points_tensor = torch.FloatTensor(new_points)
# 使用模型进行预测
model.eval()
with torch.no_grad():
predicted_points = model(new_points_tensor).detach().numpy()
误差计算
# 假设预测点和实际点的坐标已经存在
# new_points 是输入点 (x, y, z)
# predicted_points 是模型的预测点 (x, y, z)
# 生成实际的 z 值
actual_z = new_points[:, 0]**2 - new_points[:, 1]**2
# 计算预测的 z 值
predicted_z = predicted_points[:, 2]
# 计算误差
errors = np.abs(predicted_z - actual_z)
# 打印误差
print("Errors (absolute difference):", errors)
# 计算均方误差
mse = np.mean(errors**2)
print("Mean Squared Error (MSE):", mse)
# 计算均方根误差
rmse = np.sqrt(mse)
print("Root Mean Squared Error (RMSE):", rmse)
显示:
Errors (absolute difference): [0.00170907 0.15508103 0.11275303 0.18133295 0.01701735 0.14682424
0.00102288 0.07944739 0.01003169 0.18191075 0.11037481 0.0699572
0.00763437 0.07528615 0.13527524 0.17759663 0.00157501 0.07929492
0.00862253 0.1898663 0.03069082 0.15305173 0.09340668 0.13325691
0.03807937]
Mean Squared Error (MSE): 0.011934222265285684
Root Mean Squared Error (RMSE): 0.10924386603048102
可视化
# 绘制马鞍面
x = np.linspace(-1, 1, 100)
y = np.linspace(-1, 1, 100)
X, Y = np.meshgrid(x, y)
Z = X**2 - Y**2
# 使用交互式 matplotlib widget 绘制马鞍面
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.6)
# 添加预测点(如果有)
# ax.scatter(X_train[:, 0], X_train[:, 1], X_train[:, 2], c='g', marker='o', label='Surface Points')
# ax.scatter(y_train[:, 0], y_train[:, 1], y_train[:, 2], c='b', marker='o', label='Saddle Surface Points')
ax.scatter(predicted_points[:, 0], predicted_points[:, 1], predicted_points[:, 2], c='r', marker='x', s=100, label='Predicted Point')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.legend()
plt.show()
结果:

打赏博主
制作不易,如果有能帮到你,可以打赏博主一瓶可乐

1783

被折叠的 条评论
为什么被折叠?



