别让收藏夹替你学习:我搭AI知识体系的完整方法,脚本直接拿去用

目录

一、写在前面

本来这篇该写云计算的,评论区催的朋友稍安勿躁,在写了。插这篇是因为最近一个月,前后有四个人问我同一个问题:东西越学越多,感觉脑子里一团浆糊,怎么办。

问的人背景还不一样。有个研二的,论文读了一百多篇,让他复现其中一篇,卡在第一天的环境配置上。有个工作三年的后端,想转算法,收藏夹里躺了两百多篇教程,真看完的不超过十篇。还有个就是我那个非科班的徒弟,项目倒是做了几个,但他说自己心里没底,面试官随便往深了问一层,就接不住了。

这四个人问法不同,病根是同一个:学的东西没成体系,是一堆散沙。

我太懂这个感觉了,因为我散沙过两年。我学东西的方式就是看到啥学啥,公众号推什么读什么,GitHub趋势榜上有什么star什么。笔记记了一堆,各记各的,互相不认识。直到有次一个内部机会聊SVM,对面问对偶问题怎么回事,我张嘴就来:这个我学过,然后就没有然后了。学过,但说不出来,等于学了个寂寞。

后来我开始折腾知识体系这件事,断断续续磨合了三年多,地图、日志、笔记、卡片、输出,一套流程跑下来,算是把自己从散沙里捞出来了。这篇我把整套东西摊开讲,每一步都有具体的操作和能跑的代码,你直接抄走改改就能用。这套方法不神秘,核心思想就一句话:把学习这件事,当成一个工程项目来管理。你是写代码的,这套你熟。

二、散着学的代价,我是被坑过才懂的

第一个代价,重复学习。2022年我翻自己的笔记,发现关于学习率的内容记了四份,散在四个文档里,每份都不完整。为啥会这样?因为每次看到讲学习率的文章,我都觉得"这个我得记一下",但我根本不记得自己已经记过三回了。没有索引的学习,就是这样的。

第二个代价,知识不成网。AI这个领域的知识是网状的,attention跟RNN的seq2seq有渊源,batch norm跟梯度传播有关系,损失函数的选择跟数据分布有关系。散着学的时候,每个知识点都是孤零零的一条,用的时候调不出来。成网之后,碰到新问题,你能顺着网摸到相关的旧知识。我自己最明显的感受是,体系搭起来之后,读新论文的速度快了,因为百分之六七十的组件是旧识,真正的新东西就那百分之三四十。

第三个代价,不知道自己不会什么。这个最要命。散着学的时候,你的知识边界是模糊的,感觉啥都懂一点,实际上是懂的那部分反复懂,不懂的那部分从来没碰过。面试被问懵,懵的不是难题,是那个你压根不知道自己没学过的区域。

想明白这三个代价之后,我给自己定了这套流程,一共六步:画地图、记日志、建笔记、做卡片、跑体检、讲出来。下面一步一步来,每步都是详解加操作。

还有个事想先说清楚,免得有人拿着这套流程去对号入座某个课程。体系化学习和刷课是两码事。刷课是把别人的路线跑一遍,终点是"上完课";体系化是搭自己的网,终点是"网能兜住新东西"。我见过不少人把刷完课当里程碑,发个朋友圈庆祝,然后呢,三个月不碰,忘得七七八八。而体系这个东西,它不会整体遗忘,因为你不是靠背的,你是靠结构记的,忘了某个细节,顺着结构能很快捡回来。这也是为什么我后来面试不慌了,不是记住的东西变多了,是取东西的路径变短了。

三、第一步:画一张知识地图

3.1 地图不是目录,是依赖关系

我第一次画地图,画成了课程目录。就是那种"第一章机器学习基础,第二章深度学习......"的列表,画完贴墙上,看着挺唬人,实际没用。后来我才想明白,目录是别人组织知识的方式,地图得是我自己走出来的路。

区别在哪?地图上画的是依赖关系:想学A,得先会B和C。比如想搞懂attention,你得先有矩阵乘法的底子,得知道RNN为什么处理长序列会累,不然那个QKV乘出来你只会觉得"哦,算了个矩阵",体会不到它解决了什么问题。

所以画地图的正确姿势是问自己:这个知识点,是站在哪些知识点上面的?把这条依赖链画出来,地图自然就有了形状。

3.2 我的地图长什么样

我的AI知识地图分五层,从下往上:

第一层,地基:Python(含numpy和 pandas)、线性代数、概率统计、一点微积分。这层的深度要求其实不高,矩阵乘法要熟,条件概率要熟,链式法则要熟。别怕数学,用到什么补什么,比抱着教材从第一页啃到最后一页强太多。

