python计算机视觉学习————图像内容分类

8.1 K邻近分类法KNN

概述: K最近邻(k-Nearest Neighbor,KNN),指导思想是“近朱者赤,近墨者黑”,由你的邻居来推断出你的类别。
原理: 从训练集中找到和新数据最接近的k条记录,然后根据多数类来决定新数据类别,本质上,KNN是使用距离来计算相似度。

算法涉及3个主要因素:训练数据集;距离或相似度的计算衡量;k的大小。
在这里插入图片描述
已知两类“先验”数据,分别是蓝方块和红三角,他们分布在一个二维空间中;有一个未知类别的数据(绿点),需要判断它是属于“蓝方块”还是“红三角”类;考察离绿点最近的3个(或k个)数据点的类别,占多数的类别即为绿点判定类别。

计算步骤

1.算距离:给定测试对象,计算它与训练集中的每个对象的距离;
2.找邻居:圈定距离最近的k个训练对象,作为测试对象的近邻;
3. 做分类:根据这k个近邻归属的主要类别,来对测试对象分类。

相似度的衡量
距离越近应该意味着这两个点属于一个分类的可能性越大。
距离不能代表一切,有些数据的相似度衡量并不适合用距离。
相似度衡量方法:包括欧式距离、夹角余弦等。
简单应用中,一般使用欧氏距离,但对于文本分类来说,使用余弦(cosine)来计算相似度就比欧式(Euclidean)距离更合适。

类别判定
简单投票法:少数服从多数,近邻中哪个类别的点最多就分为该类。
加权投票法:根据距离的远近,对近邻的投票进行加权,距离越近则权重越大(权重为距离平方的倒数)

实现最基本的 KNN 形式非常简单。给定训练样本集和对应的标记列表,下面的代码可以用来完成这一工作。这些训练样本和标记可以在一个数组里成行摆放或者干脆摆放列表里,训练样本可能是数字、字符串等任何你喜欢的形状。将定义的类对象添加到名为 knn.py 的文件里:

class KnnClassifier(object):
 def __init__(self,labels,samples):
 """ 使用训练数据初始化分类器 """
 self.labels = labels
 self.samples = samples
 def classify(self,point,k=3):
 """ 在训练数据上采用 k 近邻分类,并返回标记 """
 
 # 计算所有训练数据点的距离
 dist = array([L2dist(point,s) for s in self.samples])
 
 # 对它们进行排序
 ndx = dist.argsort()
 
 # 用字典存储 k 近邻
 votes = {}
 for i in range(k):
 label = self.labels[ndx[i]]
 votes.setdefault(label,0)
 votes[label] += 1
 
 return max(votes)
 
def L2dist(p1,p2):
 return sqrt( sum( (p1-p2)**2) )

定义一个类并用训练数据初始化非常简单 ; 每次想对某些东西进行分类时,用 KNN方法,我们就没有必要存储并将训练数据作为参数来传递。用一个字典来存储邻近标记,我们便可以用文本字符串或数字来表示标记。

8.1.1 一个简单的二维示例

首先建立一些简单的二维示例数据集来说明并可视化分类器的工作原理,下面的脚本将创建两个不同的二维点集,每个点集有两类,用 Pickle 模块来保存创建的数据:

from numpy.random import randn
import pickle 
# 创建二维样本数据
n = 200
# 两个正态分布数据集
class_1 = 0.6 * randn(n,2)
class_2 = 1.2 * randn(n,2) + array([5,1])
labels = hstack((ones(n),-ones(n)))
# 用 Pickle 模块保存
with open('points_normal.pkl', 'w') as f:
 pickle.dump(class_1,f)
 pickle.dump(class_2,f)
 pickle.dump(labels,f)
# 正态分布,并使数据成环绕状分布
class_1 = 0.6 * randn(n,2)
r = 0.8 * randn(n,1) + 5
angle = 2*pi * randn(n,1)
class_2 = hstack((r*cos(angle),r*sin(angle)))
labels = hstack((ones(n),-ones(n)))
# 用 Pickle 保存
with open('points_ring.pkl', 'w') as f:
 pickle.dump(class_1,f)
 pickle.dump(class_2,f)
 pickle.dump(labels,f)

