Transformer 前置知识 03:导数与梯度下降
上一课讲了矩阵乘法:模型是怎么算的。这一课回答另一个问题:模型是怎么学会的?
答案藏在三个词里:导数、梯度、梯度下降。我们不从公式讲起,而是从一件很日常的小事说起:调洗澡水。
00 篇说,这个系列适合”有 C 基础”的读者。可”C 基础”这个说法太宽了:会写
for循环、懂指针和结构体,翻开 Suricata 的源码,还是会被一些写法卡住。这篇番外把读 Suricata 时会遇到的、超出入门教材的 C 语言知识挑出来,每一项都配上源码里的真实例子和位置(基于 8.0.7),并说明前面哪几篇用到了它。
先给一个总览。读 Suricata,最常卡住人的是这几类:
packed。下面逐个看。
这是系列第 10 篇,也是最后一篇。上一篇结尾,一条规则的所有条件都满足了,”产生一条告警”。可告警不是凭空出现在
eve.json里的:它要先经过一轮收尾,再由日志模块拼成 JSON、写到文件里。00 篇开头那行 http 事件,走的也是这条路。这一篇就把最后这一段走完。
先给答案,输出这一段做了三件事:
drop 这类动作,最后挂到 p->alerts 上。OutputLoggerLog() 把包交给几类”根日志器”:包、事务、文件……每一类管着一批日志模块,各模块按自己的条件,或者按事务的进度,决定写不写。画出来是这样的:
这是系列第 9 篇。上一篇讲了一条规则怎么从文本变成结构。可真实的规则集动辄几万条,如果每个包都把几万条规则挨个试一遍,再快的机器也跟不上线速。这一篇看检测引擎怎么把”几万条”压到”少数几条”:规则是怎么分组的,多模式匹配(MPM)又是怎么做预过滤的。
先给答案,靠的是两层筛选:
两层加起来,就是 00 篇第 ⑦ 站说的”先挑出一个规则组(SGH),再用 MPM 一次性搜出所有规则的关键字”:
这是系列第 8 篇。前面几篇把一个包从网卡一路送到了应用层,HTTP 请求已经被解析成事务,URI、Host 这些字段都摆好了。接下来要轮到规则了。但在检查任何一个包之前,规则文件里那一行行文本,得先变成检测引擎能直接执行的结构。这一篇只看这一步:规则是怎么被读进来、拆开、存起来的。
先给答案,规则加载做了三件事:
Signature 的字段。 动作、协议、地址、端口、方向,每条规则对应一个 Signature 结构。Setup()。 检测引擎本身不认识 content、flow、http.uri 这些关键字,它只按名字去关键字表里查,找到对应的 Setup() 函数,让它自己解析参数、决定往哪里存。拿一条规则从头到尾走一遍:
1 | alert http any any -> any any (msg:"Example"; flow:established,to_server; http.uri; content:"/admin"; nocase; sid:1000001; rev:1;) |
上一课讲了点积:两个向量对应位置相乘再相加,结果越大,两个向量越像。
可一句话里有很多词,每两个词都要算一次点积。5 个词就要算 25 次,1000 个词就是 100 万次。一个个算显然太慢了,需要一种能”一次全部算完”的方法——这就是矩阵乘法。
这是系列第 7 篇。上一篇结尾,Stream 引擎把拼好、已被对方 ACK 的字节流交给了
AppLayerHandleTCPData()。但字节流本身不会说自己是 HTTP 还是 TLS,也不会自己分成”一个请求、一个响应”。这一篇看应用层框架怎么认出协议,怎么找到对应的解析器,以及解析出来的东西长什么样。
先给答案,应用层框架做了三件事:
画出来是这样的:
这个系列想把 Transformer 从头讲明白。我们不急着翻开论文,先把它依赖的前置知识一样样补齐:向量、矩阵、导数、softmax、神经网络……等读到《Attention Is All You Need》时,每个公式在算什么,就都能看懂了。
第一课是最基础的一块:向量与点积。Transformer 里几乎所有的计算,最后都落在这两样东西上。
这是系列第 6 篇。上一篇讲了
Flow:同一条连接的包已经能归到一起了,Flow上还挂着一个protoctx指针,指向这条连接的 TCP 会话。这一篇就看这个会话:TCP 的段可能乱序到达、可能重传、可能互相重叠,还可能有一部分根本没被抓到,Suricata 怎么把它们拼成一条连续、可信的字节流,交给应用层去解析?
先给答案,Stream 引擎做了三件事:
画出来是这样的:
这是系列第 5 篇。上一篇结尾,解码层把包的五元组填好,用
FlowSetupPacket()打上PKT_WANTS_FLOW并算好了flow_hash,交给FlowWorker。到这一步为止,每个包都是孤立的:Suricata 处理完一个包就忘了它。从这一篇开始,引擎要”记住”事情了:同一条连接的包要归到一起,TCP 重组、应用层解析、很多规则,都建立在这份记忆之上。
先给答案,Flow 这一层做了三件事:
Flow,存在全局的流表里,按哈希桶加锁。哈希的算法让一条连接两个方向的包算出同一个值,落进同一个桶。Flow 上。有了 Flow,后面的模块才有地方存跨包的状态。Flow 从备用池里取,一条连接一段时间没有动静就算过期,写一条 flow 日志、清空内存,再还回备用池。内存不够时,还有一套紧急模式。一条 Flow 从创建到回收的全过程,画出来是这样的: