news 2026/11/1 1:59:07

IEKT向量矩阵流程评估

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IEKT向量矩阵流程评估

目录

认知向量和敏感度向量意义;

敏感度向量 s 用来影响“做完这道题后,历史状态 h 怎样更新”;认知向量 m 用来辅助判断“学生现在面对这道题,能否答对”。

不同学生对同一道题的理解可能不同,同一个学生对不同题目的理解也可能不同。

可以这样区分**:

因为选择认知向量时,代码每次都重新输入当前题目表示 v 和当前历史状态 h。

IEKT 的关键顺序是:先用认知向量预测当前题,再结合当前真实作答选择敏感度向量,通过 GRU 更新状态,供下一题使用。

先看一批数据怎样进入模型。

***实际训练调用链是:

其中 predict_one_step() 虽然名字带有 one_step,内部实际上会遍历这个 batch 的整段序列:

历史信息h怎么来的

接下来是当前题目的表示 v (知识点向量+题目向量)怎么得到。

然后是你关心的认知向量m怎样选出来。

拿到认知向量m后,先预测当前题的答对概率。

截至这里,当前题的真实作答还没有参与预测。

得到预测之后,才进入学习敏感度向量的选择。(和取认知向量代码类似)

具体代码层面解析:

这样,敏感度策略就能够区分“学生实际答对/答错”和“模型预测答对/答错”的组合。

随后仍然是“算概率 → 抽编号 → 查表”:

你问的**“怎样组织进入 GRU”**,就在 [`update_state()` (line 76)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:76)。

认知向量 m 没有直接拼到 GRU 输入中。直接组织进 GRU 的是题目表示 v 和敏感度向量 s。

不过,认知向量参与了本题预测,而预测结果又参与敏感度选择,所以两条路径仍然有关联。

GRU 输出的 h_t 是一个历史状态向量,不是答对概率,也不是每个知识点的掌握概率。

当前题的概率由前面的预测器输出;GRU 更新出的状态留给下一题:

整段序列跑完之后,才由 train_one_step() 计算训练损失(一样是强化学习)。

它同时训练两件事:BCE和策略损失(认知策略&敏感度策略)

这里要区分两种“更新”:

每处理一道题,更新 h:更新这一条学生序列的历史状态。

每处理完一个 batch,执行 opt.step():更新模型参数,包括参与计算的向量表、预测器、策略网络和 GRU。

最后看验证和测试怎样评估。

***评估过程是:

AUC:使用预测分数衡量区分真实正例、负例的能力,不先把概率切成 0/1。指标说明

最后用一个学生、三道题走一遍。


认知向量和敏感度向量意义;

敏感度向量s用来影响“做完这道题后,历史状态h怎样更新”;认知向量m用来辅助判断“学生现在面对这道题,能否答对”。

你说的“学生有层次”,有个体差异这层意思,但这里更准确的是:

不同学生对同一道题的理解可能不同,同一个学生对不同题目的理解也可能不同。

论文因此分别建模“答题前的认知”和“作答、获得反馈后的知识获取”。原论文

可以这样区分**:

向量想表达什么在代码里影响什么
认知向量m学生当前对这道题的理解状态当前题的预测概率p
敏感度向量s这次练习及反馈对学生知识状态的影响更新后的h,进而影响后续预测

举个直觉例子:甲、乙都在做一道乘法题。

  • 甲已经很熟练:这题对他比较容易,但再做一次,可能没有多少新的收获。
  • 乙正在学习乘法:这题对他比较困难,但经过练习和反馈,知识状态可能发生较明显的变化。

这里要表达的是两个不同的问题:

做题前:这道题对他来说理解得怎么样? ↓ 认知向量 m 做题后:这次经历对他后面的学习状态有什么影响? ↓ 敏感度向量 s

因此,“当前理解得好”与“通过这次练习获得较多新知识”不必是同一回事。

在你这份代码里,敏感度向量的实际作用很具体:

