1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159
| """ ECG + 眼动双模态认知负荷分类器
基于论文发现: - Mean NN (ECG) 是第3重要特征 - W分量 (ECG超低频) 是第4重要特征 - 瞳孔直径 是第5重要特征
去掉fNIRS后, 使用ECG+眼动实现3级分类: - Underload (低负荷) - Moderate (适中) - Overload (过载) """
import numpy as np from sklearn.ensemble import GradientBoostingClassifier from sklearn.model_selection import cross_val_score, LeaveOneGroupOut from sklearn.preprocessing import StandardScaler from scipy.signal import find_peaks, butter, filtfilt, welch from typing import Tuple
class CognitiveLoadClassifier: """ECG+眼动双模态认知负荷分类器""" def __init__(self): self.scaler = StandardScaler() self.model = GradientBoostingClassifier( n_estimators=150, max_depth=4, learning_rate=0.1, subsample=0.8, random_state=42 ) self.labels = ['低负荷', '适中', '过载'] def extract_ecg_features(self, ecg: np.ndarray, fs=250) -> np.ndarray: """提取ECG/HRV特征: Mean NN, RMSSD, LF/HF, W分量""" peaks, _ = find_peaks(ecg, distance=150, prominence=0.5) if len(peaks) < 10: return np.zeros(4) rr = np.diff(peaks) / fs mean_nn = np.mean(rr) rmssd = np.sqrt(np.mean(np.diff(rr)**2)) freqs, psd = welch(rr, fs=1/mean_nn, nperseg=min(len(rr), 64)) lf = np.sum(psd[(freqs >= 0.04) & (freqs < 0.15)]) hf = np.sum(psd[(freqs >= 0.15) & (freqs < 0.40)]) w = np.sum(psd[(freqs >= 0.01) & (freqs < 0.04)]) return np.array([mean_nn, rmssd, lf/(hf+1e-8), w]) def extract_eye_features(self, eye_data: np.ndarray, fs=30) -> np.ndarray: """提取眼动特征: 瞳孔直径, 眨眼率, 注视稳定性""" eye_openness, gaze_x, gaze_y, pupil_d = eye_data.T pupil_mean = np.mean(pupil_d) pupil_var = np.std(pupil_d) blink_threshold = 0.3 blink_events = np.diff((eye_openness < blink_threshold).astype(int)) blink_rate = np.sum(blink_events == -1) / (len(eye_openness) / fs / 60) gaze_std = np.std(np.sqrt(gaze_x**2 + gaze_y**2)) return np.array([pupil_mean, pupil_var, blink_rate, gaze_std]) def extract_features(self, ecg, eye_data): """提取双模态特征""" ecg_f = self.extract_ecg_features(ecg) eye_f = self.extract_eye_features(eye_data) return np.concatenate([ecg_f, eye_f]) def train(self, X, y, groups=None): """训练模型""" X_scaled = self.scaler.fit_transform(X) if groups is not None: logo = LeaveOneGroupOut() scores = cross_val_score( self.model, X_scaled, y, groups=groups, cv=logo, scoring='accuracy' ) print(f"LOSO准确率: {scores.mean():.4f} ± {scores.std():.4f}") self.model.fit(X_scaled, y) return self.model def predict(self, ecg, eye_data): """预测认知负荷等级""" features = self.extract_features(ecg, eye_data).reshape(1, -1) features_scaled = self.scaler.transform(features) pred = self.model.predict(features_scaled)[0] proba = self.model.predict_proba(features_scaled)[0] return { 'level': int(pred), 'level_name': self.labels[int(pred)], 'confidence': float(max(proba)), 'probabilities': {self.labels[i]: float(p) for i, p in enumerate(proba)} }
if __name__ == "__main__": np.random.seed(42) clf = CognitiveLoadClassifier() n_per_class = 30 X_list, y_list = [], [] for level in range(3): for _ in range(n_per_class): mean_nn = 0.9 - level * 0.1 + np.random.normal(0, 0.05) rmssd = 0.05 - level * 0.01 + np.random.normal(0, 0.01) lf_hf = 1.0 + level * 0.5 + np.random.normal(0, 0.2) w = 0.005 - level * 0.001 + np.random.normal(0, 0.002) pupil = 3.5 + level * 0.3 + np.random.normal(0, 0.2) pupil_var = 0.2 + level * 0.1 + np.random.normal(0, 0.05) blink_rate = 15 - level * 3 + np.random.normal(0, 2) gaze_std = 0.15 + level * 0.1 + np.random.normal(0, 0.03) features = np.array([mean_nn, rmssd, lf_hf, w, pupil, pupil_var, blink_rate, gaze_std]) X_list.append(features) y_list.append(level) X = np.array(X_list) y = np.array(y_list) groups = np.repeat(np.arange(3), n_per_class) print("=== ECG+眼动双模态认知负荷分类器 ===\n") clf.train(X, y, groups=np.repeat(np.arange(10), 9)) print("\n=== 预测示例 ===") test_ecg = np.random.normal(0, 0.1, 15000) test_ecg[np.random.choice(15000, 50, replace=False)] = 1.0 test_eye = np.column_stack([ np.random.normal(0.8, 0.05, 1800), np.random.normal(0, 0.15, 1800), np.random.normal(0, 0.15, 1800), np.random.normal(3.8, 0.2, 1800) ]) result = clf.predict(test_ecg, test_eye) print(f"预测: {result['level_name']} (置信度: {result['confidence']:.3f})") print(f"概率: {result['probabilities']}")
|