MLK | 非监督学习最强攻略

2019-07-17 11:26:27 浏览数 (1)

"MLK,即Machine Learning Knowledge,本专栏在于对机器学习的重点知识做一次梳理,便于日后温习,内容主要来自于《百面机器学习》一书,结合自己的经验与思考做的一些总结与归纳,本次主要讲解的内容是机器学习里的非监督学习经典原理与算法,非监督,也就是没有target(标签)的算法模型。"

Index

  • K-Mean聚类算法
  • 高斯混合模型
  • 自组织映射神经网络
  • 聚类算法的评估指标
  • 常见聚类算法对比
  • 常见聚类算法的Python实现

在机器学习中存在一种问题,那就是模型是没有target的,给机器输入大量的特征数据,期望机器可以学习出当中的共性或者结构又或者是关联,并不需要像监督学习那样输出某个预测值。

K-Mean聚类算法

K-Mean的基本思想就是通过迭代的方式寻找K个簇(Cluster)的一种划分方案,使得聚类结果对应的Cost Function最小,一般K-Mean的Cost Function为各个样本距离所属簇中心点的误差平方和,公式为:

其中Xi代表第i个样本,Ci是Xi所属的簇,μci代表簇对应的中心点,M是样本总数。

首先先来看一下K-Mean算法的具体步骤描述:

1)数据预处理,如归一化、异常值处理;

2)随机抽取K个簇(K由人工设定);

3)定义Cost Function:

4)不断迭代下面?步骤,直到CF收敛:

  • 对于每个样本Xi,将其分配到距离最近的簇:
  • 对于每个簇,重新计算簇中心:
K-Mean的优点

1)对于大数据集,算法还是相对高效的,计算复杂度为O(NKt),其中N为样本数,K为聚类数,t为迭代的论数;

2)一般情况下都可以满足聚类的需求。

K-Mean的缺点

1)需要人工确定K值,人工对大数据的K值预判有的时候不太好;

2)K-Mean很容易局部最优,所以效果很受一开始的初始值影响;

3)容易受到异常值,噪点的影响。

K-Mean调优思路

1)数据归一化和异常值处理。

因为K-Mean本质上是基于欧式距离度量的数据聚类方法,所以少量的极端值会影响聚类效果的,而且不同量纲的数据也会有不一样的影响,所以需要做一下预处理。

2)合理选择K值。

K值并不是拍脑袋拍出来的,需要用科学的办法去确定。一般可以通过多次试验结果决定,如采用手肘法:

其中,横轴为K的取值,纵轴为误差平方和所定义的Loss Function。

可以看出,K值越大,距离和越小,我们看到当K=3的时候,曲线出现"拐点",因此一般我们选择这个点作为我们的K值。

此外,这里还介绍一个GS(Gap Statistic)方法,可参考:

https://blog.csdn.net/baidu_17640849/article/details/70769555

3)采用核函数。

传统的欧式距离度量方式使得K-Mean算法本质上是假设各个簇的数据具有一样的先验概率,并呈现球形或者高维球形分布,但这种分布在现实中不太常见,这个时候我们引入一个核K-Mean算法,主要面对非凸的数据分布。

这类核聚类方法主要是通过一个非线性映射,将输入控件中的数据点映射到高位的特征空间中,并在新的特征空间中进行聚类,非线性映射增加了数据点线性可分的概率,从而达到更高精度的聚类结果。

再说说两种算法

1)K-Mean 算法

这个从名字上看,就是K-Mean的改良版,主要是在初始值的选取上作了改进。原先的K-Mean是随机选择初始值,而K-Mean 算法则是:

  • 第1个聚类中心也是随机;
  • 接下来的聚类中心,也就是第2个,按照距离当前聚类中心越远越好;
  • 按照上述思想,选择了k个初始的聚类中心;
  • 初始值选取完毕后,后续的流程和K-Mean是一样的。

2)ISODATA算法

当K值的大小不确定的时候,可以使用ISODATA算法,全称叫迭代自组织数据分析法。ISODATA算法在K-Mean算法的基础上增加了两个操作:

  • 分裂操作,对应着增加聚类中心数
  • 合并操作,对应着减少聚类中心数

