PyTorch 中的 apply

Abstract

  • nn.Module[List].apply(callable)
  • Tensor.apply_(callable) → Tensor
  • Function.apply(Tensor...)

nn.Module[List].apply()?

源码:

def apply(self: T, fn: Callable[['Module'], None]) -> T:"""Typical use includes initializing the parameters of a modelArgs:fn: function to be applied to each submoduleReturns:self"""for module in self.children():module.apply(fn)  # 看来这里是先 apply 了子模块fn(self)  # 最后才是根return self

nn.ModuleList 是 PyTorch 中用于存储子模块的容器,而 apply() 方法可以应用一个函数到 ModuleList 中的每个子模块。具体来说,apply() 方法会递归地将指定的函数应用到 ModuleList 中的每个子模块以及每个子模块的子模块上。这个方法的语法如下:

nn.ModuleList.apply(fn)

其中 fn 是要应用的函数,它接受一个 Module 参数并且没有返回值。在 apply() 方法被调用后,会遍历 ModuleList 中的每个子模块,并把这个函数应用到每个子模块上。

例如,假设有一个 ModuleList 包含了若干线性层(Linear),我们想要初始化所有线性层的权重为 0,可以使用 apply() 方法:

import torch
import torch.nn as nn# 创建一个 ModuleList 包含两个线性层
module_list = nn.ModuleList([nn.Linear(10, 5), nn.Linear(5, 2)])# 定义一个函数用于初始化权重为0
def init_weights(module):if isinstance(module, nn.Linear):module.weight.data.fill_(0)# 应用函数到 ModuleList 的每个子模块上
module_list.apply(init_weights)# 打印每个线性层的权重
for module in module_list:print(module.weight)

在这个例子中,我们定义了一个函数 init_weights,它会将输入的 nn.Linear 模块的权重初始化为 0。然后我们通过 apply() 方法将这个函数应用到 ModuleList 中的每个线性层上,并最终打印出每个线性层的权重。

Tensor.apply_(callable) → Tensor

对张量的每个元素执行 callable 操作, 并且是 inplace 的, 即它不返回新的张量.

import torchdef add(x):return x + 1a = torch.randn(2, 3)
print(a)
# tensor([[-1.6572, -0.7502, -0.9984],
#		  [ 0.3035, -0.6085, -0.1091]])b = a.apply_(add)
print(a)
print(b)
# tensor([[-0.6572,  0.2498,  0.0016],
#		  [ 1.3035,  0.3915,  0.8909]])
# tensor([[-0.6572,  0.2498,  0.0016],
#		  [ 1.3035,  0.3915,  0.8909]])print(b is a)
# True, 说明 a.apply_(add) 不返回新的张量, 是 inplace 的

NOTE
仅对 CPU 上的张量有效, 不应在要求高效的代码段中使用. 官方这么说, 大概是它效率不高吧.

a = torch.randn(2, 3, device='cuda:0')
a.apply_(lambda x: x + 1)
# TypeError: apply_ is only implemented on CPU tensors

NOTE
似乎没有不 in-place 的方法.

a.apply(lambda x: x + 1)
# AttributeError: 'Tensor' object has no attribute 'apply'. Did you mean: 'apply_'?

Function.apply(Tensor…)

以上的两个 apply 函数都是由对象 (Module 或 Tensor) 发起, 参数为 Callable. Function.apply(Tensor...) 不一样, 它由 Function 发起, 接收参数为张量, 起到"运行 forward"的作用. 先看 Relu 是如何求微分的:

import torch
from torch import autogradclass CustomReLUFunction(autograd.Function):@staticmethoddef forward(ctx, *args, **kwargs):x = args[0]ctx.save_for_backward(x)return x.clamp(min=0)@staticmethoddef backward(ctx, *grad_outputs):x, = ctx.saved_tensorsgrad_output = grad_outputs[0]grad_input = grad_output.clone()  # 意思是不改变传进来的 outputs 的 grad 吗?grad_input[x < 0] = 0return grad_input# 使用自定义的 ReLU 激活函数
custom_relu = CustomReLUFunction.apply  # 注意这里的 apply
a = torch.randn(5, requires_grad=True)
output = CustomReLUFunction.apply(a)
output.backward(torch.ones_like(a))print(a)
print(output)
print(a.grad)#########################
tensor([-1.8688, -0.0540, -0.6364, -0.9364,  1.2601], requires_grad=True)
tensor([0.0000, 0.0000, 0.0000, 0.0000, 1.2601],grad_fn=<CustomReLUFunctionBackward>)
tensor([0., 0., 0., 0., 1.])

没错, 代码里出现了 apply. 这需要了解 torch.autograd.

Extending torch.autograd

PyTorch 的自动微分机制是通过动态计算图实现的, 图中的张量 Tensor 是节点, 连接节点的边是叫做 Function 的东西. 一般的 PyTorch 内置运算都可以自动求微分, 这才使得优化模型时仅仅需要三行代码:

optimizer.zero_grad()
loss.backward()
optimizer.step()

就可以完成梯度下降. 如果一些运算不可微呢?比如计算一些积分, 或者比较简单的 Relu 函数在 0 处也是不可微的, 又或者运算中需要优化的部分使用了 Numpy 等其他库, 则需要我们自己实现求微分. 做法就是继承 class torch.autograd.Function, 实现其中的三个 method:

def forward(ctx: Any, *args: Any, **kwargs: Any) -> Any
def setup_context(ctx: Any, inputs: Tuple[Any, ...], output: Any)
def backward(ctx: Any, *grad_outputs: Any) -> Any

然后通过 Function.apply 导出运算. 见上面的 CustomReLUFunction, 不过它是老版的, 新版(pytorch>=2.0) 建议使用这三个方法. 先看官方给的例子:

from torch import autogradclass LinearFunction(autograd.Function):# Note that forward, setup_context, and backward are @staticmethods@staticmethoddef forward(input, weight, bias):output = input.mm(weight.t())if bias is not None:output += bias.unsqueeze(0).expand_as(output)return output@staticmethod# inputs is a Tuple of all of the inputs passed to forward.# output is the output of the forward().def setup_context(ctx, inputs, output):  # output 没用到input, weight, bias = inputsctx.save_for_backward(input, weight, bias)# This function has only a single output, so it gets only one gradient@staticmethoddef backward(ctx, grad_output):input, weight, bias = ctx.saved_tensorsgrad_input = grad_weight = grad_bias = None# These needs_input_grad checks are optional and there only to# improve efficiency. If you want to make your code simpler, you can# skip them. Returning gradients for inputs that don't require it is# not an error.if ctx.needs_input_grad[0]:grad_input = grad_output.mm(weight)if ctx.needs_input_grad[1]:grad_weight = grad_output.t().mm(input)if bias is not None and ctx.needs_input_grad[2]:grad_bias = grad_output.sum(0)return grad_input, grad_weight, grad_bias

之后, 就可以使用 Function.apply(input, weight, bias) 进行运算了(不可直接调用 forward), 它可以实现执行 forward 方法, 并通过 setup_context计算状态(输入值等)保存进 ctx 对象中, 供反向传播时的 backward 使用.

新老版的区别:
老版的 def forward(ctx, *args, **kwargs) 第一个参数是 ctx, 环境的保存需要在 forward 中完成;
新版的 def forward(*args, **kwargs) 仅接收输入就行了, 保存环境的工作交给 setup_context(ctx, inputs, output) 完成;
不过这些都不需要用户关心.
建议用新版, 因为它和 pytorch 内置的 operator 更接近, 兼容性更好.

参数数量方面需要注意的是: forwardbackward参数数量和返回值数量要对应, 互反: forward 的输出数量对应 backward 的参数数量; backward 的输出数量对应 forward 的参数数量; 这很好理解, 传播一正一反嘛, 张量和其对应的梯度!

forward 的 non-Tensor 参数的梯度必须为 None, 不能省, 数量要一致.
class MulConstant(Function):@staticmethoddef forward(tensor, constant):return tensor * constant@staticmethoddef setup_context(ctx, inputs, output):# ctx is a context object that can be used to stash information# for backward computationtensor, constant = inputsctx.constant = constant  # 非 Tensor 直接保存在 ctx 中, 而不是 save_for_backward@staticmethoddef backward(ctx, grad_output):# We return as many input gradients as there were arguments.# Gradients of non-Tensor arguments to forward must be None.return grad_output * ctx.constant, None  # const 的梯度

