基於多分類SVM對CIFAR-10數據集分類

基於多分類SVM對CIFAR-10數據集分類

最近小編在學習cs231課程,在學習過程中除了完成課程中的作業外,還加上了一些對比實驗,便於對不同方法進行比較。這篇文章記錄了基於多分類SVM對CIFAR-10數據集分類的實驗過程,實驗中分別提取了圖像的RGB像素值特徵和HOG特徵並使用自己實現的SVM演算法進行分類,並與sklearn中的SVM演算法進行比較。

實驗中的所有代碼可在此處瀏覽以及下載:

BetterBoyTph/SVM-CIFAR10?

github.com圖標

文章的主要結構如下:

  • 提取圖像的RGB像素值特徵進行分類
  • 提取圖像的HOG特徵進行分類

一、提取圖像的RGB像素值特徵進行分類

1.實現SVM演算法

小編在借鑒網上的資源的基礎上基於python3實現了SVM演算法,主要包括對損失函數,訓練函數以及預測函數的現實。

1.1損失函數

SVM損失函數的公式為:

損失函數對權重的分析梯度公式為:

1.1.1矢量化版本的損失函數如下:

def svm_loss_vectorized(self, x, y, reg): """ 功能:矢量化版本的損失函數 輸入: -x:(numpy array)樣本數據(N,D) -y:(numpy array)標籤(N,) -reg:(float)正則化強度 輸出: (float)損失函數值loss (numpy array)權重梯度dW """ loss=0.0 dW=np.zeros(self.W.shape) num_train=x.shape[0] scores=x.dot(self.W) margin=scores-scores[np.arange(num_train),y].reshape(num_train,1)+1 margin[np.arange(num_train),y]=0.0 #max操作 margin=(margin>0)*margin loss+=margin.sum()/num_train #加上正則化項 loss+=0.5*reg*np.sum(self.W*self.W) #計算梯度 margin=(margin>0)*1 row_sum=np.sum(margin,axis=1) margin[np.arange(num_train),y]=-row_sum dW=x.T.dot(margin)/num_train+reg*self.W return loss,dW

1.1.2非矢量化版本的損失函數如下:

def svm_loss_naive(self,x,y,reg): """ 功能:非矢量化版本的損失函數 輸入: -x:(numpy array)樣本數據(N,D) -y:(numpy array)標籤(N,) -reg:(float)正則化強度 輸出: (float)損失函數值loss (numpy array)權重梯度dW """ num_train=x.shape[0] num_class=self.W.shape[1] #初始化 loss=0.0 dW=np.zeros(self.W.shape) for i in range(num_train): scores=x[i].dot(self.W) #計算邊界,delta=1 margin=scores-scores[y[i]]+1 #把正確類別的歸0 margin[y[i]]=0 for j in range(num_class): #max操作 if j==y[i]: continue if margin[j]>0: loss+=margin[j] dW[:,y[i]]+=-x[i] dW[:,j]+=x[i] #要除以N loss/=num_train dW/=num_train #加上正則項 loss+=0.5*reg*np.sum(self.W*self.W) dW+=reg*self.W return loss,dW

矢量化版本運行速度更快,但實現思路更難理解;而非矢量化版本運行速度較慢,但實現思路更容易理解。以下是對兩種方法的對比:

#對兩個版本的損失函數進行比較 svm=SVM() start=time.clock() loss_naive=svm.svm_loss_naive(x_train,y_train,5e4)[0] end=time.clock() print("loss_naive is %f,cost time:%ss"%(loss_naive,str(end-start))) start=time.clock() loss_vectorized=svm.svm_loss_vectorized(x_train,y_train,5e4)[0] end=time.clock() print("loss_vectorized is %f,cost time:%ss"%(loss_vectorized,str(end-start)))

Output:

loss_naive is 19491.238872,cost time:1.0712020384680727s

loss_vectorized is 19491.238872,cost time:0.049680872904104945s

1.2訓練函數

訓練函數的作用是用隨機梯度下降法進行訓練,優化權重參數

