tensor判断是否相等_PyTorch的Tensor(中)

bf1b3dad2ae9992486c5a58414723598.png

背景

在PyTorch的Tensor系列上一篇文章中:

Gemfield:PyTorch的Tensor(上)​zhuanlan.zhihu.com
9c282381b1be088e399caada4e6c14a1.png

Gemfield介绍了一个Tensor的创建过程,特别是在创建一个Tensor的时候,调用栈从Python到C++再回到Python的过程。与此同时,在内存中对应的是一个Variable实例的创建(严格来说,Variable实例的某个field也是Variable实例)。

在本文,Gemfield将介绍PyTorch的Tensor中autograd相关的部分。autograd是PyTorch之所以是神经网络框架的一个重要原因。autograd机制提供了对Tensor上所有操作自动求微分的功能。我们知道,对于一个Variable来说,它的唯一数据成员就是impl_,这个impl_成员是TensorImpl 类型,在初始化阶段impl_会被实例化为一个Variable::Impl的实例(TensorImpl的子类):

Variable --> impl_ = Variable::Impl实例

对于一个Variable的autograd来说,autograd的部分就体现在impl_的autograd_meta_成员上。在初始化阶段,autograd_meta_会被初始化为一个Variable::AutogradMeta的实例(AutogradMetaInterface的子类),或者会被初始化为一个Variable::DifferentiableViewMeta的实例(Variable::AutogradMeta的子类),然后通过Variable的 get_autograd_meta()来访问。实际上,autograd_meta_正是一个Variable是普通tensor还是带autograd功能的tensor的唯一标识:

#1 Variable是个Tensor,没有requires_grad
Variable --> impl_ --> autograd_meta_ = None#2
Variable --> impl_ --> autograd_meta_ = Variable::AutogradMeta实例#3
Variable --> impl_ --> autograd_meta_ = Variable::DifferentiableViewMeta实例

而一个Variable::AutogradMeta实例有如下成员,这些成员正是PyTorch autograd系统的中坚:

# Variable::AutogradMeta 和 Variable::DifferentiableViewMeta
Variable grad_;
std::shared_ptr<Function> grad_fn_;
std::weak_ptr<Function> grad_accumulator_;
VariableVersion version_counter_;
std::vector<std::shared_ptr<FunctionPreHook>> hooks_;
bool requires_grad_;
bool is_view_;
uint32_t output_nr_;# 仅 Variable::DifferentiableViewMeta
Variable base_;
uint32_t attr_version;
  • 1,grad_是另外一个Variable,存储当前Variable实例的梯度;
  • 2,grad_fn是个Function的实例,非leaf variables才有。通过Variable的grad_fn()来访问,实际上,PyTorch中就是通过是否grad_fn_ == nullptr来判断一个Variable是否是leaf variable的;
  • 3,grad_accumulator_是个Function的实例,只有leaf variables才有。通过Variable的grad_accumulator()来访问;
  • 4,version_counter_里有个version number;
  • 5,hooks_可以是一组;
  • 6,requires_grad_ 是个flag,表明此Variable实例是否需要grad;
  • 7,is_view_是个flag,表明此Variable实例是否是个view(没有实际存储,基于base的variable);
  • 8,output_nr_是个数字;
  • 9,base_是view的base variable;
  • 10,attr_version是个数字。

我们通过下面这一小段代码来演示下这个能力:

gemfield = torch.ones(2, 2, requires_grad=True)
syszux = gemfield + 2
civilnet = syszux * syszux * 3
gemfieldout = civilnet.mean()
gemfieldout.backward()

特别的,对于在python会话中的每一步操作,gemfield都将映射到内存上类实例中的成员/结构体的变化。

Tensor创建:gemfield = torch.ones(2, 2, requires_grad=True)

我们使用gemfield = torch.ones(2, 2, requires_grad=True) 语句来创建了一个tensor。在https://zhuanlan.zhihu.com/p/54896021一文中已经介绍过了,这个调用会在内存中产生如下一个Variable实例:

#gemfieldVariable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[1., 1.],[1., 1.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= None--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = True--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

这个gemfield变量就是图中的leaf,为什么呢?因为这是用户直接创建的(不是经过计算得到的),位于图中最“底端/外侧”的位置,没有子节点。这个时候,Tensor gemfield的grad是None,grad_fn是None。output_nr_为0,表明这个Variable是function的第1个输出。

Tensor的简单加法:syszux = gemfield + 2

