用Python的PsychoPy库构建n-back游戏:从零实现心理学实验编程

在认知心理学领域,n-back任务被广泛用于研究工作记忆能力。这个看似简单的实验范式——要求被试判断当前刺激是否与前面第n个刺激相同——却蕴含着丰富的认知神经机制。对于技术开发者而言,用代码精确复现这类实验不仅能够加深对心理学原理的理解,更能掌握实验编程的核心方法论。

PsychoPy作为开源的心理学实验构建工具,凭借其精确的刺激呈现和时间控制能力,已成为学术研究中的标准工具之一。本文将完整展示如何用Python和PsychoPy库从零构建一个空间n-back游戏,重点解决实验编程中的关键技术难点。

1. 实验环境搭建与PsychoPy基础

1.1 安装与初始化

首先确保Python环境(建议3.7+)已配置,通过pip安装必要库:

pip install psychopy pandas openpyxl

PsychoPy的核心是 Window 类,它创建实验的主窗口。初始化时应考虑显示器的刷新率和物理尺寸:

from psychopy import visual, event, core
import random
import pandas as pd

# 创建窗口(单位使用像素更易控制UI元素)
win = visual.Window(
    size=(1000, 618), 
    color='white', 
    units='pix',
    fullscr=False  # 调试时建议关闭全屏
)

1.2 刺激材料设计

n-back实验通常使用两种视觉刺激:

  • 目标刺激 :需要被试关注的元素(如绿色方块)
  • 背景刺激 :作为参照的静态元素(如灰色九宫格)
# 九宫格位置参数(以屏幕中心为原点)
positions = [
    (-150, 150), (0, 150), (150, 150),
    (-150, 0),   (0, 0),   (150, 0),
    (-150, -150),(0, -150),(150, -150)
]
cube_size = 145  # 像素单位

# 创建刺激元素模板
gray_cube = visual.Rect(win, width=cube_size, height=cube_size, fillColor='#afafaf')
green_cube = visual.Rect(win, width=cube_size, height=cube_size, fillColor='green')

2. 实验流程实现

2.1 随机序列生成

n-back实验的关键是生成符合认知负荷要求的刺激序列。需要避免连续出现相同位置,同时控制n-back匹配的概率:

def generate_trial_sequence(length=30, n_back=2, match_prob=0.3):
    sequence = []
    match_indices = []
    
    # 生成基础序列(保证相邻不重复)
    while len(sequence) < length:
        pos = random.randint(0, 8)
        if not sequence or pos != sequence[-1]:
            sequence.append(pos)
    
    # 设置匹配项
    for i in range(n_back, length):
        if random.random() < match_prob:
            sequence[i] = sequence[i - n_back]
            match_indices.append(i)
    
    return sequence, match_indices

2.2 实验主循环

实验流程需要精确控制刺激呈现时间、反应收集和数据记录:

def run_experiment(n_levels=[1, 2], trials_per_block=20):
    data = {
        'block': [],
        'trial': [],
        'n_level': [],
        'position': [],
        'response': [],
        'correct': [],
        'rt': []  # 反应时(毫秒)
    }
    
    for block, n in enumerate(n_levels):
        sequence, matches = generate_trial_sequence(trials_per_block, n)
        for trial, pos in enumerate(sequence):
            # 绘制背景
            for i in range(9):
                gray_cube.pos = positions[i]
                gray_cube.draw()
            
            # 绘制目标刺激
            green_cube.pos = positions[pos]
            green_cube.draw()
            win.flip()
            
            # 记录反应
            clock = core.Clock()
            keys = event.waitKeys(
                keyList=[str(i+1) for i in range(9)] + ['escape'],
                timeStamped=clock
            )
            
            # 数据记录
            if keys and keys[0][0] == 'escape':
                return pd.DataFrame(data)
            
            response = int(keys[0][0])-1 if keys else None
            is_correct = (trial >= n and response == sequence[trial - n]) if response is not None else False
            
            data['block'].append(block)
            data['trial'].append(trial)
            data['n_level'].append(n)
            data['position'].append(pos)
            data['response'].append(response)
            data['correct'].append(is_correct)
            data['rt'].append(keys[0][1]*1000 if keys else None)
    
    return pd.DataFrame(data)

3. 数据管理与分析

3.1 实时数据存储

使用pandas库可以方便地处理实验数据,并支持多种格式导出:

def save_data(df, filename='nback_data.xlsx'):
    try:
        existing = pd.read_excel(filename)
        updated = pd.concat([existing, df], ignore_index=True)
        updated.to_excel(filename, index=False)
    except FileNotFoundError:
        df.to_excel(filename, index=False)

3.2 数据质量检查

实验后应立即进行基本数据验证:

def validate_data(df):
    # 检查缺失值
    missing = df.isnull().sum()
    
    # 计算准确率
    accuracy = df.groupby('n_level')['correct'].mean()
    
    # 反应时分布
    rt_stats = df.groupby('n_level')['rt'].agg(['mean', 'std'])
    
    return {
        'missing_data': missing,
        'accuracy': accuracy,
        'reaction_time': rt_stats
    }

4. 高级实现技巧与调试

4.1 时间精度优化

PsychoPy提供多种时间控制方式,适用于不同精度要求:

方法 精度 适用场景 代码示例
core.wait() 中等 简单等待 core.wait(1.0)
Clock计时器 反应时测量 clock = core.Clock()
CountdownTimer 固定时长 timer = core.CountdownTimer(5.0)

4.2 常见问题解决

问题1:刺激呈现不同步

  • 原因:未考虑显示器刷新周期
  • 解决方案:
    win.flip()
    next_flip = win.getFutureFlipTime(clock='ptb')
    

问题2:按键响应延迟

  • 优化方案:
    event.globalKeys.add('escape', func=cleanup)  # 全局热键
    

问题3:随机序列偏差

  • 改进方法:
    from psychopy.tools import sequenceutils
    seq = sequenceutils.createBalancedSequence(repeats=5, nConditions=9)
    

5. 实验扩展与个性化定制

5.1 多模态刺激支持

PsychoPy支持丰富的刺激类型,可以轻松扩展实验范式:

# 声音刺激
from psychopy import sound
tone = sound.Sound('A', octave=4, sampleRate=44100)

# 图像刺激
face_img = visual.ImageStim(win, image='face.jpg', size=(200,200))

5.2 动态难度调整

根据被试表现实时调整n值:

def adaptive_n_back(df, window_size=5):
    recent = df.tail(window_size)
    accuracy = recent['correct'].mean()
    
    if accuracy > 0.8:
        return min(df['n_level'].max() + 1, 5)
    elif accuracy < 0.6:
        return max(df['n_level'].min() - 1, 1)
    else:
        return df['n_level'].iloc[-1]

在实际项目中,n-back实验的精确实现需要考虑显示器参数校准、响应设备选择和实验伦理审查等更多因素。PsychoPy的强大之处在于它提供了从实验设计到数据收集的完整解决方案,而Python生态则让后续的数据分析工作变得异常高效。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