CS231N Assignment2 Support Vector Machine
Begin
本文主要介紹CS231N系列課程的第一項作業,寫一個SVM無監督學習訓練模型。
課程主頁:網易雲課堂CS231N系列課程
語言:Python3.6
1線形分類器
以圖像為例,一幅圖像像素為32*32*3代表長32寬32有3通道的衣服圖像,將其變為1*3072的一個向量,即該圖像的特征向量。
我們如果需要訓練1000幅圖像,那么輸入則為1000*3072的矩陣X。
我們用X點乘矩陣W得到一個計分矩陣如下所示,W乘以一幅圖像的特征向量的轉置得到一列代表分數。
每個分數對應代表一個類別,分數越高代表她所屬於此類別紀律越大,所以W其實是一個類別權重的概念。
注意:下圖為CS231N中的一張圖,它是以一幅圖為例,將X轉至為3072*1,大家理解即可,在程序中我們采用X*W來編寫。
更多細節可以參考CS231N作業1KNN詳解

2損失函數
得到每一幅圖像對應每一個類別的分數之后,我們需要計算一個損失,去評估一下W矩陣的好壞。
如下右側為SVM損失函數計算公式。
對每一幅圖像的損失用其錯誤類別的分數減去正確類別的分數,並與0比較求最大值
一般我們應該正確類別的分數高就證明沒有損失,此時錯誤類別減去正確類別一定為負值,比0小故取損失為0.
為了提高魯棒性,這里給他加了一個1。

計算所有的損失后,我們把損失累加作為最后的損失
整理后我們得到如下的公式,但是其存在一個問題,沒有考慮W的影響,不同的W可能得到同樣的損失,

因此我們引入一個正則,正則系數可以調節W對整個損失的影響,W越分散當然越好

代碼如下:
def svm_loss_native(W,X,Y,reg):
'''
本函數用於計算SVM分類器的損失以及梯度
輸入參數:
W(D,C)代表權重
D為特征向量的維度,C為分類類別的數量
X(N,D)代表訓練樣本的特征,0維代表每一個樣本,1維代表某一樣本的特征向量
對於32*32圖像,N代表有N個樣本,D=32*32*3全體像素值代表特征向量
Y(N,1)代表訓練樣本的標簽,0維代表每一個樣本,1維代表某一樣本的標簽
輸出參數:
Loss損失
'''
#獲取基礎參數
num_train = X.shape[0]#訓練樣本的數量
num_classes = W.shape[1]#划分的種類
loss = 0.0#初始化損失
dW = np.zeros(W.shape)#創建一個梯度
for i in range(num_train):#分別求每一個訓練樣本的損失
score = X[i].dot(W)#計算每個樣本的分數
#計算損失
for j in range(num_classes):
if j == Y[i]:
continue
margin = score[j] - score[Y[i]] + 1
#margin = np.max(0,score[j] - score[Y[i]] + 1)#計算損失
if margin > 0:
loss += margin
loss /= num_train
#加入正則
loss += reg * np.sum(W*W)
return loss
如此一套完整的損失函數就構造完成了,我們通過看損失可以知道這個W矩陣的好壞,那么如果損失過大該怎么調劑每一個參數呢?
此時我們引入梯度下降法和梯度的概念
3梯度
梯度下降法:
所以,我們重復利用這個方法,反復求取梯度,最后就能到達局部的最小值,這就類似於我們下山的過程。而求取梯度就確定了最陡峭的方向,也就是場景中測量方向的手段。
梯度如同求導一樣,如下圖所示,損失的導數反應着梯度狀況
如果W向前變化一格,損失增大,則dW梯度應該為正值,此時應該W向相反方向變化。


對於Lij,用其對Wj求偏導