# 根据真实作答,组织 GRU 输入 # 答对时:[v, s] # 答错时:[s, v] h_new = self.gru_h(inputs, h)

所以,即使两次交互的题目和作答结果相同,选择的s不同,GRU 得到的输入也不同,就可以产生不同的状态更新。[对应源码 (line 76)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:76)

不过,不能直接把s理解成一个“学习速度分数”:它是一整条向量,要和题目表示、旧状态一起经过 GRU,才能决定怎样更新。编号大,也不意味着学得更多。

同样,认知向量也没有把学生永久分成固定等级:

同一个学生: 做熟悉的加法题 → 可能选择一种认知向量 做陌生的方程题 → 可能选择另一种认知向量 学习一段时间后再做方程题 → 选择又可能变化
因为选择认知向量时,代码每次都重新输入当前题目表示v和当前历史状态h。

[对应源码 (line 214)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:214)

cog_levels=10表示模型有10 个可学习的认知候选向量;代码没有预先规定“编号 0 是差生、编号 9 是优生”,也没有规定编号之间的高低顺序。

IEKT 的关键顺序是:先用认知向量预测当前题,再结合当前真实作答选择敏感度向量,通过 GRU 更新状态,供下一题使用。

认知向量和敏感度向量都来自训练过程中不断更新的候选向量表。

每次选哪一个,由当前题目和学生历史信息决定。

下面按你本地当前源码说明,本轮没有修改文件。

先把几个文件的职责对应起来,后面看函数就不容易迷路:

文件负责什么
[wandb_iekt_train.py (line 4)](/D:/pykt-work/pykt-toolkit/examples/wandb_iekt_train.py:4)读取 IEKT 的参数,调用公共训练入口
[wandb_train.py (line 93)](/D:/pykt-work/pykt-toolkit/examples/wandb_train.py:93)创建数据加载器、模型和优化器,启动训练
[que_base_model.py (line 22)](/D:/pykt-work/pykt-toolkit/pykt/models/que_base_model.py:22)整理输入数据;把题号、知识点编号转换为向量
[iekt.py (line 201)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:201)逐题选择向量、预测、更新状态,以及计算训练损失
[iekt_utils.py (line 19)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt_utils.py:19)实现 MLP 和单步 GRU
[train_model.py (line 310)](/D:/pykt-work/pykt-toolkit/pykt/models/train_model.py:310)循环训练、反向传播、更新参数、验证与保存模型
[evaluate_model.py (line 52)](/D:/pykt-work/pykt-toolkit/pykt/models/evaluate_model.py:52)收集预测概率与真实标签,计算 AUC、ACC

下面用D表示emb_size。入口默认D=64,但先看维度关系更容易理解。


先看一批数据怎样进入模型。

***实际训练调用链是:

wandb_iekt_train.py → wandb_train.main() → train_model() → model_forward() → IEKT.train_one_step(data) → IEKT.predict_one_step(data, return_details=True)
其中predict_one_step()虽然名字带有one_step,内部实际上会遍历这个 batch 的整段序列:

历史信息h怎么来的

# 每条学生序列开始时,历史状态为全零 h = torch.zeros(data_len, self.model.emb_size).to(self.device) # 按时间顺序,一道题一道题处理 for seqi in range(seq_len): ...

batch_to_device()会把移位后的两侧序列拼回完整形式:

cq:题目编号 [B, T] cc:每题的知识点编号 [B, T, 最大知识点数量] cr:真实作答 [B, T]

例如,一条学生序列可以表示为:

题目: [11, 22, 33] 知识点: [[2, 5], [5, -1], [2, -1]] 真实作答: [1, 0, 1]

这里知识点里的-1是补齐位置。`h` 则保存模型根据此前作答形成的历史信息。


接下来是当前题目的表示v(知识点向量+题目向量)怎么得到。

调用的是:

v = self.model.get_ques_representation(q, c)

它进一步调用QueEmb.forward(q, c)。

