5 主成分分析PCA

主成分分析(PCA)是最常见的降维算法。

  • PCA是非监督的机器学习算法
  • 主要用于数据的降维
  • 其他应用:可视化、去噪

1 什么是PCA

1.1 分析

上图为数据在2个特征维度的分布情况,分别垂直到X轴、Y轴,这是两个维度,从上图中来看,无法说明这两个维度哪个更好。那么能否找到一个轴,使得投射到此轴的上的点,间距要比投射到X轴、Y轴间距更大(区分度更高)。如下图:

  • 那么如何找到这个让样本间间距最大的轴?
  • 如何定义样本间间距?

使用方差,Var(x) = \frac{1}{m}\sum_1^m(x_i - \overline{x})^2

1.2步骤

  1. 将样本的均值归0(demean),转化后如下图,此时Var(x) = \frac{1}{m}\sum_1^mx_i ^2
  1. 求一个轴的方向w = (w1,w2),使得所有样本映射到w以后,下面值更大
    Var(x) = \frac{1}{m}\sum_1^m(X_{project}^{(i)} - \overline{X}_{project})^2
    归0化之后,\overline{X}_{project} = 0,此时转化为求下面式子最大值
    Var(x) = \frac{1}{m}\sum_1^m||X_{project}^{(i)}||^2

w = (w1,w2)为一个单位向量

1.3 目标函数

目标:求w,使得下值最大
Var(x) = \frac{1}{m}\sum_1^m(X^{(i)}.w)^2 = \frac{1}{m}\sum_1^m(X_1^{(i)}.w_1 + ... +X_n^{(i)}.w_n)^2

  • 一个目标函数的最优化问题,使用梯度上升法解决

1.4 PCA与线性回归的区别

  • PCA是找到一个轴,使得样本空间点映射到这个轴后方差最大。
  • 线性回归尝试的是最小化预测误差。
  • 线性回归的目的是预测结果,而主成分分析不作任何预测。

上图中,左边的是线性回归的误差(垂直于横轴投影),右边则是主要成分分析(垂直于红线投影)。

2 梯度上升法求PCA问题

2.1 推导过程

目标:求w,使得下值最大
f(X) = \frac{1}{m}\sum_1^m(X_1^{(i)}.w_1 + ... +X_n^{(i)}.w_n)^2

求梯度▽f,转化为向量点乘的过程:w是列向量,X是m*n维矩阵

2.2 代码实现

def demean(X):
    """均值归0化"""
    # 减去每一列的均值
    return X - np.mean(X, axis=0)

def f(w, X):
    """目标函数"""
    return np.sum((X.dot(w)**2)) / len(X)

def df_math(w, X):
    """梯度公式"""
    return X.T.dot(X.dot(w)) * 2. / len(X)

def df_debug(w, X, epsilon=0.0001):
    """梯度调试,因为w是单位向量,比较小,因此epsilon也应该小一些"""
    res = np.empty(len(w))
    for i in range(len(w)):
        w_1 = w.copy()
        w_1[i] += epsilon
        w_2 = w.copy()
        w_2[i] -= epsilon
        res[i] = (f(w_1, X) - f(w_2, X)) / (2 * epsilon)
    return res

def direction(w):
    """让w为单位向量"""
    return w / np.linalg.norm(w)

def first_component(df, X, initial_w, eta, n_iters = 1e4, epsilon=1e-8):
    """梯度上升法求w"""
    # 几个注意点:
    # 1、w初始向量不能为0向量   
    # 2、不能使用StandardScalar 标准化数据 ,因为我们要求这个轴的最大方差,如果使用标准化,方差就为1了,就不存在最大方差了
    w = direction(initial_w)  # 注意,w初始向量不能为0向量
    cur_iter = 0

    while cur_iter < n_iters:
        gradient = df(w, X)
        last_w = w
        w = w + eta * gradient
        w = direction(w) # 注意3:每次求一个单位方向
        if(abs(f(w, X) - f(last_w, X)) < epsilon):
            break
            
        cur_iter += 1

    return w

2.3 前n个主成分

求出第一主成分之后,如何求下一个主成分?
答:将数据在第一个主成分上的分量去掉

