机器学习初识

K-Means(K-均值) ->聚类

K-Means 是一种使用广泛的聚类的算法,将各种聚类子集内的所有数据样本的均值作为该聚类的代表点。

算法主要思想:通过迭代把数据集划分为不同的类别,使得评价聚类性能的准则函数达到最优,从而使得生成的每个聚类类内紧凑,类间独立。

由于每次都要计算所有的样本与每一个质心之间的距离,在大规模数据集上收敛速度较慢。

算法思想及步骤

  1. 设置 k , 以及k个聚类中心$u_1, u_2, …, u_k$
  2. 分组:
    • 将样本分配给距离最近的聚类中心
    • 由这些样本构造不相交(non-overlapping)的聚类
  3. 确定中心: 用个聚类的均值向量作为新的聚类中心
  4. 重复2,3直至算法收敛即聚类中心达到稳定。

算法要点

  1. 选定某种距离作为数据样本间的相似性度量

    k-means聚类算法不适合处理离散型属性,对连续性属性比较适合

    计算样本之间的距离时,根据实际需要选择距离测度作为算法的相似性度量,如欧式距离,曼哈顿距离(各维差的绝对值之和),马氏距离(两个服从同一分布并且其协方差矩阵为Σ的随机变量之间的差异程度)

  2. 选择评价聚类性能的准则函数

    k-means聚类算法使用误差平方和准则函数来评价聚类性能。

    $$cost·Function = \sum_{i=1}^{k}\sum_{x_j \in S_i }(x_j - u_i)^2$$

  3. 相似性的计算根据一个簇中对象的平均值(均值向量)来进行

性能分析

Advantages:

  • 解决聚类问题的经典算法,简单,快速
  • 处理大数据集,相对可伸缩和高效率的,复杂度是O(nkt), k <<n , t << n;
  • 结果簇之间是密集的,而且簇与簇之间区别明显时,效果教好。

Disadvantages:

  • 簇的平均值被定义的情况下才能使用,处理符号属性的数据不适用。
  • 必须事先指定k
  • 对初始值敏感,对于不同的初始值,可能会导致不同的结果
  • 对于“噪声”和孤立数据点敏感,少量的该数据将产生极大的影响。

算法改进

  1. 对于离散型属性和符号型属性,度量相似度采用:
    • 比较记录,属性值相同为0,不相同为1,并取和。
    • 更新新聚类中心,选择每个簇中的属性值出现频率最大的一个或几个作为代表簇的属性值。
  2. 对于类别数k的指定,考虑类别的合并与分裂。
    • 合并: 某一类中样本太少,两类聚类中心距离过近
    • 分裂:方差过大
  3. 初始值敏感
    • 多设置不同的初始值,对比最后的结果,直至结果趋同。(耗时)
  4. 对于“噪声”和孤立数据点
    • 不采用均值,采用中心点
    • 基于最小化所有对象与其参照点之间相异度之和的原则???

算法实现(python)

导入数据

1
2
3
4
5
6
7
8
def loadDataSet(fileName):
dataMat = []
with open(fileName, 'r') as fp:
for line in fp.readlines():
curLine = line.strip().split('\t')
fltLine = [float(x) for x in curLine]
dataMat.append(fltLine)
return mat(dataMat)

生成k个点

1
2
3
4
5
6
7
8
9
10
11
12
13
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA - vecB, 2)))

def randCent(dataSet, k):
# the dimension
n = shape(dataSet)[1]
# get the random center coordinates
centroids = mat(zeros((k, n)))
for j in range(n):
minJ = min(dataSet[:, j])
rangeJ = float(max(dataSet[:, j]) - minJ)
centroids[:,j] = minJ + rangeJ * random.rand(k, 1)
return centroids

