通过率 100% · 提交 1 · 通过 1
这道 OJ 的配套动画
稳定 Softmax 与交叉熵 · 对应本题的思路、过程和代码
分类模型输出了 r 行、每行 c 个类别分数(logits),以及每行的真实类别。对每一行:先减去该行最大值,再计算 softmax 概率;随后计算全部行的平均交叉熵(自然对数)。注意:当分数跨度很大时,真实类别的概率可能下溢为 0,交叉熵必须用 log-sum-exp 的方式计算,即 −ln p = ln(Σexp(z−max)) − (z_label−max),不能对已下溢的概率取对数。
这类题属于算法机考高频题型中「华为 AI 岗 / Softmax」方向的高频题型,通常考察对「华为 AI 岗 / Softmax」的建模能力与边界条件处理。掌握本题的解题思路后,可举一反三应对同类真题方向,稳步提升机考通过率。
第一行输入 r c,分别为行数和类别数。接下来 r 行,每行 c 个实数 logits。最后一行输入 r 个整数,表示每行的真实类别(0 开始编号)。
输出 r 行,每行 c 个保留四位小数的概率,用空格分隔。最后一行输出保留六位小数的平均交叉熵。绝对值小于对应精度一半时输出 0.0000(或 0.000000)。
1 <= r <= 300;1 <= c <= 50;logits 为绝对值不超过 1000 的实数,最多三位小数。
示例 1
输入示例
1 3 1 2 3 2
输出示例
0.0900 0.2447 0.6652 0.407606
单行三分类,交叉熵为 −ln(0.6652…)。
示例 2
输入示例
1 3 1000 999 0 0
输出示例
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。
训练营动画视频讲解
登录后可查看你在本题的历史提交,以及每次的各用例通过情况。
0.7311 0.2689 0.0000 0.313262
大数行必须先减最大值再取指数,否则溢出。
时间限制 2000 ms · 内存限制 256 MB
这些是真正决定能不能 AC、但通用题解里常被略过的点。
对每行 logits 先减去该行最大值 m,再计算 exp(z_j-m) 并归一化,得到数值稳定的 Softmax 概率。真实类别的交叉熵不要从已经格式化或可能下溢的概率反求,而应直接使用 log-sum-exp 公式:ln(Σexp(z_j-m))-(z_label-m)。累加各行交叉熵后除以行数。
同一行的所有指数项同时乘以 exp(-m) 不改变归一化后的概率,因此减最大值与原始 Softmax 数学等价。交叉熵公式由 -ln(exp(z_label)/Σexp(z_j)) 直接变形而来,避免了先得到零概率再取对数的问题,所以在大分差下仍保持有限结果。
每个元素参与常数次计算,时间复杂度 O(rc)。逐行处理时,除输出外只需 O(c) 临时空间。
对数使用自然对数;概率保留四位、平均交叉熵保留六位。求和顺序按下标递增,输出前把对应精度下的负零规范为正零。
# 每行减 max 再 exp 求 softmax;交叉熵不对概率取对数,用 log-sum-exp:−ln p = ln Σexp(z−max) − (z_label−max)。
# 自然对数;概率四位、平均交叉熵六位,注意负零钳制。
import math
import sys
def fmt(value: float, digits: int, eps: float) -> str:
if abs(value) < eps:
value = 0.0
return f"{value:.{digits}f}"
def solve() -> None:
data = sys.stdin.buffer.read().split()
if not data:
return
it = iter(data)
r = int(next(it))
c = int(next(it))
rows = [[float(next(it)) for _ in range(c)] for _ in range(r)]
labels = [int(next(it)) for _ in range(r)]
out = []
ce_sum = 0.0
for row, label in zip(rows, labels):
mx = row[0]
for v in row:
if v > mx:
mx = v
denom = 0.0
exps = []
for v in row:
e = math.exp(v - mx)
denom += e
exps.append(e)
out.append(" ".join(fmt(e / denom, 4, 0.00005) for e in exps))
ce_sum += -((row[label] - mx) - math.log(denom))
out.append(fmt(ce_sum / r, 6, 0.0000005))
print("\n".join(out))
if __name__ == "__main__":
solve()