ISODATA的应用也是比较复杂的,需要填比较多的参数:

  • 预期的聚类中心数据K0:在ISODATA运行过程中聚类中心数可以自动变化,这里的K0只是一个参考值;
  • 每个类所要求的的最少样本数Nmin:如果分裂后会导致某个子类别所包含的样本数量少于该阈值,会拒绝本次分裂操作;
  • 最大方差Sigma:用于控制某个类别中样本的分散程度,当样本的分散程度超过某个阈值时,且分裂后满足第一条要求,则进行分裂操作;
  • 两个聚类中心之间所允许的最小距离Dmin:如果两个簇靠得很近,就会被进行合并操作。

高斯混合模型

高斯模型,对应着高斯分布,高斯分布也就是我们平时常说的正态分布,高斯混合模型(Gaussian Mixed Model,GMM)也是一种聚类算法,和K-Mean算法类似,都是用了EM算法进行迭代计算。高斯混合模型是假设每个簇的数据都符合正态分布,当前数据呈现的分布则是每个正态分布的混合结果。

高斯混合模型的核心思想,每个单独的分模型都是标准高斯分布模型,其均值和方差都是待估计的参数,还有一个参数π,可以理解为权重(or 生成数据的概率),其公式为:

它是一个生成式模型,并且通过EM算法框架来求解,具体的迭代过程如下:

首先,初始随机选择各个参数的值(总共3个参数,均值、方差和权重),然后迭代下面两步,直到收敛:

1)E步骤:根据当前的参数,计算每个点由某个分模型生成的概率。

2)M步骤:使用E步骤估计出来的概率,来改进每个分模型的均值、方差和权重。

高斯混合模型与K-Mean算法的相同点:

1)他们都是用于聚类的算法,都需要指定K值;

2)都是使用EM算法来求解;

3)往往都是得到局部最优。

而它相比于K-Mean算法的优点,就是它还可以用于概率密度的估计,而且可以用于生成新的样本点。

生成式模型(Generative Model):对联合分布概率p(x,y)进行建模,常见生成式模型有:隐马尔可夫模型HMM、朴素贝叶斯模型、高斯混合模型GMM、LDA等。 判别式模型(Discriminative Model):直接对条件概率p(y|x)进行建模,常见判别模型有:线性回归、决策树、支持向量机SVM、k近邻、神经网络等。

自组织映射神经网络

自组织映射神经网络(Self-Organizing Map,SOM)是无监督学习方法中的一类重要方法,可以用于聚类、高维可视化、数据压缩、特征提取等等用途,因为提出者是Teuvo Kohonen教授,因此也被称为Kohonen网络。

讲SOM之前,先科普一些生物学研究:

1)在人脑的感知通道上,神经元组织是有序排列的;

2)大脑皮层会对外界特定的信息在特定的区域产生兴奋;

3)在生物神经系统中存在着一种侧抑制现象,即一个神经细胞兴奋后,会对周围其他神经细胞产生抑制作用,这种抑制作用会使得神经细胞之间出现竞争,其结果是某些获胜,某些失败,表现则为获胜细胞兴奋,失败细胞抑制。

而我们的SOM就是对以上的生物神经系统功能的一种人工神经网络模型

SOM本质上是一个两层神经网络,包含输入层和输出层。输入层模拟感知外界输入信息,输出层模拟做出响应的大脑皮层。

1)输出层中,神经元的个数就是聚类的个数;

2)训练时采用"竞争学习"的方式,每个输入的样本,都会在输出层中找到与之最为匹配的节点,这个节点被称之为"激活节点"(winning neuron);

3)紧接着采用随机梯度下降法更新激活节点的参数,同时适当地更新激活节点附近的节点(会根据距离远近选择更新的"力度");

4)上面说到的"竞争学习",可以通过神经元之间的横向抑制连接(负反馈路径)来实现。

一般,SOM模型的常见网络结构有两种,分别是一维和二维的:

SOM的自组织学习过程,可以归纳为下面几个子过程:

1)初始化:所有连接权重都用小的随机值进行初始化。

2)竞争:神经元计算每一个输入模式各自的判别函数值,并宣布具有最小判别函数值的特定神经元为胜利者,每个神经元j的判别函数为:

3)合作:获胜的神经元决定了兴奋神经元拓扑邻域的空间位置,确定了激活节点后,更新临近的节点。

4)适应:适当调整相关兴奋神经元的连接权重,使得获胜神经元对相似输入模式的后续应用的响应增强。

5)迭代第2-4步,直到特征映射趋于稳定。

等到最后迭代结束之后,每个样本所激活的神经元就是它对应的类别。

SOM与K-Mean算法的区别

1)K-Mean算法需要事先确定好K值,而SOM不需要;

2)K-Mean算法为每个输入数据找到一个最相似的类,只更新这个类的参数;而SOM则会更新临近的节点,所以,K-Mean算法受噪声影响比较大,SOM则可能准确性方面会差一些;

3)SOM的可视化很好,有优雅的拓扑关系图。

如何训练参数

1)设定输出层神经元的数量:如果不清楚,可以尽可能设定较多的节点数。

2)设计输出节点的排列:对于不同的问题,事先选择好模式。

3)初始化权值

4)设计拓扑邻域:拓扑邻域的设计原则是使得邻域不断缩小,从而输出平面上相邻神经元对应的权向量既有区别又有相当的相似度,从而保证获胜节点对某一类模式产生最大响应时,其邻域节点也产生较大响应。

5)设计学习率:学习率是一个递减函数,可以结合拓扑邻域一起考虑。在训练开始时,可以选择较大的值,这样子比较快下降,后面慢慢减少。

聚类算法的评估指标

聚类算法不像有监督学习有一个target,更多的都是没有目标的,所以评估指标也是不一样的,下面介绍几种常用的评估指标:

1)轮廓系数(Silhouette Coefficient)

silhouette 是一个衡量一个结点与它属聚类相较于其它聚类的相似程度,取值范围-1到1,值越大表明这个结点更匹配其属聚类而不与相邻的聚类匹配。如果大多数结点都有很高的silhouette value,那么聚类适当。若许多点都有低或者负的值,说明分类过多或者过少。

定义轮廓系数结合了凝聚度和分离度,其计算步骤如下:

  • 对于第i个对象,计算它到所属簇中所有其他对象的平均距离,记为ai(体现凝聚度)
  • 对于第i个对象和不包含该对象的任意簇,记为bi(体现分离度)
  • 第i个对象的轮廓系数为si=(bi-ai)/max(ai,bi)

2)Calinski-Harabaz指数

如果标签是未知的,sklearn.metrics.calinski_harabaz_score则可以使用Calinski-Harabaz指数来评估模型,其中较高的Calinski-Harabaz分数与具有更好定义的聚类的模型相关。

优点:

  • 当集群密集且分离好时,分数更高,这与集群的标准概念有关。
  • 得分快速计算

缺点:

  • 凸群的Calinski-Harabaz指数通常高于簇的其他概念,例如通过DBSCAN获得的基于密度的集群。

3)Adjusted Rand index(调整后兰德指数)

该指标是衡量两个赋值相似度的函数,忽略排列组合

优点

  • 随机(统一)标签分配 对于任何值的ARI分数接近0.0n_clusters,n_samples(对于原始的兰德指数或V度量,情况不是这样)。
  • 有界范围[-1,1]:负值是坏的(独立标注),相似的聚类具有正的ARI,1.0是完美的匹配得分。
  • 对集群结构没有作出任何假设:可以用于比较聚类算法,例如k-means,其假设各向同性斑点形状与可以找到具有“折叠”形状的聚类的频谱聚类算法的结果。

缺点

  • 与惯性相反,ARI需要对地面真相类的知识,而在实践中几乎不可用,或者需要人工注释者的人工分配(如在受监督的学习环境中)。
  • 然而,ARI也可以在纯无人监控的设置中用作可用于聚类模型选择(TODO)的共识索引的构建块。

4)Mutual Information based scores(基于相互信息的分数)

