8a136c71fe
- Created `classification.py` for comparing image-level and patient-level classification results using various CNN models. - Implemented `create_patient_groups.py` to extract features, generate PCA/t-SNE plots, and identify patient groups via K-means clustering. - Added `figure6.py` to generate boxplots for test accuracy across multiple seeds. - Developed `simple_patient_tsne.py` to perform t-SNE visualization of patient groups and save results in a manifest file. - Introduced `simple_patient_manifest.csv` to store patient IDs, classes, image counts, and associated images.
149 lines
4.1 KiB
Python
149 lines
4.1 KiB
Python
"""
|
|
Visualization utilities for feature space exploration.
|
|
|
|
PCA and t-SNE plots to inspect class separability in the
|
|
extracted feature representations.
|
|
"""
|
|
|
|
import numpy as np
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
from sklearn.decomposition import PCA
|
|
from sklearn.manifold import TSNE
|
|
|
|
|
|
def plot_pca_tsne(X, Y, legend_names=None, perplexity=35,
|
|
random_state=42, output_path=None, figsize=(12, 5),
|
|
dpi=150):
|
|
"""Generate side-by-side PCA and t-SNE plots of feature vectors.
|
|
|
|
Parameters
|
|
----------
|
|
X : np.ndarray of shape (n_samples, n_features)
|
|
Feature matrix (e.g. VGG16 outputs).
|
|
Y : np.ndarray of shape (n_samples,)
|
|
Class labels.
|
|
legend_names : dict, optional
|
|
Mapping from raw label -> display label, e.g.
|
|
{"Bengin cases": "Benign", ...}.
|
|
perplexity : int
|
|
t-SNE perplexity (default 35).
|
|
random_state : int
|
|
Seed for reproducibility.
|
|
output_path : str, optional
|
|
Path to save the figure. If None, saved as "pca_tsne.png".
|
|
figsize : tuple
|
|
Figure dimensions.
|
|
dpi : int
|
|
Output resolution.
|
|
|
|
Returns
|
|
-------
|
|
fig : matplotlib Figure
|
|
"""
|
|
if legend_names is None:
|
|
legend_names = {}
|
|
|
|
n_samples, n_features = X.shape
|
|
|
|
# --- PCA: 50D → 2D ---
|
|
n_pca = min(50, n_samples - 1, n_features)
|
|
pca_pre = PCA(n_components=n_pca, random_state=random_state)
|
|
X_pca50 = pca_pre.fit_transform(X)
|
|
|
|
pca_2d = PCA(n_components=2, random_state=random_state)
|
|
X_pca2 = pca_2d.fit_transform(X_pca50)
|
|
explained_variance = pca_2d.explained_variance_ratio_ * 100
|
|
|
|
# --- t-SNE: 50D → 2D ---
|
|
tsne = TSNE(
|
|
n_components=2,
|
|
perplexity=perplexity,
|
|
learning_rate="auto",
|
|
init="pca",
|
|
random_state=random_state
|
|
)
|
|
X_tsne = tsne.fit_transform(X_pca50)
|
|
|
|
# --- Plot ---
|
|
fig, axes = plt.subplots(1, 2, figsize=figsize)
|
|
unique_classes = np.unique(Y)
|
|
|
|
for class_name in unique_classes:
|
|
idx = Y == class_name
|
|
label = legend_names.get(class_name, class_name)
|
|
|
|
axes[0].scatter(
|
|
X_pca2[idx, 0], X_pca2[idx, 1],
|
|
label=label, alpha=0.7, s=8
|
|
)
|
|
axes[0].set_xlabel(f"PC1 ({explained_variance[0]:.1f}%)")
|
|
axes[0].set_ylabel(f"PC2 ({explained_variance[1]:.1f}%)")
|
|
axes[0].set_title("PCA")
|
|
axes[0].legend(markerscale=2)
|
|
|
|
for class_name in unique_classes:
|
|
idx = Y == class_name
|
|
label = legend_names.get(class_name, class_name)
|
|
|
|
axes[1].scatter(
|
|
X_tsne[idx, 0], X_tsne[idx, 1],
|
|
label=label, alpha=0.7, s=8
|
|
)
|
|
axes[1].set_xlabel("t-SNE dimension 1")
|
|
axes[1].set_ylabel("t-SNE dimension 2")
|
|
axes[1].set_title("t-SNE")
|
|
axes[1].legend(markerscale=2)
|
|
|
|
plt.tight_layout()
|
|
|
|
if output_path is None:
|
|
output_path = "pca_tsne.png"
|
|
plt.savefig(output_path, dpi=dpi)
|
|
plt.close()
|
|
print(f" Saved PCA/t-SNE plot to {output_path}")
|
|
|
|
return fig
|
|
|
|
|
|
def plot_cv_accuracy(cv_results, split_type="", output_path=None,
|
|
figsize=(7, 5), dpi=150):
|
|
"""Plot cross-validation accuracy vs number of selected features.
|
|
|
|
Parameters
|
|
----------
|
|
cv_results : list of dict
|
|
Each dict has keys "nfeatures" and "best_cv_accuracy".
|
|
split_type : str
|
|
Label for the plot title, e.g. "Image-level split".
|
|
output_path : str, optional
|
|
Output file path.
|
|
figsize : tuple
|
|
dpi : int
|
|
|
|
Returns
|
|
-------
|
|
fig : matplotlib Figure
|
|
"""
|
|
fig, ax = plt.subplots(figsize=figsize)
|
|
|
|
nfeats = [r["nfeatures"] for r in cv_results]
|
|
accs = [r["best_cv_accuracy"] for r in cv_results]
|
|
|
|
ax.plot(nfeats, accs, marker="o")
|
|
ax.set_xscale("log")
|
|
ax.set_xlabel("Number of selected features")
|
|
ax.set_ylabel("Mean 5-fold validation accuracy")
|
|
if split_type:
|
|
ax.set_title(split_type)
|
|
plt.tight_layout()
|
|
|
|
if output_path is None:
|
|
output_path = f"{split_type.replace(' ', '_').replace('-', '_')}_cv.png"
|
|
plt.savefig(output_path, dpi=dpi)
|
|
plt.close()
|
|
print(f" Saved CV accuracy plot to {output_path}")
|
|
|
|
return fig
|