0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

基于numpy實現(xiàn)合成梯度

zhKF_jqr_AI ? 來源:未知 ? 作者:李倩 ? 2018-05-14 17:32 ? 次閱讀

DeepMind提出用合成梯度取代反向傳播,讓網(wǎng)絡層可以獨立學習,加快訓練速度。讓我們和DeepMind數(shù)據(jù)科學家、Udacity深度學習導師Andrew Trask一起,基于numpy實現(xiàn)合成梯度。

TLDR本文將通過從頭實現(xiàn)DeepMind的Decoupled Neural Interfaces Using Synthetic Gradients論文中的技術,學習這一技術背后的直覺。

一、合成梯度概述

通常,神經(jīng)網(wǎng)絡比較預測和數(shù)據(jù)集,以決定如何更新權重。它接著使用反向傳播找出每個權重移動的方向,使得預測更精確。然而,在合成梯度(Synthetic Gradient)的情況下,每層各自做出數(shù)據(jù)的“最佳猜測”,然后根據(jù)猜測更新其權重?!白罴巡聹y”稱為合成梯度。數(shù)據(jù)用來幫助更新每層的“猜測器”(合成梯度生成器)。在大多數(shù)情況下,這讓網(wǎng)絡層可以獨立學習,以加快訓練的速度。

上圖(來自論文)提供了一個直觀的表示(自左向右)。圓角方塊為網(wǎng)絡層,菱形為合成梯度生成器。

二、使用合成梯度

讓我們暫時忽略合成梯度是如何生成的,直接看看它們是如何使用的。上圖最左展示了如何更新神經(jīng)網(wǎng)絡的第一層。第一層前向傳播至合成梯度生成器(Mi+1),合成梯度生成器返回一個梯度。網(wǎng)絡使用這個合成梯度代替真實的梯度(計算真實梯度需要一次完整的前向傳播和反向傳播)。接著照常更新權重,假裝合成梯度是真實梯度。如果你需要溫習下權重是如何根據(jù)梯度更新的,請參考我之前寫的基于Numpy實現(xiàn)神經(jīng)網(wǎng)絡:反向傳播和梯度下降。

所以,簡單來說,合成梯度和平常的梯度一樣,而且出于一些神奇的原因,它們看起來很精確(在沒有查看數(shù)據(jù)的情況下)!看起來像魔法?讓我們看看它們是如何生成的。

三、生成合成梯度

好吧,這部分非常巧妙,坦白地說,它可以起效真令人驚訝。如何為一個神經(jīng)網(wǎng)絡生成合成梯度?好吧,你當然需要另一個網(wǎng)絡!合成梯度生成器不過是一個神經(jīng)網(wǎng)絡,該網(wǎng)絡經(jīng)訓練可以接受一個網(wǎng)絡層的輸出,然后預測該網(wǎng)絡層的梯度。

邊注:Geoffrey Hinton的相關工作

事實上這讓我回想起幾年前Geoffrey Hinton的工作,隨機合成權重支持的深度學習網(wǎng)絡(arXiv:1411.0247)?;旧?,你可以通過隨機生成矩陣進行反向傳播,仍然能夠完成學習。此外,他展示了這具有某種正則化效應。這肯定是一項有趣的工作。

好,回到合成梯度。論文同時提到其他相關信息可以用作合成梯度生成網(wǎng)絡的輸入,不過論文本身看起來在普通前饋網(wǎng)絡上只使用了網(wǎng)絡層的輸出作為生成器的輸入。此外,論文甚至聲稱單線性層可以用作合成梯度生成器。令人驚奇!我們將嘗試一下這個。

網(wǎng)絡如何學習生成梯度?

這提出了一個問題,生成合成梯度的網(wǎng)絡如何學習?當我們進行完整的前向傳播和反向傳播時,我們實際得到了“正確”的梯度。我們可以將其與“合成”梯度進行比較,就像我們通常比較神經(jīng)網(wǎng)絡輸出和數(shù)據(jù)集一樣。因此,我們可以假裝“真梯度”來自某個神秘的數(shù)據(jù)集,以此訓練合成梯度網(wǎng)絡……所以我們像訓練平常的網(wǎng)絡一樣訓練???!

等一下……如果合成梯度網(wǎng)絡需要反向傳播……這還有什么意義?

很好的問題!這一技術的全部價值在于允許獨立訓練網(wǎng)絡層,無需等待所有網(wǎng)絡層完成前向傳播和反向傳播。如果合成梯度網(wǎng)絡需要等待完整的前向/反向傳播步驟,我們豈不是又回到了原點,而且需要進行的計算更多了(比原先還糟)。為了找到答案,讓我們重新看下論文中對網(wǎng)絡架構的可視化。

讓我們聚焦左邊的第二塊區(qū)域??吹搅藳]有?梯度(Mi+2)經(jīng)fi+1反向傳播至Mi+2。如你所見,每個合成梯度生成器實際上僅僅使用下一層生成的合成梯度進行訓練。因此,只有最后一層實際在數(shù)據(jù)上訓練。其他層,包括合成梯度生成網(wǎng)絡,基于合成梯度訓練。因此,訓練每層的合成梯度生成網(wǎng)絡時,只需等待下一層的合成梯度(沒有其他依賴)。太酷了!

四、基線神經(jīng)網(wǎng)絡

到了寫代碼的時間了!我將首先實現(xiàn)一個通過反向傳播進行訓練的原味神經(jīng)網(wǎng)絡,風格與基于Numpy實現(xiàn)神經(jīng)網(wǎng)絡:反向傳播中的類似。(所以,如果你有不明白的地方,可以先去閱讀我之前寫的文章,然后再回過頭來閱讀本文)。然而,我將額外增加一層,不過這不會造成理解問題。我只是覺得,既然我們在討論減少依賴,更多的網(wǎng)絡層可能有助于形成更好的解釋。

至于我們訓練的數(shù)據(jù)集,我們將使用二進制加法生成一個合成數(shù)據(jù)集(哈哈?。?。所以,網(wǎng)絡將接受兩個隨機的二進制數(shù)作為輸入,并預測兩者之和(也是一個二進制數(shù))。這使我們可以方便地根據(jù)需要增加維度(大致相當于難度)。下面是生成數(shù)據(jù)集的代碼。

import numpy as np

import sys

def generate_dataset(output_dim = 8,num_examples=1000):

def int2vec(x,dim=output_dim):

out = np.zeros(dim)

binrep = np.array(list(np.binary_repr(x))).astype('int')

out[-len(binrep):] = binrep

return out

x_left_int = (np.random.rand(num_examples) * 2**(output_dim - 1)).astype('int')

x_right_int = (np.random.rand(num_examples) * 2**(output_dim - 1)).astype('int')

y_int = x_left_int + x_right_int

x = list()

for i in range(len(x_left_int)):

x.append(np.concatenate((int2vec(x_left_int[i]),int2vec(x_right_int[i]))))

y = list()

for i in range(len(y_int)):

y.append(int2vec(y_int[i]))

x = np.array(x)

y = np.array(y)

return (x,y)

num_examples = 1000

output_dim = 12

iterations = 1000

x,y = generate_dataset(num_examples=num_examples, output_dim = output_dim)

print("Input: two concatenated binary values:")

print(x[0])

print("\nOutput: binary value of their sum:")

print(y[0])

下面則是相應的神經(jīng)網(wǎng)絡代碼:

batch_size = 10

alpha = 0.1

input_dim = len(x[0])

layer_1_dim = 128

layer_2_dim = 64

output_dim = len(y[0])

weights_0_1 = (np.random.randn(input_dim,layer_1_dim) * 0.2) - 0.1

weights_1_2 = (np.random.randn(layer_1_dim,layer_2_dim) * 0.2) - 0.1

weights_2_3 = (np.random.randn(layer_2_dim,output_dim) * 0.2) - 0.1

for iter in range(iterations):

error = 0

for batch_i in range(int(len(x) / batch_size)):

batch_x = x[(batch_i * batch_size):(batch_i+1)*batch_size]

batch_y = y[(batch_i * batch_size):(batch_i+1)*batch_size]

layer_0 = batch_x

layer_1 = sigmoid(layer_0.dot(weights_0_1))

layer_2 = sigmoid(layer_1.dot(weights_1_2))

layer_3 = sigmoid(layer_2.dot(weights_2_3))

layer_3_delta = (layer_3 - batch_y) * layer_3 * (1 - layer_3)

layer_2_delta = layer_3_delta.dot(weights_2_3.T) * layer_2 * (1 - layer_2)

layer_1_delta = layer_2_delta.dot(weights_1_2.T) * layer_1 * (1 - layer_1)

weights_0_1 -= layer_0.T.dot(layer_1_delta) * alpha

weights_1_2 -= layer_1.T.dot(layer_2_delta) * alpha

weights_2_3 -= layer_2.T.dot(layer_3_delta) * alpha

error += (np.sum(np.abs(layer_3_delta)))

sys.stdout.write("\rIter:" + str(iter) + " Loss:" + str(error))

if(iter % 100 == 99):

print("")

現(xiàn)在,我真心覺得有必要做些我?guī)缀鯊牟辉趯W習時做的事,加上一點面向對象結構。通常,這會略微混淆網(wǎng)絡,更難看清代碼做了什么。然而,由于本文的主題是“解耦網(wǎng)絡接口”(Decoupled Neural Interfaces)及其優(yōu)勢,如果不解耦這些接口的話,解釋起來會相當困難。因此,我將把上面的網(wǎng)絡轉換為一個Layer類,之后將進一步轉換為一個DNI(解耦網(wǎng)絡接口)。

