DBSCAN聚類算法(附代碼)

上一篇我們講了K-Means的聚類算法:,K-Means算法需要事先定好聚合多少族群(即K的數(shù)量),同時(shí)會(huì)受離群點(diǎn)影響較大,如果事先不知道有多少個(gè)族群怎么辦呢?這里就到了DBSCAN算法出場(chǎng)的時(shí)候了

DBSCAN

DBSCAN是一種基于密度的聚類算法,可以根據(jù)樣本分布的緊密程度決定,同一類別的樣本之間是緊密相連的,不同類別樣本聯(lián)系是比較少的。

DBSCAN算法需要用到參數(shù):

  • eps (ε):一種距離度量,用于定位任何點(diǎn)的鄰域內(nèi)的點(diǎn)。
  • minPts:聚類在一起的點(diǎn)的最小數(shù)目,超過(guò)這一閾值才算是一個(gè)族群

DBSCAN聚類完成后會(huì)產(chǎn)生三種類型的點(diǎn):

  • 核心點(diǎn)(Core)——該點(diǎn)表示至少有m個(gè)點(diǎn)在距離n的范圍內(nèi)。
  • 邊界點(diǎn)(Border) ——該點(diǎn)表示在距離n處至少有一個(gè)核心。
  • 噪聲點(diǎn)(Noise) ——它既不是核心點(diǎn)也不是邊界點(diǎn)。并且它在距離自身n的范圍內(nèi)有不到m個(gè)點(diǎn)。

DBSCAN算法步驟

  1. 算法通過(guò)任意選取數(shù)據(jù)集中的一個(gè)點(diǎn)(直到所有的點(diǎn)都訪問(wèn)到)來(lái)運(yùn)行。
  2. 如果在該點(diǎn)的“ε”半徑范圍內(nèi)至少存在“minPoint”點(diǎn),那么認(rèn)為所有這些點(diǎn)都屬于同一個(gè)聚類。
  3. 通過(guò)遞歸地重復(fù)步驟1、步驟2 對(duì)每個(gè)相鄰點(diǎn)的鄰域計(jì)算來(lái)擴(kuò)展聚類

DBSCAN的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 不需要事先指定聚類的數(shù)量,通過(guò)密度來(lái)聚合在一起
  • 對(duì)于復(fù)雜的分布及離群點(diǎn)產(chǎn)生的結(jié)果比K-Means更加合理,如圖:

** 缺點(diǎn):**

  • 如果樣本集的密度不均勻、聚類間距差相差很大時(shí),聚類質(zhì)量較差
  • 算法較復(fù)雜,需要針對(duì)距離閾值和領(lǐng)域樣本閾值進(jìn)行調(diào)參才能產(chǎn)生較好的效果

代碼實(shí)現(xiàn)

# -*- coding: utf-8 -*-
import math
import random
import matplotlib.pyplot as plt


class DBSCAN(object):

    STATUS_UNVISITED = 'unvisited'
    STATUS_VISITED = 'visited'

    STATUS_GROUP = 1
    STATUS_NOGROUP = 0

    data = dict()

    def __init__(self, e, minPts):
        """
        e 最小距離
        minPts 最少樣本數(shù)量
        """
        self.e = e
        self.minPts = minPts

    def nearby(self, id):
        nearby_points = list()
        for link_id in self.scores[id]:
            if self.scores[id][link_id] <= self.e:
                nearby_points.append(link_id)

        return nearby_points

    def visit_nearby_points(self, points, group):
        for id in points:
            if self.data[id]['is_visited'] == self.STATUS_VISITED \
                    and self.data[id]['is_group'] == self.STATUS_GROUP:
                continue
            self.data[id]['is_visited'] = self.STATUS_VISITED

            if self.data[id]['is_group'] == self.STATUS_NOGROUP:
                group.append(id)
                self.data[id]['is_group'] = self.STATUS_GROUP

            nearby_points = self.nearby(id)
            if len(nearby_points) >= self.minPts:
                self.visit_nearby_points(nearby_points, group)

    def fit(self, data_set, scores):
        self.scores = scores
        groups = list()

        for index, item in enumerate(data_set):
           self.data[index] = {'id': index,
                                'is_visited': self.STATUS_UNVISITED,
                                'is_group': self.STATUS_NOGROUP
                                }

        for id in self.data:
            if self.data[id]['is_visited'] == self.STATUS_VISITED:
                continue

            self.data[id]['is_visited'] = self.STATUS_VISITED
            nearby_points = self.nearby(id)

            if len(nearby_points) >= self.minPts:
                group = list()
                group.append(id)
                self.data[id]['is_group'] = self.STATUS_GROUP
                self.visit_nearby_points(nearby_points, group)
                groups.append(group)

        for id in self.data:
            if self.data[id]['is_group'] == self.STATUS_NOGROUP:
                groups.append([id])

        return groups


def init_data(num, min, max):
    data = []
    for i in range(num):
        data.append([random.randint(min, max), random.randint(min, max)])

    return data


def mat_score(data_set):
    score = dict()
    for i in range(len(data_set)):
        score[i] = dict()

    for i in range(len(data_set) - 1):
        j = i + 1
        while j < len(data_set):
            score[i][j] = math.sqrt(abs(data_set[i][0] - data_set[j][0]) ** 2 + abs(data_set[i][1] - data_set[j][1]) ** 2)
            score[j][i] = score[i][j]
            j += 1

    return score


def show_cluster(data_set, groups):
    plt.title(u'DBSCAN')
    mark = ['or', 'ob', 'og', 'ok', '^r', '+r', 'sr', 'dr', '<r', 'pr']
    for index, group in enumerate(groups):
        for i in group:
            plt.plot(data_set[i][0], data_set[i][1], mark[index])

    plt.xlim(0.0, 100)
    plt.ylim(0.0, 100)
    plt.show()


if __name__ == '__main__':
    data_set1 = init_data(20, 0, 30)
    data_set2 = init_data(20, 40, 60)
    data_set3 = init_data(20, 70, 100)
    data_set = data_set1 + data_set2 + data_set3

    score_mat = mat_score(data_set)

    groups = DBSCAN(20, 3).fit(data_set, score_mat)
    show_cluster(data_set, groups)

運(yùn)行結(jié)果:

參考

[1] https://baijiahao.baidu.com/s?id=1666471511374947763
[2] https://www.cnblogs.com/hugechuanqi/p/10509307.html
[3].http://www.itdecent.cn/p/e594c2ce0ac0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容