我们使用 syszux = gemfield + 2 来得到一个新的Tensor,名字为syszux。这个加法嘛,在初始化的时候已经和C++中的THPVariable_add函数绑定上,并注册到Python的torch._C._TensorBase符号上了:

PyMethodDef variable_methods[] = {{"__add__", (PyCFunction)THPVariable_add, METH_VARARGS | METH_KEYWORDS, NULL},......

而THPVariable_add的定义如下:

static PyObject * THPVariable_add(PyObject* self_, PyObject* args, PyObject* kwargs)
{......return wrap(dispatch_add(self, r.tensor(0), r.scalar(1)));
}

1,scalar to tensor

在这个函数中,首先要将syszux = gemfield + 2 中的2从标量转换为tensor,这个转换逻辑如下:

auto tensor = scalar_to_tensor(scalar);
tensor.unsafeGetTensorImpl()->set_wrapped_number(true);
return autograd::make_variable(tensor);

现在scalar 2已经变成了内存中一个Variable的实例,在add真正执行之前,在内存中已经有2个Variable实例了,分别是gemfield和2:

#gemfield
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[1., 1.],[1., 1.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= None--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = True--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist#scalar 2
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [2.]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= None--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = False--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

不过要注意了,由于gemfield是一个leaf variable,因此在后文的加法运算中,gemfield会被触发Variable::grad_accumulator()调用,这会初始化gemfield的grad_accumulator_成员,因此在那之后,gemfield在内存中的样子就会变为:

#gemfield
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[1., 1.],[1., 1.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= None--> grad_accumulator_ (Function实例)= AccumulateGrad实例0x55ca7f304500--> ......

2,dispatch to add

既然是分发,肯定是为了分发到对应种类的Type上。分发的核心逻辑是:

dispatch_type().add(*this, other, alpha)

dispatch_type是at::Tensor类上的一个方法,根据其impl(TensorImpl类)的id、dtype等信息,推导出Type类型(VariableType)。在这个例子上,根据输入参数的类型,最终分发到torch/csrc/autograd/generated/VariableType.cpp(参考autograd的代码生成)中的VariableType的add方法上:

Tensor VariableType::add(const Tensor & self, const Tensor & other, Scalar alpha) const {std::shared_ptr<AddBackward0> grad_fn;grad_fn = std::shared_ptr<AddBackward0>(new AddBackward0(), deleteFunction);grad_fn->set_next_edges(collect_next_edges( self, other ));grad_fn->alpha = alpha;auto tmp = ([&]() {at::AutoNonVariableTypeMode non_var_type_mode(true);return baseType->add(self_, other_, alpha);})();auto result = as_variable(tmp);set_history(flatten_tensor_args( result ), grad_fn);return result;
}

匿名函数中的baseType->add最终又调用了如下的调用栈,只能说一个简单的加法在graph设计中也会变得比较复杂:

VariableType::add
|
V
baseType->add(self_, other_, alpha)
|
V
#ATen/TypeDefault.cpp
TypeDefault::add
|
V
#aten/src/ATen/native/BinaryOps.cpp
add(const Tensor& self, const Tensor& other, Scalar alpha)
|
V
#此处依赖初始化阶段的REGISTER_DISPATCH的工作
add_stub
|
V
#aten/src/ATen/native/cpu/BinaryOpsKernel.cpp.AVX2.cpp
add_kernel
|
V
#aten/src/ATen/native/cpu/BinaryOpsKernel.cpp.AVX2.cpp
binary_kernel_vec
|
V
binary_loop

3,构建autograd

3.1 构建grad_fn (AddBackward0)

AddBackward0是加法运算的反向传播算法,构建这个grad_fn实例是通过如下一行代码完成的:

std::shared_ptr<AddBackward0> grad_fn = std::shared_ptr<AddBackward0>(new AddBackward0(), deleteFunction);

可以看到,对于加法,对应的grad_fn是AddBackward0。在聊AddBackward0之前,你有必要先了解下PyTorch中的Function继承体系(可以参考 https://zhuanlan.zhihu.com/p/61765561 ),还记得吧:

class AddBackward0 : public TraceableFunction
class TraceableFunction : public Function

gemfield + 2 加法运算完成后,就会创建出来这个AddBackward0(一个Function)实例,并且使用collect_next_edges()搜集gemfield和2的grad_fn或者grad_accumulator。gemfield是leaf variable,因此搜集的就是grad_accumulator(2啥都没有),类型是AccumulateGrad(1个Function的实例),然后再向这个AddBackward0实例上注册:

grad_fn->set_next_edges(collect_next_edges( self, other ));

next edges就是self和other的gradient_edge():gradient_edge()函数返回的是Edge实例(通过Variable的grad_fn_构建)。当set_next_edges调用完成后,一个Function的next_edges_成员(类型为std::vector<Edge>)就会被初始化。

1,如果一个Variable是内部创建的(通过运算得到,比如syszux变量),那么grad_fn_就是这个Variable的gradient function;

2,如果一个Variable是用户创建的(比如gemfield变量),则grad_fn_就是这个Variable的gradient accumulator,也就是一个AccumulateGrad类(Function子类)的实例。

但不管怎样,Variable的grad_fn_成员在这里终归是要构建成一个Edge实例并以此作为gradient_edge()函数的返回:

Edge gradient_edge() const {if (const auto& gradient = grad_fn()) {return Edge(gradient, output_nr());} else {return Edge(grad_accumulator(), 0);}
}

对于leaf Variable来说,grad_fn是null,在这种情况下Variable将使用gradient accumulator, 用来累加输出给这个Variable的梯度。注意只有当Variable的requires_grad = True时才有gradient accumulators。经过构建后的AddBackward0的内存布局如下所示:

#grad_fn AddBackward0, requires_grad == True
Function实例 --> sequence_nr_ (uint64_t) = 0--> next_edges_ (edge_list) --> std::vector<Edge> = [(AccumulateGrad实例0x55ca7f304500, 0),(0, 0)]--> input_metadata_ --> [(type, shape, device)...] = None--> alpha (Scalar) = 1--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 AddBackward0 的apply#grad_fn,requires_grad == False
Function实例 --> None

3.2 构建grad_accumulator_ (AccumulateGrad)

前文已经提到过,在grad_fn调用collect_next_edges去搜集输入参数(Variable实例)的edges时,对于leaf gemfield来说,会触发Variable::grad_accumulator()调用,在一个Variable第一次调用这个API的时候,会去初始化它的grad_accumulator_成员:

result = std::make_shared<AccumulateGrad>(Variable(std::move(intrusive_from_this)));
autograd_meta->grad_accumulator_ = result;

这会new一个AccumulateGrad对象,使用UINT64_MAX(也就是18446744073709551615)来初始化Function的sequence_nr_成员。构建完成后,grad_accumulator_(0x55ca7f304500)在内存中看起来是这个样子的:

# 类AccumulateGrad,继承自Function,是一个Function实例
Function实例 --> sequence_nr_ (uint64_t) = UINT64_MAX--> next_edges_ (edge_list) --> None--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2], cpu)]--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 AccumulateGrad 的apply--> variable = gemfield

初始化完毕后,此时gemfield这个Variable的grad_accumulator_已经被赋值为AccumulateGrad实例(0x55ca7f304500)。

4 构建Variable(syszux)

在加法表达式完成之后,内存中也就产生了syszux。刚产生的syszux实例在内存中看起来是这样的:

//syszux
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[3., 3.],[3., 3.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= None--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = False--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

之后会使用set_history(flatten_tensor_args( result ), grad_fn)来设置syszux的gradient_edge,这个函数做了2件事情:

1,AddBackward0实例中的input_metadata_ 追加了Variable syszux的(type, shape, device),追加完成后,syszux信息在input_metadata_中的index就是下文中会用到的output_nr_,此处为0(第一个元素嘛);这样AddBackward0实例在内存中看起来是这样:

//grad_fn->add_input_metadata(variable);
grad_fn_ --> input_metadata_ += (variable.type, variable.shape, variable.device) = []#AddBackward0实例, requires_grad == True
Function实例 --> sequence_nr_ (uint64_t) = 0--> next_edges_ (edge_list) --> std::vector<Edge> = [(AccumulateGrad实例, 0),(0, 0)]--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2],cpu])]--> alpha (Scalar) = 1--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 AddBackward0 的apply

