RNN 原理、架构与一次完整手算

从 Embedding 到预测:RNN 原理、架构与一次完整手算

本文从 Embedding 开始,解释循环神经网络(RNN)如何逐个读取序列、更新隐藏状态、预测下一个词,并用一个可以手算的极简例子完整走一遍:前向传播、softmax、交叉熵、反向传播、梯度下降,以及更新参数后再次前向推演。


1. RNN 解决的是什么问题?

普通的多层感知机(MLP)通常接收一个固定长度的输入,一次性完成前向传播:

text 复制代码
输入向量 → 隐藏层 → 输出层

但文本、语音、时间序列都有顺序。例如:

text 复制代码
我 → 喜欢 → 吃 → 苹果

预测"吃"之后的词时,只看当前词"吃"是不够的,还需要知道前面出现过"我"和"喜欢"。因此,RNN 在处理当前输入时,会额外接收上一个时间步的隐藏状态。

RNN 的核心思想是:

每读入一个新元素,就用"当前输入 + 之前的记忆"更新一次隐藏状态。


2. 从词到向量:Embedding

神经网络不能直接处理"我""喜欢"这样的字符串。首先要把词映射成向量:

text 复制代码
"我"    → e₁
"喜欢"  → e₂
"吃"    → e₃

真实模型中,一个词的 Embedding 可能是几十维、几百维甚至更高维的向量:

e1=0.2,−0.7,1.1,... e_1=0.2,-0.7,1.1,\\ldots e1=0.2,−0.7,1.1,...

Embedding 通常可以看作一个矩阵。词 ID 是行号,取出对应行就是该词的向量。

本文为了把计算写成"1+1=2",会把 Embedding 暂时简化成一个数字:

e1=1,e2=1 e_1=1,\qquad e_2=1 e1=1,e2=1

这不是说真实 Embedding 只有一维,而是为了把 RNN 的计算过程讲清楚。


3. RNN 的架构:折叠模型与时间展开模型

3.1 折叠模型

RNN 的折叠表示把"重复使用的 RNN 单元"画成一个带回路的模块:
#mermaid-svg-MljXEGynUnbLKT2C{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-MljXEGynUnbLKT2C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-MljXEGynUnbLKT2C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-MljXEGynUnbLKT2C .error-icon{fill:#552222;}#mermaid-svg-MljXEGynUnbLKT2C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MljXEGynUnbLKT2C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-MljXEGynUnbLKT2C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MljXEGynUnbLKT2C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MljXEGynUnbLKT2C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-MljXEGynUnbLKT2C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MljXEGynUnbLKT2C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MljXEGynUnbLKT2C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MljXEGynUnbLKT2C .marker.cross{stroke:#333333;}#mermaid-svg-MljXEGynUnbLKT2C svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MljXEGynUnbLKT2C p{margin:0;}#mermaid-svg-MljXEGynUnbLKT2C .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MljXEGynUnbLKT2C .cluster-label text{fill:#333;}#mermaid-svg-MljXEGynUnbLKT2C .cluster-label span{color:#333;}#mermaid-svg-MljXEGynUnbLKT2C .cluster-label span p{background-color:transparent;}#mermaid-svg-MljXEGynUnbLKT2C .label text,#mermaid-svg-MljXEGynUnbLKT2C span{fill:#333;color:#333;}#mermaid-svg-MljXEGynUnbLKT2C .node rect,#mermaid-svg-MljXEGynUnbLKT2C .node circle,#mermaid-svg-MljXEGynUnbLKT2C .node ellipse,#mermaid-svg-MljXEGynUnbLKT2C .node polygon,#mermaid-svg-MljXEGynUnbLKT2C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MljXEGynUnbLKT2C .rough-node .label text,#mermaid-svg-MljXEGynUnbLKT2C .node .label text,#mermaid-svg-MljXEGynUnbLKT2C .image-shape .label,#mermaid-svg-MljXEGynUnbLKT2C .icon-shape .label{text-anchor:middle;}#mermaid-svg-MljXEGynUnbLKT2C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-MljXEGynUnbLKT2C .rough-node .label,#mermaid-svg-MljXEGynUnbLKT2C .node .label,#mermaid-svg-MljXEGynUnbLKT2C .image-shape .label,#mermaid-svg-MljXEGynUnbLKT2C .icon-shape .label{text-align:center;}#mermaid-svg-MljXEGynUnbLKT2C .node.clickable{cursor:pointer;}#mermaid-svg-MljXEGynUnbLKT2C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-MljXEGynUnbLKT2C .arrowheadPath{fill:#333333;}#mermaid-svg-MljXEGynUnbLKT2C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MljXEGynUnbLKT2C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MljXEGynUnbLKT2C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MljXEGynUnbLKT2C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-MljXEGynUnbLKT2C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MljXEGynUnbLKT2C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-MljXEGynUnbLKT2C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MljXEGynUnbLKT2C .cluster text{fill:#333;}#mermaid-svg-MljXEGynUnbLKT2C .cluster span{color:#333;}#mermaid-svg-MljXEGynUnbLKT2C 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-MljXEGynUnbLKT2C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-MljXEGynUnbLKT2C rect.text{fill:none;stroke-width:0;}#mermaid-svg-MljXEGynUnbLKT2C .icon-shape,#mermaid-svg-MljXEGynUnbLKT2C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-MljXEGynUnbLKT2C .icon-shape p,#mermaid-svg-MljXEGynUnbLKT2C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-MljXEGynUnbLKT2C .icon-shape .label rect,#mermaid-svg-MljXEGynUnbLKT2C .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-MljXEGynUnbLKT2C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-MljXEGynUnbLKT2C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-MljXEGynUnbLKT2C :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 当前输入 x_t
RNN 单元
上一个隐藏状态 h_t-1
当前隐藏状态 h_t
输出 y_t 或 softmax 概率
传给下一时间步

