编写高效的PyTorch代码技巧(下)

部署运行你感兴趣的模型镜像

点击上方“算法猿的成长“,关注公众号,选择加“星标“或“置顶”

总第 133 篇文章,本文大约 3000 字,阅读大约需要 15 分钟

原文:https://github.com/vahidk/EffectivePyTorch

作者:vahidk

前言

这是一份 PyTorch 教程和最佳实践笔记,目录如下所示:

  1. PyTorch 基础

  2. 将模型封装为模块

  3. 广播机制的优缺点

  4. 使用好重载的运算符

  5. 采用 TorchScript 优化运行时间

  6. 构建高效的自定义数据加载类

  7. PyTorch 的数值稳定性

上篇文章的链接如下:

编写高效的PyTorch代码技巧(上)

这次介绍后面3点,写出高效的代码以及保证做数值计算时候的稳定性。


5. 采用 TorchScript 优化运行时间

PyTorch 优化了维度很大的张量的运算操作。在 PyTorch 中对小张量进行太多的运算操作是非常低效的。所以有可能的话,将计算操作都重写为批次(batch)的形式,可以减少消耗和提高性能。而如果没办法自己手动实现批次的运算操作,那么可以采用 TorchScript 来提升代码的性能。

TorchScript 是一个 Python 函数的子集,但经过了 PyTorch 的验证,PyTorch 可以通过其 just in time(jtt) 编译器来自动优化 TorchScript 代码,提高性能。

下面给出一个具体的例子。在机器学习应用中非常常见的操作就是 batch gather ,也就是 output[i] = input[i, index[i]]。其代码实现如下所示:

import torch
def batch_gather(tensor, indices):
    output = []
    for i in range(tensor.size(0)):
        output += [tensor[i][indices[i]]]
    return torch.stack(output)

通过 torch.jit.script 装饰器来使用 TorchScript 的代码:

@torch.jit.script
def batch_gather_jit(tensor, indices):
    output = []
    for i in range(tensor.size(0)):
        output += [tensor[i][indices[i]]]
    return torch.stack(output)

这个做法可以提高 10% 的运算速度。

但更好的做法还是手动实现批次的运算操作,下面是一个向量化实现的代码例子,提高了 100 倍的速度:

def batch_gather_vec(tensor, indices):
    shape = list(tensor.shape)
    flat_first = torch.reshape(
        tensor, [shape[0] * shape[1]] + shape[2:])
    offset = torch.reshape(
        torch.arange(shape[0]).cuda() * shape[1],
        [shape[0]] + [1] * (len(indices.shape) - 1))
    output = flat_first[indices + offset]
    return output

6. 构建高效的自定义数据加载类

上一节介绍了如何写出更加高效的 PyTorch 的代码,但为了让你的代码运行更快,将数据更加高效加载到内存中也是非常重要的。幸运的是 PyTorch 提供了一个很容易加载数据的工具,即 DataLoader 。一个 DataLoader 会采用多个 workers 来同时将数据从 Dataset 类中加载,并且可以选择使用 Sampler 类来对采样数据和组成 batch 形式的数据。

如果你可以随时访问你的数据,那么使用 DataLoader 会非常简单:只需要继承 Dataset 类别并实现 __getitem__ (读取每个数据)和 __len__(返回数据集的样本数量)这两个方法。下面给出一个代码例子,如何从给定的文件夹中加载图片数据:

import glob
import os
import random
import cv2
import torch

class ImageDirectoryDataset(torch.utils.data.Dataset):
    def __init__(path, pattern):
        self.paths = list(glob.glob(os.path.join(path, pattern)))

    def __len__(self):
        return len(self.paths)

    def __item__(self):
        path = random.choice(paths)
        return cv2.imread(path, 1)

比如想将文件夹内所有的 jpeg 图片都加载,代码实现如下所示:

dataloader = torch.utils.data.DataLoader(ImageDirectoryDataset("/data/imagenet/*.jpg"), num_workers=8)
for data in dataloader:
    # do something with data

这里采用了 8 个 workers 来并行的从硬盘中读取数据。这个数量可以根据实际使用机器来进行调试,得到一个最佳的数量。

当你的数据都很大或者你的硬盘读写速度很快,采用DataLoader进行随机读取数据是可行的。但也可能存在一种情况,就是使用的是一个很慢的连接速度的网络文件系统,请求单个文件的速度都非常的慢,而这可能就是整个训练过程中的瓶颈。

