線性分類器及python實現


以下內容參考CS231n。

上一篇關於分類器的文章,使用的是KNN分類器,KNN分類有兩個主要的缺點:

  • 空間上,需要存儲所有的訓練數據用於比較。
  • 時間上,每次分類操作,需要和所有訓練數據比較。

本文開始線性分類器的學習。

和KNN相比,線性分類器才算得上真正具有實用價值的分類器,也是后面神經網絡和卷積神經網絡的基礎。 

線性分類器中包括幾個非常重要的部分:

  • 權重矩陣W,偏差向量b
  • 評分函數
  • 損失函數
    • 正則化
    • 最優化

權重矩陣W (Weights)

  • 可以理解為所有分類對應的模版向量w組成的矩陣,模版就是分類期望成為的樣子。
  • 訓練數據可以理解為是N維空間中的一個向量v,v和W中每個模版做點積,點積越大,表示兩個向量的夾角越小,也就越接近。
  • 點積最大的模版w,就是v所對應的分類。
  • W不是一直不變的。它會隨着對損失函數最優化的過程,不斷的調整。

偏差向量b (bias vector)

  b是不是可以理解為,如果不設置b,那所有的分類線都要通過原點,那其實就起不到分類的作用了?

      參考下圖?三條線都通過原點,是無法對數據做分類的。

  W和b分別對直線做旋轉和平移。

評分函數(score function)

                                                     

    之所以是線性分類器,就是因為評分函數使用線性方程計算分數。后面的神經網絡會對線性函數做非線性處理。

      下圖直觀的展示了分類器的線性。

             

損失函數 (loss function)

如何判斷當前的W和b是否合適,是否能夠輸出准確的分類?

通過損失函數,就可以計算預測的分類和實際分類之間的差異。通過不斷減小損失函數的值,也就是減少差異,就可以得到對應的W和b。

 

Python實現

數據預處理

# 每行均值
mean_image = np.mean(X_train, axis=0) # second: subtract the mean image from train and test data # 零均值化,中心化,使數據分布在原點周圍,可以加快訓練的收斂速度 X_train -= mean_image X_val -= mean_image X_test -= mean_image X_dev -= mean_image

 處理b的技巧

 

# third: append the bias dimension of ones (i.e. bias trick) so that our SVM
# only has to worry about optimizing a single weight matrix W.

# 技巧,將bias加到矩陣中,作為最后一列,直接參與矩陣運算。不用單獨計算。
# hstack,水平地把數組的列組合起來
# 每行后面 加一個元素1

X_train = np.hstack([X_train, np.ones((X_train.shape[0], 1))])
X_val = np.hstack([X_val, np.ones((X_val.shape[0], 1))])
X_test = np.hstack([X_test, np.ones((X_test.shape[0], 1))])
X_dev = np.hstack([X_dev, np.ones((X_dev.shape[0], 1))])

計算損失函數 

  使用多類支持向量機作為損失函數。 SVM loss function,

                  ,      

 

 全體訓練集上的平均損失值:

                      

 為了體現向量計算的高效,這里給出了非向量計算作為對比。

 非向量版:

 1 # 多類支持向量機損失函數
 2 # SVM的損失函數想要SVM在正確分類上的得分始終比不正確分類上的得分高出一個邊界值Delta
 3 # max(0, s1 - s2 + delta)
 4 # s1,錯誤分類, s2,正確分類
 5 def svm_loss_naive(W, X, y, reg):
 6 
 7   # N, 500
 8   # D, 3073
 9   # C, 10
10   # W (3073, 10), 用(1,3073)左乘W,得到(1, 10)
11   # X(500, 3073), y(500,)
12 
13   # 對W求導。 后面求使損失函數L減小的最快的w,也就是梯度下降最快的方向。
14   dW = np.zeros(W.shape) # initialize the gradient as zero
15     
16   # compute the loss and the gradient
17   num_classes = W.shape[1] # 10
18   num_train = X.shape[0]     # 500
19   loss = 0.0
20   for i in range(num_train):
21     # 分數 = 點積
22     # 向量(1,D)和矩陣(D,C)的點積 = (1,C)    (c,)
23     # scores中的一行,表示所有類的打分
24     scores = X[i].dot(W)
25 
26     # y[i],對的分類
27     correct_class_score = scores[y[i]]
28     for j in range(num_classes):
29       # 相等的分類不做處理
30       if j == y[i]:
31         continue
32       # 用其他分類的得分 - 最終分類的得分 + 1
33       margin = scores[j] - correct_class_score + 1 # note delta = 1
34        
35       if margin > 0:
36         loss += margin
37         
38         # (錯的-對的),所以第一個是-X,第二個是X
39         # 500張圖片的dW和,后面再除個500
40         dW[ :, y[i] ] += -X[ i, : ]
41         dW[ :, j ] += X[ i, : ]
42         
43   loss /= num_train
44   dW /= num_train
45 
46   # Add regularization to the loss.
47   # 正則化,https://blog.csdn.net/gsww404/article/details/80414675
48   # L2正則化  
49   loss += reg * np.sum(W * W)
50 
51   # 這里為什么不是2W
52   dW += reg * W
53     
54   return loss, dW

