ニューラルネットワークの重みの計算〜なぜ線形代数(行列の内積)が必要か

ニューラルネットワークの重みの計算〜なぜ線形代数(行列の内積)が必要か

ニューラルネットワークを理解するためには、まず重みの計算について知っておかなければなりません。
次の図のような2層の単純な例で考えてみます。

入力層の特徴量は2つあり、それぞれ \(x_1,x_2 \) とします。
そして、出力層は3つあり、それぞれ \(y_1,y_2,y_3 \) とします。
入力層の値は重み \(w_1 から w_6 \) がそれぞれ掛けられた後に合算されて出力層の値になります。
その結果出力層の値の計算は次のようになります。

$$
y_1 = x_1 * w_1 + x_2 * w_4 \\
y_2 = x_1 * w_2 + x_2 * w_5 \\
y_3 = x_1 * w_3 + x_2 * w_6 \\
$$
\(x_1=1,x_2=2 \) として \(w_1=1,w_2=2,w_3=3,w_4=4,w_5=5,w_6=6 \) とした場合、計算結果は次のようになります。

9 = 1*1 + 2*4
12 = 1*2 + 2*5
15 = 1*3 + 2*6

これをPythonで書くと次のようになります。

X = [1,2]
W = [[1,2,3],[4,5,6]]

y1 = X[0]*W[0][0] + X[1]*W[1][0]
y2 = X[0]*W[0][1] + X[1]*W[1][1]
y3 = X[0]*W[0][2] + X[1]*W[1][2]
print('y1 =',y1,'y2 =',y2,'y3 =',y3)

結果
y1 = 9
y2 = 12
y3 = 15

計算自体はとてもシンプルで小学生程度の計算能力で値を求めることができます。
けれども、もし入力層の特徴量が1万個あったりするとどうでしょうか。
単純計算ではありますが、すごく面倒な作業になります。
もちろんプログラムでの記述も面倒な記述となります。
例えばループ文を使ったりすると、少しスマートに記述ができますが、numpyはループがあまり効率よく使えません。

そんな計算上の問題を解決してくれるのが、線形代数の行列内積になります。

行列内積とは

それぞれの値を \(x_1=1,x_2=2 \) として \(w_1=1,w_2=2,w_3=3,w_4=4,w_5=5,w_6=6 \) とした場合、次のように行列にまとめることができます。
$$
X = \begin{pmatrix} 1 & 2 \end{pmatrix} \\
W = \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \end{pmatrix}
$$
これらの値を使って、入力層の値Xに重みWを内積すると、上で行った計算と同様の結果になります。
その結果出力層の値の計算は次のようになります。

行列の内積の計算方法

なんだか面倒臭い計算です。これを受験のために勉強するのは嫌ですが、プログラムで使うのは便利な方法です。
それはnumpyを使うと以下のように簡単な記述で済みます。

X = np.array([1,2])
W = np.array([[1,2,3],[4,5,6]])

Y = np.dot(X,W)
Y

結果
array([ 9, 12, 15])

複数の重みの計算は線形代数の行列の内積を使い、しかもPythonのnumpyを使うと数行でプログラムできるのです。
そのため、行列の内積の計算方法はざっくりと知っておいて、なぜそうしているのかさえ知っておけば十分です。