非监督学习-聚类算法概述与代码实现(*K-means, k-modes, k-prototypes, DMSCAN密度聚类, GMM, 层次聚类)

本文介绍了非监督学习中的聚类算法,如K-means、基于密度的DBSCAN和高斯混合模型GMM,涵盖了K-means的逻辑、k值选择、优缺点以及DBSCAN和GMM的独特之处。同时讨论了监督学习与非监督学习的区别,以及距离计算方法在聚类中的应用。
摘要由CSDN通过智能技术生成

聚类算法是什么

简单来说,聚类算法(clustering)就是通过某种特定的标准,比如距离,把一个数据集分割成多个类或者簇,使得同一簇中的数据对象相似性大,而在不同簇的数据对象差异性也尽可能大(尽可能大是因为会有异常值出现)。

简而言之,就是告诉我们这一坨数据可以分为几类(簇),哪些数据是”一家子“人,哪些数据不是”一家子“。但是,我们并不关心这几家子人究竟姓什么,只知道他们谁是”一家子“,谁不是”一家子“即可(聚类与分类的区别,详见下个标题)。

监督学习、非监督学习、半监督学习与强化学习的概念

说到刚才不关心这”一家子“人姓什么的问题,就要说到监督学习与非监督学习的区别。
在这里插入图片描述

  • 监督学习

提供带有正确答案的训练集,从训练集中归纳规律、规则或者某种映射关系,进而将此规则用于预测或者解释测试集的数据。

监督学习分为回归分类两种,回归即是一种连续变量的预测,而分类是离散变量的预测,简单来说就是分类可以分为具体的类别而回归则不能。

像我们平时用到的线性回归多项式回归、KNN(K临近),SVM,逻辑回归,决策树、随机森林等都是属于监督学习的范畴。

简单说,我有历史数据并知晓他们的标准答案,那么我通过学习特征值与标准答案的映射关系,那么有新的”考卷“来的时候,我就可以给出理论上准确的答案。

这里注意,永远不要说”因果关系“二字,归纳找到的知识规则、映射关系,而非真正的”因果关系“!但是通俗讲,监督学习就是可以解决:预测和追因两大问题。

  • 非监督学习

数据集没有提供正确答案,我们只需要找出这些数据中的相似性或者共同性,将其归类。

所以我们在非监督学习中,并不关心正确答案是什么,只关心哪些数据是一类。很多时候,非监督学习都是用于辅助监督学习的。我们本篇提到的聚类算法,就是属于非监督学习范畴。没有训练集数据,不关心正确答案这个标准,只关心谁是”一家子“。
非监督学习:apriori, K-means, k-modes, k-prototypes, DMSCAN密度聚类, GMM, 层次聚类

  • 半监督学习

数据中一部分有”正确答案“,一部分没有。比如当大部分数据缺失正确答案的时候,比如信息的缺失,我们先对其使用非监督学习,待信息完善,在使用监督学习。

  • 强化学习

介于监督学习和非监督学习之间。在训练中,如果得到错误的答案,会给算法一个警告,如果正确的话不会被警告,算法会自行尝试更多中可能性,直到找出最终的正确答案。其实这个警告通常情况下,就是评分,强化学习就是通过对无监督学习算法的评分,从而教导它成为优秀的算法。

距离计算方法(相似性)

1. 欧氏距离

欧式空间两点之间的直线距离。
在这里插入图片描述
适用于各向量标准统一的情况(连续变量和名义变量无法用欧式距离计算),连续变量使用。

2. 曼哈顿距离
在这里插入图片描述

马哈顿距离又称为街区距离,可以想象成计算街区中某一个房子到另外一个房子需要走的路。
在这里插入图片描述

3. 切比雪夫距离
在这里插入图片描述
切比雪夫距离和曼哈顿距离可以相互转换(旋转45度),其实它就是计算A到B点经过每个坐标的最大距离。

4. 夹角余弦相似度
在这里插入图片描述
还是刚才那个坐标系,这次计算的是两个点的夹角的大小,越小越相似。
在这里插入图片描述

