深層学習(day1)レポート
Section1 入力層~中間層
1. 要点
多層パーセプトロンとは、入力層と出力層の間に中間層(隠れ層)があるパーセプトロンのこと、ニューラルネットワークの意味ほぼ同じです。
全結合層とは、前の層のすべてのニューロンと、次の層のすべてのニューロンが重み付きで結合されている層のことです。
$w_i$は入力した特徴量と総入力$u$の間にかける重み(エッジ)です。重みは入力された特徴量がどれだけ重要かを調整するための係数(パタメータ)のことです。
$b$バイアスとは、線形変換 $WX$ に付加される学習可能な平行移動パラメータ。
2.実装演習
# 単層ニューラルネットワークの順伝播
import numpy as np
# 入力ノード数
input_num = 4
# 出力ノード数
output_num = 1
# 入力データ
X = np.random.rand(input_num)
# 重み
W = np.random.rand(input_num, output_num)
# バイアス
B = np.random.rand(output_num)
# 総入力
U = X.dot(W) + B
# 活性化関数(恒等写像)
def f(x):
return X
# 出力
Y = f(U)
3.確認テスト
- この数式をPythonで書け
$u = w_1x_1 + w_2x_2 + w_3x_3 + .. + w_nx_n + b$
u1 = np.dot(x, W1) + b1
- 1-1のファイルから中間層の出力を定義しているソースを抜き出せ
z = functions.relu(u)
Section2 活性化関数
1. 要点
活性化関数はニューラルネットワークにおいて、次の層への出力の大きさを決める非線形の関数。入力値の値によって、次の層への信号のON/OFFや強弱を定める働きをもつ。
活性化関数$h(a)$が無いと、単なる線形結合となり、線形回帰と実質的に同じ計算モデルとなる。
線形回帰でモデリングできるのであれば、ニューラルネットワークでモデリングする必要性がなくなる。
活性化関数$h(a)$があると、$x$と$y$の関係が非線形化され、ニューラルネットワークでモデリングする意義が出てくる。
\sigma(x) = \frac{1}{1 + e^{-x}}
\sigma_T(x) = \frac{1}{1 + e^{-x/T}}
Tが小さくなると、急激に0/1に近づく
Tが大きくなると、滑らかになる
$x >> 0$または$x << 0$出力が0または1に飽和、勾配消失問題が発生します、深いネットワークに不向き。
tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}
シグモイド関数と形は似ている、平均が0、勾配降下が少し安定
大きな|x|で飽和
シグモイド関数はろりマシだが、深層では問題あり
ReLU(x) = max(0, x)
正の領域で勾配は1
計算が非常に軽い
勾配消失が起きにくい
問題 Dead ReLU:x<0の時勾配=0、学習進めなくなり、学習速度が低下する可能性があります。
LeakyReLU(x)=max(ax,x)
負の領域でもわずか勾配あり
Dead ReLU問題を軽減
正確な定義
GELU(x) = x\Phi(x)
標準正規分布の累積分布関数(CDF)
\Phi(x) = \int_{-\infty}^{x}\frac{1}{\sqrt{2\pi}}e^{-t^2/2}dt
標準正規分布の確率密度関数(PDF)
\phi(x) = \frac{1}{\sqrt{2\pi}}e^{-t^2/2}
GELUの微分
\frac{d}{dx}[x\Phi(x)] = \Phi(x) + x\frac{d}{dx}\Phi(x)
$\Phi(x)$の微分
\frac{d}{dx}\Phi(x) = \phi(x)
近似式
GELU(x) \approx 0.5 x (1 + tanh(\sqrt{\frac{2}{\pi}}(x + 0.044715 x^3)))
近似式の微分
u = \sqrt{\frac{2}{\pi}}(x + 0.044715 x^3)
\frac{d}{dx} = 0.5(1 + tanh(u)) + 0.5x(1 - tanh^2(u))\frac{du}{dx}
\frac{du}{dx}=\sqrt{\frac{2}{\pi}}(1 + 3 \cdot 0.044715x^2)
ガウス関数に基づいて滑らかな非線形関数を提供、勾配消失しにくいという特性を持っています。ネットワークが大規模であるほど効率的に正則化行うことができる利点があります。
2. 実装演習
# 活性化関数の実装
import numpy as np
import matplotlib.pyplot as plt
total = 5
fig, axes = plt.subplots(total, 1, figsize=(6, 5 * total))
axes: list[plt.Axes] = axes
X = np.linspace(-10, 10 ,1000)
# シグモイド関数
def sigmoid(X):
return 1 / (1 + np.exp(-X))
Y = sigmoid(X)
index = 0
axes[index].plot(X, Y, label="$f(x) = \\frac{1}{1 + e^{-x}}$")
axes[index].set_xlim(-10, 10)
axes[index].set_ylim(-0.2, 1.2)
axes[index].grid(True)
axes[index].set_xlabel("$x$")
axes[index].set_ylabel("$y$")
axes[index].set_title("Sigmoid")
axes[index].legend()
# Tanh関数
def Tanh(X):
return (np.exp(X) - np.exp(-X)) / (np.exp(X) + np.exp(-X))
Y = Tanh(X)
index += 1
axes[index].plot(X, Y, label="$f(x) = Tanh(x)$")
axes[index].set_xlim(-10, 10)
axes[index].set_ylim(-1.2, 1.2)
axes[index].grid(True)
axes[index].set_xlabel("$x$")
axes[index].set_ylabel("$y$")
axes[index].set_title("Tanh")
axes[index].legend()
# ReLU関数
def ReLU(X):
Y = np.array([x if x > 0 else 0 for x in X])
return Y
Y= ReLU(X)
index += 1
axes[index].plot(X, Y, label="$f(x) = ReLU(x)$")
axes[index].set_xlim(-10, 10)
axes[index].set_ylim(-1, 10)
axes[index].grid(True)
axes[index].set_xlabel("$x$")
axes[index].set_ylabel("$y$")
axes[index].set_title("ReLU")
axes[index].legend()
# LeakyReLU関数
def LeakyReLU(X, nagtiveSlope = 0.01):
Y = np.array([x if x > 0 else nagtiveSlope * x for x in X])
return Y
Y = LeakyReLU(X)
index += 1
axes[index].plot(X, Y, label="$f(x) = LeakyReLU(x)$")
axes[index].set_xlim(-10, 10)
axes[index].set_ylim(-0.3, 10)
axes[index].grid(True)
axes[index].set_xlabel("$x$")
axes[index].set_ylabel("$y$")
axes[index].set_title("LeakyReLU")
axes[index].legend()
# GELU関数
def GELU(X):
return X * 0.5 * (1 + np.tanh(np.sqrt(2/np.pi) * (X + 0.0044715 * np.power(X, 3))))
Y = GELU(X)
index += 1
axes[index].plot(X, Y, label="$f(x) = GELU(x)$")
axes[index].set_xlim(-10, 10)
axes[index].set_ylim(-0.3, 10)
axes[index].grid(True)
axes[index].set_xlabel("$x$")
axes[index].set_ylabel("$y$")
axes[index].set_title("GELU")
axes[index].legend()
import torch.autograd as autograd
import torch
import math
class Sigmoid(autograd.Function):
@staticmethod
def forward(ctx: autograd.function.FunctionCtx, x: torch.Tensor):
out = 1 / (1 + torch.exp(-x))
ctx.save_for_backward(out)
return out
@staticmethod
def backward(ctx: autograd.function.FunctionCtx, grad_output: torch.Tensor):
out: torch.Tensor = ctx.saved_tensors[0]
return grad_output * out * (1 - out)
if __name__ == "__main__":
x = torch.tensor([-1, 0, 2], dtype=torch.float32, requires_grad=True)
y = Sigmoid.apply(x)
loss = y.sum()
loss.backward()
print(y)
print(x.grad)
import torch.autograd as autograd
import torch
import math
class Tanh(autograd.Function):
@staticmethod
def forward(ctx: autograd.function.FunctionCtx, x: torch.Tensor):
out = out = (torch.exp(x) - torch.exp(-x)) / (torch.exp(x) + torch.exp(-x))
ctx.save_for_backward(out)
return out
@staticmethod
def backward(ctx: autograd.function.FunctionCtx, grad_output: torch.Tensor):
out = ctx.saved_tensors[0]
return grad_output * (1 - out**2)
if __name__ == "__main__":
x = torch.tensor([-1, 0, 2], dtype=torch.float32, requires_grad=True)
y = Tanh.apply(x)
loss = y.sum()
loss.backward()
print(y)
print(x.grad)
import torch.autograd as autograd
import torch
import math
class ReLU(autograd.Function):
@staticmethod
def forward(ctx: autograd.function.FunctionCtx, x: torch.Tensor):
ctx.save_for_backward(x)
return x.clamp(min=0)
@staticmethod
def backward(ctx: autograd.function.FunctionCtx, grad_output: torch.Tensor):
x: torch.Tensor = ctx.saved_tensors[0]
grad_output = grad_output.clone()
grad_output[x <= 0] = 0
return grad_output
if __name__ == "__main__":
x = torch.tensor([-1, 0, 2], dtype=torch.float32, requires_grad=True)
y = ReLU.apply(x)
loss = y.sum()
loss.backward()
print(y)
print(x.grad)
import torch.autograd as autograd
import torch
import math
class LeakyReLU(autograd.Function):
@staticmethod
def forward(
ctx: autograd.function.FunctionCtx,
x: torch.Tensor,
negative_slope: float = 0.01,
):
ctx.save_for_backward(x)
ctx.negative_slope = negative_slope
return torch.where(x > 0, x, x * negative_slope)
@staticmethod
def backward(ctx: autograd.function.FunctionCtx, grad_output: torch.Tensor):
x = ctx.saved_tensors[0]
negative_slope = ctx.negative_slope
grad_output = grad_output.clone()
grad_output[x <= 0] *= negative_slope
return grad_output
if __name__ == "__main__":
x = torch.tensor([-1, 0, 2], dtype=torch.float32, requires_grad=True)
y = LeakyReLU.apply(x)
loss = y.sum()
loss.backward()
print(y)
print(x.grad)
import torch.autograd as autograd
import torch
import math
class GeLU(autograd.Function):
@staticmethod
def forward(ctx: autograd.function.FunctionCtx, x: torch.Tensor):
ctx.save_for_backward(x)
u = math.sqrt(2 / math.pi) * (x + 0.044715 * x**3)
out = 0.5 * x * (1 + torch.tanh(u))
return out
@staticmethod
def backward(ctx: autograd.function.FunctionCtx, grad_output: torch.Tensor):
x = ctx.saved_tensors[0]
u = math.sqrt(2 / math.pi) * (x + 0.044715 * x**3)
du_dx = math.sqrt(2 / math.pi) * (1 + 3 * 0.044715 * x**2)
tanh_u = torch.tanh(u)
d_dx = 0.5 * (1 + tanh_u) + 0.5 * x * (1 - tanh_u**2) * du_dx
grad_output = grad_output * d_dx
return grad_output
if __name__ == "__main__":
x = torch.tensor([-1, 0, 2], dtype=torch.float32, requires_grad=True)
y = GeLU.apply(x)
loss = y.sum()
loss.backward()
print(y)
print(x.grad)
3.確認テスト
z1 = functions.sigmoid(u)
Section3 出力層と損失関数
1. 要点
出力層は、特徴量はニューラルネットワークの通じて最終的に数値を出力する層です。
誤差関数はニューラルネットワークの出力値と真の値を誤差を算出する関数です、算出する誤差によってニューラルネットワークのパタメータを更新する。
ニューラルネットワークの各ニューロンに入力された値を、非線形な出力値へ変換する関数。
回帰問題では恒等関数を用いる。
2クラス分類では、シグモイド関数を用いる。
多クラス分類では、ソフトマックス関数を用いる。
損失関数は、ニューラルネットワークの「悪さ」を表す指標、現在の重みが教師データ対してどれくらい適合していないか表す。
回帰問題であれば、2乗和誤差がよく用いられる。
分類問題であれば、クロスエントロピー誤差がよく用いられる。
ワンホットベクトルとは、ある一つの要素だけが1であり、その他の要素が0であるベクトルのこと
y_k = \frac{exp(a_k)}{\sum_{i=1}^K(exp(a_i))}
MAE = \frac{1}{N}\sum_{i=1}^{N} |t_i - \hat y_i|
MSE=\frac{1}{N}\sum_n^N \ \bigl(-\sum_k^K{t_{nk} \log y_{nk}} \bigr) = - \frac{1}{N}\sum_n^N \sum_k^K{t_{nk} \log y_{nk}}
$N$:データ数
$n$:データ番号
$K$:出力層のノード数
$k$:出力層のノード番号
$y_{nk}$:データ$n$のノード$k$の出力値(通常は、0と1の間を取る値)
$t_{nk}$:データ$n$のノード$k$の正解値(通常は、0or1. つまり$t$はonehotベクトル)
説明、出力層のノード数は三つ、誤差はこの三つの誤差の総和の平均値
L = -[t_ilog(\hat y_i) + (1- t_i)log(1-\hat y_i)]
シグモイド関数付きバイナリエントロピー誤差の微分の証明
\frac{dL}{d\hat y} = -\frac{t_i}{\hat y_i} +\frac{1 - t_i}{1 - \hat y_i}
\frac{d\hat y}{dz} = (1 - \sigma(z)) \cdot \sigma(z) = (1 -\hat y_i)\hat y_i
\begin{align}
\frac{dL}{dz} & = \frac{dL}{d\hat y} \cdot \frac{d\hat y}{dz}
\\ &= (-\frac{t_i}{\hat y_i} +\frac{1 - t_i}{1 -\hat y_i}) \cdot (1 -\hat y_i)\hat y_i
\\ &= (\hat y_i - 1)t_i + (1 - t_i)\hat y_i
\\ &= \hat y_i - t_i
\end{align}
L = -{t_{i} \log y_{i}}
$L$:損失関数
$N$:データ数
$n$:データ番号
$K$:出力層のノード数
$k$:出力層のノード番号
$y_{nk}$:データ$n$のノード$k$の出力値(通常は、0と1の間を取る値)
$t_{nk}$:データ$n$のノード$k$の正解値(通常は、0or1. つまり$t$はonehotベクトル)
2. 実装演習
import numpy as np
# 活性化関数
def relu(X):
return np.maximum(X, 0)
def sigmoid(X):
return 1 / (1 + np.exp(-X))
# 誤差関数
def softmax(X):
X = X - np.max(X, axis=1, keepdims=True)
expX = np.exp(X)
return expX / np.sum(expX, axis=1, keepdims=True)
# 仮のニューラルネットワークの構造
nodes = [4, 16, 8, 2]
# 仮のデータ
batch_size = 3
X = (np.random.rand(batch_size, nodes[0]) * 100).astype(int) / 10
# ニューラルネットワークの層の定義
class Dense:
def __init__(self, input_num, output_num, activation_function, differential_function):
self.W = np.random.randn(input_num, output_num)
self.B = np.random.randn(output_num)
self.activation_function = activation_function
self.differential_function = differential_function
def forward(self, X):
return self.activation_function(X.dot(self.W) + self.B)
def __call__(self, X):
return self.forward(X)
# ニューラルネットワークの定義
class NN:
def __init__(self, nodes, activation_function, differential_activation_function, loss_function, differential_loss_function):
self.nodes = nodes
self.denses = []
# 中間層
for i in range(len(nodes) - 2):
self.denses.append(Dense(nodes[i], nodes[i + 1], activation_function, differential_activation_function))
# 最終層(loss_function)
self.denses.append(Dense(nodes[-2], nodes[-1], loss_function, differential_loss_function))
def forward(self, X):
if X.shape[1] != self.nodes[0]:
print("入力データの説明変数の数とニューラルネットワークの入力数が一致しません")
return
for dense in self.denses:
X = dense(X)
return X
def __call__(self, X):
return self.forward(X)
# 逆伝播また触れていないので割愛します
model = NN(nodes, relu, None, softmax, None)
# 多層ニューラルネットワークの順伝播
result = model(X)
print(result)
def one_hot(labels: torch.Tensor, num_classes: int):
out = torch.zeros(labels.shape[0], num_classes, device=labels.device)
out = out.scatter(1, labels.unsqueeze(1), 1)
return out
import torch.autograd as autograd
import torch.nn.functional as F
import torch
import math
class MSE(autograd.Function):
@staticmethod
def forward(ctx, pred: torch.Tensor, target: torch.Tensor):
diff = pred - target
ctx.save_for_backward(diff)
return torch.mean(diff**2)
@staticmethod
def backward(ctx, grad_output: torch.Tensor):
(diff,) = ctx.saved_tensors
N = diff.numel()
grad_pred = grad_output * (2.0 / N) * diff
grad_target = None # target は定数なので勾配不要
return grad_pred, grad_target
if __name__ == "__main__":
y_hat = torch.tensor(
[[0.3, 0.7, 0.1], [0.4, 0.5, 0.2]], dtype=torch.float32, requires_grad=True
)
t = torch.tensor([[1, 0, 0], [0, 0, 1]], dtype=torch.float32)
loss = F.mse_loss(y_hat, t)
loss2 = MSE.apply(y_hat, t)
print(loss)
loss.backward()
print(y_hat.grad)
y_hat.grad = None
print(loss2)
loss2.backward()
print(y_hat.grad)
import torch.autograd as autograd
import torch.nn.functional as F
import torch
import math
def softmax(pred, dim=0):
pred = pred - torch.max(pred, dim=dim, keepdim=True).values
exp = torch.exp(pred)
return exp / torch.sum(exp, dim=dim, keepdim=True)
class CrossEntropyWithSoftmax(autograd.Function):
@staticmethod
def forward(
ctx: autograd.function.FunctionCtx, pred: torch.Tensor, target: torch.Tensor
):
probs = softmax(pred, dim=1)
ctx.save_for_backward(probs, target)
out = -(torch.log(probs) * target).sum(dim=1).mean()
return out
@staticmethod
def backward(ctx: autograd.function.FunctionCtx, grad_output: torch.Tensor):
(probs, target) = ctx.saved_tensors
N = probs.shape[0]
grad_pred = grad_output * (probs - target) / N
grad_target = None
return grad_pred, grad_target
if __name__ == "__main__":
z = torch.tensor(
[[0.3, 0.7, 0.1], [0.4, 0.5, 0.2]], dtype=torch.float32, requires_grad=True
)
target = one_hot(torch.tensor([1, 2], dtype=torch.int32), 3)
loss = F.cross_entropy(z, target)
loss.backward()
print(loss)
print(z.grad)
z.grad = None
loss2 = CrossEntropyWithSoftmax.apply(z, target)
loss2.backward()
print(loss2)
print(z.grad)
import torch.autograd as autograd
import torch.nn.functional as F
import torch
import math
# sigmaつけバイナリエントロピー誤差関数
class BinaryEntropyWithSigma(autograd.Function):
@staticmethod
def forward(
ctx: autograd.function.FunctionCtx, pred: torch.Tensor, target: torch.Tensor
):
probs = Sigmoid.apply(pred)
ctx.save_for_backward(probs, target)
out = -(
(target * torch.log(probs) + (1 - target) * torch.log(1 - probs)).mean()
)
return out
@staticmethod
def backward(ctx: autograd.function.FunctionCtx, grad_output: torch.Tensor):
(probs, target) = ctx.saved_tensors
N = probs.numel()
grad_pred = grad_output * (probs - target) / N
grad_target = None
return grad_pred, grad_target
if __name__ == "__main__":
z = torch.tensor([[0.3, 0.7], [0.4, 0.5]], dtype=torch.float32, requires_grad=True)
target = one_hot(torch.tensor([0, 1], dtype=torch.int32), 2)
loss = F.binary_cross_entropy_with_logits(z, target)
loss.backward()
print(loss)
print(z.grad)
z.grad = None
loss2 = BinaryEntropyWithSigma.apply(z, target)
loss2.backward()
print(loss2)
print(z.grad)
3. 確認テスト
- なぜ、引き算でなく二乗するが述べよ
引き算を行うと、各値の計算誤差には正負が生じるため、総和を計算するとゼロになる可能性があります。
- 下式の1/2はどういう意味を持つか述べよ
1/2かけると微分するが簡単になります。
def softmax(x):
if x.ndim == 2:
x = x.T
x = x - np.max(x, axis=0)
y = np.exp(x) / np.sum(np.exp(x), axis=0)
return y.T
x = x - np.max(x) # オーバーフロー対策
return np.exp(x) / np.sum(np.exp(x))
$X$ の次元が 2 次元かどうかを確認します。
2 次元の場合は、計算を行いやすくするために $X$ を転置します。
オーバーフローを防ぐため、各列の最大値を引きます。
最後に、各要素の指数関数を計算し、その列の指数値の合計で割ることで正規化します。
def cross_entropy_error(d, y):
if y.ndim == 1:
d = d.reshape(1, d.size)
y = y.reshape(1, y.size)
# 教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
if d.size == y.size:
d = d.argmax(axis=1)
batch_size = y.shape[0]
return -np.sum(np.log(y[np.arange(batch_size), d] + 1e-7)) / batch_size
$d$は教師データ、$y$はニューラルネットワークの出力値です。
$y$が1次元の場合は、計算を行いやすくするために 2次元に変換します(外側に次元を追加)。
その後、教師データが one-hot ベクトルの場合は、正解ラベルのインデックスに変換します。
最後に、各データのエントロピー ($−𝑑_𝑖\log(𝑦_𝑖)$)を計算し、その平均値を求めます。
Section4 勾配降下法
1. 要点
勾配降下法はニューラルネットワークの誤差は最小化のために、微分を使ってパラメーターを最適化のことです。
$\mathbf{w}^{(t+ 1)} = \mathbf{w}^{(t)} - \varepsilon \nabla E$
$\nabla E = \frac{\partial \mathbf{E}}{\partial \mathbf{W}} = \left [ \frac{\partial E}{\partial w_1} \dots \frac{\partial E}{\partial w_M} \right ]$

