MNIST手寫識別筆記(二)

  • 初期學(xué)習(xí)速度慢的問題

此系列文章一中算法規(guī)則3可已看出,在模型學(xué)習(xí)的過程中。修正取決于s與對誤差的學(xué)習(xí)(a - y),然采用二次損失函數(shù),學(xué)習(xí)誤差需要乘以一個(gè)輸出層S函數(shù)對節(jié)點(diǎn)帶權(quán)輸入敏感度向量,而這個(gè)數(shù)取值(0-1)的一個(gè)s類型的函數(shù)!這是導(dǎo)致在誤差比較大的時(shí)候(訓(xùn)練的初期)學(xué)習(xí)比較慢的原因。

  • 解決辦法
  1. 采用交叉信息熵?fù)p失函數(shù), 在求導(dǎo)的過程中可以約掉s函數(shù)對z的偏倒, 即不需要乘以一個(gè)輸出層S函數(shù)對節(jié)點(diǎn)帶權(quán)輸入敏感度向量。下面是針對一個(gè)樣本求解,多個(gè)加和除以樣本數(shù)n即可:
      crossEntropy = - (y * log(a) + (1 - y) * log(1 - a))
  1. 對輸出層做出改變采用softmax作為輸出層節(jié)點(diǎn)函數(shù),同時(shí)采用似然函數(shù)作為損失函數(shù),這里不進(jìn)一步將,后面章節(jié)會(huì)繼續(xù)探討。
  • 下面code使用交叉信息熵作為損害函數(shù),并在前面的代碼基礎(chǔ)上做了一些小幅度的更改。增加模型訓(xùn)練的準(zhǔn)確率以及損失函數(shù)的變化趨勢圖, 為下面探討模型的過度擬合做一個(gè)鋪墊。

  • code

# encoding: utf-8

"""
@version: python3.5.2 
@author: kaenlee  @contact: lichaolfm@163.com
@software: PyCharm Community Edition
@time: 2017/8/13 20:38
purpose:過度擬合規(guī)劃優(yōu)化
"""

# 相對于前面的章節(jié),這里采用交叉信息熵函數(shù)作為損失函數(shù),好處是解決訓(xùn)練初期速度慢的問題。類似還有采用輸出層為softmax與對數(shù)似然函
# 數(shù)結(jié)合解決這一問題

import numpy as np
from tensorflow.examples.tutorials.mnist import input_data
import random
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mp

mp.style.use('ggplot')

mnist = input_data.read_data_sets(r'D:\PycharmProjects\HandWritingRecognition\TF\data', one_hot=True)
training_data = list(zip(mnist.train.images, mnist.train.labels))
test_data = list(zip(mnist.test.images, mnist.test.labels))


# 定義s函數(shù)以及偏倒
def Sigmod(z):
    return 1 / (1 + np.exp(-z))


def SigmodPrime(z):
    """對S函數(shù)求導(dǎo)"""
    return Sigmod(z) * (1 - Sigmod(z))


# 下面采用更加pythonic的寫法,從訓(xùn)練數(shù)據(jù)和測試數(shù)據(jù)準(zhǔn)確率以及損失函數(shù)來檢驗(yàn)?zāi)P褪欠襁^度擬合

## 先定義二次損失函數(shù)和信息熵?fù)p失函數(shù)

class QuadraticLossFunc:
    @staticmethod  # 不需要實(shí)例化即可調(diào)用
    def loss(a, y):
        # 對于單個(gè)樣本的輸出a和真實(shí)y的誤差向量a-y取二次范數(shù),然后平方除2
        return 1 / 2 * np.linalg.norm(a - y) ** 2

    @staticmethod
    def delta(a, y, z):
        """
        計(jì)算輸出層L的誤差向量delta
        :param a: L的輸出
        :param y: 真實(shí)
        :param z: L的帶權(quán)輸入
        :return:
        """
        return (a - y) * SigmodPrime(z)


class CrossEntropyLossFunc:
    @staticmethod
    def loss(a, y):
        # 對log函數(shù)可能會(huì)取到負(fù)無窮,nan_to_num, nan-0, (-)inf-(-)sys.maxsize
        return np.nan_to_num(-(y * np.log(a) + (1 - y) * np.log(1 - a)))

    @staticmethod
    def delta(a, y, z):
        # L的誤差向量即偏倒(C-b)
        return a - y


