コンテンツにスキップ

4.1.3 行列:データのバッチ変換

行列の線形変換グリッド図

  • 行列とは何かを直感的に理解する(1つの表 / 1組の操作)
  • 行列積の意味と計算方法を身につける
  • 転置、逆行列の直感を理解する
  • ニューラルネットワークの各層がなぜ行列積なのかを理解する
  • NumPy で行列演算を実装する

まず、とても大事な学習イメージを共有します

Section titled “まず、とても大事な学習イメージを共有します”

この節では、行列論を全部学び切ることが目的ではありません。まずは、AI でよく出てくる次の3つの感覚をしっかりつかみましょう。

  • 行列は、データをまとめて入れられる
  • 行列は、バッチ変換を表せる
  • 行列積は、後のモデルで何度も登場する

行列をただの「数字の表」として見るだけだと、だんだん抽象的に感じやすくなります。初心者には、次のように見るのがおすすめです。

行列のバッチ変換フローチャート

この節で大事なのは、定義を暗記することではなく、次を理解することです。

  • なぜ複数のデータを自然に行列で表すのか
  • なぜ行列積で一度に複数のサンプルを処理できるのか
  • なぜ深層学習のコードで X @ W が何度も出てくるのか

すぐ見返せる用語とコードの前提

Section titled “すぐ見返せる用語とコードの前提”
用語意味ここで重要な理由
batchバッチ、一緒に処理する複数のサンプル行列はよく1つのバッチを表し、行がサンプル、列が特徴量になります。
feature特徴量、面積や年齢や距離のような入力列行列の列は通常、異なる特徴量に対応します。
shapeNumPy が配列サイズを表す情報行列積は内側の次元が一致するときだけ実行できます。
bias / bバイアス、乗算後に足す学習可能なずれX @ W + b により、モデルは回転や拡大縮小だけでなく出力をずらせます。
ReLURectified Linear Unit、修正線形ユニット負の値を 0 にし、正の値をそのまま残す活性化関数です。非線形性を加えます。
determinant行列式、正方行列が空間をつぶすかを見る数0 なら、その行列には逆行列がありません。
singular matrix特異行列、逆行列を持たない行列変換で情報を失うため、元の入力に戻せません。

特に断りがない限り、コード片段は import numpy as np が必要です。描画例では import matplotlib.pyplot as plt も必要です。

見方1:行列は1つの表

ベクトルが「1枚の情報カード」だとすると、 行列はまず次のように理解できます。

  • きれいに並んだ情報カードの束

だから機械学習では、

  • 1つのサンプルはベクトル
  • 複数のサンプルは自然に行列

となります。

Pandas の DataFrame も、本質的には行列に近いものです。

import numpy as np
# 3人の学生の4科目の点数
scores = np.array([
[85, 92, 78, 90], # 学生1
[72, 88, 95, 85], # 学生2
[90, 76, 88, 92], # 学生3
])
print(f"形状: {scores.shape}") # (3, 4) → 3行4列

見方2:行列は「変換マシン」

行列にベクトルを入れると、新しいベクトルが出てきます。 これは関数のように、入力 → 変換 → 出力 を行うものです。

flowchart LR
A["入力ベクトル<br/>[x, y]"] --> M["行列 M<br/>変換マシン"]
M --> B["出力ベクトル<br/>[x', y']"]
style A fill:#e3f2fd,stroke:#1565c0,color:#333
style M fill:#fff3e0,stroke:#e65100,color:#333
style B fill:#e8f5e9,stroke:#2e7d32,color:#333

これが線形代数の最も大事な考え方です。行列 = 変換 です。

M = np.array([
[1, 2, 3],
[4, 5, 6],
])
print(f"形状 (shape): {M.shape}") # (2, 3) → 2行3列
print(f"行数: {M.shape[0]}") # 2
print(f"列数: {M.shape[1]}") # 3
print(f"要素の総数: {M.size}") # 6
print(f"データ型: {M.dtype}") # int64
print(f"1行目: {M[0]}") # [1 2 3]
print(f"1行目3列目: {M[1, 2]}") # 6

