Di recente ho fatto i compiti in cui ho dovuto imparare un modello per la classificazione a 10 cifre MNIST. L'HW aveva del codice per le impalcature e avrei dovuto lavorare nel contesto di questo codice.
I miei compiti funzionano / superano i test, ma ora sto provando a fare tutto da zero (il mio framework nn, nessun codice di impalcature hw) e sono bloccato ad applicare il grandient di softmax nella fase di backprop, e penso anche a cosa hw il codice dell'impalcatura potrebbe non essere corretto.
L'hw mi fa usare quella che chiamano "una perdita di softmax" come l'ultimo nodo di nn. Ciò significa che, per qualche motivo, hanno deciso di unire un'attivazione di softmax con la perdita di entropia crociata in un'unica soluzione, invece di trattare la softmax come una funzione di attivazione e l'entropia crociata come una funzione di perdita separata.
La funzione di perdita hw si presenta così (minimamente modificata da me):
class SoftmaxLoss:
"""
A batched softmax loss, used for classification problems.
input[0] (the prediction) = np.array of dims batch_size x 10
input[1] (the truth) = np.array of dims batch_size x 10
"""
@staticmethod
def softmax(input):
exp = np.exp(input - np.max(input, axis=1, keepdims=True))
return exp / np.sum(exp, axis=1, keepdims=True)
@staticmethod
def forward(inputs):
softmax = SoftmaxLoss.softmax(inputs[0])
labels = inputs[1]
return np.mean(-np.sum(labels * np.log(softmax), axis=1))
@staticmethod
def backward(inputs, gradient):
softmax = SoftmaxLoss.softmax(inputs[0])
return [
gradient * (softmax - inputs[1]) / inputs[0].shape[0],
gradient * (-np.log(softmax)) / inputs[0].shape[0]
]
Come puoi vedere, in avanti fa softmax (x) e quindi attraversa la perdita di entropia.
Ma su backprop, sembra fare solo la derivata dell'entropia incrociata e non della softmax. Softmax viene lasciato come tale.
Non dovrebbe anche prendere la derivata di softmax rispetto all'input di softmax?
Supponendo che dovrebbe prendere la derivata del softmax, non sono sicuro di come questo hw superi effettivamente i test ...
Ora, nella mia implementazione da zero, ho creato softmax e cross entropy nodi separati, in questo modo (p e t stanno per predire e verità):
class SoftMax(NetNode):
def __init__(self, x):
ex = np.exp(x.data - np.max(x.data, axis=1, keepdims=True))
super().__init__(ex / np.sum(ex, axis=1, keepdims=True), x)
def _back(self, x):
g = self.data * (np.eye(self.data.shape[0]) - self.data)
x.g += self.g * g
super()._back()
class LCE(NetNode):
def __init__(self, p, t):
super().__init__(
np.mean(-np.sum(t.data * np.log(p.data), axis=1)),
p, t
)
def _back(self, p, t):
p.g += self.g * (p.data - t.data) / t.data.shape[0]
t.g += self.g * -np.log(p.data) / t.data.shape[0]
super()._back()
Come puoi vedere, la mia perdita di entropia crociata (LCE) ha lo stesso derivato di quello in hw, perché quello è il derivato della perdita stessa, senza ancora entrare nella softmax.
Ma poi, dovrei ancora fare la derivata del softmax per incatenarla con il derivato della perdita. Qui è dove rimango bloccato.
Per softmax definito come:
La derivata è generalmente definita come:
Ma ho bisogno di una derivata che si traduca in un tensore della stessa dimensione dell'input di softmax, in questo caso, batch_size x 10. Quindi non sono sicuro di come applicare quanto sopra a soli 10 componenti, poiché implica che io differirebbe per tutti gli input rispetto a tutti gli output (tutte le combinazioni) o in forma di matrice.