初心者データサイエンティストの備忘録

調べたことは全部ここに書いて自分の辞書を作る

【読書記録】なっとく!ディープラーニング_第5章

目次

はじめに

前回記事に書いたように、現在ディープラーニングを勉強しています。 今回は、なっとく!ディープラーニングの第5章について解説していきたいと思います。
 なお、第5章は次の流れで説明が進んでいきます。本記事もその流れに合わせています。

  1. 入力が複数、出力が1つのニューラルネットワークの更新式を導出する
  2. 入力が1つ、出力が複数のニューラルネットワークの更新式を導出する
  3. 入力が複数、出力も複数のニューラルネットワークの更新式を導出する
  4. MNISTデータセットニューラルネットワークを適用させる

 1, 2を理解すれば3の理解は簡単です。一方で4を理解することは難しく、きっちりと理解したいのであれば、しっかりと本書を読み込むべきだと思います。

勾配降下法のおさらい

 勾配降下法とは、ディープラーニングにおいてパラメータを推定する手法の一つです。パラメータを下記の更新式にしたがって更新します。


\omega _ {(n+1)} = \omega _ {(n)} - \eta \dfrac{\partial L}{\partial \omega}(\omega _ {(n)} )

ただし、\omega _ {(n+1)}は更新後のパラメータ、\omega _ {(n)}は更新前のパラメータ、\etaは学習率、Lは損失関数です。 \dfrac{\partial L}{\partial \omega}(\omega _ {(n)} )によって更新の方向が、 \etaによって更新の大きさが決まります。
 前回記事では、入力が一つ、出力も一つ、層も一つのニューラルネットワークについて、具体的な更新式を説明しました。本記事では、入力や出力が複数になった場合のニューラルネットワークについて、勾配降下法の更新式を導出します。

入力が複数、出力は1つのニューラルネットワーク

 図1のようなニューラルネットワークにおける勾配降下法の更新式を具体的に導出します。

図1:入力が複数、出力は1つのニューラルネットワーク

 図1のニューラルネットワークの形から、予測値\hat{y}は、


\hat{y} = \omega_1 x_1 + \omega_2 x_2 + \omega_3 x_3

となります。したがって、損失関数Lは、


\begin{eqnarray}
L(\omega_1, \omega_2, \omega_3) &=& (\hat{y}-y)^2 \\
&=& (\omega_1 x_1 + \omega_2 x_2 + \omega_3 x_3-y)^2
\end{eqnarray}

となります。したがって、勾配\dfrac{\partial L}{\partial \omega}は、


\dfrac{\partial L}{\partial \omega} = 
\left(
\begin{array}{c}
\dfrac{\partial L}{\partial \omega_1} \\
\dfrac{\partial L}{\partial \omega_2} \\
\dfrac{\partial L}{\partial \omega_3} \\
\end{array}
\right)
= \left(
\begin{array}{c}
x_1 (\hat{y}-y) \\
x_2 (\hat{y}-y) \\
x_3 (\hat{y}-y) \\
\end{array}
\right)

となります。したがって、更新式は


\left(
\begin{array}{c}
\omega_{1(n+1)} \\
\omega_{2(n+1)} \\
\omega_{3(n+1)} \\
\end{array}
\right)
=
\left(
\begin{array}{c}
\omega_{1(n)} \\
\omega_{2(n)} \\
\omega_{3(n)} \\
\end{array}
\right)
-\eta
\left(
\begin{array}{c}
x_1 (\hat{y}-y) \\
x_2 (\hat{y}-y) \\
x_3 (\hat{y}-y) \\
\end{array}
\right)

となります。

入力が1つ、出力が複数のニューラルネットワーク

 図2のようなニューラルネットワークにおける勾配降下法の更新式を具体的に導出します。

図2:入力が1つ、出力が複数のニューラルネットワーク

図2のニューラルネットワークの形から、予測値\hat{y} _ 1, \hat{y} _ 2, \hat{y} _ 3は、


\begin{eqnarray}
\hat{y}_1 &=& \omega_1 x \\
\hat{y}_2 &=& \omega_2 x \\
\hat{y}_3 &=& \omega_3 x \\
\end{eqnarray}

となります。また、この場合損失関数が出力ごとに存在するため、損失関数は、


\begin{eqnarray}
L_1 &=& (\hat{y}_1-y)^2 = (\omega_1 x-y)^2 \\
L_2 &=& (\hat{y}_2-y)^2 = (\omega_2 x-y)^2\\
L_3 &=& (\hat{y}_3-y)^2 = (\omega_3 x-y)^2 \\
\end{eqnarray}

