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,一经查实,立即删除!

相关文章

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;生产管理是企业的组…

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

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

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)中需要…

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

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

svm预测结果为同一个值_SVM算法总结

一、1.感知机模型&#xff1a;感知器模型是SVM、神经网络、深度学习等算法的基础;感知器模型就是试图找到一条直线&#xff0c;能够把所有的“1”类和“-1”类分隔开&#xff0c;如果是高维空间中&#xff0c;感知器模型寻找的就是一个超平面&#xff0c;能够把所有的二元类别分…

iOS 动画绘制线条颜色渐变的折线图

效果图 .................... 概述 现状 折线图的应用比较广泛&#xff0c;为了增强用户体验&#xff0c;很多应用中都嵌入了折线图。折线图可以更加直观的表示数据的变化。网络上有很多绘制折线图的demo&#xff0c;有的也使用了动画&#xff0c;但是线条颜色渐变的折线图的…

android使碎片切换界面,玩转Android中的碎片Fragment

引言&#xff1a;在Android开发中&#xff0c;我们都知道一些界面的展示经常会用到的就是Activity,但是Activity存在着很大的局限性,比如说手机上的界面显示在平板上面就会发生各种变形的问题,Activity也无法实现局部的数据刷新,所以Android3.0之后出来了Fragment,Fragment通常…

html GPS坐标实现,JavaScript 实现GPS坐标点距离计算(两个经/纬度间的距离计算)...

在LBS(基于位置服务)的一些应用中&#xff0c;有时我们会需要计算两个用户或两个坐标点之间的距离。要解决这类问题&#xff0c;就要了解空间几何的概念并结合数学中在三角函数公式计算两点之间的值。本文介绍基于经度/纬度的&#xff0c;两个坐标点之间的距离计算&#xff0c;…

机器学习基于skcilearn tensorflow电子书_Tensorflow机器学习模型的跨平台上线

本篇文章转载自博客园&#xff0c;作者: 刘建平Pinard在用PMML实现机器学习模型的跨平台上线中&#xff0c;我们讨论了使用PMML文件来实现跨平台模型上线的方法&#xff0c;这个方法当然也适用于tensorflow生成的模型&#xff0c;但是由于tensorflow模型往往较大&#xff0c;使…

html5 observer api,基于HTML5新特性Mutation Observer实现编辑器的撤销和回退操作

MutationObserver介绍MutationObserver给开发者们提供了一种能在某个范围内的DOM树发生变化时作出适当反应的能力.该API设计用来替换掉在DOM3事件规范中引入的Mutation事件.Mutation Observer(变动观察器)是监视DOM变动的接口。当DOM对象树发生任何变动时&#xff0c;Mutation …

Maven(五)使用Nexus搭建Maven私服

文章装载于&#xff1a;http://blog.csdn.net/jun55xiu/article/details/39497089 Nexus介绍 Nexus是Maven仓库管理器&#xff0c;如果你使用Maven&#xff0c;你可以从Maven中央仓库下载所需要的构件&#xff08;artifact&#xff09;&#xff0c;但这通常不是一个好的做法&am…

计算机学测打多少字,速度测试,一分钟能打多少字?

六、速度测试——检验学习效果经过一段时间的练习&#xff0c;输入速度提高了不少吧&#xff0c;赶快来测试一下现在一分钟可以输入多少英文或汉字。金山打字通2010的“速度测试”功能不仅有基本的“屏幕对照”速度测试&#xff0c;还有“书本对照”测试及要求较高的“同声录入…

HDFS入门(1)

2015.07.12笔记 1.HDFS Distributed File System&#xff08;操作系统实现人机交互&#xff0c;最重要的功能是文件管理&#xff0c;使用文件管理系统&#xff0c;windows、Linux文件管理系统有共性&#xff1a;用户可创建文件/夹&#xff0c;删除&#xff0c;修改权限&#xf…

竞赛图 计算机网络 应用题,我校学子获2020年“中国高校计算机大赛-网络技术挑战赛”全国总决赛一等奖(图)...

近日&#xff0c;2020年“中国高校计算机大赛-网络技术挑战赛”全国总决赛在温州浙南科技城落下帷幕。我校计算机与信息安全学院陈俊彦、雷晓春老师指导的“智载车队”团队(成员&#xff1a;林楷浩、陈澳格、黄湖)在创业先锋C系列中获得全国一等奖&#xff0c;在创新创意A系列中…

建立远程桌面连接计算机无密码,win7远程桌面空密码的步骤_win7系统如何设置让远程桌面登录无需密码-win7之家...

在日常工作中&#xff0c;可能经常会使用到远程连接桌面功能&#xff0c;我们要远程桌面的话&#xff0c;通常是要输入密码才可以的&#xff0c;但是有些用户觉得麻烦&#xff0c;那么win7系统如何设置让远程桌面登录无需密码呢&#xff1f;带着大家的这个问题&#xff0c;本文…

tkinter如何lable重复显示到同一行中_如何创建包含 CAD 导入和选择的仿真 App

在使用 COMSOL 软件二次开发的过程中&#xff0c;你可能会遇到这样的问题&#xff1a;如何使用 App 开发器创建可以处理 CAD 导入并能让用户交互式选择边界条件的仿真 App&#xff1f;我需要了解编程吗&#xff1f;今天我们将为您介绍在 COMSOL 软件中创建包含 CAD 导入和选择的…

计算机科学导论课后单词,计算机科学导论课后总结

计算机科学导论课后总结1老师上课给我们演示了一个迷宫的程序&#xff0c;然后我上网查找了一下&#xff0c;大致学习了一下这个程序的思想。迷宫这个题目和数据结构—图有关迷宫的随机生成和路径搜索主要和图的遍历有关&#xff0c;一般来说图的遍历主要有两种方式&#xff1a…