アナログ回路で機械学習ができたら面白いのでは?

近年LLMが流行ってますがGPUとかお金も電気もたくさんかかるしこれをアナログ回路でやったら面白いのでは?と思いました。

ニューラルネットワークがやっている計算は掛け算と足し算です。

というわけで今回は基礎理論からざっくり整理しようと思う。

アナログ計算とは

デジタル計算機では数値を0と1の組み合わせとして表現します。計算の途中で少し電圧が変化しても0と1を区別できる範囲なら元の情報を復元できます。

一方、アナログ計算機では電圧や電流などの連続した物理量を数値に対応させます。

例えば、0ボルトを0、1ボルトを1と決めれば、0.5ボルトを0.5として扱えます。実際には回路の電源電圧や扱いたい数値の範囲に合わせて数値と電圧の対応関係を決めます。

アナログ計算機は計算手順を一個ずつ実行するというより物理現象そのものを計算に利用します。

回路に電圧を加えると電流が流れ、回路全体が物理法則に従った状態になります。そのときの電圧や電流を読み取ると、計算結果が得られるという考え方です。

もちろんアナログ回路の電圧や電流は、雑音、温度変化、部品のばらつきなどの影響を受けます。こういう扱いづらさが面倒なので、現在はデジタル計算機が主流になっています。

とはいえニューラルネットワークのように多少の誤差を許容できる処理ならアナログ計算を使える可能性があります。

ニューラルネットワークの計算

とりあえずニューラルネットワークの中でも一番基本的なニューロンだけ考えます。

ニューロンは複数の入力を受け取りそれぞれに重みを掛けてから合計します。

入力を x_1, x_2, ..., x_n、重みを w_1, w_2, ..., w_n とすると、やっていることはまず次の計算です。

$$ z=\sum_{i=1}^{n}w_i x_i+b $$

b はバイアスです。

そのあと計算結果 z を活性化関数へ通します。

$$ y=f(z) $$

なのでニューロンの処理は次の二つに分けて考えられます。

  1. 入力と重みを掛けて、すべて足し合わせる
  2. 合計値を活性化関数へ通す

このうち最初の計算を積和演算と呼びます。ニューラルネットワークではこの積和演算を大量にやっています。

というわけで、積和演算を電圧と電流で表せないかを考えます。

オームの法則で掛け算っぽいことをする

抵抗器に電圧 V を加えると、オームの法則によって電流 I が流れます。

$$ I=\frac{V}{R} $$

R は抵抗値です。

ここで抵抗値の逆数をコンダクタンス G と呼ぶことにします。

$$ G=\frac{1}{R} $$

これを使うと、オームの法則は次のように書けます。

$$ I=GV $$

この形は掛け算そのものです。

ニューラルネットワークの入力 x を電圧 V、重み w をコンダクタンス G に対応させると、次のようになります。

ニューラルネットワークアナログ回路
入力 x電圧 V
重み wコンダクタンス G
wx電流 GV

抵抗器へ電圧を加えるだけで入力と重みの積に対応する電流が出てきます。

要はデジタル計算機で乗算命令を実行する代わりに、抵抗器と電圧の性質を使って乗算結果を表現できます。

ただ、実際の回路では数値 x をそのまま電圧 V にするわけではありません。例えば入力値の範囲を0から1に制限して0から1ボルトに対応させるなど、適切なスケールを決める必要があります。

キルヒホッフの法則で足し算っぽいことをする

一つのニューロンには入力が複数あります。

それぞれの入力電圧 V_i を、重みに対応したコンダクタンス G_i へ加えると、各経路には次の電流が流れます。

$$ I_i=G_iV_i $$

これらの電流を一つの接点に集めます。

キルヒホッフの電流則では接点へ流れ込む電流の合計と、接点から流れ出す電流の合計が等しくなります。

入力側から流れ込んだ電流を出力側へ流す構成にすると出力電流は次のようになります。

$$ I_{\mathrm{out}}=I_1+I_2+\cdots+I_n $$

各電流に I_i=G_iV_i を代入すると、

$$ I_{\mathrm{out}}=G_1V_1+G_2V_2+\cdots+G_nV_n $$

となります。まとめると、

$$ I_{\mathrm{out}}=\sum_{i=1}^{n}G_iV_i $$

ということです。

これは、ニューロンの積和演算

$$ z=\sum_{i=1}^{n}w_ix_i $$

と同じ形をしています。

オームの法則で掛け算をしてキルヒホッフの電流則で足し算をする。この二つを組み合わせると、ニューロンの積和演算に対応する処理が回路上に作れそうです。

