FANN:使用从多个文件读取的数据训练ANN时发生内存泄漏

时间:2019-03-04 01:30:41

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

我有以下循环:

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<std::filesystem::path>

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

以下行导致内存泄漏:

  

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

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

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

2 个答案:

答案 0 :(得分:3)

在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中,重点是我:

  

销毁训练数据并适当地分配所有关联数据。 请务必在使用完训练数据后调用此函数。

答案 1 :(得分: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_train_datafann_wrapper会被总是销毁。这种保证不能用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等),资源都会自动清理。