前言
上一篇博客介绍了如何求解一个数据集相应的前 n n n 个主成分,但是在这里需要注意,虽然求出了这些主成分所代表的坐标轴的方向,但是我们的数据集本身依然是 n n n 维的,并没有进行降维,那么具体我们是如何利用 P C A PCA PCA 法对数据进行降维的呢?这篇博客主要介绍了如何从原有的高维数据向低维数据进行映射。
高维数据映射为低维数据
比如我这里有一个 X X X,它有 m m m 个样本, n n n 个特征。假设此时我已经求出来 X X X 的前 k k k 个主成分,每一个主成分代表一个单位方向,用 W W W 表示,这个 W W W 也是一个矩阵, 有 k k k 行,代表求出的前 k k k 个主成分,对于每一行有 n n n 个元素,代表每一个主成分那个坐标轴应该有 n n n 个元素。
那么问题来了,如何将
X
X
X 从
n
n
n 维转换成
k
k
k 维呢?回顾一下之前所学习的过程。
对于我们的一个样本(对应
X
X
X 的一行)和对应的
W
(
i
)
W(i)
W(i) (
W
k
W_k
Wk的某一行)进行点乘,点乘后的结果就是将这一个样本映射到
W
(
i
)
W(i)
W(i) 这个轴上得到的模。如果我们将这一个样本和
k
k
k 个
W
(
i
)
W(i)
W(i) 分别进行点乘,那么得到的就是这一个样本在这
k
k
k 个方向上的模(即
k
k
k 个模),这
k
k
k 个元素就能表示这一个样本映射到我们新的
k
k
k 个坐标轴上相应样本的大小。所以这个
W
k
W_k
Wk 有
k
k
k 行的话, 我们把每一行叫做
W
1
W_1
W1,
W
2
W_2
W2…一直到
W
k
W_k
Wk 的话,我们的样本 1 乘以
W
1
W_1
W1,样本 1 乘以
W
2
W_2
W2…一直到样本 1 乘以
W
k
W_k
Wk,这样就得到了
k
k
k 个数组成的向量,就是样本 1 映射到了
W
k
W_k
Wk 这
k
k
k 个坐标系上得到的一个新的
k
k
k 维的向量。由于
k
k
k 是比
n
n
n 小的,我们就完成了一个样本从
n
n
n 维向量到
k
k
k 维的映射。那么这个过程以此类推,我们的样本2,样本3,一直到样本
m
m
m 都这样做,我们就将所有的样本从
n
n
n 维映射到了
k
k
k 维。
其实这个过程我们就只做了一个矩阵的乘法:
这里为什么要进行转置(
T
T
T)呢?我们的目的是要使
X
X
X 的每一行和
W
k
W_k
Wk 的每一行做乘法,但是矩阵的运算是
X
X
X 的每一行和
W
k
W_k
Wk 中的每一列做乘法,所以我们要将
W
k
W_k
Wk 进行一个转置。也可以这么理解,
X
X
X 是一个
m
m
m x
n
n
n 的矩阵,
W
k
W_k
Wk 是一个
k
k
k x
n
n
n 的矩阵,所以
W
k
T
W_k^T
WkT 就是一个
n
n
n *
k
k
k 的矩阵,最后二者相乘得到一个矩阵
X
k
X_k
Xk 就是一个
m
m
m x
k
k
k 的矩阵。
一旦我们获取了
X
k
X_k
Xk (
m
m
m x
k
k
k)之后
而对于
W
k
W_k
Wk (
k
k
k x
n
n
n)矩阵:
依然可以反过来相应的将其恢复成原来的
n
n
n 维的数据,那么这个恢复(
r
e
s
t
o
r
e
restore
restore)的过程也很简单。其实就是现在我们每一行数据有
k
k
k 个元素,这
k
k
k 个元素和
W
k
W_k
Wk 的每一列去做乘法,我们此时把
X
k
X_k
Xk 中的每一行映射到
W
k
W_k
Wk 中每一列对应的方向中,一共有
n
n
n 列,最终会得到
m
m
m x
n
n
n 的矩阵。当然这个恢复回来的矩阵已经不是原来的
X
X
X 矩阵了,这是因为我们在降维的过程中,其实丢失了一些信息,那么再恢复回来,丢失的这些信息也是恢复不回来的。但是这个反向的操作本身从数学的角度是成立的,那么这两个矩阵相乘就会的得到
X
m
X_m
Xm:
那么这个
X
m
X_m
Xm 和原来的
X
X
X 的区别是怎样的?我们马上用编程来实验:
# PCA.py
import numpy as np
class PCA:
def __init__(self, n_components):
"""初始化PCA"""
assert n_components >= 1, "n_components must be valid"
self.n_components = n_components
self.components_ = None
def fit(self, X, eta=0.01, n_iters=1e4):
"""获得数据集X的前n个主成分"""
assert self.n_components <= X.shape[1], \
"n_components must not be greater than the feature number of X"
def deamen(X): # 均值归为0
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, n_iters=1e4, epsilon=1e-8):
w = direction(initial_w)
i_iter = 0
while i_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
i_iter += 1
return w
X_pca = deamen(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
通过这个例子,inverse_transform 的过程中是丢失信息的。也就是说,我们将我们的样本进行降维,在降维的过程中肯定失去了一些信息,失去的这些信息并不能 r e s t o r e restore restore 回来, r e s t o r e restore restore 的过程只不过在高维的空间里表达这些低维的样本而已,就像这些红色的点。
下面总结一下 P C A PCA PCA 的过程。首先它做的事情就是寻找另外的一个坐标系,这个坐标系中每一个轴依次可以表达原来的样本的重要程度,也就是称为所有的主成分。我们取出前 k k k 个最重要的主成分,然后就可以将所有的样本映射到这 k k k 个轴上,获得一个低维的数据信息。
具体代码见 34 高维数据映射为低维数据.ipynb