(4-3)dlib机器学习和图像处理:SVM分类算法

本文介绍了支持向量机(SVM)分类算法,包括二进制分类器的实现,RankingSVM用于排序问题的解决方案,以及StructSVM多分类器的应用,展示了dlib库在机器学习中的实践。
摘要由CSDN通过智能技术生成

4.3  SVM分类算法

SVM是支持向量机(Support Vector Machine)的缩写,是一类按监督学习(Supervised Learning)方式对数据进行二元分类的广义线性分类器(generalized linear classifier),其决策边界是对学习样本求解的最大边距超平面(maximum-margin hyperplane)。SVM使用铰链损失函数(hinge loss)计算经验风险,并在求解系统中加入了正则化项以优化结构风险,是一个具有稀疏性和稳健性的分类器。SVM可以通过核方法(kernel method)进行非线性分类,是常见的核学习(kernel learning)方法之一。

4.3.1  二进制SVM分类器

请看下面的实例文件svm_binary_classifier.py,使用dlib的内置库实现二进制SVM分类器。在本实例中创建了一个简单的测试数据集,展示了实现简易SVM分类器的方法。文件svm_binary_classifier.py的具体实现流程如下:

实例4-14使用dlib的内置库实现二进制SVM分类器

源码路径:daima\4\svm_binary_classifier.py

(1)导入需要的库,创建两个训练数据集。在现实应用中,通常会使用更大的训练数据集,但是就本实例来说,两个训练例子就已经足够了。对于二进制分类器而言,y标签应该都是+1或-1。代码如下:

import dlib
try:
    import cPickle as pickle
except ImportError:
    import pickle

x = dlib.vectors()
y = dlib.array()

# x.append(dlib.vector([1, 2, 3, -1, -2, -3]))
y.append(+1)

x.append(dlib.vector([-1, -2, -3, 1, 2, 3]))
y.append(-1)

(2)制作一个训练对象,此对象负责将训练数据集转换为预测模型。本实例是一个使用线性核的支持向量机训练器,如果要使用RBF内核或直方图相交内核,可以将其更改为以下之一的代码行:

svm = dlib.svm_c_trainer_histogram_intersection()
svm = dlib.svm_c_trainer_radial_basis()

代码如下:

svm = dlib.svm_c_trainer_linear()
svm.be_verbose()
svm.set_c(10)

(3)开始训练模型,返回值是能够进行预测的训练模型,然后用我们的数据运行模型并查看结果。代码如下:

classifier = svm.train(x, y)

#查看结果。
print("prediction for first sample:  {}".format(classifier(x[0])))
print("prediction for second sample: {}".format(classifier(x[1])))

(4)可以像任何其他Python对象一样,也可以使用Python内置库pickle序列化分类器模型对象。代码如下:

with open('saved_model.pickle', 'wb') as handle:
    pickle.dump(classifier, handle, 2)

执行后会输出:

objective:     0.0178571
objective gap: 0
risk:          0
risk gap:      0
num planes:    3
iter:          1

prediction for first sample:  1.0
prediction for second sample: -1.0

4.3.2  Ranking SVM算法

Learning to Rank(简称LTR)用机器学习的思想来解决排序问题,LTR有三种主要的方法:PointWise,PairWise和ListWise。Ranking SVM算法是PairWise方法的一种,由R. Herbrich等人在2000年提出, T. Joachims介绍了一种基于用户Clickthrough数据使用Ranking SVM来进行排序的方法(SIGKDD, 2002)。

在dlib的C++库中内置了SVM-Rank工具,这是一个学习排列对象的有用工具。例如,可以SVM-Rank学习根据用户的搜索引擎结果对网页进行排名。思想是将最相关的页面排名高于不相关的页面。

在下面的实例文件svm_rank.py中,我们将创建一个简单的测试数据集,并使用机器学习方法来学习一个函数。函数的目的是给“相关”对象比“非相关”对象更高的分数。其思想是使用此分数对对象进行排序,以便最相关的对象位于排名列表的顶部。文件svm_rank.py的具体实现流程如下所示