5. pearson相关性系数(斯皮尔曼,肯德尔)
在这里插入图片描述
在这里插入图片描述
这个本质上是计算相关性系数,相关性越高,距离越近。
三大相关系数中,皮尔森相关系数只适合用于连续变量中,如果存在分类变量,用斯皮尔曼相关系数。
斯皮尔曼相关性系数:
在这里插入图片描述
实际上斯皮尔曼相关系数,是为了看秩的距离,所以适用于分类变量(名义变量)。首先排序,计算秩数,在相关性计算。

之前工作中用到过斯皮尔曼相关性系数,小编手撸了一个spearman的代码如下:

def spearman(my_array, names=None):
    """
    param1: 重复变量需要用平均秩数
    param2: p = 1 - (6*sum(d^2))/n*(n^2-1)
    """
    for sort_name in names:
        my_array = my_array.sort_values(by=sort_name)
        my_array.reset_index(inplace=True, drop=True)
        my_array[sort_name+'p'] = my_array.index+1
        temp = my_array[my_array[sort_name].duplicated(keep=False)]
        my_index = list(temp[sort_name+'p'])
        temp_keep = my_array[~(my_array[sort_name+'p'].isin(my_index))]
        num_list = list(set(temp[sort_name]))
        for item in num_list:
            tempp = temp[temp[sort_name]==item]
            mean_num = tempp[sort_name+'p'].mean()
            tempp[sort_name+'p'] = mean_num
            temp_keep = temp_keep.append(tempp)
        temp_keep.reset_index(inplace=True, drop=True)
    temp_keep['d^2'] = temp_keep[names[0]+'p'] - temp_keep[names[1]+'p']
    temp_keep['d^2'] = temp_keep['d^2'].apply(lambda x: math.pow(x,2))
    # 计算相关系数
    n = len(temp_keep[names[0]])
    p = 1- ((6*(temp_keep['d^2'].sum()))/(n*(n**2 -1)))
    return p

spearman(df_11, names=['事故类型','敞口gap'])

6. 马氏距离
在欧式距离的基础上,消除了不同标尺的差异造成的而影响因素。度量样本点P到数据集D的距离。(同一分布下的两个样本集)
在这里插入图片描述
7. 海明距离
在信息领域,两个长度相等的字符串的海明距离是在相同位置上不同的字符的个数,也就是将一个字符串替换成另一个字符串需要的替换的次数。
简单说:相同长度的字符串,替换成一样的需要的次数。
举例:
“c_jiajun” 和 “h_jiajun” 只需要替换c与h,距离则为:1
十进制数字,可以转换为二进制(辗转相除法)进行距离计算。

K-means算法逻辑

一言以蔽之:

1. 选择K个不相同的点最为初始质心
2. 将每个点指派到最近的质心形成k个簇
3. 重新计算每个簇的质心(到该簇内距离均值最小的点)
4. 重复迭代2、3的操作
5. 直到簇不发生变化或者达到最大迭代次数。

在这里插入图片描述

计算公式便是上述的距离计算方法,K-means 中我们选择欧氏距离,如果第3步中的均值变为计算中位数,则变成k-medians。
在这里插入图片描述
之前项目用过一次k-means, 代码实现如下:

值得注意的是,如果出现category的变量,可以选择转换为dummy变量(哑变量),但是也可以尝试k-prototypes去做。如果只有category类型的变量,可以选择k-modes。

# from IPython.core.interactiveshell import InteractiveShell
# InteractiveShell.ast_node_interactivity = "all"
# %matplotlib inline
import pandas as pd
import numpy as np
from sklearn import preprocessing
from sklearn.cluster import KMeans
from sklearn.cluster import MiniBatchKMeans
from yellowbrick.cluster import KElbowVisualizer
from yellowbrick.cluster import SilhouetteVisualizer
from yellowbrick.cluster import InterclusterDistance
from yellowbrick.model_selection import LearningCurve
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid")
sns.set_context("notebook")

"""
计算方法
"""

def elbowmethod_yellow(X_scaled,num=None):
    plt.figure(figsize=(12,9))

    model = KMeans()

    visualizer = KElbowVisualizer(model, k=(1,num+1))
    visualizer.fit(X_scaled)       
    visualizer.show()
    
def elbowmethod(X_scaled,num=None):
    SSE = []  # 存放每次结果的误差平方和
    for k in range(1,num+1):
        estimator = KMeans(n_clusters=k)  # 构造聚类器
        estimator.fit(X_scaled)
        SSE.append(estimator.inertia_) # estimator.inertia_获取聚类准则的总和
    X = range(1,num+1)
    plt.xlabel('k')
    plt.ylabel('SSE')
    plt.plot(X,SSE,'o-')
    plt.show()

