
本文介绍如何通过向量化重构替代低效的 np.vectorize,将动态高斯混合函数的计算性能提升数十至百倍,适用于 MLE 优化等对速度敏感的场景。
本文介绍如何通过向量化重构替代低效的 `np.vectorize`,将动态高斯混合函数的计算性能提升数十至百倍,适用于 mle 优化等对速度敏感的场景。
在使用最大似然估计(MLE)拟合多峰高斯混合分布时,硬编码双峰模型(如 model(x, a, mu1, s1, mu2, s2))通常运行高效;但一旦转向通用化设计——支持任意数量高斯分量(n 峰)——若直接依赖 np.vectorize 包装动态函数,性能会急剧恶化(从秒级退化至分钟级)。根本原因在于:np.vectorize 并非真正向量化,而是对 Python 函数做隐式循环封装,丧失 NumPy 的底层 C 级并行优势,且额外引入参数解析、类型检查与 Python 调用开销。
核心优化思路:放弃 np.vectorize,改用原生广播 + 矩阵维度操作实现批量计算。
关键在于将输入数据 x(一维数组,长度为 N)与 n 个高斯参数(mu, sigma, a 各 n 个)构造成可广播的二维结构:让 x 以列向量形式(N×1)与参数向量(1×n)自动广播,一次性完成所有 (x_i, component_j) 的联合计算,再沿分量轴求和。
以下是重构后的高性能 generate_gaussian_mix 实现:
import numpy as np
def normal(x, mu, sigma, weight=1.0):
"""
向量化高斯 PDF(支持广播),返回 shape = x.shape + mu.shape 的数组。
"""
return weight * (1 / (np.sqrt(2 * np.pi) * sigma)) * np.exp(-0.5 * ((x - mu) / sigma) ** 2)
def generate_gaussian_mix(n):
"""
动态生成 n 峰高斯混合函数,完全避免 np.vectorize。
参数约定:*params = [mu1, sigma1, a1, mu2, sigma2, a2, ..., mu_{n-1}, sigma_{n-1}, a_{n-1}]
最后一个分量权重自动设为 1 - sum(a1..a_{n-1}),无需显式传入。
Returns:
callable: f(x: np.ndarray, *params) -> np.ndarray, shape == x.shape
"""
def gaussian_mix(x, *params):
if len(params) != 3 * n - 1:
raise ValueError(f"Expected {3*n-1} parameters for {n} components, got {len(params)}.")
params = np.asarray(params)
# 解包:每3个参数一组 → mu, sigma, weight(前n-1个)
mu = params[0::3] # shape: (n-1,)
sigma = params[1::3] # shape: (n-1,)
a = params[2::3] # shape: (n-1,)
x_col = np.asarray(x).reshape(-1, 1) # shape: (N, 1)
# 计算前 n-1 个加权高斯项:broadcast → (N, n-1),再按列求和 → (N,)
weighted_terms = normal(x_col, mu, sigma, a)
# 计算第 n 个(最后一个)高斯项:权重 = 1 - sum(a)
last_weight = 1.0 - np.sum(a)
last_term = normal(x_col, mu[-1], sigma[-1], last_weight) # shape: (N, 1)
# 合并:sum over components axis (axis=1) + last term
return np.sum(weighted_terms, axis=1) + last_term.flatten()
return gaussian_mix
关键改进点说明:
- ✅ 消除
np.vectorize:函数本身即支持x为任意形状的np.ndarray,利用 NumPy 广播自动并行计算所有x_i与所有高斯分量的组合; - ✅ 避免重复切片:
params[0::3]等操作仅执行一次,后续全部基于预提取的向量进行向量化运算; - ✅ 内存友好:
x.reshape(-1, 1)创建视图而非拷贝,normal()内部广播计算不产生中间大数组(除非x或n极大); - ✅ 权重约束健壮:显式计算
last_weight = 1 - sum(a),天然满足概率权重和为 1 的约束,避免数值不稳定。
使用示例:
# 生成 3 峰混合模型(需传入 3*3-1 = 8 个参数:mu1,sig1,a1,mu2,sig2,a2,mu3,sig3) model_3peak = generate_gaussian_mix(3) x_data = np.linspace(-5, 5, 1000) y = model_3peak(x_data, 0.0, 1.0, 0.4, 2.0, 0.8, 0.3, 1.5, 1.2) # 注意:最后一个权重隐含 # 与优化器无缝集成(如 dual_annealing) bounds = [(-3,3), (0.1,3), (0.01,0.99), (-1,4), (0.1,3), (0.01,0.99), (-2,2), (0.1,3)] result = fit_distribution_anneal(model_3peak, events, bounds)
注意事项:
- 若
n较大(如 >100),建议对normal()内部添加np.clip防止sigma过小导致exp上溢; - 在优化过程中,可对
a参数施加软约束(如a_i > 0且sum(a) ),由 <code>dual_annealing的bounds自动保障; - 如需更高性能(
n> 1000),可考虑numba.jit加速或切换至scipy.stats.norm.pdf(其内部已高度优化)。
综上,真正的向量化不在于装饰器,而在于理解数据维度与广播规则——将“循环逻辑”转化为“张量操作”,是科学计算中性能跃升的关键范式。










