无监督学习
文章目录
绝大多数可用数据都是没有标签的。计算机科学家
Yann LeCun
杨立昆曾有句名言:“如果智能是蛋糕,无监督学习将是蛋糕的本体,有监督学习是蛋糕上的糖霜,强化学习是蛋糕上的樱桃”,换句话说,无监督学习具有巨大的潜力,才刚开始起步。
最常见的无监督学习任务就是前面介绍的降维。
聚类Clustering
识别相似实例,并将其分配给相似实例的集群或组。
如图左,标记类别的鸢尾花数据,非常适合使用各种分类算法。如逻辑回归,随机森林,SVM
等
如图右,没有标签标记的相同的数据集,不能再使用分类算法。引入聚类。
图右的左下侧数据很容易检测到,但右上侧数据是否能分成两个集群(组),并不明显、并不确定。
如图,只是用了鸢尾花数据集的两个特征(花瓣长宽),如果使用所有特征呢,能很好的识别这个三聚类
聚类取决于上下文,不同算法会得到不同的聚类结果(不同的集群)
聚类的应用领域:
- 客户细分
- 数据分析
- 降维技术
- 异常检测
- 半监督学习
- 搜索引擎
- 分割图像
两种流行的聚类算法:K-Means
和 DBSCAN
K-Means算法
指定类别参数 K
,尝试找到 K
个集群中每个集群的中心点,并将每个实例分配给最近的集群。
示例代码:使用make_blobs
生成数据
from sklearn.datasets import make_blobs #生成用于聚类训练的数据集
blob_centers = np.array( # 五个聚类的中心点位置
[[ 0.2, 2.3],
[-1.5 , 2.3],
[-2.8, 1.8],
[-2.8, 2.8],
[-2.8, 1.3]])
blob_std = np.array([0.4, 0.3, 0.1, 0.1, 0.1]) # 每个聚类的方差
X, y = make_blobs(n_samples=2000, centers=blob_centers,
cluster_std=blob_std, random_state=7)
# n_samples=2000 样本个数,生成2000个样本实例,
# n_features,每个样本实例的特征个数,默认:2,
# centers ,聚类中心点
# 每个聚类,每个类别的方差。
#描图
def plot_clusters(X, y=None):
plt.scatter(X[:, 0], X[:, 1], c=y, s=1)
plt.xlabel("$x_1$", fontsize=14)
plt.ylabel("$x_2$", fontsize=14, rotation=0)
plt.figure(figsize=(8, 4))
plot_clusters(X)
plt.show()
示例代码:训练拟合和预测
from sklearn.cluster import KMeans
k = 5
kmeans = KMeans(n_clusters=k, random_state=42)
y_pred = kmeans.fit_predict(X)
kmeans.cluster_centers_
#array([[-2.80389616, 1.80117999],
# [ 0.20876306, 2.25551336],
# [-2.79290307, 2.79641063],
# [-1.46679593, 2.28585348],
# [-2.80037642, 1.30082566]])
如图,少数实例(左上角)贴错了标签。当集群具有不同的直径时,K-Means
算法的性能不是很好。只关心距离中心点的距离。
硬聚类和软聚类:
把每个实例分配给一个单独的集群(组),称硬聚类
为每个实例分配一个集群的相似性分数,这个分数可以是距离,也可是是高斯径向函数GBF
,软聚类
X_new = np.array([[0, 2], [3, 2], [-3, 3], [-3, 2.5]])
kmeans.predict(X_new)
# 4个测试点属于聚类:1,1,2,3:array([1, 1, 2, 2]) ,硬聚类
kmeans.transform(X_new)
# 直接给出分数(距离五个中心点的距离), 软聚类
array([[2.81093633, 0.32995317, 2.9042344 , 1.49439034, 2.88633901],
[5.80730058, 2.80290755, 5.84739223, 4.4759332 , 5.84236351],
[1.21475352, 3.29399768, 0.29040966, 1.69136631, 1.71086031],
[0.72581411, 3.21806371, 0.36159148, 1.54808703, 1.21567622]])
K-Means
算法基本思想
-
在只有参数
K
、没有中心点、也没有标签的情况下,只能先随机选择K
个中心点(如下图上左)。 -
然后,根据
K
个中心点标记实例(如下图上右),根据实例更新中心点(如下图中左),再标记实例(如下图中右) -
然后,再更新中心点(如下图下左),有限数量的迭代后必收敛(中心点停止移动),不会永远振荡。
-
该算法计算复杂度在实例数量
m
,集群数K
,维度n
方面是线性的。仅当数据具有聚类结构时才如此,否则,在最坏的情况下,复杂度会随着实例数量的增加而呈指数增长。但这种情况很少发生。
K-Means
通常是最快的聚类算法之一。
中心点初始化方法
法1: 碰巧知道或通过其他聚类算法得到了中心点。即已知中心点,可以直接设置超参数init
,并将n_init=1
from sklearn.cluster import KMeans
good_init=np.array([[-3,3],[-3,2],[-3,1],[-1,2],[0,2]])
# 已知中心点
KMeans(n_clusters=5, init=good_init, n_init=1, random_state=5)
# init=? 中心点初始值的选择方式:'random' 或 ‘k-means++‘或直接指定,如本例
# n_init= ? 用不同的初始化中心点运行算法的次数,默认为10
法2:使用不同的随机初始化多次运行算法,并保留最优解。。用每个实例与其最接近的中心点之间的均方距离作为衡量n个随机初始化的性能指标。这个指标也称模型的惯性
from sklearn.cluster import KMeans
def plot_clusterer_comparison(clusterer1, clusterer2, X, title1=None, title2=None):
clusterer1.fit(X)
clusterer2.fit(X)
plt.figure(figsize=(10, 3.2))
plt.subplot(121)
plot_decision_boundaries(clusterer1, X)
if title1:
plt.title(title1, fontsize=14)
plt.subplot(122)
plot_decision_boundaries(clusterer2, X, show_ylabels=False)
if title2:
plt.title(title2, fontsize=14)
kmeans_rnd_init1 = KMeans(n_clusters=5, init="random", n_init=1,
algorithm="full", random_state=2)
kmeans_rnd_init2 = KMeans(n_clusters=5, init="random", n_init=1,
algorithm="full", random_state=5)
# algorithm:有"auto", “full","elkan"三种选择,数据稠密选"elkan",数据稀疏选“full"
# “full"指强制使用原始K-Means算法,"elkan"指elkan K-Means算法
plot_clusterer_comparison(kmeans_rnd_init1, kmeans_rnd_init2, X,
"Solution 1", "Solution 2 (with a different random init)")
plt.show()
寻找最佳聚类数
法1:绘制惯性相对于集群数量 K
的函数曲线,肘(Elbow)的位置,即不错的K的选择。如图
法2:使用轮廓分数。它是所有实例的平均轮廓系数。实例的轮廓系数等于:
(
b
−
a
)
/
m
a
x
(
a
,
b
)
a
:
与
同
一
集
群
中
其
他
实
例
的
平
均
距
离
(
集
群
内
平
均
距
离
)
b
:
平
均
最
近
集
群
距
离
(
到
下
一
个
最
近
集
群
实
例
的
平
均
距
离
)
该
系
数
(
−
1
,
+
1
)
之
间
:
接
近
+
1
,
说
明
该
实
例
很
好
的
位
于
自
身
集
群
中
并
且
远
离
其
他
集
群
;
0
附
近
说
明
该
实
例
接
近
一
个
集
群
的
边
界
−
1
附
近
,
说
明
该
实
例
可
能
已
分
配
到
错
误
的
集
群
(b-a)/max(a,b) \\ a: 与同一集群中其他实例的平均距离(集群内平均距离)\\ b: 平均最近集群距离(到下一个最近集群实例的平均距离)\\ 该系数(-1,+1)之间:\\ 接近+1,说明该实例很好的位于自身集群中并且远离其他集群;\\ 0附近说明该实例接近一个集群的边界\\ -1附近,说明该实例可能已分配到错误的集群
(b−a)/max(a,b)a:与同一集群中其他实例的平均距离(集群内平均距离)b:平均最近集群距离(到下一个最近集群实例的平均距离)该系数(−1,+1)之间:接近+1,说明该实例很好的位于自身集群中并且远离其他集群;0附近说明该实例接近一个集群的边界−1附近,说明该实例可能已分配到错误的集群
比较不同集群数量K的轮廓分数。如图:
K-Means
的局限
尽管K-Means
有很多优点,尤其是快速且可扩展,但并不完美。
- 必须多次运行算法才能避免次优解;
- 必须指定集群数K,这很麻烦
- 当集群具有不同大小、不同密度和非球形时,
K-Means
表现也不佳。 - 还要注意,运行
K-Means
算法之前,要先缩放输入特征,否则集群可能会变形,K-Means
性能会很差。 - 总之,要根据数据,用不同的聚类算法。
使用聚类进行图像分割
import urllib.request
images_path = os.path.join(PROJECT_ROOT_DIR, "images", "unsupervised_learning")
os.makedirs(images_path, exist_ok=True)
DOWNLOAD_ROOT = "https://raw.githubusercontent.com/ageron/handson-ml2/master/"
filename = "ladybug.png"
print("Downloading", filename)
url = DOWNLOAD_ROOT + "images/unsupervised_learning/" + filename
urllib.request.urlretrieve(url, os.path.join(images_path, filename))
#下载图像
from matplotlib.image import imread
image = imread(os.path.join(images_path, filename))
image.shape
#读取图像 (533, 800, 3) (高度,宽度,颜色通道)
segmented_imgs = []
n_colors = (10, 8, 6, 4, 2)
# 对颜色通道(3维特征),10聚类,8聚类,……
for n_clusters in n_colors:
kmeans = KMeans(n_clusters=n_clusters, random_state=42).fit(X)
segmented_img = kmeans.cluster_centers_[kmeans.labels_]
# n_colors颜色数量作为聚类数量,cluster_centers_聚类后每类的中心点(可以简单理解为平均颜色)
segmented_imgs.append(segmented_img.reshape(image.shape))
# 重构5个图形(高,宽,通道)
plt.figure(figsize=(10,5))
plt.subplots_adjust(wspace=0.05, hspace=0.1)
plt.subplot(231)
plt.imshow(image)
plt.title("Original image")
plt.axis('off')
#每个idx
for idx, n_clusters in enumerate(n_colors):
plt.subplot(232 + idx)
plt.imshow(segmented_imgs[idx])
plt.title("{} colors".format(n_clusters))
plt.axis('off')
plt.show()
使用聚类进行预处理
使用digits
数据集(简版的,类似MNIST
的数据集),包含1797个代表数字0到9的灰度8×8图像。
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
X_digits, y_digits = load_digits(return_X_y=True)
X_digits, y_digits = load_digits(return_X_y=True)
X_digits.shape ,y_digits.shape #((1797, 64), (1797,))
X_train, X_test, y_train, y_test = train_test_split(X_digits, y_digits, random_state=42)
X_train.shape,X_test.shape
# (1347, 64), (450, 64) 切分训练和测试集
log_reg = LogisticRegression(multi_class="ovr", solver="lbfgs", max_iter=5000, random_state=42)
# ovr : 1对剩余的2分类;solver="lbfgs" 拟牛顿法的一种,利用损失函数二阶导数矩阵即海森矩阵来迭代优化损失函数
log_reg.fit(X_train, y_train)
log_reg_score = log_reg.score(X_test, y_test)
log_reg_score #0.9688888888888889
#评估
96.89%的准确率,BASELINE~
让我们看看使用K-Means
作为预处理步骤是否可以做得更好。
创建一管道,首先将训练集分为50个集群,并用图像到50个集群的距离替换图像,然后应用逻辑回归模型:
from sklearn.pipeline import Pipeline
from sklearn.cluster import KMeans
pipeline = Pipeline([
("kmeans", KMeans(n_clusters=50, random_state=42)),
("log_reg", LogisticRegression(multi_class="ovr", solver="lbfgs", max_iter=5000, random_state=42)),
])
pipeline.fit(X_train, y_train)
pipeline_score = pipeline.score(X_test, y_test)
pipeline_score #0.9777777777777777
#错误率降低了近30%,由3.1%--2.2%。
错误率降低了30!而且此例选择的K
值是任意选择的50。
由于K-Means只是分类流水线中的一个预处理步骤,找到一个好的K
比以前简单得多:无需执行轮廓分析或最小化惯性分析,即K
的最优解,是在交叉验证过程中的最佳分类性能的值。使用网格搜索找最佳得K
:
from sklearn.model_selection import GridSearchCV
param_grid = dict(kmeans__n_clusters=range(2, 100)) #__ 两个下划线
grid_clf = GridSearchCV(pipeline, param_grid, cv=3, verbose=2) #网格搜索依赖硬件,耗时越10分钟
grid_clf.fit(X_train, y_train)
grid_clf.best_params_
#{'kmeans__n_clusters': 95}
使用聚类进行半监督学习
聚类的另一个用例是在半监督学习中,当我们有大量未标记的实例而仅有少量标记的实例时。
如下逻辑回归模型在只有50个标记实例时的性能:
n_labeled = 50
log_reg = LogisticRegression(multi_class="ovr", solver="lbfgs", random_state=42)
log_reg.fit(X_train[:n_labeled], y_train[:n_labeled])
log_reg.score(X_test, y_test)
#0.8333333333333334
性能不佳~
首先,让我们将训练集聚类为50集群,然后对于每个集群,找到最接近中心点的图像。我们将这些图像称为代表性图像:
k = 50
kmeans = KMeans(n_clusters=k, random_state=42)
X_digits_dist = kmeans.fit_transform(X_train) #无监督
print(X_digits_dist.shape) #(1347, 50) 将1347个实例,聚类对应成50个类
representative_digit_idx = np.argmin(X_digits_dist, axis=0)
#[ 911 559 23 159 736 ... ,距离中心点最小的的50个图像的索引
X_representative_digits = X_train[representative_digit_idx]
#代表性图像
#描图,绘制这50个代表性图像。
plt.figure(figsize=(8, 2))
for index, X_representative_digit in enumerate(X_representative_digits):
plt.subplot(k // 10, 10, index + 1)
plt.imshow(X_representative_digit.reshape(8, 8), cmap="binary", interpolation="bilinear")
plt.axis('off')
plt.show()
y_train[representative_digit_idx]
#查看这50个代表性图像在原始数据集中对应的标签。
#array([4, 8, 0, 6, 8, 3, 7, 7, 9, 2, 5, 5, 8, 5, 2, 1, 2, 9, 6, 1, 1, 6,
# 9, 0, 8, 3, 0, 7, 4, 1, 6, 5, 2, 4, 1, 8, 6, 3, 9, 2, 4, 2, 9, 4,
# 7, 6, 2, 3, 1, 1])
y_representative_digits = np.array([
4, 8, 0, 6, 8, 3, 7, 7, 9, 2, 5, 5, 8, 5, 2, 1, 2, 9, 6, 1, 1, 6,
9, 0, 8, 3, 0, 7, 4, 1, 6, 5, 2, 4, 1, 8, 6, 3, 9, 2, 4, 2, 9, 4,
7, 6, 2, 3, 1, 1])
#为实现半监督学习,构建只有这50个代表图像的标签
现在我们有了一个只有50个标记实例的数据集,但它们不是完全随机的实例,而是其集群的代表性图像。让我们看看性能是否更好。
log_reg = LogisticRegression(multi_class="ovr", solver="lbfgs", max_iter=5000, random_state=42)
log_reg.fit(X_representative_digits, y_representative_digits)
log_reg.score(X_test, y_test)
#0.9222222222222223 对比前面的0.833 性能提升!
虽然还是在只有50个实例上训练模型,但性能提升很多
由于标记实例通常代价高昂且痛苦,特别是当必须由专家手动完成时,最好让他们标记代表性实例,而不仅仅是随机实例。
DBSCAN
算法
该算法将集群定义为高密度的连续区域。工作原理如下
- 对于每个实例,该算法都会计算在距离它 $\epsilon $ 范围内有多少个实例。该区域称为实例的 $ \epsilon-邻域 $。
- 如果一个实例在其 $ \epsilon-邻域 $ 内至少包含
min_samples
个实例(包括自身),则该实例被视为核心实例。换句话说,核心实例是位于密集区域中的实例。 - 核心实例附近的所有实例都属于同一集群。这个$ \epsilon-邻域 $ 可能包括其他核心实例。因此,一长串相邻的核心实例形成一个集群。
- 任何不是核心实例且邻居中没有实例的实例都视为异常。
- 如果所有集群足够密集并且被低密度区域很好的分隔开,则该算法效果很好。
DBSCAN
算法虽然有fit_predict()
方法,但不能预测新实例隶属哪个集群。可以结合其他算法使用。
from sklearn.datasets import make_moons
from sklearn.cluster import DBSCAN
X, y = make_moons(n_samples=1000, noise=0.05, random_state=42)
dbscan = DBSCAN(eps=0.05, min_samples=5) # \epsilon 半径0.05,范围内最少5个实例(含自身)
dbscan.fit(X)
len(dbscan.labels_) #1000,拿到1000个样本的标签
dbscan.labels_[:10] #array([ 0, 2, -1, -1, 1, 0, 0, 0, 2, 5], dtype=int64) -1集群代表异常
len(dbscan.core_sample_indices_) #核心样本实例808个,core_sample_indices_表示核心实例样本的索引。
dbscan.components_[:3] #核心实例本身,此例即808个核心实例的坐标。
np.unique(dbscan.labels_) #array([-1, 0, 1, 2, 3, 4, 5, 6], dtype=int64),聚类为8个集群,-1代表异常
dbscan2 = DBSCAN(eps=0.2) #修改半径 \epsilon为0.2 ,半径扩大
dbscan2.fit(X)
len(dbscan2.core_sample_indices_) # 1000个核心实例
np.unique(dbscan.labels_) # 聚类为2个集群
#描图,左图聚类成8个集群,红叉代表异常,右图完美。
DBSCAN
算法结合K-近邻算法实例代码:
dbscan = dbscan2 # 使用eps=0.2的聚类结果
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=50)
knn.fit(dbscan.components_, dbscan.labels_[dbscan.core_sample_indices_]) #使用核心实例训练K近邻
X_new = np.array([[-0.5, 0], [0, 0.5], [1, -0.1], [2, 1]])
knn.predict(X_new) #用k近邻预测4个新实例 结果:array([1, 0, 1, 0], dtype=int64)
knn.predict_proba(X_new) #查看4个新实例概率,如下
#array([[0.18, 0.82], ======1
# [1. , 0. ], ======0
# [0.12, 0.88], ======1
# [1. , 0. ]]) ======0
#如图,+号代表4个新实例,绿色区域是0号集群。
其他的聚类算法
- 聚集聚类
- BIRCH
- 均值漂移
- 相似性传播
- 谱聚类
高斯混合模型
EM
期望最大化算法:
不仅可以找到集群中心( μ ( 1 ) \mu^{(1)} μ(1) 到 μ ( k ) \mu^{(k)} μ(k)) ,而且可以找到它们的大小、形状和方向( Σ ( 1 ) \Sigma^{(1)} Σ(1) 到 Σ ( k ) \Sigma ^{(k)} Σ(k)),以及它们的相对权重( ϕ ( 1 ) \phi ^{(1)} ϕ(1) 到 ϕ ( k ) \phi ^{(k)} ϕ(k) ) 。
通过设置“协方差类型”超参数,可以对算法查找的协方差矩阵施加约束
“full”
(默认值):无约束,所有集群都可以采用任何大小的椭球形状。“tied”
:所有集群必须具有相同的形状,可以是任何椭球体(即,它们共享相同的协方差矩阵)。“spherical”
:所有集群都必须是球形的,但它们可以有不同的直径(即不同的方差)。“diag”
:集群可以呈现任何大小的任何椭球形状,但椭球的轴必须与轴平行(即协方差矩阵必须是对角的)。
代码示例(构造数据集)
from sklearn.datasets import make_blobs
X1, y1 = make_blobs(n_samples=1000, centers=((4, -4), (0, 0)), random_state=42)
print(X1.shape) #(1000,2)
X1 = X1.dot(np.array([[0.374, 0.95], [0.732, 0.598]]))
X2, y2 = make_blobs(n_samples=250, centers=1, random_state=42)
X2 = X2 + [6, -8]
X = np.r_[X1, X2]
y = np.r_[y1, y2]
print(X.shape) #(1250,2)
plt.scatter(X[:,0],X[:,1])
代码示例(聚类)
from sklearn.mixture import GaussianMixture
gm = GaussianMixture(n_components=3, n_init=10, random_state=42) # k=3,迭代次数10,默认1
gm.fit(X)
gm.weights_ #得到权重 /phi
gm.covariances_ #得到协方差 /Sigma
gm.predict(X) #查看聚类结果(预测结果)
gm.predict_proba(X) #查看聚类到每个集群的概率
代码示例(调整协方差类型)
gm_full = GaussianMixture(n_components=3, n_init=10, covariance_type="full", random_state=42)
gm_tied = GaussianMixture(n_components=3, n_init=10, covariance_type="tied", random_state=42)
gm_spherical = GaussianMixture(n_components=3, n_init=10, covariance_type="spherical", random_state=42)
gm_diag = GaussianMixture(n_components=3, n_init=10, covariance_type="diag", random_state=42)
gm_full.fit(X)
gm_tied.fit(X)
gm_spherical.fit(X)
gm_diag.fit(X)
使用高斯混合进行异常检测
异常检测(也成为离群值检测),是检测严重偏离标准的实例的任务。 正常的实例也称为内值
高斯混合可用于异常检测:位于低密度区域的实例可视为异常。必须定义要使用的密度阈值。例如,在一家试图检测缺陷产品的制造公司中,缺陷产品的比例通常是众所周知的。假设它等于4%,则可以将密度阈值设置为导致4%的实例位于该阈值密度以下区域的值:
densities = gm.score_samples(X)
density_threshold = np.percentile(densities, 4)
anomalies = X[densities < density_threshold] #得到密度小于4%的点 ,即异常值
#如图红星
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P8RFBh0i-1635767925717)(ml_handout09.assets/mixture_anomaly_detection_plot.png)]
选择聚类数
K-Means
中可以使用惯性或轮廓分数来选择适当的集群数K。
高斯混合中不行,因为当集群不是球形或具有不同大小时,上面方法不可靠
可以尝试找到最小化理论信息量准则的模型,如:贝叶斯信息标准(BIC
)或Akaike
信息标准(AIC
)
gms_per_k = [GaussianMixture(n_components=k, n_init=10, random_state=42).fit(X) for k in range(1, 11)]
#k=1 to 10 尝试10次高斯混合
bics = [model.bic(X) for model in gms_per_k]
aics = [model.aic(X) for model in gms_per_k] #得到每个k的bic和aic 绘图如下
plt.figure(figsize=(8, 3))
plt.plot(range(1, 11), bics, "bo-", label="BIC")
plt.plot(range(1, 11), aics, "go--", label="AIC")
plt.xlabel("$k$", fontsize=14)
plt.ylabel("Information Criterion", fontsize=14)
plt.axis([1, 9.5, np.min(aics) - 50, np.max(aics) + 50])
plt.annotate('Minimum',
xy=(3, bics[2]),
xytext=(0.35, 0.6),
textcoords='figure fraction',
fontsize=14,
arrowprops=dict(facecolor='black', shrink=0.1)
)
plt.legend()
plt.show()
遍历查询集群数和协方差类型超参数的最佳组合
min_bic = np.infty
for k in range(1, 11):
for covariance_type in ("full", "tied", "spherical", "diag"):
bic = GaussianMixture(n_components=k, n_init=10,
covariance_type=covariance_type,
random_state=42).fit(X).bic(X)
if bic < min_bic:
min_bic = bic
best_k = k
best_covariance_type = covariance_type
best_k
best_covariance_type
# 输出最好的K和最好的协方差类型。
贝叶斯高斯混合模型。
其他用于异常检测和新颖性检测的算法:
- PCA
- Fast-MCD(最小协方差决定)
- 隔离森林
- 局部离群因子
- 蛋类SVM