您的位置:首页 > 运维架构

opencv2.4.9中KNN算法理解

2015-11-26 20:07 357 查看

KNN算法

opencv中文版原文描述是:K近邻可能是最简单的分类器。训练数据跟类别标签放在一起,离测试数据最近的(欧氏距离最近)K个样本进行投票,确定测试数据的分类结果。这可能是你想到的最贱的方法,该方法比较有效,但是速度比较慢且对内存的需求比较大(因为它需要存储所有训练集)。

离测试数据是否最近要求计算测试样本与所有点的距离,可以把这个过程看成搜索过程,是个求top-K的问题。由于要进行投票,那么就可以分为带权投票还是不带权投票,不带权投票可能会出现近邻中不足K个或K个距离和非常大,从而使得分类出错。另外,参数K该怎么取应该就很关键了,我看到的大部分程序时人为规定参数K值,应该存在自动确定K值的算法。K近邻中的K含义是在将测试样本分到某个类别时要知道其K个近邻样本的类别,把测试样本归类到样本占多数的类别,这说明在分类新样本之前需要至少有K个样本且知道其类别,因此可以说KNN是监督学习算法。

Opencv2.4.9中实现的KNN是在CvKNearest类中,继承于CvStatModel,放在了机器学习模块。既可以用来进行分类,又可以用来做回归,并且支持增量学习,可以用新样本来更新模型。但是opencv的KNN算法不像决策树一样支持变量子集选择和属性缺失的情况。

-使用方法可以采用定义CvKNearest类的对象方法如下:

CvKNearest knn(trainData, trainClasses, 0, false, K);


,该方法会在构造函数中执行训练过程,训练的过程就是给CvMat *smaple赋予数据的过程,将样本有序的放在内存的前面,label放在内存的后面。构造代码如下:


CvKNearest::CvKNearest( const CvMat* _train_data, const CvMat* _responses,
const CvMat* _sample_idx, bool _is_regression, int _max_k )
{
samples = 0;
train( _train_data, _responses, _sample_idx, _is_regression, _max_k, false );
}


来了一个新样本只需要调用response = knn.find_nearest(&sample, K, 0, 0, nearests,

0);函数就可以得到类别了。

opencv中的实现过程

假如已经将一批带标签的数据通过构造函数的方式存入到CvKNearest的成员变量samples所指内存区,现在来了一个新的测试样本,首先计算测试样本与所有训练样本的欧氏距离,调用find_neighbors_direct计算所有测试样本到所有训练集的距离。

void CvKNearest::find_neighbors_direct( const CvMat* _samples, int k, int start, int end,
float* neighbor_responses, const float** neighbors, float* dist ) const
{
int i, j, count = end - start, k1 = 0, k2 = 0, d = var_count;
CvVectors* s = samples;

for( ; s != 0; s = s->next )
{
int n = s->count;//训练集中样本个数
for( j = 0; j < n; j++ )
{
for( i = 0; i < count; i++ )//count表示测试样本个数,无标签
{
double sum = 0;
Cv32suf si;
const float* v = s->data.fl[j];//指向已带标签样本j
const float* u = (float*)(_samples->data.ptr + _samples->step*(start + i));//指向测试样本
Cv32suf* dd = (Cv32suf*)(dist + i*k);//保存样本i的到k个近邻的距离(Cv32suf的f域)和近邻的类别(Cv32suf的i域)
float* nr;
const float** nn;
int t, ii, ii1;

for( t = 0; t <= d - 4; t += 4 )
{//计算样本i与样本j的欧式距离,最小维数为4
double t0 = u[t] - v[t], t1 = u[t+1] - v[t+1];
double t2 = u[t+2] - v[t+2], t3 = u[t+3] - v[t+3];
sum += t0*t0 + t1*t1 + t2*t2 + t3*t3;
}

for( ; t < d; t++ )
{//计算样本i与样本j的欧式距离,维数小于4
double t0 = u[t] - v[t];
sum += t0*t0;
}

si.f = (float)sum;
for( ii = k1-1; ii >= 0; ii-- )//将类别i从小到大排序,插入到ii+1位置
if( si.i > dd[ii].i )
break;
if( ii >= k-1 )
continue;

nr = neighbor_responses + i*k;
nn = neighbors ? neighbors + (start + i)*k : 0;
for( ii1 = k2 - 1; ii1 > ii; ii1-- )//插入前数据后移
{
dd[ii1+1].i = dd[ii1].i;
nr[ii1+1] = nr[ii1];
if( nn ) nn[ii1+1] = nn[ii1];
}
dd[ii+1].i = si.i;//给ii+1位置的样本赋予插入的样本的类别编号,由于是union结构,距离也保存了
nr[ii+1] = ((float*)(s + 1))[j];
if( nn )
nn[ii+1] = v;
}
k1 = MIN( k1+1, k );
k2 = MIN( k1, k-1 );
}
}
}


