Help us understand the problem. What is going on with this article?

ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装 第3章

More than 1 year has passed since last update.

deeplearning.PNG

ゼロから作るDeep Learning Pythonで学ぶディープラーニングの理論と実装

sigmoid function

from sigmoid import *

cd03_test_1_0.png

def sigmoid(x):
    return 1 / (1 + np.exp(-x))
sigmoid(7)
0.9990889488055994
x = np.array([4,6,-2,-1, 2])
sigmoid(x)
array([ 0.98201379,  0.99752738,  0.11920292,  0.26894142,  0.88079708])

step function

from step_function import *

cd03_test_6_0.png

def step_function(x):
    return np.array(x > 0, dtype=np.int)
step_function(5)
array(1)
step_function(-5)
array(0)
x = np.array([3,-6,4,-1])
step_function(x)
array([1, 0, 1, 0])
x = np.random.randn(2,3)
print(x)
print(step_function(x))
[[ 0.21780529 -0.05316613  1.28802155]
 [-0.55119659 -1.23515555  0.6576237 ]]
[[1 0 1]
 [0 0 1]]

ReLU function

Rectified Linear Unit

入力が0を超えていれば、その 入力をそのまま出力し、0 以下ならば 0 を 出力する関数です

from relu import *

cd03_test_13_0.png

def relu(x):
    return np.maximum(0, x)

多次元配列

B = np.array([[1,2], [3,4], [5,6]]); B
array([[1, 2],
       [3, 4],
       [5, 6]])
np.ndim(B)
2
B.shape
(3, 2)
A = np.array([[3,2,1], [6,5,4]])
A.dot(B)
array([[14, 20],
       [41, 56]])

$$
A \cdot B=
\left(
\begin{array}{cc}
3 & 2& 1\\
6 & 5 & 4
\end{array}
\right)
\cdot
\left(
\begin{array}{cc}
1 & 2\\
3 & 4\\
5 & 6
\end{array}
\right)
$$

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

入力層から第1層への信号の伝達

fig3_18.PNG

x = np.array([1., .5])
W1 = np.array([[.1, .3, .5], [.2, .4, .6]])
B1 = np.array([.1, .2, .3])
print('入力信号x=', x)
print('重みW1=', W1)
print('バイアスB1=', B1)
入力信号x= [ 1.   0.5]
重みW1= [[ 0.1  0.3  0.5]
 [ 0.2  0.4  0.6]]
バイアスB1= [ 0.1  0.2  0.3]
x.shape, W1.shape
((2,), (2, 3))

x, W1の次元の要素数を合わせる!

A1 = np.dot(x, W1) + B1; A1
array([ 0.3,  0.7,  1.1])

隠れ層での重み付き和(重み付き信号とバイアスの総和)を$a$で表す。

Z1 = sigmoid(A1); Z1
array([ 0.57444252,  0.66818777,  0.75026011])

活性化関数で変換された信号を$z$で表す。

sigmoid関数が図中の$h$()にあたる。


第1層から第2層への信号の伝達

fig3_19.PNG

W2 = np.array([[.1, .4], [.2, .5], [.3, .6]])
B2 = np.array([.1, .2])
print('W2=', W2)

print('B2=', B2)
W2= [[ 0.1  0.4]
 [ 0.2  0.5]
 [ 0.3  0.6]]
B2= [ 0.1  0.2]
A2 = np.dot(Z1, W2) + B2; A2
array([ 0.51615984,  1.21402696])
Z2 = sigmoid(A2); Z2
array([ 0.62624937,  0.7710107 ])

第2層から出力層への信号の伝達

fig3_20.PNG

def identity_function(x):
    """恒等関数と呼ばれる、入力をそのまま出力する、何もしない関数"""
    return x
W3 = np.array([[.1, .3], [.2, .4]])
B3 = np.array([.1, .2])
print('W3=', W3)
print('B3=', B3)
W3= [[ 0.1  0.3]
 [ 0.2  0.4]]
B3= [ 0.1  0.2]
A3 = np.dot(Z2, W3) + B3
Y= identity_function(A3); Y
array([ 0.31682708,  0.69627909])

identity_function()という関数を出力層の活性化関数として利用する。

出力層の活性化関数は$\sigma$()で表し、隠れ層の活性化関数$h$()とは区別する。

実装まとめ

def init_network():
    """重みとバイアスの初期化"""
    network = {}
    network['W1'] = np.array([[.1, .3, .5], [.2, .4, .6]])
    network['W2'] = np.array([[.1, .4], [.2, .5], [.3, .6]])
    network['W3'] = np.array([[.1, .3], [.2, .4]])
    network['b1'] = np.array([.1, .2, .3])
    network['b2'] = np.array([.1, .2])
    network['b3'] = np.array([.1, .2])
    return network
