斯坦福大學NLP課程CS224N課第一次作業第二部分(上)
斯坦福大學NLP課程CS224N課第一次作業第二部分
第二部分包括三個python檔案,同時也相當於實現三個小功能,分別是梯度檢驗、sigmoid和簡單的神經網路。
1.梯度檢驗
梯度校驗就是使用函式某點與其相鄰點組成的直線的斜率估計該點的導數,如果相鄰點為無窮小相鄰,那麼結果就等於該點的導數,公式解釋為: 下面我們看一下給我們的q2_gradcheck.py中需要我們補充的函式:
def gradcheck_naive(f, x):
""" Gradient check for a function f.
Arguments:
f -- a function that takes a single argument and outputs the
cost and its gradients
x -- the point (numpy array) to check the gradient at
"""
rndstate = random.getstate()
random.setstate(rndstate)
fx, grad = f(x) # Evaluate function value at original point
h = 1e-4 # Do not change this!
# Iterate over all indexes ix in x to check the gradient.
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
while not it.finished:
ix = it.multi_index
# Try modifying x[ix] with h defined above to compute numerical
# gradients (numgrad).
# Use the centered difference of the gradient.
# It has smaller asymptotic error than forward / backward difference
# methods. If you are curious, check out here:
# https://math.stackexchange.com/questions/2326181/when-to-use-forward-or-central-difference-approximations
# Make sure you call random.setstate(rndstate)
# before calling f(x) each time. This will make it possible
# to test cost functions with built in randomness later.
### YOUR CODE HERE:
raise NotImplementedError
### END YOUR CODE
# Compare gradients
reldiff = abs(numgrad - grad[ix]) / max(1, abs(numgrad), abs(grad[ix]))
if reldiff > 1e-5:
print "Gradient check failed."
print "First gradient error found at index %s" % str(ix)
print "Your gradient: %f \t Numerical gradient: %f" % (
grad[ix], numgrad)
return
it.iternext() # Step to next dimension
print "Gradient check passed!"
我們先看函式的輸入和輸出,然後判斷這個函式在幹嘛。輸入也就是函式的引數,傳入一個函式f和一個變數x,很容易判斷f是關於x的函式,然後輸出沒有return,而是判斷梯度校驗是否成功。 我們逐句進行分析吧: 首先映入眼簾的是一些英文註釋,它告訴我們函式引數的意義,然後前兩句程式碼是:
rndstate = random.getstate()
random.setstate(rndstate)
這句話的意義如果不會也基本能猜到就是設定種子,首先用getstate的方法得到一個種子,然後使用setstate的方法set一個種子,那麼這麼做有什麼意義呢。因為我們在做函式f運算的時候可能存在隨機操作,但是我們在梯度校驗,所以需要兩次求f的隨機設定是一樣的,所以需要設定種子。舉個例子,在第三部分要實現的word2vec模型中,我們需要進行負取樣來得到錯誤樣本,這一步就是隨機操作,如果不設定種子,梯度校驗就會出錯。 之後的兩句很簡單我們一步帶過,第三句就是求函式f,返回函式值和你的梯度,然後第四句是設定h的值,前面我們說h越小可能求的導數的準確性越高,但是如果太小,可能會超過python資料型別的表示範圍。 第五句話就比較有意思了,其實就是生成一個生成器,不斷返回x中某單個元素的值,因為x可能是個矩陣,而對於矩陣進行梯度校驗的時候每次只能改變一個值,所以這個函式不斷返回的就是矩陣(0,0)、(0,1)、···、(1,0)···的這些單個元素,然後進行迴圈,判斷時候每個地方的梯度校驗都正確。 然後下面就是迴圈了,迴圈判斷語句就是是否生成器迭代完成,如果迭代完成就停止,下一句話ix = it.multi_index也就是每次取矩陣的一個元素的意思。 中間我們需要補充的程式碼我們先不管,我們直接看後面的:
reldiff = abs(numgrad - grad[ix]) / max(1, abs(numgrad), abs(grad[ix]))
if reldiff > 1e-5:
print "Gradient check failed."
print "First gradient error found at index %s" % str(ix)
print "Your gradient: %f \t Numerical gradient: %f" % (
grad[ix], numgrad)
return
it.iternext() # Step to next dimension
我覺得比較明顯的就可以看到grad[ix]就是你計算的梯度在ix位置的值,而numgrad是根據公式一計算得到的梯度,然後計算他們的相對差值(差值除以他們的最大值),最後判斷這個相對差值是否足夠小,如果不足夠小就會出現梯度校驗失敗的輸出。然後如果校驗成功,就判斷矩陣的下一個元素,如果矩陣中所有元素都校驗成功,那麼就返回梯度校驗成功。 ok,現在我們已經分析完了他給的程式碼,而我們需要就算的就是numgrad這個引數,也就是根據公式一進行計算,我們想想如果將其轉化成程式碼:
- 設定種子,這是首先要做的,你如果不會就可以百度一下剛才那兩個設定種子的函式,因為種子已經得到了,我們不用再次回去了,直接使用random.setstate(rndstate)即可。
- 我們看公式的分子,我們需要計算f(x+h)和f(x-h),這樣很簡單,直接上手就寫:
x[ix]+=h
f_positive = f(x)[0] #返回函式值和梯度,我們只需要函式值即可
x[ix]-=2*h
f_negative = f(x)[0]
numgard = (f_positive-f_negative)/(2*h)
numgrad = np.sum(numgrad) #可能返回函式值是一個向量或者矩陣,直接求sum
這樣我們就補充完了這個函式。
2. sigmoid
我們第二個需要實現的就是sigmoid函式及其求導,這個我們只簡單說一下,因為確實比較簡單。
- sigmoid 首先什麼是sigmoid,sigmoid其實是和softmax有類似功能,不同的是sigmoid是將某個值轉化為一個0-1之間的概率值,值越大那麼轉化之後的概率值也越大,通常用於二分類,而softmax通常用於多分類問題,下面我們看一下sigmoid公式: 公式很簡單,我們看看sigmoid的圖是什麼樣的: 其中綠色線是sigmoid函式,這個圖是什麼畫的呢,其實很簡單,使用的是python的matplotlib.pyplot,這個不能直接將函式傳進去,而去傳進去很多點然後它自動擬合曲線,所以我們要做的就是先生成函式的點,然後傳進去,plot就會自動幫我們擬合,我們可以看一下畫圖的程式碼:
import numpy as np
import matplotlib.pyplot as plt
x1= np.arange(-10,10,0.01)
y1 = np.ones(x1.shape)
y2 = np.zeros(x1.shape)
y3 = 1/(1+np.exp(-x1))
y4 = np.arange(0,1,0.01)
plt.plot(x1,y1)
plt.plot(x1,y2)
plt.plot(x1,y3)
plt.show()
上面給你的程式碼相當於畫了三天曲線,當然matplotlib還有很多用法,其他用法可以自己探索,這裡我用到什麼就貼什麼。 回到正題,我們應該如何求使用python求sigmoid及其導數呢,首先是sigmoid函式:
def simgoid(x):
s = 1/(1+np.exp(-x))
return s
就是這麼簡單,而且也沒什麼好說的,直接按照公式二求即可。 接下來是sigmoid求導公式,這個我還是推導一下: 使用分數的求導公式求得: 所以: 於是sigmoid導數的函式為:
def sigmoid_grad(s):
ds = s*(1-s)
return ds
所以公式推導之後就是這麼簡單。 所以的準備工作已經完成,我們下回看看怎麼寫一個小的神經網路,包括前向演算法和BP演算法。 歡迎評論交流,也歡迎關注,會將CS224N的所有作業寫成部落格的。