2,syszux的autograd_meta中的grad_fn_ 被赋值成了上述AddBackward0实例,而autograd_meta中的output_nr_被赋值成了上文中的“当前Variable信息在input_metadata_中的index”。这样syszux实例在内存中看起来就是这样:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[3., 3.],[3., 3.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= AddBackward0实例0x55ca7f872e90(参考上面)--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = False--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

完成这一步后,如果你在python会话中打印syszux的grad_fn属性,就会调用THPVariable_get_grad_fn函数,接着调用Variable的grad_fn()函数拿到grad_fn_,然后使用 functionToPyObject()函数从cpp_function_types表中查找对应的Python表示:

>>> syszux.grad_fn
<AddBackward object at 0x7f1550f2e0d0>

civilnet = syszux * syszux * 3

现在进入civilnet = syszux * syszux * 3这个表达式的解析了,这个表达式执行期间,就会产生两次python object的__mul__(乘法)调用,并且会new一个新的Variable civilnet出来。

1,Python到C++

在初始化阶段,Variable的python绑定已经完成了下面的初始化:

PyMethodDef variable_methods[] = {{"__add__", (PyCFunction)THPVariable_add, METH_VARARGS | METH_KEYWORDS, NULL},{"__mul__", (PyCFunction)THPVariable_mul, METH_VARARGS | METH_KEYWORDS, NULL},......
}

因此这个表达式会导致对C++函数THPVariable_mul的调用,并且是2次:两次乘法。

2,dispatch to mul

和加法类似,这个乘法的调用栈如下所示(其中的一次):

#torch/csrc/autograd/generated/python_variable_methods.cpp
THPVariable_mul
|
V
#torch/csrc/autograd/generated/python_variable_methods_dispatch.h
dispatch_mul
|
V
#aten/src/ATen/core/TensorMethods.h
Tensor::mul
|
V
#需要dispatch type 了
#torch/csrc/autograd/generated/VariableType_4.cpp
Tensor VariableType::mul(const Tensor & self, const Tensor & other)
|
V
#运算符太简单了,扔回到base的default实现
#build/aten/src/ATen/TypeDefault.cpp
Tensor TypeDefault::mul(const Tensor & self, const Tensor & other)
|
V
#aten/src/ATen/native/BinaryOps.cpp
Tensor mul(const Tensor& self, const Tensor& other)
|
V
#aten/src/ATen/native/TensorIterator.cpp
TensorIterator::binary_op
|
V
#此处依赖初始化阶段的REGISTER_DISPATCH的工作
#build/aten/src/ATen/native/cpu/BinaryOpsKernel.cpp.AVX2.cpp(如果使用的是CPU版的pytorch,并且cpu支持AVX2)
void mul_kernel(TensorIterator& iter)
|
V
#aten/src/ATen/native/cpu/Loops.h
void binary_kernel_vec(TensorIterator& iter, func_t op, vec_func_t vop)

如果使用的是CPU版的PyTorch,那么这里的乘法运算最终分发到cpu的native实现上了。

3,构建Autograd信息

当乘法运算分发到VariableType::mul的时候,PyTorch将会在这个函数中构建autograd的信息。

Tensor VariableType::mul(const Tensor & self, const Tensor & other) const {std::shared_ptr<MulBackward0> grad_fn;grad_fn = std::shared_ptr<MulBackward0>(new MulBackward0(), deleteFunction);grad_fn->set_next_edges(collect_next_edges( self, other ));if (grad_fn->should_compute_output(1)) {grad_fn->self_ = SavedVariable(self, false);}if (grad_fn->should_compute_output(0)) {grad_fn->other_ = SavedVariable(other, false);}auto tmp = ([&]() {at::AutoNonVariableTypeMode non_var_type_mode(true);return baseType->mul(self_, other_);})();auto result = as_variable(tmp);set_history(flatten_tensor_args( result ), grad_fn);return result;
}

3.1 构建grad_fn (MulBackward0)

经过表达式 grad_fn = std::shared_ptr<MulBackward0>(new MulBackward0(), deleteFunction)后,一个Function的实例就产生了,类型为MulBackward0(0x55ca7ebba2a0)。

struct MulBackward0 : public TraceableFunction {variable_list apply(variable_list&& grads) override;SavedVariable self_;SavedVariable other_;
};

和AddBackward0不同的是,MulBackward0还有两个SavedVariable成员。比如使用syszux初始化SavedVariable self_的时候,进行了以下的拷贝:

#从syszux 拷贝到 SavedVariable self_
variable.output_nr() --> output_nr_
variable.requires_grad() --> requires_grad_
variable.data() --> data_
variable.grad_fn() --> grad_fn_
variable.version_counter() --> version_counter_
version_counter_.current_version() --> saved_version_

在内存中,这个MulBackward0实例的布局如下所示:

#grad_fn MulBackward0, requires_grad == True
Function实例 --> sequence_nr_ (uint64_t) = 1 (每个线程内自增)--> next_edges_ (edge_list) --> std::vector<Edge> = None--> input_metadata_ --> [(type, shape, device)...] = None--> self_ (SavedVariable) = syszux的浅拷贝--> other_ (SavedVariable) = syszux的另一个浅拷贝--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 MulBackward0 的apply

3.2,初始化MulBackward0的next_edges_

前文已经提到过,在grad_fn调用collect_next_edges去搜集输入参数(Variable实例,此处为syszux)的edges时,对于“非leaf”的syszux来说,会触发Variable::grad_fn()调用,这会得到syszux的grad_fn,也就是AddBackward0实例。使用两个syszux的grad_fn组成的edges初始化完MulBackward0实例后,MulBackward0在内存中看起来是这个样子:

#grad_fn MulBackward0, requires_grad == True,0x55ca7ebba2a0
Function实例 --> sequence_nr_ (uint64_t) = 1(每个线程内自增)--> next_edges_ (edge_list)  = [(AddBackward0实例0x55ca7f872e90,0),(AddBackward0实例0x55ca7f872e90,0)]--> input_metadata_ --> [(type, shape, device)...] = None--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 AccumulateGrad 的apply

初始化完毕后,此时MulBackward0这个Function(0x55ca7ebba2a0)的next_edges_已经被赋值为syszux中的grad_fn组成的edges。

4 构建Variable(tmp)

在第一次乘法表达式完成之后,内存中也就产生了临时Variable tmp。刚产生的tmp实例在内存中看起来是这样的:

//tmp
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[9., 9.],[9., 9.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= None--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = False--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

之后会使用set_history(flatten_tensor_args( result ), grad_fn)来设置tmp的gradient_edge,这个函数做了2件事情:

1,MulBackward0实例中的input_metadata_ 追加了Variable syszux的(type, shape, device),追加完成后,syszux信息在input_metadata_中的index就是下文中会用到的output_nr_,此处为0(第一个元素嘛);这样MulBackward0实例在内存中看起来是这样:

//grad_fn->add_input_metadata(variable);
grad_fn_ --> input_metadata_ += (variable.type, variable.shape, variable.device) = []#MulBackward0实例, requires_grad == True
Function实例 --> sequence_nr_ (uint64_t) = 1--> next_edges_ (edge_list) = [(AddBackward0实例0x55ca7f872e90,0),(AddBackward0实例0x55ca7f872e90,0)]--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2],cpu])]--> alpha (Scalar) = 1--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 MulBackward0 的apply

