如果把神經(jīng)網(wǎng)絡(luò)模型比作一個(gè)黑箱,把模型參數(shù)比作黑箱上面一個(gè)個(gè)小旋鈕,那么根據(jù)通用近似理論(universal approximation theorem),只要黑箱上的旋鈕數(shù)量足夠多,而且每個(gè)旋鈕都被調(diào)節(jié)到合適的位置,那這個(gè)模型就可以實(shí)現(xiàn)近乎任意功能(可以逼近任意的數(shù)學(xué)模型)。
創(chuàng)新互聯(lián)專注于庫(kù)車網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供庫(kù)車營(yíng)銷型網(wǎng)站建設(shè),庫(kù)車網(wǎng)站制作、庫(kù)車網(wǎng)頁(yè)設(shè)計(jì)、庫(kù)車網(wǎng)站官網(wǎng)定制、成都微信小程序服務(wù),打造庫(kù)車網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供庫(kù)車網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
顯然,這些旋鈕(參數(shù))不是由人工調(diào)節(jié)的,所謂的機(jī)器學(xué)習(xí),就是通過程序來自動(dòng)調(diào)節(jié)這些參數(shù)。神經(jīng)網(wǎng)絡(luò)不僅參數(shù)眾多(少則十幾萬,多則上億),而且網(wǎng)絡(luò)是由線性層和非線性層交替疊加而成,上層參數(shù)的變化會(huì)對(duì)下層的輸出產(chǎn)生非線性的影響,因此,早期的神經(jīng)網(wǎng)絡(luò)流派一度無法往多層方向發(fā)展,因?yàn)樗麄冋也坏侥苡糜谌我舛鄬泳W(wǎng)絡(luò)的、簡(jiǎn)潔的自動(dòng)調(diào)節(jié)參數(shù)的方法。
直到上世紀(jì)80年代,祖師爺辛頓發(fā)明了反向傳播算法,用輸出誤差的均方差(就是loss值)一層一層遞進(jìn)地反饋到各層神經(jīng)網(wǎng)絡(luò),用梯度下降法來調(diào)節(jié)每層網(wǎng)絡(luò)的參數(shù)。至此,神經(jīng)網(wǎng)絡(luò)才得以開始它的深度之旅。
本文用python自己動(dòng)手實(shí)現(xiàn)梯度下降和反向傳播算法。 請(qǐng)點(diǎn)擊這里 到Github上查看源碼。
梯度下降法是一種將輸出誤差反饋到神經(jīng)網(wǎng)絡(luò)并自動(dòng)調(diào)節(jié)參數(shù)的方法,它通過計(jì)算輸出誤差的loss值( J )對(duì)參數(shù) W 的導(dǎo)數(shù),并沿著導(dǎo)數(shù)的反方向來調(diào)節(jié) W ,經(jīng)過多次這樣的操作,就能將輸出誤差減小到最小值,即曲線的最低點(diǎn)。
雖然Tensorflow、Pytorch這些框架都實(shí)現(xiàn)了自動(dòng)求導(dǎo)的功能,但為了徹底理解參數(shù)調(diào)節(jié)的過程,還是有必要自己動(dòng)手實(shí)現(xiàn)梯度下降和反向傳播算法。我相信你和我一樣,已經(jīng)忘了之前學(xué)的微積分知識(shí),因此,到可汗學(xué)院復(fù)習(xí)下 Calculus
和 Multivariable Calculus 是個(gè)不錯(cuò)的方法,或是拜讀 這篇關(guān)于神經(jīng)網(wǎng)絡(luò)矩陣微積分的文章 。
Figure2是求導(dǎo)的基本公式,其中最重要的是 Chain Rule ,它通過引入中間變量,將“ y 對(duì) x 求導(dǎo)”的過程轉(zhuǎn)換為“ y 對(duì)中間變量 u 求導(dǎo),再乘以 u 對(duì) x 求導(dǎo)”,這樣就將一個(gè)復(fù)雜的函數(shù)鏈求導(dǎo)簡(jiǎn)化為多個(gè)簡(jiǎn)單函數(shù)求導(dǎo)。
如果你不想涉及這些求導(dǎo)的細(xì)節(jié),可以跳過具體的計(jì)算,領(lǐng)會(huì)其思想就好。
對(duì)于神經(jīng)網(wǎng)絡(luò)模型: Linear - ReLu - Linear - MSE(Loss function) 來說,反向傳播就是根據(jù)鏈?zhǔn)椒▌t對(duì) 求導(dǎo),用輸出誤差的均方差(MSE)對(duì)模型的輸出求導(dǎo),并將導(dǎo)數(shù)傳回上一層神經(jīng)網(wǎng)絡(luò),用于它們來對(duì) w 、 b 和 x (上上層的輸出)求導(dǎo),再將 x 的導(dǎo)數(shù)傳回到它的上一層神經(jīng)網(wǎng)絡(luò),由此將輸出誤差的均方差通過遞進(jìn)的方式反饋到各神經(jīng)網(wǎng)絡(luò)層。
對(duì)于 求導(dǎo)的第一步是為這個(gè)函數(shù)鏈引入中間變量:
接著第二步是對(duì)各中間變量求導(dǎo),最后才是將這些導(dǎo)數(shù)乘起來。
首先,反向傳播的起點(diǎn)是對(duì)loss function求導(dǎo),即 。 :
mse_grad()之所以用unsqueeze(-1)給導(dǎo)數(shù)增加一個(gè)維度,是為了讓導(dǎo)數(shù)的shape和tensor shape保持一致。
linear層的反向傳播是對(duì) 求導(dǎo),它也是一個(gè)函數(shù)鏈,也要先對(duì)中間變量求導(dǎo)再將所有導(dǎo)數(shù)相乘:
這些中間變量的導(dǎo)數(shù)分別是:
對(duì)向量 求導(dǎo),指的是對(duì)向量所有的標(biāo)量求偏導(dǎo)( ),即: ,這個(gè)橫向量也稱為y的梯度。
這里 ,是一個(gè)向量,因此, 求導(dǎo),指的是y的所有標(biāo)量(y_1, y_2, ..., y_n)對(duì)向量x求偏導(dǎo),即:
。
這個(gè)矩陣稱為雅克比矩陣,它是個(gè)對(duì)角矩陣,因?yàn)? ,因此 。
同理, 。
因此,所有中間導(dǎo)數(shù)相乘的結(jié)果:
lin_grad() 中的inp.g、w.g和b.g分別是求 的導(dǎo)數(shù),以inp.g為例,它等于 ,且需要乘以前面各層的導(dǎo)數(shù),即 outp.g @ w.t() ,之所以要用點(diǎn)積運(yùn)算符(@)而不是標(biāo)量相乘,是為了讓它的導(dǎo)數(shù)shape和tensor shape保持一致。同理,w.g和b.g也是根據(jù)相同邏輯來計(jì)算的。
ReLu層的求導(dǎo)相對(duì)來說就簡(jiǎn)單多了,當(dāng)輸入 = 0時(shí),導(dǎo)數(shù)為0,當(dāng)輸入 0時(shí),導(dǎo)數(shù)為1。
求導(dǎo)運(yùn)算終于結(jié)束了,接下來就是驗(yàn)證我們的反向傳播是否正確。驗(yàn)證方法是將forward_backward()計(jì)算的導(dǎo)數(shù)和Pytorch自動(dòng)微分得到的導(dǎo)數(shù)相比較,如果它們相近,就認(rèn)為我們的反向傳播算法是正確的。
首先,將計(jì)算好的參數(shù)導(dǎo)數(shù)保存到w1g、b1g、w2g和b2g中,再用Pytorch的自動(dòng)微分來求w11、b11、w22和b22的導(dǎo)數(shù)。
最后,用np.allclose()來比較導(dǎo)數(shù)間的差異,如果有任何一個(gè)導(dǎo)數(shù)不相近,assert就會(huì)報(bào)錯(cuò)。結(jié)果證明,我們自己動(dòng)手實(shí)現(xiàn)的算法是正確的。
反向傳播是遵循鏈?zhǔn)椒▌t的,它將前向傳播的輸出作為輸入,輸入作為輸出,通過遞進(jìn)的方式將求導(dǎo)這個(gè)動(dòng)作從后向前傳遞回各層。神經(jīng)網(wǎng)絡(luò)參數(shù)的求導(dǎo)需要進(jìn)行矩陣微積分計(jì)算,根據(jù)這些導(dǎo)數(shù)的反方向來調(diào)節(jié)參數(shù),就可以讓模型的輸出誤差的優(yōu)化到最小值。
歡迎關(guān)注和點(diǎn)贊,你的鼓勵(lì)將是我創(chuàng)作的動(dòng)力
常用形式
odeint(func, y0, t,args,Dfun)
一般這種形式就夠用了。
下面是官方的例子,求解的是
D(D(y1))-t*y1=0
為了方便,采取D=d/dt。如果我們令初值
y1(0) = 1.0/3**(2.0/3.0)/gamma(2.0/3.0)
D(y1)(0) = -1.0/3**(1.0/3.0)/gamma(1.0/3.0)
這個(gè)微分方程的解y1=airy(t)。
令D(y1)=y0,就有這個(gè)常微分方程組。
D(y0)=t*y1
D(y1)=y0
Python求解該微分方程。
from scipy.integrate import odeint
from scipy.special import gamma, airy
y1_0 = 1.0/3**(2.0/3.0)/gamma(2.0/3.0)
y0_0 = -1.0/3**(1.0/3.0)/gamma(1.0/3.0)
y0 = [y0_0, y1_0]
def func(y, t):
... return [t*y[1],y[0]]
def gradient(y,t):
... return [[0,t],[1,0]]
x = arange(0,4.0, 0.01)
t = x
ychk = airy(x)[0]
y = odeint(func, y0, t)
y2 = odeint(func, y0, t, Dfun=gradient)
print ychk[:36:6]
[ 0.355028 0.339511 0.324068 0.308763 0.293658 0.278806]
print y[:36:6,1]
[ 0.355028 0.339511 0.324067 0.308763 0.293658 0.278806]
print y2[:36:6,1]
[ 0.355028 0.339511 0.324067 0.308763 0.293658 0.278806]
得到的解與精確值相比,誤差相當(dāng)小。
=======================================================================================================
args是額外的參數(shù)。
用法請(qǐng)參看下面的例子。這是一個(gè)洛侖茲曲線的求解,并且用matplotlib繪出空間曲線圖。(來自《python科學(xué)計(jì)算》)
from scipy.integrate import odeint
import numpy as np
def lorenz(w, t, p, r, b):
# 給出位置矢量w,和三個(gè)參數(shù)p, r, b 計(jì)算出
# dx/dt, dy/dt, dz/dt 的值
x, y, z = w
# 直接與lorenz 的計(jì)算公式對(duì)應(yīng)
return np.array([p*(y-x), x*(r-z)-y, x*y-b*z])
t = np.arange(0, 30, 0.01) # 創(chuàng)建時(shí)間點(diǎn)
# 調(diào)用ode 對(duì)lorenz 進(jìn)行求解, 用兩個(gè)不同的初始值
track1 = odeint(lorenz, (0.0, 1.00, 0.0), t, args=(10.0, 28.0, 3.0))
track2 = odeint(lorenz, (0.0, 1.01, 0.0), t, args=(10.0, 28.0, 3.0))
# 繪圖
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
fig = plt.figure()
ax = Axes3D(fig)
ax.plot(track1[:,0], track1[:,1], track1[:,2])
ax.plot(track2[:,0], track2[:,1], track2[:,2])
plt.show()
===========================================================================
scipy.integrate.odeint(func, y0, t, args=(), Dfun=None, col_deriv=0, full_output=0, ml=None, mu=None, rtol=None, atol=None, tcrit=None, h0=0.0, hmax=0.0, hmin=0.0, ixpr=0, mxstep=0, mxhnil=0, mxordn=12, mxords=5, printmessg=0)
計(jì)算常微分方程(組)
使用 FORTRAN庫(kù)odepack中的lsoda解常微分方程。這個(gè)函數(shù)一般求解初值問題。
參數(shù):
func : callable(y, t0, ...) 計(jì)算y在t0 處的導(dǎo)數(shù)。
y0 : 數(shù)組 y的初值條件(可以是矢量)
t : 數(shù)組 為求出y,這是一個(gè)時(shí)間點(diǎn)的序列。初值點(diǎn)應(yīng)該是這個(gè)序列的第一個(gè)元素。
args : 元組 func的額外參數(shù)
Dfun : callable(y, t0, ...) 函數(shù)的梯度(Jacobian)。即雅可比多項(xiàng)式。
col_deriv : boolean. True,Dfun定義列向?qū)?shù)(更快),否則Dfun會(huì)定義橫排導(dǎo)數(shù)
full_output : boolean 可選輸出,如果為True 則返回一個(gè)字典,作為第二輸出。
printmessg : boolean 是否打印convergence 消息。
返回: y : array, shape (len(y0), len(t))
數(shù)組,包含y值,每一個(gè)對(duì)應(yīng)于時(shí)間序列中的t。初值y0 在第一排。
infodict : 字典,只有full_output == True 時(shí),才會(huì)返回。
字典包含額為的輸出信息。
鍵值:
‘hu’ vector of step sizes successfully used for each time step.
‘tcur’ vector with the value of t reached for each time step. (will always be at least as large as the input times).
‘tolsf’ vector of tolerance scale factors, greater than 1.0, computed when a request for too much accuracy was detected.
‘tsw’ value of t at the time of the last method switch (given for each time step)
‘nst’ cumulative number of time steps
‘nfe’ cumulative number of function evaluations for each time step
‘nje’ cumulative number of jacobian evaluations for each time step
‘nqu’ a vector of method orders for each successful step.
‘imxer’index of the component of largest magnitude in the weighted local error vector (e / ewt) on an error return, -1 otherwise.
‘lenrw’ the length of the double work array required.
‘leniw’ the length of integer work array required.
‘mused’a vector of method indicators for each successful time step: 1: adams (nonstiff), 2: bdf (stiff)
其他參數(shù),官方網(wǎng)站和文檔都沒有明確說明。相關(guān)的資料,暫時(shí)也找不到。
上一期提到的圖像閾值處理,不僅可以實(shí)現(xiàn)獲取你想要的目標(biāo)區(qū)域(作為mask使用),還可以幫你獲取圖像的邊緣信息,那關(guān)于圖像邊緣,本期將從另外的角度來處理。
對(duì)邊緣信息與背景差異較大的場(chǎng)景,你也可以使用threshold分割,不過若閾值不好選取,Laplacian梯度算子就不失為一直嘗試方案,而且上網(wǎng)看看,關(guān)于Laplacian算子還可以用來判斷圖像的模糊程度,這個(gè)在相機(jī)的自動(dòng)對(duì)焦當(dāng)中,是否可以嘗試判斷下?
不過處理的效果并不理想,圖像低灰階部分邊緣信息丟失嚴(yán)重。
對(duì)于sobel,laplacian算子我們可以使用cv2.filter2D()來實(shí)現(xiàn),配置相應(yīng)的核模板即可,如實(shí)現(xiàn)提取水平方向邊緣信息:
你可以依據(jù)實(shí)際的應(yīng)用需求來配置提取邊緣的角度信息,這里以45度角(垂直向下逆時(shí)針旋轉(zhuǎn)45度)為例:
對(duì)此,你可以采用下面的方式來解決:
如果函數(shù)是確定的,可以用導(dǎo)數(shù)的方法進(jìn)行計(jì)算,但是如果函數(shù)是不確定的,就需要用優(yōu)化的方法來處理了,比如常用的梯度上升法,模擬退火等,希望可以幫到你。