從零開始使用 Nadam 進(jìn)行梯度下降優(yōu)化
梯度下降是一種優(yōu)化算法,遵循目標(biāo)函數(shù)的負(fù)梯度以定位函數(shù)的最小值。
梯度下降的局限性在于,如果梯度變?yōu)槠教够虼笄剩阉鞯倪M(jìn)度可能會減慢。可以將動量添加到梯度下降中,該下降合并了一些慣性以進(jìn)行更新??梢酝ㄟ^合并預(yù)計的新位置而非當(dāng)前位置的梯度(稱為Nesterov的加速梯度(NAG)或Nesterov動量)來進(jìn)一步改善此效果。
梯度下降的另一個限制是,所有輸入變量都使用單個步長(學(xué)習(xí)率)。對梯度下降的擴(kuò)展,如自適應(yīng)運動估計(Adam)算法,該算法對每個輸入變量使用單獨的步長,但可能會導(dǎo)致步長迅速減小到非常小的值。Nesterov加速的自適應(yīng)矩估計或Nadam是Adam算法的擴(kuò)展,該算法結(jié)合了Nesterov動量,可以使優(yōu)化算法具有更好的性能。
在本教程中,您將發(fā)現(xiàn)如何從頭開始使用Nadam進(jìn)行梯度下降優(yōu)化。完成本教程后,您將知道:
- 梯度下降是一種優(yōu)化算法,它使用目標(biāo)函數(shù)的梯度來導(dǎo)航搜索空間。
- 納丹(Nadam)是亞當(dāng)(Adam)版本的梯度下降的擴(kuò)展,其中包括了內(nèi)斯特羅夫的動量。
- 如何從頭開始實現(xiàn)Nadam優(yōu)化算法并將其應(yīng)用于目標(biāo)函數(shù)并評估結(jié)果。
教程概述
本教程分為三個部分:他們是:
- 梯度下降
- Nadam優(yōu)化算法
- 娜達(dá)姆(Nadam)的梯度下降
- 二維測試問題
- Nadam的梯度下降優(yōu)化
- 可視化的Nadam優(yōu)化
梯度下降
梯度下降是一種優(yōu)化算法。它在技術(shù)上稱為一階優(yōu)化算法,因為它明確利用了目標(biāo)目標(biāo)函數(shù)的一階導(dǎo)數(shù)。
一階導(dǎo)數(shù),或簡稱為“導(dǎo)數(shù)”,是目標(biāo)函數(shù)在特定點(例如,點)上的變化率或斜率。用于特定輸入。
如果目標(biāo)函數(shù)采用多個輸入變量,則將其稱為多元函數(shù),并且可以將輸入變量視為向量。反過來,多元目標(biāo)函數(shù)的導(dǎo)數(shù)也可以視為向量,通常稱為梯度。
梯度:多元目標(biāo)函數(shù)的一階導(dǎo)數(shù)。
對于特定輸入,導(dǎo)數(shù)或梯度指向目標(biāo)函數(shù)最陡峭的上升方向。梯度下降是指一種最小化優(yōu)化算法,該算法遵循目標(biāo)函數(shù)的下坡梯度負(fù)值來定位函數(shù)的最小值。
梯度下降算法需要一個正在優(yōu)化的目標(biāo)函數(shù)和該目標(biāo)函數(shù)的導(dǎo)數(shù)函數(shù)。目標(biāo)函數(shù)f()返回給定輸入集合的分?jǐn)?shù),導(dǎo)數(shù)函數(shù)f'()給出給定輸入集合的目標(biāo)函數(shù)的導(dǎo)數(shù)。梯度下降算法需要問題中的起點(x),例如輸入空間中的隨機(jī)選擇點。
假設(shè)我們正在最小化目標(biāo)函數(shù),然后計算導(dǎo)數(shù)并在輸入空間中采取一步,這將導(dǎo)致目標(biāo)函數(shù)下坡運動。首先通過計算輸入空間中要移動多遠(yuǎn)的距離來進(jìn)行下坡運動,計算方法是將步長(稱為alpha或?qū)W習(xí)率)乘以梯度。然后從當(dāng)前點減去該值,以確保我們逆梯度移動或向下移動目標(biāo)函數(shù)。
- x(t)= x(t-1)–step* f'(x(t))
在給定點的目標(biāo)函數(shù)越陡峭,梯度的大小越大,反過來,在搜索空間中采取的步伐也越大。使用步長超參數(shù)來縮放步長的大小。
步長:超參數(shù),用于控制算法每次迭代相對于梯度在搜索空間中移動多遠(yuǎn)。
如果步長太小,則搜索空間中的移動將很小,并且搜索將花費很長時間。如果步長太大,則搜索可能會在搜索空間附近反彈并跳過最優(yōu)值。
現(xiàn)在我們已經(jīng)熟悉了梯度下降優(yōu)化算法,接下來讓我們看一下Nadam算法。
Nadam優(yōu)化算法
Nesterov加速的自適應(yīng)動量估計或Nadam算法是對自適應(yīng)運動估計(Adam)優(yōu)化算法的擴(kuò)展,添加了Nesterov的加速梯度(NAG)或Nesterov動量,這是一種改進(jìn)的動量。更廣泛地講,Nadam算法是對梯度下降優(yōu)化算法的擴(kuò)展。Timothy Dozat在2016年的論文“將Nesterov動量整合到Adam中”中描述了該算法。盡管論文的一個版本是在2015年以同名斯坦福項目報告的形式編寫的。動量將梯度的指數(shù)衰減移動平均值(第一矩)添加到梯度下降算法中。這具有消除嘈雜的目標(biāo)函數(shù)和提高收斂性的影響。Adam是梯度下降的擴(kuò)展,它增加了梯度的第一和第二矩,并針對正在優(yōu)化的每個參數(shù)自動調(diào)整學(xué)習(xí)率。NAG是動量的擴(kuò)展,其中動量的更新是使用對參數(shù)的預(yù)計更新量而不是實際當(dāng)前變量值的梯度來執(zhí)行的。在某些情況下,這樣做的效果是在找到最佳位置時減慢了搜索速度,而不是過沖。
納丹(Nadam)是對亞當(dāng)(Adam)的擴(kuò)展,它使用NAG動量代替經(jīng)典動量。讓我們逐步介紹該算法的每個元素。Nadam使用衰減步長(alpha)和一階矩(mu)超參數(shù)來改善性能。為了簡單起見,我們暫時將忽略此方面,并采用恒定值。首先,對于搜索中要優(yōu)化的每個參數(shù),我們必須保持梯度的第一矩和第二矩,分別稱為m和n。在搜索開始時將它們初始化為0.0。
- m = 0
- n = 0
該算法在從t = 1開始的時間t內(nèi)迭代執(zhí)行,并且每次迭代都涉及計算一組新的參數(shù)值x,例如。從x(t-1)到x(t)。如果我們專注于更新一個參數(shù),這可能很容易理解該算法,該算法概括為通過矢量運算來更新所有參數(shù)。首先,計算當(dāng)前時間步長的梯度(偏導(dǎo)數(shù))。
- g(t)= f'(x(t-1))
接下來,使用梯度和超參數(shù)“ mu”更新第一時刻。
- m(t)=mu* m(t-1)+(1 –mu)* g(t)
然后使用“ nu”超參數(shù)更新第二時刻。
- n(t)= nu * n(t-1)+(1 – nu)* g(t)^ 2
接下來,使用Nesterov動量對第一時刻進(jìn)行偏差校正。
- mhat =(mu * m(t)/(1 – mu))+((1 – mu)* g(t)/(1 – mu))
然后對第二個時刻進(jìn)行偏差校正。注意:偏差校正是Adam的一個方面,它與在搜索開始時將第一時刻和第二時刻初始化為零這一事實相反。
- nhat = nu * n(t)/(1 – nu)
最后,我們可以為該迭代計算參數(shù)的值。
- x(t)= x(t-1)– alpha /(sqrt(nhat)+ eps)* mhat
其中alpha是步長(學(xué)習(xí)率)超參數(shù),sqrt()是平方根函數(shù),eps(epsilon)是一個較小的值,如1e-8,以避免除以零誤差。
回顧一下,該算法有三個超參數(shù)。他們是:
- alpha:初始步長(學(xué)習(xí)率),典型值為0.002。
- mu:第一時刻的衰減因子(Adam中的beta1),典型值為0.975。
- nu:第二時刻的衰減因子(Adam中的beta2),典型值為0.999。
就是這樣。接下來,讓我們看看如何在Python中從頭開始實現(xiàn)該算法。
娜達(dá)姆(Nadam)的梯度下降
在本節(jié)中,我們將探索如何使用Nadam動量實現(xiàn)梯度下降優(yōu)化算法。
二維測試問題
首先,讓我們定義一個優(yōu)化函數(shù)。我們將使用一個簡單的二維函數(shù),該函數(shù)將每個維的輸入平方,并定義有效輸入的范圍(從-1.0到1.0)。下面的Objective()函數(shù)實現(xiàn)了此功能
- # objective function
- def objective(x, y):
- return x**2.0 + y**2.0
我們可以創(chuàng)建數(shù)據(jù)集的三維圖,以了解響應(yīng)面的曲率。下面列出了繪制目標(biāo)函數(shù)的完整示例。
- # 3d plot of the test function
- from numpy import arange
- from numpy import meshgrid
- from matplotlib import pyplot
- # objective function
- def objective(x, y):
- return x**2.0 + y**2.0
- # define range for input
- r_min, r_max = -1.0, 1.0
- # sample input range uniformly at 0.1 increments
- xaxis = arange(r_min, r_max, 0.1)
- yaxis = arange(r_min, r_max, 0.1)
- # create a mesh from the axis
- x, y = meshgrid(xaxis, yaxis)
- # compute targets
- results = objective(x, y)
- # create a surface plot with the jet color scheme
- figure = pyplot.figure()
- axis = figure.gca(projection='3d')
- axis.plot_surface(x, y, results, cmap='jet')
- # show the plot
- pyplot.show()
運行示例將創(chuàng)建目標(biāo)函數(shù)的三維表面圖。我們可以看到全局最小值為f(0,0)= 0的熟悉的碗形狀。
我們還可以創(chuàng)建函數(shù)的二維圖。這在以后要繪制搜索進(jìn)度時會很有幫助。下面的示例創(chuàng)建目標(biāo)函數(shù)的輪廓圖。
- # contour plot of the test function
- from numpy import asarray
- from numpy import arange
- from numpy import meshgrid
- from matplotlib import pyplot
- # objective function
- def objective(x, y):
- return x**2.0 + y**2.0
- # define range for input
- bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
- # sample input range uniformly at 0.1 increments
- xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
- yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
- # create a mesh from the axis
- x, y = meshgrid(xaxis, yaxis)
- # compute targets
- results = objective(x, y)
- # create a filled contour plot with 50 levels and jet color scheme
- pyplot.contourf(x, y, results, levels=50, cmap='jet')
- # show the plot
- pyplot.show()
運行示例將創(chuàng)建目標(biāo)函數(shù)的二維輪廓圖。我們可以看到碗的形狀被壓縮為以顏色漸變顯示的輪廓。我們將使用該圖來繪制在搜索過程中探索的特定點。
現(xiàn)在我們有了一個測試目標(biāo)函數(shù),讓我們看一下如何實現(xiàn)Nadam優(yōu)化算法。
Nadam的梯度下降優(yōu)化
我們可以將Nadam的梯度下降應(yīng)用于測試問題。首先,我們需要一個函數(shù)來計算此函數(shù)的導(dǎo)數(shù)。
x ^ 2的導(dǎo)數(shù)在每個維度上均為x * 2。
- f(x)= x ^ 2
- f'(x)= x * 2
derived()函數(shù)在下面實現(xiàn)了這一點。
- # derivative of objective function
- def derivative(x, y):
- return asarray([x * 2.0, y * 2.0])
接下來,我們可以使用Nadam實現(xiàn)梯度下降優(yōu)化。首先,我們可以選擇問題范圍內(nèi)的隨機(jī)點作為搜索的起點。假定我們有一個數(shù)組,該數(shù)組定義搜索范圍,每個維度一行,并且第一列定義最小值,第二列定義維度的最大值。
- # generate an initial point
- x = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
- score = objective(x[0], x[1])
接下來,我們需要初始化力矩矢量。
- # initialize decaying moving averages
- m = [0.0 for _ in range(bounds.shape[0])]
- n = [0.0 for _ in range(bounds.shape[0])]
然后,我們運行由“ n_iter”超參數(shù)定義的算法的固定迭代次數(shù)。
- ...
- # run iterations of gradient descent
- for t in range(n_iter):
- ...
第一步是計算當(dāng)前參數(shù)集的導(dǎo)數(shù)。
- ...
- # calculate gradient g(t)
- g = derivative(x[0], x[1])
接下來,我們需要執(zhí)行Nadam更新計算。為了提高可讀性,我們將使用命令式編程樣式來一次執(zhí)行一個變量的這些計算。在實踐中,我建議使用NumPy向量運算以提高效率。
- ...
- # build a solution one variable at a time
- for i in range(x.shape[0]):
- ...
首先,我們需要計算力矩矢量。
- # m(t) = mu * m(t-1) + (1 - mu) * g(t)
- m[i] = mu * m[i] + (1.0 - mu) * g[i]
然后是第二個矩向量。
- # nhat = nu * n(t) / (1 - nu)
- nhat = nu * n[i] / (1.0 - nu)
- # n(t) = nu * n(t-1) + (1 - nu) * g(t)^2
- n[i] = nu * n[i] + (1.0 - nu) * g[i]**2
然后是經(jīng)過偏差校正的內(nèi)斯特羅夫動量。
- # mhat = (mu * m(t) / (1 - mu)) + ((1 - mu) * g(t) / (1 - mu))
- mhat = (mu * m[i] / (1.0 - mu)) + ((1 - mu) * g[i] / (1.0 - mu))
偏差校正的第二時刻。
- # nhat = nu * n(t) / (1 - nu)
- nhat = nu * n[i] / (1.0 - nu)
最后更新參數(shù)。
- # x(t) = x(t-1) - alpha / (sqrt(nhat) + eps) * mhat
- x[i] = x[i] - alpha / (sqrt(nhat) + eps) * mhat
然后,針對要優(yōu)化的每個參數(shù)重復(fù)此操作。在迭代結(jié)束時,我們可以評估新的參數(shù)值并報告搜索的性能。
- # evaluate candidate point
- score = objective(x[0], x[1])
- # report progress
- print('>%d f(%s) = %.5f' % (t, x, score))
我們可以將所有這些結(jié)合到一個名為nadam()的函數(shù)中,該函數(shù)采用目標(biāo)函數(shù)和派生函數(shù)的名稱以及算法超參數(shù),并返回在搜索及其評估結(jié)束時找到的最佳解決方案。
- # gradient descent algorithm with nadam
- def nadam(objective, derivative, bounds, n_iter, alpha, mu, nu, eps=1e-8):
- # generate an initial point
- x = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
- score = objective(x[0], x[1])
- # initialize decaying moving averages
- m = [0.0 for _ in range(bounds.shape[0])]
- n = [0.0 for _ in range(bounds.shape[0])]
- # run the gradient descent
- for t in range(n_iter):
- # calculate gradient g(t)
- g = derivative(x[0], x[1])
- # build a solution one variable at a time
- for i in range(bounds.shape[0]):
- # m(t) = mu * m(t-1) + (1 - mu) * g(t)
- m[i] = mu * m[i] + (1.0 - mu) * g[i]
- # n(t) = nu * n(t-1) + (1 - nu) * g(t)^2
- n[i] = nu * n[i] + (1.0 - nu) * g[i]**2
- # mhat = (mu * m(t) / (1 - mu)) + ((1 - mu) * g(t) / (1 - mu))
- mhat = (mu * m[i] / (1.0 - mu)) + ((1 - mu) * g[i] / (1.0 - mu))
- # nhat = nu * n(t) / (1 - nu)
- nhat = nu * n[i] / (1.0 - nu)
- # x(t) = x(t-1) - alpha / (sqrt(nhat) + eps) * mhat
- x[i] = x[i] - alpha / (sqrt(nhat) + eps) * mhat
- # evaluate candidate point
- score = objective(x[0], x[1])
- # report progress
- print('>%d f(%s) = %.5f' % (t, x, score))
- return [x, score]
然后,我們可以定義函數(shù)和超參數(shù)的界限,并調(diào)用函數(shù)執(zhí)行優(yōu)化。在這種情況下,我們將運行該算法進(jìn)行50次迭代,初始alpha為0.02,μ為0.8,nu為0.999,這是經(jīng)過一點點反復(fù)試驗后發(fā)現(xiàn)的。
- # seed the pseudo random number generator
- seed(1)
- # define range for input
- bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
- # define the total iterations
- n_iter = 50
- # steps size
- alpha = 0.02
- # factor for average gradient
- mu = 0.8
- # factor for average squared gradient
- nu = 0.999
- # perform the gradient descent search with nadam
- best, score = nadam(objective, derivative, bounds, n_iter, alpha, mu, nu)
運行結(jié)束時,我們將報告找到的最佳解決方案。
- # summarize the result
- print('Done!')
- print('f(%s) = %f' % (best, score))
綜合所有這些,下面列出了適用于我們的測試問題的Nadam梯度下降的完整示例。
- # gradient descent optimization with nadam for a two-dimensional test function
- from math import sqrt
- from numpy import asarray
- from numpy.random import rand
- from numpy.random import seed
- # objective function
- def objective(x, y):
- return x**2.0 + y**2.0
- # derivative of objective function
- def derivative(x, y):
- return asarray([x * 2.0, y * 2.0])
- # gradient descent algorithm with nadam
- def nadam(objective, derivative, bounds, n_iter, alpha, mu, nu, eps=1e-8):
- # generate an initial point
- x = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
- score = objective(x[0], x[1])
- # initialize decaying moving averages
- m = [0.0 for _ in range(bounds.shape[0])]
- n = [0.0 for _ in range(bounds.shape[0])]
- # run the gradient descent
- for t in range(n_iter):
- # calculate gradient g(t)
- g = derivative(x[0], x[1])
- # build a solution one variable at a time
- for i in range(bounds.shape[0]):
- # m(t) = mu * m(t-1) + (1 - mu) * g(t)
- m[i] = mu * m[i] + (1.0 - mu) * g[i]
- # n(t) = nu * n(t-1) + (1 - nu) * g(t)^2
- n[i] = nu * n[i] + (1.0 - nu) * g[i]**2
- # mhat = (mu * m(t) / (1 - mu)) + ((1 - mu) * g(t) / (1 - mu))
- mhat = (mu * m[i] / (1.0 - mu)) + ((1 - mu) * g[i] / (1.0 - mu))
- # nhat = nu * n(t) / (1 - nu)
- nhat = nu * n[i] / (1.0 - nu)
- # x(t) = x(t-1) - alpha / (sqrt(nhat) + eps) * mhat
- x[i] = x[i] - alpha / (sqrt(nhat) + eps) * mhat
- # evaluate candidate point
- score = objective(x[0], x[1])
- # report progress
- print('>%d f(%s) = %.5f' % (t, x, score))
- return [x, score]
- # seed the pseudo random number generator
- seed(1)
- # define range for input
- bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
- # define the total iterations
- n_iter = 50
- # steps size
- alpha = 0.02
- # factor for average gradient
- mu = 0.8
- # factor for average squared gradient
- nu = 0.999
- # perform the gradient descent search with nadam
- best, score = nadam(objective, derivative, bounds, n_iter, alpha, mu, nu)
- print('Done!')
- print('f(%s) = %f' % (best, score))
運行示例將優(yōu)化算法和Nadam應(yīng)用于我們的測試問題,并報告算法每次迭代的搜索性能。
注意:由于算法或評估程序的隨機(jī)性,或者數(shù)值精度的差異,您的結(jié)果可能會有所不同??紤]運行該示例幾次并比較平均結(jié)果。
在這種情況下,我們可以看到在大約44次搜索迭代后找到了接近最佳的解決方案,輸入值接近0.0和0.0,評估為0.0。
- >40 f([ 5.07445337e-05 -3.32910019e-03]) = 0.00001
- >41 f([-1.84325171e-05 -3.00939427e-03]) = 0.00001
- >42 f([-6.78814472e-05 -2.69839367e-03]) = 0.00001
- >43 f([-9.88339249e-05 -2.40042096e-03]) = 0.00001
- >44 f([-0.00011368 -0.00211861]) = 0.00000
- >45 f([-0.00011547 -0.00185511]) = 0.00000
- >46 f([-0.0001075 -0.00161122]) = 0.00000
- >47 f([-9.29922627e-05 -1.38760991e-03]) = 0.00000
- >48 f([-7.48258406e-05 -1.18436586e-03]) = 0.00000
- >49 f([-5.54299505e-05 -1.00116899e-03]) = 0.00000
- Done!
- f([-5.54299505e-05 -1.00116899e-03]) = 0.000001
可視化的Nadam優(yōu)化
我們可以在域的等高線上繪制Nadam搜索的進(jìn)度。這可以為算法迭代過程中的搜索進(jìn)度提供直觀的認(rèn)識。我們必須更新nadam()函數(shù)以維護(hù)在搜索過程中找到的所有解決方案的列表,然后在搜索結(jié)束時返回此列表。下面列出了具有這些更改的功能的更新版本。
- # gradient descent algorithm with nadam
- def nadam(objective, derivative, bounds, n_iter, alpha, mu, nu, eps=1e-8):
- solutions = list()
- # generate an initial point
- x = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
- score = objective(x[0], x[1])
- # initialize decaying moving averages
- m = [0.0 for _ in range(bounds.shape[0])]
- n = [0.0 for _ in range(bounds.shape[0])]
- # run the gradient descent
- for t in range(n_iter):
- # calculate gradient g(t)
- g = derivative(x[0], x[1])
- # build a solution one variable at a time
- for i in range(bounds.shape[0]):
- # m(t) = mu * m(t-1) + (1 - mu) * g(t)
- m[i] = mu * m[i] + (1.0 - mu) * g[i]
- # n(t) = nu * n(t-1) + (1 - nu) * g(t)^2
- n[i] = nu * n[i] + (1.0 - nu) * g[i]**2
- # mhat = (mu * m(t) / (1 - mu)) + ((1 - mu) * g(t) / (1 - mu))
- mhat = (mu * m[i] / (1.0 - mu)) + ((1 - mu) * g[i] / (1.0 - mu))
- # nhat = nu * n(t) / (1 - nu)
- nhat = nu * n[i] / (1.0 - nu)
- # x(t) = x(t-1) - alpha / (sqrt(nhat) + eps) * mhat
- x[i] = x[i] - alpha / (sqrt(nhat) + eps) * mhat
- # evaluate candidate point
- score = objective(x[0], x[1])
- # store solution
- solutions.append(x.copy())
- # report progress
- print('>%d f(%s) = %.5f' % (t, x, score))
- return solutions
然后,我們可以像以前一樣執(zhí)行搜索,這一次將檢索解決方案列表,而不是最佳的最終解決方案。
- # seed the pseudo random number generator
- seed(1)
- # define range for input
- bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
- # define the total iterations
- n_iter = 50
- # steps size
- alpha = 0.02
- # factor for average gradient
- mu = 0.8
- # factor for average squared gradient
- nu = 0.999
- # perform the gradient descent search with nadam
- solutions = nadam(objective, derivative, bounds, n_iter, alpha, mu, nu)
然后,我們可以像以前一樣創(chuàng)建目標(biāo)函數(shù)的輪廓圖。
- # sample input range uniformly at 0.1 increments
- xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
- yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
- # create a mesh from the axis
- x, y = meshgrid(xaxis, yaxis)
- # compute targets
- results = objective(x, y)
- # create a filled contour plot with 50 levels and jet color scheme
- pyplot.contourf(x, y, results, levels=50, cmap='jet')
最后,我們可以將在搜索過程中找到的每個解決方案繪制成一條由一條線連接的白點。
- # plot the sample as black circles
- solutions = asarray(solutions)
- pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
綜上所述,下面列出了對測試問題執(zhí)行Nadam優(yōu)化并將結(jié)果繪制在輪廓圖上的完整示例。
- # example of plotting the nadam search on a contour plot of the test function
- from math import sqrt
- from numpy import asarray
- from numpy import arange
- from numpy import product
- from numpy.random import rand
- from numpy.random import seed
- from numpy import meshgrid
- from matplotlib import pyplot
- from mpl_toolkits.mplot3d import Axes3D
- # objective function
- def objective(x, y):
- return x**2.0 + y**2.0
- # derivative of objective function
- def derivative(x, y):
- return asarray([x * 2.0, y * 2.0])
- # gradient descent algorithm with nadam
- def nadam(objective, derivative, bounds, n_iter, alpha, mu, nu, eps=1e-8):
- solutions = list()
- # generate an initial point
- x = bounds[:, 0] + rand(len(bounds)) * (bounds[:, 1] - bounds[:, 0])
- score = objective(x[0], x[1])
- # initialize decaying moving averages
- m = [0.0 for _ in range(bounds.shape[0])]
- n = [0.0 for _ in range(bounds.shape[0])]
- # run the gradient descent
- for t in range(n_iter):
- # calculate gradient g(t)
- g = derivative(x[0], x[1])
- # build a solution one variable at a time
- for i in range(bounds.shape[0]):
- # m(t) = mu * m(t-1) + (1 - mu) * g(t)
- m[i] = mu * m[i] + (1.0 - mu) * g[i]
- # n(t) = nu * n(t-1) + (1 - nu) * g(t)^2
- n[i] = nu * n[i] + (1.0 - nu) * g[i]**2
- # mhat = (mu * m(t) / (1 - mu)) + ((1 - mu) * g(t) / (1 - mu))
- mhat = (mu * m[i] / (1.0 - mu)) + ((1 - mu) * g[i] / (1.0 - mu))
- # nhat = nu * n(t) / (1 - nu)
- nhat = nu * n[i] / (1.0 - nu)
- # x(t) = x(t-1) - alpha / (sqrt(nhat) + eps) * mhat
- x[i] = x[i] - alpha / (sqrt(nhat) + eps) * mhat
- # evaluate candidate point
- score = objective(x[0], x[1])
- # store solution
- solutions.append(x.copy())
- # report progress
- print('>%d f(%s) = %.5f' % (t, x, score))
- return solutions
- # seed the pseudo random number generator
- seed(1)
- # define range for input
- bounds = asarray([[-1.0, 1.0], [-1.0, 1.0]])
- # define the total iterations
- n_iter = 50
- # steps size
- alpha = 0.02
- # factor for average gradient
- mu = 0.8
- # factor for average squared gradient
- nu = 0.999
- # perform the gradient descent search with nadam
- solutions = nadam(objective, derivative, bounds, n_iter, alpha, mu, nu)
- # sample input range uniformly at 0.1 increments
- xaxis = arange(bounds[0,0], bounds[0,1], 0.1)
- yaxis = arange(bounds[1,0], bounds[1,1], 0.1)
- # create a mesh from the axis
- x, y = meshgrid(xaxis, yaxis)
- # compute targets
- results = objective(x, y)
- # create a filled contour plot with 50 levels and jet color scheme
- pyplot.contourf(x, y, results, levels=50, cmap='jet')
- # plot the sample as black circles
- solutions = asarray(solutions)
- pyplot.plot(solutions[:, 0], solutions[:, 1], '.-', color='w')
- # show the plot
- pyplot.show()
運行示例將像以前一樣執(zhí)行搜索,但是在這種情況下,將創(chuàng)建目標(biāo)函數(shù)的輪廓圖。
在這種情況下,我們可以看到在搜索過程中找到的每個解決方案都顯示一個白點,從最優(yōu)點開始,逐漸靠近圖中心的最優(yōu)點。