N高校生python備忘録

N高校生python備忘録

python始めたけど一向に勉強しないのでいい加減やります

ゼロから作るDeep Learning 4章 雑感

前回のまとめ
ニューラルネットワークの仕組みについて理解し、さらにすでに用意されたデータを元に実装まで行いました
なかなかに感動的でしたね だが無意味だ
前回一通り実装して気づいたと思いますが、常識的に考えて場合によっては万を軽く超えるであろう重みやバイアスの値を、人間が試行錯誤して設定するのは不可能です

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

と、いうわけで4章ではどのように重みやバイアスなどのパラメータを機械に設定させるのかについて見ていきます
なおニューラルネットワークの学習と銘打っていますが実質機械学習のやり方です

損失関数(loss function)

損失関数とは、そのニューラルネットワークがどれほどの精度なのかを示す指標となる関数です
返す値が大きければ大きいほどそのニューラルネットは出来が悪いと判断することができ、逆説的にこの値を最小化すること(minimize)で性能のよいニューラルネットを作ることができるでしょう
ここの説明に関してはスタンフォード大の教授Mr.アンドリューのCoursera machine learningや、やさしく学ぶ 機械学習を理解するための数学のきほん ~アヤノ&ミオと一緒に学ぶ 機械学習の理論と数学、実装まで~にわかりやすい説明が乗っているので参考に

二乗和誤差

損失関数として用いられる数式にはいくつかあるが、その中でももっとも有名なのが二乗和誤差(mean squared error)

\displaystyle E=\frac{1}{2} \sum_{k}(y_k-t_k)^{2}

ここでのykはニューラルネットの出力、tkは教師データを表し、kはデータの次元数を表す
つまりニューラルネットからの出力と教師データの各要素の差の二乗を計算し、その総和を求める
pythonの実装

def mean_squared_error(y, t):
    return 0/5 * np.sum((y-t)**2)

交差エントロピー誤差

二乗和誤差とは別の関数として、交差エントロピー誤差(cross entropy error)もよく用いられる

\displaystyle E=-\sum_{k}t_k\log{y_k}

またログかよ!という私の叫びが聞こえてくるようです
たまに中学生や高校生で機械学習に関して詳しい方がおりますが、一体彼ら/彼女らはどのようにして学習していったのでしょう
ここでのlogには底数の表記がないので自然対数(log_e)を表す
ykは同じくニューラルネットの出力、tkは正解ラベルとする 正解ラベルとは、教師データのように正解か不正解かが示された数値で、さらにここでは正解ラベルとなるインデックスだけが1で、それ以外は0であるとする(one-hot表現)例:0〜5までで2が正解ラベルの場合の行列は[0, 0, 1, 0, 0, 0]

上のグラフ(自然対数y=log_x)を見ると分かるが、xが1の時にyが0となり、xが0に近くにつれてyの値はどんどん小さく(マイナスに)なる そのため上記の式は正解ラベルに対応する出力が大きければ大きいほど0になる
pythonでも実装する

def cross_entropy_error(y, t):
    delta = 1e-4
    return -np.dum(t * np.log(y + delta))

実装ではdeltaという式が足されているが、これはnp.log(0)のような計算が発生した場合python側がマイナス無限大を返してしまい、それ以上計算が続行できなくなってしうのを防ぐため 1e-4は1/10000以下の極小の値

ミニバッチ学習

さて、前回三章で用いられたMNISTデータセットは訓練データが60000個もあった
本来であれば全てのデータを対象に損失関数を計算することが望ましいが、それでは少々時間がかかってしまうし、ビッグデータともなるとその数は数千万を超え、それら全てを対象とした損失関数の計算を行うのは現実的ではない
そこでデータの中から一部を無作為に抽出し、その一部のデータを全体の「近似」として利用する この部分的なデータをミニバッチ(小さな塊)と呼ぶことから、ミニバッチを元に機械学習を行う方法をミニバッチ学習という
例えば本書では60000個あるMNISTデータではそこから100枚を無作為に抽出して学習に使用する

np.random.choice(60000, 10)
#([ 8013, 14666, 58210, 23831, 52091, 10153, 8107, 19410, 27260, 21411])

