主成分分析PCA

1.PCA的理解

  • 一个非监督的机器学习算法(本质:从一个坐标系转变为另外一个坐标系)
  • 主要用于数据的降维,通过降维可以发现更便于人类理解的特征
  • 其他应用:可视化;去噪

Tip:注意主成分分析和线性回归的区别

如何找到让样本间间距最大的轴?

样本间的间距使用方差表示(方差描述样本整体疏密,方差越大越疏,方差越小越密)

于是问题转化为如何找到一个轴,使得样本空间的所有点映射到这个轴后,方差最大。

步骤:

  1. 将样例的均值归为0,此时方差公式为$varleft ( x ight )=frac{1}{m}sum  _{i=1}^{m} x_{i}^{2}$;
  2. 求一个轴的方向w=(w1, w2)使得所有的样本映射到w以后,有$varleft ( X_{project} ight )=frac{1}{m}sum  _{i=1}^{m} left | X_{project}^{left ( i ight )} ight |^{2}$最大;
  3. 目标:求w,使得$varleft ( X_{project} ight )=frac{1}{m}sum  _{i=1}^{m} left ( X^{left ( i ight )}*w ight )^{2}$最大;
  4. 扩展到N维,$varleft ( X_{project} ight )=frac{1}{m}sum  _{i=1}^{m} left ( X_{1}^{left ( i ight )}*w_{1} + X_{2}^{left ( i ight )}*w_{2} +...+ X_{n}^{left ( i ight )}*w_{n} ight )^{2}==frac{1}{m}sum  _{i=1}^{m} left ( sum _{j=1}^{n} X_{j}^{left ( i ight )}*w_{j} ight ) ^{2}$

2.求数据的第一个主成分

1 import numpy as np
2 import matplotlib.pyplot as plt
3 
4 X =np.empty((100, 2))
5 X[:, 0] = np.random.uniform(0., 100., size=100)
6 X[:, 1] = 0.75 * X[:, 0] + 3. + np.random.normal(0, 10., size=100)
7 
8 plt.scatter(X[:, 0], X[:, 1])
9 plt.show()

均值归0,只是数据的相对位置变化了。

1 def demean(X):  
2     return X - np.mean(X, axis=0)     #X矩阵在行方向上取均值
3     
4 X_demean = demean(X)
5 plt.scatter(X_demean[:, 0], X_demean[:, 1])
6 plt.show() 

使用梯度上升法求解

 1 #梯度上升法
 2 def f(w, X):                         #对应步骤3中的目标函数
 3     return np.sum((X.dot(w))**2) / len(X)
 4   
 5 def df_math(w, X):                   #对目标函数进行求导(过程如上图)
 6     return X.T.dot(X.dot(w)) * 2. / len(X)
 7 
 8 
 9 def df_debug(w, X, epsilon=0.0001):  #对目标函数按定义求导
10     res = np.empty(len(w))
11     for i in range(len(w)):
12         w_1 = w.copy()
13         w_1[i] += epsilon
14         w_2 = w.copy()
15         w_2[i] -= epsilon
16         res[i] = (f(w_1, X) - f(w_2, X)) / (2 * epsilon)
17     return res
18 
19 def direction(w):
20     return w / np.linalg.norm(w)
21 
22 def gradient_ascent(df, X, initial_w, eta, n_iters = 1e4, epsilon = 1e-8): 
23     
24     w = direction(initial_w)        
25     cur_iter = 0
26     
27     while cur_iter < n_iters:
28         gradient = df(w, X)
29         last_w = w
30         w = w + eta * gradient
31         w = direction(w)                  #注意1:每次求一个单位方向
32         if(abs(f(w, X) - f(last_w, X)) < epsilon):
33             break
34         
35         cur_iter += 1
36     return w
37 
38 initial_w = np.random.random(X.shape[1])  #注意2:不能用0向量开始
39 
40 eta = 0.001
41 
42 #注意3:不能用StandardScaler标准化数据
43 w = gradient_ascent(df_debug, X_demean, initial_w, eta)   #[0.78339779 0.62152064]
44 
45 plt.scatter(X_demean[:, 0], X_demean[:, 1])
46 plt.plot([0, w[0]*30], [0, w[1]*30], color='r')
47 plt.show()

