国产日韩欧美一区二区三区综合,日本黄色免费在线,国产精品麻豆欧美日韩ww,色综合狠狠操

極客小將

您現(xiàn)在的位置是:首頁(yè) » scratch編程資訊

資訊內(nèi)容

100行Python代碼,輕松搞定神經(jīng)網(wǎng)絡(luò)

極客小將2021-02-08-

upload/article/images/2021-02-08/8d9068dfb6ef284497b654cce80e850f.jpg

大數(shù)據(jù)文摘出品

來(lái)源:eisenjulian

編譯:周家樂(lè)、錢(qián)天培

用tensorflow,pytorch這類(lèi)深度學(xué)習(xí)庫(kù)來(lái)寫(xiě)一個(gè)神經(jīng)網(wǎng)絡(luò)早就不稀奇了。

可是,你知道怎么用python和numpy來(lái)優(yōu)雅地搭一個(gè)神經(jīng)網(wǎng)絡(luò)嘛?

現(xiàn)如今,有多種深度學(xué)習(xí)框架可供選擇,他們帶有自動(dòng)微分、基于圖的優(yōu)化計(jì)算和硬件加速等各種重要特性。對(duì)人們而言,似乎享受這些重要特性帶來(lái)的便利已經(jīng)是理所當(dāng)然的事兒了。但其實(shí),瞧一瞧隱藏在這些特性下的東西,能更好的幫助你理解這些網(wǎng)絡(luò)究竟是如何工作的。

所以今天,文摘菌就來(lái)手把手教大家搭一個(gè)神經(jīng)網(wǎng)絡(luò)。原料就是簡(jiǎn)單的python和numpy代碼!

文章中的所有代碼可以都在這兒獲取。

https://colab.research.google.com/github/eisenjulian/slides/blob/master/NN_from_scratch/notebook.ipynb

符號(hào)說(shuō)明

在計(jì)算反向傳播時(shí), 我們可以選擇使用函數(shù)符號(hào)、變量符號(hào)去記錄求導(dǎo)過(guò)程。它們分別對(duì)應(yīng)了計(jì)算圖中的邊和節(jié)點(diǎn)來(lái)表示它們。

給定R^n→R和x∈R^n, 那么梯度是由偏導(dǎo)?f/?j(x)組成的n維行向量

如果f:R^n→R^m?和x∈R^n,那么?Jacobian矩陣是下列函數(shù)組成的一個(gè)m×n的矩陣。

upload/article/images/2021-02-08/b7c0f2991c511b577d22418ddf91391a.jpg

對(duì)于給定的函數(shù)f和向量a和b如果a=f(b)那么我們用?a/?b?表示Jacobian矩陣,當(dāng)a是實(shí)數(shù)時(shí)則表示梯度

鏈?zhǔn)椒▌t

給定三個(gè)分屬于不同向量空間的向量a∈A及c∈C和兩個(gè)可微函數(shù)f:A→B及g:B→C使得f(a)=b和g(b)=c,我們能得到復(fù)合函數(shù)的Jacobian矩陣是函數(shù)f和g的jacobian矩陣的乘積:

upload/article/images/2021-02-08/acf4c87230f26ce933cec9041290b09a.jpg

這就是大名鼎鼎的鏈?zhǔn)椒▌t。提出于上世紀(jì)60、70年代的反向傳播算法就是應(yīng)用了鏈?zhǔn)椒▌t來(lái)計(jì)算一個(gè)實(shí)函數(shù)相對(duì)于其不同參數(shù)的梯度的。

要知道我們的最終目標(biāo)是通過(guò)沿著梯度的相反方向來(lái)逐步找到函數(shù)的最小值 (當(dāng)然最好是全局最小值), 因?yàn)橹辽僭诰植縼?lái)說(shuō), 這樣做將使得函數(shù)值逐步下降。當(dāng)我們有兩個(gè)參數(shù)需要優(yōu)化時(shí), 整個(gè)過(guò)程如圖所示:

https://cdn.china-scratch.com/timg/190502/20062a0A-3.gif

反向模式求導(dǎo)

