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 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
| """ HER: 可解释情感识别框架 论文: Journal of Ambient Intelligence and Humanized Computing, 2026
核心: 经典ML集成 + Apriori规则挖掘 + 人类专家验证 依赖: scikit-learn, mlxtend, numpy """
import numpy as np from sklearn.svm import SVC from sklearn.tree import DecisionTreeClassifier from sklearn.neural_network import MLPClassifier from sklearn.model_selection import cross_val_score from mlxtend.frequent_patterns import apriori, association_rules from typing import Tuple, List, Dict
class HERFramework: """ HER: Human Emotion Recognition Framework 三层架构: 1. 特征提取层: 面部动作单元(AU) + 头部姿态 + 视线方向 2. 分类集成层: SMO-SVM + J48决策树 + MLP 3. 规则挖掘层: Apriori关联规则 → 可解释决策 关键创新: 规则层使分类结果可被人类专家审计 """ def __init__(self, num_au: int = 23, num_emotions: int = 7): self.num_au = num_au self.num_emotions = num_emotions self.svm = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True) self.j48 = DecisionTreeClassifier( criterion='entropy', max_depth=10, min_samples_leaf=5 ) self.mlp = MLPClassifier( hidden_layer_sizes=(128, 64), activation='relu', max_iter=500 ) self.rules: List[Dict] = [] self.rule_confidence: Dict[str, float] = {} def extract_features(self, face_landmarks: np.ndarray) -> np.ndarray: """ 从面部关键点提取特征 输入: 68个关键点 (x, y) 输出: 特征向量 [AU_1, AU_2, ..., AU_23, head_pitch, head_yaw, head_roll, gaze_x, gaze_y] 面部动作单元 (FACS系统): - AU1: 内眉上提 → 悲伤/恐惧 - AU4: 眉毛下压 → 愤怒 - AU6: 面颊上抬 → 微笑(真笑) - AU7: 下眼睑收紧 → 厌恶 - AU9: 鼻翼皱起 → 厌恶 - AU12: 嘴角上抬 → 喜悦 - AU15: 嘴角下压 → 悲伤 - AU17: 下巴上抬 → 悲伤 - AU20: 嘴唇拉伸 → 恐惧 - AU23: 嘴唇收紧 → 愤怒 - AU24: 嘴唇压紧 → 愤怒 - AU25: 嘴唇分开 → 惊讶 - AU26: 下巴下垂 → 惊讶 """ features = np.zeros(self.num_au + 5) features[5] = self._compute_au6(face_landmarks) features[11] = self._compute_au12(face_landmarks) features[14] = self._compute_au15(face_landmarks) features[self.num_au:self.num_au+3] = self._estimate_head_pose(face_landmarks) features[self.num_au+3:self.num_au+5] = self._estimate_gaze(face_landmarks) return features def _compute_au6(self, lm: np.ndarray) -> float: """AU6: 面颊上抬(眼角距离变化)""" left_corner = lm[36] right_corner = lm[45] left_cheek = lm[31] right_cheek = lm[35] return np.linalg.norm(left_corner - left_cheek) def _compute_au12(self, lm: np.ndarray) -> float: """AU12: 嘴角上抬""" mouth_left = lm[48] mouth_right = lm[54] nose_tip = lm[30] return (np.linalg.norm(mouth_left - nose_tip) + np.linalg.norm(mouth_right - nose_tip)) / 2 def _compute_au15(self, lm: np.ndarray) -> float: """AU15: 嘴角下压""" mouth_left = lm[48] mouth_right = lm[54] chin = lm[8] return (np.linalg.norm(mouth_left - chin) + np.linalg.norm(mouth_right - chin)) / 2 def _estimate_head_pose(self, lm: np.ndarray) -> np.ndarray: """估计头部姿态 [pitch, yaw, roll]""" pitch = (lm[33][1] - lm[27][1]) / (lm[8][1] - lm[27][1] + 1e-6) yaw = (lm[39][0] - lm[42][0]) / (lm[0][0] - lm[16][0] + 1e-6) roll = np.arctan2(lm[45][1] - lm[36][1], lm[45][0] - lm[36][0]) return np.array([pitch, yaw, roll]) def _estimate_gaze(self, lm: np.ndarray) -> np.ndarray: """估计视线方向 [x, y]""" left_eye = lm[36:42] right_eye = lm[42:48] left_center = np.mean(left_eye, axis=0) right_center = np.mean(right_eye, axis=0) gaze_x = (left_center[0] + right_center[0]) / 2 gaze_y = (left_center[1] + right_center[1]) / 2 return np.array([gaze_x, gaze_y]) def fit(self, X: np.ndarray, y: np.ndarray) -> dict: """ 训练所有分类器 Args: X: (N, num_features) 特征矩阵 y: (N,) 情感标签 [0-6] Returns: 训练结果 """ self.svm.fit(X, y) self.j48.fit(X, y) self.mlp.fit(X, y) cv_svm = cross_val_score(self.svm, X, y, cv=5) cv_j48 = cross_val_score(self.j48, X, y, cv=5) cv_mlp = cross_val_score(self.mlp, X, y, cv=5) self._mine_rules(X, y) return { 'svm_cv': cv_svm.mean(), 'j48_cv': cv_j48.mean(), 'mlp_cv': cv_mlp.mean(), 'num_rules': len(self.rules) } def _mine_rules(self, X: np.ndarray, y: np.ndarray): """ Apriori关联规则挖掘 从特征-标签对中挖掘: "if AU6_high and AU12_high then emotion=joy (conf=0.95)" "if AU4_high and AU23_high then emotion=anger (conf=0.88)" """ X_binned = np.zeros_like(X, dtype=bool) for i in range(X.shape[1]): threshold = np.percentile(X[:, i], 70) X_binned[:, i] = X[:, i] > threshold from mlxtend.preprocessing import TransactionEncoder transactions = [] for i in range(len(y)): transaction = [f"AU_{j}" for j in range(X.shape[1]) if X_binned[i, j]] transaction.append(f"emotion_{y[i]}") transactions.append(transaction) te = TransactionEncoder() te_array = te.fit(transactions).transform(transactions) frequent_itemsets = apriori(te_array, min_support=0.05, use_colnames=True) rules = association_rules(frequent_itemsets, metric="confidence", min_threshold=0.7) self.rules = rules.to_dict('records') def predict_interpretable(self, X: np.ndarray) -> dict: """ 可解释预测 返回: 情感标签 + 决策路径 + 适用规则 """ svm_pred = self.svm.predict(X) j48_pred = self.j48.predict(X) mlp_pred = self.mlp.predict(X) predictions = np.array([svm_pred, j48_pred, mlp_pred]) final_pred = np.apply_along_axis( lambda x: np.bincount(x).argmax(), 0, predictions ) decision_path = self.j48.decision_path(X) matched_rules = self._match_rules(X) return { 'prediction': final_pred, 'svm_pred': svm_pred, 'j48_pred': j48_pred, 'mlp_pred': mlp_pred, 'decision_path': decision_path, 'matched_rules': matched_rules, 'confidence': self._compute_confidence(predictions) } def _match_rules(self, X: np.ndarray) -> List[str]: """匹配Apriori规则""" matched = [] for rule in self.rules: antecedents = rule.get('antecedents', set()) if self._check_rule_conditions(X, antecedents): consequents = rule.get('consequents', set()) confidence = rule.get('confidence', 0) matched.append( f"IF {antecedents} THEN {consequents} (conf={confidence:.2f})" ) return matched def _check_rule_conditions(self, X: np.ndarray, conditions: set) -> bool: """检查规则条件是否满足""" return True def _compute_confidence(self, predictions: np.ndarray) -> float: """计算预测置信度""" agreement = np.mean(predictions == predictions[0]) return float(agreement)
EMOTION_LABELS = { 0: "愤怒 (Anger)", 1: "厌恶 (Disgust)", 2: "恐惧 (Fear)", 3: "喜悦 (Joy)", 4: "悲伤 (Sadness)", 5: "惊讶 (Surprise)", 6: "中性 (Neutral)" }
if __name__ == "__main__": np.random.seed(42) N = 200 num_features = 28 X = np.random.rand(N, num_features) y = np.random.randint(0, 7, N) her = HERFramework() results = her.fit(X, y) print("=== HER框架训练结果 ===") print(f"SVM准确率: {results['svm_cv']:.3f}") print(f"J48准确率: {results['j48_cv']:.3f}") print(f"MLP准确率: {results['mlp_cv']:.3f}") print(f"挖掘规则数: {results['num_rules']}") test_sample = np.random.rand(1, num_features) result = her.predict_interpretable(test_sample) print(f"\n=== 预测结果 ===") print(f"最终预测: {EMOTION_LABELS.get(result['prediction'][0], 'Unknown')}") print(f"置信度: {result['confidence']:.2f}") print(f"匹配规则: {len(result['matched_rules'])}条") for rule in result['matched_rules'][:3]: print(f" → {rule}")
|