MNIST手寫識別筆記(二)

  • 初期學習速度慢的問題

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

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

  • 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:過度擬合規劃優化
"""

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

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函數以及偏倒
def Sigmod(z):
    return 1 / (1 + np.exp(-z))


def SigmodPrime(z):
    """對S函數求導"""
    return Sigmod(z) * (1 - Sigmod(z))


# 下面采用更加pythonic的寫法,從訓練數據和測試數據準確率以及損失函數來檢驗模型是否過度擬合

## 先定義二次損失函數和信息熵損失函數

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

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


class CrossEntropyLossFunc:
    @staticmethod
    def loss(a, y):
        # 對log函數可能會取到負無窮,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:
    # 定義一個神經網絡,也就是定義每一層的權重以及偏置
    def __init__(self, size, lossFunc):
        """
        給出每層的節點數量,包含輸出輸出層
        :param size: list
        """
        self.size = size
        self.Layers = len(size)
        self.initializeWeightBias()
        self.lossFunc = lossFunc

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

    def Feedward(self, a):
        """
        對網絡給定輸入,輸出對應的輸出
        :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):
        """
        隨機梯度下降法
        :param training_data:輸入模型訓練數據@[(input, output),...]
        :param epochs: 迭代的期數@ int
        :param minibatch_size: 每次計算梯度向量的取樣數量
        :param eta: 學習速率
        :param test_data: 訓練數據
        :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):
            # 每個迭代器抽樣前先打亂數據的順序
            random.shuffle(training_data)
            # 將訓練數據分解成多個mini_batch:???,這里講個樣本分批計算了和整體計算區別在哪???
            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)
                    # 計算測試數據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)
                # 計算訓練數據的cost 即loss
                c = 0  # 計算每個樣本的加總
                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采用梯度下降法,對網絡的權重進行更新
        :param mini_batch:
        :param eta:
        :return:
        """
        # 用來保存一個計算把周期的權重變換和
        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)]

        # 改變, 原始權重減去改變權重的均值
        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)
        # 獲取沒層的加權輸入
        zs = []  # 每層的加權輸入向量, 第一層沒有(輸入層)
        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)  # 從第二層開始保存帶權輸入,size-1個
            activations.append(Sigmod(z))  # 輸出信號a
            # print('a', Sigmod(z))
        # print(zs)
        # print(activations)
        # 計算輸出層L每個節點的delta
        '''學習速度初期過慢由于SigmodPrime函數導致, 通過采用交叉信息熵作為損失函數,可以完美的約掉它'''
        delta_L = self.lossFunc.delta(activations[-1], y, zs[-1])  # 每個節點輸出與y之差 乘 S 在z的偏導數
        # 輸出成L的c對b偏倒等于delta_L
        Cprime_bs = [delta_L]
        # c對w的騙到等于前一層的輸出信號裝置乘當前層的誤差
        Cprime_ws = [np.array(np.mat(delta_L).T * np.mat(activations[-2]))]
        # 計算所有的層的誤差
        temp = delta_L
        # 最后一層向前推
        for i in range(1, self.Layers - 1):
            # 僅僅需要計算到第二層(且最后一層已知),當前層的delta即b可以用下一層的w、delta表示和當前z表示
            # 從倒數第二層開始求解
            '''向前遞推計算輸出層前面層的delta時,還會乘上S函數對z 的偏倒'''
            x1 = (self.weight[-i]).T.dot(temp)  # 下一層的權重的裝置乘下一層的delta
            x2 = SigmodPrime(zs[-i - 1])  # 當前層的帶權輸入
            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:返回預測正確的數量@int
        """
        # 最大數字位置相對應記為正確
        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

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

下面放大accuracy的效果圖。


accuracy.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容