ゼロから作るDeep LearningをJuliaで実装してみる (5)MNIST手書き数字認識 その②推論

## :red_car: この記事の目的

本記事は、Deep Learningの入門書「ゼロから作るDeep Learning」を参考に、Juliaを用いて同書の内容を実装してみる、という試みです。

※これはあくまで筆者の一つの試みと提案で、**必ずしも内容が正解ではありません**のでご了承お願い致します。
また、より良いアドバイスや協力のご提案などありましたら、お気軽にお声がけ頂けますと幸いです。

## :red_car: 今回取り組むこと

本記事は、参考書の3.6.2章にあたります。
今回は、前回の記事その①画像表示の続編で、その②推論です。
参考書に紹介されている処理と同様の処理をJuliaで実装していきます。

## :red_car: 各関数の実装

参考書にならい、推論処理で使用する以下の関数を実装します。
　(1) get_data()関数
　(2) init_network()関数
　(3) sigmoid_function()関数
　(4) softmax()関数
　(5) predict()関数

#### (1) get_data()関数

MNIST手書き数字画像データを取得する関数です。
「load_mmist.jl」をインクルードし、load_mnist()関数を呼び出すことで、MNIST手書き数字画像データをMatrix型の4つの配列 x_train, t_train, x_test, t_test に取得します。また、返り値として x_test, t_test を返却します。

上記の詳細については、こちらの記事をご参照下さい。

:paperclip: ゼロから作るDeep LearningをJuliaで実装してみる (5)MNIST手書き数字認識 その①画像表示
https://qiita.com/ttabata/items/6f7025f48fa52d465c81

以下のように実装しました。

```ruby:5_load_mnist_2
include("load_mnist.jl")

function get_data()

    # MNIST画像データの取得
    x_train, t_train, x_test, t_test = load_mnist(normalize=true, flatten=true, one_hot_label=false)
    return x_test, t_test

end
```

#### (2) init_network()関数

今回計算する3層ニューラルネットワークに対する入力層のデータを取得します。
入力層データは辞書型変数networkで、要素として以下の6種類を用意します。
　network["W1"], network["W2"], network["W3"]
　network["b1"], network["b2"], network["b3"]

参考書では、予め著者側で用意されたpythonのpickleファイル(sample_weight.pkl)を読み取る手順となっていますが、本記事では上記6要素(network["W1"],network["W2"],network["W3"],network["b1"],network["b2"],network["b3"])をそれぞれに用意したCSVファイルから読み取り、全体をタプル形式で返却する関数として実装することにします。

まず、処理の冒頭に以下を実装します。
①CSVファイルを扱うため、"CSVFiles"パッケージを取得し、usingで使用を宣言する。
②CSVデータを読み取る際、データフレームとして読み取るため、"DataFrame"パッケージを取得し、usingで使用を宣言する。

①②についてのソースは以下となります。

```ruby:5_load_mnist_2
import Pkg;
Pkg.add("CSVFiles")
Pkg.add("DataFrames")
using CSVFiles, DataFrames
```

CSVデータファイルは以下の6種類を用意しました。いずれもnetworkDataフォルダ内に設置するものとしています。

　networkDataフォルダ内
　　(1) network_W1.csv
　　(2) network_W2.csv
　　(3) network_W2.csv
　　(4) network_b1.csv
　　(5) network_b2.csv
　　(6) network_b3.csv

これらのCSVファイルをDataFrameのload()関数で読み取り、さらにMatrix型の配列に置換するためにMatrix()関数を適用します。

以下、この部分のソースです。

```ruby:5_load_mnist_2
df_W1 = Matrix(DataFrame(load("networkData/network_W1.csv", header_exists=false)))
df_W2 = Matrix(DataFrame(load("networkData/network_W2.csv", header_exists=false)))
df_W3 = Matrix(DataFrame(load("networkData/network_W3.csv", header_exists=false)))
df_b1 = Matrix(DataFrame(load("networkData/network_b1.csv", header_exists=false)))
df_b2 = Matrix(DataFrame(load("networkData/network_b2.csv", header_exists=false)))
df_b3 = Matrix(DataFrame(load("networkData/network_b3.csv", header_exists=false)))
```

最後に、6つの配列を辞書型変数networkに格納し、関数の返り値として返却します。

```ruby:5_load_mnist_2
network = Dict()
network["W1"] = df_W1
network["W2"] = df_W2
network["W3"] = df_W3
network["b1"] = df_b1
network["b2"] = df_b2
network["b3"] = df_b3

return network
```

init_network()関数の実装の全体をまとめると以下のようになります。

