我的神经网络导致堆栈溢出

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;

    // ...
};

这应该有助于消除堆栈溢出。