Dav*_*s72 1 c++ machine-learning backpropagation neural-network deep-learning
我一直在观看有关深度学习/卷积神经网络的一些视频,比如这里和这里,我试图在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
Run Code Online (Sandbox Code Playgroud)
我的问题是我的网络没有任何融合.没有任何权重似乎发生变化.如果我运行它,预测大多数保持不变,而不是偶尔的异常值,它会在下一次迭代返回之前跳起来.
卷积层训练看起来像这样,删除了一些循环使其更清洁
// 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;
}
}
Run Code Online (Sandbox Code Playgroud)
因此,我创建了一个缓冲区(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];
}
}
Run Code Online (Sandbox Code Playgroud)
最后的回归层计算它的渐变,如下所示:
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;
}
Run Code Online (Sandbox Code Playgroud)
在对两个示例进行100次迭代训练之后,对每个示例的预测与另一个相同并且从一开始就保持不变.
- 像这样的卷积网络是否能够区分这两个类?
是.实际上,甚至线性分类器本身也应该能够非常容易地区分(如果图像或多或少地居中).
- 这是正确的方法吗?
最可能的原因是渐变公式中的错误.始终遵循2条简单的规则:
始终以数字方式检查您的渐变.这很容易做到,可以为你节省数小时的时间!从分析中回忆起来
[grad f(x) ]_i ~ (f(x+eps*e_i) - f(x-eps*e_i)) / 2*eps
Run Code Online (Sandbox Code Playgroud)
其中by [] _i我的意思是我的坐标,而e_i我的意思是我的规范矢量(零矢量与第i个坐标上的一个)
我应该考虑卷积层反向传播中的ReLU(最大值)吗?
是的,ReLU会改变您的渐变,因为这是一个需要区分的非线性.再次 - 回到第1点.从简单模型开始,分别添加每个元素以找出导致渐变/模型崩溃的元素.