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

时间:2017-11-30 06:44:38

标签: c++ stack-overflow

我很难创建一个最小的例子,因为我认为它与我的一些代码有关。但是,我相信我已经在下面提供了相关的代码。

我删除了一些我认为对问题不重要的课程等。

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

神经元

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();
}

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

2 个答案:

答案 0 :(得分:1)

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

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

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

将空页面映射到有效堆栈区域下方,而不是保留读取保护的防护页面或完全取消映射它是特定于环境的。

堆叠和堆叠在一起,你可以用一个足够大的分配来完全跳过防护页面,也是特定于环境的。根据您使用的编译器,您可以通过一个选项来捕获此错误,该选项强制堆栈分配以增量方式发生,一次最多一页。 (例如,GCC的-fstack-check。)

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

答案 1 :(得分:0)

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

  • 使用或管理NeuralNetwork的对象可能包含该神经网络对象的std::unique_ptr<T>:如果是其他外部类 对象需要直接访问,然后您可以简单地将其更改为 std::shared_ptr<T>

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

  • 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;

    // ...
};

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