神经微分方程Resnet变体实现内存下降和保持精度

torchdiffeq中odeint函数的输入输出参数分析 torchdiffeq中odeint函数的输入输出参数分析,内有源码 阅读详情

本文内容:

1、学习神经微分方程的笔记,主要锻炼自己学习新知识的能力和看有很多数学原理的论文能力;

2、神经微分方程可以用于时序数据建模、动力学建模等,但是本文专注于分类问题-resnet变体<比较容易理解>;


个人理解:

联合灵敏度的代码实现比较复杂,代码逻辑和算法步骤是一样的,对照看就很容易明白,其实本质上就是把梯度计算归结为求解微分的问题:

工程上实现OdeintAdjointMethod的方法是继承torch.autograd.Function类,实现forward和backward方法,将forward和backward替换成ODE求解器的方式,而不是用原来torch.autograd.Function的链式法则进行梯度求解。


基本原理:

梯度反传法是用于训练神经网络的方法,可以避免使用反向传播训练导数函数时所遇到的可扩展性问题。这种方法涉及使用普通微分方程(ODE)求解器进行前向传播,然后使用联合灵敏度方法进行反向传播,从而使得可以再次使用ODE求解器进行反向传播。为了更新导数函数的参数,需要使用联合灵敏度方法获取损失函数相对于动态函数参数的梯度。最终算法涉及设置某些变量并将信息打包到其中,然后调用ODE求解器反向传播以获得theta并更新CNN编码器和导数函数参数。

梯度反传法算法流程如下:

在这里插入图片描述

更完整的版本:

在这里插入图片描述


联合灵敏度的代码实现比较复杂,代码逻辑和算法步骤是一样的,对照看就很容易明白,其实本质上就是把梯度计算归结为求解微分的问题:

**工程上实现OdeintAdjointMethod的方法是继承torch.autograd.Function类,实现forward和backward方法,将forward和backward替换成ODE求解器的方式,而不是用原来torch.autograd.Function的链式法则进行梯度求解。**odeint则是本文使用的ODE求解器。


代码仓库提供的求解器种类如下:

SOLVERS = {
    'dopri8': Dopri8Solver,
    'dopri5': Dopri5Solver,
    'bosh3': Bosh3Solver,
    'fehlberg2': Fehlberg2,
    'adaptive_heun': AdaptiveHeunSolver,
    'euler': Euler,
    'midpoint': Midpoint,
    'rk4': RK4,
    'explicit_adams': AdamsBashforth,
    'implicit_adams': AdamsBashforthMoulton,
    # Backward compatibility: use the same name as before
    'fixed_adams': AdamsBashforthMoulton,
    # ~Backwards compatibility
    'scipy_solver': ScipyWrapperODESolver,
}

完整源码在:rtqichen/torchdiffeq: Differentiable ODE solvers with full GPU support and O(1)-memory backpropagation. (github.com),这里放上核心部分和注释,前向和反向传播部分;