3.求数据的前n个主成分

求出第一主成分后,如何求出下一个主成分?

数据进行改变,将数据在第一个主成分上的分量去掉Xproject(如图蓝色线),剩下的分量X'(如图绿色线),在新的数据上求第一主成分。

下面代码基本和2中的一样

 1 import numpy as np
 2 import matplotlib.pyplot as plt
 3 
 4 X =np.empty((100, 2))
 5 X[:, 0] = np.random.uniform(0., 100., size=100)
 6 X[:, 1] = 0.75 * X[:, 0] + 3. + np.random.normal(0, 10., size=100)
 7 
 8 def demean(X):  
 9     return X - np.mean(X, axis=0)     #X矩阵在行方向上取均值
10     
11 X = demean(X) 
12 
13 #梯度上升法
14 def f(w, X):                    #对应步骤3中的目标函数
15     return np.sum((X.dot(w))**2) / len(X)
16   
17 def df(w, X):                   #对目标函数进行求导
18     return X.T.dot(X.dot(w)) * 2. / len(X)
19 
20 def direction(w):
21     return w / np.linalg.norm(w)
22 
23 def first_component(X, initial_w, eta, n_iters = 1e4, epsilon = 1e-8): 
24     
25     w = direction(initial_w)        
26     cur_iter = 0
27     
28     while cur_iter < n_iters:
29         gradient = df(w, X)
30         last_w = w
31         w = w + eta * gradient
32         w = direction(w)                  #注意1:每次求一个单位方向
33         if(abs(f(w, X) - f(last_w, X)) < epsilon):
34             break
35         
36         cur_iter += 1
37     return w
38 
39 initial_w = np.random.random(X.shape[1])  #注意2:不能用0向量开始
40 
41 eta = 0.001
42 
43 #注意3:不能用StandardScaler标准化数据
44 w = first_component(X, initial_w, eta)
1 #去除掉第一维度的主成分之后,剩下的内容
2 X2 = X - X.dot(w).reshape(-1, 1) * w
3 plt.scatter(X2[:, 0], X2[:, 1])
4 plt.show()

数据只有两个维度,去除掉第一维度的主成分之后,只剩下第二维度的内容了。

 1 #求第二主成分对应的轴
 2 w2 = first_component(X2, initial_w, eta)   #[-0.63914289  0.76908801]这个方向与w的方向垂直
 3 
 4 print(w.dot(w2))                           #2.3703206865843818e-05趋近于0
 5 
 6 
 7 def first_n_components(n, X, eta=0.01, n_iters = 1e4, epsilon = 1e-8): 
 8     X_pca = X.copy()
 9     X_pca = demean(X_pca)
10     
11     res = []
12     for i in range(n):
13         initial_w = np.random.random(X_pca.shape[1])
14         w = first_component(X_pca, initial_w, eta) 
15         res.append(w)
16         
17         X_pca = X_pca - X_pca.dot(w).reshape(-1, 1) * w
18     return res
19 
20 print(first_n_components(2, X))         #[array([0.76907328, 0.63916061]), array([ 0.63916425, -0.76907026])]

主成分分析法的本质是从一个坐标系转变为另外一个坐标系,原来的坐标系有n个维度的话,转换后的坐标系也是n个维度,只不过我们取出前k个,这k个方向更加重要。

4.scikit-learn中的PCA

 1 import numpy as np
 2 import matplotlib.pyplot as plt
 3 from sklearn import datasets
 4 
 5 digits = datasets.load_digits()
 6 X = digits.data        #shape=(1347, 64)
 7 y = digits.target
 8 
 9 from sklearn.model_selection import train_test_split
