一起来探索宇宙的奥秘
To explore universe
今天把毕设的一部分内容——魔方颜色识别这一块拿出来说一说,既是对自己知识的巩固,也能与大家一起学习进步。
本文的魔方颜色识别,采用SVM(支持向量机)算法进行识别。在深度学习出现之前,支持向量机被认为是机器学习中最成功,表现最好的算法,尤其在数据集较小的情况下。本文主要介绍一些支持向量机理论性的知识,后面还会更新Python下的具体实现,并将其应用到魔方颜色识别中。
1.基本原理
如果要对一个数据集进行分类,假设该数据集只有两种类别,如图1.1所示,我们可以构造一条分隔线将菱形和三角形分开。我们把这条分隔线称为分隔超平面。
图1.1
从上图可以看到,将一个数据集分类的分隔超平面有无穷多个。H2和H3均为该数据集的分隔超平面,而且可以看出,H3的分类效果比H2要好,这是因为数据集上离分隔线最近的点到分割线的距离更大。设离分割线H3最近的点到H3的距离为margin3,离分割线H2最近的点到H2的距离为margin2,即满足margin3>margin2条件时,我们认为H3的分类效果更好。同时,margin3越大,则说明这条分隔超平面的鲁棒性越好,泛化能力更强。
如图1.2所示,图中的实线即为分隔超平面。那些离分隔超平面最近的点称为支持向量,即图中虚线与数据集接触的点。将支持向量与分隔超平面的距离的两倍称为间距,即图中两条虚线间的距离。
图1.2
支持向量机的最终目标就是找到数据集的最大分隔超平面,换句话说,找到的分隔超平面能将数据集正确分类,并且使间距最大。从图1.2中可以看到,我们要找到分隔超平面,就要使超平面到一侧最近点的距离等于到另一侧最近点的距离,且间距要最大。
2.公式的建立
经过前面知识的铺垫,我们知道,在二维空间里分隔超平面可以表示成:
对于高维空间来说,可以将其写成一般化的向量形式,即用
表示分割超平面。其中
w称为权重向量,x表示训练样本,n表示特征值的个数,也即训练样本的特征维度,最后一个参数b称为偏置。
为了便于后续概念的理解和叙述,我们只考虑最简单的一类情况,即数据集只有两个类别。如图1.3所示,我们画出与分隔超平面平行的两条直线,分别穿过两个类别的支持向量,将红色数据点的类别标签定义为1。蓝色数据点的类别标签定义为-1。
图1.3
则这两条虚线的方程分别为
根据点到直线的距离公式,可以得出支持向量A到分隔超平面的距离为:
其中||w||是向量w的范数,定义如下:
从图1.3中可以看到,支持向量A在下侧的虚线上,所以满足方程
,将该直线方程代入上面的距离公式,得到支持向量A到分隔超平面的距离为
这也是支持向量到分隔超平面的距离公式。
为了使最终得到的间距最大,只需找到合适的参数w,使得上述的距离d最大即可。由间距的定义,可得最大间距公式如下:
在支持向量机算法中,除了找到最大间距外,还要考虑分隔超平面是否能正确地把数据集分类。
从上图1.3中可以看出,红色数据点满足方程
的约束,而蓝色数据点满足方程
的约束。由于数据的类别是离散值,前面我们已经假设红色数据点用1表示其类别,蓝色数据点用-1表示其类别,假设数据类别用变量y表示,则有
综合上述两个公式,针对数据集中的每一个样本,可以得出分隔超平面将数据集正确分类的约束条件:
其中i是数据集中的样本编号。
只要所有样本均满足上述约束条件,则由参数w和b所确定的分隔超平面就可以将数据集正确分类。
总结一下,上面我们推导了支持向量机最大间距公式和正确分类的约束条件,所以支持向量机求解的过程就是在满足约束条件的情况下求解间距最大值。支持向量机算法还有一些很抽象的理论知识,比如核函数,这部分内容过于抽象不好理解,感兴趣的童鞋可以自行百度。之后会更新支持向量机算法在Python下的实现,使用一台电脑和摄像头就可以完成魔方颜色识别。
上面是我毕设的理论篇。下面可以进入我们的实践正题了哈,为了能清楚表达接下来要干什么,先上个最终的效果图看看。没错只是让电脑能识别魔方的颜色,然后。。。。就没有然后了
核函数的出现是为了解决线性不可分的问题,核函数的思想就是通过一个非线性变换将输入空间映射到特征空间,这样在输入空间不能使用分隔超平面分类的问题,在特征空间就能使用分隔超平面完美解决了。
理论知识已经有了,所以要做的就是使用SVM算法来识别魔方颜色,而第一步需要训练SVM模型。在上一篇推文中,我们推导了SVM的最大间距公式和正确分类的约束条件,支持向量机算法求解的参数有w和b,只有这两个参数确定了,则建立的模型才随之确定。所以SVM的训练过程也是在寻找参数w和b的过程。
可能有人会觉得奇怪,支持向量机本身是一个二分类算法,魔方颜色识别是一个多分类问题,支持向量机能解决这个问题吗?虽然SVM是二分类算法,但是将SVM扩展后仍然可以解决多分类问题。SVM解决多分类问题的基本思路是:有几个类别就设置几个分类器,对于每一个类来说,都有一个当前类和其他类的二分类器。对于魔方颜色分类器来说,支持向量机并行地设置6个分类器,对于红色类来说,样本只有红色类和其他类;对于绿色类来说,样本只有绿色类和其他类,依此类推,多分类问题就转变成了多个二分类问题。
一
本文使用的训练数据集是个人采集制作完成,但是本文的重点在于SVM的实践,所以数据集的制作过程就此略过。自监督学习需要给训练数据集打上相应的标签,并按标签分类。所以本文用相应颜色的英文单词首字母来表示颜色图像属于什么类别。魔方的颜色有:红、绿、蓝、白、橙、黄,所以对应的类别标签为:R、G、B、W、O、Y。它大概长这个样子:
这是整个数据集中绿色图像的数据,每种颜色有100张左右的图像。数据标注的格式为"颜色_序号",每张图像大小为25*25像素。
训练数据集的文件结构如下图所示:
SVM模型的训练过程大致如下:第一步将图像像素值归一化,提高运算效率;第二步将彩色图像向量化,即将25*25*3的三维张量展平成向量,得到了1875维的向量,这个向量就是一个样本的数据特征;最后将向量数据送入模型进行训练。
在开始写代码前,还需要提前配置好环境,代码在Python3.x下实现,需要安装的库有:OpenCV,PIL,skit-learn,numpy。等一切准备就绪,就可以动手撸代码了!
二
首先导入必要的库。这里的支持向量机算法需要调用skit-learn机器学习库中封装好的API
import numpy as np
import os
import time
from sklearn import svm
from sklearn.externals import joblib
from cv2 import cv2
第一步加载数据,按照上文所说的文件结构,编写代码将数据加载到内存。
def read_all_data(file_path):
cName = ['R','G', 'Y', 'W', 'O', 'B']
# 得到一个图像文件名列表flist
i = 0
for c in cName:
train_data_path = os.path.join(file_path, c)
# 获取文件夹下的所有图片路径列表
flist_ = get_file_list(train_data_path)
if i == 0:
dataMat, dataLabel = read_and_convert(flist_)
else:
dataMat_, dataLabel_ = read_and_convert(flist_)
# 按轴axis0连接array组成一个新的array
dataMat = np.concatenate((dataMat, dataMat_), axis=0)
dataLabel = np.concatenate((dataLabel, dataLabel_), axis=0)
#print(dataMat.shape)
#print(len(dataLabel))
i +=1
return dataMat, dataLabel
def read_and_convert(imgFileList):
dataLabel = [] # 存放类标签
#计算图像个数
dataNum = len(imgFileList)
# dataNum * 1875 的矩阵
dataMat = np.zeros((dataNum, 1875))
for i in range(dataNum):
imgNameStr = imgFileList[i]
# 得到 颜色_编号.jpg
imgName = get_img_name_str(imgNameStr)
# 得到 类标签(颜色) 如 B_5.jpg
classTag = imgName.split(".")[0].split("_")[0]
dataLabel.append(classTag)
dataMat[i,:] = img2vector(imgNameStr)
return dataMat, dataLabel
def get_file_list(path):
file_list=[]
# 获取path路径下的所有文件名
for file_name in os.listdir(path):
fin_path = os.path.join(path, file_name)
if (fin_path.endswith('.jpg')):
file_list.append(fin_path)
return file_list
# 按文件路径拆分并取最后一个元素,即图像文件名
def get_img_name_str(imgPath):
return imgPath.split(os.path.sep)[-1]
上面已经说过,在拿到图像数据后,对图像归一化并且将图像展平成向量。
# 将图像转换为向量
def img2vector(imgFile):
img = cv2.imread(imgFile,1)
img_arr = np.array(img)
# 对图像进行归一化
img_normlization = img_arr/255
# 1 * 1875 向量
img_arr2 = np.reshape(img_normlization, (1,-1))
return img_arr2
最后定义SVM分类器,skit-learn已经为我们提供了SVM算法的API,其中SVC类是用来做分类任务的。
首先需要选择SVM的核函数,核函数由参数kernel指定:'linear'表示线性核函数,它只能产生线性的分隔超平面;'poly'表示多项式核函数;'rbf'表示高斯核函数。多项式核函数和高斯核函数都可以产生复杂的分隔超平面。
除了指定kernel外,还需要指定gamma值,这是高斯核函数的参数,默认指定为1/features。kernel这个参数没有最优,只能是在实验过程中测试然后再调整。
def create_svm(dataMat, dataLabel, path):
clf = svm.SVC(C = 1.0, kernel='rbf')
# 开始训练模型
rf = clf.fit(dataMat, dataLabel)
#存储训练好的模型
joblib.dump(rf, path)
return clf
然后就可以把代码跑起来了。
if __name__ == '__main__':
st = time.clock()
path = '数据图片路径'
dataMat, dataLabel = read_all_data(path)
model_path = os.path.join(path,'svm_cube.model')
create_svm(dataMat, dataLabel, model_path)
et = time.clock()
print("Training spent {:.4f}s.".format((et - st)))
三
因为数据量不大,所以训练很快就能完成。接着使用已经训练好的模型,看看效果怎么样。
import numpy as np
import os
from sklearn.externals import joblib
import matplotlib.pyplot as plt
from cv2 import cv2
model_path = '模型保存路径'
img_path = "测试图片路径"
clf = joblib.load(model_path) # 加载模型
def img2vector(img):
img_arr = np.array(img)
img_normlization = img_arr/255
img_arr2 = np.reshape(img_normlization, (1,-1))
return img_arr2
i = 1
for file in os.listdir(img_path):
filepath = os.path.join(img_path, file)
img = cv2.imread(filepath)
img2arr = img2vector(img)
preResult = clf.predict(img2arr)
img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)
plt.subplot(2,3,i)
plt.imshow(img)
plt.title(preResult)
i += 1
print(preResult)
plt.show()
正常来说这里要对模型进行评估,但是本文没有划分测试集,所以评估这一步就省略了。现在只是对一张单个颜色的图像进行识别,对一张魔方图像的识别还需要能够从整张图像中找到颜色块,这涉及到一些图像处理和边缘提取的内容。我打算留到之后,单独写一篇文章来说明。
—— E N D ——
长
按
关
注
前端技术编程
用有限的生命尽可能多的学习无限的知识
一起寻找生命中的光.....