如何提高性能而不去并行我的反向人工神经网络
How to improve performance without going parallel for my backprop ANN
在分析我的反向传播算法后,我了解到它占用了我60%的计算时间。在我开始寻找并行的替代方案之前,我想看看我还能做些什么。
配置activate(const double input[])
函数只占用~5%的时间。gradient(const double input)
功能实现如下:
inline double gradient(const double input) { return (1 - (input * input)); }
所讨论的训练函数:
void train(const vector<double>& data, const vector<double>& desired, const double learn_rate, const double momentum) {
this->activate(data);
this->calculate_error(desired);
// adjust weights for layers
const auto n_layers = this->config.size();
const auto adjustment = (1 - momentum) * learn_rate;
for (size_t i = 1; i < n_layers; ++i) {
const auto& inputs = i - 1 > 0 ? this->outputs[i - 1] : data;
const auto n_inputs = this->config[i - 1];
const auto n_neurons = this->config[i];
for (auto j = 0; j < n_neurons; ++j) {
const auto adjusted_error = adjustment * this->errors[i][j];
for (auto k = 0; k < n_inputs; ++k) {
const auto delta = adjusted_error * inputs[k] + (momentum * this->deltas[i][j][k]);
this->deltas[i][j][k] = delta;
this->weights[i][j][k] += delta;
}
const auto delta = adjusted_error * this->bias + (momentum * this->deltas[i][j][n_inputs]);
this->deltas[i][j][n_inputs] = delta;
this->weights[i][j][n_inputs] += delta;
}
}
}
}
这个问题可能更适合https://codereview.stackexchange.com/.
如果你想训练/使用NN,你不能避免O(n^2)算法。但它非常适合矢量运算。例如,通过巧妙地使用SSE或AVX,你可以处理4或8个神经元块,并使用乘法-加法而不是两个单独的指令。
如果您使用现代编译器并仔细地重新制定算法并使用正确的开关,您甚至可以让编译器为您自动向量化循环,但您的里程可能会有所不同。
对于gcc,使用-O3或-ftree-vectorize激活自动矢量化。当然,您需要一个具有矢量功能的cpu,例如-march=core2 -mssse4.1或类似的内容,具体取决于目标cpu。如果您使用-ftree-vectorizer-verbose=2,您将得到详细的解释,为什么以及在哪里没有对循环进行矢量化。看一看http://gcc.gnu.org/projects/tree-ssa/vectorization.html
当然是直接使用编译器的内部函数。
你想从循环中消除条件:
const double lower_layer_output = i > 0 ? outputs[lower_layer][k] : input[k]; // input layer semantics
您可以通过提前计算第0次迭代(i==0的特殊情况)来消除此条件。
deltas[i][j][k] = delta;
weights[i][j][k] += delta;
你提到使用std::vector,所以这是vector的vector的vector?您的数据而不是将是连续的(除非每个向量是连续的)。考虑使用C风格的数组。
这些尺寸有多大?如果非常大,可能需要考虑一些缓存问题。例如,你不希望最后一个下标[k]刷新L1缓存。有时打破循环,一次处理较小范围的k个索引会有所帮助(条带开采)。
你也可以尝试稍微展开你的内部循环,例如尝试在循环内执行4或8个操作。分别增加4/8,并在另一个循环中处理任何余数。编译器可能已经这样做了。
正如其他人提到的,使用SIMD (SSE/AVX)可能是您可以获得最大收益的地方。你既可以使用编译器的内在特性(链接到Visual Studio,但gcc支持相同的语法),也可以用汇编编写(内联或其他)。正如你提到的,跨多核扩展是另一个方向。OpenMP可以帮你轻松地做到这一点。
有时候从你的代码中生成一个带注释的汇编清单是很有用的,这样可以尝试看看编译器在哪些地方做得不好。
这是一个很好的关于优化的通用资源。
我不确定编译器是否可以在您的情况下优化它,但是在较低的循环中将inverse_momentum * (learn_rate * errors[i][j])
输出到循环"k"之外的变量可能会减少CPU上的负载。
顺便说一句,你是在分析一个发布二进制文件,而不是调试二进制文件,对吗?
我不喜欢valarray,但我有一种预感,这里有相当多的机会。
闪电战++(增强)似乎有一个更好的光环围绕网络,但我不知道:)
我自己开始做PoC,但是有太多的代码缺失
void activate(const double input[]) { /* ??? */ }
const unsigned int n_layers_ns;
const unsigned int n_layers;
const unsigned int output_layer_s;
const unsigned int output_layer;
T/*double?*/ bias = 1/*.0f?/;
const unsigned int config[];
double outputs[][];
double errors [][];
double weights[][][];
double deltas [][][];
现在,从逻辑上来看,至少数组的第一个(第0个)索引是由4个缺失的常量定义的。如果可以在编译时知道这些常量,它们将成为很有价值的类模板参数:
template <unsigned int n_layers_ns = 2,
unsigned int n_layers = 3>
struct Backprop {
void train(const double input[], const double desired[], const double learn_rate, const double momentum);
void activate(const double input[]) { }
enum _statically_known
{
output_layer = n_layers_ns - 1,
output_layer_s = n_layers - 1, // output_layer with input layer semantics (for config use only)
n_hidden_layers = output_layer - 1,
};
static const double bias = 1.0f;
const unsigned int config[];
double outputs[3][50]; // if these dimensions could be statically known,
double errors[3][50]; // slap them in valarrays and
double weights[3][50][50]; // see what the compiler does with that!
double deltas[3][50][50]; //
};
template <unsigned int n_layers_ns,
unsigned int n_layers>
void Backprop<n_layers_ns, n_layers>::train(const double input[], const double desired[], const double learn_rate, const double momentum) {
activate(input);
// calculated constants
const double inverse_momentum = 1.0 - momentum;
const unsigned int n_outputs = config[output_layer_s];
// calculate error for output layer
const double *output_layer_input = output_layer > 0 ? outputs[output_layer] : input; // input layer semantics
for (unsigned int j = 0; j < n_outputs; ++j) {
//errors[output_layer][j] = f'(outputs[output_layer][j]) * (desired[j] - outputs[output_layer][j]);
errors[output_layer][j] = gradient(output_layer_input[j]) * (desired[j] - output_layer_input[j]);
}
[... snip ...]
注意我是如何对第一个循环中的语句重新排序的,以使循环变得微不足道。现在,我可以想象最后几行变成
// calculate error for output layer
const std::valarray<double> output_layer_input = output_layer>0? outputs[output_layer] : input; // input layer semantics
errors[output_layer] = output_layer_input.apply(&gradient) * (desired - output_layer_input);
这将需要为输入设置适当的(g)片。我想不出这些要怎么标注尺寸。问题的关键在于,只要这些切片尺寸可以由编译器静态地确定,您就有可能节省大量的时间,因为编译器可以在FPU堆栈或使用SSE4指令集上将它们优化为矢量化操作。我想你会像这样声明你的输出:
std::valarray<double> rawoutput(/*capacity?*/);
std::valarray<double> outputs = rawoutput[std::slice(0, n_outputs, n_layers)]; // guesswork
(我希望权重和δ必须变成gslice,因为它们是三维的)
杂项(对齐、布局)
我意识到,如果数组的秩(维度)不是最优排序(例如,valarray中的第一个秩相对较小,例如8),可能不会有太大的增益。这可能会妨碍向量化,因为参与的元素可能分散在内存中,我认为优化需要它们相邻。
从这个角度来看,重要的是要认识到,排名的"最佳"排序最终仅取决于访问模式(因此,再次配置文件和检查)。
此外,优化的机会可能会受到不幸的内存对齐[1]的阻碍。在这种情况下,您可能希望将(val)数组和的排序顺序转换为最接近的2的幂(或者更实际地说,是32字节的倍数)。
如果所有这些确实产生了很大的影响(首先配置文件/检查生成的代码!)我想支持
- Blitz++或boost可能包含帮助器(分配器?)来强制对齐
- 你的编译器将有属性(align和/或restrict类型)告诉它们它们可以假设输入指针的这些对齐方式
:
如果执行顺序不重要(即因子的相对数量级非常相似),而不是
inverse_momentum * (learn_rate * ???)
你可以取
(inverse_momentum * learn_rate) * ???
并预先计算第一个子积。然而,从它明确地以这种方式排序的事实来看,我猜这会导致更多的噪音。
[1]免责声明:我实际上没有做任何分析,我只是把它放在那里,这样你就不会错过'though joint'(英语怎么说)
- 我的神经网络不起作用 [XOR 问题]
- 为什么我的程序在打开网络设备时遇到问题
- OpenCV 3 中的神经网络权重
- 部署在张量流中训练的神经网络来火炬C++的最佳方法是什么?
- 我不确定如何引用此神经网络训练方法中的权重
- 如何在不同的平台/技术中使用经过训练的神经网络?
- 为什么我的神经网络停滞在一定的成本附近?
- 具有静态 std::array 的神经网络比使用动态 C 数组的神经网络慢
- 为什么小型和大型加载的神经网络占用相同数量的 RAM?
- 一维阵列的运动检测(神经网络或其他选项?
- 使用在R中训练的神经网络来预测C 中的新数据
- 我的神经网络只学习一些数据集
- 我的前馈网络给了我一个平均我真正想要的输出
- 超级最小的XOR神经网络无法学习..我做错了什么
- 我的神经网络学习sin x,但不学习cos x
- 我的分类神经网络有逻辑问题
- 具有大输入和输出的人工神经网络
- 使用像素颜色的人工神经网络
- 在我的神经网络程序中使用的最有效的数据结构是什么?我的程序需要动态分配吗?
- 如何提高性能而不去并行我的反向人工神经网络