手を動かして学ぶゼロからDL①メモ #1 – ニューラルネットワークを実装してみる

に公開

はじめに

勉強している本

https://www.amazon.co.jp/ゼロから作るDeep-Learning-―Pythonで学ぶディープラーニングの理論と実装-斎藤-康毅/dp/4873117585

コードの置き場

https://github.com/mirokanzashi/Practice/tree/master/deep_learning_zero1

概要

この記事の目的は、自分の学習履歴を記録することに加えて、誰かの参考になればと思っています。
今回は第三章の内容についてまとめます。

書籍のサンプルコードをもとに、自分なりにアレンジを加えながら実装してみました。

活性化関数の実装

本のサンプルを元に、出力形式を少し変化しました

追加実装の内容

  • config共通化
  • configを関数内で自動展開
  • plt(画像出力)の設定追加(フォント、タイトルなど)
    • x=0 の垂直線を追加
  • 複数ウィンドウを立ち上がります

ニューラルネットワークの実装

  • graphvizを使って、ニューラルネットワークを可視化します
  • graphvizをダウンロードしてインストールする
    • windowsでインストールする際に、青い背景の警告が出ることがありますが、気にせず進めて大丈夫です。
    • 環境変数にパスを追加する選択肢にはチェックを入れてください
  • pip install graphvizでインストールします
# 入力
x = np.array([1.0, 0.5])

# 重み
network_W=[
        np.array([[0.1,0.3,0.5],[0.2,0.4,0.6]]),
        np.array([[0.1,0.4],[0.2,0.5],[0.3,0.6]]),
        np.array([[0.1,0.3],[0.2,0.4]])
    ]
# バイアス
network_b=[
        np.array([0.1,0.2,0.3]),
        np.array([0.1,0.2]),
        np.array([0.1,0.2])
    ]

def forward(Ws,bs,x):
    W1,W2,W3=Ws[0],Ws[1],Ws[2]
    b1,b2,b3=bs[0],bs[1],bs[2]

    a1=np.dot(x,W1)+b1
    z1=func.sigmoid_func(a1)
    a2=np.dot(z1,W2)+b2
    z2=func.sigmoid_func(a2)
    a3=np.dot(z2,W3)+b3
    y= identity_function(a3)
    return y

# 出力
[0.31682708 0.69627909]

作成した画像

MNIST(手書き数字認識)

サンプルファイル(mnist_show.py)を実行してみます

目的:MNISTの1枚目の画像を表示する
処理内容:

  • 初回実行する時、train-images-idx3-ubyte.gz, train-labels-idx1-ubyte.gz, t10k-images-idx3-ubyte.gz, t10k-labels-idx1-ubyte.gzがダウンロードします
  • MNISTの1枚目の画像を取り出す
    • 取るときに、flatten=Trueで画像を1次元で格納します
    • 取り出したら、reshapeで1次元で格納下画像を元に戻す
  • 取り出した画像を表示します
    結果:画像が表示されます
img = x_train[0]
label = t_train[0]
print(label)  # 5
# img_show(img) # ここで画像表示のコードを追加すると、flattenの元データが見える
print(img.shape)  # (784,)
img = img.reshape(28, 28)  # 形状を元の画像サイズに変形
print(img.shape)  # (28, 28)

img_show(img)

ニューラルネットワークの推論処理

目的:MNISTのニューラルネットワークの推論処理を行います
処理内容:

  • MNISTデータを読み込みます
  • 学習済みの重みパラメータを読み込みます
  • predict関数で推論処理を行います

本の図3.26で各層の配列の形状が分かります

{
    'W1': ndarray(784, 50), # 第1層の重み
    'b1': ndarray(1, 50),  # 第1層のバイアス

    'W2': ndarray(50, 100),  # 第2層の重み
    'b2': ndarray(1, 100),  # 第2層のバイアス

    'W3': ndarray(100, 10), # 第3層(出力層)の重み
    'b3': ndarray(1, 10)  # 第3層のバイアス
}

実際各層の配列形状を出力します

W1: (784, 50)
b1: (50,)
W2: (50, 100)
b2: (100,)
W3: (100, 10)
b3: (10,)
  • 入力層の784:画像サイズ28*28=784
  • 出力層の10:10クラス分類(0~9計10クラス)

結果:Accuracy:0.9352が表示されます。これは今のパラメータで、93.52%のデータが正しく分類されました意味です

ニューラルネットワークのバッチ推論処理

目的:バッチでMNISTのニューラルネットワークの推論処理を行います
処理内容:

  • MNISTデータを読み込みます

  • 学習済みの重みパラメータを読み込みます

  • データを100枚ずつ学習を行います

    • y_batch :100枚でまとめて推論した結果
      y_batch.shape = (100, 10)
      [
          [0.1, 0.0, 0.2, 0.7, 0.0, 0, ...],   ← 1枚目の画像に対する10クラスのスコア
          [0.0, 0.9, 0.0, 0.1, 0.0, 0, ...],   ← 2枚目の画像
          ...
          (以下100行)
      ]
    
    
    • p = np.argmax(y_batch, axis=1):行方向(横の方向,axis=1)に対して最大値の位置を取得
      画像1 → [ 0.1  0.0  0.2  0.7  ... ] → 最大値は 0.7 → クラス 3
      画像2 → [ 0.0  0.9  0.0  0.1  ... ] → 最大値は 0.9 → クラス 1
      画像3 → [ ... ] → クラス x
      ...
    
      p = [3, 1, x, x, ..., x]   (100個)
    
    • accuracy_cnt:合っている数だけカウンタに加算
  • 正解枚数で正解率を計算します

結果:Accuracy:0.9352が表示されます。

バッチ処理のメリット

行列演算が一回で済む

1枚ずつ推論:10000枚の場合、重み行列 W と 10000回も掛け算する必要があります

for 1枚目:
    y = x @ W
for 2枚目:
    y = x @ W
...

バッチ処理(100枚ずつ):一度に 100 枚分の行列をまとめて計算できて、重み行列 W と掛け算する回数が 10000 → 100 回に減ります

Y_batch = X_batch @ W

CPU・GPU の並列処理に最適化されている

1枚ずつだと、毎回小さな計算でハードウェア性能を使い切れない

メモリ転送の回数が激減する

1枚ずつ:1枚読み込む → 推論 → 返す→ この処理を1万回繰り返す
バッチ:100枚まとめて読み込む → 推論 (行列演算をまとめて1回) → 返す→ この処理を100回繰り返す

レジを例にすると、

1枚ずつ推論

  • コンビニで1人ずつレジに並び、毎回レジを開く
  • 店員は10000回レジを開く

バッチ推論

  • 100人ずつまとめて支払い
  • 店員が1回の処理で100人分の計算
  • 店員は100回レジを開く

ベクトル化により Python のループを減らせる

loop 10000回と100回の差です

後書き

本の内容はそれほど多くありませんが、学習メモとしてまとめると意外と時間がかかります。
次回は第4章の内容を整理する予定ですが、状況によっては先に MCP を勉強してから進めるかもしれません。

GitHubで編集を提案

Discussion