系列文章目录
第一部分 KAN的理解——数学背景
第二部分 KAN的理解——网络结构
第三部分 KAN的实践——第一个例程
文章目录
- 系列文章目录
- 前言
- KAN 的第一个例程 get started
前言
这里记录我对于KAN的探索过程,每次会尝试理解解释一部分问题。欢迎大家和我一起讨论。
KAN tutorial
KAN 的第一个例程 get started
以下内容包含对于代码的理解,对于KAN训练过程的理解和代码的解释。并且包含代码的结果。
- 对于KAN进行初始化。
from kan import *
# create a KAN: 2D inputs, 1D output, and 5 hidden neurons. cubic spline (k=3), 5 grid intervals (grid=5).
model = KAN(width=[2,5,1], grid=5, k=3, seed=0)
从上面的代码可以看出,输入两维,说明要拟合的数据有两个输入变量,hidden neurons5个说明是全连接网络,还没有进行剪枝。
gird intervel表示用于拟合的样条函数的一组离散点,这些点用于分段构造样条函数。网格设定的约密集对于拟合的函数精度越高,想要提高网络的拟合能力,一般会增加grid interval的数目,在论文中称为grid extension。
这里的k是指一次样条、二次样条等这里的次数。表示在每个区间内拟合函数时,使用的是多少次数的多项式表示。
seed为随机数种子,通过设置随机数种子seed=0,模型的初始化(如权重初始化)和任何涉及随机性的过程都会产生相同的结果。
- 创建数据集,用于作为训练的输入
# create dataset f(x,y) = exp(sin(pi*x)+y^2)
f = lambda x: torch.exp(torch.sin(torch.pi*x[:,[0]]) + x[:,[1]]**2)
dataset = create_dataset(f, n_var=2)
dataset['train_input'].shape, dataset['train_label'].shape
从输出和函数定义来看,默认KAN的train number和test number都是1000
create_dataset函数的功能为生成一系列的数据字典,包括train_input,train_label,test_input,test_label
第一行lambda函数用于定义匿名函数,接收二维函数x为输入,并返回一个新张量f,为其仅进行特定的数学运算并返回结果
- 绘制初始化结果
# plot KAN at initialization
model(dataset['train_input'][:20]);
model.plot(beta=100,sample=True)
额外提一句,在做初始化的时候,这里的有一些默认参数没给出来。
在初始化时,已经生成了每个节点的被学习的weight函数曲线的可视化,且被保存在./figures下,在初始化时添加了noise,所以每个节点的曲线形状不同,且在定义模型时还有supervised mode和unsupervised mode可以选择。
这部分代码的功能主要是,在初始化网络时给出了初始化时的可视化。结果如下:
- 模型训练并设置对应的参数
# train the model
model.train(dataset, opt="LBFGS", steps=20, lamb=0.01, lamb_entropy=10.);
一些参数:
dataset:输入的训练数据
opt:优化算法选择,有LBFGS和Adam算法可供选择,分别问基于二阶导数的算法和基于一阶导数的优化算法
step:训练步数
lamb:控制整体正则化项的强度,能够增强训练的稀疏性,保留有效项
lamb_entropy:控制熵正则化项的强度,能有效减少激活函数的数量,避免出现相同或非常相似的函数
从代码的内容上看,在训练中,已经在进行有效项的保留,重复项的去除。
1000的数据量大概要处理11s
画出此时的第一次训练后的图,发现被判定为不重要的项的透明度增强了许多,在图上显示表示为不重要的部分。
结果如下:
- 剪枝
# model.prune(mode='manual',active_neurons_id=[[3],[2]] )
model.prune()
model.plot(mask=False)
做一些剪枝,直接减掉一些不重要的node。prune的原则是查看每个node的入边和出边,
如果某个节点所连接的入边和出边的属于不重要的边,那么这些边可以被剪枝。
这里的默认参数是自动剪枝,但是实际上也可以选择手动剪枝,确要保留的节点。
- 再剪枝
model = model.prune()
model(dataset['train_input'][:20])
model.plot(sample=True)
再剪枝,得到更小的模型。这里的dataset[‘train_input’]应该是用来测试目前的训练结果的。结果如下:
- 再训练
model.train(dataset, opt="LBFGS", steps=50);
现在得到的结果是去掉了一些node的结果,在更少的nodes被保留的情况下,继续进行训练
从训练的结果可以结案到现在的精确度变高了,可能是因为减少了node,保留了可信度更强的node
- 再看一遍训练结果。
model.plot()
结果如下:
- 确定要fix的项
mode = "auto" # "manual"
# 设置mannual会报错
if mode == "manual":
# manual mode
# fix_symbolic()方程下的参数,(layer index,layer index,output neuron index)
model.fix_symbolic(0,0,0,'sin');
model.fix_symbolic(0,1,0,'x^2');
model.fix_symbolic(1,0,0,'exp');
elif mode == "auto":
# automatic mode
lib = ['x','x^2','x^3','x^4','exp','log','sqrt','tanh','sin','abs']
model.auto_symbolic(lib=lib)
结果如下:
- 最后输出数学表达式
model.train(dataset, opt="LBFGS", steps=50);
model.symbolic_formula()[0][0]
这里可能出现的问题是,会多余出一些小项,比如预测了正确的公式但是结尾部分会加上一个很小的数值,或者加上一个值很小的表达式。
结果如下: