#!/usr/bin/env python
# coding: utf-8

# # Hook编程
# 
# [![下载Notebook](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.9.0/resource/_static/logo_notebook.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.9.0/tutorials/zh_cn/custom_program/mindspore_hook_program.ipynb)&emsp;[![下载样例代码](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.9.0/resource/_static/logo_download_code.svg)](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/r2.9.0/tutorials/zh_cn/custom_program/mindspore_hook_program.py)&emsp;[![查看源文件](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r2.9.0/resource/_static/logo_source.svg)](https://atomgit.com/mindspore/docs/blob/r2.9.0/tutorials/source_zh_cn/custom_program/hook_program.ipynb)
# 

# 调试深度学习网络是每一个深度学习领域的从业者需要面对且投入精力较大的工作。由于深度学习网络隐藏了中间层算子的输入、输出数据以及反向梯度，只提供网络输入数据（特征量、权重）的梯度，导致无法准确地感知中间层算子的数据变化，从而降低了调试效率。为了方便用户准确、快速地对深度学习网络进行调试，MindSpore在动态图模式下设计了Hook功能，**使用Hook功能可以捕获中间层算子的输入、输出数据以及反向梯度**。
# 
# 目前，动态图模式下提供了五种形式的Hook功能，分别是：[HookBackward](https://www.mindspore.cn/docs/zh-CN/r2.9.0/api_python/ops/mindspore.ops.HookBackward.html)算子和在Cell对象上进行注册的register_forward_pre_hook、register_forward_hook、register_backward_pre_hook、register_backward_hook功能。
# 
# ## HookBackward算子
# 
# HookBackward将Hook功能以算子的形式实现。用户初始化一个HookBackward算子，将其安插到深度学习网络中需要捕获梯度的位置。在网络正向执行时，HookBackward算子将输入数据不做任何修改后原样输出；在网络反向传播梯度时，在HookBackward上注册的Hook函数将会捕获反向传播至此的梯度。用户可以在Hook函数中自定义对梯度的操作，比如打印梯度，或者返回新的梯度。
# 
# 示例代码如下：

# In[15]:


import mindspore as ms
from mindspore import ops

ms.set_context(mode=ms.PYNATIVE_MODE)

def hook_fn(grad_out):
    """打印梯度"""
    print("hook_fn print grad_out:", grad_out)

hook = ops.HookBackward(hook_fn)
def hook_test(x, y):
    z = x * y
    z = hook(z)
    z = z * y
    return z

def net(x, y):
    return ms.grad(hook_test, grad_position=(0, 1))(x, y)

output = net(ms.Tensor(1, ms.float32), ms.Tensor(2, ms.float32))
print("output:", output)


# 更多HookBackward算子的说明可以参考[API文档](https://mindspore.cn/docs/zh-CN/r2.9.0/api_python/ops/mindspore.ops.HookBackward.html)。
# 
# ## Cell对象的register_forward_pre_hook功能
# 
# 用户可以对Cell对象使用`register_forward_pre_hook`函数来注册一个自定义的Hook函数，用来捕获正向传入该Cell对象的数据。`register_forward_pre_hook`函数接收Hook函数作为入参，并返回一个与Hook函数一一对应的`handle`对象。用户可以通过调用`handle`对象的`remove()`函数来删除与之对应的Hook函数。每一次调用`register_forward_pre_hook`函数，都会返回一个不同的`handle`对象。Hook函数应该按照以下的方式进行定义。

# In[6]:


def forward_pre_hook_fn(cell, inputs):
    print("forward inputs: ", inputs)


# 这里的cell是Cell对象，inputs是正向传入到Cell对象的数据。因此，用户可以使用register_forward_pre_hook函数来捕获网络中某一个Cell对象的正向输入数据。用户可以在Hook函数中自定义对输入数据的操作，比如查看、打印数据，或者返回新的输入数据给当前的Cell对象。如果在Hook函数中对Cell对象的原始输入数据进行计算操作后，再作为新的输入数据返回，这些新增的计算操作将会同时作用于梯度的反向传播。
# 
# 示例代码如下：

# In[2]:


import numpy as np
import mindspore as ms
import mindspore.nn as nn

ms.set_context(mode=ms.PYNATIVE_MODE)

def forward_pre_hook_fn(cell, inputs):
    print("forward inputs: ", inputs)
    input_x = inputs[0]
    return input_x

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.relu = nn.ReLU()
        self.handle = self.relu.register_forward_pre_hook(forward_pre_hook_fn)

    def construct(self, x, y):
        x = x + y
        x = self.relu(x)
        return x

net = Net()
grad_net = ms.grad(net, grad_position=(0, 1))