假設(shè)函數(shù)fi(ai)=ai+1由多于兩個(gè)函數(shù)復(fù)合而成,我們可以反復(fù)應(yīng)用公式求導(dǎo)并得到:

upload/article/images/2021-02-08/54b53de510aeb3151a271b04b7d1406c.jpg

可以有很多種方式計(jì)算這個(gè)乘積,最常見(jiàn)的是從左向右或從右向左。

如果an是一個(gè)標(biāo)量,那么在計(jì)算整個(gè)梯度的時(shí)候我們可以通過(guò)先計(jì)算?an/?an-1并逐步右乘所有的Jacobian矩陣?ai/?ai-1來(lái)得到。這個(gè)操作有時(shí)被稱(chēng)作VJP或向量-Jacobian乘積(Vector-Jacobian Product)。

又因?yàn)檎麄€(gè)過(guò)程中我們是從計(jì)算?an/?an-1開(kāi)始逐步計(jì)算?an/?an-2,?an/?an-3等梯度到最后,并保存中間值,所以這個(gè)過(guò)程被稱(chēng)為反向模式求導(dǎo)。最終,我們可以計(jì)算出an相對(duì)于所有其他變量的梯度。

upload/article/images/2021-02-08/09ca2262024f73de587c2eab1b45ab80.jpg

相對(duì)而言,前向模式的過(guò)程正相反。它從計(jì)算Jacobian矩陣如?a2/?a1開(kāi)始,并左乘?a3/?a2來(lái)計(jì)算?a3/?a1。如果我們繼續(xù)乘上?ai/?ai-1并保存中間值,最終我們可以得到所有變量相對(duì)于?a2/?a1的梯度。當(dāng)?a2/?a1是標(biāo)量時(shí),所有乘積都是列向量,這被稱(chēng)為Jacobian向量乘積(或者JVP,Jacobian-Vector Product?)。

upload/article/images/2021-02-08/8e93d13a86d66624f4eef0153f0bc95f.jpg

你大概已經(jīng)猜到了,對(duì)于反向傳播來(lái)說(shuō),我們更偏向應(yīng)用反向模式——因?yàn)槲覀兿胍鸩降玫綋p失函數(shù)對(duì)于每層參數(shù)的梯度。正向模式雖然也可以計(jì)算需要的梯度, 但因?yàn)橹貜?fù)計(jì)算太多而效率很低。

計(jì)算梯度的過(guò)程看起來(lái)像是有很多高維矩陣相乘, 但實(shí)際上,Jacobian矩陣常常是稀疏、塊或者對(duì)角矩陣,又因?yàn)槲覀冎魂P(guān)心將其右乘行向量的結(jié)果,所以就不需要耗費(fèi)太多計(jì)算和存儲(chǔ)資源。

在本文中, 我們的方法主要用于按順序逐層搭建的神經(jīng)網(wǎng)絡(luò), 但同樣的方法也適用于計(jì)算梯度的其他算法或計(jì)算圖。

關(guān)于反向和正向模式的詳盡描述可以參考這里?

http://colah.github.io/posts/2015-08-Backprop/ ?

深度神經(jīng)網(wǎng)絡(luò)

在典型的監(jiān)督機(jī)器學(xué)習(xí)算法中, 我們通常用到一個(gè)很復(fù)雜函數(shù),它的輸入是存有標(biāo)簽樣本數(shù)值特征的張量。此外,還有很多用于描述模型的權(quán)重張量。

損失函數(shù)是關(guān)于樣本和權(quán)重的標(biāo)量函數(shù), 它是衡量模型輸出與預(yù)期標(biāo)簽的差距的指標(biāo)。我們的目標(biāo)是找到最合適的權(quán)重讓損失最小。在深度學(xué)習(xí)中, 損失函數(shù)被表示為一串易于求導(dǎo)的簡(jiǎn)單函數(shù)的復(fù)合。所有這些簡(jiǎn)單函數(shù)(除了最后一個(gè)函數(shù)),都是我們指的層, 而每一層通常有兩組參數(shù): 輸入 (可以是上一層的輸出) 和權(quán)重。

