2025年聊模型创新,大家都有点审美疲劳了。光是我见过的所谓“新架构”,十个里有九个是把已有的模块换个顺序接起来,然后重新起个名字。真正让我觉得值得动手折腾一下的,是去年开始被反复讨论的CNN-LSTM-KAN这种混合结构。KAN出来之后,很多人第一反应是拿它替换MLP做分类头,但说实话,纯替换的实验结果并不稳定。真正把它和CNN、LSTM放在一起,组合成一个完整的预测模型,反而在时序任务上跑出了不少意外收获。这篇文章不堆理论,我把自己实践验证过的CNN-LSTM-KAN模型思路、PyTorch代码、训练时踩过的坑和调参经验整理出来,给正在做时间序列预测、想给模型加点真正有价值创新点的读者一些参考。
1. CNN、LSTM和KAN各自擅长什么,组合逻辑在哪里
1.1 CNN:把一维卷积当作局部特征探测器
很多做时序预测的人有个误区,觉得CNN是搞图像的,跟序列数据没关系。实际上,一维卷积在处理时间序列里极其好用。它的本质是让一个小窗口在数据上滑动,去学习局部形态特征——比如传感器数据里一个突然的尖峰、一段趋势的拐点、短期波动的振幅模式。这些局部模式不需要你手工设计特征工程,卷积核自己会学。
我用CNN不是为了替代LSTM,而是在数据进入时序建模之前,先做一层“特征预提取”。这个角色很像你先对原始信号做了一遍降噪和关键形态强化,LSTM后面接收到的就不再是原始的、带噪声的数值序列,而是更有语义的局部特征序列。
实际操作里,我给一维卷积设定的核大小一般在3到7之间,太大容易把不同周期的模式混在一起,太小又只看到个位数的点,学不到上下文。核心是用小卷积核叠加、配合池化或者步长来扩大感受野,而不是一上来就塞一个大核。这几年很多模型动辄把卷积堆到十几层,但在混合模型里,CNN只是整体架构的一个前端处理器,一两层、通道数64到128通常就够用了,堆太多反而让LSTM输入端的信息变得过于平滑。
1.2 LSTM:从局部特征到长期依赖关系
CNN抓的是“形态”,LSTM抓的是“记忆”。时间序列预测里最难的不是看眼前这几十个点,而是几十步之前的一个信号变化,会影响后面很长一段时间的走势。LSTM通过细胞状态和三个门控结构(输入门、遗忘门、输出门),决定哪些历史信息要保留、哪些要丢弃。这种机制天然适合处理CNN抽象出来的局部特征序列之间的依赖关系。
我试过用GRU替换LSTM做对比实验。GRU参数更少、训练更快,在很多数据上效果差距不大。但如果你处理的数据存在明显的长期周期性(比如风功率预测里几天甚至几周的天气系统演变),LSTM的独立记忆细胞能保留更长的上下文,训练出来的最终精度普遍比GRU高几个点。也就是说,参数多带来的容量开销,在很多场景下是值得的。
还有一个小细节:LSTM层的输出怎么接下游模块。常见有两条路——取最后一个时间步的隐藏状态,或者把所有时间步的隐藏状态全部展平。取最后一步的好处是结构简洁,但会丢掉中间时刻的信息;全部展平则特征维度暴涨,KAN层的输入规模就会变大,训练负担直接上升。我一般在预测任务里取最后一步的输出,效果稳定且计算量小。
1.3 KAN:把又笨又重的MLP换成可学习的非线性映射
KAN(Kolmogorov-Arnold Networks)跟前两者完全不是一个层面的东西。CNN和LSTM是特征提取架构,而KAN的定位是“最后的非线性映射器”——也就是把高维特征空间映射到最终预测输出的那部分。
传统模型到这一步一般接几层全连接(MLP)。MLP的问题是每个神经元内部的激活函数是固定的(ReLU、GELU这些),网络只能通过堆层数和加大宽度去逼近复杂的非线性关系。KAN的切入点是:既然要学函数映射,为什么不让“激活函数”本身可学习?在KAN里,每条连接边不再是一个标量权重,而是一个B样条函数,网络通过学习B样条基函数的组合系数来拟合任意非线性关系。
所以你会发现这个三明治结构非常合理:CNN负责从原始数据里提炼局部特征,LSTM负责把这些特征串成有时间上下文的信息流,最后KAN承担高维非线性映射。每一层干了它最擅长的事,这就是所谓“创新点”存在的合理基础,而不是为了结构复杂而复杂。
2. KAN到底改了什么:从MLP到可学习样条激活的代码级差异
2.1 MLP的瓶颈不是深度不够,而是激活函数是固定的
MLP的单个神经元做的事情可以概括为:先对输入做线性加权求和,然后过一个固定的非线性激活函数。这个设计的问题在于,网络的非线性表达能力完全靠激活函数的形状,而ReLU也好、GELU也好,它们的形状是写死的。哪怕你堆很深、搞很宽,本质上还是在用一堆固定形状的函数去拼凑目标映射,碰到复杂函数,要么靠极宽的隐层硬怼,要么训练难度陡增。
2.2 KAN的数学直觉:多元函数分解为一元函数之和
KAN的理论基础是Kolmogorov-Arnold表示定理,大致意思是:任意一个多元连续函数,都可以用有限个一元连续函数相加的形式来表示。这个定理很早就有,但一直没被做成实用的网络结构。KAN把它变成了现实:网络每一层做的事情,不再是线性变换加固定激活,而是让每个连接都对应一个可学习的一维样条函数,然后对这些函数输出求和。
这个思路用大白话讲,就是原来一条连接上只有一个音量旋钮(标量权重),现在变成了一个功能复杂的均衡器(B样条函数),每个频段都能单独调节。MLP是用一堆固定形状的砖头去拼一个雕塑,KAN是直接用可以自由塑造的黏土去捏。
在实际代码里,KAN层的输入输出接口和nn.Linear长得一模一样:你给我一个(batch, in_features)的张量,我还你一个(batch, out_features)的张量。但从参数结构到函数映射逻辑,两者差异巨大。
2.3 KANLinear和nn.Linear的直观对比
下面这张表是我在迁移代码时给自己列的对照,能帮你快速理解KAN到底在哪些方面动了手脚:
| 维度 | nn.Linear | KANLinear |
|---|---|---|
| 连接参数 | 每个连接一个标量权重 | 每个连接一组B样条基函数系数 |
| 激活函数 | ReLU / GELU 等固定函数 | B样条曲线,本身可学习 |
| 非线性来源 | 模块外部堆叠激活层 | 模块内部每个连接自带非线性 |
| 参数数量 | 相对少 | 相对多(但有剪枝手段) |
| 可解释性 | 黑盒,难以分析 | 可可视化样条形状,可剪枝 |
| 数值稳定性 | 稳定 | 对网格范围、初始化敏感 |
从这张表里能看出来,KAN不是简单地把某个激活函数换掉,而是在整个映射机制上都跟MLP分道扬镳。这也是为什么身边很多人第一次跑KAN训练时会觉得它“难伺候”——你对nn.Linear那套习以为常的参数初始化、学习率、正则化策略,到KAN上需要重新摸索。
2.4 代码里怎么用:一个小例子
我实际项目里的做法是直接用一个成熟的KAN实现库,因为自己从零写B样条基函数计算很容易在数值边界上出问题。代码里通常是这样:
from kan_impl import KANLinear # 根据你装的KAN库调整导入名称 # 使用方式几乎和 nn.Linear 一样 kan_layer = KANLinear( in_features=64, out_features=32, grid_size=5, spline_order=3, base_activation=torch.nn.SiLU ) x = torch.randn(32, 64) y = kan_layer(x) # (32, 32)这里grid_size表示每个样条函数用几个网格区间来划分,可以把它理解为“这个函数的分辨率”。spline_order是样条阶数,3阶就是三次样条,已经覆盖绝大多数使用场景。越大,函数越灵活,但参数也越多、训练更慢。
如果你用的库不一样,别慌,接口大同小异,核心就是in_features、out_features、grid_size和spline_order这四个参数。
3. 从零搭建CNN-LSTM-KAN:可复现的PyTorch模型代码
3.1 任务设定与数据预处理
为了把代码讲清楚,我以一个典型的多变量时间序列预测任务为例:用过去96个时间点(大概4天)的多维观测,预测未来24个时间点(1天)的目标变量。这在电力负荷预测、风电功率预测、设备健康度预测里都是非常常见的问题形式。
数据预处理的核心是三件事:归一化、滑窗、切分。我用StandardScaler对每个特征独立做标准化,注意只用训练集的均值和方差去变换验证集和测试集,避免未来信息泄漏。滑窗部分的关键是样本之间要不要重叠,我的经验是重叠滑窗能成倍增加样本量,对KAN这种参数偏多的结构来说,数据量往往比模型结构更能决定效果。
import numpy as np import pandas as pd import torch from torch import nn from torch.utils.data import Dataset, DataLoader from sklearn.preprocessing import StandardScaler class TimeSeriesDataset(Dataset): def __init__(self, data, history_len=96, forecast_len=24, target_idx=0): self.data = torch.FloatTensor(data) self.history_len = history_len self.forecast_len = forecast_len self.target_idx = target_idx self.samples = len(data) - history_len - forecast_len + 1 def __len__(self): return self.samples def __getitem__(self, idx): x = self.data[idx: idx + self.history_len] y = self.data[idx + self.history_len: idx + self.history_len + self.forecast_len, self.target_idx] return x, y归一化这一步特别提醒:不要对整体数据一次性做fit,我在实践中见过不少因为先整体归一化再切分导致验证集分数虚高的例子。正确的流程是训练集fit_transform,验证集和测试集只transform。
3.2 模型结构定义
模型结构是这篇的核心。CNN层把输入序列做局部特征提取,LSTM处理时间依赖,最后KAN做回归映射。
import torch.nn.functional as F class CNNLSTMKAN(nn.Module): def __init__( self, input_features: int, hidden_dim: int = 64, lstm_layers: int = 2, forecast_len: int = 24, kan_hidden: int = 32, grid_size: int = 5, spline_order: int = 3, dropout: float = 0.2, ): super().__init__() self.forecast_len = forecast_len # 1. CNN局部特征提取 self.conv_block = nn.Sequential( nn.Conv1d(in_channels=input_features, out_channels=hidden_dim, kernel_size=5, padding=2), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Conv1d(in_channels=hidden_dim, out_channels=hidden_dim, kernel_size=3, padding=1), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Dropout(dropout), ) # 2. LSTM时序建模 self.lstm = nn.LSTM( input_size=hidden_dim, hidden_size=hidden_dim, num_layers=lstm_layers, batch_first=True, dropout=dropout if lstm_layers > 1 else 0, ) # 3. KAN回归头 self.kan = nn.Sequential( KANLinear( in_features=hidden_dim, out_features=kan_hidden, grid_size=grid_size, spline_order=spline_order, ), nn.LayerNorm(kan_hidden), KANLinear( in_features=kan_hidden, out_features=forecast_len, grid_size=grid_size, spline_order=spline_order, ), ) def forward(self, x): # x: (batch, history_len, input_features) # Conv1d 需要 (batch, channels, length),所以做维度交换 x_cnn = x.permute(0, 2, 1).contiguous() x_cnn = self.conv_block(x_cnn) x_cnn = x_cnn.permute(0, 2, 1).contiguous() lstm_out, _ = self.lstm(x_cnn) last_hidden = lstm_out[:, -1, :] # 取最后时间步 out = self.kan(last_hidden) return out这个结构里有几个细节值得解释一下:
第一个是卷积层的padding。我选了kernel_size=5配padding=2、kernel_size=3配padding=1,这样卷积前后序列长度不变,LSTM拿到的还是96个时间步的特征序列。如果你想下采样,可以在中间插入MaxPool1d或AvgPool1d,但针对预测任务我测试过,保持序列长度往往效果更好,下采样容易丢边界信息。
第二个是LSTM的batch_first=True,这是PyTorch LSTM的一个常见坑点。默认情况下输入维度是(seq_len, batch, features),改成True后才是我们习惯的(batch, seq_len, features),跟数据集返回张量的形状对齐,代码再往下走就不容易出错。
第三是KAN层之间的LayerNorm。这个层在KAN前面特别重要——KAN的样条基函数对输入分布很敏感,输入如果飘到网格覆盖范围之外,样条行为会变得不可控。LayerNorm先把特征拉回到稳定范围,再进下一层KAN,训练稳定性会好很多。
3.3 训练主循环
训练部分我用的是HuberLoss作为损失函数。相比MSE,它对离群点的惩罚更温和;相比MAE,它在接近零时梯度更平滑。时间序列预测里噪声尖峰经常出现,HuberLoss能让loss曲线平稳一些。
def train_model(model, train_loader, val_loader, epochs=50, lr=1e-3, device="cuda"): model = model.to(device) optimizer = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs) criterion = nn.HuberLoss(delta=1.0) for epoch in range(epochs): model.train() train_loss = 0.0 for x, y in train_loader: x, y = x.to(device), y.to(device) optimizer.zero_grad() pred = model(x) loss = criterion(pred, y) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0) optimizer.step() train_loss += loss.item() * x.size(0) model.eval() val_loss = 0.0 with torch.no_grad(): for x, y in val_loader: x, y = x.to(device), y.to(device) pred = model(x) loss = criterion(pred, y) val_loss += loss.item() * x.size(0) scheduler.step() train_loss = train_loss / len(train_loader.dataset) val_loss = val_loss / len(val_loader.dataset) print(f"Epoch {epoch+1:03d} | Train Loss: {train_loss:.6f} | Val Loss: {val_loss:.6f}") return modelclip_grad_norm_这行不要删。KAN的B样条参数在训练初期对梯度的响应比普通线性层剧烈,梯度爆炸是常见现象,裁剪一下能避免loss直接飞到NaN。学习率调度器用CosineAnnealingLR是我试过的几种里最稳妥的,比StepLR平滑,不容易在KAN层触发震荡。
3.4 运行时常见的维度问题
维度错误是我复现这类模型时被别人问得最多的问题。十个人里有七八个会在维度上卡住,典型如下:
Conv1d期望输入是(batch, channels, length),而数据集的张量是(batch, length, features)。所以forward里必须先permute再卷积,忘记这个操作必定报错。- KAN层的输入维度必须等于LSTM隐藏层的维度。我上面代码里最后取的
lstm_out[:, -1, :]就是(batch, hidden_dim)。 - 预测目标是未来24个点的值,因此KAN最后一层
out_features=forecast_len=24,这个输出维度跟损失函数里的y要严格匹配。
如果改成“多步预测”变体,比如每一步预测一个值然后循环反馈给输入,那KAN输出维度要改成1,序列推进在推理循环里做。这个写法灵活但调试周期长,新手建议先跑通我上面这种直接多步输出的版本,把模型产出稳定下来再扩展。
4. 训练实测记录:收敛慢、loss抖动与网格参数调优
4.1 第一次跑通:“跑起来”和“跑得好”是两个世界
照着上面的代码把模型跑通,大概也就是一晚上的事。真正折磨人的是效果调优阶段。我第一次用默认参数(grid_size=5,spline_order=3,lr=1e-3)跑一组风速数据,前10个epoch的loss曲线上下乱跳,验证集loss比训练集低一个数量级,明摆着是数值问题而非泛化问题。这种状况在纯LSTM或者CNN-LSTM时代很少遇到,我当时第一反应是代码写错了,但排查下来发现问题出在KAN层的特性上。
KAN训练初期的震荡基本都跟B样条基函数有关:样条函数覆盖的区间如果跟数据分布对不上,激活值可能落到网格边界外,产生不可预料的剧烈非线性响应。数据标准化只能解决一部分问题,因为LSTM输出的隐藏状态并不严格落在标准正态分布里。
4.2 排查一:数据分布与网格范围
我先打印了训练过程中KAN层输入特征的均值和标准差,发现它并不是理想中的0和1,而是均值接近0但标准差只有0.4左右,而且有个别特征的max绝对值超过了2。默认网格范围是[-1, 1],实际上有相当比例的输入样本落在网格之外的区域,样条在那里的行为完全靠外推,剧烈抖动就不奇怪了。
解决思路有两个方向:一是把输入特征通过一个Tanh函数压回[-1, 1],二是把网格范围拉开。实际上,调整网格范围比强行压缩输入更有效,因为Tanh在两端会饱和,等于给模型额外加了一层非线性失真。我最后是直接把KAN库里的网格范围从[-1, 1]改成[-3, 3],效果立竿见影,loss曲线当天就安静下来了。
4.3 排查二:初始化和学习率
调整网格范围后loss不再乱飞,但新的问题变成了收敛极慢,50个epoch跑完验证集loss还在缓慢下降,没有明显收敛迹象。
排查后定位到两个原因。第一个是KAN层的参数初始化。KANLinear里B样条系数默认初始化的尺度比普通线性层小很多,导致早期梯度信号很弱。我把初始化里的权重缩放系数从默认值调大了一些,前向传播的激活值分布明显更合理,loss下降速度加快。第二个是学习率策略,KAN层和CNN/LSTM层对学习率的敏感度不一样——LSTM在lr=1e-3时收敛正常,但KAN层在同一个学习率下出现了优化路径不稳的情况。我的做法是分参数组设置不同的学习率:KAN层单独用lr=1e-4,CNN和LSTM用lr=1e-3,效果最理想。
4.4 不同网格参数对结果的影响
我在同一组数据上做了一个小规模对比实验,记录不同网格配置下的最终验证集RMSE:
| grid_size | spline_order | 学习率 | 验证集RMSE | 训练耗时(times) | 备注 |
|---|---|---|---|---|---|
| 3 | 2 | 1e-3 | 0.1245 | 1.0x | 收敛快,但拟合精度有限 |
| 5 | 3 | 1e-3 | 0.1162 | 1.6x | 精度提升,训练OK |
| 5 | 3 | 1e-4(KAN层) | 0.1089 | 1.8x | 最稳,推荐 |
| 8 | 3 | 1e-4(KAN层) | 0.1097 | 2.5x | 参数多了但精度没有明显提升 |
| 10 | 4 | 1e-4(KAN层) | 0.1121 | 4.2x | 过拟合,验证集反弹 |
这个结果完整呈现了KAN调参的边际效应:grid_size=5、spline_order=3已经站在性价比的拐点上,继续加大网格并不能稳定提升精度,只是让训练时间成倍上涨。spline_order=4的过拟合迹象在训练后期很明显,因为高阶样条的自由度太大,在有限样本上极容易把训练集的细节噪声学进去。
4.5 调优之后我留下的固定配置
经过几轮折腾,我现在在CNN-LSTM-KAN模型上默认使用以下配置:
grid_size=5,高维输入就降到4,输入维度小且样本充足才考虑6以上spline_order=3,这个基本不调了- KAN层单独设置学习率为其他层的十分之一
- 每个KAN层前接
LayerNorm,这是稳定训练的隐形功臣 - 梯度裁剪
max_norm=1.0,防止样条系数的梯度爆炸
这套配置我不敢说是全局最优,但在风速、电力负荷、设备振动等多组数据上表现稳定,至少不会出现训练崩溃或者验证集抖动这些入门阶段最头疼的问题。
5. 面向2025年的创新扩展方向与实验设计建议
5.1 别满足于“三明治”:把KAN放进注意力机制
很多学生上手就是拿KAN替换最后一层全连接,这当然能发小论文,但审稿人现在看到这种改法已经不太感冒了。更进一步的方向是把KAN渗透到模型内部,比如用KAN来做注意力打分。
传统注意力的QK^T是线性操作,本质上是两个向量的点积,表达能力有限。你可以让KAN学习一个非线性相似度函数,输入是查询向量和键向量的拼接,输出是注意力分数。这样做的好处是注意力权重不再受线性点积的约束,理论上能捕捉到更复杂的关联模式。代价是计算量上升,一整套注意力层跑下来比原来慢不少,适合序列长度不太大的任务。
5.2 多尺度CNN与双向LSTM的变体组合
如果你手里的数据本身包含多种时间尺度——比如既有小时级波动又有天级趋势——那单一卷积核的感受野就不够用了。我试过一个变体:三路并行的CNN,卷积核大小分别是3、7、15,每条支路独立提取特征再拼接,后面接双向LSTM,最后接KAN层。这个结构在捕捉多周期叠加效应上比单尺度版本有肉眼可见的提升,代价是模型参数膨胀得厉害,训练时间长了将近一倍。
做这种变体时,一个重要的实验纪律是:改一个地方,其他保持不动。很多人在模型parse里同时改了卷积核、加双向、换KAN网格,最后效果变差了都不知道是哪一步导致的。
5.3 怎么判断你的数据适不适合CNN-LSTM-KAN
不是所有数据都需要KAN。我自己的判断标准有三个:
- 任务的目标映射存在明显的非线性,纯线性模型或者浅层MLP的表现明显不够好。
- 样本量足够,KAN参数量比MLP大,数据太少各种过拟合问题都会浮出来。
- 时间序列本身具有局部形态特征,纯LSTM已经能跑出一个还不错的分数,但精度卡在瓶颈上,这时候换KAN回归头往往能突破。
如果数据是随机性极强的白噪声类序列,或者样本量只有几千,那KAN的多余参数只会变成负担。先跑一个CNN-LSTM-MLP的基线,如果它已经能在验证集上做到不错的表现,你再换成KAN才有对比的意义。
5.4 做消融实验的建议
写论文或者做技术汇报,消融实验是绕不开的。我建议至少做四组对比,才能说明混合结构的每一项都有贡献。
| 实验组 | CNN | LSTM | KAN | 说明 |
|---|---|---|---|---|
| 基线 | × | √ | × | 纯LSTM+MLP |
| +CNN | √ | √ | × | 验证CNN特征提取的作用 |
| +KAN | √ | √ | √ | 完整模型 |
| -LSTM替换 | √ | × | √ | 验证LSTM的时序建模贡献 |
四组实验跑完,你要是能说清楚“CNN提升了局部特征的进入质量,KAN提升了最终映射的非线性拟合能力,LSTM是不可或缺的时序骨架”,这个创新的逻辑链才算完整。
6. 避坑清单与个人实操体会
6.1 六个实际会踩的坑
第一个坑是KAN库之间的实现差异。不同开源实现的B样条基函数计算方式、网格更新策略、初始化分布都不一样,同一个模型换一个库,结果可能差一截。选定一个库之后不要频繁换,先在模型层面做验证。
第二个坑是KAN层在CPU上慢得离谱。B样条基函数的计算量比矩阵乘法大不少,尤其是网格数和样条阶数调大之后,CPU上一轮epoch可能要跑几十分钟。有条件就上GPU,或者干脆把KAN层输出维度降低,不要给KAN层太大的输入宽度。
第三个坑是网格更新带来的loss突变。有的KAN实现会在训练过程中自适应更新网格节点,导致loss出现周期性跳变。这种跳变有时并不是模型不行,而是网格在重新分配密度。如果看到这种规律性跳变,建议把网格更新频率调低,或者干脆学习固定的非自适应网格。
第四个坑是跟BatchNorm一起用容易出问题。KAN的B样条函数要求输入在网格覆盖范围内分布稳定,BatchNorm在小batch下的均值方差抖动会让输入分布来回漂移,导致样条激活不稳定。我实践下来,LayerNorm比BatchNorm跟KAN搭配效果好得多,就是上面代码里那个用法。
第五个坑是KAN在输入维度很高时效果会退化。KAN适合在特征维度适中的时候用,一旦输入达到几百维,样条参数数量爆炸不说,高维空间的稀疏采样会让网格内部的函数学习效率变得很差。所以在混合模型里,KAN只放在最后做映射,前面CNN和LSTM已经帮你完成了降维和信息压缩。
第六个坑是训练初期看loss容易自己吓自己。KAN的收敛曲线跟传统神经网络不大一样,开头几个epoch可能看起来没怎么下降,但从第15到20个epoch开始才明显加速。只要loss没有发散,就先跑满50轮再判断,别在前10轮就急着调参数、换结构。
6.2 关于“创新”这件事的一点体会
这篇内容写了这么多,最后说点更虚但更实际的东西。2025年谈模型创新,已经不是比谁的模块接得多、名字起得炫,而是看你是否理解每个模块在整体流程里承担的职责。CNN、LSTM、KAN这个组合之所以被反复讨论,核心原因是它每个部件都有明确的“分工”——特征提取、时序建模、非线性映射,三者互不重叠,又彼此衔接。
回到代码层面,如果你准备在自己的项目里试一试,我建议从我这套配置起步:两个卷积层加批归一化、两层LSTM、两层KAN加LayerNorm,学习率按参数组拆分。先跑通拿到基线,再按你的数据特性做微调。调参的时候记住一件事:KAN的感受野是“网格”,不是“核大小”,它的可解释性来自你能直接看到每条边学出来的函数形状,这对调试模型非常有帮助。