一个更好的做法就是将数据保存为一个可以连续读取的连续文件格式。例如,当你有非常大量的图片数据,可以采用 tar 命令将其压缩为一个文件,然后用 python 来从这个压缩文件中连续的读取图片。要实现这个操作,需要用到 PyTorch 的 IterableDataset。创建一个 IterableDataset 类,只需要实现 __iter__ 方法即可。

下面给出代码实现的例子:

import tarfile
import torch

def tar_image_iterator(path):
    tar = tarfile.open(self.path, "r")
    for tar_info in tar:
        file = tar.extractfile(tar_info)
        content = file.read()
        yield cv2.imdecode(content, 1)
        file.close()
        tar.members = []
    tar.close()

class TarImageDataset(torch.utils.data.IterableDataset):
    def __init__(self, path):
        super().__init__()
        self.path = path

    def __iter__(self):
        yield from tar_image_iterator(self.path)

不过这个方法有一个问题,当使用 DataLoader 以及多个 workers 读取这个数据集的时候,会得到很多重复的数据:

dataloader = torch.utils.data.DataLoader(TarImageDataset("/data/imagenet.tar"), num_workers=8)
for data in dataloader:
    # data contains duplicated items

这个问题主要是因为每个 worker 都会创建一个单独的数据集的实例,并且都是从数据集的起始位置开始读取数据。一种避免这个问题的办法就是不是压缩为一个tar 文件,而是将数据划分成 num_workers 个单独的 tar 文件,然后每个 worker 分别加载一个,代码实现如下所示:

class TarImageDataset(torch.utils.data.IterableDataset):
    def __init__(self, paths):
        super().__init__()
        self.paths = paths

    def __iter__(self):
        worker_info = torch.utils.data.get_worker_info()
        # For simplicity we assume num_workers is equal to number of tar files
        if worker_info is None or worker_info.num_workers != len(self.paths):
            raise ValueError("Number of workers doesn't match number of files.")
        yield from tar_image_iterator(self.paths[worker_info.worker_id])

所以使用例子如下所示:

dataloader = torch.utils.data.DataLoader(
    TarImageDataset(["/data/imagenet_part1.tar", "/data/imagenet_part2.tar"]), num_workers=2)
for data in dataloader:
    # do something with data

这是一种简单的避免重复数据的问题。而 tfrecord 则用了比较复杂的办法来共享数据,具体可以查看:

https://github.com/vahidk/tfrecord


7. PyTorch 的数值稳定性

当使用任意一个数值计算库,比如 NumPy 或者 PyTorch ,都需要知道一点,编写数学上正确的代码不一定会得到正确的结果,你需要确保这个计算是稳定的。

首先以一个简单的例子开始。从数学上来说,对任意的非零 x ,都可以知道式子 是成立的。但看看具体实现的时候,是不是总是正确的:

import numpy as np

x = np.float32(1)

y = np.float32(1e-50)  # y would be stored as zero
z = x * y / y

print(z)  # prints nan

代码的运行结果是打印 nan ,原因是 y 的数值对于 float32 类型来说非常的小,这导致它的实际数值是 0 而不是 1e-50。

另一种极端情况就是 y 非常的大:

y = np.float32(1e39)  # y would be stored as inf
z = x * y / y

print(z)  # prints nan

输出结果依然是 nan ,因为 y 太大而被存储为 inf 的情况,对于 float32 类型来说,其范围是 1.4013e-45 ~ 3.40282e+38,当超过这个范围,就会被置为 0 或者 inf。

下面是如何查看一种数据类型的数值范围:

print(np.nextafter(np.float32(0), np.float32(1)))  # prints 1.4013e-45
print(np.finfo(np.float32).max)  # print 3.40282e+38

为了让计算变得稳定,需要避免过大或者过小的数值。这看起来很容易,但这类问题是很难进行调试,特别是在 PyTorch 中进行梯度下降的时候。这不仅因为需要确保在前向传播过程中的所有数值都在使用的数据类型的取值范围内,还要保证在反向传播中也做到这一点。

下面给出一个代码例子,计算一个输出向量的 softmax,一种不好的代码实现如下所示:

import torch

def unstable_softmax(logits):
    exp = torch.exp(logits)
    return exp / torch.sum(exp)