如下图:X^{`(i)}即为下一个主成分

def first_n_components(n, X, eta=0.01, n_iters = 1e4, epsilon=1e-8):
    """求前n个主成分"""
    X_pca = X.copy()
    X_pca = demean(X_pca) # 归0化
    res = [] # 记录前n个主成分
    for i in range(n):
        initial_w = np.random.random(X_pca.shape[1])
        w = first_component(X_pca, initial_w, eta)
        res.append(w)
        
        X_pca = X_pca - X_pca.dot(w).reshape(-1, 1) * w  # 去除上一个主成分分量
        
    return res

3 高维数据向低维数据映射

3.1 映射分析

W_k为前k个主成分,X.W_k^T = X_k,就完成了n个维度向k个维度的映射

当然,也可以反向映射,由低维数据映射到高维数据,但是此时会有信息丢失

3.2 PCA的封装

import numpy as np

class PCA:

    def __init__(self, n_components):
        """初始化PCA"""
        assert n_components >= 1
        self.n_components = n_components
        self.components_ = None # 前n个主成分

    def fit(self, X, eta=0.01, n_iters=1e4):
        """获得数据集X的前n个主成分"""
        assert self.n_components <= X.shape[1]

        def demean(X):
            return X - np.mean(X, axis=0)

        def f(w, X):
            return np.sum((X.dot(w) ** 2)) / len(X)

        def df(w, X):
            return X.T.dot(X.dot(w)) * 2. / len(X)

        def direction(w):
            return w / np.linalg.norm(w)

        def first_component(X, initial_w, eta=0.01, n_iters=1e4, epsilon=1e-8):

            w = direction(initial_w)
            cur_iter = 0

            while cur_iter < n_iters:
                gradient = df(w, X)
                last_w = w
                w = w + eta * gradient
                w = direction(w)
                if (abs(f(w, X) - f(last_w, X)) < epsilon):
                    break

                cur_iter += 1

            return w

        X_pca = demean(X)
        self.components_ = np.empty(shape=(self.n_components, X.shape[1]))
        for i in range(self.n_components):
            initial_w = np.random.random(X_pca.shape[1])
            w = first_component(X_pca, initial_w, eta, n_iters)
            self.components_[i,:] = w

            X_pca = X_pca - X_pca.dot(w).reshape(-1, 1) * w

        return self

    def transform(self, X):
        """将给定的X,映射到各个主成分分量中"""
        assert X.shape[1] == self.components_.shape[1]

        return X.dot(self.components_.T)

    def inverse_transform(self, X):
        """将给定的X,反向映射回原来的特征空间"""
        assert X.shape[1] == self.components_.shape[0]

        return X.dot(self.components_)

    def __repr__(self):
        return "PCA(n_components=%d)" % self.n_components

3.3 降到多少维才好

我们先看下问题的产生过程:

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.decomposition import PCA

digits = datasets.load_digits()
X = digits.data
y = digits.target
# X_train.shape = (1347, 64)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)

# %%time 使用kNN算法,时间64.5 ms
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_train)
knn_clf.score(X_test, y_test) #0.98666666666666669

"""使用PCA之后"""
pca = PCA(n_components=2)
pca.fit(X_train)
X_train_reduction = pca.transform(X_train)
X_test_reduction = pca.transform(X_test)

# %%time  时间为2.93 ms,时间大大节省
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train_reduction, y_train)
knn_clf.score(X_test_reduction, y_test)  #0.60666666666666669

原来有64维度,如果降到2维,时间虽然提高了,但是精度却降低的太多了,那么应该降维到多少为好呢?

sklearn 中提供了一个方法

pca.explained_variance_ratio_   # array([ 0.14566817,  0.13735469])
# 表示2个主成分 分别能代表14.5%的方差和13.7%的方差

下面打印所有维度能代表方差的维度

from sklearn.decomposition import PCA

pca = PCA(n_components=X_train.shape[1])
pca.fit(X_train)
pca.explained_variance_ratio_
输出结果

我们绘制成图像

plt.plot([i for i in range(X_train.shape[1])], 
         [np.sum(pca.explained_variance_ratio_[:i+1]) for i in range(X_train.shape[1])])
plt.show()

sklearn 中又提供了一个方法,输入参数为保留多少方差

pca = PCA(0.95)
pca.fit(X_train)
pca.n_components_  # 28

X_train_reduction = pca.transform(X_train)
X_test_reduction = pca.transform(X_test)
# %%time  19.7 ms
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train_reduction, y_train)
knn_clf.score(X_test_reduction, y_test) #0.97999999999999998

4 总结

降维的过程可以理解成是去噪。

降维去除了噪音,有可能准确率更高!

PCA将n个特征降维到k个,可以用来进行数据压缩,如果100维的向量最后可以用10维来表示,那么压缩率为90%。

PCA对数据降维可以简化模型或是对数据进行压缩,同时最大程度的保持了原有数据的信息。

PCA是完全无参数限制的。计算过程中完全不需要人为的设定参数或是根据任何经验模型对计算进行干预,最后的结果只与数据相关,与用户是独立的。

但是,这一点同时也可以看作是缺点。如果用户对观测对象有一定的先验知识,掌握了数据的一些特征,却无法通过参数化等方法对处理过程进行干预,可能会得不到预期的效果,效率也不高。

声明:此文章为本人学习笔记,课程来源于慕课网:python3入门机器学习经典算法与应用。在此也感谢bobo老师精妙的讲解。

如果您觉得有用,欢迎关注我的公众号,我会不定期发布自己的学习笔记、AI资料、以及感悟,欢迎留言,与大家一起探索AI之路。

AI探索之路
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342