Практическое проектирование и внедрение фреймворка глубокого обучения

машинное обучение
Практическое проектирование и внедрение фреймворка глубокого обучения

Эта статья перемещена в личный блог, пожалуйста, нажмитездесьПосмотрите исходный пост в блоге.

Текущая среда глубокого обучения становится все более и более зрелой, а степень инкапсуляции для пользователей становится все выше и выше.Преимущество в том, что эти структуры теперь можно очень быстро использовать в качестве инструментов, а эксперименты можно проводить с очень небольшим количеством кода. Недостаток в том, что могут быть скрыты Реализации все скрыто. В этой статье автор поможет вам спроектировать и реализовать tinynn, легкую и простую в расширении структуру глубокого обучения с нуля, используя Python, надеясь помочь вам понять принципы глубокого обучения и структуру глубокого обучения.

В этой статье сначала будет проанализирован процесс глубокого обучения, абстрагированы ключевые компоненты в нейронной сети и определена базовая структура; затем реализован код для каждого компонента в структуре; наконец, на основе этой структуры приведен пример классификации MNIST. реализовано.


содержание


Абстракция компонента

Сначала рассмотрим процесс работы нейронной сети.Работа нейронной сети в основном включает в себя два этапа: обучение обучение и прогнозирование (или вывод).Основной процесс обучения: входные данные -> прямое распространение сетевого уровня -> потери вычислений -> сетевой уровень обратное Чтобы распространять градиенты -> параметры обновления, основной процесс прогнозирования - это входные данные -> прямое распространение сетевого уровня -> выходные результаты. С операционной точки зрения в основном существует три типа вычислений:

  1. Данные передаются непосредственно на сетевом уровне Прямое распространение и обратное распространение можно рассматривать как поток тензоров (многомерных массивов) между слоями сети (потоки прямого распространения вход и выход, градиент потоков обратного распространения), каждый слой сети выполняет определенные операции, а затем вводит результат в следующий слой

  2. Рассчитать потери Промежуточный процесс соединения прямого и обратного распространения определяет разницу между выходными данными модели и истинным значением, которое используется для последующего предоставления информации, необходимой для обратного распространения.

  3. обновление параметра Класс вычислений, которые обновляют параметры сети с использованием вычисленных градиентов.

На основе этих трех типов мы можем сделать абстракцию основных компонентов сети.

  • tensorТензор, который является базовой единицей данных в нейронной сети.
  • layerСетевой слой отвечает за получение входных данных предыдущего слоя, выполнение операции этого слоя и вывод результата на следующий слой.Поскольку поток тензора имеет два направления, прямое и обратное, для каждого типа сетевого слоя мы необходимо одновременно реализовывать как прямые, так и обратные операции
  • lossпотери, этот компонент выводит значение потерь и градиент по отношению к последнему слою с учетом прогнозируемого значения модели и истинного значения (для транзитной передачи градиента)
  • optimizerОптимизатор, отвечающий за обновление параметров модели с помощью градиентов.

Затем нам нужны некоторые компоненты, чтобы интегрировать вышеупомянутые четыре основных компонента вместе, чтобы сформировать конвейер.

  • netКомпонент отвечает за управление прямым и обратным распространением тензора между слоями и предоставляет интерфейсы для получения параметров, установки параметров и получения градиентов.
  • modelКомпоненты отвечают за интеграцию всех компонентов для формирования всего конвейера. То есть компонент сети выполняет прямое распространение -> компонент потерь вычисляет потери и градиент -> компонент сети выполняет обратное распространение градиента -> компонент оптимизатора обновляет градиент до параметров.

Базовая схема кадра выглядит следующим образом

v2-4692b3680ce5fa62d6eb27e359aaf7c8_1440w.jpeg


Реализация компонента

Согласно приведенной выше абстракции, мы можем написать весь код процесса следующим образом. Сначала определите сеть, вход сети - это несколько сетевых слоев, а затем передайте сеть, потери, оптимизатор в модель вместе. Модель реализует интерфейсы прямого, обратного и apply_grad, соответствующие прямому распространению, обратному распространению и обновлению параметров соответственно.

# define model
net = Net([layer1, layer2, ...])
model = Model(net, loss_fn, optimizer)

# training
pred = model.forward(train_X)
loss, grads = model.backward(pred, train_Y)
model.apply_grad(grads)

# inference
test_pred = model.forward(test_X)