而最后一個(gè)函數(shù)代表了損失度量, 它也有兩組參數(shù): 模型輸出y和真實(shí)標(biāo)簽y^。例如, 如果損失度量l為平方誤差, 則?l/?y為 2 avg(y-y^)。損失度量的梯度將是應(yīng)用反向模式求導(dǎo)的起始行向量。

Autograd

自動(dòng)求導(dǎo)背后的思想已是相當(dāng)成熟了。它可以在運(yùn)行時(shí)或編譯過(guò)程中完成,但如何實(shí)現(xiàn)會(huì)對(duì)性能產(chǎn)生巨大影響。我建議你能認(rèn)真閱讀 HIPS autograd的 Python 實(shí)現(xiàn),來(lái)真正了解autograd。

核心想法其實(shí)始終未變。從我們?cè)趯W(xué)校學(xué)習(xí)如何求導(dǎo)時(shí), 就應(yīng)該知道這一點(diǎn)了。如果我們能夠追蹤最終求出標(biāo)量輸出的計(jì)算, 并且我們知道如何對(duì)簡(jiǎn)單操作求導(dǎo) (例如加法、乘法、冪、指數(shù)、對(duì)數(shù)等等), 我們就可以算出輸出的梯度。

假設(shè)我們有一個(gè)線性的中間層f,由矩陣乘法表示(暫時(shí)不考慮偏置):

upload/article/images/2021-02-08/03ce8fb7a18b6b11aca35521d9b3bdb7.jpg

為了用梯度下降法調(diào)整w值,我們需要計(jì)算梯度?l/?w。這里我們可以觀察到,改變y從而影響l是一個(gè)關(guān)鍵。

每一層都必須滿(mǎn)足下面這個(gè)條件: 如果給出了損失函數(shù)相對(duì)于這一層輸出的梯度, 就可以得到損失函數(shù)相對(duì)于這一層輸入(即上一層的輸出)的梯度。

現(xiàn)在應(yīng)用兩次鏈?zhǔn)椒▌t得到損失函數(shù)相對(duì)于w的梯度:

upload/article/images/2021-02-08/89df630515e8333ab8028a621a6bde90.jpg

相對(duì)于x的是:

upload/article/images/2021-02-08/e23bf50e1202a438cbb2524551482375.jpg

因此, 我們既可以后向傳遞一個(gè)梯度, 使上一層得到更新并更新層間權(quán)重, 以?xún)?yōu)化損失, 這就行啦!

動(dòng)手實(shí)踐

先來(lái)看看代碼, 或者直接試試Colab Notebook

https://colab.research.google.com/github/eisenjulian/slides/blob/master/NN_from_scratch/notebook.ipynb

我們從封裝了一個(gè)張量及其梯度的類(lèi)(class)開(kāi)始。

現(xiàn)在我們可以創(chuàng)建一個(gè)layer類(lèi),關(guān)鍵的想法是,在前向傳播時(shí),我們返回這一層的輸出和可以接受輸出梯度和輸入梯度的函數(shù),并在過(guò)程中更新權(quán)重梯度。

然后, 訓(xùn)練過(guò)程將有三個(gè)步驟, 計(jì)算前向傳遞, 然后后向傳遞, 最后更新權(quán)重。這里關(guān)鍵的一點(diǎn)是把更新權(quán)重放在最后, 因?yàn)闄?quán)重可以在多個(gè)層中重用,我們更希望在需要的時(shí)候再更新它。

class Layer:def __init__(self):self.parameters = []
def forward(self, X):"""Override me! A simple no-op layer, it passes forward the inputs"""return X, lambda D: D
def build_param(self, tensor):"""Creates a parameter from a tensor, and saves a reference for the update step"""param = Parameter(tensor)self.parameters.append(param)return param
def update(self, optimizer):for param in self.parameters: optimizer.update(param)

標(biāo)準(zhǔn)的做法是將更新參數(shù)的工作交給優(yōu)化器, 優(yōu)化器在每一批(batch)后都會(huì)接收參數(shù)的實(shí)例。最簡(jiǎn)單和最廣為人知的優(yōu)化方法是mini-batch隨機(jī)梯度下降。