「1つのサンプル」から「複数のサンプル」へ

Section titled “「1つのサンプル」から「複数のサンプル」へ”

すでにベクトルを学んでいるなら、行列は次のように捉えられます。

たくさんのベクトルを、同じ形式で積み重ねたもの。

import numpy as np
# [面積, 築年数, 駅からの距離]
house_1 = np.array([88, 5, 1.2])
house_2 = np.array([120, 8, 0.5])
house_3 = np.array([75, 2, 1.8])
X = np.array([
house_1,
house_2,
house_3,
])
print(X)
print("形状:", X.shape) # (3, 3)

ここでの意味は次の通りです。

  • 各行が1つのサンプル
  • 各列が1つの特徴量

これは機械学習や深層学習で最もよく使うデータの並べ方です。


ベクトルと同じように、対応する位置どうしを足す / 掛ける だけです。

A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print("足し算:\n", A + B) # [[6, 8], [10, 12]]
print("スカラー倍:\n", 3 * A) # [[3, 6], [9, 12]]

行列積は、普通の数の掛け算とはまったく違います。ルールはこうです。

結果の各要素 = 左の行列のある行 と 右の行列のある列 の内積

A = np.array([[1, 2],
[3, 4]]) # 2×2
B = np.array([[5, 6],
[7, 8]]) # 2×2
# 行列積
C = A @ B # 推奨の書き方
# C = np.dot(A, B) # 同じ意味
print("A @ B =")
print(C)
# [[19, 22], ← 1*5+2*7=19, 1*6+2*8=22
# [43, 50]] ← 3*5+4*7=43, 3*6+4*8=50

手で確認してみよう

  • C[0,0] = 1×5 + 2×7 = 5 + 14 = 19
  • C[0,1] = 1×6 + 2×8 = 6 + 16 = 22
  • C[1,0] = 3×5 + 4×7 = 15 + 28 = 43
  • C[1,1] = 3×6 + 4×8 = 18 + 32 = 50

行列積のサイズルール図解

flowchart LR
A["A<br/>m × n"] --> C["C<br/>m × p"]
B["B<br/>n × p"] --> C
style A fill:#e3f2fd,stroke:#1565c0,color:#333
style B fill:#fff3e0,stroke:#e65100,color:#333
style C fill:#e8f5e9,stroke:#2e7d32,color:#333

大事なルール:左の行列の列数 = 右の行列の行数。 そして、結果の形は (左の行列の行数, 右の行列の列数) になります。

A = np.array([[1, 2, 3],
[4, 5, 6]]) # 2×3
B = np.array([[1, 2],
[3, 4],
[5, 6]]) # 3×2
C = A @ B # 2×2 ✓(3 == 3)
print(f"A({A.shape}) @ B({B.shape}) = C({C.shape})")
print(C)
# [[22, 28],
# [49, 64]]
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print("A @ B =\n", A @ B)
print("B @ A =\n", B @ A)
print("A@B == B@A?", np.array_equal(A @ B, B @ A)) # False

「サンプル行列 × 重み行列」を手計算してみる

Section titled “「サンプル行列 × 重み行列」を手計算してみる”

これは初心者がぜひ理解したい部分です。後のニューラルネットワークにそのままつながります。

X = np.array([
[1, 2],
[3, 4],
[5, 6],
]) # 3×2
W = np.array([
[0.1, 1.0],
[0.2, 0.5],
]) # 2×2
Z = X @ W
print(Z.round(2))

期待される出力:

Terminal window
[[0.5 2. ]
[1.1 5. ]
[1.7 8. ]]