```ruby:5_load_mnist_2
import Pkg;
Pkg.add("CSVFiles")
Pkg.add("DataFrames")
using CSVFiles, DataFrames

function init_network()
    
    df_W1 = Matrix(DataFrame(load("networkData/network_W1.csv", header_exists=false)))
    df_W2 = Matrix(DataFrame(load("networkData/network_W2.csv", header_exists=false)))
    df_W3 = Matrix(DataFrame(load("networkData/network_W3.csv", header_exists=false)))
    df_b1 = Matrix(DataFrame(load("networkData/network_b1.csv", header_exists=false)))
    df_b2 = Matrix(DataFrame(load("networkData/network_b2.csv", header_exists=false)))
    df_b3 = Matrix(DataFrame(load("networkData/network_b3.csv", header_exists=false)))
    
    network = Dict()
    network["W1"] = df_W1
    network["W2"] = df_W2
    network["W3"] = df_W3
    network["b1"] = df_b1
    network["b2"] = df_b2
    network["b3"] = df_b3

    return network

end
```

#### (3) sigmoid_function()関数

以下の記事にまとめましたので説明は省略します。
こちらの記事「②シグモイド関数」をご参照下さい。

:paperclip: ゼロから作るDeep LearningをJuliaで実装してみる (2)活性化関数
https://qiita.com/ttabata/items/7d4b8d92e38df47cffbb

#### (4) softmax()関数

こちらも以下の記事にまとめましたので説明は省略します。
以下の記事をご参照下さい。

:paperclip: ゼロから作るDeep LearningをJuliaで実装してみる (4)ソフトマックス関数
https://qiita.com/ttabata/items/21329897119f81a141e9

#### (5) predict()関数

入力層データとして読み込んだW1, W2, W3及びb1, b2, b3をそれぞれ学習済みの重みパラメータ及び同バイアスとし、第一層、第二層、第三層(出力層)を計算する処理です。
参考書の通り、下記のように実装します。

(注意1)
Juliaでは、行列(Matrix型配列)の掛け算や足し算、引き算はスカラー値の掛け算、足し算、引き算と同じ記法で記述できます。ただし、行列計算では掛ける行列の行数と掛けられる行列の列数が等しいことが条件です。

　例）a2,z1,W2,b2を行列とし、以下のように記述することができる
　　a2 = z1 * W2 + b2

(注意2)
行列同士を掛ける際に、掛ける行列の行数と掛けられる行列の列数を合わせる必要があります。このため、入力変数xの転置行列を取得するためにtranspose()関数を適用しています。また、transpose()関数を使用する際には、予め「using LinearAlgebra」を宣言する必要があります。

```ruby:5_load_mnist_2
using LinearAlgebra
function predict(network, x)

    W1, W2, W3 = network["W1"], network["W2"], network["W3"]
    b1, b2, b3 = network["b1"], network["b2"], network["b3"]

    a1 = transpose(x) * W1 + b1       # (注意2)
    z1 = sigmoid_function(a1)
    a2 = z1 * W2 + b2                 # (注意1)
    z2 = sigmoid_function(a2)
    a3 = z2 * W3 + b3
    y = softmax(a3)
    
    return y
    
end
```

各関数の実装は以上です。

## :red_car: 全体的な計算処理の実行

全体的な流れは以下となります。

**①** get_data()関数及びinit_network()関数を呼び出し、必要な変数x, t, networkを取得します。
**②** 正答数を格納する変数accuracy_cntを用意し、0で初期化します。
**③** 取得した10,000件のテストデータxについて、それぞれのデータを取り出し、predict()関数でニューラルネットワークによる計算を行い、予測値を算出します。
**④** 予測値はone-hot-vectorで返却されるため、そこから最も可能性の高い数字の値を判定します。Juliaでは配列のインデックスを1から数えるためPythonの書式とインデックスが一つずれることに注意が必要です。
**⑤** 正解ラベルの値と照合し、予測値が合っていたら正答数accuracy_cntを更新します。
**⑥** 最後に正解率Accuracyを計算して表示します。

①から⑥の実装は以下です。

```ruby:5_load_mnist_2
x, t = get_data()                                  # ①
network = init_network()                           # ①

accuracy_cnt = 0                                   # ②
for i = 1:size(x, 1)
    y = predict(network, x[i, :])                  # ③
    p = argmax(vec(y)) - 1                         # ④
    if p == t[i]
        accuracy_cnt += 1                          # ⑤
    end
end

println("Accuracy= ", accuracy_cnt / size(x, 1))   # ⑥
```

## :red_car: ソース全体とディレクトリ構成

今回実装したソースの全体は以下となります。
ファイル名をpredict.jlとしました。

