阳光日志 2017-12-18
摘要: 本文着重探索PCA、NMF、KNN这三种算法在实战中的表现。
我们用一些工具对数据进行降维,看看结果会怎样,PCA(主成分分析—对高维数据降维)会解决这个问题。NMF(非负矩阵分析—对高维数据降维,并且对事物的局部特性有很好的解释)在分解图像时经常会发现有用的“部分”来表达整体,并且在MNIST数据集或人脸识别数据集中产生有趣的结果。本文着重探索这三种算法如何处理这些数据集。
In [1]:
import numpy as npimport matplotlib.pyplot as plt%matplotlib inline plt.rcParams['image.cmap'] = 'gray'def read_vectors(*filenames): data = np.vstack( tuple(np.fromfile(filename, dtype=np.uint8).reshape(-1,401) for filename in filenames)) return data[:,1:], data[:,0]X_train, y_train = read_vectors(*[ "../input/snake-eyes/snakeeyes_{:02d}.dat".format(nn) for nn in range(2)])X_test, y_test = read_vectors("../input/snake-eyes/snakeeyes_test.dat")
本文算法是基于 https//www.kaggle.com/chientien/mnist-pca-nmf-lda-t-sne,其对MNIST数据集做了完整的分析。
首先,让我们看看前几个主成分“解释”数据的能力。
In [2]:
from sklearn.decomposition import PCApca = PCA(random_state=0, whiten=True) pca.fit(X_train);
In [3]:
exp_var_cum = np.cumsum(pca.explained_variance_ratio_) plt.plot(range(exp_var_cum.size), exp_var_cum) plt.grid()
In [4]:
plt.plot(range(exp_var_cum.size), exp_var_cum, '-+') plt.grid() plt.xlim(15,105) plt.ylim(0.6,0.95);
如上图所示,结果与MNIST没有太多区别,我们可以认为,PCA解释不了这些图像数据集,因此二者都不能对例子中前10个部分拥有100%的解释。这实际上是告诉我们这个问题是具有非凸性的,因为这些数据确实非常适合约束的流形,原因是大部分的变化都是来自骰子上的3-DOF变换。然而,PCA似乎并不能解释这些数据,这是因为这些各种形式的“细条”的流形遍布各处,而PCA仅能找到的一个方向。
In [5]:
plt.figure(figsize=(20,10))for k in range(40): plt.subplot(4,10,k+1) plt.imshow(pca.components_[k].reshape(20,20)) plt.axis('off')
如上所示,看起来我们得到了几组正交基函数,类似于傅里叶变换或球面傅里叶变换。在比较好的震动之后,数据集有了克拉尼图案。
值得注意的是,尽管从上面的曲线中很难看出和MNIST有什么区别,但如果我们观察一下MNIST上现在的主要部分,它们看起来仍然像某些数字的组合。第一个MNIST的主要部分的外观如下图所示。
从外形轮廓上,可以很容易看到数字“0”或“9”,在这种情况下,我们得到了一些很基础的结论。也许我们得到了第一个提示,即MNIST和Snake Eyes实际上有很大的区别。
让我们把PCA分割成90个部分(达到90%的“可解释性”),然后尝试一些NN(最近的邻居)。
In [6]:
pca = PCA(n_components=90, random_state=0, whiten=True) pca.fit(X_train) X_train_pca = pca.transform(X_train) X_test_pca = pca.transform(X_test)
但是,首先我想知道的是:PCA重建图像是什么样子的?
In [7]:
X_reconstructed_pca = pca.inverse_transform(X_test_pca)plt.figure(figsize=(20,10))for k in range(20): plt.subplot(4, 10, k*2 + 1) plt.imshow(X_test[k].reshape(20,20)) plt.axis('off') plt.subplot(4, 10, k*2 + 2) plt.imshow(X_reconstructed_pca[k].reshape(20,20)) plt.axis('off')
外形可能会很丑,但是我们可以看到所有的面。我们有想要的:用较少的变量(只有原始特征向量大小的25%)来识别图片。在这之前,我分割成了40个部分,不过表面上看起来很模糊。在以后的研究中,你可以尝试使用这个参数,本文中,我们的重点是做好的图像,并且分割成90个部分更好一些。
In [8]:
KNN_PCA_TRAIN_SIZE = 200000KNN_PCA_TEST_SIZE = 200from sklearn.neighbors import KNeighborsClassifiertemp = []for i in [1, 5]: knn_pca = KNeighborsClassifier(n_neighbors=i, n_jobs=8) knn_pca.fit(X_train_pca[:KNN_PCA_TRAIN_SIZE], y_train[:KNN_PCA_TRAIN_SIZE]) train_score_pca = knn_pca.score(X_train_pca[:KNN_PCA_TEST_SIZE], y_train[:KNN_PCA_TEST_SIZE]) test_score_pca = knn_pca.score(X_test_pca[:KNN_PCA_TEST_SIZE], y_test[:KNN_PCA_TEST_SIZE]) li = [i,train_score_pca,test_score_pca] temp.append(li)temp
Out [8]:
[[1, 1.0, 0.5], [5, 0.56499999999999995, 0.44500000000000001]]
这个算法限制了所使用数据的数量,因为NN的推论过程需要运行很长时间。你可能想利用PCA的变换矢量来代替原始矢量,这其中唯一的原因就是:节省计算时间。实际上,使用PCA可能不是一个最好主意,但是别无他法。当看见有人使用PCA直接应用于图像分类时,可以肯定的是他们大多是别无选择并且预算有限,但又非常迫切的希望在数据上运行分类模型,然后看到自己被迫做的在很多情况下都不利于分类的简化工作。
有时候在计算PCA时丢弃的信息恰好是有可能是对数据分类有用的信息,还有数据压缩即最小化重建误差。这就是LDA要做的,找到对分类真正重要的方向,而不是重建。这是两个完全不同的问题:representation和分类。事实上,人们可能想知道的是机器学习的终极目标并不是能否找到一个好的分类算法。
现在NN测试结束了,尽管没有使用所有的数据,但我们仍然得到一个很有趣的结果:分类达到了50%的准确性。其性能比之前的LDA要好很多,它显示了最近邻居的优点,以及灵活性比LDA更好。这里更明显的是,在MNIST上LDA的表现也不是太差。不管怎么说,NN运行的速度还是很慢。
如果PCA不是一个特别好的算法,我们可以使用更加非线性的算法,和更适合于图像分析的算法,如非负矩阵分解,据研究人员声称它甚至与深度学习有关,这令人感到兴奋。
训练NMF的代码如下:
In [9]:
NMF_TRAIN_SIZE = 100000 from sklearn.decomposition import NMF nmf = NMF(n_components=90, random_state=0) nmf.fit(X_train[:NMF_TRAIN_SIZE])
Out [9]:
NMF(alpha=0.0, beta_loss='frobenius', init=None, l1_ratio=0.0, max_iter=200,
n_components=90, random_state=0, shuffle=False, solver='cd', tol=0.0001,
verbose=0)
我必须强调的是,NMF训练耗时。所以我们再次减少样本数量,这只是就它是如何运行的做了一个实验。很难说整个数据集的表现(效率和精确度)是什么样的,剩下的问题就交由GPU处理了。
再次检查这些成分的外观,NMF更倾向于找到图片的“部分”,这是非负性的结果。如果你想把你输入的图像分解成比从PCA得到的更“局部”的东西,那么非负性是个非常好的策略。
我们的数据集显然有“部分”:我们正模拟这些骰子,实际上是两个可以独立移动和旋转的物理部件,或许NMF可以发现这一点。我们的基本原则应该假设你在任何地方似乎都需要表示出一个骰子的,然后我们就可以用一个表示出两个,因为它是线性合成的。
In [10]:
plt.figure(figsize=(20,10))
for k in range(40): plt.subplot(4, 10, k + 1) plt.imshow(nmf.components_[k].reshape(20,20)) plt.axis('off')
这是不是很有意思?它确实实现了一些效果。如上图,如果使用较少的成分,那么很明显就可以看到一些骰子大小的大斑点,建议你可以尝试一下。使用更多的成分,这些微小的斑点就会四处移动,并且旋转的骰子是以某种形式重建的……它看起来像单个骰子的某些部分,正如前面假设的,两个骰子或整个图像的大小没有任何变大。
这些斑点很小、难以辨认,甚至可以说更像是一个个的像素。在这里,或许NMF所做的不是寻找一个低分辨率的表示,而是比PCA更有条理的运行。
重建图像过程如下:
In [11]:
X_train_nmf = nmf.transform(X_train) X_test_nmf = nmf.transform(X_test) X_reconstructed = nmf.inverse_transform(X_test_nmf)
In [12]:
plt.figure(figsize=(20,10))for k in range(20): plt.subplot(4, 10, k*2 + 1) plt.imshow(X_test[k].reshape(20,20)) plt.axis('off') plt.subplot(4, 10, k*2 + 2) plt.imshow(X_reconstructed[k].reshape(20,20)) plt.axis('off')
如上图所示,与PCA提供的重建相反,图片的背景是黑色的,而骰子的重构图模糊的方式也不同。让我们再次在相同的NN模型中实验下,看看结果有什么不同:重建图像有59%的准确性,比PCA性能更好。
In [13]:
plt.figure(figsize=(20,10))for k in range(20): plt.subplot(4, 10, k*2 + 1) plt.imshow(X_test[k].reshape(20,20)) plt.axis('off') plt.subplot(4, 10, k*2 + 2) plt.imshow(X_reconstructed[k].reshape(20,20)) plt.axis('off')
Out [13]:
[[1, 1.0, 0.57499999999999996], [5, 0.70999999999999996, 0.59999999999999998]]
以上为译文。
文章原标题《Trying out some PCA, NMF and KNN》,译者:Mags,审校:袁虎。