手撸Golang 基本数据结构与算法 k-means聚类算法

开发 前端 算法
最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)本系列笔记拟采用golang练习之k-means聚类算法。

缘起

最近阅读<<我的第一本算法书>>(【日】石田保辉;宫崎修一)

本系列笔记拟采用golang练习之

k-means聚类算法

聚类就是在输入为多个数据时, 将“相似”的数据分为一组的操作。 k-means算法是聚类算法中的一种。 首先随机选择k个点作为簇的中心点, 然后重复执行“将数据分到相应的簇中”和 “将中心点移到重心的位置”这两个操作, 直到中心点不再发生变化为止。 k-means算法中,随着操作的不断重复, 中心点的位置必定会在某处收敛, 这一点已经在数学层面上得到证明。 摘自 <<我的第一本算法书>> 【日】石田保辉;宫崎修一

场景

  • 某地突然爆发新冠疫情, 现防疫急需根据病例分布, 查找可能的病源地
  • 首先将病例分布的坐标, 录入系统
  • 然后根据k-means算法, 按k从1到3, 分别进行聚类
  • 聚类的中心点, 可能就是病源地

流程

  1. 给定若干样本, 和样本距离计算器, 需要求解k个样本中心点
  2. 首先从样本中随机抽取k个点, 作为中心点
  3. 循环每个样本

    1. 分别计算样本点到k个中心点的距离
    2. 判断距离样本点最小的中心点
    3. 将样本划分到该最小中心点的簇
  4. 计算每个簇的中心点, 作为新的中心点

    1. 循环簇中的每个样本
    2. 计算该样本, 到本簇其他样本的距离之和
    3. 与其他样本的距离和最小的点, 就是新的中心点
  5. 重复3-4, 直到中心点不再变化, 计算完毕

设计

  • IPoint: 样本点接口, 其实是一个空接口
  • IDistanceCalculator: 距离计算器接口
  • IClassifier: 分类器接口, 将samples聚类成k个, 并返回k个中心点
  • tPerson: 病例样本点, 实现IPoint接口, 含x,y坐标
  • tPersonDistanceCalculator: 病例距离计算器, 计算两点间x,y坐标的直线距离
  • tKMeansClassifier: k-means聚类器, 实现IClassifier接口.

单元测试