2,Variable tmp的autograd_meta中的grad_fn_ 被赋值成了上述MulBackward0实例,而autograd_meta中的output_nr_被赋值成了上文中的“当前Variable信息在input_metadata_中的index”。这样tmp实例在内存中看起来就是这样:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[9., 9.],[9., 9.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= MulBackward0实例0x55ca7ebba2a0(参考上面)--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = False--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

5,第二次乘法

syszux * syszux后得到的tmp还要继续进行tmp * 3的运算,经过前面的一次加法(gemfield + 2)和一次乘法(syszux * syszux),我想我们目前已经能总结出规律了。每一次这样的运算,会经历以下的步骤:

  • 1,加法/乘法的调用栈,最终派发到某种device的实现上,如果运算输入是个scalar,进行scalar到Variable的构建;
  • 2,派发到VariableType上时,会顺便进行autograd信息的构建;
  • 2.1,构建一个加法/乘法的反向计算函数实例(比如AddBackward0,MulBackward0);
  • 2.2,初始化反向计算函数实例的next_edges_和其它相关成员,next_edges_成员的值来自前向时候的输入参数,如果输入Variable是leaf的话,则next_edges_来自输入Variable的grad_accumulator_;如果是非leaf的话,则来自Variable的grad_fn_;
  • 2.3,使用步骤3中的Variable实例来初始化反向计算函数实例的input_metadata_,
  • 3,运算后得到新的Variable,使用Variable::Impl进行构建,使用步骤2中的反向计算函数实例初始化该Variable实例的grad_fn_成员。

对于civilnet = tmp * 3的运算(civilnet = syszux * syszux * 3的第二步),上述步骤就是:

1,THPVariable_mul的分发,不再赘述;其间要使用scalar 3构建一个Variable;

2,在调用栈到达VariableType::mul的时候,构建又一个MulBackward0实例(0x55ca7fada2f0),并初始化其next_edges_成员:

#grad_fn MulBackward0, requires_grad == True,0x55ca7fada2f0
Function实例 --> sequence_nr_ (uint64_t) = 2 (每个线程内自增)--> next_edges_ (edge_list) = [(MulBackward0实例0x55ca7ebba2a0,0),(0,0)]--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType, [2, 2],cpu])]--> self_ (SavedVariable) = tmp的浅拷贝--> other_ (SavedVariable) = 3的浅拷贝--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 MulBackward0 的apply

注意sequence_nr_又自增了1。

3,构建Variable civilnet:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = [[27., 27.],[27., 27.]]--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= MulBackward0实例0x55ca7fada2f0(参考上面)--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = False--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

gemfieldout = civilnet.mean()

步骤其实类似了:

1,python调用到C++的THPVariable_mean的调用:

static PyObject * THPVariable_mean(PyObject* self_, PyObject* args, PyObject* kwargs)
{......return wrap(dispatch_mean(self));
}

调用栈如下:

#torch/csrc/autograd/generated/python_variable_methods.cpp
static PyObject * THPVariable_mean(PyObject* self_, PyObject* args, PyObject* kwargs)
|
V
#aten/src/ATen/core/TensorMethods.h
Tensor Tensor::mean()
|
V
#torch/csrc/autograd/generated/VariableType_2.cpp
Tensor VariableType::mean(const Tensor & self)
|
V
#build/aten/src/ATen/TypeDefault.cpp
Tensor TypeDefault::mean(const Tensor & self)
|
V
#aten/src/ATen/native/ReduceOps.cpp
Tensor mean(const Tensor &self)
|
V
#aten/src/ATen/native/ReduceOps.cpp
static inline Tensor mean(const Tensor &self, optional<ScalarType> dtype)
|
V
#aten/src/ATen/native/ReduceOps.cpp
static inline Tensor mean(const Tensor &self, IntArrayRef dim, bool keepdim, optional<ScalarType> dtype)
|
V
#aten/src/ATen/native/ReduceOps.cpp
Tensor &mean_out(Tensor &result, const Tensor &self, IntArrayRef dim,bool keepdim, optional<ScalarType> opt_dtype)
|
V
at::sum_out(result, self, dim, keepdim, dtype).div_(dim_prod);
|
V
#build/aten/src/ATen/TypeDefault.cpp
Tensor & TypeDefault::sum_out(Tensor & out, const Tensor & self, IntArrayRef dim, bool keepdim, ScalarType dtype)
|
V
#aten/src/ATen/native/ReduceOps.cpp
Tensor& sum_out(Tensor& result, const Tensor& self, IntArrayRef dim, bool keepdim, ScalarType dtype)
|
V
#aten/src/ATen/native/ReduceOps.cpp
Tensor& sum_out(Tensor& result, const Tensor& self, IntArrayRef dim,bool keepdim, optional<ScalarType> opt_dtype)
|
V
sum_stub ,依赖初始化阶段的REGISTER_DISPATCH的工作