def train(self,x,y,reg=1e-5,learning_rate=1e-3,num_iters=100,batch_size=200,verbose=False): """ 功能:使用隨機梯度下降法訓練SVM 輸入: -x:(numpy array)訓練樣本(N,D) -y:(numpy array)訓練樣本標籤(N,) -reg:(float)正則化強度 -learning_rate:(float)進行權重更新的學習率 -num_iters:(int)優化的迭代次數 -batch_size:(int)隨機梯度下降法每次使用的梯度大小 -verbose:(bool)取True時,列印輸出loss的變化過程 輸出:-history_loss:(list)存儲每次迭代後的loss值 """ num_train,dim=x.shape num_class=np.max(y)+1 #初始化權重 if self.W is None: self.W=0.005*np.random.randn(dim,num_class) batch_x=None batch_y=None history_loss=[] #隨機梯度下降法優化權重 for i in range(num_iters): #從訓練樣本中隨機取樣作為更新權重的小批量樣本 mask=np.random.choice(num_train,batch_size,replace=False) batch_x=x[mask] batch_y=y[mask] #計算loss和權重的梯度 loss,grad=self.svm_loss_vectorized(batch_x,batch_y,reg) #更新權重 self.W+=-learning_rate*grad history_loss.append(loss) #列印loss的變化過程 if verbose==True and i%100==0: print("iteratons:%d/%d,loss:%f"%(i,num_iters,loss)) return history_loss

1.3預測函數

預測函數的作用是使用訓練得到的SVM模型對待分類數據進行預測

def predict(self,x): """ 功能:利用訓練得到的最優權值預測分類結果 輸入: -x:(numpy array)待分類的樣本(N,D) 輸出:y_pre(numpy array)預測的便簽(N,) """ y_pre=np.zeros(x.shape[0]) scores=x.dot(self.W) y_pre=np.argmax(scores,axis=1) return y_pre

2.載入CIFAR-10數據並進行數據預處理

載入CIFAR-10數據:

def load_CIFAR10(): """ 功能:從當前路徑下讀取CIFAR10數據 輸出: -x_train:(numpy array)訓練樣本數據(N,D) -y_train:(numpy array)訓練樣本數標籤(N,) -x_test:(numpy array)測試樣本數據(N,D) -y_test:(numpy array)測試樣本數標籤(N,) """ x_t=[] y_t=[] for i in range(1,6): path_train=os.path.join(cifar-10-batches-py,data_batch_%d%(i)) data_dict=unpickle(path_train) x=data_dict[bdata].astype(float) y=np.array(data_dict[blabels]) x_t.append(x) y_t.append(y) #將數據按列堆疊進行合併,默認按列進行堆疊 x_train=np.concatenate(x_t) y_train=np.concatenate(y_t) path_test=os.path.join(cifar-10-batches-py,test_batch) data_dict=unpickle(path_test) x_test=data_dict[bdata].astype(float) y_test=np.array(data_dict[blabels]) return x_train,y_train,x_test,y_test

數據預處理:

def data_processing(): """ 功能:進行數據預處理 輸出: x_tr:(numpy array)訓練集數據 y_tr:(numpy array)訓練集標籤 x_val:(numpy array)驗證集數據 y_val:(numpy array)驗證集標籤 x_te:(numpy array)測試集數據 y_te:(numpy array)測試集標籤 x_check:(numpy array)用於梯度檢查的子訓練集數據 y_check:(numpy array)用於梯度檢查的子訓練集標籤 """ #載入數據 x_train,y_train,x_test,y_test=load_CIFAR10() num_train=10000 num_test=1000 num_val=1000 num_check=100 #創建訓練樣本 x_tr=x_train[0:num_train] y_tr=y_train[0:num_train] #創建驗證樣本 x_val=x_train[num_train:(num_train+num_val)] y_val=y_train[num_train:(num_train+num_val)] #創建測試樣本 x_te=x_test[0:num_test] y_te=y_test[0:num_test] #從訓練樣本中取出一個子集作為梯度檢查的數據 mask=np.random.choice(num_train,num_check,replace=False) x_check=x_tr[mask] y_check=y_tr[mask] #計算訓練樣本中圖片的均值 mean_img=np.mean(x_tr,axis=0) #所有數據都減去均值做預處理 x_tr+=-mean_img x_val+=-mean_img x_te+=-mean_img x_check+=-mean_img #加上偏置項變成(N,3073) #np.hstack((a,b))等價於np.concatenate((a,b),axis=1),在橫向合併 #np.vstack((a,b))等價於np.concatenate((a,b),axis=0),在縱向合併 x_tr=np.hstack((x_tr,np.ones((x_tr.shape[0],1)))) x_val=np.hstack((x_val,np.ones((x_val.shape[0],1)))) x_te=np.hstack((x_te,np.ones((x_te.shape[0],1)))) x_check=np.hstack((x_check,np.ones((x_check.shape[0],1)))) return x_tr,y_tr,x_val,y_val,x_te,y_te,x_check,y_check