def forward(network, x):
    """入力信号が出力へと変換されるプロセス"""
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    # 入力層
    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)

    # 隠れ層
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)

    # 出力層
    a3 = np.dot(z2, W3) + b3   
    return identity_function(a3)
x
array([ 1. ,  0.5])
network = init_network()
x = np.array([1., .5])
forward(network, x)
array([ 0.31682708,  0.69627909])

3.5 出力層の設計

ソフトマックス関数

$$y_k = \frac{\exp{(a_k)}}{\sum_{i=1}^n\exp{(a_i)}}$$

def softmax(a):
    """入力値aを確率で返す"""
    exp_a = np.exp(a - np.max(a))  # オーバーフロー対策
    return  exp_a/ np.sum(exp_a)

あまりに大きな値はinf="無限大"になって(オーバーフローと呼ぶ。)天井打ちしてしまい、正確な計算ができない。
そのため、以下に示すように数学的に等価となるように、$e^a$を小さい値に直してから合計値で割る。

$$
\begin{align}
y_k = \frac{\exp{(a_k)}}{\sum_{i=1}^n \exp{(a_i)}}
&= \frac{C\exp{(a_k)}}{C\sum_{i=1}^n \exp{(a_i)}}\\
&= \frac{\exp{(a_k+\log C)}}{\sum_{i=1}^n\exp{(a_i+\log C)}}\\
&= \frac{\exp{a_k + C'}}{\sum_{i=1}^n \exp{(a_i + C')}}
\end{align}
$$

上で説明していることは、つまりは$e^{a_k}$にどんな値を足しても、それを足し算した分母も同じ値足してやれば$y_k$の値に変化はないということ。

softmax(np.array([.3, 2.9, 4.]))
array([ 0.01821127,  0.24519181,  0.73659691])

データセットの取り出し

import os, sys
sys.path.append(os.pardir)  # 親ディレクトリappend
from dataset.mnist import load_mnist

以下を実行するとmnistデータセットを.gzファイルをダウンロードし、解凍してからpklファイルに納める。

終わるのに数分を要する。

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

MNIST画像の表示

ソースはch03.mnist_show.py

.BMP形式で画像が表示される。

# %load mnist_show.py
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image


def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)

img = x_train[0]
label = t_train[0]
print(label)  # 5

print(img.shape)  # (784,)
img = img.reshape(28, 28)  # 形状を元の画像サイズに変形
print(img.shape)  # (28, 28)

img_show(img)

5
(784,)
(28, 28)

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

# %load neuralnet_mnist.py
import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax


def get_data():
    """学習データのロード"""
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, flatten=True, one_hot_label=False)
    return x_test, t_test


def init_network():
    """sample_weight.pklに保存された学習済み重みパラメータの読み込み"""
    with open("sample_weight.pkl", 'rb') as f:
        network = pickle.load(f)
    return network


def predict(network, x):
    """neuralnetworkの実装
    出力層が恒等関数ではなく
    ソフトマックス関数になっている。"""
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + b1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = softmax(a3)

    return y
%%timeit
x, t = get_data()
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i])
    p= np.argmax(y) # 最も確率の高い要素のインデックスを取得
    if p == t[i]:
        accuracy_cnt += 1

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))
Accuracy:0.9352
Accuracy:0.9352
Accuracy:0.9352
Accuracy:0.9352
1 loop, best of 3: 1.25 s per loop

バッチ処理

fig3_27.PNG

バッチ処理における配列の形状の推移

バス帯域の負荷を軽減する、すなわち、データの読み込みに対して演算の割合を多くするバッチ処理によって1枚当たりの処理時間を短縮できる。

大きな配列を読み込み、一度に大きな配列を計算するほうが、分割された小さな配列を少しずつ計算するよりも演算の終了が早い。

%%timeit
# %load neuralnet_mnist_batch.py
x, t = get_data()
network = init_network()

batch_size = 100 # バッチの数
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
    x_batch = x[i:i+batch_size]
    y_batch = predict(network, x_batch)
    p = np.argmax(y_batch, axis=1)
    accuracy_cnt += np.sum(p == t[i:i+batch_size])

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

Accuracy:0.9352
Accuracy:0.9352
Accuracy:0.9352
Accuracy:0.9352
1 loop, best of 3: 269 ms per loop

バッチ処理によって約5倍速く実行できた。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away