哈喽,大家好~
今儿和大家聊一个降维算法模型:线性判别分析
简单来说,降维就是把数据从「高维」压缩到「低维」。简单来说,如果你有一个包含很多信息的数据表,把这张表的列数减少,但仍然保留大部分信息的过程就是降维。比如你有一个10维的图像数据,但画图或分析时只要2-3个维度的特征就够了,我们就用降维把10维信息压缩成2-3维,让数据更简单、易用。
这就是降维!
什么是线性判别分析(LDA)?
LDA是一个用于「有类别标签」的数据的降维方法。它的目标是「尽可能把不同类别的数据分开」,同时「尽量减少同类别数据之间的距离」。这有助于我们在低维空间更清楚地看到每个类别的分布。
用一个例子来讲:
假设你是一个侦探,有一堆侦破案件的线索数据。这些案件分成「两类」:已破案件和未破案件。每个案件可能有很多信息,比如时间、地点、嫌疑人数、目击者人数等(这些就是「维度」)。为了更直观地分析这些案件,你希望把案件信息简化,找出能够区分已破案件和未破案件的核心特征,最好是2个特征,这样画在平面图上你就能看到哪一类比较集中在图的哪一边。
LDA是怎么实现降维的?
LDA会通过以下步骤找到这些核心特征:
第一步:计算组内散布矩阵和组间散布矩阵
组内散布矩阵(Within-class scatter matrix):我们希望同一类别的数据尽量聚在一起。比如,已破案件的数据在降维后的平面图中,点与点之间的距离要尽量小。 组间散布矩阵(Between-class scatter matrix):我们希望不同类别之间尽量分开。也就是说,已破案件和未破案件在图上应该隔得远一点。
第二步:找到最优投影方向 LDA会计算一个方向(投影向量),让数据在这个方向上的投影能最大化地「区分类别」。具体来说,它会找到一个维度,使得组间散布尽量大,组内散布尽量小。
第三步:把数据投影到新空间 一旦找到最佳投影方向,我们就把高维数据映射到这个新方向上,这样每个案件的数据就变成了在低维(比如1维或2维)上的一个点。
再举个例子
假设我们有一批水果数据,其中包含「重量」和「颜色」两个特征,水果分为两类:「苹果」和「橙子」。我们想通过LDA把数据降到1维,只用一个特征来区分苹果和橙子。
LDA会先计算苹果和橙子在「重量」和「颜色」这两个特征上的分布情况,找到一个方向,让苹果和橙子在这个方向上分得最开。最终,你可能会得到一个方向(比如「0.6 * 重量 + 0.4 * 颜色」),然后把所有水果的数据投影到这个方向上,这样每个水果就变成了1维数据。在这个1维数据上,苹果可能集中在左边,橙子集中在右边。
总的来说,LDA是一种降维方法,特别适合处理有类别标签的数据。它的核心思想是找到一个方向,既让不同类别之间的距离最大化,又让同一类别的数据更集中。
用在实际中,可以帮助我们在低维空间里清晰地分辨不同类别的数据分布,便于进一步分析或可视化。
原理和案例
1. 公式推导和思路
假设我们有数据集 ,其中每个数据点 都属于 类中的某一类。LDA的目标是找到一个线性投影方向,使得投影后的类别分布在低维空间上可以最清晰地分离。
类内散布矩阵和类间散布矩阵
1. 类内散布矩阵 *:测量同类别数据的紧密程度。
其中, 是第 类数据集合, 是第 类的均值。
2. 类间散布矩阵 *:测量不同类别均值的分散程度。
其中, 是第 类数据的数量, 是整体均值。
最大化目标
LDA寻找使得类间散布 最大、类内散布 最小的方向。该方向可以通过求解如下广义特征值问题得到:
其中, 是投影方向。我们取对应最大的特征值的特征向量作为投影向量,将数据映射到低维空间。
2. 代码实现
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 生成3类数据,每类1000个样本,每个样本5个特征
np.random.seed(0)
mean1, cov1 = [2, 2, 3, 1, 1], np.diag([1, 0.5, 0.8, 1.2, 0.9]) # 类别1
mean2, cov2 = [5, 6, 1, 4, 2], np.diag([1, 0.5, 0.7, 1.3, 1.0]) # 类别2
mean3, cov3 = [8, 7, 9, 3, 6], np.diag([1, 0.6, 0.8, 1.1, 1.2]) # 类别3
data1 = np.random.multivariate_normal(mean1, cov1, 1000)
data2 = np.random.multivariate_normal(mean2, cov2, 1000)
data3 = np.random.multivariate_normal(mean3, cov3, 1000)
X = np.vstack((data1, data2, data3)) # 将三类数据合并
y = np.array([0]*1000 + [1]*1000 + [2]*1000) # 标签,类别0、1、2
# 计算每类的均值和总体均值
mean_overall = np.mean(X, axis=0)
mean_vectors = [np.mean(X[y==i], axis=0) for i in range(3)]
# 计算类内散布矩阵 SW
SW = np.zeros((5, 5))
for i, mean_vec in enumerate(mean_vectors):
class_scatter = np.cov(X[y==i].T) * (X[y==i].shape[0] - 1)
SW += class_scatter
# 计算类间散布矩阵 SB
SB = np.zeros((5, 5))
for i, mean_vec in enumerate(mean_vectors):
n = X[y==i].shape[0]
mean_diff = (mean_vec - mean_overall).reshape(5, 1)
SB += n * (mean_diff).dot(mean_diff.T)
# 求解广义特征值问题,获取投影方向
eigvals, eigvecs = np.linalg.eig(np.linalg.inv(SW).dot(SB))
# 对特征值进行排序,选择前两个特征向量(降维到2维)
sorted_indices = np.argsort(eigvals)[::-1]
w = eigvecs[:, sorted_indices[:2]] # 取前2个特征向量
# 投影数据
X_lda = X.dot(w)
# 可视化图形展示
plt.figure(figsize=(14, 10))
# 原始数据分布
plt.subplot(2, 3, 1)
plt.title("Original High-dimensional Data Distribution")
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette="Set1")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
# 类内散布矩阵可视化
plt.subplot(2, 3, 2)
plt.title("Class Means and Within-class Scatter")
for i in range(3):
sns.scatterplot(x=X[y==i][:, 0], y=X[y==i][:, 1], label=f"Class {i}")
plt.scatter(*mean_vectors[i][:2], marker='x', s=100, c="black")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
# 类间散布矩阵可视化
plt.subplot(2, 3, 3)
plt.title("Between-class Scatter")
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette="Set1")
plt.scatter(*mean_overall[:2], marker="o", s=150, c="red", label="Overall Mean")
for i, mean_vec in enumerate(mean_vectors):
plt.plot([mean_overall[0], mean_vec[0]], [mean_overall[1], mean_vec[1]], 'k--')
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
# 投影后的数据分布
plt.subplot(2, 3, 4)
plt.title("Data Distribution after LDA Projection (2D)")
sns.scatterplot(x=X_lda[:, 0], y=X_lda[:, 1], hue=y, palette="Set1")
plt.xlabel("LDA Component 1")
plt.ylabel("LDA Component 2")
# 高维数据和低维数据的散点图对比
plt.subplot(2, 3, 5)
plt.title("High-dim Data vs Low-dim Data (Projection Comparison)")
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette="Set1", alpha=0.5)
sns.scatterplot(x=X_lda[:, 0], y=X_lda[:, 1], hue=y, palette="Set1", marker="X", s=70)
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
# LDA如何有效地将高维数据映射到2维
plt.subplot(2, 3, 6)
plt.title("Projection Path Analysis")
sns.scatterplot(x=X[:, 0], y=X[:, 1], hue=y, palette="Set1")
sns.scatterplot(x=X_lda[:, 0], y=X_lda[:, 1], hue=y, palette="Set1", marker="X", s=70)
for i in range(3):
plt.annotate(f"Class {i}", (X_lda[y==i][:, 0].mean(), X_lda[y==i][:, 1].mean()))
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.tight_layout()
plt.show()
1. 数据生成
我们生成了三类数据,每类1000个样本,每个样本有5个特征。这些数据遵循正态分布,并且每类的协方差矩阵是不同的,以保证数据之间的差异。
2. 类内散布矩阵 (SW) 和 类间散布矩阵 (SB)
类内散布矩阵:计算每个类别内部的散布程度,也就是数据点在每个类别内的紧密度。
类间散布矩阵:计算每个类别的均值和整体均值之间的差异程度,衡量类别间的分离度。
3. LDA计算投影向量
使用广义特征值问题 求解得到投影向量。然后我们选择前两个最大特征值对应的特征向量作为降维到二维的方向。
原始数据分布:展示了数据的原始分布(使用前两个特征展示)。 类内散布矩阵:通过散点图展示每个类别的分布情况以及均值位置。 类间散布矩阵:展示了类别之间的分布差异,帮助理解类间散布矩阵的作用。 投影后的数据分布:展示了降维后的数据在二维空间中的分布,类别之间更加分开。 高维数据与低维数据对比:通过对比高维数据和投影后的低维数据,展示LDA如何帮助数据分类。 投影路径分析:通过标注每个类别的中心,展示LDA在二维空间中如何有效地区分不同类别。
整个的内容,展示了LDA如何从高维(5维)空间有效地将数据降维到二维,并且通过各种图形直观地展示了LDA的工作原理。各类数据在低维空间中的分离效果清晰可见,且与原始数据的分布相比,类别间的可分性得到了显著提高。