classLayer(object):

def __init__(self,input_dim, output_dim,nonlin,nonlin_deriv):

self.weights = (np.random.randn(input_dim, output_dim) * 0.2) - 0.1

self.nonlin = nonlin

self.nonlin_deriv = nonlin_deriv

def forward(self,input):

self.input = input

self.output = self.nonlin(self.input.dot(self.weights))

return self.output

def backward(self,output_delta):

self.weight_output_delta = output_delta * self.nonlin_deriv(self.output)

return self.weight_output_delta.dot(self.weights.T)

def update(self,alpha=0.1):

self.weights -= self.input.T.dot(self.weight_output_delta) * alpha

在這個Layer類中,我們有一些變量。weights是我們從輸入到輸出進行線性變換的矩陣(就像平常的線性層)。我們同時引入了一個輸出nonlin函數(shù),給我們的網(wǎng)絡輸出加上了非線性。如果我們不想要非線性,我們可以直接將其值設為lambda x:x。在我們的情形中,我們將傳入sigmoid函數(shù)。

我們傳入的第二個函數(shù)是nonlin_deriv,這是一個導數(shù)。該函數(shù)將接受我們的非線性輸出,并將其轉換為導數(shù)。就sigmoid而言,它的值為(out * (1 - out)),其中out為sigmoid的輸出。

現(xiàn)在,讓我們看下類中的幾個方法。forward,顧名思義,前向傳播,首先通過一個線性轉換,接著通過一個非線性函數(shù)。backward接受一個output_delta參數(shù),該參數(shù)表示從下一層經(jīng)反向傳播返回的真實梯度(非合成梯度)。我們接著使用這個參數(shù)來計算self.weight_output_delta,也就是權重輸出的導數(shù)。最后,反向傳播發(fā)送給前一層的誤差,并返回誤差。

update也許是其中最簡單的函數(shù)。它直接接受權重輸出的導數(shù),并使用它更新權重。如果有任何步驟不明白,請再次參考基于Numpy實現(xiàn)神經(jīng)網(wǎng)絡:反向傳播。

接著,讓我們看看layer對象是如何用于訓練的。

layer_1 = Layer(input_dim,layer_1_dim,sigmoid,sigmoid_out2deriv)

layer_2 = Layer(layer_1_dim,layer_2_dim,sigmoid,sigmoid_out2deriv)

layer_3 = Layer(layer_2_dim, output_dim,sigmoid, sigmoid_out2deriv)

for iter in range(iterations):

error = 0

for batch_i in range(int(len(x) / batch_size)):

