I'm following a Linear Discriminant Analysis tutorial from here for dimensionality reduction. After working through the tutorial (did the PCA part, too), I shortened the code using sklearn modules where applicable and verified it on the Iris data set (same code, same result), a synthetic data set (with make_classification
) and the sklearn-digits dataset.
However, then I tried the exact same code on a complete different (unfortunately non-public) data set that contains spectra recordings of two classes. The LDA crashes at the eigenvector verification part, where the $\lambda \mathbf{v}$ is supposed to be almost equal to $S_W^{-1} S_B \mathbf{v}$ (with $\lambda$ being the eigenvalue and $\mathbf{v}$ the corresponding eigenvector; $S_W$ and $S_B$ are the in/between-class scatter matrices). The first vector to be wrong seems at random positions, meaning each run it's a different vector that's causing this error.
I suspect it's related to rounding during calculations, since I get complex eigenvectors. For the PCA I just discarded the complex part (I think I read it somewhere in this forum), but this approach does not seem to work with LDA. Has anybody encountered similar problems or knows what's wrong?
Following is my code for the analysis, which is more or less the same as in the tutorial. I'm using the manual approach, since I'm interested in how many linear discriminants are needed to describe my data. (I'm not sure how to do this with sklearn's LDA.)
def LDAnalysis_manual(X, y):
n_features = X.shape[1]
n_classes = len(np.unique(y))
print("Mean vectors...")
mean_vectors = []
for cl in range(n_classes):
mean_vectors.append(np.mean(X[y == cl], axis=0))
# print("Mean vector class {}: {}".format(cl, mean_vectors[cl - 1]))
print("In-class scatter matrix...")
S_W = np.zeros((n_features, n_features))
for cl, mv in zip(range(1, n_classes), mean_vectors):
class_sc_mat = np.zeros((n_features, n_features)) # each class' scatter matrix
for row in X[y == cl]:
row, mv = row.reshape(n_features, 1), mv.reshape(n_features, 1) # column vectors
class_sc_mat += (row - mv).dot((row - mv).T)
S_W += class_sc_mat # sum class scatter matrices
overall_mean = np.mean(X, axis=0)
print("Between-class scatter matrix...")
S_B = np.zeros((n_features, n_features))
for i, mean_vec in enumerate(mean_vectors):
n = X[y == i + 1].shape[0]
mean_vec = mean_vec.reshape(n_features, 1) # make column vector
overall_mean = overall_mean.reshape(n_features, 1)
S_B += n * (mean_vec - overall_mean).dot((mean_vec - overall_mean).T)
eig_vals, eig_vecs = np.linalg.eig(np.linalg.inv(S_W).dot(S_B))
print("Eigenvector test")
for i in range(len(eig_vals)):
print("\r{:3}".format(i), end=" ")
sys.stdout.flush()
eigv = eig_vecs[:, i].reshape(n_features, 1)
np.testing.assert_array_almost_equal(np.linalg.inv(S_W).dot(S_B).dot(eigv).real,
(eig_vals[i] * eigv).real,
decimal=6, err_msg='', verbose=True)
__log.debug("\nAll values ok.")
eig_pairs = [(np.abs(eig_vals[i]), eig_vecs[:, i]) for i in
range(len(eig_vals))] # make list of value & vector tuples
eig_pairs = sorted(eig_pairs, key=lambda k: k[0], reverse=True) # Sort tuple-list from high to low
__log.info("\nEigenvalues (decending):")
for i in eig_pairs:
__log.info(i[0])
tot = sum(eig_vals)
var_exp = [(i / tot) for i in sorted(eig_vals, reverse=True)]
cum_var_exp = np.cumsum(var_exp)
cum_var_exp = cum_var_exp.real
plot(len(var_exp), var_exp, cum_var_exp)
idx_98 = next(idx for idx, val in enumerate(cum_var_exp) if val > .98)
return idx_98 + 1