class OdeintAdjointMethod(torch.autograd.Function):

    @staticmethod
    def forward(ctx, shapes, func, y0, t, rtol, atol, method, options, event_fn, adjoint_rtol, adjoint_atol, adjoint_method,
                adjoint_options, t_requires_grad, *adjoint_params):

        ctx.shapes = shapes
        ctx.func = func
        ctx.adjoint_rtol = adjoint_rtol
        ctx.adjoint_atol = adjoint_atol
        ctx.adjoint_method = adjoint_method
        ctx.adjoint_options = adjoint_options
        ctx.t_requires_grad = t_requires_grad
        ctx.event_mode = event_fn is not None

        with torch.no_grad():
            ans = odeint(func, y0, t, rtol=rtol, atol=atol, method=method, options=options, event_fn=event_fn)

            if event_fn is None:
                y = ans
                ctx.save_for_backward(t, y, *adjoint_params)
            else:
                event_t, y = ans
                ctx.save_for_backward(t, y, event_t, *adjoint_params)

        return ans

    @staticmethod
    def backward(ctx, *grad_y):
        with torch.no_grad():
            func = ctx.func
            adjoint_rtol = ctx.adjoint_rtol
            adjoint_atol = ctx.adjoint_atol
            adjoint_method = ctx.adjoint_method
            adjoint_options = ctx.adjoint_options
            t_requires_grad = ctx.t_requires_grad

            # 反向传播如果积分到达时间,不会在事件时间内反向传播。
            # Backprop as if integrating up to event time.
            # Does NOT backpropagate through the event time.
            event_mode = ctx.event_mode
            if event_mode:
                t, y, event_t, *adjoint_params = ctx.saved_tensors
                _t = t
                t = torch.cat([t[0].reshape(-1), event_t.reshape(-1)])
                grad_y = grad_y[1]
            else:
                t, y, *adjoint_params = ctx.saved_tensors
                grad_y = grad_y[0]

            adjoint_params = tuple(adjoint_params)

            ##################################
            #      创建初始化状态      #
            ##################################

            # [-1] because y and grad_y are both of shape (len(t), *y0.shape)
            aug_state = [torch.zeros((), dtype=y.dtype, device=y.device), y[-1], grad_y[-1]]  # vjp_t, y, vjp_y
            aug_state.extend([torch.zeros_like(param) for param in adjoint_params])  # vjp_params

            ##################################
            #    创建反向ODE函数    #
            ##################################

            # TODO: use a nn.Module and call odeint_adjoint to implement higher order derivatives.
            def augmented_dynamics(t, y_aug):
                # 动力学函数
                # Dynamics of the original system augmented with
                # the adjoint wrt y, and an integrator wrt t and args.
                y = y_aug[1]
                adj_y = y_aug[2]
                # ignore gradients wrt time and parameters

                with torch.enable_grad():
                    t_ = t.detach()
                    t = t_.requires_grad_(True)
                    y = y.detach().requires_grad_(True)

                    # If using an adaptive solver we don't want to waste time resolving dL/dt unless we need it (which
                    # doesn't necessarily even exist if there is piecewise structure in time), so turning off gradients
                    #如果使用自适应求解器,不想浪费时间来求解dL/dt,除非我们需要它(如果有分段结构,它甚至不存在),所以关闭梯度
                    # wrt t here means we won't compute that if we don't need it.
                    func_eval = func(t if t_requires_grad else t_, y)

                    # Workaround for PyTorch bug #39784
                    _t = torch.as_strided(t, (), ())  # noqa
                    _y = torch.as_strided(y, (), ())  # noqa
                    _params = tuple(torch.as_strided(param, (), ()) for param in adjoint_params)  # noqa

                    vjp_t, vjp_y, *vjp_params = torch.autograd.grad(
                        func_eval, (t, y) + adjoint_params, -adj_y,
                        allow_unused=True, retain_graph=True
                    )

                # autograd.grad returns None if no gradient, set to zero.
                vjp_t = torch.zeros_like(t) if vjp_t is None else vjp_t
                vjp_y = torch.zeros_like(y) if vjp_y is None else vjp_y
                vjp_params = [torch.zeros_like(param) if vjp_param is None else vjp_param
                              for param, vjp_param in zip(adjoint_params, vjp_params)]

                return (vjp_t, func_eval, vjp_y, *vjp_params)

            ##################################
            #      求解联合ODE       #
            ##################################

            if t_requires_grad:
                time_vjps = torch.empty(len(t), dtype=t.dtype, device=t.device)
            else:
                time_vjps = None
            for i in range(len(t) - 1, 0, -1):
                if t_requires_grad:
                    # Compute the effect of moving the current time measurement point.
                    # We don't compute this unless we need to, to save some computation.
                    func_eval = func(t[i], y[i])
                    dLd_cur_t = func_eval.reshape(-1).dot(grad_y[i].reshape(-1))
                    aug_state[0] -= dLd_cur_t
                    time_vjps[i] = dLd_cur_t

                # Run the augmented system backwards in time.
                # 运行增强系统反向
                aug_state = odeint(
                    augmented_dynamics, tuple(aug_state),
                    t[i - 1:i + 1].flip(0),
                    rtol=adjoint_rtol, atol=adjoint_atol, method=adjoint_method, options=adjoint_options
                )
                aug_state = [a[1] for a in aug_state]  # extract just the t[i - 1] value
                aug_state[1] = y[i - 1]  # update to use our forward-pass estimate of the state
                aug_state[2] += grad_y[i - 1]  # update any gradients wrt state at this time point

            if t_requires_grad:
                time_vjps[0] = aug_state[0]

            # 计算梯度
            # Only compute gradient wrt initial time when in event handling mode.
            if event_mode and t_requires_grad:
                time_vjps = torch.cat([time_vjps[0].reshape(-1), torch.zeros_like(_t[1:])])

            adj_y = aug_state[2]
            adj_params = aug_state[3:]

        return (None, None, adj_y, time_vjps, None, None, None, None, None, None, None, None, None, None, *adj_params)

模型架构:

使用small的residual net,在下采样后,有6个标准残差块被ODESolve替代,得到ODE-Net。

