Я не люблю valarray, но у меня есть догадка, что у тех, кто здесь есть, есть довольно широкие возможности.
Blitz ++ (boost), кажется, имеет лучшую ауру в сети, но я нене знаю:)
Я сам начинал работать на 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) индексы для массивов определяются четырьмя пропущенными константами.Если бы эти константы могли быть известны во время компиляции, это сделало бы параметры шаблона класса с большим значением:
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
(Я полагаю, что веса и дельты должны были бы стать gslices, потому что AFAICT они 3-мерные )
Разное (выравнивание, расположение)
Я понял, что, вероятно, не будет большого выигрыша, если ранги (измерения) массивов не будут оптимально упорядочены (например, первый ранг в valarray относительно мал, скажем, 8).Это может препятствовать векторизации, потому что участвующие элементы могут быть разбросаны в памяти, где, я полагаю, оптимизация требует, чтобы они были смежными.
В этом свете важно понимать, что «оптимальное» упорядочение рангов в конечном счете зависиттолько на шаблонах доступа (так что профилируйте и проверьте снова).
Кроме того, возможность оптимизации может быть затруднена неудачным выравниванием памяти [1].В этом свете вам может потребоваться переключить порядок рангов массива (val) и измерений круглого ранга на ближайшие степени 2 (или более того, например, кратные 32 байта).
Если все это действительно оказывает большое влияние (сначала профиль / проверять сгенерированный код!), Я бы предположил поддержку
- , что Blitz ++ или boost могут содержать помощников (распределителей?) Для обеспечения выравнивания
- ваш компилятор будет иметь атрибуты ( align и / или restrict вида), чтобы сообщить им, что они могут принять эти выравнивания для входных указателей
Не связано:
Если порядок исполнения не является критическим (т.е. относительные порядки величин факторов очень похожи), вместо
inverse_momentum * (learn_rate * ???)
вы можете взять
(inverse_momentum * learn_rate) * ???
и предварительно рассчитайте первый субпродукт.Тем не менее, из-за того, что он явно упорядочен таким образом, я предполагаю, что это приведет к большему количеству шума.просто выбросить его туда, чтобы не пропустить «хоть и совместное» (как это для английского)