2026年中青杯数学建模
C题 情绪维度耦合约束的脑电信号情绪识别
原题再现:
情绪识别是人机交互与智能医疗领域的核心技术。根据心理学研究,人类情绪可在效价(Valence, V)- 唤醒度(Arousal, A)- 支配度(Dominance, D)三维空间中进行量化描述。这三个维度并非相互独立,而是存在复杂的耦合关系:效价与唤醒度构成环形结构(circumplex模型),高唤醒度通常伴随高支配度,正面情绪往往伴随高控制感。脑电图(Electroencephalogram, EEG)作为一种非侵入式的脑功能监测技术,能够直接反映大脑神经电生理活动,具有时间分辨率高、便携性强等优势,已成为情绪识别研究的重要手段。请根据附件提供的数据集完成下面的问题。
问题1:脑电特征与情绪维度的关联分析
根据附件提供的脑电功率谱特征数据,首先进行数据预处理,分析不同频段功率特征与效价、唤醒度、支配度三个情绪维度的相关性。考虑到脑电信号的空间分布特性,建立数学模型量化各脑区导联对不同情绪维度的贡献度,并给出特征选择策略以降低数据冗余。进一步,基于选定的关键特征,分别建立三个独立的单任务预测模型,计算各维度的识别准确率,并从特征冗余和维度独立性假设两方面分析单任务建模的局限性。
问题2:多任务协同建模与维度耦合约束
基于问题一提取并筛选的脑电特征,设计一种多任务学习框架,通过共享编码器提取脑电信号的公共表征,并设置三个任务专属分支分别预测效价、唤醒度和支配度。为刻画情绪维度间的耦合关系,引入以下三类约束:效价与唤醒度的环形几何约束、唤醒度与支配度的能量对齐约束、效价与支配度的相关性约束。建立包含上述约束的多任务联合优化目标函数,设计模型训练算法,对比单任务与多任务模型的预测性能差异,分析耦合约束对维度间一致性预测的提升作用。
问题3:跨被试泛化与模型可靠性评估
实际应用中,情绪识别模型需要适应新用户且对数据缺失具有鲁棒性。基于问题二建立的模型,设计一种留一被试交叉验证策略评估模型的跨被试泛化能力,每次保留1名被试作为测试集,其余作为被试训练,以此循环确保每名被试均被测试,分析个体差异性对识别性能的影响。
整体求解过程概述(摘要)
针对问题一,为了探究脑电功率谱特征与效价(Valence,V)、唤醒度(Arousal,A)、支配度(Dominance,D)之间的统计关联,本文首先对23名被试、414个试次的DREAMER脑电数据进行数据理解与质量控制。考虑到不同视频刺激时长不一致以及个体静息功率存在显著基线差异,统一截取基线段和刺激段末60 s,采用Welch功率谱估计提取Delta、Theta、Alpha、Beta、Gamma五频段功率,并以刺激相对于基线的dB变化构造70维电极-频段特征;随后引入1% Winsorization、Spearman秩相关、Benjamini-Hochberg FDR校正和互信息,对相关性进行稳健检验。结果显示,A与D具有显著耦合(Spearman ρ=0.698),而V与A近似弱相关;T7_Gamma分别与A、D呈ρ=0.207和0.206的正相关。进一步构建"相关性---互信息"混合脑区贡献模型与mRMR特征筛选方法,并建立岭回归、RBF-SVR和随机森林回归三个单任务基线。以原始1---5分有序评分回归、再以4---5分为高情绪的阈值进行识别,五折按被试分组交叉验证下,V、A、D最优准确率分别达到61.78%、55.61%和60.06%。
针对问题二,鉴于三个情绪维度并非统计独立,且问题一揭示A-D之间存在强耦合,本文在mRMR筛选的共享关键特征上构建"共享编码器+三个任务专属回归分支"的多任务学习模型。为使预测结果同时满足心理学结构与数据经验关系,分别引入V-A环形几何约束、A-D能量对齐约束和V-D相关性约束,并以Smooth-L1数据拟合损失与三类耦合正则项构成联合目标函数。消融实验表明,完整耦合模型在V维上的五折准确率为61.94%,高于无耦合多任务模型的60.06%;同时V-D相关结构误差由0.171下降到0.114,A-D能量比误差由0.170下降到0.122,说明耦合约束能够明显改善跨维度一致性。对于A、D维,耦合模型的分类准确率并未全面超过问题一的树模型基线,这表明在小样本条件下结构先验主要提升的是预测协调性与可解释性,而非无条件提升所有单维精度。
针对问题三,为评估模型对新用户的跨被试泛化能力,本文基于问题二的完整耦合多任务模型设计23折留一被试交叉验证(LOSO),每一折使用22名被试训练、剩余1名被试测试,并在训练折内重新执行标准化与共享mRMR筛选,从而严格避免测试被试信息泄漏。结果表明,V、A、D三个维度的LOSO平均识别准确率分别为61.35%、53.38%和57.25%,三维平均为57.33%,其中被试16平均准确率最高(74.07%),被试19最低(38.89%),显示明显个体差异。进一步随机遮蔽10%、20%、30%的输入特征并以训练均值完成标准化空间插补,总体准确率仍保持在56.73%---57.59%之间,说明训练阶段的特征随机遮蔽策略能够在一定程度上增强对通道或特征缺失的鲁棒性。
综上,本文形成了"数据理解与探索---稳健频谱预处理---统计关联与空间贡献---mRMR降冗余---单任务基线---耦合多任务联合优化---LOSO泛化与缺失鲁棒性"的完整建模链条。该方法既保留了EEG频谱特征的生理可解释性,又通过多任务耦合把心理学先验转化为可优化约束,并以被试级交叉验证检验泛化误差。研究表明,DREAMER数据中A-D耦合最为稳定,颞区和顶区对A、D贡献较高;在当前小样本与低密度14导联条件下,模型的主要瓶颈来自显著个体差异与情绪标签重叠。本文模型可进一步扩展至跨数据集域适配、通道图神经网络和不确定性校准等实际场景。
模型假设:
假设1:23个MAT文件均来自同一DREAMER实验协议,14导联名称、128 Hz采样率和V/A/D评分定义一致。数据质量检查显示每个被试均含18个试次,故可按统一流程提取特征。
假设2:对每个试次而言,刺激段末60 s已进入相对稳定的情绪诱发阶段;基线段末60 s可代表该试次的个体静息水平。该假设用于解决视频时长不一致造成的比较偏差。
假设3:在1---45 Hz范围内,五频段的平均功率能够表征与情绪相关的主要频域变化;45 Hz以上高频信息不作为核心特征,以降低工频谐波与肌电伪迹影响。
假设4:V、A、D原始1---5分可视为有序近连续量进行回归;为了计算"识别准确率",将4---5分定义为高水平,1---3分定义为低/中水平,并以预测评分3.5为分类阈值。该阈值只用于评价,不改变训练阶段对原始评分信息的利用。
假设5:情绪维度耦合为统计软关系而非确定性等式,因此V-A、A-D、V-D约束以惩罚项加入联合目标,并允许权重小于主任务拟合损失。
假设6:LOSO中每名被试的18个试次视为一个不可分割测试组。训练折内的标准化和特征选择不会访问测试被试信息,从而可将LOSO结果解释为面向新用户的近似泛化性能。
问题分析:
问题一的建模难点与技术路线
为了探究EEG功率谱与情绪评分之间是否存在稳定关联,首先必须区分"原始脑电波形"和"可用于统计建模的试次级特征"。原始信号以128 Hz采样且刺激时长从67 s到394 s不等,若直接将全长序列输入统计模型,不仅维数远大于414个试次,还会使长视频在功率估计中占据更高权重。因此首先需要进行时长对齐与频域压缩。
鉴于EEG具有强个体基线差异和1/f频谱结构,绝对功率不适合作为跨被试比较的唯一依据。本文采用"刺激功率/基线功率"的对数dB变化,等价于在乘性尺度上完成个体归一化。随后使用Spearman相关而非仅用Pearson相关,是因为1---5分情绪量表本质上是有序离散变量,且频谱特征可能呈现偏态与非线性单调关系。
考虑到共有70个电极-频段特征,同时进行大量相关检验会产生多重比较问题,故引入FDR校正控制假发现率;同时引入互信息捕捉非线性依赖,并基于"最大相关、最小冗余"思想构建mRMR特征选择。脑区贡献度则不能简单累加,因为额区包含的导联数量多于颞区、顶区和枕区,故本文先在脑区内对特征贡献取均值,再在脑区间归一化,避免导联数量造成机械偏置。
问题二的建模难点与技术路线
问题二的核心矛盾在于:V、A、D具有共同的脑电生理基础,但三者又不完全一致。如果完全独立建模,会浪费共享信息;如果直接强制三个输出高度相关,又可能破坏V与A在本数据中的弱相关事实。因此,多任务模型必须同时包含共享与分离两种机制。本文用共享编码器提取公共表示,再用三个独立线性头保留任务差异。
为了把题目给出的耦合关系转化为可计算约束,本文不把"环形结构""能量对齐""相关性"停留在定性描述,而是分别构造三类可微正则项。V-A约束保持预测点在二维情绪平面中的极径,即保持情绪强度;A-D约束保持两个维度的批内平方能量比例;V-D约束使预测相关系数接近训练标签的经验相关系数。由于这些约束只是先验而非硬等式,采用软约束正则比强制等式更符合真实情绪数据的随机性。
问题三的建模难点与技术路线
跨被试泛化的主要困难是个体脑电基线、头皮阻抗、脑功能模式和主观量表使用方式均存在差异。随机划分试次会使同一被试同时出现在训练集与测试集,从而高估泛化性能。因此问题三必须采用Group-aware的LOSO策略,并且标准化、mRMR筛选、模型训练全部只能在22名训练被试上完成。
在此基础上进一步考虑数据缺失问题。实际可穿戴脑电中,局部电极脱落或高噪声会表现为一组特征不可用。本文将训练标准化后的0视作"训练均值",在测试阶段随机遮蔽一定比例特征并置0,模拟均值插补;训练时额外采用5%随机特征遮蔽,使共享编码器学习对局部缺失更稳定的表示。该设计能够把鲁棒性检验与问题二模型自然衔接,而不需要额外建立重复模型。
模型的建立与求解整体论文缩略图

