ニューラルネットワークとは?
ニューラルネットワークは、入力された数値を何段階かの計算で変換し、予測に使う仕組みだ。名前から脳の働きそのものを再現しているように思えるが、実際に扱うのは数値の配列と、その配列に対する演算である。画像の分類でも文章の生成でも、入力の形や規模は違うが、学習可能な数値を使って表現を変換する点は共通する。
「層を増やすと何が起こるのか」「学習するとどこが変わるのか」を分けて見ると、巨大なモデルも少し見通しがよくなる。
ニューロンを一つの計算として見る
二つの入力を受ける最小の例では、入力 にそれぞれ重み を掛け、バイアス を加える。
この をそのまま次へ渡すこともあるが、多くのネットワークでは活性化関数を通す。例えばReLUは負の値を0にし、正の値をそのまま残す。
「ニューロン」はこのような計算単位の呼び名であって、人間の神経細胞と同じ振る舞いをするという意味ではない。重みは入力ごとの効き方、バイアスは基準のずれを担う。どちらも学習で調整できる数値だ。
一つの層は、複数の計算を並べたもの
ニューロンを複数並べると、一つの入力から複数の出力を作れる。各出力を別々の式で書く代わりに、行列を使えばまとめて計算できる。次の例は、二つの数値を受け取り、途中で二つの値に変換し、最後に一つの値を返す。重みは説明のために手で決めたもので、まだ学習していない。
import numpy as np
x = np.array([[2.0, 1.0], [0.0, 1.0]])w1 = np.array([[1.0, -1.0], [1.0, 1.0]])b1 = np.array([0.0, 0.0])w2 = np.array([[2.0], [-1.0]])b2 = np.array([0.5])
hidden = np.maximum(0, x @ w1 + b1)output = hidden @ w2 + b2
print("途中の層:\n", hidden)print("出力:\n", output)実行結果は次のとおり。
途中の層: [[3. 0.] [1. 1.]]出力: [[6.5] [1.5]]x の各行が一つの入力例だ。最初の @ は行列の掛け算で、二つの入力から二つの途中の値を作る。np.maximum(0, ...) がReLUに相当する。最初の例では途中の値が [3, 0] になり、それを次の層が一つの出力 6.5 に変える。これは「6.5が正しい」と示す実験ではない。重みを固定したまま、層を通る数値の形を確かめる実験である。
ReLUのような非線形の変換を挟まなければ、線形変換を何段重ねても一つの線形変換にまとめられる。層を重ねる意味を考えるとき、活性化関数を外せない理由がここにある。ただし、層を増やせば無条件に精度が上がるわけでもない。
学習では何を変えるのか
今の例は、決め打ちした重みから出力を計算する「推論」だけを行った。学習では、入力と望ましい答えの組を用意し、出力がどれだけ外れたかを損失関数で数値化する。その損失が小さくなる方向へ、重みやバイアスを少しずつ更新する。
多層のネットワークでは、出力側の誤差を手がかりに、手前の層の各パラメータが損失へ与えた影響を計算する。これが誤差逆伝播だ。更新幅を決める方法の一つが勾配降下法である。どの入力にも同じ答えを返すだけで訓練データに合ってしまわないか、未知のデータでも働くかまで確かめる必要がある。訓練データでの損失低下だけを「理解した」証拠にはできない。
PyTorchなら nn.Linear が重みとバイアスを持つ線形層、nn.ReLU が活性化関数に当たる。nn.Module に層を組み合わせ、自動微分で勾配を計算できる。上のNumPy例は計算の形を見るためのもので、PyTorchの訓練処理を再現したものではない。
LLMと何がつながるのか
LLMでは文章をトークンに分け、トークンIDをベクトルへ変えてから、多数の層でその表現を更新する。現在の多くのLLMの中心にあるTransformerも、学習可能な重みを持つ行列計算を使う。一方、Attentionや残差接続などがあるため、先ほどの小さな全結合ネットワークをそのまま巨大化したものではない。
この第1号で押さえるのは、入力が数値になり、層が表現を変え、誤差に応じてパラメータが更新されるという骨格だ。次の重み・バイアスの記事では、最初の式の各数値が何を担うかを詳しく見る。その後、活性化関数、損失関数、勾配降下法、誤差逆伝播へ進むと、LLMの学習を支える基本部品がつながる。