数字を入れてみる

入力を二つ持つニューロンに実際に数字を入れてみます。

入力と重みは次のようにします。

$$ x_1=2,\qquad x_2=3 $$

$$ w_1=0.5,\qquad w_2=0.2 $$

普通に計算すると結果は次のようになります。

$$ z=0.5\times2+0.2\times3=1.6 $$

これを回路側に寄せて、

$$ V_1=2,\qquad V_2=3 $$

$$ G_1=0.5,\qquad G_2=0.2 $$

とします。

それぞれの抵抗器を流れる電流は

$$ I_1=G_1V_1=0.5\times2=1 $$

$$ I_2=G_2V_2=0.2\times3=0.6 $$

です。

二つの電流を合流させると

$$ I_{\mathrm{out}}=I_1+I_2=1.6 $$

となりデジタル計算の結果と同じ値になります。

実際の回路で電圧を2ボルトや3ボルトにする必要があるという意味ではありません。数値と電圧、重みとコンダクタンスの対応にスケールを設定します。ここでは仕組みを分かりやすくするため、数値をそのまま対応させています。

バイアスと負の重み

ニューロンの計算には入力と重みの積和に加えてバイアス b があります。

$$ z=\sum_{i=1}^{n}w_ix_i+b $$

バイアスは値が常に1の入力を追加すれば、通常の重みと同じように扱えます。

$$ z=\sum_{i=1}^{n}w_ix_i+w_b\cdot1 $$

回路では一定の基準電圧を追加の入力として与えたり、一定の電流源を使ったりすればバイアスを表現できそうです。

ここでまた問題があります。抵抗器のコンダクタンスは基本的に正の値です。

$$ G\geq0 $$

でもニューラルネットワークの重みは負の値を取ることがあります。

そこで重みを正の成分と負の成分に分けます。

$$ w=w^+-w^- $$

正負それぞれに対応する電流を別々に作り最後に差を取れば、

$$ I_{\mathrm{out}}=I^+-I^- $$

として負の重みを表現できます。

これなら抵抗器だけでは表現できない負の値も扱えます。その代わり必要な回路が増えるので次回の回路設計ではこのあたりも考える必要があります。

活性化関数はどうするのか

積和演算だけだとニューラルネットワークとしては不十分です。

積和演算は線形演算なので線形演算だけを何段重ねても、全体を一つの線形演算にまとめられてしまいます。複雑なパターンを扱うには途中に非線形な活性化関数が必要になります。

代表的な活性化関数にReLUがあります。

$$ \operatorname{ReLU}(z)=\max(0,z) $$

入力が正ならそのまま出力し負なら0を出力する関数です。

アナログ回路ではダイオードやトランジスタ、オペアンプなどの非線形な特性を使えば、ReLUに相当する処理を作れそうです。

ただし実際の素子は理想的な数式どおりには動きません。0を境に完全に切り替わらなかったり、漏れ電流が発生したりします。

このへんまでくると、単に抵抗器を並べれば終わりという話ではなさそうです。それでも、まずは積和演算だけでも回路で再現できれば面白そうです。

アナログ回路なら精度は無限なのか

アナログ回路の電圧や電流は連続量なので無限の精度で計算できるようにも思えます。

当然ですが実際はそんなにうまくいきません。次のような誤差が入ります。

  • 抵抗値などの製造ばらつき
  • 熱雑音
  • 電源電圧の変動
  • 温度による特性変化
  • 配線抵抗
  • 測定器の誤差

例えば重み0.5を表現するようにコンダクタンスを設定しても、実際には少し違う値になる可能性があります。

またデジタルデータを入力電圧へ変換するDACや回路の出力をデジタル値へ戻すADCも必要になりそうです。回路内部の計算が速くても、入出力の変換に時間や電力がかかる場合があります。

アナログ回路なら常にデジタル計算機より高速で省電力になるわけではありません。どこをアナログ回路で計算するのかは考える必要があります。

とはいえ、ニューラルネットワークの計算の一部を電圧と電流の変化として実行できそうなことは分かりました。

次回の予定

次は回路を考えていこうと思います。

とりあえず入力を二つ持つ小さなニューロンを想定して、抵抗器へ電圧を加えたときにどのような電流が流れるのかを考えます。必要ならオペアンプなども使って、積和演算の結果を測定できる形にしてみる予定です。

理論どおりの結果になるのか、抵抗器をつなぐだけではうまくいかないのか、そのあたりを実験できたら面白いなって。