本文へスキップ
ひもとくAI

順伝播:入力から出力までをNumPyで計算する

レッスン 3/6

たくさんの内積を、まとめて計算する

入力から隠れ層、隠れ層から出力層へと、順番に計算を進めていくことを 順伝播(じゅんでんぱ)と呼びます。前のレッスンの計算を、今度はコードで書いてみましょう。

隠れ層には、ニューロンAとBの2つがありました。どちらも「重みと入力の内積+バイアス」を計算します。ニューロンの数が増えると、内積をたくさん計算することになるので、NumPyではこれを まとめて 計算します。

まず、重みがすべて違う数の例で、並べ方を確かめましょう。重みは、列ごとに1つのニューロン の分を縦に並べます。

import numpy as np

x = np.array([1, 2])       # 入力 x1=1, x2=2

W = np.array([[3, 5],      # 1行目: x1に掛ける重み(Aは3、Bは5)
              [2, 6]])     # 2行目: x2に掛ける重み(Aは2、Bは6)

x @ W    # → [7, 17]

1列目 (3, 2) がニューロンAの重み、2列目 (5, 6) がニューロンBの重みです。行は「どの入力に掛けるか」、列は「どのニューロンの分か」を表します。

  • ニューロンA: 1×3+2×2=71 \times 3 + 2 \times 2 = 7
  • ニューロンB: 1×5+2×6=171 \times 5 + 2 \times 6 = 17

同じ並べ方で、前のレッスンのXORのネットワークを書くと次のようになります。この例では、AもBも重みがどちらも1なので、1がすべての位置に並びます。

import numpy as np

x = np.array([1, 0])            # 入力(スイッチ1=オン、スイッチ2=オフ)

# 列ごとに1つのニューロンの重みを並べる(1列目がA、2列目がB)
W1 = np.array([[1, 1],
               [1, 1]])
b1 = np.array([-0.5, -1.5])     # Aのバイアス、Bのバイアス

x @ W1           # → [1, 1]   : Aの内積、Bの内積
x @ W1 + b1      # → [0.5, -0.5]

@ は、「入力と、各列(各ニューロンの重み)との内積を、列の数だけまとめて計算する」 記号です。1列目との内積 1×1+0×1=11 \times 1 + 0 \times 1 = 1 がニューロンAの分、2列目との内積がニューロンBの分になります。バイアスを足すと、前のレッスンの表と同じ z=0.5, −0.5z = 0.5,\ -0.5 が得られます。

この計算は、高校・大学の数学では 行列の積 と呼ばれます。名前は難しそうですが、中身は第4章の内積をまとめて行っているだけです。

XORを解くネットワークをNumPyで動かす

@ を使うと、4通りの入力すべてを一度に計算することもできます。入力を1行に1件ずつ並べた表(4行×2列)にすれば、各行ごとの計算がまとめて行われます。

import numpy as np

def step(z):
    return (z > 0).astype(int)      # 0より大きければ1、そうでなければ0

X = np.array([[0, 0],
              [0, 1],
              [1, 0],
              [1, 1]])               # 4件の入力

W1 = np.array([[1, 1], [1, 1]])      # 隠れ層(A, B)の重み
b1 = np.array([-0.5, -1.5])
W2 = np.array([1, -1])               # 出力層の重み(Aに+1、Bに-1)
b2 = -0.5

H = step(X @ W1 + b1)    # 隠れ層  → [[0, 0], [1, 0], [1, 0], [1, 1]]
out = step(H @ W2 + b2)  # 出力層  → [0, 1, 1, 0]

step は、「0より大きければ1、そうでなければ0」の活性化関数です。z > 0 は、配列の各要素が0より大きいかどうかを True(はい)/False(いいえ)で返します。.astype(int) は、それを True なら1、False なら0の整数に変換します。例えば [0.5, -0.5] は [True, False] を経て [1, 0] になります。

隠れ層 H の各行が、それぞれの入力に対するニューロンA・Bの出力です。出力 out は [0, 1, 1, 0] で、XORの答えと一致します。

たった2行で、ネットワーク全体の計算ができている 点に注目してください。層が増えても、「前の層の出力 @ 重み + バイアス → 活性化関数」を層の数だけ繰り返すだけです。何百層もあるディープラーニングのモデルも、順伝播の計算はこの繰り返しです。

よくある誤解と振り返り

誤解:「@(行列の積)は、高度な数学を理解していないと使えない?」

@ がやっていることは、「表の各行」と「重みの各列」の内積を、すべての組み合わせについて計算することです。1つ1つは、第4章で手計算した内積と同じです。慣れるまでは、「行と列の数が合っているか」だけを意識すれば十分です(入力が2つなら、重みの表は2行でなければなりません)。

なぜまとめて計算するのか

for文で1つずつ内積を計算しても、答えは同じです。それでもまとめて計算するのは、NumPyやGPU(画像処理用の計算装置)が、まとめた計算を非常に速く処理できるからです。ディープラーニングが実用的な速さで動くのは、この「まとめて計算する」工夫のおかげです。

振り返り

  • 順伝播とは、入力から出力へ、層ごとに順番に計算を進めること
  • 各層の計算は「前の層の出力 @ 重み + バイアス → 活性化関数」
  • @ は、たくさんの内積をまとめて計算する記号(数学では行列の積と呼ぶ)

ここまでは、XORを解く重みを人間が考えて与えていました。次のレッスンでは、この重みを データから自動で学習する 仕組みを見ていきます。

演習

演習を読み込んでいます…