我在机器学习课程中使用C++实现人工神经网络。以下是我的“神经元”代码 –
struct Neuron{ float *inputs; // 指向输入数组的指针 int inputCount; // 输入数量 float *weights; float sum; // 输入的线性权重和 float output = 0.0; // 激活函数的输出 float bias = 1.0; float delta; // 误差梯度 Neuron(){} Neuron(int inputCount) { this->inputCount = inputCount; this->weights = new float [inputCount]; InitializeWeights(); } float ActivationFunction(float x) { return 1.0 / (1.0 + exp(-x)); } void Calculate(float *inputs) { this->inputs = inputs; float temp = 0.0; for(int i=0; i<inputCount; i++) { temp += weights[i] * inputs[i]; } temp += bias; sum = temp; output = ActivationFunction(sum); } void InitializeWeights() { for(int i=0; i<inputCount; i++) { weights[i] = rand() % 101 / 100; } } ~Neuron() { delete[] weights; }};
我还有另一个名为“Layer”的结构体,用于表示一个层。神经元在那里初始化如下 –
for(int i=0; i<neuronCount; i++){ neurons[i] = Neuron(inputCount);}
其中“neuronCount”是层中的神经元数量。问题是神经元中的权重数组会在析构函数调用时立即被释放。我应该如何防止这种情况发生?更重要的是,有没有更好的设计程序的方法?
回答:
你的代码存在几个问题。
首先,你在默认构造函数中没有初始化input
和weights
指针。因此,以下简单的程序会导致问题:
int main(){ Neuron n;}
因为n
的析构函数会尝试对未初始化的指针weights
调用delete []
。
另一个问题是你的Neuron
类不能安全地复制或赋值。以下程序也会显示内存泄漏和双重删除错误的问题:
int main(){ Neuron n(10); Neuron n2 = n; // 复制构造函数 Neuron n3(2); n = n3; // 赋值} // 一旦main()退出,就会出现内存泄漏和双重删除问题。
第三个问题是你在Calculate
函数中传递了一个float
指针,你不知道这个指针在函数内部是否有效,或者即使它是有效的,inputs
指针代表了多少个项目。如果数量少于weights
的数量,你的代码在尝试访问inputs[i]
时会发生内存溢出,一旦i
超出了input
的界限。
为了解决这些问题,最简单的方法是使用std::vector
:
#include <vector>#include <algorithm>#include <cmath>#include <cstdlib>struct Neuron{ std::vector<float> inputs; // 指向输入数组的指针 std::vector<float> weights; float sum = 0; // 输入的线性权重和 float output = 0.0; // 激活函数的输出 float bias = 1.0; float delta = 0; // 误差梯度 Neuron() {} Neuron(int inputCount) : weights(inputCount) { InitializeWeights(); } float ActivationFunction(float x) { return 1.0 / (1.0 + exp(-x)); } void Calculate(const std::vector<float>& inputs_) { inputs = inputs_; // 确保向量大小相同。如果inputs较小, // 新添加的元素将为0 inputs.resize(weights.size()); // 使用inner_product获取乘积的和。 sum = std::inner_product(std::begin(weights), std::end(weights), std::begin(inputs), 0.0f) + bias; output = ActivationFunction(sum); } void InitializeWeights() { std::generate(std::begin(weights), std::end(weights), rand() % 101 / 100); }};
请注意,我们不再使用指针,因为std::vector
负责内存管理。我们也不需要有跟踪计数的成员变量(如inputCount
),因为向量可以通过vector::size()
知道自己的大小。
此外,std::inner_product用于在Calculate
函数中生成和(在调整input
向量大小与weights
相同之后)。inputs_
作为std::vector
传递给Calculate
,因此你可以使用大括号初始化列表调用它:
someInstance.Calculate({9.8, 5.6, 4.5}); // 使用包含3个项目的向量调用Calculate。
此外,InitializeWeights
函数调用std::generate算法函数来设置weights
向量。
如果你不使用std::vector
,那么你的类将需要一个复制构造函数和赋值运算符,遵循三原则。我不会详细说明如何在不使用vector
的情况下修复你的类,因为答案会变得更加复杂。
最后一点是,在C++中使用rand()
应该被替换为使用C++11随机数设施。