batch_x = x[(batch_i * batch_size):(batch_i+1)*batch_size]

batch_y = y[(batch_i * batch_size):(batch_i+1)*batch_size]

layer_1_out = layer_1.forward(batch_x)

layer_2_out = layer_2.forward(layer_1_out)

layer_3_out = layer_3.forward(layer_2_out)

layer_3_delta = layer_3_out - batch_y

layer_2_delta = layer_3.backward(layer_3_delta)

layer_1_delta = layer_2.backward(layer_2_delta)

layer_1.backward(layer_1_delta)

layer_1.update()

layer_2.update()

layer_3.update()

如果你將上面的代碼和之前的腳本對比,基本上所有事情發(fā)生在基本相同的地方。我只是用方法調用替換了腳本中的相應操作。

所以,我們實際上做的是從之前的腳本中提取步驟,將其切分為類中不同的函數(shù)。

如果你搞不明白這個新版本的網(wǎng)絡,不要繼續(xù)下去。確保你在繼續(xù)閱讀下文之前習慣這種抽象的方式,因為下面會變得更復雜。

五、基于層輸出的合成梯度

現(xiàn)在,我們將基于了解的合成梯度的知識改寫Layer類,將其重新命名為DNI。

class DNI(object):

def __init__(self,input_dim, output_dim,nonlin,nonlin_deriv,alpha = 0.1):

# 和之前一樣

self.weights = (np.random.randn(input_dim, output_dim) * 0.2) - 0.1

self.nonlin = nonlin

self.nonlin_deriv = nonlin_deriv

# 新東西

self.weights_synthetic_grads = (np.random.randn(output_dim,output_dim) * 0.2) - 0.1

self.alpha = alpha

# 之前僅僅是`forward`,現(xiàn)在我們在前向傳播中基于合成梯度更新權重

def forward_and_synthetic_update(self,input):

# 緩存輸入

self.input = input

# 前向傳播

self.output = self.nonlin(self.input.dot(self.weights))

# 基于簡單的線性變換生成合成梯度

self.synthetic_gradient = self.output.dot(self.weights_synthetic_grads)

# 使用合成梯度更新權重

self.weight_synthetic_gradient = self.synthetic_gradient * self.nonlin_deriv(self.output)

self.weights += self.input.T.dot(self.weight_synthetic_gradient) * self.alpha

# 返回反向傳播的合成梯度(這類似Layer類的backprop方法的輸出)

# 同時返回前向傳播的輸出(我知道這有點怪……)

return self.weight_synthetic_gradient.dot(self.weights.T), self.output

# 和之前的`update`方法類似……除了基于合成權重之外

def update_synthetic_weights(self,true_gradient):

self.synthetic_gradient_delta = self.synthetic_gradient - true_gradient

self.weights_synthetic_grads += self.output.T.dot(self.synthetic_gradient_delta) * self.alpha

我們有了一些新的變量。唯一關鍵的是self.weights_synthetic_grads,這是我們的合成梯度生成器神經(jīng)網(wǎng)絡(只是一個線性層……也就是……一個矩陣)。

前向傳播和合成更新:forward方法變?yōu)閒orward_and_synthetic_update。還記得我們不需要網(wǎng)絡的其他部分來更新權重嗎?這就是魔法發(fā)生之處。首先,照常進行前向傳播。接著,我們通過將輸出傳給一個非線性生成合成梯度。這一部分本可以是一個更復雜的神經(jīng)網(wǎng)絡,不過我們沒有這么做,而是決定保持簡單性,直接使用一個簡單的線性層生成我們的合成梯度。得到我們的梯度之后,我們繼續(xù)更新權重。最后,我們反向傳播合成梯度,以便發(fā)送給之前的層。

更新合成梯度:下一層的update_synthetic_gradient方法將接受上一層的forward_and_synthetic_update方法返回的梯度。所以,如果我們位于第二層,那么第三層的forward_and_synthetic_update方法返回的梯度將作為第二層的update_synthetic_weights的輸入。接著,我們直接更新合成權重,就像在普通的神經(jīng)網(wǎng)絡中做的那樣。這和通常的神經(jīng)網(wǎng)絡的學習沒什么兩樣,只不過我們使用了一些特別的輸入和輸出而已。

