参考博文:https://www.cnblogs.com/pinard/p/6244265.html
和 http://www.cnblogs.com/jerrylead/archive/2011/04/21/2024384.html
首先感谢两位博主。研究了一天,其他都好说,在类内散列矩阵卡了不少时间。 先看的第一篇博文,把Sw理解成了协方差矩阵,结果在写算法的时候,用np.cov()计算,测试数据,总是不对。后来看到第二篇博文,才恍然大悟,这个Sw是协方差矩阵的分子部分,叫做散列矩阵。至于推导,建议看第一篇博文,即把目标函数转换成广义瑞利商函数,利用瑞利商函数的性质,直接得出目标基向量就是目标矩阵((Sw的逆矩阵).dot(Sb))的特征向量。第二篇文章不明说瑞利商矩阵,而是对同样的表达式用拉格朗日乘子法求极值的方法,得到了相同的结果。推导过程稍显复杂。 把这个过程包装瑞利商函数去理解LDA,要简单许多!
import numpy as np
import pandas as pd
class LDA():
'''
LDA(线性判别分析)降维算法
'''
def lda(self, dataSet, k):
'''
对于包含类别标签的多类别数据集(n+1)*m, 使降维至(k+1)*m数据集
:param dataSet: 包含类别标签的多类别数据集,(n+1)*m
:param k: k<n
:return: 降维后的数据集 (k+1)*m
'''
dataMat = np.mat(dataSet) # 转换成矩阵
data_X = dataMat[: -1, :] # 获取不含标签的数据集
n = dataSet.shape[0] - 1 # 获取原维度
labels = set(list(dataSet[-1, :])) # 所有类别标签的集合
sw = np.mat(np.zeros((n, n))) # 定义类内散列矩阵(注意不是协方差矩阵!区别在于没有分母)
sb = np.mat(np.zeros((n, n))) # 定义类间散列矩阵
mean = np.mean(data_X, axis=1) # 计算原始各维度的均值
for label in labels:
x = data_X[:, np.nonzero(dataSet[-1, :] == label)[0]] # 取出类别标签等于目标类别的数据集(不含标签)
sw += (x - np.mean(x, axis=1)).dot((x - np.mean(x, axis=1)).T) # 根据公式计算sw
sb += x.shape[1] * (np.mean(x, axis=1) - mean).dot((np.mean(x, axis=1) - mean).T) # 根据公式计算sb
u, s, vt = np.linalg.svd(np.linalg.inv(sw).dot(sb)) # 对目标矩阵 (sw的逆矩阵).dot(sb) 做奇异值分解
lowDDataMat = u[:k, :].dot(data_X) # u的前k行,是前k个最大的特征值 对应的 特征向量,利用左乘基向量的方法,降维
lowDDataMat = np.array(lowDDataMat) # 转换成数据形式
lowDDataMat = np.vstack((lowDDataMat, dataSet[-1, :])) # 添加最初的类别行
return lowDDataMat # 返回降维后的数据集
# 以下是测试数据
dataSet = pd.read_csv('data/iris.txt', names=['x1', 'x2', 'x3', 'x4', 'label'])
def labels(x):
'''
源数据的标签列是字符串,利用apply把字符串转换成数字
'''
if x == 'Iris-setosa':
return 1
elif x == 'Iris-versicolor':
return 2
else:
return 3
dataSet['label'] = dataSet['label'].apply(lambda x: labels(x))
dataSet = np.array(dataSet).T # 把数据集变成 (n+1)*m
lda = LDA()
lowD = lda.lda(dataSet, 2)
print(lowD)