我在Iris数据集上构建了一个用于二分类的逻辑回归模型(仅有两个标签)。该模型在所有指标上表现良好,并且通过了Andrew Ng提供的梯度检查。但当我将输出激活函数从“Sigmoid”改为“Softmax”,并使其适用于多类分类时,尽管性能指标仍然很好,但该模型未通过梯度检查。
深度神经网络也有同样的模式,我的numpy实现通过了二分类的梯度检查,但在多类分类中失败了。
逻辑回归(二分类):
我为特征选择了行主顺序的实现风格(行数,列数),而不是列主顺序,只是为了使其更直观易于理解和调试。
维度:X = (100, 4);权重 = (4, 1);y = (100,1)
算法实现代码(二分类):
梯度检查(二分类):
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。这证实了梯度计算是正确的。对于多类版本,我使用了上述相同的梯度计算,但将输出激活函数改为Softmax(也来自scipy),并使用axis = 1来识别样本的最高概率,因为我的实现是行主顺序的。
算法实现代码(多分类):
*Dimensions: X = (150, 4) ; Weights = (4,3) ; y = (150, 3)*import numpy as npfrom sklearn.datasets import load_iris, load_digitsfrom sklearn.preprocessing import LabelBinarizerfrom keras.losses import CategoricalCrossentropyfrom scipy.special import softmaxCCE = CategoricalCrossentropy()dataset = load_iris()lb = LabelBinarizer()X = dataset.datay = dataset.targetlb.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_traingradient = (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 = 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。这显然是一个不可接受的差异。现在这让我开始怀疑我对二分类的梯度检查代码是否可信。我还用同样的梯度计算测试了这个逻辑回归代码(也在digits数据集上),性能再次非常好(准确率、精确率、召回率均超过95%)。让我真正着迷的是,尽管模型的性能足够好,但它未通过梯度检查。就像我之前提到的神经网络的情况一样(二分类通过,多分类失败)。
我甚至尝试了Andrew Ng在Coursera课程中提供的代码,即使是那段代码也通过了二分类但未通过多分类。我似乎无法找出我的代码中有任何错误,如果有的话,这些小错误是如何在第一种情况下通过的?
我查看了这些Stack Overflow的问题,但我觉得它们的问题与我的不同:
这是我想要的:
-
关于我的二分类梯度计算和梯度检查代码是否准确的建议/更正。
-
关于我可能在多分类实现中犯错的方向的建议/一般指导。
你会得到什么:(:P)
一个20多岁的技术人员的感激之情,他认为每页文档都写得不好 🙂
更新:根据Alex的建议,我修正了一些拼写错误并添加了更多的代码行。我还意识到,在多类预测的情况下,我的近似梯度值(称为J_app)相当高(1e+2);因为我对原始梯度(称为gradient)乘以了一个因子(1./m),我的原始梯度值大约在(1e-1到1e-2)之间。
近似梯度值与原始梯度值范围的明显差异解释了为什么我得到了最终值的数量级(1e+1,0.3345)。但是,我无法弄清楚如何解决这个明显的错误。
回答:
你的所有计算似乎都是正确的。梯度检查失败的原因是CategoricalCrossentropy
来自keras
,默认使用单精度。因此,你在权重微小更新导致的最终损失差异中没有获得足够的精度。在脚本开始处添加以下几行,你通常会得到num/denom
约为1.e-9
: