大家早上好,本人姓吴,如果觉得文章写得还行的话也可以叫我吴老师。欢迎大家跟我一起走进数据分析的世界,一起学习!

感兴趣的朋友可以关注我或者我的数据分析专栏,里面有许多优质的文章跟大家分享哦。


目录

  • 必看前言
  • 无监督学习算法
    • 1 聚类与分类
    • 2 K-Means算法
      • 2.1 K-Means的基本原理
        • 2.1.1 K-Means 是如何工作的?
        • 2.1.2 簇内误差平方和的定义
      • 2.2 K-Means算法的python实现
        • 2.2.1 导入数据集
        • 2.2.2 编写距离计算函数
        • 2.2.3 编写随机生成质心函数
        • 2.2.4 编写 K-Means 聚类函数
        • 2.2.5 算法验证
  • 结束语

必看前言

今天这一篇文章,将跟大家分享一下无监督学习算法。

而本文将试图用简单易懂的语言来讲说到底什么是无监督学习算法,同时主要会以KMeans算法为例,跟大家详细地说明。

无监督学习算法

决策树、线性和逻辑回归都是比较常用的机器学习算法,他们虽然有着不同的功能,但却都属于“有监督学习” 的⼀部分,即是说,模型在训练的时候,即需要特征矩阵X,也需要真实标签y。机器学习当中,还有相当⼀部分算法属于 “无监督学习” ,无监督的算法在训练的时候只需要特征矩阵X,不需要标签。无监督学习的代表算法有聚类算法、降维算法。

聚类算法又叫做“无监督分类”,其目的是将数据划分成有意义或有用的组(或簇)。这种划分可以基于我们的业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。

比如在商业中,如果我们手头有大量的当前和潜在客户的信息,我们可以使用聚类将客户划分为若干组,以便进一步分析和开展营销活动,最有名的客户价值判断模型RFM(Recency Frequency Monetary),就常常和聚类分析共同使用。再比如,聚类可以用于降维和矢量量化(vectorquantization),可以将高维特征压缩到一列当中,常常用于图像,声音,视频等非结构化数据,可以大幅度压缩数据量。

1 聚类与分类

  聚类 分类
核心 将数据分成多个组,探索每个组的数据是否有联系 从已经分组的数据中去学习,把新数据放到已经分好的组中
学习类型 无监督,无需标签进行训练 有监督,需要标签进行训练
典型算法 K-Means, DBSCAN, 层次聚类,光谱聚类 决策树,贝叶斯,逻辑回归
算法输出 聚类结果是不确定的,不一定总是能够反映数据的真实分类,同样的分类根据不同的业务需求可能是好结果也可能是坏结果 分类结果是确定的,分类的优劣是客观的,不是根据业务或算法需求来决定的

聚类算法是无监督类机器学习算法中最常用的⼀类,其目的是将数据划分成有意义或有用的组(也被称为簇)。这种划分可以基于我们的业务需求或建模需求来完成,也可以单纯地帮助我们探索数据的自然结构和分布。如果目标是划分成有意义的组,则簇应当捕获数据的自然结构。然而,在某种意义下,聚类分析只是解决其他问题(如数据汇总)的起点。无论是旨在理解还是应用,聚类分析都在广泛的领域扮演着重要角色。这些领域包括:心理学和其他社会科学、生物学、统计学、模式识别、信息检索、机器学习和数据挖掘。

那么接下来,我会详细讲解无监督学习算法中的K-Means算法,来借此帮助大家了解K-Means算法。

2 K-Means算法

2.1 K-Means的基本原理

2.1.1 K-Means 是如何工作的?

  • 关键概念:簇和质心

KMeans 算法将一组 N 个样本的特征矩阵 X 划分为 K 个无交集的簇,直观上来看是簇是一组一组聚集在一起的数据,在一个簇中的数据就认为是同一类。簇就是聚类的结果表现。

簇中所有数据的均值通常被称为这个簇的“质心”(centroids)。在一个二维平面中,一簇数据点的质心的横坐标就是这一簇数据点的横坐标的均值,质心的纵坐标就是这一簇数据点的纵坐标的均值。同理可推广至高维空间。

在 KMeans 算法中,簇的个数 K 是一个超参数,需要我们人为输入来确定。KMeans 的核心任务就是根据我们设定好的 K,找出 K 个最优的质心,并将离这些质心最近的数据分别分配到这些质心代表的簇中去。

具体过程可以总结如下:

  1. 创建 k 个点作为初始质心(通常是随机选择)
  2. 当任意一个点的簇分配结果发生改变时:
    ◼ 计算质心与数据点之间的距离
    ◼ 将数据点分配到据其最近的簇
  3. 对每个簇,计算簇中所有点的均值并将均值作为新的质心
  4. 直到簇不再发生变化或者达到最大迭代次数

那什么情况下,质心的位置会不再变化呢?

当我们找到一个质心,在每次迭代中被分配到这个质心上的样本都是一致的,即每次新生成的簇都是一致的,所有的样本点都不会再从一个簇转移到另一个簇,质心就不会变化了。

下面我们可以看到例题:

我们先来看下思路(其实也就是上面提到的):

然后看下解题过程:

总之,我感觉K-Means这一整个流程还是非常容易理解和实现的。

那接下来,我们来讲一下聚类算法聚出的类有什么含义。

2.1.2 簇内误差平方和的定义

聚类算法聚出的类有什么含义?这些类有什么样的性质?我们认为,被分在同一个簇中的数据是有相似性的,而不同簇中的数据是不同的,当聚类完毕之后,我们就要分别去研究每个簇中的样本都有什么样的性质,从而根据业务需求制定不同的商业或者科技策略。

聚类算法的目的就是追求“簇内差异小,簇外差异大”(圈起来,下次要考)。而这个“差异“,由样本点到其所在簇的质心的距离来衡量。

对于一个簇来说,所有样本点到质心的距离之和越小,我们就认为这个簇中的样本越相似,簇内差异就越小。而距离的衡量方法有多种,令:
◼ x 表示簇中的一个样本点;
◼ μ表示该簇中的质心;
◼ n 表示每个样本点中的特征数目;
◼ i 表示组成点 x 的每个特征编号;

则该样本点到质心的距离可以由以下距离来度量:

  • 欧几里得距离:d(x,u)=∑i=1n(xi−ui)2d(x,u)=\sqrt{}{\sum^{n}_{i=1}{(x_i-u_i)^2}}d(x,u)=​∑i=1n​(xi​−ui​)2
  • 曼哈顿距离:d(x,u)=∑i=1n(∣xi−ui∣)d(x,u)=\sum^{n}_{i=1}{(|x_i- u_i|)}d(x,u)=∑i=1n​(∣xi​−ui​∣)
  • 余弦距离:cosθ=∑1n(i∗u)∑1n(xi)2∗∑1n(ui)2cos\theta=\frac{\sum^n_1(_i*u)}{\sqrt{}{\sum^n_1(x_i)^2}*\sqrt{}{\sum^n_1(u_i)^2}}cosθ=​∑1n​(xi​)2∗​∑1n​(ui​)2∑1n​(i​∗u)​

如我们采用欧几里得距离,则一个簇中所有样本点到质心的距离的平方和为:ClusterSumofSquare(CSS)=∑j=0m∑i=1n(xi−ui)2Cluster \, Sum \, of \, Square(CSS)=\sum^{m}_{j=0}\sum^{n}_{i=1}{(x_i-u_i)^2}ClusterSumofSquare(CSS)=j=0∑m​i=1∑n​(xi​−ui​)2◼ 其中,m 为一个簇中样本的个数;
◼ j 是每个样本的编号;

这个公式被称为簇内平方和(cluster Sum of Square),又叫做 Inertia。

而将一个数据集中的所有簇的簇内平方和相加,就得到了整体平方和(Total Cluster Sum of
Square),又叫做total inertia:TotalClusterSumofSquare=∑i=1kCSSlTotal \, Cluster \, Sum \, of \, Square=\sum^{k}_{i=1}{CSS_l}TotalClusterSumofSquare=i=1∑k​CSSl​Total Inertia 越小,代表着每个簇内样本越相似,聚类的效果就越好。(记住,后期会考!)

因此 KMeans 追求的是,求解能够让 Inertia 最小化的质心。

实际上,在质心不断变化不断迭代的过程中,总体平方和是越来越小的。当整体平方和最小的时候,质心就不再发生变化了

大家可以发现,我们的 Inertia 是基于欧几里得距离的计算公式得来的。实际上,我们也可以使用其他距离,每个距离都有自己对应的 Inertia。在过去的经验中,我们总结出不同距离所对应的质心选择方法和 Inertia,在Kmeans 中,只要使用了正确的质心和距离组合,无论使用什么样的距离,都可以达到不错的聚类效果:

距离度量 质心 Inertia
欧几里得距离 均值 最小化每个样本点到之心的欧氏距离之和
曼哈顿距离 中位值 最小化每个样本点到之心的曼哈顿距离之和
余弦距离 均值 最小化每个样本点到之心的余弦之和

而这些组合,都可以由严格的数学证明来推导。在实际中我们往往都使用欧式距离,因此我们也无需去担忧这些距离所搭配的质心选择是如何得来的了。

2.2 K-Means算法的python实现

那同样的,老规矩,我们尝试用Python来实现Kmeans算法。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all" # 解决坐标轴刻度负号乱码
plt.rcParams['axes.unicode_minus'] = False# 解决中文乱码问题
plt.rcParams['font.sans-serif'] = ['Simhei']

2.2.1 导入数据集

此处先以经典的鸢尾花数据集为例,来帮助我们建模,数据存放在 iris.txt 中。

import numpy as np
import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
%matplotlib inline
#导入数据集
iris = pd.read_csv("iris.txt",header = None)
iris


这里最后一类呢是标签,不过我们不需要,后面训练时记得不要取到最后一列。

2.2.2 编写距离计算函数

我们需要定义一个两个长度相等的数组之间欧式距离计算函数,在不直接应用计算距离计算结果,只比较距离远近的情况下,我们可以用距离平方和代替距离进行比较,化简开平方运算,从而减少函数计算量。此外需要说明的是,涉及到距离计算的,一定要注意量纲的统一。如果量纲不统一的话,模型极易偏向量纲大的那一方。此处选用鸢尾花数据集,基本不需要考虑量纲问题。

  • 函数功能:计算两个数据集之间的欧式距离
  • 输入:两个 array 数据集
  • 返回:两个数据集之间的欧氏距离(此处用距离平方和代替距离)
def distEclud(arrA, arrB):d = arrA - arrBdist = np.sum(np.power(d,2), axis=1)return dist

2.2.3 编写随机生成质心函数

在定义随机质心生成函数时,首先需要计算每列数值的范围,然后从该范围中随机生成指定个数的质心。此处我们使用 numpy.random.uniform()函数生成随机质心。

  • 函数功能:随机生成 k 个质心
  • 参数说明:
    dataSet:包含标签的数据集
    k:簇的个数
  • 返回:
    data_cent:K 个质心
def randCent(dataSet, k):n = dataSet.shape[1]                                         #  n为列数,iris一共5列data_min = dataSet.iloc[:,:n-1].min()                       # 前4列,每一列最小值data_max = dataSet.iloc[:,:n-1].max()                       # 前4列,每一列最大值data_cent = np.random.uniform(data_min,data_max,(k, n-1))    # 均匀分布中抽样,形状为(k, n-1)return data_cent
iris_cent = randCent(iris, 3)
iris_cent

2.2.4 编写 K-Means 聚类函数

