第九章 聚类

聚类

通过物品特征来计算距离,并自动分类到不同的群集或组中。


层次聚类算法

对于层次聚类算法,我们不需要预先指定分类的数量,这个算方法会将每条数据都当作是一个分类,每次迭代的时候合并距离最近的两个分类,直到剩下一个分类为止。
聚类的结果:顶层有一个大分类,这个分类下有两个子分类,每个子分类下又有两个子分类,依次内推,层级聚类也因此得名。


在合并的时候,我们会计算两个分类之间的距离,可以采用不同的方法,如下图 A、B、C 三个分类,我们可以采取:

单链聚类

在单链聚类中,分类之间的距离由两个分类相距最近的两个元素决定。如上图中分类 A 和分类 B 的距离由 A1 和 B1 的距离决定,因为这个距离小于 A1 到 B2、A2 到 B1 的距离,这样一来我们会将 A 和 B 进行合并。

全链聚类

在全链聚类中,分类之间的距离由两个分类相距最远的两个元素决定。因此上图中分类 A 和 B 的距离是 A2 到 B2 的距离,最后会将分类 B 和 C 进行合并。

平均链接聚类

在平均链接聚类中,分类之间的距离由分类之间两两元素的平均距离决定。因此上图中会将分类 B 和 C 进行合并。

例:用狗的高度和重量来进行聚类


计算欧几里得距离:


计算过程
第一步:找到最近的两个元素,进行聚类

第二部:在找到距离最近你的两个元素,进行聚类:

第三步:重复上面的步骤

算法实现层次聚类

from queue import PriorityQueue
import math
"""
层次聚类示例代码
"""

def getMedian(alist):
    """计算中位数"""
    tmp = list(alist)
    tmp.sort()
    alen = len(tmp)
    if (alen % 2) == 1:
        return tmp[alen // 2]
    else:
        return (tmp[alen // 2] + tmp[(alen // 2) - 1]) / 2

def normalizeColumn(column):
    """计算修正的标准分"""
    median = getMedian(column)
    asd = sum([abs(x - median) for x in column]) / len(column)
    result = [(x - median) / asd for x in column]
    return result

class hClusterer:
    """该聚类器默认数据的第一列是标签,其它列是数值型的特征。"""

    def __init__(self, filename):
        file = open(filename)
        self.data = {}
        self.counter = 0
        self.queue = PriorityQueue()
        lines = file.readlines()
        file.close()
        header = lines[0].split(',')
        self.cols = len(header)
        self.data = [[] for i in range(len(header))]
        for line in lines[1:]:
            cells = line.split(',')
            toggle = 0
            for cell in range(self.cols):
                if toggle == 0:
                    self.data[cell].append(cells[cell])
                    toggle = 1
                else:
                    self.data[cell].append(float(cells[cell]))
        # 标准化特征列(即跳过第一列)
        for i in range(1, self.cols):
            self.data[i] = normalizeColumn(self.data[i])

        ###
        ### 数据已经读入内存并做了标准化,对于每一条记录,将执行以下步骤:
        ### 1. 计算该分类和其他分类的距离,如当前分类的下标是1,
        ### 它和下标为2及下标为3的分类之间的距离用以下形式表示:
        ### {2: ((1, 2), 1.23), 3: ((1, 3), 2.3)... }
        ### 2. 找出距离最近的分类;
        ### 3. 将该分类插入到优先队列中。
        ###

        # 插入队列
        rows = len(self.data[0])

        for i in range(rows):
            for i in range(rows):
                minDistance = 99999
                nearestNeighbor = 0
                neighbors = {}
                for j in range(rows):
                    if i != j:
                        dist = self.distance(i, j)
                        if i < j:
                            pair = (i,j)
                        else:
                            pair = (j,i)
                        neighbors[j] = (pair, dist)
                        if dist < minDistance:
                            minDistance = dist
                            nearestNeighbor = j
                            nearestNum = j
            # 记录这两个分类的配对信息
            if i < nearestNeighbor:
                nearestPair = (i, nearestNeighbor)
            else:
                nearestPair = (nearestNeighbor, i)
            # 插入优先队列
            self.queue.put((minDistance, self.counter,
            [[self.data[0][i]], nearestPair, neighbors]))
            self.counter += 1

    def distance(self, i, j):
        sumSquares = 0
        for k in range(1, self.cols):
            sumSquares += (self.data[k][i] - self.data[k][j])**2
        return math.sqrt(sumSquares)

    def cluster(self):
        done = False
        while not done:
            topOne = self.queue.get()
            nearestPair = topOne[2][1]
            if not self.queue.empty():
                nextOne = self.queue.get()
                nearPair = nextOne[2][1]
                tmp = []

                ## 我从队列中取出了两个元素:topOne和nextOne,
                ## 检查这两个分类是否是一对,如果不是就继续从优先队列中取出元素,
                ## 直至找到topOne的配对分类为止。
                while nearPair != nearestPair:
                    tmp.append((nextOne[0], self.counter, nextOne[2]))
                    self.counter += 1
                    nextOne = self.queue.get()
                    nearPair = nextOne[2][1]
                ## 将不处理的元素退回给优先队列
                for item in tmp:
                    self.queue.put(item)
                if len(topOne[2][0]) == 1:
                    item1 = topOne[2][0][0]
                else:
                    item1 = topOne[2][0]
                if len(nextOne[2][0]) == 1:
                    item2 = nextOne[2][0][0]
                else:
                    item2 = nextOne[2][0]
                ## curCluster即合并后的分类
                curCluster = (item1, item2)

                ## 对于这个新的分类需要做两件事情:首先找到离它最近的分类,然后合并距离字典。
                ## 如果item1和元素23的距离是2,item2和元素23的距离是4,我们取较小的那个距离,即单链聚类。
                minDistance = 99999
                nearestPair = ()
                nearestNeighbor = ''
                merged = {}
                nNeighbors = nextOne[2][2]

                for (key, value) in topOne[2][2].items():
                    if key in nNeighbors:
                        if nNeighbors[key][1] < value[1]:
                            dist = nNeighbors[key]
                        else:
                            dist = value
                        if dist[1] < minDistance:
                            minDistance = dist[1]
                            nearestPair = dist[0]
                            nearestNeighbor = key
                        merged[key] = dist
                if merged == {}:
                    return curCluster
                else:
                    self.queue.put( (minDistance, self.counter, [curCluster, nearestPair, merged]))
                    self.counter += 1

    def printDendrogram(T, sep=3):
        """打印二叉树状图。树的每个节点是一个二元组。这个方法摘自:
        http://code.activestate.com/recipes/139422-dendrogram-drawing/"""

        def isPair(T):
            return type(T) == tuple and len(T) == 2

        def maxHeight(T):
            if isPair(T):
                h = max(maxHeight(T[0]), maxHeight(T[1]))
            else:
                h = len(str(T))
            return h + sep

        activeLevels = {}

        def traverse(T, h, isFirst):
            if isPair(T):
                traverse(T[0], h-sep, 1)
                s = [' ']*(h-sep)
                s.append('|')
            else:
                s = list(str(T))
                s.append(' ')
            while len(s) < h:
                s.append('-')
            if (isFirst >= 0):
                s.append('+')
                if isFirst:
                    activeLevels[h] = 1
                else:
                    del activeLevels[h]
                A = list(activeLevels)
                A.sort()
                for L in A:
                    if len(s) < L:
                        while len(s) < L:
                            s.append(' ')
                        s.append('|')
                print (''.join(s))

                if isPair(T):
                    traverse(T[1], h-sep, 0)
            traverse(T, maxHeight(T), -1)

        filename = '/Users/raz/Dropbox/guide/data/dogs.csv'

        hg = hClusterer(filename)
        cluster = hg.cluster()
        printDendrogram(cluster)

k-means 聚类算法

使用 k-means 算法时需要指定分类的数量,这也是算法名称中“k”的由来。


k-means 聚类过程
  • 1、随机选取 k 个元素作为中心点;
  • 2、根据距离将各个点分配给中心点;
  • 3、计算新的中心点;
  • 4、重复2、3,直至满足条件。

例:将一下点分成两个类:


第一步:随机选取中心点
(1, 4) 作为分类 1 的中心点,(4, 2) 作为分类 2 的中心点;

第二步:将各点分配给中心点(使用曼哈顿距离)

聚类结果:

第三步:更新中心点
通过计算平均值来更新中心点,如 x 轴的均值是:( 1 + 1 + 2 ) / 3 = 4 / 3 = 1.33
y 轴是:( 2 + 4 + 3) / 3 = 9 / 3 = 3
因此分类 1 的中心点是( 1.33, 3 ),计算得分类 2 的中心点是( 4, 2.4 )

第四步 重复第二、第三步,直到中心点不发生变化
得到分类 1 的中心点是( 1.2, 2.75 ),计算得分类 2 的中心点是( 4.5, 2.5 )

误差平方和(SSE)

可以使用误差平方和(或称离散程度)来评判聚类结果的好坏,它的计算方法是:计算每个点到中心点的距离平方和。

第一个求和符号是遍历所有的分类,比如 i = 1 时计算第一个分类,i = 2 计算第二个分类,直到计算第 k 个分类;
第二个求和符号是遍历分类中所有的点;
dist 指代距离计算公式(如曼哈顿距离、欧几里距离);
计算数据点 x 和中心点 cj 之间的距离,平方后相加。

算法实现

import math
import random

"""
K-means算法
"""
 
def getMedian(alist):
    """计算中位数"""
    tmp = list(alist)
    tmp.sort()
    alen = len(tmp)
    if (alen % 2) == 1:
        return tmp[alen // 2]
    else:
        return (tmp[alen // 2] + tmp[(alen // 2) - 1]) / 2

def normalizeColumn(column):
    """计算修正的标准分"""
    median = getMedian(column)
    asd = sum([abs(x - median) for x in column]) / len(column)
    result = [(x - median) / asd for x in column]
    return result

class kClusterer:
    """kMeans聚类算法,第一列是分类,其余列是数值型特征"""

    def __init__(self, filename, k):
        """k是分类的数量,该函数完成以下功能:
        1. 读取filename的文件内容
        2. 按列存储到self.data变量中
        3. 计算修正的标准分
        4. 随机选取起始点
        5. 将各个点分配给中心点
        """
        file = open(filename)
        self.data = {}
        self.k = k
        self.counter = 0
        self.iterationNumber = 0
        # 用于跟踪本次迭代有多少点的分类发生了变动
        self.pointsChanged = 0
        # 误差平方和
        self.sse = 0
        #
        # 读取文件
        #
        lines = file.readlines()
        file.close()
        header = lines[0].split(',')
        self.cols = len(header)
        self.data = [[] for i in range(len(header))]
        # 按列存储数据,如self.data[0]是第一列的数据,
        # self.data[0][10]是第一列第十行的数据。
        for line in lines[1:]:
            cells = line.split(',')
            toggle = 0
            for cell in range(self.cols):
                if toggle == 0:
                    self.data[cell].append(cells[cell])
                    toggle = 1
                else:
                    self.data[cell].append(float(cells[cell]))

        self.datasize = len(self.data[1])
        self.memberOf = [-1 for x in range(len(self.data[1]))]
        #
        # 标准化
        #
        for i in range(1, self.cols):
            self.data[i] = normalizeColumn(self.data[i])

        # 随机选取起始点
        random.seed()
        self.centroids = [[self.data[i][r] for i in range(1, len(self.data))] for r in random.sample(range(len(self.data[0])), self.k)]
        self.assignPointsToCluster()

    def updateCentroids(self):
        """根据分配结果重新确定聚类中心点"""
        members = [self.memberOf.count(i) for i in range(len(self.centroids))]
        self.centroids = [[sum([self.data[k][i] for i in range(len(self.data[0])) if self.memberOf[i] == centroid])/members[centroid] for k in range(1, len(self.data))] for centroid in range(len(self.centroids))]

    def assignPointToCluster(self, i):
        """根据距离计算所属中心点"""
        min = 999999
        clusterNum = -1
        for centroid in range(self.k):
            dist = self.euclideanDistance(i, centroid)
            if dist < min:
                min = dist
                clusterNum = centroid
        # 跟踪变动的点
        if clusterNum != self.memberOf[i]:
            self.pointsChanged += 1
        # 计算距离平方和
        self.sse += min**2
        return clusterNum

    def assignPointsToCluster(self):
        """分配所有的点"""
        self.pointsChanged = 0
        self.sse = 0
        self.memberOf = [self.assignPointToCluster(i) for i in range(len(self.data[1]))]

    def euclideanDistance(self, i, j):
        """计算欧几里得距离"""
        sumSquares = 0
        for k in range(1, self.cols):
            sumSquares += (self.data[k][i] - self.centroids[j][k-1])**2
        return math.sqrt(sumSquares)

    def kCluster(self):
        """开始进行聚类,重复以下步骤:
        1. 更新中心点
        2. 重新分配
        直至变动的点少于1%。
        """

        done = False
        while not done:
            self.iterationNumber += 1
            self.updateCentroids()
            self.assignPointsToCluster()
            #
            # 如果变动的点少于1%则停止迭代
            #
            if float(self.pointsChanged) / len(self.memberOf) < 0.01:
                done = True
        print("Final SSE: %f" % self.sse)

    def showMembers(self):
        """输出结果"""
        for centroid in range(len(self.centroids)):
            print ("\n\nClass %i\n========" % centroid)
            for name in [self.data[0][i] for i in range(len(self.data[0])) if self.memberOf[i] == centroid]:
                    print (name)

# 对犬种数据进行聚类,令k=3
# 请自行修改文件路径
km = kClusterer('../../data/dogs.csv', 3)
km.kCluster()
km.showMembers()

k-means++

k-means 是 50 年代发明的算法,它的实现并不复杂,但它有一个明显的缺点,在算法一开始需要随机选取 k 个起始点,有时选取的点产生最佳结果,而有时会让结果变得很差。k-means++ 则改进了起始点的选取,其余的和 k-means 一致。

k-means++ 选取起始点的过程:

  • 1、随机选取一个点;
  • 2、重复一下步骤,直到选完 k 个点:
      i.计算每个数据点(dp)到各个中心点距离(D),选取最小的值,记为 D(dp);
      ii.根据 D(dp) 的概率来随机选取一个点作为中心点。

起始点选取总结:

第一个点还是随机的,但后续的点就会尽量选择离现有中心点更远的点。

算法实现

第一步

将:

self.centroids = [[self.data[i][r] for i in range(1, len(self.data))] for r in random.sample(range(len(self.data[0])), self.k)]

修改为:

self.selectInitialCentroids()

第二步实现 selectInitialCentroids

def distanceToClosestCentroid(self, point, centroidList):
    result = self.eDistance(point, centroidList[0])
    for centroid in centroidList[1:]:
        distance = self.eDistance(point, centroid)
        if distance < result:
            result = distance
    return result

def selectInitialCentroids(self):
    """实现k-means++算法中的起始点选取过程"""
    centroids = []
    total = 0
    # 首先随机选取一个点
    current = random.choice(range(len(self.data[0])))
    centroids.append(current)
    # 开始选取剩余的点
    for i in range(0, self.k - 1):
        # 计算每个点到最近的中心点的距离
        weights = [self.distanceToClosestCentroid(x, centroids) for x in range(len(self.data[0]))]
        total = sum(weights)
        # 转换为权重
        weights = [x / total for x in weights]
        # 开始随机选取
        num = random.random()
        total = 0
        x = -1
        # 模拟轮盘游戏
        while total < num:
            x += 1
            total += weights[x]
        entroids.append(x)
    self.centroids = [[self.data[i][r] for i in range(1, len(self.data))] for r in centroids]


# -*- coding:utf-8 -*-

'''
Created on 2018年11月29日

@author: KingSley
'''

import math
import random 

"""
K-means++ 算法
"""

def getMedian(alist):
    """计算中位数"""
    tmp = list(alist)
    tmp.sort()
    alen = len(tmp)
    if (alen % 2) == 1:
        return tmp[alen // 2]
    else:
        return (tmp[alen // 2] + tmp[(alen // 2) - 1]) / 2
    
def normalizeColumn(column):
    """计算修正的标准分"""
    median = getMedian(column)
    asd = sum([abs(x - median) for x in column]) / len(column)
    result = [(x - median) / asd for x in column]
    return result

class kClusterer:
    """
    K-means 聚类算法,第一列是分类,其余列是数值型特征
    """
    
    def __init__(self, filename, k):
        """
        k 是分类的数量,该函数完成以下功能:
        1. 读取 filename 的文件内容
        2. 按列存储到 self.data 变量中
        3. 计算修正的标准分
        4. 随机选取起始点
        5. 将各个点分配给中心点
        """
        file = open(filename)
        self.data = {}
        self.k = k
        self.counter = 0
        self.iterationNumber = 0
        # 用于跟踪本次迭代有多少点的分类发生了变动
        self.pointsChanged = 0
        # 误差平方和
        self.sse = 0
        # 读取文件
        lines = file.readlines()
        file.close()
        header = lines[0].split(',')
        self.cols = len(header)
        self.data = [[] for i in range(len(header))]
        # 按列存储数据,如 self.data[0] 是第一列的数据
        # self.data[0][10] 是第一列第十行的数据
        for line in lines[1:]:
            cells = line.split(',')
            toggle = 0
            for cell in range(self.cols):
                if toggle == 0:
                    self.data[cell].append(cells[cell])
                    toggle = 1
                else:
                    self.data[cell].append(float(cells[cell]))
                    
        self.datasize = len(self.data[1])
        self.memberOf = [-1 for x in range(len(self.data[1]))]
        # 标准化
        for i in range(1, self.cols):
            self.data[i] = normalizeColumn(self.data[i])
        # 随机选取起始点
        random.seed()
        self.selectInitialCentroids()
        self.assignPointsToCluster()

    def showData(self):
        for i in range(len(self.data[0])):
            print("%20s   %8.4f  %8.4f" %
                (self.data[0][i], self.data[1][i], self.data[2][i]))

    def distanceToClosestCentroid(self, point, centroidList):
        result = self.eDistance(point, centroidList[0])
        for centroid in centroidList[1:]:
            distance = self.eDistance(point, centroid)
            if distance < result:
                result = distance
        return result
    
    def selectInitialCentroids(self):
        """实现 k-means++ 算法中的起始点选取过程"""
        centroids = []
        total = 0
        # 首先随机选取一个点
        current = random.choice(range(len(self.data[0])))
        centroids.append(current)
        # 开始选取剩余的点
        for i in range(0, self.k - 1):
            # 计算每个点到最近的中心点的距离
            weights = [self.distanceToClosestCentroid(x, centroids) 
                       for x in range(len(self.data[0]))]
            total = sum(weights)
            # 转换为权重
            weights = [x / total for x in weights]
            # 开始随机选取
            num = random.random()
            total = 0
            x = -1
            # 模拟轮盘游戏
            while total < num:
                x += 1
                total += weights[x]
            centroids.append(x)
        self.centroids = [[self.data[i][r]  for i in range(1, len(self.data))]
                            for r in centroids]
        
    def updateCentroids(self):
        """根据分配结果重新确定聚类中心点"""
        members = [self.memberOf.count(i) for i in range(len(self.centroids))]
        
        self.centroids = [[sum([self.data[k][i]
                            for i in range(len(self.data[0]))
                            if self.memberOf[i] == centroid])/members[centroid]
                           for k in range(1, len(self.data))]
                          for centroid in range(len(self.centroids))] 
        
    def assignPointToCluster(self, i):
        """根据距离计算所属中心点"""
        min = 999999
        clusterNum = -1
        for centroid in range(self.k):
            dist = self.euclideanDistance(i, centroid)
            if dist < min:
                min = dist
                clusterNum = centroid
        # 跟踪变动的点
        if clusterNum != self.memberOf[i]:
            self.pointsChanged += 1
        # 计算距离平方和
        self.sse += min**2
        return clusterNum
    
    def assignPointsToCluster(self):
        """分配所有的点"""
        self.pointsChanged = 0
        self.sse = 0
        self.memberOf = [self.assignPointToCluster(i)
                         for i in range(len(self.data[1]))]
    
    def eDistance(self, i, j):
        """点 i 到质心 j 的距离计算"""
        sumSquares = 0
        for k in range(1, self.cols):
            sumSquares += (self.data[k][i] - self.data[k][j])**2
        return math.sqrt(sumSquares)
    
    def euclideanDistance(self, i, j):
        """计算欧几里得距离"""
        sumSquares = 0
        for k in range(1, self.cols):
            sumSquares += (self.data[k][i] - self.centroids[j][k-1])**2
        return math.sqrt(sumSquares)
    
    def kCluster(self):
        """ 开始进行聚类,重复以下步骤:
        1. 更新中心点
        2. 重新分配
        直至变动的点少于 1%
        """
        done = False
 
        while not done:
            self.iterationNumber += 1
            self.updateCentroids()
            self.assignPointsToCluster()
            
            # 如果变动的点少于 1% 则停止迭代
            
            if float(self.pointsChanged) / len(self.memberOf) <  0.01:
                done = True
        print("Final SSE: %f" % self.sse)
        
    def showMembers(self):
        """输出结果"""
        for centroid in range(len(self.centroids)):
            print ("\n\nClass %i\n========" % centroid)
            for name in [self.data[0][i]  for i in range(len(self.data[0]))
                        if self.memberOf[i] == centroid]:
                print (name)

### 对犬种数据进行聚类,令 k = 3
km = kClusterer('dogs.csv', 3)
km.kCluster()
km.showMembers()

参考原文作者:Ron Zacharski CC BY-NC 3.0] https://github.com/egrcc/guidetodatamining

参考原文原文 http://guidetodatamining.com/

参考译文来自 @egrcchttps://github.com/egrcc/guidetodatamining

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容

  • 1. 章节主要内容 “聚类”(clustering)算法是“无监督学习”算法中研究最多、应用最广的算法,它试图将数...
    闪电随笔阅读 5,027评论 1 24
  • 9.1 聚类任务 常见的无监督学习任务 密度估计 异常检测 聚类 聚类任务将数据集划分为若干个不相交的子集,为每一...
    两全丶阅读 1,035评论 0 2
  • 聚类算法 前面介绍的集中算法都是属于有监督机器学习方法,这章和前面不同,介绍无监督学习算法,也就是聚类算法。在无监...
    飘涯阅读 41,288评论 3 52
  • 笔者对于亲人、师徒联袂在戏台上合作出演的剧目情有独钟。无论是父子演母子,带是师徒演父子,抑或如谭富英、姜妙香这样的...
    邹佩华阅读 829评论 0 3
  • 今天,我们上了《咏雪》。 先是让学生指读。指读的目的是读准字音。同学们要借助工具书和课下注释,完成...
    打灯笼的小星星阅读 931评论 0 2