行ごとに見ると、次の意味になります。

  • 1行目の出力 = 1つ目のサンプル [1, 2] に重み行列を適用したもの
  • 2行目の出力 = 2つ目のサンプル [3, 4] に重み行列を適用したもの
  • 3行目の出力 = 3つ目のサンプル [5, 6] に重み行列を適用したもの

行列積のすごいところは、

1つのサンプルだけでなく、複数のサンプルを一度に計算できること

です。

初心者がまず確認すべき shape の4ステップ

Section titled “初心者がまず確認すべき shape の4ステップ”

行列積でよくエラーが出るときは、あわてず次の4つを確認しましょう。

  1. 左側の行列の形は何か
  2. 右側の行列の形は何か
  3. 左側の列数は右側の行数と等しいか
  4. 期待している出力の形は何か
print("X.shape =", X.shape)
print("W.shape =", W.shape)
print("Z.shape =", (X @ W).shape)

三、行列を「変換」として見る——直感的な可視化

Section titled “三、行列を「変換」として見る——直感的な可視化”

行列はベクトルに対して、回転、拡大縮小、せん断 などの変換を行えます。 ここでは、行列で2次元の点を回転させてみます。

import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['Arial Unicode MS']
plt.rcParams['axes.unicode_minus'] = False
# 正方形の4頂点 + 始点に戻る
square = np.array([
[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0, 0], # 始点に戻ると閉じた図形として描きやすい
]).T # 2×5 に転置して、行列積しやすくする
# 45°回転行列
theta = np.radians(45) # 度をラジアンに変換
R = np.array([
[np.cos(theta), -np.sin(theta)],
[np.sin(theta), np.cos(theta)]
])
print(f"回転行列:\n{R.round(3)}")
# 回転を適用
rotated = R @ square # 行列積!
fig, axes = plt.subplots(1, 2, figsize=(12, 5))
# 変換前
axes[0].plot(square[0], square[1], 'b-o', linewidth=2, markersize=8)
axes[0].fill(square[0], square[1], alpha=0.2, color='steelblue')
axes[0].set_xlim(-1.5, 1.5)
axes[0].set_ylim(-0.5, 1.8)
axes[0].set_aspect('equal')
axes[0].grid(True, alpha=0.3)
axes[0].set_title('変換前(元の正方形)')
# 変換後
axes[1].plot(square[0], square[1], 'b--', alpha=0.3, linewidth=1)
axes[1].plot(rotated[0], rotated[1], 'r-o', linewidth=2, markersize=8)
axes[1].fill(rotated[0], rotated[1], alpha=0.2, color='coral')
axes[1].set_xlim(-1.5, 1.5)
axes[1].set_ylim(-0.5, 1.8)
axes[1].set_aspect('equal')
axes[1].grid(True, alpha=0.3)
axes[1].set_title('変換後(45°回転)')
plt.suptitle('行列変換 = 回転', fontsize=14)
plt.tight_layout()
plt.show()

重要なポイント:2×2 の行列に2次元ベクトルを掛けると、空間変換が1回できます。 この考え方は、任意の次元にも広げられます。

fig, axes = plt.subplots(1, 4, figsize=(18, 4))
# 元の形
triangle = np.array([
[0, 0], [1, 0], [0.5, 1], [0, 0]
]).T
transforms = [
(np.eye(2), '元の形(単位行列)'),
(np.array([[2, 0], [0, 2]]), '2倍に拡大'),
(np.array([[1, 0.5], [0, 1]]), '水平せん断'),
(np.array([[-1, 0], [0, 1]]), '左右反転'),
]
for ax, (M, title) in zip(axes, transforms):
transformed = M @ triangle
ax.plot(triangle[0], triangle[1], 'b--', alpha=0.3)
ax.fill(triangle[0], triangle[1], alpha=0.1, color='blue')
ax.plot(transformed[0], transformed[1], 'r-o', linewidth=2, markersize=6)
ax.fill(transformed[0], transformed[1], alpha=0.2, color='coral')
ax.set_xlim(-2.5, 2.5)
ax.set_ylim(-0.5, 2.5)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
ax.set_title(title)
plt.tight_layout()
plt.show()

