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

Posted

技术标签:

【中文标题】为啥逻辑回归中更高的学习率会产生 NaN 成本?【英文标题】:Why do higher learning rates in logistic regression produce NaN costs?为什么逻辑回归中更高的学习率会产生 NaN 成本? 【发布时间】:2019-07-04 10:19:27 【问题描述】:

总结

我正在使用 Octave 和 Ling-Spam 语料库构建垃圾邮件与非垃圾邮件的分类器;我的分类方法是逻辑回归。

更高的学习率会导致计算 NaN 值作为代价,但它不会破坏/降低分类器本身的性能。

我的尝试

注意:我的数据集已使用均值归一化进行归一化。 在尝试选择我的学习率时,我从 0.1 和 400 次迭代开始。这导致了以下情节:

1 - 图表 1

当他的线条在几次迭代后完全消失时,这是由于产生了一个 NaN 值;我认为这会导致参数值损坏,从而导致准确度下降,但在检查准确度时,我发现测试集上的准确度为 95%(这意味着梯度下降显然仍在运行)。我检查了学习率和迭代的不同值,以查看图表如何变化:

2 - 图表 2

线条不再消失,意味着没有 NaN 值,但准确度为 87%,大大降低了。

我又做了两个测试,迭代次数更多,学习率略高,在这两个测试中,图表都按预期随着迭代次数减少,但准确度约为 86-88%。那里也没有 NaN。

我意识到我的数据集有偏差,只有 481 封垃圾邮件和 2412 封垃圾邮件。因此,我计算了这些不同组合中的每一个的 FScore,希望发现后面的组合具有更高的 FScore,而准确性是由于偏差造成的。情况也并非如此 - 我已经在一个表格中总结了我的结果:

3 - 表格

所以没有过拟合,偏斜似乎不是问题;我现在不知道该怎么办!

我唯一能想到的就是我对准确率和 FScore 的计算是错误的,或者我对“消失”行的初始调试是错误的。

编辑:这个问题的关键在于为什么 NaN 值会出现在那些选择的学习率上。所以我降低学习率的临时解决方案并没有真正回答我的问题 - 我一直认为更高的学习率只是发散而不是收敛,产生 NaN 值。

我的代码

我的 main.m 代码(从文件中获取数据集):

numRecords = length(labels);

trainingSize = ceil(numRecords*0.6);
CVSize = trainingSize + ceil(numRecords*0.2);

featureData = normalise(data);

featureData = [ones(numRecords, 1), featureData];

numFeatures = size(featureData, 2);

featuresTrain = featureData(1:(trainingSize-1),:);
featuresCV = featureData(trainingSize:(CVSize-1),:);
featuresTest = featureData(CVSize:numRecords,:);

labelsTrain = labels(1:(trainingSize-1),:);
labelsCV = labels(trainingSize:(CVSize-1),:);
labelsTest = labels(CVSize:numRecords,:);

paramStart = zeros(numFeatures, 1);

learningRate = 0.0001;
iterations = 400;

[params] = gradDescent(featuresTrain, labelsTrain, learningRate, iterations, paramStart, featuresCV, labelsCV);

threshold = 0.5;
[accuracy, precision, recall] = predict(featuresTest, labelsTest, params, threshold);
fScore = (2*precision*recall)/(precision+recall);

我的 gradDescent.m 代码:​​

function [optimParams] = gradDescent(features, labels, learningRate, iterations, paramStart, featuresCV, labelsCV)

x_axis = [];
J_axis = [];
J_CV = [];

params = paramStart;

for i=1:iterations,
  [cost, grad] = costFunction(features, labels, params);
  [cost_CV] = costFunction(featuresCV, labelsCV, params);

  params = params - (learningRate.*grad);

  x_axis = [x_axis;i];
  J_axis = [J_axis;cost];
  J_CV = [J_CV;cost_CV];
endfor

graphics_toolkit("gnuplot")
plot(x_axis, J_axis, 'r', x_axis, J_CV, 'b');
legend("Training", "Cross-Validation");
xlabel("Iterations");
ylabel("Cost");
title("Cost as a function of iterations");

optimParams = params;
endfunction

我的 costFunction.m 代码:​​