k-means:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
m = shape(dataSet)[0]
clusterAssment = mat(zeros((m, 2)))
centroids = createCent(dataSet, k)
clusterChanged = True
while clusterChanged:
clusterChanged = False
# calcuate the distance
for i in range(m):
minDist = inf
minIndex = -1
for j in range(k):
distJI = distMeas(centroids[j, :], dataSet[i, :])
if distJI < minDist:
minDist = distJI
minIndex = j
if clusterAssment[i, 0] != minIndex:
# unable to be stable
clusterChanged = True
clusterAssment[i, :] = minIndex, minDist**2
# print(centroids)
# update center coordinates
for cent in range(k):
ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]
centroids[cent, :] = mean(ptsInClust, axis=0)
return centroids, clusterAssment

画图

1
2
3
4
5
def draw(dataSet, centroids, clusterAssment):
m = shape(dataSet)[0]
for i in range(m):
plt.scatter(dataSet[i, 0], dataSet[i, 1], c=color(clusterAssment[i, 0]), )
plt.show()

主程序

1
2
3
4
def main():
dataSet = loadDataSet('places.txt')
centroids, clusterAssment = kMeans(dataSet, 4)
draw(dataSet, centroids, clusterAssment)

二分k-means优化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
def biKmeans(dataSet, k, distMeas=distEclud):
# number of dataSet
m = shape(dataSet)[0]
clusterAssment = mat(zeros((m, 2)))
# the first of center coordinate
centroidOne = mean(dataSet, axis=0).tolist()[0]
centList = [centroidOne]
for j in range(m):
# SSE of the first center coordiante and every one of dataset
clusterAssment[j, 1] = distMeas(mat(centroidOne), dataSet[j, :])**2
while (len (centList) < k):
lowestSSE = inf
for i in range(len(centList)):
# 第 i 类的数据集
ptsInCurrClust = dataSet[nonzero(clusterAssment[:, 0].A == i)[0], :]
centroidMat, splitClustAss = kMeans(ptsInCurrClust, 2, distMeas)
# calculate the SSE
sseSplit = sum(splitClustAss[:, 1])
sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:, 0].A != i)[0], 1])
print("SSE of split : %d \nSSE of nosplit : %d" % (sseSplit, sseNotSplit))
if (sseSplit + sseNotSplit) < lowestSSE:
bestCentToSpit = i
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy()
lowestSSE = sseSplit + sseNotSplit

# divide it
bestClustAss[nonzero(bestClustAss[:, 0].A == 1)[0], 0] = len(centList)
bestClustAss[nonzero(bestClustAss[:, 0].A == 0)[0], 0] = bestCentToSpit

print("The bestCentToSpit is %d " % (bestCentToSpit))
print("The len of bestClustAss is %d " % (len(centList)))
# update and add center coordinate
centList[bestCentToSpit] = bestNewCents[0, :]
centList.append(bestNewCents[1, :])
# change the class which is divided into 2 parts
clusterAssment[nonzero(clusterAssment[:, 0].A == bestCentToSpit)[0], :] = bestClustAss

return centList, clusterAssment

经过几次测试,发现不如不优化。这个是通过我每次去找一个当前最优解。

KNN(近邻法)

最近邻

设有c个类$ \omega_1, \omega_2,…, \omega_c $,每类有$N_i$个样本。

待测样本的到第i类的最近距离: $g_i(x) = min|| x - x_i^k || \ \ \ (x_i^k表示的是第i类第k个样本,k=1,…N_i)$

距离我们除了可以采用欧式距离和曼哈顿距离,还可以采用明考斯基距离。

名考夫斯基距离:

$$d(a,b) = (\sum_{i = 1}^{n}|x_{a_i} - x_{b_i}|^p)^{\frac{1}{p}}$$

加权名考夫斯基距离:

$$d(a,b) = (\sum_{i = 1}^{n}w_i·|x_{a_i} - x_{b_i}|^p)^{\frac{1}{p}}$$

决策规则

把待测样本加g(x)最小的那个类中。

k = 1时称为最近邻分类器。

给定测试样本,若其最近邻样本为z,则最近邻分类器出错的概率就是x与z类别标记不同的概率