x = ms.Tensor(np.ones([1]).astype(np.float32))
y = ms.Tensor(np.ones([1]).astype(np.float32))

output = net(x, y)
print(output)
gradient = grad_net(x, y)
print(gradient)
net.handle.remove()
gradient = grad_net(x, y)
print(gradient)


# 用户如果在Hook函数中直接返回新创建的数据，而不是返回由原始输入数据经过计算后得到的数据，那么梯度的反向传播将会在该Cell对象上截止。
# 
# 示例代码如下：

# In[3]:


import numpy as np
import mindspore as ms
import mindspore.nn as nn

ms.set_context(mode=ms.PYNATIVE_MODE)

def forward_pre_hook_fn(cell, inputs):
    print("forward inputs: ", inputs)
    return ms.Tensor(np.ones([1]).astype(np.float32))

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.relu = nn.ReLU()
        self.handle = self.relu.register_forward_pre_hook(forward_pre_hook_fn)

    def construct(self, x, y):
        x = x + y
        x = self.relu(x)
        return x

net = Net()
grad_net = ms.grad(net, grad_position=(0, 1))

x = ms.Tensor(np.ones([1]).astype(np.float32))
y = ms.Tensor(np.ones([1]).astype(np.float32))

gradient = grad_net(x, y)
print(gradient)


# 为了避免脚本在切换到图模式时运行失败，不建议在Cell对象的 `construct` 函数中调用 `register_forward_pre_hook` 函数和 `handle` 对象的 `remove()` 函数。在动态图模式下，如果在Cell对象的 `construct` 函数中调用 `register_forward_pre_hook` 函数，那么Cell对象每次运行都将注册一个新的Hook函数。
# 
# 更多关于Cell对象的 `register_forward_pre_hook` 功能的说明可以参考[API文档](https://mindspore.cn/docs/zh-CN/r2.9.0/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.register_forward_pre_hook)。
# 
# ## Cell对象的register_forward_hook功能
# 
# 用户可以在Cell对象上使用`register_forward_hook`函数来注册一个自定义的Hook函数，用来捕获正向传入Cell对象的数据和Cell对象的输出数据。`register_forward_hook`函数接收Hook函数作为入参，并返回一个与Hook函数一一对应的`handle`对象。用户可以通过调用`handle`对象的`remove()`函数来删除与之对应的Hook函数。每一次调用`register_forward_hook`函数，都会返回一个不同的`handle`对象。Hook函数应该按照以下的方式进行定义。
# 
# 示例代码如下：

# In[9]:


def forward_hook_fn(cell, inputs, outputs):
    print("forward inputs: ", inputs)
    print("forward outputs: ", outputs)


# 这里的`cell`是Cell对象，`inputs`是正向传入到Cell对象的数据，`outputs`是Cell对象的正向输出数据。因此，用户可以使用`register_forward_hook`函数来捕获网络中某一个Cell对象的正向输入数据和输出数据。用户可以在Hook函数中自定义对输入、输出数据的操作，比如查看、打印数据，或者返回新的输出数据。如果在Hook函数中对Cell对象的原始输出数据进行计算操作后，再作为新的输出数据返回，这些新增的计算操作将会同时作用于梯度的反向传播。
# 
# 示例代码如下：

# In[4]:


import numpy as np
import mindspore as ms
import mindspore.nn as nn

ms.set_context(mode=ms.PYNATIVE_MODE)

def forward_hook_fn(cell, inputs, outputs):
    print("forward inputs: ", inputs)
    print("forward outputs: ", outputs)
    outputs = outputs + outputs
    return outputs

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.relu = nn.ReLU()
        self.handle = self.relu.register_forward_hook(forward_hook_fn)

    def construct(self, x, y):
        x = x + y
        x = self.relu(x)
        return x

net = Net()
grad_net = ms.grad(net, grad_position=(0, 1))

x = ms.Tensor(np.ones([1]).astype(np.float32))
y = ms.Tensor(np.ones([1]).astype(np.float32))

gradient = grad_net(x, y)
print(gradient)
net.handle.remove()
gradient = grad_net(x, y)
print(gradient)