3.進行梯度檢查

計算梯度一般可用數值梯度和分析梯度,數值梯度比較容易實現,但計算速度較慢,而且是梯度的近似值;分析梯度的計算速度較快,但實現起來比較容易出錯。因此實際訓練時一般使用分析梯度,為保證分析梯度計算正確,在訓練之前要進行梯度檢查。

梯度檢查函數為:

def svm_grad_check(f,w,analytic_grad,num_checks=10,h=1e-5): """ 功能:進行梯度檢查,查看分析梯度和數值梯度是否滿足一定的誤差要求 輸入: f:計算損失值的函數 w:權重,f(w)表示損失值 analytic_grad:分析梯度 num_checks:檢查點數目 h:求數值梯度的值的h """ for i in range(num_checks): #隨機選梯度矩陣中的一個坐標位置(row,col) row=np.random.randint(w.shape[0]) col=np.random.randint(w.shape[1]) oldval=w[row,col] #計算f(x+h) w[row,col]=oldval+h fxph=f(w) #計算f(x-h) w[row,col]=oldval-h fxsh=f(w) #計算該點數值梯度 grad_numerical=(fxph-fxsh)/(2*h) #還原改點值 w[row,col]=oldval #計算該點的分析梯度 grad_analytic=analytic_grad[row,col] #計算誤差率 error=abs(grad_analytic-grad_numerical)/(abs(grad_analytic)+abs(grad_numerical)) print("grad_numerical:%.10f,grad_analytic=%.10f,error=%e"%(grad_numerical,grad_analytic,error)) if(error<1e-7): print(誤差合理)

進行梯度檢查為:

if __name__==__main__: #創建用於梯度檢查的樣本x_check,y_check x_tr,y_tr,x_val,y_val,x_te,y_te,x_check,y_check= data_processing() #初始化權重 W=0.005*np.random.randn(3073,10) #設置reg=0.0,忽略正則項 print(忽略正則項:(error<1e-7為合理結果)) loss,analytic_grad=svm_loss_vectorized(W,x_check,y_check,reg=0.0) f=lambda w:svm_loss_vectorized(w,x_check,y_check,reg=0.0)[0] svm_grad_check(f,W,analytic_grad) print(
加上正則項:(error<1e-7為合理結果)) #設置reg=1e2,加上正則項 loss,analytic_grad=svm_loss_naive(W,x_check,y_check,reg=1e2) f=lambda w:svm_loss_naive(w,x_check,y_check,reg=1e2)[0] svm_grad_check(f,W,analytic_grad)

Output:

忽略正則項:(error<1e-7為合理結果)

grad_numerical:6.7000000008,grad_analytic=6.7000000000,error=6.326091e-11

誤差合理

grad_numerical:-11.7682860001,grad_analytic=-11.7682860000,error=3.873158e-12

誤差合理

grad_numerical:5.0079280008,grad_analytic=5.0079280000,error=8.206378e-11

誤差合理

grad_numerical:14.5400000001,grad_analytic=14.5400000000,error=3.306235e-12

誤差合理

grad_numerical:10.2525720003,grad_analytic=10.2525720000,error=1.311116e-11

誤差合理

grad_numerical:-37.2478339997,grad_analytic=-37.2478340000,error=3.451531e-12

誤差合理

grad_numerical:28.3182120000,grad_analytic=28.3182120000,error=7.885590e-13

誤差合理

grad_numerical:11.1901259999,grad_analytic=11.1901260000,error=6.239641e-12

