如何在逻辑回归的 numpy 实现中避免 NaN?

Posted

技术标签:

【中文标题】如何在逻辑回归的 numpy 实现中避免 NaN?【英文标题】:How to avoid NaN in numpy implementation of logistic regression? 【发布时间】:2019-01-24 16:25:10 【问题描述】:

编辑:我已经取得了重大进展。我当前的问题是在我最后一次编辑之后写的,可以在没有上下文的情况下回答。

我目前在 Coursera 上关注 Andrew Ng 的 Machine Learning Course,并尝试在今天实现 logistic regression。

符号:

X 是一个(m x n)-矩阵,输入变量的向量为行(m 训练样本为n-1 变量,第一列的条目处处等于 1 以表示一个常数)。 y 是预期输出样本的对应向量(具有m 条目的列向量等于01theta 是模型系数的向量(带有n 条目的行向量)

对于输入行向量x,该模型将预测sigmoid(x * theta.T) 的概率,以获得积极的结果。

这是我的 Python3/numpy 实现:

import numpy as np

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

vec_sigmoid = np.vectorize(sigmoid)

def logistic_cost(X, y, theta):
    summands = np.multiply(y, np.log(vec_sigmoid(X*theta.T))) + np.multiply(1 - y, np.log(1 - vec_sigmoid(X*theta.T)))
    return - np.sum(summands) / len(y)


def gradient_descent(X, y, learning_rate, num_iterations):
    num_parameters = X.shape[1]                                 # dim theta
    theta = np.matrix([0.0 for i in range(num_parameters)])     # init theta
    cost = [0.0 for i in range(num_iterations)]

    for it in range(num_iterations):
        error = np.repeat(vec_sigmoid(X * theta.T) - y, num_parameters, axis=1)
        error_derivative = np.sum(np.multiply(error, X), axis=0)
        theta = theta - (learning_rate / len(y)) * error_derivative
        cost[it] = logistic_cost(X, y, theta)

    return theta, cost

这个实现似乎工作正常,但我在计算物流成本时遇到了问题。在某些时候,梯度下降算法会收敛到一个非常合适的 theta,然后会发生以下情况:

对于某些输入行 X_i 与预期结果 1 X * theta.T 将变为正数并具有良好的边距(例如 23.207)。这将导致sigmoid(X_i * theta) 变为完全 1.0000(这是因为我认为失去了精度)。这是一个很好的预测(因为预期结果等于1),但这会破坏后勤成本的计算,因为np.log(1 - vec_sigmoid(X*theta.T)) 将评估为NaN。这应该不是问题,因为该术语与1 - y = 0 相乘,但是一旦出现NaN 的值,整个计算就会中断(0 * NaN = NaN)。

我应该如何在矢量化实现中处理这个问题,因为np.multiply(1 - y, np.log(1 - vec_sigmoid(X*theta.T))) 是在X 的每一行中计算的(不仅是y = 0 的位置)?

输入示例:

X = np.matrix([[1. , 0. , 0. ],
               [1. , 1. , 0. ],
               [1. , 0. , 1. ],
               [1. , 0.5, 0.3],
               [1. , 1. , 0.2]])

y = np.matrix([[0],
               [1],
               [1],
               [0],
               [1]])

那么theta, _ = gradient_descent(X, y, 10000, 10000)(是的,在这种情况下我们可以设置学习率this很大)将theta设置为:

theta = np.matrix([[-3000.04008972,  3499.97995514,  4099.98797308]])

这将导致vec_sigmoid(X * theta.T) 成为真正好的预测:

np.matrix([[0.00000000e+00],      # 0
           [1.00000000e+00],      # 1
           [1.00000000e+00],      # 1
           [1.95334953e-09],      # nearly zero
           [1.00000000e+00]])     # 1

logistic_cost(X, y, theta) 的计算结果为 NaN

编辑:

我想出了以下解决方案。我刚刚将logistic_cost 函数替换为:

def new_logistic_cost(X, y, theta):
    term1 = vec_sigmoid(X*theta.T)
    term1[y == 0] = 1
    term2 = 1 - vec_sigmoid(X*theta.T)
    term2[y == 1] = 1
    summands = np.multiply(y, np.log(term1)) + np.multiply(1 - y, np.log(term2))
    return - np.sum(summands) / len(y)

通过使用掩码,我只是在结果将乘以零的地方计算log(1)。现在log(0) 只会在梯度下降的错误实现中发生。

开放式问题:我怎样才能使这个解决方案更干净?是否有可能以更清洁的方式实现类似的效果?

【问题讨论】:

仅供参考:如果您不介意对 scipy 的依赖,可以将 vec_sigmoid 替换为 scipy.special.expit @WarrenWeckesser 我一般不介意,只是想通过自己实施一次来更好地理解算法。但这并不能解决我的问题,不是吗? 不,但它可能会更快,并且在函数的尾部更准确。我认为您可以使用scipy.special.xlog1py 来解决您的问题。即,将np.multiply(1 - y, np.log(1 - vec_sigmoid(X*theta.T))) 替换为xlog1py(1 - y, -expit(X*theta.T)))。我将此建议作为答案。 【参考方案1】:

如果您不介意使用 SciPy,可以从 scipy.special 导入 expitxlog1py

from scipy.special import expit, xlog1py

并替换表达式

np.multiply(1 - y, np.log(1 - vec_sigmoid(X*theta.T)))

xlog1py(1 - y, -expit(X*theta.T))

【讨论】:

【参考方案2】:

我知道这是一个老问题,但我遇到了同样的问题,也许它可以在将来帮助其他人,我实际上通过在附加 X0 之前对数据实施规范化来解决它。

def normalize_data(X):
    mean = np.mean(X, axis=0)
    std = np.std(X, axis=0)
    return (X-mean) / std

之后一切正常!

【讨论】:

以上是关于如何在逻辑回归的 numpy 实现中避免 NaN?的主要内容,如果未能解决你的问题,请参考以下文章

逻辑回归模型 Logistic Regression 详细推导 (含 Numpy 与PyTorch 实现)

numpy+sklearn 手动实现逻辑回归Python

为啥逻辑回归中更高的学习率会产生 NaN 成本?

处理逻辑回归的 NaN(缺失)值 - 最佳实践?

如何在 Python 中绘制逻辑回归的决策边界?

逻辑回归 - numpy.float64