$$ P(error) = 1 - \sum _{c \in \gamma}P(c | x) P(c|z)$$

最近邻分类器虽简单,但是他的泛化错误率不超过贝叶斯最优分类器的错误率的两倍。

低维嵌入

当训练样本的采集密度足够大,成为”密采样(dense sample)”,但随着维度的提升,样本数目明显不够,导致数据样本稀疏,距离计算困难等难题,即“维数灾难(curse of dimensionality)”。

缓解的一个重要途径就是降维(dimension reduction).人们观测数据样本虽是高维的,但是与学习任务密切相关的也许尽是某个低维分布。

典型的多维缩放(Multiple Dimensional Scaling , MDS):

假定m个样本在原始控件的距离矩阵为$D \in R^{m × m}, dist_{ij}$表示$x_i$到$x_j$的距离,目标是获取d’维空间的表示$Z \in R^{d’ × m}$, 且任意两个样本在d’维空间中的欧氏距离等于原始空间中的距离。

令$B = Z^{T}Z \in R^{m ×m}$, 其中B为降维后样本的内积矩阵,有$b_{ij} = z^{T}_iz_j$

$$dist_{ij}^{2} = ||z_i||^2 + ||z_j||^2 - 2z^T_iz_j = b_{ii} + b_{jj} - 2 b_{ij}$$

我们将为后的样本Z做中心化处理,也就是说$\sum_{i=1}^m z_i = 0$,易知

$$\sum_{i=1}^m dist^2_{ij} = tr(B) + m b_{jj}$$

$$\sum_{j=1}^m dist^2_{ij} = tr(B) + m b_{ii}$$

$$\sum_{i=1}^m\sum_{j=1}^m dist_{ij}^2 = 2m tr(B) $$

其中tr(B)表示B的迹,而且有$tr(B = \sum_{i = 1}^m ||z_i||^2$,我们再令

$$dist_{i·}^2 = \frac{1}{m}\sum_{j=1}^mdist_{ij}^2$$

$$dist_{·j}^2 = \frac{1}{m}\sum_{i=1}^mdist_{ij}^2$$

$$dist_{··}^2 = \frac{1}{m^2}\sum_{i=1}^m\sum_{j=1}^mdist_{ij}^2$$

所以我们有$b_{ij} = -\frac{1}{2}(dist_{ij}^2 - dist_{i·}^2 - dist_{·j}^2 + dist_{··}^2)$

通过降维前后保持不变的距离矩阵D来求取内积矩阵B.

再对B做特征值分解(eigenvalue decomposition), $B = V\Lambda V^T$, 其中$\Lambda = diag(\lambda_1, …, \lambda_d) ,(\lambda_1 \ge \lambda_2 \ge …\ge \lambda_d)$为特征值构成的对角矩阵.假定其中有d*个非零特征向值,他们构成了$\Lambda^{} = diag(\lambda_1, lambda_2, …, \lambda_{d^{}}), 令V^{}$ 为相应的特征向量矩阵,则Z可表达为$Z = \Lambda^{1/2}_{}V_{*}^{T}$

现实中为了有效降维,往往仅需降维后的距离与原始空间中的距离尽可能的接近。

MDS算法的过程:

  1. 计算$dist_{ij}^{2},dist_{i· }^{2},dist_{j·}^{2}$
  2. 计算B
  3. 对B做特征值分解
  4. 取$\Lambda为d’个最大特征值所构成的对角矩阵,V^{}$为相应得到特征向量矩阵
  5. 得到的Z,每行便是一个样本的低维坐标

算法步骤

  1. 计算出样本数据和待分类数据的距离,
  2. 为待分类数据选择k个与其距离最小的样本。
  3. 统计出k个样本中大多数样本所属的分类
  4. 这个分类就是待测数据所属的分类。

性能分析

Advantages:

KNN不仅可以用来分类,也可以用来regression

对于类域交叉或重叠较多的待测样本集来说,非常适合。

Disadvantages:

当样本不平衡时,会导致待测样本偏向于样本容量较大的类。

计算量大(可除去对分类作用不大的样本)。

不适合样本容量较小的类域,容易采用误分。

Others:

k值的减小会使近似误差(approximation error)减小,但估计误差(estimation error)会增大,意味着整体模型变得复杂,容易发生过拟合。

k值的增大可以减小估计误差,但增大了近似误差。这是与输入实例较远的(不相似)训练实例也会起预测作用,是预测发生错误,同样也使得模型变的简单。

k=N不可取。k一般取一个较小的数值,采用交叉验证法来选取最优的k值。

算法改进

参考

压缩kNN

定义两个存储器,一个存放生成的样本集称output样本集,和original样本集。

初始化:output为空,original为原样本集,从original选择一个移到output中。

在original样本集中选择第i个样本,并使用output样本集中的样本对其进行knn,若分类错误,则将该样本移动到output样本中,若正确不做处理

直至遍历完original所有的样本。output也就是压缩后的样本集。

算法实现(python )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from numpy import *
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA - vecB, 2)))