def kmeans(X_scaled,k_value=None):
    model=MiniBatchKMeans(n_clusters=k_value)
    model.fit(X_scaled)
    print("Predicted labels ----")
    model.predict(X_scaled)
    X_scaled['cluster'] = model.predict(X_scaled)

    plt.figure(figsize=(12,9))

    model=MiniBatchKMeans(n_clusters=k_value).fit(X_scaled)

    visualizer = SilhouetteVisualizer(model, colors='yellowbrick')
    visualizer.fit(X_scaled)      
    visualizer.show()

    plt.figure(figsize=(12,9))

    visualizer = InterclusterDistance(model, min_size=10000)
    visualizer.fit(X_scaled)
    visualizer.show()


"""
数据读取
"""
if __name__=='__main__':
    data = read........
    data.drop(columns=['Unnamed: 0','leader_en'],inplace=True)
    data.dtypes
    data.rename(columns={'origin_project_id':'choujian_or_not'},inplace=True)
    data['choujian_or_not'] = data['choujian_or_not'].apply(lambda x: '是' if pd.notnull(x) else '否')
    data[data['project_id'].duplicated()]
    ### 变换dummy变量
    bcd = data[['project_id','latency','efficient']]
    dummies_leader_cn = pd.get_dummies(data['leader_cn'], prefix='leader_cn')
    dummies_product = pd.get_dummies(data['product'], prefix='product')
    dummies_latency_team = pd.get_dummies(data['latency_team'], prefix='latency_team')
    dummies_tag_lv1 = pd.get_dummies(data['tag_lv1'], prefix='tag_lv1')
    dummies_tag_lv2 = pd.get_dummies(data['tag_lv2'], prefix='tag_lv2')
    dummies_media_type_cn = pd.get_dummies(data['media_type_cn'], prefix='media_type_cn')
    dummies_choujian_or_not = pd.get_dummies(data['choujian_or_not'], prefix='choujian_or_not')
    dummies_latency_team

    ### 归一化连续变量
    bcd_temp = bcd[['latency','efficient']]
    min_max_scaler = preprocessing.MinMaxScaler()
    x_scaled = min_max_scaler.fit_transform(bcd_temp)
    X_scaled = pd.DataFrame(x_scaled,columns=bcd_temp.columns)
    # X_scaled['project_id'] = bcd['project_id']
    X_scaled = pd.concat([X_scaled,dummies_leader_cn, dummies_product,dummies_latency_team,dummies_tag_lv1,dummies_tag_lv2,dummies_media_type_cn,dummies_choujian_or_not], axis=1,join='outer')



"""
调用
"""
elbowmethod_yellow(X_scaled,num=20)

kmeans(X_scaled,k_value=9)

"""
这段不重要,只是赋值
"""
X_scaled['project_id'] = data['project_id']
X_scaled.rename(columns={'latency':'latency_minmax','efficient':'efficient_minmax'},inplace=True)
X_scaled['latency'] = data['latency']
X_scaled['efficient'] = data['efficient']
X_scaled['num_choujian_30day'] = data['num_choujian_30day']
X_scaled['avg_choujian_30day'] = data['avg_choujian_30day']
X_scaled['project_id'] = X_scaled['project_id'].astype(str)
id_frame = pd.DataFrame(columns=['0','1','2','3','4','5','6','7','8'])
for i in range(0,9):
    temp = X_scaled[X_scaled['cluster']==i]
    temp_list = list(set(temp['project_id']))
    temp_list = pd.DataFrame(temp_list,columns=[str(i)])
    id_frame[str(i)] = temp_list[str(i)]

如何评价k-means聚类算法

在监督学习中,比如分类模型(树模型,KNN等),我们用混淆矩阵、ROC曲线,AUC值来评价模型的“好坏”。回归模型中我们可以用MSE,RMSE,MAE等来看。
在聚类算法中,我们不涉及到看测试集预测结果(阳性、阴性)与真实情况(真、假)的比较。我们在聚类算法中可以有以下两种常见的评估方法:

  • purity评价法:

