最近、李沐大神の動手学深度学习コースを見ています。ここで、いくつかの重要な知識点を記録し、知識があちこちに漂わないようにし、知識をまとめます。
1、PyTorch の backward はスカラーでなければならない#
import torch
x = torch.arange(4.0,requires_grad=True)
y = 2 * torch.dot(x,x)
y.backward()
# デフォルトでは、PyTorchは勾配を蓄積するため、以前の値をクリアする必要があります
# 非スカラーに対して 'backward' を呼び出すには、微分関数を指定する 'gradient' パラメータを渡す必要があります
x.grad.zero_() # 以前のxの勾配をクリア
y = x * x # ここでのyはスカラーではなく、ベクトルです
print(y)
# 等価な表現はy.backward(torch.ones(len(x)))
y.sum().backward() # y.sum()の後にベクトルがスカラーに変わります、スカラーに対して微分を求めます
x.grad
🔹 重要なポイント:PyTorch の backward はスカラーでなければならない
PyTorch では、.backward()
の設計は 連鎖律 に基づいており、主にスカラー(単一の出力値)に対する入力の勾配に焦点を当てています。
y.backward()
を呼び出す場合、ここでの y はスカラーでなければなりません、なぜなら:
-
は y の x に関する勾配(gradient) と呼ばれます。
-
は y の x に対する偏導数 です。
しかし、具体的に何であるかは、$y$ と $x$ の次元によります。
🔸 ケース 1:y はスカラー、x はベクトル
仮定:
したがって:
つまり:
長さ のベクトルで、y が各 x 成分に対する偏導数を表します。
これは機械学習で最も一般的なケースであり、例えば 損失が重みの勾配 に対してです。
🔸 ケース 2:y はベクトル、x はベクトル
仮定:
したがって:
この行列 は ヤコビ行列 と呼ばれ、その 要素は:
2、ヤコビ行列#
ヤコビ行列は:
ベクトル関数の入力ベクトルに対する偏導行列 です。
仮定:
-
入力:
-
出力:
ヤコビ行列:
直感的には:
-
第 行: がすべての に対する偏導。
-
説明:入力変数の小さな変動が各出力変数に与える線形影響です。
3、ヘッセ行列#
ヘッセ行列は:
スカラー関数の入力ベクトルに対する二次偏導行列 です。
仮定:
-
入力:
-
出力:
ヘッセ行列:
しかし、 が ベクトル の場合、勾配は実際には ヤコビ行列 であり、単一の勾配ベクトルではありません:
4、pytorch における dtype と type の違い#
まず結論を述べます:
dtype
はテンソル内の要素の具体的な数値型であり、type
はテンソルオブジェクト自体の Python 型(デバイス情報を含む)です。
それらは異なるレベルに焦点を当てています。
📍 1️⃣ dtype
とは?
-
指:テンソル内の各要素のストレージタイプ。
-
例:
-
torch.float32
→ 単精度浮動小数点数 -
torch.int64
→ 64 ビット整数 -
torch.bool
→ ブール型
-
コードを見てみましょう:
x = torch.tensor([1, 2, 3], dtype=torch.float32)
print(x.dtype) # 出力:torch.float32
これは「このテンソルの要素がどのフォーマットで保存されているか」を教えてくれます。
📍 2️⃣ type
とは?
-
指:PyTorch におけるテンソルオブジェクトのフルネームで、データ型とデバイスを含みます。
-
例:
-
torch.FloatTensor
→ float32 の CPU テンソル -
torch.cuda.FloatTensor
→ float32 の GPU テンソル -
torch.IntTensor
→ int32 の CPU テンソル
-
コードを見てみましょう:
x = torch.tensor([1, 2, 3])
print(x.type()) # 出力:torch.IntTensor(またはシステムのデフォルトタイプ)
これは「このテンソルオブジェクトが PyTorch システム内での完全なタイプ名」を教えてくれます。
📍 ⚠ 重要な違い
比較項目 | dtype | type |
---|---|---|
焦点 | 要素の数値型 | テンソルオブジェクトの完全な PyTorch タイプ(デバイス情報を含む) |
例 | torch.float32, torch.int64 | torch.FloatTensor, torch.cuda.FloatTensor |
主な用途 | 精度、ストレージ、計算関連 | 異なるテンソルカテゴリを区別する、デバッグ / チェック用 |
変更方法 | .to(dtype) 、.float() | .type() (全体のタイプオブジェクトを変更) |
5、ソフトラベル vs ハードラベル#
通常、分類モデルを訓練する際に使用するラベルは:
✅ ハードラベル(hard label)
例えば、3 分類タスクにおいて、真のラベル:
クラス A → [1, 0, 0]
クラス B → [0, 1, 0]
クラス C → [0, 0, 1]
このようなラベルは完全に ワンホットエンコーディング (one-hot) で、正誤のみを認識します。
ソフトラベル(soft label) は次のようになります:
各クラスに確率が対応し、ハードな 0 または 1 ではありません。
例えば:
ある画像に対して、ソフトラベル → [0.7, 0.2, 0.1]
これは次のことを示します:
-
クラス A である確率は 70%
-
クラス B である確率は 20%
-
クラス C である確率は 10%
言い換えれば、ラベルは「曖昧性を認める」ものであり、全か無かではありません。
6、ソフトマックス回帰とロジスティック回帰#
📦 共通点
✅ 本質的には分類モデルです
✅ 線形関数 + 活性化(シグモイドまたはソフトマックス)を使用します
✅ 損失関数として交差エントロピー(cross entropy)を使用します
しかし、異なるタスクに使用されます。
🌟 主な違い
プロジェクト | ロジスティック回帰 | ソフトマックス回帰 |
---|---|---|
タスクタイプ | 二分類(binary classification) | 多分類(multi-class classification) |
出力層活性化関数 | シグモイド(単一値出力 0~1) | ソフトマックス(ベクトル出力、各クラスの確率) |
出力次元 | 1 次元 | C 次元(C = クラス数) |
目標ラベル | 0 または 1 | ワンホットエンコーディング、例えば [0,0,1,0] |
決定方法 | 出力 > 0.5 を正クラスと判定 | 最大確率のクラスを予測結果とする |
📊 ロジスティック回帰の詳細
-
あなたが持っていると仮定します:
-
入力特徴
-
重み とバイアス
-
-
モデル計算:
ここで はシグモイド関数です。
出力 は確率値(0 から 1)で、正クラスの確率を表します。
損失関数:
📊 ソフトマックス回帰の詳細
-
あなたが持っていると仮定します:
-
入力特徴
-
重み行列 (形状: num_features × num_classes)
-
バイアス
-
-
モデル計算:
ここで
出力 は長さ C のベクトルで、各要素は対応するクラスの確率です。
損失関数:
🧠 なぜロジスティック回帰を多分類に直接使用できないのか?
シグモイドは単一の値しか出力しないため、多分類では複数のクラスの確率を出力する必要があり、これらの確率は次の条件を満たす必要があります:
合計が 1 で、互いに排他的です。
これがソフトマックスの設計目的です。
✅ まとめ一句
ロジスティック回帰 ≈ 2 クラスのソフトマックスの特例
ソフトマックス回帰 = 多分類のロジスティック回帰の拡張版
7、尤度と確率#
🌟 尤度関数とは何か?
簡単に言うと:
尤度関数(likelihood function)は、与えられたモデルパラメータの下で、観測されたデータの確率です。
あなたは次のように理解できます:
モデルがあるパラメータを仮定 → このパラメータの下で、私たちが現在持っているデータを生成する「可能性」がどれくらい高いか。
📊 確率との違いは?
多くの人が混同しやすいです:
✅ 確率:パラメータが既知で、イベントの可能性を計算します。
✅ 尤度:データが既知で、どのパラメータがこれらのデータを生成する可能性が高いかを見ます。
数学的な公式は似ていますが、用途は逆になります。
🏗️ 数学的表現
仮定:
-
データ:
-
パラメータ:
確率:
尤度関数:
違いは:
-
確率: が固定されていて、 を見ます。
-
尤度: が固定されていて、 を見ます。
🌟簡単な例を挙げると
あなたがコインを持っていて、10 回投げた結果、7 回表が出たとします。
私たちはコインの表が出る確率 を推定したいと考えます。
✅ モデル仮定
コインを投げる回数:10
表が出る確率:
イベント:7 回表
✅ 尤度関数
ここで:
-
は組み合わせ数(固定値で、最大化には影響しません)。
-
核心は:
が与えられたとき、このデータ(7 表 3 裏)を生成する確率です。
🌟 最大尤度推定(MLE)
通常、私たちは次のようにします:
尤度関数を最大にするパラメータを見つけます。
この例では:
-
を最大化します。
-
最適な
これが最大尤度推定です。
🔥 機械学習において
機械学習の多くの訓練は、実際には:
最大尤度を使用してパラメータをフィットさせることです。
例えば:
-
回帰モデル → ガウス分布の最大尤度
-
分類モデル → ソフトマックス下の最大尤度
-
ニューラルネットワーク → 交差エントロピー損失は、実際には最大尤度から導かれたものです。
✅ まとめ一句
尤度関数 = 与えられたパラメータの下で、観測された現在のデータの確率(それをパラメータの関数として見る)。
尤度を最大化することは、データを生成する可能性の高いパラメータを見つけることです。
8、パーセプトロン#
パーセプトロン(Perceptron) は非常に基本的な 二分類線形モデル であり、神経ネットワークの最初の形態(単層、隠れ層なし)と見なすことができます。
その主な特徴と要点は:
✅ 基本形式:
パーセプトロンは、重みベクトル w とバイアス b を用いて、入力特徴ベクトル x に対して線形結合を行い、符号関数(sign function)を通じて出力が +1 か -1 かを決定します。
公式:
f(x) = sign(w·x + b)
✅ 目標:
すべてのサンプルが超平面によって分けられるような w, b の組を見つけること(すなわち線形可分)。
✅ 訓練アルゴリズム:
-
重みとバイアスを初期化します(通常はゼロまたは小さな値)。
-
各誤分類サンプルに対して、重みを更新します:
w ← w + η * y * x
b ← b + η * y
ここで η は学習率、y は真のラベル(+1 または -1)。 -
すべてのサンプルが正しく分類されるまで(または最大反復回数に達するまで)繰り返します。
✅ 限界(欠点):
-
線形可分の問題しか処理できません。非線形データ(例えば XOR 問題)には全く無力です。
-
確率出力がなく、単なるハード分類です。
-
ノイズや外れ値の影響を受けやすいです。
✅ 意義と歴史的地位:
現在、深層学習はパーセプトロンを超えていますが、パーセプトロンは神経ネットワークの発展の出発点です。かつて Minsky と Papert が 1969 年に書いた『Perceptrons』という本では、XOR 問題を解決できないことが指摘され、これが AI の冬 を引き起こしました。多層ネットワーク(MLP)と逆伝播アルゴリズムが登場するまで、この限界は打破されませんでした。
9、多層パーセプトロン#
二分類問題の公式
二分類問題の公式と多分類問題の違いは、 の形状が であることです。(k はクラスの数)
各隠れ層には活性化関数が必要です(非線形関数)。活性化関数がなければ、大きな線形関数と同じです。
出力層には活性化関数は必要ありません。
多層パーセプトロンの多分類問題とソフトマックス回帰の違いは、隠れ層が追加されている点で、その他は同じです。
コード
import torch
from torch import nn
# モデルを構築し、隠れ層には256の隠れユニットを含み、ReLU活性化関数を使用
net = nn.Sequential(nn.Flatten(),nn.Linear(784,256),nn.ReLU(),nn.Linear(256,10))
コードの説明
nn.Sequential()
は、一連のサブモジュール(層、活性化関数など)を順番にスタックし、自動的に前方伝播を組織します。言い換えれば、これはあなたが望むネットワークモジュールを順番に包む順序付きコンテナです。
nn.Flatten()
は、入力の多次元テンソルを 1 次元ベクトルに展開します。
MNIST の場合、入力画像の形状は [batch_size, 1, 28, 28](グレースケール画像)で、Flatten 後は [batch_size, 784] になり、全結合層に接続しやすくなります。
10、活性化関数#
シグモイド関数#
✅ シグモイド活性化関数 は次のように定義されます:
✅ 出力範囲:
(0, 1) —— 任意の実数を 0 と 1 の間にマッピングします。
✅ 画像の特徴:
-
S 字型曲線(これがシグモイドと呼ばれる理由で、シグモイド = S 型)。
-
中心は (0, 0.5) に対称です。
-
が非常に大きいか非常に小さいとき、勾配は 0 に近づきます —— 勾配消失。
✅ 利点:
-
確率として解釈できます(特に二分類の最後の層に適しています)。
-
滑らかで、連続的で、微分可能です。
✅ 欠点(致命的な):
-
勾配消失: が非常に大きいか非常に小さいとき、導関数は 0 に近づき、逆伝播でほとんど重みを更新できません。
-
出力が 0 を中心にしていない:これにより、勾配更新時の収束が遅くなります。正の勾配と負の勾配が非対称だからです。
-
飽和しやすい:入力が大きすぎるか小さすぎると、すべてが平坦になります。
✅ 現在の主流のやり方:
-
二分類の最後の層を除いて、隠れ層ではほとんどシグモイドを使用せず、代わりに ReLU やその進化版を使用します。
-
二分類の場合、最後の層にはシグモイドを使用し、損失関数にはバイナリ交差エントロピーを使用します。
Tanh 関数#
✅ tanh(双曲線正接)活性化関数 は次のように定義されます:
✅ 出力範囲:
(-1, 1) —— 任意の実数を -1 から 1 の間に圧縮します。
✅ 画像の特徴:
-
S 字型曲線(シグモイドに似ていますが、上下対称です)。
-
中心は (0, 0) に対称です(これはシグモイドよりも良く、出力の平均が 0 に近く、最適化の収束を助けます)。
-
が非常に大きいか非常に小さいとき、勾配消失が発生します。
✅ 利点:
-
シグモイドと比較して、出力が 0 を中心にしているため、勾配更新に対してよりフレンドリーです。
-
滑らかで、連続的で、微分可能です。
✅ 欠点(核心的な問題):
- シグモイドと同様に、飽和しやすい → 勾配消失。
✅ 現在の主流のやり方:
-
対称出力が必要なモデル(例えば RNN)では、tanh は依然として有用です。
-
しかし、深層神経ネットワークの隠れ層においては、現代の主流は ReLU およびその改良版を使用しています。
ReLU 関数#
✅ ReLU(Rectified Linear Unit)活性化関数 は次のように定義されます:
✅ 出力範囲:
✅ 画像の特徴:
-
のとき、出力は 0 です。
-
のとき、出力は です。
シンプルで直接的な折れ線です。
✅ 利点:
-
計算が簡単で、収束が速い。シグモイド関数や tanh 関数と比較して、両者は指数計算を必要とし、指数演算は高価です。
-
勾配消失が発生しにくい(正の区間の勾配は常に 1 です)。
-
スパース活性化(多くのニューロンが 0 を出力し、モデルを簡素化するのに役立ちます)。
✅ 欠点:
-
死んだ ReLU 問題:もしあるニューロンが訓練中に負の区間に固定されてしまうと(出力が常に 0)、それは再び更新されなくなる可能性があります(負の区間の勾配は 0 です)。
-
入力値に対して非対称です(正の値のみを保持します)。
✅ 現代の改良版:
-
Leaky ReLU:負の区間に非常に小さな傾きを与えます。
-
Parametric ReLU (PReLU):負の区間の傾きを学習可能にします。
-
ELU、GELU、Swish:さらなる改良を加えます。
11、モデルの複雑さ#
12、正則化#
機械学習において、正則化手法はモデルの過学習を防ぎ、モデルの一般化能力を向上させるための技術の集合です。これは、モデルの学習過程にいくつかの「罰」や「制限」を導入することによって、モデルの複雑さを制約し、モデルが訓練データのすべての詳細(特にノイズ)を「完璧に」フィットさせることがないようにし、新しい、未見のデータにより適応できるようにします。
訓練中のみ使用し、予測時には必要ありません。
正則化とは何か?
一言で言えば:正則化は損失関数に「制約 / 罰」を追加することによって、モデルの自由度を積極的に制限し、シンプルで一般化が良い規則を学ばせることを強制します。訓練セットのノイズを単に暗記するのではなく。
数学的には次のように書かれます
-
:元の経験損失(交差エントロピー、MSE …)
-
:正則項(大きいほどモデルが「複雑」になります)
-
:正則強度; は正則なしに退化し、 はモデルを極端にシンプルにします
核心的な役割
目標 | 説明 |
---|---|
過学習の抑制 | 分散を小さくし、未見のサンプルに対するロバスト性を向上させます |
数値の安定性 | 重みの爆発や行列の特異性を避けます |
解釈性 | スパース化または構造化制約により、特徴 / サブネットワークがより読みやすくなります |
共線性の防止 | 高次元の共線性特徴の下でも一意の解を提供します |
L1 正則化(Lasso 回帰)#
✅** 1. L1 正則化とは?**
一言で定義すると:
L1 正則化は損失関数に すべての重みの絶対値の和 を罰則項として追加することです。
その目標は:
重要でない重みを 自動的に 0 にする ことで、モデルを「よりシンプル」にすることです。
✅ 2. 数学的形式(簡潔に見る)
通常の損失関数は:
L1 正則化を追加すると、次のようになります:
ここで:
-
は各重みパラメータ
-
は罰則強度を制御するハイパーパラメータ(大きいほど「厳しく」なります)
✅ 3. L1 の最大の特徴:一部の重みを 0 にする(スパース化)
これが L1 と L2 の重要な違いです:
したがって、モデルが重要な特徴を自動的に選択し、ゴミの特徴を捨てることを望む場合、L1 正則化は理想的な選択です。
✅ 4. 例を挙げると(想像)
あなたが住宅価格予測タスクを行っていて、100 の入力特徴があるが、実際には 5 つだけが有用であるとします。
L1 正則化を使用すると、訓練後にモデルはおそらくこの 5 つの特徴の重みだけを保持し、残りの 95 は直接 0 になります。
これはモデルが自動的に特徴選択を行ったことに相当します。
✅ まとめ一句
L1 正則化 = 重みの絶対値に罰則を与える → 一部のパラメータを 0 に促す → 重要な特徴を自動選択する。
L2 正則化(重み減衰)(Ridge 回帰)#
✅ 一言で定義
L2 正則化 は損失関数に モデルパラメータの平方の罰則項 を追加し、
重みが大きくなりすぎるのを抑制し、過学習を避けることです。
✅ 数学的形式
モデルの元の損失関数を次のように設定します:
L2 正則化を追加すると、次のようになります:
ここで:
-
:正則化強度(ハイパーパラメータ)は一般的に を取ります。
-
:すべての重みの平方和(これは L2 ノルムです)
✅ 実際の効果(直感)
L2 がない場合:
- モデルは特定の重みを狂ったように大きくする可能性があり、訓練データに非常によくフィットしますが、一般化が非常に悪くなります。
L2 正則化がある場合:
-
モデルは「より保守的」になり、パラメータを大きくすることを容易にしません。
-
モデルは入力の変動に対してより安定し(一般化能力が強化されます)。
✅ PyTorch での使用方法は?
L2 正則化 = 重み減衰 なので、PyTorch では最適化器に次のように追加するだけです:
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, weight_decay=1e-4)
この weight_decay
パラメータは実際には です!デフォルトでは L2 正則化です。
✅ sklearn での使用方法は?
from sklearn.linear_model import Ridge # Ridge は L2 を持つ線形回帰です
model = Ridge(alpha=1.0)
✅ まとめ一句
L2 正則化 = 重みの平方に罰則を与え、すべてのパラメータを圧縮するが保持し、モデルの一般化能力を向上させる。
ドロップアウト(Dropout)#
✅ 一言で定義
ドロップアウトは、神経元をランダムにマスクする正則化手法です。
これにより、神経ネットワークは訓練時により「頑健」になり、過学習を防ぎます。
✅ 背後の動機
深層神経ネットワークは過学習しやすく、特に次のような場合に:
-
層数が深い
-
訓練データが少ない
-
パラメータが多く、モデルが複雑である
理由は:
モデルが「特定の神経元の組み合わせに依存して」訓練データを記憶することを学ぶため → 一般化能力が低下します。
ドロップアウトはこの依存を打破します ——
訓練時に、一部の神経元をランダムにマスク(0 に設定)し、ネットワークが特定の神経元の小さなグループに依存できないように強制します。代わりに、冗長性を持つ必要があります。
✅ 操作方法は?
訓練時:
ある隠れ層の出力ベクトル に対して
訓練期間中に適用される:
— 保持確率 $p$ はあなたが設定します;通常は 0.5–0.9。
-
:ランダムマスクで、 番目のユニットを「保持」するかどうかを決定します。
-
:要素ごとの乗算です。
-
:inverted dropout で一般的に使用される再スケーリング因子で、 を保証し、訓練 / 推論時の活性化スケールを一致させます。
数学的な観点から見ると、これは 乗法的な二元ノイズ注入 であり、実際に神経元を物理的に削除するわけではありません。
ランダムマスク(random mask) = 0/1 の値で満たされたテンソルで、現在の前方 / 逆伝播のラウンドでどのユニットが「一時的にオフ」になり、どのユニットが「正常に動作する」かを決定します。
ドロップアウト(または他のノイズ正則化)では、これはネットワークの最小「スイッチマトリックス」に乗算的ノイズを注入するものです。
要素値
テスト時:
ドロップせず、すべてを活性化しますが、出力はスケーリングしません。
✅ 使用位置:
全結合層(Dense Layer / Linear Layer)とその後の活性化関数(ReLU、Sigmoid、Tanh など)の間または後に使用します。
- より一般的には、活性化関数の後に配置されます: Dense -> Activation -> Dropout
- これは、ドロップアウトの目的が神経元の出力をランダムに「オフ」にすることだからです。活性化関数の出力は神経元の最終的な出力信号を表し、その上でドロップアウトを行うことで、神経元のランダムな失活をより直接的にシミュレートします。
- 少数の場合、活性化関数の前に配置されることもあります: Dense -> Dropout -> Activation
これはあまり主流ではありませんが、時には効果を得ることができるという研究や実践もあります。その論理は、重みの計算結果にドロップアウトを適用し、その後活性化関数を通すことです。
通常は 1 つ以上の隠れ層(Hidden Layers)に適用されます。
- より深いネットワークの場合、複数の隠れ層の後にドロップアウトを使用することができます。
すべての隠れ層で使用するかどうか、またどの程度のドロップアウト率(dropout rate/probability)を使用するかは、通常、実験的に調整する必要があるハイパーパラメータです。
出力層(Output Layer)でのドロップアウトの使用は一般的に推奨されません。
- 出力層は最終的な予測結果を生成する役割を担っています。出力層でドロップアウトを使用すると、最終的な予測出力に干渉する可能性があり、特に分類タスクでは、特定のクラスの予測信号がランダムにドロップされる可能性があり、これは通常望ましくありません。
✅ PyTorch 実装:
import torch.nn as nn
net = nn.Sequential(
nn.Linear(256, 128),
nn.ReLU(),
nn.Dropout(p=0.5), # ドロップアウト層:訓練時に 50% の神経元をマスク
nn.Linear(128, 10)
)
✅ ドロップアウトの利点:
利点 | 説明 |
---|---|
過学習の抑制 | ネットワークが特定の特徴に依存できないように強制します |
一般化能力の向上 | 各訓練時に異なる「サブネットワーク」を訓練するようなものです |
簡単に使用できる | 一行のコードで追加できます |
⚠️ 注意事項:
-
ドロップアウトは訓練段階でのみ機能し、テスト段階ではドロップアウトを必ずオフにする必要があります。
-
モデルがすでに非常に小さいか、データ量が十分である場合、ドロップアウトは逆に性能を損なうことがあります(アンダーフィッティング)。
✅ まとめ一句:
ドロップアウトは「訓練時にランダムにドロップし、テスト時にはすべてをオンにする」戦略であり、モデルの頑健性を高め、過学習を防ぎます。
アーリーストッピング(Early Stopping)#
✅ 一言で定義
アーリーストッピングは、検証セットのパフォーマンスを監視し、モデルが過学習を始める前に訓練を停止する方法です。
🧠 なぜアーリーストッピングが必要なのか?
モデルを訓練していると、次のような現象がよく見られます:
エポック | 訓練セットの精度 | 検証セットの精度 |
---|---|---|
1 | 60% | 58% |
10 | 95% | 88% ✅ |
30 | 99% ✅ | 70% ❌ |
訓練セットはどんどん良くなりますが、検証セットはどんどん悪くなります。これは次のことを示しています:
モデルが「訓練データを暗記している」→ 過学習が始まった。
この時点で訓練を続けることは、時間の無駄であり、モデルを破壊する可能性すらあります。
✅** アーリーストッピングの核心的な考え方 **
非常にシンプルです:
検証セットのパフォーマンスを観察し、それが下降し始めたら、すぐに訓練を停止し、最良の効果を持つモデルを保持します。
こうすることで:
-
モデルは過学習しません
-
訓練速度が速くなります
-
一般的に複雑な正則項は必要ありません
👉 したがって、アーリーストッピングは訓練レベルの正則化手法であり、構造レベルのものではありません。
✅ まとめ一句
アーリーストッピングは最も素朴で非常に効果的な正則化戦略です:検証セットがもはや良くならないとき、すぐに停止します。
13、重みの初期化#
「信号の爆発や消失」を避けるために、私たちは望みます
ネットワーク内の各層の活性化(前方)と勾配(逆方)の平均 = 0、分散 = 定数。
具体的には、各層の出力 と勾配 を確率変数として扱います:
方向 | 平均 | 分散 |
---|---|---|
前方 | ||
逆方 |
ここで はあなたが定義する 2 つの定数(一般的には 1 または 2)で、すべての層 とすべてのチャネル に対して同じです。
1 層の「分散保存」条件を導出する(全結合層の例)
仮定:
一般的な仮定
-
-
-
と は独立で、要素間は近似的に独立です
したがって
出力の分散が に等しいことを要求する ⇒
逆方も同様(勾配は を介して伝播します):
両方を考慮して → 折衷案
これが Glorot/Xavier 初期化 です。ランダム初期化
Xavier 初期化(Glorot 初期化)#
一言で言えば:これはネットワークの重みの初期値を設定する戦略であり、前方の活性化 と 逆方の勾配 が各層で近い分散を保持することによって、深層ネットワーク内の勾配消失 / 爆発の問題を緩和します。提案者は Xavier Glorot と Yoshua Bengio(2010)です。
具体的な公式
サンプリング分布 | 推奨分散 | 実際のサンプリング範囲 / 標準偏差 |
---|---|---|
均一 U (−r, r) | ||
正規 𝒩(0, σ²) | 同上 |
-
n_in
: 本層の各ニューロンが受け取る入力次元 -
n_out
: 本層のニューロンの数
畳み込み層に対して:
He 初期化との違い
名称 | 推奨分散 | 適用される活性化 |
---|---|---|
Xavier/Glorot | シグモイド、tanh、soft-sign などの両側活性化 | |
He/Kaiming | ReLU、Leaky-ReLU、GELU(単側活性化はエネルギーの 1/2 を失うため、より大きな分散が必要です) |
Xavier Uniform 初期化 コード#
- 単一の線形層
import torch
import torch.nn as nn
# Linear 層を作成
linear = nn.Linear(20,256)
# linear 層を初期化
nn.init.xavier_uniform_(linear.weight)
# bias を初期化(通常は 0)
if linear.bias is not None:
nn.init.zero_(linear.bias)
- Sequential 初期化
import torch
import torch.nn as nn
# コンテナを作成
net = nn.Sequential(nn.Linear(20,256),
nn.ReLU(),
nn.Linear(256,256),
nn.ReLU(),
nn.Linear(256,1)
)
# 初期化関数を定義
def init_weight(m):
if isinstance(m,nn.Linear):
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
nn.init.zeros_(m.bias)
# 初期化を適用
model.apply(init_weight)
Xavier Normal 初期化 コード#
- 単一の線形層
import torch
import torch.nn as nn
# Linear 層を作成
linear = nn.Linear(20,256)
# linear 層を初期化
nn.init.xavier_normal_(linear.weight)
# bias を初期化(通常は 0)
if linear.bias is not None:
nn.init.zero_(linear.bias)
- Sequential 初期化
import torch
import torch.nn as nn
# コンテナを作成
net = nn.Sequential(nn.Linear(20,256),
nn.ReLU(),
nn.Linear(256,256),
nn.ReLU(),
nn.Linear(256,1)
)
# 初期化関数を定義
def init_weight(m):
if isinstance(m,nn.Linear):
nn.init.xavier_normal_(m.weight)
if m.bias is not None:
nn.init.zeros_(m.bias)
# 初期化を適用
model.apply(init_weight)
14、nn.module#
nn.module
を親クラスとして持つサブクラスを作成することで、計算プロセスを柔軟にカスタマイズできます。
nn.Parameter#
nn.Parameter は特別なテンソルであり、これを nn.Module
の属性として使用すると、自動的にモデルの訓練可能なパラメータとして登録され、model.parameters()
に現れ、訓練時にオプティマイザによって更新されます。
通常の torch.Tensor はデフォルトで requires_grad=False であり、手動で requires_grad=True に設定しても、自動的にパラメータとして登録されることはありません。nn.Parameter でラップする必要があります。
カスタムレイヤー#
自分のレイヤーを作成する
class CustomLinear(nn.Module):
def __init__(self, in_features, out_features):
super().__init__()
self.weight = nn.Parameter(torch.randn(out_features, in_features))
self.bias = nn.Parameter(torch.zeros(out_features))
def forward(self, x):
return x @ self.weight.T + self.bias
# @ は行列の乗算です
カスタムブロック#
class ResidualBlock(nn.Module):
def __init__(self, in_features):
super().__init__()
self.fc1 = nn.Linear(in_features, in_features)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(in_features, in_features)
def forward(self, x):
out = self.fc1(x)
out = self.relu(out)
out = self.fc2(out)
return x + out # 残差接続
パラメータ管理#
# まずは単一隠れ層の多層パーセプトロンに注目します
import torch
from torch import nn
net = nn.Sequential(nn.Linear(2,4),nn.ReLU(),nn.Linear(4,1))
X = torch.rand(size=(4,2))
net(X)
print(net) # ネットワーク構造を印刷
'''
Sequential(
(0): Linear(in_features=2, out_features=4, bias=True)
(1): ReLU()
(2): Linear(in_features=4, out_features=1, bias=True)
)
'''
print(net(X))
'''
tensor([[-0.1564],
[ 0.2563],
[ 0.2011],
[ 0.0006]], grad_fn=<AddmmBackward0>)
'''
print(net[2].state_dict()) # パラメータにアクセス、net[2] は最後の出力層です
'''
OrderedDict([('weight', tensor([[ 0.3754, -0.1346, -0.2410, -0.0513]])), ('bias', tensor([-0.1647]))])
'''
print(type(net[2].bias)) # 目標パラメータ
# <class 'torch.nn.parameter.Parameter'>
print(net[2].bias)
# Parameter containing:
# tensor([-0.1647], requires_grad=True)
print(net[2].bias.data)
# tensor([-0.1647])
print(net[2].weight.grad == None) # まだ逆計算を行っていないため、gradはNoneです
# True
print(*[(name, param) for name, param in net[0].named_parameters()]) # すべてのパラメータに一度にアクセス
'''
('weight', Parameter containing:
tensor([[-0.4437, 0.5371],
[ 0.5344, -0.1997],
[-0.3801, -0.6202],
[-0.3033, -0.4238]], requires_grad=True)) ('bias', Parameter containing:
tensor([0.7005, 0.0617, 0.1107, 0.6609], requires_grad=True))
'''
print(*[(name, param.shape) for name, param in net.named_parameters()]) # 0は最初の層の名前、1はReLUで、これはパラメータを持ちません
# ('0.weight', torch.Size([4, 2])) ('0.bias', torch.Size([4])) ('2.weight', torch.Size([1, 4])) ('2.bias', torch.Size([1]))
print(net.state_dict()['2.bias'].data) # 名前を使ってパラメータを取得
# tensor([-0.1647])
# 訓練データを保存
torch.save(net.state_dict(),'train_weight')
内蔵初期化#
net = nn.Sequential(nn.Linear(4,8),nn.ReLU(),nn.Linear(8,1))
def init_normal(m):
if type(m) == nn.Linear:
nn.init.normal_(m.weight, mean=0, std=0.01) # アンダースコアはm.weightの値を置き換えることを示します
nn.init.zeros_(m.bias)
net.apply(init_normal) # すべての層が初期化されるまで再帰的に呼び出されます
print(net[0].weight.data[0])
print(net[0].bias.data[0])
15、torch.deviece#
PyTorch はデフォルトで CPU 演算を行い、GPU を使用するには手動で選択する必要があります。
import torch
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# または
device = torch.cuda.device("cuda:0")
def evaluate_accuracy_gpu(net, data_iter, device=None):
"""GPUを使用してデータセット上のモデルの精度を計算します"""
if isinstance(net, torch.nn.Module):
# PyTorch モデルが渡されていることを確認します
net.eval()
# 1. 評価モードに設定し、ドロップアウトやバッチノーマルの訓練動作を無効にします
if not device:
device = next(iter(net.parameters())).device
# 2. モデルが存在するデバイスを自動的に検出します
# 手動でデバイスを渡さなかった場合、モデルパラメータから現在使用しているデバイス(例えば CPU または GPU)を自動的に取得します。
net.parameters () → モデル内のすべての訓練が必要なパラメータを返します
• iter(...)
→ パラメータイテレータを作成します
• next(...)
→ 最初のパラメータを取得します。Python の組み込み関数で、イテレータから次の要素を取得します。
• .device
→ このパラメータが存在するデバイスを取得します('cpu' または 'cuda:0' かもしれません)。
16、nn.utils#
PyTorch のユーティリティモジュールは、訓練、データの読み込み、モデルの可視化などの作業をサポートするための補助機能、ツール関数、クラスを含んでいます。
以下は torch.utils
の最も一般的なサブモジュールです:
torch.utils.data#
データの読み込みと処理に使用されます:
・Dataset:これを継承してカスタムデータセットを作成できます。
・DataLoader:データをバッチで読み込み、多スレッド、シャッフル、バッチなどの機能をサポートします。
・Subset:既存のデータセットからサブセットを選択します。
・ConcatDataset:複数のデータセットを接続します。
data.DataLoader(mnist_train,
batch_size,
shuffle=True,
num_workers=get_dataloader_workers()
)
data.DataLoader(mnist_test,
batch_size,
shuffle=False,
num_workers=get_dataloader_workers()
)