第7章 ニューロンをまとめるという考え方
こんにちは。ゆうせいです。
第6章で、ニューロン1個の計算が内積であることを確認しました。しかし、実際のニューラルネットワークには、1つの層に何百、何千というニューロンが並んでいます。GPT-2という言語モデルでは、1つの層に3072個のニューロンが並ぶ箇所があります。3072個のニューロンがあれば、内積を3072回計算することになります。この3072回の計算を、いちいち書いていくのは現実的ではありません。第7章では、この多数のニューロンをどうやってまとめて扱うかという考え方を扱います。行列の掛け算という道具そのものは第8章で導入しますので、この章はその橋渡しです。なぜまとめる必要があるのか、まとめるとはどういうことかを、先に納得しておきましょう。
ニューロンが1個から複数になるとどうなるか
1個の場合の復習
第6章で確認したとおり、ニューロン1個の計算は次のとおりです。
入力ベクトル と、重みベクトル
の内積に、バイアス
を足します。
結果 は、スカラー、つまり1個の数値です。
3個の場合
ニューロンを3個に増やしてみます。
重要な点は、各ニューロンが、それぞれ独自の重みベクトルとバイアスを持つということです。入力は共通ですが、重みは別々です。
内積を3回、それぞれ別の重みベクトルで計算します。
結果として、3個の数値が得られます。これを1本のベクトルにまとめると、出力ベクトルになります。
具体的な数値で確認する
入力が3次元だとします。
三つのニューロンの重みが、次のとおりだとします。
バイアスは、それぞれ次のとおりとします。
計算します。
出力ベクトルは、次のようになります。
入力が3次元、出力が3次元です。この例ではたまたま同じ次元数ですが、一致している必要はありません。

