k-means算法是一种简单的迭代型聚类算法,采用距离作为相似性指标,从而发现给定数据集中的K个类,且每个类的中心是根据类中所有值的均值得到,每个类用聚类中心来描述。对于给定的一个包含n个d维数据点的数据集X以及要分得的类别K,选取欧式距离作为相似度指标,聚类目标是使得各类的聚类平方和最小,即最小化:
结合最小二乘法和拉格朗日原理,聚类中心为对应类别中各数据点的平均值,同时为了使得算法收敛,在迭代过程中,应使最终的聚类中心尽可能的不变。
聚类过程
- 首先任取k个样本点作为k个簇的初始中心
- 对每一个样本点,计算它们与k个中心的距离,把它归入距离最小的中心所在的簇
- 等到所有的样本点归类完毕,重新计算k个簇的中心
- 重复以上过程直至样本点归入的簇不再变动或变动范围极小
可视化演示:
- 在每次完成聚类之后,生成图片并保存。
- 最后生成一个k-means.html,其中使用JavaScript逐秒绘制过程图
聚类过程:
不同的颜色代表一个类,黑点代表聚簇点
第一次迭代(随机选点,还未聚类)
进行迭代,更新聚类点
最后一次迭代(按点已经完成聚类)
由于数据太多,可以在k-means.html中查看变化过程
结果演示:
- 输出聚簇点
- 可视化演示
代码:使用了github.com/muesli/kmeans库,我对它的注释进行了翻译,对程序进行了部分修改,方便可视化演示
package main import ( "io" "os" "log" "fmt" "bufio" "io/ioutil" "strings" "strconv" "github.com/leeli73/go-kmeans-html-plotter/clusters" "github.com/leeli73/go-kmeans-html-plotter/kmeans" ) // 读取数据 func InitData() clusters.Observations{ fi, err := os.Open("data/iris.dat") if err != nil { log.Fatalln(err) return nil } defer fi.Close() var d clusters.Observations br := bufio.NewReader(fi) for { a, _, c := br.ReadLine() if c == io.EOF { break } temp := string(a) if temp[0] != '@'{ data := strings.Split(temp,", ") num1,_ := strconv.ParseFloat(data[0], 64) num2,_ := strconv.ParseFloat(data[1], 64) num3,_ := strconv.ParseFloat(data[2], 64) num4,_ := strconv.ParseFloat(data[3], 64) d = append(d,clusters.Coordinates{ num1, num2, num3, num4, }) } } return d } func main() { d := InitData() //定义一个k-means km, _ := kmeans.New(0.01, kmeans.SimplePlotter{}) //进行聚类运算 clusters,files, _ := km.Partition(d, 4) //生成演示html strfiles := """ + files[0] + ""," for i:=1;i<len(files) - 1;i++{ strfiles = strfiles + """ + files[i] + ""," } strfiles = strfiles + """ + files[len(files)-1] + """ clusterout := []string{} for i, c := range clusters { log.Printf("Cluster: %d %+v", i, c.Center) str := fmt.Sprintf("Cluster: %d %+v", i, c.Center) clusterout = append(clusterout,str) } clustersstr := """ + clusterout[0] + ""," for i:=1;i<len(clusterout)-1;i++{ clustersstr = clustersstr + """ + clusterout[i] + ""," } clustersstr = clustersstr + """ +clusterout[len(clusterout)-1] + """ var Data,err = ioutil.ReadFile("data/web.html") if err != nil{ log.Fatal(err) } html := string(Data) html = strings.Replace(html,"{{images}}",strfiles,-1) html = strings.Replace(html,"{{clusters}}",clustersstr,-1) ioutil.WriteFile("k-means.html",[]byte(html), 0644) }
k-means.html
<!DOCTYPE html> <html lang="en"> <head> <title>Bootstrap 4 Website Example</title> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/css/bootstrap.min.css"> <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script> <script src="https://cdn.staticfile.org/popper.js/1.12.5/umd/popper.min.js"></script> <script src="https://cdn.staticfile.org/twitter-bootstrap/4.1.0/js/bootstrap.min.js"></script> </head> <body> <div class="container" style="margin-top:30px"> <div class="row"> <div class="col-sm-6"> <h2>聚类点</h2> <div style="height: 500px"> <textarea style=" 100%; height: 100%" id="clusters"></textarea> </div> </div> <div class="col-sm-6"> <h2>k-means聚类过程</h2> <div style="height: 500px"> <canvas id="show" style=" 100%;height: 100%"></canvas> </div> </div> </div> </div> </body> <script> Images = [{{images}}] Clusters = [{{clusters}}] window.onload = function () { var CANVAS = document.getElementById('show'); context = CANVAS.getContext('2d'); var ratio = getPixelRatio(context) var img = new Image(); SetClusters() img.onload = function () { context.drawImage(img, 0, 0, 300 * ratio,150 * ratio); } var count = 0 var interval = setInterval(function(){ if(count < Images.length) { img.src = Images[count] } else { clearInterval(interval); return } count = count + 1 },1000) } function getPixelRatio(context) { var backingStore = context.backingStorePixelRatio || context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1; return (window.devicePixelRatio || 1) / backingStore; } function SetClusters(){ for(var i=0;i<Clusters.length;i++) { document.getElementById("clusters").value = document.getElementById("clusters").value + Clusters[i] + ' ' } } </script> </html>
项目地址:https://github.com/leeli73/go-kmeans-html-plotter.git
kmeans项目地址:https://github.com/muesli/kmeans.git