用不同的保存文件名运行该脚本两次,例如第一次用代码中的文件名进行保存,第二次将代码中的 points_normal_t.pkl 和 points_ring_pkl 分别改为 points_normal_test.pkl 和 points_ring_test.pkl 进行保存。你将得到 4 个二维数据集文件,每个分布都有两个文件,我们可以将一个用来训练,另一个用来做测试。

# -*- coding: utf-8 -*-
import pickle
from PCV.classifiers import knn
from PCV.tools import imtools
from pylab import *
from numpy import *


with open('points_normal_t.pkl', 'r') as f:
 class_1 = pickle.load(f)
 class_2 = pickle.load(f)
 labels = pickle.load(f)
 model = knn.KnnClassifier(labels,vstack((class_1,class_2)))

with open('points_normal_test.pkl', 'r') as f:
     class_1 = pickle.load(f)
     class_2 = pickle.load(f)
     labels = pickle.load(f)

print model.classify(class_1[0])

# 定义绘图函数
def classify(x,y,model=model):
 return array([model.classify([xx,yy]) for (xx,yy) in zip(x,y)])
# 绘制分类边界
imtools.plot_2D_boundary([-6,6,-6,6],[class_1,class_2],classify,[1,-1])
show()

在控制台上打印第一个数据点估计出来的类标记。
在这里插入图片描述
创建了一个简短的辅助函数以获取 x 和 y 二维坐标数组和分类器,并返回
一个预测的类标记数组。现在我们把函数作为参数传递给实际的绘图函数。把下面
的函数添加到文件 imtools 中:

def plot_2D_boundary(plot_range,points,decisionfcn,labels,values=[0]):
 """Plot_range 为(xmin,xmax,ymin,ymax),points 是类数据点列表,
 decisionfcn 是评估函数,labels 是函数 decidionfcn 关于每个类返回的标记列表 """
 clist = ['b','r','g','k','m','y'] # 不同的类用不同的颜色标识
 
 # 在一个网格上进行评估,并画出决策函数的边界
 x = arange(plot_range[0],plot_range[1],.1)
 y = arange(plot_range[2],plot_range[3],.1)
 xx,yy = meshgrid(x,y)
 xxx,yyy = xx.flatten(),yy.flatten() # 网格中的 x,y 坐标点列表
 zz = array(decisionfcn(xxx,yyy))
 zz = zz.reshape(xx.shape)
 # 以 values 画出边界
 contour(xx,yy,zz,values)
 # 对于每类,用 * 画出分类正确的点,用 o 画出分类不正确的点
 for i in range(len(points)):
 d = decisionfcn(points[i][:,0],points[i][:,1])
 correct_ndx = labels[i]==d
 incorrect_ndx = labels[i]!=d
 plot(points[i][correct_ndx,0],points[i][correct_ndx,1],'*',color=clist[i])
 plot(points[i][incorrect_ndx,0],points[i][incorrect_ndx,1],'o',color=clist[i])
 axis('equal')

在这里插入图片描述
示例中,不同颜色代表类标记,正确分类的点用星号表示,分类错误的点用圆点表示,曲线是分类器的决策边界。正如所看到的,kNN 决策边界适用于没有任何明确模型的类分布。

8.1.2 用稠密的SIFT作为图像特征

在整幅图像上用一个规则的网格应用 SIFT 描述子可以得到稠密 SIFT 的表示形式通过添加一些额外的参数来得到稠密 SIFT 特征。创建一个名为 dsift.py 的文件,并添加下面代码到该文件中:

import sift
def process_image_dsift(imagename,resultname,size=20,steps=10,
 force_orientation=False,resize=None):
  """ 用密集采样的 SIFT 描述子处理一幅图像,并将结果保存在一个文件中。可选的输入:
 特征的大小 size,位置之间的步长 steps,是否强迫计算描述子的方位 force_orientation
 (False 表示所有的方位都是朝上的),用于调整图像大小的元组 """
 im = Image.open(imagename).convert('L')
 if resize!=None:
 im = im.resize(resize)
 m,n = im.size
 if imagename[-3:] != 'pgm':
 # 创建一个 pgm 文件
 im.save('tmp.pgm')
 imagename = 'tmp.pgm'
 # 创建帧,并保存到临时文件
 scale = size/3.0
 x,y = meshgrid(range(steps,m,steps),range(steps,n,steps))
 xx,yy = x.flatten(),y.flatten()
 frame = array([xx,yy,scale*ones(xx.shape[0]),zeros(xx.shape[0])])
 savetxt('tmp.frame',frame.T,fmt='%03.3f')
 if force_orientation:
 cmmd = str("sift "+imagename+" --output="+resultname+
 " --read-frames=tmp.frame --orientations")
 else:
 cmmd = str("sift "+imagename+" --output="+resultname+
 " --read-frames=tmp.frame")
 os.system(cmmd)
 print 'processed', imagename, 'to', resultname

