DBSCAN

参加了老师的一个项目,要搞劣质数据对机器学习算法的影响,我负责实现DBSCAN算法

定义

参数

ε(eps):邻域半径

minPts:形成核心点最少的点数

距离

在二维(每个数据只有两个属性)的情况下,使用欧氏距离(sqrt(x^2+y^2))就可以有很好的效果,但对于更高维度的数据,欧氏距离是不适用的。

点的可达性

如果点p在距离ε内有至少minPts个点,则点p被称为核心点(core point),p距离ε内的其他点被称为p的直接可达点(directly density-reachable point)

对于点p和q,如果存在一个序列{p1,p2,…,pn},其中p1=p,pn=1,满足∀i∈N,i<n有pi+1是pi的直接可达点,则称q为p的可达点(density-reachable point)

对于点p和q,如果存在点o,p和q都是o的可达点,则称p和q是密度连接(density-connected)的。

如果点p不是任何点的可达点,则称p为局外点

聚类

一个聚类满足如下性质:

1.聚类中的任意两个点都是密度连接的。

2.聚类中的任意点的可达点仍属于这个聚类。

算法

①对于每一个点,判断其是否为核心点

②新建一个聚类cluster

③找到一个没有被访问的点,放入cluster。如果所有店都被访问,则程序结束。

④根据cluster中点的可达点进行搜索,将搜索到的未被访问的点放入cluster,并标记为已访问。

⑤重复②

代码

#include <vector>
#include <algorithm>
#include <fstream>
#include <iostream>

#define pointSet std::vector<point>
#define pointerSet std::vector<int>

#define NOT_VISTED -1
#define WRITE_TO_FILE
#define ATTRIBUTIONS 4

#define minPts 4
#define E 0.1
//it is actually squared e

class point {
public:
	double a[ATTRIBUTIONS];
	int ddr;//amount of directly density-reachable points
	int flag;
	int flag_ans;
	pointerSet *ddrs;
	point(double *attributions, int flag_ans) {
		for (int i = 0; i < ATTRIBUTIONS; ++i) {
			a[i] = attributions[i];
		}
		ddr = 0;
		ddrs = NULL;
		flag = NOT_VISTED;
		this->flag_ans = flag_ans;
	}
	double distance(const point &p0) {
		double sum = 0;
		for (int i = 0; i < ATTRIBUTIONS; ++i) {
			sum += (a[i] - p0.a[i])*(a[i] - p0.a[i]);
		}
		return sum;//there is no sqrt() because E have been squared
	}
	bool operator < (const point &p0) {
		for (int i = 0; i < ATTRIBUTIONS; ++i) {
			if (a[i] < p0.a[i]) return true;
			else if (a[i] > p0.a[i]) return false;
		}
		return false;
	}
};

class DBSCAN {
public:
	DBSCAN(std::string file) {
		std::ifstream fin(file);
		fin >> N;
		for (int i = 0; i < N; ++i) {
			double attributions[ATTRIBUTIONS];
			int flag;
			for (int i = 0; i < ATTRIBUTIONS; ++i) {
				fin >> attributions[i];
			}
			fin >> flag;
			points.push_back(point(attributions, flag));
		}
		fin.close();
	}
	//To calcute ddr(amount directly density-reachable points) of every point and save ddrs(directly density-reachable points) for each CORE points
	void calcDdr(void) {
		for (int i = 0; i < N; i++) {
			pointerSet *p = new pointerSet;
			for (int j = 0; j < N; j++) {
				if (i == j) continue;
				if (points[i].distance(points[j]) < E) {
					points[i].ddr++;
					p->push_back(j);
				}
			}
			if (points[i].ddr >= minPts) {
				points[i].ddrs = p;
			}
			else {
				delete p;//if point i is not a core point, we need not to know its directly density-reachable points
			}
		}
	}
	void DFS(int x, int flag) {
		point &p = points[x];
		p.flag = flag;
		for (int i = 0; i < p.ddrs->size(); ++i) {
			if (points[p.ddrs->at(i)].flag == NOT_VISTED) {
				points[p.ddrs->at(i)].flag = flag;
				if (points[p.ddrs->at(i)].ddr >= minPts) DFS(p.ddrs->at(i), flag);
			}
		}
	}
	void process(void) {
		calcDdr();
		int flag = 0;
		for (int i = 0; i < N; ++i) {
			if (points[i].flag == NOT_VISTED && points[i].ddr >= minPts) {
				DFS(i, flag++);
			}
		}
#ifdef WRITE_TO_FILE
		std::ofstream fout("output.txt");
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < ATTRIBUTIONS; ++j) {
				fout << points[i].a[j] << '\t';
			}
			fout << points[i].flag << '\t' << points[i].flag_ans << std::endl;
		}
		fout.close();
#else
		for (int i = 0; i < N; ++i) {
			for (int j = 0; j < ATTRIBUTIONS; ++j) {
				std::cout << points[i].a[j] << ' ';
	}
			std::cout << points[i].flag << std::endl;
}
#endif
	}
private:
	int N;
	pointSet points;
};

int main()
{
	DBSCAN dbscan = DBSCAN("input.txt");
	dbscan.process();
	system("pause");
	return 0;
}

效果

二维的情况,看起来聚类的结果还是很好的,由于只使用了四个属性中的两个,所以没有将原始数据的三个集合给区分出来,但明显能看出来将相近的点分成了同样的颜色(灰色代表噪声)。

但在高维数据上,使用欧氏距离计算密度的DBSCAN的效果就不是很好了,甚至可以说非常不好,例如我用的iris数据,如果将四个属性全用上,则不管怎么调整参数,都无法大致将数据分成三类,虽然四维图像无法显示,但从输出来看,结果跟上图是差不多了,有两类无法分开。

上篇更改博客模板
下篇汇编实现汉诺塔