入力の次元数と出力の次元数
ここで、重要な関係を整理します。
入力の次元数は、各ニューロンが受け取る数値の個数です。上の例では3です。
出力の次元数は、ニューロンの個数です。上の例では3です。
この二つは、独立に決められます。
入力が768次元で、ニューロンが3072個あれば、出力は3072次元になります。
入力が3072次元で、ニューロンが768個あれば、出力は768次元になります。
なぜ次元数を変えられるのか
各ニューロンは、入力のすべての要素を見て、1個の数値を出します。したがって、ニューロンの個数が、そのまま出力の次元数になります。
ニューロンを増やせば出力の次元が増え、減らせば減ります。この自由度が、ニューラルネットワークの設計における基本的な選択肢になります。
ディープラーニングでの実例
GPT-2のMLP層と呼ばれる部分では、次の変換が行われています。
入力768次元を、3072個のニューロンで受けて3072次元にし、それを768個のニューロンで受けて768次元に戻します。
いったん拡大してから縮小する構造です。
なぜこのような構造にするのかについては、第12章で扱います。
重みベクトルをまとめて考える
3個のニューロンには、3本の重みベクトルがあります。
これらを、バラバラに管理するのは不便です。そこで、まとめて一つのものとして扱いたくなります。
縦に並べてみます。
これは、第3章で学んだ行列そのものです。
3行3列の行列であり、各行が1つのニューロンの重みベクトルに対応しています。
行の意味と列の意味
この行列の読み方を、明確にしておきます。
行は、ニューロンに対応します。1行目は1番目のニューロン、2行目は2番目のニューロン、という具合です。
列は、入力の要素に対応します。1列目は入力の1番目の要素に掛かる重み、2列目は2番目の要素に掛かる重み、です。
したがって、この行列の大きさは次のようになります。
つまり、行数が出力次元、列数が入力次元です。
具体例で確認する
入力が768次元、ニューロンが3072個の層であれば、重み行列は3072行768列になります。
要素数を計算します。
約236万個のパラメータが、この1つの層に含まれていることになります。
第3章で学んだ、形から要素数を計算する方法が、ここで活きてきます。
バイアスもまとめる
バイアスも、ニューロンごとに1個ずつあります。
これらをまとめると、ベクトルになります。
バイアスベクトルの次元数は、ニューロンの個数、つまり出力の次元数と一致します。
まとめて書くとどうなるか
ここまでの内容を、記号でまとめます。
重み行列を 、入力ベクトルを
、バイアスベクトルを
とすると、層全体の計算は次のように書けます。
第1章で示した式が、ここでようやく意味を持ちました。
この一行が、3072回の内積計算と、3072回のバイアス加算を、すべて含んでいます。
という部分が、行列とベクトルの掛け算です。この計算の具体的な手順は、第8章で扱います。
この章では、なぜこう書きたくなるのかという動機の部分を、押さえておいてください。
なぜまとめる必要があるのか
3072回の内積を、3072行のプログラムで書いても、計算はできます。では、なぜまとめるのでしょうか。四つの理由があります。
理由1:書く量が減る
最も分かりやすい理由です。
ループを使えば、3072行を書く必要はありません。しかし、ループを使ったとしても、記述は次のようになります。
for i in range(3072):
y[i] = 0
for j in range(768):
y[i] += W[i][j] * x[j]
y[i] += b[i]
二重ループです。これが、行列積を使えば、次の一行になります。
y = W @ x + b
コードの読みやすさが、大きく変わります。
理由2:計算が速くなる
これが、実務上は最も重要な理由です。
Pythonのループは、実行速度が遅いことで知られています。二重ループで約236万回の計算を行うと、相当な時間がかかります。
一方、NumPyやPyTorchの行列積は、内部が高速な言語で実装されており、さらに複数の計算を同時に実行する仕組みが使われています。
同じ計算でも、実行時間が数十倍から数百倍違うことがあります。
演習で、実際にこの差を測定してみてください。
理由3:GPUで並列処理できる
第16章で詳しく扱いますが、GPUは多数の計算を同時に実行する装置です。
行列積という形にまとめられていれば、各要素の計算が独立しているため、GPUの多数の計算コアに割り振れます。
ループとして書かれた計算は、この並列化ができません。
現在のディープラーニングが実用的な速度で動くのは、計算が行列積の形に整理されているからです。
理由4:数式として読み書きできる
論文や技術文書では、モデルの構造が数式で説明されます。
この一行を見て、全結合層だな、と即座に判断できることが、技術文書を読むうえで必要になります。
ループとして書かれていたら、何をしているのか読み解くのに時間がかかります。
層を重ねるということ
ここまでは、1つの層の話でした。
ディープラーニングでは、この層を何層も重ねます。
1層目の出力が、2層目の入力になります。
このとき、次元数の対応が重要になります。
1層目の出力が128次元であれば、2層目の入力も128次元でなければなりません。したがって、2層目の重み行列は、列数が128でなければなりません。
この対応が合っていないと、計算ができません。第10章で扱う形が合わないエラーの、最も基本的なパターンです。
実際には活性化関数が挟まる
なお、実際のニューラルネットワークでは、層と層の間に活性化関数という処理が挟まります。
この が活性化関数です。
なぜこれが必要なのかは、第13章で扱います。線形代数の限界に関わる、重要な話です。
現時点では、実際には間に何か挟まる、とだけ覚えておいてください。
Pythonで確認する
import numpy as np
import time
print("=== ニューロン1個の計算 ===")
x = np.array([2.0, 5.0, 1.0])
w1 = np.array([1.0, 0.0, 2.0])
b1 = 1.0
y1 = w1 @ x + b1
print(f"入力: {x}")
print(f"重み: {w1}, バイアス: {b1}")
print(f"出力: {y1}")
print("\n=== ニューロン3個を個別に計算 ===")
w2 = np.array([0.0, 3.0, 1.0])
w3 = np.array([2.0, 1.0, 0.0])
b2, b3 = -2.0, 0.0
y1 = w1 @ x + b1
y2 = w2 @ x + b2
y3 = w3 @ x + b3
print(f"ニューロン1の出力: {y1}")
print(f"ニューロン2の出力: {y2}")
print(f"ニューロン3の出力: {y3}")
print(f"出力ベクトル: {np.array([y1, y2, y3])}")
print("\n=== 重みを行列にまとめる ===")
W = np.array([
[1.0, 0.0, 2.0],
[0.0, 3.0, 1.0],
[2.0, 1.0, 0.0]
])
b = np.array([1.0, -2.0, 0.0])
print(f"重み行列 W(形{W.shape}):")
print(W)
print(f"バイアスベクトル b(形{b.shape}): {b}")
y = W @ x + b
print(f"\nまとめて計算した出力: {y}")
print("個別に計算した結果と一致しています。")
print("\n=== 次元数の関係 ===")
input_dim = 768
n_neurons = 3072
W_large = np.random.randn(n_neurons, input_dim).astype(np.float32) * 0.02
b_large = np.zeros(n_neurons, dtype=np.float32)
x_large = np.random.randn(input_dim).astype(np.float32)
print(f"入力の次元数: {input_dim}")
print(f"ニューロンの個数: {n_neurons}")
print(f"重み行列の形: {W_large.shape}")
print(f"パラメータ数: {W_large.size:,}")
print(f"出力の次元数: {(W_large @ x_large).shape[0]}")
print("\n=== ループと行列積の速度比較 ===")
def loop_version(W, x, b):
n_out, n_in = W.shape
y = np.zeros(n_out)
for i in range(n_out):
total = 0.0
for j in range(n_in):
total += W[i, j] * x[j]
y[i] = total + b[i]
return y
def matrix_version(W, x, b):
return W @ x + b
n_out, n_in = 500, 500
W_test = np.random.randn(n_out, n_in)
x_test = np.random.randn(n_in)
b_test = np.random.randn(n_out)
start = time.time()
result_loop = loop_version(W_test, x_test, b_test)
time_loop = time.time() - start
start = time.time()
result_matrix = matrix_version(W_test, x_test, b_test)
time_matrix = time.time() - start
print(f"行列サイズ: {n_out} x {n_in}")
print(f"ループ版: {time_loop*1000:>10.2f} ミリ秒")
print(f"行列積版: {time_matrix*1000:>10.2f} ミリ秒")
print(f"速度比: {time_loop/time_matrix:>10.1f} 倍")
print(f"結果は一致しているか: {np.allclose(result_loop, result_matrix)}")
print("\n=== 層を重ねる ===")
np.random.seed(0)
W_layer1 = np.random.randn(128, 10) * 0.1
b_layer1 = np.zeros(128)
W_layer2 = np.random.randn(64, 128) * 0.1
b_layer2 = np.zeros(64)
W_layer3 = np.random.randn(3, 64) * 0.1
b_layer3 = np.zeros(3)
x_input = np.random.randn(10)
print(f"入力 : 形{x_input.shape}")
h1 = W_layer1 @ x_input + b_layer1
print(f"1層目通過後 : 形{h1.shape} (重み行列{W_layer1.shape})")
h2 = W_layer2 @ h1 + b_layer2
print(f"2層目通過後 : 形{h2.shape} (重み行列{W_layer2.shape})")
output = W_layer3 @ h2 + b_layer3
print(f"3層目通過後 : 形{output.shape} (重み行列{W_layer3.shape})")
print("\n各層の重み行列は、前の層の出力次元を列数として持っています。")
print("\n=== 次元が合わない場合 ===")
W_wrong = np.random.randn(64, 100)
try:
result = W_wrong @ h1
except ValueError as e:
print(f"エラー: {e}")
print(f"h1の次元は{h1.shape[0]}ですが、W_wrongの列数は{W_wrong.shape[1]}です。")
初学者がつまずきやすい点
つまずき1:重み行列の行と列を取り違える
重み行列の形は、行数が出力次元、列数が入力次元です。
入力次元が先だと思い込むと、すべての計算が合わなくなります。
覚え方として、行がニューロン、列が入力と唱えるのが有効です。1つのニューロンが1行を占め、その行の中に、入力の各要素に掛ける重みが並んでいる、というイメージです。
なお、ライブラリによっては、内部的に転置した形で保持している場合があります。PyTorchの nn.Linear がその例です。この点は、第10章で扱います。
つまずき2:入力次元と出力次元を混同する
nn.Linear(768, 3072) というコードを見たとき、768が入力、3072が出力です。
この順序を逆に覚えると、モデルの構造がまったく違うものになります。
コードを書いたら、必ず出力の shape を確認する習慣をつけてください。
つまずき3:バイアスの次元数を間違える
バイアスベクトルの次元数は、出力次元と一致します。入力次元ではありません。
各ニューロンに1個ずつのバイアスがある、と考えれば間違えません。
演習
演習1:出力を手計算する
入力を とします。
次の重み行列とバイアスベクトルを使って、出力ベクトルを計算してください。
また、この層のニューロンの個数、入力次元、出力次元をそれぞれ答えてください。
演習2:パラメータ数を計算する
次の構成のニューラルネットワークについて、全パラメータ数を計算してください。バイアスも含めてください。
入力が784次元、1層目が512個のニューロン、2層目が256個のニューロン、3層目が10個のニューロン。
演習3:速度差を測定する
import numpy as np
import time
def loop_version(W, x):
n_out, n_in = W.shape
y = np.zeros(n_out)
for i in range(n_out):
total = 0.0
for j in range(n_in):
total += W[i, j] * x[j]
y[i] = total
return y
print(f"{'サイズ':>10} | {'ループ版(ms)':>14} | {'行列積版(ms)':>14} | {'速度比':>10}")
print("-" * 56)
for size in [50, 100, 200, 400]:
W = np.random.randn(size, size)
x = np.random.randn(size)
start = time.time()
loop_version(W, x)
t_loop = (time.time() - start) * 1000
start = time.time()
for _ in range(100):
W @ x
t_matrix = (time.time() - start) * 1000 / 100
print(f"{size:>4}x{size:<5} | {t_loop:>14.3f} | {t_matrix:>14.5f} | {t_loop/t_matrix:>9.0f}倍")
print("\nサイズが大きくなるほど、速度差が広がることを確認してください。")
演習4:層の次元をつなげる
次の条件で、3層のネットワークを構成してください。
入力は100次元です。
1層目の出力は50次元です。
2層目の出力は20次元です。
3層目の出力は5次元です。
各層の重み行列の形と、バイアスベクトルの次元数を答えてください。
演習の解答例と解説
演習1の解答は、次のとおりです。
出力ベクトルは です。
ニューロンの個数は4個、入力次元は3、出力次元は4です。
重み行列が4行3列であることが、そのまま4個のニューロンが3次元の入力を受け取ることを表しています。
演習2の解答は、次のとおりです。
1層目は、重みが次のようになります。
バイアスが512ですから、合計401920です。
2層目は、重みが次のようになります。
バイアスが256ですから、合計131328です。
3層目は、重みが次のようになります。
バイアスが10ですから、合計2570です。
全パラメータ数は、次のようになります。
約53万6千個です。
この計算から、入力に近い層ほどパラメータが多くなる傾向が読み取れます。784次元という大きな入力を受ける1層目が、全体の約75パーセントを占めています。
演習3では、サイズが大きくなるほど速度差が広がることが確認できます。行列積の実装が最適化されているため、規模が大きいほど有利になります。
演習4の解答は、次のとおりです。
1層目は、重み行列が50行100列、バイアスが50次元です。
2層目は、重み行列が20行50列、バイアスが20次元です。
3層目は、重み行列が5行20列、バイアスが5次元です。
各層の重み行列の列数が、前の層の出力次元と一致している点を確認してください。この連鎖が成立していないと、計算できません。
講師向けの補足
この章は、第6章と第8章をつなぐ橋渡しの回です。新しい計算技法は出てきませんので、時間配分は控えめでも構いません。
ただし、なぜまとめるのかという動機の部分は、必ず実感してもらってください。特に、演習3の速度比較は、強い印象を残します。
ループで書けば数秒かかる計算が、行列積なら一瞬で終わる。この差を目の当たりにすると、行列積として書くことの実務的な意味が、腑に落ちます。
また、重み行列の形が出力次元、入力次元の順である点は、繰り返し強調してください。この順序を間違えると、以降すべての計算が合わなくなります。
ホワイトボードに、ニューロンを丸で描き、それぞれから入力へ線を引く図を描くと、行と列の対応が視覚的に理解できます。1つの丸から出る線の束が、行列の1行に対応する、という説明が有効です。
理解度を確認する問い
第7章の内容を、次の問いで確認してみてください。
ニューロンが3個ある層では、内積を何回計算することになるでしょうか。
重み行列の行数と列数は、それぞれ何に対応するでしょうか。
入力が768次元、ニューロンが3072個の層について、重み行列の形と、パラメータ数を答えてください。
多数のニューロンの計算をまとめて行列積として書く理由を、四つ挙げてください。
層を重ねる際、前の層と次の層の間で満たすべき条件は何でしょうか。
まとめ
ニューロンが複数ある層では、各ニューロンがそれぞれ独自の重みベクトルとバイアスを持ち、共通の入力に対して内積を計算します。ニューロンが3個あれば内積を3回、3072個あれば3072回計算することになります。
出力の次元数は、ニューロンの個数と一致します。入力の次元数とは独立に決められるため、層を通すことで次元数を変えられます。
複数の重みベクトルを縦に並べると、行列になります。この重み行列の形は、行数がニューロンの個数つまり出力次元、列数が入力次元です。バイアスも、出力次元と同じ長さのベクトルにまとめられます。
これらをまとめることで、層全体の計算が次の一行で書けるようになります。
まとめる理由は四つあります。書く量が減ること、計算が大幅に速くなること、GPUによる並列処理が可能になること、そして数式として読み書きできることです。
層を重ねる際は、前の層の出力次元と、次の層の重み行列の列数が一致していなければなりません。この対応が、実装時の形の合わせ方の基本になります。
次の第8章では、この という計算を、具体的にどう実行するのかを扱います。行列とベクトルの積の計算手順と、それが全結合層そのものであることを、詳しく確認します。この連載の中心となる章です。
第8章に進む前に、演習3のコードを実行し、ループと行列積の速度差を自分の目で確認しておいてください。この差を体感しておくと、次章以降の内容が、単なる数学の話ではなく、実務に直結する話として受け取れるはずです。
セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。
投稿者プロフィール

- 代表取締役
-
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
海外放浪の末、2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。
学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。
最新の投稿
新人エンジニア研修講師2026年8月11日第13章 線形だけでは足りない理由
新人エンジニア研修講師2026年8月11日第12章 次元の拡大と縮小の意味
新人エンジニア研修講師2026年8月11日第11章 2次元で見る線形変換:回転、伸縮、射影
新人エンジニア研修講師2026年8月11日第10章 形を合わせる:転置、reshape、ブロードキャスト
