为啥逻辑回归中更高的学习率会产生 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 成本?的主要内容,如果未能解决你的问题,请参考以下文章