第二层,经典机器学习:线性回归、逻辑回归、树模型、SVM、聚类、交叉验证和过拟合这套评估的东西。这层别跳,很多人直奔深度学习,结果欠拟合过拟合都分不清,调参全靠玄学。

第三层,深度学习基础:反向传播、各种归一化、优化器、CNN、RNN、损失函数。这是主干。

第四层,方向层:我走的是CV,检测、分割、OCR这些。走NLP的对应就是预训练、transformer、微调那一套。

第五层,工程层:数据清洗、训练技巧、部署、可视化、实验管理。这层最能打,也是接活吃饭的本钱。

每层里面的每个节点,我都标一个状态,四档:没碰过、过了一遍、能用、能讲。这个状态标注后面有大用,先记住。

3.3 用工具画出来

手画太费劲,改起来更费劲。我用mermaid画,CSDN编辑器直接渲染,代码长这样:
#mermaid-svg-NGg6PEh3UKNwPjBY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-NGg6PEh3UKNwPjBY .error-icon{fill:#552222;}#mermaid-svg-NGg6PEh3UKNwPjBY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-NGg6PEh3UKNwPjBY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-NGg6PEh3UKNwPjBY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-NGg6PEh3UKNwPjBY .marker.cross{stroke:#333333;}#mermaid-svg-NGg6PEh3UKNwPjBY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-NGg6PEh3UKNwPjBY p{margin:0;}#mermaid-svg-NGg6PEh3UKNwPjBY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-NGg6PEh3UKNwPjBY .cluster-label text{fill:#333;}#mermaid-svg-NGg6PEh3UKNwPjBY .cluster-label span{color:#333;}#mermaid-svg-NGg6PEh3UKNwPjBY .cluster-label span p{background-color:transparent;}#mermaid-svg-NGg6PEh3UKNwPjBY .label text,#mermaid-svg-NGg6PEh3UKNwPjBY span{fill:#333;color:#333;}#mermaid-svg-NGg6PEh3UKNwPjBY .node rect,#mermaid-svg-NGg6PEh3UKNwPjBY .node circle,#mermaid-svg-NGg6PEh3UKNwPjBY .node ellipse,#mermaid-svg-NGg6PEh3UKNwPjBY .node polygon,#mermaid-svg-NGg6PEh3UKNwPjBY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-NGg6PEh3UKNwPjBY .rough-node .label text,#mermaid-svg-NGg6PEh3UKNwPjBY .node .label text,#mermaid-svg-NGg6PEh3UKNwPjBY .image-shape .label,#mermaid-svg-NGg6PEh3UKNwPjBY .icon-shape .label{text-anchor:middle;}#mermaid-svg-NGg6PEh3UKNwPjBY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-NGg6PEh3UKNwPjBY .rough-node .label,#mermaid-svg-NGg6PEh3UKNwPjBY .node .label,#mermaid-svg-NGg6PEh3UKNwPjBY .image-shape .label,#mermaid-svg-NGg6PEh3UKNwPjBY .icon-shape .label{text-align:center;}#mermaid-svg-NGg6PEh3UKNwPjBY .node.clickable{cursor:pointer;}#mermaid-svg-NGg6PEh3UKNwPjBY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-NGg6PEh3UKNwPjBY .arrowheadPath{fill:#333333;}#mermaid-svg-NGg6PEh3UKNwPjBY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-NGg6PEh3UKNwPjBY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-NGg6PEh3UKNwPjBY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NGg6PEh3UKNwPjBY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-NGg6PEh3UKNwPjBY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NGg6PEh3UKNwPjBY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-NGg6PEh3UKNwPjBY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-NGg6PEh3UKNwPjBY .cluster text{fill:#333;}#mermaid-svg-NGg6PEh3UKNwPjBY .cluster span{color:#333;}#mermaid-svg-NGg6PEh3UKNwPjBY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-NGg6PEh3UKNwPjBY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-NGg6PEh3UKNwPjBY rect.text{fill:none;stroke-width:0;}#mermaid-svg-NGg6PEh3UKNwPjBY .icon-shape,#mermaid-svg-NGg6PEh3UKNwPjBY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-NGg6PEh3UKNwPjBY .icon-shape p,#mermaid-svg-NGg6PEh3UKNwPjBY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-NGg6PEh3UKNwPjBY .icon-shape .label rect,#mermaid-svg-NGg6PEh3UKNwPjBY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-NGg6PEh3UKNwPjBY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-NGg6PEh3UKNwPjBY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-NGg6PEh3UKNwPjBY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 线性代数/概率
深度学习基础
经典机器学习
CNN/检测
RNN/transformer
CV项目实战
微调与部署
工程化:Git/实验管理/可视化

这段代码你贴进CSDN的markdown编辑器,它自己就渲染成图。想改结构,改几个字母的事,比在画图软件里拖框方便多了。

地图画完不是就完了。它是个活文档,我每两个月会重新过一遍,把状态改一改,把新冒出来的节点加进去。比如2023年我把LoRA加进了第四层,把量化加进了第五层。地图不更新,它就死了。

还有个使用细节:地图要挂在最容易看见的地方。我把它放在笔记软件的首页,每次打开笔记先看见它。想学新东西之前,先上地图看一眼它挂在哪,依赖的节点是不是绿色的。依赖没补齐就硬学,事倍功半,这话是我在transformer上验证过的,第一遍没学懂就是因为我连seq2seq都没吃透。

3.4 从地图到学习计划,中间隔着一步排期

地图画完,很多人下一个问题是:那我先学哪个?我的做法是把地图变成一个季度计划,具体拆法是这样。

先圈范围。一次只主攻一个方向层的缺口加最多两个地基缺口,比如那阵子我的计划就是检测方向加概率统计。贪多的下场我领教过,2022年上半年我同时开四条线,CV、NLP、推公式、刷LeetCode,最后哪条都没推进多少,日志里四个主题的时间加起来倒是不少,单个主题都不成气候。

再定顺序。同一个范围里,按依赖从底往上,先补地基缺口,再碰方向缺口。地基的补法是快餐式的,带着方向层的问题去查,而不是从头学。比如学检测遇到mAP算了不平均的问题,回头把概率里期望那块补了,这种带着问题的回补,印象比平铺直叙深得多。

最后留余量。计划里每周至少留两个空白晚上,不给任何主题。因为真实的学习总有意外:一个环境报错耗掉一晚上(排错那篇里那些坑,随便一个都能吃掉几小时),一篇论文读得慢了。计划拉满不留白,一个意外就能把整周计划冲垮,然后整个人就摆烂了。留白不是浪费,是让计划有抗冲击能力。

季度末拿日志对着计划复盘,完成了多少、为啥没完成,都写进地图的更新记录里。这样一年的学习就有四个完整的检查点,比"感觉今年学了不少"值钱多了。

四、第二步:学习日志,用数据管住自己

4.1 为什么非要记日志

因为记忆会骗人,数据不会。

我问你,上个月你学了多少小时?分别花在什么上?百分之九十的人答不上来,剩下的百分之十会往多了报。我也一样,感觉上自己天天都在学,真让我拿证据,拿不出来。

2023年我开始记学习日志,记了两个月就抓到自己一个大问题:我看视频的时间占了百分之六十还多,动手写代码的时间只有百分之十五。看视频的时候感觉良好,跟着点头,觉得都懂了,实际上那是错觉,视频是别人的思路在跑,我的脑子在搭顺风车。

这种事不记日志,你发现不了。发现了才能改,后来我给自己定规矩,视频只用来入门,同一个主题,视频看完必须配套动手的东西,不然等于没学。

4.2 日志脚本,二十行搞定

工具越简单越能坚持,我试过好几个App,都死于打开太麻烦。最后落地的方案是一个Python脚本加一个CSV文件,双击就记完了。

先建CSV,表头一行:

text 复制代码
date,topic,minutes,kind,rating,note

六个字段:日期、主题、分钟数、类型、掌握自评、备注。类型我固定四种:read(读文档论文)、video(看视频)、code(动手写)、write(写笔记或博客)。自评打1到5分,凭直觉打,别纠结,这个数是拿来看趋势的,不是拿来做科研的。

记录脚本log.py:

python 复制代码
import csv
import sys
from datetime import date

def log(topic, minutes, kind, rating, note=''):
    # 追加写入,文件不存在会自动报错,所以第一次先手动建好csv和表头
    with open('study_log.csv', 'a', newline='', encoding='utf-8') as f:
        writer = csv.writer(f)
        writer.writerow([date.today().isoformat(), topic,
                         minutes, kind, rating, note])
    print(f'记上了:{topic} {minutes}分钟 ({kind})')

if __name__ == '__main__':
    # 用法: python log.py "attention机制" 45 code 4 "手写了QKV乘法,还有点糊"
    topic, minutes, kind, rating = sys.argv[1:5]
    note = sys.argv[5] if len(sys.argv) > 5 else ''
    log(topic, int(minutes), kind, int(rating), note)

用的时候在terminal里敲一行:

bash 复制代码
python log.py "attention机制" 45 code 4 "手写了QKV乘法,还有点糊"

为啥用命令行不用App?因为录得快。学习刚结束的那三十秒是记录意愿最强的窗口,超过一分钟就开始懒了。脚本敲一行回车就完事,没有借口。

4.3 周报脚本,让数据说话

光记不看等于白记,我每周日跑一次周报脚本,看看这周的时间都喂了谁:

python 复制代码
import pandas as pd
import matplotlib.pyplot as plt

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

df = pd.read_csv('study_log.csv', parse_dates=['date'])

# 最近7天
recent = df[df['date'] > df['date'].max() - pd.Timedelta(days=7)]

print('本周总时长(分钟):', recent['minutes'].sum())
print('按类型分:')
print(recent.groupby('kind')['minutes'].sum())
print('按主题分, 前10:')
print(recent.groupby('topic')['minutes'].sum().sort_values(ascending=False).head(10))

# 类型分布图
recent.groupby('kind')['minutes'].sum().plot(kind='bar')
plt.title('本周学习时间分布')
plt.tight_layout()
plt.savefig('weekly_report.png', dpi=150)
plt.show()

中文乱码的事我在可视化那篇讲过,SimHei这两行是老常客,Linux上没有字体就手动addfont注册一个,不重复了。

这个脚本跑出来的那张柱状图,我管它叫照妖镜。code那根柱子矮了,这周就是虚度了,别管你感觉多充实。数据的好处就是把感觉里的水分挤出去。

顺带说一句,parse_dates='date'这行别忘,不然date列是字符串,时间窗口的减法会直接报错。我当时debug了一会儿才反应过来,老教训:数据类型的错,先看读进来的dtype。

4.4 记了半年之后,数据能告诉你更狠的事

单周的图看的是执行力,半年以上的数据能看出另一些东西,说几个我从自己数据里剥出来的发现。

一个是切换成本。有阵子我的日志里,同一天会记三四个不同主题,看起来挺充实。后来我把日志按"每天主题数"和"当天总时长"摆在一起看,规律很明显:一天碰三个主题的日子,平均单主题时长只有三十多分钟,而单主题日能做到一个半小时以上,而且rating明显更高。三十分钟够干什么?刚进入状态就出来了。后来我就改成每天最多两个主题,宁可把另一主题整周延后。

另一个是评分和类型的联动。把kind和rating交叉统计之后发现,我给video打的平均分明显高,给code打的偏低。一开始我以为是视频质量高,后来想明白了,这就是上面说的"顺风车错觉":看视频时觉得自己懂了,评分自然高;写代码时被现实毒打,评分诚实。所以这个自评不能只看绝对值,得跟类型放一起看,不然你会得出"看视频效率高"的反动结论。我2023年大半年都被这个错觉骗着,想想都肉疼。

这些发现每个人的版本都不一样,所以我才说日志这东西,记录本身只占三成,剩下七成是你拿着数据问自己为什么。脚本谁都能抄,拿着数据问自己为什么的这个动作做不了假。

五、第三步:笔记系统,Obsidian这么配

5.1 我为什么放弃了Notion

先交代下选型,我换过三个方案,血泪换来的结论。

最早用Word和txt,问题不用说了,找不到东西。中间转Notion,那阵子正好流行,我折腾了整整两周,建数据库、配模板、搞看板,把系统搞得跟航天工程似的。然后呢?我发现自己每天的时间花在维护系统上,而不是学习上。最后压垮我的是它的联网加载,地铁上想查个笔记,转圈转到我下车。

现在用Obsidian,用了三年,稳了。它的好处对我来说就三条:文件是本地markdown,不联网也能打开;文件夹可以直接放进Git仓库(笔记怎么进仓库,Git那篇讲过套路,一模一样);双链,笔记之间能互相引用,这个对织知识网是刚需。

加一句劝退的话:别在装插件上上头。Obsidian的插件生态很繁荣,我见过有人装三十多个插件,界面花得跟圣诞树一样。我的配置就三个插件:日历、模板、还有一个画图的。工具的目的是让你忘掉工具。

备份也说两句,这是笔记系统的命门。我的做法是笔记仓库直接Git管理,本地提交勤着点,然后推一个私有仓库到GitHub当异地备份。有个细节:.obsidian这个配置目录里有些东西是机器相关的,workspace文件天天变,没有同步价值,我在.gitignore里把它排了,只同步主题和插件配置。为什么不放网盘?一是网盘对大量小文件的同步不太行,二是版本历史才是笔记最需要的,网盘只给你一个最新快照,昨天误删了一段笔记,网盘帮不了你,Git能。

手机端我用得少,偶尔查个笔记。Obsidian手机版免费,配上同步方案能用,但别指望在手机上写长笔记,体验美不了。我的定位很明确:手机只读,写入永远在电脑上。

5.2 我的目录长什么样,先看再讲规则