誤差合理

grad_numerical:23.3487440006,grad_analytic=23.3487440000,error=1.346056e-11

誤差合理

grad_numerical:-24.6308540000,grad_analytic=-24.6308540000,error=9.789457e-13

誤差合理

加上正則項:(error<1e-7為合理結果)

grad_numerical:-28.1751299298,grad_analytic=-28.1751299311,error=2.367528e-11

誤差合理

grad_numerical:2.8895379344,grad_analytic=2.8895379294,error=8.665920e-10

誤差合理

grad_numerical:-10.0117004877,grad_analytic=-10.0117004897,error=1.006852e-10

誤差合理

grad_numerical:-20.5885517218,grad_analytic=-20.5885517181,error=9.003053e-11

誤差合理

grad_numerical:-26.2887985571,grad_analytic=-26.2887985562,error=1.587888e-11

誤差合理

grad_numerical:10.3229925941,grad_analytic=10.3229925949,error=3.931227e-11

誤差合理

grad_numerical:12.2690163110,grad_analytic=12.2690163115,error=2.045450e-11

誤差合理

grad_numerical:-2.1297817540,grad_analytic=-2.1297817526,error=3.342477e-10

誤差合理

grad_numerical:33.7859679618,grad_analytic=33.7859679594,error=3.607376e-11

誤差合理

grad_numerical:-6.3015278627,grad_analytic=-6.3015278619,error=6.221971e-11

誤差合理

4.在訓練前要列印損失函數的變化過程來確保訓練過程正確,迭代過程中損失函數值應該是總體減少的趨勢。

#在進行交叉驗證前要可視化損失函數值的變化過程,檢驗訓練過程編程是否正確 svm=SVM() history_loss=svm.train(x_train,y_train,reg=1e5,learning_rate=1e-7,num_iters=1500,batch_size=200,verbose=True) plt.figure(figsize=(12,8)) plt.plot(history_loss) plt.xlabel(iteration) plt.ylabel(loss) plt.show()

Output:

iteratons:0/1500,loss:38811.883233

iteratons:100/1500,loss:5192.929378

iteratons:200/1500,loss:696.573076

iteratons:300/1500,loss:97.625460

iteratons:400/1500,loss:17.622664

iteratons:500/1500,loss:6.921698

iteratons:600/1500,loss:5.805760

iteratons:700/1500,loss:5.886913

iteratons:800/1500,loss:5.166627

iteratons:900/1500,loss:5.490568

iteratons:1000/1500,loss:5.532130

iteratons:1100/1500,loss:5.252407

iteratons:1200/1500,loss:5.417124

iteratons:1300/1500,loss:5.828051

iteratons:1400/1500,loss:5.500399

5.使用驗證集調參並得到最優的SVM模型

learning_rate=[7e-6,1e-7,3e-7] regularization_strength=[1e4,3e4,5e4,7e4,1e5,3e5,5e5] max_acc=-1.0 for lr in learning_rate: for rs in regularization_strength: svm=SVM() #訓練 history_loss=svm.train(x_train,y_train,reg=rs,learning_rate=lr,num_iters=2000) #預測驗證集類別 y_pre=svm.predict(x_val) #計算驗證集精度 acc=np.mean(y_pre==y_val) #選取精度最大時的最優模型 if(acc>max_acc): max_acc=acc best_learning_rate=lr best_regularization_strength=rs best_svm=svm print("learning_rate=%e,regularization_strength=%e,val_accury=%f"%(lr,rs,acc)) print("max_accuracy=%f,best_learning_rate=%e,best_regularization_strength=%e"%(max_acc,best_learning_rate,best_regularization_strength))

6.使用最優的SVM模型以及sklearn自帶的SVM演算法對測試集進行分類

#用最優svm模型對測試集進行分類的精度 #預測測試集類別 y_pre=best_svm.predict(x_test) #計算測試集精度 acc=np.mean(y_pre==y_test) print(The test accuracy with self-realized svm is:%f%(acc)) print("
Program time of self-realized svm is:%ss"%(str(end-start))) #使用自帶的svm進行分類 start=time.clock() lin_clf = s.LinearSVC() lin_clf.fit(x_train,y_train) y_pre=lin_clf.predict(x_test) acc=np.mean(y_pre==y_test) print("The test accuracy with svm.LinearSVC is:%f"%(acc)) end=time.clock() print("Program time of svm.LinearSVC is:%ss"%(str(end-start)))

