【支持向量机SVM系列教程4】SVM应用实战

4 实例:使用SVM完成人脸分类任务

SVM的功能十分强大,除了能完成线性分类任务,还能完成非线性分类任务; 除了能完成二分类任务,还能完成多分类任务。下面将使用SVM完成一个较为复杂的多分类任务:LFW数据集人脸分类任务。 该实战中一步步演示了使用SVM做人脸分类任务的流程,并且涉及到了sklearn中一些模型评估的方法。

4.1 数据集的下载

这里用的是Wild数据集。由于网络问题,直接在程序中加载数据集的速度很慢,所以要先将数据集下载到本地。

下载链接为:http://vis-www.cs.umass.edu/lfw/lfw-funneled.tgz
下载完之后解压,得到 名称为 lfw_funneled 的目录。
使用下列语句统计下该目录中的子目录个数:

ls -lR | grep "^d" | wc -l  

可得到子目录的个数为11507,每一个目录里面是同一个人的人脸图片。
总的人脸图片数为26485,说明平均每个人所占的人脸图片数很少,但其中还是有一些人物的人脸图片很多,总体数据集的分布是不均匀的。

下面将分步实现SVM人脸分类任务。

4.2 数据集的进一步提取与查看

这里选择使用Wild数据集中带标记的人脸图像作为要处理的数据集 。
sklearn中内置了接口函数fetch_lfw_people ,用于获取Wild数据集并进行进一步的提取。
代码如下:

from sklearn.datasets import fetch_lfw_people
# 读取本地数据集,输入数据集目录的绝对路径(也可以直接输入数据集的下载链接,但速度很慢)
faces = fetch_lfw_people(data_home='C:/Users/Administrator/MLDP/sklearn-examples/SVM/lfw_funneled',
                         # 只选择人脸图片数超过60张的人物对应的子目录
                         min_faces_per_person=60)

# 打印出满足条件的人物名称以及数据集的尺寸(图片数×图片高度×图片宽度)
print(faces.target_names)
print(faces.images.shape)

输出结果:

[‘Ariel Sharon’ ‘Colin Powell’ ‘Donald Rumsfeld’ ‘George W Bush’
‘Gerhard Schroeder’ ‘Hugo Chavez’ ‘Junichiro Koizumi’ ‘Tony Blair’]
(1348, 62, 47)

打印并查看该数据集的数据信息:

n_samples, h, w = faces.images.shape

# 读取数据集数据
X = faces.data
# 特征数量(每张图片的像素)
n_features = X.shape[1]

# 读取数据集标签
y = faces.target
# 标签名称
target_names = faces.target_names
n_classes = target_names.shape[0]

print("Different parameters of the dataset are as Followed:")
print("样本数: %d" % n_samples)
print("特征数(每张图片的像素): %d" % n_features)
print("类别数: %d" % n_classes)

输出结果:

Different parameters of the dataset are as Followed:
样本数: 1348
特征数(每张图片的像素): 2914
类别数: 8

取出数据集中的前12张人脸图片,看看长什么样:

import matplotlib.pyplot as plt
plt.figure(figsize=(6, 4))
fig, ax = plt.subplots(3, 4)
for i, axi in enumerate(ax.flat):
    axi.imshow(faces.images[i], cmap='bone')
    axi.set(xticks=[], yticks=[], xlabel=faces.target_names[faces.target[i]])

输出结果:

4.3 PCA降维

可以看到,每张图片的尺寸接近3000个像素,人脸图像均很清晰,非常容易分辨。但是,如果全部像素均拿来作特征,会使得计算量过大,速度太慢。所以,这里选择使用 主成分分析(PCA) 来提取出每张图片中的若干个像素,再送给SVM分类器。这个“若干”具体是多少呢?下面将进行分析。

from sklearn.decomposition import PCA
import numpy as np

pca = PCA()
pca.fit(X)

# 0.95 是解释方差比,表示提取后的图片特征的方差占的总方差达到 95% 时的像素数量
# cumsum >= 0.95是一系列True和False的数组,np.argmax可以找出第一个最大(即True)的元素的下标
cumsum = np.cumsum(pca.explained_variance_ratio_)
d = np.argmax(cumsum >= 0.95) + 1
print("Reduce the dimension of each picture to %d takes up 0.95 of the whole variance." % d)