实例4-15使用Ranking SVM算法解决排序问题

源码路径:daima\4\svm_rank.py

(1)首先准备测试数据。为了简单起见,假设我们需要对二维向量进行排序,并且在第一维中具有正值的向量的排序应该高于其他向量。因此我们要做的是制作相关(即高排名)和非相关(即低排名)向量的示例,并将它们存储到排名对对象中。代码如下:

data = dlib.ranking_pair()
# 添加两个示例。在实际应用中,可能需要大量相关和非相关向量的示例。
data.relevant.append(dlib.vector([1, 0]))
data.nonrelevant.append(dlib.vector([0, 1]))

(2)现在我们有了一些数据,接下来可以使用机器学习方法来学习一个函数,该函数的功能是给相关向量高分,给非相关向量低分。代码如下:

trainer = dlib.svm_rank_trainer()
# trainer对象具有一些控制其行为的参数。
#由于这是SVM-Rank算法,所以需要使用参数c来控制在尝试精确拟合训练数据或选择一个“更简单”的解决方案之间的权衡。
trainer.c = 10

(3)使用函数train()开始训练上面的数据,如果在向量上调用rank将输出一个排名分数,相关向量的排名得分应当大于非相关向量的得分。代码如下:

rank = trainer.train(data)

print("相关向量排名得分:     {}".format(
    rank(data.relevant[0])))
print("非相关向量的排名得分: {}".format(
    rank(data.nonrelevant[0])))

(4)如果想要一个排名精度的整体度量,可以通过调用函数test_ranking_function()来计算排序精度和平均精度值。在这种情况下,排序精度告诉我们非相关向量排在相关向量前面的频率。在本实例中,函数test_ranking_function()为这两个度量返回1,表示使用rank函数输出一个完美的排名。代码如下:

print(dlib.test_ranking_function(rank, data))

#排名得分是通过获取学习的权重向量和数据向量之间的点积来计算的。如果想查看学习的权重向量,可以这样显示:

print("Weights: {}".format(rank.weights))#在这种情况下的权重

(5)在上面的示例中,我们的数据只包含两组对象:相关集和非相关集。训练正试图找到一个排序函数,该函数使每个相关向量的得分高于每个非相关向量的得分。但是在现实应用中,有时候想要做的事情远比这要复杂一些。例如,在某用户浏览Web页面的排名应用中,我们必须根据用户的查询的页面进行排名。在这种情况下,每个查询都有自己的一组相关和非相关文档。与一个查询相关的内容很可能与另一个查询无关。所以在这种情况下,我们没有一组全局相关网页和另一组非相关网页的数据。要处理这样的情况,我们可以简单地给训练提供多个ranking_u_pair排序对实例。因此,每个排序对表示特定查询的“相关/非相关”集。例如下面的排序对实例,为了简单起见,我们重用了上面的数据来进行4个相同的“查询”。

queries = dlib.ranking_pairs()
queries.append(data)
queries.append(data)
queries.append(data)
queries.append(data)

#像以前一样训练
rank = trainer.train(queries)

(6)现在我们有了多个ranking_u_pair排序对实例,可以使用函数cross_validate_ranking_trainer()将查询拆分为多个折叠来执行交叉验证。也就是说,它可以让训练对一个子集进行训练,并对其他实例进行测试。本实例将通过4个不同的子集来实现这一点,并根据保留的数据返回总体排名精度。代码如下:

# 与test_ranking_function()一样,同时报告排序精度和平均精度。
print("Cross交叉验证结果: {}".format(
    dlib.cross_validate_ranking_trainer(trainer, queries, 4)))

(6)最后需要注意,除了在上面使用过的密集向量之外,排名工具还支持使用稀疏向量。因此通过下面的代码,可以跟上面示例程序的第一部分那样使用稀疏向量。

data = dlib.sparse_ranking_pair()
samp = dlib.sparse_vector()

(7)使samp表示与dlib.vector([1,0])相同的向量,代码如下:

samp.append(dlib.pair(0, 1))
data.relevant.append(samp)