def classfiy0(inX, dataSet, labels, k):
m = shape(dataSet)[0]
cnt = zeros(m)
dis = []
for i in range(m):
tmp = distEclud(inX, dataSet[i, :])
dis.append((tmp, labels[i]))
dis.sort()
print(dis)
for i in range(k):
cnt[dis[i][1]] += 1
id = -1
mix = 0
for i in range(m):
if(mix < cnt[i]):
mix = cnt[i]
id = i
return id

kd Tree实现(c++)

待补

principal component analysis(PCA主成分分析)

线性方法:对特征做线性组合,将高维数据投影到低维空间。

目的:寻找在最小均方意义下最能够代表原始数据的投影方法

我们需要寻找一个满足最近重构性最大可分性的超平面。

最近重构性:样本点到这个超平面的距离都足够近

最大可分性:样本点到这个超平面上的投影能尽可能的分开

最近重构性

假定数据样本进行了中心化,即$\sum x_i = 0$,再假定投影变换后得到的新坐标系为${w_1, w_2, …,w_d}$, 且为规范正交基。若丢弃一部分坐标,维度降低到d’ < d,则样本点$x_i$在低维坐标系下中的投影是$z_i = (z_{i1}; z_{i2}; …;z_{id’ })$, 其中$z_{ij}=w_{j}^{T}x_i$是$x_i$在低维下第j维的坐标。若基于$z_i$来重构$x_i$,则有$x_i = \sum_{j=1}^{d’}z_{ij}w_j$, 那么整个训练集中,样本原点与基于投影重构的样本点$x_i$之间的距离为:
$$
\sum_{i = 1}^{m}||\sum_{j=1}^{d’}z_{ij}w_j - x_i||{2}^{2} = \sum{i=1}^{m}z_i^{T}z_i - 2\sum_{i=1}^mz_i^TW^Tx_i + \sum x_i^2
$$
正比于$-tr(W^TXX^TW),s.t.W^TW = I$,

这就是主成分分析的优化目标。

使用拉格朗日乘子法得
$$
XX^Tw_i = \lambda_iw_i
$$
对于协方差矩阵$XX^T$ 进行特征值分解,可以得到
$$
E^\mathsf{T}CE=\Lambda=\begin{pmatrix}
\lambda_1 & & & \
& \lambda_2 & & \
& & \ddots & \
& & & \lambda_n
\end{pmatrix}
$$
其中特征值从大到下,再取前d’个特征值对应的特征向量构成W*, 这就是主成成分分析的解。

参考

降维后低维控件的维数d’通常是事先指定的,或通过在d’值不同的地位控件对knn(或其他开销较小的学习器)进行交叉验证来选取较好的d’值。对PCA还可以从重构的角度设置一个重构阈值,例如t = 95%, 然后选取满足不等式最小的d’
$$
\frac{\sum_{i=1}^{d’}\lambda_i}{\sum_{i=1}^{d}\lambda_i} \ge t
$$
舍弃了d-d’个特征值的特征向量,可能使样本的采集密度增大,另一方面当数据受到噪音影响时,最小的特征值所对应的特征向量往往与噪声有关。