Далее давайте посмотрим, как здесь реализована каждая часть.

  • tensor

    Тензор тензор — это базовая единица данных в нейронной сети, мы используем его непосредственно здесь.numpy.ndarrayКласс используется как реализация тензорного класса (нижний слой numpy использует C и Fortran, и сделано много оптимизаций на уровне алгоритма, и скорость работы не медленная)

  • layer

    В приведенном выше коде процесса модель выполняет прямое и обратное На самом деле нижний уровень является фактической операцией сетевого уровня, поэтому сетевой уровень должен предоставлять прямой и обратный интерфейсы для соответствующих операций. Параметры и градиенты слоя также должны быть записаны. Сначала реализуйте базовый класс следующим образом

    # layer.py
    class Layer(object):
        def __init__(self, name):
            self.name = name
            self.params, self.grads = None, None
    
        def forward(self, inputs):
            raise NotImplementedError
    
        def backward(self, grad):
            raise NotImplementedError
    

    Самым базовым сетевым уровнем является полносвязный сетевой уровень, который реализован следующим образом. Прямой метод получает входные данные верхнего уровня и реализуетwx+bwx+bОперация обратного метода получает градиент от верхнего слоя и вычисляет параметрыw,bw, bи градиент ввода, затем вернуть градиент относительно ввода. Вывод этих трех градиентов можно найти в приложении, а реализация непосредственно приведена здесь. w_init и b_init — это инициализаторы для веса и смещения параметров соответственно, которые мы храним в инициализаторе другой реализации.initializer.pyДля реализации эта часть не является основным компонентом, поэтому она не будет здесь представлена.

    # layer.py
    class Dense(Layer):
        def __init__(self, num_in, num_out,
                     w_init=XavierUniformInit(),
                     b_init=ZerosInit()):
            super().__init__("Linear")
    
            self.params = {
                "w": w_init([num_in, num_out]),
                "b": b_init([1, num_out])}
    
            self.inputs = None
    
        def forward(self, inputs):
            self.inputs = inputs
            return inputs @ self.params["w"] + self.params["b"]
    
        def backward(self, grad):
            self.grads["w"] = self.inputs.T @ grad
            self.grads["b"] = np.sum(grad, axis=0)
            return grad @ self.params["w"].T
    

    В то же время другой важной частью нейронной сети является функция активации. Функцию активации можно рассматривать как своего рода сетевой уровень, который также должен реализовывать прямые и обратные методы. Мы реализуем класс функции активации, наследуя класс Layer, где реализована наиболее часто используемая функция активации ReLU. Методы func и derivation_func реализуют прямой расчет и градиентный расчет соответствующей функции активации соответственно.

    # layer.py
    class Activation(Layer):
    		"""Base activation layer"""
        def __init__(self, name):
            super().__init__(name)
            self.inputs = None
    
        def forward(self, inputs):
            self.inputs = inputs
            return self.func(inputs)
    
        def backward(self, grad):
            return self.derivative_func(self.inputs) * grad
    
        def func(self, x):
            raise NotImplementedError
    
        def derivative_func(self, x):
            raise NotImplementedError
    
    class ReLU(Activation):
    		"""ReLU activation function"""
        def __init__(self):
            super().__init__("ReLU")
    
        def func(self, x):
            return np.maximum(x, 0.0)
    
        def derivative_func(self, x):
            return x > 0.0
    
  • net

    Как упоминалось выше, класс net отвечает за управление прямым и обратным распространением тензора между слоями. Прямой метод очень прост: он обходит все слои по порядку, и выходные данные, вычисленные каждым слоем, используются в качестве входных данных следующего слоя, обратный обходит все слои в обратном порядке и использует градиент каждого слоя в качестве входных данных для следующего слоя. следующий слой. Здесь мы также сохраняем градиент каждого параметра сетевого слоя и возвращаем его, который будет использоваться позже для обновления параметра. Кроме того, класс net также реализует интерфейсы для получения параметров, установки параметров и получения градиентов, которые также необходимы для последующих обновлений параметров.

    # net.py
    class Net(object):
        def __init__(self, layers):
            self.layers = layers
    
        def forward(self, inputs):
            for layer in self.layers:
                inputs = layer.forward(inputs)
            return inputs
    
        def backward(self, grad):
            all_grads = []
            for layer in reversed(self.layers):
                grad = layer.backward(grad)
                all_grads.append(layer.grads)
            return all_grads[::-1]
    
        def get_params_and_grads(self):
            for layer in self.layers:
                yield layer.params, layer.grads
    
        def get_parameters(self):
            return [layer.params for layer in self.layers]
    
        def set_parameters(self, params):
            for i, layer in enumerate(self.layers):
                for key in layer.params.keys():
                    layer.params[key] = params[i][key]
    
  • loss

    Мы упоминали выше, что компонент потерь должен делать две вещи: учитывая прогнозируемое значение и истинное значение, он должен вычислять значение потерь и градиент по отношению к прогнозируемому значению. Мы реализуем методы loss и grad соответственно.Здесь мы реализуем потери SoftmaxCrossEntropyLoss, обычно используемые в регрессии с несколькими классами. Формулы расчета этих потерь потерь и градиента grad выведены в приложении в конце статьи, а результаты непосредственно приведены здесь:

    Потеря многоклассовой кросс-энтропии softmax

    $$ J_{CE}(y, \hat{y}) = -\sum_{i=1}^N \log \hat{y_i^{c}} $$

    Градиент немного сложнее, и формулы расчета для целевой категории и нецелевой категории разные. Для измерения целевой категории градиент представляет собой выходную вероятность соответствующей модели измерения минус единицу, а для измерения нецелевой категории градиент представляет собой выходную вероятность самого соответствующего измерения.

    $$ \frac{\partial J_{ce}}{\partial o^c}= \begin{случаи} (\ шляпа {y} ^ c-1) / N & \ text {целевой класс} c \\ y ^ {\ тильда {c}} / N & \ text {нецелевой класс} \ тильда {c} \end{случаи} $$

    Код реализован следующим образом

    # loss.py
    class BaseLoss(object):
        def loss(self, predicted, actual):
            raise NotImplementedError
    
        def grad(self, predicted, actual):
            raise NotImplementedError
    
    class CrossEntropyLoss(BaseLoss):
      	def loss(self, predicted, actual):
            m = predicted.shape[0]
            exps = np.exp(predicted - np.max(predicted, axis=1, keepdims=True))
            p = exps / np.sum(exps, axis=1, keepdims=True)
            nll = -np.log(np.sum(p * actual, axis=1))
            return np.sum(nll) / m
    
        def grad(self, predicted, actual):
            m = predicted.shape[0]
            grad = np.copy(predicted)
            grad -= actual
            return grad / m
    
  • optimizer

    Оптимизатор в основном реализует интерфейс calculate_step, этот метод вычисляет размер шага каждого изменения параметра при возврате к фактической оптимизации в соответствии с текущим градиентом. Здесь мы реализуем часто используемый оптимизатор Adam.

    # optimizer.py
    class BaseOptimizer(object):
        def __init__(self, lr, weight_decay):
            self.lr = lr
            self.weight_decay = weight_decay
    
        def compute_step(self, grads, params):
            step = list()
            # flatten all gradients
            flatten_grads = np.concatenate(
                [np.ravel(v) for grad in grads for v in grad.values()])
            # compute step
            flatten_step = self._compute_step(flatten_grads)
            # reshape gradients
            p = 0
            for param in params:
                layer = dict()
                for k, v in param.items():
                    block = np.prod(v.shape)
                    _step = flatten_step[p:p+block].reshape(v.shape)
                    _step -= self.weight_decay * v
                    layer[k] = _step
                    p += block
                step.append(layer)
            return step
    
        def _compute_step(self, grad):
            raise NotImplementedError
    
    class Adam(BaseOptimizer):
        def __init__(self, lr=0.001, beta1=0.9, beta2=0.999,
                     eps=1e-8, weight_decay=0.0):
            super().__init__(lr, weight_decay)
            self._b1, self._b2 = beta1, beta2
            self._eps = eps
    
            self._t = 0
            self._m, self._v = 0, 0
    
        def _compute_step(self, grad):
            self._t += 1
            self._m = self._b1 * self._m + (1 - self._b1) * grad
            self._v = self._b2 * self._v + (1 - self._b2) * (grad ** 2)
            # bias correction
            _m = self._m / (1 - self._b1 ** self._t)
            _v = self._v / (1 - self._b2 ** self._t)
            return -self.lr * _m / (_v ** 0.5 + self._eps)
    
    
  • model

    Наконец, класс модели реализует три интерфейса forward, back и apply_grad, которые мы разработали в начале. Forward напрямую вызывает переадресацию net. градиент получается обратным распространением, а затем оптимизатор вычисляет размер шага, и, наконец, параметры обновляются с помощью apply_grad

    # model.py
    class Model(object):
        def __init__(self, net, loss, optimizer):
            self.net = net
            self.loss = loss
            self.optimizer = optimizer
    
        def forward(self, inputs):
            return self.net.forward(inputs)
    
        def backward(self, preds, targets):
            loss = self.loss.loss(preds, targets)
            grad = self.loss.grad(preds, targets)
            grads = self.net.backward(grad)
            params = self.net.get_parameters()
            step = self.optimizer.compute_step(grads, params)
            return loss, step
    
        def apply_grad(self, grads):
            for grad, (param, _) in zip(grads, self.net.get_params_and_grads()):
                for k, v in param.items():
                    param[k] += grad[k]
    

