01 / 本课学习路线
本课学习路线
阅读与推演约 98 分钟,练习约 60 分钟
02 / 学习目标与先修自测
学完本课你能做什么,以及开始前需要会什么
局部注意力沿用上一课的四步,改变的是「第 i 行能看谁」。先写出对称窗口与因果窗口的区间,再检查序列两端的截断。
| 学完后能做的事 | 正文位置 | 检查方式 |
|---|---|---|
| 分别写出对称窗口与因果窗口的区间式 | 第 04 节 | 自查第 2 条、代码自测 |
| 手写 n=5、w=1 的两张可见表 | 第 04 节 | 自查第 3 条 |
| 说出 w=0 与 w≥n−1 两种特殊情况的行为 | 第 04 节 | 自查第 4 条、练习 2 |
| 按空行分块、各块独立计算 | 第 06 节 | 自查第 5 条 |
| 说出全屏蔽行为什么必须按题目要求处理 | 第 04 节 | 自查第 6 条 |
| 通过 AI030 对称局部窗口注意力 | 第 05、07 节 | 自查第 1 条 |
先修自测:下面 5 题请先自己写答案,再展开对照。答不出的按括号里的位置补看再回来。本课假定你已完成上一课「注意力机制与 Q/K/V 计算」(四步与稳定 softmax 在那里讲过)。
| 题号 | 题目 | 补看位置 |
|---|---|---|
| 自测 1 | list(range(max(0, 2 - 1), min(4, 2 + 1) + 1)) 得到什么?去掉末尾的 + 1 呢? | for 循环与 range |
| 自测 2 | i=0、w=1 时 max(0, i - w) 是多少?i=4、n=5 时 min(n - 1, i + w) 是多少? | 常用内置函数 |
| 自测 3 | sum(a * b for a, b in zip([1, 1], [0, 1])) 是多少?除以 √2 呢? | 注意力机制与 Q/K/V 计算第 04 节 |
| 自测 4 | 分数 [0.7071, 0]:减最大值后取指数是多少?归一化后的两个权重是多少? | 浮点精度与稳定 softmax |
| 自测 5 | "3 2\n1 0\n\n2 3\n".split() 得到什么?空行去哪了? | 输入输出规则、数据范围与精度 |
展开先修自测答案
自测 1:[1, 2, 3];去掉 + 1 得 [1, 2]——range 右开,窗口右端会被漏掉一个。
自测 2:0 和 4。两端截断就是这两个函数:左边不小于 0,右边不大于 n−1。
自测 3:1;除以 √2 ≈ 0.7071——第 05 节示例第一块 i=0 与 j=0 的分数。
自测 4:exp(0) = 1、exp(−0.7071) = 0.4931;权重 1/1.4931 = 0.6698 与 0.4931/1.4931 = 0.3302。
自测 5:['3', '2', '1', '0', '2', '3']——按空白切开时空行自然消失,块要靠每块开头的 n、d 逐块读,不能靠数空行。
03 / 概念与术语
窗口半径、对称与因果、截断、块
分清窗口半径、两端截断和分块输入;阅读程序时对照相应变量与区间。
| 术语 | 含义 | 代码里的位置 |
|---|---|---|
| 窗口半径 w | 位置 i 向两侧最多看多远;w=0 只看自己 | 输入第一行 b, w |
| 可见集合 | 第 i 行参与打分、softmax 与加权求和的 j 的集合;本课是一个连续区间 | range(lo, hi + 1) |
| 对称窗口 | |i−j| ≤ w:j ∈ [max(0, i−w), min(n−1, i+w)] | lo, hi = max(0, i - w), min(n - 1, i + w) |
| 因果窗口 | i−w ≤ j ≤ i:只看自己和左边 w 个;AI030 不用它 | hi = i(第 07 节 local_visible 的 causal 分支) |
| 两端截断 | 窗口伸出序列的部分直接舍弃,不补零、不循环 | max(0, ·) / min(n - 1, ·) |
| 窗口内稳定 softmax | 只对可见集合里的分数减最大值、取指数、归一化 | mx = max(scores) 中的 scores 只含窗口内分数 |
| 块 | 一条独立的序列:自己的 n、d 与 n 行向量;块间输入空行分隔 | for _ in range(b): 每轮读一块 |
04 / 窗口与边界
准确写出窗口的可见范围
下表并列展示 n=5、w=1 时两种窗口的可见集合——同一个 w,两种窗口的可见集合不一样:
n=5、w=1 的可见集合对照
i=0 对称:{0,1} 因果:{0}
i=1 对称:{0,1,2} 因果:{0,1}
i=2 对称:{1,2,3} 因果:{1,2}
i=3 对称:{2,3,4} 因果:{2,3}
i=4 对称:{3,4} 因果:{3,4}
区间式:对称 j∈[max(0,i−1), min(4,i+1)] 因果 j∈[max(0,i−1), i]计算量:全量注意力每行要看 n 个位置,一共 n² 次打分;窗口半径 w 时每行最多看 min(n,2w+1) 个,一共不超过 n·min(n,2w+1) 次。n=5000、w=16 时从 2.5×10⁷ 降到约 1.65×10⁵。AI030 的上限 B=10、n=50、d=16、w=60:窗口已覆盖整条序列,每块 2500 次打分、每次 16 次乘法,十个块的打分阶段共 4×10⁵ 次乘法;加权求和另有同量级运算。
| 情况 | 可见集合 | 结果 | 题库用例 |
|---|---|---|---|
| w = 0 | 只有自己 | softmax 只有一项、权重恒为 1,输出等于输入(两位小数后) | 1 0 / 4 3 / … 输出与输入逐项相同 |
| w ≥ n − 1 | 整条序列 | 等同于上一课的全量注意力(没有因果掩码) | 1 60 / 4 2 / …、1 7 / 5 4 / … |
两种情况都不需要特殊代码:区间式加两端截断自动得到它们。提交前分别构造一组 w=0 和一组 w≥n−1 的输入,先手算预期再运行。
这类题的三个主要错误来源
① 把对称窗口当因果窗口实现(或反过来)——题目要求用哪个就实现哪个;② 序列两端的截断少算或多算一个位置;③ 一行全被屏蔽时的输出没按题目要求处理。AI030 的窗口保证含自己不会全屏蔽,但上一课「注意力机制与 Q/K/V 计算」 AI031 的自由掩码会——两题的约定不同,都以题目要求为准。
补充学习(选学)工程中常见的稀疏注意力形式约 4 分钟滑动窗口只是最简单的一种稀疏模式
滑动窗口是最简单的稀疏模式;工程实现里还有块状(把序列切块、块内全量)、扩张(隔 k 个看一个)、以及「少数全局词元(token)+ 多数局部词元」的混合模式。共同点都是把 O(n²) 的打分表变稀疏;不同点是可见集合的定义——所以这类题的第一步是把「i 能看谁」的集合写成区间或规则,机考题目会给出严格定义。
05 / 题面示例逐步
AI030 题面示例两个块的逐行计算
AI030「对称局部窗口注意力」:第一行 B w;随后 B 个块、块间一个空行,每块第一行 n d、随后 n 行 d 个实数。位置 i 只看 |i−j| ≤ w 的 j,打分 s = (x_i·x_j)/√d,窗口内稳定 softmax,输出 y_i = Σ p·x_j;每块输出 n 行两位小数、块间一个空行、最后一块后没有,-0.00 输出 0.00。题面示例:2 1 / 3 2 / 1.00 0.00 / 0.00 1.00 / 1.00 1.00 / (空行) / 2 3 / 0.50 0.50 0.00 / 0.00 0.50 0.50。
| i | 可见 j | 分数 s_j | exp(s_j − 最大) | 权重 p_j | 输出 y_i | 两位小数 |
|---|---|---|---|---|---|---|
| 0 | {0, 1} | 0.7071、0 | 1、0.4931 | 0.6698、0.3302 | 0.6698·[1,0] + 0.3302·[0,1] = [0.6698, 0.3302] | 0.67 0.33 |
| 1 | {0, 1, 2} | 0、0.7071、0.7071 | 0.4931、1、1 | 0.1978、0.4011、0.4011 | 0.1978·[1,0] + 0.4011·[0,1] + 0.4011·[1,1] = [0.5989, 0.8022] | 0.60 0.80 |
| 2 | {1, 2} | 0.7071、1.4142 | 0.4931、1 | 0.3302、0.6698 | 0.3302·[0,1] + 0.6698·[1,1] = [0.6698, 1.0] | 0.67 1.00 |
i=1 的输出:第 1 维 = 0.1978·1 + 0.4011·0 + 0.4011·1 = 0.5989,第 2 维 = 0.1978·0 + 0.4011·1 + 0.4011·1 = 0.8022,四舍五入 0.60 0.80。三行的减最大值都取窗口内的最大分——i=2 的窗口不含 j=0,最大分是 1.4142。
| i | 分数 s_0、s_1 | 权重 p_0、p_1 | 输出 | 两位小数 |
|---|---|---|---|---|
| 0 | 0.5/1.7321 = 0.2887、0.25/1.7321 = 0.1443 | 0.5360、0.4640 | [0.268, 0.5, 0.232] | 0.27 0.50 0.23 |
| 1 | 0.1443、0.2887 | 0.4640、0.5360 | [0.232, 0.5, 0.268] | 0.23 0.50 0.27 |
输出两块之间一个空行:0.67 0.33 / 0.60 0.80 / 0.67 1.00 / (空行) / 0.27 0.50 0.23 / 0.23 0.50 0.27。第二块的中间维两行都是 0.50:两个向量的第 2 维都是 0.5,任何权重的加权平均都是 0.5。
06 / 批次与空行分块
每块独立计算,窗口只作用在自己的 n 上
AI030 的输入是 B 个块、块与块之间一个空行。读入用整段按空白切开:空行自然消失,块靠每块开头的 n、d 逐块读,不需要数空行。
| 动作 | 做法 | 代码 |
|---|---|---|
| 整段读入 | sys.stdin.read().split() 得到全部记号 | data = sys.stdin.read().split() |
| 逐块读 | 先读 n、d,再读 n·d 个数;指针前移 | n, d = int(data[pos]), int(data[pos + 1]) |
| 逐块算 | 每块自己的 n 决定窗口截断 | local_attention(xs, w) |
| 逐块输出 | 块内每行一行;块间一个空行;最后一块后没有 | "\n\n".join(blocks) |
把两条序列拼成一条长序列再算是这类题的常见错误:第一块的最后一行 (i=2) 会把第二块的第一行当成 j=3 看进窗口,示例的输出从 0.67 1.00 变成 0.63 0.88,第二块第一行也从 0.27 0.50 0.23 变成 0.59 0.72(维度都对不上)。题库用例 3 1 / … 有三个块、其中一块只有一行——只有按块独立计算,那一行才会原样输出。
07 / 从窗口函数到程序
参考实现与完整程序,每一步落在哪几行
先用断言验证窗口函数,再把上一课的四步套在窗口内,最后加上分块读入与分块输出。
| 步骤 | 代码 |
|---|---|
| 可见集合:对称窗口两端截断 | lo, hi = max(0, i - w), min(n - 1, i + w) |
| 打分:点积除以 √d | sum(a * b for a, b in zip(xs[i], xs[j])) / scale |
| 窗口内稳定 softmax | mx = max(scores)、math.exp(s - mx)、e / total |
| 加权求和 | y[t] += p * xs[j][t] |
| 格式化:两位小数、-0.00 归 0.00 | fmt |
展开参考实现:窗口函数 local_visible(自带断言;先自己写完再对照)
local_visible 的参考实现(自带断言)
Pythondef local_visible(i, n, w, causal):
lo = max(0, i - w)
hi = i if causal else min(n - 1, i + w)
return list(range(lo, hi + 1))
assert local_visible(0, 5, 1, False) == [0, 1]
assert local_visible(2, 5, 1, False) == [1, 2, 3]
assert local_visible(4, 5, 1, False) == [3, 4]
assert local_visible(0, 5, 1, True) == [0]
assert local_visible(2, 5, 1, True) == [1, 2]
assert local_visible(3, 5, 0, False) == [3]
assert local_visible(3, 5, 0, True) == [3]
# w 大于等于 n−1:对称窗口覆盖整条序列,因果窗口覆盖 0..i
assert local_visible(2, 5, 4, False) == [0, 1, 2, 3, 4]
assert local_visible(2, 5, 4, True) == [0, 1, 2]9 条断言:第 04 节两张可见表的两端与中间、w=0、w≥n−1。因果只改右端 hi = i,左端与对称相同。
展开完整参考程序:AI030 对称局部窗口注意力
完整程序:AI030(标准输入 → 标准输出)
Pythonimport math
import sys
def fmt(v):
s = f"{v:.2f}"
return "0.00" if s == "-0.00" else s # -0.00 输出 0.00
def local_attention(xs, w):
n, d = len(xs), len(xs[0])
scale = math.sqrt(d)
out = []
for i in range(n):
lo, hi = max(0, i - w), min(n - 1, i + w) # 对称窗口 |i−j| <= w,两端截断
scores = [sum(a * b for a, b in zip(xs[i], xs[j])) / scale for j in range(lo, hi + 1)]
mx = max(scores) # 只在窗口内减最大值
exps = [math.exp(s - mx) for s in scores]
total = sum(exps)
y = [0.0] * d
for e, j in zip(exps, range(lo, hi + 1)):
p = e / total
for t in range(d):
y[t] += p * xs[j][t]
out.append(y)
return out
def main():
data = sys.stdin.read().split() # 按空白切开:空行自然消失,块靠 n d 逐块读
if not data:
return
b, w = int(data[0]), int(data[1])
pos = 2
blocks = []
for _ in range(b):
n, d = int(data[pos]), int(data[pos + 1])
pos += 2
xs = []
for _ in range(n):
xs.append([float(v) for v in data[pos:pos + d]])
pos += d
rows = local_attention(xs, w)
blocks.append("\n".join(" ".join(fmt(v) for v in row) for row in rows))
print("\n\n".join(blocks)) # 块间一个空行,最后一块后没有
main()把 hi 改成 i 就是因果窗口版。用题面示例核对:两块输出应与第 05 节两张表的最后一列一致。上限 B=10、n=50、d=16、w=60 时,打分阶段约 4×10⁵ 次乘法,加权阶段另有同量级运算;需在目标语言与评测环境中核对耗时。
08 / 边界、反例与复杂度
错误做法在题面示例上各输出什么
下面每一行都是一个具体的错误程序,给出输入、错误输出与正确输出;把参考程序改成对应写法就能复现。输出里的「//」表示块间空行。
| 错误做法 | 错误输出 | 正确输出 | 判题结果 |
|---|---|---|---|
对称窗口写成因果(hi = i) | 1.00 0.00 / 0.33 0.67 / 0.67 1.00 // 0.50 0.50 0.00 / 0.23 0.50 0.27 | 0.67 0.33 / 0.60 0.80 / 0.67 1.00 // 0.27 0.50 0.23 / 0.23 0.50 0.27 | 答案错误(WA) |
右端漏 + 1(range(lo, hi)) | 1.00 0.00 / 0.33 0.67 / 0.00 1.00 // …;单行块 1 3 / 1 6 / … 上 max() 空列表报错 | 同上 | 答案错误 / 运行错误 |
| 先对整行 softmax 再屏蔽 | 0.40 0.20 / 0.60 0.80 / 0.50 0.75 // 0.27 0.50 0.23 / 0.23 0.50 0.27 | 同上 | 答案错误(WA) |
| 缩放写成除以 d | 0.62 0.38 / 0.62 0.77 / 0.62 1.00 // 0.26 0.50 0.24 / 0.24 0.50 0.26 | 同上 | 答案错误(WA) |
| 不缩放 | 0.73 0.27 / 0.58 0.84 / 0.73 1.00 // 0.28 0.50 0.22 / 0.22 0.50 0.28 | 同上 | 答案错误(WA) |
| 把所有块拼成一条序列 | 0.67 0.33 / 0.60 0.80 / 0.63 0.88 // 0.59 0.72 / 0.23 0.50 | 同上 | 答案错误(WA) |
| 块间漏空行 | 五行连续输出 | 同上 | 格式错误(PE) |
| 不处理 -0.00 | 题库 performance 用例里出现 -0.00 | 0.00 | 答案错误(WA) |
把参考程序改成对应写法运行,就能得到表里的错误输出。第三行「先对整行 softmax 再屏蔽」的 i=1 恰好正确(窗口覆盖整行),i=0 与 i=2 的权重之和小于 1,输出被压小。
| 做法 | 时间 | 本课规模下 |
|---|---|---|
| 每行只扫窗口 | O(B·n·min(n,2w+1)·d) | 打分阶段最多 10 × 50 × 50 × 16 = 4×10⁵ 次乘法,加权阶段另有同量级运算 |
| 每行对全序列打分再屏蔽 | O(B·n²·d) | 本题窗口覆盖整条序列时与窗口版同量级;若 n=5000、w=16,仅按打分次数估算,全序列约为窗口版的 5000/33≈152 倍 |
09 / 渐进练习与参考答案
跟做 → 改一个条件 → 独立实现 → 迁移
每题先在纸上或文件里做完,再展开答案。
练习 1(跟做):按第 05 节第一张表的格式,把示例第一块 i=2 一行从可见集合到两位小数重算一遍。
展开练习 1 答案
可见 {1, 2};分数 (0·1 + 1·1)/√2 = 0.7071、(1 + 1)/√2 = 1.4142;减最大值后 exp(−0.7071) = 0.4931、exp(0) = 1;权重 0.3302、0.6698;输出 0.3302·[0,1] + 0.6698·[1,1] = [0.6698, 1.0] → 0.67 1.00。
练习 2(改一个条件):示例第一块把 w 改成 0,输出是什么?改成 2 呢?
展开练习 2 答案
w=0:每行只看自己,输出等于输入 1.00 0.00 / 0.00 1.00 / 1.00 1.00。w=2:窗口覆盖整条序列(w ≥ n−1),等同于全量注意力:i=0 的分数 [0.7071, 0, 0.7071],权重 [0.4011, 0.1978, 0.4011],输出 [0.8022, 0.5989] → 0.80 0.60;i=1 与 w=1 相同 0.60 0.80;i=2 的分数 [0.7071, 0.7071, 1.4142],权重 [0.2483, 0.2483, 0.5035],输出 [0.7517, 0.7517] → 0.75 0.75(用未舍入的分数和权重计算)。
练习 3(改一个条件):n=6、w=2 时位置 4 的对称窗口和因果窗口各是什么?位置 0 呢?
展开练习 3 答案
位置 4:对称 [2, 5](右端 min(5, 6) = 5 被截断),因果 [2, 4]。位置 0:对称 [0, 2],因果 [0, 0]。
练习 4(独立实现):完成「必做任务 1」的 local_visible,再加两条断言:n=5、w=4 时位置 2 的对称窗口是 [0, 1, 2, 3, 4]、因果窗口是 [0, 1, 2]。
展开练习 4 答案
local_visible 的参考实现(自带断言)
Pythondef local_visible(i, n, w, causal):
lo = max(0, i - w)
hi = i if causal else min(n - 1, i + w)
return list(range(lo, hi + 1))
assert local_visible(0, 5, 1, False) == [0, 1]
assert local_visible(2, 5, 1, False) == [1, 2, 3]
assert local_visible(4, 5, 1, False) == [3, 4]
assert local_visible(0, 5, 1, True) == [0]
assert local_visible(2, 5, 1, True) == [1, 2]
assert local_visible(3, 5, 0, False) == [3]
assert local_visible(3, 5, 0, True) == [3]
# w 大于等于 n−1:对称窗口覆盖整条序列,因果窗口覆盖 0..i
assert local_visible(2, 5, 4, False) == [0, 1, 2, 3, 4]
assert local_visible(2, 5, 4, True) == [0, 1, 2]见第 07 节展开区(同一份代码,最后两条断言就是本题)。
练习 5(迁移):把第 07 节的完整程序改成因果窗口版(只改一处),示例第一块的输出是什么?为什么第一行等于输入?
展开练习 5 答案
hi = i。输出 1.00 0.00 / 0.33 0.67 / 0.67 1.00(第 08 节错误表第一行):i=0 只看自己,权重 1,输出等于输入;i=1 看 {0, 1},分数 0 与 0.7071,权重 0.3302 与 0.6698,输出 [0.3302, 0.6698];i=2 看 {1, 2},与对称版相同。这就是上一课 AI006 的因果注意力在窗口 w=1 下的结果。
10 / 读题要求与复习自评
AI030 的要求对照,以及完成本课之后怎么复习
提交前把下表过一遍;题目页的题面与样例是最终依据。
| 项目 | 要求 | 对应代码 |
|---|---|---|
| 输入 | B w;B 个块,块间空行;每块 n d + n 行 | data = sys.stdin.read().split(),逐块读 |
| 窗口 | 对称 |i−j| ≤ w,两端截断 | max(0, i - w)、min(n - 1, i + w) |
| 打分与归一 | x_i·x_j / √d;窗口内减最大值的 softmax | scale = math.sqrt(d)、mx = max(scores) |
| 输出 | 每块 n 行两位小数;块间一个空行;最后一块后没有;-0.00 → 0.00 | fmt、"\n\n".join(blocks) |
| 规模 | B ≤ 10、n ≤ 50、d ≤ 16、w ≤ 60 | 每行只扫窗口即可 |
参考程序:需要对照解法时,展开第 07 节的完整程序。复习与自评:「学习完成检查」六条是自评,勾选不改变题目的通过(AC)状态。本课记为完成的条件:必做题 AI030 通过,并勾选全部六条学习完成检查;复习题不计入完成状态。复习时用三个问题自测:① 不看正文,写出对称与因果两种窗口的区间式;② 不看表格,重算示例第一块 i=0 的权重与输出;③ 说出 w=0、w≥n−1 两种情况的输出各是什么、块拼接为什么会错。答不出哪一条,就回到对应的节重读,再做第 09 节对应的练习。
11 / 练习
按顺序完成本课的任务
必做题已通过 0/1 道
local_visible)的两种窗口——九条断言覆盖了两端截断、w=0 和 w≥n−1 的代表情况。核对区间公式并通过这些自测后,再组合上一课的注意力四步,处理分块输入输出。参考实现与完整程序在第 07 节的展开区里:先自己写完并提交一次,再展开对照。两种窗口的可见集合函数(local_visible)
必做任务 1自主练习练习重点:对称与因果两种区间式;两端用 max/min 截断;预计用时:15 分钟
完成标准:不查表能直接写出两种窗口的区间公式
需要时查看提示
对称 range(max(0, i-w), min(n-1, i+w)+1);因果 range(max(0, i-w), i+1)。注意 Python 的区间函数(range)右开,+1 不要漏。第 04 节有两张可见表,参考实现在第 07 节。
自测代码(复制到你的代码文件中运行,检查输出是否一致)
def local_visible(i, n, w, causal):
# 待完成:返回 i 的可见 j 列表(升序)
# 对称:|i-j| <= w 因果:i-w <= j <= i
...
# n=5、w=1 的两张可见表(先手写,再运行断言)
assert local_visible(0, 5, 1, False) == [0, 1]
assert local_visible(2, 5, 1, False) == [1, 2, 3]
assert local_visible(4, 5, 1, False) == [3, 4]
assert local_visible(0, 5, 1, True) == [0]
assert local_visible(2, 5, 1, True) == [1, 2]
# w=0:对称和因果都只包含自己
assert local_visible(3, 5, 0, False) == [3]
assert local_visible(3, 5, 0, True) == [3]AI030 · 对称局部窗口注意力
必做任务 2练习重点:按空行分块、每块独立做「打分 → 掩码 → softmax → 加权求和」;预计用时:45 分钟
完成标准:能解释 w=0 和 w≥n−1 两种特殊情况的输出分别是什么
需要时查看提示
本题使用对称窗口 |i−j|≤w;上一课「注意力机制与 Q/K/V 计算」 AI006 的因果范围 j≤i 不适用于本题。按空行分块,块与块的输出之间也要空行(最后一块后没有);保留两位小数,-0.00 输出 0.00;稳定 softmax 的最大值取窗口内最大分。第 05 节有题面示例两个块的逐行表。
提交结果
提交结果说明与处理方法
- WA
答案错误
三查:窗口种类(本题是对称)、两端截断的 max/min、softmax 是否只在窗口内做(含减最大值)。第 08 节的表给出了每种错误在示例上的输出
- PE
格式错误
块与块之间一个空行、最后一块后没有空行;两位小数;-0.00 归一
- RE
运行错误
空行分块后每块要重新读 n、d;块内行数和 n 对不上会越界;右端漏 +1 在单行块上会对空列表取最大值
- TLE
超时
核对实现是否只遍历窗口内的位置,复杂度应为 O(B·n·(2w+1)·d)(上限约 10⁶ 量级);检查是否在每行对全序列打分
- AC
通过
再自行构造一组 w=0 的输入:输出应逐项等于输入(保留两位小数后)
12 / 学习完成检查
本课学习完成检查
完成本课需要:必做题全部通过,并勾选本课的全部学习完成检查;进阶练习、基础加练与复习题单独统计,不影响完成状态。登录后,勾选记录会保存到账号,并更新课程总览的完成状态。