2,构建autograd

在调用栈到达 VariableType::mean时,开始顺便构建autograd的信息。主要是构建一个op的反向计算函数实例:MeanBackward0实例(0x55ca7eb358b0)。MeanBackward0类型定义如下:

struct MeanBackward0 : public TraceableFunction {variable_list apply(variable_list&& grads) override;std::vector<int64_t> self_sizes;int64_t self_numel = 0;
};

多了self_sizes和self_numel成员。构建完成后,MeanBackward0实例在内存中看起来如下所示:

#grad_fn MeanBackward0, requires_grad == True,0x55ca7eb358b0
Function实例 --> sequence_nr_ (uint64_t) = 3 (每个线程内自增)--> next_edges_ (edge_list) = [(MulBackward0实例0x55ca7fada2f0,0)]--> input_metadata_ --> [(type, shape, device)...] = [(CPUFloatType|[]|cpu])]--> self_sizes (std::vector<int64_t>) = (2, 2)--> self_numel = 4--> pre_hooks_ = None--> post_hooks_ = None--> anomaly_metadata_ = None--> apply() --> 使用 MulBackward0 的apply

注意sequence_nr_的值又自增了1,另外就是input_metadata_中的shape为空。

3,构建Variable gemfieldout

gemfieldout在内存中的布局如下所示:

//as_variable_ref(variable).set_gradient_edge({grad_fn, output_nr});
Variable实例 --> Variable::Imple实例 --> tensor data   --> TensorImpl实例 --> Storage实例 = (27,)--> autograd_meta --> grad_ (又一个Variable实例) = None--> grad_fn_ (Function实例)= MeanBackward0实例0x55ca7eb358b0(参考上面)--> grad_accumulator_ (Function实例)= None--> version_counter_ = 0--> hooks_ len = 0--> requires_grad_ = False--> is_view_ = false--> output_nr_ = 0--> base_ = Not exist

总结