転置 = 行と列を入れ替えること。 元の i 行目が i 列目になります。

A = np.array([
[1, 2, 3],
[4, 5, 6],
])
print(f"A の形状: {A.shape}") # (2, 3)
print(f"A の転置:\n{A.T}")
print(f"転置後の形状: {A.T.shape}") # (3, 2)

出力:

A の転置:
[[1 4]
[2 5]
[3 6]]

いつ転置を使うの?

  • データ処理: 「行がサンプル、列が特徴量」を「行が特徴量、列がサンプル」にしたいとき
  • 行列演算: 公式に合わせるために転置が必要なとき
# 単位行列(対角線がすべて1)
I = np.eye(3)
print("単位行列:\n", I)
# [[1. 0. 0.]
# [0. 1. 0.]
# [0. 0. 1.]]
# 単位行列の性質:A @ I = I @ A = A
A = np.array([[1, 2], [3, 4]])
print("A @ I == A?", np.allclose(A @ np.eye(2), A)) # True

行列 A が「変換」だとすると、逆行列 A⁻¹ は**「元に戻す変換」**です。 A の操作を打ち消します。

A = np.array([[2, 1],
[1, 1]])
# 逆行列を計算
A_inv = np.linalg.inv(A)
print("A の逆行列:\n", A_inv)
# 確認:A @ A_inv = 単位行列
print("A @ A_inv =\n", (A @ A_inv).round(10))
# [[1. 0.]
# [0. 1.]] → 単位行列!

直感:A がベクトルを45°回転させたなら、A⁻¹ はそれを元に戻します。 A がベクトルを2倍にしたなら、A⁻¹ は2分の1に戻します。

# 可視化:変換 → 逆変換 = 元に戻る
v = np.array([1, 2])
transformed = A @ v # A で変換
recovered = A_inv @ transformed # A_inv で元に戻す
print(f"元の値: {v}")
print(f"変換後: {transformed}")
print(f"元に戻した後: {recovered}") # 元と同じ!

五、行列とニューラルネットワーク

Section titled “五、行列とニューラルネットワーク”

ニューラルネットワークの本質

Section titled “ニューラルネットワークの本質”

この節で最も大事な気づきです。ニューラルネットワークの各層は、本質的には「行列積 + 活性化関数」です。

flowchart LR
X["入力 X<br/>n 個の特徴量"] --> MUL["行列積<br/>X @ W + b"]
MUL --> ACT["活性化関数<br/>relu / sigmoid"]
ACT --> Y["出力 Y<br/>次の層への入力"]
style X fill:#e3f2fd,stroke:#1565c0,color:#333
style MUL fill:#fff3e0,stroke:#e65100,color:#333
style ACT fill:#f3e5f5,stroke:#7b1fa2,color:#333
style Y fill:#e8f5e9,stroke:#2e7d32,color:#333

1つのニューロンの式から行列の式へ

Section titled “1つのニューロンの式から行列の式へ”

1つのサンプルだけを見ると、ニューラルネットワークの1層は実はこうです。

出力 = 入力ベクトル · 重みベクトル + バイアス

import numpy as np
x = np.array([1.0, 0.5, -0.3])
w = np.array([0.2, -0.4, 0.6])
b = 0.1
y = x @ w + b
print(round(y, 4))

期待される出力:

Terminal window
-0.08

1つだけでなく、複数のサンプルをまとめて計算すると、自然に次の形になります。

Z = X @ W + b

ここで、

  • X はサンプル行列
  • W は重み行列
  • b はバイアス
  • Z は線形出力

です。

コードで1層のニューラルネットワークをまねる