回路不是说网络在同一时刻无限循环,而是表示:处理完第 ttt 个输入后得到的 hth_tht,会作为下一个时间步的记忆。

3.2 时间展开模型

把循环沿时间方向展开后,结构如下:
#mermaid-svg-3N5F31kFv9Nsh8ET{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-3N5F31kFv9Nsh8ET .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3N5F31kFv9Nsh8ET .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3N5F31kFv9Nsh8ET .error-icon{fill:#552222;}#mermaid-svg-3N5F31kFv9Nsh8ET .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3N5F31kFv9Nsh8ET .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3N5F31kFv9Nsh8ET .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3N5F31kFv9Nsh8ET .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3N5F31kFv9Nsh8ET .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3N5F31kFv9Nsh8ET .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3N5F31kFv9Nsh8ET .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3N5F31kFv9Nsh8ET .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3N5F31kFv9Nsh8ET .marker.cross{stroke:#333333;}#mermaid-svg-3N5F31kFv9Nsh8ET svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3N5F31kFv9Nsh8ET p{margin:0;}#mermaid-svg-3N5F31kFv9Nsh8ET .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3N5F31kFv9Nsh8ET .cluster-label text{fill:#333;}#mermaid-svg-3N5F31kFv9Nsh8ET .cluster-label span{color:#333;}#mermaid-svg-3N5F31kFv9Nsh8ET .cluster-label span p{background-color:transparent;}#mermaid-svg-3N5F31kFv9Nsh8ET .label text,#mermaid-svg-3N5F31kFv9Nsh8ET span{fill:#333;color:#333;}#mermaid-svg-3N5F31kFv9Nsh8ET .node rect,#mermaid-svg-3N5F31kFv9Nsh8ET .node circle,#mermaid-svg-3N5F31kFv9Nsh8ET .node ellipse,#mermaid-svg-3N5F31kFv9Nsh8ET .node polygon,#mermaid-svg-3N5F31kFv9Nsh8ET .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3N5F31kFv9Nsh8ET .rough-node .label text,#mermaid-svg-3N5F31kFv9Nsh8ET .node .label text,#mermaid-svg-3N5F31kFv9Nsh8ET .image-shape .label,#mermaid-svg-3N5F31kFv9Nsh8ET .icon-shape .label{text-anchor:middle;}#mermaid-svg-3N5F31kFv9Nsh8ET .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3N5F31kFv9Nsh8ET .rough-node .label,#mermaid-svg-3N5F31kFv9Nsh8ET .node .label,#mermaid-svg-3N5F31kFv9Nsh8ET .image-shape .label,#mermaid-svg-3N5F31kFv9Nsh8ET .icon-shape .label{text-align:center;}#mermaid-svg-3N5F31kFv9Nsh8ET .node.clickable{cursor:pointer;}#mermaid-svg-3N5F31kFv9Nsh8ET .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3N5F31kFv9Nsh8ET .arrowheadPath{fill:#333333;}#mermaid-svg-3N5F31kFv9Nsh8ET .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3N5F31kFv9Nsh8ET .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3N5F31kFv9Nsh8ET .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3N5F31kFv9Nsh8ET .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3N5F31kFv9Nsh8ET .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3N5F31kFv9Nsh8ET .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3N5F31kFv9Nsh8ET .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3N5F31kFv9Nsh8ET .cluster text{fill:#333;}#mermaid-svg-3N5F31kFv9Nsh8ET .cluster span{color:#333;}#mermaid-svg-3N5F31kFv9Nsh8ET 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-3N5F31kFv9Nsh8ET .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3N5F31kFv9Nsh8ET rect.text{fill:none;stroke-width:0;}#mermaid-svg-3N5F31kFv9Nsh8ET .icon-shape,#mermaid-svg-3N5F31kFv9Nsh8ET .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3N5F31kFv9Nsh8ET .icon-shape p,#mermaid-svg-3N5F31kFv9Nsh8ET .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3N5F31kFv9Nsh8ET .icon-shape .label rect,#mermaid-svg-3N5F31kFv9Nsh8ET .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3N5F31kFv9Nsh8ET .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3N5F31kFv9Nsh8ET .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3N5F31kFv9Nsh8ET :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} x_1
RNN 单元 t=1
h_0
h_1
RNN 单元 t=2
x_2
h_2
RNN 单元 t=3
x_3
h_3