本文《PyTorch的Tensor(中)》主要介绍了Tensor的autograd部分,具体来说,就是在前向运算中,autograd的信息是如何构建和联系在一起的:

  • 1,op的调用栈,最终派发到某种device的实现上,如果运算输入是个scalar,进行scalar到Variable的构建;
  • 2,派发到VariableType上时,会顺便进行autograd信息的构建;
  • 2.1,构建一个op的反向计算函数实例(比如AddBackward0,MulBackward0);
  • 2.2,初始化反向计算函数实例的next_edges_和其它相关成员,next_edges_成员的值来自前向时候的输入参数,如果输入Variable是leaf的话,则next_edges_来自输入Variable的grad_accumulator_;如果是非leaf的话,则来自Variable的grad_fn_;
  • 2.3,使用步骤3中的Variable实例来初始化反向计算函数实例的input_metadata_,
  • 3,运算后得到新的Variable,使用Variable::Impl进行构建,使用步骤2中的反向计算函数实例初始化该Variable实例的grad_fn_成员。

而在下一篇文章《PyTorch的Tensor(下)》中,将主要介绍在backward的时候,Tensor中的autograd部分是怎么运行的。不像这篇文章中的Variable实例,那个时候其grad_成员将不再是None了。

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

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

相关文章

c语言字符串未初始化strcat,C语言中字符串常用函数strcat与strcpy的用法介绍

strcpy原型声明&#xff1a;extern char *strcpy(char* dest, const char *src);头文件&#xff1a;#include 功能&#xff1a;把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间说明&#xff1a;src和dest所指内存区域不可以重叠且dest必须有足够的空间来容…

charles抓包工具使用指南

前言移动APP抓包PC端抓包查看模式其他功能问题汇总1、 前言&#xff1a; Charles是一款抓包修改工具&#xff0c;相比起burp&#xff0c;charles具有界面简单直观&#xff0c;易于上手&#xff0c;数据请求控制容易&#xff0c;修改简单&#xff0c;抓取数据的开始暂停方便等等…

android电视打印信息解析,关于液晶电视打印信息

原来是波特率的原因。。换了个波特率显示正常了&#xff0c;下面这个是红灯亮不开机的酷开k2打印信息&#xff0c;有老师能解释一下怎么看吗JHello world!{m3a} svn-r(Apr 2 2015 19:53:30)init_hwsetting_cpre_hwsetting_cpost_hwsetting_cparse hw setting DONE!internal ro…

bzoj1269 文本编辑器 splay

直接搞棵splay就行了&#xff0c;不要把光标弄到树中而是把光标当成询问或操作区间的端点标志这样会简单很多。 7点40分写到9点20分&#xff0c;包括调试总共花了一个小时40分钟&#xff0c;这次是自己独立调出来的&#xff0c;总算对splay有一定的了解。 设计操作&#xff1a;…

sap bom递归_SAP软件之化工行业特点01

作者&#xff1a;阿long声明&#xff1a;本文章仅用于SAP软件的应用、学习沟通&#xff0c;不代表SAP公司&#xff1b;文中所示截图来源SAP软件&#xff0c;相应著作权归SAP所有。SPA中关于制造的几种介绍2020.06.29简单的说&#xff0c;制造业中&#xff0c;生产管理是企业的组…

android 对话框 图片,android – AlertDialog按钮的图像

