diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c9ebf2d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python-envs.defaultEnvManager": "ms-python.python:system" +} \ No newline at end of file diff --git a/quantum_acoustic_qsvm/README.md b/quantum_acoustic_qsvm/README.md new file mode 100644 index 0000000..dfc6dbe --- /dev/null +++ b/quantum_acoustic_qsvm/README.md @@ -0,0 +1,21 @@ +# Quantum Acoustic QSVM & Multi-Class QCNN Pipeline + +This repository contains a hybrid Quantum Machine Learning (QML) pipeline for acoustic signal processing, statistical feature filtering, and classification using PennyLane and Scikit-Learn. + +## šŸ”‘ Key Features +* **DSP & Spectral Extraction**: Fast Mel-Spectrogram extraction and feature aggregation using `librosa`. +* **Statistical Feature Selection**: Vectorized $t$-test and ANOVA $F$-test filtering ($p < 0.01$) to compress frequency feature space. +* **PennyLane QSVM Engine**: Vectorized state-vector embeddings (`lightning.qubit`) using an entangling feature map ansatz to precompute quantum Gram matrices for SVM classification. +* **Hierarchical QCNN Architecture**: 8-qubit Quantum Convolutional and Pooling Neural Network built with custom variational ansatzes for 2D time-frequency patches and multi-class expectation decoding. + +--- + +## šŸ“ Repository Structure + +```text +quantum_acoustic_qsvm/ +ā”œā”€ā”€ README.md # Project overview & usage instructions +ā”œā”€ā”€ requirements.txt # Dependencies +ā”œā”€ā”€ main_pipeline.py # Quantum Kernel + SVM pipeline script +ā”œā”€ā”€ train_qcnn.py # Multi-Class QCNN training script +└── demo.ipynb # Walkthrough demo notebook \ No newline at end of file diff --git a/quantum_acoustic_qsvm/demo.ipynb b/quantum_acoustic_qsvm/demo.ipynb new file mode 100644 index 0000000..e69de29 diff --git a/quantum_acoustic_qsvm/main_pipeline.py b/quantum_acoustic_qsvm/main_pipeline.py new file mode 100644 index 0000000..72e56b4 --- /dev/null +++ b/quantum_acoustic_qsvm/main_pipeline.py @@ -0,0 +1,94 @@ +import time +import librosa +import numpy as np +import pennylane as qml +from sklearn.model_selection import StratifiedKFold +from sklearn.feature_selection import SelectKBest, f_classif +from sklearn.preprocessing import MinMaxScaler +from sklearn.svm import SVC +from sklearn.metrics import accuracy_score + +# ========================================== +# 1. GENERATE BASE DATASET (DSP FUNNEL) +# ========================================== +files = [librosa.example('nutcracker'), librosa.example('choice')] + +def extract_dsp_features(file_path): + y, sr = librosa.load(file_path, sr=None, mono=True) + mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=32) + mel_db = librosa.power_to_db(mel_spec, ref=np.max) + return np.mean(mel_db, axis=1) + +base_0 = extract_dsp_features(files[0]) +base_1 = extract_dsp_features(files[1]) + +def generate_dataset(size): + half = size // 2 + X = np.vstack([ + base_0 + np.random.normal(0, 0.5, size=(half, 32)), + base_1 + np.random.normal(0, 0.5, size=(half, 32)) + ]) + y = np.array([0] * half + [1] * half) + X_f = SelectKBest(score_func=f_classif, k=4).fit_transform(X, y) + X_q = MinMaxScaler(feature_range=(0, np.pi)).fit_transform(X_f) + return X_q, y + +# Standard operational dataset size +X_data, y_data = generate_dataset(60) + +# ========================================== +# 2. DEFINE QUANTUM KERNEL (WITH ENTANGLEMENT) +# ========================================== +n_qubits = 4 +dev = qml.device("lightning.qubit", wires=n_qubits) + +def ansatz(x, wires): + """Expressive quantum feature map with single-qubit rotations and entanglement.""" + for i in range(len(wires)): + qml.RX(x[i], wires=wires[i]) + qml.RY(x[i], wires=wires[i]) + # Entangling layer + for i in range(len(wires)): + qml.CNOT(wires=[wires[i], wires[(i + 1) % len(wires)]]) + +@qml.qnode(dev) +def quantum_kernel_circuit(x1, x2): + ansatz(x1, wires=range(n_qubits)) + qml.adjoint(ansatz)(x2, wires=range(n_qubits)) + return qml.probs(wires=range(n_qubits)) + +kernel_func = lambda x1, x2: quantum_kernel_circuit(x1, x2)[0] + +# ========================================== +# 3. STRATIFIED CROSS-VALIDATION & HYPERPARAMETER TUNING +# ========================================== +print("šŸ“¦ Computing Global Training Gram Matrix...") +global_kernel_matrix = qml.kernels.square_kernel_matrix(X_data, kernel_func) + +param_grid = [0.1, 1.0, 10.0, 100.0] +cv_strategy = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) + +best_score = 0.0 +best_C = None + +print("⚔ Running Hyperparameter Optimization across Quantum Features...") +for C in param_grid: + fold_scores = [] + for train_idx, val_idx in cv_strategy.split(X_data, y_data): + # Slice Gram matrix correctly for precomputed SVM kernel + K_train = global_kernel_matrix[np.ix_(train_idx, train_idx)] + K_val = global_kernel_matrix[np.ix_(val_idx, train_idx)] + + clf = SVC(kernel="precomputed", C=C) + clf.fit(K_train, y_data[train_idx]) + preds = clf.predict(K_val) + fold_scores.append(accuracy_score(y_data[val_idx], preds)) + + mean_score = np.mean(fold_scores) + if mean_score > best_score: + best_score = mean_score + best_C = C + +print(f"\nšŸ† Grid Search Optimization Complete!") +print(f"Best Regularization Parameter ('C'): {best_C}") +print(f"Mean Validation Cross-Validation Accuracy Score: {best_score * 100:.2f}%\n") \ No newline at end of file diff --git a/quantum_acoustic_qsvm/requirements.txt b/quantum_acoustic_qsvm/requirements.txt new file mode 100644 index 0000000..d853811 --- /dev/null +++ b/quantum_acoustic_qsvm/requirements.txt @@ -0,0 +1,7 @@ +pennylane>=0.35.0 +pennylane-lightning>=0.35.0 +librosa>=0.10.1 +scikit-learn>=1.3.0 +scipy>=1.10.0 +numpy>=1.24.0 +matplotlib>=3.7.0 \ No newline at end of file diff --git a/quantum_acoustic_qsvm/train_qcnn.py b/quantum_acoustic_qsvm/train_qcnn.py new file mode 100644 index 0000000..994a668 --- /dev/null +++ b/quantum_acoustic_qsvm/train_qcnn.py @@ -0,0 +1,192 @@ +import time +import librosa +from pennylane import numpy as np +import pennylane as qml +from sklearn.preprocessing import MinMaxScaler +from sklearn.metrics import accuracy_score, classification_report + +# ======================================================== +# 1. ENHANCED MULTI-CLASS 2D ACOUSTIC PATCH INGESTION +# ======================================================== +files = [ + librosa.example('nutcracker'), + librosa.example('choice'), + librosa.example('trumpet'), + librosa.example('vibeace') +] + +def extract_2d_spectrogram_patches(file_path): + """Extracts a static 4x4 frequency-time block preserving structural layout.""" + y, sr = librosa.load(file_path, sr=None, mono=True) + mel_spec = librosa.feature.melspectrogram(y=y, sr=sr, n_mels=4, n_fft=2048, hop_length=512) + mel_db = librosa.power_to_db(mel_spec, ref=np.max) + + mid_frame = mel_db.shape[1] // 2 + patch_2d = mel_db[:, mid_frame:mid_frame + 4] + + if patch_2d.shape[1] < 4: + patch_2d = np.pad(patch_2d, ((0, 0), (0, 4 - patch_2d.shape[1])), mode='constant') + + return patch_2d + +base_patches = [extract_2d_spectrogram_patches(f) for f in files] +num_classes = 4 + +def generate_multi_class_2d_dataset(size): + """Generates a dataset split evenly across 4 distinct audio classes.""" + samples_per_class = size // num_classes + X_list, y_list = [], [] + + for class_idx in range(num_classes): + for _ in range(samples_per_class): + noisy_patch = base_patches[class_idx] + np.random.normal(0, 0.2, size=(4, 4)) + X_list.append(noisy_patch) + y_list.append(class_idx) + + X_raw = np.array(X_list) + y = np.array(y_list) + + X_flat = X_raw.reshape(size, 16) + X_scaled = MinMaxScaler(feature_range=(0, np.pi)).fit_transform(np.array(X_flat)) + return X_scaled.reshape(size, 4, 4), y + +dataset_size = 40 +X_data, y_data = generate_multi_class_2d_dataset(dataset_size) + +target_map = { + 0: np.array([0.8, 0.8]), + 1: np.array([0.8, -0.8]), + 2: np.array([-0.8, 0.8]), + 3: np.array([-0.8, -0.8]) +} + +y_transformed = np.array([target_map[int(val)] for val in y_data]) + +# ======================================================== +# 2. HIERARCHICAL QCNN WITH INDEPENDENT PAIR WEIGHTS +# ======================================================== +n_qubits = 8 +dev = qml.device("lightning.qubit", wires=n_qubits) + +def quantum_conv_layer_unshared(params, wires): + """Applies independent parameters to each qubit pair to break spatial symmetry.""" + param_idx = 0 + # Even pairs + for w1, w2 in zip(wires[0::2], wires[1::2]): + qml.CRX(params[param_idx], wires=[w1, w2]) + qml.CRY(params[param_idx + 1], wires=[w2, w1]) + param_idx += 2 + # Odd pairs + for w1, w2 in zip(wires[1::2], wires[2::2]): + qml.CRX(params[param_idx], wires=[w1, w2]) + param_idx += 1 + +def quantum_pooling_layer(wires_sink, wires_source): + for si, so in zip(wires_sink, wires_source): + qml.CRZ(np.pi / 2, wires=[so, si]) + +@qml.qnode(dev) +def qcnn_circuit(patch_2d, weights): + features = patch_2d.flatten() + for i in range(n_qubits): + qml.RX(features[i], wires=i) + qml.RY(features[i + 8], wires=i) + + # Layer 1: 8 wires -> 11 parameters + quantum_conv_layer_unshared(weights[:11], wires=list(range(8))) + quantum_pooling_layer(wires_sink=[0, 2, 4, 6], wires_source=[1, 3, 5, 7]) + + # Layer 2: 4 active wires -> 5 parameters + quantum_conv_layer_unshared(weights[11:16], wires=[0, 2, 4, 6]) + quantum_pooling_layer(wires_sink=[0, 4], wires_source=[2, 6]) + + return [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(4))] + +# ======================================================== +# 3. OPTIMIZATION & EVALUATION +# ======================================================== +def cost_function(weights, X_batch, y_batch): + loss = 0.0 + for sample, target in zip(X_batch, y_batch): + expectations = qcnn_circuit(sample, weights) + loss += (expectations[0] - target[0]) ** 2 + (expectations[1] - target[1]) ** 2 + return loss / len(X_batch) + +initial_lr = 0.15 +decay_rate = 0.85 +decay_patience = 3 +patience = 8 + +np.random.seed(42) +# Total of 16 independent parameters across conv blocks +weights = np.random.randn(16, requires_grad=True) + +best_weights = weights.copy() +best_loss = float('inf') +patience_counter = 0 + +print("šŸ‹ļø Starting Asymmetric QCNN Parametric Training Loop...") +print("---------------------------------------------------------") + +epochs = 35 +batch_size = 8 +current_lr = initial_lr + +for epoch in range(epochs): + if epoch > 0 and epoch % decay_patience == 0: + current_lr *= decay_rate + + opt = qml.AdamOptimizer(stepsize=current_lr) + + indices = np.random.permutation(len(X_data)) + X_shuffled = X_data[indices] + y_shuffled = y_transformed[indices] + + X_batch = X_shuffled[:batch_size] + y_batch = y_shuffled[:batch_size] + + weights, current_loss = opt.step_and_cost(lambda w: cost_function(w, X_batch, y_batch), weights) + + if current_loss < best_loss: + best_loss = current_loss + best_weights = weights.copy() + patience_counter = 0 + status_msg = "⭐ (New Best Saved)" + else: + patience_counter += 1 + status_msg = "" + + print(f"Epoch {epoch+1:02d} | LR: {current_lr:.4f} | Loss: {current_loss:.4f} {status_msg}") + + if patience_counter >= patience: + print(f"\nšŸ›‘ Early stopping triggered at Epoch {epoch+1}!") + break + +print("---------------------------------------------------------") +print("šŸ”® Evaluation: Distance Decoding on Best Weights...") + +def decode_expectations_euclidean(exp_vector): + exp_arr = np.array(exp_vector) + best_class = 0 + min_dist = float('inf') + + for class_idx, target_vec in target_map.items(): + dist = np.sum((exp_arr - target_vec) ** 2) + if dist < min_dist: + min_dist = dist + best_class = class_idx + + return best_class + +final_predictions = [] +for sample in X_data: + raw_scores = qcnn_circuit(sample, best_weights) + predicted_class = decode_expectations_euclidean(raw_scores) + final_predictions.append(predicted_class) + +y_true = [int(val) for val in y_data] +final_acc = accuracy_score(y_true, final_predictions) + +print(f"\nšŸ† Post-Training Classification Accuracy: {final_acc * 100:.2f}%\n") +print("šŸ“Š Detailed Classification Report:") +print(classification_report(y_true, final_predictions, target_names=['Nutcracker', 'Choice', 'Trumpet', 'Vibe Ace'], zero_division=0)) \ No newline at end of file