注意, non-tensors should be stored directly on ctx, 如 ctx.constant = constant.

set_materialize_grads 告诉 autograd engine 梯度计算与 inputs 无关, 以提升计算效率
**class MulConstant(Function):@staticmethoddef forward(tensor, constant):return tensor * constant@staticmethoddef setup_context(ctx, inputs, output):tensor, constant = inputsctx.set_materialize_grads(False)  # 不太懂这个 materialize 啥意思ctx.constant = constant@staticmethoddef backward(ctx, grad_output):# Here we must handle None grad_output tensor. In this case we# can skip unnecessary computations and just return None.if grad_output is None:return None, None# We return as many input gradients as there were arguments.# Gradients of non-Tensor arguments to forward must be None.return grad_output * ctx.constant, None**

虽然不太懂这个 materialize 是啥意思.

明白了 loss.backward()

也许只知道一句 loss.backward() 可以求梯度, 不知为何当 loss 不是标量时需要传入一个与 output 形状相同的张量? 传入之后究竟经历了什么?

import torchx = torch.randn(2, 3, requires_grad=True)
y = torch.norm(x, dim=1)  # 是个向量shape=(2)y.retain_grad()
grad = torch.randn(2)  # y 的 grad, 平时调用 loss.backward() 空参数, 其实是 loss.backward(torch.tensor(1.0)), 也即 loss 自己的 grad
y.backward(grad)  # 调用 backward 函数会执行其 grad_fn 的 backward, 沿着计算图链式地反向传播print(grad)
print(y.grad_fn)
print(y.grad)
print(x.grad)# %%
x = torch.randn(2, 3, requires_grad=True)
z = torch.norm(x)z.retain_grad()
grad = torch.tensor(1.0)
z.backward(grad)  # 其实是 loss.backward(torch.tensor(1.0))print(z.grad_fn)
print(z.grad)
print(x.grad)

传入 xxx.backward(grad_of_xxx) 的张量 grad_of_xxxxxx 自己的 grad, 需要它来进行链式法则的计算, 在 LinearFunction.backward 中输出 *grad_output 看一看:

	@staticmethoddef backward(ctx, *grad_output):  # save_for_backward, 所以 backward 还是需要 ctx 的, 不像 forwardprint(grad_output)  # 验证 .backward(grad)x, weight, bias = ctx.saved_tensorsgrad_input = grad_weight = grad_bias = None  # 先设置好 None, 那么不需要梯度的变量, 梯度就返回 Noneif ctx.needs_input_grad[0]:grad_input = grad_output[0].mm(weight)if ctx.needs_input_grad[1]:grad_weight = grad_output[0].t().mm(x)if bias is not None and ctx.needs_input_grad[2]:grad_bias = grad_output[0].sum(0)return grad_input, grad_weight, grad_bias

输出 *grad_output:

linear = LinearFunction.apply
a = torch.randn(2, 3)
w = torch.randn(4, 3, requires_grad=True)
b = torch.randn(4, requires_grad=True)ln = linear(a, w, b)
ln.backward(torch.ones(2, 4))
##################################
(tensor([[1., 1., 1., 1.],[1., 1., 1., 1.]]),)

小结
至于 LinearFunction.apply 具体是如何工作的, 源码比较多, 看不懂! 反正比直接调用 forward 多了些工作, 为反向传播做准备!

Function.apply 问答

新旧版的参数保存方式

假如我需要在 Function 中保存一个数值 gamma, 新旧版分别是如何做的?
旧版:

class F(torch.autograd.Function):def __init__(self, gamma=0.1):super().__init__()self.gamma = gammadef forward(self, args):passdef backward(self, args):pass#################################
F(gamma)(inp)

新版:

class F_new(torch.autograd.Function):@staticmethoddef forward(ctx, args, gamma):ctx.gamma = gammapass@staticmethoddef backward(ctx, args):pass####################################
F_new.apply(inp, gamma)
  • 问: 每次调用 F.apply, 都会创建新的 “instance” with its own context 吗?
    答: 对, 每次调用 .apply 都会有a different context. 所以你可以安全地保存 everything 到其中, 并无风险.

  • 问: 我可以用 ctx.intermediary = intermediary 语句保存 intermediary results 吗?
    答: 对于 intermediary results, 你可以将它们保存到 ctx 的属性中.

  • 问: 为什么需要用 save_for_backward? 仅仅是 a convention? 或者它执行了额外的 checks?
    尝试用 save_for_backwards 保存 intermediary tensors, 但 failed, 所以我将它们作为 attributes 保存到了 self (ctx now) 中.
    答: 是的, save_for_backward is just for input and outputs, 它会执行额外的 checks (make sure that you don’t create non-collectable cycles). For intermediary results, you can save them as attribute of the context yes. [记得说求梯度的变量一定要 是 input or output]

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/718426.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大语言模型LLM代码:PyTorch库与ChatGLM模型(LLM系列21)

文章目录 通过阅读大语言模型的代码,熟悉并理解PyTorch大语言模型LLM代码:PyTorch库与ChatGLM模型(LLM系列21)大语言模型中的PyTorchChatGLM3-6B模型代码ChatGLMModel类总览ChatGLMModel类说明ChatGLMModel类核心代码片段通过阅读大语言模型的代码,熟悉并理解PyTorch 大语…

基于yolov5的飞机蒙皮缺陷检测系统,可进行图像目标检测,也可进行视屏和摄像检测(pytorch框架)【python源码+UI界面+功能源码详解】

功能演示&#xff1a; 基于yolov5的飞机蒙皮缺陷检测系统&#xff0c;系统既能够实现图像检测&#xff0c;也可以进行视屏和摄像实时检测_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于yolov5的飞机蒙皮缺陷检测系统是在pytorch框架下实现的&#xff0c;这是一个完…

单元测试:参数匹配器和参数捕捉器

目录标题 第一章、参数匹配器1.1&#xff09;参数匹配器列表1.2&#xff09;参数匹配器示例①anyInt()②any(Class<> type)③eq()④same(expectedObject)⑤endsWith() 第二章、参数捕捉器2.1&#xff09;Captor2.2&#xff09;ArgumentCaptor类 第一章、参数匹配器 1.1&…

vue模板语法中的类和样式绑定

最近公司要求&#xff0c;在学习前端vue知识&#xff0c;记录一些语法要点 在绑定类和样式时&#xff0c;如果使用三目运算(三元运算)时&#xff0c;html中使用的是大括号[], 以及使用数组绑定多个class时&#xff0c;也是大括号&#xff0c; 其他的使用中括号{} 比如&…

什么是ABA问题及ABA问题的解决方法。

什么是ABA问题 ABA问题&#xff1a;一个线程thread1读到的数据是22&#xff0c;它对数据进行操作后变为23&#xff0c;又有一个线程thread2读到数据22&#xff0c;对它进行操作后数据变为33并更新回去&#xff0c;又来一个线程3读取数据33&#xff0c;更新数据为22后又更新回去…

js【详解】自动类型转换

运算符 Symbol 数字 会报错 Cannot convert a Symbol value to a number Symbol 字符串 会报错 Cannot convert a Symbol value to a string 存在对象&#xff0c;数组&#xff0c;函数时 对象&#xff0c;数组&#xff0c;函数会先执行其 toString() 方法&#xff0c;…

用CSS实现一个扇形

用CSS实现扇形的思路和三角形基本一致&#xff0c;就是多了一个圆角的样式&#xff0c;实现一个90的扇形&#xff1a; div{border: 100px solid transparent;width: 0;heigt: 0;border-radius: 100px;border-top-color: red; }

【c++】list模拟实现

list的接口 #pragma once #include<iostream> #include<assert.h> using namespace std; namespace zjw {template<class T>struct listnode {listnode* <T>_next;listnode* <T>_prev;T _data;listnode(const T& x T()):_prev(nulllptr…

高维中介数据: 联合显着性(JS)检验法

摘要 中介分析在流行病学和临床试验中越来越受到关注。在现有的中介分析方法中&#xff0c;流行的联合显着性&#xff08;JS&#xff09;检验会产生过于保守的 I 类错误率&#xff0c;因此功效较低。但是&#xff0c;如果在使用 JS 测试高维中介假设时&#xff0c;可以准确控制…

人力资源(E-HR)应用架构的设计与实践

