- 初期學習速度慢的問題
此系列文章一中算法規則3可已看出,在模型學習的過程中。修正取決于s與對誤差的學習(a - y),然采用二次損失函數,學習誤差需要乘以一個輸出層S函數對節點帶權輸入敏感度向量,而這個數取值(0-1)的一個s類型的函數!這是導致在誤差比較大的時候(訓練的初期)學習比較慢的原因。
- 解決辦法
- 采用交叉信息熵損失函數, 在求導的過程中可以約掉s函數對z的偏倒, 即不需要乘以一個輸出層S函數對節點帶權輸入敏感度向量。下面是針對一個樣本求解,多個加和除以樣本數n即可:
crossEntropy = - (y * log(a) + (1 - y) * log(1 - a))
- 對輸出層做出改變采用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