
數(shù)據(jù)挖掘:聚類分析經(jīng)典算法講解及實現(xiàn)
本文將系統(tǒng)的講解數(shù)據(jù)挖掘領(lǐng)域的經(jīng)典聚類算法,并給予代碼實現(xiàn)示例。雖然當(dāng)下已有很多平臺都集成了數(shù)據(jù)挖掘領(lǐng)域的經(jīng)典算法模塊,但筆者認(rèn)為要深入理解算法的核心,剖析算法的執(zhí)行過程,那么通過代碼的實現(xiàn)及運行結(jié)果來進(jìn)行算法的驗證,這樣的過程是很有必要的。因此本文,將有助于讀者對經(jīng)典聚類算法的深入學(xué)習(xí)與理解。
聚類和分類的區(qū)別一開始筆者就想談?wù)勥@個話題,畢竟在數(shù)據(jù)挖掘算法領(lǐng)域,這兩者有著很大的差別,對于初學(xué)者很容易混淆。拋開晦澀的定義陳述,在此我們先通過兩個生活比喻看看什么是監(jiān)督學(xué)習(xí),什么是非監(jiān)督學(xué)習(xí)。
回首我們?nèi)松蠲篮玫亩罐⒛耆A,那時的我們,少年初長成,結(jié)束了小學(xué)生涯,步入初中,這個年齡是我們?nèi)松械谝粋€分水嶺。初中一年級剛?cè)雽W(xué)時,同學(xué)們之間彼此不認(rèn)識,老師對同學(xué)們也都不熟悉。但隨著時間的推移,同學(xué)們基本上都分成了三五群,回想一下那時的我們,是不是整天玩在一起的同學(xué)總是那幾個?我們發(fā)現(xiàn),這個過程老師是不參與的,老師不會讓同學(xué)們分成幾組,讓哪幾個同學(xué)經(jīng)常在一起學(xué)習(xí)和玩耍。想想這個過程,其實是我們自己辨別和哪些同學(xué)合得來一個過程,這期間我們可能會判斷同學(xué)的性格,學(xué)習(xí)成績,共同愛好與話題,是否和同學(xué)家離的很近還能一起上學(xué)和回家等等很多的維度因素。時間久了,班級里就會出現(xiàn)不同的幾個圈子,這個圈子的數(shù)量及細(xì)節(jié)一開始并沒有人知曉,并且這個過程無老師進(jìn)行監(jiān)督,因此我們視之為無監(jiān)督學(xué)習(xí)。在此我們指出,聚類算法是無監(jiān)督學(xué)習(xí)的算法。
初中三年級,因為我們背負(fù)著中考的重?fù)?dān),大家都為了自己的理想高中做最后的沖刺努力。設(shè)想這樣一種情況:為了更好的幫助大家不斷提高學(xué)習(xí)成績,班主任老師將班級分成了五個互幫互助小組(語文、數(shù)學(xué)、物理、生物、英語),每個小組十位同學(xué),分別是班級里這幾個科目考試成績最好的前十名同學(xué),為了達(dá)到更好的互幫互助效果,每位達(dá)到條件要求的同學(xué)只能加入一門科目小組,也就是說,如果某位同學(xué)有兩門或兩門以上的科目都排在班級前十名,則班主任老師隨機指定其加入某一小組。這樣所有同學(xué)都可以在互幫互助小組的幫助下更大程度的提升自己的薄弱科目,實現(xiàn)共贏。在此我們可以看到小組的種類,數(shù)量,都是定義好的,只需要老師指定好各個小組的成員。因此,這樣的學(xué)習(xí)過程是一種監(jiān)督學(xué)習(xí)過程,老師給出小組的種類和數(shù)量用排名的方式來監(jiān)督并激勵學(xué)生學(xué)習(xí)。在此我們指出,分類算法是監(jiān)督學(xué)習(xí)的算法。
總結(jié)一下,數(shù)據(jù)分類是分析已有的數(shù)據(jù),尋找其共同的屬性,并根據(jù)分類模型將這些數(shù)據(jù)劃分成不同的類別,這些數(shù)據(jù)賦予類標(biāo)號。這些類別是事先定義好的,并且類別數(shù)是已知的。相反,數(shù)據(jù)聚類則是將本沒有類別參考的數(shù)據(jù)進(jìn)行分析并劃分為不同的組,即從這些數(shù)據(jù)導(dǎo)出類標(biāo)號。聚類分析本身則是根據(jù)數(shù)據(jù)來發(fā)掘數(shù)據(jù)對象及其關(guān)系信息,并將這些數(shù)據(jù)分組。每個組內(nèi)的對象之間是相似的,而各個組間的對象是不相關(guān)的。不難理解,組內(nèi)相似性越高,組間相異性越高,則聚類越好。
K 均值算法,應(yīng)該是聚類算法中最為基礎(chǔ)但也最為重要的算法。其算法流程如下:
當(dāng)然算法實現(xiàn)的方法有很多,比如在選擇初始質(zhì)心時,可以隨機選擇 k 個,也可以隨機選擇 k 個離得最遠(yuǎn)的點等等,方法不盡相同。
對于 k 值,必須提前知道,這也是 kmeans 算法的一個缺點。當(dāng)然對于 k 值,我們可以有很多種方法進(jìn)行估計。本文中,我們采用平均直徑法來進(jìn)行 k 的估計。
也就是說,首先視所有的點為一個大的整體 cluster,計算所有點之間距離的平均值作為該 cluster 的平均直徑。選擇初始質(zhì)心的時候,先選擇最遠(yuǎn)的兩個點,接下來從這最兩個點開始,與這最兩個點距離都很遠(yuǎn)的點(遠(yuǎn)的程度為,該點到之前選擇的最遠(yuǎn)的兩個點的距離都大于整體 cluster 的平均直徑)可視為新發(fā)現(xiàn)的質(zhì)心,否則不視之為質(zhì)心。設(shè)想一下,如果利用平均半徑或平均直徑這一個指標(biāo),若我們猜想的 K 值大于或等于真實的 K 值,也就是簇的真實數(shù)目,那么該指標(biāo)的上升趨勢會很緩慢,但是如果我們給出的 K 值小于真實的簇的數(shù)目時,這個指標(biāo)一定會急劇上升。
根據(jù)這樣的估算思想,我們就能估計出正確的 k 值,并且得到 k 個初始質(zhì)心,接著,我們便根據(jù)上述算法流程繼續(xù)進(jìn)行迭代,直到所有質(zhì)心都不變化,從而成功實現(xiàn)算法。如下圖所示:
我們知道 k 均值總是收斂的,也就是說,k 均值算法一定會達(dá)到一種穩(wěn)定狀態(tài),在此狀態(tài)下,所有的點都不會從一個簇轉(zhuǎn)移到另一個簇,因此質(zhì)心不在發(fā)生改變。在此,我們引出一個剪枝優(yōu)化,即:k 均值最明顯的收斂過程會發(fā)生在算法運行的前期階段,故在某些情況下為了增加算法的執(zhí)行效率,我們可以替換上述算法的第五步,采用“迭代至僅有 1%~3%的點在影響質(zhì)心”或“迭代至僅有 1%~3%的點在改變簇”。
k 均值適用于絕大多數(shù)的數(shù)據(jù)類型,并且簡單有效。但其缺點就是需要知道準(zhǔn)確的 k 值,并且不能處理異形簇,比如球形簇,不同尺寸及密度的簇,環(huán)形簇等等。
本文主要為算法講解及實現(xiàn),因此代碼實現(xiàn)暫不考慮面向?qū)ο笏枷?,采用面向過程的實現(xiàn)方式,如果數(shù)據(jù)多維,可能會需要做數(shù)據(jù)預(yù)處理,比如歸一化,并且修改代碼相關(guān)方法即可。
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Comparator; import java.util.PriorityQueue; import java.util.Queue; public class Kmeans { private class Node { int label;// label 用來記錄點屬于第幾個 cluster double[] attributes; public Node() { attributes = new double[100]; } } private class NodeComparator { Node nodeOne; Node nodeTwo; double distance; public void compute() { double val = 0; for (int i = 0; i < dimension; ++i) { val += (this.nodeOne.attributes[i] - this.nodeTwo.attributes[i]) * (this.nodeOne.attributes[i] - this.nodeTwo.attributes[i]); } this.distance = val; } } private ArrayList<Node> arraylist; private ArrayList<Node> centroidList; private double averageDis; private int dimension; private Queue<NodeComparator> FsQueue = new PriorityQueue<NodeComparator>(150, // 用來排序任意兩點之間的距離,從大到小排 new Comparator<NodeComparator>() { public int compare(NodeComparator one, NodeComparator two) { if (one.distance < two.distance) return 1; else if (one.distance > two.distance) return -1; else return 0; } }); public void setKmeansInput(String path) { try { BufferedReader br = new BufferedReader(new FileReader(path)); String str; String[] strArray; arraylist = new ArrayList<Node>(); while ((str = br.readLine()) != null) { strArray = str.split(","); dimension = strArray.length; Node node = new Node(); for (int i = 0; i < dimension; ++i) { node.attributes[i] = Double.parseDouble(strArray[i]); } arraylist.add(node); } br.close(); } catch (IOException e) { e.printStackTrace(); } } public void computeTheK() { int cntTuple = 0; for (int i = 0; i < arraylist.size() - 1; ++i) { for (int j = i + 1; j < arraylist.size(); ++j) { NodeComparator nodecomp = new NodeComparator(); nodecomp.nodeOne = new Node(); nodecomp.nodeTwo = new Node(); for (int k = 0; k < dimension; ++k) { nodecomp.nodeOne.attributes[k] = arraylist.get(i).attributes[k]; nodecomp.nodeTwo.attributes[k] = arraylist.get(j).attributes[k]; } nodecomp.compute(); averageDis += nodecomp.distance; FsQueue.add(nodecomp); cntTuple++; } } averageDis /= cntTuple;// 計算平均距離 chooseCentroid(FsQueue); } public double getDistance(Node one, Node two) {// 計算兩點間的歐氏距離 double val = 0; for (int i = 0; i < dimension; ++i) { val += (one.attributes[i] - two.attributes[i]) * (one.attributes[i] - two.attributes[i]); } return val; } public void chooseCentroid(Queue<NodeComparator> queue) { centroidList = new ArrayList<Node>(); boolean flag = false; while (!queue.isEmpty()) { boolean judgeOne = false; boolean judgeTwo = false; NodeComparator nc = FsQueue.poll(); if (nc.distance < averageDis) break;// 如果接下來的元組,兩節(jié)點間距離小于平均距離,則不繼續(xù)迭代 if (!flag) { centroidList.add(nc.nodeOne);// 先加入所有點中距離最遠(yuǎn)的兩個點 centroidList.add(nc.nodeTwo); flag = true; } else {// 之后從之前已加入的最遠(yuǎn)的兩個點開始,找離這兩個點最遠(yuǎn)的點, // 如果距離大于所有點的平均距離,則認(rèn)為找到了新的質(zhì)心,否則不認(rèn)定為質(zhì)心 for (int i = 0; i < centroidList.size(); ++i) { Node testnode = centroidList.get(i); if (centroidList.contains(nc.nodeOne) || getDistance(testnode, nc.nodeOne) < averageDis) { judgeOne = true; } if (centroidList.contains(nc.nodeTwo) || getDistance(testnode, nc.nodeTwo) < averageDis) { judgeTwo = true; } } if (!judgeOne) { centroidList.add(nc.nodeOne); } if (!judgeTwo) { centroidList.add(nc.nodeTwo); } } } } public void doIteration(ArrayList<Node> centroid) { int cnt = 1; int cntEnd = 0; int numLabel = centroid.size(); while (true) {// 迭代,直到所有的質(zhì)心都不變化為止 boolean flag = false; for (int i = 0; i < arraylist.size(); ++i) { double dis = 0x7fffffff; cnt = 1; for (int j = 0; j < centroid.size(); ++j) { Node node = centroid.get(j); if (getDistance(arraylist.get(i), node) < dis) { dis = getDistance(arraylist.get(i), node); arraylist.get(i).label = cnt; } cnt++; } } int j = 0; numLabel -= 1; while (j < numLabel) { int c = 0; Node node = new Node(); for (int i = 0; i < arraylist.size(); ++i) { if (arraylist.get(i).label == j + 1) { for (int k = 0; k < dimension; ++k) { node.attributes[k] += arraylist.get(i).attributes[k]; } c++; } } DecimalFormat df = new DecimalFormat("#.###");// 保留小數(shù)點后三位 double[] attributelist = new double[100]; for (int i = 0; i < dimension; ++i) { attributelist[i] = Double.parseDouble(df.format(node.attributes[i] / c)); if (attributelist[i] != centroid.get(j).attributes[i]) { centroid.get(j).attributes[i] = attributelist[i]; flag = true; } } if (!flag) { cntEnd++; if (cntEnd == numLabel) {// 若所有的質(zhì)心都不變,則跳出循環(huán) break; } } j++; } if (cntEnd == numLabel) {// 若所有的質(zhì)心都不變,則 success System.out.println("run kmeans successfully."); break; } } } public void printKmeansResults(String path) { try { PrintStream out = new PrintStream(path); computeTheK(); doIteration(centroidList); out.println("There are " + centroidList.size() + " clusters!"); for (int i = 0; i < arraylist.size(); ++i) { out.print("("); for (int j = 0; j < dimension - 1; ++j) { out.print(arraylist.get(i).attributes[j] + ", "); } out.print(arraylist.get(i).attributes[dimension - 1] + ") "); out.println("belongs to cluster " + arraylist.get(i).label); } out.close(); System.out.println("Please check results in: " + path); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { Kmeans kmeans = new Kmeans(); kmeans.setKmeansInput("c:/kmeans.txt"); kmeans.printKmeansResults("c:/kmeansResults.txt"); } }
給出一組簡單的二維測試數(shù)據(jù):
1,1 2,1 1,2 2,2 6,1 6,2 7,1 7,2 1,5 1,6 2,5 2,6 6,5 6,6 7,5 7,6
There are 4 clusters! (1.0, 1.0) belongs to cluster 1 (2.0, 1.0) belongs to cluster 1 (1.0, 2.0) belongs to cluster 1 (2.0, 2.0) belongs to cluster 1 (6.0, 1.0) belongs to cluster 3 (6.0, 2.0) belongs to cluster 3 (7.0, 1.0) belongs to cluster 3 (7.0, 2.0) belongs to cluster 3 (1.0, 5.0) belongs to cluster 4 (1.0, 6.0) belongs to cluster 4 (2.0, 5.0) belongs to cluster 4 (2.0, 6.0) belongs to cluster 4 (6.0, 5.0) belongs to cluster 2 (6.0, 6.0) belongs to cluster 2 (7.0, 5.0) belongs to cluster 2 (7.0, 6.0) belongs to cluster 2
凝聚式層次聚類,就是在初始階段將每一個點都視為一個簇,之后每一次合并兩個最接近的簇,當(dāng)然對于接近程度的定義則需要指定簇的鄰近準(zhǔn)則。
分裂式層次聚類,就是在初始階段將所有的點視為一個簇,之后每次分裂出一個簇,直到最后剩下單個點的簇為止。
本文中我們將詳細(xì)介紹凝聚式層次聚類算法。
對于凝聚式層次聚類,指定簇的鄰近準(zhǔn)則是非常重要的一個環(huán)節(jié),在此我們介紹三種最常用的準(zhǔn)則,分別是 MAX, MIN, 組平均。如下圖所示:
凝聚式層次聚類算法也是一個迭代的過程,算法流程如下:
下面我們看一個例子:
下圖是一個有五個點的而為坐標(biāo)系:
下表為這五個點的歐式距離矩陣:
|
|||||
---|---|---|---|---|---|
|
P1 | P2 | P3 | P4 | P5 |
P1 | 0 | 0.81 | 1.32 | 1.94 | 1.82 |
P2 | 0.81 | 0 | 1.56 | 2.16 | 1.77 |
P3 | 1.32 | 1.56 | 0 | 0.63 | 0.71 |
P4 | 1.94 | 2.16 | 0.63 | 0 | 0.71 |
P5 | 1.82 | 1.77 | 0.71 | 0.71 | 0 |
根據(jù)算法流程,我們先找出距離最近的兩個簇,P3, P4。
合并 P3, P4 為 {P3, P4},根據(jù) MIN 原則更新矩陣如下:
MIN.distance({P3, P4}, P1) = 1.32;
MIN.distance({P3, P4}, P2) = 1.56;
MIN.distance({P3, P4}, P5) = 0.70;
|
||||
---|---|---|---|---|
|
P1 | P2 | {P3, P4} | P5 |
P1 | 0 | 0.81 | 1.32 | 1.82 |
P2 | 0.81 | 0 | 1.56 | 1.77 |
{P3, P4} | 1.32 | 1.56 | 0 | 0.71 |
P5 | 1.82 | 1.77 | 0.71 | 0 |
接著繼續(xù)找出距離最近的兩個簇,{P3, P4}, P5。
合并 {P3, P4}, P5 為 {P3, P4, P5},根據(jù) MIN 原則繼續(xù)更新矩陣:
MIN.distance(P1, {P3, P4, P5}) = 1.32;
MIN.distance(P2, {P3, P4, P5}) = 1.56;
|
|||
---|---|---|---|
|
P1 | P2 | {P3, P4, P5} |
P1 | 0 | 0.81 | 1.32 |
P2 | 0.81 | 0 | 1.56 |
{P3, P4, P5} | 1.32 | 1.56 | 0 |
接著繼續(xù)找出距離最近的兩個簇,P1, P2。
合并 P1, P2 為 {P1, P2},根據(jù) MIN 原則繼續(xù)更新矩陣:
MIN.distance({P1,P2}, {P3, P4, P5}) = 1.32
|
||
---|---|---|
|
{P1, P2} | {P3, P4, P5} |
{P1, P2} | 0 | 1.32 |
{P3, P4, P5} | 1.32 | 0 |
最終合并剩下的這兩個簇即可獲得最終結(jié)果,如下圖:
MAX,組平均算法流程同理,只是在更新矩陣時將上述計算簇間距離變?yōu)榇亻g兩點最大歐式距離,和簇間所有點平均歐式距離即可。
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.text.DecimalFormat; import java.util.ArrayList; public class Hierarchical { private double[][] matrix; private int dimension;// 數(shù)據(jù)維度 private class Node { double[] attributes; public Node() { attributes = new double[100]; } } private ArrayList<Node> arraylist; private class Model { int x = 0; int y = 0; double value = 0; } private Model minModel = new Model(); private double getDistance(Node one, Node two) {// 計算兩點間的歐氏距離 double val = 0; for (int i = 0; i < dimension; ++i) { val += (one.attributes[i] - two.attributes[i]) * (one.attributes[i] - two.attributes[i]); } return Math.sqrt(val); } private void loadMatrix() {// 將輸入數(shù)據(jù)轉(zhuǎn)化為矩陣 for (int i = 0; i < matrix.length; ++i) { for (int j = i + 1; j < matrix.length; ++j) { double distance = getDistance(arraylist.get(i), arraylist.get(j)); matrix[i][j] = distance; } } } private Model findMinValueOfMatrix(double[][] matrix) {// 找出矩陣中距離最近的兩個簇 Model model = new Model(); double min = 0x7fffffff; for (int i = 0; i < matrix.length; ++i) { for (int j = i + 1; j < matrix.length; ++j) { if (min > matrix[i][j] && matrix[i][j] != 0) { min = matrix[i][j]; model.x = i; model.y = j; model.value = matrix[i][j]; } } } return model; } private void processHierarchical(String path) { try { PrintStream out = new PrintStream(path); while (true) {// 凝聚層次聚類迭代 out.println("Matrix update as below: "); for (int i = 0; i < matrix.length; ++i) {// 輸出每次迭代更新的矩陣 for (int j = 0; j < matrix.length - 1; ++j) { out.print(new DecimalFormat("#.00").format(matrix[i][j]) + " "); } out.println(new DecimalFormat("#.00").format(matrix[i][matrix.length - 1])); } out.println(); minModel = findMinValueOfMatrix(matrix); if (minModel.value == 0) {// 當(dāng)找不出距離最近的兩個簇時,迭代結(jié)束 break; } out.println("Combine " + (minModel.x + 1) + " " + (minModel.y + 1)); out.println("The distance is: " + minModel.value); matrix[minModel.x][minModel.y] = 0;// 更新矩陣 for (int i = 0; i < matrix.length; ++i) {// 如果合并了點 p1 與 p2,則只保留 p1,p2 其中之一與其他點的距離,取較小值 if (matrix[i][minModel.x] <= matrix[i][minModel.y]) { matrix[i][minModel.y] = 0; } else { matrix[i][minModel.x] = 0; } if (matrix[minModel.x][i] <= matrix[minModel.y][i]) { matrix[minModel.y][i] = 0; } else { matrix[minModel.x][i] = 0; } } } out.close(); System.out.println("Please check results in: " + path); } catch (Exception e) { e.printStackTrace(); } } public void setInput(String path) { try { BufferedReader br = new BufferedReader(new FileReader(path)); String str; String[] strArray; arraylist = new ArrayList<Node>(); while ((str = br.readLine()) != null) { strArray = str.split(","); dimension = strArray.length; Node node = new Node(); for (int i = 0; i < dimension; ++i) { node.attributes[i] = Double.parseDouble(strArray[i]); } arraylist.add(node); } matrix = new double[arraylist.size()][arraylist.size()]; loadMatrix(); br.close(); } catch (IOException e) { e.printStackTrace(); } } public void printOutput(String path) { processHierarchical(path); } public static void main(String[] args) { Hierarchical hi = new Hierarchical(); hi.setInput("c:/hierarchical.txt"); hi.printOutput("c:/hierarchical_results.txt"); } }
給出一組簡單的二維測試數(shù)據(jù)
0.7,1.2 0.8,2 2,1 2.6,0.8 2.5,1.5
Matrix update as below: .00 .81 1.32 1.94 1.82 .00 .00 1.56 2.16 1.77 .00 .00 .00 .63 .71 .00 .00 .00 .00 .71 .00 .00 .00 .00 .00 Combine 3 4 The distance is: 0.6324555320336759 Matrix update as below: .00 .81 1.32 .00 1.82 .00 .00 1.56 .00 1.77 .00 .00 .00 .00 .00 .00 .00 .00 .00 .71 .00 .00 .00 .00 .00 Combine 4 5 The distance is: 0.7071067811865475 Matrix update as below: .00 .81 1.32 .00 .00 .00 .00 1.56 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 Combine 1 2 The distance is: 0.806225774829855 Matrix update as below: .00 .00 1.32 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 Combine 1 3 The distance is: 1.3152946437965907 Matrix update as below: .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00 .00
考慮一種情況,點的分布不均勻,形狀不規(guī)則時,Kmeans 算法及層次聚類算法將面臨失效的風(fēng)險。
如下坐標(biāo)系:
我們可以看到上面的點密度不均勻,這時我們考慮采用基于密度的聚類算法:DBSCAN。
如上圖坐標(biāo)系所示,我們設(shè)定掃描半徑 Eps 為 1.5,密度閾值 threshold 為 3,則通過上述算法過程,我們可以得到下圖:
通過計算各個點之間的歐式距離及其所在掃描半徑內(nèi)的密度值來判斷這些點屬于核心點,邊界點或是噪聲點。因為我們設(shè)定了掃描半徑為 1.5,密度閾值為 3,所以:
import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; public class DBSCAN { private int dimension;// 數(shù)據(jù)維度 private double eps = 1.5; private int threshold = 3; private double distance[][]; private Map<Integer, Integer> id = new HashMap<Integer, Integer>(); private int countClusters = 0; private ArrayList<Integer> keyPointList = new ArrayList<Integer>();// private int[] flags;// 標(biāo)記邊緣點 private class Edge { int p, q; double weight; } private class Node { double[] attributes; public Node() { attributes = new double[100]; } } private ArrayList<Node> nodeList; private ArrayList<Edge> edgeList; private double getDistance(Node one, Node two) {// 計算兩點間的歐氏距離 double val = 0; for (int i = 0; i < dimension; ++i) { val += (one.attributes[i] - two.attributes[i]) * (one.attributes[i] - two.attributes[i]); } return Math.sqrt(val); } public void loadEdges() {// 給所有在掃描半徑內(nèi)的核心點之間加邊,標(biāo)記邊界點并且自動忽略噪聲點 edgeList = new ArrayList<Edge>(); flags = new int[nodeList.size()]; int[] countPoint = new int[nodeList.size()]; for (int i = 0; i < countPoint.length; ++i) { countPoint[i] = 1;// 每一個點一開始都是核心點 } for (int i = 0; i < nodeList.size(); ++i) { for (int j = i + 1; j < nodeList.size(); ++j) { distance[i][j] = getDistance(nodeList.get(i), nodeList.get(j)); if (distance[i][j] <= eps) {// 兩點間距離小于掃描半徑 countPoint[i]++; if (countPoint[i] > 0 && countPoint[i] < threshold) { flags[i] = j;// 記錄邊界點 } if (countPoint[i] >= threshold) {// 如果記錄當(dāng)前點的掃描半徑內(nèi)密度值大于或等于給定閾值 flags[i] = 0; if (!keyPointList.contains(i)) { keyPointList.add(i); } } countPoint[j]++; if (countPoint[j] > 0 && countPoint[j] < threshold) { flags[j] = i;// 記錄邊界點 } if (countPoint[j] >= threshold) {// 如果記錄當(dāng)前點的掃描半徑內(nèi)密度值大于或等于給定
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
LSTM 模型輸入長度選擇技巧:提升序列建模效能的關(guān)鍵? 在循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)家族中,長短期記憶網(wǎng)絡(luò)(LSTM)憑借其解決長序列 ...
2025-07-11CDA 數(shù)據(jù)分析師報考條件詳解與準(zhǔn)備指南? ? 在數(shù)據(jù)驅(qū)動決策的時代浪潮下,CDA 數(shù)據(jù)分析師認(rèn)證愈發(fā)受到矚目,成為眾多有志投身數(shù) ...
2025-07-11數(shù)據(jù)透視表中兩列相乘合計的實用指南? 在數(shù)據(jù)分析的日常工作中,數(shù)據(jù)透視表憑借其強大的數(shù)據(jù)匯總和分析功能,成為了 Excel 用戶 ...
2025-07-11尊敬的考生: 您好! 我們誠摯通知您,CDA Level I和 Level II考試大綱將于 2025年7月25日 實施重大更新。 此次更新旨在確保認(rèn) ...
2025-07-10BI 大數(shù)據(jù)分析師:連接數(shù)據(jù)與業(yè)務(wù)的價值轉(zhuǎn)化者? ? 在大數(shù)據(jù)與商業(yè)智能(Business Intelligence,簡稱 BI)深度融合的時代,BI ...
2025-07-10SQL 在預(yù)測分析中的應(yīng)用:從數(shù)據(jù)查詢到趨勢預(yù)判? ? 在數(shù)據(jù)驅(qū)動決策的時代,預(yù)測分析作為挖掘數(shù)據(jù)潛在價值的核心手段,正被廣泛 ...
2025-07-10數(shù)據(jù)查詢結(jié)束后:分析師的收尾工作與價值深化? ? 在數(shù)據(jù)分析的全流程中,“query end”(查詢結(jié)束)并非工作的終點,而是將數(shù) ...
2025-07-10CDA 數(shù)據(jù)分析師考試:從報考到取證的全攻略? 在數(shù)字經(jīng)濟蓬勃發(fā)展的今天,數(shù)據(jù)分析師已成為各行業(yè)爭搶的核心人才,而 CDA(Certi ...
2025-07-09【CDA干貨】單樣本趨勢性檢驗:捕捉數(shù)據(jù)背后的時間軌跡? 在數(shù)據(jù)分析的版圖中,單樣本趨勢性檢驗如同一位耐心的偵探,專注于從單 ...
2025-07-09year_month數(shù)據(jù)類型:時間維度的精準(zhǔn)切片? ? 在數(shù)據(jù)的世界里,時間是最不可或缺的維度之一,而year_month數(shù)據(jù)類型就像一把精準(zhǔn) ...
2025-07-09CDA 備考干貨:Python 在數(shù)據(jù)分析中的核心應(yīng)用與實戰(zhàn)技巧? ? 在 CDA 數(shù)據(jù)分析師認(rèn)證考試中,Python 作為數(shù)據(jù)處理與分析的核心 ...
2025-07-08SPSS 中的 Mann-Kendall 檢驗:數(shù)據(jù)趨勢與突變分析的有力工具? ? ? 在數(shù)據(jù)分析的廣袤領(lǐng)域中,準(zhǔn)確捕捉數(shù)據(jù)的趨勢變化以及識別 ...
2025-07-08備戰(zhàn) CDA 數(shù)據(jù)分析師考試:需要多久?如何規(guī)劃? CDA(Certified Data Analyst)數(shù)據(jù)分析師認(rèn)證作為國內(nèi)權(quán)威的數(shù)據(jù)分析能力認(rèn)證 ...
2025-07-08LSTM 輸出不確定的成因、影響與應(yīng)對策略? 長短期記憶網(wǎng)絡(luò)(LSTM)作為循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)的一種變體,憑借獨特的門控機制,在 ...
2025-07-07統(tǒng)計學(xué)方法在市場調(diào)研數(shù)據(jù)中的深度應(yīng)用? 市場調(diào)研是企業(yè)洞察市場動態(tài)、了解消費者需求的重要途徑,而統(tǒng)計學(xué)方法則是市場調(diào)研數(shù) ...
2025-07-07CDA數(shù)據(jù)分析師證書考試全攻略? 在數(shù)字化浪潮席卷全球的當(dāng)下,數(shù)據(jù)已成為企業(yè)決策、行業(yè)發(fā)展的核心驅(qū)動力,數(shù)據(jù)分析師也因此成為 ...
2025-07-07剖析 CDA 數(shù)據(jù)分析師考試題型:解鎖高效備考與答題策略? CDA(Certified Data Analyst)數(shù)據(jù)分析師考試作為衡量數(shù)據(jù)專業(yè)能力的 ...
2025-07-04SQL Server 字符串截取轉(zhuǎn)日期:解鎖數(shù)據(jù)處理的關(guān)鍵技能? 在數(shù)據(jù)處理與分析工作中,數(shù)據(jù)格式的規(guī)范性是保證后續(xù)分析準(zhǔn)確性的基礎(chǔ) ...
2025-07-04CDA 數(shù)據(jù)分析師視角:從數(shù)據(jù)迷霧中探尋商業(yè)真相? 在數(shù)字化浪潮席卷全球的今天,數(shù)據(jù)已成為企業(yè)決策的核心驅(qū)動力,CDA(Certifie ...
2025-07-04CDA 數(shù)據(jù)分析師:開啟數(shù)據(jù)職業(yè)發(fā)展新征程? ? 在數(shù)據(jù)成為核心生產(chǎn)要素的今天,數(shù)據(jù)分析師的職業(yè)價值愈發(fā)凸顯。CDA(Certified D ...
2025-07-03