关于推理的问题 - 抓住作弊的学生

机器算法验证 回归 假设检验 聚类 推理
2022-03-22 06:13:07

在他们的论文“抓住作弊的学生”中,Levitt 和 Lin 提出了一种简单的简化形式的方法来识别学生在考试中的作弊行为。

该策略的工作原理如下:对于每对可能的学生,他们计算这些学生给出相同答案的问题的数量。然后他们估计以下简单回归:

similar_answersi=β0+β1neighbori+ui,
在哪里similar_answersi指pair的相似答案的数量i, 和neighbor是一个指标,如果学生坐在一起,则等于 1,否则取 0。所以,β1衡量实际坐在彼此旁边的个人是否有更多的相似答案。

使用 OLS 简单地估计这个模型,并且对标准误差不做任何特别的事情。我的感觉是这不可能是正确的,因为观察在某些群体中是相互关联的:首先,一个人成对出现。其次,观察结果也可能与行相关。例如,如果个人12坐在一起作弊,但个人2还复制个人的答案3那么这些对将不会相互独立。

我的问题:你会怎么做来解释这种相关性?

1个回答

虽然很容易想到,这种对关系在某种程度上是自相关的,这会导致推理问题,但直截了当的答案是,这在这里不是问题

它背后的基本原理接近典型的聚类问题。聚类不会破坏在单元级别变化的变量的重要性,它会产生太重要的变量,在集群级别变化。如果我们引入了学生级别的变量,而不是配对级别的变量,那么它应该经常很重要。

由于这是潜在的自相关问题,所讨论的值将是感兴趣参数的估计量的 p 值:β1. 我们担心False Positives的潜在错误数量。


为了检查误报率,我提出了蒙特卡罗模拟,并给出了假设:

  • 学生不作弊。我们检查误报率,则不需要引入作弊机制。
  • n ( 250) 学生排成一排,每个学生有两个邻居(第一个和最后一个)。
  • 学生有k ( 20) 个答案的测试,每个答案都有( )个2可能性。答案是随机的,概率相等。
  • 学生成对配对,如果他们坐在一起,则被标记为邻居。计算每对相似答案的数量。

然后进行蒙特卡罗模拟(单位:一对):

  • 回归similar_answersi=β0+β1neighboursi+εi被评估。P值b1估计器被保存。
  • 过程重复N ( 10000) 次。
  • 份额,p 值小于0.5, 0.2, 0.1,0.05的次数:
p < 0.50: 0.5227
p < 0.20: 0.2166
p < 0.10: 0.1147
p < 0.05: 0.0511

10000蒙特卡洛模拟的份额并没有太大的不同。看起来很公平的论点,即误报率没有被破坏。


复制代码(python):

import pandas as pd
import random
import numpy as np
from multiprocessing import Pool

# number of students:
n = 250
# number of possible answers and length of the test:
a = 2
k = 20
# number of monte carlo sims:
N = 10000
# number of processors:
cpu = 2

def get_pvals(iter = 0):
    print(iter)
    answers = []
    for i in range(n):
         answers.append(np.random.choice(range(a),k))

    pairs = []
    for i1 in range(n):
        for i2 in range(i1+1, n):
            neigh = 0
            sim_ans = sum(answers[i1] == answers[i2])
            if i1 != i2:
                if i1 == i2-1:
                    neigh = 1
                if i2 == i1-1:
                    neigh = 1
            pairs.append({"sim_ans":sim_ans, "neigh":neigh})

    d = pd.DataFrame(rows)
    import statsmodels.formula.api as sm
    result = sm.ols(formula = "sim_ans ~ neigh", data = d).fit()
    p = result.pvalues['neigh']
    return p

pvals = []

if __name__ == '__main__':
    with Pool(cpu) as p:
        pvals = p.map(get_pvals, range(N))

print(pvals)

print(sum(np.array(pvals) < 0.5)/N)
print(sum(np.array(pvals) < 0.2)/N)
print(sum(np.array(pvals) < 0.1)/N)
print(sum(np.array(pvals) < 0.05)/N)