降维、特征提取与流形学习--非负矩阵分解(NMF)

非负矩阵分解(NMF)是一种无监督学习算法,目的在于提取有用的特征(可以识别出组合成数据的原始分量),也可以用于降维,通常不用于对数据进行重建或者编码。

  • NMF将每个数据点写成一些分量的加权求和(与PCA相同),并且分量和系数都大于0,
  • 只能适用于每个特征都是非负的数据(正负号实际上是任意的)。

1、将NMF应用于模拟数据

应用NMF时,我们必须保证数据是正的

📣如图

  • 两个分量的NMF:分量指向边界,所有的数据点都可以写成这两个分量的正数组合。

  • 一个分量的NMF:分量指向平均值,指向这里可以对数据做出最好的解释。

在NMF中,不存在“第一非负分量”,所有分量地位平等,减少分量个数会删除一些方向。NMF使用了随机初始化,根据随机种子的不同可能会产生不同的结果。

2、将NMF应用于人脸图像

NMF的主要参数(n_components参数):想要提取的分量个数。这个数字通常要小于输入特征的个数(否则将每个像素作为单独的分量就可以解释数据)。

(1)先观察一下运用NMF找到的15个分量长什么样

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

  people = fetch_lfw_people(min_faces_per_person=40,resize=0.7)
  image_shape = people.images[0].shape

  #每个人最多有50张照片,防止数据偏斜
  mask = np.zeros(people.target.shape,dtype=bool)
  for target in np.unique(people.target):
      mask[np.where(people.target==target)[0][:50]]=1

  X_people = people.data[mask]
  y_people = people.target[mask]


  X_train, X_test, y_train, y_test = train_test_split(X_people,y_people,stratify=y_people,random_state=42)

  #画出nmf模型训练得到的各个分量(这里指定15个),每个分量都是一张有点人形的图片(因为每个分量保留了所有的原始特征)。
  #所有的数据点都可以写成这些分量的加权求和

  from sklearn.decomposition import NMF
  nmf = NMF(n_components=15,random_state=0)
  nmf.fit(X_train)

  X_train_nmf = nmf.transform(X_train)
  X_test_nmf = nmf.transform(X_test)

  fix,axes = plt.subplots(3,5,figsize=(15,12),subplot_kw={'xticks':(),'yticks':()})

  for i ,(component,ax) in enumerate(zip(nmf.components_,axes.ravel())):
      ax.imshow(component.reshape(image_shape))
      ax.set_title("{}.component".format(i))

(2)、按照某个分量,重建数据点

  #将数据样本点按照第10个分量排序,绘制数据点中前10张图片

  compn = 10

  inds = np.argsort(X_train_nmf[:,compn])[::-1] #按照第三个分量排序

  fig,axes = plt.subplots(2,5,figsize=(15,8),subplot_kw={'xticks':(),'yticks':()})

  for i ,(ind,ax) in enumerate(zip(inds,axes.ravel())):
      ax.imshow(X_train[ind].reshape(image_shape))

  • 可以看出在所有数据点中,分量10排名前10的数据点长什么样(它们的具有分量10提取的特点,脸有点歪)
  • 每个分量提取了数据的不同模式,将这些分量叠加(加权求和)就能重构出训练集中的每一张图像。

3、应用于具有叠加结构的数据(信号源数据)

(1)先了解一下数据集

  S = mglearn.datasets.make_signals()
  plt.figure(figsize=(10,2))
  plt.plot(S,'-')
  plt.xlabel("Time")
  plt.ylabel("Signal")

  print(S.shape)
  print(S)

  #输出

  (2000, 3)
  [[2.65408203 2.48908887 1.07757433]
   [2.94981947 3.45507031 0.79929765]
   [2.97649958 3.65235694 0.73473133]
   ...
   [2.22337048 1.33481395 4.31421863]
   [2.36722058 1.56522921 4.53698235]
   [1.77945297 1.62362822 0.47660599]]
  • 可以看出该数据具有2000条,每条有对应三个信号源的数据

(2)将混合信号分解为原始分量

  • 我们假设有100台测量装置来观测混合信号,得到了2000条具有100维特征的信号数据X

    #将数据混合成100维的状态
    A = np.random.RandomState(0).uniform(size=(100,3))
    X = np.dot(S,A.T)
    print(X)
    print(X.shape)
    
  • 应用NMF还原这个混合信号

    #用nmf还原这三个信号被混合成100维的信号X
    
    nmf = NMF(n_components=3,random_state=42)
    S_nmf =nmf.fit_transform(X)
    
    #用于对比的pca
    
    pca = PCA(n_components=3,random_state=42)
    S_pca = pca.fit_transform(X)  #S_pca就是H
    
    #画图
    
    models = [X,S,S_nmf,S_pca]
    names = ["Obsevations(first measurements)",
            "Ture sourses",
            "NMF recovered signals",
            "PCA recovered signals"]
    
    fig, axes = plt.subplots(4,figsize=(10,5),gridspec_kw={'hspace':.5},subplot_kw={'xticks':(),'yticks':()})
    
    for model,name,ax in zip(models,names,axes):
        ax.set_title(name)
        ax.plot(model,'-')
    

📣

  • NMF在发现原始信号源时得到了不错的结果,而PCA失败了(PCA不适合这种叠加数据结构)
  • NMF生成的分量是没有顺序的,如果分量顺序和原始信号完全相同(线的颜色)只是偶然。

4、参考文献

《Pyhon机器学习基础教程》P120-P126

posted @ 2022-04-29 15:19  朝南烟  阅读(1019)  评论(0编辑  收藏  举报
body { color: #000; background-color: #e6e6e6; font-family: "Helvetica Neue",Helvetica,Verdana,Arial,sans-serif; font-size: 12px; min-height: 101%; background: url(https://images.cnblogs.com/cnblogs_com/caolanying/1841633/o_2009041…ly1geq8oc9owbj21hc0u0th5.jpg) fixed; } #home { margin: 0 auto; opacity: 0.8; width: 65%; min-width: 1080px; background-color: #fff; padding: 30px; margin-top: 50px; margin-bottom: 50px; box-shadow: 0 2px 6px rgba(100, 100, 100, 0.3); }