<jupyter_start><jupyter_text>计算下面这个函数的导函数：$$y = x^2\bullet e^x$$它的导函数是：$${dy \over dx} = 2x\bullet e^x + x^2 \bullet e^x$$来看看autograd的计算结果与手动求导计算结果的误差。<jupyter_code>def f(x):
    # return np.power(x, 2) * np.exp(x)
    return x ** 2 * t.exp(x)

def grad_f(x):
    # return 2 * x * np.exp(x) + f(x)
    return 2 * x * t.exp(x) + f(x)
x = t.randn(3, 4, requires_grad=True)
y = f(x)
y
y.backward(t.ones(y.size()))  # 这个参数有点类似于上一级得到的梯度
x.grad
# autograd的计算结果与利用公式的手动计算结果一致
grad_f(x)<jupyter_output><empty_output><jupyter_text>3.2.2 计算图PyTorch中`autograd`的底层采用了计算图，计算图是一种特殊的有向无环图（DAG），用于记录算子与变量之间的关系。一般用矩形表示算子，椭圆形表示变量。如表达式$ \textbf {z = wx + b}$可分解为$\textbf{y = wx}$和$\textbf{z = y + b}$，其计算图如图3-3所示，图中`MUL`，`ADD`都是算子，$\textbf{w}$，$\textbf{x}$，$\textbf{b}$即变量。![图3-3:computation graph](imgs/com_graph.svg) 如上有向无环图中，$\textbf{X}$和$\textbf{b}$是叶子节点（leaf node），这些节点通常由用户自己创建，不依赖于其他变量。$\textbf{z}$称为根节点，是计算图的最终目标。利用链式法则很容易求得各个叶子节点的梯度。$${\partial z \over \partial b} = 1,\space {\partial z \over \partial y} = 1\\{\partial y \over \partial w }= x,{\partial y \over \partial x}= w\\{\partial z \over \partial x}= {\partial z \over \partial y} {\partial y \over \partial x}=1 * w\\{\partial z \over \partial w}= {\partial z \over \partial y} {\partial y \over \partial w}=1 * x\\$$而有了计算图，上述链式求导即可利用计算图的反向传播自动完成，其过程如图3-4所示。![图3-4：计算图的反向传播](imgs/com_graph_backward.svg)在PyTorch实现中，autograd会随着用户的操作，记录生成当前variable的所有操作，并由此建立一个有向无环图。用户每进行一个操作，相应的计算图就会发生改变。更底层的实现中，图中记录了操作`Function`，每一个变量在图中的位置可通过其`grad_fn`属性在图中的位置推测得到。在反向传播过程中，autograd沿着这个图从当前变量（根节点$\textbf{z}$）溯源，可以利用链式求导法则计算所有叶子节点的梯度。每一个前向传播操作的函数都有与之对应的反向传播函数用来计算输入的各个variable的梯度，这些函数的函数名通常以`Backward`结尾。下面结合代码学习autograd的实现细节。<jupyter_code>x = t.ones(1)
b = t.rand(1, requires_grad=True)
w = t.rand(1, requires_grad=True)
y = w * x  # equal to y = w.mul(x)
z = y + b  # equal to z = y.add(b)
x.requires_grad, b.requires_grad, w.requires_grad
y.requires_grad
x.is_leaf, w.is_leaf, b.is_leaf
y.is_leaf, z.is_leaf
z.grad_fn
# next_functions保存grad_fn的输入，是一个tuple，tuple的元素也是Function
# 第一个是y，它是乘法(mul)的输出，所以对应的反向传播函数y.grad_fn是MulBackward
# 第二个是b，它是叶子节点，由用户创建，grad_fn为None，但是有
z.grad_fn.next_functions 
z.grad_fn.next_functions[0][0] == y.grad_fn
# 第一个是w，叶子节点，需要求导，梯度是累加的
# 第二个是x，叶子节点，不需要求导，所以为None
y.grad_fn.next_functions
# 叶子借点的grad_fn是None
w.grad_fn, x.grad_fn<jupyter_output><empty_output><jupyter_text>计算w的梯度的时候，需要用到x的数值(${\partial y\over \partial w} = x $)，这些数值在前向过程中会保存成buffer，在计算完梯度之后会自动清空。为了能够多次反向传播需要指定`retain_graph`来保留这些buffer。<jupyter_code># 使用retain_graph来保存buffer
z.backward(retain_graph=True)
w.grad
# 多次反向传播，梯度累加，这也就是w中AccumulateGrad标识的含义
z.backward()
w.grad<jupyter_output><empty_output><jupyter_text>PyTorch使用的是动态图，它的计算图在每次前向传播时都是从头开始构建，所以它能够使用Python控制语句（如for、if等）根据需求创建计算图。这点在自然语言处理领域中很有用，它意味着你不需要事先构建所有可能用到的图的路径，图在运行时才构建。<jupyter_code>def abs(x):
    if x.data[0] > 0: return x
    else: return -x

x = t.ones(1, requires_grad=True)
y = abs(x)
y.backward()
x.grad
x = -1 * t.ones(1)
x = x.requires_grad_()
y = abs(x)
y.backward()
print(x.grad)
y
x
x.requires_grad
def f(x):
    result = 1
    for ii in x:
        if ii.item() > 0:
            result = ii * result
    return result

x = t.arange(-2, 4, dtype=t.float32).requires_grad_()
y = f(x)  # y = x[3] * x[4] * x[5]
y.backward()
x.grad<jupyter_output><empty_output><jupyter_text>变量的`requires_grad`属性默认为False，如果某一个节点requires_grad被设置为True，那么所有依赖它的节点`requires_grad`都是True。这其实很好理解，对于$ \textbf{x}\to \textbf{y} \to \textbf{z}$，x.requires_grad = True，当需要计算$\partial z \over \partial x$时，根据链式法则，$\frac{\partial z}{\partial x} = \frac{\partial z}{\partial y} \frac{\partial y}{\partial x}$，自然也需要求$ \frac{\partial z}{\partial y}$，所以y.requires_grad会被自动标为True. 有些时候我们可能不希望autograd对tensor求导。认为求导需要缓存许多中间结构，增加额外的内存/显存开销，那么我们可以关闭自动求导。对于不需要反向传播的情景（如inference，即测试推理时），关闭自动求导可实现一定程度的速度提升，并节省约一半显存，因其不需要分配空间计算梯度。<jupyter_code>x = t.ones(1, requires_grad=True)
w = t.rand(1, requires_grad=True)
y = x * w
# y依赖于w，而w.requires_grad = True
x.requires_grad, w.requires_grad, y.requires_grad
t.no_grad??
t.set_grad_enabled(False)
x = t.ones(1)
w = t.rand(1, requires_grad=True)
y = x * w
# y依赖于w和x, 虽然w.requires_grad=True,但是y的requires_grad为False
x.requires_grad, w.requires_grad, y.requires_grad
# 恢复默认设置
t.set_grad_enabled(True)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.15801722131719806, 'fraction_numerical': 0.010937863625785431, 'mean_word_length': 5.865814696485623, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '473289', 'license': 'MIT', 'n_tokens_mistral': 2502, 'n_tokens_neox': 2689, 'n_words': 266, 'path': 'chapter03-tensor_and_autograd/Autogradbymyself.ipynb', 'repo_name': 'LRY89757/pytorch_explore', 'size': '4297'}