となります。したがって、勾配\dfrac{\partial L}{\partial \omega}は、


\dfrac{\partial L}{\partial \omega} = 
\left(
\begin{array}{c}
\dfrac{\partial L}{\partial \omega_1} \\
\dfrac{\partial L}{\partial \omega_2} \\
\dfrac{\partial L}{\partial \omega_3} \\
\end{array}
\right)
= \left(
\begin{array}{c}
x (\hat{y}_1-y) \\
x (\hat{y}_2-y) \\
x (\hat{y}_3-y) \\
\end{array}
\right)

となります。したがって、更新式は


\left(
\begin{array}{c}
\omega_{1(n+1)} \\
\omega_{2(n+1)} \\
\omega_{3(n+1)} \\
\end{array}
\right)
=
\left(
\begin{array}{c}
\omega_{1(n)} \\
\omega_{2(n)} \\
\omega_{3(n)} \\
\end{array}
\right)
-\eta
\left(
\begin{array}{c}
x (\hat{y}_1-y) \\
x (\hat{y}_2-y) \\
x (\hat{y}_3-y) \\
\end{array}
\right)

となります。

入力が複数、出力も複数のニューラルネットワーク

 図3のようなニューラルネットワークにおける勾配降下法の更新式を具体的に導出します。

図3:入力も出力も複数のニューラルネットワーク

考え方はシンプルで、図3のニューラルネットワークを二つに分解します(図4)。

図4:ニューラルネットワークの分解

図4のニューラルネットワークのうち、A、Bの部分はそれぞれ入力が複数、出力が一つのニューラルネットワークと同じ形をしています。したがって、更新式の導出は上で説明した考え方を使えば良いです。

 上で説明した考え方を使うと、Aの部分の更新式は


\left(
\begin{array}{c}
\omega_{11(n+1)} \\
\omega_{21(n+1)} \\
\omega_{31(n+1)} \\
\end{array}
\right)
=
\left(
\begin{array}{c}
\omega_{11(n)} \\
\omega_{21(n)} \\
\omega_{31(n)} \\
\end{array}
\right)
-\eta
\left(
\begin{array}{c}
x_1 (\hat{y}_1-y_1) \\
x_2 (\hat{y}_1-y_1) \\
x_3 (\hat{y}_1-y_1) \\
\end{array}
\right)

となります。同様にBの更新式は、


\left(
\begin{array}{c}
\omega_{12(n+1)} \\
\omega_{22(n+1)} \\
\omega_{32(n+1)} \\
\end{array}
\right)
=
\left(
\begin{array}{c}
\omega_{12(n)} \\
\omega_{22(n)} \\
\omega_{32(n)} \\
\end{array}
\right)
-\eta
\left(
\begin{array}{c}
x_1 (\hat{y}_2-y_2) \\
x_2 (\hat{y}_2-y_2) \\
x_3 (\hat{y}_2-y_2) \\
\end{array}
\right)

です。これらの式を行列の形でまとめると(つまり、Aの部分を1列目、Bの部分を2列目にする)


\left(
\begin{array}{c}
\omega_{11(n+1)} & \omega_{12(n+1)} \\
\omega_{21(n+1)} & \omega_{22(n+1)} \\
\omega_{31(n+1)} & \omega_{32(n+1)} \\
\end{array}
\right)
=
\left(
\begin{array}{c}
\omega_{11(n)} & \omega_{12(n)} \\
\omega_{21(n)} & \omega_{22(n)} \\
\omega_{31(n)} & \omega_{32(n)} \\
\end{array}
\right)
-\eta
\left(
\begin{array}{c}
x_1 (\hat{y}_1-y_1) & x_1 (\hat{y}_2-y_2) \\
x_2 (\hat{y}_1-y_1) & x_2 (\hat{y}_2-y_2) \\
x_3 (\hat{y}_1-y_1) & x_3 (\hat{y}_2-y_2) \\
\end{array}
\right)

となります。

コードに落とし込む

 最後の更新式をJuliaで書くと以下のようになります。

using LinearAlgebra

# 特徴量
toes = [8.5, 9.5, 9.9, 9.0]
wlrec = [0.65, 0.8, 0.8, 0.9]
nfans = [1.2, 1.3, 0.5, 1.0]

# 目的変数
hurt = [0.1, 0.0, 0.0, 0.1]
win = [1, 1, 0, 1]
sad = [0.1, 0.0, 0.1, 0.2]

# ニューラルネットワークの入力
input = [toes[1], wlrec[1], nfans[1]]

