【问题标题】:FANN: Memory leak when training ANN using data read from multiple filesFANN:使用从多个文件读取的数据训练 ANN 时的内存泄漏
【发布时间】:2019-07-25 07:26:25
【问题描述】:

我有以下循环:

for (int i = 1; i <= epochs; ++i) {
    for (std::vector<std::filesystem::path>::iterator it = batchFiles.begin(); it != batchFiles.end(); ++it) {
        struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());
        fann_shuffle_train_data(data);
        float error = fann_train_epoch(ann, data);
    }
}

ann 是网络。
batchFilesstd::vector&lt;std::filesystem::path&gt;

此代码遍历文件夹中的所有训练数据文件,并每次使用它来训练 ANN,次数由 epochs 变量确定。

以下行导致内存泄漏:

struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());

问题是我必须经常在训练文件之间切换,因为我没有足够的内存来一次加载它们,否则我只会加载一次训练数据。

为什么会这样?我该如何解决这个问题?

【问题讨论】:

  • 我不熟悉 ANN,但 documentation here 建议应该在每次迭代时调用 fann_destroy_train
  • 谢谢。我不知道数据有自己的破坏功能,因为fann_destroy 不接受训练数据。

标签: c++ memory-leaks neural-network fann


【解决方案1】:

在 C++ 中,当管理它的对象超出范围时,会自动释放内存。 (假设类被正确编写。)这就是RAII

但 FANN 提供的是 C API,而不是 C++ API。在 C 中,您需要在完成后手动释放内存。通过扩展,当 C 库为您创建一个对象时,它通常需要您在完成该对象时告诉它。库没有很好的方法来自行确定何时应该释放对象的资源。

约定是,只要 C API 为您提供像 struct foo* create_foo() 这样的函数,您就应该寻找像 void free_foo(struct foo* f) 这样的对应函数。它是对称的。

就您而言,正如 PaulMcKenzie 最初指出的那样,您需要 void fann_destroy_train_data(struct fann_train_data * train_data)。来自the documentation,强调我的:

销毁训练数据并正确释放所有相关数据。 请务必在使用完训练数据后调用此函数。

【讨论】:

    【解决方案2】:

    由于需要调用 fann_destroy_train_data,因此您可以使用以下包装器来利用 C++ 和 RAII:

    struct fann_wrapper
    {
       fann_train_data *td;
       fann_wrapper(fann_train_data* p) : td(p) {}
       ~fann_wrapper() { fann_destroy_train_data(td); }
    };
    //...
    for (int i = 1; i <= epochs; ++i) {
        for (std::vector<std::filesystem::path>::iterator it = batchFiles.begin(); it != batchFiles.end(); ++it) {
            struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());
    
            // the next line ensures that fann_destroy_train_data is called
            fann_wrapper fw(data);
    
            fann_shuffle_train_data(data);
            float error = fann_train_epoch(ann, data);
        }  // when this curly brace is encountered, the fann_destroy_train_data is always called
    }  
    

    fann_wrapper 只保存fain_train_data 指针,在fann_wrapper 销毁时,fann_train_data 被销毁。

    这比原始的C 方法更安全的原因是在可能引发异常的情况下(无论出于何种原因)。如果抛出异常,那么在使用 fann_wrapper 时,fann_train_data总是被销毁。 C 方法无法保证这种情况,因为异常会完全跳过任何具有 fann_destroy_train_data 的行。

    例子:

    for (int i = 1; i <= epochs; ++i) {
        for (std::vector<std::filesystem::path>::iterator it = batchFiles.begin(); it != batchFiles.end(); ++it) {
            struct fann_train_data *data = fann_read_train_from_file(it->string().c_str());
            fann_shuffle_train_data(data);
            float error = fann_train_epoch(ann, data);
    
            fann_destroy_train_data(data); // this line is not executed if an exception is thrown above, thus a memory leak
        }
    }  
    

    这就是为什么 RAII 是 C++ 中的一个重要概念。无论退出可执行代码块的原因是什么(抛出异常、return 完成等),资源都会自动清理。

    【讨论】:

      猜你喜欢
      • 2012-11-19
      • 1970-01-01
      • 2020-01-27
      • 2023-03-26
      • 2019-12-07
      • 2017-11-08
      • 1970-01-01
      • 2014-01-13
      相关资源
      最近更新 更多