目录结构先亮出来,各条法则都是在结构上长出来的:

text 复制代码
notes/
  00-地图.md          # 知识地图首页
  10-地基/
  20-经典ML/
  30-深度学习/
  40-CV方向/
  50-工程/
  90-MOC/            # 各分类的索引页
  91-卡片/            # 值得进Anki的碎片,一个文件一张卡

前面两位数字强迫目录按我想要的顺序排,地图永远在最顶上。这个数字前缀是从程序员的目录命名里抄来的,简单,但每天都真在起作用。91-卡片这个目录就是给第六节的genanki脚本准备的,脚本扫它,出卡,笔记和卡片同源,不用维护两套。

5.3 笔记的原子化和模板

我的笔记规则就两条,都是吃过亏定的。

第一条,一篇笔记只讲一个概念。反例就是我早年的笔记,一篇叫"深度学习笔记三"的东西,里面从CNN讲到dropout再讲到数据增强,找东西的时候根本没法检索。原子化之后,检索的单位就是知识的单位,好找也好链接。

另一面也得说清楚,原子化不是碎片化。碎片化是记的时候就零散,原子化是每篇都完整但只管一件事,区别在链接:碎片是一堆散沙,原子是被双链粘起来的沙。所以我有个硬性自查,每写完一篇新笔记,必须至少给它找一个上级(挂进某个MOC)和一个亲戚(链到一篇相关笔记)。找不到亲戚的笔记,多半是我自己没学透,孤立的知识点要么回头补链,要么过几天重学。这个自查动作看着小,其实就是第三节那句话的落地:知识是网状的,你得亲手把它织成网,不然记的再多也是沙。

第二条,概念笔记统一用三段式模板。我在模板插件里存的:

text 复制代码
## 一句话解释
(用自己的话说这个东西是干嘛的,说不出来就是没懂)

## 细节
(公式、代码、参数。代码只贴自己敲过的关键段,别整篇复制)

## 我的坑
(自己踩过的坑、跟别的东西的联系。这节最重要,是独家资产)

第三段"我的坑"是灵魂。网上讲attention的文章一万篇,讲你自己那次把mask位置搞反的文章只有一篇,就是你自己写的。面试也好、干活也好,真正区分你和别人的,恰恰是这部分。

5.4 MOC:给笔记一张目录网

笔记多了之后,靠搜索不够,得有索引页。Obsidian圈子里这叫MOC(Map of Content),说白了就是一篇手写的目录笔记,比如我有一篇"深度学习基础MOC",里面就是一堆链接:

text 复制代码
# 深度学习基础 MOC

## 优化
- [[SGD]]
- [[Adam]]
- [[学习率调度]]

## 结构
- [[CNN]]
- [[RNN]]
- [[attention]]

双链双方括号一打,点一下就跳。维护成本几乎为零,新笔记写完,想想它该挂到哪个MOC底下,加一行链接的事。

到这一步,你的知识就有了物理形态:地图管全局,笔记管单个知识点,MOC管分类,链接管关系。接下来解决下一个问题------记住了的东西会忘。

六、第四步:间隔重复,用代码批量做卡片

6.1 遗忘曲线这事,不服不行

间隔重复的理论根基是遗忘曲线,这个大家都听腻了,我讲个自己的实测。

2023年我背过一批东西,其中一条是batch norm在训练和推理时行为的区别。当时记得滚瓜烂熟,两周之后同事问我,我说训练时用batch统计量,推理时用......用啥来着?就卡在那了。这不是记性差,是人的出厂设置就这样:不用就删,给大脑省内存。

间隔重复就是反着来:在你快忘的时候复习一把,记忆就续期了,而且续一次,下次能撑得更久。Anki这个软件就是干这个的,它替你算每个知识点该哪天复习,你只管每天打开过卡。我用Anki两年多,日均十分钟,性价比高得离谱。

6.2 不是什么都配做卡片

先泼冷水,Anki用废的人比用成的人多,废在什么都往里塞。

我的判断标准:能靠推导得出的,不进卡片;需要死记的孤立事实,进卡片。

反例:把"什么是卷积"做成卡片。这个不该背,该理解,理解了忘不掉。正例:torch.nn.CrossEntropyLoss内部已经带了log_softmax,所以别在外面再接一个------这种坑型事实,忘一次踩一次,值得背。我现在的卡组里,大头就是各种坑、各种参数的隐藏行为、各种数字结论,理解型的知识一张都没有。

一句话:卡片是给易忘且有用的碎片准备的,不是给整个知识体系准备的。

6.3 genanki:从笔记批量生成卡片

手动在Anki里建卡太慢,我用genanki这个库直接从markdown笔记批量生成。这篇是教程,所以从安装开始一步步走。