class SGDOptimizer():def __init__(self, lr=0.1):self.lr = lr
def update(self, param):param.tensor -= self.lr * param.gradientparam.gradient.fill(0)

在此框架下, 并使用前面計(jì)算的結(jié)果后, 線性層如下所示:

class Linear(Layer):def __init__(self, inputs, outputs):super().__init__()tensor = np.random.randn(inputs, outputs) * np.sqrt(1 / inputs)self.weights = self.build_param(tensor)self.bias = self.build_param(np.zeros(outputs))
def forward(self, X):def backward(D):self.weights.gradient += X.T @ Dself.bias.gradient += D.sum(axis=0)return D @ self.weights.tensor.Treturn X @ self.weights.tensor + self.bias.tensor, backward

接下來(lái)看看另一個(gè)常用的層,激活層。它們屬于點(diǎn)式(pointwise)非線性函數(shù)。點(diǎn)式函數(shù)的 Jacobian矩陣是對(duì)角矩陣, 這意味著當(dāng)乘以梯度時(shí), 它是逐點(diǎn)相乘的。

class ReLu(Layer):def forward(self, X):mask = X > 0return X * mask, lambda D: D * mask

計(jì)算Sigmoid函數(shù)的梯度略微有一點(diǎn)難度,而它也是逐點(diǎn)計(jì)算的:

class Sigmoid(Layer):def forward(self, X):S = 1 / (1 + np.exp(-X))def backward(D):return D * S * (1 - S)return S, backward

當(dāng)我們按序構(gòu)建很多層后,可以遍歷它們并先后得到每一層的輸出,我們可以把backward函數(shù)存在一個(gè)列表內(nèi),并在計(jì)算反向傳播時(shí)使用,這樣就可以直接得到相對(duì)于輸入層的損失梯度。就是這么神奇:

class Sequential(Layer):def __init__(self, *layers):super().__init__()self.layers = layersfor layer in layers:self.parameters.extend(layer.parameters)
def forward(self, X):backprops = []Y = Xfor layer in self.layers:Y, backprop = layer.forward(Y)backprops.append(backprop)def backward(D):for backprop in reversed(backprops):D = backprop(D)return Dreturn Y, backward

正如我們前面提到的,我們將需要定義批樣本的損失函數(shù)和梯度。一個(gè)典型的例子是MSE,它被常用在回歸問(wèn)題里,我們可以這樣實(shí)現(xiàn)它:

def mse_loss(Yp, Yt):diff = Yp - Ytreturn np.square(diff).mean(), 2 * diff / len(diff)

就差一點(diǎn)了!現(xiàn)在,我們定義了兩種層,以及合并它們的方法,下面如何訓(xùn)練呢?我們可以使用類(lèi)似于scikit-learn或者Keras中的API。

class Learner():def __init__(self, model, loss, optimizer):self.model = modelself.loss = lossself.optimizer = optimizer
def fit_batch(self, X, Y):Y_, backward = self.model.forward(X)L, D = self.loss(Y_, Y)backward(D)self.model.update(self.optimizer)return L
def fit(self, X, Y, epochs, bs):losses = []for epoch in range(epochs):p = np.random.permutation(len(X))X, Y = X[p], Y[p]loss = 0.0for i in range(0, len(X), bs):loss += self.fit_batch(X[i:i + bs], Y[i:i + bs])losses.append(loss)return losses

這就行了!如果你跟隨著我的思路,你可能就會(huì)發(fā)現(xiàn)其實(shí)有幾行代碼是可以被省掉的。

這代碼能用不?

現(xiàn)在可以用一些數(shù)據(jù)測(cè)試下我們的代碼了。

X = np.random.randn(100, 10)w = np.random.randn(10, 1)b = np.random.randn(1)Y = X @ W + B
model = Linear(10, 1)learner = Learner(model, mse_loss, SGDOptimizer(lr=0.05))learner.fit(X, Y, epochs=10, bs=10)

upload/article/images/2021-02-08/eb8b933b380358f15d37e7d62240d0c0.jpg

我一共訓(xùn)練了10輪。

我們還能檢查學(xué)到的權(quán)重和真實(shí)的權(quán)重是否一致。

