第8章 行列とベクトルの積:全結合層の正体
こんにちは。ゆうせいです。
第7章で、多数のニューロンの計算をまとめたいという動機を確認しました。第8章では、そのまとめ方を具体的に扱います。行列とベクトルの積です。この連載の中心となる章であり、ここを理解すれば、ニューラルネットワークの計算の大半が読み解けるようになります。実は、計算そのものは新しくありません。第6章で学んだ内積を、繰り返すだけです。ただし、どの行とどのベクトルの内積なのか、結果がどの位置に入るのかという対応関係を、正確に押さえる必要があります。この章では、その手順を丁寧に確認したうえで、それが全結合層そのものであることを示します。
行列とベクトルの積の計算手順
基本の考え方
行列とベクトルの積は、次のように計算します。
行列の各行と、ベクトルの内積を取ります。その結果を、順に並べます。
行列が3行4列であれば、行が3本あるので、内積を3回計算します。結果は3次元のベクトルになります。
具体例で確認する
次の行列とベクトルの積を計算します。
1行目とベクトルの内積を計算します。
2行目とベクトルの内積を計算します。
3行目とベクトルの内積を計算します。
結果を縦に並べます。
これが答えです。
第7章で個別に計算した結果と、一致していることを確認してください。まったく同じ計算を、まとめて書いただけです。
手順を言葉でまとめる
計算手順を、あらためて整理します。
第一に、行列の1行目を取り出します。
第二に、その行とベクトルの内積を計算します。
第三に、結果を、答えのベクトルの1番目の要素とします。
第四に、これを行列のすべての行について繰り返します。
答えのi番目の要素は、行列のi行目とベクトルの内積である。この対応を、しっかり押さえてください。
計算が成立する条件
行列とベクトルの積には、明確な条件があります。
行列の列数と、ベクトルの次元数が、一致していなければなりません。
なぜでしょうか。
行列の1行には、列数と同じ個数の数値が並んでいます。この行とベクトルの内積を計算するには、両者の要素数が一致している必要があります。第6章で確認したとおり、要素数が違うベクトルの内積は定義できません。
したがって、列数とベクトルの次元数が一致していなければ、計算できないのです。
形の変化を追う
計算前後の形を整理します。
3行4列の行列と、4次元のベクトルを掛けると、3次元のベクトルになります。
内側の4が一致し、消えるイメージです。外側の3が残ります。
この内側が一致して消え、外側が残るという感覚は、第9章の行列どうしの積でも同じです。

