【发布时间】:2020-10-01 20:34:45
【问题描述】:
我已经为 Iris 数据集(只有两个标签)上的二进制分类建立了一个逻辑回归模型。这个模型在所有指标上都取得了良好的性能,并且还通过了 Andrew Ng 给出的梯度检查。但是当我将输出激活从“Sigmoid”更改为“Softmax”并使其适用于多类分类时,即使性能指标非常好,该模型也无法通过梯度检查。
深度神经网络的模式相同,我的 numpy 实现通过了二进制分类的梯度检查,但多类失败。
逻辑回归(二进制):
我为我的功能(行数,列数)选择了以行为主的实现风格,但没有选择以列为主的风格,只是为了便于理解和调试。
尺寸: X = (100, 4) ;权重 = (4, 1); y = (100,1)
算法实现代码(二进制):
import numpy as np
from sklearn.datasets import load_iris, load_digits
from sklearn.preprocessing import LabelBinarizer
from sklearn.metrics import log_loss
from keras.losses import CategoricalCrossentropy
from scipy.special import softmax
def sigmoid(x):
return ( (np.exp(x)) / (1 + np.exp(x) ) )
dataset = load_iris()
lb = LabelBinarizer() # Not used for binary classification
X = dataset.data
y = dataset.target
data = np.concatenate((X[:100],y[:100].reshape(-1,1)), axis = 1)
np.random.shuffle(data)
X_train = data[:, :-1]
X_b = np.c_[np.ones((X_train.shape[0] , 1)), X_train]
y_train = data[:, -1].reshape(-1,1)
num_unique_labels = len( np.unique(y_train) )
Weights = np.random.randn(X_train.shape[1]+1, num_unique_labels-1)* np.sqrt(1./ (X_train.shape[1]+1) )
m = X_b.shape[0]
yhat = sigmoid( np.dot(X_b, Weights))
loss = log_loss(y_train, yhat)
error = yhat - y_train
gradient = (1./m) * ( X_b.T.dot(error) )
梯度检查(二进制):
grad = gradient.reshape(-1,1)
Weights_delta = Weights.reshape(-1,1)
num_params = Weights_delta.shape[0]
JP = np.zeros((num_params,1))
JM = np.zeros((num_params,1))
J_app = np.zeros((num_params,1))
ep = float(1e-7)
for i in range(num_params):
Weights_add = np.copy(Weights_delta)
Weights_add[i] = Weights_add[i] + ep
Z_add = sigmoid(np.dot(X_b, Weights_add.reshape(X_train.shape[1]+1,num_unique_labels-1)))
JP[i] = log_loss( y_train, Z_add)
Weights_sub = np.copy(Weights_delta)
Weights_sub[i] = Weights_sub[i] - ep
Z_sub = sigmoid(np.dot(X_b, Weights_sub.reshape(X_train.shape[1]+1,num_unique_labels-1)))
JM[i] = log_loss( y_train, Z_sub)
J_app[i] = (JP[i] - JM[i]) / (2*ep)
num = np.linalg.norm(grad - J_app)
denom = np.linalg.norm(grad) + np.linalg.norm(J_app)
num/denom
这会产生一个值 (num/denom):8.244172628899919e-10。这证实了梯度计算是合适的。对于 multi_class 版本,我使用了与上面相同的梯度计算,但将输出激活更改为 Softmax(也取自 scipy),并使用 axis = 1 来识别样本的最高概率,因为我是行主要实现。
算法实现代码(multi_class):
*Dimensions: X = (150, 4) ; Weights = (4,3) ; y = (150, 3)*
import numpy as np
from sklearn.datasets import load_iris, load_digits
from sklearn.preprocessing import LabelBinarizer
from keras.losses import CategoricalCrossentropy
from scipy.special import softmax
CCE = CategoricalCrossentropy()
dataset = load_iris()
lb = LabelBinarizer()
X = dataset.data
y = dataset.target
lb.fit(y)
data = np.concatenate((X,y.reshape(-1,1)), axis = 1)
np.random.shuffle(data)
X_train = data[:, :-1]
X_b = np.c_[np.ones((X_train.shape[0] , 1)), X_train]
y_train = lb.transform(data[:, -1]).reshape(-1,3)
num_unique_labels = len( np.unique(y) )
Weights = np.random.randn(X_train.shape[1]+1, num_unique_labels) * np.sqrt(1./ (X_train.shape[1]+1) )
m = X_b.shape[0]
yhat = softmax( np.dot(X_b, Weights), axis = 1)
cce_loss = CCE(y_train, yhat).numpy()
error = yhat - y_train
gradient = (1./m) * ( X_b.T.dot(error) )
梯度检查(multi_class):
grad = gradient.reshape(-1,1)
Weights_delta = Weights.reshape(-1,1)
num_params = Weights_delta.shape[0]
JP = np.zeros((num_params,1))
JM = np.zeros((num_params,1))
J_app = np.zeros((num_params,1))
ep = float(1e-7)
for i in range(num_params):
Weights_add = np.copy(Weights_delta)
Weights_add[i] = Weights_add[i] + ep
Z_add = softmax(np.dot(X_b, Weights_add.reshape(X_train.shape[1]+1,num_unique_labels)), axis = 1)
JP[i] = CCE( y_train, Z_add).numpy()
Weights_sub = np.copy(Weights_delta)
Weights_sub[i] = Weights_sub[i] - ep
Z_sub = softmax(np.dot(X_b, Weights_sub.reshape(X_train.shape[1]+1,num_unique_labels)), axis = 1)
JM[i] = CCE( y_train, Z_sub).numpy()
J_app[i] = (JP[i] - JM[i]) / (2*ep)
num = np.linalg.norm(grad - J_app)
denom = np.linalg.norm(grad) + np.linalg.norm(J_app)
num/denom
这产生了一个值:0.3345。这显然是不可接受的差异。现在这让我想知道我是否可以首先信任我的二进制标签的梯度检查代码。我已经在数字数据集上测试了这个逻辑回归代码(具有相同的梯度计算),性能再次非常好(>95% 准确率、精度、召回率)。真正让我着迷的是,即使模型的性能足够好,它也没有通过梯度检查。与我之前提到的神经网络的情况相同(二进制通过,多类失败)。
我什至尝试了 Andrew Ng 作为他的 coursera 课程的一部分提供的代码,即使该代码通过二进制和多类失败。我似乎无法弄清楚我的代码在哪里有任何错误,如果他们有小错误,他们怎么能在第一种情况下通过?
我查看了这些 SO,但我觉得它们的问题与我的不同:
2.Checking the gradients when doing ...
3.problem with ann back-propagation ..
这就是我要找的东西:
建议/更正我的梯度计算和用于二进制预测的梯度检查代码是否准确。
关于多类实现可能出错的地方的建议/一般说明。
你会得到什么:(:P)
感谢 20 多岁的技术人员,他们认为每个文档页面都写得不好:)
更新:按照 Alex 的建议,更正了一些拼写错误并添加了更多代码行。我还意识到,在多类预测的情况下,我的近似梯度值(名为 J_app )相当高( 1e+2 );因为我将 (1./m) 的因子乘以我的原始渐变(名称为渐变),所以我的原始渐变值大约是 (1e-1 到 1e-2)。
近似梯度值范围与我的原始梯度的明显差异解释了为什么我得到的最终值大约为 (1e+1, 0.3345 )。但是,我想不通的是,我该如何解决这个看似明显的错误。
【问题讨论】:
-
您应该在此处发布您的实际代码,而不是带有“#几乎相同的东西”、“#softmax from scipy”的版本。很明显,例如multi_class 案例中的权重不应该是 (4, 1) 形状,而是 (4, 3) 如果你有 3 个类。我会说你在那部分犯了一个错误,但是你的代码也有
Weights_add.reshape(..., 3),所以可能在你的实际代码中你确实有形状权重(4、3)。因此,如果您希望有人帮助您 - 编辑问题以获得您的实际代码,这将大大增加有人帮助您的机会。 -
@AlexanderPivovarov 感谢您的反馈。我同意我的第一个版本的代码中有很多错别字,而且我通过代码提供的信息并不明显,而且很难调试问题。所以,我现在做了这些改变。如果您也对可能出现的问题提供见解甚至是一般性指导,而不是严厉责备和抱怨,我会更加感激。
-
我确实尽力帮助了你。我加载了您的代码并进行了所有更改,以便可以运行您在原始问题中提供的 sn-ps。在做了所有这些之后,我仍然无法真正看到你的计算到底在做什么(因为你没有真正展示重要的部分)——你要求帮助调试的计算。所以我确实去请您添加有关该问题的更多详细信息。但当然,从你的角度来看,我只是在抱怨。
-
@AlexanderPivovarov ,感谢您添加我的代码中缺失的部分并试图找出我哪里出错了。正如我所说,通过添加更多相关信息,我应该在创建问题时更加关注。感谢您的时间:)!
标签: python machine-learning neural-network logistic-regression gradient-descent