以图像分割为例浅谈支持向量机(SVM)

1. 什么是支持向量机?

  在机器学习中,分类问题是一种很是常见也很是重要的问题。常见的分类方法有决策树聚类方法贝叶斯分类等等。举一个常见的分类的例子。以下图1所示,在平面直角坐标系中,有一些点,已知这些点能够分为两类,如今让你将它们分类。
(图1)
显然咱们能够发现全部的点一类位于左下角,一类位于右上角。因此咱们能够很天然将它们分为两类,如图2所示:红色的点表明一类,蓝色的点表明一类。
(图2)
如今若是让你用一条直线将这两类点分开,这应该是一件很是容易的事情,好比如图3所示的三条直线均可以办到这点。
(图3)
事实上,能够很容易发现,咱们能够做无数条直线将这两类点分开。这里,咱们不由要问,是否是全部的直线分类的效果都同样好呢?若是不是,那么哪一条直线分类效果最好呢?评判的标准又是什么?好比对于如图4所示的两条直线,\(line1\)\(line2\),这两条直线哪条分类效果更好呢?
(图4)
直观上能够发现,\(line1\)的分类效果要比\(line2\)更好的,这是由于\(line1\)几乎位于这两类点的中间,不偏向于任何一类点;而\(line2\)则偏向右上部分的点更多一些。若是这时又增长了一些点让你将它们归为这两类,显然\(line1\)要更“公正”一些,而\(line2\)则有可能将原本属于右上类的点错误地归为左下类。说到这里,你可能会问,如何才能肯定那个最佳分类的直线呢?其实这正是支持向量机(\(SVM,Support Vector Machine\))要解决的问题。
  更通常地状况下,如图5所示,有时两类点(图5中红色的点和蓝色的点)是交错分布的,“你中有我,我中有你”,根本不可能用一条直线分开,这个时候该怎么办呢?这也是支持向量机要解决的问题,并且是支持向量机的优点所在。这类问题叫作非线性分类问题
(图5)
  说到这里,你可能大概有些明白支持向量机是用来干什么的了。支持向量机的基本模型是定义在特征空间上的间隔最大线性分类器。它是一种二分类模型。当采用了核技巧以后,支持向量机便可以用于非线性分类。不一样类型的支持向量机解决不一样的问题。python

1.线性可分支持向量机:当训练数据线性可分时,经过硬间隔最大化,学习一个线性可分支持向量机。

2. 线性支持向量机:当训练数据近似可分时,经过软间隔最大化,学习一个线性支持向量机。

3. 非线性支持向量机:当训练数据线性不可分时,经过使用核技巧以及软间隔最大化,学习一个非线性支持向量机。

  以上只是对于支持向量机的最粗浅的说明,其实支持向量机内在的数学原理仍是很是复杂的,其内容也十分丰富。我在学习的过程当中参考了很多教材,好比《数据挖掘导论》、《神经网络与机器学习》、《Python大战机器学习》等。里面对于支持向量机有很是详细的说明,并且还从数学的角度推导了一遍。我的以为好好研究一下原理以及数学推导对于深入理解支持向量机仍是很是有帮助的。鉴于我这里只是介绍,而非严格地教程,因此公式就不罗列了,感兴趣的请自行阅读相关文献与书籍。算法

2. 如何理解支持向量机?

  若是不从数学公式的角度出发,在不涉及公式细节的状况下,如何直观理解支持向量机呢?虽然这并不是易事(由于支持向量机的复杂性),可是仍是能够办到的。我在查阅资料的过程当中,看到了知乎上的一个问题,里面有几个答案我以为很是棒,可让你在不理解数学公式的状况下,对于支持向量机有一个直观的了解。地址以下:支持向量机(SVM)是什么意思?。这里我仍然以两类点的分类问题为例来谈谈我本身的理解。以图1中的两类点为例,前面咱们已经说过了,存在无穷多条直线能够将这两类点分开。如今咱们的目标是在必定的准则下,找出划分最好的那一条。从直观的理解来看,这条最佳直线应该知足“公正性”:即不偏向任何一类点,或者说处于中间位置。如今假设咱们已经找到了一条分割直线\(l\),每个样本点都到这条直线存在一个距离。设直线\(l\)的方程为:\(wx + b = 0\),共有\(n\)个点,\(n\)个点的坐标为\((x_1,y_1),(x_2,y_2),\cdots,(x_n,y_n)\)\(n\)个点到直线\(l\)的距离分别为\(d_1,d_2,\cdots,d_n\),如今咱们须要找\(d_1,d_2,\cdots,d_n\)中的最小值:\(d_{min} = min\{d_1,d_2,\cdots,d_n\}\),显然咱们但愿\(d_{min}\)越大越好,\(d_{min}\)越大,说明它距离两类的距离都较远。因而问题转化为在全部可行的直线划分中,找到 使得\(d_{min}\)最大的那条便是最佳划分直线。对于线性可分的状况而言,咱们能够证实,这样的最佳直线老是存在的。咱们称找到的最佳划分两类的直线为:最大几何间隔分离超平面(对于二维点而言是直线,三维空间中则是平面,更高维则是超平面了,这里统称为超平面)。数组

什么是支持向量

  支持向量机(\(SVM\))之因此称之为支持向量机,是由于有一个叫做支持向量(\(Support Vector\))的东西。那么什么叫做支持向量呢?假设咱们如今已经找到了最大几何间隔分离超平面,容易理解,咱们能够找到许多条与这条直线平行的直线,在全部平行的直线中,存在两条直线,它们刚好能够划分两类点,所谓刚好是指,若是再平移哪怕一点点,就会不能正确划分两类点,这两条临界直线(超平面)被称之为间隔边界。对于线性可分的状况而言,咱们能够证实,在样本点中总会有一些样本点落在间隔边界上(可是对于线性不可分的状况,则未必如此),落在间隔边界上的这些样本点就被咱们称为支持向量。之因此被称之为支持向量呢,是由于咱们肯定的最大几何间隔分离超平面只与这些支持向量有关,与其余的样本点无关,也就是说哪怕你去掉再多非支持向量的点,最大几何间隔分离超平面也同样不变。这也就是支持向量机名字的来源。网络

支持向量机如何处理线性不可分的状况?

  这个问题其实涉及到\(SVM\)的核心了。在以前咱们屡次提到了一个词:核技巧。那么什么是核技巧呢?首先,咱们须要明确输入空间特征空间这两个概念。所谓输入空间就是咱们定义样本点的空间,因为问题线性不可分,因此咱们没法用一个超平面将两类点分开,可是咱们总能够找到一个合适的超曲面将两类点正确划分。问题的关键就是找到这个超曲面。直接寻找显然是很困难的,因此咱们聪明的数学家就定义了一个映射,简单来讲就是从低维到高维的映射,研究发现,若是映射定义地恰当,则原来在低维线性不可分的问题,到了高维竟然就线性可分了!这真的是一个让人惊喜的发现。因此咱们只要在高维按照以前线性可分的状况去找最大几何间隔分离超平面,找到以后,再还原到低维就能够了。理论上已经证实,在低维线性不可分的状况下,咱们总能够找到合适的从低维到高维的映射,使得在高维线性可分。因而问题的关键就是找这个从低维到高维的映射了,这个其实就是核函数(核技巧)要干的事情了。具体的定义较为复杂,这里不展开了。在给定核函数的状况下,咱们能够利用求解线性分类问题的方法来求解非线性分类问题的支持向量机,学习是隐式地在特征空间(也就是映射以后的高维空间)进行的,这被称之为核技巧。在实际应用中,每每直接依赖经验选择核函数,而后再验证其是有效的便可。经常使用的核函数有:多项式核函数高斯核函数sigmoid核函数等。app

3. 支持向量机的实际应用举例(附matlab代码与Python代码)

1. 将两类点分类(二维平面)

  做为第一个例子,咱们首先解决开头提到的那个平面上两类点的分类问题。咱们找出最大几何间隔分离超平面支持向量,而后验证该最佳超平面可否对新加入的点进行准确分类。这里咱们分别使用Matlab与Pyhton来实现这个例子。Matlab中的\(svmtrain\)\(svmclassify\)函数以及Python sklearn(一个机器学习的库)均对SVM有很好的支持。若是想要详细了解两者的用法,对于Matlab能够直接查看其帮助手册,对于Pyhton则能够参考相关机器学习的书籍或者直接去看sklearn的网站学习。
