kan代码阅读
目录
工具
torch.linspace(a,b,n)
张量的克隆(复制)
mlp文件夹
导入准备
mlp类
定义to函数
get_act获取激活值
前向传播forward()
@property 装饰器辅助获取权重
torch.einsum语法
爱因斯坦求和约定
示例
点积
矩阵-向量乘法
矩阵乘法
转置和乘法
优点
注意事项
kan文件夹
__init__
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
类,这通常是一个自定义的优化器类。
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获取激活值
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_act
def 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,只要两句话就好了
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就会把这个目录识别为一个可以导入的包。
只有三行
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
设备
下棋待续。