Section titled “コードで1層のニューラルネットワークをまねる”
# 1層ニューラルネットワークの順伝播をまねる
# 入力:3サンプル、それぞれ4特徴量
X = np.array([
[1.0, 0.5, -0.3, 0.8],
[0.2, -0.1, 0.7, 0.3],
[0.9, 0.4, 0.1, -0.5],
])
print(f"入力 X: {X.shape}") # (3, 4)
# 重み行列:4特徴量から2出力へ写す
rng = np.random.default_rng(seed=42)
W = rng.normal(size=(4, 2)) * 0.5
print(f"重み W: {W.shape}") # (4, 2)
# バイアス
b = np.zeros(2)
# 順伝播:行列積 + バイアス
Z = X @ W + b # (3, 4) @ (4, 2) = (3, 2)
print(f"線形出力 Z: {Z.shape}")
# 活性化関数(ReLU:負の値は0、正の値はそのまま)
def relu(x):
return np.maximum(0, x)
output = relu(Z)
print(f"活性化後の出力: {output.shape}") # (3, 2)
print(f"\n最終出力:\n{output.round(3)}")

seed=42 を使ったときの期待される出力:

Terminal window
入力 X: (3, 4)
重み W: (4, 2)
線形出力 Z: (3, 2)
活性化後の出力: (3, 2)
最終出力:
[[0.684 0. ]
[0. 0. ]
[0.158 0. ]]

読み解き

  • 3つのサンプル(3行)、各サンプルに4特徴量(4列)
  • 重み行列 W は 4×2 で、4次元特徴量を2次元へ写す
  • 行列積で全サンプルを一度に処理できる。これがバッチ計算の強みです
  • バイアス b の形状は (2,) です。NumPy はこれを Z の各行に自動で足します。これは broadcasting(ブロードキャスト)と呼ばれます。

多層ネットワーク = 行列の連続積

Section titled “多層ネットワーク = 行列の連続積”
# 3層ニューラルネットワークをまねる
rng = np.random.default_rng(seed=42)
X = rng.normal(size=(5, 10)) # 5サンプル、10特徴量
# 第1層:10 → 8
W1 = rng.normal(size=(10, 8)) * 0.3
h1 = relu(X @ W1)
print(f"第1層の出力: {h1.shape}") # (5, 8)
# 第2層:8 → 4
W2 = rng.normal(size=(8, 4)) * 0.3
h2 = relu(h1 @ W2)
print(f"第2層の出力: {h2.shape}") # (5, 4)
# 第3層(出力層):4 → 2
W3 = rng.normal(size=(4, 2)) * 0.3
output = h2 @ W3 # 出力層では通常 ReLU を使わない
print(f"最終出力: {output.shape}") # (5, 2)

初心者がよくやる3つの行列ミス

Section titled “初心者がよくやる3つの行列ミス”
  1. 要素ごとの掛け算 A * B を、行列積だと勘違いする 本当の行列積は A @ B を使います。

  2. 形状を見ずにそのまま掛け始める shape が合っていないと、必ずエラーになります。

  3. 「各行が何を表すのか」が分からなくなる 「各行は1つのサンプル」と覚えるだけで、かなり理解しやすくなります。


六、実用例:連立一次方程式を解く

Section titled “六、実用例:連立一次方程式を解く”

行列の代表的な応用の1つが、連立一次方程式を解くことです。

2x + y = 5
x + 3y = 7

行列で書くと、A @ x = b です。

# 係数行列
A = np.array([[2, 1],
[1, 3]])
# 右辺の定数
b = np.array([5, 7])
# 方程式を解く
x = np.linalg.solve(A, b)
print(f"解: x = {x[0]:.2f}, y = {x[1]:.2f}")
# 解: x = 1.60, y = 1.80
# 確認
print(f"確認: A @ x = {A @ x}") # [5. 7.] ✓