在dlib中,稀疏向量只是由成对对象组成的数组,每对存储一个索引和一个值。此外,支持向量机排序工具需要对稀疏向量进行排序,并具有唯一的索引。这意味着索引是按递增顺序展示的,索引值不会出现一次以上。在日常应用中,可以使用dlib.make_sparse_vector()使稀疏向量对象正确排序并包含唯一的索引。

(8)我们可以让samp表示与dlib.vector相同的向量([0,1]),最后使用函数train()开始训练上面的数据。代码如下:

samp.clear()
samp.append(dlib.pair(1, 1))
data.nonrelevant.append(samp)

trainer = dlib.svm_rank_trainer_sparse()
rank = trainer.train(data)
print("相关向量的排名分数:     {}".format(
    rank(data.relevant[0])))
print("非相关向量的排名分数: {}".format(
    rank(data.nonrelevant[0])))

执行后会输出

相关向量排名得分:     0.5
非相关向量的排名得分: -0.5
ranking_accuracy: 1  mean_ap: 1
Weights: 0.5
-0.5
Cross交叉验证结果: ranking_accuracy: 1  mean_ap: 1
相关向量的排名分数:     0.5
非相关向量的排名分数: -0.5

4.3.3  Struct SVM多分类器

通过使用dlib内置的SVM-Rank工具,可以实现SVM Struct多分类请看下面的实例文件svm_struct.py,演示了使用Struct SVM多分类器的知识。本例使用dlib的Struct SVM学习一个简单的多类分类器的参数。首先实现多类分类器模型,然后利用结构支持向量机工具进行遍历,找到该分类模型的参数。实例文件svm_struct.py的具体实现代码如下所示

实例4-16使用dlib的Struct SVM学习一个简单的多类分类器的参数

源码路径:daima\4\svm_struct.py

(1) 在本例中创建三种类型的示例:类0、1或2。也就是说,我们的每个样本向量分为三类。为了使这个例子非常简单,除了一个地方外,每个样本向量在任何地方都是零。每个向量的非零维数决定了向量的类别。例如,samples的第一个元素的类为1,因为samples[0][1]是samples[0]中唯一的非零元素。代码如下:

def main():
    samples = [[0, 2, 0], [1, 0, 0], [0, 4, 0], [0, 0, 3]]
    # 由于我们想使用机器学习方法来学习3类分类器,所以我们需要记录样本的标签。这里的samples[i]有一个标签labels[i]的类标签。
    labels = [1, 0, 1, 2]
    problem = ThreeClassClassifierProblem(samples, labels)
    weights = dlib.solve_structural_svm_problem(problem)

(2)打印权重信息,然后对每个训练样本计算predict_label(),请注意,每个样本都预测了正确的标签。代码如下:

    print(weights)
    for k, s in enumerate(samples):
        print("Predicted label for sample[{0}]: {1}".format(
            k, predict_label(weights, s)))

(3)创建函数predict_label(weights, sample),设置3类分类器的9维权向量,预测指定3维样本向量的类。因此,此函数的输出为0、1或2(即三个可能的标签之一)。我们可以将三类分类器模型看作是包含3个独立的线性分类器。因此,为了预测样本向量的类别,需要评估这三个分类器中的每一个。下面的代码只是从权重中提取三个独立的权重向量,然后根据样本对每个向量求值。单个分类器的得分存储在得分和最高得分中索引作为标签返回。代码如下:

def predict_label(weights, sample):
    w0 = weights[0:3]
    w1 = weights[3:6]
    w2 = weights[6:9]
    scores = [dot(w0, sample), dot(w1, sample), dot(w2, sample)]
    max_scoring_label = scores.index(max(scores))
    return max_scoring_label

(4)编写函数dot(a, b),计算两个向量a和b之间的点积。代码如下:

def dot(a, b):
    return sum(i * j for i, j in zip(a, b))

