Với thời đại dữ liệu bùng nổ như ngày nay, dữ liệu ta thu thập được rất lớn. Trong thực tế, các vector đặc trưng (feature vectors) có thể có số chiều rất lớn, tới vài nghìn. Đồng thời, lượng điểm dữ liệu cũng rất lớn. Điều đó sẽ gây khó khăn cho việc lưu trữ và tính toán. Vì vậy, một trong những bước quan trọng trong nhiều bài toán học máy là ta phải giảm chiều dữ liệu (dimentionality reduction).
Giảm chiều dữ liệu còn là phương pháp được sử dụng để giảm vấn đề quá khớp (overfitting),nó có hai hướng là hướng lựa chọn đặc trưng (feature selection) và hướng trích xuất đặc trưng (feature extraction). Hôm nay ta sẽ tìm hiểu về một thuật toán theo hướng trích xuất đặc trưng là Principal Component Analysis (PCA).
Cùng là 1 chú lạc đà, tuy nhiên với các góc nhìn khác nhau (trục thông tin), chúng ta có những cách thu nhận thông tin khác nhau và cho ta những kết luận khác nhau.
PCA là thuật toán tìm một không gian mới (với số chiều nhỏ hơn không gian cũ), các trục tọa độ trong không gian mới được xây dựng sao cho trên mỗi trục, độ biến thiên của dữ liệu trên đó là lớn nhất có thể.
Ví dụ minh họa:
Cho $N$ giá trị $x_1, x_2, ..., x_N$.
$$\bar{x} = \frac{1}{N} \sum_{i=1}^{N}x_i$$Minh hoạ ma trận hiệp phương sai: $$S = \begin{bmatrix}{var(x)} & {cov(x,y)} \\ {cov(y,x)} & {var(y)} \end{bmatrix}$$
Cho một ma trận vuông $A \in R^{n \times n}$, nếu số vô hướng $\lambda$ và vector $x \ne 0 \in R^n$ thoả mãn:
$$Ax = \lambda x$$thì $\lambda$ được gọi là một trị riêng của $A$ và $x$ được gọi là vector riêng tương ứng với trị riêng đó.
B1: giải phương trình đặc trưng để tìm trị riêng: $$det(A - \lambda I) = 0$$
B2: giải hệ phương trình tìm vector riêng $u_i$ tương ứng với trị riêng $\lambda_i$ $$(A - \lambda I)u = 0$$
PCA về cơ bản là một kỹ thuật giảm kích thước đơn giản, biến đổi các cột của bộ dữ liệu thành một tập các đặc trưng mới. Nó thực hiện điều này bằng cách tìm một tập hợp các hướng mới (như trục X và Y) giải thích sự biến đổi tối đa trong dữ liệu, tức là hướng đó ta tìm được maximum của variance.
Tại sao lại là maximum của variance?
Như ta có thể thấy, variance thể hiện độ phân tán của dữ liệu, khi variance lớn thì độ phân tán lớn và ngược lại. Mà thuật toán là muốn lấy tối đa các thông tin, nên nó sẽ lấy theo hướng variance lớn, dữ liệu sẽ có độ biến thiên cao, mang lại nhiều thông tin.
Trục tọa độ hệ thống mới này được gọi là Principal Components (PCs). Các phép chiếu của dữ liệu gốc trên bộ trục tọa độ (PC) mới đóng vai trò là bộ dữ liệu được chuyển đổi mới.
Nhưng tại sao lại tính toán PCs?
Vì thông tin chứa trong một cột dữ liệu tỉ lệ thuận với lượng phương sai của nó. Tương tự, các PCs có lượng phương sai lớn sẽ mang lại nhiều thông tin và từ đó ta có thể chọn các PCs chứa nhiều thông tin nhất.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# Load data
df_wine = pd.read_csv('https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None)
# create train set and test set
X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, stratify=y, random_state=0)
# standardize the features
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.transform(X_test)
# Compute eigenvalue and eigenvector of covarian matrix
cov_mat = np.cov(X_train_std.T)
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)
print('\nEigenvalues \n%s' % eigen_vals)
Trước khi đi tới bước chọn K vector riêng, ta sẽ vẽ một biểu đồ thể hiện tỉ lệ phương sai (variance ratios) của trị riêng. Công thức của nó là: $$\text{variance ratio} = \frac{\lambda_i}{\sum_{i=1}^n \lambda_i}$$
tot = sum(eigen_vals)
var_exp = [(i / tot) for i in
sorted(eigen_vals, reverse=True)]
cum_var_exp = np.cumsum(var_exp)
plt.bar(range(1,14), var_exp, alpha=0.5, align='center',
label='Individual explained variance')
plt.step(range(1,14), cum_var_exp, where='mid',
label='Cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal component index')
plt.legend(loc='best')
plt.tight_layout()
plt.show()
Như ta có thể thấy, chỉ với 2 principal component đầu đã chiếm khoảng gần 60% lượng thông tin.
# Make a list of (eigenvalue, eigenvector) tuples
eigen_pairs = [(np.abs(eigen_vals[i]), eigen_vecs[:, i]) for i in range(len(eigen_vals))]
# Sort the (eigenvalue, eigenvector) tuples from high to low
eigen_pairs.sort(key=lambda k: k[0], reverse=True)
U = np.hstack((eigen_pairs[0][1][:, np.newaxis], eigen_pairs[1][1][:, np.newaxis]))
print('Matrix U:\n', U)
Matrix $U$ chính là ma trận giúp ta chuyển đổi dữ liệu D chiều sang K chiều với K $\leq$ D
x_origin = X_train_std[0].shape[0]
x_new = X_train_std[0].dot(U).shape[0]
print('Từ', x_origin, 'chiều còn lại', x_new, 'chiều')
X_train_pca = X_train_std.dot(U)
print(X_train_std.shape, X_train_pca.shape)
colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']
for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train==l, 0], X_train_pca[y_train==l, 1], c=c, label=l, marker=m)
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()
from matplotlib.colors import ListedColormap
def plot_decision_regions(X, y, classifier, resolution=0.02):
    # setup marker generator and color map
    markers = ('s', 'x', 'o', '^', 'v')
    colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan')
    cmap = ListedColormap(colors[:len(np.unique(y))])
    # plot the decision surface
    x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1
    x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1
    xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), np.arange(x2_min, x2_max, resolution))
    Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
    Z = Z.reshape(xx1.shape)
    plt.contourf(xx1, xx2, Z, alpha=0.4, cmap=cmap)
    plt.xlim(xx1.min(), xx1.max())
    plt.ylim(xx2.min(), xx2.max())
    # plot examples by class
    for idx, cl in enumerate(np.unique(y)):
        plt.scatter(x=X[y == cl, 0], y=X[y == cl, 1], alpha=0.6, color=cmap(idx), edgecolor='black', marker=markers[idx], label=cl)
from sklearn.linear_model import LogisticRegression
from sklearn.decomposition import PCA
# initializing the PCA transformer and
# logistic regression estimator:
pca = PCA(n_components=2)
lr = LogisticRegression(multi_class='ovr', random_state=1, solver='lbfgs')
# dimensionality reduction:
X_train_pca = pca.fit_transform(X_train_std)
X_test_pca = pca.transform(X_test_std)
# fitting the logistic regression model on the reduced dataset:
lr.fit(X_train_pca, y_train)
Phân loại dữ liệu trên tập train chỉ với dữ liệu 2 chiều
plot_decision_regions(X_train_pca, y_train, classifier=lr)
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()
Phân loại dữ liệu trên tập test chỉ với dữ liệu 2 chiều
plot_decision_regions(X_test_pca, y_test, classifier=lr)
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend(loc='lower left')
plt.tight_layout()
plt.show()
Ta có thể thấy chỉ có một vài điểm bị phân loại sai, logistic regression hoạt động khá tốt trên không gian hai chiều này.