模拟一个简单知觉决策

在日常生活中,我们每时每刻都在面临各种需要决策的情境,而其中许多决策都是基于我们对外界环境的感知和理解。例如,在过马路时,你会通过对车辆速度和距离的估计,判断现在是否可以安全的通过马路,从而决定是否在现在通过马路;在购物时,你会通过观察水果的颜色形状和质地来判断商品的质量和新鲜程度,以决定是否购买。这被称为知觉决策,即个体通过感官获取外部信息,对这些信息进行处理和判断,从而做出行动选择的过程。

为了理解知觉决策的过程,我们需要引入一个经典的知觉决策范式——随机点运动(Random Dot Kinematogram)如下图所示,在随机点运动实验中,屏幕上会呈现出大量以随机方式排列的移动点,其中的一部分点会按照特定的方向运动(左或者右),而其余的点将会向随机方向(随机向左或向右)运动,其中按照特定方向运动的点的比例被称为coherence。

参与者将会在短暂的观察后判断点阵的总体运动方向,显然,随着coherence的增加,我们越容易做出正确的判断。Glod和Shedlen在2004年的一篇研究也验证了这一点,如下图所示,随着coherence的增加,被试会有更高的正确率;同时,随着viewing time,也就是被试观看刺激的时间增加,正确率也会提高。

这些结论看上去非常符合我们的直觉,我们无法根据我们的经验提出任何异议,但是我们好奇的是这样的现象背后反映了怎样的认知加工过程,我们的大脑如何完成这样的一个简单的知觉决策任务并涌现出这样的行为规律呢?接下来我们通过模拟一个简单的知觉决策任务,深入的探究知觉决策背后隐藏的认知加工过程。

被试究竟是如何观察这些点的运动并做出正确选择的呢?我们提出一个基本假设:在观察刺激的过程中,被试逐一统计朝不同方向移动的点的数量,然后比较这些数量,以此做出决策。那么我们假设在一个随机点运动实验的试次中:

随机点的数量 D=100D = 100, 刺激呈现的帧数 T=2T = 2 ,coh=0.51coh = 0.51

import numpy as np

T = 2 # 刺激呈现的帧数
D = 100 # 刺激中的点数
f = 0.51 # 向正确方向移动的点占多大比率

随机移动的点的移动方向满足伯努利分布,那么每一帧朝正确方向(假设是右边)移动的点的比例为:

f=coh+1coh2=coh2+12f = coh + \frac{1-coh}{2} = \frac{coh}{2} + \frac{1}{2}

根据我们的基本假设,我们可以简单的模拟这一过程:

from scipy.stats import bernoulli

N_correct = 0
N_wrong = 0
for i in range(T*D): # 循环每一帧和每一个点
    dir = bernoulli.rvs(f, size=1)
    N_correct = N_correct + dir # 正确的点数量+1
    N_wrong = N_wrong + (1-dir) # 错误的点数量+1

if N_correct > N_wrong:
    choice = 1 # 我们在这个试次中做出了正确的反应
elif N_correct < N_wrong:
    choice = 0 # 我们在这个试次中做出了错误的反应
else:
    choice = bernoulli.rvs(0.5, size=1)

print(choice)

上述的代码模拟了一个随机点运动的试次,但在实际应用中,我们往往要操纵cohcohTT的水平,因此我们可以将进行一次决策的过程写为一个函数:

# 模拟一次决策
def makeOneDecision(D=100, T=2, f=0.6):
    '''
    <D>: 总共的点数
    <T>: 帧数(刺激持续时间)
    <f>: 向正确方向移动的点所占的比率
    '''
    N_correct = 0
    N_wrong = 0
    for i in range(T*D): # loop 点
        dir = bernoulli.rvs(f, size=1)
        N_correct = N_correct + dir # 正确的点数量+1
        N_wrong = N_wrong + (1-dir) # 错误的点数量+1

    if N_correct > N_wrong:
        accuracy = 1
    elif N_correct < N_wrong:
        accuracy = 0
    else:
        accuracy = bernoulli.rvs(0.5, size=1)
    return accuracy

在真实的实验中,我们不仅要操纵变量的数值,我们还会进行多个试次,因此我们可以写一个函数来同时进行多个试次的决策,并最终返回平均的正确率:

def makeManyDecision(D=100, T=2, f=0.6, nTrial=100):    
    '''
    <D>: 总共的点数
    <T>: 帧数(刺激持续时间)
    <f>: 向正确方向移动的点所占的比率
    <nTrial>: 试次数
    '''
    decision = np.empty(nTrial)
    for i in range(nTrial):
        decision[i]=makeOneDecision(D, T, f)

    return decision.sum()/nTrial

在真实的实验中,我们往往会设置多个coherence和duration的水平进行,这里我们假设设置了5个coherence的水平和3个duration的水平:

nTrial = 1000 # 每种条件进行多少个试次

# 我们设置了5个一致性水平
coh = np.array([0.032, 0.064, 0.128, 0.256, 0.52])
f = (coh+1)/2 # 将一致性转换为点的比率

# 我们设置了5个刺激持续时间
dur = np.arange(1, 22, 5) # 帧数,刺激持续时间

# 将这些值打印出来
print(f)
print(dur)

接下来,我们可以循环这些实验条件来获取在每个水平下的决策正确率:

acc = np.empty((coh.size, dur.size))
for iCoh, cc in enumerate(f): # 循环每个一致性条件
    for iDur, dd in enumerate(dur): # 循环每个持续时间条件
        acc[iCoh, iDur] = makeManyDecision(D=10, T=dd, f=cc, nTrial=100)

我们模拟出了一些实验结果,让我们来看看结果如何吧!

import matplotlib.pyplot as plt

label = [f'coh={i:.3f}' for i in coh]
fig = plt.figure(figsize=(4,4))
for iCoh, dd in enumerate(coh):
    plt.plot(dur*1000/30, acc[iCoh,:], '-o', label=label[iCoh])
# 记住我们在这里将帧数转换为毫秒数, duration * 1000/30 假设电脑屏幕的刷新率为30hz
# 
plt.legend()
plt.ylim([0.45, 1.02])
plt.xlabel('Duration (ms)')
plt.ylabel('Accuracy')

将模拟实验的结果可视化后,我们发现和Shedlen等人在真实的实验中得到的结果非常相似。

最后更新于