目录
1.线性判别分析 (LDA) 降维算法的Python实现
线性判别分析(Linear Discriminant Analysis,LDA)是一种常用的降维技术,特别适用于有监督的分类问题。它通过投影到较低维度空间,最大化类间方差并最小化类内方差,从而实现数据的线性分离。LDA 不仅降低了数据的维度,还保留了用于区分类别的信息,使其在分类问题中具有良好的表现。
2.LDA算法的基本思想
LDA旨在通过寻找一个将数据投影到的线性子空间,使得投影后的类间方差最大化,同时类内方差最小化。这样可以保证不同类别的样本在投影后尽可能分离,且同一类别的样本尽可能聚集。
2.1类间方差矩阵 S B S_B SB
类间方差矩阵 S B S_B SB 用于衡量不同类别的均值之间的散布程度。公式为:
S B = ∑ i = 1 k N i ( μ i − μ ) ( μ i − μ ) T S_B = \sum_{i=1}^{k} N_i (\mu_i - \mu)(\mu_i - \mu)^T SB=i=1∑kNi(μi−μ)(μi−μ)T
其中:
- N i N_i Ni 表示第 i i i 类的样本数
- μ i \mu_i μi 是第 i i i 类的均值向量
- μ \mu μ 是所有样本的均值向量
- k k k 是类别的数量
2.2类内方差矩阵 S W S_W SW
类内方差矩阵 S W S_W SW 用于衡量每个类别内部的样本散布程度。公式为:
S W = ∑ i = 1 k ∑ x ∈ C i ( x − μ i ) ( x − μ i ) T S_W = \sum_{i=1}^{k} \sum_{x \in C_i} (x - \mu_i)(x - \mu_i)^T SW=i=1∑kx∈Ci∑(x−μi)(x−μi)T
其中:
- x x x 表示每个样本
- C i C_i Ci 表示第 i i i 类的样本集合
2.3优化目标
LDA的目标是找到一个投影矩阵 W W W,使得投影后的样本最大化类间方差和类内方差的比值:
W = argmax ∣ W T S B W ∣ ∣ W T S W W ∣ W = \text{argmax} \frac{|W^T S_B W|}{|W^T S_W W|} W=argmax∣WTSWW∣∣WTSBW∣
通过求解该优化问题,LDA可以找到最优的投影矩阵,将高维数据投影到低维空间。
3.LDA的Python实现
接下来,我们使用面向对象编程(OOP)的思想在Python中实现LDA算法。我们将创建一个 LDA
类,包含训练模型、降维和预测功能。
import numpy as np
class LDA:
def __init__(self, n_components=None):
"""
初始化LDA模型
:param n_components: 降维后的目标维度,如果为None,则降至类别数-1的维度
"""
self.n_components = n_components
self.means_ = None
self.scalings_ = None
self.explained_variance_ratio_ = None
def fit(self, X, y):
"""
训练LDA模型
:param X: 输入数据矩阵,形状为 (n_samples, n_features)
:param y: 标签数组,形状为 (n_samples,)
"""
n_samples, n_features = X.shape
classes = np.unique(y)
n_classes = len(classes)
if self.n_components is None:
self.n_components = n_classes - 1
# 计算总体均值
mean_overall = np.mean(X, axis=0)
# 初始化类内方差矩阵和类间方差矩阵
S_W = np.zeros((n_features, n_features))
S_B = np.zeros((n_features, n_features))
for c in classes:
X_c = X[y == c]
mean_c = np.mean(X_c, axis=0)
S_W += (X_c - mean_c).T @ (X_c - mean_c)
n_c = X_c.shape[0]
mean_diff = (mean_c - mean_overall).reshape(n_features, 1)
S_B += n_c * (mean_diff @ mean_diff.T)
# 求解广义特征值问题
A = np.linalg.inv(S_W) @ S_B
eigvals, eigvecs = np.linalg.eig(A)
# 按照特征值的绝对值大小排序
eigvecs = eigvecs[:, np.argsort(-np.abs(eigvals))]
eigvals = eigvals[np.argsort(-np.abs(eigvals))]
# 选择前n_components个特征向量
self.scalings_ = eigvecs[:, :self.n_components]
self.explained_variance_ratio_ = np.abs(eigvals[:self.n_components]) / np.sum(np.abs(eigvals))
# 保存每个类别的均值
self.means_ = {}
for c in classes:
self.means_[c] = np.mean(X[y == c], axis=0)
def transform(self, X):
"""
将数据投影到LDA子空间
:param X: 输入数据矩阵,形状为 (n_samples, n_features)
:return: 投影后的数据,形状为 (n_samples, n_components)
"""
return X @ self.scalings_
def predict(self, X):
"""
使用LDA模型进行分类预测
:param X: 输入数据矩阵,形状为 (n_samples, n_features)
:return: 预测标签,形状为 (n_samples,)
"""
X_projected = self.transform(X)
preds = []
for x in X_projected:
distances = [np.linalg.norm(x - mean) for mean in self.means_.values()]
preds.append(np.argmin(distances))
return np.array(preds)
def fit_transform(self, X, y):
"""
训练模型并返回投影后的数据
:param X: 输入数据矩阵,形状为 (n_samples, n_features)
:param y: 标签数组,形状为 (n_samples,)
:return: 投影后的数据,形状为 (n_samples, n_components)
"""
self.fit(X, y)
return self.transform(X)
4.代码解析
-
初始化:
__init__
方法初始化了LDA模型,包括目标维度n_components
,类均值means_
,特征向量scalings_
,以及解释方差比explained_variance_ratio_
。
-
训练模型:
fit
方法计算类内方差矩阵 S W S_W SW 和类间方差矩阵 S B S_B SB,并通过求解广义特征值问题找到投影矩阵scalings_
。同时,该方法保存每个类别的均值以便后续分类使用。
-
数据投影:
transform
方法将输入数据投影到LDA子空间,返回降维后的数据。
-
预测分类:
predict
方法将投影后的数据与每个类别的均值进行比较,基于欧氏距离进行分类预测。
-
训练并投影:
fit_transform
方法结合了fit
和transform
,方便一次性完成训练和投影。
5.实际应用场景:手写数字识别
为了展示LDA的实际应用,我们使用手写数字数据集(如MNIST)来实现分类任务。这个数据集包含0-9的手写数字图像,通过LDA降维后,我们可以将其投影到2D或3D空间中进行可视化,并在低维空间中进行分类。
5.1数据准备
首先,我们从数据集中提取样本,并将图像展平为一维向量。
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
# 加载数据集
digits = load_digits()
X, y = digits.data, digits.target
# 数据标准化
scaler = StandardScaler()
X = scaler.fit_transform(X)
# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
5.2使用LDA降维
接下来,我们使用LDA将数据降维至2D,并可视化投影结果。
# 初始化LDA模型并进行训练
lda = LDA(n_components=2)
X_train_lda = lda.fit_transform(X_train, y_train)
# 可视化LDA投影结果
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
for i in range(10):
plt.scatter(X_train_lda[y_train == i, 0], X_train_lda[y_train == i, 1], label=f'Class {i}')
plt.xlabel('LDA Component 1')
plt.ylabel('LDA Component 2')
plt.legend()
plt.title('LDA Projection of Digits Dataset')
plt.show()
5.3分类效果
最后,我们使用LDA进行分类,并评估模型的性能。
# 使用LDA
进行分类预测
y_pred = lda.predict(X_test)
# 计算准确率
from sklearn.metrics import accuracy_score
accuracy = accuracy_score(y_test, y_pred)
print(f'LDA Classification Accuracy: {accuracy:.4f}')
6.总结
通过LDA算法,我们可以在保证分类信息的前提下将高维数据投影到低维空间,从而减少计算复杂度并提高分类效率。本文展示了LDA的数学原理、Python实现以及在手写数字识别中的应用。通过面向对象编程的方式,我们实现了一个LDA类,集成了训练、投影和分类功能,便于在各种分类问题中应用LDA算法。