第一步,装库:

bash 复制代码
pip install genanki

第二步,理解它的三个概念:Model定义卡片的正反面样式,Note是一条具体的卡片,Deck是一副牌。写代码的顺序也是这个顺序。完整脚本make_cards.py:

python 复制代码
import genanki
import re

# 这两个id必须写死,不能每次随机,原因见下文
MODEL_ID = 160739231900
DECK_ID = 205940011000

# 卡片样式:问题在正面,答案在反面
model = genanki.Model(
    MODEL_ID,
    'AI知识卡',
    fields=[{'name': 'Question'}, {'name': 'Answer'}],
    templates=[{
        'name': 'card1',
        'qfmt': '{{Question}}',
        'afmt': '{{FrontSide}}<hr id="answer">{{Answer}}',
    }])

deck = genanki.Deck(DECK_ID, 'AI进阶卡组')

def parse_note(path):
    """我的卡片笔记格式:文件里用 --- 分隔,上面是问题下面是答案"""
    text = open(path, encoding='utf-8').read()
    parts = [p.strip() for p in text.split('---') if p.strip()]
    return parts[0], '<br>'.join(parts[1:])  # anki里换行得用<br>

import glob
for f in glob.glob('cards/*.md'):
    q, a = parse_note(f)
    note = genanki.Note(model=model, fields=[q, a])
    deck.add_note(note)
    print('加卡:', q[:30])

deck.write_to_file('ai_cards.apkg')
print('导出完成: ai_cards.apkg')

第三步,导入Anki:双击生成的ai_cards.apkg,或者打开Anki,文件-导入。卡组就出现了,之后每天花十分钟过卡,Anki自己安排哪张卡今天该见你。

6.4 一个必踩的坑:id不能随机

这个坑我替你踩过了,详细说说。

我最开始的脚本里,MODEL_ID写的是random.randrange(1 << 30, 1 << 31),想着id嘛,随机数才像id。结果每跑一次脚本,Anki里就多出一套一模一样的卡组。跑了四回,卡组里同样的卡片四份,复习的时候一张卡要过四遍,纯纯的自我折磨。

原理说白了一句话:Anki靠这个id认卡片。id每次变,Anki就每次都当成新卡;id固定,同样的内容只导入一次,改了内容再导入,它认得出是同一张卡,帮你更新而不是复制。

所以MODEL_ID和DECK_ID要用写死的数。怎么生成?Python里跑一次random.randrange(1 << 30, 1 << 31),把出来的数抄进代码,从此不动。注释里那句"必须写死"就是我给自己留的血泪备忘。

再补一个小的:卡片里的换行。Anki的卡片是按HTML渲染的,你从markdown里带来的\n它不认,得换成<br>。上面代码里那句'<br>'.join就是在干这个。我第一次生成的卡片,答案全挤成一行,查了半天才发现是这事。

6.5 每日上限和积压处理,决定你能不能坚持下去

工具跑顺了,卡组会越来越大,两个现实问题就来了:每天复习要花多久?积压了怎么办?

我的设置是每日新卡20张上限,Anki默认是20,我没改。新卡宜少不宜多,因为每张新卡背后跟着未来两周的好几次复习,今天贪20张,下周的复习量就不是你能控制的了。复习上限我倒是不设,因为复习是欠的债,限了它只会越滚越多。

但总会积压。出差几天、赶项目一周,回来一看待复习卡片三百多张,这个时候千万别硬清,我试过一次连着复习一小时十分钟,第二天直接对这个软件生理性厌恶,差点卸载。正确姿势是Anki设置里有个"展示顺序"选项,把每日上限临时压到50,分几天消化,同时暂停新卡。债要还,但要还得起,心态坏了这套系统就崩了,系统崩了比欠三百张卡严重多了。

还有一类卡片,值得直接删除:连续三次你都秒答、心里还烦"这也要问"的卡,它对你的边际价值已经没了。我的卡组从八百多张被我现在删到五百多张,越删越健康,卡组不是资产,记在脑子里才是。

七、第五步:知识体检,扫出自己的盲区

7.1 状态是会骗人的

地图上每个节点有状态:没碰过、过了一遍、能用、能讲。这个状态是我自己标的,自己标自己的东西,水分自然少不了。人有个毛病,学着学着就把"过了一遍"在心里升格成"能用"。

我2024年年初对自己的地图做过一次诚实度检查,方法很土:把标着"能用"的节点挨个拿出来,不开资料,写一段能跑的核心代码或者讲一遍原理。结果触目惊心,标了能用的一共二十一个节点,当场翻车九个。包括SVM------对,又是它,三年了它还在我的假账里。