这个很简单,就是单纯看正确分类的样本占总数的多少。换句话说,就是是不是你聚类完了,会有很多异常值“脚踏n条船”。一个占比率,就可以generally的看出聚类效果的好坏。
在这里插入图片描述

  • 轮廓系数法:

轮廓系数结合了聚类的凝聚度分离度, 用于评估聚类的效果。该值处于(-1,1)之间,值越大,表示聚类效果越好。
具体计算方法如下:

  1. 对于每个样本点 i ii,计算点i与其同一个簇内的所有其他元素距离的平均值,记作a ( i ) a(i)a(i),用于量化簇内的凝聚度。
  2. 选取i外的一个簇b bb,计算i ii与b bb中所有点的平均距离,遍历所有其他簇,找到最近的这个平均距离,记作b ( i ) b(i)b(i),即为i的邻居类,用于量化簇之间分离度。
  3. 对于样本点i ii,轮廓系数在这里插入图片描述
    计算所有i的轮廓系数,求出平均值即为当前聚类的整体轮廓系数,度量数据聚类的紧密程度。

通俗讲,也就是遍历每一个样本点i, 找出它最邻近的簇F,看每个样本点i是否完美的镶嵌于本簇当中(凝聚度),且完美的与其他簇分隔开(分离度)。

k-means聚类算法如何选出最佳k值

最后一个问题,就是选择k值。我们之前提到,k-means第一步就是随机算则k个不相同的初始质心点。那么我们怎么知道到底选几个?

其实说到这个问题,也是k-means, k-medians, k-modes, k-prototypes等一系列partion-based聚类算法的弱点-事先要选择初始k值(均值飘移聚类,DNSCAN,层次聚类等不需要事先确定k值)。

我们可以通过以下两种常用方法选择最佳k值:

1. 手肘法(elbow method)
手肘法的核心指标是SSE(sum of the squared errors,误差平方和),
在这里插入图片描述
简单说,加入k值=1, 那么误差会极大,加入k值趋向于样本量,那么每一个样本点就是一个簇,那肯定没有误差,但是脱离聚类算法本意。我们想找的,就是随着k值不断增加,误差越来越小,到底达到那个k值的时候,误差平方SSE会开始不再“明显大幅度降低”,而趋于平缓。这个图做出来就像一个手肘一样,因此叫手肘法。
在这里插入图片描述
之前项目用过一次手肘法,代码如下:

import pandas as pd
import numpy as np
from sklearn import preprocessing
from sklearn.cluster import KMeans
from sklearn.cluster import MiniBatchKMeans
from yellowbrick.cluster import KElbowVisualizer
from yellowbrick.cluster import SilhouetteVisualizer
from yellowbrick.cluster import InterclusterDistance
from yellowbrick.model_selection import LearningCurve
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_style("whitegrid")
sns.set_context("notebook")

def elbowmethod_yellow(X_scaled,num=None):
    plt.figure(figsize=(12,9))

    model = KMeans()

    visualizer = KElbowVisualizer(model, k=(1,num+1))
    visualizer.fit(X_scaled)       
    visualizer.show()
    
def elbowmethod(X_scaled,num=None):
    SSE = []  # 存放每次结果的误差平方和
    for k in range(1,num+1):
        estimator = KMeans(n_clusters=k)  # 构造聚类器
        estimator.fit(X_scaled)
        SSE.append(estimator.inertia_) # estimator.inertia_获取聚类准则的总和
    X = range(1,num+1)
    plt.xlabel('k')
    plt.ylabel('SSE')
    plt.plot(X,SSE,'o-')
    plt.show()
    
"""
调用手肘法
"""
elbowmethod_yellow(X_scaled,num=20)

2. 轮廓系数法

上问中讲到了轮廓系数发评价模型效果,我们其实可以通过这种方法去选择K值,说白了就是遍历所有K值,选择平均轮廓系数的绝对值最接近1的那一个K值。

K-means的优缺点及应用场景

优点

  • 原理易懂、易于实现
  • 当簇间的区别较明显时,聚类效果较好
  • 计算时耗费内存小,时间复杂度并不高(前提是别选那么大的K值)

缺点

  • 当样本集规模大时,收敛速度会变慢
  • 容易局部最优,对孤立点数据敏感,少量噪声就会对平均值造成较大影响;(这个时候可以考虑看中位数)
  • k的取值十分关键,需要实现初始化K值,在不知道K值得情况下,耗费算力很大。
  • 对于特殊图形非凸(non-convex)数据的聚类无法操作,比如球形(此时考虑高斯混合模型GMM)