# 用户如果在Hook函数中直接返回新创建的数据，而不是将原始的输出数据经过计算后，将得到的新输出数据返回，那么梯度的反向传播将会在该Cell对象上截止。该现象可以参考`register_forward_pre_hook`函数的用例说明。
# 为了避免脚本在切换到图模式时运行失败，不建议在Cell对象的`construct`函数中调用`register_forward_hook`函数和`handle`对象的`remove()`函数。在动态图模式下，如果在Cell对象的`construct`函数中调用`register_forward_hook`函数，那么Cell对象每次运行都将注册一个新的Hook函数。
# 
# 更多关于Cell对象的`register_forward_hook`功能的说明可以参考[API文档](https://mindspore.cn/docs/zh-CN/r2.9.0/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.register_forward_hook)。
# 
# ## Cell对象的register_backward_pre_hook功能
# 
# 用户可以在Cell对象上使用`register_backward_pre_hook`函数来注册一个自定义的Hook函数，用来捕获网络反向传播时与Cell对象相关联的梯度。`register_backward_pre_hook`函数接收Hook函数作为入参，并返回一个与Hook函数一一对应的`handle`对象。用户可以通过调用`handle`对象的`remove()`函数来删除与之对应的Hook函数。每一次调用`register_backward_pre_hook`函数，都会返回一个不同的`handle`对象。
# 
# 与HookBackward算子所使用的自定义Hook函数有所不同，`register_backward_pre_hook`使用的Hook函数的入参中，包含了表示Cell对象信息`cell`以及反向传入到Cell对象的梯度。
# 示例代码如下：

# In[ ]:


def backward_pre_hook_function(cell, grad_output):
    print(grad_output)


# 这里的`cell`是Cell对象信息，`grad_output`是网络反向传播时，传入到Cell对象的梯度。因此，用户可以使用`register_backward_pre_hook`函数来捕获网络中某一个Cell对象的反向梯度输入值。用户可以在Hook函数中自定义对梯度的操作，比如查看、打印梯度，或者返回新的输入梯度。如果需要在Hook函数中返回新的输入梯度时，返回值必须是`tuple`的形式。
# 
# 示例代码如下：

# In[5]:


import numpy as np
import mindspore as ms
import mindspore.nn as nn

ms.set_context(mode=ms.PYNATIVE_MODE)