Matlab 对两类点分类的代码:dom

% 使用SVM(支持向量机)分割两类点并画出图形
XY1 = 2 + rand(100,2); % 随机产生100行2列在2-3之间的点
XY2 = 3+ rand(100,2);% 随机产生100行2列在3-4之间的点
XY = [XY1;XY2]; % 合并两点
Classify =[zeros(100,1);ones(100,1)]; % 第一类点用0表示,第二类点用1表示
Sample = 2+ 2*rand(100,2); % 测试点
%figure(1);
%plot(XY1(:,1),XY1(:,2),'r*'); % 第一类点用红色表示
%hold on;
%plot(XY2(:,1),XY2(:,2),'b*'); % 第二类点用蓝色表示
% 训练SVM
SVM = svmtrain(XY,Classify,'showplot',true);
% 给测试点分类,并做出最大间隔超平面(一条直线)
svmclassify(SVM,Sample,'showplot',true);

获得结果如图6所示:
(图6)
图6中的直线便是所求的最大几何间隔分离超平面,画黑圈的点为支持向量,并且能够看出其对新增长的点划分得很好,这说明了SVM最大几何间隔分离超平面分类的有效性。
再来看Python的代码:机器学习

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time   : 2017/7/22 10:45
# @Author : Lyrichu
# @Email  : 919987476@qq.com
# @File   : svm_split_points.py
'''
@Description:使用svm对两类点进行分类(线性可分)
'''
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import LinearSVC # 导入SVM 线性分类器
XY1 = 2 + np.random.rand(100,2) # 100行2列在2到3之间的数据点
XY2 = 4 + np.random.rand(100,2) # 100行2列在4到5之间的数据点
XY = np.concatenate((XY1,XY2),axis=0)
test_data = 2 + 3*np.random.rand(100,2) # 测试数据,2-5之间
label = np.append(np.zeros(100),np.ones(100)) # XY1 用0标志,XY2用1标志
svm = LinearSVC()
svm.fit(XY,label)
predict_test =svm.predict(test_data) # 对测试数据进行预测
coef = svm.coef_ # 系数(w向量)
intercept = svm.intercept_ # 截距(b)
# print("coef:",coef)
# print("intercept:",intercept)
# print("predict_test:",predict_test)
sort1_index = predict_test == 0. # 测试数据属于第一类的序号(bool 数组)
sort2_index = predict_test == 1. # 测试数据属于第二类的序号(bool 数组)
test_sort1 = test_data[sort1_index,:] # 测试数据属于第一类的点
test_sort2 = test_data[sort2_index,:] # 测试数据属于第二类的点
# 最大间隔超平面的方程为:Wx + b = 0
# 画图
plt.plot(XY1[:,0],XY1[:,1],'r*',label='train data 1')
plt.plot(XY2[:,0],XY2[:,1],'b*',label='train data 2')
line_x = np.arange(2,5,0.01) # 直线x坐标
line_y = (coef[0,0]*line_x + intercept[0])/(-coef[0,1]) # 直线y坐标
# 画出直线
plt.plot(line_x,line_y,'-')
# 画出预测点
plt.plot(test_sort1[:,0],test_sort1[:,1],'r+',label='test data 1')
plt.plot(test_sort2[:,0],test_sort2[:,1],'b+',label='test data 2')
plt.legend(loc = 'best')
plt.show()

结果以下图7所示:
(图7)
其中那条直线便是做出的最大几何间隔分离超平面,train data 1 和 train data 2为第1、二类训练数据,test data 1和 test data 2 为第1、二类测试数据。能够看出 SVM 分类的效果很好。函数

2. 将图像中的某个物体从背景中分割出来(这里以分割在湖中游泳的鸭子为例)

  如图8所示,湖面上有一只鸭子,如今咱们但愿将鸭子从湖水(背景)中分割出来,该怎么作呢?