```ruby:predict.jl
import Pkg;
Pkg.add("CSVFiles")
Pkg.add("DataFrames")
using CSVFiles, DataFrames
using LinearAlgebra
include("load_mnist.jl")

function get_data()

    # MNIST画像データの取得
    x_train, t_train, x_test, t_test = load_mnist(normalize=true, flatten=true, one_hot_label=false)
    return x_test, t_test

end

function init_network()
    
    df_W1 = Matrix(DataFrame(load("networkData/network_W1.csv", header_exists=false)))
    df_W2 = Matrix(DataFrame(load("networkData/network_W2.csv", header_exists=false)))
    df_W3 = Matrix(DataFrame(load("networkData/network_W3.csv", header_exists=false)))
    df_b1 = Matrix(DataFrame(load("networkData/network_b1.csv", header_exists=false)))
    df_b2 = Matrix(DataFrame(load("networkData/network_b2.csv", header_exists=false)))
    df_b3 = Matrix(DataFrame(load("networkData/network_b3.csv", header_exists=false)))
    
    network = Dict()
    network["W1"] = df_W1
    network["W2"] = df_W2
    network["W3"] = df_W3
    network["b1"] = df_b1
    network["b2"] = df_b2
    network["b3"] = df_b3

    return network

end

function sigmoid_function(x)
    
    return map(i -> 1 / (1 + exp(-i)), x)
    
end

function softmax(a)
    
    c = maximum(a)
    exp_a = [exp(i - c) for i in a]
    sum_exp_a = sum(exp_a)
    y = [i / sum_exp_a for i in exp_a ]
    return reshape(y, 1, :)
    
end

function predict(network, x)

    W1, W2, W3 = network["W1"], network["W2"], network["W3"]
    b1, b2, b3 = network["b1"], network["b2"], network["b3"]

    a1 = transpose(x) * W1 + b1
    z1 = sigmoid_function(a1)
    a2 = z1 * W2 + b2
    z2 = sigmoid_function(a2)
    a3 = z2 * W3 + b3
    y = softmax(a3)
    
    return y
    
end

x, t = get_data()
network = init_network()

accuracy_cnt = 0
for i = 1:size(x, 1)
    y = predict(network, x[i, :])
    p = argmax(vec(y)) - 1
    if p == t[i]
        accuracy_cnt += 1
    end
end

println("Accuracy= ", accuracy_cnt / size(x, 1))
```

また、ディレクトリ構成は以下としました。

```ruby:ディレクトリ構成
predict.jl　　　　　　 # Juliaスクリプトファイル
load_mnist.jl　　　　　# Juliaスクリプトファイル
mnistDataSet　　　　　 # フォルダ
　└ mnist_data.dat　　 # MNIST手書き画像データ(シリアライズ化されたデータ)
networkData　　　　　　# フォルダ
　└ network_W1.csv　　 # 入力層CSVデータ(W1)
　　network_W2.csv　　 # 入力層CSVデータ(W2)
　　network_W3.csv　　 # 入力層CSVデータ(W3)
　　network_b1.csv　　 # 入力層CSVデータ(b1)
　　network_b2.csv　　 # 入力層CSVデータ(b2)
　　network_b3.csv　　 # 入力層CSVデータ(b3)
```

## :red_car: 実行結果と評価

実行結果は**「Accucary: 0.9352」**で、参考書のPythonによる計算処理の実行結果と同等の結果となりました。
また実行に要した処理時間ですが、Pythonとも1秒程度の処理時間なので比較することができませんでした。
処理所要時間の比較は別の機会に臨みたいと思います。


## :red_car: もくじ

[(1)論理回路](https://qiita.com/ttabata/items/f9253d5ef94ca3807f42)
[(2)活性化関数](https://qiita.com/ttabata/items/7d4b8d92e38df47cffbb)
[(3)3層ニューラルネットワークの実装](https://qiita.com/ttabata/items/4f50ec32c0276d21d966)
[(4)ソフトマックス関数](https://qiita.com/ttabata/items/21329897119f81a141e9)
[(5)MNIST手書き数字認識 その①画像表示](https://qiita.com/ttabata/items/6f7025f48fa52d465c81)
[(5)MNIST手書き数字認識 その②推論](https://qiita.com/ttabata/items/0781e1aacb11d9148053)

## :red_car: 関連情報

:paperclip: Julia早引きノート［01］変数・定数の使い方
https://qiita.com/ttabata/items/a1ada2c0cba03672e105

:paperclip: Julia - 公式ページ
https://julialang.org/

:paperclip: Julia - 日本語公式ドキュメント
https://julia-doc-ja.readthedocs.io/ja/latest/index.html

:paperclip: 初めてのJuliaとインストール (Windows & Linux)
https://qiita.com/ttlabo/items/b05bb43d06239f968035

## :red_car: 出典

■参考書
「ゼロから作るDeep Learning ～ Pythonで学ぶディープラーニングの理論と実装」
　斎藤康毅 著／オライリー・ジャパン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/399135/4a558c91-5e9c-9d6f-3673-d44fbb583103.png)

## :red_car: ご意見など

ご意見、間違い訂正などございましたらお寄せ下さい。