DeepLearning実装 #1

名著『ゼロから作るDeepLearning』を読み終わったので特に重要であった4,5,6,7章について4回に渡って解説をしたいと思う.
第1回目は4章の「ニューラルネットワークの学習」である.

4.1 データから学習する

ニューラルネットワークは他の機械学習の手法と違ってデータから学習できるというのが特徴.
すなわち重みパラメータの値をデータから自動で決定できる.
例えば手書き文字の認識を行いたいとき機械学習は人間が決めた「角がいくつあるか?」「横線がいくつあるか?」などの特徴量を抽出する.
しかしニューラルネットワークではその過程を自動でやってくれるため, 手書き文字の画像を突っ込めばよいだけである.

4.2 損失関数

2乗和誤差や交差エントロピー誤差がよく用いられる.
交差エントロピー誤差とは E = -\sum_{k}t_{k}\log{y_{k}} で表されます.
ここで t_{k} はone-hot-vectorなのでEは実質的に正解ラベルのものの確率の対数にマイナスをつけたものである.
例えば正解ラベルが「2」のときt = numpy.array([0,0,1,0,0,0,0,0,0,0])となっており y_{2} = 0.6ならば
 E = -\log{0.6} = 0.51 となる.
f:id:umashika5555:20170626082644p:plain
上図は y = -log(x)のグラフである. xが1に近づくほど(入力手書き文字画像が「2」である確率が1に近づくと)損失が少ないということがわかる.

ここでミニバッチ学習を適用すると損失関数は「バッチすべてのデータにおける訓練データ1つあたりの損失」となるので損失関数の定義は
 E = -\frac{1}{N}\sum_{n}\sum_{k}t_{nk}\log{y_{nk}}となる.

ミニバッチをランダムに取り出すときのテクニックとしてランダムに選ぶ方法がある.

#0-60000未満の数字の中からランダムに10個の数字を選ぶ
np.random.choice(60000,10)

バッチに対応した交差エントロピー誤差の実装として
正解ラベルがone-hot-vectorの場合とargmax(one-hot-vector)の2つの場合が考えられる.
どちらでも対応出来るように処理を書くと下のようになる.

def cross_entropy_error(y, t):
    """損失関数:クロスエントロピー誤差"""
    if y.ndim == 1:
        t = t.reshape(1, t.size)
        y = y.reshape(1, y.size)
    # 教師データがone-hot-vectorの場合、正解ラベルのインデックスに変換
    if t.size == y.size:
        t = t.argmax(axis=1)
    batch_size = y.shape[0]
    return -np.sum(np.log(y[np.arange(batch_size), t])) / batch_size

4.3 数値微分

損失関数を小さくするために勾配の情報を用いる.
勾配を得るために数値微分が必要となる.
微分の定義は \frac{df(x)}{dx} = \lim_{h \to 0}\frac{f(x+h)-f(x)}{h}である.
Pythonで実装する際にこのhは十分に小さい値だからといって1e-50などを選択してはいけない.
なぜなら丸め誤差の影響で計算機上では0と同値であるからである.
数値微分の誤差を減らす工夫として(x+h)と(x-h)での関数fの差分を計算して
 \frac{df(x)}{dx} = \lim_{h \to 0}\frac{f(x+h)-f(x-h)}{2h}より

def numeric_diff(f,x):
    h = 1e-4
    return (f(x+h)-f(x-h))/(2*h)

次に勾配法について説明する.
各地点において関数の値を最も減らす方向を示すのが勾配.
勾配が指す先は関数が小さくなる方向ではあるが関数の最小値とは限らないのが注意.
入力が x_{0},x_{1}とすると勾配法は
 x_{0} = x_{0}-\eta\frac{\partial f}{\partial x_{0}}
 x_{1} = x_{1}-\eta\frac{\partial f}{\partial x_{1}}
 \etaは学習率
関数の勾配がわかったところでニューラルネットワークに適用する.
すなわち行列式における勾配を求める.
ニューラルネットワークの重みをWとすると
\begin{equation}W= \begin{pmatrix}w_{11} &w_{12}\\ a_{21} &a_{22}\end{pmatrix}\end{equation}
ここで損失関数LをWで微分すると
\begin{equation}\frac{\partial L}{\partial W} = \begin{pmatrix}\frac{\partial L}{\partial w_{11}} &\frac{\partial L}{\partial w_{12}}\\ \frac{\partial L}{\partial w_{21}} &\frac{\partial L}{\partial w_{22}}\end{pmatrix}\end{equation}
というように各要素でLを偏微分する.

数値微分を用いた2層ニューラルネットワークを実装する.

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 = predict(x)
        y = np.argmax(y,axis=1)
        t = np.argmax(t,axis=1)
        
        accuracy = np.sum(y == t)/float(x.shape[0])
        
    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

MNISTデータセットをダウンロードしてトレーニングする.

#MNISTのインストール

train_loss_list = []

iters_num = 10000
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.1

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

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_radient(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)

これにテストをして精度を見る.
精度に関しては次の章で改めて書くと思うので省略.