参考资料:
CS231n
线性分类
线性分类简单来说就是将一个样本输入一个线性函数,输出为这个样本在各个类别上的得分,取得分高的类别作为其分类结果。
如下图所示,猫为待分类的样本,f(x,W)为一个线性函数,将样本输入后就得到了它在十个类别上的得分。
其中dog这一类的得分最高,为8.02分,所以分类器将这个样本分类为dog,显然分类错误了。
SVM
支持向量机,用来计算分类损失。其公式如图。
其中 i 代表第 i 个训练样本;yi 代表这个样本的标签,即真实分类;S 代表得分,Sj 即这个样本在 j 这个类别上的得分,Syi 即这个样本在 yi (其自身的真实类别)这个类别上的得分。
其中的 Sj - Syi + 1 最好是写作 Sj + 1 - Syi,这样比较好理解。其中的 1 其实是无关紧要的,作为 Sj 与 Syi 之间距离的一个预留量。
参照下图,结合SVM的公式,如果你把 Sj + 1 看作一个整体,记作 S’,那么第 i 个样本在 j 这个类别上的损失其实就是 S’ 与 Syi 的差值。当然,当这个差值小于零的时候,损失为零。(说明第 i 个样本在 yi 上的得分远大于在 j 上的得分,这里的远大于即公式中预留的距离 1,所以说这个预留的距离是按需可变的)
而后将这第 i 个样本在各个类别(除去它本身真实类别)上的损失进行累加,即得到了这个样本最终的损失。
把所有样本的损失都计算完成之后,取它们的加权平均,即为这一次训练所得到的损失。如下图所示:
在得到了训练损失之后,就可以利用梯度下降算法来更新权重,进行迭代训练了。
代码实现
#SVM_object.py
import numpy as np
class SVM(object):
def __init__(self):
self.W = None
self.reg = 3.5e+4
self.num_classes = 10
self.learning_rate = 7.5e-8
def svm_loss_vectorized(self, W, X, y, reg):
loss = 0.0
dW = np.zeros(W.shape) # 初始化梯度为0
scores = X.dot(W) # 计算得分 N×10
num_train = X.shape[0] # N
scores_correct = scores[np.arange(num_train), y] # 整形数组访问方式
scores_correct = np.reshape(scores_correct, (num_train, -1)) # N×1
margins = scores - scores_correct + 1 # SVM的loss计算公式
margins = np.maximum(0, margins)
margins[np.arange(num_train), y] = 0 # 正确分类处的损失置零
loss += np.sum(margins) / num_train # 计算均值损失
loss += 0.5 * reg * np.sum(W * W) # 正则化
margins[margins > 0] = 1 # 布尔型数组访问方式
row_sum = np.sum(margins, axis=1) # N×1
margins[np.arange(num_train), y] = -row_sum
dW += np.dot(X.T, margins) / num_train + reg * W # 计算梯度
return loss, dW
def train(self, X, y, num_classes=10, learning_rate=7.5e-8, reg=3.5e+4, num_iters=100,
batch_size=200, verbose=False):
"""
使用随机梯度下降来训练这个分类器
输入:
-X :一个numpy数组,维数为(N,D)
-Y : 一个numpy数组,维数为(N,)
-learning rate: float ,优化的学习率
-reg : float,正则化强度
-num_iters: integer, 优化时训练的步数
-batch_size:integer, 每一步使用的训练样本数
-ver bose : boolean, 若为真,优化时打印过程
输出:
一个存储每次训练的损失函数值的List
"""
num_train, dim = X.shape
self.num_classes = num_classes
if self.W is None:
self.W = 0.001 * np.random.randn(dim, num_classes) # 初始化W
# 使用随机梯度下降优化W
loss_history = []
for it in range(num_iters):
X_batch = None
y_batch = None
"""
从训练集中采样batch_size个样本和对应的标签,在这一轮梯度下降中使用。
把数据存储在X_batch中,把对应的标签存储在y_batch中
采样后,X_batch的形状为(dim,batch_size),y_batch的形状为(batch_size,)
"""
batch_inx = np.random.choice(num_train, batch_size)
X_batch = X[batch_inx, :]
y_batch = y[batch_inx]
loss, grad = self.svm_loss_vectorized(
self.W, X_batch, y_batch, reg)
loss_history.append(loss)
"""
使用梯度和学习率更新权重
"""
self.W = self.W - learning_rate * grad
if verbose and it % 10 == 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])
scores = X.dot(self.W)
y_pred = np.argmax(scores, axis=1) # 返回每一行最大值的序号
return y_pred
#SVM_run.py
from SVM_object import SVM
import time
import numpy as np
def unpickle(file):
import pickle
with open(file, 'rb') as fo:
dict = pickle.load(fo, encoding='bytes')
return dict
dataTrain = []
labelTrain = []
for i in range(1, 6):
dic = unpickle(
"D:/Desktop/论文及算法实现/DATA_SETS/cifar-10-batches-py/data_batch_"+str(i))
for item in dic[b"data"]:
# print(item.shape)
item = item.tolist()
item.append(1)
dataTrain.append(item)
for item in dic[b"labels"]:
labelTrain.append(item)
dataTest = []
labelTest = []
dic = unpickle("D:/Desktop/论文及算法实现/DATA_SETS/cifar-10-batches-py/test_batch")
for item in dic[b"data"]:
item = item.tolist()
item.append(1)
dataTest.append(item)
for item in dic[b"labels"]:
labelTest.append(item)
dataTr = np.asarray(dataTrain)
dataTs = np.asarray(dataTest)
labelTr = np.asarray(labelTrain)
labelTs = np.asarray(labelTest)
print('dataTr.shape: {}'.format(dataTr.shape))
svm = SVM()
tic = time.time()
loss_hist = svm.train(dataTr, labelTr, num_classes=10, learning_rate=7.5e-8, reg=3.5e+4,
num_iters=1000, batch_size=1000, verbose=True)
toc = time.time()
print('That took %fs' % (toc - tic))
# 测试
y_train_pred = svm.predict(dataTr)
print('training accuracy: %f' % (np.mean(labelTr == y_train_pred), ))
y_val_pred = svm.predict(dataTs)
print('validation accuracy: %f' % (np.mean(labelTs == y_val_pred), ))
iteration 960 / 1000: loss 9.890029
iteration 970 / 1000: loss 9.573637
iteration 980 / 1000: loss 9.638673
iteration 990 / 1000: loss 8.619956
That took 44.813690s
training accuracy: 0.259320
validation accuracy: 0.254400