【问题标题】:Gradient Checking works for binary but fails for multi class梯度检查适用于二进制但对多类失败
【发布时间】: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,但我觉得它们的问题与我的不同:

  1. Gradient checking in backpropogation

2.Checking the gradients when doing ...

3.problem with ann back-propagation ..

这就是我要找的东西:

  1. 建议/更正我的梯度计算和用于二进制预测的梯度检查代码是否准确。

  2. 关于多类实现可能出错的地方的建议/一般说明。

你会得到什么:(: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


【解决方案1】:

您的所有计算似乎都是正确的。梯度检查失败的原因是因为keras 中的CategoricalCrossentropy 默认以单精度运行。因此,由于权重的小更新导致的最终损失差异没有足够的精度。在脚本开头添加以下行,您将得到 num/denom 通常在 1.e-9 附近:

import keras
keras.backend.set_floatx('float64')

【讨论】:

  • 它就像一个魅力! :) ... 太感谢了 :) ! !我度过了许多不眠之夜,想着如果我不能解决这个问题,我的生活会如何改善(哈哈)......没有意识到关于 keras 后端精度的这个细节......再次感谢:D
  • 乐于助人。最初我主要怀疑梯度计算有问题。只有当您提供完全工作的可重现脚本时,我才能找到根本原因。
  • Alex,只是对这段代码的一个有趣方面感到好奇,我可能发现了一些奇怪的东西,看看你是否对此感兴趣。如果我从我的 weights 中删除 Xavier 初始化变量,就像使用 Weights = np.random.randn(X_train.shape[1]+1, num_unique_labels) 一样;并在没有任何其他更改的情况下运行代码,我一直得到 1e-1 的结果。但是,如果我将 xavier 初始化放回我的权重,我会得到 1e-9 的结果。很想知道您对此有何想法。您的解决方案没有问题,只是想看看是否还有其他问题..
  • 在某些时候,即使是双精度也不足以进行这种梯度近似。这完全取决于您拥有的计算图。在这种特殊情况下,似乎只要您对正确类别的预测非常小,权重的微小变化就不足以改变最终结果。您仍然可以通过创建 CCE = CategoricalCrossentropy(from_logits=True) 来解决此问题,并为 CCE 提供 logits(pre-softmax 值)以进行损失计算。这将提高计算的精度,您将再次看到梯度匹配。
  • 初始化在这里很重要,因为对于小的绝对权重,你将有更低的机会遇到对正确类的预测非常小的问题。因此,具有更高绝对权重的初始化经常会给你错误的结果(但并不总是取决于随机种子)。顺便说一句 - 我绝对建议始终使用 CCE 的 from_logits 版本,而不是实际使用 softmaxing 值。
猜你喜欢
  • 2011-09-27
  • 2012-08-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-09
  • 1970-01-01
  • 2013-07-24
相关资源
最近更新 更多