图上看起来有三个 RNN 单元,实际上它们共享同一组参数:

Wx,Wh,bh W_x,\quad W_h,\quad b_h Wx,Wh,bh

也就是说,RNN 是"同一个计算单元在不同时间步重复使用"。

下面是按节点和连线绘制的完整架构图:

3.3 RNN 的数学公式

最基础的 RNN 可以写成:

ht=ϕ(Wxxt+Whht−1+bh) h_t=\phi(W_xx_t+W_hh_{t-1}+b_h) ht=ϕ(Wxxt+Whht−1+bh)

输出层再根据隐藏状态计算 logits:

zt=Wyht+by z_t=W_yh_t+b_y zt=Wyht+by

如果要把 logits 转换成词表中每个词的概率:

pt=softmax⁡(zt) p_t=\operatorname{softmax}(z_t) pt=softmax(zt)

其中:

  • xtx_txt:第 ttt 个时间步的输入向量,文本任务中通常是词向量;
  • ht−1h_{t-1}ht−1:上一个时间步的隐藏状态;
  • hth_tht:当前隐藏状态;
  • Wx,Wh,WyW_x,W_h,W_yWx,Wh,Wy:可训练参数;
  • ϕ\phiϕ:激活函数,基础 RNN 常用 tanh;
  • ztz_tzt:每个候选词的原始分数;
  • ptp_tpt:每个候选词的概率。

4. 一个可以完全手算的极简 RNN

我们只保留两个输入词和两个候选输出词:

text 复制代码
输入序列:我 喜欢
候选词:  苹果、香蕉
真实答案:苹果

为了方便计算,做以下简化:

  1. 每个 Embedding 只有一个数;
  2. 隐藏状态也只有一个数;
  3. 暂时不使用 tanh,因此 RNN 单元是线性的;
  4. 初始隐藏状态为 h0=0h_0=0h0=0;
  5. 学习率为 η=0.1\eta=0.1η=0.1。

RNN 更新公式简化为:

ht=aet+rht−1 h_t=a e_t+r h_{t-1} ht=aet+rht−1

参数初始值如下:

参数 含义 初始值
e1e_1e1 "我"的 Embedding 1
e2e_2e2 "喜欢"的 Embedding 1
aaa 当前输入的权重 1
rrr 上一个隐藏状态的权重 1
uuu "苹果"的输出权重 0
vvv "香蕉"的输出权重 1

整体计算链可以表示为:
#mermaid-svg-CC5AQXyukJLiOMXp{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-CC5AQXyukJLiOMXp .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-CC5AQXyukJLiOMXp .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-CC5AQXyukJLiOMXp .error-icon{fill:#552222;}#mermaid-svg-CC5AQXyukJLiOMXp .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-CC5AQXyukJLiOMXp .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-CC5AQXyukJLiOMXp .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-CC5AQXyukJLiOMXp .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-CC5AQXyukJLiOMXp .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-CC5AQXyukJLiOMXp .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-CC5AQXyukJLiOMXp .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-CC5AQXyukJLiOMXp .marker{fill:#333333;stroke:#333333;}#mermaid-svg-CC5AQXyukJLiOMXp .marker.cross{stroke:#333333;}#mermaid-svg-CC5AQXyukJLiOMXp svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-CC5AQXyukJLiOMXp p{margin:0;}#mermaid-svg-CC5AQXyukJLiOMXp .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-CC5AQXyukJLiOMXp .cluster-label text{fill:#333;}#mermaid-svg-CC5AQXyukJLiOMXp .cluster-label span{color:#333;}#mermaid-svg-CC5AQXyukJLiOMXp .cluster-label span p{background-color:transparent;}#mermaid-svg-CC5AQXyukJLiOMXp .label text,#mermaid-svg-CC5AQXyukJLiOMXp span{fill:#333;color:#333;}#mermaid-svg-CC5AQXyukJLiOMXp .node rect,#mermaid-svg-CC5AQXyukJLiOMXp .node circle,#mermaid-svg-CC5AQXyukJLiOMXp .node ellipse,#mermaid-svg-CC5AQXyukJLiOMXp .node polygon,#mermaid-svg-CC5AQXyukJLiOMXp .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-CC5AQXyukJLiOMXp .rough-node .label text,#mermaid-svg-CC5AQXyukJLiOMXp .node .label text,#mermaid-svg-CC5AQXyukJLiOMXp .image-shape .label,#mermaid-svg-CC5AQXyukJLiOMXp .icon-shape .label{text-anchor:middle;}#mermaid-svg-CC5AQXyukJLiOMXp .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-CC5AQXyukJLiOMXp .rough-node .label,#mermaid-svg-CC5AQXyukJLiOMXp .node .label,#mermaid-svg-CC5AQXyukJLiOMXp .image-shape .label,#mermaid-svg-CC5AQXyukJLiOMXp .icon-shape .label{text-align:center;}#mermaid-svg-CC5AQXyukJLiOMXp .node.clickable{cursor:pointer;}#mermaid-svg-CC5AQXyukJLiOMXp .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-CC5AQXyukJLiOMXp .arrowheadPath{fill:#333333;}#mermaid-svg-CC5AQXyukJLiOMXp .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-CC5AQXyukJLiOMXp .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-CC5AQXyukJLiOMXp .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CC5AQXyukJLiOMXp .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-CC5AQXyukJLiOMXp .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CC5AQXyukJLiOMXp .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-CC5AQXyukJLiOMXp .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-CC5AQXyukJLiOMXp .cluster text{fill:#333;}#mermaid-svg-CC5AQXyukJLiOMXp .cluster span{color:#333;}#mermaid-svg-CC5AQXyukJLiOMXp 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-CC5AQXyukJLiOMXp .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-CC5AQXyukJLiOMXp rect.text{fill:none;stroke-width:0;}#mermaid-svg-CC5AQXyukJLiOMXp .icon-shape,#mermaid-svg-CC5AQXyukJLiOMXp .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-CC5AQXyukJLiOMXp .icon-shape p,#mermaid-svg-CC5AQXyukJLiOMXp .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-CC5AQXyukJLiOMXp .icon-shape .label rect,#mermaid-svg-CC5AQXyukJLiOMXp .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-CC5AQXyukJLiOMXp .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-CC5AQXyukJLiOMXp .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-CC5AQXyukJLiOMXp :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} "我"
e_1=1
"喜欢"
e_2=1
h_1
h_0=0
h_2
z_苹果=0, z_香蕉=2
softmax 概率


5. 第一次前向传播:从输入到概率

5.1 第一个时间步

读取"我":

h1=ae1+rh0 h_1=a e_1+r h_0 h1=ae1+rh0

代入参数:

h1=1×1+1×0=1 h_1=1\times1+1\times0=1 h1=1×1+1×0=1

因此,读取第一个词后,隐藏状态为:

h1=1 h_1=1 h1=1

5.2 第二个时间步

读取"喜欢":

h2=ae2+rh1 h_2=a e_2+r h_1 h2=ae2+rh1

h2=1×1+1×1=2 h_2=1\times1+1\times1=2 h2=1×1+1×1=2

这一步体现了 RNN 的记忆机制:h2h_2h2 同时包含当前输入 e2e_2e2 和上一步留下的 h1h_1h1。

5.3 输出 logits

对两个候选词分别计算分数:

z苹果=uh2=0×2=0 z_{\text{苹果}}=u h_2=0\times2=0 z苹果=uh2=0×2=0

z香蕉=vh2=1×2=2 z_{\text{香蕉}}=v h_2=1\times2=2 z香蕉=vh2=1×2=2

所以 logits 为:

z=0,2 z=0,2 z=0,2

5.4 Softmax 转成概率

p苹果=e0e0+e2=11+7.389≈0.1192 p_{\text{苹果}} =\frac{e^0}{e^0+e^2} =\frac{1}{1+7.389} \approx0.1192 p苹果=e0+e2e0=1+7.3891≈0.1192

p香蕉=e2e0+e2≈0.8808 p_{\text{香蕉}} =\frac{e^2}{e^0+e^2} \approx0.8808 p香蕉=e0+e2e2≈0.8808

模型当前的预测分布是:

候选词 概率
苹果 11.92%
香蕉 88.08%

因此第一次预测是"香蕉",但真实答案是"苹果"。


6. 计算损失

真实答案是"苹果",交叉熵损失为:

L=−log⁡p苹果 L=-\log p_{\text{苹果}} L=−logp苹果

L=−log⁡(0.1192)≈2.1269 L=-\log(0.1192)\approx2.1269 L=−log(0.1192)≈2.1269

真实答案的概率越低,损失越大。


7. 反向传播:梯度如何传回来?

7.1 从 softmax + 交叉熵开始

对于 softmax 和交叉熵的组合,有一个很重要的简化结果:

∂L∂zi=pi−yi \frac{\partial L}{\partial z_i}=p_i-y_i ∂zi∂L=pi−yi

"苹果"是真实答案,因此:

y苹果=1,y香蕉=0 y_{\text{苹果}}=1,\qquad y_{\text{香蕉}}=0 y苹果=1,y香蕉=0

于是:

∂L∂z苹果=0.1192−1=−0.8808 \frac{\partial L}{\partial z_{\text{苹果}}}=0.1192-1=-0.8808 ∂z苹果∂L=0.1192−1=−0.8808

∂L∂z香蕉=0.8808−0=0.8808 \frac{\partial L}{\partial z_{\text{香蕉}}}=0.8808-0=0.8808 ∂z香蕉∂L=0.8808−0=0.8808

负梯度意味着苹果的分数应该提高,正梯度意味着香蕉的分数应该降低。

7.2 更新输出层参数

由于:

z苹果=uh2 z_{\text{苹果}}=u h_2 z苹果=uh2

所以:

∂L∂u=∂L∂z苹果h2=−0.8808×2=−1.7616 \frac{\partial L}{\partial u} =\frac{\partial L}{\partial z_{\text{苹果}}}h_2 =-0.8808\times2=-1.7616 ∂u∂L=∂z苹果∂Lh2=−0.8808×2=−1.7616

同理:

∂L∂v=0.8808×2=1.7616 \frac{\partial L}{\partial v} =0.8808\times2=1.7616 ∂v∂L=0.8808×2=1.7616

7.3 继续向隐藏状态传播

∂L∂h2=∂L∂z苹果u+∂L∂z香蕉v \frac{\partial L}{\partial h_2} =\frac{\partial L}{\partial z_{\text{苹果}}}u +\frac{\partial L}{\partial z_{\text{香蕉}}}v ∂h2∂L=∂z苹果∂Lu+∂z香蕉∂Lv

代入 u=0,v=1u=0,v=1u=0,v=1:

∂L∂h2=(−0.8808)×0+0.8808×1=0.8808 \frac{\partial L}{\partial h_2} =(-0.8808)\times0+0.8808\times1=0.8808 ∂h2∂L=(−0.8808)×0+0.8808×1=0.8808

由于:

h2=ae2+rh1 h_2=a e_2+r h_1 h2=ae2+rh1

所以:

∂L∂h1=∂L∂h2r=0.8808×1=0.8808 \frac{\partial L}{\partial h_1} =\frac{\partial L}{\partial h_2}r =0.8808\times1=0.8808 ∂h1∂L=∂h2∂Lr=0.8808×1=0.8808

7.4 计算所有参数的梯度

继续使用链式法则:

∂L∂a=∂L∂h2e2+∂L∂h1e1=0.8808+0.8808=1.7616 \frac{\partial L}{\partial a} =\frac{\partial L}{\partial h_2}e_2 +\frac{\partial L}{\partial h_1}e_1 =0.8808+0.8808=1.7616 ∂a∂L=∂h2∂Le2+∂h1∂Le1=0.8808+0.8808=1.7616

∂L∂r=∂L∂h2h1=0.8808 \frac{\partial L}{\partial r} =\frac{\partial L}{\partial h_2}h_1 =0.8808 ∂r∂L=∂h2∂Lh1=0.8808

∂L∂e2=∂L∂h2a=0.8808 \frac{\partial L}{\partial e_2} =\frac{\partial L}{\partial h_2}a =0.8808 ∂e2∂L=∂h2∂La=0.8808

∂L∂e1=∂L∂h1a=0.8808 \frac{\partial L}{\partial e_1} =\frac{\partial L}{\partial h_1}a =0.8808 ∂e1∂L=∂h1∂La=0.8808

完整梯度如下:

参数 梯度
aaa 1.7616
rrr 0.8808
uuu -1.7616
vvv 1.7616
e1e_1e1 0.8808
e2e_2e2 0.8808

在真实 RNN 中,et,hte_t,h_tet,ht 都是向量,参数是矩阵;但反向传播依然遵循同样的链式法则。


8. 梯度下降更新参数

更新规则是:

θnew=θold−η∂L∂θ \theta_{\text{new}} =\theta_{\text{old}} -\eta\frac{\partial L}{\partial\theta} θnew=θold−η∂θ∂L

学习率为:

η=0.1 \eta=0.1 η=0.1

例如更新"苹果"的输出权重 uuu:

unew=0−0.1×(−1.7616)=0.17616 u_{\text{new}} =0-0.1\times(-1.7616) =0.17616 unew=0−0.1×(−1.7616)=0.17616

全部参数更新如下:

参数 更新前 梯度 更新后
aaa 1.0000 1.7616 0.8238
rrr 1.0000 0.8808 0.9119
uuu 0.0000 -1.7616 0.1762
vvv 1.0000 1.7616 0.8238
e1e_1e1 1.0000 0.8808 0.9119
e2e_2e2 1.0000 0.8808 0.9119

注意:这里的 Embedding 也参与了更新。除非显式冻结 Embedding,否则它通常和 RNN、输出层一起训练。


9. 用更新后的参数重新前向计算

这是训练过程非常关键的一步:不能更新参数后就结束,而要用新参数再算一遍,检查损失和预测是否改善。

9.1 重新计算隐藏状态

新的参数为:

a=0.8238,r=0.9119,e1=e2=0.9119 a=0.8238,\quad r=0.9119,\quad e_1=e_2=0.9119 a=0.8238,r=0.9119,e1=e2=0.9119

先计算:

h1=0.8238×0.9119≈0.7513 h_1=0.8238\times0.9119\approx0.7513 h1=0.8238×0.9119≈0.7513

再计算:

h2=0.8238×0.9119+0.9119×0.7513≈1.4364 h_2 =0.8238\times0.9119+0.9119\times0.7513 \approx1.4364 h2=0.8238×0.9119+0.9119×0.7513≈1.4364

9.2 重新计算 logits

z苹果=0.1762×1.4364≈0.2530 z_{\text{苹果}} =0.1762\times1.4364 \approx0.2530 z苹果=0.1762×1.4364≈0.2530

z香蕉=0.8238×1.4364≈1.1833 z_{\text{香蕉}} =0.8238\times1.4364 \approx1.1833 z香蕉=0.8238×1.4364≈1.1833

9.3 重新计算 softmax

p苹果=e0.2530e0.2530+e1.1833≈0.2829 p_{\text{苹果}} =\frac{e^{0.2530}}{e^{0.2530}+e^{1.1833}} \approx0.2829 p苹果=e0.2530+e1.1833e0.2530≈0.2829

p香蕉≈0.7171 p_{\text{香蕉}}\approx0.7171 p香蕉≈0.7171

更新前后对比:

词 更新前 更新一次后
苹果 11.92% 28.29%
香蕉 88.08% 71.71%

因此,一次更新后预测仍然是"香蕉"。但这不是更新失败:

  • "苹果"的概率从 11.92% 上升到了 28.29%;
  • 损失从 2.1269 下降到了约 1.2628;
  • 正确答案的概率明显增加了。

梯度下降优化的是损失,不保证一次更新就让最大概率类别立即翻转。

9.4 继续训练,直到预测翻转

每一步都重复:
#mermaid-svg-hv0migU7UYfIrUDC{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-hv0migU7UYfIrUDC .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hv0migU7UYfIrUDC .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hv0migU7UYfIrUDC .error-icon{fill:#552222;}#mermaid-svg-hv0migU7UYfIrUDC .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hv0migU7UYfIrUDC .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hv0migU7UYfIrUDC .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hv0migU7UYfIrUDC .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hv0migU7UYfIrUDC .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hv0migU7UYfIrUDC .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hv0migU7UYfIrUDC .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hv0migU7UYfIrUDC .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hv0migU7UYfIrUDC .marker.cross{stroke:#333333;}#mermaid-svg-hv0migU7UYfIrUDC svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hv0migU7UYfIrUDC p{margin:0;}#mermaid-svg-hv0migU7UYfIrUDC .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hv0migU7UYfIrUDC .cluster-label text{fill:#333;}#mermaid-svg-hv0migU7UYfIrUDC .cluster-label span{color:#333;}#mermaid-svg-hv0migU7UYfIrUDC .cluster-label span p{background-color:transparent;}#mermaid-svg-hv0migU7UYfIrUDC .label text,#mermaid-svg-hv0migU7UYfIrUDC span{fill:#333;color:#333;}#mermaid-svg-hv0migU7UYfIrUDC .node rect,#mermaid-svg-hv0migU7UYfIrUDC .node circle,#mermaid-svg-hv0migU7UYfIrUDC .node ellipse,#mermaid-svg-hv0migU7UYfIrUDC .node polygon,#mermaid-svg-hv0migU7UYfIrUDC .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hv0migU7UYfIrUDC .rough-node .label text,#mermaid-svg-hv0migU7UYfIrUDC .node .label text,#mermaid-svg-hv0migU7UYfIrUDC .image-shape .label,#mermaid-svg-hv0migU7UYfIrUDC .icon-shape .label{text-anchor:middle;}#mermaid-svg-hv0migU7UYfIrUDC .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hv0migU7UYfIrUDC .rough-node .label,#mermaid-svg-hv0migU7UYfIrUDC .node .label,#mermaid-svg-hv0migU7UYfIrUDC .image-shape .label,#mermaid-svg-hv0migU7UYfIrUDC .icon-shape .label{text-align:center;}#mermaid-svg-hv0migU7UYfIrUDC .node.clickable{cursor:pointer;}#mermaid-svg-hv0migU7UYfIrUDC .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hv0migU7UYfIrUDC .arrowheadPath{fill:#333333;}#mermaid-svg-hv0migU7UYfIrUDC .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hv0migU7UYfIrUDC .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hv0migU7UYfIrUDC .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hv0migU7UYfIrUDC .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hv0migU7UYfIrUDC .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hv0migU7UYfIrUDC .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hv0migU7UYfIrUDC .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hv0migU7UYfIrUDC .cluster text{fill:#333;}#mermaid-svg-hv0migU7UYfIrUDC .cluster span{color:#333;}#mermaid-svg-hv0migU7UYfIrUDC 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-hv0migU7UYfIrUDC .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hv0migU7UYfIrUDC rect.text{fill:none;stroke-width:0;}#mermaid-svg-hv0migU7UYfIrUDC .icon-shape,#mermaid-svg-hv0migU7UYfIrUDC .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hv0migU7UYfIrUDC .icon-shape p,#mermaid-svg-hv0migU7UYfIrUDC .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hv0migU7UYfIrUDC .icon-shape .label rect,#mermaid-svg-hv0migU7UYfIrUDC .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hv0migU7UYfIrUDC .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hv0migU7UYfIrUDC .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hv0migU7UYfIrUDC :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 前向传播
计算损失
反向传播求梯度
梯度下降更新参数

在同一个简化例子中继续迭代,结果大致如下:

总更新步数 苹果概率 香蕉概率 当前预测
0 11.92% 88.08% 香蕉
1 28.29% 71.71% 香蕉
2 36.84% 63.16% 香蕉
3 42.10% 57.90% 香蕉
4 45.84% 54.16% 香蕉
5 48.84% 51.16% 香蕉
6 51.47% 48.53% 苹果

第 6 次更新后,苹果概率第一次超过香蕉,模型的最终预测才变成"苹果"。这正是"参数更新后继续前向推演"的意义:用新的参数验证模型是否真的在向正确方向学习。


10. 训练阶段和预测阶段有什么不同?

10.1 训练阶段

训练阶段会进行完整的闭环:

text 复制代码
输入 → Embedding → RNN 前向传播 → logits → softmax → 损失
                                                        ↓
                                                反向传播求梯度
                                                        ↓
                                                  优化器更新参数

被更新的参数通常包括:

  • Embedding 参数;
  • RNN 的输入权重和循环权重;
  • 输出层参数;
  • 偏置参数。

由于隐藏状态之间存在时间依赖,梯度会沿着时间方向传回去,这叫 BPTT(Backpropagation Through Time,通过时间的反向传播)。

10.2 预测阶段

预测阶段通常只做前向传播:

text 复制代码
输入 → RNN → 概率分布 → 选择下一个词 → 把新词继续输入

预测阶段不会计算损失,也不会更新模型权重。需要区分:

  • 隐藏状态会随着新词不断变化;
  • 模型参数不会因为一次预测而改变。

11. RNN 和普通 MLP 的区别

对比项 MLP RNN
输入方式 一次性输入固定长度向量 按时间步逐个输入
是否有隐藏状态 没有跨时间记忆 有 ht−1→hth_{t-1}\rightarrow h_tht−1→ht
参数使用 不同层通常使用不同参数 不同时间步共享参数
处理变长序列 需要截断、补齐或改结构 天然支持逐步处理
结构图 普通层状前馈图 折叠图或时间展开图
训练方法 反向传播 BPTT,本质仍是链式法则
主要问题 难以表达顺序关系 长序列中的梯度消失或爆炸

展开后的 RNN 确实看起来像一个很深的前馈网络,但它与普通 MLP 的关键差异仍然是:

时间状态传递+跨时间参数共享 \text{时间状态传递} + \text{跨时间参数共享} 时间状态传递+跨时间参数共享


12. 常见问题

12.1 RNN 是不是 MLP?

RNN 单元内部通常包含线性层、偏置和激活函数,因此局部计算和 MLP 有相似之处。但整个 RNN 还多了隐藏状态和时间递归,不能简单等同于普通 MLP。

12.2 Embedding 是先训练好再输入 RNN 吗?

不一定。最常见的端到端训练方式是:Embedding、RNN 和输出层一起训练。损失产生的梯度会一直传到 Embedding 表。

也可以加载预训练 Embedding,或者冻结 Embedding,使其不再更新。

12.3 一次梯度更新后一定会预测正确吗?

不一定。梯度下降只保证在当前局部方向上尝试降低损失。像本文的例子中,一次更新后苹果概率从 11.92% 变成 28.29%,但仍低于香蕉;继续更新后才翻转。

