0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

StudyAI E資格講座レポート(深層学習day1)

0
Last updated at Posted at 2025-12-21

深層学習(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$の関係が非線形化され、ニューラルネットワークでモデリングする意義が出てくる。

  • 活性化関数の種類

image.png

  • シグモイド関数 温度パラメータつけシグモイド関数

\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関数

tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}

シグモイド関数と形は似ている、平均が0、勾配降下が少し安定
大きな|x|で飽和
シグモイド関数はろりマシだが、深層では問題あり

  • ReLU関数

ReLU(x) = max(0, x)

正の領域で勾配は1
計算が非常に軽い
勾配消失が起きにくい

問題 Dead ReLU:x<0の時勾配=0、学習進めなくなり、学習速度が低下する可能性があります。

  • LeakyReLU関数

LeakyReLU(x)=max(ax,x)

負の領域でもわずか勾配あり
Dead ReLU問題を軽減

  • GELU(Gaussian Error Linear Unit)

正確な定義

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. 実装演習

  • numpyで実装

# 活性化関数の実装
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()

実行結果:
image.png

  • pytorchでSigmoid関数を実装

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)

実行結果:
image.png

  • pytorchでTanh関数を実装

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)

実装結果:
image.png

  • pytorchでReLU関数を実装

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)

実行結果:
image.png

  • pytorchでLeakyReLU関数の実装

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)

実行結果:
image.png

  • pytorchでGELU関数を実装

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)

実行結果:
image.png

3.確認テスト

  • 線形と非線形の違いを図に書いて簡易に説明せよ。
    image.png
  • 配布されたソースコードより
    該当する箇所を抜き出せ。
z1 = functions.sigmoid(u)

Section3 出力層と損失関数

1. 要点

  • 出力層

出力層は、特徴量はニューラルネットワークの通じて最終的に数値を出力する層です。
誤差関数はニューラルネットワークの出力値と真の値を誤差を算出する関数です、算出する誤差によってニューラルネットワークのパタメータを更新する。
ニューラルネットワークの各ニューロンに入力された値を、非線形な出力値へ変換する関数。

  • 出力層の種類

image.png

  • 出力層の設計

回帰問題では恒等関数を用いる。
2クラス分類では、シグモイド関数を用いる。
多クラス分類では、ソフトマックス関数を用いる。

  • 損失関数

損失関数は、ニューラルネットワークの「悪さ」を表す指標、現在の重みが教師データ対してどれくらい適合していないか表す。

  • 損失関数の設計

回帰問題であれば、2乗和誤差がよく用いられる。
分類問題であれば、クロスエントロピー誤差がよく用いられる。

  • ワンホットベクトル(one-hot vector)

ワンホットベクトルとは、ある一つの要素だけが1であり、その他の要素が0であるベクトルのこと

  • マックスソフト関数

y_k = \frac{exp(a_k)}{\sum_{i=1}^K(exp(a_i))}
  • 平均絶対誤差(MAE)

MAE = \frac{1}{N}\sum_{i=1}^{N} |t_i - \hat y_i|
  • 平均二乗誤差 バッチ対応版(mean squard error)

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)

実行結果:
image.png

  • pytorchでOne-hot関数を実装

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
  • pytorchで平均二乗誤差の関数を実装

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)

実行結果:
image.png

  • pytorchでクロスエントロピー誤差関数(ソフトマックス関数つけ)を実装

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)

実行結果:
image.png

  • pytorchでバイナリエントロピー誤差関数(シグモイド関数つけ)を実装

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)

実行結果:
image.png

3. 確認テスト

  • なぜ、引き算でなく二乗するが述べよ

引き算を行うと、各値の計算誤差には正負が生じるため、総和を計算するとゼロになる可能性があります。

  • 下式の1/2はどういう意味を持つか述べよ

1/2かけると微分するが簡単になります。

  • ①~③の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。
    -image.png
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$ を転置します。
オーバーフローを防ぐため、各列の最大値を引きます。
最後に、各要素の指数関数を計算し、その列の指数値の合計で割ることで正規化します。

  • ①~③の数式に該当するソースコードを示し、一行づつ処理の説明をせよ。
    image.png
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 ]$
image.png

  • 確率的勾配降下法(SGD)

無作為に選べたデータに対して行う学習方法

  • ミニバッチ勾配降下法

ミニバッチ勾配降下法とは、学習データを小さなバッチに分けて、その小さなバッチ毎に重みを更新していく学習方法。一つのバッチに含まれるデータ数をバッチサイズという。

2.実装演習

  • pytorchでSDGを実装する

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.確認テスト

  • image.png
  # 誤差逆伝播
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]
  • オンライン学習とは何か2行でまとめよ(2分)

オンライン学習は全データを一度に使わず、新しいデータが来る度にモデルを更新する学習方法である。

  • image.png
    $w_t$t時刻の重みから時間tの損失の勾配を引くとt+1時刻の重みを求める。

Section5 誤差逆伝播法

1.要点

  • 誤差逆伝播法

ニューラルネットワークにおいて、各層ごとに次の層から伝播してくる誤差(勾配)を用いて、当該層のパラメータの勾配を求める手法。再帰的に計算でき、重複した計算を避けられる。
今の層の出力値(次の層の入力値)の誤差を求める重要です。

  • 連鎖律

微分法において連鎖律とは、複数の関数が合成関数を微分する時、その導関数がそれぞれの導関数の積で与えられるという原理のこと。

\begin{align}
& u = g(x)\\
& \frac{d}{dx} f(g(x)) = \frac{df}{du} \cdot \frac{du}{dx}
\end{align}
  • 計算グラフと自動微分

計算グラフを使って「連鎖律」を機械的に適用し、勾配を正確に計算する仕組みです。

アフィン(Affine)レイヤの逆伝播
image.png
image.png
image.png
image.png
image.png

  • autograd がやっていること(本質)

基本運算の計算グラフ集合

演算 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.実装演習

  • pytorchを使ってmnist数字を識別する全結合ニューラルネットワークを実装する

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()

image.png

# 自分で書いた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)

image.png

# モデルを訓練そして訓練の誤差と正解率を出力する
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)

image.png

# テストデータを使ってモデルを評価する

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)

image.png

# 予測結果を可視化

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()

image.png

3.確認テスト

  • 誤差逆伝播法では不要な再帰的処理を避ける事が出来る。既に行った計算結果を保持しているソースコードを抽出せよ。

delta2 = functions.d_mean_squared_error(d, y)
delta1 = np.dot(delta2, W2.T) * functions.d_sigmoid(z1)
  • image.png
delta2 = functions.d_mean_squared_error(d, y)
grad['W2'] = np.dot(z1.T, delta2)

【参考文献】

<< 書籍 >>
1.機械学習(1), Tatsuro Fukuda (日本電子専門学校 2025)
2.ディープラーニングE資格精選問題集、小林範久、小林寛幸
3.Skillupディープラーニング基礎講座

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?