目录
我们前面讲了数据的预处理,也着重讲了其中的特征工程。现在我们来理解下也是常用操作之一的降维。那么什么是降维,降维就是在大量特征面前适当的减少我们需要的特征,不要去选择一些对结果影响不大的特征。那为什么要降维呢?
为什么要降维
这个涉及到一个叫做维度灾难的问题。我们从头解释下就是在针对解决一个问题时做特征选择的时候:
1、如果我们只选择一个特征,那么很明显各个数据点都在一条线上,线上的每个点就是不同的样本;
2、如果我们选择两个特征,我们就会在一个平面上表示,用数轴表示即X轴是一个特征,Y轴是一个特征,一个样本就是一个点;
3、如果我们选择三个特征,那么就是用立体三维来表示,这没问题吧?说白了X轴是一个特征,Y轴是一个特征,Z轴是一个特征。
如图表示:
我们仔细看,会发现一个问题,随着维度的增加,彼此同一个点之间的距离似乎可能画的下更多的点。比如一个点的单位为1,任意找一个线上两个点距离比如为3(._.),那么他们之间可以容下最多3个点,换成二维呢,如点(1,1)和点(4,4)他们之间的是不是直线距离是不是比3²+3²=18>4²,就是说可以塞下4个点了。
维度灾难也就是说我们需要填充的 剩余空间的 数据点的 数量 随着空间维度的增加而呈指数增长。
这个时候就有一个问题了,当我们的模型在测试时遇到新的数据点,如果多维度下使得离之前遇到过的点很远时,它就会发慌,因为他不知道自己属于哪一类。如果维度降低,距离就会比多维的小,就会一定程度避免它发慌而不知道自己是什么东西。
主成分分析
我们上诉讲了为什么降维,现在说下降维的一个主要技术——“主成分分析”(Principal Component Analysis,PCA):
我们之前举的例子都没有一个实际的图片的例子,我们现在来我们举个图片的例子加以理解。我们举个黑白图片(就是二维图像)例子,它有一个特征就是灰度值,就类似三维彩色图片的RGB值一样。那么像素为m*n的二维图片,我们转成特征向量就是mn*1的长度,其中第i个值就是第i个像素的灰度值:
如果我们用维度来表示各个像素点的灰度值,那么需要的就是256的m*n次方(灰度值在0~255,请注意这个图片的维度是各个像素点的可能的灰度值,比如像素点1可能就有256个灰度值,像素点2也是256,那么1*2大小的图片就有256的2次方)。这个对于一个正常图片吧来说太大了对吧,而且灰度值去描述一张图片的内容也不是最好的方式。这个时候我们就要用到PCA了——分析主成分!
我们不要像上面那样去表述,我么换个方法,我们选取两个特征,一个是像素x轴的灰度值即n,一个是y轴的灰度值m,如果我们把这两个点上的坐标表示出来,正常情况下是会成“高斯分布”的(高斯分布也叫作“正态分布”,即中间高两头低)。为什么?因为一个图片的色彩大多是像素点是在一个范围内的,就像你一眼看过去,大部分都是一个颜色。
import numpy as np
import matplotlib.pyplot as plt
# 峰值为(20,20)这个点
mean = [20, 20]
# 协方差,必须是正方形矩阵所以是二维二列
cov = [[5, 0], [25, 25]]
# 每次都取一样的值
np.random.seed(42)
# 做一个高斯分布图
x, y = np.random.multivariate_normal(mean, cov, 1000).T
plt.style.use('ggplot')
%matplotlib inline
plt.figure(figsize=(10, 6))
plt.plot(x, y, 'o', zorder=1)
plt.axis([0, 40, 0, 40])
plt.xlabel('feature 1')
plt.ylabel('feature 2');
PCA所做的就是旋转所有的数据点,使它们分布到可以解释大部分分布的两标系中。在openCV中执行PCA就很简单:
# 首先把特征向量X,Y组合成一个特征矩阵X
X = np.vstack((x, y)).T
# 在特征矩阵上计算PCA,指定空数组用作蒙版参数,就是要用矩阵上所有的点
import cv2
mu, eig = cv2.PCACompute(X, np.array([])) #输出array([[ 0.71481632, 0.69931225],
[-0.69931225, 0.71481632]])
这个返回的两个值:投影前减去的平均值(mean)和协方差矩阵的特征向量(eig)。这些特征向量指向PCA认为的最有信息性的方向。我们画出来看下就懂了:
plt.figure(figsize=(10, 6))
plt.plot(x, y, 'o', zorder=1)
plt.quiver(mean[0], mean[1], eig[:, 0], eig[:, 1], zorder=3, scale=0.2, units='xy')
plt.text(mean[0] + 5 * eig[0, 0], mean[1] + 5 * eig[0, 1], 'u1', zorder=5,
fontsize=16, bbox=dict(facecolor='white', alpha=0.6))
plt.text(mean[0] + 7 * eig[1, 0], mean[1] + 4 * eig[1, 1], 'u2', zorder=5,
fontsize=16, bbox=dict(facecolor='white', alpha=0.6))
plt.axis([0, 40, 0, 40])
plt.xlabel('feature 1')
plt.ylabel('feature 2');
其中u1表示的是数据分布最大的方向(也叫作第一主成分),u2表示的是主要方差方向(第二主成分)。看出来了吗,这些主成分的方向并不是X,Y轴的方向,所以PCA告诉我们,选择u1、u2作为坐标轴比选择X,Y轴更有意义!是不是很神奇。
我们为了证明下是不是u1、u2作为坐标轴更好,我们将数据旋转下(使用cv2.PCAProject):
X2 = cv2.PCAProject(X, mu, eig)
旋转好了,现在画出来看下结果:
plt.figure(figsize=(10, 6))
plt.plot(X2[:, 0], X2[:, 1], 'o')
plt.xlabel('first principal component')
plt.ylabel('second principal component')
plt.axis([-20, 20, -10, 10])
独立成分分析
上面讲了openCV中的主成分分析技术,现在讲实现降维的另一个技术——“独立成分分析”(Independent Component Analysis,ICA)。这个技术是scikit-learn提供的,跟PCA有着密切的相关,存在于ICA在decomposition模块中。
from sklearn import decomposition
ica = decomposition.FastICA()
X2 = ica.fit_transform(X)
plt.figure(figsize=(10, 6))
plt.plot(X2[:, 0], X2[:, 1], 'o')
plt.xlabel('first independent component')
plt.ylabel('second independent component')
plt.axis([-0.2, 0.2, -0.2, 0.2])
plt.savefig('ica.png')
实现非负矩阵分解
还有一种有名的降维技术叫做——“非负矩阵分解”(Non-negative Matrix Factorization,NMF)。它和PCA、ICA差不多,但是有一点很特殊,就是它只能操作非负的数据,也就是说NMF中不能有负值,分解的结果也不会有负值。
nmf = decomposition.NMF()
X2 = nmf.fit_transform(X)
plt.figure(figsize=(10, 6))
plt.plot(X2[:, 0], X2[:, 1], 'o')
plt.xlabel('first non-negative component')
plt.ylabel('second non-negative component')
plt.axis([0, 7, -0, 15])