"""
第 09 章配套代码：深层可训练性的工程突破
(1) ReLU vs sigmoid 的梯度（为什么 ReLU 缓解梯度消失）
(2) 残差连接如何让梯度直达（ResNet）
(3) Adam 优化器的一步更新
Runnable with: numpy only.  python3 09_enablers.py
"""
import numpy as np


def sigmoid(x): return 1 / (1 + np.exp(-x))


print("=== (1) 深层网络里激活函数导数的连乘 ===")
# 10 层，每层乘一次激活导数。sigmoid 导数最大仅 0.25 => 连乘指数衰减
L = 10
# sigmoid'(z) 在 z=0 处最大 = 0.25
sig_grad_chain = 0.25 ** L
# ReLU'(z) = 1 (正区间) => 连乘不衰减
relu_grad_chain = 1.0 ** L
print(f"  sigmoid: 10 层导数连乘(每层最多0.25) ~ {sig_grad_chain:.2e}  -> 梯度消失")
print(f"  ReLU   : 10 层导数连乘(正区间每层1.0) ~ {relu_grad_chain:.2e}  -> 梯度保持")

print("\n=== (2) 残差连接 y = x + F(x) 的梯度 ===")
# 普通层 dy/dx = F'(x)；残差层 dy/dx = 1 + F'(x)，那个 '1' 保证梯度有直达通路
for Fp in [0.01, 0.5, 2.0]:
    print(f"  F'(x)={Fp:>4}:  普通层 dy/dx={Fp:>5}  |  残差层 dy/dx=1+{Fp}={1+Fp}"
          f"  ({'梯度近乎消失' if Fp < 0.1 else '梯度健康'} vs 残差恒有直达项)")

print("\n=== (3) Adam 优化器一步更新（自适应动量）===")


def adam_step(g, m, v, t, lr=0.01, b1=0.9, b2=0.999, eps=1e-8):
    m = b1 * m + (1 - b1) * g           # 一阶矩(动量)
    v = b2 * v + (1 - b2) * g * g       # 二阶矩(梯度平方的滑动平均)
    m_hat = m / (1 - b1 ** t)           # 偏差校正
    v_hat = v / (1 - b2 ** t)
    update = lr * m_hat / (np.sqrt(v_hat) + eps)   # 每个参数自适应步长
    return update, m, v


# 模拟一个参数，梯度尺度差异很大
m, v = 0.0, 0.0
for t in range(1, 6):
    g = np.array([10.0, 0.01])[t % 2]   # 交替大/小梯度
    upd, m, v = adam_step(g, m, v, t)
    print(f"  t={t} 原始梯度={g:>6}  Adam 更新量={upd:.5f}  (大小梯度被自适应地归一到相近步长)")
