1. 程式人生 > >Enjoy the pleasure in the ocean of big data

Enjoy the pleasure in the ocean of big data

birch簡述

birch全名利用層次方法的平衡迭代規約和聚類。
birch只需要單遍掃描資料集就可以進行聚類,它最小化IO,天生來應對大資料。brich是通過聚類特徵樹(CF-tree/ClusterFeature-tree)實現的,單遍掃描資料集後建立一棵存放於記憶體中的CF-tree,可以看作是資料的多層壓縮。

聚類特徵

每一個CF是一個三元組,可以用(N,LS,SS)表示。其中N代表這個CF中擁有的樣本點的數量;LS代表了這個CF中擁有的樣本點各特徵維度的和向量,SS代表了這個CF中擁有的樣本點各特徵維度的平方和。
CF有一個很好的性質,就是滿足線性關係,也就是CF1+CF2=(N1+N2,LS1+LS2,SS1+SS2)。
一棵CF樹,還需要幾個重要引數:
B:內部節點平衡因子,每個內部節點的子節點最大個數
L:葉子節點平衡因子,每個葉子節點的子節點最大個數
T:簇直徑閾值,每個簇直徑最大閾值,超過後,簇分裂

聚類特徵樹構造

一個聚類特徵樹樣例:
CF-tree

  1. 演算法最初,掃描資料集,拿到第一個資料點,建立一個空的Leaf和MinCluster,MinCluster座位Leaf的一個孩子。
  2. 當後續點需要插入樹中時,把這個點封裝為一個MinCluster,把新到的資料點記為CF_new,從樹的根節點開始,根據D2(歐式距離)來找到CF_new與那個節點最近,就把CF_new加入那個子樹上面去。這是一個遞迴的過程。遞迴的終止點是要把CF_new加入到一個MinCluster中,如果加入之後MinCluster的直徑沒有超過T,則直接加入,否則CF_new要單獨作為一個簇,成為MinCluster的兄弟結點。插入之後注意更新該節點及其所有祖先節點的CF值。
  3. 插入新節點後,可能有些節點的孩子數大於了B(或L),此時該節點要分裂對於Leaf,它現在有L+1個MinCluster,我們要新建立一個Leaf,使它作為原Leaf的兄弟結點,同時注意每新建立一個Leaf都要把它插入到雙向連結串列中。L+1個MinCluster要分到這兩個Leaf中,怎麼分呢?找出這L+1個MinCluster中距離最遠的兩個Cluster(根據D2),剩下的Cluster看離哪個近就跟誰站在一起。分好後更新兩個Leaf的CF值,其祖先節點的CF值沒有變化,不需要更新。這可能導致祖先節點的遞迴分裂,因為Leaf分裂後恰好其父節點的孩子數超過了B。

程式碼描述

private
ClusteringFeature buildCFTree() { NonLeafNode rootNode = null; LeafNode leafNode = null; Cluster cluster = null; for (String[] record : totalDataRecords) { cluster = new Cluster(record); if (rootNode == null) { // CF樹只有1個節點的時候的情況 if (leafNode == null) { leafNode = new LeafNode(); } leafNode.addingCluster(cluster); if (leafNode.getParentNode() != null) { rootNode = leafNode.getParentNode(); } } else { if (rootNode.getParentNode() != null) { rootNode = rootNode.getParentNode(); } // 從根節點開始,從上往下尋找到最近的新增目標葉子節點 LeafNode temp = rootNode.findedClosestNode(cluster); temp.addingCluster(cluster); } } // 從下往上找出最上面的節點,返回根節點 LeafNode node = cluster.getParentNode(); NonLeafNode upNode = node.getParentNode(); if (upNode == null) { return node; } else { while (upNode.getParentNode() != null) { upNode = upNode.getParentNode(); } return upNode; } }

可以看出,插入過程是從根節點開始找到最近的新增目標葉子節點,然後呼叫葉子節點的addingCluster方法把節點新增到樹中。
leafNode的addingCluster方法

public void addingCluster(ClusteringFeature clusteringFeature) {
        //更新聚類特徵值
        directAddCluster(clusteringFeature);

        // 尋找到的目標叢集
        Cluster findedCluster = null;
        Cluster cluster = (Cluster) clusteringFeature;

        double disance = Integer.MAX_VALUE;
        double errorDistance = 0;
        boolean needDivided = false;
        if (clusterChilds == null) {
            clusterChilds = new ArrayList<>();
            clusterChilds.add(cluster);
            cluster.setParentNode(this);
        } else {
            for (Cluster c : clusterChilds) {
                errorDistance = ClusteringFeature.computerClusterDistance(c, cluster);
                if (errorDistance < disance) {
                    // 選出簇間距離最近的
                    disance = errorDistance;
                    findedCluster = c;
                }
            }

            ArrayList<double[]> data1 = (ArrayList<double[]>) findedCluster.getData().clone();
            ArrayList<double[]> data2 = cluster.getData();
            data1.addAll(data2);
            // 如果新增後的聚類的簇間距離超過給定閾值,需要額外新建簇
            if (ClusteringFeature.computerInClusterDistance(data1) > BIRCHTool.T) {
                // 如果新增後簇的簇間距離超過T,當前簇作為新的簇
                clusterChilds.add(cluster);
                cluster.setParentNode(this);
                // 葉子節點的孩子數不能超過平衡因子L
                if (clusterChilds.size() > BIRCHTool.L) {
                    needDivided = true;
                }
            } else {
                findedCluster.directAddCluster(cluster);
                cluster.setParentNode(this);
            }
        }

        if(needDivided){
            if(parentNode == null){
                parentNode = new NonLeafNode();
            }else{
                parentNode.getLeafChilds().remove(this);
            }

            LeafNode[] nodeArray = divideLeafNode();
            for(LeafNode n: nodeArray){
                parentNode.addingCluster(n);
            }
        }
    }

先找到距離最近的簇,把當前簇新增到最近的簇中,如果新增後簇的簇間距離超過T,當前簇作為新的簇,如果葉子節點的孩子數超過平衡因子L,則葉子節點需要分裂。分裂後分裂為2個葉子節點,然後呼叫非葉子節點的addingCluster方法,依次向上更新父節點。
NonLeafNode的addingCluster方法:

public void addingCluster(ClusteringFeature clusteringFeature) {
        LeafNode leafNode = null;
        NonLeafNode nonLeafNode = null;
        NonLeafNode[] nonLeafNodeArrays;
        boolean neededDivide = false;
        // 更新聚類特徵值
        directAddCluster(clusteringFeature);

        if (clusteringFeature instanceof LeafNode) {
            leafNode = (LeafNode) clusteringFeature;
        } else {
            nonLeafNode = (NonLeafNode) clusteringFeature;
        }

        if (nonLeafNode != null) {
            neededDivide = addingNeededDivide(nonLeafNode);

            if (neededDivide) {
                if (parentNode == null) {
                    parentNode = new NonLeafNode();
                } else {
                    parentNode.nonLeafChilds.remove(this);
                }

                nonLeafNodeArrays = this.nonLeafNodeDivided();
                for (NonLeafNode n1 : nonLeafNodeArrays) {
                    parentNode.addingCluster(n1);
                }
            }
        } else {
            neededDivide = addingNeededDivide(leafNode);

            if (neededDivide) {
                if (parentNode == null) {
                    parentNode = new NonLeafNode();
                } else {
                    parentNode.nonLeafChilds.remove(this);
                }

                nonLeafNodeArrays = this.leafNodeDivided();
                for (NonLeafNode n2 : nonLeafNodeArrays) {
                    parentNode.addingCluster(n2);
                }
            }
        }
    }