PCA是一种无参数技术,也就是说面对同样的数据,如果不考虑清洗,谁来做结果都一样,没有主观参数的介入,所以PCA便于通用实现,但是本身无法个性化的优化。

算法

输入m条n列的数据,低维空间维数d’

  1. 对数据进行中心化处理,$x_i = x_i - \frac{1}{m} \sum_{i=1}^mx_i$
  2. 计算样本的协方差矩阵$XX^T$
  3. 对协方差最特征值分解,求出特征值以及特征向量
  4. 取最大的d’ 个特征值所对应的特征向量$w_1, w_2, w_3..$

输出投影矩阵$W^* = (w_1, w_2, .., w_{d’})$

python实现

PCA实现,输入m条n列(维)数据和d’,输出的是降维后的坐标,降维恢复后的坐标,以及投影矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def pca(dataSet, topFeat=1627406066):
"""
return the data in low Dimensions and the recover data from that
"""
meanVals = mean(dataSet, axis=0)
meanRemoved = dataSet - meanVals
covMat = cov(meanRemoved, rowvar=False)
eigVals, eigVects = linalg.eig(mat(covMat))
eigValIndex = argsort(eigVals)
eigValIndex = eigValIndex[:-(topFeat+1):-1]
sortedEigVects = eigVects[:,eigValIndex]
lowData = meanRemoved * sortedEigVects
reData = (lowData * sortedEigVects.T) + meanVals
return lowData, reData, sortedEigVects

Linear Discriminant Analysis(LDA线性判别分析)

思想:给定训练集,设法将样例投影到一条直线上,是的同类样例的投影点尽可能的接近、异类样例的投影点尽量远离,在对新样本进行分类时,将其投影到同样的这条直线上,在根据投影点到为止来确定样本的类别。

这个与PCA的想法相似但又截然不同。

给定数据集$D={ (x_i,y_i) }, y_i \in {0, 1}$. 令$X_i, \mu_i, \sum_i$分别表示第i类示例的集合、均值向量、协方差矩阵。若将数据投影到直线w上,则两类样本的中心在直线上的投影分别为$w^T \mu_0和w^T \mu_1$, 若将数据投影到直线w上。

欲使同类样例的投影点尽可能的接近,让同类的协方差尽可能的小,即$ w^T \sum_0w + w^T\sum_1w $, 而欲使异类样例的投影点尽可能原理,即$||w^T \mu_0 - w^T \mu_1||_2^2$尽可能大, 最大化的目标:
$$
J = \frac{||w^T \mu_0 - w^T \mu_1||_2^2}{ w^T \sum_0w + w^T\sum_1w } \ = \frac{w^T(\mu_0 - \mu_1)(\mu_0+\mu_1)^Tw}{w^T(\sum_0 + \sum_1)w}
$$
类内散度矩阵(within-class scatter matrix)

$S_w = \sum_0 + \sum_1$

$$= \sum_{x \in X_0} (x - u_0)(x - u_0)^T + \sum_{x \in X_1}(x - \mu_1)(x - \mu_1)^T​$$

类间散度矩阵(between-class scatter matrix):
$$
S_b = (\mu_0 - \mu_1)(\mu_0 - \mu_1)^T
$$

$$
J = \frac{w^TS_Bw}{w^TS_ww}
$$

这就是我们要最大化的目标,即$S_b 和S_w$的广义瑞利商(generalized Rayleigh quotient).

不失一般性,我们令分母为1,那么就有

$$J = w^TS_bw \ \ \ \ s.t. \ \ w^TS_ww = 1$$

拉格朗日乘子得:
$$
S_b w = \lambda S_w w
$$
这时$S_bw的方向与(\mu_0 - \mu_1)$一样,就可以令

$S_bw = \lambda (\mu_0 - \mu_1)$带回去就有