对比 process_image() 函数,为了使用命令行处理,我们用 savetxt() 函数
将帧数组存储在一个文本文件中,该函数的最后一个参数可以在提取描述子之前对
图像的大小进行调整,例如,传递参数 imsize=(100, 100) 会将图像调整为 100×100
像素的方形图像。最后,如果 force_orientation 为真,则提取出来的描述子会基于
局部主梯度方向进行归一化;否则,则所有的描述子的方向只是简单地朝上。

在一幅图像上应用稠密SIFT描述子的例子
可视化代码:

from PCV.localdescriptors import dsift,sift
from numpy import *
from PIL import Image
from pylab import *

dsift.process_image_dsift('shangdalou.jpg','shangdalou.sift',90,40,True)
l,d = sift.read_features_from_file('shangdalou.sift')
im = array(Image.open('shangdalou.jpg'))
sift.plot_features(im,l,True)
show()

在这里插入图片描述

8.1.3 图像分类:手势识别

用静态手势(Static Hand Posture)数据库(参http://www.idiap.ch/resource/gestures/)中的一些图像进行演示。在该数据库主页上下载数据较小的测试集 test set 16.3Mb,将下载后的所有图像放在一个名为 uniform 的文件夹里,每一类均分两组,并分别放入名为 train 和 test 的两个文件夹中。
将图像分辨率调成了常见的固定大小。这是非常重要的,否则这些图像会有不同数量的描述子,从而每幅图像的特征向量长度也不一样,这将导致在后面比较它们时出错。

计算手势图像稠密SIFT描述子,并可视化:

# -*- coding: utf-8 -*-
import os
from PCV.localdescriptors import sift, dsift
from pylab import *
from PIL import Image

imlist=['D:/uniform/train/C-uniform01.ppm', 'D:/uniform/train/B-uniform01.ppm',
        'D:/uniform/train/A-uniform01.ppm', 'D:/uniform/train/Five-uniform01.ppm',
        'D:/uniform/train/Point-uniform01.ppm', 'D:/uniform/train/V-uniform01.ppm']

figure()
for i, im in enumerate(imlist):
    print (im)
    dsift.process_image_dsift(im,im[:-3]+'dsift',50,20,True)
    l, d = sift.read_features_from_file(im[:-3]+'dsift')
    dirpath, filename=os.path.split(im)
    im = array(Image.open(im))
    #显示手势含义title
    titlename=filename[:-14]
    subplot(2,3,i+1)
    sift.plot_features(im,l,True)
    title(titlename)
show()

在这里插入图片描述

# -*- coding: utf-8 -*-
from PCV.localdescriptors import dsift
import os
from PCV.localdescriptors import sift
from pylab import *
from PCV.classifiers import knn

def get_imagelist(path):
    """ Returns a list of filenames for
 all jpg images in a directory. """

    return [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.ppm')]

def read_gesture_features_labels(path):
    # create list of all files ending in .dsift
    featlist = [os.path.join(path,f) for f in os.listdir(path) if f.endswith('.dsift')]
    # read the features
    features = []
    for featfile in featlist:
        l,d = sift.read_features_from_file(featfile)
        features.append(d.flatten())
    features = array(features)
    # create labels
    labels = [featfile.split('/')[-1][0] for featfile in featlist]
    return features,array(labels)

def print_confusion(res,labels,classnames):
    n = len(classnames)
    # confusion matrix
    class_ind = dict([(classnames[i],i) for i in range(n)])
    confuse = zeros((n,n))
    for i in range(len(test_labels)):
        confuse[class_ind[res[i]],class_ind[test_labels[i]]] += 1
    print 'Confusion matrix for'
    print classnames
    print confuse

filelist_train = get_imagelist('D:\\uniform\\train')
filelist_test = get_imagelist('D:\\uniform\\test')
imlist=filelist_train+filelist_test

# process images at fixed size (50,50)
for filename in imlist:
    featfile = filename[:-3]+'dsift'
    dsift.process_image_dsift(filename,featfile,10,5,resize=(50,50))

features,labels = read_gesture_features_labels('D:\\uniform\\train\\')
test_features,test_labels = read_gesture_features_labels('D:\\uniform\\test\\')
classnames = unique(labels)

# test kNN
k = 1
knn_classifier = knn.KnnClassifier(labels,features)
res = array([knn_classifier.classify(test_features[i],k) for i in
range(len(test_labels))])
# accuracy
acc = sum(1.0*(res==test_labels)) / len(test_labels)
print 'Accuracy:', acc

print_confusion(res,test_labels,classnames)

用训练数据及其标记作为输入,创建分类器对象;然后,我们在整个测试集
上遍历并用 classify() 方法对每幅图像进行分类。将布尔数组和 1 相乘并求和,可
以计算出分类的正确率。由于该例中真值为 1,所以很容易计算出正确分类数。它
应该会打印出一个类似下面的结果:

Accuracy: 0.811518324607

这说明该例中有 81% 的图像是正确的。该结果会随 k 值及稠密 SIFT 图像描述子参
数的选择而变化。
而控制台输出结果不理想,有待解决。
在这里插入图片描述

8.2 贝叶斯分类器

朴素贝叶斯分类器是分类算法集合中基于贝叶斯理论的一种算法。它不是单一存在的,而是一个算法家族,在这个算法家族中它们都有共同的规则。例如每个被分类的特征对与其他的特征对都是相互独立的。

开始之前,先看一下数据集。

这是一个虚构的数据集,这个数据集描述的是天气是否适合打高尔夫球。已知天气情况,每个元组都分成合适(“Yes”)或者不合适(“NO”)打高尔夫。

下面的截图就是表示的数据集表格:
在这里插入图片描述
数据集分为两个部分,也就是说,特征矩阵(feature matrix)响应向量(response vector)
1.特征矩阵包含数据集中所有的向量(行),每个向量是由依赖特征组成的。在上面的数据集中,特征就是“天气”,“温度”,“湿度”还有“刮风”。
2.响应向量包含的是特征矩阵每一行的类变量(预测或者输出)值。在上述的数据集中,类变量名为“Play golf”。
假设
朴素贝叶斯基础假设是,对于每一个特征都有:独立,相等
与我们的数据集关联起来,我们可以这样理解这个概念:
1,我们假设没有特征对是相互依赖的。温度热不热跟湿度没有任何关系,天气是否下雨也不影响是否刮风。因此,这就是假设特征相互独立。
2,其次,每个特征都有相同的权重(或者是重要性)。例如,只知道温度和湿度是不能准确地推断出结果的。任何属性都与结果是有关系的,并且影响程度是相同的。

注意:如果在现实情况中,这个假设就使得朴素贝叶斯不能一般性地正确了。实际上独立这个假设就根本不可能成立,但是又往往在实践中能够很方便地计算。
贝叶斯理论
贝叶斯理论指的是,根据一个已发生事件的概率,计算另一个事件的发生概率。贝叶斯理论从数学上的表示可以写成这样:
在这里插入图片描述
在这里A和B都是事件,概率非0。
1,基本上,只要我们给出了事件B为真,那么就能算出事件A发生的概率,事件B也被称为证据。
2,P(A)是事件A的先验(先验概率,例如,在证据之前发生的概率)。证据是一个未知事件的一个属性值(在这里就是事件B)。
3,P(A|B)是B的后验概率,例如在证据之后发生的概率。
现在再考虑一下我们的数据集,我们可以这样用贝叶斯理论:
在这里插入图片描述
在这里y是类变量,X是依赖特征向量(大小为n):
在这里插入图片描述
为了更加清晰点,我们这个例子的特征向量和相关类变量是(数据集的第一行):

X = (Rainy, Hot, High, False)
y = No

朴素假设
现在是时候为贝叶斯理论添加假设了,也就是每个特征之间都是相互独立的。所以我们可以将证据分成每个独立的部分。

如何两个事件A和B是相互独立的,那么有:
在这里插入图片描述
因此我们可以得到以下结果:
在这里插入图片描述
于是又可以写成:
在这里插入图片描述
因为分母与输入数据是常量相关的,所以我们可以除去这一项:
在这里插入图片描述
现在我们需要建立一个分类模型,我们用已知的类变量y的所有可能的值计算概率,并选择输出概率是最大的结果。数学表达式可以这么写:
在这里插入图片描述
所以最后剩下的只有P(y)P(y)与P(xi|y)P(xi|y)的计算了。
请注意:P(y)也被称为类概率,P(xi|y)也被称为条件概率。
不同的朴素贝叶斯分类器差异主要在P(xi|y)分布的假设。
我们试着将上面的式子用在天气数据集上。这样,我们先对数据集做一些预处理。

我们得求出每一个X中的xi,y中的yi。所有这些计算都被列在了下面的表格中:
在这里插入图片描述
直到现在我们已经完成了预处理工作,分类器也准备好了。
所以玩高尔夫的概率是:
在这里插入图片描述
不打高尔夫的概率是:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上述讨论的方法只能应用在离散数据上。如果是连续数据的话,我们需要对每个特征数据的分布做一些假设。
该分类器是通过将各个特征的条件概率相乘得到一个类的总概率,然后选取概率最
高的那个类构造出来的。使用高斯概率分布模型的贝叶斯分类器基本实现,也就是用从
训练数据集计算得到的特征均值和方差来对每个特征单独建模。把下面的 Bayes
Classifier 类添加到文件 bayes.py 中:

class BayesClassifier(object):
 def __init__(self):
 """ 使用训练数据初始化分类器 """
 self.labels = [] # 类标签
 self.mean = [] # 类均值
 self.var = [] # 类方差
 self.n = 0 # 类别数
 def train(self,data,labels=None):
 """ 在数据 data(n×dim 的数组列表)上训练,标记 labels 是可选的,默认为 0…n-1 """
 if labels==None:
 labels = range(len(data))
 self.labels = labels
 self.n = len(labels)
 for c in data:
 self.mean.append(mean(c,axis=0))
 self.var.append(var(c,axis=0))
 def classify(self,points):
 """ 通过计算得出的每一类的概率对数据点进行分类,并返回最可能的标记 """
 # 计算每一类的概率
 est_prob = array([gauss(m,v,points) for m,v in zip(self.mean,self.var)])
 # 获取具有最高概率的索引,该索引会给出类标签
 ndx = est_prob.argmax(axis=0)
 est_labels = array([self.labels[n] for n in ndx])
 return est_labels, est_prob

该模型每一类都有两个变量,即类均值和协方差。train() 方法获取特征数组列表
(每个类对应一个特征数组),并计算每个特征数组的均值和协方差。classify() 方
法计算数据点构成的数组的类概率,并选概率最高的那个类,最终返回预测的类标
记及概率值,同时需要一个高斯辅助函数:

def gauss(m,v,x):
 """ 用独立均值 m 和方差 v 评估 d 维高斯分布 """
 if len(x.shape)==1:
 n,d = 1,x.shape[0]
 else:
 n,d = x.shape
 # 协方差矩阵,减去均值
 S = diag(1/v)
 x = x-m
 # 概率的乘积
 y = exp(-0.5*diag(dot(x,dot(S,x.T))))
 # 归一化并返回
 return y * (2*pi)**(-d/2.0) / ( sqrt(prod(v)) + 1e-6)

该函数用来计算单个高斯分布的乘积,返回给定一组模型参数 m 和 v 的概率

在脚本中载入二维数据,并训练出一个分类器

import pickle
from PCV.classifiers import bayes
from PCV.tools import imtools
from pylab import *

with open('points_normal_t.pkl', 'r') as f:
 class_1 = pickle.load(f)
 class_2 = pickle.load(f)
 labels = pickle.load(f)

bc = bayes.BayesClassifier()
bc.train([class_1,class_2],[1,-1])

with open('points_normal_test.pkl', 'r') as f:
 class_1 = pickle.load(f)
 class_2 = pickle.load(f)
 labels = pickle.load(f)

 print bc.classify(class_1[:10])[0]


def classify(x, y, bc=bc):
     points = vstack((x, y))
     return bc.classify(points.T)[0]


imtools.plot_2D_boundary([-6, 6, -6, 6], [class_1, class_2], classify, [1, -1])
show()

在这里插入图片描述
该脚本会将前 10 个二维数据点的分类结果打印输出到控制台,输出结果如下:
在这里插入图片描述
用贝叶斯分类器对二维数据进行分类。每个例子中的颜色代表了类标记。正确分类
的点用星号表示,误错分类的点用圆点表示,曲线是分类器的决策边界

8.3 支持向量机

支持向量机(support vector machines)是一种二分类模型,它的目的是寻找一个超平面来对样本进行分割,分割的原则是间隔最大化,最终转化为一个凸二次规划问题来求解。
给定训练样本集D=(x1,y1),(x2,y2),⋯,(xm,ym)D=(x1,y1),(x2,y2),⋯,(xm,ym),其中yi∈{−1,+1}yi∈{−1,+1},分类学习最基本的想法就是基于训练集D在样本空间中找到一个划分超平面,将不同类别的样本分开。
在这里插入图片描述
直观看上去,能将训练样本分开的划分超平面有很多,但应该去找位于两类训练样本“正中间”的划分超平面,即图中
红色的那条
,因为该划分超平面对训练样本局部扰动的“容忍”性最好,例如,由于训练集的局限性或者噪声的因素,训练集外的样本可能比图中的训练样本更接近两个类的分隔界,这将使许多划分超平面出现错误。而红色超平面的影响最小,简言之,这个划分超平面所产生的结果是鲁棒性的。

​ 那什么是线性可分呢?

如果一个线性函数能够将样本分开,称这些数据样本是线性可分的。那么什么是线性函数呢?其实很简单,在二维空间中就是一条直线,在三维空间中就是一个平面,以此类推,如果不考虑空间维数,这样的线性函数统称为超平面。我们看一个简单的二维空间的例子,O代表正类,X代表负类,样本是线性可分的,但是很显然不只有这一条直线可以将样本分开,而是有无数条,我们所说的线性可分支持向量机就对应着能将数据正确划分并且间隔最大的直线。
在这里插入图片描述

为什么要间隔最大?

一般来说,一个点距离分离超平面的远近可以表示分类预测的确信度,如图中的A B两个样本点,B点被预测为正类的确信度要大于A点,所以SVM的目标是寻找一个超平面,使得离超平面较近的异类点之间能有更大的间隔,即不必考虑所有样本点,只需让求得的超平面使得离它近的点间隔最大。

怎么计算间隔?

只有计算出了间隔,才能使得间隔最大化。在样本空间中,划分超平面可通过如下线性方程来描述:
在这里插入图片描述
其中w为法向量,决定了超平面的方向,b为位移量,决定了超平面与原点的距离。
假设超平面能将训练样本正确地分类,即对于训练样本(xi,yi),满足以下公式:
在这里插入图片描述
该公式被称为最大间隔假设,yi=+1 表示样本为正样本,yi=−1 表示样本为负样本,式子前面选择大于等于+1,小于等于-1只是为了计算方便,原则上可以是任意常数,但无论是多少,都可以通过对 w 的变换使其为 +1 和 -1 。实际上该公式等价于:
在这里插入图片描述
如下图,距离超平面最近的这几个样本点满足yi(WTxi+b)=1,它们被称为“支持向量”。虚线称为边界,两条虚线间的距离称为间隔(margin)。
在这里插入图片描述
​ 关于间隔的计算:它就等于两个异类支持向量的差在 W方向上的投影 ,W方向是指图所示实线的法线方向。
在这里插入图片描述
所以有:
在这里插入图片描述
得到:
在这里插入图片描述
至此,我们求得了间隔,SVM的思想是使得间隔最大化,也就是:
在这里插入图片描述
显然,最大化 2/||w|| 相当于最小化 ||w||,为了计算方便,将公式转化成如下公式,它即为支持向量机的基本型:
在这里插入图片描述
该基本型是一个凸二次规划问题,可以采用拉格朗日乘子法对其对偶问题求解求解,拉格朗日函数:
在这里插入图片描述
对W,b求导可得
在这里插入图片描述
令其分别为0,可得:
在这里插入图片描述
将其带入拉格朗日函数(8)中,可得:
在这里插入图片描述
该过程的KTT条件为;

在这里插入图片描述
这里显示出了支持向量机的重要特征:当训练完成后,大部分样本都不需要保留,最终模型只与支持向量有关。

对于非线性问题,线性可分支持向量机并不能有效解决,要使用非线性模型才能很好地分类。先看一个例子,如下图,很显然使用直线并不能将两类样本分开,但是可以使用一条椭圆曲线(非线性模型)将它们分开。非线性问题往往不好求解,所以希望能用解线性分类问题的方法求解,因此可以采用非线性变换,将非线性问题变换成线性问题。

对于这样的问题,可以将训练样本从原始空间映射到一个更高维的空间,使得样本在这个空间中线性可分,如果原始空间维数是有限的,即属性是有限的,那么一定存在一个高维特征空间使样本可分。令ϕ(x)表示将 x 映射后的特征向量,于是在特征空间中,划分超平面所对应的的模型可表示为:
在这里插入图片描述
于是有最小化函数:
在这里插入图片描述
求解后得到:
在这里插入图片描述
(数学推导过程摘自https://blog.csdn.net/qq_35992440/article/details/80987664)
这里的函数 κ(xi,xj) 就是核函数,在实际应用中,通常人们会从一些常用的核函数里选择(根据样本数据的不同,选择不同的参数,实际上就得到了不同的核函数)
在这里插入图片描述

使用LibSVM

https://www.lfd.uci.edu/~gohlke/pythonlibs/#libsvm
从链接处下载libsvm-3.23-cp27-cp27m-win_amd64.whl
命令行 安装pip install libsvm-3.23-cp27-cp27m-win_amd64.whl
安装成功 即可使用
LibSVM[7] 是最好的、使用最广泛的 SVM 实现工具包。LibSVM 为 Python 提供了
一个良好的接口(也为其他编程语言提供了接口)。
下面的脚本会载入在前面kNN 范例分类中用到的数据点,并用径向基函数训练一个 SVM 分类器:

import pickle
from svmutil import *
import imtools
# 用 Pickle 载入二维样本点
with open('points_normal.pkl', 'r') as f:
 class_1 = pickle.load(f)
 class_2 = pickle.load(f)
 labels = pickle.load(f)
# 转换成列表,便于使用 libSVM
class_1 = map(list,class_1)
class_2 = map(list,class_2)
labels = list(labels)
samples = class_1+class_2 # 连接两个列表
# 创建 SVM 
prob = svm_problem(labels,samples)
param = svm_parameter('-t 2')
# 在数据上训练 SVM
m = svm_train(prob,param)
# 在训练数据上分类效果如何?
res = svm_predict(labels,samples,m)

用与前面一样的方法载入数据集,但是这次需要把数组转成列表,因为LibSVM 不支持数组对象作为输入。这里,我们用 Python 的内建函数 map() 进行转换,map() 函数中用到了对角一个元素都会进行转换的 list() 函数。紧接着我们创建了一个 svm_problem 对象,并为其设置了一些参数。调用 svm_train() 求解该优化问题用以确定模型参数,然后就可以用该模型进行预测了。最后一行调用 svm_predict(),用求得的模型 m 对训练数据分类,并显示出在训练数据中分类的正确率,打印输出结果如下:
在这里插入图片描述
结果表明该分类器完全分开了训练数据,400 个数据点全部分类正确。
现在,载入其他数据集,并对该分类器进行测试:

# 用 Pickle 模块载入测试数据
with open('points_normal_test.pkl', 'r') as f:
 class_1 = pickle.load(f)
 class_2 = pickle.load(f)
 labels = pickle.load(f)
# 转换成列表,便于使用 LibSVM
class_1 = map(list,class_1)
class_2 = map(list,class_2)
# 定义绘图函数
def predict(x,y,model=m):
 return array(svm_predict([0]*len(x),zip(x,y),model)[0])
 # 绘制分类边界
imtools.plot_2D_boundary([-6,6,-6,6],[array(class_1),array(class_2)],predict,[-1,1])
show()

我们需要再次为 LibSVM 将数据转成列表,同时和之前一样,我们定义了一个辅
助函数 predict() 来绘制分类的边界。注意,如果无法获取真实的标记,我们用
([0]*len(x)) 列表来代替标记列表。只要代替的标记列表长度正确,你可以使用任意
的列表。图显示了两个不同数据集在二维平面上的分布情况。
在这里插入图片描述

发布了32 篇原创文章 · 获赞 6 · 访问量 1万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览