print(unstable_softmax(torch.tensor([1000., 0.])).numpy())  # prints [ nan, 0.]

这里计算 logits 的指数数值可能会得到超出 float32 类型的取值范围,即过大或过小的数值,这里最大的 logits 数值是 ln(3.40282e+38) = 88.7,超过这个数值都会导致 nan

那么应该如何避免这种情况,做法很简单。因为有 ,也就是我们可以对 logits 减去一个常量,但结果保持不变,所以我们选择logits 的最大值作为这个常数,这种做法,指数函数的取值范围就会限制为 [-inf, 0] ,然后最终的结果就是 [0.0, 1.0] 的范围,代码实现如下所示:

import torch

def softmax(logits):
    exp = torch.exp(logits - torch.reduce_max(logits))
    return exp / torch.sum(exp)

print(softmax(torch.tensor([1000., 0.])).numpy())  # prints [ 1., 0.]

接下来是一个更复杂点的例子。

假设现在有一个分类问题。我们采用 softmax 函数对输出值 logits 计算概率。接着定义采用预测值和标签的交叉熵作为损失函数。对于一个类别分布的交叉熵可以简单定义为 :

所以有一个不好的实现交叉熵的代码实现为:

def unstable_softmax_cross_entropy(labels, logits):
    logits = torch.log(softmax(logits))
    return -torch.sum(labels * logits)

labels = torch.tensor([0.5, 0.5])
logits = torch.tensor([1000., 0.])

xe = unstable_softmax_cross_entropy(labels, logits)

print(xe.numpy())  # prints inf

在上述代码实现中,当 softmax 结果趋向于 0,其 log 输出会趋向于无穷,这就导致计算结果的不稳定性。所以可以对其进行重写,将 softmax 维度拓展并做一些归一化的操作:

def softmax_cross_entropy(labels, logits, dim=-1):
    scaled_logits = logits - torch.max(logits)
    normalized_logits = scaled_logits - torch.logsumexp(scaled_logits, dim)
    return -torch.sum(labels * normalized_logits)

labels = torch.tensor([0.5, 0.5])
logits = torch.tensor([1000., 0.])

xe = softmax_cross_entropy(labels, logits)

print(xe.numpy())  # prints 500.0

可以验证计算的梯度也是正确的:

logits.requires_grad_(True)
xe = softmax_cross_entropy(labels, logits)
g = torch.autograd.grad(xe, logits)[0]
print(g.numpy())  # prints [0.5, -0.5]

这里需要再次提醒,进行梯度下降操作的时候需要额外的小心谨慎,需要确保每个网络层的函数和梯度的范围都在合法的范围内,指数函数和对数函数在不正确使用的时候都可能导致很大的问题,它们都能将非常小的数值转换为非常大的数值,或者从很大变为很小的数值。


精选AI文章

1. 10个实用的机器学习建议

2. 深度学习算法简要综述(上)

3. 深度学习算法简要综述(上)

4. 常见的数据增强项目和论文介绍

5. 实战|手把手教你训练一个基于Keras的多标签图像分类器

精选python文章

1.  python数据模型

2. python版代码整洁之道

3. 快速入门 Jupyter notebook

4. Jupyter 进阶教程

5. 10个高效的pandas技巧

精选教程资源文章

1. [资源分享] TensorFlow 官方中文版教程来了

2. [资源]推荐一些Python书籍和教程,入门和进阶的都有!

3. [Github项目推荐] 推荐三个助你更好利用Github的工具

4. Github上的各大高校资料以及国外公开课视频

5. GitHub上有哪些比较好的计算机视觉/机器视觉的项目?

欢迎关注我的微信公众号--算法猿的成长,或者扫描下方的二维码,大家一起交流,学习和进步!

 

如果觉得不错,在看、转发就是对小编的一个支持!

您可能感兴趣的与本文相关的镜像

PyTorch 2.5

PyTorch 2.5

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

