PCA 降维算法详解 以及代码示例

转载地址:http://blog.csdn.net/watkinsong/article/details/38536463

1. 前言

PCA : principal component analysis ( 主成分分析)

最近发现我的一篇关于PCA算法总结以及个人理解的博客的访问量比较高, 刚好目前又重新学习了一下PCA (主成分分析) 降维算法, 所以打算把目前掌握的做个全面的整理总结, 能够对有需要的人有帮助。 自己再看自己写的那个关于PCA的博客, 发现还是比较混乱的, 希望这里能过做好整理。 本文的所有总结参考了Andrew Ng的PCA教程, 有兴趣的可以自己学习。

上一篇关于PCA 的博客: http://blog.csdn.net/watkinsong/article/details/8234766, 在这篇博客中,有关于我最初在读研的时候关于PCA的认识, 但是不是很系统, 然后里面却给出了很多我总结的网络上的资料, 以及根据我个人使用的经验总结的感悟, 所以还是收到了很多的好评, o(∩∩)o...哈哈, 谢谢各位的支持。

@copyright by watkins.song ^_^

2. PCA的应用范围

PCA的应用范围有:

1. 数据压缩

1.1 数据压缩或者数据降维首先能够减少内存或者硬盘的使用, 如果内存不足或者计算的时候出现内存溢出等问题, 就需要使用PCA获取低维度的样本特征。

1.2 其次, 数据降维能够加快机器学习的速度。

2. 数据可视化

在很多情况下, 可能我们需要查看样本特征, 但是高维度的特征根本无法观察, 这个时候我们可以将样本的特征降维到2D或者3D, 也就是将样本的特征维数降到2个特征或者3个特征, 这样我们就可以采用可视化观察数据。

3. PCA原理简介

3.1 基础入门

这里我只给出在需要使用PCA的时候需要了解的最基本的PCA的原理, 了解这些原理后对于正常的使用没有问题, 如果想要深入了解PCA, 需要学习一些矩阵分析的知识, 更加详细的PCA算法请见wikipedia。

首先, 我们定义样本和特征, 假定有 m 个样本, 每个样本有 n 个特征, 可以如下表示:

由简到难, 先看一下从2D 降维到1D的比较直观的表示:

在上图中, 假设只有两个特征x1, x2, 然后需要降维到1D, 这个时候我们可以观察途中X所表示的样本点基本上分布在一条直线上, 那么就可以将所有的用(x1, x2)平面表示的坐标映射到图像画出的直线z上, 上图中的黑色铅笔线表示样本点映射的过程。

映射到直线Z后, 如果只用直线Z表示样本的空间分布, 就可以用1个坐标表示每个样本了, 这样就将2D的特征降维到1D的特征。 同样的道理, 如果将3D的特征降维到2D, 就是将具有3D特征的样本从一个三维空间中映射到二维空间。

在上图中, 将所有的二维特征的样本点映射到了一维直线上,  这样, 从上图中可以看出在映射的过程中存在映射误差。

在上图中, 用圆圈表示了样本映射后的坐标位置。这些位置可以叫做近似位置, 以后还要用到这些位置计算映射误差。

因为在降维映射的过程中, 存在映射误差, 所有在对高维特征降维之前, 需要做特征归一化(feature normalization), 这个归一化操作包括: (1) feature scaling (让所有的特征拥有相似的尺度, 要不然一个特征特别小, 一个特征特别大会影响降维的效果) (2) zero mean normalization (零均值归一化)。

在上图中, 也可以把降维的过程看作找到一个或者多个向量u1, u2, ...., un, 使得这些向量构成一个新的向量空间(需要学习矩阵分析哦), 然后把需要降维的样本映射到这个新的样本空间上。

对于2D -> 1D 的降维过程, 可以理解为找到一个向量u1,  u1表示了一个方向, 然后将所有的样本映射到这个方向上, 其实, 一个向量也可以表示一个样本空间。

对于3D -> 2D 的降维过程, 可以理解为找到两个向量u1, u2, (u1, u2) 这两个向量定义了一个新的特征空间, 然后将原样本空间的样本映射到新的样本空间。

对于n-D -> k-D 的降维过程, 可以理解为找到 k 个向量 u1, u2, ..., uk, 这k个向量定义了新的向量空间, 然后进行样本映射。

3.2  Cost Function