# ニューラルネットワークの出力
truth = [hurt[1], win[1], sad[1]]

# 重みの初期値
weights = [0.1 0.1 -0.3; 0.1 0.2 0.0; 0.0 1.3 0.1]

# 学習率
alpha = 0.005

# 100回だけ更新
for i = 1:100
    ## pred_vecの計算
    pred_vec = weights*input

    ## delta_vecの計算
    delta_vec = pred_vec-truth

    ## weights_delta_matrixの計算
    weights_delta_matrix = zeros(size(input)[1], size(delta_vec)[1])

    for i = 1:size(input)[1]
        for j = 1:size(delta_vec)[1]
            setindex!(weights_delta_matrix, input[i]*delta_vec[j], i, j)
        end
    end

    ## 更新
    weights -= alpha*weights_delta_matrix

    ## Errorの計算
    pred_vec = weights*input
    for delta in (pred_vec-truth)
        println(delta^2)
    end

    println("---")
    
end

MNISTデータセットニューラルネットワークを適用させる

 本書の5.7節ではMNISTデータセットに対して、ニューラルネットワークを構築するトピックが出ています。本節では、理論の解説がなされているだけでコードが載っていなかったので、自分でJuliaで書いてみました。
 ただし、ここまでの流れに沿ってデータが一つしかない場合ニューラルネットワークを構築しています。したがって、本書と結果が異なることはご了承ください。また、問題設定はここには記載しません。詳しくは本書を読んでいただければと思います。

パッケージの導入

 まずは、必要なパッケージをインストールします。

import Pkg
# 必要なパッケージのinstall
Pkg.add("MLDatasets")
Pkg.add("Flux")

データセットの入手

 次にデータセットを入手します。今回はデータセットを一つしか使わない、かつ精度検証も行わないので学習用データだけ読み込みます。

using MLDatasets
x_train, y_train = MLDatasets.MNIST.traindata(Float32)

データの前処理

 データを今回作ったニューラルネットワークのコードに流し込める形に変換します。

using Flux
using Flux.Data: DataLoader
using Flux: onehotbatch, onecold

x_train = Flux.flatten(x_train)
y_train = onehotbatch(y_train, 0:9)

ニューラルネットワークの学習

 最後にニューラルネットワークを学習させます。

# ニューラルネットワークの入力
input = x_train[:, 1]

# ニューラルネットワークの出力
truth = y_train[:, 1]

# 重みの初期値
weights = zeros(size(y_train)[1], size(x_train)[1], )

# 学習率
alpha = 0.005

# 10回だけ更新
for i = 1:100
    ## pred_vecの計算
    pred_vec = weights*input

    ## delta_vecの計算
    delta_vec = pred_vec-truth

    ## weights_delta_matrixの計算
    weights_delta_matrix = zeros(size(delta_vec)[1], size(input)[1])

    for i = 1:size(delta_vec)[1]
        for j = 1:size(input)[1]
            setindex!(weights_delta_matrix, delta_vec[i]*input[j], i, j)
        end
    end

    ## 更新
    weights -= alpha*weights_delta_matrix

    ## Errorの計算
    pred_vec = weights*input
    
    for delta in (pred_vec-truth)
        println(delta^2)
    end

    println("---")
    
end

結果

 最後に学習されたパラメータをヒートマップで表示しました(図5)。なんとなく、5と書かれていることが見て取れます(向きは変ですが、、、)。 これはデータセットの1個目の目的変数の値が5だったためです。

using Pkg
Pkg.add("Plots")
using Plots

# ヒートマップで推定されたパラメータを表示する
heatmap(reshape(reshape(weights, 10, 784)'[:, 6], 28, 28)) # 5の場合

図5:推定されたパラメータをヒートマップで表示した結果

まとめ

 本記事では、なっとく!ディープラーニングにしたがい、下記の流れで単層のニューラルネットワークに関する理論を紹介しました。

  1. 入力が複数、出力が1つのニューラルネットワークの更新式を導出する
  2. 入力が1つ、出力が複数のニューラルネットワークの更新式を導出する
  3. 入力が複数、出力も複数のニューラルネットワークの更新式を導出する
  4. MNISTデータセットニューラルネットワークを適用させる

 また、「入力が複数、出力が複数のニューラルネットワークの更新式を導出する」と「MNISTデータセットニューラルネットワークを適用させる」については、Juliaによるコードも付記しました。
 次回はいよいよ層を複数に拡張したニューラルネットワークについて勉強していきます。非常に楽しみです。