私は、arrayfire を使用して、生物学に触発されたニューラル ネットワークをシミュレートするプロジェクトに取り組んでいます。いくつかのタイミング テストを行うところまで来ましたが、得られた結果にがっかりしました。私は、タイミング テスト ケースの最も高速で汚れのないモデルの 1 つである Izhikevich モデルを試してみることにしました。そのモデルで新しいテストを実行したところ、結果はさらに悪化しました。私が使用しているコードは以下のとおりです。派手なことをしているわけではありません。それは単なる標準的な行列代数です。ただし、たった 10 個のニューロンの方程式を 1 回評価するのに 5 秒以上かかります。その後のすべての停車時間もほぼ同じです。
コード:
unsigned int neuron_count = 10;
array a = af::constant(0.02, neuron_count);
array b = af::constant(0.2, neuron_count);
array c = af::constant(-65.0, neuron_count);
array d = af::constant(6, neuron_count);
array v = af::constant(-70.0, neuron_count);
array u = af::constant(-20.0, neuron_count);
array i = af::constant(14, neuron_count);
double tau = 0.2;
void StepIzhikevich()
{
v = v + tau*(0.04*pow(v, 2) + 5 * v + 140 - u + i);
//af_print(v);
u = u + tau*a*(b*v - u);
//Leaving off spike threshold checks for now
}
void TestIzhikevich()
{
StepIzhikevich();
timer::start();
StepIzhikevich();
printf("elapsed seconds: %g\n", timer::stop());
}
異なる数のニューロンのタイミング結果を次に示します。
結果:
neurons seconds
10 5.18275
100 5.27969
1000 5.20637
10000 4.86609
ニューロンの数を増やしても、大きな効果はないようです。タイムが少し下がります。ここで何か間違ったことをしていますか?より良い結果を得るために arrayfire を最適化するより良い方法はありますか?
v 方程式を pow(v, 2) の代わりに v*v を使用するように切り替えたところ、ステップに必要な時間は 3.75762 に短縮されました。ただし、それでも非常に遅いため、奇妙なことが起こっています。
[編集] 処理を分割しようとしたところ、何か新しいことがわかりました。ここに私が今使っているコードがあります。
コード:
unsigned int neuron_count = 10;
array a = af::constant(0.02, neuron_count);
array b = af::constant(0.2, neuron_count);
array c = af::constant(-65.0, neuron_count);
array d = af::constant(6, neuron_count);
array v = af::constant(-70.0, neuron_count);
array u = af::constant(-20.0, neuron_count);
array i = af::constant(14, neuron_count);
array g = af::constant(0.0, neuron_count);
double tau = 0.2;
void StepIzhikevich()
{
array j = tau*(0.04*pow(v, 2));
//af_print(j);
array k = 5 * v + 140 - u + i;
//af_print(k);
array l = v + j + k;
//af_print(l);
v = l; //If this line is here time is long on second loop
//g = l; //If this is here then time is short.
//u = u + tau*a*(b*v - u);
//Leaving off spike threshold checks for now
}
void TestIzhikevich()
{
timer::start();
StepIzhikevich();
printf("elapsed seconds: %g\n", timer::stop());
timer::start();
StepIzhikevich();
printf("elapsed seconds: %g\n", timer::stop());
}
v に再度割り当てたり、新しい変数 g に割り当てたりせずに実行すると、1 回目と 2 回目の実行の両方でステップの時間が短くなります。
結果:
経過秒数: 0.0036143
経過秒数: 0.00340621
ただし、 v = l; と入力すると、戻ると、最初に実行するときは高速ですが、それ以降は遅くなります。
結果:
経過秒数: 0.0034497
経過秒数: 2.98624
これを引き起こしている原因についてのアイデアはありますか?
[編集2]
なぜこれを行うのかはまだわかりませんが、再度使用する前に v 配列をコピーすることで回避策を見つけました。
コード:
unsigned int neuron_count = 100000;
array v = af::constant(-70.0, neuron_count);
array u = af::constant(-20.0, neuron_count);
array i = af::constant(14, neuron_count);
double tau = 0.2;
void StepIzhikevich()
{
//array vp = v;
array vp = v.copy();
//af_print(vp);
array j = tau*(0.04*pow(vp, 2));
//af_print(j);
array k = 5 * vp + 140 - u + i;
//af_print(k);
array l = vp + j + k;
//af_print(l);
v = l; //If this line is here time is long on second loop
}
void TestIzhikevich()
{
for (int i = 0; i < 10; i++)
{
timer::start();
StepIzhikevich();
printf("loop: %d ", i);
printf("elapsed seconds: %g\n", timer::stop());
timer::start();
}
}
これが今の結果です。2 回目の実行は少し遅いですが、その後は高速です。以前に比べて大幅な改善。
結果: ループ: 0 秒経過: 0.657355
ループ: 1 秒経過: 0.981287
ループ: 2 経過秒: 0.000416182
ループ: 3 経過秒: 0.000415045
ループ: 4 経過秒: 0.000421014
ループ: 5 経過秒: 0.000413339
ループ: 6 経過秒: 0.00041675
ループ: 7 経過秒: 0.000412202
ループ: 8 経過秒: 0.000473321
ループ: 9 経過秒: 0.000677432