人工检查太费劲,我就写了个脚本,管它叫知识体检。思路:笔记是学习留下的实体证据,一个知识点如果你笔记里连一篇都没有,那不管地图上标什么状态,都值得怀疑。

7.2 体检脚本

python 复制代码
import os
import glob

# 技能清单:从知识地图抄下来的节点
checklist = {
    '线性代数': ['矩阵乘法', '特征值'],
    '经典ML': ['逻辑回归', 'SVM', '树模型', '交叉验证'],
    '深度学习': ['反向传播', 'batchnorm', 'dropout', 'CNN', 'RNN', 'attention'],
    'CV方向': ['yolo', '图像增强', 'mAP', '分割'],
    '工程': ['git', '实验管理', '可视化', '部署'],
}

# 扫描笔记文件名,攒成一个字符串池
note_names = []
for path in glob.glob('notes/**/*.md', recursive=True):
    note_names.append(os.path.splitext(os.path.basename(path))[0].lower())
pool = ' '.join(note_names)

print('=== 知识体检报告 ===')
missing = []
for category, items in checklist.items():
    for item in items:
        if item.lower() not in pool:
            missing.append(f'{category} / {item}')

if missing:
    print('以下节点在笔记里找不到实体证据:')
    for m in missing:
        print('  -', m)
    print(f'共 {len(missing)} 个盲区,地图状态先降级,再排期补课')
else:
    print('清单全部有笔记覆盖,可以考虑给每个节点做"能不能讲"抽查')

脚本逻辑简单到不好意思叫算法:把笔记目录里所有md的文件名收下来拼成一坨,然后拿清单挨个查,查不到的报出来。匹配是土办法的in判断,所以笔记命名得规矩一点,这是我第五章强调原子化和命名规范的原因,一套体系的各部分是咬合的。

跑一次的输出,我今年年初的真实结果长这样(节选):

text 复制代码
=== 知识体检报告 ===
以下节点在笔记里找不到实体证据:
  - 经典ML / SVM
  - 深度学习 / RNN
  - CV方向 / 分割
共 3 个盲区,地图状态先降级,再排期补课

SVM又一次上榜,RNN属于"早年学过、后来一直用CNN、笔记没留下"的典型。查出来之后,SVM我把状态降回"过了一遍",RNN花了一个周末重学补了笔记。

这个脚本每月一号跑一次,配合每两个月的人肉地图复盘,一个管证据一个管手感,双保险。

体检脚本还有个进阶玩法,我在用的是第二版:查死链。笔记多了以后,MOC里链接指向的笔记可能改名、可能被合并,链接就断了,断了你还不知道。思路也不复杂,扫所有md里的\[链接],检查对应文件存不存在:

python 复制代码
import re, os, glob

broken = []
for path in glob.glob('notes/**/*.md', recursive=True):
    text = open(path, encoding='utf-8').read()
    for link in re.findall(r'\[\[([^\]]+)\]\]', text):
        if not os.path.exists(f'notes/{link}.md'):
            broken.append(f'{path} 里的 [[{link}]]')

print('断链数:', len(broken))
for b in broken:
    print(' ', b)

别小看这个事。断链多的笔记系统,看起来一张网,实际是一堆断开的线段,你顺着链接查东西,查到一半断掉,多来几回你就不信任自己的系统了,然后系统就被废弃了。笔记系统死于失去信任。定期扫一遍死链,跟给代码跑测试一个道理,都是防微小退化累积成崩塌。

八、第六步:讲出来,知识才算你的

前面五步都是输入侧的管理,最后这步是输出,也是我认为含金量最高的一步:把学到的东西讲出来。形式随意,写博客、给同事做分享、带人、提PR,都算。

我的论据是我自己。这五年我在CSDN写的每一篇,写作的过程都比我预想的痛苦,因为一落笔就发现,好多我自以为懂的地方,其实是"眼熟"。眼熟和懂之间隔着一条沟,写作就是逼你跳过去的那个过程。写那篇环境配置的文章时,我把conda的机制查了个底朝天,这个查的过程,才是学习真正发生的时刻。有个学生读者说他靠那篇少折腾了一晚上,我读完私信挺高兴的,但说实话,受益最大的还是我自己。

再说徒弟那条路,他非科班,学历普普通通,去年进的大厂。他的路线就是标准的以讲代学:找一个活跃的开源库,从good first issue这种入门标签的任务开始接。改一个文档错字也是贡献,但接到第三个第四个,就得读源码了,读完你还得在PR里跟维护者讲清楚你为什么这么改------这不就是最严格的费曼测试吗?讲的漏洞会被全球的维护者指出来。他的笔记我看过,每个PR背后都有一篇踩坑记录,这套东西攒下来,面试的时候就是降维打击。

