【笔记】求数据的对应主成分PCA(第一主成分)

求数据的第一主成分

(在notebook中)

将包加载好,再创建出一个虚拟的测试用例,生成的X有两个特征,特征一为0到100之间随机分布,共一百个样本,对于特征二,其和特征一有一个基本的线性关系(为什么要有一个基本的线性关系?是因为含有一个基本的线性关系,这样对数据降维的效果会更加的明显)

  import numpy as np
  import matplotlib.pyplot as plt
  X = np.empty((100,2))
  X[:,0] = np.random.uniform(0. ,100. , size=100)
  X[:,1] = 0.75 * X[:,0] + 3. + np.random.normal(0. ,10. ,size=100)

绘制一下生成的样例

  plt.scatter(X[:,0],X[:,1])

图像如下

具体操作

那么我们就开始进行数据的降维的具体操作

首先我们先进行demean的操作,我们设置一个函数为demean,传来一个X(矩阵),返回均值归0以后所得到的矩阵,具体计算可以使用X减去X的均值即可,需要注意的是,X代表一个矩阵,每一行代表一个样本,那么操作实际上就是每一个样本中的每一个特征都要减去这个特征所对应的均值,所以X减去的应该是个向量(每一个特征所对应的均值,也就是在行方向上去一个均值),其就是每一列的均值,这样就可以得到均值归0以后的样本了

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

那么我们测试一下这个函数

  X_demean = demean(X)

然后绘制一下这个图

  plt.scatter(X_demean[:,0],X_demean[:,1])

图像如下

可以看出来整体分布是个之前一样的,不过从坐标轴可以看出来,(0,0)大概在中心位置,现在对于这些数据来说,在两个方向上的均值都为0

那么我们可以验证一下是不是这样的

使用np.mean(X_demean[:,0])看一下对于第0维度来说均值是多少,结果如下

使用np.mean(X_demean[:,1])看一下对于第1维度来说均值是多少,结果如下

我们可以看出来,这两个值都是很小的,十分接近0,可以说这两个均值是为0的

使用梯度上升法来求解我们数据的主成分

那么做完均值归零以后,我们就要开始使用梯度上升法来求解我们数据的主成分

求解过程和梯度下降法非常的像

首先我们先设置一下求解目标函数的函数f,传上w和x,只要将X点乘上w,再进行平方和以后除以样本数即可

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

我们将求解目标函数对应的梯度的方法设为df_mean(其实应该是叫df_math,打快了没注意),其就是计算的公式的实现,即x的转置点乘上x点乘w乘2再除以样本数

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

为了验证我们的这个是正确的,使用这个df_debug这个函数,和线性下降法一样,使两个点之间连成的直线不断的靠近应得的直线,使其斜率相当,注意的是,这里的epsilon取值比较小,是因为在PCA的梯度上升法中,w是一个方向向量,其模为1,所以w的每一个维度其实都很小,那么为了适应,相应的epsilon也要小一些

  def df_debug(w,X,epsilon=0.0001):
      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

那么下面就开始梯度上升法的过程

每一次循环中先计算一下梯度,然后记录一下上一次的w是多少,之后计算一下使用梯度上升法后得到的新的w,之后就对新的w和旧的w进行计算,看一下两次对应的增加的值有没有超过给定的值,还没有超过就继续循环,否则的话,我们就直接break回去,返回最终的w

需要注意的是,w本来是个方向向量,其模应该为1,从之前推导的公式的条件也可以看出来,需要w的模为1,但是在计算过程中,很可能会让w的模不为1,虽然也可以使用,但是会使得整个计算的过程很不顺畅,且还要将eta的值变得特得特别小,这样会导致程序的效率非常的低

所以每次我们需要对w进行一下将其模变为1的操作,这里使用direction函数将其化成一个只表示方向的单位向量,只要返回w除以w对应的模(求向量的模可以使用np.linalg.norm(w))就可以,这样我们在使用的时候首先就要将initial_w转换成单位向量,在每求到一次新的w之后相应的也调用一下这个函数来将模变成1,这样就完成了使用梯度上升法求解PCA的代码

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

  def gradient_ascent(df,X,initial_w,eta,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

我们想要调用这个函数,还需要注意,我们调用的时候是需要初始化一个w的值,但是这个初始化的值是不可以为0向量的,因为对于这个目标函数来说,w等于0本身就是一个极值点,只不过这个极值点是最小值点,其梯度值也为0,所以带入0是不可以的,因此我们开始的时候需要随机化一个向量,在向量中对应的随机的元素个数就是X这个矩阵的列数,也就是样本的特征数

  initial_w = np.random.random(X.shape[1])
  initial_w

结果如下

我们设置eta为0.001

  eta=0.001

还有一个注意点,对于PCA问题,不能使用StandardScaler来标准化数据,这是为什么呢?因为对于PCA问题来说,本身就是求一个轴,使样本映射以后的方差最大,但是一旦对样本进行标准化了,将样本的方差打散以后会使样本的方差为1,这就使得方差的最大值不存在,就会求不出来PCA真正想要得到的结果了

我们先使用df_debug来求一下对应的轴

  gradient_ascent(df_debug,X_demean,initial_w,eta)

结果如下

然后可以使用df_mean来求一下

  gradient_ascent(df_mean,X_demean,initial_w,eta)

结果如下

可以看出来结果是一样的,这就说明了我们的结果是正确的

然后我们可视化一下,看一下这个轴对应的方向是怎么个方向,先将原始的数据绘制出来,然后绘制轴对应的方向,方法是对于x和y,首先绘制(0,0)这个点,然后绘制一下w这个点,即w[0]和w[1]对应的点,但是由于w很小,正常显示可能看不清,所以对其乘上一个系数,最后是其颜色为红色

  w = gradient_ascent(df_mean,X_demean,initial_w,eta)

  plt.scatter(X_demean[:,0],X_demean[:,1])
  plt.plot([0,w[0]*30],[0,w[1]*30],color='r')

图像如下

相应的这个轴就是一个主成分,在这里使我们所求出来的第一个主成分,因此我们也称其为第一主成分

极端情况

我们使用一下极端的结果来试一下,还是使用原来的虚假样例,但是不添加噪音

  X2 = np.empty((100,2))
  X2[:,0] = np.random.uniform(0. ,100. , size=100)
  X2[:,1] = 0.75 * X2[:,0] + 3.

绘制出图像

  plt.scatter(X2[:,0],X2[:,1])

图像如下

可以发现其完全在一条直线上,不过依然是分布在一个二维的平面上

然后我们开始求解问题,首先进行demean操作,然后调用gradient_ascent函数进行计算并存在w2中

  X2_demean = demean(X2)
  w2 = gradient_ascent(df_mean,X2_demean,initial_w,eta)

绘制图像

  plt.scatter(X2_demean[:,0],X2_demean[:,1])
  plt.plot([0,w2[0]*30],[0,w2[1]*30],color='r')

图像如下

这个轴和这些点是重合的,且我们可以得出gradient_ascent(df_mean,X2_demean,initial_w,eta)为0趋近于0.8和趋近于0.6组成的,对应的我们的系数0.75

经过这两个方面可知是算法正确的

posted @ 2021-01-20 00:10  DbWong_0918  阅读(1494)  评论(0编辑  收藏  举报