Проблемы с реализацией активации softmax и потери кросс-энтропии и их производных в нейронной сети

Я реализовал простой многослойный перцептрон (всего с 1 скрытым слоем), который может изучать проблемы регрессии. Я написал это так, чтобы можно было указать выбор между активациями сигмовидной, тангенциальной и реактивной формы. Квадрат ошибки затем реализуется как функция потерь с каждым из них.

Теперь я хочу предоставить возможность использовать одну и ту же модель для изучения задач классификации нескольких классов, и поэтому хотел бы реализовать выбор использования активации softmax вместе с потерей перекрестной энтропии. В моем коде ниже единственные изменения, которые нужно будет внести (я надеюсь), - это реализовать их в функциях activation() и loss(), и тогда это должно работать из коробки как в прямом проходе, так и в обратном. Этот код запускает симуляцию моей модели, изучающей функцию XOR, где выбранная функция активации должна быть раскомментирована вверху.

Однако я действительно заблудился в реализации обеих этих функций, а тем более их производных. Любая помощь и руководство приветствуются.

import sys
import numpy as np

activation = 'sigmoid'
# activation = 'tanh'
# activation = 'relu'
# activation = 'softmax'

numEpochs = 10000

class DataSet:

    def __init__(self, data, trainSplit=1):
        self.size = len(data)
        self.trainSize = int(self.size * trainSplit)
        self.testSize = self.size - self.trainSize
        self.inputs, self.labels = [], []
        for i in range(len(data)):
            self.inputs.append(data[i][0])
            self.labels.append(data[i][1])

        self.trainInputs = self.inputs[:self.trainSize]
        self.trainLabels = self.labels[:self.trainSize]
        self.testInputs = self.inputs[self.trainSize:]
        self.testLabels = self.labels[self.trainSize:]

        try:
            self.numInputs = len(self.inputs[0])
        except TypeError:
            self.numInputs = 1
        try:
            self.numOutputs = len(self.labels[0])
        except TypeError:
            self.numOutputs = 1


class MLP:

    def __init__(self, numInputs, numHidden, numOutputs, activationFunction):
        # MLP architecture sizes
        self.numInputs = numInputs
        self.numHidden = numHidden
        self.numOutputs = numOutputs

        self.activationFunction = activationFunction.lower()

        # MLP weights
        self.IH_weights = np.random.rand(numInputs, numHidden)      # Input -> Hidden
        self.HO_weights = np.random.rand(numHidden, numOutputs)     # Hidden -> Output

        # MLP biases
        self.IH_bias = np.zeros((1, numHidden))
        self.HO_bias = np.zeros((1, numOutputs))

        # 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)

        # Gradients corresponding to biases computed during backprop
        self.IH_b_gradients = np.zeros_like(self.IH_bias)
        self.HO_b_gradients = np.zeros_like(self.HO_bias)

        # Input, hidden and output layer neuron values
        self.I = np.zeros(numInputs)    # Inputs
        self.L = np.zeros(numOutputs)   # Labels
        self.H = np.zeros(numHidden)    # Hidden
        self.O = np.zeros(numOutputs)   # Output

    def activation(self, x, derivative=False):
        if self.activationFunction == 'sigmoid':
            if derivative:
                return x * (1 - x)
            return 1 / (1 + np.exp(-x))

        if self.activationFunction == 'tanh':
            if derivative:
                return 1 - np.tanh(x) ** 2
            return np.tanh(x)

        if self.activationFunction == 'relu':
            if derivative:
                return (x > 0).astype(float)
            return np.maximum(0, x)

        # TO DO ################################################################
        if self.activationFunction == 'softmax':
            if derivative:
                return 0
            return 0

        print("ERROR: Activation function not found.")
        sys.exit()

    def loss(self, labels, predictions, derivative=False):
        # TO DO ################################################################
        # Cross-Entropy
        if self.activationFunction == 'softmax':
            if derivative:
                return 0
            return 0
        # Squared Error
        else:
            if derivative:
                return (-2 * labels) + (2 * predictions)
            return (labels - predictions) ** 2

    def forward(self, inputs):
        # Ensure that inputs is a list
        try:
            len(inputs)
        except TypeError:
            inputs = [inputs]

        self.I = np.array(inputs).reshape(1, self.numInputs)
        self.H = self.I.dot(self.IH_weights) + self.IH_bias
        self.H = self.activation(self.H)
        self.O = self.H.dot(self.HO_weights) + self.HO_bias
        self.O = self.activation(self.O)

    def backwards(self, labels):
        # Ensure that labels is a list
        try:
            len(labels)
        except TypeError:
            labels = [labels]

        self.L = np.array(labels)
        self.O_error = self.loss(self.O, self.L)
        self.O_delta = self.loss(self.O, self.L, derivative=True) * self.activation(self.O, derivative=True)

        self.H_error = self.O_delta.dot(self.HO_weights.T)
        self.H_delta = self.H_error * self.activation(self.H, derivative=True)

        self.IH_w_gradients += self.I.T.dot(self.H_delta)
        self.HO_w_gradients += self.H.T.dot(self.O_delta)

        self.IH_b_gradients += self.H_delta
        self.HO_b_gradients += 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_bias += learningRate * self.IH_b_gradients
        self.HO_bias += learningRate * self.HO_b_gradients

        self.IH_w_gradients = np.zeros_like(self.IH_weights)
        self.HO_w_gradients = np.zeros_like(self.HO_weights)
        self.IH_b_gradients = np.zeros_like(self.IH_bias)
        self.HO_b_gradients = np.zeros_like(self.HO_bias)

    def process(self, data, train=False, learningRate=0):
        if train:
            size = data.trainSize
            inputs = data.trainInputs
            labels = data.trainLabels
        else:
            size = data.testSize
            inputs = data.testInputs
            labels = data.testLabels

        errors = []
        for i in range(size):
            self.forward(inputs[i])
            errors.append(self.backwards(labels[i]))
        if train:
            self.updateWeights(learningRate)
        return np.mean(errors)

