ML課堂作業,第五個是三層前向網絡,老數據集ex4Data,首先把作業要求貼上來,一共可以分爲兩個部分,分別是手寫版和tensorflow版,因爲三層前向網絡已經不是線性模型,所以也無法繪製出分類線,通過計算準確性來評估模型。
數據集是老數據集了,需要下載的可以在圖中或前面的文章找到鏈接。三層前向網絡是一個非常基礎的神經網絡,可以看成是若干個感知機的複合,可以選擇任意激活函數,這裏爲了求導方便選擇sigmoid,激活函數的作用是引入非線性變化,使得網絡可以擬合非線性函數,下面給出網絡結構
輸入層的xi表示樣本的每一個特徵,輸出層的yi表示樣本屬於第i類的概率,因爲三層前向網絡已經不是線性模型,所以我們無法向前面的模型一樣在樣本圖上畫出分類線,所以我們用正確率來評估模型的性能,爲了避免過擬合以及能夠準確評估模型,我們使用五重交叉檢驗,並取測試集上正確率的平均值作爲模型最後的正確率。
訓練過程的幾個關鍵如上圖,數據集以矩陣對的形式,其中樣本x共有d個特徵,可分爲L個類別,我們還可以看出這種全連接的網絡參數數量非常之大,在深度學習中這種全連接層一般放在最後或者乾脆不用,最優化方法採用梯度下降,則對每個參數都要求對應的梯度,由於前向網絡事實上是一個線性函數和激活函數的多重複合,所以梯度求導將運用鏈式法測,對鏈式法則不熟悉的同學可以回顧一下高數課本。
簡單介紹一下交叉驗證,我們學習模型的最終目的是在未知的樣本上獲得更好的預測性能,而不是追求在訓練集上的最好性能,雖然訓練集能一定程度的反應未知樣本的特性,但可能存在無法反映的或者只有訓練集中才有的特性,被我們的學習模型學得,這樣會降低模型的泛化能力,所以我們並不使用整個數據集都來訓練,我們選取一部分數據來訓練一部分不參加訓練用作測試。
k折交叉驗證法就是將整個數據集分爲不相交的k個部分,然後選取其中的一個部分爲測試集,其他部分爲訓練集,獲得測試準確率,然後重複上述步驟,直到每個部分都充當過一次測試集,我們以平均準確率作爲模型的準確率。
學習過程分爲兩個步驟,首先是數據的前向傳播,也就是讓樣本通過整個網絡得到當前網絡的輸出,然後是誤差的反向傳播,根據輸出和標籤以及對參數求梯度的結果,反向逐步計算出各參數的梯度值,叫做誤差的反向傳播,通過課件中的圖片來簡單講解一下。
首先我們計算隱層和輸出層之間的權值的梯度
這是一個簡單的鏈式求導,我們定義了OutputLayer_error,輸出層神經元偏置的梯度則爲
緊接着求輸入層到隱層的連接權值的梯度:
我們可以看到前面我們計算過的OutputLayer_error在這裏用上了,並且計算下一層的梯度就是上一層的誤差乘上本層的本地梯度,便可以得到本層的誤差,在更多層的前向網絡中梯度的計算和這裏並無太大差別,無非就是傳遞的層數多一些,這看上去與很像誤差從輸出層反向傳播至輸入層,所以叫做BP(反向傳播)算法,結合CS231N中計算圖來理解將會更好理解BP算法,將每種計算(例如:加減乘除、sigmoid)或者說每個操作看成一個f函數並以一個計算節點表示,計算節點輸入層的梯度就等於輸出層傳回的梯度乘上本地f對輸入層的梯度,這樣對無法求解析式的複雜網絡我們也可以計算對某個樣本值的梯度,這使得bp算法成爲現在神經網絡中廣泛運用的算法。
最後我們有隱層偏置的梯度:
下面給出代碼:
import numpy as np
import matplotlib.pyplot as plt
import nolinear
learn_rate = 0.05
output_size = 2
hidden_size = 7
data_x = np.loadtxt("ex4Data/ex4x.dat")
data_y = np.loadtxt("ex4Data/ex4y.dat")
mean = data_x.mean(axis=0)
variance = data_x.std(axis=0)
data_x = (data_x-mean)/variance
data_y = data_y.reshape(-1, 1) # 拼接
temp = np.ones(data_y.size)
data_x = np.c_[data_x, temp]
y = np.zeros([data_y.size, output_size])
for i in range(data_y.size):
if data_y[i] == 0:
y[i][0] = 1
if data_y[i] == 1:
y[i][1] = 1
data_sets = list()
for i in range(data_y.size):
if i % 8 == 0 and i != 0:
data_sets.append(data_x[i-8:i, :])
if i == data_y.size-1:
data_sets.append(data_x[i-7:i+1, :])
positive_x = data_x[0:40, :]
positive_label = y[0:40, :]
negative_x = data_x[40:80, :]
negative_label = y[40:80, :]
total = 0
for j in range(5):
x_ = np.r_[np.delete(positive_x, range(j*8, (j+1)*8), 0), np.delete(negative_x, range(j*8, (j+1)*8), 0)]
y_ = np.r_[np.delete(positive_label, range(j*8, (j+1)*8), 0), np.delete(negative_label, range(j*8, (j+1)*8), 0)]
test_x = np.r_[data_sets[j], data_sets[j+5]]
test_label = np.r_[data_y[j*8:(j+1)*8], data_y[(j+5)*8:(j+6)*8]]
# data_x = np.mat(data_x)
data_x = np.mat(x_)
temp = np.ones(data_y.size-16)
weight_input = np.mat(np.random.normal(size=(data_x.shape[1], hidden_size)))
weight_hidden = np.mat(np.random.normal(size=(hidden_size+1, output_size)))
steps = 600
loss_values = list()
for i in range(steps):
# 前向傳播過程
hidden_input = data_x*weight_input # ah+yh
hidden_out = nolinear.sigmods(hidden_input) # bn
hidden_out_ = np.c_[hidden_out, temp]
# print(hidden_out) # weight_hidden 最後一行纔是bias
output_input = hidden_out_*weight_hidden
output = nolinear.sigmods(output_input)
# 計算損失
loss = 0.5*np.sum(np.multiply(output-y_, output-y_))
loss_values.append(loss)
# 反向傳播過程
output_error = np.multiply(np.multiply(output-y_, output), 1-output)
dew_hidden = hidden_out_.T*output_error
output_error_ = dew_hidden[7]
weight_hidden_ = np.delete(weight_hidden, 7, axis=0)
hidden_error = np.multiply(np.multiply(output_error_*weight_hidden_.T, hidden_out), 1-hidden_out)
dew_input = data_x.T*hidden_error
weight_hidden = weight_hidden-learn_rate*dew_hidden
weight_input = weight_input-learn_rate*dew_input
plt.plot(loss_values)
plt.show()
temp = np.ones(test_label.size)
hidden_input = test_x*weight_input
hidden_out = nolinear.sigmods(hidden_input)
hidden_out_ = np.c_[hidden_out, temp]
output_input = hidden_out_*weight_hidden
output = nolinear.sigmods(output_input)
count = 0
print(output)
print(test_label)
output = np.array(output)
for i in range(test_label.size):
outs = output[i].ravel()
outs = outs.tolist()
if int(test_label[i]) == outs.index(max(outs)):
count = count+1
print("test accuracy", count/test_label.size)
total = total+count/test_label.size
print("accuracy:", total/5)
程序會畫出每次的loss值變化以及打印每次驗證的準確率和最後的平均準確率,數據劃分是有序的,如果改成無序則每次檢驗其準確率差距會更小,注意保證訓練子集和測試子集在概率分佈上與整個數據集一致。
第一次檢驗,用編號爲0-7的正例和40-47的反例爲測試集其餘爲訓練集。
第二次檢驗是以編號爲8-15的正例和48-55的反例爲測試集,其餘爲訓練集:
第三次以及後以此類推:
第四次:
第五次以及最後平均準確率:
最後平均準確率是80%,因爲是有序劃分,第三第四測試集可能數據都集中在中間混雜在一起的位置,所以測試準確率比較低,對程序稍作修改使其輸出每次訓練集上的準確率:
可以看到在第四次檢測模型表現得很不好,這基本上就是本次的全部工作,後續可能有更深一步的探究,待更新……