若是你手中有相似PS这样的软件,完成这个任务应该并不困难,不就是抠图么!!!可是,抠图须要咱们本身手动找分割线啊,多麻烦呢,能不能让计算机自动完成这个工做呢?固然是能够的,利用上面说的SVM就能够办到。那么该怎么作呢?咱们知道,彩色图片本质上是由一个一个的像素点组成的,每个像素点由RGB三色组成,或者说本质上彩色图像就是三维数组,而灰度图像则是二维数组。若是咱们将湖水和鸭子看作两类物体,那么如今的任务则是从整个图像中将这两类分割出来。显然鸭子与湖水的界限并非一条单纯的直线,甚至有些地方是交杂在一块儿的,因此本质上这是一个非线性可分的问题。从图中能够看出,鸭子的颜色偏黑色和灰色,掺杂有少许白色以及黄色(鸭脚),而湖水则是浅绿色的。因此咱们能够以颜色为标准对两者进行分类,即以RGB为分类标准。为了使用SVM,首先咱们须要选取训练样本,这里就是找出典型的属于鸭子的像素点RGB值(为一个长度为3的向量),和属于湖水的RGB值。关于如何肯定图像上某一点的RGB值,有不少办法,这里我推荐使用一个名为Colorpix的小软件,这个软件只有几百kb,一个exe执行文件,能够找出屏幕上任何一点的像素属性,用起来很方便,若是要用,请你们自行搜索。这里我对于湖水和鸭子分别选取了10个像素点,这样我就获得了一个20行3列的样本数据(每一行是一个样本,共有20个样本)。将湖水的像素点标记为0,鸭子的像素点标记为1,这样咱们就能够获得长度为20的、前10个元素为0,后10个元素为1的向量。因为图像原始数据为三维矩阵,好比设其维度为\((m,n,k)\),咱们首先须要将其转化为2维,即转化为\((mn,k)\)的矩阵,而后使用线性不可分的SVM训练样本数据,接着使用训练好的SVM对\((mn,k)\)矩阵进行归类,咱们获得一个长为\(mn\)的数据取0或者1的一维数组\(predict\),为0的部分就是表明对应的像素点断定为湖水了。接着将\(predict\)数组在行的方向上扩展为3列,即变为\((predict,predict,predict)\),扩展以后的矩阵维度为\((mn,k)\),再将其变回三维矩阵,即\((m,n,k)\)的矩阵。该矩阵与原始图像三维矩阵对应,该矩阵数据点为\((0,0,0)\)的部分即断定为湖水,咱们将图像上该像素点的RGB值变为\((255,255,255)\)(白色),因而咱们就能够获得去掉湖水(变为白色背景)的鸭子了。
  以上就是使用SVM将鸭子从湖水中分割出来的步骤了。下面给出代码:学习

1. Matlab 代码

% 使用SVM将鸭子从湖面分割
% 导入图像文件引导对话框
[filename,pathname,flag] = uigetfile('*.jpg','请导入图像文件');
Duck = imread([pathname,filename]);
%使用ColorPix软件从图上选取几个湖面的表明性点的RGB的值
LakeTrainData = [147,168,125;151 173 124;143 159 112;150 168 126;...
    146 165 120;145 161 116;150 171 130;146 112 137;149 169 120;144 160 111];
% 从图中选取几个有表明性的鸭子点的RGB值
DuckTrainData = [81 76 82;212 202 193;177 159 157;129 112 105;167 147 136;...
    237 207 145;226 207 192;95 81 68;198 216 218;197 180 128];
% 属于湖的点为0,鸭子的点为1
group = [zeros(size(LakeTrainData,1),1);ones(size(DuckTrainData,1),1)];
% 训练获得支持向量分类机
LakeDuckSVM = svmtrain([LakeTrainData;DuckTrainData],group,'kernel_function','polynomial',...
    'polyorder',2);
[m,n,k] = size(Duck); % 图像三维矩阵
% 将Duck转化为双精度的m*n行,3列的矩阵
Duck1 = double(reshape(Duck,m*n,k));
% 根据训练获得的支持向量机对整个图像像素点进行分类
IndDuck = svmclassify(LakeDuckSVM,Duck1);
% 属于湖的点的逻辑数组
IndLake = ~IndDuck;
result = reshape([IndLake,IndLake,IndLake],[m,n,k]); % 与图片的维数对应
Duck2 = Duck;
Duck2(result)= 255; % 湖面的点变为白色
figure;
imshow(Duck2); % 显示分割以后的图像