NumPyのrandom.choiceを用いれば選び出すインデックスを配列として簡単に取得できる 上記のコードは0〜60000のうちランダムに10個の数字を選び出す

[バッチ対応版]交差エントロピー誤差の実装

先ほどのミニバッチ学習を踏まえた上で再度交差エントロピー誤差の実装を行う

#one-hot表現
def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    batch_size = y.shape[0]
    return -np.sum(t * np.log(y + 1e-7)) / batch_size

#ラベルとして与えられた場合
def cross_entropy_error(y, t):
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7) / batch_size

数値微分

微分とはある瞬間においての変化の量を示したものである  わか、わからない……

微分については私がまだお勉強していないので割愛 正直今も微分について理解せずに機械学習の勉強を進めています なぜ勉強しようと思ったのか…
どうやら微小な差分によって微分を求めることを数値微分と呼ぶらしい

偏微分と勾配

f(x_{0}, x_{1})=x^{2}_{0}+x^{2}_{1}

さて、こちらの関数には変数が二つ存在する こういった複数の変数からなる関数の微分偏微分というらしい
偏微分する場合それぞれの変数に対して微分値が導き出されるが、それらの数値をベクトルとしてまとめたものを勾配(gradient)という
実装

def numerical_gradient(f, x):
    h = 1e-4  # 0.0001
    grad = np.zeros_like(x)    
    for idx in range(x.size):
        tmp_val = x[idx]
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)  # f(x+h)        
        x[idx] = tmp_val - h 
        fxh2 = f(x)  # f(x-h)
        grad[idx] = (fxh1 - fxh2) / (2*h)        
        x[idx] = tmp_val  # 値を元に戻す       
    return grad

?…………?……?
一瞬戸惑ったがどうやらxにNumPy配列を対応させたものらしい コードよめないけど
さて、上記の関数で返される勾配にはどのような意味があるのか
まずこちらが偏微分の対象となるような複数の変数をもつ関数のグラフ
式はx^{2}_{0}+x^{2}_{1}

そして勾配の結果にマイナスをかけたベクトルの描画

高校数学では微分をするときに増減表(矢印)を作るが、マイナスをかけたということはつまり矢印はその地点から低い方向を指していると言えるでしょう 

勾配法

勾配がわかることによって何が起こるのか
機械学習の問題の多くは、学習の際に最適なパラメータを探索する ニューラルネットでは重みとバイアスにおいて、最適なものを見つけなければならない ここで、最適なパラメータというのは損失関数が最小値を取るときのパラメータの値だ
つまり損失関数における勾配を利用して、損失関数の最小値を探せばいい、ということらしい
この方法を勾配法(gradient method)という
より具体的には、まず関数において適当な場所をとり、その地点においての勾配(つまり最も関数の値を減らす方向)を求めその方向へ一定距離進む 続いて再び移動した先で勾配を求め、またさらに一定距離移動する……というのを繰り返せば、いずれはその関数においての最小値(および極小点)にたどり着ける
勾配法を数式で表すと次のようになる

\displaystyle x_{0}:=x_{0}- \eta \frac{\partial f}{\partial x_{0}}

\displaystyle x_{1}:=x_{1}- \eta \frac{\partial f}{\partial x_{1}}

式のη(イータ)は更新の量(勾配方向にどれだけ進むか)を表す ニューラルネットにおいては学習率(learning rate)と呼ばれ、また重みやバイアスとは違い人の手によって設定される値であることからハイパーパラメータと呼ばれるものの一種
学習率は基本的に大きすぎても小さすぎても「良い場所」にたどり着くことができない 大きすぎる場合最小値を飛び越えて進んでしまい、小さすぎても最小値にたどり着くのに時間がかかりすぎてしまう
ではpythonでの実装

def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x
    for i in range(step_num):
        grad = numerical_gradient(f, x)
        x -= lr * grad
    return x

引数fはファンクション、init_xは初期値、lrはラーニングレート、step_numは勾配法の繰り返しの回数 関数の勾配は前回実装したnumerical_gradientを利用し、返した値にlrをかけて更新、それをstep_numをリスト化してforループで規定回繰り返す
さて果たしてうまく最小値、および極小値にたどり着けるのか

