简介:基于DEAP脑电数据集的情绪二分类算法,面向脑机接口与机器学习入门者,提供了一套从原始脑电信号到情绪类别判别的完整代码流程,涵盖快速傅里叶变换(FFT)频域处理、特征提取、数据预处理和分类模型调用等关键环节。压缩包共7个文件,以4个Python脚本为核心,分别对应FFT变换、特征提取、脑电绘图和流程整合,另附说明文档与附赠资源包,整体仅8KB,轻量易部署;目前已有134人学习,适合作为课程设计或科研入门参考。代码选用决策树、SVM和KNN三种经典分类器,均直接调用库实现,无需复杂调参即可复现二分类结果,预处理环节包含滤波、去伪迹与归一化等步骤,有助于提升分类可靠性;用户通过学习该项目,可掌握频域分析和传统机器学习在脑电数据上的应用,并以此作为后续引入深度模型的基线对比。
1. 基于DEAP脑电数据集的脑电情绪二分类:先别急着上深度学习
第一次跑DEAP数据集的脑电情绪二分类,我把32通道原始信号直接丢给一维卷积网络,调了半天参,准确率停在61%。同等特征下,一个线性SVM却跑到了79%。那次的教训很直接:基于DEAP脑电数据集的脑电情绪二分类算法,重点从来不是模型层数,而是你对信号做了什么。DEAP记录了32名被试观看情绪视频时的脑电与外围生理信号,每个试次是一分钟情感诱发,二分类任务通常把标签切成valence高/低或arousal高/低,再用脑电特征判断当前情绪属于哪一类。这篇文章按我踩过的坑,从数据加载讲到交叉验证和特征评估,适合正在做情绪识别、脑机接口课题的工程师或学生,也适合想快速验证某个特征是否有效的算法同学。
2. 先把DEAP数据集拆开:数据形态、标签对齐与baseline处理
2.1 用scipy.io读取mat文件:40通道、8064采样点怎么理解
DEAP数据集的核心是32个mat文件,文件名对应不同被试编号。每个文件里有两个关键字段:data和labels。先用一行代码把结构打出来,比什么都直观:
import numpy as np from scipy.io import loadmat seg = loadmat('s01.mat') print(seg['data'].shape) # (40, 40, 8064) print(seg['labels'].shape) # (40, 4)data的第一个维度是40个试次,第二个维度是40个通道,第三个维度是8064个采样点。8064来自128Hz采样率乘以63秒——每个试次实际是63秒而不是60秒。前3秒(384个采样点)是被试休息状态的baseline,后60秒是视频刺激段的脑电信号。labels的40行和试次一一对应,4列分别是valence、arousal、dominance、liking的评分,范围1到9。
读取后要立刻做一件事:把40通道里的后8个外周通道拿掉,只保留前32个EEG通道。DEAP采集时同时记录了眼电、肌电、呼吸等外周三通道,如果混进后续特征计算,模型会先学到眨眼和心率这些非脑电信息。常见做法是一开始就切片:
eeg = seg['data'][:, :32, :] # 只取前32个脑电通道 print(eeg.shape) # (40, 32, 8064)单个mat文件读入后占的内存不小,32个文件全部load进内存会吃掉好几个GB。如果只是验证特征方案,按被试逐个读取、提取特征后释放即可,不必一次性全载入。我习惯把每个被试的特征矩阵保存为npy文件,后面只加载特征,不再碰原始波形。
提示:DEAP官网还提供提取好特征的feature版本。复现示例论文可以用,但如果你想自定义频段、自定义baseline窗口或加新特征,建议直接用原始mat数据,feature版本是为某些基线实验定制的,自由度不够。
2.2 扣baseline的两种做法与一个顺序坑
DEAP每个试次的前3秒是没有情绪刺激的休息状态。如果不把这部分基线水平从刺激段里减掉,模型学到的可能不是"情绪诱发效果",而是"这个人静息状态下脑电长什么样"。个体基线差异在跨被试场景下尤其致命——同一个人的静息alpha功率可能是另一个人的两倍,模型一旦抓住这个差异,换个被试就失效。
最常见的做法是逐通道减去baseline段的均值:
SAMPLE_RATE = 128 BASELINE_SAMPLES = 3 * SAMPLE_RATE # 384 def remove_baseline_mean(trial): # trial shape: (32, 8064) baseline = trial[:, :BASELINE_SAMPLES].mean(axis=1, keepdims=True) return trial - baseline clean = np.array([remove_baseline_mean(t) for t in eeg])参数说明:keepdims=True保留通道维度,否则mean返回形状(32,)无法广播。算术均值对极端值敏感,如果某个通道前3秒有明显漂移,可以改用np.median(trial[:, :BASELINE_SAMPLES], axis=1, keepdims=True)。中位数对眨眼和突发噪声更鲁棒,代价是略微增加计算量。
真正容易踩的坑在顺序:先滤波还是先扣baseline。我的经验是先做带通滤波再扣baseline。原因是滤波会消除部分直流偏移和超低频漂移,让baseline均值更接近真实的静息水平;如果先扣baseline,滤波时信号边缘会产生振铃,反而引入新的基线偏差。顺序一旦固定,后面所有实验都保持一致,不要在换特征时就换顺序。
2.3 标签切分:valence/arousal选哪个,阈值怎么定
DEAP的标签是1到9的整数评分,情绪二分类要先把连续评分切成两类。很多人默认用5做阈值,因为5是量表中间值,但实际数据里评分分布并不以5为对称中心。有的被试给出的valence很少超过6,有的被试很少低于4,全局阈值会直接造成类别不平衡。
我建议先看分布再切:
valence = seg['labels'][:, 0] print('median:', np.median(valence)) print('bincount:', np.bincount(valence))二分类的默认策略是用训练集的中位数作为切分阈值,大于中位数归为正类,小于等于归为负类。中位数切分保证两类数量基本均衡,也避免了跨被试评分尺度不一致的问题。如果你执意用5,至少先确认正负样本比例,别等分类器全预测多数类时才发现问题。
valence和arousal我建议分开做。valence(愉悦度)的脑电区分度通常比arousal更稳定,尤其是额叶alpha不对称性这个经典特征;arousal更依赖心率、皮电等外周信号,单靠EEG做二分类难度更高。如果你的题目没有明确指定维度,从valence入手最容易跑出可信结果。至于"用被试内的数据切训练测试"还是"换被试去预测",这个选择直接影响结论的解读——前者验证特征区分度,后者验证算法泛化能力,后文会专门展开。
3. 信号预处理与特征提取:从原始波形到分类器能吃的特征
3.1 预处理管线:陷波、带通、坏通道检查
扣完baseline之后,原始信号还有三类污染:50Hz工频干扰、高频肌电伪迹、低频漂移。标准做法是陷波加带通滤波,我用filtfilt而不是sosfilt,因为零相位滤波不会引入群延迟,对后续时间窗对齐更友好:
from scipy.signal import butter, filtfilt, iirnotch def preprocess_epoch(epoch, sfreq=128, notch_freq=50): # epoch shape: (32, 8064) b_notch, a_notch = iirnotch(notch_freq, Q=30, fs=sfreq) x = filtfilt(b_notch, a_notch, epoch, axis=-1) b_band, a_band = butter(4, [4, 45], btype='bandpass', fs=sfreq) x = filtfilt(b_band, a_band, x, axis=-1) return x clean_data = np.array([preprocess_epoch(t) for t in clean])参数说明:iirnotch的Q值控制陷波带宽,Q越大带宽越窄,对正常信号损伤越小,但若工频频率有偏移则滤不干净。大多数地区电网是50Hz,少数是60Hz,拿不准时先对信号做FFT找峰值再设置。butter的阶数取4,阶数过高会让边缘振荡明显;filtfilt是零相位双向滤波,等效阶数翻倍到8,边缘处理比普通filter好得多。带通范围4-45Hz是DEAP论文里最常见的区间,下限滤掉缓慢基线漂移,上限滤掉肌电主导的高频噪声。
预处理完成后一定要做一次通道检查。我的习惯是计算每个通道的方差,打印出来扫一眼:某个通道的方差比其他通道大两个数量级,通常是电极接触不良或运动伪迹;某个通道方差接近0,说明信号断线。这些坏通道如果在特征阶段不剔除,会让标准化后的特征被单个通道主导,模型权重全堆在坏道上。
3.2 时域特征:统计量与Hjorth三参数
时域特征直接从时间序列上计算统计量,优点是计算快、物理意义明确,缺点是对baseline处理质量极其敏感。如果第2章没扣干净,时域特征的均值项基本等于个体基线,模型学的不是情绪而是人。
我常用的组合是每通道的均值、标准差、均方根,加上Hjorth三参数。Hjorth的activity就是方差,mobility反映信号斜率变化的快慢,complexity描述波形的不规则程度:
def hjorth(x): x = np.asarray(x, dtype=np.float64) dx = np.diff(x) ddx = np.diff(dx) activity = np.var(x) mobility = np.sqrt(np.var(dx) / (np.var(x) + 1e-12)) complexity = np.sqrt(np.var(ddx) / (np.var(dx) + 1e-12)) / (mobility + 1e-12) return np.array([activity, mobility, complexity]) def time_features(epoch): feats = [] for ch in epoch: rms = np.sqrt(np.mean(ch ** 2)) feats.append([np.mean(ch), np.std(ch), rms]) feats.append(hjorth(ch)) return np.concatenate(feats) f_temporal = np.array([time_features(e) for e in clean_data]) print(f_temporal.shape) # (40, 32 * 6) = (40, 192)代码里加1e-12是防止方差接近0的通道除零。一旦某个通道断线导致方差为0,mobility和complexity会算出极端值,标准化后依然污染模型。建议在特征提取前就把异常通道剔除,而不是靠模型自己扛。时域特征的维度是每通道6维,32通道共192维。
3.3 频域特征:频段功率与微分熵的取舍
脑电情绪识别最经典的特征是各频段的功率谱密度,尤其是theta、alpha、beta三个频段。用Welch方法估计PSD再按频段积分,是效率最高也最容易复现的方案:
from scipy.signal import welch BANDS = { 'delta': (1, 4), 'theta': (4, 8), 'alpha': (8, 13), 'beta': (13, 30), 'gamma': (30, 45), } def band_power_features(epoch, sfreq=128): feats = [] for ch in epoch: freqs, psd = welch(ch, fs=sfreq, nperseg=256, noverlap=128, window='hamming') for fmin, fmax in BANDS.values(): mask = (freqs >= fmin) & (freqs < fmax) feats.append(np.trapezoid(psd[mask], freqs[mask])) return np.array(feats) f_band = np.array([band_power_features(e) for e in clean_data]) print(f_band.shape) # (40, 160)参数说明:nperseg=256在128Hz采样率下对应2秒窗,频率分辨率0.5Hz,足以分清alpha(8-13Hz)和theta(4-8Hz)的边界。如果窗口缩短到128点,分辨率降到1Hz,8Hz处的alpha与7.5Hz的theta边界会混叠,特征区分度明显下降。noverlap=128表示50%重叠,窗数翻倍后PSD估计方差更小。低频频段比如delta只有1-4Hz,在0.5Hz分辨率下只有6个频率点,积分误差偏大,可以考虑对delta单独用更长窗,代价是特征提取耗时增加。
微分熵(DE)在许多情绪分类论文里几乎是标配。它的本质是假设信号在频带内近似高斯分布,用对数方差估计:
def de_features(epoch, sfreq=128): feats = [] for ch in epoch: for fmin, fmax in BANDS.values(): b, a = butter(4, [fmin, fmax], btype='bandpass', fs=sfreq) y = filtfilt(b, a, ch, axis=-1) feats.append(0.5 * np.log(2.0 * np.pi * np.e * np.var(y))) return np.array(feats) f_de = np.array([de_features(e) for e in clean_data]) print(f_de.shape) # (40, 160)注意微分熵和band power高度相关,二者同时加入会让特征冗余。我的做法是:band power做主体特征,DE只保留gamma频段,因为gamma段的传统功率估计方差大,DE的log压缩能压低极端值,两者叠加才有增量信息。特征不是越多越好,堆到五六百维后SVM的RBF核训练变慢,随机森林会抓到大量重复分支。
3.4 特征拼接与切窗策略:先跑整体还是先切窗
上面三个小节对每个试次得到一组特征向量:时域192维、频域160维、DE的gamma段32维,加起来384维。此时有两个路线:把整个60秒算成一个特征向量,每试次一个样本;或者把试次切成短窗,每窗提取特征,得到一组时间序列特征。
我建议先跑整体。全试次特征的特点是样本量小(32被试×40试次=1280条)、训练快、能快速判断特征是否有效。整体路线跑通后,再考虑切窗。切窗的收益是样本量增加,模型能捕捉情绪随时间变化的动态;代价是同一试次的相邻窗口高度相关,交叉验证时如果不按试次分组,会造成严重的样本泄漏。
如果决定切窗,常见参数是窗长4秒、步长2秒:
def sliding_windows(epoch, win_len=4, step=2, sfreq=128): win_len_samples = int(win_len * sfreq) step_samples = int(step * sfreq) starts = range(0, epoch.shape[1] - win_len_samples + 1, step_samples) return np.stack([epoch[:, s:s + win_len_samples] for s in starts]) # clean_data 已去掉baseline,只剩60秒 windowed = sliding_windows(clean_data[0]) # (29, 32, 512)这里60秒数据去掉384点baseline后剩7680点,窗长512点、步长256点,总共29个窗口。切窗后的窗口特征会被拼接成三维数组(试次数,窗口数,特征维度),后续建模必须把试次编号记牢,任何随机划分都要以试次为单位。
4. 特征标准化与二分类建模:参数、对比、评估协议
4.1 标准化的泄漏坑:fold内fit是最低要求
之前做过一个模拟项目,把所有样本合在一起做StandardScaler,再跑五折交叉验证,准确率87%。改成每折只fit训练集后,准确率掉到74%。这个落差不是分类器变弱了,而是之前的87%偷看了测试集的均值和方差,属于典型的数据泄漏。
正确的标准化方式是每个fold单独fit:
from sklearn.preprocessing import StandardScaler from sklearn.model_selection import StratifiedKFold from sklearn.svm import SVC from sklearn.metrics import accuracy_score, roc_auc_score X = np.hstack([f_temporal, f_band, f_de]) y = (seg['labels'][:, 0] > np.median(seg['labels'][:, 0])).astype(int) skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) accs, aucs = [], [] for tr_idx, te_idx in skf.split(X, y): scaler = StandardScaler().fit(X[tr_idx]) X_tr = scaler.transform(X[tr_idx]) X_te = scaler.transform(X[te_idx]) clf = SVC(kernel='rbf', C=1.0, gamma='scale', probability=True, random_state=42) clf.fit(X_tr, y[tr_idx]) accs.append(accuracy_score(y[te_idx], clf.predict(X_te))) aucs.append(roc_auc_score(y[te_idx], clf.predict_proba(X_te)[:, 1])) print(f'5-CV accuracy: {np.mean(accs):.3f}, AUC: {np.mean(aucs):.3f}')参数说明:gamma='scale'按1除以特征数量乘以方差自动计算gamma,比固定gamma值更适合高维特征。C=1.0是RBF-SVM的稳妥起点,后续可搜索[0.1, 1, 10]。probability=True启用Platt缩放计算概率,才能算AUC,只看准确率时可以关掉提升速度。random_state固定42是为了复现,换随机种子结果若有大幅波动,说明样本量或特征稳定性有问题。
4.2 分类器对比:SVM、随机森林与深度学习怎么选
RBF-SVM是DEAP二分类最稳的默认选择,尤其在特征维度300维附近、样本量1280时,SVM对高维稀疏结构敏感但不容易过拟合。随机森林不需要标准化,还自带特征重要性,方便做降维分析,但精度通常比SVM低几个点。深度学习在这类任务上不是首选,除非你做了切窗并引入时间维度。
| 分类器 | 标准化要求 | 特征重要性 | 训练速度 | 过拟合风险 | 适用阶段 |
|---|---|---|---|---|---|
| RBF-SVM | 需要 | 无 | 快 | 低 | 全试次特征主力模型 |
| 线性SVM | 需要 | 系数可解释 | 最快 | 低 | 特征筛选基线 |
| 随机森林 | 不需要 | 有 | 中 | 中 | 快速验证特征有效性 |
| LSTM/CNN | 建议 | 难以解释 | 慢 | 高 | 切窗时序建模 |
如果RBF-SVM和随机森林准确率都低于65%,先别急着换深度学习。DEAP全试次只有1280个样本,深度模型在这个规模上几乎没有优势。等全试次特征跑到75%以上,再考虑切窗后用LSTM建模时序信息。
随机森林的典型用法是看feature_importances_,找出排名前100的特征重新训练:
from sklearn.ensemble import RandomForestClassifier rf = RandomForestClassifier(n_estimators=200, max_depth=10, min_samples_leaf=2, random_state=42, n_jobs=-1) rf.fit(X_tr, y[tr_idx]) sorted_idx = np.argsort(rf.feature_importances_)[::-1] top100 = sorted_idx[:100]max_depth限制在10左右相当于强正则化,防止1280条样本上的过拟合。min_samples_leaf=2让叶子节点至少两个样本,对噪声更鲁棒。如果取前100维特征后AUC没有明显下降,说明原始特征里有大量冗余,可以放心压缩。
4.3 评估协议:五折交叉验证与留一被试验证
五折交叉验证是"试次级"划分,训练集和测试集可能来自同一个人,这属于被试内评估。它回答的问题是:如果知道一个人的标签先验,能否用他过去的试次预测未来的试次。这个设置能验证特征是否有效,但不能说明算法能否用于陌生人。
更严苛的是留一被试交叉验证(LOSO),每次留出一个被试的全部40个试次做测试,其余31个被试做训练:
from sklearn.model_selection import LeaveOneGroupOut groups = np.repeat(np.arange(32), 40) # 每个被试40个试次 logo = LeaveOneGroupOut() accs_loso = [] for tr_idx, te_idx in logo.split(X, y, groups=groups): scaler = StandardScaler().fit(X[tr_idx]) clf = SVC(kernel='rbf', C=1.0, gamma='scale') clf.fit(scaler.transform(X[tr_idx]), y[tr_idx]) acc = accuracy_score(y[te_idx], clf.predict(scaler.transform(X[te_idx]))) accs_loso.append(acc) print(f'LOSO accuracy: {np.mean(accs_loso):.3f}')groups数组长度必须等于X行数,这里32个被试各40个试次共1280条。pass内fit标准化只用了训练被试,测试被试的统计量完全没有参与。LOSO结果通常比五折低10到15个百分点,这是正常的,跨被试EEG分类本身就是难问题。如果你的LOSO准确率接近随机水平,先尝试每个被试单独做标准化,再看是否有个别被试的准确率拖后腿。某些被试情绪反应弱、注意力分散,单被试准确率低于随机是常见现象,这时候要报告分布而不是只报均值。
5. 情绪二分类的5个常见坑与排查思路
5.1 全局标准化泄漏,评估结果虚高
现象:对全量特征做StandardScaler后跑五折交叉验证,准确率85%;改成每折内部fit后掉到74%。
原因:全局fit把测试集的均值和方差写进了变换,模型在训练阶段已经间接看到测试分布。这个泄漏在RBF-SVM上格外严重,因为核函数完全依赖特征尺度。
解决:用Pipeline把标准化和分类器绑成整体,从流程上杜绝漏fit:
from sklearn.pipeline import make_pipeline from sklearn.model_selection import cross_val_score pipe = make_pipeline( StandardScaler(), SVC(kernel='rbf', C=1.0, gamma='scale') ) scores = cross_val_score(pipe, X, y, cv=skf, scoring='accuracy') print(scores.mean())Pipeline保证cross_val_score的每一折都在训练折内部执行fit,测试折只走transform。以后再写分类实验,scaling、PCA这些预处理步骤全部塞进Pipeline,不要再手动在外面做了。
5.2 baseline窗口没剔除,特征混入静息信号
现象:时域特征分类效果不错,换到频域特征后准确率反而下降;或者换被试后结果崩盘。
原因:特征提取时把前384个baseline采样点也算进去了。baseline是无刺激的休息状态,混入后模型学到的是"静息状态vs情绪状态"的二分类,而不是情绪高低分类,特征语义被污染。
解决:特征函数只处理baseline之后的60秒。先定义常量再切片:
STIM_START = 384 # 3秒 * 128Hz def band_power_features_on_stim(epoch, sfreq=128): stim = epoch[:, STIM_START:] # 继续执行 Welch 特征提取同时做一个负向验证:只对baseline段提取相同特征跑分类器,如果准确率显著高于0.5,说明你的pipeline没把静息信号隔离干净。
5.3 切窗样本泄漏:同一试次的窗进了两边
现象:全试次特征准确率75%,切窗后样本量暴增,交叉验证准确率跳到90%,但换LOSO直接崩到55%。
原因:每段60秒切成29个窗口后,相邻窗口高度相关。随机划分时,同一试次的窗口会同时出现在训练集和测试集,模型记住了视频片段特征而非情绪类别。
解决:切窗后的任何划分都必须以试次为最小单位:
trial_ids = np.repeat(np.arange(len(windowed_X)), 29) # 每个试次29个窗 logo = LeaveOneGroupOut() for tr_idx, te_idx in logo.split(windowed_X, windowed_y, groups=trial_ids): pass # 同一试次的所有窗口不会跨集合如果做普通五折,也要把trial_ids作为分组传给GroupKFold,而不是StratifiedKFold。
5.4 类别不平衡让accuracy失真
现象:valence按固定阈值5切分,某被试正负样本比变成1:4,模型把所有样本预测为负类,accuracy却高达80%。
原因:DEAP评分分布依被试而异,全局阈值和个体实际中位数错位。accuracy指标在类别不平衡时完全失真,多数类主导了得分。
解决:切阈值前先打印正负样本分布:
print(np.bincount(y))用训练集中位数替代固定阈值,评估时同时看AUC和F1:
from sklearn.metrics import f1_score f1 = f1_score(y[te_idx], y_pred)AUC对类别不平衡不敏感,F1能反映少数类的召回情况。如果AUC大于0.6但F1接近0,说明你的模型在牺牲少数类,需要考虑类别加权或过采样。
5.5 全试次平均掩盖情绪动态,结果反复横跳
现象:同一套代码重复跑,AUC在0.56到0.68之间大幅波动,没有稳定结论。
原因:60秒视频里情绪是动态变化的,全试次平均把所有时间点压缩成一个向量,个体差异被平均抹掉,同时放大单次伪迹段的影响。加上样本量只有1280,随机种子一变结果就会晃。
解决:先做切窗特征并逐窗预测,最后对试次内窗口做多数投票:
from scipy.stats import mode # window_preds shape: (num_trials, 29) trial_votes = mode(window_preds, axis=1).mode trial_pred = trial_votes.ravel()多数投票让每个试次的最终标签由29个窗口共同决定,稳定性远高于单一全试次特征。投票后准确率通常会略低于全试次最优结果,但方差会明显变小,结论更可信。
6. 进阶验证方法:从一条准确率到一整套可解释结论
跑通一套SVM二分类后,先别急着写结论,用三把尺子量一量。
第一把是时间窗迹线。把每个试次切成2秒窗、步长1秒,共59个窗口,逐窗提取频域特征并用训练好的分类器预测,画出准确率随时间的曲线。如果后30秒准确率持续跌向0.5,说明情绪诱发效应衰减,或者你的特征只捕捉到了刺激起始阶段的注意反应;如果前5秒就接近随机,可能是被试还没进入情绪状态,模型在纯噪声上瞎猜。时间窗迹线能告诉你模型真正依赖的是哪段信号,比单一准确率信息量大得多。
第二把是额叶不对称性检查。情绪研究有个经典假设:左侧额叶alpha功率与正性情绪相关,右侧额叶alpha功率与负性情绪相关。从特征矩阵里取出F3、F4通道的alpha频段功率,算不对称指数(F4-F3)/(F4+F3),分别看两类标签下的分布。如果两类的分布几乎完全重叠,说明你这个模型学的是整体arousal或个体基线,不是valence效价。这个检查能帮你判断特征方向对不对,避免拿着一个高准确率模型却不知道它在学什么。
第三把是负控实验。把通道索引随机打乱再提取同样的特征训练同样的分类器,如果随机通道特征的准确率接近真实特征,说明整条pipeline存在泄漏。我的做法是固定其他所有环节,只随机置换通道名,跑十次取均值作为最低可接受线。真实特征至少要比这个基线高出10个百分点。
我现在每拿一个新数据集,第一件事是打印标签分布和时间窗迹线,第二件事才是调参。先确认特征有信号,再谈模型复杂度;先解决泄漏和分布问题,再追求准确率数值。这套流程帮我避免过很多无效加班。希望这些经验也能帮你少踩几个坑,把DEAP脑电情绪二分类做成一个稳定、可复现的实验。
本文还有配套的精品资源,点击获取