<think>我们被要求使用PyTorch编写KAN(Kolmogorov-Arnold Networks)网络的代码。根据引用[2]的描述,KAN是一种基于Kolmogorov-Arnold表示定理的网络架构,它使用可学习的激活函数(通常是一元函数)来代替传统MLP中的固定激活函数(如ReLU)。在KAN中,每个边上的权重被替换成一个可学习的一元函数(通常用样条函数或神经网络来表示)。 基本思想: 传统MLP:线性变换 + 非线性激活函数(固定)。 KAN:将线性变换和非线性激活合并为可学习的一元函数,即每个连接(边)都是一个可学习的一元函数。 结构对比: MLP层:输入维度n,输出维度m,则一个线性层有(n, m)的权重矩阵和m个偏置。激活函数固定(如ReLU)。 KAN层:输入节点数n,输出节点数m。每个输入节点到输出节点之间有一个可学习的一元函数(而不是一个标量权重)。因此,从输入到输出,每个输出节点是n个一元函数的和(Kolmogorov-Arnold表示定理的形式)。 具体来说,对于一个输入向量$x=(x_1,...,x_n)$,KAN层的输出$y_j$为: $$y_j = \sum_{i=1}^{n} \phi_{ij}(x_i)$$ 其中,$\phi_{ij}$是连接输入节点$i$和输出节点$j$的可学习一元函数。 在实际实现中,每个$\phi_{ij}$可以用一个小型神经网络(如多层感知机)或者样条函数(例如B样条)来参数化。为了简化,我们可以使用一个简单的全连接网络(只有几层)来表示每个一元函数。 然而,由于每个连接都是一个函数,参数量可能会很大。因此,实际实现中可能会采用一些技巧(如共享参数)来减少计算量。 设计步骤: 1. 定义可学习的一元函数:我们可以定义一个小的神经网络模块(例如,一个包含几个隐藏层的全连接网络)来表示每个函数。 2. 构建KAN层:对于输入维度和输出维度,我们为每一对输入输出节点创建一个一元函数。 3. 前向传播:对于每个输入样本,我们对每个输出节点,计算所有输入节点经过对应一元函数后的和。 注意:在实际的KAN网络中,可能会采用更高效的参数化方式(如样条函数),但为了简化,我们可以先用简单的多层感知机来实现每个一元函数。 实现计划: 我们创建一个`KANLayer`模块,它包含: - 输入特征数:`in_features` - 输出特征数:`out_features` - 每个一元函数的隐藏层结构(例如,[4,4]表示两个隐藏层,每层4个神经元) 对于每个连接(共`in_features * out_features`个连接),我们创建一个小的MLP(作为一元函数)。 前向传播: 输入张量x: (batch_size, in_features) 输出张量y: (batch_size, out_features) 对每个输出特征j,计算: y_j = sum_{i=0}^{in_features-1} func_{ij}(x_i) 其中,func_{ij}是第i个输入特征到第j个输出特征的函数。 由于每个一元函数独立,我们可以这样实现: - 将x扩展为(batch_size, in_features, out_features)(复制out_features份),然后对每个i,j,将x[:,i]输入到对应的函数func_{ij},得到输出(形状为(batch_size))。 - 然后将每个输出特征j对应的所有i的函数的输出相加。 但是,这样实现需要大量的循环,效率较低。我们可以利用矩阵运算来并行化。 另一种方式: 我们可以将每个一元函数设计为:输入是标量,输出是标量。因此,我们可以: 创建一个大矩阵,其元素是每个连接的一元函数。但是,由于每个一元函数是一个神经网络,我们不能直接用矩阵乘法。 因此,我们采用以下方式: 1. 将输入x按列拆分(即每个输入特征),然后对每个输入特征i,我们将其复制out_features次(因为每个输入特征要参与所有输出特征的计算),然后通过一个由所有该输入特征对应的输出通道的函数组成的网络?这样不是很直接。 另一种更高效的方式(参考已有的KAN实现): 我们可以将每个一元函数用一个小的MLP(例如,Linear(1, hidden_size) -> ... -> Linear(hidden_size, 1))来实现,然后将所有函数并行计算。 具体步骤: 我们创建一个三维张量:对于每个输入特征i和输出特征j,我们有一个函数。我们可以将所有这些函数存储在一个`nn.ModuleList`中,但是这样无法并行计算。 为了并行化,我们可以将所有的函数组织成一个大的网络,但这样会复杂。或者,我们可以将每个一元函数看作一个单独的模块,然后在一个循环中计算,但这样效率低。 参考已有的实现(如GitHub上的KAN项目)通常使用更高效的方式(如利用分组卷积或自定义CUDA内核),但为了简单,我们这里使用循环,但注意在批量计算时可以使用向量化。 由于每个一元函数是独立的,我们可以将输入x重复out_features次(在最后一个维度上复制),然后将其reshape成一个大的向量,然后通过一个并行处理多个一元函数的大网络?但这样每个函数的输出需要独立。 这里我们采用一种实用的方法: 我们构建一个模块,它包含一个参数:`functions`,这是一个`nn.ModuleList`,其中包含`in_features * out_features`个小的MLP(每个MLP输入1维,输出1维)。 前向传播时: 输入x: (batch_size, in_features) 输出y: (batch_size, out_features),初始化为0 对于每个输入特征i(0<=i<in_features)和每个输出特征j(0<=j<out_features): idx = i * out_features + j # 取x的第i列: (batch_size,) # 将其reshape为(batch_size,1)输入到functions[idx]中,得到输出形状(batch_size,1),然后压缩成(batch_size) # 将这个输出加到y的第j列上 这样显然效率不高,特别是在输入输出维度较高时。但作为示例,我们先这样实现。 另一种高效方式(使用张量操作): 我们可以将输入x复制成形状(batch_size, in_features, out_features)的张量,这样每个位置(i,j)对应一个输入值(即x[:,i])。 然后,我们构建一个大的网络,它能够同时处理所有这些输入。但是,由于每个一元函数都是独立的,我们可以设计一个网络,它由多个并行的子网络组成,每个子网络处理一个输入特征到输出特征的映射?但这样我们需要为每个(i,j)对设置独立的权重。 我们可以使用一个`nn.Conv1d`或`nn.Linear`来模拟,但需要自定义权重。 考虑到时间,我们先用简单(但效率低)的方法实现一个示例。 我们定义: 每个一元函数:一个小的MLP(例如:Linear(1, hidden_size)->ReLU->Linear(hidden_size,1)) 注意:KAN的原始论文中,一元函数是参数化的样条函数,但我们这里用MLP代替。 代码结构: 1. 定义一个小的MLP模块(`LearnableUnivariate`): 输入:1 输出:1 隐藏层:可以自定义隐藏层大小和层数。 2. 定义`KANLayer`: 参数:in_features, out_features, hidden_size (可选,默认为4), num_hidden_layers (可选,默认为1) 初始化:创建一个包含in_features * out_features个`LearnableUnivariate`的ModuleList 前向传播: 输入x: (batch_size, in_features) 输出y: (batch_size, out_features) 初始为0 对于每个输入特征i: 对于每个输出特征j: idx = i * out_features + j # 取x[:, i] (batch_size,) -> 转为(batch_size,1) out_ij = self.functions[idx](x[:, i].unsqueeze(1)) # (batch_size,1) y[:, j] += out_ij.squeeze(1) # 加到第j列 注意:这样需要两层循环(特征维度的循环),批量大小用向量化。 3. 定义整个KAN网络: 由多个KAN层组成,中间可以插入非线性(但注意:KAN层本身已经有非线性函数,所以通常不需要额外的激活函数)。 但是,原始KAN表示定理中,函数是连续函数,所以整个网络是连续函数的叠加。因此,我们可以堆叠多个KAN层。 注意:在连续堆叠KAN层时,中间不需要额外的激活函数,因为每个连接已经是一个非线性函数。 然而,为了增加表达能力,我们可能需要多个KAN层。例如: 输入层 -> KANLayer1 (in_features, hidden_features) -> KANLayer2 (hidden_features, out_features) 这里,我们定义两个KAN层,第一个将输入维度in_features映射到hidden_features,第二个将hidden_features映射到out_features。 我们也可以定义更深的结构。 由于循环效率较低,我们还可以尝试优化,但作为示例,我们先这样实现。 另外,在原始KAN中,为了简化,可能使用共享参数的技巧,但这里我们独立参数。 代码实现: 首先,定义一元函数模块: </think>以下是使用 PyTorch 实现 KAN(Kolmogorov-Arnold Networks)网络的完整代码示例,结合了 KAN 的核心理论和 PyTorch 的最佳实践: ### 一、KAN 理论基础 KAN 基于 **Kolmogorov-Arnold 表示定理**:任何多元连续函数 $f(\mathbf{x})$ 可表示为有限个一元函数的组合: $$ f(\mathbf{x}) = \sum_{q=1}^{2n+1} \Phi_q \left( \sum_{p=1}^{n} \phi_{q,p}(x_p) \right) $$ 其中 $\phi_{q,p}$ 和 $\Phi_q$ 均为**可学习的一元函数**(传统 MLP 使用固定激活函数)。 ### 二、PyTorch 实现代码 ```python import torch import torch.nn as nn import torch.optim as optim import numpy as np class UnivariateFunction(nn.Module): """可学习的一元函数(使用 B 样条参数化)""" def __init__(self, num_knots=5, degree=3): super().__init__() self.knots = nn.Parameter(torch.linspace(-1, 1, num_knots)) # 样条节点 self.coeffs = nn.Parameter(torch.randn(num_knots)) # 样条系数 self.degree = degree def forward(self, x): # B 样条基函数计算(简化版) basis = torch.zeros_like(x) for i in range(len(self.knots) - self.degree): mask = (x >= self.knots[i]) & (x < self.knots[i+1]) basis[mask] = (x[mask] - self.knots[i]) / (self.knots[i+1] - self.knots[i]) return torch.sum(self.coeffs * basis) class KANLayer(nn.Module): """KAN 网络层实现""" def __init__(self, input_dim, output_dim, num_functions=5): super().__init__() # 创建 Φ_q 和 φ_{q,p} 函数集 self.phi = nn.ModuleList([ nn.ModuleList([UnivariateFunction() for _ in range(input_dim)]) for _ in range(num_functions) ]) self.Phi = nn.ModuleList([UnivariateFunction() for _ in range(output_dim)]) self.num_functions = num_functions def forward(self, x): # 第一层求和:∑φ_{q,p}(x_p) inner_sum = torch.zeros(x.size(0), self.num_functions) for q in range(self.num_functions): for p in range(x.shape[1]): inner_sum[:, q] += self.phi[q][p](x[:, p]) # 第二层求和:Φ_q(inner_sum) output = torch.zeros(x.size(0), len(self.Phi)) for idx, phi_func in enumerate(self.Phi): output[:, idx] = phi_func(inner_sum) return output class KAN(nn.Module): """完整 KAN 网络""" def __init__(self, input_dim, hidden_dim, output_dim): super().__init__() self.layer1 = KANLayer(input_dim, hidden_dim) self.layer2 = KANLayer(hidden_dim, output_dim) def forward(self, x): x = torch.tanh(self.layer1(x)) # 添加正则化非线性 return self.layer2(x) # 示例训练代码 if __name__ == "__main__": # 1. 创建数据集 (y = sin(x1) + cos(x2)) x = torch.randn(1000, 2) * 2 * np.pi y = torch.sin(x[:, 0]) + torch.cos(x[:, 1]) # 2. 初始化模型 model = KAN(input_dim=2, hidden_dim=8, output_dim=1) optimizer = optim.Adam(model.parameters(), lr=0.01) criterion = nn.MSELoss() # 3. 训练循环 for epoch in range(500): optimizer.zero_grad() outputs = model(x) loss = criterion(outputs.squeeze(), y) loss.backward() optimizer.step() if epoch % 50 == 0: print(f'Epoch {epoch}, Loss: {loss.item():.4f}') # 4. 测试 test_x = torch.tensor([[1.0, 2.0], [3.0, 4.0]]) pred_y = model(test_x) print("\nPredictions:", pred_y.detach().numpy()) ``` ### 三、关键实现要点 1. **一元函数参数化**: - 使用 **B 样条基函数**实现 `UnivariateFunction` [^2] - 样条节点和系数设为可学习参数 - 支持高阶连续逼近能力 2. **网络结构优化**: - 双 KAN 层结构增强表达能力 - 添加 $\tanh$ 正则化防止过拟合 - 自适应隐藏层维度配置 3. **训练技巧**: - Adam 优化器自适应调整学习率 - 周期性损失监控 - 轻量级二维函数拟合验证 ### 四、KAN 的核心优势 | 特性 | KAN | 传统 MLP | |---------------|-------------------------|-----------------------| | **函数表示** | 显式一元函数组合 | 固定激活函数 | | **可解释性** | 可视化学习到的一元函数 | 黑箱结构 | | **参数效率** | 指数级逼近效率[^4] | 多项式级逼近效率 | | **结构设计** | 基于数学定理 | 启发式设计 | > 此实现完整展示了 KAN 的核心思想:**通过可学习的一元函数组合逼近复杂函数**,相比传统 MLP 具有更强的数学理论基础和可解释性[^2]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

spearhead_cai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值