这一部分相对来说比较麻烦一点,python基础不是很好的朋友也不用太过在意,了解为主,而且之后也会介绍如何利用sklearn实现K-Means算法。

在执行 K-Means 的时候,需要不断的迭代质心,因此我们需要两个可迭代容器来完成该目标:

第一个容器用于存放和更新质心,该容器可考虑使用 list 来执行,list 不仅是可迭代对象,同时 list内不同元素索引位置也可用于标记和区分各质心,即各簇的编号;即代码中的 centroids。

第二个容器则需要记录、保存和更新各点到质心之间的距离,并能够方便对其进行比较,该容器考虑使用一个三列的数组来执行,其中:

  • 第一列用于存放最近一次计算完成后某点到各质心的最短距离。
  • 第二列用于记录最近一次计算完成后根据最短距离得到的代表对应质心的数值索引,即所属簇,即质心的编号。
  • 第三列用于存放上一次某点所对应质心编号(某点所属簇),后两列用于比较质心发生变化后某点所属簇的情况是否发生变化。

函数功能:k-均值聚类算法

参数说明:

  • dataSet:带标签数据集
  • k:簇的个数
  • distMeas:距离计算函数
  • createCent:随机质心生成函数

返回:

  • centroids:质心
  • result_set:所有数据划分结果
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):    # iris为150*5m,n = dataSet.shape                                             # m是行数(数据量),n是列数iris为150*5# 下面生成的centroids,即第一个容器,后面用来存储最新更新的质心centroids = createCent(dataSet, k)                              # centroids为3*4,用三个长度为4的一维数组记载3个质心# 第一次centroids是随机生成的# 这段生成的result_set,即第二个容器# result_set结构: [数据集,  该行到最近质心的距离, 本次迭代中最近质心编号,上次迭代中最近质心编号]clusterAssment = np.zeros((m,3))            # clusterAssment为150*3clusterAssment[:, 0] = np.inf               # np.inf为无穷大clusterAssment[:, 1: 3] = -1                # 此时clusterAssment为150*3result_set = pd.concat([dataSet, pd.DataFrame(clusterAssment)], axis=1,ignore_index = True)     # result_set为150*8clusterChanged = Truewhile clusterChanged:clusterChanged = Falsefor i in range(m):                      # 遍历result_set中每一行,一共m行# 小心,下面的n为5,而resulit_set的列数已经变成8dist = distMeas(dataSet.iloc[i, :n-1].values, centroids)       # 第i行与三个质心的距离,dist为3*1result_set.iloc[i, n] = dist.min()                             # result_set[i,n]记录该行与3个质心的最小距离result_set.iloc[i, n+1] = np.where(dist == dist.min())[0]      # result_set[i,n]记录最近质心的索引clusterChanged = not (result_set.iloc[:, -1] == result_set.iloc[:,-2]).all()    # 只要result_set最后两列不完全相等,意味着本次for循环结束时,m行所有的新质心与上次while循环留下的不完全一样# 后果:clusterChanged为True,while继续循环# clusterChanged为True,则需要运行下面的if语句代码块,重置第一个容器centroids和第二个容器result_setif clusterChanged:cent_df = result_set.groupby(n+1).mean()               # 按照列索引为n+1(质心索引)(第6列)进行分组求均值# 即:按照最新的簇分类,计算最新3个质心的位置centroids = cent_df.iloc[:,:n-1].values                # 重置centroids,用最新质心位置,替换上次的。3*4result_set.iloc[:, -1] = result_set.iloc[:, -2]        # result_set最后一列,本次的簇分类编码,替换掉上次的return centroids, result_set

鸢尾花数据集带进去,查看模型运行效果:

iris_cent,iris_result = kMeans(iris, 3)
iris_cent

iris_result.head()