当谈到人力资源管理时&#xff0c;电子人力资源&#xff08;E-HR&#xff09;系统已经成为现代企业不可或缺的组成部分。E-HR系统的设计与实践对于提高组织的人力资源管理效率和员工体验至关重要。本文将探讨E-HR应用架构的设计与实践&#xff0c;以及如何借助信息技术优化人力…

docker清理闲置镜像邮件发送

脚本名称 DockerImage_Clear.sh脚本内容 #!/bin/bash # 清除闲置的docker镜像 #docker image prune -a -f # 列出可清理的docker镜像 /usr/local/bin/docker images | grep "<none>" # 列出可清理的docker镜像数量 /usr/local/bin/docker images | grep …

怎么使用Pyecharts库对淘宝数据进行可视化展示

目录 一、准备工作 二、数据预处理 三、使用Pyecharts进行可视化展示 柱状图展示销量和评价数 散点图展示价格与销量关系 词云图展示商品标题关键词 四、总结与建议 在当今的大数据时代&#xff0c;数据可视化已经成为了一个非常重要的技能。Pyecharts是一个基于Python的…

NIO群聊系统的实现

一、前言 通过NIO编写简单版聊天室&#xff0c;客户端通过控制台输入发送消息到其他客户端。注意&#xff1a;并未处理粘包半包问题。 二、逻辑简述 服务器&#xff1a; 1&#xff09;创建服务器NIO通道&#xff0c;绑定端口并启动服务器 2&#xff09;开启非阻塞模式 3&…

C++ //练习 10.24 给定一个string,使用bind和check_size在一个int的vector中查找第一个大于string长度的值。

C Primer&#xff08;第5版&#xff09; 练习 10.24 练习 10.24 给定一个string&#xff0c;使用bind和check_size在一个int的vector中查找第一个大于string长度的值。。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /*****…

Altium Designer 22焊接辅助工具 Interactivehtmlbom 插件使用教程

目录 AD22 由Interactivehtmlbom 插件生成的焊接辅助图&#xff0c;交互方式很多&#xff0c;十分方便的为我们提供便利。 介绍安装教程&#xff1a; 前去这个网站 Interactivehtmlbom 插件&#xff0c;单击下载zip 下载完成后找个地方解压&#xff0c;双击Initablelize.bat文…

河道水库雨水情自动测报系统

随着科学技术的不断进步&#xff0c;以及城市化进程的影响&#xff0c;水库的管理变得更加复杂&#xff0c;要实现城市水库的精细化管理&#xff0c;必须借助先进的信息技术手段为支撑&#xff0c;实现对三防、水资源、供水安全的实时监控&#xff0c;优化管理模式和创新管理手…

餐饮废水处理设备定制厂家

诸城市鑫淼环保小编带大家了解一下餐饮废水处理设备定制厂家 1.餐饮废水问题 餐饮业是重要的经济领域&#xff0c;但其废水排放带来的环境问题不容忽视。餐饮废水含有高浓度的有机物、油脂、残渣等&#xff0c;若未经处理直接排放&#xff0c;会严重污染水源&#xff0c;危害环…

Matlab 机器人工具箱 例程:运动学+动力学+路径规划+可视化

文章目录 1 创建机器人2 机器人显示3 机器人示教4 机器人路径规划&#xff1a;给定关节角路径5 机器人路径规划&#xff1a;给定末端位姿&#xff0c;求关节角路径6 工作空间可视化参考链接 1 创建机器人 clc;clear;close all; deg pi/180;L1 Revolute(d, 0, a, 0, alpha, 0,…

【Python】快速入门Python一天学完基础语法

文章目录 前言1. HelloWorld2. 变量与数据类型2.1 变量2.2 数据类型2.2.1 String 字符串类型2.2.2 基本类型转换2.2.2 元组2.2.3 字典2.2.4 拆包 2.3 运算2.3.1 双除号/双乘号2.3.2 常见运算函数举例2.3.3 布尔运算 3. 控制流程3.1 if-else 语句3.2 while 循环3.3 for 循环 4. …

子线程如何获取Request

子线程获取Request 有时候在进行业务处理时对于一些对于业务不那么重要且对于返回结果无关的情况会开启一个新的线程进行处理&#xff0c;但是在开启新线程进行处理时发现无法从RequestContextHolder中获取到当前的请求&#xff0c;取出来是null 这是因为RequestContextHolder中…