Я смотрел несколько видео по нейронным сетям глубокого обучения / сверточного типа, таких как Вот а также Вот, и я попытался реализовать свой собственный в C ++. Я попытался сделать входные данные достаточно простыми для моей первой попытки, поэтому идея состоит в том, чтобы провести различие между крестом и кружком, у меня есть небольшой набор данных, состоящий примерно из 25 (64 * 64 изображений), они выглядят так:
Сама сеть состоит из пяти слоев:
Convolution (5 filters, size 3, stride 1, with a ReLU)
MaxPool (size 2)
Convolution (1 filter, size 3, stride 1, with a ReLU)
MaxPool (size 2)
Linear Regression classifier
Моя проблема в том, что моя сеть не сходится ни к чему. Ни один из весов, кажется, не меняется. Если я запускаю его, прогнозы в большинстве случаев остаются такими же, как и случайные выбросы, которые всплывают, прежде чем вернуться к следующей итерации.
Пример сверточного слоя выглядит примерно так: убраны некоторые петли, чтобы сделать его чище
// Yeah, I know I should change the shared_ptr<float>
void ConvolutionalNetwork::Train(std::shared_ptr<float> input,std::shared_ptr<float> outputGradients, float label)
{
float biasGradient = 0.0f;
// Calculate the deltas with respect to the input.
for (int layer = 0; layer < m_Filters.size(); ++layer)
{
// Pseudo-code, each loop on it's own line in actual code
For z < depth, x <width - filterSize, y < height -filterSize
{
int newImageIndex = layer*m_OutputWidth*m_OutputHeight+y*m_OutputWidth + x;
For the bounds of the filter (U,V)
{
// Find the index in the input image
int imageIndex = x + (y+v)*m_OutputWidth + z*m_OutputHeight*m_OutputWidth;
int kernelIndex = u +v*m_FilterSize + z*m_FilterSize*m_FilterSize;
m_pGradients.get()[imageIndex] += outputGradients.get()[newImageIndex]*input.get()[imageIndex];
m_GradientSum[layer].get()[kernelIndex] += m_pGradients.get()[imageIndex] * m_Filters[layer].get()[kernelIndex];
biasGradient += m_GradientSum[layer].get()[kernelIndex];
}
}
}
// Update the weights
for (int layer = 0; layer < m_Filters.size(); ++layer)
{
For z < depth, U & V < filtersize
{
// Find the index in the input image
int kernelIndex = u +v*m_FilterSize + z*m_FilterSize*m_FilterSize;
m_Filters[layer].get()[kernelIndex] -= learningRate*m_GradientSum[layer].get()[kernelIndex];
}
m_pBiases.get()[layer] -= learningRate*biasGradient;
}
}
Итак, я создаю буфер (m_pGradients), который является размерами входного буфера для подачи градиентов обратно на предыдущий слой, но использую сумму градиентов для настройки весов.
Максимальное объединение вычисляет градиенты обратно так (сохраняет максимальные индексы и обнуляет все остальные градиенты)
void MaxPooling::Train(std::shared_ptr<float> input,std::shared_ptr<float> outputGradients, float label)
{
for (int outputVolumeIndex = 0; outputVolumeIndex <m_OutputVolumeSize; ++outputVolumeIndex)
{
int inputIndex = m_Indices.get()[outputVolumeIndex];
m_pGradients.get()[inputIndex] = outputGradients.get()[outputVolumeIndex];
}
}
И последний слой регрессии вычисляет свои градиенты следующим образом:
void LinearClassifier::Train(std::shared_ptr<float> data,std::shared_ptr<float> output, float y)
{
float * x = data.get();
float biasError = 0.0f;
float h = Hypothesis(output) - y;
for (int i =1; i < m_NumberOfWeights; ++i)
{
float error = h*x[i];
m_pGradients.get()[i] = error;
biasError += error;
}
float cost = h;
m_Error = cost*cost;
for (int theta = 1; theta < m_NumberOfWeights; ++theta)
{
m_pWeights.get()[theta] = m_pWeights.get()[theta] - learningRate*m_pGradients.get()[theta];
}
m_pWeights.get()[0] -= learningRate*biasError;
}
После 100 итераций обучения на двух примерах прогноз на каждом из них такой же, как на другом, и не изменился с самого начала.
- Должна ли такая сверточная сеть различать два класса?
Да. Фактически, даже сам линейный классификатор должен очень легко различать (если изображения более или менее центрированы).
- Это правильный подход?
Наиболее вероятная причина — ошибка в ваших формулах градиента. Всегда соблюдайте 2 простых правила:
Всегда проверить ваши градиенты численно. Это так легко сделать и сэкономит вам часы отладки! Напомним из анализа, что
[grad f(x) ]_i ~ (f(x+eps*e_i) - f(x-eps*e_i)) / 2*eps
где под [] _i я подразумеваю i-ю координату, а под e_i я имею в виду i-й канонический вектор (нулевой вектор с единицей на i-й координате)
Должен ли я учитывать ReLU (max) в обратном распространении слоя свертки?
Да, ReLU изменяет ваш градиент, так как это нелинейность, которую нужно дифференцировать. Опять же — вернемся к пункту 1. Начните с простых моделей и добавьте каждый элемент отдельно, чтобы определить, какой из них вызывает сбой вашей градиента / модели.
Других решений пока нет …