赞
踩
autograd.py
文件:实现计算图框架、自动微分框架(automatic differentiation framework)ops.py
文件:实现各种算子。反向传播的粒度很细,会以一个个算子为单位,而不是以网络层为单位。其实现基础是numpy,后面会自己实现自己的运算库PowerScalar
算子实现 a s c a l a r a^{scalar} ascalar,其gradient()函数就是返回 ( o u t _ g r a d × s e l f . s c a l a r × l h s s e l f . s c a l a r − 1 ) (out\_grad\times self.scalar\times lhs^{self.scalar - 1} ) (out_grad×self.scalar×lhsself.scalar−1)
def find_topo_sort(node_list: List[Value]) -> List[Value]: visited = set() topo_order = [] for node in node_list: if node not in visited: topo_sort_dfs(node, visited, topo_order) return topo_order def topo_sort_dfs(node, visited, topo_order): if node in visited: return for next in node.inputs: topo_sort_dfs(next, visited, topo_order) visited.add(node) topo_order.append(node)
def compute_gradient_of_variables(output_tensor, out_grad): # 创建一个空字典,用于存储每个节点在计算图中的梯度信息 node_to_output_grads_list: Dict[Tensor, List[Tensor]] = { } # 将输出张量的梯度存储在字典中,作为输出节点的梯度 node_to_output_grads_list[output_tensor] = [out_grad] # find_topo_sort([output_tensor])从输出张量开始计算图的拓扑排序(因为输出张量通过node.inputs连着整张图,所以可以实现) reverse_topo_order = list(reversed(find_topo_sort([output_tensor]))) for node in reverse_topo_order: # 计算当前节点的梯度:就是把所有偏导数加到一起 sum_grad = node_to_output_grads_list[node][0] for t in node_to_output_grads_list[node][1:]: sum_grad = sum_grad + t node.grad = sum_grad # 叶子节点即没有op的节点,跳过后面的for循环 if node.is_leaf(): continue # 计算节点的输入节点相对于 for i, grad in enumerate(node.op.gradient_as_tuple(node.grad, node)): input_ = node.inputs[i] if input_ not in node_to_output_grads_list: node_to_output_grads_list[input_] = [] node_to_output_grads_list[input_].append(grad)
举个例子有助于理解自动微分的过程原理:
def softmax_loss(Z, y_one_hot):
a = ndl.ops.summation(Z*y_one_hot)
b = ndl.ops.summation(ndl.ops.log(ndl.ops.summation(ndl.ops.exp(Z), axes=(1,))))
return (b-a) / Z.shape[0]
.backward()
即可自动对每个参数求导,再对W1和W2更新即可def nn_epoch(X, y, W1, W2, lr = 0.1, batch=100): for i in range(X.shape[0]//batch+1): # 首先不变的,采样batch start, end = i * batch, min((i+1)*batch, X.shape[0]) m = end - start if m == 0: break Xb, yb = X[start:end], y[start:end] # hw0这里是计算梯度,是通过手动计算得出数学公式然后实现的 # 现在则是使用自动微分机制,算出loss后直接.backward()即可 # 下面并没有对X和y设置自动微分,因为参数更新过程中只需要对参数矩阵W1和W2自动微分即可,不需要对输入数据和得出来的结果微分(node_to_grad第一个值是loss) Xb, yb = X[start:end], y[start:end] Xb = ndl.Tensor(Xb, requires_grad=False) Z1 = Xb @ W1 A1 = ndl.ops.relu(Z1) Z2 = A1 @ W2 y_one_hot = np.zeros(Z2.shape, dtype=np.float32) y_one_hot[np.arange(Z2.shape[0]), yb] = 1 y_one_hot = ndl.Tensor(y_one_hot, requires_grad=False) loss = softmax_loss(Z2, y_one_hot) loss.backward() # 更新参数矩阵 W1.data = W1.data - lr * ndl.Tensor(W1.grad.numpy().astype(np.float32)) W2.data = W2.data - lr * ndl.Tensor(W2.grad.numpy().astype(np.float32)) return W1, W2
Tensor
是autograd包的核心类,若将其属性.requires_grad
设为True,它将开始追踪在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可调用.backward()
来完成所有的梯度计算。此Tensor
的梯度将累积到.grad
属性中。Function
是另一个核心类,Tensor
和Function
结合就可以构建一个记录有整个计算过程的有向无环图。每个Tensor
的.grad_fn
属性记录着这个Tensor最近是通过哪一个运算得到的,若其不是通过某个运算得到的,.grad_fn
属性值为None
,像grad_fn
属性值为None
的称为叶子节点x = torch.ones(2, 2, requires_grad=True)
y = x + 2
z = y * y * 3
out = z.mean()
print(z, out)
输出为:
tensor([[27., 27.],
[27., 27.]], grad_fn=<MulBackward>) tensor(27., grad_fn=<MeanBackward1>)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。