📋 项目简介

                我们要做什么?

想象一个场景:

你戴着脑电帽坐在电脑前
屏幕上出现向左的箭头 ←
你就在脑海中想象"左手在动"
(注意:不是真的动,只是想!)

电脑分析你的脑电信号
自动判断:你在想左手还是右手?

这就是"运动想象脑机接口"!

🪜 第1级:数据初探

        🎯 目标

拿到新数据,就像拿到一本新书
不要急着分析,先翻一翻,看个大概:
  - 这本书有多少页?(数据有多长)
  - 字有多大?(采样率多高)
  - 是什么类型的书?(有多少通道)

         💻 完整代码

# ========== 环境设置 ==========

# matplotlib.use('TkAgg'):
#   告诉电脑用什么"画笔"来画图
#   TkAgg 是 Windows 系统自带的画笔,不需要额外安装
#   这行必须在 import pyplot 之前写
import matplotlib
matplotlib.use('TkAgg')

# pyplot:画图的工具箱
# plt 是大家约定俗成的简写
import matplotlib.pyplot as plt

# mne:脑电分析的核心工具包
import mne

# numpy:数学计算工具包
# np 是大家约定俗成的简写
import numpy as np

# os:处理文件路径的工具
import os

# warnings:控制警告信息
# 有时候程序会弹出警告(不是错误),我们用这行把它们隐藏
import warnings
warnings.filterwarnings('ignore')

# ========== 中文字体设置 ==========
# 让图表能显示中文,不然会变成方块 □
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("="*60)
print("🪜 第1级:数据初探")
print("="*60)

# ---------- 加载数据 ----------

# mne.datasets.sample.data_path():
#   找到 MNE 自带的示例数据在哪里
#   这个数据你之前下载过了,所以不会重新下载
sample_data_folder = mne.datasets.sample.data_path()

# os.path.join():安全地拼接文件路径
# Windows 用 \,Mac 用 /,这个函数自动处理
raw_fname = os.path.join(
    sample_data_folder,        # 数据文件夹
    'MEG',                     # MEG 子文件夹
    'sample',                  # sample 子文件夹
    'sample_audvis_raw.fif'    # 数据文件名
)

# read_raw_fif():读取脑电数据文件
# .fif 是 MNE 专用的脑电数据格式
# preload=False:先不全部加载到内存(省内存)
raw = mne.io.read_raw_fif(raw_fname, preload=False)

# pick_types(eeg=True, stim=True):
#   只保留两种通道:
#     eeg=True  → EEG通道(脑电信号,我们要分析的)
#     stim=True → 刺激通道(记录实验事件的,后面会用到)
# 就像从工具箱里只拿出需要的工具
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

# ---------- 问3个最基本的问题 ----------

print("\n📋 数据的'身份证'信息:")

# 问题1:有多少个通道?
# raw_eeg.ch_names 是通道名称的列表
# len() 计算列表中有多少个元素
print(f"  问题1:有多少个通道? → {len(raw_eeg.ch_names)} 个")

# 问题2:每秒采多少个点?
# raw_eeg.info['sfreq'] 是采样率
# 采样率 = 每秒记录多少个数据点
# 250Hz = 每秒250个点 = 每4毫秒一个点
print(f"  问题2:每秒采多少个点? → {raw_eeg.info['sfreq']} Hz")

# 问题3:录了多长时间?
# raw_eeg.times[-1] 是最后一个时间点的秒数
# 约等于总录制时长
print(f"  问题3:录了多长时间? → {raw_eeg.times[-1]:.0f} 秒")
print(f"          (约 {raw_eeg.times[-1]/60:.1f} 分钟)")

# ---------- 看一眼数据 ----------

# plot():画原始数据的时间序列图
# 横轴 = 时间(秒)
# 纵轴 = 电压(微伏)
print("\n👀 看一眼前10秒的数据...")
raw_eeg.plot(
    duration=10,        # 一次显示10秒
    n_channels=5,       # 只显示5个通道(太多看不清)
    scalings='auto',    # 自动调整显示比例
    block=True,         # 保持窗口打开
    title='这就是原始脑电数据 - 先混个脸熟'
)

# ---------- 看一眼频谱 ----------

# plot_psd():画功率谱密度图
# PSD = Power Spectral Density
# 
# 通俗理解:
#   就像把一首歌分解成"低音、中音、高音"各有多少
#   横轴 = 频率(Hz,低音在左,高音在右)
#   纵轴 = 能量(这个频率有多强)
print("\n👀 看一眼频谱...")
raw_eeg.plot_psd(
    fmin=0.5,           # 最低频率 0.5 Hz
    fmax=50,            # 最高频率 50 Hz
    picks='eeg',        # 只看EEG通道
    average=True,       # 取所有通道的平均值
    show=True
)
plt.suptitle('功率谱密度 - 看看哪些频率能量强', fontsize=14)
plt.show(block=True)

print("\n✅ 第1级完成!")
print("你现在能回答:")
print("  1. 数据有多少通道、多长、采样率多高")
print("  2. 脑电信号'看起来'像什么样(上下波动的线)")
print("  3. 低频能量比高频能量强(这是脑电的普遍特征)")

        📖 知识解释

                采样率是什么?

想象你在拍视频:

普通视频 = 30帧/秒 = 每秒30张照片
脑电采样 = 250Hz = 每秒250个数据点

采样率越高 → 数据越精细 → 文件越大
250Hz 意味着每 4 毫秒记录一次
(1000毫秒 ÷ 250 = 4毫秒)

                功率谱密度怎么看?

功率 ↑
     │
     │  ╱╲
     │ ╱  ╲           ← 这里能量高(低频)
     │╱    ╲___
     │         ╲___   ← 这里能量低(高频)
     │             ╲
     └────────────────→ 频率 (Hz)
     0   10   20   30   40   50

脑电的普遍规律:
  低频能量高(大脑的慢节奏活动)
  高频能量低(快节奏活动相对少)

🪜 第2级:电极位置

        🎯 目标

上一级我们看到一堆通道名叫 EEG 001、EEG 002...
这就像座位号,但不知道具体在哪里