CODE2 LOSS & 梯度 循環形式
def svm_loss_native(W,X,Y,reg):
'''
本函數用於計算SVM分類器的損失以及梯度
輸入參數:
W(D,C)代表權重
D為特征向量的維度,C為分類類別的數量
X(N,D)代表訓練樣本的特征,0維代表每一個樣本,1維代表某一樣本的特征向量
對於32*32圖像,N代表有N個樣本,D=32*32*3全體像素值代表特征向量
Y(N,1)代表訓練樣本的標簽,0維代表每一個樣本,1維代表某一樣本的標簽
輸出參數:
Loss損失
'''
#獲取基礎參數
num_train = X.shape[0]#訓練樣本的數量
num_classes = W.shape[1]#划分的種類
loss = 0.0#初始化損失
dW = np.zeros(W.shape)#創建一個梯度
for i in range(num_train):#分別求每一個訓練樣本的損失
score = X[i].dot(W)#計算每個樣本的分數
#計算損失
for j in range(num_classes):
if j == Y[i]:
continue
margin = score[j] - score[Y[i]] + 1
#margin = np.max(0,score[j] - score[Y[i]] + 1)#計算損失
if margin > 0:
loss += margin
dW[:,Y[i]] += -X[i,:].T
dW[:,j] += X[i,:].T
loss /= num_train
dW /= num_train
#加入正則
loss += reg * np.sum(W*W)
dW += reg * W
return loss,dW
CODE3 LOSS & 梯度 向量矩陣形式
def svm_loss_vectorized(W,X,Y,reg):
loss = 0.0
num_train = X.shape[0]
dW = np.zeros(W.shape)
scores = np.dot(X,W)
correct_class_score = scores[np.arange(num_train),Y]
correct_class_score = np.reshape(correct_class_score,(num_train,-1))
margin = scores - correct_class_score + 1.0
margin[np.arange(num_train),Y] = 0.0
margin[margin<0] = 0.0
loss += np.sum(margin)/num_train
loss += 0.5*reg*np.sum(W*W)
margin[margin>0] = 1.0
row_sum = np.sum(margin,axis = 1)
margin[np.arange(num_train),Y] = -row_sum
dW = 1.0/num_train*np.dot(X.T,margin) + reg*W # ** #
return loss,dW
4訓練函數
在得到損失和梯度后我們就可以根據梯度去調節W矩陣,這里需要引入TRAIN函數的一些參數。
一般需要有以下參數:
訓練次數:要循環訓練多少步。
學習率:每一次根據梯度去修正W矩陣的系數。
樣本數:每一次訓練可能不是選擇所有樣本,需要取樣一定樣本。
核心點在於在循環中不斷去計算損失以及梯度,然后利用下面公式去調節。
CODE4 梯度下降法
def train(self,X,Y,learning_rate=1e-3,reg=1e-5,num_iters=100,batch_size=200,verbose=False):
'''
隨機梯度下降法訓練分類器
輸入參數:
-learning_rate學習率
-reg正則化強度
-num_iters步長值
-batch_size每一步使用的樣本數量
-verbose若為真則打印過程
輸出參數:
list損失值
'''
num_train,dim = X.shape
num_classes = np.max(Y) + 1
#if self.W is None:
#初始化W矩陣
self.W = 0.001 * np.random.randn(dim,num_classes)
loss_history = []
#開始訓練num_iters步
for it in range(num_iters):
X_batch = None
Y_batch = None
########################
# 選取部分訓練樣本
# 隨機生成一個序列
batch_inx = np.random.choice(num_train,batch_size)
X_batch = X[batch_inx,:]
Y_batch = Y[batch_inx]
#########################
# 計算損失與梯度
loss,grade = self.loss(self.W,X_batch,Y_batch,reg)
loss_history.append(loss)
########################
# 參數更新
# 梯度為正表示損失增大,應該減少,成負相關
self.W = self.W - learning_rate * grade
#打印結果
if verbose and it % 100 == 0:
print('iteration %d / %d : loss %f'%(it ,num_iters,loss))
return loss_history
運行結果如

5預測predict
在訓練完模型后會得到一個較好的W矩陣,然后根據這個W去預測一下測試集看看模型的效果
def predict(self,X_train):
y_predict = np.zeros(X_train.shape[1])
#根據訓練后的W矩陣計算分數
scores = X_train.dot(self.W)
#找到得分中最大的值作為類別
y_predict = np.argmax(scores,axis = 1)#計算每一行最大值
return y_predict
在主函數中運行如下代碼觀察預測情況
score1 = SVM1.predict(X_dev)
print('The predit result %f' %(np.mean(score1 == Y_dev)))
score1 = SVM1.predict(X_test)
print('The Test Data predit result %f' %(np.mean(score1 == Y_test)))
預測結果如下,用訓練集本身去預測得到0.756,用測試集去預測才0.218,不是太好

6參數調整
上述即完成了一整體的SVM模型庫,那么我們如何自動訓練出一個好的學習率和正則化強度參數呢?
我們需要不斷去測試每一個參數的好壞,用下面一個程序可以完成這個任務
#調參
#兩個參數,學習率;正則化強度
learning_rate = [2e-7,0.75e-7,1.5e-7,1.25e-7,0.75e-7]
regularization_strengths = [3e4,3.25e4,3.5e4,3.75e4,4e4]
results = {}
best_val = 0
best_svm = None
######################################
# 循環執行代碼
# 對不同的學習率以及正則化強度進行測試
#
for rate in learning_rate:
for regular in regularization_strengths:
SVM2 = SVM()
#訓練
SVM2.train(X_train,Y_train,learning_rate=rate,reg=regular,num_iters=1000)
#預測
Y1 = SVM2.predict(X_train)
Y2 = SVM2.predict(X_val)
accuracy_train = np.mean(Y1==Y_train)
accuracy_val = np.mean(Y2==Y_val)
#判斷優略
if best_val < accuracy_val:
best_val = accuracy_val
best_svm = SVM2#保存當前模型
#存儲數據
results[rate,regular] = (accuracy_train,accuracy_val)
#打印數據
for lr,reg in sorted(results):
accuracy_train,accuracy_val = results[(lr,reg)]
print('lr:%e reg %e train accuracy: %f val val accuracy : %f'%(lr,reg,accuracy_train,accuracy_val))
運行結果如下:

7 可視化效果
在得到最優W時,我們有時要看一下W的可視化效果,從w的圖像可以看出權重高低,類似於一個反應這個類別的模板。
#可視化結果數據
w = best_svm.W[:,:]
w=w.reshape(32,32,3,10)
w_min,w_max = np.min(w),np.max(w)
classes = ['plane','car','bird','cat','deer','dog','frog','hors','ships','truck']#類別划分 列表
for i in range(10):
plt.subplot(2,5,i+1)
wimg = 255.0 * (w[:,:,:,i].squeeze()-w_min) / (w_max - w_min)
plt.imshow(wimg.astype('uint8'))
plt.axis('off')
plt.title(classes[i])
plt.show()
如下圖所示

不知我這圖為啥和別人不一樣~~~~~~~看着不夠清晰呢?還望大神指點
在完成本案例過程中參考了一些文章和帖子,由於書寫過程中沒有過多記錄,故沒有標注出來,如有侵權請聯系備注!