import numpy as np
# ========== 行列を作る ==========
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
I = np.eye(2) # 単位行列
Z = np.zeros((3, 4)) # 全要素0の行列
rng = np.random.default_rng(seed=42)
R = rng.normal(size=(3, 4)) # ランダム行列
# ========== 基本演算 ==========
print("足し算:\n", A + B)
print("スカラー倍:\n", 2 * A)
print("要素ごとの掛け算:\n", A * B) # 注意:これは行列積ではありません!
# ========== 行列積 ==========
print("行列積:\n", A @ B) # 推奨
print("行列積:\n", np.dot(A, B)) # 同じ
print("行列積:\n", np.matmul(A, B)) # 同じ
# ========== 転置 ==========
print("転置:\n", A.T)
# ========== 逆行列 ==========
print("逆行列:\n", np.linalg.inv(A))
# ========== 行列式 ==========
print("行列式:", np.linalg.det(A)) # -2.0
# ========== 方程式を解く ==========
b = np.array([1, 2])
x = np.linalg.solve(A, b)
print("方程式の解:", x)

ここまで学んだら、次は何を考える?

Section titled “ここまで学んだら、次は何を考える?”

行列を学んだあと、次の節へ持っていくとよい問いは次の3つです。

  1. 行列は、ほとんどのベクトルをどう変えるのか?
  2. 変換後も向きが変わらない、特別な方向はあるのか?
  3. その「特別な方向」が、なぜ PCA や次元削減につながるのか?

この3つは、そのまま次の話につながります。


このページを終えたら、この evidence card を残します。

数学対象
ベクトル、行列、固有値、基底、またはベクトル空間の概念
数値例
これを計算するために使う小さな数値または NumPy のスニペット
可視化または出力
形状、変換後の点、類似度スコア、固有方向、または射影
AI との関係
これが embeddings、バッチ、PCA、ニューラル層、または attention のどこに現れるか
期待される成果
計算と、それを AI の操作に結びつける1文
概念直感的な理解NumPy 実装
行列1つの表 / 1つの変換np.array([[1,2],[3,4]])
行列積行と列の内積の組み合わせA @ B
転置行と列を入れ替えるA.T
単位行列「何もしない」変換np.eye(n)
逆行列変換を元に戻すnp.linalg.inv(A)
方程式を解くAx = b → x = ?np.linalg.solve(A, b)

この節で一番持ち帰ってほしいこと

Section titled “この節で一番持ち帰ってほしいこと”
  • 行列は「データをまとめる」ためにも、「変換を表す」ためにも使われる
  • 行列積は、まず「バッチの内積」として理解するとよい
  • だから AI のコードでは X @ W が何度も出てくる

次が与えられています。

A = np.array([[1, 0, 2],
[0, 3, 1]]) # 2×3
B = np.array([[2, 1],
[0, 4],
[3, 2]]) # 3×2
  1. まず A @ B の結果を手計算する
  2. そのあと NumPy で確認する

回転行列を使って三角形を 90° 回転させ、変換前後を比較する図を描いてみましょう。

ヒント:90° の回転行列は [[0, -1], [1, 0]] です。

練習3:2層ニューラルネットワークをまねる

Section titled “練習3:2層ニューラルネットワークをまねる”

2層ネットワークを作り、100個のサンプル(各5特徴量)を入力し、1層目で3つの値、2層目で1つの値を出力するようにしてください。 各層の入力と出力の形状を表示しましょう。

参考実装と解説
  • 与えられた行列では、手計算の結果は A @ B = [[8, 5], [3, 14]] です。NumPy の結果も一致するはずです。
  • 90 度回転行列 [[0,-1],[1,0]](x,y)(-y,x) に写します。三角形は大きさと形を保ったまま反時計回りに回転します。
  • 2 層ネットワークでは、shape が (100,5) @ (5,3) -> (100,3)、次に (100,3) @ (3,1) -> (100,1) と流れます。shape の証拠がこの問題の最良の安全確認です。