得多所有距离后,采用冒泡排序方式得到topK近邻,并计算样本数最多的类别。

float CvKNearest::write_results( int k, int k1, int start, int end,
const float* neighbor_responses, const float* dist,
CvMat* _results, CvMat* _neighbor_responses,
CvMat* _dist, Cv32suf* sort_buf ) const
{
float result = 0.f;
int i, j, j1, count = end - start;
double inv_scale = 1./k1;
int rstep = _results && !CV_IS_MAT_CONT(_results->type) ? _results->step/sizeof(result) : 1;

for( i = 0; i < count; i++ )//count=1
{
const Cv32suf* nr = (const Cv32suf*)(neighbor_responses + i*k);
float* dst;
float r;
if( _results || start+i == 0 )
{
if( regression )
{//不执行
double s = 0;
for( j = 0; j < k1; j++ )
s += nr[j].f;
r = (float)(s*inv_scale);
}
else
{
int prev_start = 0, best_count = 0, cur_count;
Cv32suf best_val;

for( j = 0; j < k1; j++ )//复制前K1个数据
sort_buf[j].i = nr[j].i;

for( j = k1-1; j > 0; j-- )//表示排序次数k1-1次,使类别标签有序
{
bool swap_fl = false;
for( j1 = 0; j1 < j; j1++ )//c从前往后,比较浅j个数
if( sort_buf[j1].i > sort_buf[j1+1].i )//从小到大排序
{
int t;
CV_SWAP( sort_buf[j1].i, sort_buf[j1+1].i, t );
swap_fl = true;
}
if( !swap_fl )//如果已经有序,则跳出循环
break;
}

best_val.i = 0;//记录样本数最多的类别
for( j = 1; j <= k1; j++ )//冒泡排序k1-1次
if( j == k1 || sort_buf[j].i != sort_buf[j-1].i )
{//遇到新的类别
cur_count = j - prev_start;//类别计算
if( best_count < cur_count )
{
best_count = cur_count;
best_val.i = sort_buf[j-1].i;
}
prev_start = j;
}
r = best_val.f;
}

if( start+i == 0 )
result = r;

if( _results )
_results->data.fl[(start + i)*rstep] = r;
}

if( _neighbor_responses )
{
dst = (float*)(_neighbor_responses->data.ptr +
(start + i)*_neighbor_responses->step);
for( j = 0; j < k1; j++ )
dst[j] = nr[j].f;//从nr到dst
for( ; j < k; j++ )
dst[j] = 0.f;
}

if( _dist )
{
dst = (float*)(_dist->data.ptr + (start + i)*_dist->step);
for( j = 0; j < k1; j++ )
dst[j] = dist[j + i*k];
for( ; j < k; j++ )
dst[j] = 0.f;
}
}

return result;
}


转载请注明作者和出处http://blog.csdn.net/CHIERYU 未经允许请勿用于商业用途
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息