def function_2(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=o.1, step_num=100)
#array([ -6.1110793e-10, 8.14814391e-10])

最終的な結果は(-6e-10, 8e-10)と、真の最小値である(0, 0)に限りなく近い数値なので成功したといって良いでしょう
こちら更新のプロセスを可視化した図

ニューラルネットワークについても同様、本質的な仕組みは同じなので上記コードをNumPy行列にも拡張すればよい

学習アルゴリズムの実装

さてこの章で学んだことを一旦整理し、その上でそれらを組み合わせ学習アルゴリズムを実装していく

  1. 前提
    ニューラルネットワークには適応可能な重みとバイアスがあり、この重みとバイアスを訓練データに適応するように調整することを「学習」と呼ぶ

  2. ミニバッチ
    訓練データの中からランダムに一部データを選び出す

  3. 勾配の算出
    ミニバッチにおける損失関数を減らすために、各重みパラメータの勾配を算出 ここで勾配は、損失関数を最も減らす方向を示す

  4. パラメータ更新
    重みパラメータを勾配方向へ微小量だけ更新

  5. 繰り返し
    2, 3, 4を繰り返す

上記の方法では学習にミニバッチを用いてるため、確率的勾配降下法(stochastic gradient descent)と呼ばれる

二層ニューラルネット

長いです

class TwoLayerNet:
    def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
        # 重みの初期化
        self.params = {}
        self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
        self.params['b1'] = np.zeros(hidden_size)
        self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
        self.params['b2'] = np.zeros(output_size)
    def predict(self, x):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        return y
    # x:入力データ, t:教師データ
    def loss(self, x, t):
        y = self.predict(x)
        return cross_entropy_error(y, t)
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape[0])
        return accuracy
    # x:入力データ, t:教師データ
    def numerical_gradient(self, x, t):
        loss_W = lambda W: self.loss(x, t)
        grads = {}
        grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
        grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
        grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
        grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
        return grads

ながい つらい

まずW1, W2, b1, b2をそれぞれ入力層×隠れ層分、正規分布に基づいて乱数を生成する
続いてそれらを層状に、入力×W1+b1=a1 → a1×sigmoid=z1 → z1×W2+b2=a2 → a2×softmax → 出力!
loss(self, x, t)で出力した結果と正解ラベルを元に損失関数を求め、またpredictを元に出力した数列からaxis=1で最大値の場所を抽出、正解ラベルとの正答率を求める
最後にnumerical_gradientで勾配を求める

う、うーん もう厳しくなってきたぞう

ミニバッチ学習実装

上で実装したTwoLayerNetを対象にミニバッチ学習を行う
では実装

import sys, os
sys.path.append(os.pardir)  # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet

# データの読み込み
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

iters_num = 10000  # 繰り返しの回数を適宜設定する
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

for i in range(iters_num):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]
    
    # 勾配の計算
    grad = network.numerical_gradient(x_batch, t_batch)
    #grad = network.gradient(x_batch, t_batch)
    
    # パラメータの更新
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]
    
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss)
    

f:id:python_nnn:20180909163600p:plain:w200:right おおー
理論の話はわかるけどこれを実装できる気がしない

テストデータによる評価

上記のコードを実装し、徐々に損失関数の値が現象していくことを確認したが、この損失関数の値は訓練データに対する損失関数の値であり、他のデータセットにも同じ精度を発揮できるかどうかは分からない
ニューラルネットの学習において、ある特定のデータセットに適合してしまい汎用性を失うことを過学習という
それを避けるために定期的に訓練データとテストデータを対象に認識精度を記録する 本書では1エポックごとに認識精度を記録する

青線が訓練データに対する認識精度、オレンジ破線がテストデータに対する認識精度

これをみると両者ともに認識精度が上がっており、また二つとも認識精度に大きな差はないように見える
このことから今回は過学習は起きていないと言える

……なかなかに長かった上に私があまり理解できていませんが、お疲れ様でした