весь кадр

Наконец, мы реализуем основную часть кода файловой структуры следующим образом.

tinynn
├── core
│   ├── __init__.py
│   ├── initializer.py
│   ├── layer.py
│   ├── loss.py
│   ├── model.py
│   ├── net.py
│   └── optimizer.py

вinitializer.pyЭтот модуль не обсуждался выше и в основном реализует общие методы инициализации параметров для инициализации параметров сетевого уровня.


пример MNIST

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

  • набор данных:MNIST
  • Тип задачи: мультиклассификация
  • Структура сети: трехуровневое полное соединениеINPUT(784) -> FC(400) -> FC(100) -> OUTPUT(10), эта сеть получает(N,784)(N, 784)вход, гдеNNколичество выборок на вход, 784(28,28)(28, 28)Сглаженный вектор изображения, выходной размер(N,10)(N, 10)NN- количество образцов, а 10 - вероятность соответствующего изображения в 10 категориях.
  • Функция активации: ReLU
  • Функция потерь: SoftmaxCrossEntropy
  • оптимизатор: Адам (lr=1e-3)
  • размер партии: 128
  • Число_эпох: 20

Здесь мы игнорируем некоторые подготовительные коды, такие как загрузка данных и предварительная обработка, и вставляем только определение базовой структуры сети и обучающий код следующим образом.

# example/mnist/run.py
net = Net([
  Dense(784, 400),
  ReLU(),
  Dense(400, 100),
  ReLU(),
  Dense(100, 10)
])
model = Model(net=net, loss=SoftmaxCrossEntropyLoss(), optimizer=Adam(lr=args.lr))

iterator = BatchIterator(batch_size=args.batch_size)
evaluator = AccEvaluator()
for epoch in range(num_ep):
    for batch in iterator(train_x, train_y):
      	# training
        pred = model.forward(batch.inputs)
        loss, grads = model.backward(pred, batch.targets)
        model.apply_grad(grads)
    # evaluate every epoch
    test_pred = model.forward(test_x)
    test_pred_idx = np.argmax(test_pred, axis=1)
    test_y_idx = np.asarray(test_y)
    res = evaluator.evaluate(test_pred_idx, test_y_idx)
    print(res)

Текущий результат выглядит следующим образом

# tinynn
Epoch 0 	 {'total_num': 10000, 'hit_num': 9658, 'accuracy': 0.9658}
Epoch 1 	 {'total_num': 10000, 'hit_num': 9740, 'accuracy': 0.974}
Epoch 2 	 {'total_num': 10000, 'hit_num': 9783, 'accuracy': 0.9783}
Epoch 3 	 {'total_num': 10000, 'hit_num': 9799, 'accuracy': 0.9799}
Epoch 4 	 {'total_num': 10000, 'hit_num': 9805, 'accuracy': 0.9805}
Epoch 5 	 {'total_num': 10000, 'hit_num': 9826, 'accuracy': 0.9826}
Epoch 6 	 {'total_num': 10000, 'hit_num': 9823, 'accuracy': 0.9823}
Epoch 7 	 {'total_num': 10000, 'hit_num': 9819, 'accuracy': 0.9819}
Epoch 8 	 {'total_num': 10000, 'hit_num': 9820, 'accuracy': 0.982}
Epoch 9 	 {'total_num': 10000, 'hit_num': 9838, 'accuracy': 0.9838}
Epoch 10 	 {'total_num': 10000, 'hit_num': 9825, 'accuracy': 0.9825}
Epoch 11 	 {'total_num': 10000, 'hit_num': 9810, 'accuracy': 0.981}
Epoch 12 	 {'total_num': 10000, 'hit_num': 9845, 'accuracy': 0.9845}
Epoch 13 	 {'total_num': 10000, 'hit_num': 9845, 'accuracy': 0.9845}
Epoch 14 	 {'total_num': 10000, 'hit_num': 9835, 'accuracy': 0.9835}
Epoch 15 	 {'total_num': 10000, 'hit_num': 9817, 'accuracy': 0.9817}
Epoch 16 	 {'total_num': 10000, 'hit_num': 9815, 'accuracy': 0.9815}
Epoch 17 	 {'total_num': 10000, 'hit_num': 9835, 'accuracy': 0.9835}
Epoch 18 	 {'total_num': 10000, 'hit_num': 9826, 'accuracy': 0.9826}
Epoch 19 	 {'total_num': 10000, 'hit_num': 9819, 'accuracy': 0.9819}

