【问题标题】:My Neural network is causing a stack overflow我的神经网络导致堆栈溢出
【发布时间】:2017-11-30 06:44:38
【问题描述】:

我很难生成一个最小的示例,因为我认为它与我的更多代码有关。不过,我相信我已经在下面提供了相关代码。

我已经删除了我认为对问题不是很重要的类等部分。

我有一个使用神经元的神经网络类:

神经元

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&lt;86,86,2&gt;,一切正常,但考虑到我需要更多输入变量,即NeuralNetwork&lt;170,170,2&gt;,当我启用-O2 编译器标志时,FeedForward 方法会产生堆栈溢出。设置-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();
}

这没有意义,因为循环没有嵌套......

【问题讨论】:

  • NumOutputs 的典型值是多少? returnValue 分配在堆栈上,所以如果它足够大,它会导致你的问题。在您的“不”示例中,由于您从不写信给它,returnValue 可能会被优化掉。
  • NumOutputs 是 2
  • 另外:你是在堆栈还是堆上分配网络?那是一个大类,所以当你到达那个点时,你可能已经没有多少筹码了。
  • 我在堆栈上分配。但你知道为什么,我刚刚意识到我正在复制它,而这在代码的另一部分是不必要的。在我调用FeedForward 时,第二个本地堆栈变量应该已经被删除,但删除它似乎可以解决问题。将来,我会在堆上分配这么大的东西
  • 在 Windows 上,默认最大值。堆栈大小为每个线程 1MB。在我的计算中,您的 NeuralNetwork&lt;170,170,2&gt; 已经大于 700,000 字节。我推荐像std::make_unique&lt;NeuralNetwork&lt;170, 170, 2&gt;&gt; 这样的堆分配。

标签: c++ stack-overflow


【解决方案1】:

堆栈溢出仅在堆栈边界之外的第一个实际写入时检测到,分别在实际命中保护页时。由于您使用 0 初始化 Neuron 类中的所有内容,这使得您的 Neuron 最初都是 Nullbytes。这与您的环境将内存初始化到的内容完全匹配(实际上并未初始化,但映射到仅包含 Nullbytes 的共享只读页面)。

一旦第一个非空字节被写入保护页面,它就会触发页面错误(如果写入地址是合法的,共享空页面会被 RAM 中的真实页面替换)。结果,堆栈溢出被检测到,因为这个地址不应该被写入。

在您的情况下,您实际上早就离开了堆栈,并且分配之后的所有内容都已经与堆发生冲突。你只是没有注意到,因为守卫没有触发并且被完全跳过。

在有效堆栈区域下方映射一个空页,而不是留下一个读保护的保护页或让它完全未映射是特定于环境的。

堆栈和堆靠得很近,你实际上可以完全跳过保护页,分配足够大,也是特定于环境的。根据您使用的编译器,您可以使用强制堆栈分配增量发生的选项来捕获此错误,一次最多一页。 (例如,-fstack-check 代表 GCC。)

使用像 Valgrind 这样的工具,它可以设置一个更具防御性的环境,以便更轻松地捕获此类错误。这将在创建数组时触发,而不是仅在第一次非零写入时触发。

【讨论】:

    【解决方案2】:

    由于您的 Neuron 类是一个“大型类对象”,在堆上创建它可能是一个更好的解决方案,包含对象也是如此。这种性质的东西:


    • 使用或管理 NeuralNetwork 的对象可能包含该 NeuralNetwork 对象的 std::unique_ptr&lt;T&gt;:如果其他外部类 对象需要直接访问它,然后您可以简单地将其更改为 std::shared_ptr&lt;T&gt;

    • NeuralNetwork 本身可能在其多个容器中包含大量的 Neuron 对象。同样在这里,我们可以使用智能容器,但在这种情况下,我认为在 std::array&lt;&gt; 容器中使用 std::shared_ptr&lt;T&gt; 会更安全,因为这些神经元可能也需要被其他类访问。如果这些神经元被管理并完全包含在 NeuralNetwork 类中,并且只有 1 个可用的 NeuralNetwork 对象,那么std::unique_ptr&lt;T&gt; 将起作用。我会在这个类中使用std::shared_ptr&lt;T&gt;,以防有人想要多个共享神经元资源的神经网络对象。

    • 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;
    
        // ...
    };
    

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

    【讨论】:

      猜你喜欢
      • 2015-05-21
      • 2014-02-14
      • 1970-01-01
      • 1970-01-01
      • 2017-02-04
      • 1970-01-01
      • 1970-01-01
      • 2014-09-06
      • 2015-02-02
      相关资源
      最近更新 更多