第14章 勾配ベクトルとは何か
こんにちは。ゆうせいです。
第13章までで、入力から出力へ向かう順方向の計算を扱ってきました。第14章からは、第IV部として、学習の仕組みに現れる線形代数を扱います。ここまでの説明では、重み行列の値は学習によって決まる、と述べてきました。では、その学習とは、具体的に何をしているのでしょうか。答えは、パラメータを少しずつ調整することです。そして、どちらの方向に、どれだけ調整すればよいかを教えてくれるのが、勾配です。この勾配は、実はベクトルです。この章では、勾配がベクトルであることの意味と、それが学習でどう使われるのかを確認します。微分の詳細には深入りせず、線形代数の観点から見ていきます。
学習とは何をしているのか
損失関数
まず、学習の目標を確認します。
モデルの予測が、正解からどれだけずれているかを表す指標を、損失関数と呼びます。
たとえば、予測値と正解値の差の2乗を使う方法があります。
損失が小さいほど、良い予測です。
重要な点は、損失が1個の数値、つまりスカラーであることです。
複数のデータ、複数の出力があっても、最終的には1個の数値にまとめられます。だからこそ、これを小さくするという明確な目標が立てられます。
損失はパラメータの関数である
ここが、理解の鍵です。
損失の値は、モデルのパラメータによって決まります。
パラメータを変えれば予測が変わり、予測が変われば損失が変わります。
パラメータが100万個あれば、100万個の変数を持つ関数です。
学習とは、この関数を最小にするパラメータの組み合わせを探す作業です。
1変数の場合から考える
いきなり100万変数は扱えません。まず、変数が1個の場合から考えます。
傾きが教えてくれること
パラメータが 一つだけだとします。
損失 を、
で微分した値を、傾きと呼びます。
この値が教えてくれることは、次のとおりです。
傾きがプラスであれば、 を増やすと損失が増えます。したがって、
を減らすべきです。
傾きがマイナスであれば、 を増やすと損失が減ります。したがって、
を増やすべきです。
傾きが0であれば、その付近では損失が変化しません。

更新の規則
以上から、次の更新規則が導かれます。
は学習率です。どれだけ大きく動かすかを決める、小さな正の値です。
傾きがプラスなら、マイナスを掛けることで が減ります。
傾きがマイナスなら、マイナスを掛けることで が増えます。
いずれの場合も、損失が減る方向に動きます。
傾きの大きさの意味
傾きの絶対値が大きいということは、そのパラメータを少し動かすだけで、損失が大きく変わるということです。
したがって、大きく動かすべきです。
更新規則では、傾きそのものを掛けているため、傾きが大きいほど更新量も大きくなります。この性質が、自動的に組み込まれています。
多変数になると勾配ベクトルになる
偏微分
パラメータが複数ある場合、それぞれについて微分を考えます。
他のパラメータを固定して、このパラメータだけを動かしたら、損失はどう変わるかを計算します。
これを偏微分と呼び、次のように書きます。
丸みを帯びた記号を使うのが、偏微分の記法です。
勾配ベクトル
すべてのパラメータについての偏微分を、並べたものが勾配ベクトルです。
記号 は、ナブラと読みます。
重要な点は、勾配ベクトルの次元数が、パラメータの数と一致することです。
パラメータが100万個あれば、勾配ベクトルも100万次元です。