输出结果:

Reduce the dimension of each picture to 160 takes up 0.95 of the whole variance.

可以看到,将图片的尺寸从接近3000像素降维到160像素之后,就可以达到95%的解释方差比,所以初步考虑将维数降低到160。但是,这个95%是一拍脑袋瞎定的,为了能更直观、更准确地找到合适的降维数,还需要进行可视化操作,用于辅助寻找更加合适的降维数。
下面便绘制出了解释方差比随降维后的维度数变化的曲线图:

plt.figure(figsize=(6, 4))
plt.plot(cumsum, 'r',linewidth=3)
plt.axis([0, 400, 0, 1])
plt.xlabel("Dimensions")
plt.ylabel("Explained Variance Ratio")
# 画出解释方差比为 0.95 时所对应的点
plt.plot([d, d], [0, 0.95], "k:")
plt.plot([0, d], [0.95, 0.95], "k:")
plt.plot(d, 0.95, "ko")
# 画出肘部(曲线走势开始变缓的转折点)
plt.annotate("Elbow", xy=(50, 0.85), xytext=(70, 0.7), arrowprops=dict(arrowstyle="->"), fontsize=14)
plt.grid(True)
plt.show()

输出结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0MQESEGg-1632992268888)(431.png)]

从图中可以看到,160之后,解释方差比随维度变化的趋势已经变得很慢,维度提升的速度远远快于方差比上升的速度。所以折衷考虑,将维度降低到160是明智的选择。但是选择降到200维,方差比也有些许提升。这里为了更加准确,选择降到200维。
确定了降维后的维数之后,接下来就可以定义模型了。

4.4 模型的定义及拟合

下面将降维和SVM分类器通过管道封装到一起。代码如下:

from sklearn.svm import SVC
from sklearn.pipeline import make_pipeline
# 使用PCA方法将每张人脸图片提取成200维的特征
pca = PCA(n_components=200, whiten=True, random_state=42)
# 定义高斯核SVM
svc = SVC(kernel='rbf', class_weight='balanced')
model = make_pipeline(pca, svc)

这里有一个细节:在定义svc的时候,定义了参数 class_weight =‘balanced’。官方文档中对该参数给出了下列解释:

class_weight:dict or‘balanced’, default=None Set the parameter C of class i to class_weight[i]*C for SVC.

  • If not given, all classes are supposed to have weight one.
  • The “balanced” mode uses the values of y to automatically adjust weights inversely proportional to class frequencies in the input data as n_samples / (n_classes * np.bincount(y)) `

简单翻译一下就是说:该参数是配合参数 C 一起使用的。

  • class_weight不指定时,默认为None,对于不同类,class_weight值均为1,C值设置为class_weight×C,即C本身;

  • class_weight指定为’balanced’时,对于不同类,class_weight值为:
    c l a s s _ w e i g h t = n _ s a m p l e s n _ c l a s s e s × n p . b i n c o u n t ( y ) class\_weight=\frac{n\_samples} {n\_classes × np.bincount(y)} class_weight=n_classes×np.bincount(y)n_samples
    即:
    各类的 c l a s s _ w e i g h t 值 = 数据集总样本数 / 类别总数 × n p . b i n c o u n t ( 数据集各样本的标签 ) 各类的class\_weight值={数据集总样本数} / {类别总数×np.bincount(数据集各样本的标签)} 各类的class_weight=数据集总样本数/类别总数×np.bincount(数据集各样本的标签)
    对于第 i i i 类, C C C 值设置为:当前类的 c l a s s _ w e i g h t class\_weight class_weight c l a s s _ w e i g h t [ i ] × C class\_weight[i] × C class_weight[i]×C

看一下下面代码的直观演示:

print("n_samples=", y.shape[0])
print("n_classes=", 8)
class_weights = 1348 / (8 * np.bincount(y))
print("class_weight=", class_weights)
for i in range(8):
    print("class_weight of the %dth class is %.8f" % (i+1, class_weights[i]))

输出结果如下:

n_samples= 1348
n_classes= 8
class_weight= [2.18831169 0.71398305 1.39256198 0.31792453 1.54587156 2.37323944
2.80833333 1.17013889]
class_weight of the 1th class is 2.18831169
class_weight of the 2th class is 0.71398305
class_weight of the 3th class is 1.39256198
class_weight of the 4th class is 0.31792453
class_weight of the 5th class is 1.54587156
class_weight of the 6th class is 2.37323944
class_weight of the 7th class is 2.80833333
class_weight of the 8th class is 1.17013889

可以看到,不同类别的class_weight值不同,取决于当前类的样本数。样本数越多,对应的class_weight值越大,初始化的C值也越大。这样的初始化方式在样本数据分布不均匀的时候尤为有效,会比C比全部初始化为1时取得更好的效果。所以,当各类别的样本数量的分布不均匀的时候,设置**class_weight =‘balanced’**是比较合理的方法。

4.5 划分训练集和测试集

为了可以对训练后的模型进行评估,下面将数据集划分为训练集和测试集:

from sklearn.model_selection import train_test_split

# 为指定划分的比例时,默认的测试集比例为0.25
Xtrain, Xtest, ytrain, ytest = train_test_split(faces.data, 
                                                faces.target,
# 定义随机数种子,确保每次分割方式均相同,使得代码可复现
                                                random_state=42)

4.6 调参

分割完之后,接下来在训练数据集[Xtrain, ytrain]上进行网格搜索,寻找最佳的参数组合:

from sklearn.model_selection import GridSearchCV

# 使用网格搜索寻找最佳参数组合
param_grid = {'svc__C': [1, 5, 10, 50],
              'svc__gamma': [0.0001, 0.0005, 0.001, 0.005]}
grid = GridSearchCV(model, param_grid, n_jobs=-1)

%time grid.fit(Xtrain, ytrain)
print(grid.best_params_)

输出结果如下:

Wall time: 28.6 s

{svc__C: 5, svc__gamma: 0.001}

得到的最佳参数组合即可用来定义最佳模型,然后用最佳模型来预测测试集:

model_best = grid.best_estimator_
# 该模型在测试集上的预测结果
y_pred= model_best.predict(Xtest)

得到了模型在测试集上的预测结果 y_pred 后,结合测试集的实际标签 ytest,我们就可以获取模型测试集上的表现。

4.7 可视化预测结果

下面将打印出模型在前30张人脸上的预测结果。代码如下:

# 定义PCA模型用于降维(注意该模型应该在Xtrain上进行拟合)
pca = PCA(n_components=200, whiten=True, random_state=42).fit(Xtrain)
# 定义特征脸(PCA模型提取出的解释方差比最大。人脸特征最显著的前200个特征的可视化图片)
eigenfaces = pca.components_.reshape((200, h, w))

# 画出人脸图片和对应预测结果和实际标签
def plot_gallery(images, titles, h, w, n_row=5, n_col=6):
    plt.figure(figsize=(1.8 * n_col, 2.4 * n_row))
    plt.subplots_adjust(bottom=0, left=0.1, right=0.99, top=0.99, hspace=0.4)
    for i in range(n_row * n_col):
        plt.subplot(n_row, n_col, i + 1)
        plt.imshow(images[i].reshape((h, w)), cmap=plt.cm.gray)
        plt.title(titles[i], size=12)
        plt.xticks(())
        plt.yticks(())

# 定义人脸图片的对应预测结果和实际标签
def title(y_pred, ytest, target_names, i):
    pred_name = target_names[y_pred[i]].rsplit(' ', 1)[-1]
    true_name = target_names[ytest[i]].rsplit(' ', 1)[-1]
    return 'Predicted: %s\n Label:      %s' % (pred_name, true_name)

prediction_titles = [title(y_pred, ytest, target_names, i) for i in range(y_pred.shape[0])]

print("Here are the faces with predicted labels and actual labels: ")
plot_gallery(Xtest, prediction_titles, h, w)

plt.show()

输出结果如下:

Here are the faces with predicted labels and actual labels:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B3jIVXbZ-1632992268890)(471.png)]

在这30张人脸中,只有第三行第三列的那一张预测错误,其他均预测正确。该SVM分类器的分类效果似乎还不错。

4.8 特征脸的绘制

前面定义了一个将人脸图片降维到200的PCA模型。该PCA模型所提取出的这200个特征,直白来说,就是使得人脸图片最有区分度的特征。 我们可以尝试将这些特征可视化,看看PCA模型究竟提取出了人脸中的哪些特征。代码如下:

eigenface_titles = ["eigenface %d" % i for i in range(eigenfaces.shape[0])]
print("Here are the eigenfaces sorted of explained variance ratio: ")
plot_gallery(eigenfaces, eigenface_titles, h, w)
plt.show()Here are the eigenfaces sorted of explained variance ratio: 

输出结果如下:

Here are the eigenfaces sorted of explained variance ratio:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ewy5ksll-1632992268892)(481.png)]

可以看到,PCA模型所提取出的特征大多聚焦到五官和人脸轮廓部分,使得不同的人脸易于区分。这为加强SVM分类器的分类效果起了促进作用。

4.9 获取模型的评估参数

当然,在感性认识方面判断模型好坏是远远不够的,我们依然需要获取客观数据,从理性认识方面判断模型的好坏。 下列代码列举出了模型在不同类的人脸图片上的参数指标:

from sklearn.metrics import classification_report
print(classification_report(ytest, y_pred, target_names=faces.target_names))

输出结果如下:

precision recall f1-score support

Ariel Sharon 0.68 0.87 0.76 15
Colin Powell 0.80 0.93 0.86 68

Donald Rumsfeld 0.77 0.74 0.75 31
George W Bush 0.93 0.83 0.88 126
Gerhard Schroeder 0.72 0.78 0.75 23
Hugo Chavez 0.93 0.70 0.80 20
Junichiro Koizumi 0.85 0.92 0.88 12
Tony Blair 0.88 0.90 0.89 42

accuracy 0.85 337
macro avg 0.82 0.83 0.82 337

weighted avg 0.85 0.85 0.85 337

模型在测试集上取得了0.85的 f1分数加权平均准确率。

4.10 绘制混淆矩阵

下面画出混淆矩阵,使得我们可以从直观上感知到模型在不同类别上的分类效果:

from sklearn.metrics import confusion_matrix
import seaborn as sns
mat = confusion_matrix(ytest, y_pred)
plt.figure(figsize=(6, 6))
sns.heatmap(mat.T, square=True, annot=True, fmt='d', 
            xticklabels=faces.target_names,
            yticklabels=faces.target_names)
plt.xlabel('Actual Label')
plt.ylabel('Predicted Label');

输出结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RzG01nUH-1632992268895)(4_10_1.png)]

其中,横轴表示实际标签,纵轴表示预测结果,对角线上方块表示每一类人脸中分对的数量,同一列中的其他方块表示对应类误分到其他各类中的数量。从图中就可以直观看出不同类别样本数量的多少,以及各自的分类情况。

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
很好,下面我来为你介绍一下 OpenCV-Python 实现目标检测的过程。 OpenCV-Python 是一款基于 Python 的计算机视觉库,它可以帮助我们完成各种图像处理任务,包括图像的读取、处理、显示等。在使用它进行目标检测时,我们可以使用传统方法 HOG+SVM 实现。 HOG(Histogram of Oriented Gradient)特征是一种常用的图像特征,它可以描述图像中的边缘、角点等信息,其中梯度方向直方图是 HOG 特征的核心部分。在目标检测中,我们需要先提取出图像中的 HOG 特征,然后再使用 SVM(Support Vector Machine)进行分类,从而实现目标检测。 下面是一个简单的 OpenCV-Python 实现目标检测的示例代码: ```python import cv2 # 加载 SVM 分类器 svm = cv2.ml.SVM_load('svm.xml') # 加载测试图片 img = cv2.imread('test.jpg') # 创建 HOG 描述符 hog = cv2.HOGDescriptor() # 设置 SVM 分类器 hog.setSVMDetector(svm) # 检测目标并绘制矩形框 rects, weights = hog.detectMultiScale(img, winStride=(8, 8), padding=(32, 32), scale=1.05) for (x, y, w, h) in rects: cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) # 显示结果 cv2.imshow('result', img) cv2.waitKey(0) cv2.destroyAllWindows() ``` 在代码中,我们首先加载了训练好的 SVM 分类器,并加载了测试图片。然后创建了 HOG 描述符,并设置 SVM 分类器。最后使用 detectMultiScale 函数检测目标,并绘制矩形框,最终在窗口中显示检测结果。 当然,这仅仅是一个简单的示例,实际的目标检测过程还需要根据具体的应用场景进行调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Juicy B

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

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

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

打赏作者

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

抵扣说明:

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

余额充值