data1 = DataSet([
    [[0, 0], 0],
    [[0, 1], 1],
    [[1, 0], 1],
    [[1, 1], 0]
])

data2 = DataSet([
    [[0, 0], -1],
    [[0, 1], 1],
    [[1, 0], 1],
    [[1, 1], -1]
])

data3 = DataSet([
    [[0, 0], [1, 0]],
    [[0, 1], [0, 1]],
    [[1, 0], [0, 1]],
    [[1, 1], [1, 0]]
])

if activation == 'sigmoid':
    data = data1
    mlp = MLP(data.numInputs, 2, data.numOutputs, 'sigmoid')
    learningRate = 1
if activation == 'tanh':
    data = data2
    mlp = MLP(data.numInputs, 2, data.numOutputs, 'tanh')
    learningRate = 0.1
if activation == 'relu':
    data = data1
    mlp = MLP(data.numInputs, 2, data.numOutputs, 'relu')
    learningRate = 0.001
if activation == 'softmax':
    data = data3
    mlp = MLP(data.numInputs, 2, data.numOutputs, 'softmax')
    learningRate = 0.01

################################################################################
# TO DO: UPDATE WEIGHTS AT INTERVALS, NOT EVERY EPOCH
################################################################################
losses = []
for epoch in range(numEpochs):
    epochLoss = mlp.process(data, train=True, learningRate=learningRate)
    losses.append(epochLoss)
    if epoch % 1000 == 0 or epoch == numEpochs - 1:
        print("EPOCH:", epoch)
        print("LOSS: ", epochLoss, "\n")
0
0
850
1

Ответы 1

К сожалению, softmax не так прост, как другие функции активации, которые вы опубликовали. Для функции активации вы должны вычислить exp (y_i), а затем разделить на сумму exp (y_k) для каждого y_k в Y. Для производной вы должны вычислить каждую комбинацию (n ^ 2 комбинаций) частных производных каждого выхода по каждому входу нейрона. К счастью, потерю немного легче понять, так как вы можете думать о softmax, дающем вам некоторые вероятности (так что это похоже на распределение вероятностей), и вы вычисляете перекрестную энтропию, как есть между возвращаемыми значениями и целевыми.

Другие вопросы по теме