12.4 为什么需要时间展开?

折叠图适合说明 RNN 的结构;时间展开图适合计算梯度。展开后,RNN 变成一个沿时间方向的计算链,误差可以从后往前传递。

12.5 RNN 为什么会梯度消失或梯度爆炸?

长序列反向传播时,梯度会连续乘上循环权重和激活函数的导数。如果这些因子长期小于 1,梯度会越来越小;如果长期大于 1,梯度可能越来越大。

这也是 LSTM、GRU 引入门控机制的原因之一。

12.6 输出 y_t 代表什么?

在语言模型中,yty_tyt 通常是根据前 ttt 个词预测下一个词的概率分布:

yt=P(xt+1∣x1,...,xt) y_t=P(x_{t+1}\mid x_1,\ldots,x_t) yt=P(xt+1∣x1,...,xt)

如果只使用最后一个隐藏状态做分类,则是 many-to-one;如果每个时间步都输出结果,则是 many-to-many。


13. 最小 PyTorch 结构

对应真实代码,结构大致如下:

python 复制代码
import torch
import torch.nn as nn

class TinyRNNLanguageModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_size):
        super().__init__()
        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.rnn = nn.RNN(
            input_size=embedding_dim,
            hidden_size=hidden_size,
            batch_first=True
        )
        self.output = nn.Linear(hidden_size, vocab_size)

    def forward(self, token_ids):
        # [B, T] -> [B, T, E]
        x = self.embedding(token_ids)
        # h_sequence: [B, T, H]
        h_sequence, h_last = self.rnn(x)
        # 每个时间步都可以预测下一个词
        logits = self.output(h_sequence)
        return logits

训练时:

python 复制代码
logits = model(input_ids)
loss = criterion(
    logits.reshape(-1, vocab_size),
    target_ids.reshape(-1)
)

optimizer.zero_grad()
loss.backward()   # BPTT
optimizer.step()

预测时:

python 复制代码
with torch.no_grad():
    logits = model(input_ids)
    probs = torch.softmax(logits[:, -1, :], dim=-1)
    next_token = torch.argmax(probs, dim=-1)

14. 总结

RNN 的完整逻辑可以概括为:

词 ID→Embedding→当前输入与上一步状态结合→更新隐藏状态→输出 logits→softmax 概率 \text{词 ID} \rightarrow \text{Embedding} \rightarrow \text{当前输入与上一步状态结合} \rightarrow \text{更新隐藏状态} \rightarrow \text{输出 logits} \rightarrow \text{softmax 概率} 词 ID→Embedding→当前输入与上一步状态结合→更新隐藏状态→输出 logits→softmax 概率

训练时再加上:

损失→BPTT 反向传播→梯度下降更新参数 \text{损失} \rightarrow \text{BPTT 反向传播} \rightarrow \text{梯度下降更新参数} 损失→BPTT 反向传播→梯度下降更新参数

本文的极简例子虽然把向量和矩阵都缩成了数字,但它完整保留了真实 RNN 的核心机制:

  1. Embedding 把离散词转换成连续向量;
  2. RNN 通过隐藏状态保留前文信息;
  3. 输出层把隐藏状态转换成候选词分数;
  4. softmax 把分数转换成概率;
  5. 交叉熵衡量预测与真实答案的差距;
  6. 反向传播计算梯度;
  7. 优化器更新参数;
  8. 更新后必须重新前向计算,检查损失和预测是否改善。

RNN 可以看成"带有时间记忆和参数共享的神经网络"。这也是它与普通 MLP 最本质的区别。

相关推荐
weixin_438338511 小时前
CUDA 安装理解
人工智能·深度学习
喵本喵叁肆1 小时前
diffprivlib 实战(四):训练差分隐私机器学习模型——sklearn 一行换 import 就是 DP 版
人工智能·机器学习·sklearn
郝学胜-神的一滴1 小时前
AI 编程智能体 02:AI智能体到底是什么
开发语言·人工智能·python·程序人生·pycharm
天一生水water1 小时前
超参分析入门教程
人工智能
LaughingZhu1 小时前
Product Hunt 每日热榜 | 2026-10-01
数据库·人工智能·深度学习·神经网络·搜索引擎
橘和柠1 小时前
CUDA 与 N 卡驱动安装
人工智能
悟天特斯1 小时前
边缘计算:让智慧园区的治理能力“下沉“到最后一公里
人工智能·边缘计算
宋哥转AI1 小时前
AgentScope Java 实战 05:Spring Boot 整合——11 个官方 starter 的自动装配路线
人工智能·ai·ai编程
I Am a robert girl1 小时前
从一张图到碎裂瞬间:FracGen 如何用物理信号“导演“物体撕裂
人工智能·深度学习·计算机视觉·生成模型·视频生成·物理仿真·断裂模拟