10 X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
11 
12 #不降维,使用KNN算法进行分类
13 from sklearn.neighbors import KNeighborsClassifier
14 
15 knn_clf = KNeighborsClassifier()
16 knn_clf.fit(X_train, y_train)
17 
18 knn_clf.score(X_test, y_test)                #0.9866666666666667
19 
20 #使用PCA进行降维后
21 from sklearn.decomposition import PCA 
22 
23 pca = PCA(n_components=2)
24 pca.fit(X_train)
25 X_train_reduction = pca.transform(X_train)   #训练集和测试集使用同样的PCA
26 X_test_reduction = pca.transform(X_test)
27 
28 knn_clf = KNeighborsClassifier()
29 knn_clf.fit(X_train_reduction, y_train)
30 
31 knn_clf.score(X_test_reduction, y_test)      #0.6066666666666667只有2个维度时速度快了,精确率下降了

n_components取多少维合适呢?看参数explained_variance_ratio_ 

1 pca.explained_variance_ratio_                #array([0.14566817, 0.13735469])表示上面2个维度维持原来数据的百分之多少
2 
3 pca = PCA(n_components=64)
4 pca.fit(X_train)
5 pca.explained_variance_ratio_               #表示每一个方向轴的重要程度
 1 array([1.45668166e-01, 1.37354688e-01, 1.17777287e-01, 8.49968861e-02,
 2        5.86018996e-02, 5.11542945e-02, 4.26605279e-02, 3.60119663e-02,
 3        3.41105814e-02, 3.05407804e-02, 2.42337671e-02, 2.28700570e-02,
 4        1.80304649e-02, 1.79346003e-02, 1.45798298e-02, 1.42044841e-02,
 5        1.29961033e-02, 1.26617002e-02, 1.01728635e-02, 9.09314698e-03,
 6        8.85220461e-03, 7.73828332e-03, 7.60516219e-03, 7.11864860e-03,
 7        6.85977267e-03, 5.76411920e-03, 5.71688020e-03, 5.08255707e-03,
 8        4.89020776e-03, 4.34888085e-03, 3.72917505e-03, 3.57755036e-03,
 9        3.26989470e-03, 3.14917937e-03, 3.09269839e-03, 2.87619649e-03,
10        2.50362666e-03, 2.25417403e-03, 2.20030857e-03, 1.98028746e-03,
11        1.88195578e-03, 1.52769283e-03, 1.42823692e-03, 1.38003340e-03,
12        1.17572392e-03, 1.07377463e-03, 9.55152460e-04, 9.00017642e-04,
13        5.79162563e-04, 3.82793717e-04, 2.38328586e-04, 8.40132221e-05,
14        5.60545588e-05, 5.48538930e-05, 1.08077650e-05, 4.01354717e-06,
15        1.23186515e-06, 1.05783059e-06, 6.06659094e-07, 5.86686040e-07,
16        1.71368535e-33, 7.44075955e-34, 7.44075955e-34, 7.15189459e-34])
1 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])])
2 plt.show()

PCA中封装了这个函数,在给PCA函数中不传入n_components,而是0~1之间的数

 1 pca = PCA(0.95)          #表示能够解释原来数据95%以上的方差
 2 pca.fit(X_train)
 3 print(pca.n_components_) #28,即对于原来64维的数据来说,使用28维数据仅丢失了5%的信息
 4 X_train_reduction = pca.transform(X_train)   
 5 X_test_reduction = pca.transform(X_test)
 6 
 7 knn_clf = KNeighborsClassifier()
 8 knn_clf.fit(X_train_reduction, y_train)
 9 
10 knn_clf.score(X_test_reduction, y_test)      #0.98稍微丢失了一点精度,但速度快了很多,还是值得的

数据降到二维,便于可视化

1 pca = PCA(n_components=2)
2 pca.fit(X_train)
3 X_reduction = pca.transform(X)        #(1797, 2)
4 
5 for i in range(10):
6     plt.scatter(X_reduction[y==i, 0], X_reduction[y==i, 1], alpha=0.8)
7 plt.show()

原文地址:https://www.cnblogs.com/cxq1126/p/13523351.html