Sequential(
  (0): Conv2d(1, 64, kernel_size=(3, 3), stride=(1, 1))
  (1): GroupNorm(32, 64, eps=1e-05, affine=True)
  (2): ReLU(inplace=True)
  (3): Conv2d(64, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1))
  (4): GroupNorm(32, 64, eps=1e-05, affine=True)
  (5): ReLU(inplace=True)
  (6): Conv2d(64, 64, kernel_size=(4, 4), stride=(2, 2), padding=(1, 1))
  (7): ODEBlock(
    (odefunc): ODEfunc(
      (norm1): GroupNorm(32, 64, eps=1e-05, affine=True)
      (relu): ReLU(inplace=True)
      (conv1): ConcatConv2d(
        (_layer): Conv2d(65, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      )
      (norm2): GroupNorm(32, 64, eps=1e-05, affine=True)
      (conv2): ConcatConv2d(
        (_layer): Conv2d(65, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
      )
      (norm3): GroupNorm(32, 64, eps=1e-05, affine=True)
    )
  )
  (8): GroupNorm(32, 64, eps=1e-05, affine=True)
  (9): ReLU(inplace=True)
  (10): AdaptiveAvgPool2d(output_size=(1, 1))
  (11): Flatten()
  (12): Linear(in_features=64, out_features=10, bias=True)
)

结果如下:

参数量和内存比Resnet优,但是时间没有明确展示出来,只是展示时间复杂度;

在这里插入图片描述


### 运行resnet demo:
import os
import argparse
import logging
import time
import numpy as np
import torch
import torch.nn as nn
from torch.utils.data import DataLoader
import torchvision.datasets as datasets
import torchvision.transforms as transforms

parser = argparse.ArgumentParser()
parser.add_argument('--network', type=str, choices=['resnet', 'odenet'], default='odenet')
parser.add_argument('--tol', type=float, default=1e-3)
parser.add_argument('--adjoint', type=eval, default=False, choices=[True, False])
parser.add_argument('--downsampling-method', type=str, default='conv', choices=['conv', 'res'])
parser.add_argument('--nepochs', type=int, default=160)
parser.add_argument('--data_aug', type=eval, default=True, choices=[True, False])
parser.add_argument('--lr', type=float, default=0.1)
parser.add_argument('--batch_size', type=int, default=128)
parser.add_argument('--test_batch_size', type=int, default=1000)

parser.add_argument('--save', type=str, default='./experiment1')
parser.add_argument('--debug', action='store_true')
parser.add_argument('--gpu', type=int, default=0)
args = parser.parse_args()

if args.adjoint:
    from torchdiffeq import odeint_adjoint as odeint
else:
    from torchdiffeq import odeint


def conv3x3(in_planes, out_planes, stride=1):
    """3x3 convolution with padding"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)


def conv1x1(in_planes, out_planes, stride=1):
    """1x1 convolution"""
    return nn.Conv2d(in_planes, out_planes, kernel_size=1, stride=stride, bias=False)


def norm(dim):
    return nn.GroupNorm(min(32, dim), dim)


class ResBlock(nn.Module):
    expansion = 1

    def __init__(self, inplanes, planes, stride=1, downsample=None):
        super(ResBlock, self).__init__()
        self.norm1 = norm(inplanes)
        self.relu = nn.ReLU(inplace=True)
        self.downsample = downsample
        self.conv1 = conv3x3(inplanes, planes, stride)
        self.norm2 = norm(planes)
        self.conv2 = conv3x3(planes, planes)

    def forward(self, x):
        shortcut = x

        out = self.relu(self.norm1(x))

        if self.downsample is not None:
            shortcut = self.downsample(out)

        out = self.conv1(out)
        out = self.norm2(out)
        out = self.relu(out)
        out = self.conv2(out)

        return out + shortcut


class ConcatConv2d(nn.Module):

    def __init__(self, dim_in, dim_out, ksize=3, stride=1, padding=0, dilation=1, groups=1, bias=True, transpose=False):
        super(ConcatConv2d, self).__init__()
        module = nn.ConvTranspose2d if transpose else nn.Conv2d
        self._layer = module(
            dim_in + 1, dim_out, kernel_size=ksize, stride=stride, padding=padding, dilation=dilation, groups=groups,
            bias=bias
        )

    def forward(self, t, x):
        tt = torch.ones_like(x[:, :1, :, :]) * t
        ttx = torch.cat([tt, x], 1)
        return self._layer(ttx)


class ODEfunc(nn.Module):

    def __init__(self, dim):
        super(ODEfunc, self).__init__()
        self.norm1 = norm(dim)
        self.relu = nn.ReLU(inplace=True)
        self.conv1 = ConcatConv2d(dim, dim, 3, 1, 1)
        self.norm2 = norm(dim)
        self.conv2 = ConcatConv2d(dim, dim, 3, 1, 1)
        self.norm3 = norm(dim)
        self.nfe = 0

    def forward(self, t, x):
        self.nfe += 1
        out = self.norm1(x)
        out = self.relu(out)
        out = self.conv1(t, out)
        out = self.norm2(out)
        out = self.relu(out)
        out = self.conv2(t, out)
        out = self.norm3(out)
        return out


class ODEBlock(nn.Module):

    def __init__(self, odefunc):
        super(ODEBlock, self).__init__()
        self.odefunc = odefunc
        self.integration_time = torch.tensor([0, 1]).float()

    def forward(self, x):
        self.integration_time = self.integration_time.type_as(x)
        out = odeint(self.odefunc, x, self.integration_time, rtol=args.tol, atol=args.tol)
        return out[1]

    @property
    def nfe(self):
        return self.odefunc.nfe

    @nfe.setter
    def nfe(self, value):
        self.odefunc.nfe = value


class Flatten(nn.Module):

    def __init__(self):
        super(Flatten, self).__init__()

    def forward(self, x):
        shape = torch.prod(torch.tensor(x.shape[1:])).item()
        return x.view(-1, shape)


class RunningAverageMeter(object):
    """Computes and stores the average and current value"""

    def __init__(self, momentum=0.99):
        self.momentum = momentum
        self.reset()

    def reset(self):
        self.val = None
        self.avg = 0

    def update(self, val):
        if self.val is None:
            self.avg = val
        else:
            self.avg = self.avg * self.momentum + val * (1 - self.momentum)
        self.val = val


def get_mnist_loaders(data_aug=False, batch_size=128, test_batch_size=1000, perc=1.0):
    if data_aug:
        transform_train = transforms.Compose([
            transforms.RandomCrop(28, padding=4),
            transforms.ToTensor(),
        ])
    else:
        transform_train = transforms.Compose([
            transforms.ToTensor(),
        ])

    transform_test = transforms.Compose([
        transforms.ToTensor(),
    ])

    train_loader = DataLoader(
        datasets.MNIST(root='.data/mnist', train=True, download=True, transform=transform_train), batch_size=batch_size,
        shuffle=True, num_workers=2, drop_last=True
    )

    train_eval_loader = DataLoader(
        datasets.MNIST(root='.data/mnist', train=True, download=True, transform=transform_test),
        batch_size=test_batch_size, shuffle=False, num_workers=2, drop_last=True
    )

    test_loader = DataLoader(
        datasets.MNIST(root='.data/mnist', train=False, download=True, transform=transform_test),
        batch_size=test_batch_size, shuffle=False, num_workers=2, drop_last=True
    )

    return train_loader, test_loader, train_eval_loader


def inf_generator(iterable):
    """Allows training with DataLoaders in a single infinite loop:
        for i, (x, y) in enumerate(inf_generator(train_loader)):
    """
    iterator = iterable.__iter__()
    while True:
        try:
            yield iterator.__next__()
        except StopIteration:
            iterator = iterable.__iter__()


def learning_rate_with_decay(batch_size, batch_denom, batches_per_epoch, boundary_epochs, decay_rates):
    initial_learning_rate = args.lr * batch_size / batch_denom

    boundaries = [int(batches_per_epoch * epoch) for epoch in boundary_epochs]
    vals = [initial_learning_rate * decay for decay in decay_rates]

    def learning_rate_fn(itr):
        lt = [itr < b for b in boundaries] + [True]
        i = np.argmax(lt)
        return vals[i]

    return learning_rate_fn


def one_hot(x, K):
    return np.array(x[:, None] == np.arange(K)[None, :], dtype=int)


def accuracy(model, dataset_loader):
    total_correct = 0
    for x, y in dataset_loader:
        x = x.to(device)
        y = one_hot(np.array(y.numpy()), 10)

        target_class = np.argmax(y, axis=1)
        predicted_class = np.argmax(model(x).cpu().detach().numpy(), axis=1)
        total_correct += np.sum(predicted_class == target_class)
    return total_correct / len(dataset_loader.dataset)


def count_parameters(model):
    return sum(p.numel() for p in model.parameters() if p.requires_grad)


def makedirs(dirname):
    if not os.path.exists(dirname):
        os.makedirs(dirname)


def get_logger(logpath, filepath, package_files=[], displaying=True, saving=True, debug=False):
    logger = logging.getLogger()
    if debug:
        level = logging.DEBUG
    else:
        level = logging.INFO
    logger.setLevel(level)
    if saving:
        info_file_handler = logging.FileHandler(logpath, mode="a")
        info_file_handler.setLevel(level)
        logger.addHandler(info_file_handler)
    if displaying:
        console_handler = logging.StreamHandler()
        console_handler.setLevel(level)
        logger.addHandler(console_handler)
    logger.info(filepath)
    with open(filepath, "r") as f:
        logger.info(f.read())

    for f in package_files:
        logger.info(f)
        with open(f, "r") as package_f:
            logger.info(package_f.read())

    return logger


if __name__ == '__main__':

    makedirs(args.save)
    logger = get_logger(logpath=os.path.join(args.save, 'logs'), filepath=os.path.abspath(__file__))
    logger.info(args)

    device = torch.device('cuda:' + str(args.gpu) if torch.cuda.is_available() else 'cpu')

    is_odenet = args.network == 'odenet'

    if args.downsampling_method == 'conv':
        downsampling_layers = [
            nn.Conv2d(1, 64, 3, 1),
            norm(64),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, 4, 2, 1),
            norm(64),
            nn.ReLU(inplace=True),
            nn.Conv2d(64, 64, 4, 2, 1),
        ]
    elif args.downsampling_method == 'res':
        downsampling_layers = [
            nn.Conv2d(1, 64, 3, 1),
            ResBlock(64, 64, stride=2, downsample=conv1x1(64, 64, 2)),
            ResBlock(64, 64, stride=2, downsample=conv1x1(64, 64, 2)),
        ]

    feature_layers = [ODEBlock(ODEfunc(64))] if is_odenet else [ResBlock(64, 64) for _ in range(6)]
    fc_layers = [norm(64), nn.ReLU(inplace=True), nn.AdaptiveAvgPool2d((1, 1)), Flatten(), nn.Linear(64, 10)]

    model = nn.Sequential(*downsampling_layers, *feature_layers, *fc_layers).to(device)

    logger.info(model)
    logger.info('Number of parameters: {}'.format(count_parameters(model)))

    criterion = nn.CrossEntropyLoss().to(device)

    train_loader, test_loader, train_eval_loader = get_mnist_loaders(
        args.data_aug, args.batch_size, args.test_batch_size
    )

    data_gen = inf_generator(train_loader)
    batches_per_epoch = len(train_loader)

    lr_fn = learning_rate_with_decay(
        args.batch_size, batch_denom=128, batches_per_epoch=batches_per_epoch, boundary_epochs=[60, 100, 140],
        decay_rates=[1, 0.1, 0.01, 0.001]
    )

    optimizer = torch.optim.SGD(model.parameters(), lr=args.lr, momentum=0.9)

    best_acc = 0
    batch_time_meter = RunningAverageMeter()
    f_nfe_meter = RunningAverageMeter()
    b_nfe_meter = RunningAverageMeter()
    end = time.time()

    for itr in range(args.nepochs * batches_per_epoch):

        for param_group in optimizer.param_groups:
            param_group['lr'] = lr_fn(itr)

        optimizer.zero_grad()
        x, y = data_gen.__next__()
        x = x.to(device)
        y = y.to(device)
        logits = model(x)
        loss = criterion(logits, y)

        if is_odenet:
            nfe_forward = feature_layers[0].nfe
            feature_layers[0].nfe = 0

        loss.backward()
        optimizer.step()

        if is_odenet:
            nfe_backward = feature_layers[0].nfe
            feature_layers[0].nfe = 0

        batch_time_meter.update(time.time() - end)
        if is_odenet:
            f_nfe_meter.update(nfe_forward)
            b_nfe_meter.update(nfe_backward)
        end = time.time()

        if itr % batches_per_epoch == 0:
            with torch.no_grad():
                train_acc = accuracy(model, train_eval_loader)
                val_acc = accuracy(model, test_loader)
                if val_acc > best_acc:
                    torch.save({'state_dict': model.state_dict(), 'args': args}, os.path.join(args.save, 'model.pth'))
                    best_acc = val_acc
                logger.info(
                    "Epoch {:04d} | Time {:.3f} ({:.3f}) | NFE-F {:.1f} | NFE-B {:.1f} | "
                    "Train Acc {:.4f} | Test Acc {:.4f}".format(
                        itr // batches_per_epoch, batch_time_meter.val, batch_time_meter.avg, f_nfe_meter.avg,
                        b_nfe_meter.avg, train_acc, val_acc
                    )
                )

训练过程:

Epoch 0000 | Time 3.425 (3.425) | NFE-F 32.0 | NFE-B 0.0 | Train Acc 0.0987 | Test Acc 0.0958
Epoch 0001 | Time 3.279 (0.840) | NFE-F 20.3 | NFE-B 0.0 | Train Acc 0.9755 | Test Acc 0.9779
Epoch 0002 | Time 3.500 (0.839) | NFE-F 20.1 | NFE-B 0.0 | Train Acc 0.9858 | Test Acc 0.9875
Epoch 0003 | Time 3.403 (0.828) | NFE-F 20.1 | NFE-B 0.0 | Train Acc 0.9884 | Test Acc 0.9879
Epoch 0004 | Time 3.303 (0.807) | NFE-F 20.1 | NFE-B 0.0 | Train Acc 0.9926 | Test Acc 0.9921
Epoch 0005 | Time 3.308 (0.801) | NFE-F 20.1 | NFE-B 0.0 | Train Acc 0.9940 | Test Acc 0.9930
Epoch 0006 | Time 3.255 (0.804) | NFE-F 20.1 | NFE-B 0.0 | Train Acc 0.9917 | Test Acc 0.9894
Epoch 0007 | Time 3.376 (0.808) | NFE-F 20.2 | NFE-B 0.0 | Train Acc 0.9948 | Test Acc 0.9929
Epoch 0008 | Time 3.260 (0.806) | NFE-F 20.1 | NFE-B 0.0 | Train Acc 0.9935 | Test Acc 0.9934
Epoch 0009 | Time 3.248 (0.832) | NFE-F 20.4 | NFE-B 0.0 | Train Acc 0.9948 | Test Acc 0.9909
Epoch 0010 | Time 3.286 (0.817) | NFE-F 20.4 | NFE-B 0.0 | Train Acc 0.9959 | Test Acc 0.9947
Epoch 0011 | Time 3.281 (0.827) | NFE-F 20.8 | NFE-B 0.0 | Train Acc 0.9967 | Test Acc 0.9951
Epoch 0012 | Time 3.382 (0.825) | NFE-F 20.9 | NFE-B 0.0 | Train Acc 0.9949 | Test Acc 0.9929
Epoch 0013 | Time 3.299 (0.862) | NFE-F 22.0 | NFE-B 0.0 | Train Acc 0.9976 | Test Acc 0.9949
Epoch 0014 | Time 3.326 (0.824) | NFE-F 20.8 | NFE-B 0.0 | Train Acc 0.9947 | Test Acc 0.9936
Epoch 0015 | Time 3.291 (0.839) | NFE-F 21.3 | NFE-B 0.0 | Train Acc 0.9974 | Test Acc 0.9948
Epoch 0016 | Time 3.467 (0.935) | NFE-F 24.4 | NFE-B 0.0 | Train Acc 0.9977 | Test Acc 0.9941
Epoch 0017 | Time 3.483 (0.900) | NFE-F 23.2 | NFE-B 0.0 | Train Acc 0.9970 | Test Acc 0.9939
Epoch 0018 | Time 3.309 (0.872) | NFE-F 22.2 | NFE-B 0.0 | Train Acc 0.9961 | Test Acc 0.9932
Epoch 0019 | Time 3.294 (0.913) | NFE-F 23.6 | NFE-B 0.0 | Train Acc 0.9974 | Test Acc 0.9954
Epoch 0020 | Time 3.504 (0.984) | NFE-F 25.9 | NFE-B 0.0 | Train Acc 0.9983 | Test Acc 0.9951
Epoch 0021 | Time 3.589 (0.966) | NFE-F 25.2 | NFE-B 0.0 | Train Acc 0.9966 | Test Acc 0.9929
Epoch 0022 | Time 3.503 (0.994) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9977 | Test Acc 0.9949
Epoch 0023 | Time 3.457 (0.995) | NFE-F 26.1 | NFE-B 0.0 | Train Acc 0.9978 | Test Acc 0.9939
Epoch 0024 | Time 3.529 (0.985) | NFE-F 26.0 | NFE-B 0.0 | Train Acc 0.9985 | Test Acc 0.9958
Epoch 0025 | Time 3.459 (0.988) | NFE-F 26.0 | NFE-B 0.0 | Train Acc 0.9973 | Test Acc 0.9947
Epoch 0026 | Time 3.541 (0.988) | NFE-F 26.0 | NFE-B 0.0 | Train Acc 0.9979 | Test Acc 0.9946
Epoch 0027 | Time 3.513 (0.993) | NFE-F 26.1 | NFE-B 0.0 | Train Acc 0.9986 | Test Acc 0.9959
Epoch 0028 | Time 3.505 (0.996) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9982 | Test Acc 0.9953
Epoch 0029 | Time 3.501 (0.990) | NFE-F 26.1 | NFE-B 0.0 | Train Acc 0.9985 | Test Acc 0.9953
Epoch 0030 | Time 3.475 (0.992) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9983 | Test Acc 0.9954
Epoch 0031 | Time 3.506 (0.993) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9986 | Test Acc 0.9947
Epoch 0032 | Time 3.527 (0.995) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9981 | Test Acc 0.9954
Epoch 0033 | Time 3.529 (0.996) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9976 | Test Acc 0.9945
Epoch 0034 | Time 3.545 (0.996) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9988 | Test Acc 0.9959
Epoch 0035 | Time 3.479 (0.995) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9990 | Test Acc 0.9953
Epoch 0036 | Time 3.479 (0.997) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9989 | Test Acc 0.9963
Epoch 0037 | Time 3.540 (0.998) | NFE-F 26.2 | NFE-B 0.0 | Train Acc 0.9988 | Test Acc 0.9957

参考:

1、神经常微分方程 (Neural ODE):入门教程 - 知乎 (zhihu.com)

2、rtqichen/torchdiffeq: Differentiable ODE solvers with full GPU support and O(1)-memory backpropagation. (github.com)

快速熟悉torchdiffeq用法,从数理逻辑到完整案例【第二、三部分】 第一部分 torchdiffeq背后的数理逻辑第二部分 torchdiffeq的基本用法第三部分 trochdiffeq的升级用法第四部分 torchdifffeq的案例和代码解析第五部分 总结第二部分的参考网站:https://github.com/rtqichen/torchdiffeq。 阅读详情

相关推荐

深度学习核心:反向传播与梯度下降原理详解

反向传播和梯度下降是深度学习的数学基础,构成了模型从数据中学习规律的核心机制。反向传播通过链式法则高效计算梯度,将输出层的误差逐层传递到每一层的参数上;梯度下降则利用这些梯度信息,沿误差曲面的负梯度方向调整参数,逐步逼近最优解。这些技术不仅支撑了现代深度学习的快速发展,还在计算机视觉、自然语言处理等领域展现出强大的应用价值。理解这些基础原理,有助于开发者更高效地调试模型、设计优化策略,并针对特定任务选择合适的超参数。随着深度学习模型的不断演进,反向传播和梯度下降的优化与改进仍是研究热点,如Adam优化器、分

weixin_33843409的博客 423

torchdiffeq:具有完整GPU支持和O(1)内存反向传播的可微分ODE求解器

可区分ODE解算器的PyTorch实现 该库提供了在PyTorch中实现的常微分方程(ODE)求解器。 使用adjoint方法支持所有求解器的反向传播。 有关在深度学习应用程序中使用ODE求解器的信息,请参见[1]。 由于求解器是在PyTorch中实现的,因此完全支持该存储库中的算法以在GPU上运行。 安装 要安装最新的稳定版本: pip install torchdiffeq 要在GitHub上安装最新版本: pip install git+https://github.com/rtqichen/torchdiffeq 例子 示例位于目录中。 我们鼓励对使用此库感兴趣的人看一下以了解如何使用torchdiffeq来拟合简单的螺旋ODE。 基本用法 该库提供了一个主界面odeint ,其中包含用于解决初值问题(IVP)的通用算法,并且为所有主要参数实现了渐变。 初始值问题由ODE和

AI工业落地的三大硬骨头:神经可塑性、能耗瓶颈与新场景泛化

人工智能在工业场景中的真正挑战,早已超越算法精度与模型规模,直指系统级物理约束。神经可塑性关乎模型能否持续适应动态产线数据流,避免灾难性遗忘;能耗瓶颈决定AI能否在边缘设备长期稳定运行,而非依赖云端算力堆砌;新场景泛化能力则回应低资源、小样本、跨模态等现实限制,要求模型具备物理常识与结构化学习能力。这些并非理论空谈,而是医疗影像部署、风电预测、智能质检等一线项目中反复出现的工程瓶颈。本文聚焦可验证、可部署、可量化的技术解法,涵盖液态神经网络、脉冲神经网络、能耗-精度联合优化、故障树引导采样等工业级实践路径,

weixin_34101784的博客 444

ResNet及其变体ResNeXt学习笔记

ResNet及其变体ResNeXt 1. ResNet ResNet作为分类网络赢得了2015年ILSVRC冠军,且将top-5 error降低到了3.57%。这一年,深度学习第一次在这个任务上打败了人类(top-5 95%)。 同时作者还测试了ResNet对于计算机视觉下游任务中的提升效果,仅仅通过替换方法中的backbone(比如将faster rcnn中的vgg-16网络换成了resnet-101),ResNet就在2015的ImageNet 目标检测、Image...

qq_36936443的博客 6380

物理信息神经网络(PINN)训练全流程:从原理到实战调优

物理信息神经网络(PINN)是一种融合深度学习与科学计算的创新方法,其核心原理是将描述物理世界的偏微分方程作为约束条件,直接嵌入神经网络的损失函数中。这一设计使得网络在训练时不仅拟合观测数据,还必须遵循物理定律,从而保证了解的物理可信度。从技术价值看,PINN通过自动微分高效计算高阶导数,解决了传统方法在复杂反问题中计算量大、对噪声敏感等痛点。在应用场景上,它广泛适用于流体力学、热传导、结构分析等领域的正反问题求解,尤其擅长处理数据稀疏或带噪声的工程仿真任务。本文以PINN训练流程为主线,深入剖析了损失函数

weixin_30883311的博客 428

深度学习与生物神经网络的本质差异:从计算契约到工程妥协

人工神经网络(ANN)并非对生物神经网络的简化模拟,而是在数学可微性、硬件并行性与训练效率约束下演化出的独立信息处理范式。其核心原理是将感知-决策任务建模为可优化的非线性映射,依赖反向传播实现全局梯度更新,以换取GPU上毫秒级收敛能力。这种设计牺牲了生物系统的时空连续性、事件驱动性与稀疏通信等物理真实,却换来了确定性训练、模块化架构与跨任务泛化等关键技术价值。典型应用场景包括图像识别、自然语言处理及推荐系统等数据密集型任务,其中Transformer的全局注意力与CNN的层次化特征提取,正是计算契约在不同约

赵士杰的博客 569

ResNet最新变体!性能反超Transformer,模型准确率达98.42%

目前ResNet(残差网络)有两大主流创新思路:一是与其他技术或模型结合,比如;二是尽管ResNet通过残差学习有效改善了深层网络的训练和性能,但同时它也面临着计算资源消耗大、过拟合风险、网络冗余和梯度相关性等挑战。为克服这些挑战,研究者们,比如性能反超Transformer的ConvNeXt、准确率高达98.42%的FC-ResNet等。这些改进方案因此,为帮助同学们深入了解,这次我整理了,有2024最新的,也有经典必看的,模型原文以及开源代码已附,方便同学们复现。

学姐带你玩AI的博客 7798

神经回路策略NCP:源自线虫的可解释、鲁棒AI新范式

神经回路策略(NCP)是一种将生物神经回路结构与动力学建模融入人工智能的新型计算范式,其核心在于用具备明确物理意义的液态时间常数(LTC)神经元和稀疏拓扑连接,替代传统深度学习中的黑箱统计拟合。它基于秀丽隐杆线虫connectome实证设计,天然支持前向可追溯的激活路径、抗噪鲁棒性与边缘端低功耗部署。相比CNN或Transformer,NCP不追求更高精度,而是在自动驾驶、工业控制等高可信场景中提供可验证决策、可调试行为与可部署规模的三位一体能力——这正是当前AI从‘能用’迈向‘敢用’的关键跃迁。

355

四大经典CNN架构图解:从LeNet到ResNet的结构演进与可视化实践

卷积神经网络(CNN)架构可视化是深度学习工程实践的关键环节,它连接数学原理、计算逻辑与硬件实现。理解LeNet-5的稀疏连接设计、AlexNet的11×11大核降维策略、VGG-16的3×3小核堆叠优势,以及ResNet-50的残差映射机制,本质是在掌握模型如何平衡表达能力、计算效率与训练稳定性。这些结构选择直接受限于数据规模、算力条件与优化理论进展,其可视化不仅需准确呈现层类型、尺寸与参数,更要揭示通道数变化、跳跃连接位置、归一化层作用等工程细节。本文以出版级图表生成为切入点,融合PyTorch复现验证

474

世界模型如何用神经网络实现薛定谔猫式的不确定性建模

世界模型是一种面向动态环境的概率性生成框架,其核心在于将未来状态建模为条件概率分布而非确定性输出。这一范式突破了传统神经网络的确定性函数逼近局限,通过潜变量分布(如VAE输出的μ/σ²)显式表征认知不确定性,其演化机制与量子叠加态在数学结构上高度同源——多假设共存于同一隐状态,决策时再通过采样实现‘坍缩’。神经网络凭借端到端可微、高表达力与强扩展性,成为实现该范式的唯一工程可行载体。本文聚焦World Model与Neural Network的技术耦合,解析VAE-RNN-Diffusion协同构建‘猫态’

weixin_30954265的博客 405

mHC:重构神经网络信息流的动态路由架构

神经网络的信息传递本质是通信问题,而非单纯计算。传统架构将层间连接视为静态管道,导致梯度失衡、状态冗余与拓扑僵化等底层通信效率瓶颈。mHC提出‘神经高速公路’新范式,通过层间解耦、动态路由和可重构拓扑三大机制,实现信息流从被动承载到主动调度的跃迁。其核心创新在于Path Controller与State Keeper协同构成的实时交通管制系统,支持梯度感知决策、无感状态缓存与互信息驱动的连接演化。该技术显著提升大模型训练稳定性、多模态融合鲁棒性及边缘部署能效,适用于视觉、语言、时序等广泛任务场景。

cuanmo8775的博客 387

神经导航函数:用可学习PDE实现零样本运动规划

标量场导航是一种将空间路径规划转化为连续势能建模的新型范式,其核心原理是构建满足偏微分方程约束的可微导航函数,使机器人沿负梯度流自动产生安全、平滑、物理可行的轨迹。相比传统搜索算法,该方法具备实时性高、零样本泛化强、多目标自然耦合等技术价值,在动态仓储、自动驾驶和医疗手术导航等场景中展现出显著工程优势。本文聚焦Neural Navigation Functions(神经导航函数)与Learnable PDEs(可学习偏微分方程)的协同机制,解析其如何通过数据驱动方式发现隐式空间规律,替代人工设计的几何推理与

weixin_33896726的博客 478

神经科学如何驱动AI工程化落地:从机制映射到PyTorch模块

人工智能正面临鲁棒性、能效与泛化能力的瓶颈,而神经科学提供了经亿万年演化验证的计算原理。从视觉皮层的对比度增益控制到前额叶突触可塑性的动力学建模,生物机制若能转化为可微分、可量化、可插入的数学表达,便能直接优化深度学习架构。这种‘机制映射’范式跳出了简单结构类比,强调稀疏编码、时间尺度分离、能量约束等核心原则,在工业质检、自动驾驶和边缘推理中显著提升抗干扰性与功耗效率。本文聚焦神经科学启发的AI工程实践路径,结合脉冲神经网络、动态学习率设计与硬件感知编译等关键技术,展示如何将Nature Neuroscie

weixin_33796177的博客 397

【信息科学与工程学】【产品体系】第二十篇 CDN网络101 现代CDN(内容分发网络)背后深刻的数学基础

网络流量传播、缓存更新与失效的动态过程,以及多函数逻辑切分,其数学建模和协同优化对于构建高性能、高可靠的网络系统至关重要。​微分方程随机过程优化理论和逻辑分解是理解和设计这些系统的关键工具。通过将宏观流体模型、微观随机建模与缓存策略、模块化设计相结合,可以有效优化网络性能与资源利用率。以上方法体现了数学理论在CDN系统中的深度应用:李群处理对称性与运动规划,LWR模型宏观预测流量,逻辑切分提升模块化,MDP优化动态决策。

weixin_49199313的博客 754

物理信息神经网络在流体仿真中的工程落地实践

物理信息神经网络(PINN)是一种融合第一性原理与数据驱动的建模范式,其核心在于将控制方程(如Navier-Stokes)、边界条件等物理约束显式嵌入深度学习模型结构或损失函数中,从而突破纯黑箱模型在泛化性、守恒性与跨工况鲁棒性上的瓶颈。相比传统CFD的高计算成本与纯数据驱动方法的物理失真风险,PINN通过‘物理引导+数据校准’双轨架构,在保证质量/动量守恒的前提下显著加速仿真推理——典型场景下可实现千倍级提速且误差可控。该技术已广泛应用于电池热管理、风洞预分析、微流控芯片设计及光刻机液流控制等工业场景,尤

weixin_30374009的博客 325

神经网络发展史:从生物启发到数学建模的认知跃迁

神经网络并非凭空诞生的算法,而是源于对生物神经系统持续破译的跨学科探索。其核心原理植根于逻辑建模、微积分优化与泛函分析等数学工具的迭代升级——从1943年M-P神经元的布尔逻辑抽象,到反向传播赋予多层结构可训练性,再到Transformer中自注意力所依赖的核方法与流形学习。这一演进过程不仅塑造了深度学习的技术价值,更深刻影响着工业缺陷检测、时序预测、边缘部署等现实场景的建模范式选择。理解神经网络的历史脉络,本质是掌握一套诊断模型失效根源的认知框架,尤其在面对梯度消失、线性不可分或硬件适配瓶颈时,历史提供的

cm333666的博客 430

物理信息神经网络实战:流体力学AI建模的工业落地路径

物理信息神经网络(PINN)是一种将偏微分方程先验嵌入深度学习模型的核心技术,其本质是通过损失函数强约束网络输出满足守恒律与控制方程,从而突破纯数据驱动模型在泛化性、物理一致性和小样本场景下的根本局限。相比传统代理模型或黑箱预测,PINN在湍流模拟、微流控设计和MHD建模中展现出显著鲁棒性与可解释性,尤其适用于高雷诺数、稀疏实验数据及多物理场耦合等典型工程瓶颈。结合数据同化、流场超分辨率重建与物理校准代理模型,可构建端到端可信AI仿真链路。本文聚焦工业级实现细节,涵盖PDE残差构建、边界硬编码、自适应权重平

weixin_34195546的博客 391

ICLR 2022十大论文:神经网络理论建模的关键跃迁

深度学习正从经验调参迈向理论建模阶段,其核心驱动力在于对模型‘如何学习’的本质追问。泛化理论不再依赖传统复杂度上界,而是通过局部弹性界、本征维度等几何与因果视角重新刻画模型行为;神经网络可解释性也突破可视化局限,转向注意力机制的因果干预、梯度路径的隐式偏好分析等原理级理解。这些进展不仅提升了模型鲁棒性与校准能力,更在医疗诊断、工业检测、边缘联邦学习等场景中实现工程落地。ICLR 2022作为该范式转移的标志性节点,集中呈现了泛化理论、神经网络可解释性等关键方向的突破性思想与可复现洞见。

chunchan1381的博客 421
上一篇: 基于锁和条件变量的线程安全队列
下一篇: android Jetpack MVVM的理解
KPer_Yang
博客等级 码龄4年 281粉丝 181原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

KPer_Yang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值