既然样本映射存在误差, 就需要计算每次映射的误差大小。 采用以下公式计算误差大小:

X-approx表示的是样本映射以后的新的坐标, 这个坐标如果位置如果用当前的样本空间表示, 维度和 样本X是一致的。

要特别注意, PCA降维和linear regression是不一样的, 虽然看上去很一致, 但是linear regression的cost function的计算是样本上线垂直的到拟合线的距离, 而PCA的cost function 是样本点到拟合线的垂直距离。 差别如下图所示:

3.3 PCA 计算过程

(A) Feature Normalization

首先要对训练样本的特征进行归一化, 特别强调的是, 归一化操作只能在训练样本中进行, 不能才CV集合或者测试集合中进行, 也就是说归一化操作计算的各个参数只能由训练样本得到, 然后测试样本根据这里得到的参数进行归一化, 而不能直接和训练样本放在一起进行归一化。

另外, 在训练PCA降维矩阵的过程中,也不能使用CV样本或者测试样本, 这样做是不对的。 有很多人在使用PCA训练降维矩阵的时候, 直接使用所有的样本进行训练, 这样实际上相当于作弊的, 这样的话降维矩阵是在包含训练样本和测试样本以及CV样本的情况下训练得到的, 在进行测试的时候, 测试样本会存在很大的优越性, 因为它已经知道了要降维到的空间情况。

特征归一化直接给出代码参考:

function [X_norm, mu, sigma] = featureNormalize(X)
%FEATURENORMALIZE Normalizes the features in X
%   FEATURENORMALIZE(X) returns a normalized version of X where
%   the mean value of each feature is 0 and the standard deviation
%   is 1. This is often a good preprocessing step to do when
%   working with learning algorithms.  

mu = mean(X);
X_norm = bsxfun(@minus, X, mu);  

sigma = std(X_norm);
X_norm = bsxfun(@rdivide, X_norm, sigma);
end  

注意: 这里的X是一个m * n 的矩阵, 有 m 个样本, 每个样本包含 n 个特征, 每一行表示一个样本。 X_norm是最终得到的特征, 首先计算了所有训练样本每个特征的均值, 然后减去均值, 然后除以标准差。

(B) 计算降维矩阵

B1. 首先计算样本特征的协方差矩阵

如下图所示, 如果是每个样本单独计算, 则采用图中横线上的公式, 如果是采用矩阵化的计算, 则采用横线下的公式。

B2. 计算协方差矩阵的特征值和特征向量

采用奇异值分解的算法计算协方差矩阵的特征值和特征向量,  奇异值分解是个比较复杂的概念, 如果有兴趣可以查看wikipedia, 也可以直接使用matlab或者octave已经提供的奇异值分解的接口。

在上图中, U 则是计算得到的协方差矩阵的所有特征向量, 每一列都是一个特征向量, 并且特征向量是根据特征大小由大到小进行排序的, U 的维度为 n * n 。 U 也被称为降维矩阵。 利用U 可以将样本进行降维。 默认的U 是包含协方差矩阵的所有特征向量, 如果想要将样本降维到 k 维, 那么就可以选取 U 的前 k 列, Uk 则可以用来对样本降维到  k 维。 这样 Uk 的维度为 n * k

(C) 降维计算

获得降维矩阵后, 即可通过降维矩阵将样本映射到低维空间上。 降维公式如下图所示:

如果是对于矩阵X 进行降维, X 是 m * n的, 那么降维后就变为 m * k 的维度, 每一行表示一个样本的特征。

3.4  贡献率 (降维的k的值的选择)

在 http://blog.csdn.net/watkinsong/article/details/8234766 这篇文章中, 很多人问了关于贡献率的问题, 这就是相当于选择k的值的大小。 也就是选择降维矩阵 U 中的特征向量的个数。

k 越大, 也就是使用的U 中的特征向量越多, 那么导致的降维误差越小, 也就是更多的保留的原来的特征的特性。 反之亦然。

从信息论的角度来看, 如果选择的 k 越大, 也就是系统的熵越大, 那么就可以认为保留的原来样本特征的不确定性也就越大, 就更加接近真实的样本数据。 如果 k 比较小, 那么系统的熵较小, 保留的原来的样本特征的不确定性就越少, 导致降维后的数据不够真实。 (完全是我个人的观点)

关于 k 的选择, 可以参考如下公式:

上面这个公式 要求 <= 0.01, 也就是说保留了系统的99%的不确定性。

