Machine Learning(Andrew) ex4-Neural Networks Learning
椰汁筆記
Neural Networks
- 1.1 Visualizing the data
- 1.2 Model representation
由於本次作業的數據和網絡結構和上次的作業相同,因此這兩步已經在上次作業中完成,這裏不再贅述。
- 1.3 Feedforward and cost function
當前網絡的前向傳播計算方法
最後利用輸出層計算cost
這裏的y需要注意代表的意義
數字 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |
---|---|---|---|---|---|---|---|---|---|---|
對應的向量下標 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a1 = X # (5000,400)
a1 = np.insert(a1, 0, 1, axis=1) # (5000,401)
z2 = a1.dot(theta1.T) # (5000,25)
a2 = sigmoid(z2) # (5000,25)
a2 = np.insert(a2, 0, 1, axis=1) # (5000,26)
z3 = a2.dot(theta2.T) # (5000,10)
a3 = sigmoid(z3) # (5000,10)
cost = np.mean(np.sum((-y) * np.log(a3) - (1 - y) * np.log(1 - a3), axis=1))
print(cost)#0.2876291651613189
在封裝裝成函數時,發現這個其實可以寫成一個循環去處理每層。
在傳入theta時需要考慮到後面使用高級的優化方法時theta只能是一維向量,所有我們需要在傳入多個theta時先展開成一個向量,在內部再恢復
def serialize(thetas):
"""
將多個參數多維數組,映射到一個向量上
:param thetas: tuple or list,按順序存儲每層的theta參數,每個爲ndarray
:return: ndarray,一維化的參數向量
"""
res = np.array([0])
for t in thetas:
res = np.concatenate((res, t.ravel()), axis=0)
return res[1:]
def deserialize(theta):
"""
將向量還原爲多個參數(只適用當前網絡)
:param theta: ndarray,一維化的參數向量
:return: tuple ,按順序存儲每層的theta參數,每個爲ndarray
"""
return theta[:25 * 401].reshape(25, 401), theta[25 * 401:].reshape(10, 26)
直接使用循環進行計算,每層計算的操作都是先添加偏置單元,再計算z,再計算A。
def not_regularized_cost(thetas, X, y):
"""
計算非正則化的損失值
:param theta: ndarray,一維參數向量
:param X: ndarray,輸入層的輸入值
:param y: ndarray,數據的標記
:return: float,損失值
"""
for t in deserialize(thetas):
X = np.insert(X, 0, 1, axis=1)
X = X.dot(t.T)
X = sigmoid(X)
return np.mean(np.sum((-y) * np.log(X) - (1 - y) * np.log(1 - X), axis=1))
從邏輯迴歸那裏我們知道,由於特徵太多,可能會出現過擬合的現象,需要正則化來解決
直接在之前的損失函數計算中,加入懲罰項即可。注意不要懲罰偏執單元。
def regularized_cost(theta, X, y, l):
"""
計算正則化的損失值
:param theta: ndarray,一維參數向量
:param X: ndarray,輸入層的輸入值
:param y: ndarray,數據的標記
:param l: float,懲罰參數
:return: float,損失值
"""
m = X.shape[0]
part2 = 0.0
for t in deserialize(theta):
X = np.insert(X, 0, 1, axis=1)
X = X.dot(t.T)
X = sigmoid(X)
t = t[..., 1:] # 要去掉bias unit
part2 += (l / (2 * m)) * np.sum(t * t)
part1 = np.mean(np.sum((-y) * np.log(X) - (1 - y) * np.log(1 - X), axis=1))
return part1 + part2
print(regularized_cost(theta, X, y, 1))#0.38376985909092365
Backpropagation
反向傳播算法爲梯度下降的計算提供了一個方法。這裏我們先不考慮起原理,直接動手實現。
反向傳播算法,細節舉例(這裏以課程中的4層網絡舉例)
這裏沒有d1要注意!!!
完整的反向傳播算法
- 2.1 Sigmoid gradient
在計算d時需要用到sigmoid函數的導數,這裏很簡單可以自己推到一下,這裏我們實現
def sigmoid_gradient(z):
return sigmoid(z) * (1 - sigmoid(z))
print(sigmoid_gradient(0))#0.25
- 2.2 Random initialization
神經網絡的參數初始化是非常講究的,不能像以前一樣全部初始化爲0,這樣會導致所有隱藏單元計算的值相同高度冗餘,這裏選擇將參數隨機化到一個[-e,e]的範圍內,這裏的e選擇方法
def random_initialize_weights(shape, e=0.12):
"""
隨機初始化參數,範圍爲[-e, e]
:param shape: tuple or list,需要初始化的參數的規格
:param e: float,邊界
:return: ndarray,參數矩陣
"""
return (np.random.rand(shape[0], shape[1]) - 0.5) * 2 * e
- 2.3 Backpropagation
直接按照反向傳播算法做就好
def back(theta, X, y, l):
"""
反向傳播算法
:param theta: ndarray,一維參數向量
:param X: ndarray,輸入層的輸入值
:param y: ndarray,數據的標籤
:param l: float,懲罰參數
:return: ndarray,下降後一維參數向量
"""
A, Z = feedforward(theta, X)
a1, a2, a3 = A # a1(5000,401), a2(5000,26), a3(5000,10)
z2, z3 = Z # z2(5000,25), z3(5000,10)
theta1, theta2 = deserialize(theta) # theta1(25,401), theta2(10,26)
m = X.shape[0]
d3 = a3 - y # d3(5000,10)
d2 = d3.dot(theta2)[..., 1:] * sigmoid_gradient(z2) # d2(5000,25)
theta1 = np.insert(np.delete(theta1, 0, axis=1), 0, 0, axis=1)
theta2 = np.insert(np.delete(theta2, 0, axis=1), 0, 0, axis=1)
D1 = (1 / m) * d2.T.dot(a1) + (l / m) * theta1 # D1(25,401)
D2 = (1 / m) * d3.T.dot(a2) + (l / m) * theta2 # D2(10,26)
return serialize((D1, D2))
- 2.4 Gradient checking
爲了方便檢測我們的梯度下降算法是否正確,我們採用近似計算的方式來對比
def gradient_checking(theta, X, y, l, e=10 ** -4):
"""
檢測反向傳播算法是否正確運行
:param theta: ndarray,一維參數向量
:param X: ndarray,輸入層的輸入值
:param y: ndarray,數據的標籤
:param l: float,懲罰參數
:param e: float,微小擾動
:return: ndarray,下降後一維參數向量
"""
res = np.zeros(theta.shape)
for i in range(len(theta)):
left = np.array(theta)
left[i] -= e
right = np.array(theta)
right[i] += e
gradient = (regularized_cost(right, X, y, l) - regularized_cost(left, X, y, l)) / (2 * e)
res[i] = gradient
return res
這個計算是非常耗時的,應用到一部分計算中對比結果,檢測反向傳播算法是否正確。之後要拿出。
- 2.5 Regularized Neural Networks
在上面我就一併做了正則化
- 2.6 Learning parameters using fmincg
下面開始訓練我們的模型,這裏建議訓練完後將參數保存一下,因爲訓練時間還是比較久,免得後面重跑。
# 以上是爲了測試寫得是否正確,所以按照提供數據更改了y,下面我們將使用最自然的y表示方式
y = convert(data['y'])
theta1 = random_initialize_weights((25, 401))
theta2 = random_initialize_weights((10, 26))
theta = serialize((theta1, theta2))
# 參數初始化得不同,有時會導致溢出
res = opt.minimize(fun=regularized_cost, x0=theta, args=(X, y, 1), method="TNC", jac=back)
print(res)
theta1, theta2 = deserialize(res.x)
sio.savemat("parametersWeights.mat", {"theta1": theta1, "theta2": theta2})
接着評價一下結果
def predict(theta, X):
a3 = feedforward(theta, X)[0][-1]
p = np.zeros((1, 10))
for i in a3:
index = np.argmax(i)
temp = np.zeros((1, 10))
temp[0][index] = 1
p = np.concatenate((p, temp), axis=0)
return p[1:]
print(classification_report(y, predict(res.x, X)))
visualizing_the_hidden_layer(res.x, X)
可以看到,神經網絡相較於邏輯迴歸擬合得更好,但是計算量確實大,我們這裏只有一個隱藏層久需要計算數分鐘,很難想象大型網絡得計算需要多大得計算資源。當然我們這裏得神經網絡是最簡單得全連接神經網絡,參數確實多。神經網絡還有其他帶有非全連接得網絡結構,這些結構會使參數量下降。
Visualizing the hidden layer
最後我麼可以看看隱藏層輸出得圖像是個什麼樣子
注意在計算後顯示要去掉偏執單元
def visualizing_the_hidden_layer(theta, X):
"""
可視化顯示隱藏層的輸入和輸出
:param theta: ndarray,一維參數向量
:param X: ndarray,輸入層的輸入值
:return: None
"""
A, _ = feedforward(theta, X)
a1, a2, a3 = A
# 要去掉bias unit
input = a1[..., 1:][:25]
output = a2[..., 1:][:25]
input = mapping(input, 5)
output = mapping(output, 5)
plt.subplot(1, 2, 1)
plt.axis('off')
plt.imshow(input.T)
plt.title("hidden layer input")
plt.subplot(1, 2, 2)
plt.axis('off')
plt.imshow(output)
plt.title("hidden layer output")
plt.show()
只觀察0得數據,可以看到,輸出後得圖像是存在一些相似。
總結一下簡單的神經網絡使用方法
- 1.選擇一個網絡結構
- 將當前得特徵數量作爲輸入層得單元數量
- 將分類得類數作爲輸出單元數量
- 當隱藏層大於1層時,通常所有隱藏層得單元數一樣
- 2.訓練網絡
- 隨機初始化單元連接之間得權重
- 實現前向傳播
- 實現計算損失值
- 實現反向傳播
- 實現梯度下降
- 最小化損失值
- 3.評價模型
- 在訓練網絡前,需要將數據隨機分爲三部分,作爲訓練集、交叉驗證集和測試集
- 使用訓練集訓練網絡
- 交叉驗證集用於,當需要從多個網絡模型中擇優時,利用交叉驗證集計算誤差,通過量化的方式選擇,模型
- 測試集用於最終評價模型
完整的代碼會同步在我的github
歡迎指正錯誤