由于不推荐使用onPrepareDialog,因此您可以使用onShowListener.你也应该设置Drawable边界,或者它将被放置在最左边.下面的代码输出public class MyDialog extends DialogFragment {Overridepublic Dialog onCreateDialog(Bundle savedInstanceState) {final AlertDialog dialog…

并发事务正确性的准则 可串行化_从0到1理解数据库事务(上):并发问题与隔离级别...

最近准备写一篇关于Spanner事务的分享&#xff0c;所以先分享一些基础知识&#xff0c;涉及ACID、隔离级别、MVCC、锁&#xff0c;由于太长&#xff0c;只好拆分成上下两篇&#xff1a;上&#xff1a;并发问题与隔离级别主要讲事务所要解决的问题、思路&#xff0c;先理解为什么…

Windows 8.1 Windows Phone 开发环境安装遇到的问题

以前使用 Windows 8 开装 WP 8 的开发环境&#xff0c;很是顺利。 将系统升级到 Windows 8.1 后&#xff0c;先是安装了 Windows 8.1 旗舰本&#xff0c;安装后发现没有 Hyper-V 功能。 换成企业版&#xff0c;确定有 Hyper-V 功能后&#xff0c;安装 VS2010 WP8 SDK 后&#x…

android eclipse services,Eclipse中调试Android Framework本地代码模块部分过程记录

环境&#xff1a;Amlogic S905M2 ubunt12.04 win7 adt_bundle_linux Android SDK过程&#xff1a;1.为避免用Eclipse基础版本(Luna)后需要安装包括adt、cdt等插件麻烦&#xff0c;直接解压adt_bundle_linux安装IDE环境2.调试准备&#xff1a;目标设备(Amlogic S905M2)中需要…

python数独代码_python 实现计算数独

输入文件格式&#xff1a;008309100900060004007504800036000540001000600042000970005907300600010008004608200输出结果&#xff1a;yuan********************0 0 8 3 0 9 1 0 09 0 0 0 6 0 0 0 40 0 7 5 0 4 8 0 00 3 6 0 0 0 5 4 00 0 1 0 0 0 6 0 00 4 2 0 0 0 9 7 00 0 5…

MyEclipse配色字体等配置的解决方案

Myeclipse黑色配色方案&#xff08;精心修改版&#xff09; http://download.csdn.net/detail/rehongchen/6579945 如何改变Myeclipse编辑区背景色 http://my.oschina.net/epiclight/blog/465506 -------------------------------------------------------------------------…

android studio内置终端,Android Studio Terminal xx不是内部或者外部命令

背景在使用AS工具自带命令面板Terminal的时候&#xff0c;相信你有碰到过&#xff1a;xx不是内部或者外部命令&#xff0c;也不是可运行程序。解决办法就是配置环境变量一、在 Terminal 中使用 adb&#xff0c;报 adb 不是内部或者外部命令&#xff0c;也不是可运行的程序。解决…

python pp模块_python常用模块

1、re模块re模块用于对python的正则表达式的操作1.1 什么是正则正则就是用一些具有特殊含义的符号组合到一起(称为正则表达式)来描述字符或者字符串的方法。或者说&#xff1a;正则就是用来描述一类事物的规则。(在Python中)它内嵌在Python中&#xff0c;并通过 re 模块实现。正…

[Leetcode]@python 90. Subsets II.py

题目链接 https://leetcode.com/problems/subsets-ii/ 题目原文 Given a collection of integers that might contain duplicates, nums, return all possible subsets. Note: Elements in a subset must be in non-descending order. The solution set must not contain dupli…

android glu,我可以使用glu与Android NDK

Does OpenGL ES not have glu?OpenGL ES 1.1&#xff1a;#include #include OpenGL ES 2.0&#xff1a;#include #include Is there a port I can use?是的&#xff0c;Android的部分端口为GLU – GLU ES(支持gluErrorString&#xff0c;gluPerspective和许多其他功能)&#…

线程间通信 GET POST

线程间通信有三种方法&#xff1a;NSThread GCD NSOperation 进程&#xff1a;操作系统里面每一个app就是一个进程、 一个进程里面可以包含多个线程&#xff0c;并且我们每一个app里面有且仅有一条主线程。scrollViewDelegate 的方法 &#xff1a; &#xff08;UIView *&…

android 恢复app 到前台,android手机把app disable了,怎么恢复

程序源代码如下:main(){int i,j,k;printf("\n");for(i1;i<5;i)    &#xff0f;*以下为三重循环*/for(j1;j<5;j)for (k1;k<5;k){if (i!k&&i!j&&j!k)    /*确保i、j、k三位互不相同*/printf("%d,%d,%d\n",i,j,k);}}main(){…

python mount回调函数_为python回调函数设置argtype

我对Python很在行&#xff0c;所以希望我能正确地表达这个问题。在整个问题涉及从Python调用C例程。我可以通过把一些相关的问题/答案凑在一起来接近&#xff0c;但我似乎不能把事情安排得很好。有两个方面&#xff1a;第一个是用指针调用C例程&#xff0c;第二个是使用回调函数…

参考 备份

原链接http://help.jd.com/Vender/question-1004.html 京东开放平台招商基础资质标准 一、基础资质标准&#xff1a; 注&#xff1a;商家可以选择京东质检平台中的第三方机构检测服务&#xff0c;申请地址&#xff1a;http://fw.jd.com/ser/list.action?page1&cid400 店铺…

android透明像素效率,android-非透明像素上的ImageView ColorFilter.夹

我有一个带位图的ImageView.该位图具有Alpha通道和透明像素.当我尝试将ColorFiter与Mode.OVERLAY(由于蜂窝)一起使用时-提供的颜色覆盖了整个imageview(整个矩形),但是我只想覆盖非透明像素.我如何裁剪imageview的画布以在需要的地方执行过滤器&#xff1f;更新我在png中有灰色…