print(np.linalg.norm(m.weights.tensor - W), (m.bias.tensor - B)[0])> 1.848553648022619e-05 5.69305886743976e-06

好了,就這么簡(jiǎn)單。讓我們?cè)僭囋嚪蔷€性數(shù)據(jù)集,例如y=x1x2,并且再加上一個(gè)Sigmoid非線性層和另一個(gè)線性層讓我們的模型更復(fù)雜些。像下面這樣:

X = np.random.randn(1000, 2)Y = X[:, 0] * X[:, 1]
losses1 = Learner(Sequential(Linear(2, 1)),mse_loss,SGDOptimizer(lr=0.01)).fit(X, Y, epochs=50, bs=50)
losses2 = Learner(Sequential(Linear(2, 10),Sigmoid(),Linear(10, 1)),mse_loss,SGDOptimizer(lr=0.3)).fit(X, Y, epochs=50, bs=50)
plt.plot(losses1)plt.plot(losses2)plt.legend(['1 Layer', '2 Layers'])plt.show()

upload/article/images/2021-02-08/b0fc4e87ea182b49f3da3242d33d1759.jpg

比較單一層vs兩層模型在使用sigmoid激活函數(shù)的情況下的訓(xùn)練損失。

最后

希望通過(guò)搭建這個(gè)簡(jiǎn)單的神經(jīng)網(wǎng)絡(luò),你已經(jīng)掌握了用python和numpy實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)的基本思路。

在這篇文章中,我們只定義了三種類(lèi)型的層和一個(gè)損失函數(shù), 所以還有很多事情可做,但基本原理都相似。感興趣的同學(xué)可以試著實(shí)現(xiàn)更復(fù)雜的神經(jīng)網(wǎng)絡(luò)哦!

References

[1] Thinc Deep Learning Library?

https://github.com/explosion/thinc

[2] PyTorch Tutorial?

https://pytorch.org/tutorials/beginner/nn_tutorial.html
[3] Calculus on Computational Graphs

http://colah.github.io/posts/2015-08-Backprop/

[4] HIPS Autograd?

https://github.com/HIPS/autograd

相關(guān)報(bào)道:

https://eisenjulian.github.io/deep-learning-in-100-lines/


實(shí)習(xí)/全職編輯記者招聘ing

加入我們,親身體驗(yàn)一家專(zhuān)業(yè)科技媒體采寫(xiě)的每個(gè)細(xì)節(jié),在最有前景的行業(yè),和一群遍布全球最優(yōu)秀的人一起成長(zhǎng)。坐標(biāo)北京·清華東門(mén),在大數(shù)據(jù)文摘主頁(yè)對(duì)話(huà)頁(yè)回復(fù)“招聘”了解詳情。簡(jiǎn)歷請(qǐng)直接發(fā)送至zz@bigdatadigest.cn


志愿者介紹

后臺(tái)回復(fù)“志愿者”加入我們https://cdn.china-scratch.com/timg/190502/2006314543-12.jpgupload/article/images/2021-02-08/7b6ed6e0c16b1e7ccdf6a357bbd94149.jpg

upload/article/images/2021-02-08/45fa33259937e7ee5ba918a570963df4.jpg


聲明:本文章由網(wǎng)友投稿作為教育分享用途,如有侵權(quán)原作者可通過(guò)郵件及時(shí)和我們聯(lián)系刪除

預(yù)約試聽(tīng)課

已有385人預(yù)約都是免費(fèi)的,你也試試吧...

主站蜘蛛池模板: 绵竹市| 东乌珠穆沁旗| 青河县| 武邑县| 佛坪县| 清流县| 张家口市| 尤溪县| 沙坪坝区| 镶黄旗| 三门县| 二连浩特市| 巴塘县| 福鼎市| 大同县| 萨嘎县| 济南市| 察雅县| 汝阳县| 新闻| 新化县| 临洮县| 临汾市| 濉溪县| 秦安县| 石泉县| 化隆| 中西区| 武威市| 巨野县| 延津县| 望都县| 平顺县| 北流市| 东兴市| 永宁县| 舒兰市| 贞丰县| 林芝县| 杭锦后旗| 通榆县|