# 螺旋資料分類：比較激活函數與最佳化器（3 個隱藏層、每層 8 個神經元、小批次 20、150 回合）
import math
import torch
from torch import nn

def spiral(n=200, seed=7):
    g = torch.Generator().manual_seed(seed)
    c = torch.arange(n) % 2                                    # 兩類交錯
    s = 0.25 + 3.2 * torch.rand(n, generator=g)
    ang = s * math.pi + c * math.pi
    r = s / 3.45 * 1.8
    x = torch.stack([r * torch.cos(ang), r * torch.sin(ang)], 1) + 0.05 * torch.randn(n, 2, generator=g)
    return x, c.float().unsqueeze(1)

x, y = spiral()
ACT = {"ReLU": nn.ReLU, "tanh": nn.Tanh, "sigmoid": nn.Sigmoid}
for act, opt_name, lr in [("ReLU", "Adam", 0.01), ("tanh", "Adam", 0.01), ("ReLU", "SGD", 0.1), ("sigmoid", "SGD", 0.1)]:
    torch.manual_seed(1)
    layers, width = [], 2
    for _ in range(3):
        layers += [nn.Linear(width, 8), ACT[act]()]
        width = 8
    net = nn.Sequential(*layers, nn.Linear(8, 1))              # 輸出不加 sigmoid：交給 BCEWithLogitsLoss，數值較穩
    opt = (torch.optim.Adam if opt_name == "Adam" else torch.optim.SGD)(net.parameters(), lr=lr)
    loss_fn = nn.BCEWithLogitsLoss()                           # 二元交叉熵
    for epoch in range(150):
        for idx in torch.randperm(len(x)).split(20):           # 每回合洗牌、每 20 筆更新一次
            opt.zero_grad()
            loss_fn(net(x[idx]), y[idx]).backward()
            opt.step()
    with torch.no_grad():
        acc = ((net(x) > 0).float() == y).float().mean().item()
        loss = loss_fn(net(x), y).item()
    print(f"{act:7s} + {opt_name:4s}（lr {lr}）：交叉熵 {loss:.3f}，準確率 {acc:.1%}")