(5)定义类ThreeClassClassifierProblem,使用dlib.solve_structural_svm_problem()告诉Struct SVM多分类如何处理我们的问题。Struct SVM支持向量机是一种有监督的机器学习方法,用于学习预测复杂的输出。这与只做简单的“是/否”预测的二元分类器形成了对比。另一方面,是/否向量机可以学习预测复杂的输出,例如整个解析树或DNA序列比对。为此,它学习一个函数F(x,y),该函数测量特定数据样本x与标签y的匹配程度,其中标签可能是一个复杂的东西,比如解析树。然而,为了使本实例程序尽量简单,我们只使用了3个类别的标签输出。在测试时,新x的最佳标签由最大化F(x,y)的y给出,把它放到当前示例的上下文中,F(x,y)能够计算给定样本和类标签的分数。因此,预测的类标签是使F(x,y)最大的y的任何值。这正是predict_u label()所做的。也就是说,它计算F(x,0)、F(x,1)和F(x,2),然后报告哪个标签的值最大。代码如下:

class ThreeClassClassifierProblem:

    def __init__(self, samples, labels):
        # dlib.solve_structural_svm_problem() 要求类具有num_samples和num_dimensions字段。这些字段应分别包含训练样本数和PSI特征向量的维数
        self.num_samples = len(samples)
        self.num_dimensions = len(samples[0])*3

        self.samples = samples
        self.labels = labels

(6)编写函数make_psi(self, x, label)计算PSI(x,label),在这里所做的就是取x。在本实例程序中是一个三维样本向量,把它放到一个9维PSI向量的3个位置之一,然后返回。所以这个函数make_psi()返回PSI(x,label)。要了解为什么要这样设置PSI,请回想一下predict_u label()的工作原理,它接受一个9维的权重向量,并将向量分成3部分。然后每个片段定义一个不同的分类器,我们用一对一的方式使用它们来预测标签。所以现在在Struct SVM向量机代码中,我们必须定义PSI向量来对应这个用法。也就是说,我们需要告诉结构支持向量机解算器我们要解决什么样的问题。代码如下:

    def make_psi(self, x, label):
    
        psi = dlib.vector()
        # 设置有9个维度,向量元素初始化为0。
        psi.resize(self.num_dimensions)
        dims = len(x)
        if label == 0:
            for i in range(0, dims):
                psi[i] = x[i]
        elif label == 1:
            for i in range(dims, 2 * dims):
                psi[i] = x[i - dims]
        else:  # 标签必须为2
            for i in range(2 * dims, 3 * dims):
                psi[i] = x[i - 2 * dims]
        return psi

(7)编写dlib直接调用的两个成员函数get_truth_joint_feature_vector(self, idx)separation_oracle(self, idx, current_solution),在get_truth_joint_feature_vector()中,当第idx个训练样本具有真实标签时,只需返回该样本的PSI()向量,所以这里它会返PSI(self.samples[idx],self.labels[idx])。代码如下:

    def get_truth_joint_feature_vector(self, idx):
        return self.make_psi(self.samples[idx], self.labels[idx])

    def separation_oracle(self, idx, current_solution):
        samp = self.samples[idx]
        dims = len(samp)
        scores = [0, 0, 0]
        # 计算三个分类器的得分
        scores[0] = dot(current_solution[0:dims], samp)
        scores[1] = dot(current_solution[dims:2*dims], samp)
        scores[2] = dot(current_solution[2*dims:3*dims], samp)

        if self.labels[idx] != 0:
            scores[0] += 1
        if self.labels[idx] != 1:
            scores[1] += 1
        if self.labels[idx] != 2:
            scores[2] += 1

        max_scoring_label = scores.index(max(scores))
        if max_scoring_label == self.labels[idx]:
            loss = 0
        else:
            loss = 1

        # 最后返回刚刚找到的标签对应的损失和PSI向量。
        psi = self.make_psi(samp, max_scoring_label)
        return loss, psi


if __name__ == "__main__":
    main()

执行后会输出:

0.25
-0.166665
-0.111114
-0.125011
0.333332
-0.111111
-0.124989
-0.166667
0.222225
Predicted label for sample[0]: 1
Predicted label for sample[1]: 0
Predicted label for sample[2]: 1
Predicted label for sample[3]: 2

未完待续

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值