为什么Logistic回归中较高的学习率会产生NaN成本?

时间:2019-02-10 22:34:26

标签: debugging machine-learning octave logistic-regression

摘要

我正在使用Octave和Ling-Spam语料库为垃圾邮件和火腿电子邮件建立分类器;我的分类方法是逻辑回归。

较高的学习率导致可以计算出成本的NaN值,但不会破坏/降低分类器本身的性能。

我的尝试

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

1-图1

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

2-图2

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

我进行了两次以上的测试,这些迭代具有更多的迭代,并且学习率略高,在这两个图中,图形均按预期的那样随着迭代的减少而下降,但准确性约为〜88-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

我的Forecast.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

1 个答案:

答案 0 :(得分:0)

应收款项:

这里的答案是一个很大的帮助:https://stackoverflow.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,从而得出对数()不返回-Inf。

在学习率为0.1且迭代为2000/1000/400的情况下进行测试时,将绘制完整图形,并且在检查时不会产生NaN值。

**Graph 1 now**

NB:以防万一有人想知道,此后精度和FScore并没有改变,因此尽管在以较高的学习率计算成本时存在错误,但精度确实是不错的。