向量版:

def svm_loss_vectorized(W, X, y, reg):
  loss = 0.0
  dW = np.zeros(W.shape) # initialize the gradient as zero

  num_classes = W.shape[1]
  num_train = X.shape[0]

  scores = X.dot(W) # (500, 10)
  correct_scores = scores[ np.arange(num_train),  y]  #(500, ),  取( 0~500, y[0~500]) 

  correct_scores = np.tile(correct_scores.reshape(num_train, 1) , (1, num_classes)) # (500, 10), 復制出10列,一行里的值是相同的
  margin = scores - correct_scores + 1

  margin[np.arange(num_train), y] = 0 # 正確的分類,不考慮
  margin[margin < 0] = 0          # 小於0的置為0,不考慮
  loss = np.sum(margin) / num_train
  loss += reg * np.sum(W * W)
  
  margin[ margin > 0 ] = 1  # 大於0的是造成損失的權重
  row_sum = np.sum( margin, axis = 1 ) # 對每行取和, dW[ :, y[i] ] += -X[ i, : ], 參考非向量的寫法,每次遍歷里,正確的分類都有被減一次
  margin[np.arange( num_train ), y] = -row_sum # 正確分類是負的,-X
  # 點積
  dW += np.dot(X.T, margin) / num_train + reg * W  

  return loss, dW

隨機初始化W,計算loss和gradient

# randn,高斯分布(正態分布)隨機數
# 初始權重采用小一點的值
W = np.random.randn(3073, 10) * 0.0001 

# grad,analytic gradient loss, grad
= svm_loss_naive(W, X_dev, y_dev, 0.000005)

 

檢查梯度值的正確性。用數值梯度值(numerical gradient)和分析梯度值(analytic gradient)比較。 

檢查梯度方法:

def grad_check_sparse(f, x, analytic_grad, num_checks=10, h=1e-5):
  for i in range(num_checks):
    # 隨機取一個位置
    ix = tuple([randrange(m) for m in x.shape])

    oldval = x[ix]
    x[ix] = oldval + h # increment by h
    fxph = f(x) # evaluate f(x + h)
    x[ix] = oldval - h # increment by h  # decrement ?
    fxmh = f(x) # evaluate f(x - h)
    x[ix] = oldval # reset

    # 對稱差分
    grad_numerical = (fxph - fxmh) / (2 * h)
    grad_analytic = analytic_grad[ix]
    
    # 相對誤差 ??
    rel_error = abs(grad_numerical - grad_analytic) / (abs(grad_numerical) + abs(grad_analytic))

比較分析梯度和數值梯度:

# 檢查計算的梯度值是否正確
# Compute the loss and its gradient at W.
loss, grad = svm_loss_naive(W, X_dev, y_dev, 0.0)

# Numerically compute the gradient along several randomly chosen dimensions, and
# compare them with your analytically computed gradient. The numbers should match
# almost exactly along all dimensions.
from cs231n.gradient_check import grad_check_sparse
f = lambda w: svm_loss_naive(w, X_dev, y_dev, 0.0)[0] # 取loss
grad_numerical = grad_check_sparse(f, W, grad)

# do the gradient check once again with regularization turned on
# you didn't forget the regularization gradient did you?
loss, grad = svm_loss_naive(W, X_dev, y_dev, 5e1)
f = lambda w: svm_loss_naive(w, X_dev, y_dev, 5e1)[0]
grad_numerical = grad_check_sparse(f, W, grad)

 

創建一個線性分類器類

最優化方法使用 隨機梯度下降法(SGD,Stochastic Gradient Descent)。

