对于我来说,很难产生一个最小的示例,因为我认为它与我的其他代码有关。但是,我相信我已经在下面提供了相关代码。
我删除了我认为对该问题不很重要的类等部分。
我有一个利用神经元的神经网络课程:
神经元
template<std::size_t NumInputs>
class Neuron
{
public:
Neuron()
{
for(auto& i : m_inputValues)
i = 0;
for(auto& e : m_eligibilityTraces)
e = 0;
for(auto& w : m_weights)
w = 0;
m_biasWeight = 0;
m_biasEligibilityTrace = 0;
m_outputValue = 0;
}
void SetInputValue(const std::size_t index, const double value)
{
m_inputValues[index] = value;
}
void SetWeight(const std::size_t index, const double weight)
{
if(std::isnan(weight))
throw std::runtime_error("Shit! this is a nan bread");
m_weights[index] = weight;
}
void SetBiasWeight(const double weight)
{
m_biasWeight = weight;
}
double GetInputValue(const std::size_t index) const
{
return m_inputValues[index];
}
double GetWeight(const std::size_t index) const
{
return m_weights[index];
}
double GetBiasWeight() const
{
return m_biasWeight;
}
double CalculateOutput()
{
double m_outputValue = 0;
for(std::size_t i = 0; i < NumInputs; ++i)
{
m_outputValue += m_inputValues[i] * m_weights[i];
}
m_outputValue += 1.0 * m_biasWeight;
m_outputValue = sigmoid(m_outputValue);
return m_outputValue;
}
double GetOutput() const
{
return m_outputValue;
}
double GetEligibilityTrace(const std::size_t index) const
{
return m_eligibilityTraces[index];
}
void SetEligibilityTrace(const std::size_t index, const double eligibility)
{
m_eligibilityTraces[index] = eligibility;
}
void SetBiasEligibility(const double eligibility)
{
m_biasEligibilityTrace = eligibility;
}
double GetBiasEligibility() const
{
return m_biasEligibilityTrace;
}
private:
std::array<double,NumInputs> m_inputValues;
std::array<double,NumInputs> m_weights;
std::array<double,NumInputs> m_eligibilityTraces;
double m_biasWeight;
double m_biasEligibilityTrace;
double m_outputValue;
};
神经网络
template<std::size_t NumInputs, std::size_t NumHidden, std::size_t NumOutputs>
class NeuralNetwork
{
public:
...
std::array<double,NumOutputs> FeedForward(const std::array<double,NumInputs>& inputValues)
{
for(auto& hiddenNeuron : m_hiddenNeurons)
{
for(std::size_t i = 0; i < NumInputs; ++i)
hiddenNeuron.SetInputValue(i,inputValues[i]);
hiddenNeuron.CalculateOutput();
}
std::array<double, NumOutputs> returnValue;
for(std::size_t h = 0; h < NumHidden; ++h)
{
auto hiddenOutput = m_hiddenNeurons[h].GetOutput();
for(std::size_t o = 0; o < NumOutputs; ++o)
m_outputNeurons[o].SetInputValue(h, hiddenOutput);
}
for(std::size_t o = 0; o < NumOutputs; ++o)
{
returnValue[o] = m_outputNeurons[o].CalculateOutput();
}
return returnValue;
}
private:
std::array<Neuron<NumInputs>,NumHidden> m_hiddenNeurons;
std::array<Neuron<NumHidden>,NumOutputs> m_outputNeurons;
};
一切工作正常,NeuralNetwork<86,86,2>
但是考虑到我需要更多的输入变量后,即启用编译器标志后,NeuralNetwork<170,170,2>
该FeedForward
方法将产生堆栈溢出-O2
。-g
设置标志不会产生此问题。
如果删除FeedForward
方法的这一部分,则不会导致堆栈溢出:
for(std::size_t h = 0; h < NumHidden; ++h)
{
auto hiddenOutput = m_hiddenNeurons[h].GetOutput();
for(std::size_t o = 0; o < NumOutputs; ++o)
m_outputNeurons[o].SetInputValue(h, hiddenOutput);
}
我不明白为什么这会产生堆栈溢出。隐藏单元数为170,输出单元数为2;当然,这还不足以导致溢出,特别是考虑到上述情况,我将170个输入循环到170个隐藏单元。
正如您在Neuron
类中看到的那样,该GetOutput()
方法不涉及任何其他函数调用,SetInputValue()
也不执行任何类似的操作。没有递归。
删除的部分可以正常工作而没有内部循环。但是,下面它之外的循环会导致堆栈溢出。
即,这会导致堆栈溢出:
for(std::size_t h = 0; h < NumHidden; ++h)
{
auto hiddenOutput = m_hiddenNeurons[h].GetOutput();
// for(std::size_t o = 0; o < NumOutputs; ++o)
// m_outputNeurons[o].SetInputValue(h, hiddenOutput);
}
for(std::size_t o = 0; o < NumOutputs; ++o)
{
returnValue[o] = m_outputNeurons[o].CalculateOutput();
}
而这不是:
for(std::size_t h = 0; h < NumHidden; ++h)
{
auto hiddenOutput = m_hiddenNeurons[h].GetOutput();
// for(std::size_t o = 0; o < NumOutputs; ++o)
// m_outputNeurons[o].SetInputValue(h, hiddenOutput);
}
for(std::size_t o = 0; o < NumOutputs; ++o)
{
//returnValue[o] = m_outputNeurons[o].CalculateOutput();
}
这是没有意义的,因为循环不是嵌套的...
堆栈溢出仅在分别实际到达保护页时才在堆栈边界之外的第一次实际写操作中检测到。由于您要Neuron
使用0初始化类中的所有内容,因此Neuron
最初的大小为Nullbytes。它与您的环境将内存初始化到的环境完全匹配(未实际初始化,但映射到仅包含Nullbytes的共享只读页)。
一旦第一个非空字节写入保护页,就会触发页错误(如果合法写入地址,则共享的空页将被RAM中的实际页替换)。结果,然后检测到堆栈溢出,因为不应将该地址写入该地址。
在您的情况下,您实际上已经离开堆栈很长时间了,分配之后的所有内容都已经与堆发生冲突。您只是没有注意到,因为后卫没有触发并且被完全跳过了。
将空页映射到有效堆栈区域下方,而不是保留读取的受保护保护页或使其完全未映射是特定于环境的。
使堆栈紧密堆积在一起,实际上您可以完全跳过保护页面(分配足够大的内存),这也是特定于环境的。根据所使用的编译器的不同,可以使用一个选项来捕获此错误,该选项强制堆栈分配以增量方式进行,一次最多一页。(例如-fstack-check
,GCC。)
使用Valgrind之类的工具来设置防御性更强的环境,以更轻松地捕获此类错误。然后,这将在创建数组时触发,而不仅仅是在第一个非零写入时触发。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句