Я пытался решить эту проблему в течение нескольких дней, но безуспешно. Я реализовал простую нейронную сеть с одним скрытым слоем с нуля, просто для моего собственного понимания. Я успешно реализовал его с помощью sigmoid, tanh и relu активаций для бинарных классификаций, и сейчас пытаюсь использовать softmax на выходе для мультиклассовых классификаций.
В каждом уроке, который я встречал для реализации softmax, включая заметки моего лектора, производная от кросс-энтропийной ошибки softmax на выходном слое упрощается до predictions - labels
, таким образом, по существу вычитая 1 из прогнозируемого значения в положение истинного ярлыка.
Однако я обнаружил, что если это использовать, то ошибка моей сети будет постоянно увеличиваться до тех пор, пока она не сойдет к постоянному прогнозированию одного случайного класса со 100%, а другого - с 0%. Интересно, что если я изменю это значение на labels - predictions
, оно отлично работает в моем простом тесте изучения двоичной функции XOR ниже. К сожалению, если я затем попытаюсь применить ту же сеть к более сложной проблеме (рукописные буквы - 26 классов), она снова сходится к выводу одного класса с вероятностью 100% очень быстро, когда либо labels - predictions
, либо predictions - labels
б.
Я понятия не имею, почему эта неправильная строка кода работает для простой двоичной классификации, но не для классификации со многими классами. Я предполагаю, что в моем коде есть что-то обратное, и это неправильное изменение по существу отменяет эту другую ошибку, но я не могу найти, где это может быть.
import numpy as np
class MLP:
def __init__(self, numInputs, numHidden, numOutputs):
# MLP architecture sizes
self.numInputs = numInputs
self.numHidden = numHidden
self.numOutputs = numOutputs
# MLP weights
self.IH_weights = np.random.rand(numInputs, numHidden) # Input -> Hidden
self.HO_weights = np.random.rand(numHidden, numOutputs) # Hidden -> Output
# Gradients corresponding to weight matrices computed during backprop
self.IH_w_gradients = np.zeros_like(self.IH_weights)
self.HO_w_gradients = np.zeros_like(self.HO_weights)
def sigmoid(self, x):
return 1 / (1 + np.exp(-x))
def sigmoidDerivative(self, x):
return x * (1 - x)
def softmax(self, x):
# exps = np.exp(x)
exps = np.exp(x - np.max(x)) # Allows for large values
return exps / np.sum(exps)
def forward(self, input):
self.I = np.array(input).reshape(1, self.numInputs) # (numInputs, ) -> (1, numInputs)
self.H = self.I.dot(self.IH_weights)
self.H = self.sigmoid(self.H)
self.O = self.H.dot(self.HO_weights)
self.O = self.softmax(self.O)
self.O += 1e-10 # Allows for log(0)
return self.O
def backwards(self, label):
self.L = np.array(label).reshape(1, self.numOutputs) # (numOutputs, ) -> (1, numOutputs)
self.O_error = - np.sum([t * np.log(y) for y, t in zip(self.O, self.L)])
# self.O_delta = self.O - self.L # CORRECT (not working)
self.O_delta = self.L - self.O # INCORRECT (working)
self.H_error = self.O_delta.dot(self.HO_weights.T)
self.H_delta = self.H_error * self.sigmoidDerivative(self.H)
self.IH_w_gradients += self.I.T.dot(self.H_delta)
self.HO_w_gradients += self.H.T.dot(self.O_delta)
return self.O_error
def updateWeights(self, learningRate):
self.IH_weights += learningRate * self.IH_w_gradients
self.HO_weights += learningRate * self.HO_w_gradients
self.IH_w_gradients = np.zeros_like(self.IH_weights)
self.HO_w_gradients = np.zeros_like(self.HO_weights)
data = [
[[0, 0], [1, 0]],
[[0, 1], [0, 1]],
[[1, 0], [0, 1]],
[[1, 1], [1, 0]]
]
mlp = MLP(2, 5, 2)
numEpochs = 10000
learningRate = 0.1
for epoch in range(numEpochs):
epochLosses, epochAccuracies = [], []
for i in range(len(data)):
prediction = mlp.forward(data[i][0])
# print(prediction, "\n")
label = data[i][1]
loss = mlp.backwards(label)
epochLosses.append(loss)
epochAccuracies.append(np.argmax(prediction) == np.argmax(label))
mlp.updateWeights(learningRate)
if epoch % 1000 == 0 or epoch == numEpochs - 1:
print("EPOCH:", epoch)
print("LOSS: ", np.mean(epochLosses))
print("ACC: ", np.mean(epochAccuracies) * 100, "%\n")