应用场景

所有不需要得知“正确答案”的聚类or分类(非监督学习),我们都可以尝试使用这种算法。目的是看有多少人是“一家子人”。 当然,我们还可以用聚类算法去寻找异常值,在做用户画像处理特征值的时候,聚类算法可以辅助监督学习。

改进和其他常用聚类算法

k-means对初始K值的设置很敏感,k-means对噪声和离群值非常敏感,k-means只用于连续型变量的数据,不适用于categorical类型数据,k-means不能解决非凸(non-convex)数据,另外,很多教程都告诉我们Partition-based methods聚类多适用于中等体量的数据集,数据集越大,效果可能越差。

与K-means算法类似的k-modes和k-prototypes

我们刚才提到,K-means 只适用于连续型变量numerical,虽然可以强行将category变量转换为哑变量dummy,但是效果并不好。
所谓partion-based的聚类算法,就是三个字:算距离。 所以上文中提到的距离计算方法,就会适用于不同的模型。

  1. 对于离散型数据集,我们可以尝试k-modes
    我们记得之前k-means是计算欧式距离,而k-modes是计算汉明距离,这几个聚类的本质区别就是计算的距离不同。

  2. 如果是混合在一起的情况,一个数据集既有连续变量,又有离散变量,我们可以选择k-prototypes
    k-prototypes就是k-means+k-modes的结合,构造一个新的损失函数,一个原型prototype, 看每个点与该原型的偏离度。
    简而言之,就是连续变量计算欧氏距离,离散变量计算汉明距离,再加到一起。

Density-based methods基于密度的聚类算法 DBSCAN

区别于partition-based的聚类算法,density-based聚类算法不用事先初始化k-值,可以不必知道。所以可以解决一些非凸(non-convex)数据的聚类。

“形象来说,我们可以认为这是系统在众多样本点中随机选中一个,围绕这个被选中的样本点画一个圆,规定这个圆的半径以及圆内最少包含的样本点,如果在指定半径内有足够多的样本点在内,那么这个圆圈的圆心就转移到这个内部样本点,继续去圈附近其它的样本点,向高密度的位置传导。等到这个滚来滚去的圈发现所圈住的样本点数量少于预先指定的值,就停止了。那么我们称最开始那个点为核心点,如A,停下来的那个点为边界点,如B、C,没得滚的那个点为离群点。”

在这里插入图片描述
倘若如果使用k-means等partition-based的聚类算法,就无法准确展示聚类的效果,如下:
在这里插入图片描述

高斯混合模型GMM聚类算法

使用高斯混合模型(GMM)做聚类首先假设数据点是呈高斯分布的,相对应K-Means假设数据点是圆形的,高斯分布(椭圆形)给出了更多的可能性。
大致上是,选择簇的数量(k-值)初始化每个簇的均值和标准差,计算每个样本点属于每个簇的概率,即这个样本点越接近这个高斯分布的中心,则越可能属于这个高斯分布。
在这里插入图片描述
但是这个算法也有缺点,比如和k-means类似需要初始化k值,而且需要满足前提假设,数据点是服从高斯分布(正态分布、随机)的。

层次聚类算法

其实这个算法比较好理解,就是将所有数据点本身作为簇,n个数据点就是n个簇,然后找出距离最近的两个簇将它们合为一个,不断重复以上步骤直到达到预设的簇的个数。
距离最近的判断方式有三种:

  • Ward: 最小化各数据点在簇内的误差平方和。
  • Maximum or complete linkage: 最小化各数据点在所有簇中的最大距离。
  • average linkage: 最小化各数据点在所有簇中的平均距离。

其实说白了,就是“自下而上去分堆”,本质上还是算距离。
在这里插入图片描述

ward的距离方法,是不是很像我们上文提到的手肘法中图形从右到左的过程~
不过这种算法非常消耗算力,费时费力。

感言

在这里插入图片描述

之前还信誓旦旦要读PHD,越看越发现永无止境,真的会秃头吧。再次立一个flag,每天尝试上手一种算法,但愿世久生情。愿大家都能不忘初心,欢迎一起学习。

  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值