def backward_pre_hook_function(cell, grad_output):
    print(grad_output)

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Conv2d(1, 2, kernel_size=2, stride=1, padding=0, weight_init="ones", pad_mode="valid")
        self.bn = nn.BatchNorm2d(2, momentum=0.99, eps=0.00001, gamma_init="ones")
        self.handle = self.bn.register_backward_pre_hook(backward_pre_hook_function)
        self.relu = nn.ReLU()

    def construct(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

net = Net()
grad_net = ms.grad(net)
output = grad_net(ms.Tensor(np.ones([1, 1, 2, 2]).astype(np.float32)))
print(output)
net.handle.remove()
output = grad_net(ms.Tensor(np.ones([1, 1, 2, 2]).astype(np.float32)))
print("-------------\n", output)


# 为了避免脚本在切换到图模式时运行失败，不建议在Cell对象的 `construct` 函数中调用 `register_backward_pre_hook` 函数和 `handle` 对象的 `remove()` 函数。在PyNative模式下，如果在Cell对象的 `construct` 函数中调用 `register_backward_pre_hook` 函数，那么Cell对象每次运行都将注册一个新的Hook函数。
# 
# 更多关于Cell对象的`register_backward_pre_hook`功能的说明可以参考[API文档](https://mindspore.cn/docs/zh-CN/r2.9.0/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.register_backward_pre_hook)。
# 
# ## Cell对象的register_backward_hook功能
# 
# 用户可以在Cell对象上使用`register_backward_hook`函数来注册一个自定义的Hook函数，用来捕获网络反向传播时与Cell对象相关联的梯度。`register_backward_hook`函数接收Hook函数作为入参，并返回一个与Hook函数一一对应的`handle`对象。用户可以通过调用`handle`对象的`remove()`函数来删除与之对应的Hook函数。每一次调用`register_backward_hook`函数，都会返回一个不同的`handle`对象。
# 
# 与HookBackward算子所使用的自定义Hook函数有所不同，`register_backward_hook`使用的Hook函数的入参中，包含了表示Cell对象信息`cell`、反向传入到Cell对象的梯度、以及Cell对象的反向输出的梯度。
# 
# 示例代码如下：

# In[ ]:


def backward_hook_function(cell, grad_input, grad_output):
    print(grad_input)
    print(grad_output)


# 这里的`cell`是Cell对象信息，`grad_input`是Cell对象反向输出的梯度，`grad_output`是网络反向传播时，传入到Cell对象的梯度。因此，用户可以使用`register_backward_hook`函数来捕获网络中某一个Cell对象的反向传入和反向输出梯度。用户可以在Hook函数中自定义对梯度的操作，比如查看、打印梯度，或者返回新的输出梯度。如果需要在Hook函数中返回新的输出梯度时，返回值必须是`tuple`的形式。
# 
# 示例代码如下：

# In[6]:


import numpy as np
import mindspore as ms
import mindspore.nn as nn

ms.set_context(mode=ms.PYNATIVE_MODE)

def backward_hook_function(cell, grad_input, grad_output):
    print(grad_input)
    print(grad_output)

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Conv2d(1, 2, kernel_size=2, stride=1, padding=0, weight_init="ones", pad_mode="valid")
        self.bn = nn.BatchNorm2d(2, momentum=0.99, eps=0.00001, gamma_init="ones")
        self.handle = self.bn.register_backward_hook(backward_hook_function)
        self.relu = nn.ReLU()

    def construct(self, x):
        x = self.conv(x)
        x = self.bn(x)
        x = self.relu(x)
        return x

net = Net()
grad_net = ms.grad(net)
output = grad_net(ms.Tensor(np.ones([1, 1, 2, 2]).astype(np.float32)))
print(output)
net.handle.remove()
output = grad_net(ms.Tensor(np.ones([1, 1, 2, 2]).astype(np.float32)))
print("-------------\n", output)


# 为了避免脚本在切换到图模式时运行失败，不建议在Cell对象的 `construct` 函数中调用 `register_backward_hook` 函数和 `handle` 对象的 `remove()` 函数。在PyNative模式下，如果在Cell对象的 `construct` 函数中调用 `register_backward_hook` 函数，那么Cell对象每次运行都将注册一个新的Hook函数。
# 
# 更多关于Cell对象的 `register_backward_hook` 功能的说明可以参考[API文档](https://mindspore.cn/docs/zh-CN/r2.9.0/api_python/nn/mindspore.nn.Cell.html#mindspore.nn.Cell.register_backward_hook)。
# 
# ## Cell对象使用多个hook功能
# 
# 当 `register_backward_pre_hook` 函数、 `register_backward_hook` 函数、`register_forward_pre_hook` 函数、 `register_forward_hook` 函数同时作用于同一Cell对象时，如果 `register_forward_pre_hook` 和 `register_forward_hook` 函数中有添加其他算子进行数据处理，这些新增算子会在Cell对象执行前或者执行后参与数据的正向计算，但是这些新增算子的反向梯度不在 `register_backward_pre_hook` 函数和 `register_backward_hook` 函数的捕获范围内。 `register_backward_pre_hook` 中注册的Hook函数仅捕获原始Cell对象的输入梯度。`register_backward_hook` 中注册的Hook函数仅捕获原始Cell对象的输入、输出梯度。
# 
# 示例代码如下：

# In[14]:


import numpy as np
import mindspore as ms
import mindspore.nn as nn

ms.set_context(mode=ms.PYNATIVE_MODE)

def forward_pre_hook_fn(cell, inputs):
    print("forward inputs: ", inputs)
    input_x = inputs[0]
    return input_x

def forward_hook_fn(cell, inputs, outputs):
    print("forward inputs: ", inputs)
    print("forward outputs: ", outputs)
    outputs = outputs + outputs
    return outputs

def backward_pre_hook_fn(cell, grad_output):
    print("grad input: ", grad_output)

def backward_hook_fn(cell, grad_input, grad_output):
    print("grad input: ", grad_output)
    print("grad output: ", grad_input)

class Net(nn.Cell):
    def __init__(self):
        super(Net, self).__init__()
        self.relu = nn.ReLU()
        self.handle = self.relu.register_forward_pre_hook(forward_pre_hook_fn)
        self.handle2 = self.relu.register_forward_hook(forward_hook_fn)
        self.handle3 = self.relu.register_backward_pre_hook(backward_pre_hook_fn)
        self.handle4 = self.relu.register_backward_hook(backward_hook_fn)

    def construct(self, x, y):
        x = x + y
        x = self.relu(x)
        return x

net = Net()
grad_net = ms.grad(net, grad_position=(0, 1))
gradient = grad_net(ms.Tensor(np.ones([1]).astype(np.float32)), ms.Tensor(np.ones([1]).astype(np.float32)))
print(gradient)


# 这里的 `grad_output` 是梯度反向传播时传入`self.relu`的梯度，而不是传入 `forward_hook_fn` 函数中，新增的 `Add` 算子的梯度。这里的 `grad_input` 是梯度反向传播时 `self.relu` 反向输出的梯度，而不是 `forward_pre_hook_fn` 函数中新增 `Add` 算子的反向输出梯度。 `register_forward_pre_hook` 函数和 `register_forward_hook` 函数是在Cell对象执行前后起作用，不会影响Cell对象上反向Hook函数的梯度捕获范围。
# 
# 
