線性分類器
在線性網絡中,以一次線性函數作為計算的網絡,SVM,softmax,以及淺層神經網絡中,關于基本實現上,最重要的就是loss和gradient的實現,train的過程,其實最終都是求loss和gradient,所以,其他部分我不會記錄太多。
SVM
在cs231n的作業1中,每個分類器都用loop和非loop方法,或是否直接處理矩陣方法,其中留給我們寫的大多都是其中核心內容,所以額外部分我也不多說。
現在基本上都會直接使用非loop方法或者直接操作矩陣。所以我也只說這部分。
線性函數計算無外乎:?
其中,W權重和b偏置是我們需要進行訓練的參數,就直接以CIFAR-10的數據集來說,我們知道得到的結果有10類,所以有10個不同類別的得分情況
上面的矩陣代表了某張圖片的每個類別的得分情況,其中有一個代表了正確的得分,correct_scores,其余代表了其他幾個類別的得分,那么SVM的loss是怎么計算的?這里要引入一個新的參數,邊界值,delta
其他的九個類別分別減去正確類別的得分,只有當其他類的得分比正確類得分小于邊界值時,loss為0.上式常被稱為折葉損失(hinge loss)。還有一種平方折葉損失,使用的是? ,將更強烈地懲罰過界的邊界值。標準版是折葉損失,但是在某些數據集中,平方折葉損失可能會更好。
正則化(Regularization): 假設我們目前的loss為0,在這個前提下,權重W并不是唯一的:可能有很多相似的W都能正確地分類所有的數據。一個簡單的例子:如果W能夠正確分類所有數據,即對于每個數據,損失值都是0。那么當x > 1時,任何數乘都能使得損失值為0,因為這個變化將所有分值的大小都均等地擴大了,所以它們之間的絕對差值也擴大了。 最常用的正則化懲罰是L2范式,L2范式通過對所有參數進行逐元素的平方懲罰來抑制大數值的權重。
loss= 0.0
num_train= X.shape[0]
delta= 1.0
scores= X.dot(W)
correct_scores= scores[np.arange(num_train), y]
# correct_class_score[:,np.newaxis] 把correct_class_score.shape = (N,)
#turn to correct_class_score[:,np.newaxis].shape = (N,1)
margins= np.maximum(0, scores-correct_scores[:,np.newaxis] +delta)
margins[np.arange(num_train), y] = 0.0
loss+= np.sum(margins) /num_train
loss+= 0.5*reg*np.sum(W*W)
因為是以整個矩陣來進行計算,所以為了除去loop,我們會將10個類都計算一次,包括正確分類,但是第9行代碼會在最后將正確分類的損失設為0.第11行就是正則化懲罰,來選擇一個分布相對均勻的。舉個例子:下面分別是W1,W2,X
如果偏置bias是固定的。那么scores = wx + b W1和W2得到的結果是一樣的。所以loss也是一樣的,但是他們兩個中誰會更好一點,L2正則化懲罰就是來選取在結果相同的情況下選取更優的權重。我們可以看出W2 每個元素是0.25的會更好一點。因此,我們在相同結果下,最優的權重更加均勻分布。這也是cs231n視頻中所提到的問題。
我們現在來看看gradient,最后的激活函數,我們可以這么理解。np.maxmium(0, scores - correct_scores) 跟Relu acitivity function 有點類似。等于0的梯度不會進行BP,大于0的梯度為1,以下為代碼部分
margins[margins>0] = 1.0
row_sum= np.sum(margins, axis=1)
margins[np.arange(num_train), y] = -row_sum
dW+= np.dot(X.T, margins) /num_train+reg*W
計算loss和gradient是每個神經網絡中最重要的部分,而train的部分,大多都用mini_batch SGD方法來訓練,batch_size為每批的訓練量,FP(forward propagation)得到loss,BP(backwork propagation)得到gradient。然后用gradient和learning_rate 的乘積來更新參數。batch = np.random.choice(num_train, batch_size, replace = True) replace參數代表取出是否放回,True放回反之不放回。
predict:計算出每個類的得分scores,然后用np.argsort(scores, axis = -1)[:,-1]得到每一行的最大值的下標。
accuracy:np.mean(y_pred == y) 得到精確率。
Softmax
前面所有的計算與SVM的過程是一致的,最后的激活函數不同,softmax是以百分比來描述每個類別的可能性,對scores進行指數操作np.exp(scores)。
? 求和得到分母,然后每個類別的可能性?
loss: Softmax的loss跟SVM不一樣,將SVM的折葉損失替換為交叉熵損失(cross-entropy loss)。公式如下:
? 或等價于 ?
下面引用知乎上的一段話。
在上式中,使用? 來表示分類評分向量?f 中的第j個元素。和之前一樣,整個數據集的損失值是數據集中所有樣本數據的損失值?
的均值于正則化損失
之和。其中函數
?被稱為softmax函數:其輸入值是一個向量,向量中元素為任意實數的評分值(z 中的),函數對其進行壓縮,輸出一個向量,其中每個元素值在0到1之間,且所有元素之和為1.所以,包含softmax函數的完整交叉熵損失看起來唬人,實際上還是比較容易理解的。
loss= 0.0
# 樣本數量
num_train= X.shape[0]
# 類別數
C= W.shape[1]
# 在某些情況下,當scores的數值過大,計算exp指數時會導致數值爆炸
# 防止數值爆炸,一般會用減去最大值或者均值
scores= np.dot(X,W)
scores= scores-np.max(scores,axis= 1,keepdims= True)
exp_scores= np.exp(scores)
margins= np.sum(exp_scores,axis=1,keepdims=True)
# 第一種表示方式
margins_log= np.log(exp_scores/margins)
loss= -(loss+np.sum(margins_log)) /num_train+np.sum(W*W) *reg
# 第二種表示方式
loss= -scores[np.arange(num_train),y].sum() +np.log(np.sum(exp_scores,axis=1)).sum()
loss/= num_train
loss+= reg*np.sum(W*W)
gradient:我們對上式 ? 進行求導。
這部分取自知乎我也是看知乎的回答之后,才慢慢弄懂得。
直譯就是對于某個樣本,其對正確分類的貢獻比錯誤分類的貢獻小?,計算好右邊那一大堆后,直接對正確分類減1就行了
margins_grad= exp_scores/margins
margins_grad[np.arange(num_train),y] += -1
dW+= np.dot(X.T, margins_grad) /num_train+reg*W
trian&predict:基本上和SVM沒有區別,他們之間主要的區別還是loss function。
SVM和Softmax的比較
兩個分類器都計算了同樣的分值向量f(本節中是通過矩陣乘來實現)。不同之處在于對f中分值的解釋:SVM分類器將它們看做是分類評分,它的損失函數鼓勵正確的分類的分值比其他分類的分值高出至少一個邊界值。Softmax分類器將這些數值看做是每個分類沒有歸一化的對數概率,鼓勵正確分類的歸一化的對數概率變高,其余的變低。SVM的最終的損失值是1.58,Softmax的最終的損失值是0.452,但要注意這兩個數值沒有可比性。只在給定同樣數據,在同樣的分類器的損失值計算中,它們才有意義。
Softmax分類器為每個分類提供了“可能性”:SVM的計算是無標定的,而且難以針對所有分類的評分值給出直觀解釋。Softmax分類器則不同,它允許我們計算出對于所有分類標簽的可能性。舉個例子,針對給出的圖像,SVM分類器可能給你的是一個[12.5, 0.6, -23.0]對應分類“貓”,“狗”,“船”。而softmax分類器可以計算出這三個標簽的”可能性“是[0.9, 0.09, 0.01],這就讓你能看出對于不同分類準確性的把握。為什么我們要在”可能性“上面打引號呢?這是因為可能性分布的集中或離散程度是由正則化參數λ直接決定的,λ是你能直接控制的一個輸入參數。舉個例子,假設3個分類的原始分數是[1, -2, 0],那么softmax函數就會計算:
現在,如果正則化參數λ更大,那么權重W就會被懲罰的更多,然后他的權重數值就會更小。這樣算出來的分數也會更小,假設小了一半吧[0.5, -1, 0],那么softmax函數的計算就是:
?
現在看起來,概率的分布就更加分散了。還有,隨著正則化參數λ不斷增強,權重數值會越來越小,最后輸出的概率會接近于均勻分布。這就是說,softmax分類器算出來的概率最好是看成一種對于分類正確性的自信。和SVM一樣,數字間相互比較得出的大小順序是可以解釋的,但其絕對值則難以直觀解釋
雙層神經網絡
繼續從loss和gradient來開始說,我們先了解兩層神經網絡的結構。
數據集還是CIFAR-10,因此我們的數據是圖像
def __init__(self, input_size, hidden_size, output_size, std=1e-4):
"""
Initialize the model. Weights are initialized to small random values and
biases are initialized to zero. Weights and biases are stored in the
variable self.params, which is a dictionary with the following keys:
W1: First layer weights; has shape (D, H)
b1: First layer biases; has shape (H,)
W2: Second layer weights; has shape (H, C)
b2: Second layer biases; has shape (C,)
Inputs:
- input_size: The dimension D of the input data.
- hidden_size: The number of neurons H in the hidden layer.
- output_size: The number of classes C.
"""
self.params = {}
self.params['W1'] = std * np.random.randn(input_size, hidden_size)
self.params['b1'] = np.zeros(hidden_size)
self.params['W2'] = std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
其中input_size,hidden_size,output_size 是初始化權重與偏置的參數。
input - fully connected layer - ReLU - fully connected layer - softmax
這是雙層神經網絡的結構,輸入經過全連層,經過Relu 激活函數,在經過一層全連層,經過softmax激活函數,這是FP過程得到loss。
W1,b1 = self.params['W1'],self.params['b1']
W2,b2 = self.params['W2'],self.params['b2']
N, D = X.shape
full_con1 = np.dot(X, W1) + b1
relu1 = np.maximum(full_con1,0)
scores = np.dot(relu1, W2) + b2
# Compute the loss
loss = 0.0
# 防止指數計算數值爆炸
scores = scores - np.max(scores,axis=1,keepdims=True)
exp_scores = np.exp(scores) # N,c
margins = np.sum(exp_scores, axis=1,keepdims=True)
loss = -scores[np.arange(N),y].sum() + np.log(np.sum(exp_scores,axis=1)).sum()
loss /= N
loss += 0.5*reg*(np.sum(W1*W1) + np.sum(W2*W2))
backword propagation:
其中最重要的就是把每一層的shape給記住,在進行矩陣點乘的時候就不會出錯。雙層神經網絡求導還沒有那么困難,所以把求導過程寫在紙上,然后邊看邊去用代碼實現,過程中注意矩陣點乘時的shape是否符合要求,是否需要進行轉置就行了。
grads = {}
grads['W1'] = np.zeros_like(W1)
grads['b1'] = np.zeros_like(b1)
grads['W2'] = np.zeros_like(W2)
grads['b2'] = np.zeros_like(b2)
margins_grad = exp_scores / margins
margins_grad[np.arange(N),y] += -1
dL = margins_grad #shape N,C
dL /= N
grads['b2'] = np.sum(dL,axis=0) #shape (C,)
grads['W2'] = relu1.T.dot(dL) + reg * W2 # shape H,C
dReluf1[full_con1 <= 0] = 0
grads['b1'] = np.sum(dReluf1,axis=0) #shap H,
grads['W1'] = X.T.dot(dReluf1) + reg * W1 #shape D,H
train&predict:和SVM、Softmax沒什么區別。求導的時候先在草稿紙上寫出過程,會比空頭想會好很多。dL /= N 這個一定要記得加,我之前就忘記加了。其實我也不是很清楚為什么要加這個,哦,可能是我們通過計算margins_grad,其實跟loss有點像,因為我們的導數dL其實就是根據loss求導來的,因為loss /= N 除以 數據量N,但是margins_grad 的結果還沒有除以數據量N,所以要加上dL /= N。寫博客的好處就是將之前的知識總結并且有時候能把其中沒弄懂得弄懂。哈哈