这一级我们要:
  1. 给每个通道改个有意义的名字
  2. 知道每个电极在头皮上的位置
  3. 认识几个"关键电极"

        💻 完整代码

# ========== 环境设置 ==========
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import mne
import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("="*60)
print("🪜 第2级:电极位置")
print("="*60)

# ---------- 加载数据 ----------
sample_data_folder = mne.datasets.sample.data_path()
raw_fname = os.path.join(sample_data_folder, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(raw_fname, preload=False)
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

# ---------- 看看现在的通道名 ----------
print("\n📋 现在的通道名(看不出位置):")
eeg_chs = [ch for ch in raw_eeg.ch_names if ch.startswith('EEG')]
print(f"  前10个: {eeg_chs[:10]}")
print(f"  问题:EEG 001 在头顶还是后脑勺?看不出来!")

# ---------- 给电极改名字 ----------
# 
# make_standard_montage('standard_1020'):
#   创建一个"标准电极位置表"
#   里面记录了每个标准电极名的3D坐标
#   例如:'Fz' 在 (0, 0.07, 0) 位置
#         'Cz' 在 (0, 0, 0.07) 位置
montage = mne.channels.make_standard_montage('standard_1020')

# 从标准表中取相同数量的名字
standard_names = montage.ch_names[:len(eeg_chs)]

# dict(zip(旧名列表, 新名列表)):
#   把两个列表配对成字典
#   就像:{'EEG 001': 'Fz', 'EEG 002': 'Cz', ...}
rename_dict = dict(zip(eeg_chs, standard_names))

# rename_channels():批量重命名
raw_eeg.rename_channels(rename_dict)

# set_montage():设置每个电极的3D坐标
raw_eeg.set_montage(montage)

print(f"\n📋 改名后的通道名(能看出位置了):")
print(f"  前10个: {raw_eeg.ch_names[:10]}")

# ---------- 理解电极命名规则 ----------
print(f"\n📖 电极命名规则:")
print(f"  字母 = 脑区位置")
print(f"    F = Frontal   (额叶,前额)")
print(f"    C = Central   (中央,头顶)")
print(f"    P = Parietal  (顶叶,头顶后部)")
print(f"    T = Temporal  (颞叶,耳朵附近)")
print(f"    O = Occipital (枕叶,后脑勺)")
print(f"")
print(f"  数字 = 左右位置")
print(f"    奇数(1,3,5,7) = 左侧")
print(f"    偶数(2,4,6,8) = 右侧")
print(f"    z = zero = 中线")

# 举例
print(f"\n  举例:")
print(f"    Fz = 额叶中线")
print(f"    Cz = 头顶中央")
print(f"    C3 = 中央区左侧(左脑运动皮层)")
print(f"    C4 = 中央区右侧(右脑运动皮层)")

# ---------- 画出电极位置 ----------
print(f"\n👀 画出电极在头皮上的位置...")
raw_eeg.plot_sensors(
    show_names=True,    # 显示电极名字
    title='电极位置图 - 鼻子朝上',
    block=True
)

# ---------- 认识关键电极 ----------
print(f"\n📌 运动想象最关键的3个通道:")
print(f"   Cz = 头顶中央")
print(f"   C3 = 左脑运动皮层(控制右手!)")
print(f"   C4 = 右脑运动皮层(控制左手!)")
print(f"")
print(f"   为什么C3控制右手?")
print(f"   因为大脑是'对侧支配'的:")
print(f"   左脑 → 控制右手和右脚")
print(f"   右脑 → 控制左手和左脚")

print(f"\n✅ 第2级完成!")
print("你现在能回答:")
print("  1. Fz、Cz、Pz 分别在什么位置")
print("  2. C3 和 C4 哪个在左边")
print("  3. 为什么运动想象要看 C3 和 C4")

        📖 知识解释

                对侧支配是什么?

大脑控制身体是"交叉"的:

        大脑              身体
    ┌─────────┐
    │ 左脑    │──────→ 右手 ✋
    │         │
    │ 右脑    │──────→ 左手 ✋
    └─────────┘

所以:
  想象左手动 → 右脑活跃 → C4通道会变化
  想象右手动 → 左脑活跃 → C3通道会变化

🪜 第3级:事件提取

        🎯 目标

实验不是一直做下去的,而是:
  出现提示 → 被试想象动作 → 休息 → 下一个提示

我们需要知道:
  1. 每次提示是什么时候出现的?
  2. 提示的是"左手"还是"右手"?

这些信息记录在"刺激通道"中

        💻 完整代码

# ========== 环境设置 ==========
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import mne
import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("="*60)
print("🪜 第3级:事件提取")
print("="*60)

# ---------- 加载数据 ----------
sample_data_folder = mne.datasets.sample.data_path()
raw_fname = os.path.join(sample_data_folder, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(raw_fname, preload=False)
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

# ---------- 理解"刺激通道" ----------
print("\n📖 什么是刺激通道?")
print("  刺激通道不是真实的电极")
print("  它是一个'事件记录器'")
print("  平时值是 0")
print("  有刺激时变成对应的编号")
print("")
print("  就像实验助手的记录本:")
print("    第10秒:出现左手提示 → 记下编号1")
print("    第15秒:出现右手提示 → 记下编号2")
print("    第20秒:出现左手提示 → 记下编号1")
print("    ...")

# ---------- 找到刺激通道 ----------
stim_chs = [ch for ch in raw_eeg.ch_names if 'STI' in ch]
print(f"\n📋 刺激通道名称: {stim_chs}")

# ---------- 提取事件 ----------
# find_events():从刺激通道中自动找到所有事件
# 返回一个数组,每行 = [采样点编号, 持续时间, 事件编号]
events = mne.find_events(raw_eeg, stim_channel='STI 014')

print(f"\n📋 提取结果:")
print(f"  共找到 {len(events)} 个事件")
print(f"  事件数组格式:[采样点, 持续时间, 事件编号]")

# 看前5个事件
print(f"\n  前5个事件:")
for i in range(5):
    sample = events[i, 0]  # 第0列:采样点编号
    duration = events[i, 1]  # 第1列:持续时间
    code = events[i, 2]  # 第2列:事件编号
    
    # 采样点编号 ÷ 采样率 = 时间(秒)
    time_sec = sample / raw_eeg.info['sfreq']
    print(f"    第{time_sec:.0f}秒: 事件编号={code}")

# ---------- 理解事件编号 ----------
print(f"\n📋 这个实验有4种刺激:")
print(f"  编号1 = 听觉/左耳 = 我们用来类比'想象左手'")
print(f"  编号2 = 听觉/右耳 = 我们用来类比'想象右手'")
print(f"  编号3 = 视觉/左眼 = 本次不用")
print(f"  编号4 = 视觉/右眼 = 本次不用")

# ---------- 给事件起名字 ----------
# event_id 字典:{名称: 编号}
# 以后就可以用名称来索引,不用记编号了
event_id = {
    '条件A(类比左手)': 1,
    '条件B(类比右手)': 2,
}

# ---------- 统计 ----------
print(f"\n📊 事件统计:")
for name, code in event_id.items():
    # np.sum(events[:,2] == code):统计编号为code的行数
    count = np.sum(events[:, 2] == code)
    print(f"  {name}: {count} 次")

# ---------- 可视化事件 ----------
print(f"\n👀 画出事件分布图...")
mne.viz.plot_events(
    events,
    sfreq=raw_eeg.info['sfreq'],
    first_samp=raw_eeg.first_samp,
    event_id=event_id,
    show=True
)
plt.suptitle('实验事件分布 - 每一竖线是一次刺激', fontsize=14)
plt.show(block=True)

print(f"\n✅ 第3级完成!")
print("你现在能回答:")
print("  1. 刺激通道是做什么的")
print("  2. 事件数组的3列分别是什么")
print("  3. 每种条件有多少次")

        📖 知识解释

                事件数组的结构:

events = [
    [15000, 0, 1],    ← 第1个事件
    [23000, 0, 2],    ← 第2个事件
    [31000, 0, 1],    ← 第3个事件
    ...
]

每一行 = [采样点编号, 持续时间, 事件编号]

第1个事件:
  采样点15000 → 15000÷250Hz = 第60秒
  持续时间0 → 瞬时事件
  事件编号1 → 左手提示

就像:
  第60秒:左手!
  第92秒:右手!
  第124秒:左手!
  ...

🪜 第4级:数据预处理

        🎯 目标

原始数据就像一个嘈杂的菜市场:
  有你要听的人说话(脑电信号)
  有空调嗡嗡声(低频漂移)
  有远处的高频噪音(肌肉活动)

预处理 = 把噪音去掉,只留你想听的声音

        💻 代码

# ========== 第4级:数据预处理 ==========
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import mne
import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("="*60)
print("🪜 第4级:数据预处理 - 把数据'洗干净'")
print("="*60)

# --- 加载数据 ---
sample_data_folder = mne.datasets.sample.data_path()
raw_fname = os.path.join(sample_data_folder, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(raw_fname, preload=False)
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

# 重命名通道(第2级学的)
eeg_names = [ch for ch in raw_eeg.ch_names if ch.startswith('EEG')]
montage = mne.channels.make_standard_montage('standard_1020')
standard_names = montage.ch_names[:len(eeg_names)]
raw_eeg.rename_channels(dict(zip(eeg_names, standard_names)))
raw_eeg.set_montage(montage)

# ⚠️ 必须先加载到内存
raw_eeg.load_data()

# --- 看"洗之前"的频谱 ---
print("\n🔍 洗之前:看原始频谱")
raw_eeg.plot_psd(fmin=0.5, fmax=50, picks='eeg', average=True, show=True)
plt.suptitle('洗之前 - 所有频率都有', fontsize=14)
plt.show(block=True)

# --- 带通滤波 7-30 Hz ---
print("\n🧹 带通滤波(7-30 Hz)...")
print("  去掉<7Hz的慢波(出汗、漂移)")
print("  去掉>30Hz的快波(肌肉噪声)")
print("  保留mu节律(8-12Hz)和beta节律(13-30Hz)")
raw_eeg.filter(l_freq=7, h_freq=30, picks='eeg', verbose=False)
print("  ✅ 滤波完成")

# --- 看"洗之后"的频谱 ---
print("\n🔍 洗之后:看滤波后频谱")
raw_eeg.plot_psd(fmin=0.5, fmax=50, picks='eeg', average=True, show=True)
plt.suptitle('洗之后 - 只保留了7-30 Hz', fontsize=14)
plt.show(block=True)

# --- 平均参考 ---
print("\n🧹 平均参考...")
print("  每个通道 = 原值 - 所有通道平均值")
raw_eeg.set_eeg_reference('average', verbose=False)
print("  ✅ 平均参考完成")

print(f"\n✅ 第4级完成!")

        📖 知识解释

                滤波通俗理解:

滤波前(原始数据):
  ├── 0.1-7Hz:慢波(出汗、漂移)    ← 去掉!
  ├── 7-30Hz:mu+beta节律           ← 保留!
  └── 30-200Hz:肌电、噪声          ← 去掉!

就像调收音机,只听想听的频道

                平均参考通俗理解:

量身高的问题:
  ❌ 每个人站在不同高度的台阶上 → 无法比较
  ✅ 所有人都站在同一个平面上 → 公平比较

平均参考 = 统一测量基准
每个通道 = 原值 - 所有通道平均值

🪜 第5级:数据分段

        🎯 目标

连续数据就像一整段录音
但我们需要分析的是"每次刺激前后的反应"

分段 = 把每次刺激前后的一小段时间"切"出来

        💻 代码

# ========== 第5级:数据分段 ==========
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import mne
import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("="*60)
print("🪜 第5级:数据分段(Epochs)")
print("="*60)

# --- 加载+预处理(第1-4级) ---
sample_data_folder = mne.datasets.sample.data_path()
raw_fname = os.path.join(sample_data_folder, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(raw_fname, preload=False)
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

eeg_names = [ch for ch in raw_eeg.ch_names if ch.startswith('EEG')]
montage = mne.channels.make_standard_montage('standard_1020')
standard_names = montage.ch_names[:len(eeg_names)]
raw_eeg.rename_channels(dict(zip(eeg_names, standard_names)))
raw_eeg.set_montage(montage)
raw_eeg.load_data()
raw_eeg.filter(l_freq=7, h_freq=30, picks='eeg', verbose=False)
raw_eeg.set_eeg_reference('average', verbose=False)

# --- 提取事件(第3级) ---
events = mne.find_events(raw_eeg, stim_channel='STI 014')
event_id = {'条件A': 1, '条件B': 2}

# --- 理解分段 ---
print("\n📖 分段的时间轴:")
print("    -0.2秒 ─── 0秒 ─────── 1.0秒")
print("     ↑         ↑           ↑")
print("   基线期    刺激点      反应期")
print("")
print("  基线期(-0.2~0秒):刺激前的'平静状态'")
print("  反应期(0~1.0秒):刺激后的'大脑反应'")
print("  基线校正:每个分段减去基线期的平均值")
print("  效果:所有分段从同一起跑线开始")

# --- 创建Epochs ---
epochs = mne.Epochs(
    raw_eeg, events, event_id=event_id,
    tmin=-0.2, tmax=1.0,
    baseline=(-0.2, 0),
    reject=dict(eeg=150e-6),
    preload=True, verbose=False
)

print(f"\n📊 分段结果:")
print(f"  总分段数: {len(epochs)}")
print(f"  条件A: {len(epochs['条件A'])} 个")
print(f"  条件B: {len(epochs['条件B'])} 个")
print(f"  数据形状: {epochs.get_data().shape}")
print(f"    (分段数, 通道数, 时间点数)")

# --- 可视化 ---
print(f"\n👀 前10个分段...")
epochs[:10].plot(n_epochs=10, n_channels=5, scalings='auto', show=True)
plt.suptitle('每个分段 = 一次刺激的数据快照', fontsize=14)
plt.show(block=True)

print(f"\n✅ 第5级完成!")

        📖 知识解释

                Epochs 三维结构:

epochs.get_data() 返回三维数组:
  (分段数, 通道数, 时间点数)

例子:(144, 60, 301)
  144个分段 × 60个通道 × 301个时间点

就像一摞卡片:
  每张卡片 = 一个分段
  卡片上 = 60行 × 301列的表格

                基线校正通俗理解:

问题:不同分段可能在不同"高度"

分段1:~~~~~/~~~~~    整体偏上(+20 μV)
分段2:___/‾‾‾‾‾___    整体偏下(-10 μV)

校正后:
分段1:___/‾‾‾‾‾___    都从0开始
分段2:___/‾‾‾‾‾___    都从0开始

🪜 第6级:ERP分析

        🎯 目标

单个分段噪声太大,看不清
把所有分段叠加平均 → 噪声抵消 → 真正的大脑反应浮现

这就是 ERP(事件相关电位)

        💻 代码

# ========== 第6级:ERP分析 ==========
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import mne
import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("="*60)
print("🪜 第6级:ERP分析 - 大脑的'平均反应'")
print("="*60)

# --- 加载+预处理+分段(第1-5级) ---
sample_data_folder = mne.datasets.sample.data_path()
raw_fname = os.path.join(sample_data_folder, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(raw_fname, preload=False)
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

eeg_names = [ch for ch in raw_eeg.ch_names if ch.startswith('EEG')]
montage = mne.channels.make_standard_montage('standard_1020')
standard_names = montage.ch_names[:len(eeg_names)]
raw_eeg.rename_channels(dict(zip(eeg_names, standard_names)))
raw_eeg.set_montage(montage)
raw_eeg.load_data()
raw_eeg.filter(l_freq=7, h_freq=30, picks='eeg', verbose=False)
raw_eeg.set_eeg_reference('average', verbose=False)

events = mne.find_events(raw_eeg, stim_channel='STI 014')
event_id = {'条件A': 1, '条件B': 2}

epochs = mne.Epochs(raw_eeg, events, event_id=event_id,
                    tmin=-0.2, tmax=1.0, baseline=(-0.2, 0),
                    reject=dict(eeg=150e-6), preload=True, verbose=False)

# --- 叠加平均 ---
print("\n📖 叠加平均原理:")
print("  条件A有72个分段,每个都有随机噪声")
print("  72个取平均 → 噪声抵消 → 真正的反应浮现")
print("  就像拍100张照片叠在一起,噪点消失")

evoked_A = epochs['条件A'].average()
evoked_B = epochs['条件B'].average()

print(f"\n  条件A: {evoked_A.nave} 个分段 → 1个ERP")
print(f"  条件B: {evoked_B.nave} 个分段 → 1个ERP")

# --- 画ERP波形 ---
print(f"\n👀 画Cz通道的ERP波形...")
plot_ch = 'Cz' if 'Cz' in evoked_A.ch_names else evoked_A.ch_names[0]

fig, ax = plt.subplots(figsize=(12, 6))
times_ms = evoked_A.times * 1000
ax.plot(times_ms, evoked_A.get_data(picks=plot_ch)[0]*1e6, 'b-', linewidth=2, label='条件A')
ax.plot(times_ms, evoked_B.get_data(picks=plot_ch)[0]*1e6, 'r-', linewidth=2, label='条件B')
ax.axvline(x=0, color='k', linestyle='--', alpha=0.5, label='刺激点')
ax.axhline(y=0, color='k', linestyle='-', alpha=0.3)
ax.set_xlabel('时间 (毫秒)', fontsize=12)
ax.set_ylabel('振幅 (微伏)', fontsize=12)
ax.set_title(f'ERP波形对比 - {plot_ch}通道', fontsize=14, fontweight='bold')
ax.legend(fontsize=11)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('erp_comparison.png', dpi=150)
print("✅ 已保存为 erp_comparison.png")
plt.show(block=True)

# --- 脑地形图 ---
print(f"\n👀 画出脑地形图...")
print("  红色=正电压,蓝色=负电压")

evoked_A.plot_topomap(
    times=[0.10, 0.15],
    ch_type='eeg',
    show=True,
    average=0.02,
    time_unit='ms'
)
plt.suptitle('脑地形图 - 头皮上的电压分布', fontsize=14)
plt.show(block=True)

print(f"\n✅ 第6级完成!")

        📖 知识解释

                叠加平均为什么有效?

假设:
  真正的脑电反应:每次刺激后都出现 +2 μV
  随机噪声:每次不同(+3, -1, -2, +2...)

分段1:信号(+2) + 噪声(+3) = +5
分段2:信号(+2) + 噪声(-1) = +1
分段3:信号(+2) + 噪声(-2) =  0
分段4:信号(+2) + 噪声(+2) = +4
...
平均:(+5 + 1 + 0 + 4 + ...) / 72 ≈ +2

随机噪声平均后趋近于0,信号浮现!

🪜 第7级:时频分析(ERD/ERS)

        🎯 目标

上一级我们看了 ERP(事件相关电位)
ERP 告诉我们:刺激后,大脑的电压怎么变化?

但 ERP 有个局限:
  它只能看到"时间 × 振幅"
  就像听歌时只能听到音量变化
  听不出是高音还是低音

时频分析 = 时间 × 频率 × 功率
  就像看乐谱:
    横轴 = 时间(什么时候)
    纵轴 = 频率(什么音高)
    颜色 = 功率(多大声)

        📖 什么是 ERD/ERS?

ERD = Event-Related Desynchronization(事件相关去同步)
    = 某个频率的功率降低了
    = 大脑变活跃了
    = 图上显示为蓝色

ERS = Event-Related Synchronization(事件相关同步)
    = 某个频率的功率升高了
    = 大脑变抑制了
    = 图上显示为红色

通俗理解:
  大脑在"休息"时,很多神经元同步活动 → 功率高
  大脑在"干活"时,神经元各干各的 → 功率降低 → ERD

运动想象时:
  想象左手动 → 右脑运动皮层活跃 → C4通道出现ERD(蓝色)
  想象右手动 → 左脑运动皮层活跃 → C3通道出现ERD(蓝色)

        💻 完整代码

# ========== 第7级:时频分析(ERD/ERS) ==========
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import mne
import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 60)
print("🪜 第7级:时频分析(ERD/ERS)")
print("=" * 60)

# --- 加载+预处理+分段(第1-5级的内容,这里合并在一起) ---
sample_data_folder = mne.datasets.sample.data_path()
raw_fname = os.path.join(sample_data_folder, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(raw_fname, preload=False)
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

# 重命名通道
eeg_names = [ch for ch in raw_eeg.ch_names if ch.startswith('EEG')]
montage = mne.channels.make_standard_montage('standard_1020')
standard_names = montage.ch_names[:len(eeg_names)]
raw_eeg.rename_channels(dict(zip(eeg_names, standard_names)))
raw_eeg.set_montage(montage)

# 预处理
raw_eeg.load_data()
raw_eeg.filter(l_freq=7, h_freq=30, picks='eeg', verbose=False)
raw_eeg.set_eeg_reference('average', verbose=False)

# 提取事件
events = mne.find_events(raw_eeg, stim_channel='STI 014')
event_id = {'条件A': 1, '条件B': 2}

# 创建Epochs(时频分析需要更长的分段)
epochs = mne.Epochs(raw_eeg, events, event_id=event_id,
                    tmin=-0.5, tmax=1.5, baseline=None,
                    reject=dict(eeg=150e-6), preload=True, verbose=False)

# ===== 时频分析 =====
print("\n📖 时频分析 vs ERP:")
print("  ERP:时间 × 振幅(只看'音量')")
print("  时频:时间 × 频率 × 功率(还能看'音调')")
print("  蓝色(ERD)=功率降低=大脑活跃")
print("  红色(ERS)=功率升高=大脑抑制")

# 选Cz通道(头顶中央)
ch = 'Cz' if 'Cz' in epochs.ch_names else epochs.ch_names[0]
freqs = np.arange(7, 31, 1)  # 分析 7-30 Hz

print(f"\n⏳ 计算 {ch} 通道的时频表示...")

# 🔑 Morlet小波变换
# 小波变换 = 同时分析时间和频率的方法
# 就像用不同大小的"放大镜"看信号
#   大放大镜 → 看低频(慢变化)
#   小放大镜 → 看高频(快变化)
power_A = mne.time_frequency.tfr_morlet(
    epochs['条件A'].copy().pick([ch]),  # 只分析Cz通道
    freqs=freqs,                         # 要分析的频率
    n_cycles=freqs/2,                    # 小波周期数
    return_itc=False,                    # 不计算相位
    average=True,                        # 对所有trial取平均
    verbose=False
)

power_B = mne.time_frequency.tfr_morlet(
    epochs['条件B'].copy().pick([ch]),
    freqs=freqs, n_cycles=freqs/2,
    return_itc=False, average=True, verbose=False
)

# 提取数据
data_A = power_A.data[0]  # 形状: (频率数, 时间点数)
data_B = power_B.data[0]
times = power_A.times

# 🔑 基线校正
# 和ERP的基线校正同理:用刺激前的一段时间做参考
# 这里用 -0.4 到 -0.1 秒作为基线
mask = (times >= -0.4) & (times <= -0.1)
baseline_A = np.mean(data_A[:, mask], axis=1, keepdims=True)
baseline_B = np.mean(data_B[:, mask], axis=1, keepdims=True)

# ERD/ERS = (数据 - 基线) / 基线 × 100%
# 正值 = 功率升高 = ERS
# 负值 = 功率降低 = ERD
erd_A = (data_A - baseline_A) / baseline_A * 100
erd_B = (data_B - baseline_B) / baseline_B * 100

print(f"  ERD/ERS 范围: {erd_A.min():.0f}% ~ {erd_A.max():.0f}%")

# ===== 画图 =====
print(f"\n👀 绘制 ERD/ERS 对比图...")
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

# 左图:条件A
im1 = axes[0].imshow(
    erd_A,                              # 数据
    aspect='auto',                      # 自动调整宽高比
    origin='lower',                     # 原点在左下角
    cmap='RdBu_r',                      # 颜色:红-白-蓝(_r表示反转)
    extent=[times[0], times[-1],        # X轴范围
            freqs[0], freqs[-1]],       # Y轴范围
    vmin=-50, vmax=50                   # 颜色范围 ±50%
)
axes[0].axvline(x=0, color='k', linestyle='--', linewidth=1, label='刺激点')
axes[0].set_title('条件A', fontsize=12, fontweight='bold')
axes[0].set_xlabel('时间 (秒)')
axes[0].set_ylabel('频率 (Hz)')
axes[0].legend(fontsize=8, loc='upper right')

# 右图:条件B
im2 = axes[1].imshow(erd_B, aspect='auto', origin='lower', cmap='RdBu_r',
                     extent=[times[0], times[-1], freqs[0], freqs[-1]],
                     vmin=-50, vmax=50)
axes[1].axvline(x=0, color='k', linestyle='--', linewidth=1, label='刺激点')
axes[1].set_title('条件B', fontsize=12, fontweight='bold')
axes[1].set_xlabel('时间 (秒)')
axes[1].set_ylabel('频率 (Hz)')
axes[1].legend(fontsize=8, loc='upper right')

# 颜色条
cbar = fig.colorbar(im2, ax=axes.ravel().tolist(), shrink=0.6, pad=0.08)
cbar.set_label('ERD/ERS (%)', fontsize=10)

plt.suptitle(f'ERD/ERS 对比({ch}通道)\n蓝=ERD(大脑活跃)  红=ERS(大脑抑制)', 
             fontsize=14, fontweight='bold')
plt.tight_layout()
plt.savefig('07_erd_ers.png', dpi=150, bbox_inches='tight')
print("✅ 已保存为 07_erd_ers.png")
plt.show(block=True)

print(f"\n✅ 第7级完成!")

        📖 代码详解

                1. 什么是 Morlet 小波?

小波变换 = 同时分析时间和频率的工具

想象你有一排放大镜:
  大放大镜(低频)→ 看大范围,但看不清细节
  小放大镜(高频)→ 看小范围,细节清晰

Morlet小波 = 一种特定形状的"放大镜"
  由一个正弦波(提供频率信息)
  乘以一个高斯窗(限制时间范围)

n_cycles 控制放大镜的大小:
  n_cycles=3.5(7Hz时)→ 大放大镜,看低频
  n_cycles=15(30Hz时)→ 小放大镜,看高频
问题:如何同时看到"时间"和"频率"?

傅里叶:只看频率,不看时间
  信号 → [傅里叶] → 哪些频率强?
  "10Hz很强" ← 但不知道什么时候强

短时傅里叶:固定窗口看时间和频率
  信号 → [固定窗口] → 每个窗口内什么频率强?
  "第1秒10Hz强,第2秒20Hz强"
  但窗口大小固定,低频看不清,高频不够快

Morlet 小波:自适应窗口看时间和频率
  信号 → [小窗口看高频 + 大窗口看低频] → 精确!
  "精确到毫秒的频率变化"
  低频用大窗口(精确频率)
  高频用小窗口(精确时间)

        Morlet 小波就像一个聪明的"音乐分析器",它知道低频声音要仔细听一会儿才能确定音高,高频声音可以快速判断。所以它用不同大小的"耳朵"去听不同频率的声音,最终画出完整的"乐谱"——这就是我们第7级画的 ERD/ERS 时频图。

                2. 基线校正怎么做的?

和 ERP 的基线校正同理:

基线期:-0.4 到 -0.1 秒(刺激前)
  → 这段时间没有刺激,是"安静状态"

每个频率都计算基线期的平均功率
然后用这个平均值做参考:
  ERD/ERS = (刺激后功率 - 基线功率) / 基线功率 × 100%

例如:
  基线期 Alpha(10Hz) 功率 = 100
  刺激后 Alpha(10Hz) 功率 = 70
  ERD/ERS = (70-100)/100 × 100% = -30%
  负值 = ERD = 功率降低了30%

                3. 时频图怎么看?

频率(Hz) ↑
    30 ┤
       │     ░░
    20 ┤    ░░░░
       │   ░░░░░░  ← 蓝色=ERD(大脑活跃)
    10 ┤  ░░░░░░░░
       │ ░░░░░░░░░░
     7 ┤────────────────→ 时间(秒)
      -0.5   0   0.5   1.0   1.5
              ↑
           刺激点

横轴 = 时间(什么时候)
纵轴 = 频率(什么频率在变化)
颜色 = ERD/ERS 百分比

蓝色 = 负值 = ERD = 功率降低 = 大脑活跃
红色 = 正值 = ERS = 功率升高 = 大脑抑制
白色 = 接近0 = 没有变化

🪜 第8级:CSP+LDA分类——让机器学习区分大脑状态

        🎯 目标

前7级我们一直在"看"数据:
  看波形、看频谱、看地形图、看时频图...

这一级我们要做一件新的事情:
  让机器"学习"如何区分两种大脑状态!

具体来说:
  输入:条件A的脑电 和 条件B的脑电
  输出:机器判断这是条件A还是条件B

这就是"分类"——脑机接口的核心!

        📖 核心概念

                CSP 是什么?

CSP = Common Spatial Patterns(共空间模式)

通俗理解:找到"最佳观察角度"

想象你在一个嘈杂的房间里:
  左边有个人在说中文
  右边有个人在说英文
  你站在中间,两个声音混在一起

CSP 就像找到两个"最佳听音位置":
  位置1:离左边近 → 中文清楚,英文模糊
  位置2:离右边近 → 英文清楚,中文模糊

脑电中的 CSP:
  我们有22个电极(22个"麦克风")
  CSP 找到一种"重新组合"电极的方法
  让条件A和条件B的差异最大化

                用数学理解 CSP:

原始数据:22个电极的信号
  Ch1, Ch2, Ch3, ..., Ch22

CSP 创建"虚拟电极":
  虚拟电极1 = w1×Ch1 + w2×Ch2 + ... + w22×Ch22
  虚拟电极2 = w1'×Ch1 + w2'×Ch2 + ... + w22'×Ch22
  
  权重(w)是 CSP 自动计算的
  目的是让虚拟电极1对条件A敏感
        虚拟电极2对条件B敏感

                LDA 是什么?

LDA = Linear Discriminant Analysis(线性判别分析)

通俗理解:画一条线,把两类分开

想象桌上有两种水果:
  苹果:红色、圆的、直径约8cm
  橘子:橙色、圆的、直径约7cm

LDA 找到一个"最佳分界线":
  颜色红 + 直径大 → 苹果
  颜色橙 + 直径小 → 橘子

脑电中的 LDA:
  CSP 提取了4个特征值
  LDA 在这4个特征上画一条线(实际上是超平面)
  线的一边是条件A,另一边是条件B

                用图理解 LDA:

  CSP特征2
    ↑
    │  ● ●
    │ ● ● ●       条件A(比如左手)
    │  ● ●
    │  ─ ─ ─ ─ ─ ← LDA分界线
    │        ▲ ▲
    │       ▲ ▲ ▲  条件B(比如右手)
    │        ▲ ▲
    └──────────────→ CSP特征1

分界线左边 → 预测为条件A
分界线右边 → 预测为条件B

                什么是交叉验证?

交叉验证 = 测试模型是否真的学会了

问题:如果考试题和练习题一模一样
      → 学生可能只是背答案,不是真懂

解决:把数据分成两份
     训练集(80%):用来学习
     测试集(20%):用来考试

这样做10次,每次换不同的80%和20%
取10次的平均准确率

就像:
  10次模拟考试,每次考题不同
  平均分反映了真实水平

        💻 完整代码

# ========== 第8级:CSP+LDA分类 ==========
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import mne
import numpy as np
import os
import warnings
warnings.filterwarnings('ignore')

# 🔑 导入机器学习工具
# Pipeline:流水线,把多个步骤串起来
from sklearn.pipeline import Pipeline
# LDA:线性判别分析分类器
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis as LDA
# cross_val_score:交叉验证评分
# ShuffleSplit:随机划分训练集和测试集
from sklearn.model_selection import cross_val_score, ShuffleSplit
# CSP:共空间模式,脑电特征提取
from mne.decoding import CSP

plt.rcParams['font.sans-serif'] = ['Microsoft YaHei', 'SimHei']
plt.rcParams['axes.unicode_minus'] = False

print("=" * 60)
print("🪜 第8级:CSP+LDA分类 - 让机器学习区分")
print("=" * 60)

# --- 加载+预处理+分段(第1-5级的内容) ---
sample_data_folder = mne.datasets.sample.data_path()
raw_fname = os.path.join(sample_data_folder, 'MEG', 'sample', 'sample_audvis_raw.fif')
raw = mne.io.read_raw_fif(raw_fname, preload=False)
raw_eeg = raw.copy().pick_types(eeg=True, stim=True)

eeg_names = [ch for ch in raw_eeg.ch_names if ch.startswith('EEG')]
montage = mne.channels.make_standard_montage('standard_1020')
standard_names = montage.ch_names[:len(eeg_names)]
raw_eeg.rename_channels(dict(zip(eeg_names, standard_names)))
raw_eeg.set_montage(montage)
raw_eeg.load_data()
raw_eeg.filter(l_freq=7, h_freq=30, picks='eeg', verbose=False)
raw_eeg.set_eeg_reference('average', verbose=False)

events = mne.find_events(raw_eeg, stim_channel='STI 014')
event_id = {'条件A': 1, '条件B': 2}

epochs = mne.Epochs(raw_eeg, events, event_id=event_id,
                    tmin=-0.2, tmax=1.0, baseline=(-0.2, 0),
                    reject=dict(eeg=150e-6), preload=True, verbose=False)

# ===== 理解分类流程 =====
print("\n📖 分类流程(通俗版):")
print("  ┌─────────────────────────────────────────┐")
print("  │ 原始脑电(22通道 × 301时间点)          │")
print("  │         ↓                               │")
print("  │ CSP 提取特征(22通道 → 4个特征值)      │")
print("  │         ↓                               │")
print("  │ LDA 分类(4个特征 → 预测类别)          │")
print("  │         ↓                               │")
print("  │ 输出:这是条件A还是条件B?               │")
print("  └─────────────────────────────────────────┘")

# ===== 准备数据 =====
print("\n📊 准备分类数据...")

# 只取两个条件的数据
epochs_binary = epochs['条件A', '条件B']

# X:数据(脑电信号)
# 形状:(样本数, 通道数, 时间点数)
X = epochs_binary.get_data()

# y:标签(每个样本属于哪一类)
# y 中的值是事件编号(1或2)
y = epochs_binary.events[:, -1]

print(f"  样本总数: {len(X)}")
print(f"  每个样本: {X.shape[1]} 通道 × {X.shape[2]} 时间点")
print(f"  条件A: {np.sum(y == 1)} 个样本")
print(f"  条件B: {np.sum(y == 2)} 个样本")

# ===== 创建分类流水线 =====
print("\n🔧 创建分类流水线...")

# Pipeline:把 CSP 和 LDA 串起来
# 就像工厂流水线:
#   原料(脑电数据)→ 加工1(CSP)→ 加工2(LDA)→ 成品(分类结果)
clf = Pipeline([
    # 第1步:CSP 特征提取
    # n_components=4:提取4个特征
    #   前2个对条件A最敏感
    #   后2个对条件B最敏感
    ('CSP', CSP(
        n_components=4,        # 提取4个空间滤波器
        reg='ledoit_wolf',     # 正则化方法(防止过拟合)
        log=True,              # 对特征取对数(让数据更稳定)
        norm_trace=False       # 不归一化
    )),
    
    # 第2步:LDA 分类
    # 在 CSP 特征上画分界线
    ('LDA', LDA())
])

print("  ✅ 流水线: 原始脑电 → CSP(4特征) → LDA → 预测结果")

# ===== 交叉验证 =====
print("\n⏳ 进行交叉验证...")
print("  把数据分成10份")
print("  每次用9份训练,1份测试")
print("  重复10次,每次测试的那份不同")

# ShuffleSplit:随机划分
# n_splits=10:重复10次
# test_size=0.2:每次20%做测试
# random_state=42:固定随机种子(让结果可重复)
cv = ShuffleSplit(n_splits=10, test_size=0.2, random_state=42)

# cross_val_score:自动完成训练+测试+评分
scores = cross_val_score(
    clf,        # 分类流水线
    X,          # 数据
    y,          # 标签
    cv=cv,      # 交叉验证方式
    n_jobs=1    # 单核运行
)

# ===== 结果 =====
print(f"\n📊 分类结果:")
print(f"  每次准确率: ", end="")
for i, s in enumerate(scores):
    print(f"{s*100:.0f}%", end=" ")
print()
print(f"  平均准确率: {scores.mean()*100:.1f}%")
print(f"  标准差: {scores.std()*100:.1f}%")
print(f"  最高: {scores.max()*100:.1f}%")
print(f"  最低: {scores.min()*100:.1f}%")
print(f"  随机水平: 50%")

# 判断
if scores.mean() > 0.7:
    print(f"\n  🎉 分类效果好!远超随机水平")
elif scores.mean() > 0.6:
    print(f"\n  👍 分类有效果,高于随机水平")
else:
    print(f"\n  ⚠️ 接近随机水平")
    print(f"     (正常,我们用的是听觉数据模拟BCI)")
    print(f"     (真正的运动想象数据准确率通常在70-85%)")

# ===== 画分类结果图 =====
print(f"\n👀 绘制分类结果图...")
fig, ax = plt.subplots(figsize=(10, 6))

# 画每次交叉验证的准确率柱状图
colors = ['#2ecc71' if s > 0.6 else '#e74c3c' for s in scores]
bars = ax.bar(range(1, 11), scores * 100, color=colors, alpha=0.8, edgecolor='white')

# 标注数值
for bar, score in zip(bars, scores):
    height = bar.get_height()
    ax.text(bar.get_x() + bar.get_width()/2., height + 1,
            f'{score*100:.0f}%', ha='center', va='bottom', fontsize=10, fontweight='bold')

# 参考线
ax.axhline(y=50, color='gray', linestyle=':', linewidth=2, label='随机水平: 50%')
ax.axhline(y=scores.mean()*100, color='#e74c3c', linestyle='--', linewidth=2, 
           label=f'平均: {scores.mean()*100:.1f}%')

# 美化
ax.set_xlabel('交叉验证次数', fontsize=12)
ax.set_ylabel('准确率 (%)', fontsize=12)
ax.set_title('CSP+LDA 分类结果\n(10次交叉验证)', fontsize=14, fontweight='bold')
ax.set_xticks(range(1, 11))
ax.set_xticklabels([f'第{i}次' for i in range(1, 11)])
ax.set_ylim(0, 100)
ax.legend(fontsize=10, loc='lower right')
ax.grid(True, alpha=0.3, axis='y')

plt.tight_layout()
plt.savefig('08_classification.png', dpi=150, bbox_inches='tight')
print("✅ 已保存为 08_classification.png")
plt.show(block=True)

# ===== 总结 =====
print(f"\n" + "=" * 60)
print(f"🎉 全部8级完成!")
print(f"=" * 60)
print(f"""
  你完成了一个完整的 BCI 分析流程:

  第1级:加载数据,看看长什么样
  第2级:设置电极位置,认识关键通道
  第3级:提取实验事件
  第4级:滤波 + 重参考(洗数据)
  第5级:切分数据段(Epochs)
  第6级:ERP 分析(看大脑平均反应)
  第7级:时频分析(看频率变化)
  第8级:CSP+LDA 分类(机器学习)✨

  分类结果:{scores.mean()*100:.1f}% ± {scores.std()*100:.1f}%
""")

        📖 代码详解

                1. Pipeline(流水线)是什么?

clf = Pipeline([
    ('CSP', CSP(n_components=4)),
    ('LDA', LDA())
])
Pipeline = 工厂流水线

没有流水线时(手动操作):
  步骤1:用 CSP 处理数据 → 得到特征
  步骤2:把特征交给 LDA → 得到预测
  容易出错,代码冗长

有流水线时(自动化):
  数据 → [CSP] → [LDA] → 结果
  一条龙服务,简洁可靠

就像:
  原料(脑电)→ 机器1(CSP)→ 机器2(LDA)→ 成品(分类)

                2. CSP 的 n_components=4 是什么意思

CSP 创建"虚拟电极",每个虚拟电极是一个特征

n_components=4 表示提取4个特征:
  特征1:对条件A最敏感(条件A的方差最大)
  特征2:对条件A次敏感
  特征3:对条件B最敏感(条件B的方差最大)
  特征4:对条件B次敏感

为什么是4个?
  太少(比如2个):可能丢失信息
  太多(比如10个):可能包含噪声
  4个是常用的平衡选择

                3. 交叉验证怎么理解?

交叉验证 = 多次"模拟考试"

不使用交叉验证的问题:
  用同一份数据训练和测试
  → 就像用练习题当考试题
  → 分数高不代表真会了

10折交叉验证:
  把数据分成10份
  第1次:用第1-9份训练,用第10份测试 → 分数1
  第2次:用第1-8、10份训练,用第9份测试 → 分数2
  ...
  第10次:用第2-10份训练,用第1份测试 → 分数10

  最终得分 = 10次分数的平均
  → 更能反映真实水平

                4. 分类结果图怎么看?

准确率 ↑
 100% │
      │
  80% │  ▓▓▓▓     ▓▓▓▓
      │  ▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓
  60% │  ▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓
      │  ▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓
  50% │--▓▓▓▓-▓▓▓-▓▓▓▓-▓▓▓▓-▓▓▓▓-▓▓▓▓-▓▓▓▓-▓▓▓▓-- ← 随机水平
      │  ▓▓▓▓ ▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓ ▓▓▓▓
      │
   0% └──────────────────────────────────────────
       第1次 第2次 第3次 ...                   第10次

绿色柱子:准确率 > 60%
红色柱子:准确率 ≤ 60%

红色虚线:10次的平均准确率
灰色虚线:随机水平(50%)

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