以上代码编写时,有以下几点需要特别注意:

  • 设置统一的操作对象 result_set
    为了调用和使用的方便,此处将 clusterAssment 转换为了 DataFrame 并与输入 DataFrame 合并,组成的对象可作为后续调用的统一对象,该对象内即保存了原始数据,也保存了迭代运算的中间结果,包括数据所属簇标记和数据质心距离等,该对象同时也作为最终函数的返回结果;
  • 判断质心发生是否发生改变条件
    注意,在 K-Means 中判断质心是否发生改变,即判断是否继续进行下一步迭代的依据并不是某点距离新的质心距离变短,而是某点新的距离向量(到各质心的距离)中最短的分量位置是否发生变化,即质心变化后某点是否应归属另外的簇。在质心变化导致各点所属簇发生变化的过程中,点到质心的距离不一定会变短,即判断条件不能用下述语句表示
if not (result_set.iloc[:, -1] == result_set.iloc[:, -2]).all()
  • 质心和类别一一对应
    即在最后生成的结果中,centroids 的行标即为 result_set 中各点所属类别。

2.2.5 算法验证

函数编写完成后,先以 testSet 数据集测试模型运行效果(为了可以直观看出聚类效果,此处采用一个二维数据集进行验证)。testSet 数据集是一个二维数据集,每个观测值都只有两个特征,且数据之间采用空格进行分隔,因此可采用 pd.read_table()函数进行读取。

testSet = pd.read_csv(r"testSet.txt", sep='\t')
testSet.head()
testSet.shape


然后利用二维平面图形观察其分布情况:

plt.scatter(testSet.iloc[:,0], testSet.iloc[:,1]);


可以大概看出数据大概分布在空间的四个角上,后续我们将对此进行验证。然后利用我们刚才编写的 K-Means 算法对其进行聚类,在执行算法之前需要添加一列虚拟标签列(算法是从倒数第二列开始计算特征值,因此这里需要人为增加多一列到最后)

label = pd.DataFrame(np.zeros(testSet.shape[0]).reshape(-1, 1))
test_set = pd.concat([testSet, label], axis=1, ignore_index = True)
test_set.head()


然后带入算法进行计算,根据二维平面坐标点的分布特征,我们可考虑设置四个质心,即将其分为四个簇,并简单查看运算结果:

test_cent, test_cluster = kMeans(test_set, 4)
test_cent
test_cluster.head()


将分类结果进行可视化展示,使用 scatter 函数绘制不同分类点不同颜色的散点图,同时将质心也放入同一张图中进行观察:

plt.scatter(test_cluster.iloc[:,0], test_cluster.iloc[:, 1],c=test_cluster.iloc[:, -1])
plt.scatter(test_cent[:, 0], test_cent[:, 1], color='red',marker='x',s=100);


从图的结果来看,结果还是非常符合我们预期的。

结束语

那么到这里,关于我们无监督学习及K-Means算法的介绍先告一段落啦。在下一篇文章中,我会介绍如何利用sklearn玩转K-Means算法,以及无监督算法模型如何评估,感兴趣的朋友可以关注我下面的专栏啦。

推荐关注的专栏

