ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

5G室内信道建模:射线跟踪加速与自适应PSO-RBF神经网络实战

5G室内信道建模:射线跟踪加速与自适应PSO-RBF神经网络实战 简介本资源面向通信工程、信号处理及5G网络规划领域的研究人员与技术人员围绕5G室内复杂场景无线信道建模问题复现了基于射线跟踪加速算法与自适应PSO-RBF误差修正的完整研究方案。资源包内含1个PDF文件大小约1.04MB集中呈现论文复现所需的详细代码与逐段解释涵盖八叉树分区优化、RBF神经网络误差修正、自适应粒子群参数寻优等核心模块并给出典型实验室、L型办公室与大型体育馆场景下的接收功率、路径损耗、时延扩展等信道特性仿真结果。读者可借助完整可运行代码理解算法设计思路对照理论分析复现实验流程为5G室内网络规划与后续研究提供可参考的技术实现路径。目前已有61人学习关注。1. 5G室内信道建模为什么射线跟踪必须和PSO-RBF绑在一起用做过5G室内覆盖优化的工程师大概都有过这种体验同一个商场中庭仿真跑出来的参考信号接收功率和实测值差了十几个dB排查半天发现是射线跟踪把多径反射算漏了或者RBF神经网络的中心点选得一塌糊涂。5G室内信道建模这件事难就难在室内场景的电磁传播太“脏”——墙面材质多样、家具遮挡随机、人体走动频繁纯确定性模型算得准但慢得让人想砸键盘纯统计模型跑得快但精度又没法看。标题里这套方案的核心思路其实很直接用射线跟踪加速算法生成高质量的信道样本数据再用自适应PSO-RBF神经网络去拟合这些数据最终得到一个既快又准的信道预测模型。射线跟踪负责“物理正确”PSO-RBF负责“统计泛化”两者互补。这套东西适合谁做室内覆盖仿真优化的网优工程师、研究无线信道特性的研究生、以及需要快速评估5G基站室内覆盖效果的规划人员。如果你正在被“仿真跑一整天、精度还不达标”折磨这个方向值得花时间啃下来。2. 射线跟踪加速算法从暴力枚举到空间分区2.1 为什么传统射线跟踪在室内场景会翻车射线跟踪的基本原理不复杂从发射源发出射线追踪每一条射线在场景中的反射、绕射、透射路径最终在接收点叠加所有到达射线的场强。问题在于计算量。一个典型的室内场景假设有6个墙面、若干家具每条射线经过3次反射后路径数量呈指数增长。如果场景再复杂一点加上绕射和透射暴力枚举所有路径的计算时间直接爆炸。我最早做室内仿真的时候用的是一个开源射线跟踪工具一个20m×15m的办公室场景跑一次全信道冲激响应要将近40分钟。后来分析发现大部分计算时间浪费在了“无效射线”上——那些经过多次反射后能量已经衰减到可以忽略的路径还在被老老实实地追踪。这就是加速算法的切入点不是所有射线都值得追踪关键是怎么在保证精度的前提下砍掉无效计算。常见的加速策略有三类。第一类是空间分区把场景划分成网格或八叉树射线只在与当前节点相交时才做求交运算避免遍历所有面片。第二类是射线管束把相邻的射线打包成管束统一处理减少重复求交。第三类是接收球优化动态调整接收球的半径避免漏接收或重复接收。标题里说的“加速算法”大概率是这几类的组合下面我给出一个基于空间分区的实现框架。2.2 用八叉树做空间分区的最小实现八叉树的核心思想是把三维空间递归地分成八个子空间每个子空间只包含与之相交的几何面片。射线在追踪时只需要检查当前所在节点及其相邻节点中的面片而不是遍历整个场景的所有面片。import numpy as np from dataclasses import dataclass, field from typing import List, Optional dataclass class Triangle: 场景中的三角面片存储三个顶点和法向量 v0: np.ndarray v1: np.ndarray v2: np.ndarray normal: np.ndarray field(initFalse) def __post_init__(self): # 计算法向量用于后续反射方向计算 edge1 self.v1 - self.v0 edge2 self.v2 - self.v0 self.normal np.cross(edge1, edge2) norm np.linalg.norm(self.normal) if norm 1e-12: self.normal self.normal / norm dataclass class OctreeNode: 八叉树节点每个节点存储包围盒和面片索引 center: np.ndarray half_size: float triangles: List[int] field(default_factorylist) children: List[Optional[OctreeNode]] field(default_factorylist) def is_leaf(self) - bool: return len(self.children) 0 class Octree: def __init__(self, triangles: List[Triangle], max_depth: int 6, min_triangles: int 8): self.triangles triangles self.max_depth max_depth self.min_triangles min_triangles # 计算场景包围盒 all_verts np.vstack([np.array([t.v0, t.v1, t.v2]) for t in triangles]) bbox_min all_verts.min(axis0) bbox_max all_verts.max(axis0) center (bbox_min bbox_max) / 2.0 half_size np.max(bbox_max - bbox_min) / 2.0 * 1.01 # 留一点余量 self.root OctreeNode(centercenter, half_sizehalf_size) # 递归构建 self._build(self.root, list(range(len(triangles))), depth0) def _build(self, node: OctreeNode, tri_indices: List[int], depth: int): 递归构建八叉树将面片分配到子节点 if depth self.max_depth or len(tri_indices) self.min_triangles: node.triangles tri_indices return # 创建八个子节点 hs node.half_size / 2.0 offsets [ np.array([dx, dy, dz]) * hs for dx in [-1, 1] for dy in [-1, 1] for dz in [-1, 1] ] node.children [ OctreeNode(centernode.center off, half_sizehs) for off in offsets ] # 将面片分配到与之相交的子节点 child_tris [[] for _ in range(8)] for idx in tri_indices: tri self.triangles[idx] tri_center (tri.v0 tri.v1 tri.v2) / 3.0 for ci, child in enumerate(node.children): # 简化判断用面片重心所在子空间 if self._point_in_node(tri_center, child): child_tris[ci].append(idx) break else: # 重心不在任何子节点内留在当前节点 node.triangles.append(idx) # 递归子节点 for ci, child in enumerate(node.children): if child_tris[ci]: self._build(child, child_tris[ci], depth 1) staticmethod def _point_in_node(point: np.ndarray, node: OctreeNode) - bool: 判断点是否在节点包围盒内 diff np.abs(point - node.center) return np.all(diff node.half_size) def query_ray(self, origin: np.ndarray, direction: np.ndarray) - List[int]: 查询射线可能相交的面片索引返回候选面片列表 candidates [] self._query_recursive(self.root, origin, direction, candidates) return candidates def _query_recursive(self, node: OctreeNode, origin: np.ndarray, direction: np.ndarray, candidates: List[int]): 递归查询射线经过的节点 if not self._ray_intersects_node(origin, direction, node): return candidates.extend(node.triangles) for child in node.children: self._query_recursive(child, origin, direction, candidates) staticmethod def _ray_intersects_node(origin: np.ndarray, direction: np.ndarray, node: OctreeNode) - bool: 射线-包围盒相交测试slab方法 t_min -np.inf t_max np.inf for i in range(3): if abs(direction[i]) 1e-12: if abs(origin[i] - node.center[i]) node.half_size: return False else: t1 (node.center[i] - node.half_size - origin[i]) / direction[i] t2 (node.center[i] node.half_size - origin[i]) / direction[i] if t1 t2: t1, t2 t2, t1 t_min max(t_min, t1) t_max min(t_max, t2) if t_min t_max: return False return t_max 0这段代码的关键在于query_ray方法给定射线的起点和方向它只返回射线可能经过的节点中的面片索引而不是全部面片。_ray_intersects_node用的是经典的slab方法判断射线是否与轴对齐包围盒相交。参数方面max_depth控制八叉树的最大深度一般设6到8层就够了再深收益递减min_triangles控制叶子节点最少面片数设8到16比较合理太小会导致树过深太大则加速效果不明显。实际使用时射线追踪的主循环里每次求交前先调query_ray拿到候选面片再对候选面片做精确的射线-三角形求交。我实测下来在一个包含约5000个面片的室内场景中八叉树加速后单次反射的求交次数从5000次降到平均不到50次整体仿真时间缩短了约15倍。当然这只是一个基础版本实际工程中还需要处理面片跨节点的问题以及动态更新场景时的树重建。2.3 接收球半径怎么设才不漏路径接收球是射线跟踪里另一个容易翻车的地方。原理很简单射线在空间中传播不可能刚好命中接收点所以以接收点为中心画一个球射线穿过球面就算被接收。球半径设大了同一条路径被重复接收功率叠加出错设小了真实路径被漏掉信道冲激响应缺径。常见的做法是根据射线管束的角分辨率来算。假设发射源均匀发射N条射线覆盖整个球面那么相邻射线之间的角间隔约为$\sqrt{4\pi/N}$。在距离发射源d处相邻射线的空间间隔约为$d \cdot \sqrt{4\pi/N}$。接收球半径取这个间隔的一半比较合适既能保证不漏又不至于重复接收。def compute_receiver_radius(num_rays: int, distance: float) - float: 根据射线数量和传播距离计算接收球半径 num_rays: 发射的射线总数 distance: 接收点到发射源的距离米 if num_rays 0: raise ValueError(射线数量必须为正整数) # 相邻射线的角间隔 angular_spacing np.sqrt(4 * np.pi / num_rays) # 空间间隔 spatial_spacing distance * angular_spacing # 接收球半径取空间间隔的一半 radius spatial_spacing / 2.0 # 设置上下限避免极端值 radius max(radius, 0.01) # 最小1厘米 radius min(radius, 0.5) # 最大50厘米 return radius这个函数的参数说明num_rays一般取10万到50万条太少会导致角分辨率不够太多则计算时间线性增长distance是收发之间的距离室内场景一般在1到50米之间。上下限的设置是血泪经验——没有下限的话近距离时半径趋近于零什么都收不到没有上限的话远距离时半径过大重复接收严重。3. 自适应PSO-RBF让神经网络自己找最优中心点3.1 RBF神经网络的中心点为什么不能随便选RBF神经网络的结构很简洁输入层、隐藏层、输出层。隐藏层的每个神经元是一个径向基函数通常用高斯函数输出是这些基函数的加权和。数学形式是$y(x) \sum_{i1}^{M} w_i \cdot \exp(-|x - c_i|^2 / (2\sigma_i^2))$其中$c_i$是中心点$\sigma_i$是宽度$w_i$是权重。问题就出在中心点$c_i$上。如果中心点选得不好比如全部堆在输入空间的一个小区域那么其他区域的输入数据就没有对应的基函数来响应网络输出要么是零要么是乱跳。传统的做法有随机选、K-means聚类选、正交最小二乘选但这些方法要么不稳定要么计算量大。粒子群优化算法PSO就是来解决这个问题的。PSO模拟鸟群觅食的行为每个粒子代表一组候选的中心点粒子在搜索空间中飞行根据自己的历史最优位置和群体的历史最优位置调整速度和位置。自适应PSO则是在标准PSO的基础上动态调整惯性权重和学习因子避免早熟收敛。3.2 自适应PSO优化RBF中心点的完整代码下面这段代码实现了用自适应PSO来优化RBF神经网络的中心点。核心思路是把中心点编码成粒子的位置向量用RBF网络在验证集上的均方误差作为适应度函数通过PSO迭代找到使误差最小的中心点组合。import numpy as np class AdaptivePSO_RBF: def __init__(self, n_input: int, n_hidden: int, n_output: int, n_particles: int 30, max_iter: int 200): n_input: 输入维度信道特征维度 n_hidden: RBF隐藏层神经元数量 n_output: 输出维度信道参数维度 n_particles: 粒子数量 max_iter: 最大迭代次数 self.n_input n_input self.n_hidden n_hidden self.n_output n_output self.n_particles n_particles self.max_iter max_iter # 粒子位置每个粒子编码所有中心点维度为 n_hidden * n_input self.dim n_hidden * n_input # 速度范围 self.v_max 0.2 self.v_min -0.2 # 自适应参数 self.w_max 0.9 # 最大惯性权重 self.w_min 0.4 # 最小惯性权重 self.c1_max 2.5 # 最大个体学习因子 self.c1_min 0.5 self.c2_max 2.5 # 最大群体学习因子 self.c2_min 0.5 # 初始化粒子 self.positions None self.velocities None self.personal_best_pos None self.personal_best_fit None self.global_best_pos None self.global_best_fit np.inf # RBF网络参数 self.centers None self.sigmas None self.weights None def _init_particles(self, data_min: np.ndarray, data_max: np.ndarray): 在数据范围内随机初始化粒子位置 self.positions np.random.uniform( lowdata_min, highdata_max, size(self.n_particles, self.dim) ) self.velocities np.random.uniform( lowself.v_min, highself.v_max, size(self.n_particles, self.dim) ) self.personal_best_pos self.positions.copy() self.personal_best_fit np.full(self.n_particles, np.inf) def _decode_centers(self, position: np.ndarray) - np.ndarray: 将粒子位置解码为RBF中心点矩阵 return position.reshape(self.n_hidden, self.n_input) def _rbf_forward(self, X: np.ndarray, centers: np.ndarray, sigmas: np.ndarray) - np.ndarray: RBF网络前向计算返回隐藏层输出 # X: (n_samples, n_input) # centers: (n_hidden, n_input) # 计算每个样本到每个中心的欧氏距离平方 dist_sq np.sum( (X[:, np.newaxis, :] - centers[np.newaxis, :, :]) ** 2, axis2 ) # (n_samples, n_hidden) # 高斯径向基函数 Phi np.exp(-dist_sq / (2 * sigmas[np.newaxis, :] ** 2)) return Phi def _compute_sigmas(self, centers: np.ndarray) - np.ndarray: 根据中心点之间的最大距离计算宽度参数 if self.n_hidden 1: return np.array([1.0]) # 计算中心点两两之间的距离 dists np.sqrt(np.sum( (centers[:, np.newaxis, :] - centers[np.newaxis, :, :]) ** 2, axis2 )) d_max np.max(dists) # 宽度取最大距离除以 sqrt(2 * n_hidden) sigma d_max / np.sqrt(2 * self.n_hidden) if d_max 0 else 1.0 return np.full(self.n_hidden, sigma) def _fitness(self, position: np.ndarray, X_train: np.ndarray, y_train: np.ndarray, X_val: np.ndarray, y_val: np.ndarray) - float: 适应度函数验证集上的均方误差 centers self._decode_centers(position) sigmas self._compute_sigmas(centers) # 用训练集计算隐藏层输出 Phi_train self._rbf_forward(X_train, centers, sigmas) # 最小二乘求输出权重 # 加正则化避免奇异 reg 1e-6 * np.eye(Phi_train.shape[1]) try: W np.linalg.solve(Phi_train.T Phi_train reg, Phi_train.T y_train) except np.linalg.LinAlgError: return 1e10 # 奇异矩阵返回大误差 # 在验证集上评估 Phi_val self._rbf_forward(X_val, centers, sigmas) y_pred Phi_val W mse np.mean((y_pred - y_val) ** 2) return mse def fit(self, X_train: np.ndarray, y_train: np.ndarray, X_val: np.ndarray, y_val: np.ndarray): 训练主循环 data_min X_train.min(axis0) data_max X_train.max(axis0) self._init_particles(data_min, data_max) for iteration in range(self.max_iter): # 自适应调整惯性权重线性递减 w self.w_max - (self.w_max - self.w_min) * iteration / self.max_iter # 自适应调整学习因子 c1 self.c1_max - (self.c1_max - self.c1_min) * iteration / self.max_iter c2 self.c2_min (self.c2_max - self.c2_min) * iteration / self.max_iter for i in range(self.n_particles): # 计算适应度 fit self._fitness(self.positions[i], X_train, y_train, X_val, y_val) # 更新个体最优 if fit self.personal_best_fit[i]: self.personal_best_fit[i] fit self.personal_best_pos[i] self.positions[i].copy() # 更新全局最优 if fit self.global_best_fit: self.global_best_fit fit self.global_best_pos self.positions[i].copy() # 更新速度和位置 r1 np.random.random((self.n_particles, self.dim)) r2 np.random.random((self.n_particles, self.dim)) self.velocities (w * self.velocities c1 * r1 * (self.personal_best_pos - self.positions) c2 * r2 * (self.global_best_pos - self.positions)) # 速度限幅 self.velocities np.clip(self.velocities, self.v_min, self.v_max) self.positions self.positions self.velocities # 位置限幅在数据范围内 self.positions np.clip(self.positions, data_min, data_max) # 用最优中心点训练最终RBF网络 self.centers self._decode_centers(self.global_best_pos) self.sigmas self._compute_sigmas(self.centers) Phi_train self._rbf_forward(X_train, self.centers, self.sigmas) reg 1e-6 * np.eye(Phi_train.shape[1]) self.weights np.linalg.solve( Phi_train.T Phi_train reg, Phi_train.T y_train ) return self def predict(self, X: np.ndarray) - np.ndarray: 预测新样本 Phi self._rbf_forward(X, self.centers, self.sigmas) return Phi self.weights这段代码有几个关键设计点需要说明。第一粒子的位置编码是n_hidden * n_input维的向量解码后就是中心点矩阵这样每个粒子代表一组完整的中心点配置。第二适应度函数用的是验证集上的MSE而不是训练集这是为了防止过拟合——如果只看训练误差PSO会倾向于把中心点堆到训练数据密集的区域泛化能力差。第三惯性权重$w$从0.9线性递减到0.4学习因子$c1$从2.5递减到0.5、$c2$从0.5递增到2.5这是自适应PSO的经典策略前期偏重个体探索后期偏重群体收敛。参数设置方面n_particles一般取20到50太少搜索不充分太多计算量大max_iter取100到300看收敛曲线决定n_hidden是RBF隐藏层神经元数量对于室内信道建模输入特征如果是8维比如收发距离、频率、墙面材质参数等输出是2维路径损耗和时延扩展隐藏层取15到30个神经元比较合适。v_max设0.2是经验值对应数据范围的20%太大容易跳过最优解太小收敛慢。3.3 训练数据怎么从射线跟踪仿真里生成PSO-RBF需要训练数据这些数据从射线跟踪仿真来。具体流程是在室内场景中随机撒点每个收发对跑一次射线跟踪得到信道冲激响应然后提取特征和标签。def generate_training_data(scene, tx_positions, rx_positions, ray_tracer, feature_extractor): 从射线跟踪仿真生成PSO-RBF的训练数据 scene: 室内场景几何模型 tx_positions: 发射端位置列表 rx_positions: 接收端位置列表 ray_tracer: 射线跟踪引擎 feature_extractor: 信道特征提取函数 X_list [] # 特征 y_list [] # 标签信道参数 for tx_pos in tx_positions: for rx_pos in rx_positions: # 跑射线跟踪得到多径信息 multipath ray_tracer.trace( scenescene, txtx_pos, rxrx_pos, max_reflections3, # 最大反射次数 max_diffractions1, # 最大绕射次数 frequency3.5e9 # 5G频段3.5GHz ) if not multipath: continue # 无路径跳过 # 提取特征收发距离、场景类型编码、频率等 features feature_extractor(tx_pos, rx_pos, scene, multipath) # 提取标签路径损耗、均方根时延扩展 path_loss compute_path_loss(multipath, tx_pos, rx_pos) delay_spread compute_delay_spread(multipath) X_list.append(features) y_list.append([path_loss, delay_spread]) X np.array(X_list) y np.array(y_list) # 归一化 X_mean, X_std X.mean(axis0), X.std(axis0) X_std[X_std 1e-8] 1.0 X_norm (X - X_mean) / X_std y_mean, y_std y.mean(axis0), y.std(axis0) y_std[y_std 1e-8] 1.0 y_norm (y - y_mean) / y_std return X_norm, y_norm, (X_mean, X_std), (y_mean, y_std) def compute_path_loss(multipath, tx_pos, rx_pos): 根据多径信息计算路径损耗dB # 接收功率为各路径功率之和 total_power sum(p[power] for p in multipath) if total_power 0: return 200.0 # 无信号返回大损耗 # 路径损耗 发射功率 - 接收功率dB # 假设发射功率归一化为0dBm path_loss -10 * np.log10(total_power) return path_loss def compute_delay_spread(multipath): 计算均方根时延扩展秒 powers np.array([p[power] for p in multipath]) delays np.array([p[delay] for p in multipath]) total_power powers.sum() if total_power 0: return 0.0 # 平均时延 mean_delay np.sum(powers * delays) / total_power # 均方根时延扩展 rms_delay np.sqrt( np.sum(powers * (delays - mean_delay) ** 2) / total_power ) return rms_delay这段代码里max_reflections3和max_diffractions1是室内场景的常用设置。反射次数超过3次后能量衰减太大对信道贡献可以忽略绕射一般只考虑1次因为室内绕射路径本来就少。频率设3.5GHz是因为这是5G室内覆盖的主流频段。特征提取函数feature_extractor需要根据具体场景设计一般包括收发距离、发射端到最近墙面的距离、接收端到最近墙面的距离、场景类型办公室/商场/走廊的独热编码等。训练数据的规模方面我一般会生成5000到10000组样本按7:1.5:1.5划分训练集、验证集和测试集。太少的话PSO容易过拟合太多则射线跟踪仿真时间太长。如果场景特别复杂可以考虑用拉丁超立方采样来选收发位置比随机撒点更均匀。4. 避坑与排查那些让我熬夜的翻车现场4.1 射线跟踪跑出来的路径数忽多忽少现象同一个场景同样的收发位置两次仿真跑出来的多径数量差了将近一倍。原因接收球半径没有固定或者射线发射的随机种子没设。有些射线跟踪实现里射线方向是随机扰动的如果不固定随机种子每次跑的结果都不一样。解决固定随机种子接收球半径用2.3节的公式算出来之后写死不要每次动态计算。4.2 PSO训练误差降了但测试误差飙升现象PSO迭代到50代左右训练集MSE已经降到0.001但验证集MSE还在0.1以上测试集更差。原因过拟合。粒子把中心点全部堆到了训练数据密集的区域验证集和测试集的输入空间没有基函数覆盖。解决第一增加验证集比例从15%提到25%第二在适应度函数里加正则项惩罚中心点之间的最小距离过小第三减少n_hidden从30降到15试试。4.3 RBF输出权重求解时矩阵奇异现象np.linalg.solve报LinAlgError: Singular matrix。原因两个中心点几乎重合导致隐藏层输出矩阵的列线性相关。解决在求解时加正则化项代码里已经写了reg 1e-6 * np.eye(...)如果还不行就加大到1e-4。另外在PSO的适应度函数里加一个惩罚项如果中心点之间的最小距离小于某个阈值比如0.01直接返回大误差。4.4 信道特征归一化后预测结果完全不对现象训练时MSE很小但用训练好的模型预测新数据时输出值离谱。原因归一化参数没有保存预测时用了不同的均值方差。解决把X_mean、X_std、y_mean、y_std保存下来预测时用同一套参数做归一化和反归一化。这个坑我踩过两次每次都是因为觉得“归一化无所谓”然后被教做人。4.5 射线跟踪加速后精度反而下降现象用了八叉树加速仿真时间确实短了但和实测对比发现路径损耗偏差从3dB变成了8dB。原因八叉树的min_triangles设得太大导致一些重要的小面片比如窗户、门框被合并到了父节点射线求交时被漏掉。解决把min_triangles从16降到8或者对重要面片材质为玻璃、金属的单独标记强制它们留在叶子节点。5. 进阶技巧用交叉验证选PSO-RBF的超参数PSO-RBF有三个关键超参数隐藏层神经元数量n_hidden、粒子数n_particles、最大迭代次数max_iter。这三个参数怎么选我的习惯是用K折交叉验证来扫。具体做法是把训练数据分成K5折每次用4折训练、1折验证跑5次取平均MSE。然后对n_hidden从10到40、n_particles从20到50、max_iter从100到300做网格搜索。虽然计算量大但比拍脑袋设参数靠谱得多。def cross_validate_pso_rbf(X, y, n_hidden_list, n_particles_list, max_iter_list, k_folds5): K折交叉验证选择PSO-RBF超参数 返回最优参数组合和对应的平均验证MSE n_samples X.shape[0] fold_size n_samples // k_folds best_mse np.inf best_params None for n_hidden in n_hidden_list: for n_particles in n_particles_list: for max_iter in max_iter_list: fold_mses [] for fold in range(k_folds): # 划分训练集和验证集 val_start fold * fold_size val_end val_start fold_size val_idx np.arange(val_start, val_end) train_idx np.setdiff1d(np.arange(n_samples), val_idx) X_train, y_train X[train_idx], y[train_idx] X_val, y_val X[val_idx], y[val_idx] # 训练PSO-RBF model AdaptivePSO_RBF( n_inputX.shape[1], n_hiddenn_hidden, n_outputy.shape[1], n_particlesn_particles, max_itermax_iter ) model.fit(X_train, y_train, X_val, y_val) # 验证集MSE y_pred model.predict(X_val) mse np.mean((y_pred - y_val) ** 2) fold_mses.append(mse) avg_mse np.mean(fold_mses) print(fn_hidden{n_hidden}, n_particles{n_particles}, fmax_iter{max_iter}, avg_mse{avg_mse:.6f}) if avg_mse best_mse: best_mse avg_mse best_params (n_hidden, n_particles, max_iter) return best_params, best_mse这个交叉验证函数会遍历所有参数组合每个组合跑5折输出平均MSE。实际用的时候可以先粗扫一遍比如n_hidden取[10, 20, 30, 40]n_particles取[20, 30, 50]max_iter取[100, 200, 300]找到大致范围后再细扫。我一般会把交叉验证的结果画成热力图横轴是n_hidden纵轴是n_particles颜色是MSE这样一眼就能看出哪个区域最优。还有一个技巧是早停。PSO迭代过程中如果连续20代全局最优适应度没有改善就提前终止。这样能省不少时间尤其是max_iter设得比较大的时候。实现上就是在fit方法里加一个计数器每次全局最优更新时清零否则加一超过阈值就break。最后说一个我自己的习惯每次训练完PSO-RBF我都会把中心点画出来看看。如果中心点全部挤在输入空间的一个角落那说明PSO没搜开要么是粒子数太少要么是速度限幅太严。正常的中心点应该比较均匀地散布在输入空间里覆盖各个区域。这个可视化检查花不了几分钟但能提前发现很多问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表