支持向量机(SVM)是Vapnik和Corinna Cortes提出的,是基于优美的数学理论的一种机器学习算法。SVM是针对线性可分情景的广义线性回归模型,对于线性不可分的情况,则通过“核技巧”将低维特征空间的线性不可分样本映射到高维空间中,实现对非线性样本进行线性分析。SVM在小样本的情况下表现也不错,但是其计算代价较高,计算速度慢,且性能逐渐被新晋的树类集成算法和深度算法所领先。SVM目前在小样本、非线性、高维的应用场景中仍有一席之地,且其数学理论完备,非常值得学习。
SVM是一个二分类的分类模型,即寻找一个超平面将样本分为两类,若是多分类问题,则通过one vs rest构建多个二分类SVM形成多分类SVM。支持向量机用作回归(SVR)时,即学习得一个超平面函数f(x),使得f(x)尽量与标签y接近,而与传统回归模型不同的是,当f(x)与y的差值大于设定阈值时才开始计算损失。
SVM的重点在于超平面分割、核技巧映射、凸二次规划问题求解等等,推荐这位大佬的理论推导,非常详细。本人数学知识也是比较有限,就不在理论上班门弄斧了。
参考李飞飞在2005年发表的论文《A Bayesian Hierarchical Model for Learning Natural Scene Categories》中的bag of words思想,先通过对每张图进行SIFT+Kmeans聚类+直方图统计构建场景特征的特征库(词库),即每种场景都可由特征库中的特征表示,然后利用SVM对场景进行分类。
这是要用到的15类场景数据库:
主要流程如下:
数据预处理:
1、 读取15个类别共8970张图片建立总数据集
2、分层随机抽样划分训练集与测试集
Bag Of Word词库建立:
1、 对训练集与测试集中每张图进行SIFT特征提取,出于减少计算量的考虑,只计算通过固定步长进行网格采样获得的点的SIFT特征。
2、指定聚类中心即总单词个数后对训练集中的所有SIFT特征向量进行K-Means聚类
3、对训练集与测试集中每张图对应的SIFT特征向量分别进行直方图统计,对应生成直方图统计数据的训练集与测试集
SVM模型构建:
1、调用sklearn库中的SVM模型,在默认参数下换用不同的核函数进行训练,并计算其在测试集上的表现。
2、对上一步的模型在训练集上进行5折交叉验证的网格调参以提高模型的性能,并计算调参后的模型在测试集上的表现
sklearn库SVM中常用的核函数有三种:
kernel='linear'
用于线性可分情形,参数少,速度快
kernel='rbf'
主要用于线性不可分情形,参数多,需要调参
kernel='poly'
多项式核函数,可用于非线性分类
各SVM的主要调参对象是惩罚系数c,c越小,则允许更多错分样本。
接下来就上代码啦:
import cv2
import numpy as np
import matplotlib.pyplot as plt
import sklearn
from sklearn.model_selection import train_test_split
from sklearn.cluster import KMeans
from sklearn import preprocessing
from sklearn.svm import LinearSVC
import copy
from sklearn.metrics import roc_auc_score,roc_curve,auc,accuracy_score,classification_report,confusion_matrix,f1_score
import itertools
from sklearn.metrics import confusion_matrix
import pandas as pd
from sklearn.svm import SVC
import glob
from sklearn.model_selection import cross_val_score
from sklearn import metrics
from sklearn.model_selection import GridSearchCV
class_names = [name[11:] for name in glob.glob('D:/Computer_Vision/scene15/*')] #读取目录下所有文件夹名
for i in range(len(class_names)): #提取15个类别名
class_names[i]=class_names[i].split('\\')[1]
class_names = dict(zip(range(0,len(class_names)), class_names)) #对15种类别进行labelencoding
print (class_names)
def load_dataset(path, num_per_class=-1): #通过遍历读取每个文件夹中的图片,num_per_class设置每个类别中读取的图片数,默认全部读取
data = []
labels = []
for id, class_name in class_names.items():
img_path_class = glob.glob(path + class_name + '/*.jpg')
if num_per_class > 0:
img_path_class = img_path_class[:num_per_class]
labels.extend([id]*len(img_path_class))
for filename in img_path_class:
data.append(cv2.pyrDown(cv2.imread(filename), 0))
return data, labels
X, y = load_dataset('D:/Computer_Vision/scene15/') #调用load_dataset()函数构建数据集及标签集
X_num= len(X)
train_data,test_data,train_label,test_label= train_test_split(X,y, test_size=0.2,#随机抽取划分训练集与测试集,stratify的设置保留了原数据集中的样本分布
shuffle=True,
random_state=111,
stratify=y)
def computeSIFT(data):
x = []
for i in range(0, len(data)):
sift = cv2.xfeatures2d.SIFT_create() #构建SIFT特征提取器
img = data[i]
step_size = 15 #设置固定步长进行网格采样
kp = [cv2.KeyPoint(x, y, step_size) for x in range(0, img.shape[0], step_size) for y in range(0, img.shape[1], step_size)]
dense_feat = sift.compute(img, kp) #计算SIFT特征
x.append(dense_feat[1])
return x
x_train = computeSIFT(train_data) #对训练集和测试集分别计算SIFT特征
x_test = computeSIFT(test_data)
all_train_desc = [] #通过遍历展开训练集的list
for i in range(len(x_train)):
for j in range(x_train[i].shape[0]):
all_train_desc.append(x_train[i][j,:])
all_train_desc = np.array(all_train_desc)
def clusterFeatures(all_train_desc, k):#k表示聚类中心数即单词数
kmeans = KMeans(n_clusters=k, random_state=0,n_jobs=2).fit(all_train_desc) #创建K-means模型,n_jobs指定并行内核数
return kmeans
def formTrainingSetHistogram(x_train, kmeans, k):
train_hist = []
for i in range(len(x_train)):
data = copy.deepcopy(x_train[i])
predict = kmeans.predict(data)
train_hist.append(np.bincount(predict, minlength=k).reshape(1,-1).ravel()) #对每幅图的SIFT特征进行直方图统计
return np.array(train_hist)
k = 50
kmeans = clusterFeatures(all_train_desc, k) #进行kmeans聚类
train_hist = formTrainingSetHistogram(x_train, kmeans, k) #生成训练集和测试集的直方图集
test_hist = formTrainingSetHistogram(x_test, kmeans, k)
scaler = preprocessing.StandardScaler().fit(train_hist) #进行归一化
train_hist = scaler.transform(train_hist)
test_hist = scaler.transform(test_hist)
svm = sklearn.svm.SVC(kernel='linear',class_weight='balanced',probability=True) #使用线性支持向量机进行训练及预测
svm.fit(train_hist, train_label)
predict=svm.predict(test_hist)
print('准确率是:%s'%(accuracy_score(test_label,predict)))
print(classification_report(test_label,predict)) #输出其在测试集上的表现
print(confusion_matrix(test_label,predict))
########### 对linearsvm分类器进行网格调参 ############
param_test1 = {'C': np.arange(0.01, 1.0001, 0.01) #设定网格搜寻范围
}
gsearch1 = GridSearchCV(estimator = SVC(kernel='linear',class_weight='balanced',probability=True),
param_grid = param_test1, scoring ="accuracy",cv=5,n_jobs=5,verbose=2) #cv指定交叉验证折数
gsearch1.fit(train_hist, train_label)
print(gsearch1.best_params_)
print(gsearch1.best_score_)
print(gsearch1.best_estimator_)
svm_t = sklearn.svm.SVC(C=0.9400000000000001, break_ties=False, cache_size=200, #调参后线性支持向量机性能检验
class_weight='balanced', coef0=0.0, decision_function_shape='ovr', degree=3,
gamma='scale', kernel='linear', max_iter=-1, probability=True,
random_state=None, shrinking=True, tol=0.001, verbose=False)
svm_t.fit(train_hist, train_label)
predict_t=svm_t.predict(test_hist)
print('准确率是:%s'%(accuracy_score(test_label,predict_t)))
print(classification_report(test_label,predict_t))
print(confusion_matrix(test_label,predict_t))
svm_rbf= sklearn.svm.SVC(kernel='rbf',class_weight='balanced',probability=True) #使用径向基支持向量机进行训练及预测
svm_rbf.fit(train_hist, train_label)
predict_rbf=svm_rbf.predict(pd.DataFrame(test_hist))
print('准确率是:%s'%(accuracy_score(test_label,predict_rbf)))
print(classification_report(test_label,predict_rbf)) #输出其在测试集上的表现
print(confusion_matrix(test_label,predict_rbf))
svm_poly= sklearn.svm.SVC(kernel='poly',class_weight='balanced',probability=True) #使用多项式支持向量机进行训练及预测
svm_poly.fit(train_hist, train_label)
predict_poly=svm_poly.predict(pd.DataFrame(test_hist))
print('准确率是:%s'%(accuracy_score(test_label,predict_poly)))
print(classification_report(test_label,predict_poly)) #输出其在测试集上的表现
print(confusion_matrix(test_label,predict_poly))
def plot_confusion_matrix(cm, classes,
normalize=False,
title='Confusion matrix',
cmap=plt.cm.Blues):
if normalize:
cm = cm.astype('float') / cm.sum(axis=1)[:, np.newaxis]
plt.imshow(cm, interpolation='nearest', cmap=cmap)
plt.title(title)
plt.colorbar()
tick_marks = np.arange(len(classes))
plt.xticks(tick_marks, classes, rotation=45)
plt.yticks(tick_marks, classes)
fmt = '.2f' if normalize else 'd'
thresh = cm.max() / 2.
for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):
plt.text(j, i, format(cm[i, j], fmt),
horizontalalignment="center",
color="white" if cm[i, j] > thresh else "black")
plt.tight_layout()
plt.ylabel('True label')
plt.xlabel('Predicted label')
cnf_matrix = confusion_matrix(np.array([test_label]).T, predict_poly) #指定不同predict_poly更换不同混淆矩阵画图数据
np.set_printoptions(precision=2)
plt.figure(figsize=(18, 6))
plot_confusion_matrix(cnf_matrix, classes=class_names,
title='15 scene Confusion matrix')
plt.figure(figsize=(18, 6))
plot_confusion_matrix(cnf_matrix, classes=class_names, normalize=True,
title='15 scene Confusion matrix')
plt.show()
线性支持向量机默认参数在独立测试集上的各项指标如下:
调参后的线性支持向量机性能有少量上升,在实验中可以观察到不同的核函数带来的差异也是不小的,同时,随着单词数的增加性能不断上升,在该应用场景中多项式支持向量机的准确度最高。
要想进一步提高SVM的准确度,可以从特征工程中进行改进,比如参考这篇论文中的空间金字塔特征构建方法,特征大幅度增加后SVM模型的性能也大幅度提高。
如果换用下其它机器学习模型(比如LightGBM),就会发现SVM的性能真的有点低了。
Spatial Pyramid Matching Scene Recognition
《深度学习原理与TensorFlow实践》 黄理灿