function [cost, grad] = costFunction(features, labels, params)
  numRecords = length(labels);

  hypothesis = sigmoid(features*params);

  cost = (-1/numRecords)*sum((labels).*log(hypothesis)+(1-labels).*log(1-hypothesis));

  grad = (1/numRecords)*(features'*(hypothesis-labels));
endfunction

我的 predict.m 代码:​​

function [accuracy, precision, recall] = predict(features, labels, params, threshold)
numRecords=length(labels);

predictions = sigmoid(features*params)>threshold;

correct = predictions == labels;

truePositives = sum(predictions == labels == 1);
falsePositives = sum((predictions == 1) != labels);
falseNegatives = sum((predictions == 0) != labels);

precision = truePositives/(truePositives+falsePositives);
recall = truePositives/(truePositives+falseNegatives);

accuracy = 100*(sum(correct)/numRecords);
endfunction

【问题讨论】:

只是吹毛求疵,这不是具有线性内核的 SVM。您正在执行逻辑回归。 啊,谢谢你的挑剔!我必须承认我不太确定两者之间的差异,所以我很感激更正。我已经编辑了我的答案以反映这一点:)。如果有任何其他我遗漏/出错的事情,请随时编辑。 没问题。差异超出了这个范围,但关键的区别在于逻辑回归和 SVM 如何处理具有完全可分离数据的案例。逻辑回归无法收敛,而 SVM 会找到最佳的超平面,从而找到最佳的支持向量。此外,两种方法之间的成本函数是不同的。 SVM 需要使用二次规划进行约束优化以最小化成本函数,而逻辑回归可以使用简单的梯度下降。您正在优化的是二元交叉熵,而不是具有线性内核的 SVM。 我还注意到您降低了学习率,同时增加了迭代次数,注意到准确性下降。您是否尝试过在增加迭代次数的同时保持学习率不变?您似乎同时在此处更改了两个超参数,因此尚不清楚导致准确性降低的根本原因是什么。此外,“消失”很可能是由于成本函数产生了NaN 值。如果分类中产生的概率(即 sigmoid 值)接近于零(即log(0))(续),就会发生这种情况。 您可以通过查看以下任一是否为真来检查成本函数值中是否有NaN 值:any(isnan(J_axis))any(isnan(J_CV))。如果其中任何一个的计算结果为true,那么这是如何评估成本函数的问题。请参阅这篇关于如何修改交叉熵成本函数以避免NaN 值的帖子:***.com/questions/35419882/… 【参考方案1】:

信用到期:

一个很大的帮助是这个答案:https://***.com/a/51896895/8959704 所以这个问题有点重复,但我没有意识到,一开始并不明显......我会尽力尝试解释为什么该解决方案也有效,以避免简单地复制答案。

解决方案:

问题实际上是我的数据中出现的 0*log(0) = NaN 结果。为了解决这个问题,在我计算成本时,它变成了:

cost = (-1/numRecords)*sum((labels).*log(hypothesis)+(1-labels).*log(1-hypothesis+eps(numRecords, 1)));

(请参阅有关变量值等的问题,当仅此行更改时包含其余部分似乎是多余的)

说明:

eps()函数定义如下:

返回一个标量、矩阵或 N 维数组,其元素都是 eps,机器精度。

更准确地说,eps 是任意两个相邻的 相对间距 机器的浮点系统中的数字。这个数字是 显然依赖于系统。在支持 IEEE 浮动的机器上 点算术,双精度的 eps 约为 2.2204e-16 和 1.1921e-07 用于单精度。

当使用多个参数调用时,前两个参数是 作为行数和列数以及任何进一步的参数 指定额外的矩阵维度。可选参数类 指定返回类型,可以是“double”或“single”。

所以这意味着将此值添加到由 Sigmoid 函数计算的值(之前非常接近 0 以至于被视为 0)将意味着它是最接近 0 的值而不是 0,从而使 log () 不返回 -Inf。

当以 0.1 的学习率和 2000/1000/400 的迭代次数进行测试时,绘制了全图,并且在检查时没有产生 NaN 值。

注意:以防万一有人想知道,此后准确度和 FScores 没有改变,因此尽管在计算具有更高学习率的成本时出现错误,但准确度确实很好。

【讨论】:

以上是关于为啥逻辑回归中更高的学习率会产生 NaN 成本?的主要内容,如果未能解决你的问题,请参考以下文章

对于某些 theta 值,逻辑回归的成本函数输出 NaN

可以为逻辑回归定义自己的成本函数吗?

满足 'firebase_admob' 的规范,但它们需要 Flutter 中更高的最小部署目标

为啥逻辑回归的代价函数有对数表达式?

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

为啥 sklearn 中逻辑回归的等效 class_weights 会产生不同的结果?