以《简单易懂》的语言带你搞懂无监督学习算法【附Python代码详解】机器学习系列之K-Means篇相关推荐

  1. python 最小二乘回归 高斯核_从简单数学建模开始:08最小二乘准则的应用(附python代码)...

    模型拟合一般来说有这么三种: 切比雪夫近似准则 极小化绝对偏差之和 最小二乘准则 这几个原则各有各的适用范围.其中最小二乘准则是比较容易计算的.接下来我将简要的介绍最小二乘准则以及举例说明如何用pyt ...

  2. LeetCode刷题复盘笔记—一文搞懂完全背包之322. 零钱兑换问题(动态规划系列第十四篇)

    今日主要总结一下动态规划完全背包的一道题目,322. 零钱兑换 题目:322. 零钱兑换 Leetcode题目地址 题目描述: 给你一个整数数组 coins ,表示不同面额的硬币:以及一个整数 amo ...

  3. 二维特征分类的基础_带你搞懂朴素贝叶斯分类算法

    贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类.而朴素朴素贝叶斯分类是贝叶斯分类中最简单,也是常见的一种分类方法.这篇文章我尽可能用直白的话语总结一下我们学习会上讲到 ...

  4. 『带你学AI』一文带你搞懂OCR识别算法CRNN:解析+源码

    目录 前言 一.CRNN 1.1 CRNN 介绍 1.2 CRNN 网络结构 1.2.1 CNN 1.2.2 Map-to-Sequence 1.2.3 RNN 1.2.4 CTC Loss 1.3 ...

  5. 还在抱怨数据结构难? 一文带你搞懂如何AC算法题(2022版)

  6. C语言实现三子棋小游戏(编程思路以及代码详解)

    目录 前言 一.三子棋游戏的实现逻辑 二.创建菜单并控制游戏开始或游戏结束 三.创建棋盘并且初始化棋盘 四.打印棋盘并验证打印棋盘和初始化棋盘模块的实现 五.玩家下棋 六.电脑下棋 六.输赢判断 七. ...

  7. 一文带你入门图论和网络分析(附Python代码)

    作者:Srivatsa 翻译:和中华 校对:丁楠雅 本文约6300字,建议阅读20+分钟. 本文从图的概念以及历史讲起,并介绍了一些必备的术语,随后引入了networkx库,并以一个航班信息数据集为例 ...

  8. NNLM神经网络语言模型简单实现词语预测(含python代码详解)

    文章目录 一.NNLM简单介绍 二.NNLM词语预测代码 1. 导入包 2. 文本数据处理 3. 自定义mini-batch迭代器 4. 定义NNLM模型 1. 定义模型结构 2. NNLM参数设置 ...

  9. 实战案例:带你了解并验证基金定投,附Python代码!

  10. 带你了解并验证基金定投,附Python代码

最新文章

  1. linux 笔记--while循环、函数和进程管理
  2. Windows Server 2012正式版RDS系列④
  3. android 触摸监听重写_Android监听屏幕的滑动事件
  4. 联想拯救者y7000电池耗电快_游戏新选择:联想2020款拯救者Y7000/R7000爆料
  5. 什么是javax.ws.rs.core.context? [ 第2部分 ]
  6. 【常见笔试面试算法题12】动态规划算法案例分析
  7. 计算机等级考试二级ACCESS考试大纲
  8. iOS开发:几种静态扫描工具的使用与对比
  9. Struts2→MCV、环境搭建第一个样例、工作原理、核心文件、XML中常用元素、通配符、action后缀、action接收参数、result、标签
  10. 虚拟网站禁用php,PHP虚拟主机建议禁用函数列表
  11. 在Visual Studio 2010中创建多项目(解决方案)模板【一】
  12. python中什么是句柄_python中的句柄操作的方法示例
  13. EVMC6678L时钟主频配置
  14. 微信语音java_java微信企业号开发之发送消息(文本、图片、语音)
  15. 比尔·盖茨持有过的中国股票
  16. 如何进行数据可视化制图
  17. 李宏毅——终身学习lifelong learning
  18. 一款用autoit3写的小游戏,大家娱乐下
  19. wxpython制作桌面悬浮球
  20. 组装多媒体计算机步骤及配件,组装计算机实习报告

热门文章

  1. VBA-使用inputbox函数
  2. My Fifteenth Page - 快乐数 - By Nicolas
  3. 支教笔记 我在泸定的那十天
  4. Spring中常用注解及其作用(二)
  5. IT从业者创业公司生存指南:创业中期 ---- 先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。
  6. IO---缓冲流、字符集、转换流、序列化和反序列化
  7. vue.js毕业设计,基于vue.js前后端分离在线教育视频点播系统设计与实现(H5移动项目)
  8. JavaScript广告图片跟随滚动
  9. 大牛云集!清华大学2019年姚班及智班第一届AI本科生名单公布!
  10. 蚂蚁金服自研架构 SOFA 背后的工程师|1024快乐