上一篇《从 RNN / LSTM / GRU 到早期 Attention》结尾我留了一句话:早期的 attention 并不是为了取代 RNN 而出现的,它只是夹在 encoder–decoder 中间解决 fixed-context bottleneck;但“根据当前需求去读取一组 representation”这种机制,并不一定要依附在 RNN 上。这篇接着讲那个“另一个故事”。
整理完风险指标,再看夏普比率时,我关注的是公式里的两个选择:为什么分子减去无风险收益率,分母又为什么选波动率?“收益除以风险”这句简称,把这两件事省掉了。
收益更高,所以更好吗 # 先看两个简化到只剩数字的策略。为了把注意力放在比较上,先假设这段时间的无风险收益率近似为 0,收益和超额收益暂时是同一个数:
在收益率那篇里,同样赚了 10%,可能来自完全不同的路径。要继续比较,就得知道风险指标各自省略了什么。
这次复习时,我把波动率、最大回撤和 VaR 放在一起看:它们分别描述收益的不稳定程度、历史上的峰谷跌幅,以及给定概率下的损失阈值。用哪一个,取决于想回答什么问题。
上一篇《从 RNN 到 LSTM,再到 GRU》把 gate 机制理了一遍:LSTM / GRU 把 recurrent state 的信息流变成了可学习的动态控制,决定什么写入、什么保留、什么暴露。写到那里,我以为 RNN 这条线差不多收尾了。
一笔买入、一笔卖出 # 前段时间把基金从业资格考试的两门科目考完了。准备的过程里,我第一次完整地看到"收益率"这个词下面的各种定义,也发现过去自己对它的把握只有一句话:赚到的钱除以本金。
很多项目随着工作年限增加,会自然从简历里消失,这很正常。但从简历里消失,不代表这些事情没有发生过。
最近翻到了自己 2017 年的简历,用 LaTeX 写的。看到这一段:
塔防类游戏《像素战争》(类似《部落冲突》) 小组合作;使用 Unity 3D 以及 PlayMaker,主要负责 AI 算法和游戏逻辑的编写。
最近复习 RNN、LSTM 和 GRU,我把它们的状态更新公式放在一起看。有一处值得单独想想:candidate 和 input gate 最后只是相乘,为什么要分成两组参数来学?从这个问题往回看,才能看清几个 gate 各自控制了什么。
复习 RNN 的 backward 时,我关注的是参数共享:同一份 weight 在多个 timestep 被使用,各处产生的梯度最后怎么汇到它上面?把时间展开成计算图之后,这件事就可以用普通的 chain rule 来算。
training loop 那篇重新理过 forward / backward / optimizer 的职责划分,RNN state transition 那篇理过 hidden state、output、prediction 的区别和 weight sharing,next-token prediction 那篇解释了为什么每个 timestep 都能产生一个 loss。这篇把这几块拼起来,回答一个问题:
起因 # 在上一篇《从进程视角重新理解 Docker》中,我总结了 Docker 最核心的价值:它通过 namespace 和 cgroup 给进程构造了独立的视图和资源限制,并把这套运行环境固化成了可以直接分发的 artifact。
最近在重拾一些深度学习基础,发现了一个很有意思的视角。
以前在看 RNN(Recurrent Neural Network)的长距离梯度传播公式时,总能看到一堆 Jacobian 矩阵的连乘,比如 \(\frac{\partial h_T}{\partial h_0} = J_T J_{T-1} \dots J_1\)。
起因 # 最近在家办公,需要连公司 VPN(UniVPN)进内网。但一连上 VPN,浏览器里的 chatgpt.com 就直接打不开了。
起因 # 最近在团队内部重新整理 Python 项目的打包、发布和依赖管理规范,逐渐把各个工程统一收敛到:
1pyproject.toml 2uv 3uv.lock 4wheel 5src layout 6internal package registry 在这个过程中,大家交流最多的往往是一些具体的工具用法:私有源怎么配、build backend 选哪一个、uv 的命令参数怎么写。但我越梳理越发现,这些工程细节很容易盖住一个更基础的问题:
起因 # 第一次接触 Docker 是 2017 年,在深圳网警机房驻场做审核项目。机房完全没有外网,模型、依赖、运行环境全靠 U 盘拷贝 docker image 来传输。后来做模型转换系统,再到量化行业负责机器学习平台,和 Docker、K8s 打了不少交道。但距离上一次系统梳理 namespace、cgroup 这些底层机制,已经过去好几年了。
起因 # 写训练代码的时候,经常从这里开始:
1dataset = load_dataset(...) 2model = build_model(...) 3train(model, dataset) dataset 像是一个天然存在的东西。打开一个 benchmark,下载一个压缩包,解压,加载,开始训练。ML 课程、论文、demo 大多也是从这一步开始。
起因 # 最近偶然看到一篇文章,里面提到一个 Linux 和 Windows 的差异:在 Linux 下,即使机器实际上没有足够的物理内存,一个很大的 malloc 仍然可能成功返回。而 Windows 对 committed memory 的管理要严格得多,类似的 allocation 更容易被直接拒绝。
起因 # 最近在继续 CS336 和 MIT 6.S191。
之前那篇写了 BPE tokenizer——从训练到编解码,关注的是 raw text 怎么变成 token,token 怎么映射成 token ID。做完之后,对这条链路的前半段算是搞清楚了:
起因 # 上一篇把 RNN 拆成了两层:state transition 和 output projection。State transition 维护 hidden state,output projection 从 hidden state 中映射出当前任务需要的预测结果。
起因 # 最近在复习 MIT 6.S191 的 RNN 部分,看到下面这张 slide 时停了一下。
左边代码里这一行:
起因 # CS336 的 training loop 里,loss.backward() 和 optimizer.step() 挨在一起。前者计算 gradient,后者更新 parameter;但 gradient 存在哪里、什么时候清零、optimizer 还维护什么状态,需要分开看。于是结合 MIT 6.S191 的 Lecture 1,把一次训练迭代重新过了一遍。
推理任务能放下,为什么训练任务放不下 # 上一篇讨论了模型调度中的 Capacity Constraint:一组模型到底能不能同时放进一张 GPU。