解决Python从零实现线性回归中的数值溢出问题

聖光之護
发布: 2025-08-20 22:32:02
原创
846人浏览过

解决python从零实现线性回归中的数值溢出问题

本文深入探讨了在Python中从零实现线性回归时可能遇到的数值溢出问题及其解决方案。当输入特征和目标值过大时,梯度下降算法中的成本函数计算和参数更新步骤容易产生超出浮点数表示范围的中间结果,导致RuntimeWarning: overflow和invalid value错误。核心解决方案在于对输入数据进行适当的缩放,以确保数值稳定性,从而使模型能够正确收敛。

1. 线性回归与梯度下降基础

线性回归是一种基本的预测模型,通过找到最佳的线性关系来拟合输入特征(features)与目标值(targets)之间的关系。其核心在于定义一个假设函数(hypothesis),一个成本函数(cost_function)来衡量模型预测的准确性,并通过梯度下降(gradientDescent)算法迭代地更新模型参数(params),以最小化成本函数。

一个典型的线性回归实现会包含以下关键组件:

  • 假设函数 (Hypothesis):通常表示为 $h_\theta(x) = \theta_0 + \theta_1 x_1 + \dots + \theta_n xn$,其中 $\theta$ 是模型参数,x 是特征向量。在向量化实现中,通常会在特征矩阵 $X$ 的第一列添加一个全1的偏置项,使 $h\theta(X) = X\theta$。
  • 成本函数 (Cost Function):最常用的是均方误差(Mean Squared Error, MSE),用于衡量预测值与真实值之间的差异。其数学表达式通常为 $J(\theta) = \frac{1}{2m} \sum{i=1}^m (h\theta(x^{(i)}) - y^{(i)})^2$,其中 $m$ 是样本数量。
  • 梯度下降 (Gradient Descent):一种优化算法,通过沿着成本函数梯度的负方向迭代更新参数,以找到成本函数的局部最小值。参数更新规则为 $\theta_j := \theta_j - \alpha \frac{\partial}{\partial \theta_j} J(\theta)$,其中 $\alpha$ 是学习率。

以下是一个从零实现的线性回归类的基本结构:

import numpy as np

class LinearRegression:

    def __init__(
    self, 
    features: np.ndarray[np.float64],
    targets: np.ndarray[np.float64],
    ) -> None:
        # 在特征矩阵X前添加一列1,用于偏置项
        self.features = np.concatenate((np.ones((features.shape[0], 1)), features), axis=1)
        self.targets = targets
        # 随机初始化参数
        self.params = np.random.randn(features.shape[1] + 1)
        self.num_samples = features.shape[0]
        self.num_feats = features.shape[1]
        self.costs = [] # 用于记录每次迭代的成本

    def hypothesis(self) -> np.ndarray[np.float64]:
        # 假设函数:X * theta
        return np.dot(self.features, self.params)

    def cost_function(self) -> np.float64:
        # 均方误差成本函数 J(theta) = 1/(2m) * sum((h(x) - y)^2)
        pred_vals = self.hypothesis()
        return (1 / (2 * self.num_samples)) * np.dot((pred_vals - self.targets).T, pred_vals - self.targets)

    def update(self, alpha: np.float64) -> None:
        # 参数更新:theta = theta - alpha/m * (X.T @ (h(x) - y))
        self.params = self.params - (alpha / self.num_samples) * (self.features.T @ (self.hypothesis() - self.targets))

    def gradientDescent(self, alpha: np.float64, threshold: np.float64, max_iter: int) -> None:
        converged = False
        counter = 0
        while not converged:
            counter += 1
            curr_cost = self.cost_function()
            self.costs.append(curr_cost)
            self.update(alpha) # 更新参数
            new_cost = self.cost_function()
            # 判断收敛条件:成本函数变化小于阈值或达到最大迭代次数
            if abs(new_cost - curr_cost) < threshold:
                converged = True
            if counter > max_iter:
                converged = True
登录后复制

2. 识别数值溢出问题

在上述线性回归实现中,当输入数据(features和targets)的数值范围过大时,梯度下降过程极易出现数值溢出(overflow)和无效值(invalid value)警告。

立即学习Python免费学习笔记(深入)”;

例如,如果使用以下方式初始化和运行模型:

# 使用大范围的输入数据
regr = LinearRegression(features=np.linspace(0, 1000, 200, dtype=np.float64).reshape((20, 10)), 
                        targets=np.linspace(0, 200, 20, dtype=np.float64))
regr.gradientDescent(0.1, 1e-3, 1e+3)
regr.cost_function()
登录后复制

可能会遇到以下运行时警告:

RuntimeWarning: overflow encountered in scalar power
RuntimeWarning: invalid value encountered in scalar subtract
RuntimeWarning: overflow encountered in matmul
登录后复制

这些警告表明在计算过程中产生了超出float64数据类型表示范围的巨大数值(如inf或-inf),或者由这些无限值导致的无效计算结果(如inf - inf产生NaN)。

原因分析:

腾讯智影-AI数字人
腾讯智影-AI数字人

基于AI数字人能力,实现7*24小时AI数字人直播带货,低成本实现直播业务快速增增,全天智能在线直播

腾讯智影-AI数字人73
查看详情 腾讯智影-AI数字人
  1. 成本函数中的平方项:在cost_function中,计算 (pred_vals - self.targets) 的平方和。如果 pred_vals 和 self.targets 本身就很大,它们的差值也可能很大,其平方值会迅速增长,远超float64的表示上限(约 $10^{308}$),导致溢出。
  2. 梯度更新中的矩阵乘法:在update方法中,self.features.T @ (self.hypothesis() - self.targets) 涉及特征矩阵与误差项的乘积。如果特征值本身很大,或者误差项很大(由于预测值与目标值差距大),矩阵乘法的结果也会非常大,同样容易导致溢出。
  3. 累积效应:一旦发生溢出,参数 self.params 可能会被更新为 inf 或 NaN。随后的迭代中,任何涉及这些参数的计算都会继续传播 inf 或 NaN,导致模型无法收敛。

3. 解决方案:数据缩放

解决数值溢出问题的最有效方法是对输入数据进行缩放(Scaling)。通过将特征和目标值转换到一个较小的、标准化的范围,可以显著提高数值稳定性,并帮助梯度下降算法更有效地收敛。

常见的缩放方法包括:

  • Min-Max 归一化 (Normalization):将数据线性缩放到一个固定范围,通常是 [0, 1] 或 [-1, 1]。 $X{norm} = \frac{X - X{min}}{X{max} - X{min}}$
  • 标准化 (Standardization / Z-score Normalization):将数据转换成均值为0,标准差为1的分布。 $X_{std} = \frac{X - \mu}{\sigma}$ 其中 $\mu$ 是均值,$\sigma$ 是标准差。

对于本例中的问题,简单地将输入数据除以一个较大的常数,使其数值范围缩小,即可有效避免溢出。

修正后的代码示例:

# 将特征和目标值缩小1000倍
regr = LinearRegression(features=np.linspace(0, 1000, 200, dtype=np.float64).reshape((20, 10))/1000, 
                        targets=np.linspace(0, 200, 20, dtype=np.float64)/1000)
regr.gradientDescent(0.1, 1e-3, 1e+3)
final_cost = regr.cost_function()
print(f"最终成本函数值: {final_cost}")
# 示例输出:最终成本函数值: 0.00474225348416323
登录后复制

通过将 features 和 targets 都除以1000,它们的数值范围显著缩小,从而避免了在成本函数和梯度更新计算中出现中间结果溢出。模型现在能够正常运行,并收敛到一个合理的成本函数值。

4. 注意事项与最佳实践

  • 数据预处理的重要性:数据缩放是机器学习流程中至关重要的一步,尤其是在使用基于梯度下降的优化算法时。它不仅能解决数值稳定性问题,还能加速模型收敛,并防止某些特征因数值范围过大而主导模型训练。
  • 选择合适的缩放方法:Min-Max 归一化适用于已知数据边界的情况,而标准化则更适用于数据分布未知或存在异常值的情况。对于线性回归,标准化通常是更稳健的选择。
  • 学习率(alpha)的选择:即使数据经过缩放,过大的学习率也可能导致梯度爆炸和模型发散。因此,选择一个合适的学习率至关重要。通常需要通过实验或交叉验证来确定最佳学习率。
  • 收敛条件:在 gradientDescent 方法中,使用成本函数的变化量作为收敛条件 (abs(new_cost - curr_cost) < threshold) 是一个好的实践。同时设置最大迭代次数 (max_iter) 可以防止无限循环。
  • 调试数值问题:在实现机器学习算法时,如果遇到 NaN 或 inf 值,首先应检查输入数据的数值范围,然后逐步检查中间计算结果,以定位溢出或无效操作发生的位置。
  • 使用成熟库:对于生产环境或更复杂的任务,推荐使用像 Scikit-learn 这样的成熟机器学习库,它们内置了数据预处理工具和经过优化的算法实现,能够自动处理许多数值稳定性问题。

总结

在Python中从零实现线性回归等机器学习算法时,数值稳定性是一个不容忽视的关键问题。当输入数据数值范围过大时,计算过程中可能发生浮点数溢出,导致模型训练失败。通过对数据进行适当的缩放(如归一化或标准化),可以将数值保持在可管理的范围内,从而有效解决溢出问题,确保梯度下降算法的稳定运行和模型的正确收敛。理解并应用数据预处理技术是构建健壮机器学习模型的基石。

以上就是解决Python从零实现线性回归中的数值溢出问题的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号