在本次实验中,将实现主成分分析方法,并使用它获得人脸图像的低维表示。
本次实验需要用到的数据集包括:
评分标准如下:
#%%
import os
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sb
from scipy.io import loadmat
import scipy
%matplotlib inline
在本部分实验中,将实现主成分分析算法。 算法步骤如下:
Step 1: 对所有样本进行标准化使得样本的均值为0,标准差为1
Step 2: 计算样本的协方差矩阵: X X T / N \mathbf{X}\mathbf{X}^T/N XXT/N,其中 N N N为样本个数
Step 3: 对协方差矩阵 X X T / N \mathbf{X}\mathbf{X}^T/N XXT/N 做奇异值分解(或特征值分解)
Step 4: 取最大的 d d d 个特征值所对应的特征向量 w 1 , ⋯ , w d \mathbf{w}_1,\cdots,\mathbf{w}_{d} w1,⋯,wd
输出: 投影矩阵 W = [ w 1 , ⋯ , w d ] \mathbf{W}=[\mathbf{w}_1,\cdots,\mathbf{w}_{d}] W=[w1,⋯,wd]
要点 1:
在下方cell中,请实现’‘数据标准化’'的代码。
# ====================== 在这里填入代码 =======================
def Normalize(X):
"""
输入
----------
X : 尺寸为 (D, N)的矩阵,第i列为第i个样本,D为样本的维数,N为样本的个数。
输出
-------
X_norm : 尺寸为 (D, N)的矩阵。
"""
X_norm = X - X.mean(axis=1, keepdims=True)
X_norm = X_norm / X.std(axis=1, keepdims=True)
return X_norm
# =============================================================
要点 2:
在下方cell中,请实现’‘主成分分析’'的代码。
# ====================== 在这里填入代码 =======================
def PCA(X,d):
"""
输入
----------
X : 尺寸为 (D, N)的矩阵,第i列为第i个样本,D为样本的维数,N为样本的个数。
d: 期望的维数
输出
-------
W : 尺寸为 (D, d)的投影矩阵,第i列为协方差矩阵的第i个特征向量。
"""
# Step 1: 标准化(调用要点1中的代码)
X_norm = Normalize(X)
# Step 2: 计算协方差矩阵
X_norm = X_norm @ X_norm.T
# Step 3: 奇异值分解(查询numpy中进行奇异值分解的函数)
# vals, vects = np.linalg.eig(np.mat(X_norm))
U, sigma, VT = np.linalg.svd(X_norm)
# Step 4: 取最大的个特征值所对应的特征向量并组成矩阵W
sorted = np.argsort(-sigma)
topn_index = sorted[:d]
topn_vects = U[:, topn_index]
W = topn_vects
return W
# =============================================================
如果完成了上述函数 pca,以下代码可用于测试。如果结果为[-0.707107, -0.707107],则计算通过。
# Load the dataset into the variable X
# data = loadmat(os.path.join('Data', 'ex4data1.mat'))
data = loadmat(os.path.join( 'ex4data1.mat'))
X = data['X']
X=X.T
# Run PCA
W= PCA(X,2)
# print(W)
print('第一个特征向量: W[:, 0] = [{:.6f}, {:.6f}]'.format(W[0, 0], W[1, 0]))
在本部分实验中,将已实现的PCA算法应用于数据集1,该数据集中的样本维数为2,因此降维结束后,可通过可视化观察降维前后的结果。
# 可视化原始数据集
plt.plot(X[0, :], X[1, :], 'bo', ms=10, mec='k', mew=1)
plt.axis([0.5, 6.5, 2, 8])
plt.gca().set_aspect('equal')
plt.grid(False)
X_norm=Normalize(X)
plt.plot(X_norm[0, :], X_norm[1, :], 'bo', ms=10, mec='k', mew=1)
plt.axis([-3, 2.75, -3, 2.75])
plt.gca().set_aspect('equal')
plt.grid(False)
利用上述PCA代码可将仿真数据降至1维。
要点 3:
在下方cell中,请实现’‘ProjectData’'的代码,输出降维后的数据。
# ====================== 在这里填入代码 =======================
def ProjectData(X, W):
"""
输入
----------
X : 尺寸为 (D, N)的矩阵,第i列为第i个样本,D为样本的维数,N为样本的个数。
W : 尺寸为 (D, d)的投影矩阵,第i列为协方差矩阵的第i个特征向量。
输出
-------
Z : 尺寸为 (d, N)的矩阵,第i列为第i个降维后的数据。
"""
# Step 1: 标准化(调用要点1中的代码)
X_norm = Normalize(X)
# Step 2: 计算降维后的数据
Z = W.T @ X_norm
return Z
# =============================================================
如果完成了上述函数 ProjectData,以下代码可用于测试。如果结果为 1.496313,则计算通过。
W = PCA(X,1)
Z = ProjectData(X, W)
print('第一个样本降维后的数据: {:.6f}'.format(Z[0, 0]))
利用降维后的数据矩阵 Z \mathbf{Z} Z,可近似重构原始数据。
要点 4:
在下方cell中,请实现’‘RecoverData’'的代码,输出重构后的数据。
# ====================== 在这里填入代码 =======================
def RecoverData(Z, W):
"""
输入
----------
Z : 尺寸为 (d, N)的矩阵,第i列为第i个降维后的数据。
W : 尺寸为 (D, d)的投影矩阵,第i列为协方差矩阵的第i个特征向量。
输出
-------
X_rec : 尺寸为 (D, N)的矩阵,第i列为第i个重构后的数据。
"""
X_rec = W @ Z
return X_rec
# =============================================================
如果完成了上述函数 RecoverData,以下代码可用于测试。如果结果为[-1.058053, -1.058053],则计算通过。
X_rec = RecoverData(Z, W)
# print('第一个重构后的数据: {:.6f}'.format(X_rec[:, 0]))
print('第一个重构后的数据: X_rec[:, 0] = [{:.6f}, {:.6f}]'.format(X_rec[0, 0], X_rec[1, 0]))
#可视化重构前后的结果
X_norm=Normalize(X)
# Plot the normalized dataset (returned from featureNormalize)
fig, ax = plt.subplots(figsize=(5, 5))
ax.plot(X_norm[0, :], X_norm[1, :], 'bo', ms=8, mec='b', mew=0.5)
ax.set_aspect('equal')
ax.grid(False)
plt.axis([-3, 2.75, -3, 2.75])
# Draw lines connecting the projected points to the original points
ax.plot(X_rec[0, :], X_rec[1, :], 'ro', mec='r', mew=2, mfc='none')
for xnorm, xrec in zip(X_norm.T, X_rec.T):
ax.plot([xnorm[0], xrec[0]], [xnorm[1], xrec[1]], '--k', lw=1)
在本部分实验中,将已实现的PCA算法应用于数据集2,为LFW人脸数据集合的子集。
# Load Face dataset
data = loadmat(os.path.join( 'ex4data2.mat'))
X = data['X']
X=X.T
X = X[:,:100]
#定义显示人脸图像函数
def displayImage(X):
"""
输入
----------
X : 尺寸为 (D, N)的矩阵,第i列为第i个样本,D为样本的维数,N为样本的个数。
"""
# Compute number of items to display
N=X.shape[1]
display_rows = int(np.floor(np.sqrt(N)))
display_cols = int(np.ceil(N / display_rows))
figsize=(N,N)
fig, ax_array = plt.subplots(display_rows, display_cols)
ax_array = [ax_array] if N == 1 else ax_array.ravel()
for i, ax in enumerate(ax_array):
ax.imshow(X[:,i].reshape(32, 32, order='F'), cmap='gray')
ax.axis('off')
要点 5:
利用PCA代码将人脸数据降至64维,并显示前16个特征向量。
# ====================== 在这里填入代码 =======================
#Step 1: 利用PCA对原始数据降维
W = PCA(X,64)
#Step 2: 调用前文中的displayImage函数显示前16个特征向量所对应的图像
displayImage(W[:,:16])
# =============================================================
要点 6:
利用降维后的数据重构原始人脸数据,并显示前16个重构前后的人脸数据。
# ====================== 在这里填入代码 =======================
#Step 1: 利用RecoverData函数得到重构数据
Z = ProjectData(X, W)
X_rec = RecoverData(Z, W)
#Step 2: 利用displayImage函数显示前16个原始图像
displayImage(X[:,:16])
plt.gcf().suptitle('Original faces')
#Step 3: 利用displayImage函数显示前16个重构图像
displayImage(X_rec[:,:16])
plt.gcf().suptitle('Recovered faces')
# =============================================================