全部论文请见下方" 只会建模 QQ名片" 点击QQ名片即可
程序代码:
部分程序如下:
python
from __future__ import annotations
import os, glob, json, math, random, warnings
from pathlib import Path
from collections import defaultdict
import numpy as np
import pandas as pd
from scipy.io import loadmat
from scipy.signal import welch
from scipy.stats import spearmanr, pearsonr
from sklearn.feature_selection import mutual_info_regression
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import GroupKFold
from sklearn.metrics import accuracy_score, f1_score, confusion_matrix, mean_absolute_error, mean_squared_error
from sklearn.linear_model import Ridge
from sklearn.svm import SVR
from sklearn.ensemble import RandomForestRegressor
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from matplotlib import font_manager
import torch
import torch.nn as nn
import torch.nn.functional as F
warnings.filterwarnings('ignore')
SEED = 2026
np.random.seed(SEED)
random.seed(SEED)
torch.manual_seed(SEED)
torch.set_num_threads(max(1, min(4, os.cpu_count() or 1)))
# ------------------------- 全局配置 -------------------------
BASE_DIR = Path(__file__).resolve().parent
OUT_DIR = BASE_DIR / 'C题_输出结果'
FIG_DIR = OUT_DIR / 'figures'
OUT_DIR.mkdir(exist_ok=True)
FIG_DIR.mkdir(exist_ok=True)
# 频段上界控制在 45 Hz,避免 50/60 Hz 工频及高频肌电污染
BANDS = {
'Delta': (1, 4),
'Theta': (4, 8),
'Alpha': (8, 13),
'Beta': (13, 30),
'Gamma': (30, 45),
}
TARGETS = ['V', 'A', 'D']
TARGET_CN = {'V':'效价V', 'A':'唤醒度A', 'D':'支配度D'}
REGION_MAP = {
'AF3':'额区', 'F7':'额区', 'F3':'额区', 'FC5':'额区',
'FC6':'额区', 'F4':'额区', 'F8':'额区', 'AF4':'额区',
'T7':'颞区', 'T8':'颞区', 'P7':'顶区', 'P8':'顶区',
'O1':'枕区', 'O2':'枕区',
}
# 美观绘图配置
try:
# 在Linux竞赛环境中强制注册Noto中文字体,避免图中文字显示为方框。
noto_path = '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc'
if os.path.exists(noto_path):
font_manager.fontManager.addfont(noto_path)
fp = font_manager.FontProperties(fname=noto_path)
plt.rcParams['font.family'] = fp.get_name()
plt.rcParams['font.sans-serif'] = [fp.get_name()]
else:
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC', 'Microsoft YaHei', 'SimHei']
except Exception:
pass
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['figure.dpi'] = 150
plt.rcParams['savefig.dpi'] = 240
plt.rcParams['axes.grid'] = True
plt.rcParams['grid.alpha'] = 0.18
plt.rcParams['axes.spines.top'] = False
plt.rcParams['axes.spines.right'] = False
PALETTE = ['#335C81', '#6A8EAE', '#A8C4D8', '#E09F3E', '#9E2A2B', '#5F6F52']
def savefig(name):
plt.tight_layout()
plt.savefig(FIG_DIR / name, bbox_inches='tight')
plt.close()
# ------------------------- 数据读取与特征工程 -------------------------
def find_mat_files():
files = sorted(BASE_DIR.glob('DREAMER_subject_*.mat'))
if len(files) != 23:
print(f'[提示] 当前检测到 {len(files)} 个 mat 文件,标准题设应为 23 个。')
return files
def as_str(x):
if isinstance(x, np.ndarray):
x = np.squeeze(x)
if x.size == 1:
return str(x.item())
return str(x)
def bandpower_from_psd(freqs, psd, lo, hi):
mask = (freqs >= lo) & (freqs < hi)
if mask.sum() < 2:
return np.full(psd.shape[1], np.nan)
return np.trapz(psd[mask, :], freqs[mask], axis=0)
def extract_subject(path: Path):
d = loadmat(path, squeeze_me=True, struct_as_record=False)
sid = int(np.asarray(d['SubjectID']).squeeze())
fs = int(np.asarray(d['EEG_SamplingRate']).squeeze())
channels = [as_str(x) for x in np.atleast_1d(d['EEG_Electrodes'])]
data = d['Data']
n_trial = int(np.asarray(d['noOfVideoSequences']).squeeze())
labels_v = np.asarray(data.ScoreValence).astype(int).reshape(-1)
labels_a = np.asarray(data.ScoreArousal).astype(int).reshape(-1)
labels_d = np.asarray(data.ScoreDominance).astype(int).reshape(-1)
rows, qc_rows = [], []
eps = 1e-12
for i in range(n_trial):
baseline = np.asarray(data.EEG.baseline[i], dtype=np.float64)
stim = np.asarray(data.EEG.stimuli[i], dtype=np.float64)
# 数据质量控制:非有限值线性修复(DREAMER 原始数据通常无缺失,这一步增强代码鲁棒性)
for arr in [baseline, stim]:
if not np.isfinite(arr).all():
for c in range(arr.shape[1]):
s = pd.Series(arr[:, c]).replace([np.inf, -np.inf], np.nan).interpolate(limit_direction='both')
arr[:, c] = s.fillna(s.median()).values
# 为使不同视频时长具有可比性,统一截取刺激段和基线段末 60 s;
# 末段更接近完成情绪诱发后的稳态区间,也显著降低变长序列带来的计算偏差。
use_n = min(60*fs, baseline.shape[0], stim.shape[0])
baseline_psd = baseline[-use_n:, :]
stim_psd = stim[-use_n:, :]
# Welch PSD:2 s 汉宁窗,50% 重叠;对非平稳 EEG 获得低方差谱估计
nperseg = min(256, baseline_psd.shape[0], stim_psd.shape[0])
noverlap = nperseg // 2
fb, pb = welch(baseline_psd, fs=fs, window='hann', nperseg=nperseg,
noverlap=noverlap, detrend='constant', axis=0, scaling='density')
fs_, ps = welch(stim_psd, fs=fs, window='hann', nperseg=nperseg,
noverlap=noverlap, detrend='constant', axis=0, scaling='density')
feat = {}
for band, (lo, hi) in BANDS.items():
bp_b = bandpower_from_psd(fb, pb, lo, hi)
bp_s = bandpower_from_psd(fs_, ps, lo, hi)
# 刺激相对基线的 dB 功率变化,抑制个体静息幅值差异
db = 10.0 * np.log10((bp_s + eps) / (bp_b + eps))
for ci, ch in enumerate(channels):
feat[f'{ch}_{band}'] = float(db[ci])
feat.update({
'subject': sid, 'trial': i + 1,
'age': float(np.asarray(data.Age).squeeze()),
'gender': as_str(data.Gender),
'baseline_sec': baseline.shape[0] / fs,
'stimulus_sec': stim.shape[0] / fs,
'V': int(labels_v[i]), 'A': int(labels_a[i]), 'D': int(labels_d[i])
})
rows.append(feat)
qc_rows.append({
'subject':sid, 'trial':i+1, 'fs':fs,
'baseline_samples':baseline.shape[0], 'stimulus_samples':stim.shape[0],
'channels':baseline.shape[1],
'baseline_finite_ratio':float(np.isfinite(baseline).mean()),
'stimulus_finite_ratio':float(np.isfinite(stim).mean())
})
return rows, qc_rows, channels
def load_and_extract():
all_rows, qc_rows, channels = [], [], None
for p in find_mat_files():
rows, qc, ch = extract_subject(p)
all_rows.extend(rows); qc_rows.extend(qc)
channels = ch
df = pd.DataFrame(all_rows)
qc = pd.DataFrame(qc_rows)
feature_cols = [f'{ch}_{b}' for b in BANDS for ch in channels]
# 稳健 Winsorization:对谱功率变化特征逐列压缩极端 1% 尾部,降低残余伪迹影响
raw = df[feature_cols].copy()
winsor_info = []
for c in feature_cols:
lo, hi = raw[c].quantile([0.01, 0.99])
before = ((raw[c] < lo) | (raw[c] > hi)).sum()
df[c] = raw[c].clip(lo, hi)
winsor_info.append({'feature':c, 'q01':lo, 'q99':hi, 'clipped_n':int(before)})
pd.DataFrame(winsor_info).to_csv(OUT_DIR/'winsorization.csv', index=False, encoding='utf-8-sig')
df.to_csv(OUT_DIR/'eeg_bandpower_features.csv', index=False, encoding='utf-8-sig')
qc.to_csv(OUT_DIR/'data_quality_control.csv', index=False, encoding='utf-8-sig')
return df, qc, feature_cols, channels
# ------------------------- 统计关联与 mRMR -------------------------
def benjamini_hochberg(pvals):
p = np.asarray(pvals, float)
n = len(p)
order = np.argsort(p)
ranked = p[order]
q = ranked * n / np.arange(1, n+1)
q = np.minimum.accumulate(q[::-1])[::-1]
q = np.clip(q, 0, 1)
out = np.empty(n)
out[order] = q
return out
def association_analysis(df, feature_cols):
rows=[]
X=df[feature_cols].values
for t in TARGETS:
y=df[t].values
mi=mutual_info_regression(X, y.astype(float), random_state=SEED)
rhos=[]; ps=[]
for j,c in enumerate(feature_cols):
rho,p=spearmanr(df[c].values,y)
rhos.append(float(rho)); ps.append(float(p))
q=benjamini_hochberg(ps)
mi_norm = mi / (mi.max()+1e-12)
for j,c in enumerate(feature_cols):
ch,band=c.split('_',1)
rows.append({'target':t,'feature':c,'channel':ch,'band':band,'region':REGION_MAP[ch],
'spearman_rho':rhos[j],'p':ps[j],'q_fdr':q[j],
'mutual_information':float(mi[j]),'mi_norm':float(mi_norm[j])})
assoc=pd.DataFrame(rows)
assoc.to_csv(OUT_DIR/'feature_target_associations.csv',index=False,encoding='utf-8-sig')
# 混合脑区贡献:绝对 Spearman + MI,经特征内归一后按脑区平均、脑区间归一
contr=[]
for t in TARGETS:
sub=assoc[assoc.target==t].copy()
sub['rho_norm']=sub.spearman_rho.abs()/(sub.spearman_rho.abs().max()+1e-12)
sub['hybrid']=0.5*sub['rho_norm']+0.5*sub['mi_norm']
r=sub.groupby('region')['hybrid'].mean()
r=r/r.sum()
for region,val in r.items():
contr.append({'target':t,'region':region,'contribution':float(val)})
contrib=pd.DataFrame(contr)
contrib.to_csv(OUT_DIR/'region_contribution.csv',index=False,encoding='utf-8-sig')
return assoc, contrib
def mrmr_select(X, y, feature_names, k=20):
"""互信息相关性 - Pearson 冗余惩罚的轻量 mRMR(连续有序评分版)。"""
X=np.asarray(X,float); y=np.asarray(y,float)
mi=mutual_info_regression(X,y,random_state=SEED)
rel=mi/(mi.max()+1e-12)
corr=np.abs(np.corrcoef(X,rowvar=False))
corr=np.nan_to_num(corr,nan=0.0)
selected=[]; remaining=list(range(X.shape[1]))
for _ in range(min(k,X.shape[1])):
if not selected:
idx=max(remaining,key=lambda j:rel[j])
else:
idx=max(remaining,key=lambda j: rel[j] - 0.45*np.mean(corr[j,selected]))
selected.append(idx); remaining.remove(idx)
return selected, [feature_names[i] for i in selected], rel[selected]
def multitask_mrmr_select(X, Y, feature_names, k=30):
"""三任务共享 mRMR:以三个情绪评分互信息的均值刻画共享相关性。"""
X=np.asarray(X,float); Y=np.asarray(Y,float)
scores=[]
for j in range(Y.shape[1]):
mi=mutual_info_regression(X,Y[:,j],random_state=SEED+j)
scores.append(mi/(mi.max()+1e-12))
rel=np.mean(scores,axis=0)
corr=np.abs(np.corrcoef(X,rowvar=False)); corr=np.nan_to_num(corr,nan=0.0)
selected=[]; remaining=list(range(X.shape[1]))
for _ in range(min(k,X.shape[1])):
if not selected:
idx=max(remaining,key=lambda z:rel[z])
else:
idx=max(remaining,key=lambda z: rel[z]-0.45*np.mean(corr[z,selected]))
selected.append(idx); remaining.remove(idx)
return selected,[feature_names[i] for i in selected],rel[selected]