当前 IEKT 默认参数虽然写着emb_type="qid",但代码会把iekt-qid映射为qc_merge,实际会同时使用题目和知识点信息。

[核心计算 (line 103)](/D:/pykt-work/pykt-toolkit/pykt/models/que_base_model.py:103) 是:

# 将本题涉及的有效知识点向量求平均 concept_avg = self.get_avg_skill_emb(c) # 按题号查出题目向量 que_emb = self.que_emb(q) # 拼接:知识点均值在前,题目向量在后 v = torch.cat([concept_avg, que_emb], dim=-1)

于是:

知识点平均向量:D 维 题目向量: D 维 拼接后的 v: 2D 维

get_avg_skill_emb()会让补齐的知识点对应零向量,并且不把补齐位置计入知识点数量。

这个v描述当前题目;学生此前的历史信息在h中。


然后是你关心的认知向量m怎样选出来。

模型初始化时创建了一张表:

self.cog_matrix = nn.Parameter( torch.randn(cog_levels, emb_size * 2), requires_grad=True )

默认是:

10 个候选认知向量 每个向量 2D 维,即默认 128 维

表中的数值开始时随机初始化,随后通过训练更新。

不同学生共用这张表,但每次选择哪一行可以不同;编号本身没有预先规定的“低、中、高”含义。

[逐题选择代码 (line 214)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:214) 是:

# 当前题目表示与历史状态拼起来 ques_h = torch.cat([v, h], dim=1) # 2D + D = 3D # MLP 给各候选项打分,softmax 转成选择概率 pi_cog = self.model.pi_cog_func(ques_h) # 按概率抽取一个候选编号 action_cog = Categorical(pi_cog).sample() # 按编号从候选表中取出认知向量 m = self.model.cog_matrix[action_cog, :] # 2D 维

这里有两个不同的东西:

pi_cog:各个认知向量被选中的概率 m: 实际抽中的那个认知向量

源码采用的是“抽一个编号,再取一行向量”,不是按概率对所有候选向量求加权平均。


拿到认知向量m后,先预测当前题的答对概率。

对应 [`obtain_v()` (line 68)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:68):

# 拼接顺序是历史状态、题目表示、认知向量 predict_input = torch.cat([h, v, m], dim=1) # 输出一个原始预测分数 logits = self.predictor(predict_input) # 转换为答对概率 prob = torch.sigmoid(logits)

维度关系是:

h:D 维 v:2D 维 m:2D 维 ↓ 拼接 预测器输入:5D 维 ↓ MLP logit:1 个数 ↓ sigmoid 答对概率 p:1 个数

注意两个地方的拼接顺序不同:

选择认知向量时:[v, h] 实际预测时: [h, v, m]
截至这里,当前题的真实作答还没有参与预测。

得到预测之后,才进入学习敏感度向量的选择。(和取认知向量代码类似)

它也有一张可训练的候选表:

self.acq_matrix = nn.Parameter( torch.randn(acq_levels, emb_size * 2), requires_grad=True )

默认同样是10个候选,每个2D维。

这次选择需要结合:

  • 当前题目v和历史信息h;
  • 学生实际上答对还是答错;
  • 模型刚才预测学生会答对还是答错。

[源码中的组织过程 (line 228)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:228),可以写成下面的数学形式:

a = [h, v] 共 3D 维 r = 当前真实作答 0 或 1 r_hat = 1,如果 p > 0.5,否则为 0 out_x = [ r × a, (1-r) × a, r_hat × a, (1-r_hat) × a ]

四块都是3D维,所以out_x一共12D维。

例如:

真实作答r预测作答r_hatout_x的组成
11[a, 0, a, 0]
10[a, 0, 0, a]
01[0, a, a, 0]
00[0, a, 0, a]

表中的0表示与a等长的零向量。

