我的神经网络导致堆栈溢出
My Neural network is causing a stack overflow
我很难给出一个最小的例子,因为我认为它与我的更多代码有关。但是,我相信我已经在下面提供了相关代码。
我删除了 classes 等我认为对问题不是很重要的部分。
我有一个使用神经元的神经网络 class:
神经元
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
class 中看到的那样,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();
}
这没有意义,因为循环没有嵌套...
堆栈溢出仅在第一个 actual 写入堆栈边界外时检测到,分别是在实际命中保护页时。由于您将 Neuron
class 中的所有内容都初始化为 0,这使得您的 Neuron
最初都是空字节。这与您的环境将内存初始化为的内容完全匹配(实际上并未初始化,而是映射到仅包含 Nullbytes 的共享只读页面)。
一旦第一个非空字节被写入保护页面,它就会触发页面错误(如果写入该地址是合法的,共享空页面将被 RAM 中的真实页面替换)。结果,检测到堆栈溢出,因为不应该写入此地址。
在你的例子中,你实际上早已离开堆栈,并且分配之后的所有内容都已经与堆发生冲突。你只是没有注意到,因为守卫没有触发并且被完全跳过了。
在有效堆栈区域下方映射一个空页,而不是保留一个读保护保护页或完全不映射它是特定于环境的。
堆栈和堆靠近在一起,您实际上可以完全跳过保护页,分配足够大,也是特定于环境的。根据您使用的编译器,您可以使用一个选项来捕获此错误,该选项强制堆栈分配以增量方式发生,一次最多一页。 (例如 -fstack-check
用于 GCC。)
使用诸如 Valgrind 之类的工具,它可以设置更具防御性的环境来更轻松地捕获此类错误。这将在创建数组时触发,而不是仅在第一次非零写入时触发。
由于您的神经元 class 是 "large class object" 在堆上创建它可能是更好的解决方案,因此包含对象也是。这种性质的东西:
使用或管理 NeuralNetwork 的对象可能包含该 NeuralNetwork 对象的 std::unique_ptr<T>
:如果其他 class
objects 需要直接访问它然后你可以简单地将它更改为
std::shared_ptr<T>
神经网络本身可能在其多个容器中包含大量神经元对象。同样在这里,我们可以使用智能容器,但在这种情况下,我认为在 std::array<>
容器中使用 std::shared_ptr<T>
会更安全,因为这些神经元可能需要被其他 classes 访问以及。如果这些神经元被管理并完全包含在 NeuralNetwork class 中,并且只有 1 个可用的 NeuralNetwork 对象,那么 std::unique_ptr<T>
将起作用。我会在这个 class 中使用 std::shared_ptr<T>
以防万一想要有多个共享神经元资源的 NeuralNetwork 对象。
Neuron Class 对象本身在源代码中看起来很好
你提供的。
// Some object that uses NeuralNetwork
#include <memory>
#include <std::container(s)>
#include "NeuralNetwork.h";
class SomeClass {
private:
std::unique_ptr<NeuralNetwork> neuralNetwork;
// ....
};
// NeuralNetwork
#include <memory>
#include <std::container(s)>
#include "Neuron.h"
template<std::size_t NumInputs, std::size_t NumHidden, std::size_t NumOutputs>
class NeuralNetwork {
private:
std::array<std::shared_ptr<Neuron<NumInputs>>,NumHidden> m_hiddenNeurons;
std::array<std::shared_ptr<Neuron<NumHidden>>,NumOutputs> m_outputNeurons;
// ...
};
// Neuron
// #include <Memory> // Shouldn't be needed
#include <std::container(s)>
template<std::size_t NumInputs>
class Neuron {
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;
// ...
};
这应该有助于消除堆栈溢出。
我很难给出一个最小的例子,因为我认为它与我的更多代码有关。但是,我相信我已经在下面提供了相关代码。
我删除了 classes 等我认为对问题不是很重要的部分。
我有一个使用神经元的神经网络 class:
神经元
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
class 中看到的那样,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();
}
这没有意义,因为循环没有嵌套...
堆栈溢出仅在第一个 actual 写入堆栈边界外时检测到,分别是在实际命中保护页时。由于您将 Neuron
class 中的所有内容都初始化为 0,这使得您的 Neuron
最初都是空字节。这与您的环境将内存初始化为的内容完全匹配(实际上并未初始化,而是映射到仅包含 Nullbytes 的共享只读页面)。
一旦第一个非空字节被写入保护页面,它就会触发页面错误(如果写入该地址是合法的,共享空页面将被 RAM 中的真实页面替换)。结果,检测到堆栈溢出,因为不应该写入此地址。
在你的例子中,你实际上早已离开堆栈,并且分配之后的所有内容都已经与堆发生冲突。你只是没有注意到,因为守卫没有触发并且被完全跳过了。
在有效堆栈区域下方映射一个空页,而不是保留一个读保护保护页或完全不映射它是特定于环境的。
堆栈和堆靠近在一起,您实际上可以完全跳过保护页,分配足够大,也是特定于环境的。根据您使用的编译器,您可以使用一个选项来捕获此错误,该选项强制堆栈分配以增量方式发生,一次最多一页。 (例如 -fstack-check
用于 GCC。)
使用诸如 Valgrind 之类的工具,它可以设置更具防御性的环境来更轻松地捕获此类错误。这将在创建数组时触发,而不是仅在第一次非零写入时触发。
由于您的神经元 class 是 "large class object" 在堆上创建它可能是更好的解决方案,因此包含对象也是。这种性质的东西:
使用或管理 NeuralNetwork 的对象可能包含该 NeuralNetwork 对象的
std::unique_ptr<T>
:如果其他 class objects 需要直接访问它然后你可以简单地将它更改为std::shared_ptr<T>
神经网络本身可能在其多个容器中包含大量神经元对象。同样在这里,我们可以使用智能容器,但在这种情况下,我认为在
std::array<>
容器中使用std::shared_ptr<T>
会更安全,因为这些神经元可能需要被其他 classes 访问以及。如果这些神经元被管理并完全包含在 NeuralNetwork class 中,并且只有 1 个可用的 NeuralNetwork 对象,那么std::unique_ptr<T>
将起作用。我会在这个 class 中使用std::shared_ptr<T>
以防万一想要有多个共享神经元资源的 NeuralNetwork 对象。Neuron Class 对象本身在源代码中看起来很好 你提供的。
// Some object that uses NeuralNetwork
#include <memory>
#include <std::container(s)>
#include "NeuralNetwork.h";
class SomeClass {
private:
std::unique_ptr<NeuralNetwork> neuralNetwork;
// ....
};
// NeuralNetwork
#include <memory>
#include <std::container(s)>
#include "Neuron.h"
template<std::size_t NumInputs, std::size_t NumHidden, std::size_t NumOutputs>
class NeuralNetwork {
private:
std::array<std::shared_ptr<Neuron<NumInputs>>,NumHidden> m_hiddenNeurons;
std::array<std::shared_ptr<Neuron<NumHidden>>,NumOutputs> m_outputNeurons;
// ...
};
// Neuron
// #include <Memory> // Shouldn't be needed
#include <std::container(s)>
template<std::size_t NumInputs>
class Neuron {
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;
// ...
};
这应该有助于消除堆栈溢出。