时间为友,记录点滴。
本来还想在特征点检测的道路上再走一走,学习一下Harr级联或者HOG什么的,但总有些概念感觉绕不过去。择日不如撞日,撞日不如今日。我们先揭开机器学习中的一个小角,看看大热的机器学习到底是什么东西。
啥是机器学习?
如今机器学习在中国的大热,还是要感谢2016年Alpha Go和李世石的那场人机围棋对决。外行人人谈AI,内行深度神经网络大红大紫。人工智能,机器学习,深度学习的关系
从关系上看,机器学习仅仅是实现人工智能的其中一种方式。所谓机器学习,我更愿意把机器拟人化,跟人类学习一样,可以从经验(数据)中去成长(学习),然后可以解决实际的问题。这个过程就是学习。人的过程就是人的学习,计算机的过程就是机器学习。
我们可以用这么定义:
机器学习=数据+算法+硬件
机器学习的分类
硬件:
我们结合上面的公式,先来看看硬件:CPU GPU 专用芯片最开始的机器学习当然是写成程序运行在CPU通用处理器上了;
但是随着深度学习的发展,人们发现GPU的架构更适合做深度神经网络这种大量的简单运算(要么NVIDIA的股票biubiu地飞);
后来,人们发现可以为某个算法单独设计一款ASIC,这样的运算能效比比GPU更高(要么Bitcoin能挖这么快);
数据和算法:
为什么把数据和算法放一块呢?因为他们总是相辅相成,特定的数据就需要有更优的算法来匹配。
在机器学习的算法世界里,如OpenCV的传统算法一样,浩如烟海,如果真的有人想要搞懂大部分的算法,一定是惊为天人了。听说下面这个哥们用numpy手写了好多。ddbourgin/numpy-mlgithub.com
OpenCV中的机器学习
OpenCV的ml模块实现了很多算法,包括朴素贝叶斯、K近邻、SVM、决策树、Boosting、GBT、随机森林、神经网络等。其大多继承自同一基类,训练和预测的接口都是train(),predict(),使用较为方便。
我们进入到代码里面瞅一瞅:
在源码的\opencv-4.1.1\opencv-4.1.1\modules\ml\include\opencv2\ml.hpp文件中有提到:ml.hpp里面包含的内容
SVM(Support Vector Machines)
如果之前没接触过机器学习,那么越说多越迷糊;如果之前就听过NG大神的课,那多说就是废话。我们既然不是以机器学习算法研究推导为主题,那么就让我们在OpenCV中感受一个比较简单的算法,这样可以对机器学习的认识再向前前进一小步。
拿谁开刀呢?SVM!
SVM(Support Vector Machines)中文翻译是支持向量机,SVM算法在在1995年正式发表,在针对中小型数据规模的分类任务上有着卓越的效果,同时有着完整的理论证明,在20世纪末的几年和21世纪初的10年完胜神经网络,直到深度学习的兴起。但即便这样,现在SVM算法依旧被广泛的使用。
1、SVM要解决什么问题?
如果要搞清楚SVM是什么东西,还是要从我们为什么要引入SVM说起。
SVM主要用于解决模式识别领域中的数据分类问题,属于有监督学习算法的一种。SVM要解决的问题可以用一个经典的二分类问题加以描述。
我们把需求简化一下,两个数据在笛卡尔直角平面坐标系中分别被标定成红色点和蓝色点,那么我显然可以通过一条直线把两个数据区分开(在数字图像中,可以把两个点理解成特征点和背景,所以区分数据就是提取特征点的过程)
那么问题来了,这条线该怎么画?SVM就是来解决如何划线的问题。监督:人为规定此球(数据)为红色,彼球(数据)为蓝色的过程。
分类:就是把红球和篮球分开;
2、SVM中如何画线?
如何判断上图中的三条线,哪条比较好?直觉是红色的那条,为什么呢?因为当数据变多时,红色的线是最大可能分区数据的那条。可以画一条虚线连接两个圆的圆心作为辅助线,红线距离两个圆的圆心都比较远
嗯,感觉好有道理,这和SVM有什么关系呢?
我们知道,在计算机的世界里,所有的点都是离散的,比如下图:
如何区分上图的众多红球和篮球的过程中,你可以画一条A这样的线,也可以画一条B这样的线,他们都可以把红球和篮球区分开,但是哪个是最优的呢?
可不可以借鉴两个圆连接圆心求最大距离的思路呢?当然可以!
我们取(b)图放大,此时A就是此时的分界线,专业称为“决策线”。我们沿着决策线做向两边的平行线,直到碰到两边的第一个有效数据,画两条虚线。
定义两条虚线之间的距离为W为“分类间隔”,两边被虚线划中的点叫做“支持向量”
那么最佳的决策线应该是所有的支持向量到决策线的垂直距离d都相等,且最大。
所以,找线的问题就转变成求解支持向量到决策线的垂直距离d最大的问题。
在SVM支持向量中,所有的这些数据在空间中的表达式就是一个向量,而用于直接支持确定决策线的这些点,就叫做支持向量。因此,这个算法被叫做支持向量机。
数据即向量;
支持确立决策线的数据即支持向量;
通过支持向量求决策线的算法即支持向量机;
好了,我们今天了解了支持向量机能做什么以及为什么叫他支持向量机。至于公式推导SVM如何做到这一切的就留给明天吧,我们今天先来看看OpenCV中官方的SVM怎么使用的。
官方文件目录:opencv-4.1.1\samples\cpp\tutorial_code\ml\introduction_to_svm\introduction_to_svm.cpp
C++
这里面的三个关键点:
1、数据和标签
int labels[4] = {1, 1, -1, -1};
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };trainingData为四个数据点(可以认为是球的圆心坐标)。
labels为对应数据的标签(监督,可以认为1为红球,-1为篮球)
2、训练
svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);
如下是OpenCV中train的函数原型:
/** @brief Trains the statistical model@param samples training samples@param layout See ml::SampleTypes.@param responses vector of responses associated with the training samples.*/
CV_WRAP virtual bool train( InputArray samples, int layout, InputArray responses );
其中:Sample表示训练样本数据
Layout 有两种组织方式ROW_SAMPLE与COL_SAMPLE
Responses 每个输入样本的标签
这里其实就是寻找决策线的过程。
3、分类
float response = svm->predict(sampleMat);
如下是OpenCV的predict原型
@param samples The input samples, floating-point matrix
@param results The optional output matrix of results.
@param flags The optional flags, model-dependent. See cv::ml::StatModel::Flags.
*/
CV_WRAP virtual float predict( InputArray samples, OutputArray results=noArray(), int flags=0 ) const = 0;
其中:samples:表示输入样本,供SVM判定。
OutputArray :表示输出矩阵
flag:表示支持模式
返回值为设定的标签值(这里是1或者-1),由此可以判定当前输入应该被分到哪一类中(也可以理解成是分在决策线的哪一边。)
#include #include #include #include #include
using namespace cv;
using namespace cv::ml;
int main(int, char**)
{
// Set up training data //! [setup1] int labels[4] = {1, 1, -1, -1};
float trainingData[4][2] = { {501, 10}, {255, 10}, {501, 255}, {10, 501} };
//! [setup1] //! [setup2] Mat trainingDataMat(4, 2, CV_32F, trainingData);
Mat labelsMat(4, 1, CV_32SC1, labels);
//! [setup2]
// Train the SVM //! [init] Ptr svm = SVM::create();
svm->setType(SVM::C_SVC);
svm->setKernel(SVM::LINEAR);
svm->setTermCriteria(TermCriteria(TermCriteria::MAX_ITER, 100, 1e-6));
//! [init] //! [train] svm->train(trainingDataMat, ROW_SAMPLE, labelsMat);
//! [train]
// Data for visual representation int width = 512, height = 512;
Mat image = Mat::zeros(height, width, CV_8UC3);
// Show the decision regions given by the SVM //! [show] Vec3b green(0,255,0), blue(255,0,0);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
Mat sampleMat = (Mat_(1,2) << j,i);
float response = svm->predict(sampleMat);
if (response == 1)
image.at(i,j) = green;
else if (response == -1)
image.at(i,j) = blue;
}
}
//! [show]
// Show the training data //! [show_data] int thickness = -1;
circle( image, Point(501, 10), 5, Scalar( 0, 0, 0), thickness );
circle(image, Point(255, 10), 5, Scalar(0, 0, 0), thickness);
circle( image, Point(501, 255), 5, Scalar(255, 255, 255), thickness );
circle( image, Point( 10, 501), 5, Scalar(255, 255, 255), thickness );
//! [show_data]
// Show support vectors //! [show_vectors] thickness = 2;
Mat sv = svm->getUncompressedSupportVectors();
for (int i = 0; i < sv.rows; i++)
{
const float* v = sv.ptr(i);
circle(image, Point( (int) v[0], (int) v[1]), 6, Scalar(0, 0, 255), thickness);
}
//! [show_vectors]
imwrite("result.png", image); // save the image
imshow("SVM Simple Example", image); // show it to the user waitKey();
return 0;
}
有了上面的监督数据+训练+预测,代码就好理解多了。直接看结果吧:代码我稍作了修改,主要是修改了标签值。
读者也可以自己改一下标签纸,感受一下支持向量和决策边界的移动。