需要计算的就是, 找到一个最小的 k 使得上面的公式成立, 但是如果计算上面公式, 计算量太大, 并且对于每一个  k  取值都需要重新计算降维矩阵。

可以采用下面的公式计算 k 的取值, 因为在 对协方差矩阵进行奇异值分解的时候返回了 S , S 为协方差矩阵的特征值, 并且 S 是对角矩阵, 维度为  n * n, 计算 k 的取值如下:

3.5  重构 (reconstruction, 根据降维后数据重构原数据), 数据还原

获得降维后的数据, 可以根据降维后的数据还原原始数据。

还原原始数据的过程也就是获得样本点映射以后在原空间中的估计位置的过程, 即计算 X-approx的过程。

使用降维用的降维矩阵 Uk, 然后将 降维后的样本 z 还原回原始特征, 就可以用上图所示的公式。

4. PCA的应用示例

貌似本页已经写的太多了, 所以这里示例另外给出。

请狂点: http://blog.csdn.net/watkinsong/article/details/38539289

由于篇幅问题, 这里只给出代码, 关于代码的解释和插图, 请访问上面链接

%% Initialization
clear ; close all; clc  

fprintf(‘this code will load 12 images and do PCA for each face.\n‘);
fprintf(‘10 images are used to train PCA and the other 2 images are used to test PCA.\n‘);  

m = 4000; % number of samples
trainset = zeros(m, 32 * 32); % image size is : 32 * 32  

for i = 1 : m
    img = imread(strcat(‘./img/‘, int2str(i), ‘.bmp‘));
    img = double(img);
    trainset(i, :) = img(:);
end  

%% before training PCA, do feature normalization
mu = mean(trainset);
trainset_norm = bsxfun(@minus, trainset, mu);  

sigma = std(trainset_norm);
trainset_norm = bsxfun(@rdivide, trainset_norm, sigma);  

%% we could save the mean face mu to take a look the mean face
imwrite(uint8(reshape(mu, 32, 32)), ‘meanface.bmp‘);
fprintf(‘mean face saved. paused\n‘);
pause;  

%% compute reduce matrix
X = trainset_norm; % just for convience
[m, n] = size(X);  

U = zeros(n);
S = zeros(n);  

Cov = 1 / m * X‘ * X;
[U, S, V] = svd(Cov);
fprintf(‘compute cov done.\n‘);  

%% save eigen face
for i = 1:10
    ef = U(:, i)‘;
    img = ef;
    minVal = min(img);
    img = img - minVal;
    max_val = max(abs(img));
    img = img / max_val;
    img = reshape(img, 32, 32);
    imwrite(img, strcat(‘eigenface‘, int2str(i), ‘.bmp‘));
end  

fprintf(‘eigen face saved, paused.\n‘);
pause;  

%% dimension reduction
k = 100; % reduce to 100 dimension
test = zeros(10, 32 * 32);
for i = 4001:4010
    img = imread(strcat(‘./img/‘, int2str(i), ‘.bmp‘));
    img = double(img);
    test(i - 4000, :) = img(:);
end  

% test set need to do normalization
test = bsxfun(@minus, test, mu);  

% reduction
Uk = U(:, 1:k);
Z = test * Uk;
fprintf(‘reduce done.\n‘);  

%% reconstruction
%% for the test set images, we only minus the mean face,
% so in the reconstruct process, we need add the mean face back
Xp = Z * Uk‘;
% show reconstructed face
for i = 1:5
    face = Xp(i, :) + mu;
    face = reshape((face), 32, 32);
    imwrite(uint8(face), strcat(‘./reconstruct/‘, int2str(4000 + i), ‘.bmp‘));
end  

%% for the train set reconstruction, we minus the mean face and divide by standard deviation during the train
% so in the reconstruction process, we need to multiby standard deviation first,
% and then add the mean face back
trainset_re = trainset_norm * Uk; % reduction
trainset_re = trainset_re * Uk‘; % reconstruction
for i = 1:5
    train = trainset_re(i, :);
    train = train .* sigma;
    train = train + mu;
    train = reshape(train, 32, 32);
    imwrite(uint8(train), strcat(‘./reconstruct/‘, int2str(i), ‘train.bmp‘));
end  

fprintf(‘job done.\n‘);

  

时间: 2024-08-05 02:18:11

PCA 降维算法详解 以及代码示例的相关文章