落到操作上,我的建议是这样:每学完一个地图上的节点,给自己定一个讲出来的任务,小的写篇笔记的"一句话解释"就够(我的三段式模板第一节,其实就是这个用途),大的写成博客。讲不出来,回炉,别硬往下走。

博客选题也有讲究,别凭热点定,从地图上定。我的习惯是优先写两类:一类是刚从"过了一遍"升到"能用"的节点,趁热写,这个阶段的沉淀最多,晚了就忘了当时不懂的感觉;另一类是体检扫出来的盲区,补完课马上写,因为你的视角最新,最知道卡在哪里,写出来的东西对同样卡住的人最友好。你看我那几篇文章的结构,为啥报错部分写那么细,就是刚踩完写的,位置都还记得。要是隔个半年再写,就只剩结论没有过程了。

至于博客和笔记的分工,我是这么划的:笔记是私有的,写给未来的自己,可以乱、可以断片、可以只写半句;博客是公开的,写给读者,得成文、得能自洽。同一份知识过两遍手,第一遍是收纳,第二遍是锻造,互不替代。有人问我为啥不直接一步到位写成博客,老实说试过,写作压力一大,频率就没了,而频率是这套系统里最值钱的东西。先允许自己写得烂,才能写得久。

九、串成闭环,以及写在最后

最后把六步串起来,看看一个完整的循环长什么样。拿我2023年学LoRA举个例子,真实的流程:

先在地图上找位置,LoRA挂第四层,依赖transformer和微调的基础,检查一下依赖节点状态,都是"能用",OK可以学。然后是学,论文加两篇解读,这一步日志里记read。接着动手,找了个开源实现跑通,再把核心的低秩分解部分自己敲了一遍,记code。学完落笔记,一篇原子的《LoRA》,挂进MOC,易忘的数字和结论做成卡片进Anki。一个月后体检脚本跑过,LoRA这个节点有笔记实体,绿了。最后是讲,给徒弟讲了一遍为什么低秩可行,他问倒我两个问题,回头又补了半篇笔记。

一个节点走完这一圈,才敢在地图上标"能用"。这一套看着繁琐,实际磨合顺了之后,多出来的时间大概每天二十分钟,换的是心里有数:学了什么、会了什么、下一步学什么,全部有据可查。那种"学了很多但心里没底"的焦虑,本质是没有账目,这套东西就是记账。

三个常见误区,最后提一嘴。第一,收藏不是学习,star了一千个仓库不等于会了一千个技能,收藏夹的正确用途是待办清单,不是功德箱。第二,别过度工程化,我在Notion上浪费的两周就是教训,工具为你服务,不是你为工具打工,任何时候维护系统的开销超过学习本身,就该砍功能了。第三,地图允许过时,它本来就该跟着你的方向变,我2022年的地图上还有一大块NLP,后来走CV就砍了,砍了不可惜,聚焦才值钱。

方法讲完了,就这么多,没什么黑科技。你会发现整套东西其实就是把工程里的好习惯------文档、日志、测试、code review------搬到了学习上。你们要是已经在用类似的方法,评论区说说你的版本,特别是用什么工具记学习账的,我最近想把日志和笔记打通,还在选型。

相关推荐
伞伞悦读9 分钟前
【第83期】特征选择中的验证泄漏排查的实验与排查
人工智能·深度学习·机器学习
Mininglamp_271835 分钟前
Mano-P 低配部署:多模型并行的实际配置清单
ai·本地部署·mano-p
用户697625682561 小时前
用 Alook 把本机编程 agent 拉进频道:Pi + MiniMax 实操
人工智能
P1Browser1 小时前
账号触发平台安全审核?拆解标准申诉流程与材料清单
大数据·人工智能·安全·多账号管理·指纹浏览器·账户安全·运营合规
丘山录1 小时前
本地部署 Edge0-145B,98GB 放 NAS 慢 51 倍
人工智能
楚山之石1 小时前
【Nat Biomed Eng】Connectome 2.0 超高梯度扫描仪:500 mT/m 与 600 T/m/s 把活体人脑微观结构分辨推进到亚微米级
图像处理·学习·数据分析·mri
一只废狗狗狗狗狗狗狗狗狗1 小时前
自监督式学习
人工智能·深度学习·机器学习·bert
武子康2 小时前
给 H3-World 叠加 Turbo,动作 LoRA 为什么还要重新验收?
人工智能·agent·claude
Dawson Zhu2 小时前
【分布式训练④】张量并行与序列并行:把单层参数切碎分到多张卡
人工智能·语言模型·架构·aigc·agi