Я пытаюсь создать CNN, который предсказывает цифры из набора данных mnist. Я использую RELU для функции активации и MSE для функции потерь. Моя архитектура такая же, как у в руководстве по keras для набора данных mnist, за исключением слоев Dropout.
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))
Проблема, по-видимому (по крайней мере, мне) в моей реализации функции ReLU и использовании мини-пакета. На первой итерации процесса обучения значения выходов очень велики. Они варьируются от 50 до 1000 (я думаю, что это проблема взрывающегося градиента), и это только для первого прямого распространения. На втором все значения на выходе равны 0. Неважно, если я использую мини-пакет 128 или 1, это всегда то же самое. Я пробовал с темпами обучения в диапазоне от 0,01 до 0,0000001, если они всегда одинаковы. Однако, когда я изменяю функцию активации на сигмовидную, все немного по-другому. С мини-пакетом равняется 1 (я думаю, что это SGD, если я использую только 1 пример на прямую / обратную пропину), выходы все на случайным при первом запуске, но если я повторю тренировочный процесс только с одним примером из учебного набора, я вижу, что по крайней мере CNN может запомнить этот один пример. Это реализация производной функции потерь (MSE) :
void backprop(Layer* layer) override
{
for (size_t i = 0; i < size; i++)
{
derivativeWRToInput[i] = -2 * (observedValues[i] - predictedValue[i]);
}
}
PredictedValue исходит от CNN, а наблюдаемое значение является фактическим значением (Is равно 0 или 1) , Когда я использую ReLU и все выходы из CNN равны 0, тогда производная потери, скажем, (-2, 0, 0 ...), если фактическое значение было 1. Это реализация функции ReLU. .
struct relu
{
float operator()(const float& input) const
{
if (input > 0)
return input;
else
return 0;
}
float operator()(const float& chain_rule_input, const float& activation_output) const
{
if (activation_output > 0)
return chain_rule_input;
else
return 0.01f;
}
};
Я не уверен, правильно ли мое понимание мини-градиентного спуска. Я реализовал так же, как SGD, но не обновлял веса во время backprop, просто сохраняя ошибки весов в памяти, после всех примеров в одном пакете, я беру среднее значение ошибки весов (weights_error / batch_size) и обновляю их со скоростью обучения.