Output:

The test accuracy with self-realized svm is:0.359000

Program time of self-realized svm is:150.62748391204354s

The test accuracy with svm.LinearSVC is:0.172000

Program time of svm.LinearSVC is:600.8316302231658s

為了節省時間,實驗中只選取了10000個訓練樣本,1000個驗證樣本和1000個測試樣本。使用調參訓練後的最優模型對測試樣本的分類精度為35.9%,而官方給出的精度是接近40%,這可能是由於實驗中選取的樣本與官方的不一致導致分類精度出現差異,另一方面,實驗中調參時只嘗試了少量的參數,如何獲得更加好的參數來提高分類精度還需要進行深入研究。使用sklearn自帶的SVM演算法在訓練後對測試樣本的分類精度為17.2%。可能由於使用sklearn自帶的SVM演算法時都是設置默認參數,導致精度過低,如何優化該演算法的參數提高分類精度還需要進行更深入的研究。

7.可視化最優的SVM模型學習到的權重

def VisualizeWeights(best_W): #去除最後一行偏置項 w=best_W[:-1,:] w=w.T w=np.reshape(w,[10,3,32,32]) #對應類別 classes=[airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck] num_classes=len(classes) plt.figure(figsize=(12,8)) for i in range(num_classes): plt.subplot(2,5,i+1) #將圖像拉伸到0-255 x=w[i] minw,maxw=np.min(x),np.max(x) wimg=(255*(x.squeeze()-minw)/(maxw-minw)).astype(uint8) r=Image.fromarray(wimg[0]) g=Image.fromarray(wimg[1]) b=Image.fromarray(wimg[2]) #合併三通道 wimg=Image.merge("RGB",(r,g,b))# wimg=np.array(wimg) plt.imshow(wimg) plt.axis(off) plt.title(classes[i])

實驗中遇到的問題為將w=np.reshape(w,[10,3,32,32])這句改為w=np.reshape(w,[32,32,3,10]),後面的代碼也相應的做調整,結果出來結果不對,小編暫時還沒弄清這個原因是什麼,有知友知道的話歡迎評論指正。

二、提取圖像的HOG特徵進行分類

1.提取HOG特徵

def hog_extraction(data,size=8): """ 功能:提取圖像HOG特徵 輸入: data:(numpy array)輸入數據[num,3,32,32] size:(int)(size,size)為提取HOG特徵的cellsize 輸出: data_hogfeature:(numpy array):data的HOG特徵[num,dim] """ num=data.shape[0] data=data.astype(uint8) #提取訓練樣本的HOG特徵 data1_hogfeature=[] for i in range(num): x=data[i] r=Image.fromarray(x[0]) g=Image.fromarray(x[1]) b=Image.fromarray(x[2]) #合併三通道 img=Image.merge("RGB",(r,g,b)) #轉為灰度圖 gray=img.convert(L)# out=gray.resize((100,100),Image.ANTIALIAS) #轉化為array gray_array=np.array(gray) #提取HOG特徵 hogfeature=ft.hog(gray_array,pixels_per_cell=(size,size)) data1_hogfeature.append(hogfeature) #把data1_hogfeature中的特徵按行堆疊 data_hogfeature=np.reshape(np.concatenate(data1_hogfeature),[num,-1]) return data_hogfeature

自己編寫的提取圖像HOG特徵的函數,好像實現略微複雜了,如果知友有更簡單的實現方法,歡迎評論共享代碼讓大家受益哦。

2.使用驗證集調參