鉴于labels_true相同样本的基本真实类分配和我们的聚类算法分配的知识labels_pred, 互信息是衡量两个分配的一致性的函数,忽略排列。这种措施的两个不同的标准化版本是可用的,归一化互信息(NMI)和调整的相互信息(AMI)。文献中经常使用NMI,而最近提出了AMI,并针对机会进行归一化:

优点

  • 随机的(均匀的)标签指定具有AMI得分接近0.0 为任何值n_clusters和n_samples(其不是生互信息或V-措施例如的情况下)。
  • 有界范围[0,1]:接近零的值表示两个主要独立的标签分配,而接近1的值表示重要的一致性。此外,恰好为0的值表示纯独立的标签分配,并且恰好为1的AMI表示两个标签分配是相等的(有或没有排列)。
  • 对集群结构没有作出任何假设:可以用于比较聚类算法,例如k-means,其假设各向同性斑点形状与可以找到具有“折叠”形状的聚类的频谱聚类算法的结果。

缺点

  • 与惯性相反,基于MI的措施需要了解地面真相类,而在实践中几乎不可用,或需要人为注释者的人工分配(如在受监督的学习环境中)。然而,基于MI的措施也可用于纯粹无监督的设置,作为可用于聚类模型选择的共识索引的构建块。

常见聚类算法对比

下面一张图介绍几种Scikit learn的常用聚类算法的比较:

常见聚类算法的Python实现

上面说了这么多聚类算法,还是在最后面,把算法的Python实现代码给大家贴一下,我们全文使用鸾尾花数据集:

代码语言:javascript复制
'''
使用Iris数据集(鸢尾花卉数据集)来进行我们的第一次预测。
该数据集包含150条记录的一组数据,有5个属性——花瓣长度,花瓣宽度,萼片长度,萼片宽度和类别。
三个类别分别是Iris Setosa(山鸢尾),Iris Virginica(维吉尼亚鸢尾)和Iris Versicolor(变色鸢尾)。
对于我们的无监督算法,我们给出鸢尾花的这四个特征,并预测它属于哪一类。
'''
# Importing Modules
from sklearn import datasets
import matplotlib.pyplot as plt

# Loading dataset 
iris_df = datasets.load_iris()
print(dir(iris_df))

# Features 
print(iris_df.feature_names)

# Targets
print(iris_df.target)

# Target Names
print(iris_df.target_names)
label = {0:'red',1:'blue',2:'green'}

# Dataset slicing
x_axis = iris_df.data[:,0]
y_axis = iris_df.data[:,2]

# Plotting
plt.scatter(x_axis, y_axis, c=iris_df.target)
plt.show()

1)K-Means聚类

代码语言:javascript复制
# Importing Modules
from sklearn import datasets
from sklearn.cluster import KMeans

# Loaning dataset
iris_df = datasets.load_iris()

# Declaring Model 初始化模型
model = KMeans(n_clusters=3)

# Fitting Model
model.fit(iris_df.data)

# Predicting a single input
predicted_label = model.predict([[7.2, 3.5, 0.8, 1.6]])

# Prediction on the entire data
all_predictions = model.predict(iris_df.data)

# Printing Predictions
print(predicted_label)
print(all_predictions)

2)分层聚类(Hierarchical clustering)

代码语言:javascript复制
from scipy.cluster.hierarchy import linkage, dendrogram
import matplotlib.pyplot as plt
import pandas as pd

seeds_df = pd.read_csv(
    "https://raw.githubusercontent.com/vihar/unsupervised-learning-with-python/master/seeds-less-rows.csv")

# remove the grain species from the DataFrame, save for later
varieties = list(seeds_df.pop('grain_variety'))

# extract the measurements as a NumPy array
samples = seeds_df.values

"""
Perform hierarchical clustering on samples using the
linkage() function with the method='complete' keyword argument.
Assign the result to mergings.
"""
mergings = linkage(samples, method='complete')

"""
Plot a dendrogram using the dendrogram() function on mergings,
specifying the keyword arguments labels=varieties, leaf_rotation=90,
and leaf_font_size=6.
"""

dendrogram(mergings,
           labels=varieties,
           leaf_rotation=90,
           leaf_font_size=6,
           )

plt.show()

3)t-SNE聚类

代码语言:javascript复制
# Importing Modules
from sklearn import datasets
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# Loading dataset
iris_df = datasets.load_iris()

# Defining Model
model = TSNE(learning_rate=100)

# Fitting Model
transformed = model.fit_transform(iris_df.data)

# Plotting 2d t-Sne
x_axis = transformed[:, 0]
y_axis = transformed[:, 1]

plt.scatter(x_axis, y_axis, c=iris_df.target)
plt.show()

'''
这里Iris数据集具有四个特征(4d),它被变换并以二维图形表示。类似地,t-SNE模型可以应用于具有n个特征的数据集
'''

4)DBSCAN聚类

代码语言:javascript复制
# Importing Modules
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.cluster import DBSCAN
from sklearn.decomposition import PCA

# Load Dataset
iris = load_iris()

# Declaring Model
dbscan = DBSCAN()

# Fitting
dbscan.fit(iris.data)

# Transoring Using PCA
pca = PCA(n_components=2).fit(iris.data)
pca_2d = pca.transform(iris.data)

# Plot based on Class
for i in range(0, pca_2d.shape[0]):
    if dbscan.labels_[i] == 0:
        c1 = plt.scatter(pca_2d[i, 0], pca_2d[i, 1], c='r', marker=' ')
    elif dbscan.labels_[i] == 1:
        c2 = plt.scatter(pca_2d[i, 0], pca_2d[i, 1], c='g', marker='o')
    elif dbscan.labels_[i] == -1:
        c3 = plt.scatter(pca_2d[i, 0], pca_2d[i, 1], c='b', marker='*')

plt.legend([c1, c2, c3], ['Cluster 1', 'Cluster 2', 'Noise'])
plt.title('DBSCAN finds 2 clusters and Noise')
plt.show()

5)MiniBatchKMeans

代码语言:javascript复制
'''
    Comparison of the K-Means and MiniBatchKMeans clustering algorithms

'''

print(__doc__)

import time

import numpy as np
import matplotlib.pyplot as plt

from sklearn.cluster import MiniBatchKMeans, KMeans
from sklearn.metrics.pairwise import pairwise_distances_argmin
from sklearn.datasets.samples_generator import make_blobs

# #############################################################################
# Generate sample data
np.random.seed(0)

batch_size = 45
centers = [[1, 1], [-1, -1], [1, -1]]
n_clusters = len(centers)
X, labels_true = make_blobs(n_samples=300000, centers=centers, cluster_std=0.7)

# #############################################################################
# Compute clustering with Means

k_means = KMeans(init='k-means  ', n_clusters=3, n_init=10)
t0 = time.time()
k_means.fit(X)
t_batch = time.time() - t0

# #############################################################################
# Compute clustering with MiniBatchKMeans

mbk = MiniBatchKMeans(init='k-means  ', n_clusters=3, batch_size=batch_size,
                      n_init=10, max_no_improvement=10, verbose=0)
t0 = time.time()
mbk.fit(X)
t_mini_batch = time.time() - t0

# #############################################################################
# Plot result

fig = plt.figure(figsize=(8, 3))
fig.subplots_adjust(left=0.02, right=0.98, bottom=0.05, top=0.9)
colors = ['#4EACC5', '#FF9C34', '#4E9A06']

# We want to have the same colors for the same cluster from the
# MiniBatchKMeans and the KMeans algorithm. Let's pair the cluster centers per
# closest one.
k_means_cluster_centers = np.sort(k_means.cluster_centers_, axis=0)
mbk_means_cluster_centers = np.sort(mbk.cluster_centers_, axis=0)
k_means_labels = pairwise_distances_argmin(X, k_means_cluster_centers)
mbk_means_labels = pairwise_distances_argmin(X, mbk_means_cluster_centers)
order = pairwise_distances_argmin(k_means_cluster_centers,
                                  mbk_means_cluster_centers)

