直接用 np.arccos 算夹角易出错,因浮点误差使 cosθ 超出 [-1,1] 导致 nan;须先 clip 截断并检查零向量,推荐用 np.einsum 点积+手动 arccos。

为什么直接用 np.arccos 算夹角容易出错
因为 np.arccos 的输入必须在 [-1, 1] 区间内,而浮点误差可能导致点积除以模长的结果略大于 1 或小于 -1(比如 1.0000000000000002),触发 RuntimeWarning: invalid value encountered in arccos,返回 nan。
常见错误场景:两个几乎平行或反向的向量、高维稀疏向量、归一化不彻底的向量。
- 务必先对结果做截断:
np.clip(dot_product / (norm_a * norm_b), -1.0, 1.0) - 避免单独计算分母再相除——若任一向量为零向量,会触发除零警告;应提前检查
norm_a == 0 or norm_b == 0 - 不要用
np.linalg.norm(x, axis=1)对行向量批量算模长时漏掉keepdims=True,否则广播失败
单对向量夹角:用 np.einsum 比 np.dot 更稳
np.einsum 在处理高维向量(如 shape=(n,) 或 (1, n))时更少隐式转置,且支持原生广播,减少 reshape 错误。
推荐写法:
立即学习“Python免费学习笔记(深入)”;
def vector_angle(a, b):
a, b = np.asarray(a), np.asarray(b)
norm_a, norm_b = np.linalg.norm(a), np.linalg.norm(b)
if norm_a == 0 or norm_b == 0:
return np.nan
cos_theta = np.clip(np.einsum('i,i->', a, b) / (norm_a * norm_b), -1.0, 1.0)
return np.arccos(cos_theta)
注意:np.einsum('i,i->', a, b) 等价于点积但不依赖向量维度顺序,比 a @ b 或 np.dot(a, b) 对一维/二维输入更鲁棒。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
批量计算多对向量夹角:避免 Python 循环
当有 N 对向量(如 A.shape == B.shape == (N, D)),用循环调 vector_angle 是最慢的。应向量化点积和模长计算。
- 点积用
np.einsum('ij,ij->i', A, B)(逐行点积) - 模长用
np.linalg.norm(A, axis=1, keepdims=False)和同理算B,注意别漏keepdims=False保证结果为 (N,) 形状 - 除法前仍需
np.clip(..., -1.0, 1.0),且要对分母加 epsilon 防零(np.finfo(float).eps)
示例关键片段:
dot_ab = np.einsum('ij,ij->i', A, B)
norm_a = np.linalg.norm(A, axis=1)
norm_b = np.linalg.norm(B, axis=1)
denom = norm_a * norm_b + np.finfo(float).eps
cos_theta = np.clip(dot_ab / denom, -1.0, 1.0)
angles = np.arccos(cos_theta)
要不要用 scipy.spatial.distance.cosine?
它返回的是 1 − cosθ(即余弦距离),不是角度本身;而且内部没做 clip,遇到浮点越界照样报 nan —— 实测在某些 NumPy 版本下比手写还脆弱。
如果你只需要余弦相似度(cosθ),用 sklearn.metrics.pairwise.cosine_similarity 更合适,它做了数值保护;但若明确要弧度/角度值,自己控制 arccos + clip 更可靠。
真正省事又安全的做法:只封装一次带 clip 和零向量检查的函数,复用它。别迷信“封装好就一定健壮”。

















