摘要:看過(guò)上的代碼并對(duì)有一定經(jīng)驗(yàn)的人會(huì)發(fā)現(xiàn),矩陣和向量被小的隨機(jī)數(shù)填充。一般來(lái)說(shuō),損失函數(shù)用來(lái)表征我們與理想解決方案的距離。損失函數(shù)和準(zhǔn)確率計(jì)算反向傳播許多缺乏經(jīng)驗(yàn)的深度學(xué)習(xí)愛(ài)好者認(rèn)為反向傳播是一種難以理解的算法。
BP(Back Propagation)神經(jīng)網(wǎng)絡(luò)是1986年由Rumelhart和McCelland為首的科學(xué)家小組提出,是一種==按誤差逆?zhèn)鞑ニ惴ㄓ?xùn)練的多層前饋網(wǎng)絡(luò)==,是目前應(yīng)用最廣泛的神經(jīng)網(wǎng)絡(luò)模型之一。BP網(wǎng)絡(luò)能學(xué)習(xí)和存貯大量的==輸入-輸出模式映射關(guān)系==,而無(wú)需事前揭示描述這種映射關(guān)系的數(shù)學(xué)方程。它的學(xué)習(xí)規(guī)則是使用梯度下降法,通過(guò)反向傳播來(lái)不斷調(diào)整網(wǎng)絡(luò)的權(quán)值和閾值,使==網(wǎng)絡(luò)的誤差平方和最小==。BP神經(jīng)網(wǎng)絡(luò)模型拓?fù)浣Y(jié)構(gòu)包括輸入層(input)、隱層(hidden layer)和輸出層(output layer)。
注:本文將包含大量用 Python 編寫的代碼片段。希望讀起來(lái)不會(huì)太無(wú)聊。
Keras、TensorFlow、PyTorch 等高級(jí)框架可以幫助我們快速構(gòu)建復(fù)雜模型。深入研究并理解其中的理念很有價(jià)值。下面嘗試只使用 NumPy 構(gòu)建一個(gè)全運(yùn)算的神經(jīng)網(wǎng)絡(luò),通過(guò)解決簡(jiǎn)單的分類問(wèn)題來(lái)測(cè)試模型,并將其與 Keras 構(gòu)建的神經(jīng)網(wǎng)絡(luò)進(jìn)行性能比較。
密集神經(jīng)網(wǎng)絡(luò)架構(gòu)
磨刀不誤砍柴工在開(kāi)始編程之前,需要先整理一個(gè)基本的路線圖。我們的目標(biāo)是創(chuàng)建一個(gè)程序,該程序能創(chuàng)建一個(gè)擁有特定架構(gòu)(層的數(shù)量和大小以及激活函數(shù)都是確定的)的密集連接神經(jīng)網(wǎng)絡(luò)。圖 1 給出了網(wǎng)絡(luò)的示例。最重要的是,網(wǎng)絡(luò)必須可訓(xùn)練且能進(jìn)行預(yù)測(cè)。
神經(jīng)網(wǎng)絡(luò)框圖
上圖顯示了在訓(xùn)練神經(jīng)網(wǎng)絡(luò)時(shí)需要執(zhí)行的操作。它還顯示了在單次迭代的不同階段,需要更新和讀取多少參數(shù)。構(gòu)建正確的數(shù)據(jù)結(jié)構(gòu)并熟練地管理其狀態(tài)是任務(wù)中最困難的部分之一。
l 層的權(quán)值矩陣 W 和偏置向量 b 的維數(shù)。
神經(jīng)網(wǎng)絡(luò)層初始化首先初始化每一層的權(quán)值矩陣 W 和偏置向量 b。在圖 3 中。先準(zhǔn)備一個(gè)為系數(shù)分配適當(dāng)維數(shù)的清單。上標(biāo) [l] 表示當(dāng)前層的索引 (從 1 數(shù)起),值 n 表示給定層中的單位數(shù)。假設(shè)描述 NN 架構(gòu)的信息將以類似 Snippet 1 的列表形式傳遞到程序中,列表的每一項(xiàng)是一個(gè)描述單個(gè)網(wǎng)絡(luò)層基本參數(shù)的字典:input_dim 是輸入層信號(hào)向量的大小,output_dim 是輸出層激活向量的大小,activation 是在內(nèi)層使用的激活函數(shù)。
nn_architecture = [ {"input_dim": 2, "output_dim": 4, "activation": "relu"}, {"input_dim": 4, "output_dim": 6, "activation": "relu"}, {"input_dim": 6, "output_dim": 6, "activation": "relu"}, {"input_dim": 6, "output_dim": 4, "activation": "relu"}, {"input_dim": 4, "output_dim": 1, "activation": "sigmoid"}, ]
Snippet 1:包含描述特定神經(jīng)網(wǎng)絡(luò)參數(shù)的列表。該列表對(duì)應(yīng)圖 1 所示的 NN。
如果你對(duì)這個(gè)話題很熟悉,你可能已經(jīng)在腦海中聽(tīng)到一個(gè)焦慮的聲音:「嘿,嘿!這里有問(wèn)題!有些領(lǐng)域是不必要的……」是的,這次你內(nèi)心的聲音是對(duì)的。前一層輸出的向量是下一層的輸入,所以實(shí)際上只知道一個(gè)向量的大小就足夠了。但我特意使用以下符號(hào)來(lái)保持所有層之間目標(biāo)的一致性,使那些剛接觸這一課題的人更容易理解代碼。
def init_layers(nn_architecture, seed = 99): np.random.seed(seed) number_of_layers = len(nn_architecture) params_values = {} for idx, layer in enumerate(nn_architecture): layer_idx = idx + 1 layer_input_size = layer["input_dim"] layer_output_size = layer["output_dim"] params_values["W" + str(layer_idx)] = np.random.randn( layer_output_size, layer_input_size) * 0.1 params_values["b" + str(layer_idx)] = np.random.randn( layer_output_size, 1) * 0.1 return params_values
Snippet 2:初始化權(quán)值矩陣和偏置向量值的函數(shù)。
最后是這一部分最主要的任務(wù)——層參數(shù)初始化。看過(guò) Snippet 2 上的代碼并對(duì) NumPy 有一定經(jīng)驗(yàn)的人會(huì)發(fā)現(xiàn),矩陣 W 和向量 b 被小的隨機(jī)數(shù)填充。這種做法并非偶然。權(quán)值不能用相同的數(shù)字初始化,不然會(huì)出現(xiàn)「對(duì)稱問(wèn)題」。如果所有權(quán)值一樣,不管輸入 X 是多少,隱藏層中的所有單位都相同。在某種程度上,我們?cè)诔跏茧A段就會(huì)陷入死循環(huán),無(wú)論訓(xùn)練模型時(shí)間多長(zhǎng)、網(wǎng)絡(luò)多深都無(wú)法逃脫。線性代數(shù)是不會(huì)被抵消的。
在第一次迭代中,使用較小的數(shù)值可以提高算法效率。通過(guò)圖 4 所示的 sigmoid 函數(shù)圖可以看到,對(duì)于較大數(shù)值,它幾乎是平的,這十分影響 NN 的學(xué)習(xí)速度??傊?,使用小隨機(jī)數(shù)進(jìn)行參數(shù)初始化是一種簡(jiǎn)單的方法,能保證我們的算法有足夠好的起點(diǎn)。準(zhǔn)備好的參數(shù)值存儲(chǔ)在帶有唯一標(biāo)定其父層的 python 字典中。字典在函數(shù)末尾返回,因此算法的下一步是訪問(wèn)它的內(nèi)容。
算法中使用的激活函數(shù)
我們將使用的函數(shù)中,有幾個(gè)函數(shù)非常簡(jiǎn)單但功能強(qiáng)大。激活函數(shù)可以寫在一行代碼中,但卻能使神經(jīng)網(wǎng)絡(luò)表現(xiàn)出自身所需的非線性性能和可表達(dá)性?!笡](méi)有它們,我們的神經(jīng)網(wǎng)絡(luò)就會(huì)變成由多個(gè)線性函數(shù)組合而成的線性函數(shù)。」可選激活函數(shù)很多,但在這個(gè)項(xiàng)目中,我決定使用這兩種——sigmoid 和 ReLU。為了能夠得到完整循環(huán)并同時(shí)進(jìn)行前向和反向傳播,我們還需要求導(dǎo)。
def sigmoid(Z): return 1/(1+np.exp(-Z)) def relu(Z): return np.maximum(0,Z) def sigmoid_backward(dA, Z): sig = sigmoid(Z) return dA * sig * (1 - sig) def relu_backward(dA, Z): dZ = np.array(dA, copy = True) dZ[Z <= 0] = 0; return dZ;
Snippet 3:ReLU 和 Sigmoid 激活函數(shù)及其導(dǎo)數(shù)。
前向傳播設(shè)計(jì)好的神經(jīng)網(wǎng)絡(luò)有一個(gè)簡(jiǎn)單的架構(gòu)。信息以 X 矩陣的形式沿一個(gè)方向傳遞,穿過(guò)隱藏的單元,從而得到預(yù)測(cè)向量 Y_hat。為了便于閱讀,我將前向傳播分解為兩個(gè)多帶帶的函數(shù)——對(duì)單個(gè)層進(jìn)行前向傳播和對(duì)整個(gè) NN 進(jìn)行前向傳播。
def single_layer_forward_propagation(A_prev, W_curr, b_curr, activation="relu"): Z_curr = np.dot(W_curr, A_prev) + b_curr if activation is "relu": activation_func = relu elif activation is "sigmoid": activation_func = sigmoid else: raise Exception("Non-supported activation function") return activation_func(Z_curr), Z_curr
Snippet 4:?jiǎn)螌忧跋騻鞑ゲ襟E
這部分代碼可能是最容易理解的。給定上一層的輸入信號(hào),我們計(jì)算仿射變換 Z,然后應(yīng)用選定的激活函數(shù)。通過(guò)使用 NumPy,我們可以利用向量化——一次性對(duì)整個(gè)層和整批示例執(zhí)行矩陣運(yùn)算。這減少了迭代次數(shù),大大加快了計(jì)算速度。除了計(jì)算矩陣 A,我們的函數(shù)還返回一個(gè)中間值 Z。作用是什么呢?答案如圖 2 所示。我們需要在反向傳播中用到 Z。
在前向傳播中使用的單個(gè)矩陣的維數(shù)
使用預(yù)設(shè)好的一層前向函數(shù)后,就可以輕松地構(gòu)建整個(gè)前向傳播。這個(gè)函數(shù)稍顯復(fù)雜,它的作用不僅是預(yù)測(cè),還要管理中間值的集合。它返回 Python 字典,其中包含為特定層計(jì)算的 A 和 Z 值。
def full_forward_propagation(X, params_values, nn_architecture): memory = {} A_curr = X for idx, layer in enumerate(nn_architecture): layer_idx = idx + 1 A_prev = A_curr activ_function_curr = layer["activation"] W_curr = params_values["W" + str(layer_idx)] b_curr = params_values["b" + str(layer_idx)] A_curr, Z_curr = single_layer_forward_propagation(A_prev, W_curr, b_curr, activ_function_curr) memory["A" + str(idx)] = A_prev memory["Z" + str(layer_idx)] = Z_curr return A_curr, memory
Snippnet 5:完整前向傳播步驟
損失函數(shù)為了觀察進(jìn)度,保證正確方向,我們通常需要計(jì)算損失函數(shù)的值?!敢话銇?lái)說(shuō),損失函數(shù)用來(lái)表征我們與『理想』解決方案的距離。」我們根據(jù)要解決的問(wèn)題來(lái)選擇損失函數(shù),像 Keras 這樣的框架會(huì)有多種選擇。因?yàn)槲矣?jì)劃測(cè)試我們的 NN 在兩類點(diǎn)上的分類,所以選擇二進(jìn)制交叉熵,它定義如下。為了獲得更多學(xué)習(xí)過(guò)程的信息,我決定引入一個(gè)計(jì)算準(zhǔn)確率的函數(shù)。
Snippnet 6:損失函數(shù)和準(zhǔn)確率計(jì)算
反向傳播許多缺乏經(jīng)驗(yàn)的深度學(xué)習(xí)愛(ài)好者認(rèn)為反向傳播是一種難以理解的算法。微積分和線性代數(shù)的結(jié)合常常使缺乏數(shù)學(xué)基礎(chǔ)的人望而卻步。所以如果你無(wú)法馬上理解,也不要擔(dān)心。相信我,我們都經(jīng)歷過(guò)這個(gè)過(guò)程。
def single_layer_backward_propagation(dA_curr, W_curr, b_curr, Z_curr, A_prev, activation="relu"): m = A_prev.shape[1] if activation is "relu": backward_activation_func = relu_backward elif activation is "sigmoid": backward_activation_func = sigmoid_backward else: raise Exception("Non-supported activation function") dZ_curr = backward_activation_func(dA_curr, Z_curr) dW_curr = np.dot(dZ_curr, A_prev.T) / m db_curr = np.sum(dZ_curr, axis=1, keepdims=True) / m dA_prev = np.dot(W_curr.T, dZ_curr) return dA_prev, dW_curr, db_curr
Snippnet 7:?jiǎn)螌臃聪騻鞑ゲ襟E
人們常常混淆反向傳播與梯度下降,但實(shí)際上這是兩個(gè)獨(dú)立的問(wèn)題。前者的目的是有效地計(jì)算梯度,而后者是利用計(jì)算得到的梯度進(jìn)行優(yōu)化。在 NN 中,我們計(jì)算關(guān)于參數(shù)的代價(jià)函數(shù)梯度(之前討論過(guò)),但是反向傳播可以用來(lái)計(jì)算任何函數(shù)的導(dǎo)數(shù)。這個(gè)算法的本質(zhì)是在已知各個(gè)函數(shù)的導(dǎo)數(shù)后,利用微分學(xué)中的鏈?zhǔn)椒▌t計(jì)算出結(jié)合成的函數(shù)的導(dǎo)數(shù)。對(duì)于一層網(wǎng)絡(luò),這個(gè)過(guò)程可用下面的公式描述。本文主要關(guān)注的是實(shí)際實(shí)現(xiàn),故省略推導(dǎo)過(guò)程。通過(guò)公式可以看出,預(yù)先記住中間層的 A 矩陣和 Z 矩陣的值是十分必要的。
一層中的前向和反向傳播
就像前向傳播一樣,我決定將計(jì)算分為兩個(gè)獨(dú)立的函數(shù)。第一個(gè)函數(shù)(Snippnet7)側(cè)重一個(gè)多帶帶的層,可以歸結(jié)為用 NumPy 重寫上面的公式。第二個(gè)表示完全反向傳播,主要在三個(gè)字典中讀取和更新值。然后計(jì)算預(yù)測(cè)向量(前向傳播結(jié)果)的代價(jià)函數(shù)導(dǎo)數(shù)。這很簡(jiǎn)單,它只是重述了下面的公式。然后從末端開(kāi)始遍歷網(wǎng)絡(luò)層,并根據(jù)圖 6 所示的圖計(jì)算所有參數(shù)的導(dǎo)數(shù)。最后,函數(shù)返回 python 字典,其中就有我們想求的梯度。
def full_backward_propagation(Y_hat, Y, memory, params_values, nn_architecture): grads_values = {} m = Y.shape[1] Y = Y.reshape(Y_hat.shape) dA_prev = - (np.divide(Y, Y_hat) - np.divide(1 - Y, 1 - Y_hat)); for layer_idx_prev, layer in reversed(list(enumerate(nn_architecture))): layer_idx_curr = layer_idx_prev + 1 activ_function_curr = layer["activation"] dA_curr = dA_prev A_prev = memory["A" + str(layer_idx_prev)] Z_curr = memory["Z" + str(layer_idx_curr)] W_curr = params_values["W" + str(layer_idx_curr)] b_curr = params_values["b" + str(layer_idx_curr)] dA_prev, dW_curr, db_curr = single_layer_backward_propagation( dA_curr, W_curr, b_curr, Z_curr, A_prev, activ_function_curr) grads_values["dW" + str(layer_idx_curr)] = dW_curr grads_values["db" + str(layer_idx_curr)] = db_curr return grads_values
Snippnet 8:全反向傳播步驟
更新參數(shù)值該方法的目標(biāo)是利用梯度優(yōu)化來(lái)更新網(wǎng)絡(luò)參數(shù),以使目標(biāo)函數(shù)更接近最小值。為了實(shí)現(xiàn)這項(xiàng)任務(wù),我們使用兩個(gè)字典作為函數(shù)參數(shù):params_values 存儲(chǔ)參數(shù)的當(dāng)前值;grads_values 存儲(chǔ)根據(jù)參數(shù)計(jì)算出的代價(jià)函數(shù)導(dǎo)數(shù)。雖然該優(yōu)化算法非常簡(jiǎn)單,只需對(duì)每一層應(yīng)用下面的方程即可,但它可以作為更高級(jí)優(yōu)化器的一個(gè)良好起點(diǎn),所以我決定使用它,這也可能是我下一篇文章的主題。
def update(params_values, grads_values, nn_architecture, learning_rate): for layer_idx, layer in enumerate(nn_architecture): params_values["W" + str(layer_idx)] -= learning_rate * grads_values["dW" + str(layer_idx)] params_values["b" + str(layer_idx)] -= learning_rate * grads_values["db" + str(layer_idx)] return params_values;
Snippnet 9:利用梯度下降更新參數(shù)值
組合成型任務(wù)中最困難的部分已經(jīng)過(guò)去了,我們已經(jīng)準(zhǔn)備好了所有必要的函數(shù),現(xiàn)在只需把它們按正確的順序組合即可。為了更好地理解操作順序,需要對(duì)照?qǐng)D 2 的表。該函數(shù)經(jīng)過(guò)訓(xùn)練和期間的權(quán)值變化返回了最優(yōu)權(quán)重。只需要使用接收到的權(quán)重矩陣和一組測(cè)試數(shù)據(jù)即可運(yùn)行完整的前向傳播,從而進(jìn)行預(yù)測(cè)。
def train(X, Y, nn_architecture, epochs, learning_rate): params_values = init_layers(nn_architecture, 2) cost_history = [] accuracy_history = [] for i in range(epochs): Y_hat, cashe = full_forward_propagation(X, params_values, nn_architecture) cost = get_cost_value(Y_hat, Y) cost_history.append(cost) accuracy = get_accuracy_value(Y_hat, Y) accuracy_history.append(accuracy) grads_values = full_backward_propagation(Y_hat, Y, cashe, params_values, nn_architecture) params_values = update(params_values, grads_values, nn_architecture, learning_rate) return params_values, cost_history, accuracy_history
Snippnet 10:訓(xùn)練模型
David vs Goliath如果對(duì)Python編程、網(wǎng)絡(luò)爬蟲、機(jī)器學(xué)習(xí)、數(shù)據(jù)挖掘、web開(kāi)發(fā)、人工智能、面試經(jīng)驗(yàn)交流。感興趣可以519970686,群內(nèi)會(huì)有不定期的發(fā)放免費(fèi)的資料鏈接,這些資料都是從各個(gè)技術(shù)網(wǎng)站搜集、整理出來(lái)的,如果你有好的學(xué)習(xí)資料可以私聊發(fā)我,我會(huì)注明出處之后分享給大家。
現(xiàn)在可以檢驗(yàn)我們的模型在簡(jiǎn)單的分類問(wèn)題上的表現(xiàn)了。我生成了一個(gè)由兩類點(diǎn)組成的數(shù)據(jù)集,如圖 7 所示。然后讓模型學(xué)習(xí)對(duì)兩類點(diǎn)分類。為了便于比較,我還在高級(jí)框架中編寫了 Keras 模型。兩種模型具有相同的架構(gòu)和學(xué)習(xí)速率。盡管如此,這樣對(duì)比還是稍有不公,因?yàn)槲覀儨?zhǔn)備的測(cè)試太過(guò)于簡(jiǎn)單。最終,NumPy 模型和 Keras 模型在測(cè)試集上的準(zhǔn)確率都達(dá)到了 95%,但是我們的模型需要多花幾十倍的時(shí)間才能達(dá)到這樣的準(zhǔn)確率。在我看來(lái),這種狀態(tài)主要是由于缺乏適當(dāng)?shù)膬?yōu)化。
測(cè)試數(shù)據(jù)集
兩種模型實(shí)現(xiàn)的分類邊界可視化
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/44899.html
摘要:注在數(shù)據(jù)庫(kù)中的表,每一行表示一個(gè)記錄,每一列表示一個(gè)字段而在深度學(xué)習(xí)的數(shù)據(jù)集中,每一行表示一個(gè)分類,每一列表示一個(gè)特征。 目錄 1 模型的改變 1.1?采用Mini-Batch(N samples)的形式 2 代碼的改變 2.1 構(gòu)造一個(gè)多層的神經(jīng)網(wǎng)絡(luò) 2.2 代碼的改變 2.2.1? 數(shù)據(jù)...
摘要:在本節(jié)中,我們將看到一些最流行和最常用的庫(kù),用于機(jī)器學(xué)習(xí)和深度學(xué)習(xí)是用于數(shù)據(jù)挖掘,分析和機(jī)器學(xué)習(xí)的最流行的庫(kù)。愿碼提示網(wǎng)址是一個(gè)基于的框架,用于使用多個(gè)或進(jìn)行有效的機(jī)器學(xué)習(xí)和深度學(xué)習(xí)。 showImg(https://segmentfault.com/img/remote/1460000018961827?w=999&h=562); 來(lái)源 | 愿碼(ChainDesk.CN)內(nèi)容編輯...
摘要:理論基礎(chǔ)什么是神經(jīng)網(wǎng)絡(luò)我們知道深度學(xué)習(xí)是機(jī)器學(xué)習(xí)的一個(gè)分支,是一種以人工神經(jīng)網(wǎng)絡(luò)為架構(gòu),對(duì)數(shù)據(jù)進(jìn)行表征學(xué)習(xí)的算法。深度神經(jīng)網(wǎng)絡(luò)中的深度指的是一系列連續(xù)的表示層,數(shù)據(jù)模型中包含了多少層,這就被稱為模型的深度。 理論基礎(chǔ) 什么是神經(jīng)網(wǎng)絡(luò) 我們知道深度學(xué)習(xí)是機(jī)器學(xué)習(xí)的一個(gè)分支,是一種以人工神經(jīng)網(wǎng)絡(luò)為架構(gòu),對(duì)數(shù)據(jù)進(jìn)行表征學(xué)習(xí)的算法。而深度神經(jīng)網(wǎng)絡(luò)又是深度學(xué)習(xí)的一個(gè)分支,它在 wikipedia...
閱讀 1408·2021-11-25 09:43
閱讀 1962·2021-11-12 10:36
閱讀 6287·2021-09-22 15:05
閱讀 3535·2019-08-30 15:55
閱讀 2091·2019-08-26 14:06
閱讀 3702·2019-08-26 12:17
閱讀 568·2019-08-23 17:55
閱讀 2512·2019-08-23 16:23