"""第一篇实践：已知生成规律的模拟实验；没有真实设备数据。"""

from math import isfinite
from random import Random
from statistics import mean, stdev


def make_data(count, noise, seed):
    rng = Random(seed)
    data = []
    for _ in range(count):
        pages = rng.randint(5, 25)
        seconds = 3 * pages + 20 + rng.gauss(0, noise)
        data.append((pages, seconds))
    return data


def predict(pages, a, b):
    # 让输入落在 [-1, 1]，避免原始页数尺度使步长过大。
    return a * ((pages - 15) / 10) + b


def fit(data, rate=0.2, steps=1000):
    a, b = 0.0, 0.0
    history = [mean(y*y for _, y in data)]
    for _ in range(steps):
        errors = [predict(x, a, b) - y for x, y in data]
        da = 2 * mean(error * ((x-15)/10) for error, (x, _) in zip(errors, data))
        db = 2 * mean(errors)
        a, b = a - rate * da, b - rate * db
        loss = mean((predict(x, a, b) - y)**2 for x, y in data)
        history.append(loss)
        if not isfinite(loss) or loss > 1e12:
            break  # 停止明显发散的演示，避免继续放大数值。
        if max(abs(da), abs(db)) < 1e-10:
            break
    return a, b, history


def reference_mae(a, b):
    # 仅在模拟中知道无噪声真函数，用固定网格衡量拟合的偏离。
    pages = range(5, 26)
    return mean(abs(predict(x, a, b) - (3*x+20)) for x in pages)


def repeated_errors(count, noise):
    errors = []
    for seed in range(1000, 1030):
        a, b, _ = fit(make_data(count, noise, seed))
        errors.append(reference_mae(a, b))
    return errors


def main():
    data = make_data(80, noise=2, seed=2026)
    for rate in [0.02, 0.2, 1.1]:
        a, b, history = fit(data, rate, steps=120)
        if not isfinite(history[-1]) or history[-1] > 1e12:
            print(f"学习率 {rate:g}：损失过大，在第 {len(history)-1} 步停止")
        else:
            print(f"学习率 {rate:g}：120 步后训练 MSE={history[-1]:.3f}")
    a, b, _ = fit(data)
    print(f"换回页数单位的直线：y ≈ {a/10:.3f} x + {b-1.5*a:.3f}")
    for noise in [2, 10]:
        for count in [8, 80]:
            errors = repeated_errors(count, noise)
            print(f"噪声标准差={noise}，样本数={count}：参考 MAE 均值={mean(errors):.3f}，实验间标准差={stdev(errors):.3f}")


if __name__ == "__main__":
    main()