k_means_test.go

  1. package others 
  2.  
  3. import ( 
  4.     km "learning/gooop/others/k_means" 
  5.     "strings" 
  6.     "testing" 
  7.  
  8. func Test_KMeans(t *testing.T) { 
  9.     // 创建样本点 
  10.     samples := []km.IPoint { 
  11.         km.NewPerson(211), 
  12.         km.NewPerson(28), 
  13.         km.NewPerson(26), 
  14.  
  15.         km.NewPerson(312), 
  16.         km.NewPerson(310), 
  17.  
  18.         km.NewPerson(47), 
  19.         km.NewPerson(43), 
  20.  
  21.         km.NewPerson(511), 
  22.         km.NewPerson(59), 
  23.         km.NewPerson(52), 
  24.  
  25.         km.NewPerson(79), 
  26.         km.NewPerson(76), 
  27.         km.NewPerson(73), 
  28.  
  29.         km.NewPerson(812), 
  30.  
  31.         km.NewPerson(93), 
  32.         km.NewPerson(95), 
  33.         km.NewPerson(910), 
  34.  
  35.         km.NewPerson(103), 
  36.         km.NewPerson(106), 
  37.         km.NewPerson(1012), 
  38.  
  39.         km.NewPerson(119), 
  40.     } 
  41.  
  42.     fnPoints2String := func(points []km.IPoint) string { 
  43.         items := make([]string, len(points)) 
  44.         for i,it := range points { 
  45.             items[i] = it.String() 
  46.         } 
  47.         return strings.Join(items, " "
  48.     } 
  49.  
  50.     for k:=1;k<=3;k++ { 
  51.         centers := km.KMeansClassifier.Classify(samples, km.PersonDistanceCalculator, k) 
  52.         t.Log(fnPoints2String(centers)) 
  53.     } 

测试输出

  1. $ go test -v k_means_test.go  
  2. === RUN   Test_KMeans 
  3.     k_means_test.go:53: p(7,6
  4.     k_means_test.go:53: p(5,9) p(7,3
  5.     k_means_test.go:53: p(9,10) p(3,10) p(7,3
  6. --- PASS: Test_KMeans (0.00s) 
  7. PASS 
  8. ok      command-line-arguments  0.002s 

IPoint.go

样本点接口, 其实是一个空接口

  1. package km 
  2.  
  3. import "fmt" 
  4.  
  5. type IPoint interface { 
  6.     fmt.Stringer 

IDistanceCalculator.go

距离计算器接口

  1. package km 
  2.  
  3. type IDistanceCalculator interface { 
  4.     Calc(a, b IPoint) int 

IClassifier.go

分类器接口, 将samples聚类成k个, 并返回k个中心点

  1. package km 
  2.  
  3. type IClassifier interface { 
  4.     // 将samples聚类成k个, 并返回k个中心点 
  5.     Classify(samples []IPoint, calc IDistanceCalculator, k int) []IPoint 

tPerson.go

病例样本点, 实现IPoint接口, 含x,y坐标

  1. package km 
  2.  
  3. import "fmt" 
  4.  
  5. type tPerson struct { 
  6.     x int 
  7.     y int 
  8.  
  9. func NewPerson(x, y int) IPoint { 
  10.     return &tPerson{x, y, } 
  11.  
  12. func (me *tPerson) String() string { 
  13.     return fmt.Sprintf("p(%v,%v)", me.x, me.y) 

tPersonDistanceCalculator.go

病例距离计算器, 计算两点间x,y坐标的直线距离

  1. package km 
  2.  
  3.  
  4. type tPersonDistanceCalculator struct { 
  5.  
  6. var gMaxInt = 0x7fffffff_ffffffff 
  7.  
  8. func newPersonDistanceCalculator() IDistanceCalculator { 
  9.     return &tPersonDistanceCalculator{} 
  10.  
  11. func (me *tPersonDistanceCalculator) Calc(a, b IPoint) int { 
  12.     if a == b { 
  13.         return 0 
  14.     } 
  15.  
  16.     p1, ok := a.(*tPerson) 
  17.     if !ok { 
  18.         return gMaxInt 
  19.     } 
  20.  
  21.     p2, ok := b.(*tPerson) 
  22.     if !ok { 
  23.         return gMaxInt 
  24.     } 
  25.  
  26.     dx := p1.x - p2.x 
  27.     dy := p1.y - p2.y 
  28.  
  29.     d := dx*dx + dy*dy 
  30.     if d < 0 { 
  31.         panic(d) 
  32.     } 
  33.     return d 
  34.  
  35. var PersonDistanceCalculator = newPersonDistanceCalculator() 

tKMeansClassifier.go

k-means聚类器, 实现IClassifier接口.

  1. package km 
  2.  
  3. import ( 
  4.     "math/rand" 
  5.     "time" 
  6.  
  7. type tKMeansClassifier struct { 
  8.  
  9. type tPointEntry struct { 
  10.     point IPoint 
  11.     distance int 
  12.     index int 
  13.  
  14. func newPointEntry(p IPoint, d int, i int) *tPointEntry { 
  15.     return &tPointEntry{ 
  16.         p, d, i, 
  17.     } 
  18.  
  19. func newKMeansClassifier() IClassifier { 
  20.     return &tKMeansClassifier{} 
  21.  
  22. // 将samples聚类成k个, 并返回k个中心点 
  23. func (me *tKMeansClassifier) Classify(samples []IPoint, calc IDistanceCalculator, k int) []IPoint { 
  24.     sampleCount := len(samples) 
  25.     if sampleCount <= k { 
  26.         return samples 
  27.     } 
  28.  
  29.     // 初始化, 随机选择k个中心点 
  30.     rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 
  31.     centers := make([]IPoint, k) 
  32.     for selected, i:= make(map[int]bool, 0), 0;i < k; { 
  33.         n := rnd.Intn(sampleCount) 
  34.         _,ok := selected[n] 
  35.  
  36.         if !ok { 
  37.             selected[n] = true 
  38.             centers[i] = samples[n] 
  39.             i++ 
  40.         } 
  41.     } 
  42.  
  43.  
  44.     // 根据到中心点的距离, 划分samples 
  45.     for { 
  46.         groups := me.split(samples, centers, calc) 
  47.  
  48.         newCenters := make([]IPoint, k) 
  49.         for i,g := range groups { 
  50.             newCenters[i] = me.centerOf(g, calc) 
  51.         } 
  52.  
  53.         if me.groupEquals(centers, newCenters) { 
  54.             return centers 
  55.         } 
  56.         centers = newCenters 
  57.     } 
  58.  
  59. // 将样本点距离中心点的距离进行分簇 
  60. func (me *tKMeansClassifier) split(samples []IPoint, centers []IPoint, calc IDistanceCalculator) [][]IPoint { 
  61.     k := len(centers) 
  62.     result := make([][]IPoint, k) 
  63.     for i := 0;i<k;i++ { 
  64.         result[i] = make([]IPoint, 0
  65.     } 
  66.  
  67.     entries := make([]*tPointEntry, k) 
  68.     for i,c := range centers { 
  69.         entries[i] = newPointEntry(c, 0, i) 
  70.     } 
  71.  
  72.     for _,p := range samples { 
  73.         for _,e := range entries { 
  74.             e.distance = calc.Calc(p, e.point) 
  75.         } 
  76.  
  77.         center := me.min(entries) 
  78.         result[center.index] = append(result[center.index], p) 
  79.     } 
  80.  
  81.     return result 
  82.  
  83. // 计算一簇样本的重心. 重心就是距离各点的总和最小的点 
  84. func (me *tKMeansClassifier) centerOf(samples []IPoint, calc IDistanceCalculator) IPoint { 
  85.     entries := make([]*tPointEntry, len(samples)) 
  86.     for i,src := range samples { 
  87.         distance := 0 
  88.         for _,it := range samples { 
  89.             distance += calc.Calc(src, it) 
  90.         } 
  91.         entries[i] = newPointEntry(src, distance, i) 
  92.     } 
  93.  
  94.     return me.min(entries).point 
  95.  
  96. // 判断两组点是否相同 
  97. func (me *tKMeansClassifier) groupEquals(g1, g2 []IPoint) bool { 
  98.     if len(g1) != len(g2) { 
  99.         return false 
  100.     } 
  101.  
  102.     for i,v := range g1 { 
  103.         if g2[i] != v { 
  104.             return false 
  105.         } 
  106.     } 
  107.  
  108.     return true 
  109.  
  110. // 查找距离最小的点 
  111. func (me *tKMeansClassifier) min(entries []*tPointEntry) *tPointEntry { 
  112.     minI := 0 
  113.     minD := gMaxInt 
  114.     for i,it := range entries { 
  115.         if it.distance < minD { 
  116.             minI = i 
  117.             minD = it.distance 
  118.         } 
  119.     } 
  120.  
  121.     return entries[minI] 
  122.  
  123.  
  124. var KMeansClassifier = newKMeansClassifier() 

 

 

 

责任编辑:张燕妮 来源: Go语言中文网
相关推荐

2012-08-09 09:57:54

K-means

2024-04-18 15:44:20

2020-12-31 05:31:01

数据结构算法

2023-03-08 08:03:09

数据结构算法归并排序

2017-09-12 16:57:43

机器学习K-means算法Python

2020-10-21 14:57:04

数据结构算法图形

2023-10-27 07:04:20

2023-04-27 09:13:20

排序算法数据结构

2023-03-10 08:07:39

数据结构算法计数排序

2023-03-02 08:15:13

2018-04-25 08:10:50

算法k-means代码

2021-05-12 09:07:09

Java数据结构算法

2023-03-07 08:02:07

数据结构算法数列

2020-10-12 11:48:31

算法与数据结构

2019-03-29 09:40:38

数据结构算法前端

2017-08-31 09:45:43

JavaArrayList数据

2023-11-06 06:43:23

单链表查询数据结构

2023-09-15 10:33:41

算法数据结构

2023-03-13 10:08:31

数据结构算法

2023-02-08 07:52:36

跳跃表数据结构
点赞
收藏

51CTO技术栈公众号