设计模式之模板方法模式(Template Method)详解及代码示例

一.模板方法模式的定义与特点 模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤.它是一种类行为型模式. 二.模板方法模式优缺点 该模式的主要优点如下. 它封装了不变部分,扩展可变部分.它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展. 它在父类中提取了公共的部分代码,便于代码复用. 部分方法是由子类实现的,因此子类可以通过扩展

java中的List详解以及代码示例

一:概念List是Java集合Collection中的一个接口,一般用ArrayList类和LinkedList类去实现这个接口.Collection集合还有其他接口:Map,Set(在我的另一篇博客)二:LIST的使用List的常用方法 boolean add(E e) //尾插 e void add(int index, E element) //将 e 插入到 index 位置 boolean addAll(Collection<? extends E> c) //尾插 c 中的元素 E

设计模式之策略模式(Strategy)详解及代码示例

一.策略模式的定义 策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户.策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理. 二.策略模式优缺点 策略模式的主要优点如下. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件转移语句. 符合开闭原则,可以在不修改原代码的情况下,灵活增加新算法. 算法使用和实现隔离分离,提高算法的保密性和

linux进程间通信之System V共享内存详解及代码示例

共享内存是最快最为高效的进程间通信方式,当共享内存映射到共享它的某个进程的地址空间后,进程间的数据传递就不再牵扯到内核,进程可以直接读取内核,不需要通过内核系统调用进行数据拷贝.一般使用情况,从共享内存中写入或读取数据的进程间需要做同步,例如通过信号量,互斥锁去同步. 共享内存有System V 共享内存和Posix共享内存,本文介绍System V 共享内存.System V共享内存头文件及相关函数原型:#include <sys/shm.h> int shmget(key_t key, s

设计模式之代理模式(Proxy)详解及代码示例

一.代理模式的定义 代理模式的定义:由于某些原因需要给某对象提供一个代理以控制对该对象的访问.这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介,代理模式也叫做委托模式. 二.为什么使用代理模式 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口. 开闭原则,增加功能:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委

设计模式之原型模式(Prototype)详解及代码示例

一.原型模式的定义与特点 原型(Prototype)模式的定义如下:用一个已经创建的实例作为原型,通过复制该原型对象来创建一个和原型相同或相似的新对象.它属于创建型设计模式,用于创建重复的对象,同时又能保证性能(用这种方式创建对象非常高效). 这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆.当直接创建对象的代价比较大时,则采用这种模式.例如,一个对象需要在一个高代价的数据库操作之后被创建.我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用.

设计模式之装饰模式(Decorator)详解及代码示例

一.装饰模式的定义 装饰(Decorator)模式的定义:指在不改变现有对象结构的情况下,动态地给该对象增加一些职责(即增加其额外功能)的模式,它属于对象结构型模式. 二.装饰模式优缺点 装饰(Decorator)模式的主要优点有: 采用装饰模式扩展对象的功能比采用继承方式更加灵活. 可以设计出多个不同的具体装饰类,创造出多个不同行为的组合. 其主要缺点是: 装饰模式增加了许多子类,如果过度使用会使程序变得很复杂. 三.装饰模式的实现 通常情况下,扩展一个类的功能会使用继承方式来实现.但继承具有

KMP算法详解(图示+代码)

算法过程非常绕,不要企图一次就能看明白,多尝试就会明白一些.下面试图用比较直观的方法解释这个算法,对KMP算法的解释如下: 1. 首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较.因为B与A不匹配,所以搜索词后移一位. 2. 因为B与A不匹配,搜索词再往后移. 3. 就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止. 4. 接着比较字符串和搜索词的下一个字符,还是相同. 5. 直到字

DEM山体阴影原理以及算法详解

山体阴影原理以及算法详解 山体阴影基本原理: 山体阴影是假想一个光源在某个方向和某个太阳高度的模拟下,用过临近像元的计算来生成一副0-255的灰度图. 一.山体阴影的主要参数: 1.  太阳光线的入射角度:这个角度的量算起点是正北方向,按照顺时针的方向,角度的范围是0到360度,如下图所示,默认的角度是315度,西北方向,如下图所示: 2.  太阳高度角:太阳高度角也简称太阳高度.是太阳光线和当地地平面之间的夹角,范围是0-90度,默认的太阳高度是45度,如下图所示: 二.山体阴影计算方法 山体