结果如图8所示:
(图8)
能够基本看到鸭子的轮廓了,可是鸭子身体中有不少小点被扣去了(属于误判为湖水),这种状况能够改变一些选取的像素点,或者增长一些样本,能够优化分割的效果。
再来看Python的实现吧。测试

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time   : 2017/7/22 13:58
# @Author : Lyrichu
# @Email  : 919987476@qq.com
# @File   : svm_split_picture.py
'''
@Description:SVM 将在湖中的一只鸭子与湖水分割出来
'''
from PIL import Image
import numpy as np
from sklearn.svm import SVC # 非线性 分类 SVM
pic = 'duck.jpg' # 鸭子图片
img = Image.open(pic)
img.show() # 显示原始图像
img_arr = np.asarray(img,np.float64)
# 选取湖面上的关键点RGB值(10个)
lake_RGB = np.array(
    [[147,168,125],[151,173,124],[143,159,112],[150,168,126],[146,165,120],
     [145,161,116],[150,171,130],[146,112,137],[149,169,120],[144,160,111]]
)
# 选取鸭子上的关键点RGB值(10个)
duck_RGB = np.array(
    [[81,76,82],[212,202,193],[177,159,157],[129,112,105],[167,147,136],
     [237,207,145],[226,207,192],[95,81,68],[198,216,218],[197,180,128]]
)
RGB_arr = np.concatenate((lake_RGB,duck_RGB),axis=0) # 按列拼接
# lake 用 0标记,duck用1标记
label = np.append(np.zeros(lake_RGB.shape[0]),np.ones(duck_RGB.shape[0]))
# 本来 img_arr 形状为(m,n,k),如今转化为(m*n,k)
img_reshape = img_arr.reshape([img_arr.shape[0]*img_arr.shape[1],img_arr.shape[2]])
svc = SVC(kernel='poly',degree=3) # 使用多项式核,次数为3
svc.fit(RGB_arr,label) # SVM 训练样本
predict = svc.predict(img_reshape) # 预测测试点
lake_bool = predict == 0. # 为湖面的序号(bool)
lake_bool = lake_bool[:,np.newaxis] # 增长一列(一维变二维)
lake_bool_3col = np.concatenate((lake_bool,lake_bool,lake_bool),axis=1) # 变为三列
lake_bool_3d = lake_bool_3col.reshape((img_arr.shape[0],img_arr.shape[1],img_arr.shape[2])) # 变回三维数组(逻辑数组)
img_arr[lake_bool_3d] = 255. # 将湖面像素点变为白色
img_split = Image.fromarray(img_arr.astype('uint8')) # 数组转image
img_split.show() # 显示分割以后的图像
img_split.save('split_duck.jpg') # 保存

结果如图9所示:
(图9)
能够看出,图9的效果要比图8好不少,基本已经将湖水所有去除了,只有少数点没有去除,若是增长一些训练样本,训练的效果应该会更好,你们有兴趣的能够本身尝试一下。不过我很奇怪的是,Matlab与pyhton我选取的像素点是如出一辙的,SVM训练设置参数也是同样的,为何python的效果要明显好于Matlab呢?我没有阅读两者SVM的源码,很差下结论,姑且认为是Python大法好吧!!!哈哈哈......
  以上就是主要要讲的内容了。其实SVM在最近几年神经网络大火以前仍是很是受欢迎的,不过如今作复杂分类(好比图像分类,语音识别等)好像更倾向于神经网络了,SVM的一个重大缺点就是其对于处理大规模数据不是很适合,由于其主流的算法复杂度都是\(O(n^2)\)的,不过其在高维数据以及规模适中的状况下作分类效果仍是很不错的。之后有机会再来和你们探讨深度学习以及神经网络吧,目前正入坑中。。。

Reference

  1. 《数据挖掘概念与技术》
  2. 《神经网络与机器学习》
  3. 《Python大战机器学习》
  4. 《Matlab在数学建模中的应用》 特别感谢《Matlab在数学建模中的应用》,图像分割的那个例子Matlab代码改编于此,Python代码也是基于此书改写的。
相关文章
相关标签/搜索