单高斯分布
多维变量X服从高斯分布时,它的概率密度函数PDF为:
x是维度为d的列向量,u是模型期望,Σ是模型方差。在实际应用中u通常用样本均值来代替,Σ通常用样本方差来代替。很容易判断一个样x本是否属于类别C。因为每个类别都有自己的u和Σ,把x代入(1)式,当概率大于一定阈值时我们就认为x属于C类。
高斯混合模型
从几何上讲,单高斯分布模型在二维空间应该近似于椭圆,在三维空间上近似于椭球。遗憾的是在很多分类问题中,属于同一类别的样本点并不满足“椭圆”分布的特性。这就引入了高斯混合模型;认为数据可以由几个高斯模型来描述
K需要事先确定好,就像K-means中的K一样。πk是样本属于某个高斯分布的概率。其中的任意一个高斯分布N(x;uk,Σk)叫作这个模型的一个component。当K取得足够大,高斯混合模型就可以用来逼近任意连续的概率密度分布。
在GMM中使用EM算法聚类
我们使用k个多元高斯分布的混合高斯分布GMM来对数据进行聚类,其中每一个分布代表一个数据簇。首先,随机选择k个对象代表各个簇的均值(中心),猜测每一个簇的协方差矩阵,并假定初始状态 时每个簇的概率相等; 然后,根据多元高斯密度函数求出每一个对象属于每一个簇的概率,并求出数据的似然函数值;最后,根据每一个数据点属于每一个簇的概率,来更新每一个簇的均值,协方差矩阵,和每一个簇的概率。不断迭代以上两步,直到算法收敛。 这时我们根据每一个对象属于每一个簇的概率,将对象指派的概率最高的簇中。
关键部分就是EM算法部分。
算法中只知道每个向量的坐标,要将这些向量聚类为k个gauss分布中,但这k个cluster的高斯分布的参数未知,当然另一个未知量是各个向量所归属的类别。
首先初始化各个gauss分布的参数
E-step:
根据这k个gauss分布的参数求得各个向量归属各个cluster的概率矩阵
M-step:
根据E-step概率矩阵更新gauss分布参数
以上两步交替进行,直到收敛。
这样看来GMM聚类和k均值聚类是不是有些像。不过k均值给出的结果要么属于这一类,要么属于那一类,GMM给出的则是软性的。
下面是我的实现,先用kmeans初始化了聚类中心。
#include "stdafx.h"
#include<set>
#include<vector>
#include<cstdlib>
#include<time.h>
#include<iostream>
using namespace std;
#define PI 3.1415926
class GMM;
class kmeans
{
friend class GMM;
private:
double**dataset;
int datanums;
unsigned int k;
unsigned int dim;
typedef vector<double> Centroid;
vector<Centroid> center;
vector<set<int>>cluster_ID;
vector<Centroid>new_center;
vector<set<int>>new_cluster_ID;
double threshold;
private:
void init();
void assign();
double distance(Centroid cen, int k2);
void split(vector<set<int>>&clusters, int kk);
void update_centers();
bool isfinish();
public:
kmeans()
{
threshold = 0.0001;
}
void apply(double**data, int datanum, int numofcluster, int dim);
};
//template <typename T>
void kmeans::init()
{
center.resize(k);
set<int>bb;
for (int i = 0; i < k; i++)
{
int id = double(rand()) / double(RAND_MAX + 1.0)*datanums;
while (bb.find(id) != bb.end())
{
id = double(rand()) / double(RAND_MAX + 1.0)*datanums;
}
bb.insert(id);
center[i].resize(dim);
for (int j = 0; j < dim; j++)
center[i][j] = dataset[id][j];
}
}
bool kmeans::isfinish()
{
double error = 0;
for (int i = 0; i < k; i++)
{
for (int j = 0; j < dim; j++)
error += pow(center[i][j] - new_center[i][j], 2);
}
return error < threshold ? true : false;
}
void kmeans::assign()
{
for (int j = 0; j < datanums; j++)
{
double mindis = 10000000;
int belongto = -1;
for (int i = 0; i < k; i++)
{
double dis = distance(center[i], j);
if (dis < mindis)
{
mindis = dis;
belongto = i;
}
}
new_cluster_ID[belongto].insert(j);
}
for (int i = 0; i < k; i++)
{
if (new_cluster_ID[i].empty())
{
split(new_cluster_ID, i);
}
}
}
double kmeans::distance(Centroid cen, int k2)
{
double dis = 0;
for (int i = 0; i < dim; i++)
dis += pow(cen[i] - dataset[k2][i], 2);
return sqrt(dis);
}
void kmeans::split(vector<set<int>>&clusters, int kk)
{
int maxsize = 0;
int th = -1;
for (int i = 0; i < k; i++)
{
if (clusters[i].size() > maxsize)
{
maxsize = clusters[i].size();
th = i;
}
}
#define DELTA 1
vector<double>tpc1, tpc2;
tpc1.resize(dim);
tpc2.resize(dim);
for (int i = 0; i < dim; i++)
{
tpc2[i] = center[th][i] - DELTA;
tpc1[i] = center[th][i] + DELTA;
}
for (set<int>::iterator it = clusters[th].begin(); it != clusters[th].end(); it++)
{
double d1 = distance(tpc1, *it);
double d2 = distance(tpc2, *it);
if (d2 < d1)
{
clusters[kk].insert(*it);
}
}
_ASSERTE(!clusters[kk].empty());
for (set<int>::iterator it = clusters[kk].begin(); it != clusters[kk].end(); it++)
clusters[th].erase(*it);
}
void kmeans::update_centers()
{
for (int i = 0; i < k; i++)
{
Centroid temp;
temp.resize(dim);
for (set<int>::iterator j = new_cluster_ID[i].begin(); j != new_cluster_ID[i].end(); j++)
{
for (int m = 0; m < dim; m++)
temp[m] += dataset[*j][m];
}
for (int m = 0; m < dim; m++)
temp[m] /= new_cluster_ID[i].size();
new_center[i] = temp;
}
}
void kmeans::apply(double**data, int datanum, int numofcluster, int dim)
{
this->dim = dim;
datanums = datanum;
dataset = data;
k = numofcluster;
init();
new_center.resize(k);
new_cluster_ID.resize(k);
assign();
update_centers();
int iter = 0;
while (!isfinish())
{
center = new_center;
cluster_ID = new_cluster_ID;
new_center.clear();
new_center.resize(k);
new_cluster_ID.clear();
new_cluster_ID.resize(k);
assign();
update_centers();
iter++;
}
}
class GMM {
/**
* 待分类的向量的个数
*/
private:
int numVec;
/**
* 向量的维数
*/
int numDim;
/**
* 聚类的数目
*/
int numClusters;
/**
* 最大迭代次数
*/
int maxIteration = 500;
/**
* 待聚类的向量数组
*/
double** data;
/**
* 第i个向量属于第j类的概率
*/
double** probabilities;
/**
* 每一个类的均值向量
*/
double** uVectors;
/**
* 每一个类的先验概率
*/
double* priorProb;
/**
* 每一个类的协方差矩阵,用于计算n维正态随机变量的概率密度
*/
double*** convMatrix;
/**
* 聚类的结果,result[i]为第i个向量的类标
*/
int* result;
/**
* 一个很小的数
*/
const double SMALLNUMBER = 0.000000000000001;
/**
* 存储log likelihood函数值,其值在E-Step里进行计算,最终目标即要使该值最大
*/
double log_likely;
/**
* 在高斯混合模型下用EM算法进行聚类,聚类结果存放于整数数组中
*
* @param fdata
* 待聚类的向量
* @param fnumClusters
* 聚类的个数
* @return 返回向量的类标数组
*/
public:
~GMM()
{
for (int i = 0; i < numVec; i++)
{
delete[]probabilities[i];
}
for (int i = 0; i < numClusters; i++)
{
for (int j = 0; j < numDim; j++)
delete[]convMatrix[i][j];
delete[]convMatrix[i];
delete[]uVectors[i];
}
delete[]convMatrix;
delete[]probabilities;
delete[]priorProb;
delete[]uVectors;
}
int* GMM_Cluster(double** fdata, int datanums, int dim, int fnumClusters) {
initCluster(fdata, datanums, dim, fnumClusters); // 初始化
expectation(); // E步
maximization(); // M步
double l2 = log_likely;
// 不断迭代直到收敛
int time = 1;
do {
l2 = log_likely;
expectation();
maximization();
time++;
} while (abs(l2 - log_likely) > SMALLNUMBER&&time < maxIteration); // 如果收敛过慢,可以适当调整迭代条件SMALLNUMBER
for (int i = 0; i < numVec; i++) // 比较第i个向量属于各个类的概率,把第i个向量划入概率最大的那一类
{
int temp = 0;// 第i个向量最大可能属于某类的类标
for (int j = 1; j < numClusters; j++) {
if (probabilities[i][j] > probabilities[i][temp]) {
temp = j;
}
}
result[i] = temp;
}
return result; // 返回类标数组
}
double**get_mean()
{
return uVectors;
}
/**
* 求矩阵行列式
*
* @param param
* fconvMatrix 矩阵
* @return 返回矩阵的行列式
*/
private:
double determinant(double** fconvMatrix) {
double det = 1.0;
for (int i = 0; i < numDim; i++)// 由于协方差矩阵是对角矩阵,所以直接对角线相乘
{
det = det * fconvMatrix[i][i];
}
return det; // 返回协方差矩阵的行列式
}
/**
* 求矩阵的逆矩阵
*
* @param fconvMatrix
* 矩阵
* @return fconvMatrix的逆矩阵
*/
double** inverse(double** fconvMatrix) {
// 复制原矩阵
double** a = new double*[numDim];
for (int i = 0; i < numDim; i++)
a[i] = new double[numDim];
for (int i = 0; i < numDim; i++)
for (int j = 0; j < numDim; j++)
a[i][j] = fconvMatrix[i][j];
for (int i = 0; i < numDim; i++) // 由于协方差矩阵是对角矩阵,所以直接将对角线的元素翻转
{
a[i][i] = 1 / a[i][i];
}
return a; // 返回协方差矩阵的逆矩阵
}
/**
* 求多维高斯分布概率密度
*
* @param fvector
* 多维空间中的点坐标,即要求该点上的概率密度
* @param fuVec
* 高斯分布的均值向量
* @param fconvMatrix
* 高斯分布的协方差矩阵
* @return 多维空间中对应点的概率密度
*/
double gauss(double* fvector, double* fuVec,
double** fconvMatrix) {
double* temp1 = new double[numDim];
double* temp2 = new double[numDim];
for (int i = 0; i < numDim; i++) {
temp1[i] = fvector[i] - fuVec[i]; // temp1存储(X-u)'向量
temp2[i] = 0.0; // 初始化temp2为0.0
}
double** a = inverse(fconvMatrix);// a为协方差矩阵的逆矩阵
// 算exp函数的指数部分
for (int i = 0; i < numDim; i++) {
temp2[i] = a[i][i] * temp1[i];
}
for (int i = 0; i < numDim; i++)
delete[]a[i];
delete[]a;
double temp = 0.0;
for (int i = 0; i < numDim; i++) {
temp += temp1[i] * temp2[i];
}
temp = temp / -2.0; // 求得exp函数的指数部分temp
temp = exp(temp);
double det = determinant(fconvMatrix);// det为协方差矩阵的行列式
double temp3;
temp3 = temp / sqrt(pow(2 * PI, numDim) * det); // 计算出向量的概率密度为temp3
temp3 += SMALLNUMBER;// 加一个很小的数
return temp3;
}
/**
* 做一些初始化工作 求初始聚类中心
*/
void initCluster(double** fdata, int datanums, int dim, int fnumClusters)
{
numVec = datanums; // 初始化成员变量numVec(numVec是待分类向量的个数)
numDim = dim; // 初始化成员变量numDim(numDim是向量的维数)
numClusters = fnumClusters;// 初始化成员变量numClusters(numClusters是分类的数目)
data = fdata;
//利用kmeans做初始化
kmeans km;
km.apply(data, datanums, fnumClusters, dim);
// 初始化向量的概率矩阵为零矩阵(第i个向量属于第j类的概率为零)
probabilities = new double*[numVec];
for (int i = 0; i < numVec; i++)
{
probabilities[i] = new double[numClusters];
memset(probabilities[i], 0, sizeof(double)*numClusters);
}
// 初始化每一个类的先验概率为相等,以后每次迭代的M步会不断求精
priorProb = new double[numClusters];
for (int i = 0; i < numClusters; i++) {
priorProb[i] = 1.0 / (double)(numClusters);
}
// 初始化类标数组
result = new int[numVec];
//memset(result, 0, sizeof(int)*numVec);
for (int i = 0; i < fnumClusters; i++)
{
for (set<int>::iterator it = km.cluster_ID[i].begin()
; it != km.cluster_ID[i].end(); it++)
result[*it] = i;
}
// 初始化每一个类的均值向量,以后每次迭代的M步会不断求精
uVectors = new double*[numClusters];
for (int i = 0; i < numClusters; i++)
uVectors[i] = new double[numDim];
int index;
for (int k = 0; k < numClusters; k++) {
//index = numVec*double(rand()) / (RAND_MAX + 1.0);
//while ((int)(result[index]) == 1) {
// index = numVec*double(rand()) / (RAND_MAX + 1.0);
//}
//result[index] = 1;
for (int j = 0; j < numDim; j++) {
//uVectors[k][j] = data[index][j];
uVectors[k][j] = km.center[k][j];
}
}
// 初始化每个类的协方差矩阵为单位矩阵,以后每次迭代的M步会不断求精
convMatrix = new double**[numClusters];
for (int i = 0; i < numClusters; i++)
{
convMatrix[i] = new double*[numDim];
for (int j = 0; j < numDim; j++)
convMatrix[i][j] = new double[numDim];
}
for (int i = 0; i < numClusters; i++) {
for (int j = 0; j < numDim; j++) {
for (int k = 0; k < numDim; k++)
{
if (j == k)
convMatrix[i][j][k] = 100.01;
else
convMatrix[i][j][k] = 0;
}
}
}
}
/**
* EM算法的E-Step
*
*/
void expectation() {
for (int i = 0; i < numVec; i++) {
log_likely = 0.0; // l为似然函数值
// 计算第i个向量的概率temp
double temp = 0.0;
for (int k = 0; k < numClusters; k++) {
double g1 = gauss(data[i], uVectors[k], convMatrix[k]);
temp += g1 * priorProb[k];
}
// 计算第i个向量属于第j类的概率,计算公式见《Top 10 algorithms in data mining》的EM算法部分
for (int j = 0; j < numClusters; j++) {
double g2 = gauss(data[i], uVectors[j], convMatrix[j]);
probabilities[i][j] = priorProb[j] * g2 / temp; // 计算第i个向量属于第j类的概率
}
// 计算log似然函数,其值的变化影响迭代次数
log_likely += log(temp);
}
}
/**
* EM算法的M-Step
*
*/
void maximization()
{
for (int j = 0; j < numClusters; j++) {
// 更新每个类的先验概率,计算公式见《Top 10 algorithms in data mining》的EM算法部分
double temp = 0.0;
for (int i = 0; i < numVec; i++) {
temp += probabilities[i][j];
}
priorProb[j] = temp / (double)numVec;
// 更新每一个类的均值向量,计算公式见《Top 10 algorithms in data mining》的EM算法部分
for (int k = 0; k < numDim; k++)// 先将第j类的均值向量清零,以便重新计算
{
uVectors[j][k] = 0.0;
}
for (int i = 0; i < numVec; i++) {
for (int k = 0; k < numDim; k++) {
uVectors[j][k] += data[i][k] * probabilities[i][j];
}
}
for (int k = 0; k < numDim; k++) {
uVectors[j][k] /= temp;
}
// 更新协方差矩阵,计算公式见《Top 10 algorithms in data mining》的EM算法部分
for (int k = 0; k < numDim; k++)// 先将协方差矩阵清零,以便重新计算
{
convMatrix[j][k][k] = 0.0;
}
for (int i = 0; i < numVec; i++)// 重新计算协方差矩阵
{
double* temp2 = new double[numDim];
for (int k = 0; k < numDim; k++) {
temp2[k] = data[i][k] - uVectors[j][k];
}
for (int k = 0; k < numDim; k++) {
convMatrix[j][k][k] += probabilities[i][j] * temp2[k]
* temp2[k];
}
delete[]temp2;
}
for (int k = 0; k < numDim; k++) {
convMatrix[j][k][k] = convMatrix[j][k][k] / temp;
// convMatrix[j][k][k] += 0.000000000001;//加一个很小的数
}
}
}
}
;
int main(){
time_t t;
srand(time(&t));
int datanums = 100;
int dim = 2;
int clusterNums = 5;
double**data = new double*[datanums];
for (int i = 0; i < datanums; i++)
{
data[i] = new double[dim];
for (int j = 0; j < dim; j++)
data[i][j] = double(rand()) / RAND_MAX * 500;
}
GMM gmm;
int *result = gmm.GMM_Cluster(data, datanums, dim, clusterNums);
//multimap<int, pair<double, double>>cluster;
vector<vector<int>>clusters;
clusters.resize(clusterNums);
for (int i = 0; i < datanums; i++)
{
//cluster.insert(pair<int, pair<double, double>>
// (result[i], pair<double, double>(data[i][0], data[i][1])));
clusters[result[i]].push_back(i);
//cout << result[i] << endl;
}
for (int i = 0; i < clusterNums; i++)
{
cout << "第" << i << "个簇的中心为(" <<
gmm.get_mean()[i][0] << "," << gmm.get_mean()[i][1]
<< ")。包涵下列数据:" << endl;
//multimap<int, pair<double, double>>::const_iterator cit = cluster.upper_bound(i);
// 输出: pythonzone.com,python-zone.com
//while (cit != cluster.end())
//{
// cout << "(" << cit->second.first << "," << cit->second.second<<")" << endl;
// ++cit;
//}
for (int j = 0; j < clusters[i].size(); j++)
cout << "(" << data[clusters[i][j]][0] << "," << data[clusters[i][j]][1] << "), ";
cout << endl << endl;
}
for (int i = 0; i < datanums; i++)
{
delete[]data[i];
}
delete[]data;
delete[]result;
system("pause");
return 0;
}
下图是结果
混合高斯模型(Mixtures of Gaussians)和EM算法
概率分布 –> http://zh.wikipedia.org/wiki/概率分布
Hessian matrix –> http://zh.wikipedia.org/wiki/海森矩阵
最大似然估计 –> http://zh.wikipedia.org/wiki/最大似然估计
Expectation-Maximization(EM) 算法