Можно видеть, что точность тестового набора медленно улучшается по мере прохождения обучения, что показывает, что данные действительно передаются и правильно рассчитываются в структуре. Чтобы сравнить эффект, я использовал Tensorflow (1.13.1) для реализации той же структуры сети, применения того же метода инициализации выборки, настройки оптимизатора и т. д. Результаты следующие.

# Tensorflow 1.13.1
Epoch 0 	 {'total_num': 10000, 'hit_num': 9591, 'accuracy': 0.9591}
Epoch 1 	 {'total_num': 10000, 'hit_num': 9734, 'accuracy': 0.9734}
Epoch 2 	 {'total_num': 10000, 'hit_num': 9706, 'accuracy': 0.9706}
Epoch 3 	 {'total_num': 10000, 'hit_num': 9756, 'accuracy': 0.9756}
Epoch 4 	 {'total_num': 10000, 'hit_num': 9722, 'accuracy': 0.9722}
Epoch 5 	 {'total_num': 10000, 'hit_num': 9772, 'accuracy': 0.9772}
Epoch 6 	 {'total_num': 10000, 'hit_num': 9774, 'accuracy': 0.9774}
Epoch 7 	 {'total_num': 10000, 'hit_num': 9789, 'accuracy': 0.9789}
Epoch 8 	 {'total_num': 10000, 'hit_num': 9766, 'accuracy': 0.9766}
Epoch 9 	 {'total_num': 10000, 'hit_num': 9763, 'accuracy': 0.9763}
Epoch 10 	 {'total_num': 10000, 'hit_num': 9791, 'accuracy': 0.9791}
Epoch 11 	 {'total_num': 10000, 'hit_num': 9773, 'accuracy': 0.9773}
Epoch 12 	 {'total_num': 10000, 'hit_num': 9804, 'accuracy': 0.9804}
Epoch 13 	 {'total_num': 10000, 'hit_num': 9782, 'accuracy': 0.9782}
Epoch 14 	 {'total_num': 10000, 'hit_num': 9800, 'accuracy': 0.98}
Epoch 15 	 {'total_num': 10000, 'hit_num': 9837, 'accuracy': 0.9837}
Epoch 16 	 {'total_num': 10000, 'hit_num': 9811, 'accuracy': 0.9811}
Epoch 17 	 {'total_num': 10000, 'hit_num': 9793, 'accuracy': 0.9793}
Epoch 18 	 {'total_num': 10000, 'hit_num': 9818, 'accuracy': 0.9818}
Epoch 19 	 {'total_num': 10000, 'hit_num': 9811, 'accuracy': 0.9811}

v2-480f35b313dcbcbc68f3aaa4e69c96b4_1440w.jpeg

Видно, что эффект от двух неплох, и немного лучше, чем у Tensorflow в одном эксперименте.


Суммировать

Исходный код, связанный с Tinynn, находится здесьrepoвнутри. В настоящее время поддерживает:

  • слой: полносвязный слой, слой 2D-свертки, слой 2D-деконволюции, слой MaxPooling, слой Dropout, слой BatchNormalization, слой RNN и функции активации, такие как ReLU, Sigmoid, Tanh, LeakyReLU, SoftPlus
  • потери: SigmoidCrossEntropy, SoftmaxCrossEntroy, MSE, MAE, Huber
  • оптимизатор: RAam, Adam, SGD, RMSProp, Momentum и другие оптимизаторы, а также добавить динамическую настройку скорости обучения LRScheduler
  • Реализованы общие модели, такие как mnist (классификация), nn_paint (регрессия), DQN (обучение с подкреплением), AutoEncoder и DCGAN (неконтролируемая). Видетьtinynn/examples

Есть еще много областей, которые tinynn может продолжать улучшать в связи с тем, что время не истекло (реализация слоя рекуррентной нейронной сети, слоя BatchNorm, оптимизация эффективности вычислений и т. д.), автор будет поддерживать и обновлять в моем Свободное время.

Конечно, tinynn может быть плохим выбором с точки зрения продакшн-приложений, причина в том, что использование python неизбежно приведет к проблемам с производительностью в таких ресурсоемких сценариях, без поддержки GPU, без распределенной поддержки, многие алгоритмы еще не реализованы. реализовано и т. д. Подождите, этот маленький проектОтправной точкой является больше обучения, В процессе проектирования и реализации tinynn автор многому научился, в том числе, как абстрагироваться, как проектировать интерфейсы компонентов, как реализовывать более эффективно, специфические детали алгоритмов и так далее. Для автора написание этого небольшого фреймворка имеет еще одно преимущество, помимо понимания дизайна и реализации фреймворка глубокого обучения: последующие действия могут бытьБыстро реализовать некоторые новые алгоритмы на этом фреймворке, например, в новой статье предлагается новый метод инициализации параметров, новый алгоритм оптимизации и новый дизайн сетевой структуры, которые можно быстро опробовать на этой небольшой платформе.

Если вы также заинтересованы в самостоятельной разработке и внедрении фреймворка глубокого обучения, я надеюсь, что чтение этой статьи поможет вам, и вы можете отправить PR и внести свой код вместе ~ ?


приложение

Потеря кросс-энтропии Softmax и получение градиента

Перекрестная энтропийная потеря при множественной классификации выглядит следующим образом:

JCE(y,y^)=i=1Nk=1Kyiklogyik^J_{CE}(y, \hat{y}) = -\sum_{i=1}^N\sum_{k=1}^K y_i^k \log \hat{y_i^k}

вy,y^y, \hat{y}- фактическое значение и значение, предсказанное моделью, соответственно,NNколичество образцов,KKэто количество категорий. Поскольку реальное значение обычно представляет собой однократный вектор (за исключением того, что размерность реальной категории равна 1, все остальные равны 0), поэтому приведенную выше формулу можно упростить как

JCE(y,y^)=i=1Nlogyic^J_{CE}(y, \hat{y}) = -\sum_{i=1}^N \log \hat{y_i^{c}}

вccнастоящая категория,yic^\hat{y_i^c}представительiiобразцыccПрогнозируемая вероятность класса. То есть то, что нам нужно рассчитать, - это сумма логарифмов прогнозируемых вероятностей каждого образца в реальной категории, а затем отрицать отрицатель потери кредита энтропии.

Затем выведите, как решить градиент этой потери по отношению к выходным данным модели, используяooПредставляет выходные данные модели. В мультиклассификации Softmax обычно используется для нормализации выходных данных сети в распределение вероятности. Выходные данные после Softmax

y^c=exp(oc)k=1Kexp(ok)\hat{y}^c = \frac{\exp (o^c)}{\sum_{k=1}^{K} \exp (o^k)}

Подставьте в функцию потерь выше

JCE=i=1N(oiclogk=1Kexp(oik))J_{CE} =-\sum_{i=1}^{N} \left( o_i^c - \log \sum_{k=1}^{K} \exp (o_i^k) \right)

решатьJCEJ_{CE}О выходном вектореooградиент , который может бытьooРазделены на измерение целевой категорииoco^cи параметры нецелевой категорииoc~o^{\tilde{c}}. Сначала посмотрите на размер целевой категорииoco^c

Jceoc=i=1N(1exp(oc)k=1Kexp(ok))=i=1N(y^c1)\frac{\partial J_{ce}}{\partial o^c} = -\sum_{i=1}^N \left( 1-\frac{\exp (o^c)}{\sum_{k=1}^{K} \exp (o^k)} \right) = \sum_{i=1}^N(\hat{y}^c-1)

Посмотрите на размер нецелевой категорииoc~o^{\tilde{c}}

Jceoc~=i=1N(exp(oc)k=1Kexp(ok))=i=1Nyc~\frac{\partial J_{ce}}{\partial o^{\tilde{c}}} = -\sum_{i=1}^N \left( -\frac{\exp (o^c)}{\sum_{k=1}^{K} \exp (o^k)} \right) = \sum_{i=1}^N y^{\tilde{c}}

Можно видеть, что для измерения целевой категории градиент представляет собой выходную вероятность соответствующей модели измерения минус единица, а для измерения нецелевой категории градиент представляет собой выходную вероятность самого соответствующего измерения.