なぜベクトルとして扱うのか
各パラメータの偏微分を、バラバラに管理することもできます。しかし、まとめてベクトルとして扱うことには、大きな利点があります。
第一に、更新規則が簡潔に書けます。
$latex \mathbf{w}{new} = \mathbf{w}{old} - \eta \nabla L$
第5章で学んだ、ベクトルのスカラー倍と引き算です。
第二に、計算がまとめて実行できます。100万個の更新を、1回のベクトル演算で処理できます。
第三に、勾配ベクトルの長さといった、全体的な性質を扱えるようになります。
勾配ベクトルの方向が意味すること
最も急な方向
勾配ベクトルには、重要な性質があります。
勾配ベクトルの方向は、損失が最も急激に増加する方向です。
したがって、その反対方向、つまりマイナスをつけた方向が、損失が最も急激に減少する方向になります。
更新規則でマイナスをつけているのは、この理由によります。
山下りのたとえ
よく使われる比喩を紹介します。
霧のかかった山の中にいて、麓を目指しているとします。周囲が見えないため、遠くの様子は分かりません。
分かるのは、足元の傾きだけです。
そこで、最も急な下り方向に、一歩進みます。そこで再び足元の傾きを調べ、また最も急な下り方向に進みます。
これを繰り返せば、いずれ低い場所にたどり着きます。
勾配降下法と呼ばれる手法は、まさにこの戦略です。
この比喩の限界
ただし、この比喩には注意点があります。
山下りの比喩は、2次元や3次元を想定しています。実際のパラメータ空間は、100万次元です。
高次元空間では、直感が通用しない現象が起こります。
たとえば、2次元ではくぼみに落ちて抜け出せなくなる状況が想像されますが、高次元では、あらゆる方向で上りになっている点は、かなり稀です。多くの方向のうち、どれか一つでも下りがあれば、そちらに抜けられるためです。
高次元では、局所的なくぼみより、鞍点と呼ばれる、ある方向では下りで別の方向では上りという地点のほうが、問題になりやすいと言われています。
勾配ベクトルの長さ
第2章で、ベクトルの長さを扱いました。勾配ベクトルの長さにも、意味があります。
長さが表すもの
勾配ベクトルの長さは、損失の変化の急峻さを表します。
長さが大きければ、パラメータを少し動かすだけで損失が大きく変わります。急な斜面にいる状態です。
長さが0に近ければ、平坦な場所にいます。損失の最小値付近か、あるいは学習が停滞している状態です。
勾配消失と勾配爆発
実務では、勾配ベクトルの長さが問題になることがあります。
勾配消失とは、長さが極端に小さくなり、学習が進まなくなる現象です。
深い層を持つモデルで、入力に近い層ほど起こりやすくなります。
勾配爆発とは、逆に長さが極端に大きくなり、パラメータが暴れて学習が破綻する現象です。
勾配クリッピング
勾配爆発への対策として、勾配クリッピングという手法があります。
勾配ベクトルの長さが、あらかじめ決めた閾値を超えたら、長さだけを縮小します。
が閾値です。
これは、第5章で扱ったスカラー倍です。方向は変えず、長さだけを調整します。
方向の情報は保ちつつ、更新量が大きくなりすぎるのを防ぐ、という仕組みです。
重み行列に対する勾配
ここまで、パラメータをベクトルとして扱ってきました。しかし、実際のニューラルネットワークでは、パラメータの多くは行列の形をしています。
形は元のパラメータと同じ
重み行列 が768行3072列だとします。
この行列に対する勾配は、同じく768行3072列の行列になります。
各要素が、対応する重みについての偏微分です。
$latex \left(\frac{\partial L}{\partial W}\right){ij} = \frac{\partial L}{\partial W{ij}}$
更新も要素ごと
更新規則も、そのまま適用できます。
行列どうしの引き算と、行列のスカラー倍です。第5章で扱った演算が、行列に拡張されただけです。
メモリへの影響
ここで、実務上の重要な帰結があります。
勾配は、パラメータと同じ形と大きさを持ちます。
パラメータが1億2400万個あれば、勾配も1億2400万個です。
さらに、多くの最適化手法では、過去の勾配の情報を保持します。Adamと呼ばれる代表的な手法では、パラメータ1個につき、追加で2個の値を保持します。
したがって、学習時に必要なメモリは、パラメータだけの場合の数倍になります。
float32で保持する場合、次のようになります。
パラメータが約496メガバイト。
勾配が約496メガバイト。
Adamの状態が約992メガバイト。
合計で約2ギガバイトです。これに加えて、各層の中間出力も保持する必要があります。
推論だけなら動くモデルが、学習させようとするとメモリ不足になる。この現象の理由が、ここにあります。
ミニバッチと勾配の平均
第9章で、複数のデータをまとめて処理することを扱いました。勾配についても、同様です。
各データから勾配が得られる
1件のデータについて損失を計算すれば、そのデータに対する勾配が得られます。
32件のデータがあれば、32本の勾配ベクトルが得られます。
平均を取る
これらの平均を取って、更新に使います。
第5章で扱った、ベクトルの足し算とスカラー倍の組み合わせです。
平均を取る理由
1件のデータだけから勾配を計算すると、そのデータの特殊性に強く影響されます。
たまたま例外的なデータであれば、誤った方向にパラメータが動きます。
複数のデータの勾配を平均すると、個々のばらつきが打ち消され、より信頼できる更新方向が得られます。
一方で、平均を取りすぎると、更新が滑らかになりすぎるという側面もあります。適度なばらつきが、学習に有利に働く場合もあります。
このバランスを調整するのが、バッチサイズという設定です。
Pythonで確認する
import numpy as np
print("=== 1変数の場合:傾きが方向を教える ===")
def loss_1d(w):
return (w - 3) ** 2
def gradient_1d(w):
return 2 * (w - 3)
print("損失関数: L(w) = (w - 3)^2 (最小値は w=3 のとき)")
print(f"\n{'w':>8} | {'損失':>10} | {'傾き':>10} | {'進むべき方向':>14}")
print("-" * 50)
for w in [0.0, 1.0, 3.0, 5.0, 7.0]:
g = gradient_1d(w)
direction = "増やす" if g < 0 else ("減らす" if g > 0 else "そのまま")
print(f"{w:>8.1f} | {loss_1d(w):>10.2f} | {g:>10.2f} | {direction:>14}")
print("\n=== 勾配降下法で最小値を探す ===")
w = 0.0
lr = 0.1
print(f"初期値: w = {w}, 学習率 = {lr}")
print(f"\n{'ステップ':>8} | {'w':>10} | {'損失':>10} | {'勾配':>10}")
print("-" * 44)
for step in range(11):
g = gradient_1d(w)
if step % 2 == 0:
print(f"{step:>8} | {w:>10.4f} | {loss_1d(w):>10.4f} | {g:>10.4f}")
w = w - lr * g
print(f"\n最終的な w: {w:.6f} (正解は 3.0)")
print("\n=== 多変数の場合:勾配ベクトル ===")
def loss_2d(w):
return (w[0] - 2)**2 + 3 * (w[1] + 1)**2
def gradient_2d(w):
return np.array([2 * (w[0] - 2), 6 * (w[1] + 1)])
w = np.array([0.0, 0.0])
print("損失関数: L = (w1-2)^2 + 3(w2+1)^2")
print(f"初期値: {w}")
print(f"勾配ベクトル: {gradient_2d(w)}")
print(f"勾配の長さ: {np.linalg.norm(gradient_2d(w)):.4f}")
print("\n=== 勾配降下法(2変数)===")
w = np.array([0.0, 0.0])
lr = 0.1
print(f"{'ステップ':>8} | {'w':>20} | {'損失':>10} | {'勾配の長さ':>12}")
print("-" * 58)
for step in range(21):
g = gradient_2d(w)
if step % 4 == 0:
print(f"{step:>8} | {str(w.round(4)):>20} | {loss_2d(w):>10.4f} | {np.linalg.norm(g):>12.4f}")
w = w - lr * g
print(f"\n最終的な w: {w.round(6)} (正解は [2, -1])")
print("最小値に近づくほど、勾配の長さが0に近づいています。")
print("\n=== 学習率の影響 ===")
for lr in [0.01, 0.1, 0.3, 0.35]:
w = np.array([0.0, 0.0])
for _ in range(50):
w = w - lr * gradient_2d(w)
print(f"学習率 {lr:>5}: 50ステップ後の w = {w.round(4)}, 損失 = {loss_2d(w):.6f}")
print("\n学習率が大きすぎると、発散する場合があります。")
print("\n=== 勾配クリッピング ===")
np.random.seed(0)
gradients = [
np.random.randn(5) * 0.1,
np.random.randn(5) * 1.0,
np.random.randn(5) * 10.0
]
threshold = 1.0
print(f"閾値: {threshold}")
print(f"\n{'元の長さ':>12} | {'調整後の長さ':>14} | {'処理':>10}")
print("-" * 42)
for g in gradients:
norm = np.linalg.norm(g)
if norm > threshold:
g_clipped = g * (threshold / norm)
action = "縮小"
else:
g_clipped = g
action = "そのまま"
print(f"{norm:>12.4f} | {np.linalg.norm(g_clipped):>14.4f} | {action:>10}")
print("\n方向は変えず、長さだけを制限しています。")
print("\n=== 重み行列に対する勾配 ===")
W = np.random.randn(4, 3)
grad_W = np.random.randn(4, 3) * 0.1
print(f"重み行列の形: {W.shape}")
print(f"勾配の形: {grad_W.shape}")
print("形が完全に一致しています。")
lr = 0.01
W_new = W - lr * grad_W
print(f"\n更新前の1行目: {W[0].round(4)}")
print(f"更新後の1行目: {W_new[0].round(4)}")
print(f"変化量: {(W_new - W)[0].round(6)}")
print("\n=== メモリ量の見積もり ===")
n_params = 124_000_000
bytes_per_param = 4
param_mem = n_params * bytes_per_param / 1024**3
grad_mem = param_mem
adam_mem = param_mem * 2
print(f"パラメータ数: {n_params:,}")
print(f"パラメータ: {param_mem:.2f} GB")
print(f"勾配: {grad_mem:.2f} GB")
print(f"Adamの状態(2種類): {adam_mem:.2f} GB")
print(f"合計: {param_mem + grad_mem + adam_mem:.2f} GB")
print("\n推論だけならパラメータ分で足りますが、学習には数倍必要です。")
print("\n=== バッチサイズと勾配のばらつき ===")
np.random.seed(42)
true_grad = np.array([1.0, -0.5, 0.2, 0.8, -0.3])
print(f"{'バッチサイズ':>12} | {'真の値との差の大きさ':>22}")
print("-" * 38)
for bs in [1, 4, 16, 64, 256]:
errors = []
for _ in range(100):
grads = np.random.randn(bs, 5) * 0.5 + true_grad
mean_grad = grads.mean(axis=0)
errors.append(np.linalg.norm(mean_grad - true_grad))
print(f"{bs:>12} | {np.mean(errors):>22.6f}")
print("\nバッチサイズが大きいほど、真の勾配に近づきます。")
初学者がつまずきやすい点
つまずき1:勾配の符号を取り違える
勾配は、損失が増える方向を指します。
したがって、更新ではマイナスをつけます。
$latex \mathbf{w}{new} = \mathbf{w}{old} - \eta \nabla L$
プラスにすると、損失が増える方向に進んでしまいます。
つまずき2:勾配の形を誤解する
勾配は、パラメータと同じ形を持ちます。
重み行列が768行3072列なら、勾配も768行3072列です。
勾配は1個の数値と誤解する人がいますが、それは損失の値です。勾配は、パラメータの数だけ要素を持ちます。
つまずき3:山下りの比喩を過信する
2次元や3次元での直感は、高次元では通用しないことがあります。
くぼみに落ちて抜け出せなくなるという心配は、高次元ではあまり当てはまりません。
比喩は理解の助けになりますが、そのまま高次元に持ち込むと、誤った判断につながることがあります。
演習
演習1:傾きから方向を判断する
損失関数が のとき、次の各点で、
を増やすべきか減らすべきかを答えてください。
微分は です。
演習2:勾配ベクトルを求める
損失関数が次の形だとします。
のときの勾配ベクトルを求めてください。
偏微分は、 、
です。
また、学習率を0.1として、1回更新した後のパラメータを求めてください。
演習3:メモリ量を計算する
パラメータ数が70億個のモデルについて、次を計算してください。float32を前提とします。
パラメータのメモリ量
勾配のメモリ量
Adamの状態を含めた合計
演習4:勾配降下法を実装する
import numpy as np
def loss(w):
"""L(w) = (w1 - 1)^2 + 10*(w2 - 2)^2"""
return (w[0] - 1)**2 + 10 * (w[1] - 2)**2
def gradient(w):
return np.array([2 * (w[0] - 1), 20 * (w[1] - 2)])
print("=== 学習率による挙動の違い ===")
print("損失関数: L = (w1-1)^2 + 10(w2-2)^2")
print("最小値: w = [1, 2]\n")
for lr in [0.01, 0.05, 0.09, 0.11]:
w = np.array([0.0, 0.0])
history = []
for step in range(100):
w = w - lr * gradient(w)
history.append(loss(w))
if np.isnan(loss(w)) or loss(w) > 1e10:
break
status = "発散" if (np.isnan(history[-1]) or history[-1] > 1e10) else "収束"
print(f"学習率 {lr:>5}: {step+1:>3}ステップで{status}, "
f"最終的な w = {w.round(4)}, 損失 = {history[-1]:.6e}")
print("\n=== 勾配の長さの推移 ===")
w = np.array([0.0, 0.0])
lr = 0.05
print(f"{'ステップ':>8} | {'損失':>12} | {'勾配の長さ':>12}")
print("-" * 38)
for step in range(0, 101, 20):
if step > 0:
for _ in range(20):
w = w - lr * gradient(w)
g = gradient(w)
print(f"{step:>8} | {loss(w):>12.6f} | {np.linalg.norm(g):>12.6f}")
print("\n最小値に近づくほど、勾配の長さが0に近づきます。")
演習の解答例と解説
演習1の解答は、次のとおりです。
のとき、
です。マイナスなので、
を増やすべきです。
のとき、
です。最小値にいるため、動かす必要はありません。
のとき、
です。プラスなので、
を減らすべきです。
いずれの場合も、 に近づく方向が示されています。
演習2の解答は、次のとおりです。
勾配ベクトルを計算します。
勾配ベクトルは です。
更新後のパラメータを計算します。
なお、勾配ベクトルの長さは次のようになります。
第2章で扱った計算です。
演習3の解答は、次のとおりです。
パラメータのメモリ量を計算します。
約28ギガバイト、正確には約26.1ギビバイトです。
勾配も同じ大きさですから、約28ギガバイトです。
Adamの状態は、パラメータ1個につき2個の値を保持するため、約56ギガバイトです。
合計は、約112ギガバイトです。
一般的なGPUの搭載メモリは、24ギガバイトから80ギガバイト程度です。したがって、70億パラメータのモデルを通常の方法で学習させるには、複数のGPUが必要になります。
推論だけであれば28ギガバイトで済むため、1台のGPUでも動作します。
動かせるが学習はできないという状況が生じる理由が、この計算から分かります。
演習4では、学習率が大きすぎると発散することが確認できます。
この損失関数では、 の係数が10と大きいため、
方向の勾配が急です。学習率が0.1を超えると、
が振動して発散します。
また、最小値に近づくにつれて、勾配の長さが0に近づくことも確認できます。これは、平坦な場所に到達したことを意味します。
講師向けの補足
この章では、微分の計算そのものには深入りしないでください。
目的は、勾配がベクトルであるという点と、学習の計算も線形代数で記述されるという点を伝えることです。
微分の詳細に立ち入ると、線形代数の連載ではなく微分の連載になってしまいます。
効果的なのは、演習4のような実験です。学習率を変えたときの挙動の違いを、数値で見せてください。実務で学習率を調整する場面が必ずあるため、この経験は直接役立ちます。
また、メモリ量の計算は、必ず扱ってください。学習にはパラメータの数倍のメモリが必要という事実は、実務で必ず直面する制約です。
山下りの比喩については、便利である一方、高次元では通用しない面もある点を、必ず添えてください。
理解度を確認する問い
第14章の内容を、次の問いで確認してみてください。
損失関数の値は、なぜスカラーでなければならないのでしょうか。
勾配ベクトルの次元数は、何と一致するでしょうか。
パラメータの更新規則で、勾配にマイナスをつける理由を説明してください。
勾配クリッピングは、勾配ベクトルの何を変え、何を変えないのでしょうか。
学習時に必要なメモリが、パラメータだけの場合の数倍になるのは、なぜでしょうか。
まとめ
学習とは、損失関数を最小にするパラメータを探す作業です。損失は1個の数値、つまりスカラーであり、パラメータを変数とする関数として捉えられます。
1変数の場合、微分した傾きが、パラメータをどちらに動かすべきかを教えてくれます。傾きがプラスなら減らし、マイナスなら増やせば、損失が減ります。
多変数の場合、すべてのパラメータについての偏微分を並べたものが、勾配ベクトルです。次元数は、パラメータの数と一致します。勾配ベクトルの方向は損失が最も急に増える方向であり、その反対に進むことで、損失を減らせます。
更新規則は、次のように書けます。
$latex \mathbf{w}{new} = \mathbf{w}{old} - \eta \nabla L$
第5章で学んだ、ベクトルのスカラー倍と引き算です。
勾配ベクトルの長さは、損失の変化の急峻さを表します。長さが極端に小さくなる勾配消失、極端に大きくなる勾配爆発は、いずれも学習を妨げます。勾配クリッピングは、方向を保ったまま長さだけを制限する手法であり、これもスカラー倍です。
重み行列に対する勾配は、その行列と同じ形を持ちます。したがって、学習時には、パラメータと同じ量のメモリが勾配のために必要になり、最適化手法の状態も含めると、パラメータだけの場合の数倍のメモリが必要です。
複数のデータから得られた勾配は、平均して使われます。これも、ベクトルの足し算とスカラー倍です。
次の第15章では、この勾配がどのように計算されるのかを扱います。誤差逆伝播法という仕組みの中で、行列の転置が重要な役割を果たします。第10章で学んだ転置が、なぜ学習の計算に現れるのかを確認します。
第15章に進む前に、演習4のコードを実行し、学習率を変えたときの挙動の違いを確認しておいてください。発散する様子を実際に見ておくと、実務で学習率を調整する際の勘所がつかめます。
セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。
投稿者プロフィール

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