"""第 2 章：差商、链式法则与梯度下降，只使用标准库。"""


def loss(w):
    return (w - 3) ** 2


def gradient(w):
    return 2 * (w - 3)


def central_difference(function, x, h=1e-5):
    return (function(x + h) - function(x - h)) / (2 * h)


def descend(rate, steps=10, start=0.0):
    w = start
    history = [(w, loss(w))]
    for _ in range(steps):
        w = w - rate * gradient(w)
        history.append((w, loss(w)))
    return history


def sample_loss(w, b, x=2, y=7):
    return (w * x + b - y) ** 2


def sample_gradient(w, b, x=2, y=7):
    error = w * x + b - y
    return 2 * error * x, 2 * error


def main():
    for h in [1.0, 0.1, 0.01]:
        quotient = ((2 + h) ** 2 - 2 ** 2) / h
        print(f"h={h:g}，平方函数在 2 附近的差商：{quotient:.2f}")
    print(f"L 在 w=0 的数值导数：{central_difference(loss, 0):.4f}")
    for rate in [0.1, 0.5, 0.9, 1.1]:
        w, value = descend(rate)[-1]
        print(f"学习率 {rate:.1f}，10 步后 w={w:.4f}，损失={value:.4f}")

    w, b = 2.0, 0.0
    dw, db = sample_gradient(w, b)
    numeric_dw = central_difference(lambda value: sample_loss(value, b), w)
    numeric_db = central_difference(lambda value: sample_loss(w, value), b)
    print(f"解析梯度：({dw:.4f}, {db:.4f})")
    print(f"数值梯度：({numeric_dw:.4f}, {numeric_db:.4f})")
    # 两个梯度都在旧参数处计算，再同时更新。
    w, b = w - 0.05 * dw, b - 0.05 * db
    print(f"更新后 w={w:.2f}，b={b:.2f}，损失={sample_loss(w, b):.2f}")


if __name__ == "__main__":
    main()