class NetWorks:
    # 定義一個(gè)神經(jīng)網(wǎng)絡(luò),也就是定義每一層的權(quán)重以及偏置
    def __init__(self, size, lossFunc):
        """
        給出每層的節(jié)點(diǎn)數(shù)量,包含輸出輸出層
        :param size: list
        """
        self.size = size
        self.Layers = len(size)
        self.initializeWeightBias()
        self.lossFunc = lossFunc

    def initializeWeightBias(self):
        # 普通的初始化權(quán)重方法, 后面會(huì)給出更好的
        self.bias = [np.random.randn(num) for num in self.size[1:]]  # 輸入層沒有bias
        # 每層的權(quán)重取決于row取決于該層的節(jié)點(diǎn)數(shù)量,從來取決于前面一層的輸出即節(jié)點(diǎn)數(shù)
        self.weight = [np.random.randn(row, col) for row, col in zip(self.size[1:], self.size[:-1])]

    def Feedward(self, a):
        """
        對網(wǎng)絡(luò)給定輸入,輸出對應(yīng)的輸出
        :param a:@iterable給定的輸入向量
        :return:
        """
        a = np.array([i for i in a])  # 輸入向量
        for b, w in zip(self.bias, self.weight):
            z = w.dot(a) + b  # 帶全輸入信號
            a = Sigmod(z)  # 輸出信號
        return a

    def SGD(self, training_data, epochs, minibatch_size, eta, test_data=None, isplot=False):
        """
        隨機(jī)梯度下降法
        :param training_data:輸入模型訓(xùn)練數(shù)據(jù)@[(input, output),...]
        :param epochs: 迭代的期數(shù)@ int
        :param minibatch_size: 每次計(jì)算梯度向量的取樣數(shù)量
        :param eta: 學(xué)習(xí)速率
        :param test_data: 訓(xùn)練數(shù)據(jù)
        :return:
        """
        if test_data:
            n_test = len(test_data)
        n = len(training_data)
        accuracy_train = []
        accuracy_test = []
        cost_train = []
        cost_test = []
        for e in range(epochs):
            # 每個(gè)迭代器抽樣前先打亂數(shù)據(jù)的順序
            random.shuffle(training_data)
            # 將訓(xùn)練數(shù)據(jù)分解成多個(gè)mini_batch:???,這里講個(gè)樣本分批計(jì)算了和整體計(jì)算區(qū)別在哪???
            mini_batches = [training_data[k:(k + minibatch_size)] for k in range(0, n, minibatch_size)]
            for batch in mini_batches:
                # print('bias', self.bias)
                self.Update_miniBatchs(batch, eta)

            if test_data:
                totall_predRight = self.Evalueate(test_data)
                print('Epoch {0}: {1}/{2}'.format(e, totall_predRight, n_test))

                if isplot:
                    accuracy_test.append(totall_predRight / n_test)
                    # 計(jì)算測試數(shù)據(jù)c
                    c = 0
                    for x, y in test_data:
                        c += self.lossFunc.loss(self.Feedward(x), y)
                    cost_test.append(c / n_test)

            if isplot:
                accuracy_train.append(self.Evalueate(training_data) / n)
                # 計(jì)算訓(xùn)練數(shù)據(jù)的cost 即loss
                c = 0  # 計(jì)算每個(gè)樣本的加總
                for x, y in training_data:
                    c += self.lossFunc.loss(self.Feedward(x), y)
                cost_train.append(c / n)

        if isplot:
            plt.plot(np.arange(1, epochs + 1), accuracy_train, label='train')
            plt.plot(np.arange(1, epochs + 1), accuracy_test, label='test')
            plt.xlabel('epoch')
            plt.legend()
            plt.savefig('accuracy.png')

            plt.plot(np.arange(1, epochs + 1), cost_train, label='train')
            plt.plot(np.arange(1, epochs + 1), cost_test, label='test')
            plt.xlabel('epoch')
            plt.legend()
            plt.savefig('cost.png')

    def Update_miniBatchs(self, mini_batch, eta):
        """
        對mini_batch采用梯度下降法,對網(wǎng)絡(luò)的權(quán)重進(jìn)行更新
        :param mini_batch:
        :param eta:
        :return:
        """
        # 用來保存一個(gè)計(jì)算把周期的權(quán)重變換和
        B_change = [np.zeros(b.shape) for b in self.bias]
        W_change = [np.zeros(w.shape) for w in self.weight]
        for x, y in mini_batch:
            Cprime_bs, Cprime_ws = self.BackProd(x, y)
            B_change = [i + j for i, j in zip(B_change, Cprime_bs)]
            W_change = [i + j for i, j in zip(W_change, Cprime_ws)]

        # 改變, 原始權(quán)重減去改變權(quán)重的均值
        n = len(mini_batch)
        # print('change bias', B_change)
        self.bias = [bias - eta / n * change for bias, change in zip(self.bias, B_change)]
        self.weight = [weight - eta / n * change for weight, change in zip(self.weight, W_change)]

    def BackProd(self, x, y):
        """
        反向算法
        :param x: iterable,
        :param y: iterable
        :return:
        """
        x = np.array(x)
        y = np.array(y)
        # 獲取沒層的加權(quán)輸入
        zs = []  # 每層的加權(quán)輸入向量, 第一層沒有(輸入層)
        activations = [x]  # 每層的輸出信號,第一層為x本身
        for b, w in zip(self.bias, self.weight):
            # print(w.shape)
            # print("z", activations[-1])
            z = w.dot(activations[-1]) + b
            zs.append(z)  # 從第二層開始保存帶權(quán)輸入,size-1個(gè)
            activations.append(Sigmod(z))  # 輸出信號a
            # print('a', Sigmod(z))
        # print(zs)
        # print(activations)
        # 計(jì)算輸出層L每個(gè)節(jié)點(diǎn)的delta
        '''學(xué)習(xí)速度初期過慢由于SigmodPrime函數(shù)導(dǎo)致, 通過采用交叉信息熵作為損失函數(shù),可以完美的約掉它'''
        delta_L = self.lossFunc.delta(activations[-1], y, zs[-1])  # 每個(gè)節(jié)點(diǎn)輸出與y之差 乘 S 在z的偏導(dǎo)數(shù)
        # 輸出成L的c對b偏倒等于delta_L
        Cprime_bs = [delta_L]
        # c對w的騙到等于前一層的輸出信號裝置乘當(dāng)前層的誤差
        Cprime_ws = [np.array(np.mat(delta_L).T * np.mat(activations[-2]))]
        # 計(jì)算所有的層的誤差
        temp = delta_L
        # 最后一層向前推
        for i in range(1, self.Layers - 1):
            # 僅僅需要計(jì)算到第二層(且最后一層已知),當(dāng)前層的delta即b可以用下一層的w、delta表示和當(dāng)前z表示
            # 從倒數(shù)第二層開始求解
            '''向前遞推計(jì)算輸出層前面層的delta時(shí),還會(huì)乘上S函數(shù)對z 的偏倒'''
            x1 = (self.weight[-i]).T.dot(temp)  # 下一層的權(quán)重的裝置乘下一層的delta
            x2 = SigmodPrime(zs[-i - 1])  # 當(dāng)前層的帶權(quán)輸入
            delta_now = x1 * x2
            Cprime_bs.append(delta_now)
            Cprime_ws.append(np.array(np.mat(delta_now).T * np.mat(activations[-i - 2])))
            temp = delta_now

        # 改變輸出的順序
        Cprime_bs.reverse()
        Cprime_ws.reverse()
        return (Cprime_bs, Cprime_ws)

    def Evalueate(self, test_data):
        """
        評估模型
        :param test_data:
        :return:返回預(yù)測正確的數(shù)量@int
        """
        # 最大數(shù)字位置相對應(yīng)記為正確
        res_pred = [np.argmax(self.Feedward(x)) == np.argmax(y) for x, y in test_data]
        return sum(res_pred)


if __name__ == '__main__':
    net = NetWorks([784, 20, 10], CrossEntropyLossFunc)
    # print(net.Feedward([1, 1, 1]))
    # print(net.BackProd([1, 1, 1], [1, 0]))
    # net.Update_miniBatchs([([1, 1, 1], [1, 0]), ([0, 0, 0], [0, 1])], 0.1)
    net.SGD(training_data, 30, 10, 3, test_data, True)
  • 過度擬合問題
cost.png

上面兩條曲線為準(zhǔn)確率的變換情況,下面面loss函數(shù)變換情況。圖中accuracy和cost的變換趨勢都反映了模型雖則迭代期數(shù)變換。就訓(xùn)練數(shù)據(jù)而言,accuracy和cost均在近似相同的期數(shù)收斂到最佳(存在不一一致的情況以accuracy為準(zhǔn),模型很快就收斂!?。。?。相對訓(xùn)練數(shù)據(jù),訓(xùn)練數(shù)據(jù)模型在測試達(dá)到最優(yōu)化后還在緩慢的優(yōu)化,識別率接近99.5%。結(jié)論:模型訓(xùn)練數(shù)據(jù)存在過度擬合的情況。

下面放大accuracy的效果圖。


accuracy.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容