形が合わない場合
3行4列の行列と、5次元のベクトルは掛けられません。
プログラムで実行すると、エラーになります。
ValueError: matmul: Input operand 1 has a mismatch in its core dimension 0
このエラーは、実務で最も頻繁に遭遇するものの一つです。第10章で、対処法を詳しく扱います。
これが全結合層の正体である
ここで、この連載の中心となる対応関係を確認します。
全結合層の計算は、次の式で表されます。
この式の各要素が、何に対応するかを整理します。
は、入力ベクトルです。次元数は、入力の特徴量の個数です。
は、重み行列です。行数はニューロンの個数、列数は入力の次元数です。
は、バイアスベクトルです。次元数は、ニューロンの個数です。
は、出力ベクトルです。次元数は、ニューロンの個数です。
そして、 という計算が、この章で学んだ行列とベクトルの積です。
コードとの対応
PyTorchで全結合層を作るコードは、次のように書きます。
layer = nn.Linear(768, 3072)
この一行が意味しているのは、次のことです。
入力は768次元です。
出力は3072次元、つまりニューロンが3072個あります。
内部には、重み行列とバイアスベクトルが保持されています。
このとき、重み行列の形は3072行768列です。
実行時に何が起きているか
このレイヤーに、768次元のベクトルを入力すると、次の計算が実行されます。
3072個の各ニューロンについて、768次元の重みベクトルと入力ベクトルの内積を計算します。
内積は、768回の掛け算と767回の足し算です。
これを3072回繰り返します。
掛け算の総回数は、次のようになります。
約236万回の掛け算が、レイヤーを1回通すだけで実行されています。
このコードの背後で行われている計算の量を、実感してください。
重み行列の各行が持つ意味
第6章で、重みベクトルはそのニューロンが反応したいパターンを表すと説明しました。
行列としてまとめた場合、この解釈はどうなるでしょうか。
重み行列の各行が、それぞれ1つのパターンを表しています。
3072行あれば、3072種類のパターンが記録されていることになります。
入力ベクトルが与えられると、3072種類すべてのパターンとの類似度が、一度に計算されます。
出力ベクトルの各要素は、入力がそのパターンにどれだけ合致しているかを表す数値です。
特徴抽出という見方
この見方をすると、全結合層は特徴抽出器として機能していることが分かります。
入力を、あらかじめ用意された多数のパターンと照合し、それぞれとの一致度を出力する。
次の層は、その一致度の組み合わせを、さらに別のパターンと照合する。
層を重ねるほど、より複雑で抽象的なパターンが検出される、という構造になっています。
縦ベクトルと横ベクトル
ここで、記法についての注意点を扱います。
数学の教科書では、ベクトルを縦に書き、次のように表します。
行列が左、ベクトルが右です。
一方、ディープラーニングの文献やコードでは、次の書き方も頻繁に登場します。
ベクトルが左、行列が右です。この場合、ベクトルは横向きに扱われ、重み行列の形も転置されています。
なぜ二つの流儀があるのか
理由は、実装上の都合です。
第9章で扱いますが、実際には複数のデータをまとめて処理します。このとき、データを行方向に並べるのが自然です。
1行が1件のデータ、という構造にすると、 という書き方のほうが素直になります。
PyTorchの実装も、この形を採用しています。
混乱を避けるために
どちらの記法を見ても、やっていることは同じです。
重要なのは、記号の順序ではなく、形が合っているかどうかです。
の場合、
の列数と
の次元数が一致します。
の場合、
の次元数と
の行数が一致します。
コードを読むときは、shape を確認して、どちらの流儀かを判断してください。
なお、PyTorchの nn.Linear では、weight という属性が出力次元、入力次元の形で保持されており、内部で転置して使われています。この点は、第10章であらためて扱います。
Pythonで確認する
import numpy as np
print("=== 行列とベクトルの積の基本 ===")
W = np.array([
[1.0, 0.0, 2.0],
[0.0, 3.0, 1.0],
[2.0, 1.0, 0.0]
])
x = np.array([2.0, 5.0, 1.0])
print(f"W(形{W.shape}):")
print(W)
print(f"x(形{x.shape}): {x}")
y = W @ x
print(f"\nW @ x = {y}")
print("\n=== 行ごとの内積として確認 ===")
for i in range(W.shape[0]):
row = W[i]
dot = row @ x
print(f"{i+1}行目 {row} との内積: {dot}")
print("\n=== 形の変化 ===")
test_cases = [
(3, 4, 4),
(5, 10, 10),
(3072, 768, 768)
]
for rows, cols, vec_dim in test_cases:
W_test = np.zeros((rows, cols))
x_test = np.zeros(vec_dim)
y_test = W_test @ x_test
print(f"({rows}, {cols}) × ({vec_dim},) → {y_test.shape}")
print("\n=== 形が合わない場合 ===")
W_bad = np.zeros((3, 4))
x_bad = np.zeros(5)
try:
W_bad @ x_bad
except ValueError as e:
print(f"エラー: {e}")
print("行列の列数(4)とベクトルの次元数(5)が一致していません。")
print("\n=== 全結合層の実装 ===")
np.random.seed(0)
class FullyConnectedLayer:
def __init__(self, input_dim, output_dim):
self.W = np.random.randn(output_dim, input_dim) * 0.1
self.b = np.zeros(output_dim)
self.input_dim = input_dim
self.output_dim = output_dim
def forward(self, x):
return self.W @ x + self.b
def num_parameters(self):
return self.W.size + self.b.size
layer = FullyConnectedLayer(768, 3072)
print(f"入力次元: {layer.input_dim}")
print(f"出力次元(ニューロン数): {layer.output_dim}")
print(f"重み行列の形: {layer.W.shape}")
print(f"バイアスの形: {layer.b.shape}")
print(f"パラメータ数: {layer.num_parameters():,}")
print(f"掛け算の回数: {layer.W.size:,}")
x_input = np.random.randn(768)
y_output = layer.forward(x_input)
print(f"\n入力の形: {x_input.shape}")
print(f"出力の形: {y_output.shape}")
print("\n=== 重み行列の各行がパターンを表す ===")
W_pattern = np.array([
[1.0, 0.0, 0.0, 0.0], # 1番目の特徴だけを見る
[0.0, 1.0, 0.0, 0.0], # 2番目の特徴だけを見る
[1.0, 1.0, 0.0, 0.0], # 1番目と2番目の合計
[1.0, -1.0, 0.0, 0.0], # 1番目と2番目の差
[0.25, 0.25, 0.25, 0.25] # すべての平均
])
names = ["1番目のみ", "2番目のみ", "1+2の和", "1-2の差", "全体の平均"]
x_sample = np.array([10.0, 4.0, 6.0, 8.0])
y_sample = W_pattern @ x_sample
print(f"入力: {x_sample}")
print(f"\n{'ニューロン':>12} | {'重み':>26} | {'出力':>8}")
print("-" * 52)
for i, name in enumerate(names):
print(f"{name:>12} | {str(W_pattern[i]):>26} | {y_sample[i]:>8.2f}")
print("\n=== PyTorchでの確認 ===")
try:
import torch
import torch.nn as nn
torch.manual_seed(0)
linear = nn.Linear(768, 3072)
print(f"nn.Linear(768, 3072) の内部:")
print(f" weight の形: {linear.weight.shape}")
print(f" bias の形: {linear.bias.shape}")
print(f" パラメータ数: {sum(p.numel() for p in linear.parameters()):,}")
x_torch = torch.randn(768)
y_torch = linear(x_torch)
print(f"\n入力の形: {x_torch.shape}")
print(f"出力の形: {y_torch.shape}")
manual = linear.weight @ x_torch + linear.bias
print(f"手動計算と一致するか: {torch.allclose(y_torch, manual, atol=1e-6)}")
except ImportError:
print("PyTorchがインストールされていないため、この部分はスキップします。")
初学者がつまずきやすい点
つまずき1:行と列のどちらと内積を取るのか迷う
行列とベクトルの積では、行列の行とベクトルの内積を取ります。列ではありません。
覚え方として、行列の行を、ベクトルの上に横倒しにして重ねるというイメージが有効です。
紙の上で、行列の1行目を指でなぞりながら、ベクトルの各要素と対応させてみてください。一度手を動かすと、迷わなくなります。
つまずき2:結果の次元数を間違える
結果の次元数は、行列の行数です。列数ではありません。
3行4列の行列と4次元のベクトルを掛けると、3次元のベクトルになります。
行の数だけ内積を計算するから、行の数だけ結果が出る、と考えれば間違えません。
つまずき3:転置が必要かどうかで混乱する
と
のどちらを使うべきか、迷うことがあります。
判断基準は、形が合うかどうかです。合わなければ、転置が必要です。
コードを書くときは、まず shape を確認し、内側の次元が一致するように配置してください。
第10章で、この判断を体系的に扱います。
演習
演習1:手計算する
次の行列とベクトルの積を計算してください。
また、結果の次元数が、なぜその値になるのかを説明してください。
演習2:全結合層の計算を手計算する
入力を とします。
を計算してください。
演習3:形が合うかを判定する
次の組み合わせについて、計算が可能かどうかを判定してください。可能な場合は、結果の形を答えてください。
(3, 5) の行列と、5次元のベクトル
(4, 4) の行列と、4次元のベクトル
(2, 7) の行列と、3次元のベクトル
(768, 3072) の行列と、3072次元のベクトル
演習4:計算回数を数える
次の全結合層について、1回の計算で行われる掛け算の回数と、足し算の回数を求めてください。バイアスの加算も含めてください。
入力512次元、出力256次元の層
演習5:多層ネットワークを実装する
import numpy as np
np.random.seed(42)
class Layer:
def __init__(self, input_dim, output_dim, name):
self.W = np.random.randn(output_dim, input_dim) * 0.1
self.b = np.zeros(output_dim)
self.name = name
def forward(self, x):
y = self.W @ x + self.b
print(f"{self.name:>8}: 入力{x.shape} × 重み{self.W.shape} → 出力{y.shape}")
return y
def num_params(self):
return self.W.size + self.b.size
layers = [
Layer(784, 128, "1層目"),
Layer(128, 64, "2層目"),
Layer(64, 10, "3層目")
]
print("=== 各層の構成 ===")
total_params = 0
for layer in layers:
params = layer.num_params()
total_params += params
print(f"{layer.name}: 重み{layer.W.shape}, バイアス{layer.b.shape}, パラメータ{params:,}個")
print(f"\n全パラメータ数: {total_params:,}")
print("\n=== 順伝播の実行 ===")
x = np.random.randn(784)
print(f"{'入力':>8}: {x.shape}")
h = x
for layer in layers:
h = layer.forward(h)
print(f"\n最終出力: {h.round(4)}")
print("\n=== 掛け算の総回数 ===")
total_mults = sum(layer.W.size for layer in layers)
print(f"1回の順伝播で行われる掛け算: {total_mults:,}回")
演習の解答例と解説
演習1の解答は、次のとおりです。
1行目:
2行目:
3行目:
結果は です。
結果が3次元になる理由は、行列の行数が3だからです。行の数だけ内積を計算するため、結果の要素数も3になります。
演習2の解答は、次のとおりです。
まず を計算します。
1行目:
2行目:
3行目:
次にバイアスを足します。
答えは です。
演習3の解答は、次のとおりです。
(3, 5) と5次元は、計算可能です。列数5とベクトルの次元5が一致しています。結果は3次元です。
(4, 4) と4次元は、計算可能です。結果は4次元です。
(2, 7) と3次元は、計算できません。列数7とベクトルの次元3が一致していません。
(768, 3072) と3072次元は、計算可能です。結果は768次元です。
四つ目は、GPT-2のMLP層の後半部分に相当します。3072次元を768次元に戻す変換です。
演習4の解答は、次のとおりです。
掛け算の回数は、重み行列の要素数と一致します。
約13万1千回です。
足し算の回数は、次のように計算されます。
各ニューロンの内積で、512個の項を足すため、511回の足し算が必要です。これが256個のニューロン分あります。
さらに、バイアスの加算が256回あります。
足し算も約13万1千回です。
掛け算と足し算が、ほぼ同数になることが分かります。実務では、この二つを合わせて計算量を見積もることが多くあります。
演習5では、各層で形がどう変化するかが表示されます。784次元の入力が、128次元、64次元、10次元と段階的に縮小していく様子を確認してください。
講師向けの補足
この章は、連載全体の中心です。時間配分を最も厚くしてください。
計算手順の説明では、必ず紙の上で手を動かしてもらってください。3行3列程度の小さな例で構いません。指で行をなぞりながら計算する経験が、記憶に残ります。
そのうえで、nn.Linear(768, 3072) と書いたとき、内部で236万回の掛け算が行われているという事実を示してください。コードの一行が持つ重みが実感されます。
また、演習5のように、実際に多層ネットワークを構成して、各層で形が変わっていく様子を表示させると、理解が定着します。形の変化を追う習慣が、第10章のエラー対処にもつながります。
受講者から、なぜ行と内積を取るのか、列ではだめなのか、という質問が出ることがあります。これは良い問いです。1つのニューロンが1行を占めているから、という第7章の内容に立ち返って説明してください。
理解度を確認する問い
第8章の内容を、次の問いで確認してみてください。
行列とベクトルの積の計算手順を、自分の言葉で説明してみてください。
m行n列の行列と、k次元のベクトルが計算できるための条件と、結果の次元数を答えてください。
という式の各記号が、全結合層の何に対応するかを説明してください。
重み行列の各行は、何を表していると解釈できるでしょうか。
nn.Linear(512, 256) と書いたとき、内部では何回の掛け算が行われるでしょうか。
まとめ
行列とベクトルの積は、行列の各行とベクトルの内積を計算し、その結果を順に並べたものです。答えのi番目の要素は、行列のi行目とベクトルの内積になります。
計算が成立する条件は、行列の列数とベクトルの次元数が一致することです。結果の次元数は、行列の行数と一致します。
そして、この計算こそが、全結合層の正体です。
重み行列の行数がニューロンの個数、列数が入力の次元数です。バイアスベクトルの次元数は、ニューロンの個数と一致します。
重み行列の各行は、そのニューロンが反応したいパターンを表しています。入力ベクトルが与えられると、すべてのパターンとの類似度が一度に計算され、その結果が出力ベクトルになります。
記法には、 と
の二つの流儀があります。どちらも同じ計算を表しており、重要なのは記号の順序ではなく、形が合っているかどうかです。
次の第9章では、行列と行列の積を扱います。この章では入力が1件のベクトルでしたが、実際には複数のデータをまとめて処理します。入力を行列にすると何が起きるのか、なぜまとめて処理するほうが効率的なのかを解説します。
第9章に進む前に、演習1と演習2を、必ず紙の上で手計算してみてください。行と内積を取るという手順を、指でなぞって確認しておくことが、以降の章の理解を支えます。
セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。
投稿者プロフィール

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