目录
[@property 装饰器辅助获取权重](#@property 装饰器辅助获取权重)
[class kanlayer](#class kanlayer)
工具
torch.linspace(a,b,n)
和np的一样,n(亦作steps)是点的个数
t = torch.linspace(2, 10, 5)
print(t)输出:tensor([ 2., 4., 6., 8., 10.])
张量的克隆(复制)
torch.clone 和 torch.copy_ 在 PyTorch 中用于创建张量的副本,但它们在用法和目的上有所不同。
- 
torch.clone:- 返回一个新的张量,它是输入张量的一个深拷贝。
- 新张量和原始张量在内存中是独立的,修改新张量不会影响原始张量,反之亦然。
- 使用方法:++new_tensor = original_tensor.clone()++
 
- 
torch.copy_:- 将所有数据从源张量复制到目标张量,并原地修改目标张量。
- 目标张量必须已经存在于内存中,且必须足够大以容纳源张量的数据。
- 使用方法:++destination_tensor.copy_(source_tensor)++
 
下面是两者主要的区别:
- 调用方式:clone()是一个独立函数,而copy_()是一个原地(in-place)操作。
- 目标张量:clone()创建一个新的张量,而copy_()需要一个预先存在的目标张量,并在原地修改它。
- 用途:clone()常用于需要创建独立副本的场合,而copy_()常用于在原地更新张量的场合。
例如:
import torch
# 使用 clone
original = torch.tensor([1, 2, 3])
cloned = original.clone()
cloned[0] = 0
print(original)  # 输出: tensor([1, 2, 3])
print(cloned)    # 输出: tensor([0, 2, 3])
# 使用 copy_
destination = torch.zeros(3)
destination.copy_(original)
destination[0] = 0
print(original)  # 输出: tensor([1, 2, 3])
print(destination)  # 输出: tensor([0, 2, 3])在上面的例子中,可以看到 clone() 创建了一个独立的新张量,而 copy_() 则将数据复制到了预先存在的 destination 张量中。
mlp文件夹
导入准备
from tqdm import tqdm- 
这行代码从 tqdm模块导入tqdm函数,它用于在Python循环中添加一个进度条,提供直观的进度显示。from .LBFGS import LBFGS 
- 
这行代码从当前目录下的 LBFGS模块导入LBFGS类,这通常是一个自定义的优化器类。
            
            
              python
              
              
            
          
          seed = 0- 
这行代码设置一个名为 seed的变量,值为0,通常用于设置随机数生成器的种子,以确保结果的可重复性。torch.manual_seed(seed) 
- 
这行代码将PyTorch的随机数生成器种子设置为 seed变量的值,确保每次运行代码时,随机数生成器的结果是一致的。
mlp类
class MLP(nn.Module):- 
这行代码定义了一个名为 MLP的类,它继承自nn.Module。这是创建自定义神经网络模型的标准做法。def __init__(self, width, act='silu', save_act=True, seed=0, device='cpu'):
- 
这行代码定义了 MLP类的构造函数__init__,它接受几个参数:width(网络的宽度,即每层的神经元数量),act(激活函数类型,默认为'silu'),save_act(是否保存激活值,默认为True),seed(随机数种子,默认为0),device(运行设备,默认为'cpu')。super(MLP, self).__init__()
所以,这行代码的作用是调用 nn.Module 类的构造函数,这是必要的,因为 MLP 是从 nn.Module 继承而来的,并且需要初始化父类中的属性和方法。在自定义PyTorch模型时,这是一个常见的做法,以确保所有的初始化步骤都被正确执行,例如注册参数和钩子等。
简而言之,super(MLP, self).__init__() 确保了 MLP 类能够继承并使用 nn.Module 类的所有功能和特性。
- 
这行代码调用父类 nn.Module的构造函数,这是在自定义PyTorch模块中常见的做法。
- 
更进一步------ 
- 
super (MLP, self):这部分代码是获取当前类MLP的父类(在这种情况下是nn.Module)的引用,self是当前类的实例。
- 
.__init__():这部分代码是调用父类nn.Module的构造函数__init__()。torch.manual_seed(seed)
- 
这行代码再次设置随机数生成器的种子,以确保网络初始化的一致性。 linears = [] self.width = width self.depth = depth = len(width) - 1
- 
这三行代码初始化一个空列表 linears用于存储线性层,并将width参数保存为实例变量。同时计算网络深度depth,即width列表的长度减1。for i in range(depth): linears.append(nn.Linear(width[i], width[i+1]))
- 
这行代码通过循环为网络添加线性层(全连接层 ),每个线性层的输入和输出尺寸分别由 width列表中的连续两个元素指定。self.linears = nn.ModuleList(linears)
- 
linears是由nn.linear()构成的列表 
- 
这行代码将包含所有线性层的列表(上一行那个列表)转换为 nn.ModuleList,这是PyTorch中用于存储模块列表的一种方式。self.act_fun = torch.nn.SiLU()
- 
这行代码创建一个SiLU激活函数,它是一种常用的激活函数。 self.save_act = save_act self.acts = None
- 
这两行代码设置一个标志 save_act,用于指示是否保存激活值 ,并初始化一个变量acts用于存储激活值。self.cache_data = None
- 
这行代码初始化一个变量 cache_data**,它可能用于存储其他数据** ,但目前设置为None。self.device = device self.to(device)
- 
这两行代码将网络的设备设置为指定的设备(如CPU或GPU),并将网络的所有参数和缓冲区移动到该设备上。 
定义to函数
def to(self, device):- 
定义了一个名为 to的方法,它接受一个参数device。这个方法用于将模型的所有参数和缓冲区移动到指定的设备上,这个设备可以是CPU或者GPU。super(MLP, self).to(device)
- 
使用 super()调用父类(这里是nn.Module)的to方法,并将当前实例self和device参数传递给它。这行代码的作用是将当前模型(MLP的实例)的所有参数和缓冲区移动到指定的device上。self.device = device
- 
这行代码将 device参数的值赋给实例变量self.device。这样做是为了在后续的操作中可以引用这个设备,比如在模型的其他方法中使用这个设备来创建或操作张量。return self
- 
这行代码返回了 self,即当前模型的实例。这使得to方法可以被链式调用,也就是说,你可以像这样连续调用方法:model.to(device).train()。
这段代码总体上是为了确保模型可以在不同的设备之间移动,并保持对当前设备引用的跟踪。这在处理多设备环境时非常有用,尤其是在使用GPU进行训练时。
get_act获取激活值
            
            
              python
              
              
            
          
             def get_act(self, x=None):
3 months ago
add swap method
        if isinstance(x, dict):
            x = x['train_input']
        if x == None:
            if self.cache_data != None:
                x = self.cache_data
            else:
                raise Exception("missing input data x")
        save_act = self.save_act
        self.save_act = True
        self.forward(x)
        self.save_act = save_actdef get_act(self, x=None):- 
定义了一个名为 get_act的方法,它接受一个可选参数x。这个方法可能是用来获取神经网络在给定输入x下的激活值。if isinstance(x, dict): x = x['train_input']
- 
这行代码检查参数 x是否是一个字典。如果是,它将x的值更新为x字典中键为'train_input'的值。这意味着如果输入是一个字典,它期望字典中包含一个键为'train_input'的条目,该条目是网络的实际输入。if x == None: if self.cache_data != None: x = self.cache_data else: raise Exception("missing input data x")
- 
这是一个条件判断,如果 x是None,它将检查self.cache_data是否不为None。如果self.cache_data存在,则将x设置为self.cache_data的值。如果self.cache_data也是None,则抛出一个异常,指出缺少输入数据x。save_act = self.save_act
- 
这行代码将 self.save_act的当前值保存到局部变量save_act中。self.save_act可能是一个布尔值,指示是否应该保存激活值。self.save_act = True
- 
这行代码将 self.save_act设置为True,确保在接下来的forward调用中保存激活值。
python
复制
    self.forward(x)- 
调用 self.forward方法,这通常是神经网络的前向传播方法,它使用输入x计算网络的输出。由于前面设置了self.save_act为True,这个过程也会保存激活值。self.save_act = save_act
- 
这行代码将 self.save_act的值恢复为最初保存的值 ,即save_act变量的值。这样,get_act方法结束后,self.save_act的值不会被永久改变。
整体上,这个方法是为了获取给定输入 x 的网络激活值,并且可以选择性地使用缓存的数据。如果输入数据缺失,它会抛出一个异常。这个方法还确保了在获取激活值之后,self.save_act 的值会被恢复到原来的状态。
前向传播forward()
如果只是要求传播完的x,只要两句话就好了
            
            
              python
              
              
            
          
          def forward(self,x):
    for i in range(self.depth):
        x=self.linears[i](x)
    return x
    但是,还有很多细节要考虑,比如是否保存激活值:
if self.save_act:
            act = x.clone()
            act_scale = torch.std(x, dim=0)
            wa_forward = act_scale[None, :] * self.linears[i].weight
            self.acts.append(act)
            if i > 0:
                self.acts_scale.append(act_scale)
            self.wa_forward.append(wa_forward)- 
如果 self.save_act为True,则克隆当前激活值x,计算其标准差act_scale,计算权重与激活值尺度的乘积wa_forward,并将这些值添加到相应的列表中。
- 
具体算法------------ 表达式 wa_forward = act_scale[None, :] * self.linears[i].weight是在PyTorch中常见的一种操作,用于在神经网络的前向传播过程中对权重进行调整。这里,act_scale是一个张量,表示每个特征维度的标准差,而self.linears[i].weight是第i个线性层的权重。下面解释这个表达式的各个部分:
- 
act_scale[None, :]:- act_scale是一个一维张量,假设它的形状是- (C,),其中- C是特征的数量。
- [None, :]是一个索引操作,它将- act_scale增加一个维度,使其形状变为- (1, C)。这样做的目的是为了确保在进行广播操作时,- act_scale可以与- self.linears[i].weight的形状兼容。
 
- 
self.linears[i].weight:- self.linears[i]是一个线性层(例如- nn.Linear),而- .weight是这个线性层的权重张量。
- 假设这个线性层的输入特征数量是 C,输出特征数量是D,那么self.linears[i].weight的形状是(D, C)。
 
- 
*:- 这是逐元素乘法操作。在这里,它将形状为 (1, C)的act_scale[None, :]与形状为(D, C)的self.linears[i].weight进行逐元素相乘。
 
- 这是逐元素乘法操作。在这里,它将形状为 
- 
结果 wa_forward:- 结果的形状将是 (D, C),与self.linears[i].weight的形状相同。
- 这个操作的效果是调整第 i个线性层的权重,使得每个输出特征维度的权重都乘以相应的特征标准差act_scale。
 
- 结果的形状将是 
总括------
- 如果 self.save_act为True,则克隆当前激活值x,计算其标准差act_scale,计算权重与激活值尺度的乘积wa_forward,并将这些值添加到相应的列表中。
- 如果当前层不是最后一层(i < self.depth - 1),则应用激活函数self.act_fun到输出x上。这意味着在每一层线性层之后(除了最后一层),都会进行激活。
- 如果当前层是最后一层,并且 self.save_act为True,则计算最后一层激活值的标准差act_scale并保存。
每经过一层线性层(除了最后一层)都会激活一次。这是深度学习中常见的模式,其中非线性激活函数被用于引入非线性,使得模型能够学习更复杂的函数。最后一层通常不使用激活函数,特别是在回归问题或某些特定的分类问题中,例如输出层的维度与目标维度相同的情况。@property 装饰器辅助获取权重
在Python中,@property 装饰器用于将一个方法转换为属性访问,这样就可以像访问属性一样访问这个方法,而不需要使用传统的函数调用方式(即不带括号)。这在实现面向对象编程时非常有用,特别是在需要通过属性访问来获取或设置对象状态时。
@property 装饰器用于 w 方法:
@property
def w(self):
    return [self.linears[l].weight for l in range(self.depth)]以下是 @property 装饰器的作用:
- 
属性访问 :允许用户通过 self.w而不是self.w()来访问权重列表,这使得代码更加简洁和直观。
- 
只读属性 :在这个例子中, w方法没有定义一个设置器(setter),这意味着它是一个只读属性。用户可以获取权重列表,但不能直接修改它。
- 
隐藏实现细节 :通过将方法转换为属性,可以在不公开内部实现细节的情况下,提供对对象状态的访问。在这个例子中,用户不需要知道权重是如何被组织或计算的,他们只需要知道可以通过 self.w获取它们。
下面是如何使用这个属性的一个例子:
# 假设有一个 MLP 实例叫做 mlp
weights = mlp.w  # 获取所有层的权重,不需要调用 mlp.w() 作为函数在这个例子中,每次你访问 mlp.w,它都会返回一个列表,其中包含了 mlp 对象中所有线性层的权重。由于 w 是一个只读属性,你不能通过赋值来改变它,例如 mlp.w = something 是不允许的,除非你定义了一个相应的设置器方法。
1暂时还没看到在哪里用了
torch.einsum语法
torch.einsum 是PyTorch中一个强大的函数,它允许用户使用爱因斯坦求和约定(Einstein summation convention)来执行多维数组(张量)的运算。爱因斯坦求和约定是一种表示多维数组(张量)运算的方法,它通过省略求和符号和索引来简化数学表达式的书写。
下面是对 torch.einsum 函数的具体讲解:
torch.einsum(equation, *operands)- equation: 一个字符串,定义了操作的形式,包括输入和输出张量的索引以及它们之间的关系。
- operands: 一个或多个张量,它们将根据- equation中定义的规则进行运算。
爱因斯坦求和约定
在爱因斯坦求和约定中,如果一个索引在输入张量中出现两次,那么它就表示对该索引进行求和。如果索引在输出张量中出现,那么它就表示该索引是输出张量的一个维度。
示例
以下是一些使用 torch.einsum 的示例:
点积
计算两个一维张量的点积:
x = torch.randn(5)
y = torch.randn(5)
result = torch.einsum('i,i', x, y)  # 等价于 torch.dot(x, y)在这个例子中,'i,i' 表示对索引 i 求和,x 和 y 的对应元素相乘。
矩阵-向量乘法
计算矩阵和向量的乘积:
A = torch.randn(4, 5)
x = torch.randn(5)
result = torch.einsum('ik,k', A, x)  # 等价于 torch.mv(A, x)这里,'ik,k' 表示对索引 k 求和,A 的每一行与 x 相乘。
矩阵乘法
计算两个矩阵的乘积:
A = torch.randn(4, 5)
B = torch.randn(5, 6)
result = torch.einsum('ik,kj', A, B)  # 等价于 torch.mm(A, B)在这个例子中,'ik,kj' 表示对索引 k 求和,A 的行与 B 的列相乘。
转置和乘法
计算矩阵的转置与另一个矩阵的乘积:
A = torch.randn(4, 5)
B = torch.randn(4, 6)
result = torch.einsum('ji,jk', A, B)  # 等价于 torch.mm(A.t(), B)这里,'ji,jk' 表示 A 被转置(通过交换索引 i 和 j),然后与 B 相乘。
优点
- 表达能力强:可以简洁地表示复杂的多维数组运算。
- 性能优化:torch.einsum在某些情况下可以比传统的矩阵乘法函数(如torch.mm或torch.matmul)更高效,因为它允许PyTorch优化器选择最佳的执行路径。
注意事项
- 使用 torch.einsum时,需要确保输入张量的维度与求和约定中的索引相匹配。
- 对于非常大的张量,使用 torch.einsum可能不如直接使用专门的函数(如torch.mm或torch.matmul)高效,因为后者通常经过了更深入的优化。
++91行++
kan文件夹
init
在Python中,`init.py` 文件是用于标识一个目录为Python包(package)的特殊文件。在这个图片中,虽然"init.py"这个名称出现在列表中,但通常它不会以这种方式显示为一个普通文件。相反,它会作为包内部的一个隐藏或特殊的文件存在。
如果你希望创建一个新的Python包,你应该在你的包目录下创建一个空的 `init.py` 文件。这样,Python就会把这个目录识别为一个可以导入的包。
只有三行
            
            
              python
              
              
            
          
          from .MultKAN import *
from .utils import *
#torch.use_deterministic_algorithms(True)这个文件可以非常简单,甚至可以为空,只要它的文件名恰好是 __init__.py 即可。然而,你也可以在其中放置一些特定的代码和行为,这些代码会在导入该包时自动执行。
kanlayer
class kanlayer
参数解释
in_dim: int
输入维度
out_dim: int
输出维度
num: int
网格间隔的数量
k: int
样条函数的分段多项式阶数
noise_scale: float
初始化时样条函数的尺度
coef: 2D torch.tensor
B样条基的系数
scale_base_mu: float
残差函数 b(x) 从 N(μ, σ^2) 中抽取的量级,(magnitude),μ = sigma_base_mu
scale_base_sigma: float
残差函数 b(x) 从 N(μ, σ^2) 中抽取的量级,μ = sigma_base_sigma
scale_sp: float
样条函数 spline(x) 的量级
base_fun: fun
残差函数 b(x)
mask: 1D torch.float
样条函数的掩码。将掩码中的某些元素设置为零意味着将相应的激活函数设置为零。
grid_eps: float ∈ [0,1]
在 update_grid_from_samples 中使用的超参数。当 grid_eps = 1 时,网格是均匀的;当 grid_eps = 0 时,网格使用样本的百分位数进行划分。0 < grid_eps < 1 在两个极端之间插值。
锁定的激活函数的ID
device: str
设备
下棋待续。