# KMeans
ax = fig.add_subplot(1, 3, 1)
for k, col in zip(range(n_clusters), colors):
    my_members = k_means_labels == k
    cluster_center = k_means_cluster_centers[k]
    ax.plot(X[my_members, 0], X[my_members, 1], 'w',
            markerfacecolor=col, marker='.')
    ax.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col,
            markeredgecolor='k', markersize=6)
ax.set_title('KMeans')
ax.set_xticks(())
ax.set_yticks(())
plt.text(-3.5, 1.8,  'train time: %.2fsninertia: %f' % (
    t_batch, k_means.inertia_))

# MiniBatchKMeans
ax = fig.add_subplot(1, 3, 2)
for k, col in zip(range(n_clusters), colors):
    my_members = mbk_means_labels == order[k]
    cluster_center = mbk_means_cluster_centers[order[k]]
    ax.plot(X[my_members, 0], X[my_members, 1], 'w',
            markerfacecolor=col, marker='.')
    ax.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col,
            markeredgecolor='k', markersize=6)
ax.set_title('MiniBatchKMeans')
ax.set_xticks(())
ax.set_yticks(())
plt.text(-3.5, 1.8, 'train time: %.2fsninertia: %f' %
         (t_mini_batch, mbk.inertia_))

# Initialise the different array to all False
different = (mbk_means_labels == 4)
ax = fig.add_subplot(1, 3, 3)

for k in range(n_clusters):
    different  = ((k_means_labels == k) != (mbk_means_labels == order[k]))

identic = np.logical_not(different)
ax.plot(X[identic, 0], X[identic, 1], 'w',
        markerfacecolor='#bbbbbb', marker='.')
ax.plot(X[different, 0], X[different, 1], 'w',
        markerfacecolor='m', marker='.')
ax.set_title('Difference')
ax.set_xticks(())
ax.set_yticks(())

plt.show()

6)Affinity Propagation(近邻传播)

代码语言:javascript复制
# Demo of affinity propagation clustering algorithm
print(__doc__)

from sklearn.cluster import AffinityPropagation
from sklearn import metrics
from sklearn.datasets.samples_generator import make_blobs

# #############################################################################
# Generate sample data
centers = [[1, 1], [-1, -1], [1, -1]]
X, labels_true = make_blobs(n_samples=300, centers=centers, cluster_std=0.5,
                            random_state=0)

# #############################################################################
# Compute Affinity Propagation
af = AffinityPropagation(preference=-50).fit(X)
cluster_centers_indices = af.cluster_centers_indices_
labels = af.labels_

n_clusters_ = len(cluster_centers_indices)

print('Estimated number of clusters: %d' % n_clusters_)
print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels_true, labels))
print("Completeness: %0.3f" % metrics.completeness_score(labels_true, labels))
print("V-measure: %0.3f" % metrics.v_measure_score(labels_true, labels))
print("Adjusted Rand Index: %0.3f"
      % metrics.adjusted_rand_score(labels_true, labels))
print("Adjusted Mutual Information: %0.3f"
      % metrics.adjusted_mutual_info_score(labels_true, labels))
print("Silhouette Coefficient: %0.3f"
      % metrics.silhouette_score(X, labels, metric='sqeuclidean'))

# #############################################################################
# Plot result
import matplotlib.pyplot as plt
from itertools import cycle

plt.close('all')
plt.figure(1)
plt.clf()

colors = cycle('bgrcmykbgrcmykbgrcmykbgrcmyk')
for k, col in zip(range(n_clusters_), colors):
    class_members = labels == k
    cluster_center = X[cluster_centers_indices[k]]
    plt.plot(X[class_members, 0], X[class_members, 1], col   '.')
    plt.plot(cluster_center[0], cluster_center[1], 'o', markerfacecolor=col,
             markeredgecolor='k', markersize=14)
    for x in X[class_members]:
        plt.plot([cluster_center[0], x[0]], [cluster_center[1], x[1]], col)

plt.title('Estimated number of clusters: %d' % n_clusters_)
plt.show()

Reference

《百面机器学习》——chapter5

0 人点赞