K均值聚类
K均值聚类是基于样本集合划分的聚类算法。K均值聚类将样本集合划分为k个子集,构成k个类,将n个样本分到k个类中,每个样本到其所属类的中心距离最小,每个样本仅属于一个类,这就是k均值聚类,同时根据一个样本仅属于一个类,也表示了k均值聚类是一种硬聚类算法。
K均值聚类是生成式还是判别式方法?
判别式
KNN VS. K-means
KNN | K-Means |
---|---|
分类算法,输入数据均带有标签 即,有监督学习 | 聚类算法,输入的数据没有标签 即,无监督学习 |
K代表K个最邻近的点 | K代表K个类别 |
没有明显的前期训练过程 | 有明显的前期训练过程 |
共同点:都用到了邻近算法。
主成分分析
主成分分析算法(PCA)是最常用的线性降维方法,它的目标是通过某种线性投影,将高维的数据映射到低维的空间中,并期望在所投影的维度上数据的信息量最大(方差最大),以此使用较少的数据维度,同时保留住较多的原数据点的特性。
PCA降维的目的,就是为了在尽量保证“信息量不丢失”的情况下,对原始特征进行降维,也就是尽可能将原始特征往具有最大投影信息量的维度上进行投影。将原特征投影到这些维度上,使降维后信息量损失最小。
LDA VS. PCA
相同点:
- 两者均可以对数据进行降维
- 两者在降维时均使用了矩阵特征分解的思想
- 两者都假设数据符合高斯分布
不同点:
- LDA是有监督的降维方法,而PCA是无监督的降维方法
- LDA降维最多降到类别数k-1的维数,PCA没有限制
- LDA除了可以用于降维,还可以用于分类
- LDA选择分类性能最好的投影方向,而PCA选择样本点投影具有最大方差的方向
奇异值分解(SVD)
奇异值分解(SVD)在降维,数据压缩,推荐系统等有广泛的应用,任何矩阵都能进行SVD分解,SVD可以用于行降维和列降维,SVD在数据压缩、推荐系统和语义分析有广泛的应用,SVD与PCA的缺点一样,分解出的矩阵解释性不强 。
特征人脸方法(Eigenface)
Eigenfaces就是特征脸的意思,是一种从主成分分析(Principal Component Analysis,PCA)中导出的人脸识别和描述技术。特征脸方法的主要思路就是将输入的人脸图像看作一个个矩阵,通过在人脸空间中一组正交向量,并选择最重要的正交向量,作为“主成分”来描述原来的人脸空间。
潜在语义分析 (LSA)
潜在语义分析(latent semantic analysis, LSA)是一种无监督方法,主要用于文本的话题分析,其特点是通过矩阵分解发现文本与单词之间的基于话题的语义关系。潜在语义分析是非概率的话题分析方法,将文本集合表示为 单词-文本矩阵,对该矩阵进行进行奇异值分解,从而得到 话题向量空间和 文本在话题向量空间中的表示。也可以使用矩阵的因子分解方法进行分解。
期望最大化算法(EM)
EM算法是一种迭代优化策略,由于它的计算方法中每一次迭代都分两步,其中一个为期望步(E步),另一个为极大步(M步),所以算法被称为EM算法(Expectation-Maximization Algorithm)。EM算法受到缺失思想影响,最初是为了解决数据缺失情况下的参数估计问题,其算法基础和收敛有效性等问题在Dempster、Laird和Rubin三人于1977年所做的文章《Maximum likelihood from incomplete data via the EM algorithm》中给出了详细的阐述。其基本思想是:首先根据己经给出的观测数据,估计出模型参数的值;然后再依据上一步估计出的参数值估计缺失数据的值,再根据估计出的缺失数据加上之前己经观测到的数据重新再对参数值进行估计,然后反复迭代,直至最后收敛,迭代结束。
K-means是最简单的EM算法?
k-means是EM算法的一种特殊情况,是较为简单的EM算法
编程实现EM算法
硬币问题
//阶乘代码
double CFactorial(const int len, const int m){
if (len < m || len < 1 || len < m) { return 0.0; }
double numerator = 1.0, denominator = 1.0;
for (size_t i = 1; i <= m; i++) {
numerator *= 1.0 * (len - i + 1);
denominator *= 1.0 * i;
}
return numerator / denominator;
}
//EM算法求解硬币问题
void EMTrain(const EMDATA *data, const int cls_num, EMRESULT *theta){
if (cls_num < 1 || data->width < 1 || data->height < 1){ TFLYERROR("size error\n"); return; }
const EMDATA *head_data = data;
int *per_group_cls_num = (int *)malloc(head_data->height * sizeof(int)); //硬币问题,只有两种类别。记录正面朝上的硬币个数
memset(per_group_cls_num, 0, head_data->height * sizeof(int));
int *head_sum = per_group_cls_num;
for (size_t i = 0; i < head_data->height; i++) { //统计每组数据中硬币朝上的个数
for (size_t j = 0; j < head_data->width; j++) {
*head_sum += head_data->data[i][j];
}
head_sum++;
}
double pA = 0.0, pB = 0.0;
double thetaA = theta[0], thetaB = theta[1], error = 1e-8; //表示theta的error表示误差
double tmp_thetaA = thetaA, tmp_thetaB = thetaB;
while (1){
//硬币A正面和反面出现的次数 与 硬币B正面和反面出现的次数
double sum_frontA = 0.0, sum_backA = 0.0;
double sum_frontB = 0.0, sum_backB = 0.0;
//E-step 求解
head_sum = per_group_cls_num;
for (size_t i = 0; i < head_data->height; i++) { //正样本
double fact = CFactorial(head_data->width, *head_sum);
pA = fact * pow(thetaA, *head_sum) * pow(1 - thetaA, head_data->width - *head_sum);
pB = fact * pow(thetaB, *head_sum) * pow(1 - thetaB, head_data->width - *head_sum);
pA = pA / (pA + pB); //更新硬币A的概率
pB = 1 - pA; //更新硬币B的概率
sum_frontA += pA * *head_sum; //硬币A正面出现的次数累加和
sum_backA += pA * (head_data->width - *head_sum); //硬币A背面出现的次数累加和
sum_frontB += pB * *head_sum; //硬币B正面出现的次数累加和
sum_backB += pB * (head_data->width - *head_sum); //硬币B背面出现的次数累加和
++head_sum;
}
//M-step 更新参数thetaA和thetaB
thetaA = sum_frontA / (sum_frontA + sum_backA);
thetaB = sum_frontB / (sum_frontB + sum_backB);
printf("thetaA=%lf, thetaB=%lf\n", thetaA, thetaB);
if (fabs(thetaA - tmp_thetaA) < error && fabs(thetaB - tmp_thetaB) < error){ //迭代终止条件,也可以使用
break;
}
tmp_thetaA = thetaA;
tmp_thetaB = thetaB;
}
*theta++ = thetaA; //返回最终的结果
*theta++ = thetaB;
free(per_group_cls_num);
return;
}
//模型预测结果
void EMPredicted(const EMDATA *pred_data, const int cls_num, const EMRESULT *theta, int *pred_cls){
if (cls_num < 1 || pred_data->width < 1 || pred_data->height < 1){ TFLYERROR("size error\n"); return; }
const EMDATA *head_data = pred_data;
int *per_group_cls_num = (int *)malloc(head_data->height * sizeof(int)); //硬币问题,只有两种类别。记录正面朝上的硬币个数
memset(per_group_cls_num, 0, head_data->height * sizeof(int));
int *head_sum = per_group_cls_num;
for (size_t i = 0; i < head_data->height; i++) { //统计每组数据中硬币朝上的个数
for (size_t j = 0; j < head_data->width; j++) {
*head_sum += head_data->data[i][j];
}
head_sum++;
}
double *cls_value = (double *)malloc(cls_num * sizeof(double));//每种类别的类别和
head_sum = per_group_cls_num;
for (int i = 0; i < head_data->height; i++) {
memset(cls_value, 0, cls_num * sizeof(double));
double *head_cls_value = cls_value;
double fact = CFactorial(head_data->width, *head_sum);
const EMRESULT *head_theta = theta;
double sum = 0.0;
//方法1
double max_prob = -1.0; //概率值大于等于0
int real_class = 0;
for (size_t j = 0; j < cls_num; j++) {
*head_cls_value = fact * pow(*head_theta, *head_sum) * pow(1 - *head_theta, head_data->width - *head_sum);
if (max_prob < *head_cls_value) { max_prob = *head_cls_value; real_class = j; }
++head_cls_value; ++head_theta;
}
//方法2
//for (size_t j = 0; j < cls_num; j++) {
// *head_cls_value = fact * pow(*head_theta, *head_sum) * pow(1 - *head_theta, head_data->width - *head_sum);
// sum += *head_cls_value;
// ++head_cls_value; ++head_theta;
//}
//head_cls_value = cls_value;
//double max_prob = -1.0; //概率值大于等于0
//int real_class = 0;
//for (size_t j = 0; j < cls_num; j++) {
// double per_prob = *head_cls_value / sum; //同一组数据,每种类别的概率,取概率最大值作为预测结果
// if (max_prob < per_prob) { max_prob = per_prob; real_class = j; }
// ++head_cls_value;
//}
*pred_cls++ = real_class;
++head_sum;
}
free(cls_value);
free(per_group_cls_num);
}