class LinearClassifier(object):

  def __init__(self):
    self.W = None

  # 訓練,也是最優化,調參的過程。
  def train(self, X, y, learning_rate=1e-3, reg=1e-5, num_iters=100,
            batch_size=200, verbose=False):
    """
    Train this linear classifier using stochastic gradient descent.

    Inputs:
    - X: A numpy array of shape (N, D) containing training data; there are N
      training samples each of dimension D.
    - y: A numpy array of shape (N,) containing training labels; y[i] = c
      means that X[i] has label 0 <= c < C for C classes. C是所有類別吧?
    - learning_rate: (float) learning rate for optimization.
    - reg: (float) regularization strength.
    - num_iters: (integer) number of steps to take when optimizing
    - batch_size: (integer) number of training examples to use at each step.
    - verbose: (boolean) If true, print progress during optimization.

    Outputs:
    A list containing the value of the loss function at each training iteration.
    """
    num_train, dim = X.shape
    num_classes = np.max(y) + 1 # assume y takes values 0...K-1 where K is number of classes
    if self.W is None:
      self.W = 0.001 * np.random.randn(dim, num_classes)

    loss_history = []
    for it in range(num_iters):
      X_batch = None
      y_batch = None

      # 默認是replacement = true, 隨機出來的數,還要再放回到樣本池中
      randomRows = np.random.choice(num_train, batch_size)
      X_batch = X[randomRows] 
      y_batch = y[randomRows]

      # evaluate loss and gradient
      loss, grad = self.loss(X_batch, y_batch, reg)
      loss_history.append(loss)

      # perform parameter update
      self.W += -learning_rate * grad

      if verbose and it % 100 == 0:
        print('iteration %d / %d: loss %f' % (it, num_iters, loss))

    return loss_history

  # 預測
  def predict(self, X):
    y_pred = np.zeros(X.shape[0])
    y_pred = np.argmax( np.dot( X, self.W ), axis=1 )

    return y_pred
  
  # 計算損失值
  def loss(self, X_batch, y_batch, reg):
    pass

繼承基類LinearClassifier,定義SVM分類器

class LinearSVM(LinearClassifier):
  """ A subclass that uses the Multiclass SVM loss function """

  def loss(self, X_batch, y_batch, reg):
    return svm_loss_vectorized(self.W, X_batch, y_batch, reg)

 

每次隨機取出200個數據,訓練1500次。 下圖顯示,損失值隨着訓練的迭代,追減降低。

                   

 
        

對於CIFAR-10的訓練集和驗證集的准確度如下。

training accuracy: 0.377939
validation accuracy: 0.383000
 
原文中還對學習率和Regulariztion Strength(不知道如何翻譯好)做了各種調整。下面是各種組合的准確率統計。
顏色越深,表示准確率越高。可以看出來較低的學習率,訓練的准確率越高。但是學習率越低,訓練的時間也就越長。這是一個需要權衡的地方。
             
 
        
 
        

             

 

下圖是最終訓練出來的W所對應的圖片。從圖中可以看出最終的W,通過5萬張圖的訓練, 提取出了具有指定分類的特征。

              

 

python相關:

  • np.mean(a, axis=None),
    •   計算平均值。 axis=0,對整列計算均值。
  • np.hstack(tup),
    •   在水平方向上合並
  • np.vstack(tup),
    •   在豎直方向上合並
  • np.stack(arrays, axis=0),
    •   增加了一個維度,而且合並的兩個的形狀必須一樣。
  • np.tile(A, reps),
    •   A是被重復的對象,reps是在不同維度上重復的次數
  • np.random.randn,
    •   [0,1)范圍內的隨機數,滿足高斯分布
  • tuple([randrange(m) for m in x.shape]),
    •   隨機選擇x中的任意位置
  • f = lambda w: svm_loss_naive(w, X_dev, y_dev, 0.0)[0],
    •   lambda函數,w是參數,:后面是函數體。
  • np.random.choice(a, size=None, replace=True, p=None),
    •   從a中隨機選擇size個數。replace=True,表示隨機取出的數,還要放回去。
  • np.max(a, axis=None, out=None, keepdims=np._NoValue),
    •   取最大值,也可以取每行或者每列中的最大值。
  • np.argmax,
    •   和np.max類似,區別是返回的是索引。

 

Reference:

1、知乎CS231n中文版,https://zhuanlan.zhihu.com/p/20918580?refer=intelligentunit

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM