径向基函数神经网络RBFNN案例实操

简介

(来自ChatGPT的介绍,如有更正建议请指出)
径向基函数神经网络(Radial Basis Function Neural Network, RBFNN)是一种特殊的前馈神经网络,其结构和特点与其他常见的神经网络有所不同,主要表现在以下几个方面:

  1. 网络结构
  • 三层结构:RBF神经网络通常由三层组成:输入层、隐藏层和输出层。与常规的多层感知器(MLP)相比,其隐藏层的激活函数采用径向基函数(通常是高斯函数)。
  • 隐层节点:每个隐藏层节点表示一个中心点,输入到该节点的距离通过径向基函数进行转换,因此隐层节点的作用类似于一种局部响应单位。
  1. 径向基函数
  • 局部特性:径向基函数的输出是基于输入数据与某个中心的距离,输出在离中心较近的地方较大,离中心较远时趋近于零。常见的径向基函数包括高斯函数、多项式、逆多二次函数等。
  • 非线性映射:由于径向基函数可以对输入进行非线性映射,RBFNN具备了处理复杂非线性问题的能力。
  1. 训练过程
  • 两阶段训练:RBFNN的训练分为两个阶段。首先是通过无监督学习(如K均值聚类)确定隐藏层节点的中心,然后在第二阶段使用线性优化算法(如最小二乘法)来训练输出层的权重。这种方式相比传统的反向传播算法收敛速度更快。
  • 少量参数优化:因为RBF神经网络的输出层通常是线性权重,因此需要优化的参数数量较少,训练速度快。
  1. 应用场景
  • 函数逼近与插值:RBFNN特别适合用于函数逼近和插值问题,因为它对输入空间进行局部化响应,能够较好地拟合复杂的函数。
  • 分类和回归:RBFNN可以用来解决分类和回归问题,尤其适合那些具有明显聚类特性的任务。
  1. 特点总结
  • 良好的逼近能力: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()

结果:
在这里插入图片描述

打赏博主

制作不易,如果有能帮到你,可以打赏博主一瓶可乐
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值