#用驗證集來尋找最優的SVM模型以及cellsize cellsize=[2,4,6,8,10] learning_rate=[1e-5,5e-5,1e-6,7e-6,1e-7,3e-7,1e-8] regularization_strength=[1e1,1e2,1e3,1e4,5e4,1e5,3e5,1e6,1e7] max_acc=-1.0 for cs in cellsize: #提取訓練集和驗證集的HOG特徵 hog_train=hog_extraction(x_train,size=cs) hog_val=hog_extraction(x_val,size=cs) for lr in learning_rate: for rs in regularization_strength: svm=SVM() #訓練 history_loss=svm.train(hog_train,y_train,reg=rs,learning_rate=lr,num_iters=2000) #預測驗證集類別 y_pre=svm.predict(hog_val) #計算驗證集精度 acc=np.mean(y_pre==y_val) #選取精度最大時的最優模型 if(acc>max_acc): max_acc=acc best_learning_rate=lr best_regularization_strength=rs best_cellsize=cs best_svm=svm print("cellsize=%d,learning_rate=%e,regularization_strength=%e,val_accury=%f"%(cs,lr,rs,acc)) #輸出最大精度 print("max_accuracy=%f,best_cellsize=%d,best_learning_rate=%e,best_regularization_strength=%e"%(max_acc,best_cellsize,best_learning_rate,best_regularization_strength))

3.使用訓練後的最後模型以及sklearn中自帶的SVM演算法對測試集分類

由於提取HOG特徵需要設置cellsize,所以調參也需要考慮該參數

#用最優svm模型對測試集進行分類的精度 #提取測試集HOG特徵 hog_test=hog_extraction(x_test,size=best_cellsize) #預測測試集類別 y_pre=best_svm.predict(hog_test) #計算測試集精度 acc=np.mean(y_pre==y_test) print(The test accuracy with self-realized svm is:%f%(acc)) end=time.clock() print(Program time with self-realized svm is:%ss%(str(end-start))) print(

) #用自帶的模型進行調參 start=time.clock() max_acc=-1.0 for cs in cellsize: #提取訓練集和驗證集的HOG特徵 hog_train=hog_extraction(x_train,size=cs) hog_val=hog_extraction(x_val,size=cs) lin_clf = s.LinearSVC() lin_clf.fit(hog_train,y_train) y_pre=lin_clf.predict(hog_val) acc=np.mean(y_pre==y_val) if(acc>max_acc): max_acc=acc best_cellsize=cs best_s=lin_clf print("cellsize=%d,accuray=%f"%(cs,acc)) print("max_accuray=%f,best_cellsize=%d"%(max_acc,best_cellsize)) #用最優模型對測試集分類 #提取測試集HOG特徵 hog_test=hog_extraction(x_test,size=best_cellsize) #分類 y_pre=best_s.predict(hog_test) acc=np.mean(y_pre==y_test) print("The test accuracy with svm.LinearSVC is:%f"%(acc)) end=time.clock() print("
Program time of svm.LinearSVC is:%ss"%(str(end-start)))

Output:

The test accuracy with self-realized svm is:0.323000

Program time with self-realized svm is:3151.102742384628s

The test accuracy with svm.LinearSVC is:0.494000

Program time of svm.LinearSVC is:125.11966691204725s

該實驗用的數據和基於圖像RGB像素值特徵進行分類的數據相同,實驗中在提取測試樣本的HOG特徵後,用調整訓練的最優模型對測試數據的分類精度為32.3%,該精度較低的原因可能是由於實驗中調參時只考慮了一部分參數,如何獲取更優的參數來提高分類精度還需要實驗。用sklearn中自帶的SVM演算法對測試數據的分類精度為49.4%,可見該精度遠高於基於圖像RGB像素值特徵進行分類的精度,因此也說明了提取圖像的HOG特徵能提高分類精度。

由於實驗中只選取了部分樣本,而小編也處於正在學習深度學習的菜鳥階段,調參時只選取了部分參數,而沒有進行仔細調整尋找更優的參數,因此導致獲得的精度並不理想。文章中有什麼錯誤歡迎知友指正。


一枚喜歡編程正在努力學習深度學習的小夥子分割線。。。。


推薦閱讀:

GitHub 上有什麼值得學習,簡單的,易讀的 Python 項目?
數據分析項目--如何選擇你的航班
Python每日一練0023
Python如何輸出包含在對象中的中文字元?
ImagePy開發文檔 —— 管理器

TAG:機器學習 | 深度學習DeepLearning | Python |