$$w = S_w^{-1} (\mu_0 - \mu_1)$$

考虑数值解的稳定性,通常将$S_w$进行奇异值分解(SVD),即$S_w = U\sum V^T, S_w^{-1} = V\sum^{-1}U^T$

当两类数据同先验,满足高斯分布且协方差相等时,LDA可达到最优分类

推广:

存在N个类,且第i个类示例数为mi,定义全局散度矩阵
$$
S_t = S_b + S_w = \sum_{i=1}^{m}(x_i - \mu)(x_i - \mu)^T \
S_w = \sum_{i = 1}^N S_{w_i} \
S_{w_i} = \sum_{x \in X_i}(x - \mu_i) (x - \mu_i)^T \
S_b = S_t - S_w = \sum_{i = 1}^N (u_i - u)(u_i-u)^T
$$
多分类LDA有多种实现方法,使用$S_b, S_w, S_t$三者中任何两个即可,常见的是采用优化目标

$$\underset{W}{max} \frac{tr(W^TS_bW)}{tr(W^TS_wW) } $$

其中$W \in R^{d×(N-1)}$

上式同理可以转化为$S_bW = \lambda S_w W$

W的闭式解则是$S_w^{-1}S_b$的d’个最大非零广义特征值所对应的特征向量所组成的矩阵,$d’ \le N - 1$

W投影到d‘维空间,LDA也被视为一种经典的监督降维技术。

算法

输入m条n列的数据,

  1. 计算$S_w,S_b$
  2. 计算矩阵$S_w^{-1}S_b$ 所有特征值和对应的特征向量
  3. 取最大的d’ 个特征值所对应的特征向量$w_1, w_2, w_3..$

输出投影矩阵$W^* = (w_1, w_2, .., w_{d’})$

python算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def lda(dataSet, label, topFeat=1627406066):
"""
return the data in low Dimensions and the recover data from that
"""
m, n = shape(dataSet)
d = int(max(label)) + 1
classifedData = [[] for i in range(d)]
labelAverage = []
for i in range(m):
if(int(label[i]) < len(classifedData)):
classifedData[int(label[i])].append(dataSet[i])
else :
print("Error label : " + label[i])
withinScatterMat = zeros((n, n))
for i in range(d):
labelAverage.append(mean(classifedData[i], axis=0))
tmp = mat(classifedData[i] - labelAverage[i])
withinScatterMat = withinScatterMat + tmp.T * tmp

meanVals = mean(dataSet, axis=0)
betweenScatterMat = zeros((n, n))
for i in range(d):
tmp = mat(labelAverage[i] - meanVals)
betweenScatterMat += tmp.T * tmp
ScatterMat = (withinScatterMat ** (-1)) * betweenScatterMat
eigVals, eigVects = linalg.eig(mat(ScatterMat))
print(eigVals)
eigValIndex = argsort(eigVals)
eigValIndex = [i for i in eigValIndex[:-(topFeat+1):-1] if(i > 0)]
sortedEigVects = eigVects[:,eigValIndex]
loadDataSet = dataSet * sortedEigVects
reData = loadDataSet * sortedEigVects.T
return loadDataSet, reData, sortedEigVects
文章目录
  1. 1. K-Means(K-均值) ->聚类
    1. 1.1. 算法思想及步骤
    2. 1.2. 算法要点
    3. 1.3. 性能分析
    4. 1.4. 算法改进
    5. 1.5. 算法实现(python)
      1. 1.5.1. 二分k-means优化
  2. 2. KNN(近邻法)
    1. 2.1. 算法步骤
    2. 2.2. 性能分析
    3. 2.3. 算法改进
    4. 2.4. 算法实现(python )
    5. 2.5. kd Tree实现(c++)
  3. 3. principal component analysis(PCA主成分分析)
    1. 3.1. 算法
    2. 3.2. python实现
  4. 4. Linear Discriminant Analysis(LDA线性判别分析)
    1. 4.1. 算法
    2. 4.2. python算法实现
|