Jacobi 解特征值及特征向量
由于最近利用PCA统计图像主方向,用到了特征值求解算法,网上搜罗看普通解法是jacobi算法,也正好复习一下,在此做一分享,希望对有需要的人有所帮助:
###原理
jacobi算法用于求实对称矩阵的全部特征值及特征向量,利用平面旋转矩阵对矩阵A做相似变换,化A为对角阵,进而求出特征值及特征向量
对于p!=q,下面定义n阶矩阵Uqp是平面旋转矩阵
变换过程
在保证相似条件下,使主对角线外元素趋于0,jacobi算法是一种迭代过程,每一步迭代,需要求出旋转后的新的矩阵,其过程如下:

###算法过程
- 初始化特征向量Vec为对角阵
- 在A的非主对角线元素中,找到一个绝对值最大的元素A[p,q]
- 利用公式1.3计算theta值
- 利用公式1、2、3计算特征向量及特征值
- 当迭代前非对角元素最大值小于阈值或迭代次数小于给定次数、停止计算
- 对特征值及特征向量进行排序
###code
void Jacobi(double *A, int dims, double *eigenvalue, double *vec, int times, double errorValue)
{
// initial vector
memset(vec, 0, sizeof(double) * dims * dims);
for (int i = 0; i < dims; i++)
{
vec[i * dims + i] = 1;
}
int nCount = 0;
while (1)
{
// 在非对角元素上找最大元素
double maxValue = abs(A[1]);
int row = 0;
int col = 1;
for (int i = 0; i < dims; i++)
{
for (int j = 0; j < dims; j++)
{
if (i != j && abs(A[i * dims + j]) > maxValue)
{
maxValue = abs(A[i * dims + j]);
row = i;
col = j;
}
}
}
if (maxValue < errorValue || nCount > times)
{
break;
}
double Apq = A[row * dims + col];
double App = A[row * dims + row];
double Aqq = A[col * dims + col];
double theta = 0.5 * atan2(-2 * Apq, Aqq - App);
double cost = cos(theta);
double sint = sin(theta);
A[row * dims + row] = App * cost * cost + Aqq * sint * sint + 2 * Apq * cost * sint;
A[col * dims + col] = App * sint * sint + Aqq * cost * cost - 2 * Apq * cost * sint;
A[row * dims + col] = 0.5 * (Aqq - App) * sin(2 * theta) + Apq * cos(2 * theta);
A[col * dims + row] = A[row * dims + col];
for (int i = 0; i < dims; i++)
{
if ( i != row && i != col)
{
maxValue = A[i * dims + row];
A[i * dims + row] = cost * maxValue + sint * A[i * dims + col];
A[i * dims + col] = cost * A[i * dims + col] - sint * maxValue;
}
}
for (int j = 0; j < dims; j++)
{
if (j != row && j != col)
{
maxValue = A[row * dims + j];
A[row * dims + j] = cost * maxValue + sint * A[col * dims + j];
A[col * dims + j] = cost * A[col * dims + j] - sint * maxValue;
}
}
// 计算特征向量
for (int i = 0; i < dims; i++)
{
maxValue = vec[i * dims + row];
vec[i * dims + row] = cost * maxValue + sint * vec[i * dims + col];
vec[i * dims + col] = cost * vec[i * dims + col] - sint * maxValue;
}
nCount++;
}
// 对特征值进行排序,特征值既A主对角线上的元素
// map 在插入元素时,会自动按照从大道小排序
map<double, int> mapEigen;
for (int i = 0; i < dims; i++)
{
eigenvalue[i] = A[i * dims + i];
mapEigen.insert(make_pair(eigenvalue[i], i));
}
double *vecTemp = new double[dims * dims];
map<double, int>::reverse_iterator iter = mapEigen.rbegin();
for (int j = 0; iter != mapEigen.rend(), j < dims; iter++, j++)
{
for (int i = 0; i < dims; i++)
{
vecTemp[i * dims + j] = vec[i * dims + iter->second];
}
// 特征值重排序
eigenvalue[j] = iter->first; // key value
}
memcpy(vec, vecTemp, sizeof(double) * dims * dims);
delete[] vecTemp;
}
文中用到了stl中的标准c++库map,其在添加元素时,自动对特征值进行排序,用时还是特别方便的。
###Mark
对于一个实例如下图所示

其中a为输入矩阵,v和c分别为其特征向量及特征值,黑色背景为上面代码所演算特征值及特征向量,可以看出,有一个特征向量符号正好相反,可能原因是对称矩阵在对角化过程中结果不唯一性所致,其结果仍然是正确的。
此文章版权归snailgoers所有,如有转载,请注明來自原作者
评论