具体代码层面解析:
先分清: - **`out_x`:提供给选择网络的信息。** - **`pi_sens`:选择各个敏感度候选的概率。** - **`s`:按概率选中后,从表里取出的敏感度向量。** 它们是三个不同的东西。 **① `out_x` 到底装了什么?** 先把学生旧状态和当前题目表示拼起来: ```python a = [h, v] ``` 再用四个固定位置,表达“实际答题结果”和“模型预测结果”: ```text out_x = [ 实际答对的位置, 实际答错的位置, 预测答对的位置, 预测答错的位置 ] ``` 每个位置放的都是 **`a` 或者与 `a` 等长的零向量**。 比如学生实际答对,但模型预测会答错: ```text out_x = [a, 0, 0, a] ``` 网络因此既能看到学生和题目的信息,也能从所在位置区分这次答题的情况。 **② 为什么这么大?因为四个位置都占长度,补零也占长度。** 按你现在的 `emb_size=64`: ```text h:64 个数 v:128 个数 a = [h, v]:192 个数 out_x:4 × 192 = 768 个数 ``` 即使其中两块全是零,它仍然有 768 个数。**长度增加来自重复放置和补零,并没有凭空增加四倍的新信息。** 缩小来看,假设: ```text a = [2, 3, 4] 实际答对、预测答错: out_x = [2,3,4, 0,0,0, 0,0,0, 2,3,4] 实际对 实际错 预测对 预测错 ``` 它就是一个拼接后的向量。实际按批处理时,形状是 `[B, 768]`。 **③ 把 `out_x` 传进去,为什么就得到 `pi_sens`?** 看 [pi_sens_func 的实际代码](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:101): ```python def pi_sens_func(self, x, softmax_dim=1): return F.softmax(self.checker_emb(x), dim=softmax_dim) ``` 它内部有一个训练出来的神经网络 `checker_emb`。按你的配置,计算过程是: ```text out_x:768 个数 ↓ Linear + ReLU 768 个中间特征 ↓ Linear 10 个候选的分数 ↓ softmax pi_sens:10 个候选的选择概率 ``` 例如,假设网络算出: ```text 候选编号: 0 1 2 3 4 5 6 7 8 9 选择概率:0.05 0.10 0.05 0.10 0.30 0.10 0.05 0.10 0.10 0.05 ``` 这 10 个概率加起来是 1。 **`pi_sens[4]=0.30` 表示“有 30% 的概率选择第 4 个敏感度候选”,不是“学生答对概率为 30%”。** 然后: ```python action_sens = Categorical(pi_sens).sample() # 如果这次抽到编号 4,就取敏感度表的第 4 行 s = self.model.acq_matrix[action_sens, :] ``` 取出来的 `s` 才是 **128 维敏感度向量**,之后再与题目表示 `v` 组合送进 GRU。 ```text out_x【768 维信息】 ↓ 选择网络 pi_sens【10 个选择概率】 ↓ 抽样 编号,例如 4 ↓ 从 acq_matrix 取第 4 行 s【128 维敏感度向量】 ```
这样,敏感度策略就能够区分“学生实际答对/答错”和“模型预测答对/答错”的组合。
随后仍然是“算概率 → 抽编号 → 查表”:
pi_sens = self.model.pi_sens_func(out_x) action_sens = Categorical(pi_sens).sample() s = self.model.acq_matrix[action_sens, :] # 2D 维

s用于更新本题之后的状态。当前题的预测概率已经算完,不会被这次更新重新计算。


你问的**“怎样组织进入 GRU”**,就在 [`update_state()` (line 76)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt.py:76)。

这里传入的是:

h = self.model.update_state( h, # 旧历史状态 v, # 当前题目表示 s, # 本次选择的敏感度向量 r # 当前真实作答 )

源码先通过真实作答,把v和s放到不同位置。用分块形式表示:

v_cat = [r×v, (1-r)×v] e_cat = [(1-r)×s, r×s] inputs = v_cat + e_cat

因为r只能是0或1,可以直接化简:

学生真实作答v_cate_cat最终 GRU 输入
答对,`r=1`[v, 0][0, s][v, s]
答错,`r=0`[0, v][s, 0][s, v]

所以最终是:

GRU 本题输入 inputs:4D 维 GRU 旧状态 h: D 维 ↓ GRU 新状态: D 维

认知向量m没有直接拼到 GRU 输入中。直接组织进 GRU 的是题目表示v和敏感度向量s。

不过,认知向量参与了本题预测,而预测结果又参与敏感度选择,所以两条路径仍然有关联。

这个项目使用的是自定义的单步 GRU:[`mygru.forward()` (line 36)](/D:/pykt-work/pykt-toolkit/pykt/models/iekt_utils.py:36)。

核心计算如下:

def forward(self, x, h): # 重置门:生成候选新状态时,使用多少旧状态信息 # 这里的 r_t 是门值,不是学生的真实作答标签 r_t = self.sigmoid( self.g_ir(x) + self.g_hr(h) ) # 更新门:最终保留多少旧状态 z_t = self.sigmoid( self.g_iz(x) + self.g_hz(h) ) # 根据本题输入和旧状态,生成候选新状态 n_t = self.tanh( self.g_in(x) + self.g_hn(h).mul(r_t) ) # 将候选新状态与旧状态混合 h_t = (1 - z_t) * n_t + z_t * h return h_t

最需要理解的是最后一行:

新状态 = (1 - 更新门) × 候选新状态 + 更新门 × 旧状态

在这份实现里,`z_t` 越接近1,越倾向于保留旧状态。

GRU 输出的h_t是一个历史状态向量,不是答对概率,也不是每个知识点的掌握概率。

当前题的概率由前面的预测器输出;GRU 更新出的状态留给下一题:
第 1 题:用 h0 预测 → 看到 r1 → 更新为 h1 第 2 题:用 h1 预测 → 看到 r2 → 更新为 h2 第 3 题:用 h2 预测 → 看到 r3 → 更新为 h3

整段序列跑完之后,才由train_one_step()计算训练损失(一样是强化学习)。

它同时训练两件事:BCE和策略损失(认知策略&敏感度策略)

损失训练目的
答题预测损失 BCE让预测答对概率接近真实作答标签
认知策略损失、敏感度策略损失根据累计奖励,调整候选向量的选择概率

奖励仍然是前面讲过的:

模型预测正确:原始奖励 1 模型预测错误:原始奖励 0

代码将奖励除以序列长度,再从后往前计算:

G_t = 当前奖励 + gamma × G_(t+1)

两套策略分别计算:

认知策略损失 = -log(本次认知动作的概率) × G_t 敏感度策略损失 = -log(本次敏感度动作的概率) × G_t

这是用回报训练离散选择概率的策略梯度做法。PyTorch 官方说明

当前实现给两套策略使用相同的累计回报;敏感度动作发生在当前预测之后,但它使用的回报仍包含当前奖励。这是源码的具体处理方式。

最终:

总损失 = BCE + lamb × 两套策略损失之和 / 参与计算的作答记录总数

然后外层执行:

opt.zero_grad() # 清除上一次的梯度 loss.backward() # 根据总损失计算梯度 opt.step() # 更新网络参数和候选向量表

这里要区分两种“更新”:

  • 每处理一道题,更新h:更新这一条学生序列的历史状态。

  • 每处理完一个 batch,执行opt.step():更新模型参数,包括参与计算的向量表、预测器、策略网络和 GRU。

当前源码的训练 BCE 会包含首题,并按seq_num截取数据;前面确认过的“题号0导致seq_num少算”问题仍在,这里没有修改。


最后看验证和测试怎样评估。

每个 epoch 训练结束后,外层调用:

evaluate(model, valid_loader, model.model_name)

***评估过程是:

切换到评估模式,不计算梯度 ↓ predict_one_step() 按顺序预测整条序列 ↓ 返回 prob_tensor[:, 1:],去掉首题预测 ↓ 用 smasks 筛出参与评估的位置 ↓ 与对应的真实标签 rshft 对齐 ↓ 拼接所有 batch 的预测概率和标签 ↓ 计算一次整体 AUC 和 ACC

[实际汇总代码 (line 186)](/D:/pykt-work/pykt-toolkit/pykt/models/evaluate_model.py:186) 相当于:

# 先从每个 batch 中取出需要计分的位置 y = torch.masked_select(y, sm) t = torch.masked_select(rshft, sm) # 收集完所有 batch 后,拼接在一起 ps = np.concatenate(y_scores) ts = np.concatenate(y_trues) auc = metrics.roc_auc_score(ts, ps) acc = metrics.accuracy_score( ts, [1 if p >= 0.5 else 0 for p in ps] )

其中:

  • ACC:按0.5阈值判断答对/答错,看判断正确的比例。
  • AUC:使用预测分数衡量区分真实正例、负例的能力,不先把概率切成0/1。指标说明

这里算的是所有被选中的作答记录汇总后的整体 AUC,不是各学生或各 batch 的 AUC 平均值。

评估时模型参数不更新,但仍会在每题预测之后,用真实作答更新临时状态h。因此,预测第 3 题时可以使用前两题的真实作答历史。

还有两个源码细节:

  • eval()后仍会执行两处Categorical.sample(),所以认知、敏感度选择仍然带随机性。
  • 验证 AUC 比已记录值高出超过0.001才保存新检查点;连续10轮没有达到这个更新条件就早停。之后通过 [wandb_predict.py (line 60)](/D:/pykt-work/pykt-toolkit/examples/wandb_predict.py:60) 加载检查点,分别计算普通测试集和窗口测试集指标。

意思是,每次都随机抽某一行


最后用一个学生、三道题走一遍。

以下向量、选择结果和网络输出是教学用假设值,不是你的真实训练日志。

为方便看清数组,设:

D = 2 题目序列:[11, 22, 33] 真实作答:[1, 0, 1] 初始历史状态 h0 = [0, 0]

先详细展开第 1 题。

假设它关联知识点2、5,当前向量表中:

知识点 2 的向量:[0.1, 0.3] 知识点 5 的向量:[0.3, 0.5] 平均后: [0.2, 0.4] 题目 11 的向量: [0.6, 0.8] 所以题目表示: v1 = [0.2, 0.4, 0.6, 0.8] 共 2D=4 维

认知策略接收:

[v1, h0] = [0.2, 0.4, 0.6, 0.8, 0, 0] 共 3D=6 维

假设网络算出的选择概率是[0.2, 0.6, 0.2],这次抽中了编号1:

m1 = cog_matrix[1] = [0.1, 0.2, 0.3, 0.4]

那么预测器的输入就是:

[h0, v1, m1] = [0, 0, 0.2, 0.4, 0.6, 0.8, 0.1, 0.2, 0.3, 0.4] 共 5D=10 维

假设预测器输出:

logit ≈ 1.386294 p1 = sigmoid(logit) = 0.8

模型预测学生会答对,随后读到真实作答r1=1。

因此,敏感度策略的输入是:

a = [h0, v1] = [0, 0, 0.2, 0.4, 0.6, 0.8] out_x = [a, 六个0, a, 六个0] 共 12D=24 维

假设敏感度选择概率是[0.4, 0.6],这次抽中了编号0:

s1 = acq_matrix[0] = [0.05, 0.1, 0.15, 0.2]

因为学生真实答对,所以 GRU 输入是:

u1 = [v1, s1] = [0.2, 0.4, 0.6, 0.8, 0.05, 0.1, 0.15, 0.2] 共 4D=8 维

为了把 GRU 也算出具体数值,假设示例权重使两个门都是[0.5, 0.5],并且:

g_in(u1) = [u1[0] + u1[4], u1[1] + u1[5]] = [0.25, 0.5] g_hn(h0) = h0 = [0, 0]

那么:

候选新状态 n1 = tanh([0.25, 0.5] + 0.5 × [0, 0]) ≈ [0.244919, 0.462117] 新历史状态 h1 = 0.5 × n1 + 0.5 × h0 ≈ [0.122459, 0.231059]

下一题就带着这个h1继续计算。

假设三题最终得到:

题目预测时使用的历史状态预测答对概率真实作答本题 GRU 输入更新后的状态
11h00.81[v1, s1]h1
22h10.60[s2, v2]h2
33h20.71[v3, s3]h3

训练时:

预测概率: [0.8, 0.6, 0.7] 真实作答: [1, 0, 1] 原始奖励: [1, 0, 1] 除以长度 3:[1/3, 0, 1/3] gamma = 0.93 时: 累计回报 G ≈ [0.621633, 0.31, 0.333333] BCE ≈ 0.498703

如果三个时间步抽中动作的概率分别是:

认知策略: [0.6, 0.5, 0.7] 敏感度策略: [0.4, 0.8, 0.5]

则:

认知策略损失合计 ≈ 0.651314 敏感度策略损失合计 ≈ 0.869820 lamb = 40 时: 总损失 = 0.498703 + 40 × (0.651314 + 0.869820) / 3 ≈ 20.780489

这个总损失被交给backward()和opt.step(),用于更新模型参数。

评估时,假设仍得到上述预测概率,并且后两题都参与计分,代码会先去掉首题:

参与评估的概率:[0.6, 0.7] 对应真实标签: [0, 1]
  • 按0.5阈值分类,预测为[1, 1],两题只对一题,所以ACC = 0.5。
  • 真实正例的概率0.7高于真实负例的概率0.6,这一对样本排序正确,所以这个小例子的AUC = 1。这只是两条记录的计算示范,不代表实际模型效果。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/11/1 1:58:46

从立案到归档:律师全流程的AI工具配置攻略

很多文章在推单个AI工具,但律师真实需要的是一条流水线。单点工具解决单点痛点,流程化系统才解决系统性耗时。我把办案全流程拆开,逐环节给配置建议——这是我边执业边调整大半年的版本。阶段一:立案前检索环节核心需求是类案和法…

作者头像 李华
网站建设 2026/11/1 1:58:27

优秘智能灵秘的持续表达:身份、授权声音、知识与作品检查

优秘智能灵秘的持续表达涉及几个不同对象:已获授权的人物形象、对应声音、经过确认的业务资料,以及准备发布的具体作品。身份与声音是制作依据,资料承载事实,作品需要在声音、嘴型和画面上保持协调。这些对象相互关联,…

作者头像 李华
网站建设 2026/11/1 1:57:45

Java转AI Agent教程(第三周)工具调用

W3 教程:工具调用 —— 让模型能"查东西",而不是"编东西"本周交付物:3 个电商工具 参数强校验 一个带工具循环的 ToolAgent 25 条准确率测试。 验收线(计划表原文):20 条输入工具选…

作者头像 李华
网站建设 2026/11/1 1:56:37

[Playwright][10]Locator 实战:使用 XPath ancestor 精准定位 Job Card

在 HPC Dashboard 的 Job Explorer 模块中,页面会展示多个 Job Card,每个卡片都有 View Details 等操作按钮。 如果直接定位按钮,可能匹配多个元素。因此,我采用了 先定位 Job Card,再定位卡片内部按钮 的方式。1. 从 …

作者头像 李华
网站建设 2026/11/1 1:56:17

华为OD机试真题 新系统 2026-10-28 JavaGoC【社交网络消息转发】

目录 题目 思路 Code 题目 题目内容: 在一个社交网络中,有 N 个用户(编号 0 到 N−1);每个用户都有一个「转发名单」(整数),该数二进制格式中的第 i 位为 1 表示此用户会把消息转发给第 i 个用户。 给定一个整数数组 forwardLists 和一个整数 initialSenders,请…

作者头像 李华