首先要了解一下下面的概念:
X
的分布函数的形式已知,但它的一个或多个参数未知,借助于总体X
的一个样本来估计总体未知参数的值的问题称为参数的点估计问题。主成分分析 (Principal Component Analysis, PCA) 是一种统计方法,通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。PCA 的主要应用有:降维、特征提取、去噪、故障检测。
1,2
个轴正交的平面中方差最大的。依次类推,可以得到 n n n 个这样的坐标轴。通过这种方式获得的新的坐标轴,我们发现,大部分方差都包含在前面 k k k 个坐标轴中,后面的坐标轴所含的方差几乎为0
。于是,我们可以忽略余下的坐标轴,只保留前面 k k k 个含有绝大部分方差的坐标轴。事实上,这相当于只保留包含绝大部分方差的维度特征,而忽略包含方差几乎为0
的特征维度,实现对数据特征的降维处理。下面的实例中会用到 Matlab 中生成正态分布随机数的函数randn()
,下面对其用法做简要的介绍。
randn(n)
:产生一个 n*n
的随机数方阵randn(m,n)
:产生一个 m*n
的随机数矩阵除此之外还有,Matlab
关于随机数的生成函数还有:
rand
:产生均值为0.5、幅度在0~1之间的伪随机数。randperm(n)
:产生1到n的均匀分布随机序列。normrnd(a,b,c,d)
:产生均值为a、方差为b大小为c*d
服从正态分布的随机矩阵。下面对randn
做一个简单的验证:产生两列数据,分别画出它们的概率分布,看看图形是否符合正态分布。代码和绘制结果如下,可以看出结果符合预期。
%% 检验 randn 函数
function rTest()
data=randn(600,2);
figure
hold on
subplot(121)
t=2;
p=capaplot(data(:,1),[-t,t]);
subplot(122)
p=capaplot(data(:,2),[-t,t]);
end
function [coeff, score, latent, tsquared, explained, mu] = pca(x,varargin)
输入:x
是样本,N*P
=N个样本量,P维
输出:
N×P
数据矩阵X
的主成分系数。X
的行对应于观测值,列对应于变量。每列系数包含一个主成分的系数。各列按主成分方差(latent)降序排列。默认情况下,PCA将数据居中并使用奇异值分解算法。对于非默认选项,请使用名称/值对参数。X
在主成分空间中的表示。score的行对应观察值,列对应主成分。中心数据可以用SCORE*COEFF
重建。X
的协方差矩阵的特征值,特征值从大到小进行排序。X
中每个观测值的Hotelling T平方统计值。PCA使用所有主分量计算TSQUARED(在整个空间中计算),即使请求的组件较少(请参见下面的“NumComponents”选项)。对于缩小空间中的TSQUARED,使用MAHAL(SCORE,SCORE)。Centered
设置为true
时返回估计的平均值MU
;设置为false
时返回所有零。对于一维的情况,我们可以用置信区间来描述一个值落在某个区间(区域)的概率(置信水平),这个区间用几何表示是一个线段;对于二维的情况,几何意义上就从一条直线变成了一个平面,类似于置信区间,便有了置信椭圆(confidence ellipse)的概念。
下面是笔者的思考,一些自说自话罢了。
其实,写这篇博文的初衷是为了搞清楚『为什么两个随机变量不相关,其误差椭圆的长短轴平行于坐标轴』。还是看个例子,用数据说话吧。在看下面的内容之前首先要明确下面几点:
randn
函数来生成了两列均值为 0 方差为 1 的随机数;我们对这列随机数的理解为:每一列数据都表示对一个『待求量』的多次量测,显然我们已经假设我们的量测存在一个服从高斯分布的噪声,并且我们的『待求量』真值是0
。我们也可以将每一列数据理解为对一个『随机变量』的多次抽样或观测。randn
函数虽然一次生成了两列数据,但是这两列数据是毫无关联的(当然也可以一次生成一列数据,分两次生成);换言之,这两列数据代表的两个『随机变量』是不相关的。首先用randn
生成了两列均值为0、方差为1的随机数据,数据量是300*n
;为了探究不同数据量的区别,因此画了6幅图如下,数据量分别是300*n
,n=1,2,3,4,5,6
。图中,len
表示数据量的大小,a,b
分别表示椭圆长轴和短轴长度;红色和蓝色箭头(好吧,红色可能看不到箭头)分别表示长轴和短轴方向。可以看到,画出来的图形几近于圆,数据量越多,它越接近于圆。但是它的轴并不平行于坐标轴,可以这样解释:因为这两列数据的方差都是1
,所以长短轴理论上应该相等,所以画出来的图形理论上应该是个圆(实际情况也是,数据量越多,它越接近于圆),所以其指向就无所谓了。
为了让两列数据的方差不同,我们将第一列数据乘上了2
,不同数据量的绘图结果如下。可以看到,它确实是长短轴平行于坐标轴的椭圆!!
%% 两个不相关变量的特性
function Test1(wid,hei,a,b)
figure
pmag=50; %the size of margin pix
marx(1:b+1,1)=3;marx(1)=8;
mary(1:a+1,1)=3;mary(1)=8;
sumx=sum(marx);sumy=sum(mary);
marx=marx./sumx;mary=mary./sumy;
lefy=pmag/hei/mary(1);
lefx=pmag/wid/marx(1);
marx=marx.*lefx;
mary=mary.*lefy;
wid1=(1-lefx)/b;
hei1=(1-lefy)/a;
xt=-4:2:4;xtl1=sprintfc('%g',xt);xtl2=kcell(size(xtl1));xl=[-5 5];
yt1=-4:2:4;ytl1=sprintfc('%g',yt1);ytl2=kcell(size(ytl1));yl=[-5 5];
set(gcf,'position',[0 0 wid hei])
for i=1:a
magy=(a-i)*hei1+sum(mary(1:end-i));
for j=1:b
magx=(j-1)*wid1+sum(marx(1:j));
n=(i-1)*b+j;
subplot(a,b,n);
set(gca,'position',[magx magy wid1 hei1],'box','on');
% plot here!
len=300;
data=randn(n*len, 2); % get the data
PCA(data);
set(gca,'XLim',xl,'XTick',xt,'XTicklabel',xtl2);
set(gca,'YLim',yl,'YTick',yt1,'YTicklabel',ytl2);
if j==1
ylabel('Ylab');
set(gca,'YTick',yt1,'YTicklabel',ytl1);
end
if i==a
xlabel('Xlab');
set(gca,'XTick',xt,'XTicklabel',xtl1);
end
end
end
end
% get a null cell which is a*b dims
function data=kcell(m)
a=m(1);b=m(2);
data=cell(a,b);
for i=1:a
for j=1:b
data(i,j)=cellstr(num2str(data{i,j}));
end
end
end
%% PCA
function PCA(data)
len=size(data,1);
data(:,1)=2*data(:,1);
center = mean(data);
[coeff, ~, latent, ~, ~] = pca(data);
% r1 r2 为自定义的向量大小参数(模)
r1 = 6;
r2 = 3;
% p1 p2 为第一主轴和第二主轴上的点
p1 = r1*coeff(:, 1)'+center;
p2 = r2*coeff(:, 2)'+center;
% 主轴方向与X轴之间的夹角
angle = cart2pol(coeff(1, :), coeff(2, :))*180/pi;
beta = angle(1, 1);
% 置信椭圆坐标(以 95% 为例)
semimajor = sqrt(latent(1, 1)); % 长轴长度(一半)
semiminor = sqrt(latent(2, 1)); % 短轴长度(一半)
alpha = linspace(0, 360, 2000)';
% 卡方分布表
% https://people.richland.edu/james/lecture/m170/tbl-chi.html
% level = 4.605; % 90%
level = 5.991; % 95%
% level = 9.210; % 99%
% 椭圆坐标点
ellipse_X = center(1, 1)+sqrt(level)*(semimajor*cosd(alpha)*cosd(beta)-...
semiminor*sind(alpha)*sind(beta));
ellipse_Y = center(1, 2)+sqrt(level)*(semimajor*cosd(alpha)*sind(beta)+...
semiminor*sind(alpha)*cosd(beta));
%% 可视化
% figure
hold on
box on
grid on
% 原始数据
scatter(data(:, 1), data(:, 2), 15, 'LineWidth', 1.2,...
'MarkerEdgeColor', [151, 138, 189]/255,...
'MarkerFaceColor', [151, 138, 189]/255);
xlim([-5, 5]);
ylim([-5, 5]);
set(gca, 'linewidth', 1.5)
% 置信椭圆
plot(ellipse_X, ellipse_Y, 'Color', [0, 102, 255]/255,...
'LineStyle', '-', 'LineWidth', 3),
% 第一主轴方向
arrow_1 = annotation('arrow', 'Color', [162, 20, 47]/255,...
'HeadStyle', 'cback2', 'LineWidth', 3, 'HeadWidth', 20, 'HeadLength', 20);
arrow_1.Parent = gca;
arrow_1.X = [center(1, 1), p1(1, 1)];
arrow_1.Y = [center(1, 2), p1(1, 2)];
% 第二主轴方向
arrow_2 = annotation('arrow', 'Color', [0, 114, 189]/255,...
'HeadStyle', 'cback2', 'LineWidth', 3, 'HeadWidth', 20, 'HeadLength', 20);
arrow_2.Parent = gca;
arrow_2.X = [center(1, 1), p2(1, 1)];
arrow_2.Y = [center(1, 2), p2(1, 2)];
% 中心点
plot(center(1, 1), center(1, 2),...
'Marker', 'o',...
'MarkerSize', 8,...
'MarkerEdgeColor', [162, 20, 47]/255,...
'MarkerFaceColor', [162, 20, 47]/255);
% title('主轴方向和置信椭圆', 'FontSize', 16, 'FontWeight', 'bold')
str=sprintf('len=%d\na=%.2f\nb=%.2f',len,semimajor,semiminor);
text(2, -3, str, 'FontSize', 16, 'FontWeight', 'bold')
axis equal
end
为了让两个随机变量相关,我们记randn
生成的两列数据为 X = [ a , b ] X=[a,b] X=[a,b](其中 a a a 的均值为 0,方差为 2; b b b 的均值为 0,方差为 1),将其乘上一个矩阵得到新的数据 Y Y Y:
Y = [ a ′ , b ′ ] = [ a , b ] ⋅ [ 1 1 − 1 2 ] = X ⋅ Z Y=[a',b']=[a,b]\cdot \left[ \begin{array}{ccc} 1 & 1 \\ -1 & 2 \\ \end{array} \right]=X\cdot Z Y=[a′,b′]=[a,b]⋅[1−112]=X⋅Z
也就是说, a ′ = a − b , b ′ = a + 2 b a'=a-b,\ b'=a+2b a′=a−b, b′=a+2b,然后我们绘制其置信椭圆如下图所示:
%% 两个相关变量的特征
function Test2()
len=300;
data=randn(5*len, 2); % get the data
data(:,1)=2*data(:,1); % 第一列方差变为2
data1=zeros(size(data));
Z=[1 1;-1 2];
data1=data*Z;
% data1(:,1)=data(:,1)-data(:,2);
% data1(:,2)=data(:,1)+2*data(:,2);
PCA(data1);
end
我们能让两个不相关的随机变量通过乘以一个矩阵变成相关的;同样的我们也可以通过乘一个矩阵使它们『解相关』。对于这组数据,我们已经知道了使它们两个变成相关的矩阵 Z Z Z,那么再乘 Z − 1 Z^{-1} Z−1 显然可以解相关;但是对于一个我们不知道变换矩阵(对应此例也就是上文的 Z − 1 Z^{-1} Z−1 ) 的数据,我们一般用下面的方法来求『变换阵』。
PS:之前的我还以为用 LU 分解来求变换阵,并且认为求解变换阵的方法是『高斯变换』,变换阵被称为『高斯变换阵』。但是并不是,是通过特征值和特征向量来求的。
现在的问题是:已知两个随机变量的许多量测(两列数据),而且这两个随机变量是相关的;怎么找到一个矩阵,使得这两列数据乘上这个矩阵之后,这两列数据就不相关了。笔者觉得有两种方法,一种是几何意义上的:将置信椭圆旋转一下,使得其长短轴平行于坐标轴;另一种是纯代数方法:通过矩阵变换找到这样的旋转矩阵(旋转在代数意义上就是乘以一个矩阵)。
实际上,上述问题用主成分分析找主元、降相关就可以解决。
基于特征值分解协方差矩阵实现 PCA 算法的步骤
下面对此种方法求『解相关矩阵』进行一个简单的证明。
基于上述步骤,对于相关变量得到了其变换阵 X 1 X1 X1,绘图结果如下,左上是原始数据,左下和右上对应中可行的转换矩阵,右下啥也不是。我们发现得到的变换阵 X 1 X1 X1 并不是 Z − 1 Z^{-1} Z−1 ( D a t a Y = D a t a X ⋅ Z Data_Y=Data_X\cdot Z DataY=DataX⋅Z),这说明变换阵不唯一?
%% PCA 求 变换矩阵 并绘图
function Test3()
len=300;
data=randn(5*len, 2); % get the data
data(:,1)=2*data(:,1); % 第一列方差变为2
Z=[1 1;-1 3];
data1=data*Z;
A=data1'*data1/(5*len);
[X,D]=eig(A); % 求特征值 D 和特征向量 X; X 各列是相应的特征向量
% A*X(:,1)-D(1,1)*X(:,1)
data2=data1*X;
X1=[X(:,2),X(:,1)]; % 因为第一个特征值比较大,所以优先将第一个特征向量放在前面
data3=data1*X1;
data4=data1*X1';
% Plot
subplot(2,2,1);
PCA(data1); % 原始数据
subplot(2,2,2);
PCA(data2); % 去相关之后的 y 轴长
subplot(2,2,3);
PCA(data3); % 去相关之后的 x 轴长
subplot(2,2,4);
PCA(data4); % 啥也不是
end
PS:实际上,笔者使用的 PCA 函数中已经做了主成分分析(求出了样本的方差-协方差矩阵),里面用的是 Matlab 自带的 pca 函数,为了更详细地了解 PCA 的过程,笔者又做了点额外工作,PCA 函数在本博文中仅仅发挥一个画图的功能。
.m
文件,戳我下载。