机器学习
引言
基本术语
监督学习与无监督学习
监督学习
无监督学习
线性回归
基本形式
单变量线性回归
代价函数
代价函数推导
代价函数直观理解
- 如图 2.4的假设函数
梯度下降
梯度下降
线性回归的梯度下降
线性代数回顾
矩阵与向量
加法和标量乘法
矩阵向量乘法
矩阵乘法
矩阵乘法特征
多元线性回归
多元线性回归
多元梯度下降
多元梯度下降法演练 1 - 特征缩放
多元梯度下降法演练 2 - 学习率
特征和多项式回归
正规方程
正规方程与矩阵不可逆
逻辑回归 (Logistic Regression)
分类问题
假设陈述
决策界限
代价函数
梯度下降
高级优化
多元分类: 一对多
正则化 (Regularization)
过拟合的问题
代价函数
线性回归的正则化
逻辑回归的正则化
损失函数与代价函数
常见的损失函数
常见的代价函数
神经网络: 表述
神经网络: 表述
神经元和大脑
模型表示 1
模型表示 2
神经网络的学习
聚类(Clustering)
在监督学习中,数据集是带标签的,我们的目标是找到能够区分正样本和负样本的决策边界。即在监督学习中,我们需要根据一系列标签拟合一个假设函数。而在无监督学习中,数据集是没有附带任何标签的,如图 1.1
图上有一系列点,但它们没有标签 y,因此该数据集可以表示位:{x(1), x(2), x(3), . . . , x(m)};则在无监督学习中,将这类数据集运用到算法中,而算法的任务计算找到隐含在数据中的内在结构。如图上的数据看起来可以分成两个分开的点集(称为簇),而这个能找到这些簇的算法,就被称为聚类算法。当然,除聚类算法外还有其他无监督学习算法,它们可以为我们找到其他类型的结构或者其他的一些模式,而不只是簇。
K-均值算法 (K-means)
基本定义
K-均值算法是一种迭代求解的聚类分析算法,算法接受一个未标记的数据集 (即数据集不带 y),然后将数据聚类成不同的组 (簇)。其步骤是预将分层 K 组,则会随机选取 K 个对象作为初始的聚类中心,然后计算每个对象与各个聚类中心之间的距离,把每个对象分配给距离它最近的聚类中心,此时数据集会分成了 K 个簇,然后计算这个簇中所有点的均值,并将该簇的聚类中心移动到这个均值上。不断重复这个过程,直至中心点不再变化为止。
K-均值算法会做两件事:1. 簇分配;2. 移动聚类中心。
例如:假设有一个无标签的数据集,想将其分为两个簇,执行 K-均值算法。如图 1.3 所示,首先随机生成两点,作为聚类中心,然后根据距离将样本分配给距离最近的聚类中心,然后将聚类中心移动到对应簇的均值上,不断迭代该过程,直至中心点不再变化为止
K-Means 算法步骤
1.输入:
①样本集:
D
=
{
X
1
⃗
,
X
2
⃗
,
…
,
X
m
⃗
}
D=\{\vec{X_1},\vec{X_2},\dots,\vec{X_m}\}
D={X1,X2,…,Xm}。其中,每个样本
X
j
⃗
\vec{X_j}
Xj是由
n
n
n个属性组成的特征向量
(
x
j
1
;
x
j
2
;
…
,
x
j
n
)
(x_{j1};x_{j2};\dots,x_{jn})
(xj1;xj2;…,xjn)
② 聚类簇数
k
k
k
2.算法目标:确定最优的聚类中心
3.算法过程:
①从数据集
D
D
D中随机选择k个样本作为初始聚类中心:
{
μ
1
⃗
,
μ
2
⃗
,
…
,
μ
k
⃗
}
\{ \vec{\mu_1},\vec{\mu_2},\dots,\vec{\mu_k}\}
{μ1,μ2,…,μk},(相当于图中红色和蓝色叉的位置)
② 定义k个簇类的空集合,用于存储本簇的样本。即令
C
i
=
∅
(
1
≤
i
≤
k
)
C_i=\varnothing (1\leq i \leq k)
Ci=∅(1≤i≤k)
③分别计算每个聚类样本
X
j
⃗
\vec{X_j}
Xj到各聚类中心
μ
i
⃗
(
1
≤
i
≤
k
)
\vec{\mu_i}(1\leq i \leq k)
μi(1≤i≤k)的距离(欧氏距离):
d
j
i
=
∣
∣
x
j
−
μ
i
∣
∣
2
d_{ji}=||x_j-\mu_i||_2
dji=∣∣xj−μi∣∣2;并找到与该样本的距离最近的聚类中心:
λ
j
=
a
r
g
m
i
n
i
i
n
{
1
,
2
,
…
,
k
}
d
j
i
\lambda_j= arg\ min_{i in\{1,2,\dots,k\}}d_{ji}
λj=arg miniin{1,2,…,k}dji,然后将该样本
X
j
⃗
\vec{X_j}
Xj划入对应的簇中:
C
λ
j
=
C
λ
j
∪
{
X
j
⃗
}
C_{\lambda j}=C_{\lambda j} \cup \{\vec{X_j}\}
Cλj=Cλj∪{Xj}
④ 重新计算每个聚类中心,
μ
‘
=
1
∣
c
(
i
)
∣
∑
x
∈
c
(
i
)
x
\mu`=\frac{1}{|c^{(i)}|}\sum_{x \in c^{(i)}}x
μ‘=∣c(i)∣1∑x∈c(i)x
⑤ 如果聚类中心
μ
‘
≠
μ
i
\mu` \neq \mu_i
μ‘=μi,则更新聚类中心
μ
i
\mu_i
μi为
μ
‘
\mu`
μ‘
⑥如果聚类中心
μ
‘
=
μ
i
\mu` = \mu_i
μ‘=μi,则保持聚类中心不变
⑦ 重复步骤
(
1
−
6
)
(1-6)
(1−6),直到达到最大迭代次数或者聚类中心不再变化,则停止,否则,继续操作。
⑧输出最终簇划分
C
=
{
C
1
,
C
2
,
…
,
C
k
}
C=\{C_1,C_2,\dots,C_k\}
C={C1,C2,…,Ck}
K-均值算法伪代码
Repeat {
for i = 1 to m
c ( i ) := index ( form 1 to K ) of cluster centroid closest to x ( i )
for k = 1 to K
k := average ( mean ) of points assigned to cluster k
}
K-均值算法的关键:
内循环中,第一个for循环(即步骤2)是簇分配步骤,用
c
(
i
)
c^{(i)}
c(i)来表示第1到第
k
k
k个最接近
x
(
i
)
x^{(i)}
x(i)的聚类中心,该循环会根据它离哪个聚类中心近一些,将其分配给对应聚类中心的簇。另一种表达方式是:想要计算
c
(
i
)
c^{(i)}
c(i),就要用第
i
i
i个样本
x
(
i
)
x^{(i)}
x(i),然后计算出这个样本距离每个聚类中心的距离,找出能够最小化距离的聚类中心的k值,赋值给
c
(
i
)
c^{(i)}
c(i),数学表达如下:
c
(
i
)
=
m
i
n
k
∣
∣
x
(
i
)
−
μ
k
∣
∣
2
c^{(i)}=\mathop{min}\limits_{k}||x^{(i)}-\mu_k||_2
c(i)=kmin∣∣x(i)−μk∣∣2
第二个for循环(即步骤4-6)是移动聚类中心,
μ
k
\mu_k
μk表示这个簇中所有点的均值,即
μ
k
=
1
∣
c
(
i
)
∣
∑
x
∈
c
(
i
)
x
\mu_k=\frac{1}{|c^{(i)}|}\sum_{x \in c^{(i)}}x^{}
μk=∣c(i)∣1∑x∈c(i)x,其中
x
x
x为簇
c
(
i
)
c^{(i)}
c(i)的均值向量。例如存在
x
(
1
)
,
x
(
5
)
,
x
(
6
)
x^{(1)},x^{(5)},x^{(6)}
x(1),x(5),x(6)对应的
c
(
1
)
=
2
,
c
(
5
)
=
2
,
c
(
6
)
=
2
c^{(1)}=2,c^{(5)}=2,c^{(6)}=2
c(1)=2,c(5)=2,c(6)=2,即表示样本1,5,6分配给了聚类中心2,则移动聚类中心时
μ
2
=
[
x
(
1
)
+
x
(
5
)
+
x
(
6
)
]
3
\mu_2 = \frac{[x^{(1)}+x^{(5)}+x^{(6)}]}{3}
μ2=3[x(1)+x(5)+x(6)],此时
μ
2
\mu_2
μ2为n维向量,因为
x
(
i
)
x^{(i)}
x(i)都是n维向量,计算出均值后
μ
2
\mu_2
μ2就会移动到这个均值上
那么如果存在一个没有点的聚类中心,最常见的做法是直接移除这个聚类中心,但如果这么做的话,会得到K-1个簇而不是K个簇。如果确实需要K个簇,那么可以重新随机初始化这个聚类中心。
K-均值算法也可以用来分离不佳的簇,如下图数据集与前面不同的是没有明显的分组,但同样可以应用该算法将其分成几个簇。
优化目标
K-均值最小化问题是要最小化所有的数据点与其所关联的聚类中心点之间的距离之和,因此K-均值的代价函数(又称畸变函数)为
J
(
c
(
1
)
,
…
,
c
(
m
)
,
μ
1
,
…
,
μ
k
)
=
1
m
∑
i
=
1
m
∣
∣
x
(
i
)
−
μ
c
(
i
)
∣
∣
2
J(c^{(1)},\dots,c^{(m)},\mu_1,\dots,\mu_k)=\frac 1m \sum_{i=1}^{m}||x^{(i)}-\mu_{c^{(i)}}||^2
J(c(1),…,c(m),μ1,…,μk)=m1i=1∑m∣∣x(i)−μc(i)∣∣2
其中
μ
c
(
i
)
\mu_{c^{(i)}}
μc(i)代表与
x
(
i
)
x^{(i)}
x(i)最近的聚类中心点。例如
x
(
i
)
=
5
x^{(i)}=5
x(i)=5,则其
c
(
i
)
=
5
c^{(i)}=5
c(i)=5,也就是说
x
(
i
)
x^{(i)}
x(i)被分配到第五个簇,因此
μ
c
(
i
)
=
μ
5
\mu_{c^{(i)}}=\mu_5
μc(i)=μ5
我们的优化目标是要找出使得代价函数最小的
c
(
1
)
,
…
,
c
(
m
)
,
μ
1
,
…
,
μ
k
c^{(1)},\dots,c^{(m)},\mu_1,\dots,\mu_k
c(1),…,c(m),μ1,…,μk,即:
m
i
n
c
(
1
)
,
…
,
c
(
m
)
,
μ
1
,
…
,
μ
k
J
(
c
(
1
)
,
…
,
c
(
m
)
,
μ
1
,
…
,
μ
k
)
\mathop{min}\limits_{c^{(1)},\dots,c^{(m)},\mu_1,\dots,\mu_k} J(c^{(1)},\dots,c^{(m)},\mu_1,\dots,\mu_k)
c(1),…,c(m),μ1,…,μkminJ(c(1),…,c(m),μ1,…,μk)
在K-均值算法中,第一个循环实际上是在最小化代价函数
J
J
J中的
c
(
i
)
c^{(i)}
c(i)参数,此时要保持最近的聚类中心,即
μ
1
,
…
,
μ
k
\mu_1,\dots,\mu_k
μ1,…,μk的位置不变,该循环会选出
c
(
1
)
,
…
,
c
(
m
)
c^{(1)},\dots,c^{(m)}
c(1),…,c(m)来最小化代价函数;第二个循环是选择
μ
\mu
μ来最小化代价函数
J
J
J的
μ
1
,
…
,
μ
k
\mu_1,\dots,\mu_k
μ1,…,μk。在迭代的过程中,每一次的代价函数应该都在减小或者保持不变,如果出现代价函数增大的情况,则说明实现的过程可能存在错误
随机初始
在进行k-均值的过程中,首先要随机初始化所有的聚类中心点,初始点选择的不同,可能会得到不同的聚类结果,在初始化时应该注意以下两点:
- 我们应该选择 K < m K<m K<m,即聚类中心点的个数要小于所有训练集样本的数量
- 随机选择 K K K个训练样本,然后令 K K K个聚类中心 ( μ 1 , … , μ k ) (\mu_1,\dots,\mu_k) (μ1,…,μk)分别与这 K K K个训练样本相等
K-均值的一个问题在于,它有可能会得到一个局部最优值,而这取决于初始化的情况,初始不同,得到的局部最优值也不同,如下图。
图 1.6 中 1、2、3 采用了不同的初始化聚类中心。而 1. 是全局最优的聚类、2. 和 3. 则是比较失败的聚类,它们只收敛到了局部最优解,而没达到全局最优。
为了解决这个问题,我们通常需要多次运行 K-均值算法,每一次都重新进行随机初始化,最后再比较多次运行 K-均值的结果,选择代价函数最小的结果。
上式中循环次数$ i
设
为
100
,
一
般
取
50
−
1000
之
间
,
应
该
选
择
设为100,一般取50-1000之间,应该选择
设为100,一般取50−1000之间,应该选择K < m $。如果K 在2至10,一般可得到比较好的局部最优解;如果K比较大(比10大很大),那么多次随机初始化对聚类结果不会有太大的改善
选择聚类数
选择聚类数的方法,通常是需要根据不同的问题,人工进行选择的,选择能最好服务于聚类目的的数量。
另一种方法是使用“肘部原则”,但不能期望它每次都有效果。我们所需要做的是改变 K值,也就是聚类类别数目的总数,然后运行 K-均值算法,并计算畸变函数 J。
图 1.7 中的左图是情况较好的,像一个人的肘部。这就是“肘部法则”所做的。从图中可以看出它的畸变值会迅速下降,从 1 到 2,从 2 到 3 之后,在 3 的时候达到一个肘点。在此之后,畸变值就下降的非常慢,那么我们就选 K=3。当你应用“肘部法则”的时候,如果你得到了一个像上面这样的图,那么这将是一种用来选择聚类个数的合理方法。但是有时候会得到像右图一样的曲线,它没有明显的肘点,那么将无法使用该方法选择聚类数
K-均值算法实例
假设存在一个不带标签的数据集,如图 1.8。其中横轴 (x 轴) 为西瓜密度,纵轴 (y 轴)为西瓜含糖量 现在目的是将数据集分成 3 个簇,
如图1.9,算法首先在样本集中随机选取3个样本作为聚类中心,然后计算各个样本与这3个聚类中心的距离,然后把各个样本划分给距离该样本最近的聚类中心所对应的簇。例如某样本与3个聚类中KaTeX parse error: Expected '}', got 'EOF' at end of input: …},\vec{\mu_3}\}的距离为:
0.283
,
0.506
,
0.032
0.283,0.506,0.032
0.283,0.506,0.032,因为该样本与
μ
3
⃗
\vec{\mu_3}
μ3的距离最近,所以将该样本划分给
μ
3
⃗
\vec{\mu_3}
μ3对应的
C
3
C_3
C3类别中,计算完全部样本后,更新聚类中心,让聚类中心往其对应簇的中心移动。不断迭代,直至达到最大迭代次数或者全部聚类中心不在变化时,则停止算法,如图中迭代第三次时,可以说已经收敛不在变化了。则此时的三个聚类中心为最优聚类中心,由此将样本集分成3个簇。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 功能: 计算样本与聚类中心的距离, 返回离簇中心最近的类别
# params: sample: 单个数据样本, centers: k个簇中心
# return: 返回的是当前的样本数据属于那一个簇中心的id或者索引
def distance(sample1, centers1):
# 这里使用欧氏距离计算公式
dist = np.sqrt(np.sum(np.square(sample1 - centers1), axis=1))
minIdx = np.argmin(dist)
return minIdx
# 功能: 对当前的分类集进行可视化展示
def clusters_show(clusters, center, step):
color = ["g", "b", "y"]
plt.figure(figsize=(8, 8))
plt.title("迭代次数: {}".format(step))
plt.xlabel("密度", loc="center")
plt.ylabel("糖含量", loc="center")
# 用颜色区分k个簇的数据样本
for i, cluster in enumerate(clusters):
cluster = np.array(cluster)
plt.scatter(center[:, 0], center[:, 1], marker='x', color='red', s=100)
plt.scatter(cluster[:, 0], cluster[:, 1], c=color[i], marker='.', s=150)
# 功能: 根据输入的样本集与划分的簇数,分别返回k个簇样本
# params: samples:样本集, k:聚类簇数
# return:返回是每个簇的簇类中心
def k_means(samples, k):
data_number = len(samples)
centers_flag = np.zeros((k,))
# 随机在数据中选择k个聚类中心
center = samples[np.random.choice(data_number, k, replace=False)]
plt.title("初始化原型向量")
plt.xlabel("密度", loc="center")
plt.ylabel("糖含量", loc="center")
plt.scatter(center[:, 0], center[:, 1], marker='x', color='red', s=100)
plt.scatter(samples[:, 0], samples[:, 1], c='black')
step = 0
while True:
# 计算每个样本距离簇中心的距离, 然后分到距离最短的簇中心中
clusters = [[] for i in range(k)]
for sample1 in samples:
ci = distance(sample1, center)
clusters[ci].append(sample1)
# 可视化当前的聚类结构
clusters_show(clusters, center, step)
# 分完簇之后更新每个簇的中心点, 得到了簇中心继续进行下一步的聚类
for i, sub_clusters in enumerate(clusters):
new_center = np.array(sub_clusters).mean(axis=0)
# 如果数值有变化则更新, 如果没有变化则设置标志位为1,当所有的标志位为1则退出循环
if (center[i] != new_center).all():
center[i] = new_center
else:
centers_flag[i] = 1
step += 1
if centers_flag.all():
break
return center
# 功能: 根据簇类中心对簇进行分类,获取最后的分类结果
# params: samples是全部的数据样本,centers是聚类好的簇中心
# return: 返回的是子数组
def split_data(samples, centers1):
# 根据中心样本得知簇数
k = len(centers1)
clusters = [[] for i in range(k)]
for samples in samples:
ci = distance(samples, centers1)
clusters[ci].append(samples)
return clusters
if __name__ == '__main__':
# 功能: 设置随机种子, 确保结果可复现
np.random.seed(5)
data = pd.read_csv('watermelon4.0.csv', header=None)
sample = data.iloc[:, 1:3].values
centers = k_means(sample, 3)
plt.show()
学习向量量化 (LVQ)
基本定义
“学习向量量化 (LVQ)”算法目标是通过找到一组原型向量来代表聚类的中心。与其他聚类算法不同的是,LVQ 是有标签的聚类。即假设每个样本是有类别标签的,LVQ 通过这些假设的标签来辅助聚类。
基本思想:
(1) 初始化 q 个原型向量(q 代表需要聚类的类别个数):根据样本的类别标记,从各类中
分别随机选出一个样本作为该类簇的原型,从而组成了一个原型特征向量组。
(2) 对原型向量进行优化:在每一轮迭代中,从样本集中随机挑选一个样本,计算其与原型向量组中每个向量的距离,并选取距离最小的原型向量所在的类簇作为它的划分结果,再与真实类标比较。若划分结果正确,则对应原型向量向这个样本靠近一些;若划分结果不正确,则对应原型向量向这个样本远离一些。
缺点:因为一般使用欧氏距离,各特征的权重是相同的,无法反映不同特征的重要性差异。
LVQ算法实现:
1.输入:
①样本集:
D
=
(
X
1
⃗
,
y
1
)
,
(
X
2
⃗
,
y
2
)
,
…
,
(
X
m
⃗
,
y
m
)
D={(\vec{X_1},y_1),(\vec{X_2},y_2),\dots,(\vec{X_m},y_m)}
D=(X1,y1),(X2,y2),…,(Xm,ym)。其中,每个样本
X
j
⃗
\vec{X_j}
Xj是由
n
n
n个属性组成的特征向量
(
x
j
1
;
x
j
2
;
…
,
x
j
n
)
(x_{j1};x_{j2};\dots,x_{jn})
(xj1;xj2;…,xjn),
y
i
∈
γ
y_i \in \gamma
yi∈γ是样本
X
j
⃗
\vec{X_j}
Xj的类别标签。
② 原型向量的个数
q
q
q,各原型向量预设的类别标记
{
t
1
,
t
2
,
…
,
t
q
}
\{t_1,t_2,\dots,t_q\}
{t1,t2,…,tq}
③ 学习率
η
∈
(
0
,
1
)
\eta \in (0,1)
η∈(0,1)
2.目标:学得一组
n
n
n维原型向量
{
p
1
⃗
,
p
2
⃗
,
…
,
p
q
⃗
}
\{ \vec{p_1},\vec{p_2},\dots,\vec{p_q}\}
{p1,p2,…,pq},每个原型向量代表一个聚类簇,簇标记
t
i
∈
γ
t_i \in \gamma
ti∈γ
3.算法过程:
①初始化一组原型向量
{
p
1
⃗
,
p
2
⃗
,
…
,
p
q
⃗
}
\{ \vec{p_1},\vec{p_2},\dots,\vec{p_q}\}
{p1,p2,…,pq}
②从样本集中随机选取样本
(
X
i
⃗
,
y
i
)
(\vec{X_i},y_i)
(Xi,yi)
③ 计算样本
X
i
⃗
\vec{X_i}
Xi与
p
i
⃗
(
1
≤
i
≤
q
)
\vec{p_i} (1\leq i \leq q)
pi(1≤i≤q)的距离:
d
j
i
=
∣
∣
X
i
⃗
−
p
i
⃗
∣
∣
2
=
∑
u
=
1
n
∣
x
i
u
−
x
j
u
∣
2
d_{ji}=|| \vec{X_i}-\vec{p_i} ||_2=\sqrt{\sum_{u=1}^{n}|x_{iu}-x_{ju}|^2}
dji=∣∣Xi−pi∣∣2=∑u=1n∣xiu−xju∣2
④ 找出与
X
i
⃗
\vec{X_i}
Xi距离最近的原型向量
p
i
∗
p_{i*}
pi∗,其中
i
∗
=
a
r
g
m
i
n
i
∈
{
1
,
2
,
…
,
q
}
d
j
i
i* = arg\ min_{i \in \{1,2,\dots,q\} } d_{ji}
i∗=arg mini∈{1,2,…,q}dji
⑤ 如果样本的标签
y
j
=
=
t
i
∗
y_j==t_{i*}
yj==ti∗,即
X
j
⃗
\vec{X_j}
Xj与
p
i
∗
p_{i*}
pi∗的类别相同,则
p
‘
=
p
i
∗
+
η
⋅
(
X
j
⃗
−
p
i
∗
⃗
)
p`=p_{i*}+\eta\cdot( \vec{X_j}-\vec{p_{i*}})
p‘=pi∗+η⋅(Xj−pi∗),即聚类中心向
X
j
⃗
\vec{X_j}
Xj靠近
⑥如果样本的标签
y
j
≠
t
i
∗
y_j \neq t_{i*}
yj=ti∗,即
X
j
⃗
\vec{X_j}
Xj与
p
i
∗
p_{i*}
pi∗的类别不同,则
p
‘
=
p
i
∗
−
η
⋅
(
X
j
⃗
−
p
i
∗
⃗
)
p`=p_{i*}-\eta\cdot( \vec{X_j}-\vec{p_{i*}})
p‘=pi∗−η⋅(Xj−pi∗),即聚类中心向
X
j
⃗
\vec{X_j}
Xj远离
⑦将原型向量
p
i
∗
p_{i*}
pi∗更新为
p
‘
p`
p‘
⑧重复步骤
2
−
7
2-7
2−7直到满足停止条件(一般是原型向量不再变动或者变动很小,或者达到了最大的迭代次数)
⑨ 输出原型向量
{
p
1
⃗
,
p
2
⃗
,
…
,
p
q
⃗
}
\{ \vec{p_1},\vec{p_2},\dots,\vec{p_q}\}
{p1,p2,…,pq}
LVQ 算法的关键:
其循环内的操作(即步骤
2
−
7
2-7
2−7)是在更新原型向量。即对于样本
x
j
⃗
\vec{x_j}
xj,如果最近的原型向量
p
i
∗
p_{i*}
pi∗与
x
j
⃗
\vec{x_j}
xj的类别相同,则
p
i
∗
p_{i*}
pi∗向
x
j
⃗
\vec{x_j}
xj的方向靠拢,即步骤5所示,此时新的原型向量为:
p
‘
=
p
i
∗
+
η
⋅
(
X
j
⃗
−
p
i
∗
⃗
)
p`=p_{i*}+\eta\cdot( \vec{X_j}-\vec{p_{i*}})
p‘=pi∗+η⋅(Xj−pi∗)
此时
p
‘
与
X
j
⃗
p`\text{与}\vec{X_j}
p‘与Xj的距离为:
因为 η ∈ ( 0 , 1 ) \eta \in (0,1) η∈(0,1),所以距离 ∣ ∣ p ‘ − X j ⃗ ∣ ∣ 2 < ∣ ∣ p i ∗ − X j ⃗ ∣ ∣ 2 || p`-\vec{X_j} ||_2< || p_{i*}-\vec{X_j}||_2 ∣∣p‘−Xj∣∣2<∣∣pi∗−Xj∣∣2,即原型向量更新为 p ‘ p` p‘后距离 x j ⃗ \vec{x_j} xj更近了。
同理,如果最近的原型向量 p i ∗ p_{i*} pi∗与 x j ⃗ \vec{x_j} xj的类别不同,则更新后的原型向量与 x j ⃗ \vec{x_j} xj的距离为: ( 1 + η ) ⋅ ∣ ∣ p i ∗ − X j ⃗ ∣ ∣ 2 (1+\eta)\cdot|| p_{i*}-\vec{X_j}||_2 (1+η)⋅∣∣pi∗−Xj∣∣2,距离变大了,也就是说更远离 x j ⃗ \vec{x_j} xj
一次循环(即步骤
2
−
7
2-7
2−7)生成一组新原型向量后,即实现对样本空间
χ
\chi
χ进行簇划分,对于任意样本
X
⃗
\vec{X}
X,将被划分到与其距离最近的原型向量所代表的簇中;也就是说,对于每个原型向量
p
i
p_{i}
pi都定义了区域
R
i
R_i
Ri,在这个区域内每个样本与
p
i
p_{i}
pi的距离小于该样本与其他原型向量
p
i
‘
(
i
‘
≠
i
)
p_{i`} (i` \neq i )
pi‘(i‘=i)的距离,即
R
i
=
{
x
⃗
∈
χ
∣
∣
∣
x
⃗
−
p
i
⃗
∣
∣
2
≤
∣
∣
x
⃗
−
p
i
‘
⃗
∣
∣
2
,
i
‘
≠
i
}
R_i = \{\vec{x}\in \chi|\ ||\vec{x}-\vec{p_i}||_2 \leq ||\vec{x}-\vec{p_i`}||_2,i` \neq i\}
Ri={x∈χ∣ ∣∣x−pi∣∣2≤∣∣x−pi‘∣∣2,i‘=i}
由此形成对样本空间
χ
\chi
χ的簇划分
{
R
1
,
R
2
,
…
,
R
q
}
\{R_1,R_2,\dots,R_q \}
{R1,R2,…,Rq},该簇划分通常称为``Voronoi部分’’。
LVQ 算法实例
假设存在一个带标签的数据集,如图\ref{C10}。其中横轴(即
x
x
x轴)为西瓜密度,纵轴(即
y
y
y轴)为西瓜含糖量;不同颜色代表不同类别标记,记黄色的类别标记为
c
1
c1
c1(好瓜=否),黑色的类别标记为
c
2
c2
c2(好瓜=是)
现在目的是将数据集分成5个簇,即
q
=
5
q=5
q=5。也就是说要找到5个原型向量
{
p
1
⃗
,
p
2
⃗
,
p
3
⃗
,
p
4
⃗
,
p
5
⃗
}
\{ \vec{p_1},\vec{p_2},\vec{p_3},\vec{p_4},\vec{p_5}\}
{p1,p2,p3,p4,p5},并定义其对应的类别标记为:
c
1
,
c
2
,
c
3
,
c
4
,
c
5
c_1,c_2,c_3,c_4,c_5
c1,c2,c3,c4,c5,(即分成3个’‘好瓜=是’'的簇和2个``好瓜=否’’的簇)
如图,算法首先在样本集中随机选取5个样本作为原型向量,然后计算各个样本与这5个原型向量的距离,然后把各个样本划分给距离该样本最近的原型向量。例如某样本与5个原型向量
{
p
1
⃗
,
p
2
⃗
,
p
3
⃗
,
p
4
⃗
,
p
5
⃗
}
\{ \vec{p_1},\vec{p_2},\vec{p_3},\vec{p_4},\vec{p_5}\}
{p1,p2,p3,p4,p5}的距离为:
0.283
,
0.506
,
0.434
,
0.260
,
0.032
0.283,0.506,0.434,0.260,0.032
0.283,0.506,0.434,0.260,0.032,因为该样本与
p
5
⃗
\vec{p_5}
p5的距离最近,所以将该样本划分给
p
5
⃗
\vec{p_5}
p5对应的
c
1
c_1
c1类别中,然后
p
5
⃗
\vec{p_5}
p5就会向该样本靠近,更新后的新原型向量
p
5
⃗
\vec{p_5}
p5为(此处学习率为0.1):
如图中所示通过不断迭代更新,最终会划分成5个不同簇,迭代到500次以后基本上已经收敛了
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
def LVQ(X1, y1, pNum, learningRate=0.1):
# 随机选择pNum个样本作为p向量
idx = np.random.choice(X1.shape[0], pNum)
p = X1[idx, :]
# 获取p的标签
py = y1[idx]
# 画图用
fig, ax = plt.subplots(3, 3, figsize=(12, 12), sharex='all', sharey='all')
# 解决中文显示问题
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
# 初始化原型向量图
ax[0, 0].scatter(X1[:, 0], X1[:, 1], c=y1)
ax[0, 0].scatter(p[:, 0], p[:, 1], marker='x', color='red', s=100)
ax[0, 0].set_title("初始化原型向量")
ax[0, 0].set_xlim(xlim)
ax[0, 0].set_ylim(ylim)
j = 0
for i in range(2001):
# 随机选择一个样本xi
idx = np.random.choice(X1.shape[0], 1)
xi = X1[idx, :]
# 计算xi到各个p向量的距离
dist = np.sqrt(np.sum(np.square(xi - p), axis=1))
# 找到最小距离p向量的索引
minIdx = np.argmin(dist)
# 如果xi的标签与该向量的标签相等,则靠近,不然就原理
if y1[idx] == py[minIdx]:
p[minIdx] = p[minIdx] + learningRate * (xi - p[minIdx])
else:
p[minIdx] = p[minIdx] - learningRate * (xi - p[minIdx])
# 每循环500次画图
if (i > 0) and (i in [20, 50, 100, 200, 500, 1000, 1500, 2000]):
j += 1
clusters = []
# 对于样本里的每一个x,找到它属于哪个类
for x in X1:
dist = np.sqrt(np.sum(np.square(x - p), axis=1))
label = np.argmin(dist)
clusters.append(label)
if j < 3:
k = 0
elif j < 6:
k = 1
else:
k = 2
if not ((k == 0) and ((j % 3) == 0)):
ax[k, j % 3].scatter(X[:, 0], X[:, 1], c=clusters)
ax[k, j % 3].scatter(p[:, 0], p[:, 1], marker='x', color='red', s=100)
ax[k, j % 3].set_title("迭代次数: %d" % i)
ax[k, j % 3].set_xlim(xlim)
ax[k, j % 3].set_ylim(ylim)
if __name__ == "__main__":
data = pd.read_csv('watermelon4.0.csv', header=None)
data['y'] = np.zeros((data.shape[0], 1), dtype=int)
data.iloc[9:22, 3] = 1
X = data.iloc[:, 1:3].values
y = data.iloc[:, 3].values
plt.scatter(X[:, 0], X[:, 1], c=y)
xlim = (plt.axis()[0], plt.axis()[1])
ylim = (plt.axis()[2], plt.axis()[3])
LVQ(X, y, 5)
plt.show()