無作為に選べたデータに対して行う学習方法
ミニバッチ勾配降下法とは、学習データを小さなバッチに分けて、その小さなバッチ毎に重みを更新していく学習方法。一つのバッチに含まれるデータ数をバッチサイズという。
2.実装演習
import torch
from torch import Tensor
class SGD:
def __init__(self, params, lr: float = 0.01):
self.params = list(params) # ここはlistしないと更新できない
self.lr = lr
def zero_grad(self):
for param in self.params:
if param.grad is not None:
param.grad.zero_()
def step(self):
with torch.no_grad():
for param in self.params:
if param.grad is not None:
param -= self.lr * param.grad
3.確認テスト
# 誤差逆伝播
def backward(x, d, z1, y):
# print("\n##### 誤差逆伝播開始 #####")
grad = {}
W1, W2 = network['W1'], network['W2']
b1, b2 = network['b1'], network['b2']
# 出力層でのデルタ
delta2 = functions.d_mean_squared_error(d, y)
# b2の勾配
grad['b2'] = np.sum(delta2, axis=0)
# W2の勾配
grad['W2'] = np.dot(z1.T, delta2)
# 中間層でのデルタ
#delta1 = np.dot(delta2, W2.T) * functions.d_relu(z1)
## 試してみよう
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
delta1 = delta1[np.newaxis, :]
# b1の勾配
grad['b1'] = np.sum(delta1, axis=0)
x = x[np.newaxis, :]
# W1の勾配
grad['W1'] = np.dot(x.T, delta1)
# print_vec("偏微分_重み1", grad["W1"])
# print_vec("偏微分_重み2", grad["W2"])
# print_vec("偏微分_バイアス1", grad["b1"])
# print_vec("偏微分_バイアス2", grad["b2"])
return grad
for key in ('W1', 'W2', 'b1', 'b2'):
network[key] -= learning_rate * grad[key]
オンライン学習は全データを一度に使わず、新しいデータが来る度にモデルを更新する学習方法である。
Section5 誤差逆伝播法
1.要点
ニューラルネットワークにおいて、各層ごとに次の層から伝播してくる誤差(勾配)を用いて、当該層のパラメータの勾配を求める手法。再帰的に計算でき、重複した計算を避けられる。
今の層の出力値(次の層の入力値)の誤差を求める重要です。
微分法において連鎖律とは、複数の関数が合成関数を微分する時、その導関数がそれぞれの導関数の積で与えられるという原理のこと。
\begin{align}
& u = g(x)\\
& \frac{d}{dx} f(g(x)) = \frac{df}{du} \cdot \frac{du}{dx}
\end{align}
計算グラフを使って「連鎖律」を機械的に適用し、勾配を正確に計算する仕組みです。
基本運算の計算グラフ集合
| 演算 | forward | backward |
|---|---|---|
| add | y = a + b | ∂a = 1, ∂b = 1 |
| sub | y = a - b | ∂a = 1, ∂b = -1 |
| mul | y = a * b | ∂a = b, ∂b = a |
| div | y = a / b | ∂a = 1/b, ∂b = -a/b² |
| neg | y = -a | ∂a = -1 |
| pow | y = a^n | ∂a = n·a^(n-1) |
| exp | y = exp(x) | ∂x = y |
| log | y = log(x) | ∂x = 1/x |
| sqrt | y = √x | ∂x = 1/(2√x) |
| abs | y = |x| | ∂x = sign(x), x≠0 |
| sum | y = Σx | ∂x = 1 |
| mean | y = Σx / N | ∂x = 1/N |
| max | y = max(x) | argmax のみ 1 |
| min | y = min(x) | argmin のみ 1 |
| matmul | y = A·B | ∂A = grad·Bᵀ, ∂B = Aᵀ·grad |
| where | y = cond?a:b | 選ばれた側のみ |
| reshape | shape変更 | reshapeで戻す |
| transpose | 転置 | transpose |
| permute | 次元並替 | 逆permute |
| broadcast | 自動拡張 | sumで畳む |
どんな関数も基本運算に分解できる
chain rule で backward できる
autograd 的には問題ない
でも基本演算だけ微分すると、計算が複雑の可能性がありますので(計算のコスト大きくなる)、計算のスピードを上がるため、そして数値安定性ために(exp → overflow log → -inf) 1のforward、backwardに求めることもあります
2.実装演習
Linear、ReLU、CrossEntropyLoss、SGDは、前の演習で自分が実装したクラスを使用している
import torch
from torch import Tensor
# 乱数を固定する(別にしなくても大丈夫です)
def set_seed(seed: int):
torch.manual_seed(seed)
set_seed(42)
# データをダウンロードし、 ToTensorで[0,1]の範囲に正規化してdataディレクトリに保存する
from torchvision import datasets, transforms
data_transforms = transforms.Compose([
transforms.ToTensor()
])
train_dataset = datasets.MNIST(
root="./data",
train=True,
transform=data_transforms,
download=True
)
test_dataset = datasets.MNIST(
root="./data",
train=False,
transform=data_transforms,
download=True
)
# DataLoaderを使ってダウンロードしたデータをメモリに読み込ませる
from torch.utils.data import DataLoader
train_loader = DataLoader(
train_dataset,
batch_size=64,
shuffle=False
)
test_loader = DataLoader(
test_dataset,
batch_size=64,
shuffle=False
)
# データを可視化する
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
imgs: Tensor
labels: Tensor
imgs, labels = next(iter(train_loader))
figure = plt.figure(figsize=(10, 12))
axes: list[Axes] = figure.subplots(8, 8).flatten()
for i in range(len(axes)):
ax: Axes = axes[i]
ax.imshow(imgs[i].squeeze(), cmap="gray")
ax.set_title(f"label={labels[i].item()}")
ax.axis("off")
plt.tight_layout()
plt.show()
# 自分で書いたModelパーツを使って普通の全結合ニューラルネットワークを組み込んでみよう
from etorch import nn
from torch.nn import Module
from torch import Tensor
class MnistNet(Module):
def __init__(self):
super().__init__()
self.fc1 = nn.Linear(784, 128)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(128, 10)
def forward(self, x: Tensor):
x = x.view(x.size(0), -1)
x = self.fc1(x)
x = self.relu(x)
x = self.fc2(x)
return x
def __call__(self, x: Tensor):
return self.forward(x)
# MnistNetとクロスエントロピー誤差をインスタンスする
import etorch.optim as optim
model = MnistNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SimpleSDG(model.parameters(), lr=0.01)
print(model)
# モデルを訓練そして訓練の誤差と正解率を出力する
import etorch.nn.functional as F
def train(model: MnistNet, train_loader: DataLoader, cross_entropy_loss: nn.CrossEntropyLoss, optimizer: optim.SimpleSDG, epochs: int):
model.train() # モデルを訓練モードに設定する
for epoch in range(epochs):
total_loss = 0
correct = 0
total = 0
for imgs, labels in train_loader:
optimizer.zero_grad()
outputs = model(imgs)
one_hots_labels = F.one_hot(labels, 10)
loss = cross_entropy_loss(outputs, one_hots_labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
avg_loss = total_loss / len(train_loader)
accuracy = 100 * correct / total
print(f"Epoch [{epoch+1}/{epochs}], Loss: {avg_loss:.4f}, TrainAccuracy: {accuracy:.2f}%")
train(model, train_loader, criterion, optimizer, 10)
# テストデータを使ってモデルを評価する
def evaluate(model: Module, test_loader: DataLoader):
model.eval() # 評価モードに設定する
correct = 0
total = 0
with torch.no_grad():
for imgs, labels in test_loader:
outputs = model(imgs)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f"TestAccuracy: {accuracy:.2f}%")
return accuracy
evaluate(model, test_loader)
# 予測結果を可視化
model.eval()
imgs, labels = next(iter(test_loader))
with torch.no_grad():
outputs = model(imgs)
_, predicted = torch.max(outputs, 1)
figure = plt.figure(figsize=(12, 14))
axes: list[Axes] = figure.subplots(8, 8).flatten()
for i, ax in enumerate(axes):
ax.imshow(imgs[i].squeeze(), cmap="gray")
true_label = labels[i].item()
pred_label = predicted[i].item()
# 正解なら緑、不正解なら赤
color = 'green' if true_label == pred_label else 'red'
ax.set_title(f"True: {true_label}, Pred: {pred_label}", color=color)
ax.axis("off")
plt.tight_layout()
plt.show()
3.確認テスト
delta2 = functions.d_mean_squared_error(d, y)
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
delta2 = functions.d_mean_squared_error(d, y)
grad['W2'] = np.dot(z1.T, delta2)
【参考文献】
<< 書籍 >>
1.機械学習(1), Tatsuro Fukuda (日本電子専門学校 2025)
2.ディープラーニングE資格精選問題集、小林範久、小林寛幸
3.Skillupディープラーニング基礎講座



