基于合成梯度方法訓練網(wǎng)絡,我發(fā)現(xiàn)它不像我預料的那樣收斂。我的意思是,它在收斂,但是收斂得非常慢。我仔細調查了一下,發(fā)現(xiàn)隱藏的表示(也就是梯度生成器的輸入)在開始時比較扁平和隨機。換句話說,兩個不同的訓練樣本在不同網(wǎng)絡層結果會有幾乎一樣的輸出表示。這大大增加了梯度生成器工作的難度。在論文中,作者使用的解決方案是批歸一化,批歸一化將所有網(wǎng)絡層輸出縮放至0均值和單位方差。此外,論文還提到你可以使用其他形式的梯度生成器輸入。對于我們的簡單玩具神經(jīng)網(wǎng)絡而言,批歸一化會加入大量復雜度。因此,我嘗試了使用輸出數(shù)據(jù)集。這并沒有破壞解耦狀態(tài)(秉持了DNI的精神),但在開始階段給網(wǎng)絡提供了非常強力的信息。

進行了這一改動后,訓練起來快多了!思考哪些可以充當梯度生成器的優(yōu)良輸入真是一項迷人的活動。也許輸入數(shù)據(jù)、輸出數(shù)據(jù)、批歸一化層輸出的某種組合會是最佳的(歡迎嘗試?。┫M阆矚g這篇教程。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權轉載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴

原文標題:基于Numpy實現(xiàn)神經(jīng)網(wǎng)絡:合成梯度

文章出處:【微信號:jqr_AI,微信公眾號:論智】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    什么是NumPy?選擇NUMPY的原因及其工作原理是什么

    NumPy 是一個免費的 Python 編程語言開源庫,它功能強大、已經(jīng)過充分優(yōu)化,并增加了對大型多維數(shù)組(也稱為矩陣或張量)的支持。
    的頭像 發(fā)表于 07-15 09:37 ?3888次閱讀

    如何更新權重實現(xiàn)梯度下降

    實現(xiàn)梯度下降
    發(fā)表于 07-15 10:09

    Numpy的學習總結

    Numpy學習筆記
    發(fā)表于 07-16 08:27

    機器學習:隨機梯度下降和批量梯度下降算法介紹

    梯度下降和批量梯度下降是兩種迭代求解思路,下面從公式和實現(xiàn)的角度對兩者進行分析。下面的h(x)是要擬合的函數(shù),J(theta)損失函數(shù),theta是參數(shù),要迭代求解的,theta求解出來了那最終要擬合的函數(shù)h(theta)就出來
    發(fā)表于 11-28 04:00 ?8807次閱讀
    機器學習:隨機<b class='flag-5'>梯度</b>下降和批量<b class='flag-5'>梯度</b>下降算法介紹

    基于python的numpy深度解析

    numpy(Numerical Python)提供了python對多維數(shù)組對象的支持:ndarray,具有矢量運算能力,快速、節(jié)省空間。numpy支持高級大量的維度數(shù)組與矩陣運算,此外也針對數(shù)組運算提供大量的數(shù)學函數(shù)庫。
    的頭像 發(fā)表于 01-24 13:55 ?5208次閱讀
    基于python的<b class='flag-5'>numpy</b>深度解析

    基于Numpy實現(xiàn)同態(tài)加密神經(jīng)網(wǎng)絡

    在分布式AI環(huán)境下,同態(tài)加密神經(jīng)網(wǎng)絡有助于保護商業(yè)公司知識產(chǎn)權和消費者隱私。本文介紹了如何基于Numpy實現(xiàn)同態(tài)加密神經(jīng)網(wǎng)絡。
    的頭像 發(fā)表于 03-27 14:52 ?7896次閱讀
    基于<b class='flag-5'>Numpy</b><b class='flag-5'>實現(xiàn)</b>同態(tài)加密神經(jīng)網(wǎng)絡

    基于Numpy實現(xiàn)神經(jīng)網(wǎng)絡:反向傳播

    和DeepMind數(shù)據(jù)科學家、Udacity深度學習導師Andrew Trask一起,基于Numpy手寫神經(jīng)網(wǎng)絡,更深刻地理解反向傳播這一概念。
    的頭像 發(fā)表于 04-01 09:29 ?5080次閱讀
    基于<b class='flag-5'>Numpy</b><b class='flag-5'>實現(xiàn)</b>神經(jīng)網(wǎng)絡:反向傳播

    如何使用Python和Numpy等技術實現(xiàn)圖像處理

    本文檔的主要內容詳細介紹的是如何使用Python、Numpy、Scipy和matplotlib執(zhí)行圖像處理任務。
    發(fā)表于 08-28 09:36 ?8次下載
    如何使用Python和<b class='flag-5'>Numpy</b>等技術<b class='flag-5'>實現(xiàn)</b>圖像處理

    最詳細的 NumPy 圖解教程!

    NumPy是Python中用于數(shù)據(jù)分析、機器學習、科學計算的重要軟件包。它極大地簡化了向量和矩陣的操作及處理。python的不少數(shù)據(jù)處理軟件包依賴于NumPy作為其基礎架構的核心部分(例如
    的頭像 發(fā)表于 06-09 18:03 ?2398次閱讀
    最詳細的 <b class='flag-5'>NumPy</b> 圖解教程!

    Numpy詳解-軸的概念

    NumPy數(shù)組的維數(shù)稱為秩(rank),一維數(shù)組的秩為1,二維數(shù)組的秩為2,以此類推。在NumPy中,每一個線性的數(shù)組稱為是一個軸(axes),秩其實是描述軸的數(shù)量。
    的頭像 發(fā)表于 04-25 10:25 ?2851次閱讀

    Numpy數(shù)組的高級操作總結

    NumPy 包含一個迭代器對象numpy.nditer。它是一個有效的多維迭代器對象,可以用于在數(shù)組上進行迭代。數(shù)組的每個元素可使用 Python 的標準Iterator接口來訪問。
    的頭像 發(fā)表于 05-13 12:53 ?1311次閱讀

    使用Numpy和OpenCV實現(xiàn)傅里葉和逆傅里葉變換

      文章從實際出發(fā),講述了什么是傅里葉變換,它的理論基礎以及Numpy和OpenCV實現(xiàn)傅里葉和逆傅里葉變換,并最終用高通濾波和低通濾波的示例。
    的頭像 發(fā)表于 07-05 16:04 ?1547次閱讀

    Python 梯度計算模塊如何實現(xiàn)一個邏輯回歸模型

    AutoGrad 是一個老少皆宜的 Python 梯度計算模塊。 對于初高中生而言,它可以用來輕易計算一條曲線在任意一個點上的斜率。 對于大學生、機器學習愛好者而言,你只需要傳遞給它Numpy這樣
    的頭像 發(fā)表于 10-21 11:01 ?465次閱讀
    Python <b class='flag-5'>梯度</b>計算模塊如何<b class='flag-5'>實現(xiàn)</b>一個邏輯回歸模型

    List和Numpy Array有什么區(qū)別

    Numpy 是Python科學計算的一個核心模塊。它提供了非常高效的數(shù)組對象,以及用于處理這些數(shù)組對象的工具。一個Numpy數(shù)組由許多值組成,所有值的類型是相同的。 Python的核心庫提供了
    的頭像 發(fā)表于 10-30 10:49 ?810次閱讀
    List和<b class='flag-5'>Numpy</b> Array有什么區(qū)別

    基于NumPy的機器學習算法實現(xiàn)

    David Bourgin 表示他一直在慢慢寫或收集不同模型與模塊的純 NumPy 實現(xiàn),它們跑起來可能沒那么快,但是模型的具體過程一定足夠直觀。每當我們想了解模型 API 背后的實現(xiàn),卻又不想看復雜的框架代碼,那么它可以作為快
    發(fā)表于 01-17 12:36 ?181次閱讀
    基于<b class='flag-5'>NumPy</b>的機器學習算法<b class='flag-5'>實現(xiàn)</b>