- 初期學(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í)比較慢的原因。
- 解決辦法
- 采用交叉信息熵?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))
- 對輸出層做出改變采用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