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

3

我很难提供一个最小的例子,因为我认为这与我的其他代码有关。不过,我相信我已经提供了下面的相关代码。

我删除了一些我认为对问题不太重要的类等部分。

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

神经元

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>时,启用-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次。
正如在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”在堆栈上分配,因此如果它足够大,它会导致问题。在您的“doesn't”示例中,由于您从未对其进行写入操作,“returnValue”可能会被优化掉。 - user4442671
NumOutputs 是 2。 - NeomerArcana
还有:您将网络分配在栈上还是堆上? 那是一个非常大的类,因此当您首次到达该点时可能没有太多的栈空间。 - user4442671
1
我正在堆栈上分配内存。但是你知道为什么吗,我刚刚意识到在代码的另一部分中我正在复制它,这是不必要的。那个第二个本地堆栈变量应该在我调用FeedForward时被删除,但是删除它似乎解决了问题。将来,我会在堆上分配这个大块内存。 - NeomerArcana
在Windows上,每个线程的默认最大堆栈大小为1MB。根据我的计算,您的 NeuralNetwork<170,170,2> 已经超过了700,000字节。我建议使用堆分配,例如 std::make_unique<NeuralNetwork<170, 170, 2>> - Arne Vogel
2个回答

1
堆栈溢出仅在第一次实际写入超出堆栈边界时检测到,也就是当实际命中保护页时。由于您在Neuron类中使用0初始化了所有内容,因此这使得您的Neuron最初都是Nullbytes。这与您的环境将内存初始化为完全匹配(实际上没有初始化,但映射到只包含Nullbytes的共享只读页面)。

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

在您的情况下,您实际上已经离开了堆栈,并且分配之后的所有内容已经与堆碰撞。您只是没有注意到,因为保护没有触发并被完全跳过。

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

拥有如此接近的堆栈和堆,以至于您可以使用足够大的分配完全跳过守卫页,这也是与环境有关的。根据您使用的编译器,您可以使用强制堆栈分配逐步进行的选项来捕获此错误,最多每次一页面(例如GCC的-fstack-check)。
使用类似Valgrind的工具设置更具防御性的环境以更轻松地捕获此类错误。这将在创建数组时立即触发,而不仅仅是在第一个非零写入时触发。

0

由于您的神经元类是一个“大型类对象”,在堆上创建它可能是更好的解决方案,同时包含它的对象也是如此。可以尝试以下方式:


  • 使用或管理NeuralNetwork的对象可以包含该NeuralNetwork对象的std::unique_ptr<T>:如果其他外部类对象需要直接访问它,那么可以简单地将其更改为std::shared_ptr<T>

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

  • 从您提供的源代码来看,神经元类对象本身似乎很好。


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

    // ...
};

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接