"""第 5 章：伯努利似然、交叉熵与 KL 散度；自然对数，单位 nat。"""

from math import exp, inf, isclose, log


LABELS = [1] * 7 + [0] * 3


def bernoulli_nll(labels, probability):
    if not 0 < probability < 1:
        raise ValueError("本例的候选概率必须严格位于 0 和 1 之间")
    return -sum(log(probability) if y == 1 else log(1 - probability) for y in labels)


def cross_entropy(p, q):
    if len(p) != len(q) or not p:
        raise ValueError("两个分布必须非空且类别一一对应")
    for distribution in (p, q):
        if any(value < 0 for value in distribution) or not isclose(sum(distribution), 1):
            raise ValueError("概率必须非负且总和为 1")
    result = 0.0
    for actual, predicted in zip(p, q):
        if actual == 0:
            continue  # 采用 0 log 0 = 0 的连续极限约定
        if predicted == 0:
            return inf
        result -= actual * log(predicted)
    return result


def main():
    for probability in [0.2, 0.5, 0.7, 0.9]:
        value = bernoulli_nll(LABELS, probability)
        likelihood = exp(-value)
        print(f"p={probability:.1f}，似然={likelihood:.6f}，平均负对数似然={value / len(LABELS):.6f}")
    candidates = [i / 100 for i in range(1, 100)]
    best = min(candidates, key=lambda p: bernoulli_nll(LABELS, p))
    print(f"网格搜索选择的概率：{best:.2f}")
    p, q = [0.75, 0.25], [0.5, 0.5]
    entropy = cross_entropy(p, p)
    cross = cross_entropy(p, q)
    reverse_kl = cross_entropy(q, p) - cross_entropy(q, q)
    print(f"H(p)={entropy:.6f}，H(p,q)={cross:.6f}")
    print(f"KL(p||q)={cross - entropy:.6f}，KL(q||p)={reverse_kl:.6f}")


if __name__ == "__main__":
    main()
