年 9 月 2015
ボリューム 30 番号 9
テストの実行 - 人工スパイキング ニューロンによるコンピューティング
コンピューター サイエンスの魅力的な分野に、人工スパイキング ニューロンによるコンピューティングがあります。この人工スパイキング ニューロンとは、生物学上のニューロンの動きをモデル化する小さなソフトウェア コンポーネントです。人工スパイキング ニューロンは、一般的なソフトウェア ニューラル ネットワークの人工ニューロンと関連がありますが、まったく別物です。
最初に述べておきますが、今回の人工スパイキング ニューロンというテーマは、プログラミングの日常作業にすぐに役立つものではありません。ただし、コンピューティングの未来について洞察を得たり、人工スパイキング ニューロン自体に興味が湧くと思います。
いつものように、スパイキング ニューロンの雰囲気を感じ、今回の目標を確認するには、図 1 のデモ プログラムを見るのが一番です。
図 1 人工スパイキング ニューロンのデモ
このデモは、1 つのスパイキング ニューロンの入力値と出力値を示しています。3 つの入力ストリームがあります。入力ストリームは、スパイク トレーニングと呼ばれることもあります。
それぞれの入力スパイク トレーニングには、0 または 1 の値が 16 個あります。3 つの入力スパイク トレーニングは以下のとおりです。
0 0 1 0 1 0 1 1 1 1 0 0 1 0 1 1
1 0 1 1 0 0 0 1 1 1 0 1 1 0 1 1
1 1 0 1 0 0 0 1 1 0 1 1 1 1 1 1
これらの 0 または 1 の値は、他のスパイキング ニューロンから受け取る時系列の入力を表します。つまり、時間 t = 0 時点の最初のスパイク トレーニングの入力値は 0 です。t = 1 時点の入力値が 0、t = 2 時点の入力値が 1 と続き、t = 15 時点の入力値が 1 です。
t = 0 時点では、スパイキング ニューロンは 3 つのストリームすべてから入力を受け取ります。したがって、t = 0 時点で受け取る完全な入力は (0, 1, 1) です。t = 1 時点の入力が (0, 0, 1)、t = 15 時点の入力が (1, 1, 1) です。
デモ プログラムの次の部分では、ニューロンの動きを定義する数値定数を表示しています。値がそれぞれ (4, -2, 3) の 3 つの重みがあり、それぞれの入力トレーニングに対応します。図では、リーク電位 (leak potential)=1、しきい電位 (threshold potential)=8、スパイク電位 (spike potential)=4、スパイク後の待機時間 (post-spike latency)=2 を表示していますが、この意味を簡単に説明しておきます。スパイキング ニューロンでは、「potential」という用語を「可能性」や「近い将来起こりうる」という意味ではなく、(大まかには)「電圧」、「電位」の意味で使います。
デモ プログラムは 16 個の時間単位でシミュレーションを行います。各時間単位には、0 または 1 の 3 つの入力値、ニューロンの状態 (active/inactive)、およびニューロンの現在の電位 (V) を表示します。t = 6 時点では、ニューロンが電位 V = 8 に達し、V = 12 へとスパイク (急上昇) します。この背後では、一連の各入力値が処理され、出力値 0 または 1 を生成して、バッファーに保存します。すべての入力値の処理が完了すると、デモ プログラムは結果の出力スパイク トレーニングを表示します。
0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0
ここまで説明しても、あまりピンとこないと思います。0 または 1 のかたまりを入力して、0 または 1 のかたまりを出力しているにすぎません。ですが、もう少し我慢して最後までお読みいただければ、人工スパイキング ニューロンが、コンピューターやコンピューティングを根本的に変える可能性があることがわかります。
今回は、初級以上のプログラミング スキルがあることを前提にしていますが、スパイキング ニューロンについて何も知らなくても問題ありません。デモ プログラムは C# を使用してコーディングしていますが、コードを Python や JavaScript などの別の言語にリファクタリングするのもそれほど難しくはありません。デモ コードは短いため、このコラムにコード全体を掲載していますが、付属のコード ダウンロードから入手することもできます。
スパイキング ニューロンについて
図 2 のグラフをご覧ください。グラフは、デモのスパイキング ニューロンの電位値 V を t = 0 ~ t = 15 の範囲で示しています。スパイキング ニューロンには多種多様なバリエーションがあります。今回のデモ プログラムは、「リークを有する積分発火スパイキング ニューロン」(leaky integrate-and-fire spiking neuron) というモデルに基づいています。
図 2 リークを有する積分発火スパイキング ニューロンの動き
ニューロンの電位は、時間の経過と共に増加する傾向があります。電位 (V) の値が、破線で示すしきい電位 8 になるか、それを超えると瞬時に 4 電位 (スパイク電位) 分急上昇した直後 0 にリセットされます。このスパイキング イベントが t = 6 と t = 13 で発生しています。スパイキング イベントが発生すると 1、それ以外は 0 がスパイク トレーニングの出力になります。
そこで、各 t 値の V の計算方法が問題です。t = 0 時点の 3 つの入力値は (0, 1, 1) です。ニューロンの 3 つの重み値は (4, -2, 3) でした。まず、各入力値と各重み値の積の総和を求め、その結果を現在の V 値に加算します。デモ開始時点の V が 0 だとすると、以下のように計算します。
V = V + sum
= 0 + (0)(4) + (1)(-2) + (1)(3)
= 0 + 1
= 1
これは積分の手順です。次に、リーク値を減算します。今回のデモの場合はリーク電位を 1 にしているので、以下の計算になります。
V = V - leak
= 1 - 1
= 0
新しい電位 V = 0 はしきい電位 8 を超えないため、ニューロンでスパイクは発生せず、スパイク トレーニングの出力は 0 です。次に、t = 1 時点の入力値は (0, 1, 1) です。積分の手順とリークの手順から、以下の計算式になります。
V = V + sum - leak
= 0 + (0)(4) + (0)(-2) + (1)(3) - 1
= 0 + 3 - 1
= 2
これが、リークを有する積分発火モデルの人工スパイキング ニューロンで実にシンプルです。値がすべて整数値のため、設計が簡潔になります。つまり、この人工ニューロンは、ソフトウェアまたはハードウェアで効率よく実装できます。
図 2 では、スパイキング イベントが t = 6 時点で発生していますが、これは t = 5 時点の V = 5 から積分手順とリーク手順を使用して以下のように求められます。
V = V + sum - leak
= 5 + (1)(4) + (0)(-2) + (0)(3) - 1
= 5 + 4 - 1
= 8
この時点で V はしきい電位の 8 に達するため、V の値は 8 + 4 = 12 に急上昇してトレーニング結果として 1 を出力後、電位 V が即座に 0 にリセットされます。スパイキング イベント発生後は、シミュレーション対象のニューロンは非アクティブ (inactive) の状態に入るため、入力値は無視されます。今回のデモでは、スパイキング後の待機時間を 2 にしているため、t = 7 時点と t = 8の時点は入力の値に関係なく V は 0 のままです。t = 9 時点で、ニューロンがアクティブ (active) になり、通常の動きに戻ります。
デモ プログラムの実装
スペースを節約するために少し編集したデモ プログラムのコードを図 3 に示します。デモ プログラムを作成するには、Visual Studio を起動して、「SpikingNeuron」という新しい C# コンソール アプリケーション プロジェクトを作成します。このデモ プログラムは、Microsoft .NET Framework との大きな依存関係がないので、比較的新しいバージョンの Visual Studio であれば動作します。
図 3 スパイキング ニューロン プログラム
using System;
namespace SpikingNeuron
{
class SpikingNeuronProgram
{
static void Main(string[] args)
{
Console.WriteLine("Begin spiking neuron demo");
int[][] inputs = new int[3][];
inputs[0] = new int[] { 0, 0, 1, 0, 1, 0, 1, 1,
1, 1, 0, 0, 1, 0, 1, 1 };
inputs[1] = new int[] { 1, 0, 1, 1, 0, 0, 0, 1,
1, 1, 0, 1, 1, 0, 1, 1 };
inputs[2] = new int[] { 1, 1, 0, 1, 0, 0, 0, 1,
1, 0, 1, 1, 1, 1, 1, 1 };
Console.WriteLine("The inputs are: ");
for (int i = 0; i < inputs.Length; ++i)
ShowVector(inputs[i], false);
Console.WriteLine("");
int[] output = new int[16];
int[] wts = new int[] { 4, -2, 3 };
Console.Write("The weights are: ");
ShowVector(wts, true);
Console.WriteLine("");
int leak = 1;
Console.WriteLine("Leak potential is: " + leak);
int v = 0; // electrical potential (voltage)
int thresh = 8; // Threshold
int spike = 4; // Increase in v at spike
int tNext = 0; // Time when neuron is active
int latency = 2; // Inactive after spike
Console.WriteLine("Threshold is: " + thresh);
Console.WriteLine("Spike is: " + spike);
Console.WriteLine("Latency time is: " + latency);
Console.WriteLine("Starting processing\");
for (int t = 0; t < 16; ++t)
{
Console.WriteLine("----------------------");
Console.Write(" ");
Console.Write("t = ");
if (t <= 9) Console.Write(" ");
Console.Write(t + ". ");
Console.Write("Inputs = " + inputs[0][t] +
" " + inputs[1][t] +
" " + inputs[2][t]);
if (t != tNext) // Neuron not active
{
Console.Write(". Neuron is inactive. ");
Console.WriteLine("V = " + v);
output[t] = 0;
}
else // Neuron is active
{
Console.Write(". Neuron is active. ");
int sum = 0;
for (int j = 0; j < inputs.Length; ++j)
sum += inputs[j][t] * wts[j];
v = v + sum;
v = v - leak;
if (v < 0)
v = 0;
Console.WriteLine("V = " + v);
if (v >= thresh) // Spike and reset
{
v = v + spike;
Console.WriteLine(" Spiking, V = " + v);
output[t] = 1;
v = 0;
tNext = t + 1 + latency;
}
else
{
output[t] = 0;
tNext = t + 1;
}
} // Active
} // t
Console.WriteLine("----------------------");
Console.WriteLine("Output spike train = ");
ShowVector(output, false);
Console.WriteLine("End spiking neuron demo");
Console.ReadLine();
} // Main
static void ShowVector(int[] vector, bool plus)
{
for (int i = 0; i < vector.Length; ++i)
{
if (plus == true && vector[i] >= 0)
Console.Write("+");
Console.Write(vector[i] + " ");
}
Console.WriteLine("");
}
} // Program
} // ns
テンプレート コードがテキスト エディターに読み込まれたら、ソリューション エクスプローラー ウィンドウで Program.cs の名前を「SpikingNeuronProgram.cs」に変更します。これにより、Visual Studio が自動的に Program クラスの名前を変更します。ソース コードの先頭にある不要な using ステートメントをすべて削除し、最上位レベルの System 名前空間への参照のみを残します。
Main メソッドのコードでは、まず、入力データと、出力値用のストレージ バッファーをセットアップします。
int[][] inputs = new int[3][];
inputs[0] = new int[] { 0, 0, 1, 0, 1, 0, 1, 1,
1, 1, 0, 0, 1, 0, 1, 1 };
...
int[] output = new int[16];
上記のコードは、3 つの入力ストリームを設定しています。人工スパイキング ニューロンでは、任意の数のストリームを利用できます。各ストリームには、16 個の 0 または 1 の値を用意します。入力の長さは任意ですが、プログラマとしては、デモの各入力ストリームを 16 ビットの符号なし値と考えることができます。
次に、ニューロンの動きを定義する値を設定します。
int[] wts = new int[] { 4, -2, 3 };
int leak = 1;
int v = 0; // Electrical potential (voltage)
int thresh = 8; // Needed to fire an output spike
int spike = 4; // Increase in v at spike event
int tNext = 0; // Next time when neuron is active
int latency = 2; // Number t inactive after spike
入力ストリームの配列 0 と 2 の重みは正の値としてニューロンの電位を増やす働きをもたせ、配列 1 の重みは負として電位を減らす働きをもたせます。このような重みを、興奮作用 (増加) 入力と抑制作用 (減少) 入力と区別することもあります。
上記のコードでは、リーク電位 (leak) を 1 に設定していますが、リークの確率変数にすることも考えられます。つまり、各時間単位でリーク電位を、たとえば、0 ~ 3 の範囲でランダムに変化させることもできます。または、現在の電位に比例してリーク電位を変化させてもかまいません。また、スパイキング後の待機時間 (latency) をランダム値にすることも考えられます。
すべての処理は、時間主導のループで制御します。
for (int t = 0; t < 16; ++t)
{
// Compute new V
// Spike if V >= threshold
// Emit a 0 or 1
}
時間ループの内部では、まず、ニューロンが非アクティブ (inactive) どうかをチェックします。
if (t != tNext) // Neuron is not active
{
Console.Write(". Neuron is inactive. ");
Console.WriteLine("V = " + v);
output[t] = 0;
}
else
{
// Active
}
変数 tNext は、ニューロンが次回アクティブ (active) になる時間単位を示す値です。通常 tNext は t+1 になります。非アクティブなニューロンは基本的に休眠状態のため、電位に変化はなく、スパイクも発生しません。ニューロンがアクティブな場合の分岐内部で電位 V を計算します。
int sum = 0;
for (int j = 0; j < inputs.Length; ++j)
sum += inputs[j][t] * wts[j];
v = v + sum;
v = v - leak;
if (v < 0) v = 0;
上記の j は入力ストリーム (0, 1, 2) のインデックスで、t は時間単位のインデックスです。たとえば、inputs[1][8] は、時間単位 t = 8 における入力ストリーム配列 1 の値 (0 または 1) です。電位 (v) からリーク電位 (leak) を減算して求めた値をチェックして、電位 (v) が負の値になったかどうかをチェックします。ただし、実際のニューロンでは、電位が負になることもあるため、v が負の値になることを許容するのも選択肢の 1 つです。
電位 (v) を計算後、求めた値をチェックして、スパイキング イベントが発生するかどうかをチェックします (図 4 参照)。
図 4 スパイキング イベントが発生するかどうかのチェック
if (v >= thresh) // Spike and reset
{
v = v + spike;
Console.WriteLine(" Spiking, V = " + v);
output[t] = 1; // Fire
v = 0;
tNext = t + 1 + latency;
}
else // No spike
{
output[t] = 0;
tNext = t + 1;
}
スパイキング イベントが発生するときは、電位 (v) の現在値を増加 (今回のデモでは 4) した後、すぐに 0 にリセットします。つまり、一時的に急上昇した電位値 (v) は使用しません。このように電位 (v) を 0 にリセットしない方法もあります。その場合、急上昇した電位値 (v) から何らかの値を減算してリセットを行います。たとえば、今回のデモでは、t = 6 時点で電位が一時的に 8 から 12 に急上昇します。このとき、スパイクのリセット値に 10 を使用して、ニューロンを 12 から 0 ではなく、12 から 2 にリセットします。
まとめ
人工スパイキング ニューロンは、さまざまな研究者グループがさまざまな目的で研究しています。神経生物学者は、生体内作用についての洞察を得るために、実際のニューロンの動きを再現するソフトウェア モデルの作成を試みています。リークを有する積分スパイク型のニューロン モデルは、シンプルすぎて実際のニューロンの動きを完全に再現することはできないため、通常はもっと複雑なモデルが使用されます。
人工スパイキング ニューロンは、機械学習分類システムの基礎としての役割も果たします。実際の値でシミュレーションを行うニューロンを使用する人工ニューラル ネットワークは、数十年研究されていますが、人工スパイキング ニューロンのネットワークはあまり研究されていません。個人的な意見としては、この分野の研究結果は結論に達しておらず、人工スパイキング ニューロンのネットワークが従来の実際の値を使用する人工ニューロンに勝るメリットを提供するかどうかは定かではありません。まだ解決されていない研究課題はたくさんあります。
最後に、人工スパイキング ニューロンは、コンピューターやプログラミングにとってまったく新しいアプローチを作成する試みで使用されています。米国国防総省国防高等研究事業局 (DARPA) の神経形態学的塑性的順応スケーラブル エレクトロニクス システム(SyNAPSE: Systems of Neuromorphic Adaptive Plastic Scalable Electronics) プロジェクトは、生物学レベルにスケール変換可能なコンピューターの作成を目指しています。将来有望な設計としては、従来のハードウェア アーキテクチャではなく、リークを有する積分発火型人工スパイキング ニューロンを莫大な数相互接続するモデルが使用されています。このような努力が実れば、想像を絶するほど強力なコンピューターを作成できるようになるでしょう。
Dr. James McCaffreyは、ワシントン州レドモンドにある Microsoft Research に勤務しています。これまでに、Internet Explorer、Bing などの複数のマイクロソフト製品にも携わってきました。McCaffrey 博士の連絡先は、jammc@microsoft.com (英語のみ) です。
この記事のレビューに協力してくれたマイクロソフト技術スタッフの Todd Bello および Dan LieblingShai に心より感謝いたします。